Forth Lisp Python Continuum: A small highly dynamic self-bootstrapping language
Forth
225
11 commits
updated Feb 20, 2022
(Preview version. Even more of a code dump than usual. Should still run. Please report any issues.)
The Forth Lisp Python Continuum is a language made under the following incorrect assumption.
Python is Lisp with syntactic sugar and Lisp is Forth with syntactic sugar.
rebind: including the loop for reading input, compiling functions, looking up names and rebind: itself![1] The first time around, the description of the syntax change has to be written in the old Flpc of course.
All of the of the following are equivalent in (the default syntax of) Flpc. Python-like syntax
fib <- fun[i]:
if i < 3:
return(1)
return(fib(x - 1) + fib(x - 2))
Lisp-like syntax
bind("fib" fun([i]
[if(<(i 3) [return(1)])
return(+(fib(-(i 1)) fib(-(i 2))))]))
Forth-like syntax
bind("fib" ["i" assign_() i 3 <() [1 return()] if()
i 1 -() fib() i 2 -() fib() +() return()])
More Forth-like syntax
F'
[ 1 return2 ] bind: base-case
[ newfunc1 assign: i
pick: i pushi: 3 < pushf: base-case if
pick: i 1 - fib pick: i 2 - fib + return1
] bind: fib
'F
or just
F'
[ pick1 pushi: 3 < [ drop1 1 return ] if
pick1 1 - fib pick2 - fib + s21 drop1 return ] bind: fib
'F
Or anything in between. For example, Lisp with sweet expressions:
bind("fib" fun([i]
[if(i < 3 [return(1)])
return(fib(i - 1) + fib(i - 2))]))
Eventually, the grammar itself will be modifiable at runtime (needs the Flpc compiler written in Flpc to be complete for that).
With the use optional linters to enforce the style combination of your choosing (not yet implemented or designed).
For technical reasons, all of the above need to be prepended with the line (this is because of the recursive call).
bind("fib" ["Dummy"])
Run FlpcPython programs precompiled to FlpcForth with
nim c --gc:orc -d:danger flpc.nim
./flpc precompiled/interpreter.f
(or one of precompiled/self.f, precompiled/flpc-gen.f, precompiled/compiler.f, ). Tested with Nim Compiler Version 1.4.6.
Recompile .f files with
python compiler.py <list of sources> -o <output file>
compiler.py depends on pymetaterp. Install it with pip install -r requirements.txt.
The existing precompiled files were created with
python compiler.py lib/stage{0,1{a,b,b2,b3,c,d},3{a,b}}.flpc lib/flpc_grammar.flpc lib/stage{6{a,b},7a2,7a}.flpc -o precompiled/interpreter.f
python compiler.py lib/stage{0,1{a,b,b2,b3,c,d},3{a,b}}.flpc lib/flpc_grammar.flpc lib/stage6{a,b}.flpc -o precompiled/compiler.f
python compiler.py lib/stage{0,1{a,b,b2,b3,c,d}}.flpc lib/grammar.flpc lib/stage{2,3a}.flpc test/stage3-test.flpc -o precompiled/flpc-gen.f
python compiler.py lib/stage{0,1{a,b,b2,b3,c,d},3{a,b}}.flpc lib/flpc_grammar.flpc lib/stage6{a,b}.flpc test/self.flpc -o precompiled/self.f
To run tests (in test/), compile up to the needed test and append the test.
python compiler.py lib/stage{0,1{a,b,c}} test/stage1c-test.flpc -o precompiled/test.f
See Bootstrapping Sequence to get an idea of combinations that may work.
Almost everything is modifiable at runtime using rebind: including the loop for reading input, compiling functions, looking up names and rebind: itself!
This is a long list but the size of each file is relatively small.
init_memory.dat (a text file despite the name). Contains the REPL and compilation loop. init_memory.dat also contains names.get which implicitly represents the function names dictionary.bind:, rebind: and the debugger.boot_obj, boot_array, boot_dict)stage1b.flpc): Object system (abandoned and unused for now; runs but is too slow without some kind of caching)names.get with hashtable lookups. Rewire existing name map.obj . attrib with hashtable lookups. Rewire existing attribs.resizable arrays, node and Input for reading files (unfortunately all in the style of the basic object system instead of the intended one from stage1b)flpc_grammar.flpc (by adding the content of stage1-test.flpc).stage1.flpc; change the hard-coded value for a different file) into its Abstract Syntax Tree.compiler.py: loops, list comprehension, string manipulation, some classes.compiler.py to FLPC.test/pyexec.flpc for sample usage.test/make_dict.flpc).Its not clear what should go first, both as the next thing to write and the next thing to run in the boot sequence. (In fact, maybe the current boot sequence should be reordered).
compiler.py (so the only remaining source file in the project that is not Flpc would be flpc.c). However, competing with that arestage1*.flpc files are compiled, it takes up millions of memory cells. A number of those could be reclaimed.names.get with the method of an actual dictionary (as in stage1b.flpc). Binding and rebinding would be much easier and we can maybe also replace the memoizer.Hopefully these will also help with determining what some of the undocumented functions do with just some trial.
ps or printstate prints the formatted debug stack, formatted call_stack and next command.

