Jorth
A small stack-based language interpreter implemented in Pico-8, oh and a text editor too. It's a programming toy. If you know Forth, this language won't be too hard to learn.
Controls
Ctrl-G: Switches modes
Ctrl-D: Deletes the current code
Ctrl-C: Copies the code (bugged)
Ctrl-V: Pastes whatever is in the clipboard (bugged)
Ctrl-H: Replaces the current content of the program with a help menu
Click to run the code.
Changelog
0.1:
- First version!
0.2:
- Added Ctrl-H for docs
- Added STOP intrinsic
0.3:
- New intrinsics for working with Lua
- New docs for IF-ELSE, DEF, and Lua intrinsics
- Smarter error messages
0.4:
- @CALL_LIMIT meta intrinsic added, and docs for it
- LET bindings!
Also agree with @SwordF , I'm struggling.
strings viewed as text with dump show as 0, but "TXTB" as number with . displays 0.0002 ???
How about adding a new LUA keyword ?
10 30 20
3 "mid" LUA
.
Would display 20
the parameters of the LUA call would be the number of stack elements to pop and pass to the lua function, and the function name. The return value(s) of the call would be put on the stack.
Example :
def cosinus
1 "cos" LUA
end
0.5 cosinus . # -1
@SwordF I added some documentation, you can use Ctrl-H to see it.
@RealShadowCaster A LUA keyword would be pretty nice! But, it would be hard to implement. I would have to know the number of arguments a Lua function takes to know how many arguments to pop off the stack, but as far as I know there is no way to get this information. I will try making a global table with the function name, function itself, and number of arguments, but this would only work on the functions I add to the table.
@Jaypi
I don't think the number of parameters is the main problem for the LUA keyword :
It would take two parameters : the number of elements of the stack to consume as parameters and the function name.
You can check if the function call is valid by checking the type of _ENV[function_name] and then call it with the popped parameters.
Functions like poke that naturally have N arguments to write to memory will need the number of parameters anyway.
What I don't see is how to pass booleans or arrays to function calls for example.
Let's say you want to set color 3 to opaque.
palt(3,false)
you could try
3 #color 3
1 0 = # false
2 "palt" LUA #call palt with 2 parameters
, but
1 0 =
returns 0 in JORTH, and 0 is true in Lua, so the transparency of color 3 would be set to true...
@RealShadowCaster Ah, I see now. I was confused about the keyword at first. About the Boolean and array problem, I think I have a simple fix.
I could add intrinsics that take a value, and transform it into a Lua value. This would work on booleans and strings. Nil could have its own intrinsic. If you try to operate on these values normally, you’ll get an error.
For arrays where the keys are numbers, you could call the PACK function from Lua if the arguments are on the stack.
Edit: It’s done!
Hey, I tried to program the display of my logo in JORTH, and failed miserably.
original plan was to have X Y direction fractal_level on the stack as parameters.
I couldn't find a way to access X (the 4th element in the stack)
so I changed to 128Y+X D L
LUA calls seem to use parameters backwards :
10 64 "pset" 2 LUA
draws in X=64 and Y=10
when there's infinite recursion, you get an OUT OF MEMORY crash and don't get any of the output...
Here's the buggy WIP code
# display RealShadowCaster's logo in JORTH # stack : x y direction (0123=enws) level (0 is nothing drawn) # can't grab 4th element # stack 128*y+x direction level def fractleft #"l" puts dup 0 = if else 1 - tl fractright fw tr fractleft fw fractleft tr fw fractright tl 1 + end end def fractright dup 0 = if else 1 - tr fractleft fw tl fractright fw fractright tl fw fractleft tr 1 + end end # turn left def tl "l" puts swap 1 + dup 4 = if drop 0 end swap end #trun right def tr "r" puts swap 1 - dup 0 < if drop 3 end swap end #forward (2 pixels) def fw "f" puts swap dup 0 = if #x=x+2 rot 2 + rot rot end dup 1 = if #y=y-2 rot 256 - rot rot swap end dup 2 = if #x=x-2 rot 2 - rot rot swap end dup 3 = if # y=y+2 rot 256 + rot rot swap end rot dup dup 127 "band" 2 lua #x "x=" puts dup . swap 128 / #y "y=" puts dup . "line" 2 lua dup . 10 emit rot rot end "cls" 0 lua 10 64 "pset" 2 lua dump 10 64 128 * + 0 2 fractleft 10 emit dump "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua "flip" 0 lua |
@RealShadowCaster Wow! That's really cool! To address the problems you have:
The recursion thing is something I wanted to fix. I'm going to add a function call limit. If you exceed the limit, the program will crash. This error is very similar to stack overflow errors in other languages.
For not being able to access arguments on the stack, I've been thinking about maybe adding an intrinsic for accessing specific arguments on the stack, but I think I'm going to add an entire new language feature. I've seen this used in a language called Porth. The LET keyword will introduce a new binding, you can use it like this:
1 2 3
let c b a in
a . b . c . a . # 1231
end
dump # []
It basically adds variables to the language.
I've looked at FORTH documentation, and there's a lot of ways :
2SWAP swaps the top two pairs of the stack.
There's also PICK that duplicates the Nth element of the stack, so
0 PICK does the same as DUP
1 PICK does the same as OVER
What I was after was 3 PICK to duplicate the 4th element of the stack. (X in my example.
During my studies, there was a rivalry where half the class was using the TI-85 (me included) that was programmed with TI-BASIC, and the other half the HP48G or HP48GX witch was more powerful but was programmed with a less accessible language that was a sort of extended FORTH. One of the keywords was STO.
2 'x' STO would store value 2 in the variable named x .
You could retrieve the value with RCL if I remember correctly :
'x' RCL
All these variables were permanent and tended to pile up, storage was in short supply, and the calculator owner was responsible for manual garbage collection...
The TI85 was no better in that regard. Since there was no keyword to delete variables in TI-BASIC, the best you could programmatically do was assign an empty string to your big array at the end the program... Worse yet, the TI85 namespace was global, and programs were protected from deletion by re-assignation, so if you borrowed a friend's calculator and created a program named I, pretty much every previously working program would start to choke with cryptic error messages when trying to use a for loop...
HP48 trolling was also rampant, the two most memorable trolls were a game that would silently litter the memory with random folders and variables, and another one that had a fake "turn off" option that cleared every pixel of the screen and turned on the infra-red emitter while keeping the infrared indicator off, then stayed active in an infinite loop, leading to empty batteries in a couple hours time... This was deemed "OK", despite the real losses in time and battery money. The only two agreed no-nos were deleting other people's stuff and irreversible hardware damage. Strange times...
@RealShadowCaster PICK was definitely in my mind when I was implementing LET. (I'll probably add it in v0.5, 2SWAP as well). It's just, it's way easier to think about things in LET bindings for me than manipulating the stack. So, I went for this one first.
I think I've heard of a stack-based programming language for a calculator, but I never knew what it actually was. A STO instruction is interesting, but for where I'm heading with the language, it doesn't really make sense here, so I'll put that off the table. I'll look into its other features.
I've tried programming in TI-BASIC before, and know that you have to zero out all the variables you'll use in your program before using them. This problem comes directly from the variables being shared between programs, I think. This is a pretty big problem in my eyes, and it's kinda a chore. The variables in this case are more like memory slots instead of variables. Memory slots sounds like a good idea to me, but not for use in variables. Maybe I should explore that more.
@Jaypi, tried to implement PICK with LUA calls.
Unfortunately, it seems you only ever get the 1st return value of a call.
For example, cursor() returns three values (X,Y and color) but only the X value is returned on the JORTH stack. No way to use unpack() to retrieve a modified array in its entirety for example.(I wanted to implement PICK with LUA calls to pack(), deli(), add() and unpack() )
I also think handling the parameters of LUA calls should be in the opposite order:
v1 v2 -
puts v1-v2 on the stack
but if you do
v1 v2 "shl" 2 LUA
you get v2<<v1 on the stack instead of v1<<v2 that would be in line with the minus function.
@Jaypi
PICK in JORTH
Fixed with let ... in statements
# display RealShadowCaster's logo in JORTH # stack : x y direction (0123=enws) level (0 is nothing drawn) 20000 @call_limit def fractleft dup 0 = if else 1 - tl fractright fw tr fractleft fw fractleft tr fw fractright tl 1 + end end def fractright dup 0 = if else 1 - tr fractleft fw tl fractright fw fractright tl fw fractleft tr 1 + end end # turn left def tl swap 1 + dup 4 = if drop 0 end swap end #trun right def tr swap 1 - dup 0 < if drop 3 end swap end #forward (2 pixels) def fw let level direction y x in direction 0 = if #x=x+2 x 2 + y end direction 1 = if #y=y-2 x y 2 - end direction 2 = if #x=x-2 x 2 - y end direction 3 = if # y=y+2 x y 2 + end let ny nx in ny nx y x "line" 4 lua nx ny direction level end end end def frame_wait dup 0 > if "flip" 0 lua 1 - frame_wait else drop end end def wait 30 * frame_wait end "cls" 0 lua 12 "color" 1 lua dump 0 127 0 6 fractleft dump 1 wait "screen" luastring "extcmd" 1 lua |
[Please log in to post a comment]