Especially in tweetcarts, using
sub(s,i,i) has always felt like a waste of so many chars, and less intuitive than the standard
s[i] in most other programming languages. Also, because of the massive amount of string parsing that happens in Pico-8 for data compression, and the fact that Lua does support this feature with a small amount of work, I'd really like this feature added. chr and ord were a step in the right direction (as previously, these needed two lookup tables generated at startup), but I think with the recent addition of
sub(s,i,_), it's clear that a major string parsing use case is missing.
See this link, the implementation of __index that will still support t:func(a) is at the bottom of the page:
Why should this be added to the API though? Unfortunately, the main issue with people implementing this feature themselves seems to be that the basic Lua string library isn't present.
getmetatable('') returns nil in Pico-8 code, so you can't assign functions to it. You could assign a custom metatable via setmetatable that re-implements all of the string functionality in every string created, but that would destroy your token count, as it would have to cover concatenation, construction, tostr, etc. And since the carts that usually need this are character limited (tweetcarts and maxed out carts), you don't have the space to do it yourself anyway.
I'm suggesting injecting a header in the Lua code that adds this functionality to the string type, for zero end-user token/char cost. See this discussion thread for an example: https://www.lexaloffle.com/bbs/?pid=110420#p . I'm guessing the header code doesn't look like this anymore, but effectively just putting the code in the same place as all() and add() and so on.
Since sub/chr/ord are already function calls, I feel like the metatable overhead of a pure-Lua implementation won't be an issue, but presumably there are more optimized ways to go about this in C as well. String concatenation might take a hit as well, and I'm curious if that's why this hasn't been added yet. I've been here long enough to remember when 0.1.2 let you do this via
getmetatable('').__index = function(str,i) return sub(str,i,i) end (it was released with some kind of full API exposed), so I'm guessing it's a much easier fix in the internal systems than it would be elsewhere.
This has been raised before when I was much more averse to messing with the language, but I'm on board now! It is a nice feature to have, and does allow sub(str,pos,_) to return to the pre-0.2.4 behaviour (instead of returning a single character, which I agree was a mistake).
Thanks for the suggested implementation. I think it will make sense to alter the Lua C implementation in this case, even though it is probably not a big performance concern. And the change can be very low impact (it's just an extra case in luaV_gettable() if anyone is interested).
I floated the idea on twitter too, and replies also naturally leaned towards matching the behaviour of sub(str,i,i): https://twitter.com/lexaloffle/status/1562695370698010625
So, return "" when out of range, and negative indices mean count from the end of the string.
Unless any objections come up soon, I'll include it in 0.2.5
So nice to see!
If it's vaguely compatible with current vanilla Lua, maybe submit it as a change. It's something that has never made sense to me.
The only possible issue anyone might have is that Lua strings are immutable, so
s='x' is not possible, but... so what? Just generate a runtime error if someone tries to do it:
They won't do it again.
In all honesty though, this has been my most wanted feature for a long time. Thank you so much, it makes string usage so much easier to deal with. And also opens the door for magic tweetcart functions that can accept strings or tables and treat them similarly. :P
I'm glad you have a better implementation working, too. In hindsight, the issues on that lua-users page don't seem to matter anyway, since you can't set a metatable on a string in Pico-8, but may be worth checking that is still the case. Otherwise it seems like a clean patch.
That was my concern too -- it looks a little like a C style array which comes with some expectations. I've added an error message similar to yours, and maybe is even a nice way to discover that Lua strings are immutable ~ it's really not that apparent without reading through the manual.
> opens the door for magic tweetcart functions that can accept strings or tables and treat them similarly
I see the changelog says s[i] is supposed to return nil if i is not valid, but I see it returns "".
("" can be a bit more convenient as it can be used with ../#/sub/etc without extra checks, but checking for "" takes more tokens and nil feels more correct from a typing perspective)
Fully agree with @Felice.
I was hoping with the advent of
print a(string,char) you could do it like QBasic:
a$="hello" mid$(a$,2,1)="x" print a$
Results would be:
or for Pico-8:
a="hello" a="x" print(a)
But nope, you get this message instead:
Attempt to modify global 'A' (strings are immutable)
WHY do I want this, @zep ?
So we can have byte arrays of course.
table="" for i=1,255 do table..=chr(0) end table=chr(14)
Can use either
chr(14) or just 14. It knows it's a string so both would work. Or
Also could you please add a function like QBasic uses, STRING so you have:
Which is the same as the For/End above, where string$ could be named _string where the first value is the number of characters and the 2nd value is either a number or string, so it is possible:
The reason is, try this code:
a="" for i=0,32767 do a..=" " end
Yep, that is actually Pico-8 slowing down. Takes a full second to do this. Instead it could be:
Those string tools feel like overkill, @dw817. There's already '\*' in P8SCII for repeated printed characters, and char replace or insert is easy with a small function:
function setchar(s,i,c) return sub(s,1,i-1)..c..sub(s,i+1) end function inschar(s,i,c) return sub(s,1,i-1)..c..sub(s,i) end cls() x="henlo orld" x=inschar(setchar(x,3,'l'),7,'w') ?x
Creating byte arrays feels unnecessary, and extracting from them is easy with
ord(s,i,n). The rest feels like edge-cases that aren't general purpose enough to pico-8 gamedev to be covered by the API.
That's an interesting point about
\*n Unfortunately it does not work for what I would want:
cls() a="\*9." print(#a) 3 b=a print(#b) 3
I would want the
\*n to create a true string, in this case 9-characters long, not just the display, so the function
_string() or equivalent would still be desired for this.
Yeah, but then what's the use case for what you want here? Strings aren't very functional as a live data type, since they are immutable, so short of printing repeated characters I can't see the purpose of that kind of string initialization for games. You can and probably should use tables instead, even for building string data.
Additionally, your slow for-loop approach could be vastly improved by just doubling the string each time, and this would also reduce the backend inefficiency of creating 32767 unneeded temporary strings:
--a="" for i=0,32767 do a..=" " end a=" " for i=1,15 do a..=a end --does the same, much more quickly
There's a point where you have to draw the line on what an API offers, particularly where it masks inefficiencies that programmers may not be aware of, like how every string change requires the creation of an entirely new string. But also, I feel like everything you're asking for can be done in minimal tokens in your own source code, so if you really need those features there's still an easy way to get them. I don't really see why any of these things need to be implemented elsewhere, am I just missing some big use case for them?
@shy, that is a genius solution to my trying to achieve that lovely 32767-string size character. Gizzymo. Just how big is that string when you're done ?
Oh, it's a very neat 32768-characters in size. If I really want 32767 I can always trim the tail.
Thanks ! Good solution that.
Note that you went from 0 to 32767, so I think your string was the same length, since Lua's for loops do the last iteration as well. This is basically a simplified version of how multiplication is handled on some processors, repeated shifts (doubling) and adds (+1). If you copy that formula, you can hit any number of copies in the minimal number of steps, without trimming.
I'm definitely for anything that makes accessing data simpler and more compact, so this seems nice. With that said though, I'm wondering what kinds of things this will be a significant help with, besides printing out single symbols. For data storage, isn't ord() more efficient? Just wondering if there are some uses I haven't thought of.
I agree it'd be nice if we could change characters in-situ in a string, but it's a fundamental tenet of Lua that strings are read-only.
Yes, zep has created his own dialect of Lua that differs from the original, so technically he could ignore this tenet, but it would turn PICO-8 Lua into a language where you could no longer rely on a string that you were passed not changing without your knowledge. This is a shoot-yourself-in-the-foot feature present in many other languages, which offers a great deal of power, but there IS that issue of the occasional hole in your foot.
Given that PICO-8 straddles the fence between being a fun toy for seasoned programmers and a genuine learning/growth space for new programmers, I think it's probably better if we don't subject the new crew to all the hazards of what are basically pointers. Let them migrate to other languages and learn there.
[Please log in to post a comment]