-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcompile
executable file
·782 lines (627 loc) · 26.8 KB
/
compile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
#!/bin/bash
# description: target compiler for the Automate threaded code VM.
# dependencies: apart from the VM, additionally needed files are:
# primitives.list - a list of the VM code labels.
# library.inc - extensions and cold start entry point.
# bumping version number requires change to VM too:
# generated programs contain this version. VM compares it against its own version,
# which must be identical. This prevents execution of programs not compatible with
# the VM any longer or have never been.
# version is changed when VM, compiler or lib changes render programs incompatible.
# For other changes, bump subversion. VM doesn't check for subversion.
version="0.19"
subversion="0"
# 20201227ls 0.19.0
# added hex number literals ( $xxxx )
# first go at std io: added file monitor for stdin, writing to stdout
# 20201227ls 0.18.4
# fixed string literals
# 20201227ls 0.18.3
# fixed parsing *, no need for bash -f any longer
# 20201227ls 0.18.2
# new name for array find: compiled. more informative error message.
# a bit more error checking. added input parsing skip, arg, parse.
# renamed "directive" to "interpreted", fixed setting precedence bit.
# added ( \ ' , factored header creation of defining words. Revisited
# inline, changed white space removal. seperated compileline from
# compilefile, as compiler doesn't require a statement a line any longer,
# but supported multistatement lines.
# parsing of compile time string literal still needs fix.
# 20201219ls 0.18.1 alias support added (multiple names to same xt in primitives.inc)
# constant variable create @ , can be used in both host and compile mode
# 20201218ls 0.18.0 VM: added lshift rshift /mod */ . int slit type
# VM and compile: renamed literal to dolit
# compile: renamed lit to literal, factored literal parser,
# added string literal detection and handling.
# code file format has changed, as previous format was incompatible with literal strings containing spaces
# 20201218ls 0.17.1 compiler: changed colon to :
# changed all flow control statements from _statement to STATEMENT
# added include (include location defaults to directory of library)
# allot directive fetches arg from hstack
# interpret time literals are pushed to stack
# compile time literals are compiled
# added fail handler which aborts, contrasting with error which proceeds
# added "immediate" directives which execute during compilation
# added [ and ] to turn off and on compilation
# added fulllisting directive, replacing former listing directive
# former listing directive lists now only user program
# test programs and library:
# changed all flow control statements _statement to STATEMENT
# changed all colon to :
# removed all occurances of lit
# 20201217ls 0.17.0 compiler: added [tick] and execute.
# VM: added execute primitive, speeded up program loading
# 20201217ls 0.16.2 detect number, no need for preceding them with lit
# preparing removal of second symbol table
# 20201215ls 0.16.1 added literals. VM checks for code files matching.
# removed bug with non-empty lines containing white space only.
#version=0.15" # 20201214ls v0.15: added library, flow control.
# --- general settings ---
srcext="prog" # source files default extension
codeext="code" # compiler output files extension
base="$HOME/FlowForth" # subdirectories relative to $base
primitives="$base/lib/primitives.inc" # location and name of primitives list
library="$base/lib/library.inc" # location and name of library
# --- data space ---
program="$1"
dp=0 # initial compilation address
declare -a m # memory
declare -A symbol # symbol table (labels)
declare -a forwardrefs
declare -A interpreted # non-code assembler directives
declare -A compiled # word name to excecution token (primitive and hilevel) or code (runtime)
declare -A headerflag # vehicle to remove one of those two seperate symbol tables.
immediate=1 # precendence bit, "immediate"
declare -a h # compile time data stack (hoststack)
compiling=0 # 0 or 1, depending on whether host compiles or interpretes
errors=0 # error counter
fail=1 # exit code for fail
success=0 # exit code for success
# having written several times to get rid of seperate symbol lists -
# keeping those may actually not be a bad idea:
# rename them to "interpreted" and "compiled", similar with what cmforth on Novix does.
# but name list "symbols" could go - it's only used for labels, of which exists only two:
# cold and end
# --- general subroutines and misc helpers ---
debug() {
printf -- '--- debug: %s %s\n' "${FUNCNAME[1]}" "$@" >&2
}
error() {
{
printf 'error: %s\n' "$@"
[[ -z "$file" ]] || {
errline="${src[line]%$l}"
printf '%s\n' "file ${file}:$((line+1)): $errline"
}
printf 'call trace: '
printf '[%s] ' "${FUNCNAME[@]:1}"
printf '\n'
(( ${#h[@]} )) && {
printf 'host stack: '
printf '[%s] ' "${h[@]}"
printf '\n'
}
(( ${#s[@]} )) && {
printf 'string stack: '
printf '[%s] ' "${s[@]}"
printf '\n'
}
printf -- '-------------------------\n'
} >&2
(( errors++ ))
code="/dev/null" # prevent code generation
}
fail() {
error "$@"
exit "$fail"
}
needs() { error "${FUNCNAME[2]} needs $1"; }
needarg() { needs "an argument" "$@"; }
needname() { needs "a name argument"; }
needvalue() { needs "a value argument"; }
needfile() { needs "a file name argument"; }
# ---
[[ -d "$base" ]] || fail "base directory ($base) not found or not accessable"
[[ -f "$primitives" ]] || fail "no primitives table" # refuse execution without primitives
[[ -z "$program" ]] && fail "missing program name" # refuse compilation without file name given
[[ "${program##*.}" == "$codeext" ]] && # don't clobber source if extension matches code
fail "source extension may not be $codeext"
[[ ! -f "$program" && -f "$program.$srcext" ]] && # conditionally append default source extension
program+=".$srcext"
[[ -f "$program" ]] || fail "$program not found"
code="${program%.*}.$codeext" # change extension of output file
sources="$library $program" # files included by compiler run
### --- subroutines: general ---
title() { printf -- '--- %s ---\n' "$1"; }
hcompile() {
m[dp++]="$1"
} # "host compile", write arg into target program space
forwardref() { # by compiling the symbol name which can't yet be
arg="$1" # resolved into target space, and adding the
[[ -z "$arg" ]] && needarg # compilation address to a list of fwd refs,
forwardrefs[${#forwardrefs[@]}]="$dp" # we can later hunt these references and resolve
hcompile "$arg" # them.
}
### --- subroutines: input classifiers ---
isnumber() { # is arg a valid number?
[[ -z "${1//[0-9+-]/}" ]] # a bit simplistic, can be cheated easily
}
issymbol() { # is arg a defined symbol?
[[ -z "$1" ]] && needname
[[ -n "${symbol[$1]}" ]]
}
ishexnumber() {
[[ "${1:0:1}" == '$' ]] || return 1
n="${1:1}"
[[ -z "${n//[0-9a-f]/}" ]]
}
isinterpreted() { [[ -n "${interpreted["$1"]}" ]]; }
isword() { [[ -n "${compiled["$1"]}" ]]; }
isimmediate() { (( ${headerflag["$1"]:=0} & immediate )); }
# compile a ref, either resolve immediately, or as forward ref
href() {
ref="$1"
#debug "ref=[$ref]"
[[ -z "$ref" ]] && needname
if issymbol "$ref"; then hcompile "$ref" # hcompile if resolvable symbol
elif isnumber "$ref"; then hcompile "$ref" # hcompile if number
else forwardref "$ref" # forwardref if neither number nor resolvable symbol
fi
}
# --- numeric host stack ---
hpush() {
hdepth="${#h[@]}"
h[hdepth]="$1"
}
hpop() { # host pop: move top element to variable tos
hdepth="${#h[@]}"
nos="$tos" # not really nos: first pop moves to tos. next pop moved former tos to nos, makes current tos (which actually was nos) tos
tos="${h[@]:0 -1}"
unset "h[hdepth-1]"
}
# --- string stack ---
# interface to directives and words which aren't yet string stack converted:
# copy top string stack element to variable tos (where it can serve as argument)
speek() { tos="${s[@]:0 -1}"; }
spush() { sdepth="${#s[@]}"; s[sdepth]="$@"; }
spop() { sdepth="${#s[@]}"; speek; unset "s[sdepth-1]"; } # host string pop: move top string stack element to tos
sdup() { spush "${s[@]:0 -1}"; }
sdepth() { hpush "${#s[@]}"; }
# --- input parsing ---
# not a string stack operation: works on line tail $l.
# trims leading whitesp. rename to minleading (-leading)
skip() {
#debug "1: $l"
whitespace="${l%%[![:blank:]]*}"
l="${l#$whitespace}"
#debug "2: $l"
return "${#l}"
}
# remove next white space delimited string from line tail,
# return as tos. no string stack involved.
arg() {
#debug "1: [$l]"
skip
tos="${l%%[[:blank:]]*}" # delimited string to tos
#debug "2: [$tos]"
l="${l#"$tos"}" # strip from command line tail
#debug "3: [$l]"
return "${#l}"
}
# interface to string stack:
# removes a white space delimited string from line tail,
# pushs it to string stack
# assumes that leading white space has already been trimmed from line tail.
# can't easily use parse, because whitespace can be tabs or spaces
word() {
arg
spush "$tos"
return "${#l}"
}
# similar, but split at specified delimiter instead of at white space
# untested
parse() {
spop # delimiter to $tos
delim="$tos" # must move because tos will get clobbered
tos="${l%%${delim}*}" # delimited string to tos
spush "$tos" # make available
l="${l#${tos}${delim}}" # strip including delimiter from command line tail
return "${#l}"
}
dot_s() {
printf '[%s] ' "${s[@]}"
printf '\n'
}
### --- compiler directives ---
# directives are meant to operate in interpret mode only, unless the precedence bit has been set.
# They never get compiled, but they may compile to target program as result of their executed action.
# that makes them akin to state smart, or "immediate", Forth words.
# $1: lookup name
# $2: function name
interpreted() {
[[ -z "$1" ]] && needname
[[ -z "$2" ]] && needvalue
last="$1"
#debug "[$1: $2]"
interpreted["$1"]="$2"
}
immediate() { # by flagging a directive as immediate, it will be able to execute during compilation
w="${headerflag["$last"]}" # "[" is a bit tricky to use as index for a hash:
(( w |= immediate )) # while the commented out version fails, avoiding to
headerflag["$last"]="$w" # index the array within a math expression works.
}
# --- host constants ---
interpreted "false" "zero"; zero() { hpush "0"; }
interpreted "true" "minone"; minone() { hpush "-1"; }
# --- host stack directives ---
interpreted "dup" "dup"; dup() { hpush "${h[@]:0 -1}"; }
interpreted "drop" "hpop"
interpreted "swap" "swap"; swap() { hpop; hpop; hpush "$nos"; hpush "$tos"; }
interpreted "over" "over"; over() { hpush "${h[@]:0 -2:1}"; }
interpreted "nip" "nip"; nip() { swap; hpop; }
interpreted "tuck" "tuck"; tuck() { swap; over; }
interpreted "?dup" "qdup"; qdup() { hpop; (( tos )) && hpush "$tos"; }
interpreted "depth" "depth"; depth() { hpush "${#h[@]}"; }
interpreted "2dup" "twodup"; twodup() { over; over; }
interpreted "2drop" "twodrop"; twodrop() { hpop; hpop; }
interpreted "sdup" "sdup"
interpreted "sdrop" "spop"
# --- host compares ---
interpreted "0=" "notzero"; notzero() { hpop; hpush $((~tos)); }
interpreted "=" "equal"; equal() { hpop; hpop; hpush $(( -(tos == nos) )); }
interpreted "<>" "notequal"; notequal() { equal; notzero; }
interpreted "<" "less"; less() { hpop; hpop; hpush $(( -(tos < nos) )); }
interpreted ">" "more"; more() { swap; less; }
interpreted "<=" "lessequal";lessequal() { more; notzero; }
interpreted ">=" "morezero"; moreequal() { less; notzero; }
# --- host (exec) bitwise logic directives
wrap=$(((1<<32)-1))
interpreted "and" "and"; and() { hpop; hpop; hpush $(( tos & nos )); }
interpreted "or" "or"; or() { hpop; hpop; hpush $(( tos | nos )); }
interpreted "xor" "xor"; xor() { hpop; hpop; hpush $(( tos ^ nos )); }
interpreted "invert" "invert"; invert() { hpop; hpush $(( ~tos )); }
interpreted "lshift" "lshift"; lshift() { hpop; hpop; hpush $(( (tos "<<" nos) & wrap )); } # quotes circumvent geany colour highlighting from never finding a match, albeit quoting the operator, not the operands, looks a tad unconventional.
interpreted "rshift" "rshift"; rshift() { hpop; hpop; hpush $(( (tos & wrap) >> nos )); }
# --- host (exec) memory access and allocation directives
interpreted "@" "fetch"; fetch() { hpop; hpush "${m[tos]}"; }
interpreted "!" "store"; store() { hpop; hpop; m[nos]="$tos"; }
interpreted "+!" "plusstore";plusstore() { hpop; hpop; (( m[nos] += tos )); }
interpreted "allot" "allot"; allot() { hpop; (( dp += tos )); } # reserve a number of cells by advancing compilation pointer # ( x -- )
interpreted "here" "here"; here() { hpush "$dp"; } # pushs current compilation address on host stack
interpreted "," "comma"; comma() { hpop; hcompile "$tos"; }
# --- host (exec) arithmethics
interpreted "*/" "starslash";starslash() { hpop; hpop; hdepth="${#h[@]}"; h[hdepth-1]="$(( h[hdepth-1] * tos / nos ))"; }
interpreted "/mod" "slashmod"; slashmod() { hpop; hpop; hpush $(( tos % nos )); hpush $(( tos / nos )); }
interpreted "1+" "oneplus"; oneplus() { hpop; hpush $(( tos + 1 )); }
interpreted "1-" "oneminus"; oneminus() { hpop; hpush $(( tos - 1 )); }
interpreted "+" "plus"; plus() { hpop; hpop; hpush $(( tos + nos )); }
interpreted "-" "minus"; minus() { hpop; hpop; hpush $(( tos - nos )); }
interpreted "*" "mul"; mul() { hpush 1; starslash; }
interpreted "/" "div"; div() { slashmod; nip; }
interpreted "mod" "mod"; mod() { slashmod; hpop; }
interpreted "negate" "negate"; negate() { hpop; hpush $(( -tos )); }
interpreted "abs" "abs"; abs() { hpop; hpush $(( ${tos#-} )); }
interpreted "min" "min"; min() { hpop; hpop; (( nos < tos )) && hpush "$nos" || hpush "$tos"; }
interpreted "max" "max"; max() { hpop; hpop; (( nos > tos )) && hpush "$nos" || hpush "$tos"; }
interpreted "[" "["; [() { compiling=0; }; immediate # suspend compilation, switch to interpretation
interpreted "]" "]"; ]() { compiling=1; } # switch to compilation
# --- output directives ---
interpreted "cr" "cr"; cr() { printf '\n'; }
interpreted "." "dot"; dot() { hpop; echo "$tos"; }
interpreted "type" "type"; type() { spop; echo "$tos"; }
interpreted "#" "comment"; immediate
interpreted "\\" "comment"; comment() { l=""; }; immediate
interpreted "(" "paren"; paren() { spush ")"; parse; spop; }; immediate
# --- host words defining directives ---
# ( ss: string -- )
interpreted "label" "label"
label() {
arg; [[ -z "$tos" ]] && needname # string from line tail
symbol["$tos"]="$dp" # just a name in $find, no runtime, no allocation
}
header() {
label
compiled["$tos"]="$dp"
}
# ( ss: string -- )
interpreted "constant" "constant"
constant() { # constant name value
header; name="$tos"
hpop; value="$tos"
hcompile "doconst"
href "$value"
interpreted "$name" "$name" # also add constant to host
eval "function $name { hpush "$value"; }"
}
# ( ss: string -- )
interpreted "create" "create"
create() { # header with dovar runtime and no allocation
header
hcompile "dovar"
interpreted "$tos" "$tos" # also add constant to host
eval "function $tos { hpush "$dp"; }"
}
# variable foo # create variable foo, initialized to 0
# variable foo 15 # create variable foo, initialized to 15
# variable foo bla # create variable foo, initialized to symbol bla or forward reference
interpreted "variable" "variable"
variable() { # variable: dovar runtime with 1 cell allocated
#debug "l=[$l]"
create
href "0"
}
interpreted ":" "colon"
colon() {
header # create new header
hcompile "nest" # compile nest runtime
] # turn on compilation
#debug "compiling=$compiling"
}
interpreted ";" "semicolon"; immediate # immediate so it can be used to finish compiling a colon word
semicolon() {
#debug "end of def"
hcompile "${compiled[unnest]}" # compile an unnest execution token
[ # turn off compilation
}
# --- flow control compiling directives ---
interpreted "begin" "BEGIN"; immediate
BEGIN() {
hpush "$dp"
}
interpreted "again" "AGAIN"; immediate
AGAIN() {
hcompile "${compiled[branch]}"
comma
}
interpreted "until" "UNTIL"; immediate
UNTIL() {
hcompile "${compiled[branch0]}"
comma
}
interpreted "while" "WHILE"; immediate
WHILE() {
hcompile "${compiled[branch0]}"
hpush "$dp"
hcompile 0
}
interpreted "repeat" "REPEAT"; immediate
REPEAT() {
hcompile "${compiled[branch]}"
hpop; m[tos]=$((dp + 1))
comma
}
interpreted "if" "IF"; immediate
IF() { WHILE; }
interpreted "else" "ELSE"; immediate
ELSE() {
hcompile "${compiled[branch]}"
hpop; m[tos]=$((dp + 1))
hpush "$dp"; hcompile 0
}
interpreted "then" "THEN"; immediate
THEN() {
hpop; m[tos]="$dp"
}
# --- host not sorted yet ---
interpreted "sliteral" "sliteral"; immediate
sliteral() {
(( compiling )) && { # when compiling compile a string literal
hcompile "${compiled[slit]}"
spop
hcompile "$tos"
} # otherwise simply leave item on stack
}
interpreted "literal" "literal"; immediate
literal() {
(( compiling )) && { # when compiling compile a literal
hcompile "${compiled[dolit]}"
comma
} # otherwise simply leave item on stack
}
interpreted "'" "tick"
tick() {
arg
[[ -z "$tos" ]] && needarg
hpush "${compiled[$tos]}"
}
interpreted ">body" "tobody"
tobody() { # xt -> pfa
hpop
hpush $((tos+1))
}
interpreted "[']" "[tick]"; immediate
[tick]() { # compile a literal (an in-code number)
tick
literal
}
# not well tested yet. seems to work
interpreted "include" "include"
include() {
local file
local src
local line
local nlines
arg; file="$tos" # read and strip filename from caller line tail
local l
[[ -z "$file" ]] && needfile
#debug "[$tos] [$file] [$l]"
file="${library%/*}/$file"
[[ -f "$file" ]] || fail "no such file: $file"
title "including $file"
compilefile "$file"
}
tabulate() {
name="$1"
value="$2"
printf '%s:\t' "$name"
(( ${#name} < 7 )) && printf '\t'
printf '%3d\n' "$value"
}
interpreted "symbols" "symbols"
symbols() {
title "symbols"
for name in "${!symbol[@]}"; do
tabulate "$name" "${symbol["$name"]}"
done | sort -n -k2,2
cr
}
interpreted "words" "words"
words() {
title "words"
for name in "${!compiled[@]}"; do
tabulate "$name" "${compiled["$name"]}"
done | sort -nk 2,2
cr
}
listingfromto() {
if (( ended )); then
for ((from="$1"; from<"$2"; from++)); do
[[ -z "${m[from]}" ]] ||
printf '%d: %s\n' "$from" "${m[from]}"
(( from == ${symbol[cold]}-1 )) &&
printf '%s\n' "--- program starts here (run time library above) ---"
done
cr
else
error "invoke listing after end statement only"
fi
}
interpreted "listing" "listing"
listing() {
title "listing"
listingfromto "${symbol[cold]}" "$dp"
}
interpreted "fulllisting" "fulllistingx"
fulllistingx() {
title "full listing"
listingfromto "0" "$dp"
}
# second pass patching in unresolved forward references
interpreted "end" "end"
end() {
local l="end"; label # resolves dictionary pointer forward ref
(( ${#forwardrefs[@]} )) && { # unresolved reference, need second pass
for ref in "${forwardrefs[@]}"; do
l="${m[ref]}" # read symbol from mem
a="${symbol[$l]}" # lookup its address
[[ -z "$a" ]] && {
error "unresolved symbol: $l"
}
m[ref]="$a" # replace against value
done
}
to="$dp" # write generated code to file
{ for ((from=0; from<to; from++)); do
o="${m[from]}" # read next memory location
printf '%s\n' "${o:-"-1"}" # replace unitialized locations with -1
done
} > $code # write to file
ended=1
}
# memory map of a .code file, as loaded into VM memory:
# addr use
# 0 coldstart vector
# 1 compiler/library version (vm has its version in a variable,
# so the program loader can check whether program and VM match.
# 2-?? system variables (labelled, reference by name)
# ??-?? primitives (labelled, reference by name)
# ??-?? run time library (included, labelled, reference by name)
# ??-?? user program
#______ the next adress is first free address in user program space.
# rubber banding memory allocation is possible, or simply static
# allocation by modifying the the pointer to this address.
# In fact does library have words to do so:
# "here" returns that address,
# "allot" modifies it.
# --- initialize memory with cold start vector and system variables
forwardref "cold" # cold start vector at address 0
hcompile "$((10#${version//./}))" # store version number in address 1
l="dp end"; variable # let target know where code ends
# --- compiler and interpret helpers
# interpret or compile lines containing literals
inline() {
arg="$1"
# not an instruction. is it a (decimal) number?
isnumber "$arg" && {
hpush "$arg"
literal
return 0
}
# not a decimal number. is it a hex number?
ishexnumber "$arg" && {
hpush "$((16#${arg:1}))"
literal
return 0
}
# a string?
[[ "${arg:0:1}" == '"' ]] && {
delimiter='"'
spush "$delimiter"; parse
spop
spush "${arg#"$delimiter"} $tos"
sliteral
return 0
}
# a forward reference?
(( compiling )) && { forwardref "$arg"; return 0; } # compile as yet unresolved reference
error "not found: $arg"
return 3
# a bird?
}
# --- convert VM code labels to primitives ---
while read -r primitive aliases; do
[[ -z "$primitive" ]] && continue
[[ "${primitive:0:1}" == "#" ]] && continue
compiled["$primitive"]="$dp" # name -> execution token
for alias in ${aliases%%#*}; do # add aliases: same xt, different name
compiled["$alias"]="$dp"
done
hcompile "$primitive" # compile xt
done < $primitives
### --- compiler ---
compileline() {
l="${@}"
# debug "l=[$l]"
until skip; do # until skip returns 0 # until line completely processed
arg
word="$tos" # don't keep in $tos as it may get clobbered
if (( compiling )); then # processes statements between : and semicolon, or after ]
if isword "$word"; then # it's a word
hcompile "${compiled["$word"]}" # compile execution token
elif isinterpreted "$word"; then
if isimmediate "$word"; then
"${interpreted[$word]}" "$l" # execute directive with args
else
error "interpret only: $word"
fi
else
inline "$word" "$l" # passing un-arrayed args, because it may contain string literal.
fi
elif isinterpreted "$word"; then # deals with statements outside of colon and semicolon, or after [
# debug "[$word] [${interpreted[$word]}] [$l]" # execute directive with args
"${interpreted[$word]}" "$l" # execute directive with args
else
inline "$word" "$l"
fi
done
}
compilefile() {
file="$1" # file name
[[ -f "$file" ]] && {
readarray -t src < "$file" # this way of reading input,
nlines="${#src[@]}"
line=0
for ((line=0; line<nlines;line++)); do
compileline "${src[line]}"
done
}
}
ended=0 # detection for "end" in source
for file in $sources ; do
compilefile "$file"
done
(( ended )) || end
(( errors )) || exit "$success"
many="${errors%1}"
printf '%s\n' "compilation failed: $errors error${many:+s}"
exit "$fail"