Initially created by following along https://interpreterbook.com and making many changes/simplification/cleanups. And pretty much a complete rewrite in 0.25 with interning, everything a node/expression (flatter ast), etc... (kept the tests though).
There is also now a discord bot as well as a wasm
version that runs directly in your browser, try it on grol.io
Install/run it:
CGO_ENABLED=0 go install -trimpath -ldflags="-w -s" -tags "no_net,no_json,no_pprof" grol.io/grol@latest
Or with docker:
docker run -ti ghcr.io/grol-io/grol:latest
On a mac
brew install grol-io/tap/grol
Or get one of the binary releases
Sample:
$ grol -parse
10:53:12.675 grol 0.29.0 go1.22.5 arm64 darwin - welcome!
$ fact = func(n) {if (n<=1) {return 1} n*self(n-1)} // could be n*fact(n-1) too
== Parse ==> fact = func(n) {
if n <= 1 {
return 1
}
n * self(n - 1)
} // could be n*fact(n-1) too
== Eval ==> func(n){if n<=1{return 1}n*self(n-1)}
$ n=fact(6)
== Parse ==> n = fact(6)
== Eval ==> 720
$ m=fact(7)
== Parse ==> m = fact(7)
== Eval ==> 5040
$ m/n
== Parse ==> m / n
== Eval ==> 7
$ func fx(n) {if n>0 {return fx(n-1)}; info.all_ids}; fx(3)
== Parse ==> func fx(n) {
if n > 0 {
return fx(n - 1)
}
info.all_ids
}
fx(3)
== Eval ==> {0:["E","PI","abs","fact","fx","log2","n","printf"],1:["n","self"],2:["fx","n","self"],3:["fx","n","self"],4:["fx","n","self"]}
$ info["gofuncs"] // other way to access map keys, for when they aren't strings for instance
== Parse ==> info["gofuncs"] // other way to access map keys, for when they aren't strings for instance
== Eval ==> ["acos","asin","atan","ceil","cos","eval","exp","floor","json","ln","log10","pow","round","sin","sprintf","sqrt","tan","trunc","unjson"]
$ info.keywords
== Parse ==> info.keywords
== Eval ==> ["else","error","false","first","func","if","len","log","macro","print","println","quote","rest","return","true","unquote"]
The interactive repl mode has extra features:
- Editable history (use arrow keys, Ctrl-A etc...) to navigate previous commands
- Hit the
<tab>
key at any time to get id/keywords/function completion history
command to see the current history, prefixed by a number- You can use for instance
!23
to repeat the 23rd statement - State is auto saved/loaded from
.gr
file in current directory unless-no-auto
is passed - A short
help
Functional int, float, string and boolean expressions
Functions, lambdas, closures (including recursion in anonymous functions, using self()
)
Arrays, ordered maps (including map.key as map["key"] shorthand access and ability to put any type, including arrays, maps and functions as keys)
print, log
macros and more all the time (like canonical reformat using grol -format
and wasm/online version etc)
automatic memoization
for loops (in addition to recursion based iterations)
easy extensions/adding Go functions to grol (see extensions/extension.go for a lot of math
additions)
variadic functions both Go side and grol side (using ..
on grol side)
Use info
to see all the available functions, keywords, operators etc... (can be used inside functions too to examine the stack)
save("filename")
and load("filename")
current state.
See also sample.gr and others in that folder, that you can run with
GOMEMLIMIT=1GiB grol examples/*.gr
or copypaste to the online version on grol.io.
There is also more involved code in grol-io/grol-discord-bot/discord.gr.
go install golang.org/x/tools/cmd/stringer@latest
make # for stripped down executable including build tags etc to make it minimal
See Open Issues for what's left to do
Click for detailed reading notes
-
See the commit history for improvements/changes (e.g redundant state in lexer etc)
-
interface nil check in parser
-
Do we really need all these
let
, wouldn'tx = a + 3
be enough? made optional -
Seems like ast and object are redundant to a large extent
-
Introduced errors sooner, it's sort of obviously needed
-
Put handling of return/error once at the top instead of peppered all over
-
Make all the Eval functions receiver methods on State instead of passing environment around
-
made built ins like len() tokens (cheaper than carrying the string version during eval)
-
fix up == and != in 3 places (int, string and default)
-
change int to ... float? number? or rather add float/double (maybe also or big int?...)
-
use + for concat of arrays and merging of maps
-
call maps maps and not hash (or maybe assoc array but that's long)
-
don't make a slice to join with , when there is already a strings builder. replace byte buffers by string builder.
-
generalized tokenized built in (token id based instead of string)
-
Add "extension" internal functions (calling into a go function), with variadic params, param types etc
-
Identifiers are letter followed by alphanum*
-
map of interface correctly equals the actual underlying types, no need for custom hashing -> implies death to pointers (need non pointer receiver and use plain objects and not references)
-
unicode (work as is in strings already)
-
flags for showing parse or not (default not pass
-parse
to see parsing) -
file input vs stdin repl (made up .gr for gorepl)
-
actual name for the language - it's not monkey (though it's monkey
compatiblederived, just better/simpler/...) -
multiline support in stdin repl
-
add >= and <= comparison operators
-
add comments support (line)
- add /* */ style
-
line numbers for errors (for file mode)
-
use
func
instead offn
for functions -
figure out how to get syntax highlighting (go style closest - done thx to viulisti -> .gitattributes)
-
assignment to maps keys and arrays indexes
-
for loop
-
switched to non pointer receivers in Object and (base/integer) Ast so equality checks in maps work without special hashing (big win)
grol 0.72.0 usage:
grol [flags] *.gr files to interpret or `-` for stdin without prompt or no arguments for stdin repl...
or 1 of the special arguments
grol {help|envhelp|version|buildinfo}
flags:
-c string
command/inline script to run instead of interactive mode
-compact
When printing code, use no indentation and most compact form
-empty-only
only allow load()/save() to ./.gr
-eval
show eval results (default true)
-format
don't execute, just parse and reformat the input
-history file
history file to use (default "~/.grol_history")
-max-depth int
Maximum interpreter depth (default 149999)
-max-duration duration
Maximum duration for a script to run. 0 for unlimited.
-max-history size
max history size, use 0 to disable. (default 99)
-max-save-len int
Maximum len of saved identifiers, use 0 for unlimited (default 4000)
-no-auto
don't auto load/save the state to ./.gr
-no-load-save
disable load/save of history
-panic
Don't catch panic - only for development/debugging
-parse
show parse tree
-parse-debug
show all parenthesis in parse tree (default is to simplify using precedence)
-quiet
Quiet mode, sets loglevel to Error (quietly) to reduces the output
-restrict-io
restrict IOs (safe mode)
-s #! script mode: next argument is a script file to run, rest are args to the script
-shared-state
All files share same interpreter state (default is new state for each)
(excluding logger control, see gorepl help
for all the flags, of note -logger-no-color
will turn off colors for gorepl too, for development there are also -profile*
options for pprof, when building without no_pprof
)
If you don't want to pass a flag and want to permanently change the grol
history file location from your HOME directory, set GROL_HISTORY_FILE
in the environment.