VIEK2XMYJX37ELGVE2P5PZQU47NJ3GBXPV3XY2RWG2TDHIZEV57QC
4YBBAZM32EOQGX736RWC6NDEDRIIL64RFHFDNTRCATUS4KD77Y7AC
LJDWCB3JTDL5XVZ5UGFQXL6H2G3HXECQZQO44FGOJH3RKQ5QYJQAC
GNXMAQY6IMH7NW7XUGLJ2T4S7JYAMDSAEB66FYPRJNZSRRZ3H5JAC
UW27LKXM2BJ77FQLTY4WPKDSSWI2RFNFRJ7CB4U3TS7KYVIV72LQC
2CXQ53RHKGIT5KR7VHOVRVHCD5MK4V2J3AJDQ3CNSJLHJXXA4GXQC
KBD2A5MUJHKGV4JD6MLJXTKNAYZ5D44LX5WRB2ZXX5YCWBODWR3AC
5XO7IKBGCVXGVWMDJDE5MELS4FWRITKAU6NNV36NQ4TOZRR7UQ7QC
JDZASPALXSFZOL3MXCKBPX74CUD3W743ZJ6W2422FIJ7NJOD67ZAC
GOXQFKWDQUYUH3XLJSAUL5LPNQ46NDO3ZGFJNVTECB4ARGZ4DW3QC
I3MID22EURRK26C4JZ2U5FBEZ57GZVMBT44KFKTU4BBYXKLILWRAC
MPN7OJSZD5CS5N7WWS3ZSOYE7ZRCABIBHZDMHVS6IT25EO2INK7AC
A2JAXDJWT2FAKADYOY6QOQ7LQRMTTCDIOYT7STSESVHLZQEQJBMAC
DG4L5ELJFZS5JATC47CHOCESJM5H3XSL4WDBXNV4ADTUTMMKWZDAC
_forget_parse_state(_TOKENS, _WHERE, _INSIDE_STRING)
# it appears that during BEGIN, if we are operating on a file
# specified on the command line, FILENAME is not set yet. So we
# have to find out whether any filename was specified, instead.
_forget_parse_state(tokens, where, inside_string)
# maybe we are running from a BEGIN and so FILENAME is not set
# yet. instead we have to find whether any filename was specified.
}
while((getline) > 0) {
# the reason to keep parser state outside tokenize and read is
# so that we can have forms that span lines, rather than
# requiring each line of input to be exactly one complete
# form.
tokenize($0, tokens, where, inside_string)
read_forms_into(to_eval, tokens, where, inside_string)
{
# the reason to keep global arrays of parser state (_TOKENS,
# _TO_EVAL) is so we can have forms that span lines, rather than
# requiring each line of input to be a complete form.
tokenize($0, _TOKENS, _WHERE, _INSIDE_STRING)
read_forms_into(_TO_EVAL, _TOKENS, _WHERE, _INSIDE_STRING)
if(length(tokens) == 0 && length(to_eval) == 0) {
# we have come to the end of all complete expressions in the
# input so far. this is important so that we don't GC data
# that we have read but not yet eval'd.
_maybe_gc()
if(!FILENAME || (FILENAME == "-")) _print_eval_all(_TO_EVAL)
else _just_eval_all(_TO_EVAL)
delete _TO_EVAL
if(length(_TOKENS) == 0 && length(_TO_EVAL) == 0) {
# we have come to the end of all complete expressions in the
# input so far. this is important so that we don't GC data
# that we have read but not yet eval'd.
_maybe_gc()
_prompt()
# and also, all previous input having completed, this is
# when we should print the next prompt.
_prompt()
}
else if(car == _symbol("dump-append-changing-main"))
# the breaking point was here. here, unlike the other dumps,
# we break out the awkification of the parameters into the awk
# function implementing the form.
return _dump_append_changing_main(_eval3(_cadr(form), env, env, d+1),
_eval3(_caddr(form), env, env, d+1))
(defun save-lisp-and-die (me-file new-file)
(let* ((snip-string "# IMAGE BEGINS QMGFAMRISGQJ48IWDOWPHOOGW3MLKKGSPXD4DTFIG0")
(script (sprintf "/^%s/ { exit 0 } { print }" snip-string)))
(defun save-lisp-and-die (me-file new-file main-symbol)
(let* ((image-begin "# IMAGE BEGINS QMGFAMRISGQJ48IWDOWPHOOGW3MLKKGSPXD4DTFIG0")
(script (sprintf "/^%s/ { exit 0 } { print }" image-begin)))
function _dump(filename, append, i, t, v, s, line) {
logg_dbg("_dump", "dumping " (append ? ">> " : "> ") filename)
function _dump(filename, append, main, i, t, v, s, line) {
# This is what code to execute when this image is started. It has
# to be a glotawk form with no parameters. Usually it is the repl
# special form, so that when we start we'll do the REPL. But maybe
# you want some other function to happen at startup.
if(!main) main = "repl"
logg_dbg("_dump", "dumping " (append ? ">> " : "> ") filename ". main symbol is " main)
# we are storing here the name of the _MAIN symbol as an awk
# string, which we may have received in the main parameter. that's
# why it's a little awkward
print " _MAIN = \"" awkescape(main) "\"" >>filename
print " main_expression = _cons(_symbol(_MAIN), _nil())" >>filename
print " _eval(main_expression)" >>filename
function _dump_append_changing_main(g_filename, g_main, filename, main) {
if(_TYPE[g_filename] == "s") {
if(_TYPE[g_main] == "'") {
filename = _STRING[g_filename]
main = _SYM_NUMBERS[g_main]
logg_dbg("_dump_append_changing_main", "filename is " filename " and the form to evaluate at startup will be " main)
return _dump(filename, 1, main)
} else {
logg_err("_dump_append_changing_main", "the value of the second parameter, main, must be a symbol")
return _nil()
}
} else {
logg_err("_dump_append_changing_main", "the value of the first parameter, filename, must be a string")
return _nil()
}
}
This function takes two parameters: (1) the filename of a glotawk
interpreter; (2) a filename to which to write a new interpreter. The
AWK code from glotawk is written into the new file, and then a dump of
the current heap. In this way, you can construct a single-file
interpreter containing not only glotawk's base library, but also
definitions of your own, which will be instantly available when you
run the new interpreter, without the overhead of parsing and without
the need to place copies of the files containing the definitions on
machines where the code needs to run.
So, previous to this change, you could put a built copy of ~glotawk~
on the machine where you need to run the code, plus copies of the
files where you define all your functions, ~a.glotawk~, ~b.glotawk~,
and ~c.glotawk~, and the file where you call them, ~end-use.glotawk~.
Then you'd run =glotawk a.glotawk b.glotawk c.glotawk
end-use.glotawk=. All the expressions in each file would be parsed and
evaluated. Most of the code by volume would be definitions. Not all
defined functions would be called. Startup time would be slower.
As of this change, instead, you could run ~/wherever/glotawk~, tell it
to =(load "a.glotawk")= and etc., and =(save-lisp-and-die
"/wherever/glotawk" "/wherever-else/my-new-interpreter")=. Then
~my-new-interpreter~ will contain the definitions inside it.
Maybe I was a little too excited when naming this function, because it
doesn't yet support changing which code runs at startup.
This function takes three parameters: (1) the filename of a glotawk
interpreter; (2) a filename to which to write a new interpreter; (3) a
symbol, whose value is a function to run at startup (usually, this is
~'repl~). The AWK code from glotawk is written into the new file, and
then a dump of the current heap. In this way, you can construct a
single-file interpreter containing not only glotawk's base library,
but also definitions of your own, which will be instantly available
without the overhead of parsing and without the need to distribute
more files. In addition, if you define your own main function and pass
its symbol in, the file written will run your function on startup,
instead of the REPL.