Compare commits
10 Commits
ecf90feab9
...
2055aa3b1f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2055aa3b1f | ||
|
|
5dd361e563 | ||
|
|
e2159bbca2 | ||
|
|
f4d688cac1 | ||
|
|
4508433206 | ||
|
|
cda34e61bd | ||
|
|
78bf6c132f | ||
|
|
fd030be086 | ||
|
|
2193e0bf3c | ||
|
|
fd115f31dc |
7
SPEC.md
7
SPEC.md
@@ -40,9 +40,11 @@ This document reflects the implementation that ships in this repository today (`
|
||||
- **Word definitions** – Always `word name ... end`. Redefinitions overwrite the previous entry (a warning prints to stderr). `inline word name ... end` marks the definition for inline expansion; recursive inline calls are rejected. `immediate` and `compile-only` apply to the most recently defined word.
|
||||
- **Priority-based redefinition** – Use `priority <int>` before `word`, `:asm`, `:py`, or `extern` to control conflicts for the same name. Higher priority wins; lower-priority definitions are ignored. Equal priority keeps last definition (with a redefinition warning). The compiler prints a note indicating which priority was selected.
|
||||
- **Control forms** – Built-in tokens drive code emission:
|
||||
- Default parser-level implementations for `if`, `else`, `for`, `while`, and `do` are always available.
|
||||
- Import `stdlib/control.sl` to override these defaults with custom compile-time words; when an override is active, the compiler warns and uses the custom implementation.
|
||||
- `if ... end` and `if ... else ... end`. To express additional branches, place `if` on the same line as the preceding `else` (e.g., `else <condition> if ...`); the reader treats that form as an implicit chained clause, so each inline `if` consumes one flag and jumps past later clauses on success.
|
||||
- `while <condition> do <body> end`; the conditional block lives between `while` and `do` and re-runs every iteration.
|
||||
- `n for ... end`; the loop count is popped, stored on the return stack, and decremented each pass. The compile-time word `i` exposes the loop index inside macros.
|
||||
- `n for ... end`; the loop count is popped, stored on the return stack, and decremented each pass. The compile-time word `i` exposes the loop index inside macros and cannot be used in runtime-emitted words.
|
||||
- `label name` / `goto name` perform local jumps within a definition.
|
||||
- `&name` pushes a pointer to word `name` (its callable code label). This is intended for indirect control flow; `&name jmp` performs a tail jump to that word and is compatible with `--ct-run-main`.
|
||||
- **Text macros** – `macro name [param_count] ... ;` records raw tokens until `;`. `$0`, `$1`, ... expand to positional arguments. Macro definitions cannot nest (attempting to start another `macro` while recording raises a parse error).
|
||||
@@ -57,6 +59,8 @@ This document reflects the implementation that ships in this repository today (`
|
||||
- Strings/numbers: `string=`, `string-length`, `string-append`, `string>number`, `int>string`.
|
||||
- Lexer utilities: `lexer-new`, `lexer-pop`, `lexer-peek`, `lexer-expect`, `lexer-collect-brace`, `lexer-push-back` (used by `libs/fn.sl` to parse signatures and infix expressions).
|
||||
- Token management: `next-token`, `peek-token`, `inject-tokens`, `token-lexeme`, `token-from-lexeme`.
|
||||
- Control-frame helpers: `ct-control-frame-new`, `ct-control-get`, `ct-control-set`, `ct-control-push`, `ct-control-pop`, `ct-control-peek`, `ct-control-depth`, `ct-control-add-close-op`, `ct-new-label`, `ct-emit-op`, `ct-last-token-line`.
|
||||
- Control registration: `ct-register-block-opener`, `ct-unregister-block-opener`, `ct-register-control-override`, `ct-unregister-control-override`.
|
||||
- Reader hooks: `set-token-hook` installs a word that receives each token (pushed as a `Token` object) and must leave a truthy handled flag; `clear-token-hook` disables it. `libs/fn.sl`'s `extend-syntax` demonstrates rewriting `foo(1, 2)` into ordinary word calls.
|
||||
- Prelude/BSS control: `prelude-clear`, `prelude-append`, `prelude-set`, `bss-clear`, `bss-append`, `bss-set` let user code override the `_start` stub or `.bss` layout.
|
||||
- Definition helpers: `emit-definition` injects a `word ... end` definition on the fly (used by the struct macro). `parse-error` raises a custom diagnostic.
|
||||
@@ -74,6 +78,7 @@ This document reflects the implementation that ships in this repository today (`
|
||||
|
||||
## 8. Standard Library Overview (`stdlib/`)
|
||||
- **`core.sl`** – Stack shuffles, integer arithmetic, comparisons, boolean ops, memory access, syscall stubs (`mmap`, `munmap`, `exit`), argument helpers (`argc`, `argv`, `argv@`), and pointer helpers (`mem`).
|
||||
- **`control.sl`** – Optional custom control-structure words (`if`, `else`, `for`, `while`, `do`) that can override parser defaults when imported.
|
||||
- **`mem.sl`** – `alloc`/`free` wrappers around `mmap`/`munmap` plus a byte-wise `memcpy` used by higher-level utilities.
|
||||
- **`io.sl`** – `read_file`, `write_file`, `read_stdin`, `write_buf`, `ewrite_buf`, `putc`, `puti`, `puts`, `eputs`, and a smart `print` that detects `(addr,len)` pairs located inside the default `.data` region.
|
||||
- **`utils.sl`** – String and number helpers (`strcmp`, `strconcat`, `strlen`, `digitsN>num`, `toint`, `count_digits`, `tostr`).
|
||||
|
||||
613
examples/snake.sl
Normal file
613
examples/snake.sl
Normal file
@@ -0,0 +1,613 @@
|
||||
# Terminal Snake (classic real-time: WASD steer, q quit)
|
||||
|
||||
import stdlib.sl
|
||||
import arr.sl
|
||||
import linux.sl
|
||||
|
||||
macro WIDTH 0 20 ;
|
||||
macro HEIGHT 0 12 ;
|
||||
macro CELLS 0 WIDTH HEIGHT * ;
|
||||
|
||||
macro CH_W 0 119 ;
|
||||
macro CH_A 0 97 ;
|
||||
macro CH_S 0 115 ;
|
||||
macro CH_D 0 100 ;
|
||||
macro CH_Q 0 113 ;
|
||||
macro CH_w 0 87 ;
|
||||
macro CH_a 0 65 ;
|
||||
macro CH_s 0 83 ;
|
||||
macro CH_d 0 68 ;
|
||||
macro CH_q 0 81 ;
|
||||
macro FRAME_DELAY_NS 0 350000000 ;
|
||||
|
||||
macro TCGETS 0 21505 ;
|
||||
macro TCSETS 0 21506 ;
|
||||
macro LFLAG_OFF 0 12 ;
|
||||
macro ECHO 0 8 ;
|
||||
macro ICANON 0 2 ;
|
||||
|
||||
# state layout (qwords)
|
||||
macro ST_DIR 0 0 ;
|
||||
macro ST_LEN 0 8 ;
|
||||
macro ST_FOOD_X 0 16 ;
|
||||
macro ST_FOOD_Y 0 24 ;
|
||||
macro ST_GAME_OVER 0 32 ;
|
||||
macro ST_QUIT 0 40 ;
|
||||
macro ST_WIN 0 48 ;
|
||||
|
||||
# direction constants
|
||||
macro DIR_RIGHT 0 0 ;
|
||||
macro DIR_DOWN 0 1 ;
|
||||
macro DIR_LEFT 0 2 ;
|
||||
macro DIR_UP 0 3 ;
|
||||
|
||||
#xy_idx [*, x | y] -> [* | idx]
|
||||
word xy_idx
|
||||
WIDTH * +
|
||||
end
|
||||
|
||||
#board_get [*, board, x | y] -> [* | value]
|
||||
word board_get
|
||||
xy_idx
|
||||
1 - arr_get
|
||||
end
|
||||
|
||||
#board_set [*, board, x, y | value] -> [*]
|
||||
word board_set
|
||||
>r
|
||||
xy_idx
|
||||
r> swap 1 - arr_set
|
||||
end
|
||||
|
||||
#state_dir@ [* | state] -> [* | dir]
|
||||
word state_dir@
|
||||
ST_DIR + @
|
||||
end
|
||||
|
||||
#state_dir! [*, state | dir] -> [*]
|
||||
word state_dir!
|
||||
swap ST_DIR + swap !
|
||||
end
|
||||
|
||||
#state_len@ [* | state] -> [* | len]
|
||||
word state_len@
|
||||
ST_LEN + @
|
||||
end
|
||||
|
||||
#state_len! [*, state | len] -> [*]
|
||||
word state_len!
|
||||
swap ST_LEN + swap !
|
||||
end
|
||||
|
||||
#state_food_x@ [* | state] -> [* | x]
|
||||
word state_food_x@
|
||||
ST_FOOD_X + @
|
||||
end
|
||||
|
||||
#state_food_x! [*, state | x] -> [*]
|
||||
word state_food_x!
|
||||
swap ST_FOOD_X + swap !
|
||||
end
|
||||
|
||||
#state_food_y@ [* | state] -> [* | y]
|
||||
word state_food_y@
|
||||
ST_FOOD_Y + @
|
||||
end
|
||||
|
||||
#state_food_y! [*, state | y] -> [*]
|
||||
word state_food_y!
|
||||
swap ST_FOOD_Y + swap !
|
||||
end
|
||||
|
||||
#state_game_over@ [* | state] -> [* | flag]
|
||||
word state_game_over@
|
||||
ST_GAME_OVER + @
|
||||
end
|
||||
|
||||
#state_game_over! [*, state | flag] -> [*]
|
||||
word state_game_over!
|
||||
swap ST_GAME_OVER + swap !
|
||||
end
|
||||
|
||||
#state_quit@ [* | state] -> [* | flag]
|
||||
word state_quit@
|
||||
ST_QUIT + @
|
||||
end
|
||||
|
||||
#state_quit! [*, state | flag] -> [*]
|
||||
word state_quit!
|
||||
swap ST_QUIT + swap !
|
||||
end
|
||||
|
||||
#state_win@ [* | state] -> [* | flag]
|
||||
word state_win@
|
||||
ST_WIN + @
|
||||
end
|
||||
|
||||
#state_win! [*, state | flag] -> [*]
|
||||
word state_win!
|
||||
swap ST_WIN + swap !
|
||||
end
|
||||
|
||||
#term_enter [*] -> [*]
|
||||
word term_enter
|
||||
# Enter alternate screen: ESC[?1049h
|
||||
27 putc 91 putc 63 putc 49 putc 48 putc 52 putc 57 putc 104 putc
|
||||
# Hide cursor: ESC[?25l
|
||||
27 putc 91 putc 63 putc 50 putc 53 putc 108 putc
|
||||
end
|
||||
|
||||
#term_raw_on [*, orig | work] -> [*]
|
||||
:asm term_raw_on {
|
||||
; stack: orig (NOS), work (TOS)
|
||||
mov r14, [r12] ; work
|
||||
mov r15, [r12 + 8] ; orig
|
||||
add r12, 16
|
||||
|
||||
; ioctl(0, TCGETS, orig)
|
||||
mov rax, 16
|
||||
mov rdi, 0
|
||||
mov rsi, 21505
|
||||
mov rdx, r15
|
||||
syscall
|
||||
|
||||
; copy 64 bytes orig -> work
|
||||
mov rcx, 8
|
||||
mov rsi, r15
|
||||
mov rdi, r14
|
||||
.copy_loop:
|
||||
mov rbx, [rsi]
|
||||
mov [rdi], rbx
|
||||
add rsi, 8
|
||||
add rdi, 8
|
||||
loop .copy_loop
|
||||
|
||||
; clear ECHO | ICANON in c_lflag (offset 12)
|
||||
mov eax, [r14 + 12]
|
||||
and eax, 0xFFFFFFF5
|
||||
mov [r14 + 12], eax
|
||||
|
||||
; c_cc[VTIME]=0 (offset 17+5), c_cc[VMIN]=0 (offset 17+6)
|
||||
mov byte [r14 + 22], 0
|
||||
mov byte [r14 + 23], 0
|
||||
|
||||
; ioctl(0, TCSETS, work)
|
||||
mov rax, 16
|
||||
mov rdi, 0
|
||||
mov rsi, 21506
|
||||
mov rdx, r14
|
||||
syscall
|
||||
}
|
||||
;
|
||||
|
||||
#stdin_nonblock_on [* | old_flags_ptr] -> [*]
|
||||
:asm stdin_nonblock_on {
|
||||
mov r14, [r12]
|
||||
add r12, 8
|
||||
|
||||
; old_flags = fcntl(0, F_GETFL, 0)
|
||||
mov rax, 72
|
||||
mov rdi, 0
|
||||
mov rsi, 3
|
||||
xor rdx, rdx
|
||||
syscall
|
||||
mov [r14], rax
|
||||
|
||||
; fcntl(0, F_SETFL, old_flags | O_NONBLOCK)
|
||||
mov rbx, rax
|
||||
or rbx, 2048
|
||||
mov rax, 72
|
||||
mov rdi, 0
|
||||
mov rsi, 4
|
||||
mov rdx, rbx
|
||||
syscall
|
||||
}
|
||||
;
|
||||
|
||||
#stdin_nonblock_off [* | old_flags_ptr] -> [*]
|
||||
:asm stdin_nonblock_off {
|
||||
mov r14, [r12]
|
||||
add r12, 8
|
||||
|
||||
mov rax, 72
|
||||
mov rdi, 0
|
||||
mov rsi, 4
|
||||
mov rdx, [r14]
|
||||
syscall
|
||||
}
|
||||
;
|
||||
|
||||
#sleep_tick [* | ts_ptr] -> [*]
|
||||
:asm sleep_tick {
|
||||
mov r14, [r12]
|
||||
add r12, 8
|
||||
|
||||
; nanosleep(ts_ptr, NULL)
|
||||
mov rax, 35
|
||||
mov rdi, r14
|
||||
xor rsi, rsi
|
||||
syscall
|
||||
}
|
||||
;
|
||||
|
||||
#wait [* | ts_ptr] -> [*]
|
||||
word wait
|
||||
sleep_tick
|
||||
end
|
||||
|
||||
#term_raw_off [* | orig] -> [*]
|
||||
:asm term_raw_off {
|
||||
mov r14, [r12]
|
||||
add r12, 8
|
||||
|
||||
mov rax, 16
|
||||
mov rdi, 0
|
||||
mov rsi, 21506
|
||||
mov rdx, r14
|
||||
syscall
|
||||
}
|
||||
;
|
||||
|
||||
#term_leave [*] -> [*]
|
||||
word term_leave
|
||||
# Show cursor: ESC[?25h
|
||||
27 putc 91 putc 63 putc 50 putc 53 putc 104 putc
|
||||
# Leave alternate screen: ESC[?1049l
|
||||
27 putc 91 putc 63 putc 49 putc 48 putc 52 putc 57 putc 108 putc
|
||||
end
|
||||
|
||||
#clear_screen_home [*] -> [*]
|
||||
word clear_screen_home
|
||||
# Clear full screen: ESC[2J
|
||||
27 putc 91 putc 50 putc 74 putc
|
||||
# Move cursor home: ESC[H
|
||||
27 putc 91 putc 72 putc
|
||||
end
|
||||
|
||||
#clear_board [* | board] -> [*]
|
||||
word clear_board
|
||||
0
|
||||
while dup CELLS < do
|
||||
over over 8 * + 0 !
|
||||
1 +
|
||||
end
|
||||
drop
|
||||
drop
|
||||
end
|
||||
|
||||
#init_state [* | state] -> [*]
|
||||
word init_state
|
||||
dup DIR_RIGHT state_dir!
|
||||
dup 3 state_len!
|
||||
dup 0 state_food_x!
|
||||
dup 0 state_food_y!
|
||||
dup 0 state_game_over!
|
||||
dup 0 state_quit!
|
||||
dup 0 state_win!
|
||||
drop
|
||||
end
|
||||
|
||||
#init_snake [*, board, xs | ys] -> [*]
|
||||
word init_snake
|
||||
with b xs ys in
|
||||
WIDTH 2 /
|
||||
HEIGHT 2 /
|
||||
with cx cy in
|
||||
xs 0 cx swap 1 - arr_set
|
||||
ys 0 cy swap 1 - arr_set
|
||||
b cx cy 1 board_set
|
||||
|
||||
xs 1 cx 1 - swap 1 - arr_set
|
||||
ys 1 cy swap 1 - arr_set
|
||||
b cx 1 - cy 1 board_set
|
||||
|
||||
xs 2 cx 2 - swap 1 - arr_set
|
||||
ys 2 cy swap 1 - arr_set
|
||||
b cx 2 - cy 1 board_set
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#spawn_food [*, board | state] -> [*]
|
||||
word spawn_food
|
||||
with b s in
|
||||
rand syscall.getpid + CELLS %
|
||||
0
|
||||
0
|
||||
with start tried found in
|
||||
while tried CELLS < do
|
||||
start tried + CELLS %
|
||||
dup b swap 1 - arr_get 0 == if
|
||||
dup WIDTH % s swap state_food_x!
|
||||
dup WIDTH / s swap state_food_y!
|
||||
drop
|
||||
1 found !
|
||||
CELLS tried !
|
||||
else
|
||||
drop
|
||||
tried 1 + tried !
|
||||
end
|
||||
end
|
||||
|
||||
found 0 == if
|
||||
s 1 state_win!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#draw_game [*, board, xs, ys | state] -> [*]
|
||||
word draw_game
|
||||
with b xs ys s in
|
||||
"Snake (WASD to steer, q to quit)" puts
|
||||
"Score: " puts
|
||||
s state_len@ 3 - puti
|
||||
10 putc
|
||||
|
||||
xs drop
|
||||
ys drop
|
||||
|
||||
0
|
||||
while dup HEIGHT < do
|
||||
0
|
||||
while dup WIDTH < do
|
||||
over s state_food_y@ == if
|
||||
dup s state_food_x@ == if
|
||||
42 putc
|
||||
else
|
||||
over WIDTH * over +
|
||||
b swap 1 - arr_get
|
||||
if 111 putc else 46 putc end
|
||||
end
|
||||
else
|
||||
over WIDTH * over +
|
||||
b swap 1 - arr_get
|
||||
if 111 putc else 46 putc end
|
||||
end
|
||||
1 +
|
||||
end
|
||||
drop
|
||||
10 putc
|
||||
1 +
|
||||
end
|
||||
drop
|
||||
|
||||
s state_game_over@ if
|
||||
"Game over!" puts
|
||||
end
|
||||
s state_win@ if
|
||||
"You win!" puts
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#read_input [*, input_buf | state] -> [*]
|
||||
word read_input
|
||||
with ibuf s in
|
||||
FD_STDIN ibuf 8 syscall.read
|
||||
dup 0 <= if
|
||||
drop
|
||||
else
|
||||
drop
|
||||
ibuf c@
|
||||
|
||||
dup CH_Q == if
|
||||
drop
|
||||
s 1 state_quit!
|
||||
else dup CH_q == if
|
||||
drop
|
||||
s 1 state_quit!
|
||||
else dup CH_W == if
|
||||
drop
|
||||
s state_dir@ DIR_DOWN != if
|
||||
s DIR_UP state_dir!
|
||||
end
|
||||
else dup CH_w == if
|
||||
drop
|
||||
s state_dir@ DIR_DOWN != if
|
||||
s DIR_UP state_dir!
|
||||
end
|
||||
else dup CH_S == if
|
||||
drop
|
||||
s state_dir@ DIR_UP != if
|
||||
s DIR_DOWN state_dir!
|
||||
end
|
||||
else dup CH_s == if
|
||||
drop
|
||||
s state_dir@ DIR_UP != if
|
||||
s DIR_DOWN state_dir!
|
||||
end
|
||||
else dup CH_A == if
|
||||
drop
|
||||
s state_dir@ DIR_RIGHT != if
|
||||
s DIR_LEFT state_dir!
|
||||
end
|
||||
else dup CH_a == if
|
||||
drop
|
||||
s state_dir@ DIR_RIGHT != if
|
||||
s DIR_LEFT state_dir!
|
||||
end
|
||||
else dup CH_D == if
|
||||
drop
|
||||
s state_dir@ DIR_LEFT != if
|
||||
s DIR_RIGHT state_dir!
|
||||
end
|
||||
else dup CH_d == if
|
||||
drop
|
||||
s state_dir@ DIR_LEFT != if
|
||||
s DIR_RIGHT state_dir!
|
||||
end
|
||||
else
|
||||
drop
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#step_game [*, board, xs, ys | state] -> [*]
|
||||
word step_game
|
||||
with b xs ys s in
|
||||
xs 0 1 - arr_get
|
||||
ys 0 1 - arr_get
|
||||
with hx hy in
|
||||
hx
|
||||
hy
|
||||
# Compute next head from direction.
|
||||
s state_dir@ DIR_RIGHT == if
|
||||
drop
|
||||
hx 1 +
|
||||
hy
|
||||
else s state_dir@ DIR_DOWN == if
|
||||
drop
|
||||
hx
|
||||
hy 1 +
|
||||
else s state_dir@ DIR_LEFT == if
|
||||
drop
|
||||
hx 1 -
|
||||
hy
|
||||
else
|
||||
drop
|
||||
hx
|
||||
hy 1 -
|
||||
end
|
||||
|
||||
with nx ny in
|
||||
# dead flag from wall collision
|
||||
0
|
||||
nx 0 < if drop 1 end
|
||||
nx WIDTH >= if drop 1 end
|
||||
ny 0 < if drop 1 end
|
||||
ny HEIGHT >= if drop 1 end
|
||||
|
||||
with dead in
|
||||
dead if
|
||||
s 1 state_game_over!
|
||||
else
|
||||
# grow flag
|
||||
0
|
||||
nx s state_food_x@ == if
|
||||
ny s state_food_y@ == if
|
||||
drop 1
|
||||
end
|
||||
end
|
||||
|
||||
with grow in
|
||||
# when not growing, remove tail before collision check
|
||||
grow 0 == if
|
||||
s state_len@ 1 -
|
||||
with ti in
|
||||
xs ti 1 - arr_get
|
||||
ys ti 1 - arr_get
|
||||
with tx ty in
|
||||
b tx ty 0 board_set
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# self collision
|
||||
b nx ny board_get if
|
||||
s 1 state_game_over!
|
||||
else
|
||||
# shift body
|
||||
s state_len@
|
||||
grow if
|
||||
# start at len for growth
|
||||
else
|
||||
1 -
|
||||
end
|
||||
while dup 0 > do
|
||||
dup >r
|
||||
xs r@ xs r@ 2 - arr_get swap 1 - arr_set
|
||||
ys r@ ys r@ 2 - arr_get swap 1 - arr_set
|
||||
rdrop
|
||||
1 -
|
||||
end
|
||||
drop
|
||||
|
||||
# write new head
|
||||
xs 0 nx swap 1 - arr_set
|
||||
ys 0 ny swap 1 - arr_set
|
||||
b nx ny 1 board_set
|
||||
|
||||
grow if
|
||||
s state_len@ 1 + s swap state_len!
|
||||
b s spawn_food
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
word main
|
||||
CELLS 8 * alloc
|
||||
CELLS 8 * alloc
|
||||
CELLS 8 * alloc
|
||||
56 alloc
|
||||
8 alloc
|
||||
64 alloc
|
||||
64 alloc
|
||||
8 alloc
|
||||
16 alloc
|
||||
|
||||
with board xs ys state input term_orig term_work fd_flags sleep_ts in
|
||||
board clear_board
|
||||
state init_state
|
||||
board xs ys init_snake
|
||||
board state spawn_food
|
||||
|
||||
sleep_ts 0 !
|
||||
sleep_ts 8 + FRAME_DELAY_NS !
|
||||
|
||||
term_orig term_work term_raw_on
|
||||
fd_flags stdin_nonblock_on
|
||||
term_enter
|
||||
|
||||
1
|
||||
while dup do
|
||||
drop
|
||||
clear_screen_home
|
||||
board xs ys state draw_game
|
||||
|
||||
state state_game_over@ if
|
||||
0
|
||||
else state state_win@ if
|
||||
0
|
||||
else state state_quit@ if
|
||||
0
|
||||
else
|
||||
input state read_input
|
||||
state state_quit@ if
|
||||
0
|
||||
else
|
||||
board xs ys state step_game
|
||||
sleep_ts wait
|
||||
1
|
||||
end
|
||||
end
|
||||
end
|
||||
drop
|
||||
|
||||
clear_screen_home
|
||||
board xs ys state draw_game
|
||||
|
||||
fd_flags stdin_nonblock_off
|
||||
term_orig term_raw_off
|
||||
term_leave
|
||||
|
||||
sleep_ts 16 free
|
||||
fd_flags 8 free
|
||||
term_work 64 free
|
||||
term_orig 64 free
|
||||
input 8 free
|
||||
state 56 free
|
||||
ys CELLS 8 * free
|
||||
xs CELLS 8 * free
|
||||
board CELLS 8 * free
|
||||
end
|
||||
|
||||
0
|
||||
end
|
||||
@@ -48,7 +48,6 @@ word sh
|
||||
!
|
||||
|
||||
syscall.fork
|
||||
syscall
|
||||
dup 0 < if
|
||||
>r
|
||||
1 rpick
|
||||
@@ -67,11 +66,9 @@ word sh
|
||||
dup
|
||||
32 +
|
||||
syscall.execve
|
||||
syscall
|
||||
drop
|
||||
127
|
||||
syscall.exit
|
||||
syscall
|
||||
else
|
||||
mem
|
||||
40 +
|
||||
@@ -79,7 +76,6 @@ word sh
|
||||
0
|
||||
0
|
||||
syscall.wait4
|
||||
syscall
|
||||
dup 0 < if
|
||||
>r
|
||||
rdrop
|
||||
|
||||
621
main.py
621
main.py
@@ -344,6 +344,7 @@ OP_LIST_BEGIN = 8
|
||||
OP_LIST_END = 9
|
||||
OP_LIST_LITERAL = 10
|
||||
OP_OTHER = 11
|
||||
OP_RET = 12
|
||||
|
||||
_OP_STR_TO_INT = {
|
||||
"word": OP_WORD,
|
||||
@@ -357,6 +358,7 @@ _OP_STR_TO_INT = {
|
||||
"list_begin": OP_LIST_BEGIN,
|
||||
"list_end": OP_LIST_END,
|
||||
"list_literal": OP_LIST_LITERAL,
|
||||
"ret": OP_RET,
|
||||
}
|
||||
|
||||
|
||||
@@ -442,6 +444,7 @@ _PEEPHOLE_CANCEL_PAIRS = frozenset({
|
||||
("inc", "dec"), ("dec", "inc"),
|
||||
})
|
||||
_PEEPHOLE_SHIFT_OPS = frozenset({"shl", "shr", "sar"})
|
||||
_DEFAULT_CONTROL_WORDS = frozenset({"if", "else", "for", "while", "do"})
|
||||
|
||||
|
||||
class Op:
|
||||
@@ -776,6 +779,9 @@ class Parser:
|
||||
self.source: str = ""
|
||||
self.macro_recording: Optional[MacroDefinition] = None
|
||||
self.control_stack: List[Dict[str, str]] = []
|
||||
self.block_openers: Set[str] = {"word", "with", "for", "while", "begin"}
|
||||
self.control_overrides: Set[str] = set()
|
||||
self._warned_control_overrides: Set[str] = set()
|
||||
self.label_counter = 0
|
||||
self.token_hook: Optional[str] = None
|
||||
self._last_token: Optional[Token] = None
|
||||
@@ -929,29 +935,43 @@ class Parser:
|
||||
return label, hidden_word
|
||||
|
||||
def _handle_end_control(self) -> None:
|
||||
"""Handle unified 'end' for all block types"""
|
||||
"""Close one generic control frame pushed by compile-time words."""
|
||||
if not self.control_stack:
|
||||
raise ParseError("unexpected 'end' without matching block")
|
||||
|
||||
entry = self.control_stack.pop()
|
||||
if not isinstance(entry, dict):
|
||||
raise ParseError("invalid control frame")
|
||||
|
||||
if entry["type"] in ("if", "elif"):
|
||||
# For if/elif without a trailing else
|
||||
if "false" in entry:
|
||||
self._append_op(_make_op("label", entry["false"]))
|
||||
if "end" in entry:
|
||||
self._append_op(_make_op("label", entry["end"]))
|
||||
elif entry["type"] == "else":
|
||||
self._append_op(_make_op("label", entry["end"]))
|
||||
elif entry["type"] == "while":
|
||||
self._append_op(_make_op("jump", entry["begin"]))
|
||||
self._append_op(_make_op("label", entry["end"]))
|
||||
elif entry["type"] == "for":
|
||||
# Emit ForEnd node for loop decrement
|
||||
self._append_op(_make_op("for_end", {"loop": entry["loop"], "end": entry["end"]}))
|
||||
elif entry["type"] == "begin":
|
||||
self._append_op(_make_op("jump", entry["begin"]))
|
||||
self._append_op(_make_op("label", entry["end"]))
|
||||
close_ops = entry.get("close_ops")
|
||||
if close_ops is None:
|
||||
return
|
||||
if not isinstance(close_ops, list):
|
||||
raise ParseError("control frame field 'close_ops' must be a list")
|
||||
|
||||
for spec in close_ops:
|
||||
op_name: Optional[str] = None
|
||||
data: Any = None
|
||||
if isinstance(spec, dict):
|
||||
candidate = spec.get("op")
|
||||
if isinstance(candidate, str):
|
||||
op_name = candidate
|
||||
if "data" in spec:
|
||||
data = spec["data"]
|
||||
elif isinstance(spec, (list, tuple)):
|
||||
if not spec:
|
||||
raise ParseError("close_ops contains empty sequence")
|
||||
if isinstance(spec[0], str):
|
||||
op_name = spec[0]
|
||||
data = spec[1] if len(spec) > 1 else None
|
||||
elif isinstance(spec, str):
|
||||
op_name = spec
|
||||
else:
|
||||
raise ParseError(f"invalid close op descriptor: {spec!r}")
|
||||
|
||||
if not op_name:
|
||||
raise ParseError(f"close op missing valid 'op' name: {spec!r}")
|
||||
self._append_op(_make_op(op_name, data))
|
||||
|
||||
# Parsing ------------------------------------------------------------------
|
||||
def parse(self, tokens: Iterable[Token], source: str) -> Module:
|
||||
@@ -994,20 +1014,13 @@ class Parser:
|
||||
_KW_PY = 6
|
||||
_KW_EXTERN = 7
|
||||
_KW_PRIORITY = 8
|
||||
_KW_IF = 9
|
||||
_KW_ELSE = 10
|
||||
_KW_FOR = 11
|
||||
_KW_WHILE = 12
|
||||
_KW_DO = 13
|
||||
_KW_RET = 9
|
||||
_keyword_dispatch = {
|
||||
"[": _KW_LIST_BEGIN, "]": _KW_LIST_END, "word": _KW_WORD,
|
||||
"end": _KW_END, ":asm": _KW_ASM, ":py": _KW_PY,
|
||||
"extern": _KW_EXTERN, "priority": _KW_PRIORITY,
|
||||
"if": _KW_IF, "else": _KW_ELSE, "for": _KW_FOR,
|
||||
"while": _KW_WHILE, "do": _KW_DO,
|
||||
"extern": _KW_EXTERN, "priority": _KW_PRIORITY, "ret": _KW_RET,
|
||||
}
|
||||
_kw_get = _keyword_dispatch.get
|
||||
|
||||
_tokens = self.tokens
|
||||
try:
|
||||
while self.pos < len(_tokens):
|
||||
@@ -1054,16 +1067,10 @@ class Parser:
|
||||
self._parse_extern(token)
|
||||
elif kw == _KW_PRIORITY:
|
||||
self._parse_priority_directive(token)
|
||||
elif kw == _KW_IF:
|
||||
self._handle_if_control()
|
||||
elif kw == _KW_ELSE:
|
||||
self._handle_else_control()
|
||||
elif kw == _KW_FOR:
|
||||
self._handle_for_control()
|
||||
elif kw == _KW_WHILE:
|
||||
self._handle_while_control()
|
||||
elif kw == _KW_DO:
|
||||
self._handle_do_control()
|
||||
elif kw == _KW_RET:
|
||||
self._handle_ret(token)
|
||||
continue
|
||||
if self._try_handle_builtin_control(token):
|
||||
continue
|
||||
if self._handle_token(token):
|
||||
_tokens = self.tokens
|
||||
@@ -1120,6 +1127,161 @@ class Parser:
|
||||
label = entry["label"]
|
||||
self._append_op(_make_op("list_end", label))
|
||||
|
||||
def _should_use_custom_control(self, lexeme: str) -> bool:
|
||||
# Fast path: default parser controls unless explicitly overridden.
|
||||
if lexeme not in self.control_overrides:
|
||||
return False
|
||||
word = self.dictionary.lookup(lexeme)
|
||||
if word is None:
|
||||
return False
|
||||
return bool(word.immediate)
|
||||
|
||||
def _warn_control_override(self, token: Token, lexeme: str) -> None:
|
||||
if lexeme in self._warned_control_overrides:
|
||||
return
|
||||
self._warned_control_overrides.add(lexeme)
|
||||
sys.stderr.write(
|
||||
f"[warn] default control structure ({lexeme}) has been overridden; using custom implementation\n"
|
||||
)
|
||||
|
||||
def _try_handle_builtin_control(self, token: Token) -> bool:
|
||||
lexeme = token.lexeme
|
||||
if lexeme not in _DEFAULT_CONTROL_WORDS:
|
||||
return False
|
||||
if self._should_use_custom_control(lexeme):
|
||||
self._warn_control_override(token, lexeme)
|
||||
return False
|
||||
if lexeme == "if":
|
||||
self._handle_builtin_if(token)
|
||||
return True
|
||||
if lexeme == "else":
|
||||
self._handle_builtin_else(token)
|
||||
return True
|
||||
if lexeme == "for":
|
||||
self._handle_builtin_for(token)
|
||||
return True
|
||||
if lexeme == "while":
|
||||
self._handle_builtin_while(token)
|
||||
return True
|
||||
if lexeme == "do":
|
||||
self._handle_builtin_do(token)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _handle_builtin_if(self, token: Token) -> None:
|
||||
# Support shorthand `else <cond> if` by sharing the previous else-end label.
|
||||
if self.control_stack:
|
||||
top = self.control_stack[-1]
|
||||
if (
|
||||
top.get("type") == "else"
|
||||
and isinstance(top.get("line"), int)
|
||||
and top["line"] == token.line
|
||||
):
|
||||
prev_else = self._pop_control(("else",))
|
||||
shared_end = prev_else.get("end")
|
||||
if not isinstance(shared_end, str):
|
||||
shared_end = self._new_label("if_end")
|
||||
false_label = self._new_label("if_false")
|
||||
self._append_op(_make_op("branch_zero", false_label))
|
||||
self._push_control(
|
||||
{
|
||||
"type": "if",
|
||||
"false": false_label,
|
||||
"end": shared_end,
|
||||
"close_ops": [
|
||||
{"op": "label", "data": false_label},
|
||||
{"op": "label", "data": shared_end},
|
||||
],
|
||||
"line": token.line,
|
||||
"column": token.column,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
false_label = self._new_label("if_false")
|
||||
self._append_op(_make_op("branch_zero", false_label))
|
||||
self._push_control(
|
||||
{
|
||||
"type": "if",
|
||||
"false": false_label,
|
||||
"end": None,
|
||||
"close_ops": [{"op": "label", "data": false_label}],
|
||||
"line": token.line,
|
||||
"column": token.column,
|
||||
}
|
||||
)
|
||||
|
||||
def _handle_builtin_else(self, token: Token) -> None:
|
||||
entry = self._pop_control(("if",))
|
||||
false_label = entry.get("false")
|
||||
if not isinstance(false_label, str):
|
||||
raise ParseError("invalid if control frame")
|
||||
end_label = entry.get("end")
|
||||
if not isinstance(end_label, str):
|
||||
end_label = self._new_label("if_end")
|
||||
self._append_op(_make_op("jump", end_label))
|
||||
self._append_op(_make_op("label", false_label))
|
||||
self._push_control(
|
||||
{
|
||||
"type": "else",
|
||||
"end": end_label,
|
||||
"close_ops": [{"op": "label", "data": end_label}],
|
||||
"line": token.line,
|
||||
"column": token.column,
|
||||
}
|
||||
)
|
||||
|
||||
def _handle_builtin_for(self, token: Token) -> None:
|
||||
loop_label = self._new_label("for_loop")
|
||||
end_label = self._new_label("for_end")
|
||||
frame = {"loop": loop_label, "end": end_label}
|
||||
self._append_op(_make_op("for_begin", dict(frame)))
|
||||
self._push_control(
|
||||
{
|
||||
"type": "for",
|
||||
"loop": loop_label,
|
||||
"end": end_label,
|
||||
"close_ops": [{"op": "for_end", "data": dict(frame)}],
|
||||
"line": token.line,
|
||||
"column": token.column,
|
||||
}
|
||||
)
|
||||
|
||||
def _handle_builtin_while(self, token: Token) -> None:
|
||||
begin_label = self._new_label("begin")
|
||||
end_label = self._new_label("end")
|
||||
self._append_op(_make_op("label", begin_label))
|
||||
self._push_control(
|
||||
{
|
||||
"type": "while_open",
|
||||
"begin": begin_label,
|
||||
"end": end_label,
|
||||
"line": token.line,
|
||||
"column": token.column,
|
||||
}
|
||||
)
|
||||
|
||||
def _handle_builtin_do(self, token: Token) -> None:
|
||||
entry = self._pop_control(("while_open",))
|
||||
begin_label = entry.get("begin")
|
||||
end_label = entry.get("end")
|
||||
if not isinstance(begin_label, str) or not isinstance(end_label, str):
|
||||
raise ParseError("invalid while control frame")
|
||||
self._append_op(_make_op("branch_zero", end_label))
|
||||
self._push_control(
|
||||
{
|
||||
"type": "while",
|
||||
"begin": begin_label,
|
||||
"end": end_label,
|
||||
"close_ops": [
|
||||
{"op": "jump", "data": begin_label},
|
||||
{"op": "label", "data": end_label},
|
||||
],
|
||||
"line": token.line,
|
||||
"column": token.column,
|
||||
}
|
||||
)
|
||||
|
||||
def _parse_priority_directive(self, token: Token) -> None:
|
||||
if self._eof():
|
||||
raise ParseError(f"priority value missing at {token.line}:{token.column}")
|
||||
@@ -1139,6 +1301,9 @@ class Parser:
|
||||
self._pending_priority = None
|
||||
return value
|
||||
|
||||
def _handle_ret(self, token: Token) -> None:
|
||||
self._append_op(_make_op("ret", loc=token))
|
||||
|
||||
# Internal helpers ---------------------------------------------------------
|
||||
|
||||
def _parse_extern(self, token: Token) -> None:
|
||||
@@ -1469,52 +1634,6 @@ class Parser:
|
||||
handled = self.compile_time_vm.pop()
|
||||
return bool(handled)
|
||||
|
||||
def _handle_if_control(self) -> None:
|
||||
token = self._last_token
|
||||
if (
|
||||
self.control_stack
|
||||
and self.control_stack[-1]["type"] == "else"
|
||||
and token is not None
|
||||
and self.control_stack[-1].get("line") == token.line
|
||||
):
|
||||
entry = self.control_stack.pop()
|
||||
end_label = entry.get("end")
|
||||
if end_label is None:
|
||||
end_label = self._new_label("if_end")
|
||||
false_label = self._new_label("if_false")
|
||||
self._append_op(_make_op("branch_zero", false_label))
|
||||
self._push_control({"type": "elif", "false": false_label, "end": end_label})
|
||||
return
|
||||
false_label = self._new_label("if_false")
|
||||
self._append_op(_make_op("branch_zero", false_label))
|
||||
self._push_control({"type": "if", "false": false_label})
|
||||
|
||||
def _handle_else_control(self) -> None:
|
||||
entry = self._pop_control(("if", "elif"))
|
||||
end_label = entry.get("end")
|
||||
if end_label is None:
|
||||
end_label = self._new_label("if_end")
|
||||
self._append_op(_make_op("jump", end_label))
|
||||
self._append_op(_make_op("label", entry["false"]))
|
||||
self._push_control({"type": "else", "end": end_label})
|
||||
|
||||
def _handle_for_control(self) -> None:
|
||||
loop_label = self._new_label("for_loop")
|
||||
end_label = self._new_label("for_end")
|
||||
self._append_op(_make_op("for_begin", {"loop": loop_label, "end": end_label}))
|
||||
self._push_control({"type": "for", "loop": loop_label, "end": end_label})
|
||||
|
||||
def _handle_while_control(self) -> None:
|
||||
begin_label = self._new_label("begin")
|
||||
end_label = self._new_label("end")
|
||||
self._append_op(_make_op("label", begin_label))
|
||||
self._push_control({"type": "begin", "begin": begin_label, "end": end_label})
|
||||
|
||||
def _handle_do_control(self) -> None:
|
||||
entry = self._pop_control(("begin",))
|
||||
self._append_op(_make_op("branch_zero", entry["end"]))
|
||||
self._push_control(entry)
|
||||
|
||||
def _try_end_definition(self, token: Token) -> bool:
|
||||
if len(self.context_stack) <= 1:
|
||||
return False
|
||||
@@ -3886,6 +4005,7 @@ class CompileTimeVM:
|
||||
_OP_JUMP = OP_JUMP
|
||||
_OP_LABEL = OP_LABEL
|
||||
_OP_LIST_BEGIN = OP_LIST_BEGIN
|
||||
_OP_RET = OP_RET
|
||||
_OP_LIST_END = OP_LIST_END
|
||||
_OP_LIST_LITERAL = OP_LIST_LITERAL
|
||||
try:
|
||||
@@ -4189,6 +4309,9 @@ class CompileTimeVM:
|
||||
ip += 1
|
||||
continue
|
||||
|
||||
if kind == _OP_RET:
|
||||
return
|
||||
|
||||
self.current_location = _node.loc
|
||||
raise ParseError(f"unsupported compile-time op (opcode={kind})")
|
||||
finally:
|
||||
@@ -4313,9 +4436,21 @@ class FunctionEmitter:
|
||||
escaped = path.replace("\\", "\\\\").replace('"', '\\"')
|
||||
self.text.append(f'%line {line}+{increment} "{escaped}"')
|
||||
|
||||
def set_location(self, loc: Optional[SourceLocation]) -> None:
|
||||
def set_location(self, loc) -> None:
|
||||
if not self.debug_enabled:
|
||||
return
|
||||
# Defensive: if loc is a Token, convert to SourceLocation, did not have a better solution, works for me
|
||||
if loc is not None and not hasattr(loc, 'path') and hasattr(loc, 'line') and hasattr(loc, 'column'):
|
||||
# Assume self has a reference to the parser or a location_for_token function
|
||||
# If not, fallback to generic source path
|
||||
try:
|
||||
loc = self.location_for_token(loc)
|
||||
except Exception:
|
||||
from pathlib import Path
|
||||
loc = type('SourceLocation', (), {})()
|
||||
loc.path = Path('<source>')
|
||||
loc.line = getattr(loc, 'line', 0)
|
||||
loc.column = getattr(loc, 'column', 0)
|
||||
if loc is None:
|
||||
if self._current_loc is None:
|
||||
return
|
||||
@@ -4362,6 +4497,9 @@ class FunctionEmitter:
|
||||
_a(f" mov {register}, [r12]")
|
||||
_a(" add r12, 8")
|
||||
|
||||
def ret(self) -> None:
|
||||
self.text.append(" ret")
|
||||
|
||||
|
||||
def _int_trunc_div(lhs: int, rhs: int) -> int:
|
||||
if rhs == 0:
|
||||
@@ -6442,6 +6580,10 @@ class Assembler:
|
||||
builder.emit(" mov [r12], rax")
|
||||
return
|
||||
|
||||
if kind == OP_RET:
|
||||
builder.ret()
|
||||
return
|
||||
|
||||
raise CompileError(f"unsupported op {node!r} while emitting '{self._emit_stack[-1]}'" if self._emit_stack else f"unsupported op {node!r}")
|
||||
|
||||
def _emit_mmap_alloc(self, builder: FunctionEmitter, size: int, target_reg: str = "rax") -> None:
|
||||
@@ -6702,7 +6844,8 @@ class Assembler:
|
||||
suffix = f" while emitting '{self._emit_stack[-1]}'" if self._emit_stack else ""
|
||||
raise CompileError(f"unknown word '{name}'{suffix}")
|
||||
if word.compile_only:
|
||||
return # silently skip compile-time-only words during emission
|
||||
suffix = f" while emitting '{self._emit_stack[-1]}'" if self._emit_stack else ""
|
||||
raise CompileError(f"word '{name}' is compile-time only and cannot be used at runtime{suffix}")
|
||||
if getattr(word, "inline", False):
|
||||
if isinstance(word.definition, Definition):
|
||||
if word.name in self._inline_stack:
|
||||
@@ -6943,18 +7086,29 @@ def macro_with(ctx: MacroContext) -> Optional[List[Op]]:
|
||||
raise ParseError("'with' requires at least one variable name")
|
||||
|
||||
body: List[Token] = []
|
||||
else_line: Optional[int] = None
|
||||
depth = 0
|
||||
while True:
|
||||
if parser._eof():
|
||||
raise ParseError("unterminated 'with' block (missing 'end')")
|
||||
tok = parser.next_token()
|
||||
if else_line is not None and tok.line != else_line:
|
||||
else_line = None
|
||||
if tok.lexeme == "end":
|
||||
if depth == 0:
|
||||
break
|
||||
depth -= 1
|
||||
body.append(tok)
|
||||
continue
|
||||
if tok.lexeme in ("with", "if", "for", "while", "begin", "word"):
|
||||
if tok.lexeme == "if":
|
||||
# Support shorthand elif form `else <cond> if` inside with-blocks.
|
||||
# This inline `if` shares the same closing `end` as the preceding
|
||||
# branch and therefore must not increment nesting depth.
|
||||
if else_line != tok.line:
|
||||
depth += 1
|
||||
elif tok.lexeme == "else":
|
||||
else_line = tok.line
|
||||
elif tok.lexeme in parser.block_openers:
|
||||
depth += 1
|
||||
body.append(tok)
|
||||
|
||||
@@ -6963,14 +7117,26 @@ def macro_with(ctx: MacroContext) -> Optional[List[Op]]:
|
||||
_, helper = parser.allocate_variable(name)
|
||||
helper_for[name] = helper
|
||||
|
||||
emitted: List[str] = []
|
||||
emitted_tokens: List[Token] = []
|
||||
|
||||
def _emit_lex(lex: str, src_tok: Optional[Token] = None) -> None:
|
||||
base = src_tok or template or Token(lexeme="", line=0, column=0, start=0, end=0)
|
||||
emitted_tokens.append(
|
||||
Token(
|
||||
lexeme=lex,
|
||||
line=base.line,
|
||||
column=base.column,
|
||||
start=base.start,
|
||||
end=base.end,
|
||||
)
|
||||
)
|
||||
|
||||
# Initialize variables by storing current stack values into their buffers
|
||||
for name in reversed(names):
|
||||
helper = helper_for[name]
|
||||
emitted.append(helper)
|
||||
emitted.append("swap")
|
||||
emitted.append("!")
|
||||
_emit_lex(helper, template)
|
||||
_emit_lex("swap", template)
|
||||
_emit_lex("!", template)
|
||||
|
||||
i = 0
|
||||
while i < len(body):
|
||||
@@ -6980,23 +7146,23 @@ def macro_with(ctx: MacroContext) -> Optional[List[Op]]:
|
||||
if helper is not None:
|
||||
next_tok = body[i + 1] if i + 1 < len(body) else None
|
||||
if next_tok is not None and next_tok.lexeme == "!":
|
||||
emitted.append(helper)
|
||||
emitted.append("swap")
|
||||
emitted.append("!")
|
||||
_emit_lex(helper, tok)
|
||||
_emit_lex("swap", tok)
|
||||
_emit_lex("!", tok)
|
||||
i += 2
|
||||
continue
|
||||
if next_tok is not None and next_tok.lexeme == "@":
|
||||
emitted.append(helper)
|
||||
_emit_lex(helper, tok)
|
||||
i += 1
|
||||
continue
|
||||
emitted.append(helper)
|
||||
emitted.append("@")
|
||||
_emit_lex(helper, tok)
|
||||
_emit_lex("@", tok)
|
||||
i += 1
|
||||
continue
|
||||
emitted.append(tok.lexeme)
|
||||
_emit_lex(tok.lexeme, tok)
|
||||
i += 1
|
||||
|
||||
ctx.inject_tokens(emitted, template=template)
|
||||
ctx.inject_token_objects(emitted_tokens)
|
||||
return None
|
||||
|
||||
|
||||
@@ -7276,6 +7442,105 @@ def _ct_loop_index(vm: CompileTimeVM) -> None:
|
||||
vm.push(idx)
|
||||
|
||||
|
||||
def _ct_control_frame_new(vm: CompileTimeVM) -> None:
|
||||
type_name = vm.pop_str()
|
||||
vm.push({"type": type_name})
|
||||
|
||||
|
||||
def _ct_control_get(vm: CompileTimeVM) -> None:
|
||||
key = vm.pop_str()
|
||||
frame = vm.pop()
|
||||
if not isinstance(frame, dict):
|
||||
raise ParseError("ct-control-get expects a control frame")
|
||||
vm.push(frame.get(key))
|
||||
|
||||
|
||||
def _ct_control_set(vm: CompileTimeVM) -> None:
|
||||
value = vm.pop()
|
||||
key = vm.pop_str()
|
||||
frame = vm.pop()
|
||||
if not isinstance(frame, dict):
|
||||
raise ParseError("ct-control-set expects a control frame")
|
||||
frame[key] = value
|
||||
vm.push(frame)
|
||||
|
||||
|
||||
def _ct_control_push(vm: CompileTimeVM) -> None:
|
||||
frame = vm.pop()
|
||||
if not isinstance(frame, dict):
|
||||
raise ParseError("ct-control-push expects a control frame")
|
||||
vm.parser._push_control(dict(frame))
|
||||
|
||||
|
||||
def _ct_control_pop(vm: CompileTimeVM) -> None:
|
||||
if not vm.parser.control_stack:
|
||||
raise ParseError("control stack underflow")
|
||||
vm.push(dict(vm.parser.control_stack.pop()))
|
||||
|
||||
|
||||
def _ct_control_peek(vm: CompileTimeVM) -> None:
|
||||
if not vm.parser.control_stack:
|
||||
vm.push(None)
|
||||
return
|
||||
vm.push(dict(vm.parser.control_stack[-1]))
|
||||
|
||||
|
||||
def _ct_control_depth(vm: CompileTimeVM) -> None:
|
||||
vm.push(len(vm.parser.control_stack))
|
||||
|
||||
|
||||
def _ct_new_label(vm: CompileTimeVM) -> None:
|
||||
prefix = vm.pop_str()
|
||||
vm.push(vm.parser._new_label(prefix))
|
||||
|
||||
|
||||
def _ct_emit_op(vm: CompileTimeVM) -> None:
|
||||
data = vm.pop()
|
||||
op_name = vm.pop_str()
|
||||
vm.parser.emit_node(_make_op(op_name, data))
|
||||
|
||||
|
||||
def _ct_control_add_close_op(vm: CompileTimeVM) -> None:
|
||||
data = vm.pop()
|
||||
op_name = vm.pop_str()
|
||||
frame = vm.pop()
|
||||
if not isinstance(frame, dict):
|
||||
raise ParseError("ct-control-add-close-op expects a control frame")
|
||||
close_ops = frame.get("close_ops")
|
||||
if close_ops is None:
|
||||
close_ops = []
|
||||
elif not isinstance(close_ops, list):
|
||||
raise ParseError("control frame field 'close_ops' must be a list")
|
||||
close_ops.append({"op": op_name, "data": data})
|
||||
frame["close_ops"] = close_ops
|
||||
vm.push(frame)
|
||||
|
||||
|
||||
def _ct_last_token_line(vm: CompileTimeVM) -> None:
|
||||
tok = vm.parser._last_token
|
||||
vm.push(0 if tok is None else tok.line)
|
||||
|
||||
|
||||
def _ct_register_block_opener(vm: CompileTimeVM) -> None:
|
||||
name = vm.pop_str()
|
||||
vm.parser.block_openers.add(name)
|
||||
|
||||
|
||||
def _ct_unregister_block_opener(vm: CompileTimeVM) -> None:
|
||||
name = vm.pop_str()
|
||||
vm.parser.block_openers.discard(name)
|
||||
|
||||
|
||||
def _ct_register_control_override(vm: CompileTimeVM) -> None:
|
||||
name = vm.pop_str()
|
||||
vm.parser.control_overrides.add(name)
|
||||
|
||||
|
||||
def _ct_unregister_control_override(vm: CompileTimeVM) -> None:
|
||||
name = vm.pop_str()
|
||||
vm.parser.control_overrides.discard(name)
|
||||
|
||||
|
||||
def _ct_list_get(vm: CompileTimeVM) -> None:
|
||||
index = vm.pop_int()
|
||||
lst = _ensure_list(vm.pop())
|
||||
@@ -7874,6 +8139,21 @@ def _register_compile_time_primitives(dictionary: Dictionary) -> None:
|
||||
register("list-extend", _ct_list_extend, compile_only=True)
|
||||
register("list-last", _ct_list_last, compile_only=True)
|
||||
register("i", _ct_loop_index, compile_only=True)
|
||||
register("ct-control-frame-new", _ct_control_frame_new, compile_only=True)
|
||||
register("ct-control-get", _ct_control_get, compile_only=True)
|
||||
register("ct-control-set", _ct_control_set, compile_only=True)
|
||||
register("ct-control-push", _ct_control_push, compile_only=True)
|
||||
register("ct-control-pop", _ct_control_pop, compile_only=True)
|
||||
register("ct-control-peek", _ct_control_peek, compile_only=True)
|
||||
register("ct-control-depth", _ct_control_depth, compile_only=True)
|
||||
register("ct-control-add-close-op", _ct_control_add_close_op, compile_only=True)
|
||||
register("ct-new-label", _ct_new_label, compile_only=True)
|
||||
register("ct-emit-op", _ct_emit_op, compile_only=True)
|
||||
register("ct-last-token-line", _ct_last_token_line, compile_only=True)
|
||||
register("ct-register-block-opener", _ct_register_block_opener, compile_only=True)
|
||||
register("ct-unregister-block-opener", _ct_unregister_block_opener, compile_only=True)
|
||||
register("ct-register-control-override", _ct_register_control_override, compile_only=True)
|
||||
register("ct-unregister-control-override", _ct_unregister_control_override, compile_only=True)
|
||||
|
||||
register("prelude-clear", _ct_prelude_clear, compile_only=True)
|
||||
register("prelude-append", _ct_prelude_append, compile_only=True)
|
||||
@@ -8427,6 +8707,68 @@ class Compiler:
|
||||
word.intrinsic = self._emit_syscall_intrinsic
|
||||
|
||||
def _emit_syscall_intrinsic(self, builder: FunctionEmitter) -> None:
|
||||
def _try_pop_known_syscall_setup() -> Optional[Tuple[int, int]]:
|
||||
"""Recognize and remove literal setup for known-argc syscalls.
|
||||
|
||||
Supported forms right before `syscall`:
|
||||
1) <argc> <nr>
|
||||
2) <nr> <argc> ___linux_swap
|
||||
Returns (argc, nr) when recognized.
|
||||
"""
|
||||
|
||||
# Form 1: ... push argc ; push nr ; syscall
|
||||
nr = Assembler._pop_preceding_literal(builder)
|
||||
if nr is not None:
|
||||
argc = Assembler._pop_preceding_literal(builder)
|
||||
if argc is not None and 0 <= argc <= 6:
|
||||
return argc, nr
|
||||
# rollback if second literal wasn't argc
|
||||
builder.push_literal(nr)
|
||||
|
||||
# Form 2: ... push nr ; push argc ; ___linux_swap ; syscall
|
||||
text = builder.text
|
||||
swap_tail = [
|
||||
"mov rax, [r12]",
|
||||
"mov rbx, [r12 + 8]",
|
||||
"mov [r12], rbx",
|
||||
"mov [r12 + 8], rax",
|
||||
]
|
||||
if len(text) >= 4 and [s.strip() for s in text[-4:]] == swap_tail:
|
||||
del text[-4:]
|
||||
argc2 = Assembler._pop_preceding_literal(builder)
|
||||
nr2 = Assembler._pop_preceding_literal(builder)
|
||||
if argc2 is not None and nr2 is not None and 0 <= argc2 <= 6:
|
||||
return argc2, nr2
|
||||
# rollback conservatively if match fails
|
||||
if nr2 is not None:
|
||||
builder.push_literal(nr2)
|
||||
if argc2 is not None:
|
||||
builder.push_literal(argc2)
|
||||
text.extend(swap_tail)
|
||||
|
||||
return None
|
||||
|
||||
known = _try_pop_known_syscall_setup()
|
||||
if known is not None:
|
||||
argc, nr = known
|
||||
builder.push_literal(nr)
|
||||
builder.pop_to("rax")
|
||||
if argc >= 6:
|
||||
builder.pop_to("r9")
|
||||
if argc >= 5:
|
||||
builder.pop_to("r8")
|
||||
if argc >= 4:
|
||||
builder.pop_to("r10")
|
||||
if argc >= 3:
|
||||
builder.pop_to("rdx")
|
||||
if argc >= 2:
|
||||
builder.pop_to("rsi")
|
||||
if argc >= 1:
|
||||
builder.pop_to("rdi")
|
||||
builder.emit(" syscall")
|
||||
builder.push_from("rax")
|
||||
return
|
||||
|
||||
label_id = self._syscall_label_counter
|
||||
self._syscall_label_counter += 1
|
||||
|
||||
@@ -10164,6 +10506,26 @@ def _run_docs_tui(
|
||||
" 3 1 syscall # 3 args, nr=1 (write)"
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "ret",
|
||||
"category": "Control Flow",
|
||||
"syntax": "ret",
|
||||
"summary": "Return from a word",
|
||||
"detail": (
|
||||
"Returns from a word.\n\n"
|
||||
"Example:\n"
|
||||
" word a\n"
|
||||
" \"g\" puts\n"
|
||||
" ret\n"
|
||||
" \"g\" puts\n"
|
||||
" end\n\n"
|
||||
" word main\n"
|
||||
" a\n"
|
||||
" end\n"
|
||||
"Output:\n"
|
||||
" g\n"
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "exit",
|
||||
"category": "System",
|
||||
@@ -10717,6 +11079,54 @@ def _run_docs_tui(
|
||||
" word <name> <body...> end\n"
|
||||
" into the parser's token stream.\n"
|
||||
"\n"
|
||||
" ── Control-frame helpers (for custom control structures)\n"
|
||||
"\n"
|
||||
" ct-control-frame-new [* | type] -> [* | frame]\n"
|
||||
" Create a control frame map with a `type` field.\n"
|
||||
"\n"
|
||||
" ct-control-get [*, frame | key] -> [* | value]\n"
|
||||
" Read key from a control frame map.\n"
|
||||
"\n"
|
||||
" ct-control-set [*, frame, key | value] -> [* | frame]\n"
|
||||
" Write key/value into a control frame map.\n"
|
||||
"\n"
|
||||
" ct-control-push [* | frame] -> [*]\n"
|
||||
" Push a frame onto the parser control stack.\n"
|
||||
"\n"
|
||||
" ct-control-pop [*] -> [* | frame]\n"
|
||||
" Pop and return the top parser control frame.\n"
|
||||
"\n"
|
||||
" ct-control-peek [*] -> [* | frame] || [* | nil]\n"
|
||||
" Return the top parser control frame without popping.\n"
|
||||
"\n"
|
||||
" ct-control-depth [*] -> [* | n]\n"
|
||||
" Return parser control-stack depth.\n"
|
||||
"\n"
|
||||
" ct-control-add-close-op [*, frame, op | data] -> [* | frame]\n"
|
||||
" Append a close operation descriptor to frame.close_ops.\n"
|
||||
"\n"
|
||||
" ct-new-label [* | prefix] -> [* | label]\n"
|
||||
" Allocate a fresh internal label with the given prefix.\n"
|
||||
"\n"
|
||||
" ct-emit-op [*, op | data] -> [*]\n"
|
||||
" Emit an internal op node directly into the current body.\n"
|
||||
"\n"
|
||||
" ct-last-token-line [*] -> [* | line]\n"
|
||||
" Return line number of the last parser token (or 0).\n"
|
||||
"\n"
|
||||
" ct-register-block-opener [* | name] -> [*]\n"
|
||||
" Mark a word name as a block opener for `with` nesting.\n"
|
||||
"\n"
|
||||
" ct-unregister-block-opener [* | name] -> [*]\n"
|
||||
" Remove a word name from block opener registration.\n"
|
||||
"\n"
|
||||
" ct-register-control-override [* | name] -> [*]\n"
|
||||
" Register a control word override so parser can delegate\n"
|
||||
" built-in control handling to custom compile-time words.\n"
|
||||
"\n"
|
||||
" ct-unregister-control-override [* | name] -> [*]\n"
|
||||
" Remove a control word override registration.\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
|
||||
" § 7 LEXER OBJECTS\n"
|
||||
@@ -10935,6 +11345,21 @@ def _run_docs_tui(
|
||||
" add-token Token [* | s] -> [*]\n"
|
||||
" add-token-chars Token [* | s] -> [*]\n"
|
||||
" emit-definition Token [*, name | body] -> [*]\n"
|
||||
" ct-control-frame-new Control [* | type] -> [* | frame]\n"
|
||||
" ct-control-get Control [*, frame | key] -> [* | value]\n"
|
||||
" ct-control-set Control [*, frame, key | value] -> [* | frame]\n"
|
||||
" ct-control-push Control [* | frame] -> [*]\n"
|
||||
" ct-control-pop Control [*] -> [* | frame]\n"
|
||||
" ct-control-peek Control [*] -> [* | frame]\n"
|
||||
" ct-control-depth Control [*] -> [* | n]\n"
|
||||
" ct-control-add-close-op Control [*, frame, op | data] -> [* | frame]\n"
|
||||
" ct-new-label Control [* | prefix] -> [* | label]\n"
|
||||
" ct-emit-op Control [*, op | data] -> [*]\n"
|
||||
" ct-last-token-line Control [*] -> [* | line]\n"
|
||||
" ct-register-block-opener Control [* | name] -> [*]\n"
|
||||
" ct-unregister-block-opener Control [* | name] -> [*]\n"
|
||||
" ct-register-control-override Control [* | name] -> [*]\n"
|
||||
" ct-unregister-control-override Control [* | name] -> [*]\n"
|
||||
" set-token-hook Hook [* | name] -> [*]\n"
|
||||
" clear-token-hook Hook [*] -> [*]\n"
|
||||
" prelude-clear Assembly [*] -> [*]\n"
|
||||
|
||||
@@ -39,8 +39,8 @@ word arr_cap 8 + @ end
|
||||
#arr_data [* | arr] -> [* | ptr]
|
||||
word arr_data 16 + @ end
|
||||
|
||||
#arr_free [* | arr] -> [*]
|
||||
word arr_free
|
||||
#dyn_arr_free [* | arr] -> [*]
|
||||
word dyn_arr_free
|
||||
dup arr_cap 8 * 24 + free
|
||||
end
|
||||
|
||||
@@ -81,7 +81,7 @@ word arr_reserve
|
||||
arr_copy_elements
|
||||
|
||||
# Free old and return new
|
||||
swap arr_free
|
||||
swap dyn_arr_free
|
||||
nip
|
||||
end
|
||||
end
|
||||
@@ -116,15 +116,15 @@ word arr_pop
|
||||
end
|
||||
end
|
||||
|
||||
#arr_get [*, arr | i] -> [* | x]
|
||||
#dyn_arr_get [*, arr | i] -> [* | x]
|
||||
# Get element at index i
|
||||
word arr_get
|
||||
word dyn_arr_get
|
||||
swap arr_data swap 8 * + @
|
||||
end
|
||||
|
||||
#arr_set [*, arr, x | i] -> [*]
|
||||
#dyn_arr_set [*, arr, x | i] -> [*]
|
||||
# Set element at index i to x
|
||||
word arr_set
|
||||
word dyn_arr_set
|
||||
rot arr_data swap 8 * + swap !
|
||||
end
|
||||
|
||||
@@ -149,21 +149,21 @@ word arr_item_ptr
|
||||
swap 8 * swap 8 + +
|
||||
end
|
||||
|
||||
#arr_get_static [*, arr | i] -> [* | x]
|
||||
#arr_get [*, arr | i] -> [* | x]
|
||||
# Get element from built-in static array
|
||||
word arr_get_static
|
||||
word arr_get
|
||||
swap arr_item_ptr @
|
||||
end
|
||||
|
||||
#arr_set_static [*, arr, x | i] -> [*]
|
||||
#arr_set [*, arr, x | i] -> [*]
|
||||
# Set element in built-in static array
|
||||
word arr_set_static
|
||||
word arr_set
|
||||
rot arr_item_ptr swap !
|
||||
end
|
||||
|
||||
#arr_static_free [* | arr] -> [*]
|
||||
#arr_free [* | arr] -> [*]
|
||||
# Free built-in static array allocation produced by list literals.
|
||||
word arr_static_free
|
||||
word arr_free
|
||||
dup @ 1 + 8 * free
|
||||
end
|
||||
|
||||
|
||||
137
stdlib/control.sl
Normal file
137
stdlib/control.sl
Normal file
@@ -0,0 +1,137 @@
|
||||
# Optional control-structure overrides for L2 parser defaults.
|
||||
# Import this file when you want custom compile-time implementations of
|
||||
# if/else/for/while/do instead of the built-in Python parser behavior.
|
||||
|
||||
word ct-if-open
|
||||
"if_false" ct-new-label
|
||||
dup "branch_zero" swap ct-emit-op
|
||||
"if" ct-control-frame-new
|
||||
swap "false" swap ct-control-set
|
||||
nil "end" swap ct-control-set
|
||||
dup "false" ct-control-get "label" swap ct-control-add-close-op
|
||||
ct-control-push
|
||||
end
|
||||
compile-only
|
||||
|
||||
word ct-if-open-with-end
|
||||
"if_false" ct-new-label
|
||||
dup "branch_zero" swap ct-emit-op
|
||||
"if" ct-control-frame-new
|
||||
swap "false" swap ct-control-set
|
||||
swap "end" swap ct-control-set
|
||||
dup "false" ct-control-get "label" swap ct-control-add-close-op
|
||||
dup "end" ct-control-get "label" swap ct-control-add-close-op
|
||||
ct-control-push
|
||||
end
|
||||
compile-only
|
||||
|
||||
word if-base ct-if-open end
|
||||
immediate
|
||||
compile-only
|
||||
|
||||
word if
|
||||
ct-control-depth 0 > if-base
|
||||
ct-control-peek
|
||||
dup "type" ct-control-get "else" string= if-base
|
||||
dup "line" ct-control-get ct-last-token-line == if-base
|
||||
drop
|
||||
ct-control-pop >r
|
||||
r@ "end" ct-control-get dup nil? if-base
|
||||
drop "if_end" ct-new-label
|
||||
end
|
||||
ct-if-open-with-end
|
||||
r> drop
|
||||
exit
|
||||
end
|
||||
end
|
||||
drop
|
||||
end
|
||||
ct-if-open
|
||||
end
|
||||
immediate
|
||||
compile-only
|
||||
|
||||
word else
|
||||
ct-control-pop >r
|
||||
r@ "end" ct-control-get dup nil? if-base
|
||||
drop "if_end" ct-new-label
|
||||
end
|
||||
dup "jump" swap ct-emit-op
|
||||
r@ "false" ct-control-get "label" swap ct-emit-op
|
||||
"else" ct-control-frame-new
|
||||
swap "end" swap ct-control-set
|
||||
dup "end" ct-control-get "label" swap ct-control-add-close-op
|
||||
ct-control-push
|
||||
r> drop
|
||||
end
|
||||
immediate
|
||||
compile-only
|
||||
|
||||
word for
|
||||
"for_loop" ct-new-label
|
||||
"for_end" ct-new-label
|
||||
map-new
|
||||
"loop" 3 pick map-set
|
||||
"end" 2 pick map-set
|
||||
"for_begin" swap ct-emit-op
|
||||
"for" ct-control-frame-new
|
||||
swap "end" swap ct-control-set
|
||||
swap "loop" swap ct-control-set
|
||||
dup "end" ct-control-get >r
|
||||
dup "loop" ct-control-get >r
|
||||
map-new
|
||||
"loop" r> map-set
|
||||
"end" r> map-set
|
||||
"for_end" swap ct-control-add-close-op
|
||||
ct-control-push
|
||||
end
|
||||
immediate
|
||||
compile-only
|
||||
|
||||
word while
|
||||
"begin" ct-new-label
|
||||
"end" ct-new-label
|
||||
over "label" swap ct-emit-op
|
||||
"while_open" ct-control-frame-new
|
||||
swap "end" swap ct-control-set
|
||||
swap "begin" swap ct-control-set
|
||||
ct-control-push
|
||||
end
|
||||
immediate
|
||||
compile-only
|
||||
|
||||
word do
|
||||
ct-control-pop >r
|
||||
r@ "end" ct-control-get "branch_zero" swap ct-emit-op
|
||||
"while" ct-control-frame-new
|
||||
r@ "begin" ct-control-get "begin" swap ct-control-set
|
||||
r@ "end" ct-control-get "end" swap ct-control-set
|
||||
dup "begin" ct-control-get "jump" swap ct-control-add-close-op
|
||||
dup "end" ct-control-get "label" swap ct-control-add-close-op
|
||||
r> drop
|
||||
ct-control-push
|
||||
end
|
||||
immediate
|
||||
compile-only
|
||||
|
||||
word block-opener
|
||||
next-token token-lexeme ct-register-block-opener
|
||||
end
|
||||
immediate
|
||||
compile-only
|
||||
|
||||
word control-override
|
||||
next-token token-lexeme ct-register-control-override
|
||||
end
|
||||
immediate
|
||||
compile-only
|
||||
|
||||
block-opener if
|
||||
block-opener for
|
||||
block-opener while
|
||||
|
||||
control-override if
|
||||
control-override else
|
||||
control-override for
|
||||
control-override while
|
||||
control-override do
|
||||
@@ -442,6 +442,14 @@
|
||||
}
|
||||
;
|
||||
|
||||
:asm rswap {
|
||||
mov rax, [r13] ; get top
|
||||
mov rbx, [r13 + 8] ; get second
|
||||
mov [r13], rbx ; swap
|
||||
mov [r13 + 8], rax
|
||||
}
|
||||
;
|
||||
|
||||
#pick [* | n] -> [* | x]
|
||||
:asm pick {
|
||||
mov rcx, [r12] ; get index
|
||||
|
||||
20278
stdlib/linux.sl
20278
stdlib/linux.sl
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,7 @@ word memcpy
|
||||
r> dup -rot - swap
|
||||
end
|
||||
|
||||
#memset [*, value, len | addr] -> [*]
|
||||
#memset [*, addr, len | value] -> [*]
|
||||
word memset
|
||||
swap
|
||||
0 swap for
|
||||
@@ -51,13 +51,29 @@ word memset
|
||||
2drop drop
|
||||
end
|
||||
|
||||
#memdump [*, len | addr] -> [* | addr]
|
||||
# memset_bytes [*, addr, len | value] -> [*]
|
||||
word memset_bytes
|
||||
swap
|
||||
0 swap for
|
||||
-rot swap 2 pick + 2dup swap c! 1 + -rot swap
|
||||
end
|
||||
2drop drop
|
||||
end
|
||||
|
||||
#memdump [*, addr | len] -> [* | addr]
|
||||
word memdump
|
||||
for
|
||||
dup @ puti cr 8 +
|
||||
end
|
||||
end
|
||||
|
||||
#memdump_bytes [*, addr | len] -> [* | addr]
|
||||
word memdump_bytes
|
||||
for
|
||||
dup c@ puti cr 1 +
|
||||
end
|
||||
end
|
||||
|
||||
#realloc [*, addr, old_len | new_len] -> [* | new_addr]
|
||||
word realloc
|
||||
2 pick swap alloc
|
||||
|
||||
117
stdlib/utils.sl
117
stdlib/utils.sl
@@ -1,7 +1,16 @@
|
||||
|
||||
#strcmp [*, addr, len, addr | len] -> [*, addr, len, addr, len | bool]
|
||||
#strcmp [*, addr, len, addr | len] -> [* | bool]
|
||||
word strcmp
|
||||
3 pick 2 pick @ swap @ ==
|
||||
>r nip r> for
|
||||
2dup c@ swap c@ != if drop drop 0 rdrop ret end
|
||||
1 + swap 1 +
|
||||
end
|
||||
drop drop 1
|
||||
end
|
||||
|
||||
# strdup [*, addr | len] -> [*, addr, len, addr1 | len1]
|
||||
word strdup
|
||||
dup alloc 2 pick 2 pick memcpy
|
||||
end
|
||||
|
||||
#strconcat [*, addr, len, addr | len] -> [*, addr | len]
|
||||
@@ -378,3 +387,107 @@ word format
|
||||
drop # drop counter (0)
|
||||
end
|
||||
end
|
||||
|
||||
# rotate N elements of the top of the stack
|
||||
# nrot [*, x1 ... xN - 1 | xN] -> [*, xN, xN - 1 ... x2 | x1]
|
||||
word nrot
|
||||
dup 1 + 1 swap for
|
||||
dup pick swap 2 +
|
||||
end
|
||||
1 - 2 / pick
|
||||
dup for
|
||||
swap >r rswap
|
||||
end
|
||||
1 + for
|
||||
nip
|
||||
end
|
||||
for
|
||||
rswap r>
|
||||
end
|
||||
end
|
||||
|
||||
# convert a string to a sequence of ascii codes of its characters and push the codes on to the stack,
|
||||
# Warning! the sequence is reversed so the ascii code of the last character ends up first on the stack
|
||||
# toascii [*, addr | LEN] -> [*, x, x1 ... xLEN - 1 | xLEN + 1]
|
||||
word toascii
|
||||
0 swap
|
||||
for
|
||||
2dup + c@
|
||||
-rot
|
||||
1 +
|
||||
end
|
||||
2drop
|
||||
end
|
||||
|
||||
# rm_zero_len_str [*, addr0, len0 ... addrN, lenN | N] -> [*, addrX, lenX ... addrY, lenY | Z]
|
||||
word rm_zero_len_str
|
||||
dup for
|
||||
swap dup 0 == if
|
||||
drop nip 1 -
|
||||
else
|
||||
>r rswap swap >r rswap
|
||||
end
|
||||
end
|
||||
|
||||
dup 2 * for
|
||||
rswap r> swap
|
||||
end
|
||||
end
|
||||
|
||||
# emit_strs [*, addr | len] -> [*, addr0, len0 ... addrN | lenN]
|
||||
# given an addr and len emits pairs (addr, len) of strings in the given memopry region
|
||||
word emit_strs
|
||||
0 >r
|
||||
>r
|
||||
while r@ 0 > do
|
||||
dup strlen dup r> swap - >r
|
||||
over over + rswap r> 1 + >r rswap
|
||||
while dup c@ 0 == do 1 + r> 1 - >r end
|
||||
end
|
||||
drop rdrop
|
||||
end
|
||||
|
||||
# splitby_str [*, addr, len, addr1, len1] -> [*, addr0, len0 ... addrN, lenN | N]
|
||||
# splits a string by another string and emmits a sequence of the new (addr, len) pairs on to the stack as well as the number of strings the oprtation resulted in.
|
||||
word splitby_str
|
||||
2 pick for
|
||||
3 pick 0 2 pick 4 pick swap
|
||||
strcmp 1 == if 3 pick over 0 memset_bytes end
|
||||
>r >r swap 1 + swap r> r>
|
||||
end
|
||||
2drop 2dup - >r nip r> swap emit_strs r>
|
||||
rm_zero_len_str
|
||||
end
|
||||
|
||||
# splitby [*, addr, len, addr1 | len1] -> [*, addr0, len0 ... addrN, lenN | N]
|
||||
# split a string by another string, delegates to either splitby_char or splitby_str based on the length of the delimiter.
|
||||
word splitby
|
||||
dup 1 == if
|
||||
splitby_char
|
||||
else
|
||||
splitby_str
|
||||
end
|
||||
end
|
||||
|
||||
# splitby_char [*, addr, len, addr1 | len] -> [*, addr1, len1 ... addrN, lenN | N]
|
||||
# split a string by a given character, the resulting (addr, len) pairs are pushed on to the stack followed by the number of the pushed strings.
|
||||
word splitby_char
|
||||
2 pick >r
|
||||
>r >r 2dup r> r> 2swap 2dup
|
||||
>r >r toascii 1 rpick nrot r> r>
|
||||
|
||||
dup 3 + pick c@
|
||||
|
||||
swap for
|
||||
dup
|
||||
3 pick == if over 0 c! end
|
||||
swap 1 + swap >r nip r>
|
||||
end
|
||||
|
||||
2drop 2drop drop
|
||||
|
||||
r>
|
||||
emit_strs
|
||||
r>
|
||||
rm_zero_len_str
|
||||
end
|
||||
|
||||
39
test.py
39
test.py
@@ -224,6 +224,7 @@ class TestCase:
|
||||
expected_stdout: Path
|
||||
expected_stderr: Path
|
||||
compile_expected: Path
|
||||
asm_forbid: Path
|
||||
stdin_path: Path
|
||||
args_path: Path
|
||||
meta_path: Path
|
||||
@@ -324,6 +325,7 @@ class TestRunner:
|
||||
expected_stdout=source.with_suffix(".expected"),
|
||||
expected_stderr=source.with_suffix(".stderr"),
|
||||
compile_expected=source.with_suffix(".compile.expected"),
|
||||
asm_forbid=source.with_suffix(".asm.forbid"),
|
||||
stdin_path=source.with_suffix(".stdin"),
|
||||
args_path=source.with_suffix(".args"),
|
||||
meta_path=meta_path,
|
||||
@@ -391,6 +393,10 @@ class TestRunner:
|
||||
return CaseResult(case, compile_status, "compile", compile_note, compile_details, duration)
|
||||
if compile_status == "updated" and compile_note:
|
||||
updated_notes.append(compile_note)
|
||||
asm_status, asm_note, asm_details = self._check_asm_forbidden_patterns(case)
|
||||
if asm_status == "failed":
|
||||
duration = time.perf_counter() - start
|
||||
return CaseResult(case, asm_status, "asm", asm_note, asm_details, duration)
|
||||
if case.config.compile_only:
|
||||
duration = time.perf_counter() - start
|
||||
if updated_notes:
|
||||
@@ -633,6 +639,39 @@ class TestRunner:
|
||||
parts.append(proc.stderr)
|
||||
return "".join(parts)
|
||||
|
||||
def _check_asm_forbidden_patterns(self, case: TestCase) -> Tuple[str, str, Optional[str]]:
|
||||
"""Fail test if generated asm contains forbidden markers listed in *.asm.forbid."""
|
||||
if not case.asm_forbid.exists():
|
||||
return "passed", "", None
|
||||
|
||||
asm_path = case.build_dir / f"{case.binary_stub}.asm"
|
||||
if not asm_path.exists():
|
||||
return "failed", f"missing generated asm file {asm_path.name}", None
|
||||
|
||||
asm_text = asm_path.read_text(encoding="utf-8")
|
||||
patterns: List[str] = []
|
||||
for raw in case.asm_forbid.read_text(encoding="utf-8").splitlines():
|
||||
line = raw.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
patterns.append(line)
|
||||
|
||||
hits: List[str] = []
|
||||
for pattern in patterns:
|
||||
if pattern.startswith("re:"):
|
||||
expr = pattern[3:]
|
||||
if re.search(expr, asm_text, re.MULTILINE):
|
||||
hits.append(pattern)
|
||||
continue
|
||||
if pattern in asm_text:
|
||||
hits.append(pattern)
|
||||
|
||||
if not hits:
|
||||
return "passed", "", None
|
||||
|
||||
detail = "forbidden asm pattern(s) matched:\n" + "\n".join(f"- {p}" for p in hits)
|
||||
return "failed", "assembly contains forbidden patterns", detail
|
||||
|
||||
def _compare_nob_test_stdout(
|
||||
self,
|
||||
case: TestCase,
|
||||
|
||||
@@ -19,22 +19,22 @@ word main
|
||||
dup arr_len puti cr
|
||||
dup arr_cap puti cr
|
||||
|
||||
# arr_get
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
# dyn_arr_get
|
||||
dup 0 dyn_arr_get puti cr
|
||||
dup 1 dyn_arr_get puti cr
|
||||
dup 2 dyn_arr_get puti cr
|
||||
|
||||
# arr_set
|
||||
dup 99 1 arr_set
|
||||
dup 1 arr_get puti cr
|
||||
# dyn_arr_set
|
||||
dup 99 1 dyn_arr_set
|
||||
dup 1 dyn_arr_get puti cr
|
||||
|
||||
# arr_reserve (with len > 0 so element copy path is exercised)
|
||||
dup 8 arr_reserve
|
||||
dup arr_cap puti cr
|
||||
dup arr_len puti cr
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
dup 0 dyn_arr_get puti cr
|
||||
dup 1 dyn_arr_get puti cr
|
||||
dup 2 dyn_arr_get puti cr
|
||||
|
||||
# arr_pop (including empty pop)
|
||||
arr_pop puti cr
|
||||
@@ -43,16 +43,16 @@ word main
|
||||
arr_pop puti cr
|
||||
dup arr_len puti cr
|
||||
|
||||
arr_free
|
||||
dyn_arr_free
|
||||
|
||||
# arr_to_dyn (convert std list to dynamic array)
|
||||
[ 7 8 9 ] dup arr_to_dyn
|
||||
dup arr_len puti cr
|
||||
dup arr_cap puti cr
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
arr_free
|
||||
dup 0 dyn_arr_get puti cr
|
||||
dup 1 dyn_arr_get puti cr
|
||||
dup 2 dyn_arr_get puti cr
|
||||
dyn_arr_free
|
||||
|
||||
# free list allocation: bytes = (len + 1) * 8
|
||||
dup @ 1 + 8 * free
|
||||
@@ -64,21 +64,21 @@ word main
|
||||
dup 2 arr_push
|
||||
|
||||
dup dyn_arr_sorted
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
arr_free
|
||||
dup 0 dyn_arr_get puti cr
|
||||
dup 1 dyn_arr_get puti cr
|
||||
dup 2 dyn_arr_get puti cr
|
||||
dyn_arr_free
|
||||
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
dup 0 dyn_arr_get puti cr
|
||||
dup 1 dyn_arr_get puti cr
|
||||
dup 2 dyn_arr_get puti cr
|
||||
|
||||
# dyn_arr_sort (alias) sorts in place
|
||||
dyn_arr_sort
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
arr_free
|
||||
dup 0 dyn_arr_get puti cr
|
||||
dup 1 dyn_arr_get puti cr
|
||||
dup 2 dyn_arr_get puti cr
|
||||
dyn_arr_free
|
||||
|
||||
# dyn_arr_sorted (alias) returns a sorted copy
|
||||
5 arr_new
|
||||
@@ -87,13 +87,13 @@ word main
|
||||
dup 6 arr_push
|
||||
|
||||
dup dyn_arr_sorted
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
arr_free
|
||||
dup 0 dyn_arr_get puti cr
|
||||
dup 1 dyn_arr_get puti cr
|
||||
dup 2 dyn_arr_get puti cr
|
||||
dyn_arr_free
|
||||
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
arr_free
|
||||
dup 0 dyn_arr_get puti cr
|
||||
dup 1 dyn_arr_get puti cr
|
||||
dup 2 dyn_arr_get puti cr
|
||||
dyn_arr_free
|
||||
end
|
||||
|
||||
@@ -4,22 +4,22 @@ import ../stdlib/arr.sl
|
||||
|
||||
word main
|
||||
[ 4 1 3 2 ] dup arr_sort
|
||||
dup 0 arr_get_static puti cr
|
||||
dup 1 arr_get_static puti cr
|
||||
dup 2 arr_get_static puti cr
|
||||
dup 3 arr_get_static puti cr
|
||||
arr_static_free
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
dup 3 arr_get puti cr
|
||||
arr_free
|
||||
|
||||
[ 9 5 7 ] dup arr_sorted
|
||||
dup 0 arr_get_static puti cr
|
||||
dup 1 arr_get_static puti cr
|
||||
dup 2 arr_get_static puti cr
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
|
||||
swap
|
||||
dup 0 arr_get_static puti cr
|
||||
dup 1 arr_get_static puti cr
|
||||
dup 2 arr_get_static puti cr
|
||||
dup 0 arr_get puti cr
|
||||
dup 1 arr_get puti cr
|
||||
dup 2 arr_get puti cr
|
||||
|
||||
arr_static_free
|
||||
arr_static_free
|
||||
arr_free
|
||||
arr_free
|
||||
end
|
||||
|
||||
5
tests/custom_control_structures.expected
Normal file
5
tests/custom_control_structures.expected
Normal file
@@ -0,0 +1,5 @@
|
||||
11
|
||||
22
|
||||
33
|
||||
5
|
||||
3
|
||||
36
tests/custom_control_structures.sl
Normal file
36
tests/custom_control_structures.sl
Normal file
@@ -0,0 +1,36 @@
|
||||
import stdlib/stdlib.sl
|
||||
import stdlib/control.sl
|
||||
|
||||
word main
|
||||
1 if
|
||||
11 puti cr
|
||||
else
|
||||
99 puti cr
|
||||
end
|
||||
|
||||
0 if
|
||||
99 puti cr
|
||||
else
|
||||
22 puti cr
|
||||
end
|
||||
|
||||
0 if
|
||||
500 puti cr
|
||||
else 1 if
|
||||
33 puti cr
|
||||
else
|
||||
44 puti cr
|
||||
end
|
||||
|
||||
0
|
||||
5 for
|
||||
1 +
|
||||
end
|
||||
puti cr
|
||||
|
||||
0
|
||||
while dup 3 < do
|
||||
1 +
|
||||
end
|
||||
puti cr
|
||||
end
|
||||
1
tests/i_non_compile_time.compile.expected
Normal file
1
tests/i_non_compile_time.compile.expected
Normal file
@@ -0,0 +1 @@
|
||||
[error] word 'i' is compile-time only and cannot be used at runtime while emitting 'main'
|
||||
4
tests/i_non_compile_time.meta.json
Normal file
4
tests/i_non_compile_time.meta.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"expect_compile_error": true,
|
||||
"description": "'i' is compile-time only and rejected in runtime code"
|
||||
}
|
||||
8
tests/i_non_compile_time.sl
Normal file
8
tests/i_non_compile_time.sl
Normal file
@@ -0,0 +1,8 @@
|
||||
import stdlib/stdlib.sl
|
||||
|
||||
word main
|
||||
0
|
||||
3 for
|
||||
i puti cr
|
||||
end
|
||||
end
|
||||
@@ -5,13 +5,13 @@ import ../stdlib/arr.sl
|
||||
# Get element from static array, preserving the array pointer
|
||||
# [*, arr | i] -> [*, arr | value]
|
||||
word aget
|
||||
over swap arr_get_static
|
||||
over swap arr_get
|
||||
end
|
||||
|
||||
# Set element in static array, preserving the array pointer
|
||||
# [*, arr, value | i] -> [* | arr]
|
||||
word aset
|
||||
rot dup >r -rot arr_set_static r>
|
||||
rot dup >r -rot arr_set r>
|
||||
end
|
||||
|
||||
# Swap elements at indices i and j in a static array
|
||||
@@ -86,7 +86,7 @@ end
|
||||
word print_arr
|
||||
dup @ 0
|
||||
while 2dup > do
|
||||
2 pick over arr_get_static puti cr
|
||||
2 pick over arr_get puti cr
|
||||
1 +
|
||||
end
|
||||
2drop drop
|
||||
|
||||
1
tests/ret_test.expected
Normal file
1
tests/ret_test.expected
Normal file
@@ -0,0 +1 @@
|
||||
g
|
||||
11
tests/ret_test.sl
Normal file
11
tests/ret_test.sl
Normal file
@@ -0,0 +1,11 @@
|
||||
import stdlib.sl
|
||||
|
||||
word g
|
||||
"g" puts
|
||||
ret
|
||||
"g" puts
|
||||
end
|
||||
|
||||
word main
|
||||
g
|
||||
end
|
||||
@@ -1,4 +1,13 @@
|
||||
1
|
||||
g
|
||||
g
|
||||
0
|
||||
hello world hello world hello world hello world hello world
|
||||
hello
|
||||
hello
|
||||
hello
|
||||
hello
|
||||
o
|
||||
d he
|
||||
o wor
|
||||
d he
|
||||
o wor
|
||||
he
|
||||
|
||||
15
tests/str.sl
15
tests/str.sl
@@ -1,15 +1,24 @@
|
||||
import stdlib.sl
|
||||
|
||||
word main
|
||||
"g" "g"
|
||||
"ggggggggh" "ggggggggh"
|
||||
strcmp
|
||||
puti cr
|
||||
|
||||
"ggggggggh" "ggggggggd"
|
||||
strcmp
|
||||
puti cr
|
||||
puts
|
||||
puts
|
||||
|
||||
"hello world hello world hello " "world hello world hello world"
|
||||
strconcat
|
||||
2dup
|
||||
puts
|
||||
free
|
||||
|
||||
"hello world hello" "world" splitby
|
||||
for puts end
|
||||
"hello world hello world" "world" splitby
|
||||
for puts end
|
||||
"hello world hello world hello" "l" splitby
|
||||
for puts end
|
||||
end
|
||||
|
||||
4
tests/syscall_write.asm.forbid
Normal file
4
tests/syscall_write.asm.forbid
Normal file
@@ -0,0 +1,4 @@
|
||||
# Ensure known-argc syscall lowering avoids generic dynamic syscall boilerplate.
|
||||
clamp arg count to [0, 6]
|
||||
re:syscall_\d+_count_
|
||||
re:syscall_\d+_skip_
|
||||
@@ -6,7 +6,6 @@ word main
|
||||
1
|
||||
"hello"
|
||||
syscall.write
|
||||
syscall
|
||||
#drop
|
||||
|
||||
1
|
||||
|
||||
4
tests/with_else_if_shorthand.expected
Normal file
4
tests/with_else_if_shorthand.expected
Normal file
@@ -0,0 +1,4 @@
|
||||
neg
|
||||
zero
|
||||
small
|
||||
big
|
||||
23
tests/with_else_if_shorthand.sl
Normal file
23
tests/with_else_if_shorthand.sl
Normal file
@@ -0,0 +1,23 @@
|
||||
import stdlib/stdlib.sl
|
||||
|
||||
word classify
|
||||
with n in
|
||||
n 0 < if
|
||||
"neg" puts
|
||||
else n 0 == if
|
||||
"zero" puts
|
||||
else n 10 < if
|
||||
"small" puts
|
||||
else
|
||||
"big" puts
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
word main
|
||||
-1 classify
|
||||
0 classify
|
||||
3 classify
|
||||
20 classify
|
||||
0
|
||||
end
|
||||
220
tools/gen_linux_sl.py
Normal file
220
tools/gen_linux_sl.py
Normal file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate stdlib/linux.sl from syscall_64.tbl metadata."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SRC = ROOT / "syscall_64.tbl"
|
||||
DST = ROOT / "stdlib" / "linux.sl"
|
||||
|
||||
|
||||
def _sanitize_alias(alias: str) -> str:
|
||||
name = alias.strip()
|
||||
if not name:
|
||||
return ""
|
||||
if name.startswith("__x64_sys_"):
|
||||
name = name[len("__x64_sys_") :]
|
||||
elif name.startswith("sys_"):
|
||||
name = name[len("sys_") :]
|
||||
name = re.sub(r"[^A-Za-z0-9_]", "_", name)
|
||||
name = re.sub(r"_+", "_", name).strip("_")
|
||||
if not name:
|
||||
return ""
|
||||
if name[0].isdigit():
|
||||
name = "n_" + name
|
||||
return name
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SyscallEntry:
|
||||
argc: int
|
||||
num: int
|
||||
aliases: tuple[str, ...]
|
||||
|
||||
|
||||
def _parse_table(path: Path) -> list[SyscallEntry]:
|
||||
entries: list[SyscallEntry] = []
|
||||
for raw in path.read_text(encoding="utf-8").splitlines():
|
||||
line = raw.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = line.split(maxsplit=2)
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
try:
|
||||
argc = int(parts[0])
|
||||
num = int(parts[1])
|
||||
except ValueError:
|
||||
continue
|
||||
aliases = tuple(a for a in parts[2].split("/") if a)
|
||||
if not aliases:
|
||||
continue
|
||||
entries.append(SyscallEntry(argc=argc, num=num, aliases=aliases))
|
||||
return entries
|
||||
|
||||
|
||||
def _emit_header(lines: list[str]) -> None:
|
||||
lines.extend(
|
||||
[
|
||||
"# Autogenerated from syscall_64.tbl",
|
||||
"# Generated by tools/gen_linux_sl.py",
|
||||
"# Linux syscall constants + convenience wrappers for L2",
|
||||
"",
|
||||
"# File descriptor constants",
|
||||
"macro FD_STDIN 0 0 ;",
|
||||
"macro FD_STDOUT 0 1 ;",
|
||||
"macro FD_STDERR 0 2 ;",
|
||||
"",
|
||||
"# Common open(2) flags",
|
||||
"macro O_RDONLY 0 0 ;",
|
||||
"macro O_WRONLY 0 1 ;",
|
||||
"macro O_RDWR 0 2 ;",
|
||||
"macro O_CREAT 0 64 ;",
|
||||
"macro O_EXCL 0 128 ;",
|
||||
"macro O_NOCTTY 0 256 ;",
|
||||
"macro O_TRUNC 0 512 ;",
|
||||
"macro O_APPEND 0 1024 ;",
|
||||
"macro O_NONBLOCK 0 2048 ;",
|
||||
"macro O_CLOEXEC 0 524288 ;",
|
||||
"",
|
||||
"# lseek(2)",
|
||||
"macro SEEK_SET 0 0 ;",
|
||||
"macro SEEK_CUR 0 1 ;",
|
||||
"macro SEEK_END 0 2 ;",
|
||||
"",
|
||||
"# mmap(2)",
|
||||
"macro PROT_NONE 0 0 ;",
|
||||
"macro PROT_READ 0 1 ;",
|
||||
"macro PROT_WRITE 0 2 ;",
|
||||
"macro PROT_EXEC 0 4 ;",
|
||||
"macro MAP_PRIVATE 0 2 ;",
|
||||
"macro MAP_ANONYMOUS 0 32 ;",
|
||||
"macro MAP_SHARED 0 1 ;",
|
||||
"",
|
||||
"# Socket constants",
|
||||
"macro AF_UNIX 0 1 ;",
|
||||
"macro AF_INET 0 2 ;",
|
||||
"macro AF_INET6 0 10 ;",
|
||||
"macro SOCK_STREAM 0 1 ;",
|
||||
"macro SOCK_DGRAM 0 2 ;",
|
||||
"macro SOCK_NONBLOCK 0 2048 ;",
|
||||
"macro SOCK_CLOEXEC 0 524288 ;",
|
||||
"",
|
||||
"macro INADDR_ANY 0 0 ;",
|
||||
"",
|
||||
"# Generic syscall helpers with explicit argument count",
|
||||
"# Stack form:",
|
||||
"# syscall -> <argN> ... <arg0> <argc> <nr> syscall",
|
||||
"# syscallN -> <argN-1> ... <arg0> <nr> syscallN",
|
||||
"",
|
||||
"# swap impl is provided so this can be used without stdlib",
|
||||
"# ___linux_swap [*, x1 | x2] -> [*, x2 | x1]",
|
||||
":asm ___linux_swap {",
|
||||
" mov rax, [r12]",
|
||||
" mov rbx, [r12 + 8]",
|
||||
" mov [r12], rbx",
|
||||
" mov [r12 + 8], rax",
|
||||
"}",
|
||||
";",
|
||||
"",
|
||||
"macro syscall0 0",
|
||||
" 0",
|
||||
" ___linux_swap",
|
||||
" syscall",
|
||||
";",
|
||||
"",
|
||||
"macro syscall1 0",
|
||||
" 1",
|
||||
" ___linux_swap",
|
||||
" syscall",
|
||||
";",
|
||||
"",
|
||||
"macro syscall2 0",
|
||||
" 2",
|
||||
" ___linux_swap",
|
||||
" syscall",
|
||||
";",
|
||||
"",
|
||||
"macro syscall3 0",
|
||||
" 3",
|
||||
" ___linux_swap",
|
||||
" syscall",
|
||||
";",
|
||||
"",
|
||||
"macro syscall4 0",
|
||||
" 4",
|
||||
" ___linux_swap",
|
||||
" syscall",
|
||||
";",
|
||||
"",
|
||||
"macro syscall5 0",
|
||||
" 5",
|
||||
" ___linux_swap",
|
||||
" syscall",
|
||||
";",
|
||||
"",
|
||||
"macro syscall6 0",
|
||||
" 6",
|
||||
" ___linux_swap",
|
||||
" syscall",
|
||||
";",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _emit_entry(lines: list[str], alias: str, argc: int, num: int) -> None:
|
||||
safe_argc = max(0, min(argc, 6))
|
||||
lines.extend(
|
||||
[
|
||||
f"macro syscall.{alias} 0",
|
||||
f" {num}",
|
||||
f" syscall{safe_argc}",
|
||||
";",
|
||||
"",
|
||||
f"macro syscall.{alias}.num 0",
|
||||
f" {num}",
|
||||
";",
|
||||
"",
|
||||
f"macro syscall.{alias}.argc 0",
|
||||
f" {safe_argc}",
|
||||
";",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def generate() -> str:
|
||||
entries = _parse_table(SRC)
|
||||
|
||||
lines: list[str] = []
|
||||
_emit_header(lines)
|
||||
|
||||
emitted: set[str] = set()
|
||||
for entry in sorted(entries, key=lambda e: (e.num, e.aliases[0])):
|
||||
for alias in entry.aliases:
|
||||
name = _sanitize_alias(alias)
|
||||
if not name:
|
||||
continue
|
||||
key = f"syscall.{name}"
|
||||
if key in emitted:
|
||||
continue
|
||||
_emit_entry(lines, name, entry.argc, entry.num)
|
||||
emitted.add(key)
|
||||
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
output = generate()
|
||||
DST.parent.mkdir(parents=True, exist_ok=True)
|
||||
DST.write_text(output, encoding="utf-8")
|
||||
print(f"wrote {DST}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user