Each element of the data stack is printed as <name>: <value>, except for separators which are shown as ----- (five dashes).
print_stack prints only the stack portion.
Once debugger is defined, enters the debugger. Special commands are
s step (step into)n next (step over)r return (step until return)l prints stack, same as psOther commands are run (added to the call stack) with control resuming after the command is finished.
rebind: either debugger_inner or debugger for the debugger to behave differently.
Typed print. Prints (and consumes) the top of the data stack. The value is formatted according to its type.
Prints the function-end separated memory with one character representing each cell.
gcc -gdwarf-2 -g3 flpc_all.c -o flpc
gdb ./flpc
(gdb) b _error
(gdb) b bp
(gdb) r <input-file>
call ps() and p cstring(input_next_token()) should help determine the program state when a breakpoint or error is reached.
The end of the C source contains a complete list (starting from int (*primitives[])(void) = ...)
+ - * 0 1 pushi:|| &&memory.set memory.get memory.append push: pushf: functions_end functions_end.increaseassign: pick: check: pick1 s21 shuffle: newfuncX returnX return_no_valueXif if-else repeat repeat_if return_ifprint print_state mprinput.next_token next_token2file_open fd_*set_outputis_str is_alpha string_equal char_between str_join sub_str int_to_strmemoizer_get memoizer_set memoizer_reset[1] Should be in a module. But there's no module system yet. [2] Should be defined using other primitives instead
Most everything else is defined at runtime and can be rebind: (once rebind: itself is defined).
TODO: document this more
flpc_grammar.flpc and grammar.flpccompiler.py and run it.To document.
To document.
Forth
82.2%
C
8.2%
Nim
7.4%
Python
2.2%
Forth Lisp Python Continuum: A small highly dynamic self-bootstrapping language
Forth
225
11 commits
updated Feb 20, 2022
(Preview version. Even more of a code dump than usual. Should still run. Please report any issues.)
The Forth Lisp Python Continuum is a language made under the following incorrect assumption.
Python is Lisp with syntactic sugar and Lisp is Forth with syntactic sugar.
rebind: including the loop for reading input, compiling functions, looking up names and rebind: itself![1] The first time around, the description of the syntax change has to be written in the old Flpc of course.
All of the of the following are equivalent in (the default syntax of) Flpc. Python-like syntax
fib <- fun[i]:
if i < 3:
return(1)
return(fib(x - 1) + fib(x - 2))
Lisp-like syntax
bind("fib" fun([i]
[if(<(i 3) [return(1)])
return(+(fib(-(i 1)) fib(-(i 2))))]))
Forth-like syntax
bind("fib" ["i" assign_() i 3 <() [1 return()] if()
i 1 -() fib() i 2 -() fib() +() return()])
More Forth-like syntax
F'
[ 1 return2 ] bind: base-case
[ newfunc1 assign: i
pick: i pushi: 3 < pushf: base-case if
pick: i 1 - fib pick: i 2 - fib + return1
] bind: fib
'F
or just
F'
[ pick1 pushi: 3 < [ drop1 1 return ] if
pick1 1 - fib pick2 - fib + s21 drop1 return ] bind: fib
'F
Or anything in between. For example, Lisp with sweet expressions:
bind("fib" fun([i]
[if(i < 3 [return(1)])
return(fib(i - 1) + fib(i - 2))]))
Eventually, the grammar itself will be modifiable at runtime (needs the Flpc compiler written in Flpc to be complete for that).
With the use optional linters to enforce the style combination of your choosing (not yet implemented or designed).
For technical reasons, all of the above need to be prepended with the line (this is because of the recursive call).
bind("fib" ["Dummy"])
Run FlpcPython programs precompiled to FlpcForth with
nim c --gc:orc -d:danger flpc.nim
./flpc precompiled/interpreter.f
(or one of precompiled/self.f, precompiled/flpc-gen.f, precompiled/compiler.f, ). Tested with Nim Compiler Version 1.4.6.
Recompile .f files with
python compiler.py <list of sources> -o <output file>
compiler.py depends on pymetaterp. Install it with pip install -r requirements.txt.
The existing precompiled files were created with
python compiler.py lib/stage{0,1{a,b,b2,b3,c,d},3{a,b}}.flpc lib/flpc_grammar.flpc lib/stage{6{a,b},7a2,7a}.flpc -o precompiled/interpreter.f
python compiler.py lib/stage{0,1{a,b,b2,b3,c,d},3{a,b}}.flpc lib/flpc_grammar.flpc lib/stage6{a,b}.flpc -o precompiled/compiler.f
python compiler.py lib/stage{0,1{a,b,b2,b3,c,d}}.flpc lib/grammar.flpc lib/stage{2,3a}.flpc test/stage3-test.flpc -o precompiled/flpc-gen.f
python compiler.py lib/stage{0,1{a,b,b2,b3,c,d},3{a,b}}.flpc lib/flpc_grammar.flpc lib/stage6{a,b}.flpc test/self.flpc -o precompiled/self.f
To run tests (in test/), compile up to the needed test and append the test.
python compiler.py lib/stage{0,1{a,b,c}} test/stage1c-test.flpc -o precompiled/test.f
See Bootstrapping Sequence to get an idea of combinations that may work.
Almost everything is modifiable at runtime using rebind: including the loop for reading input, compiling functions, looking up names and rebind: itself!
This is a long list but the size of each file is relatively small.
init_memory.dat (a text file despite the name). Contains the REPL and compilation loop. init_memory.dat also contains names.get which implicitly represents the function names dictionary.bind:, rebind: and the debugger.boot_obj, boot_array, boot_dict)stage1b.flpc): Object system (abandoned and unused for now; runs but is too slow without some kind of caching)names.get with hashtable lookups. Rewire existing name map.obj . attrib with hashtable lookups. Rewire existing attribs.resizable arrays, node and Input for reading files (unfortunately all in the style of the basic object system instead of the intended one from stage1b)flpc_grammar.flpc (by adding the content of stage1-test.flpc).stage1.flpc; change the hard-coded value for a different file) into its Abstract Syntax Tree.compiler.py: loops, list comprehension, string manipulation, some classes.compiler.py to FLPC.test/pyexec.flpc for sample usage.test/make_dict.flpc).Its not clear what should go first, both as the next thing to write and the next thing to run in the boot sequence. (In fact, maybe the current boot sequence should be reordered).
compiler.py (so the only remaining source file in the project that is not Flpc would be flpc.c). However, competing with that arestage1*.flpc files are compiled, it takes up millions of memory cells. A number of those could be reclaimed.names.get with the method of an actual dictionary (as in stage1b.flpc). Binding and rebinding would be much easier and we can maybe also replace the memoizer.Hopefully these will also help with determining what some of the undocumented functions do with just some trial.
ps or printstate prints the formatted debug stack, formatted call_stack and next command.

Each element of the data stack is printed as <name>: <value>, except for separators which are shown as ----- (five dashes).
print_stack prints only the stack portion.
Once debugger is defined, enters the debugger. Special commands are
s step (step into)n next (step over)r return (step until return)l prints stack, same as psOther commands are run (added to the call stack) with control resuming after the command is finished.
rebind: either debugger_inner or debugger for the debugger to behave differently.
Typed print. Prints (and consumes) the top of the data stack. The value is formatted according to its type.
Prints the function-end separated memory with one character representing each cell.
gcc -gdwarf-2 -g3 flpc_all.c -o flpc
gdb ./flpc
(gdb) b _error
(gdb) b bp
(gdb) r <input-file>
call ps() and p cstring(input_next_token()) should help determine the program state when a breakpoint or error is reached.
The end of the C source contains a complete list (starting from int (*primitives[])(void) = ...)
+ - * 0 1 pushi:|| &&memory.set memory.get memory.append push: pushf: functions_end functions_end.increaseassign: pick: check: pick1 s21 shuffle: newfuncX returnX return_no_valueXif if-else repeat repeat_if return_ifprint print_state mprinput.next_token next_token2file_open fd_*set_outputis_str is_alpha string_equal char_between str_join sub_str int_to_strmemoizer_get memoizer_set memoizer_reset[1] Should be in a module. But there's no module system yet. [2] Should be defined using other primitives instead
Most everything else is defined at runtime and can be rebind: (once rebind: itself is defined).
TODO: document this more
flpc_grammar.flpc and grammar.flpccompiler.py and run it.To document.
To document.
Forth
82.2%
C
8.2%
Nim
7.4%
Python
2.2%