This commit is contained in:
IgorCielniak
2025-12-11 20:07:10 +01:00
parent 2ea49d26f7
commit a33ae22573
22 changed files with 1855 additions and 278 deletions

Binary file not shown.

BIN
a.out Executable file

Binary file not shown.

5
a.sl Normal file
View File

@@ -0,0 +1,5 @@
import stdlib.sl
: main
"hello world" puts
;

View File

@@ -0,0 +1,289 @@
section .text
%define DSTK_BYTES 65536
%define RSTK_BYTES 65536
%define PRINT_BUF_BYTES 128
global _start
_start:
; initialize data/return stack pointers
lea r12, [rel dstack_top]
mov r15, r12
lea r13, [rel rstack_top]
call word_main
mov rax, 0
cmp r12, r15
je .no_exit_value
mov rax, [r12]
add r12, 8
.no_exit_value:
mov rdi, rax
mov rax, 60
syscall
word_puts:
mov rax, [r12]
add r12, 8
mov rbx, rax
mov r8, 0
cmp rbx, 0
jge puts_abs
neg rbx
mov r8, 1
puts_abs:
lea rsi, [rel print_buf_end]
mov rcx, 0
mov r10, 10
cmp rbx, 0
jne puts_digits
dec rsi
mov byte [rsi], '0'
inc rcx
jmp puts_sign
puts_digits:
puts_loop:
xor rdx, rdx
mov rax, rbx
div r10
add dl, '0'
dec rsi
mov [rsi], dl
inc rcx
mov rbx, rax
test rbx, rbx
jne puts_loop
puts_sign:
cmp r8, 0
je puts_finish_digits
dec rsi
mov byte [rsi], '-'
inc rcx
puts_finish_digits:
mov byte [rsi + rcx], 10
inc rcx
mov rax, 1
mov rdi, 1
mov rdx, rcx
mov r9, rsi
mov rsi, r9
syscall
ret
word_dup:
mov rax, [r12]
sub r12, 8
mov [r12], rax
ret
word_drop:
add r12, 8
ret
word_over:
mov rax, [r12 + 8]
sub r12, 8
mov [r12], rax
ret
word_swap:
mov rax, [r12]
mov rbx, [r12 + 8]
mov [r12], rbx
mov [r12 + 8], rax
ret
word__2b:
mov rax, [r12]
add r12, 8
add qword [r12], rax
ret
word__2d:
mov rax, [r12]
add r12, 8
sub qword [r12], rax
ret
word__2a:
mov rax, [r12]
add r12, 8
imul qword [r12]
mov [r12], rax
ret
word__2f:
mov rbx, [r12]
add r12, 8
mov rax, [r12]
cqo
idiv rbx
mov [r12], rax
ret
word__25:
mov rbx, [r12]
add r12, 8
mov rax, [r12]
cqo
idiv rbx
mov [r12], rdx
ret
word__3d_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
sete bl
mov [r12], rbx
ret
word__21_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setne bl
mov [r12], rbx
ret
word__3c:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setl bl
mov [r12], rbx
ret
word__3e:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setg bl
mov [r12], rbx
ret
word__3c_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setle bl
mov [r12], rbx
ret
word__3e_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setge bl
mov [r12], rbx
ret
word__40:
mov rax, [r12]
mov rax, [rax]
mov [r12], rax
ret
word__21:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
mov [rax], rbx
add r12, 8
ret
word_mmap:
mov r9, [r12]
add r12, 8
mov r8, [r12]
add r12, 8
mov r10, [r12]
add r12, 8
mov rdx, [r12]
add r12, 8
mov rsi, [r12]
add r12, 8
mov rdi, [r12]
mov rax, 9
syscall
mov [r12], rax
ret
word_munmap:
mov rsi, [r12]
add r12, 8
mov rdi, [r12]
mov rax, 11
syscall
mov [r12], rax
ret
word_exit:
mov rdi, [r12]
add r12, 8
mov rax, 60
syscall
ret
word__3er:
mov rax, [r12]
add r12, 8
sub r13, 8
mov [r13], rax
ret
word_r_3e:
mov rax, [r13]
add r13, 8
sub r12, 8
mov [r12], rax
ret
word_rdrop:
add r13, 8
ret
word_pick:
mov rcx, [r12]
add r12, 8
mov rax, [r12 + rcx * 8]
sub r12, 8
mov [r12], rax
ret
word_rpick:
mov rcx, [r12]
add r12, 8
mov rax, [r13 + rcx * 8]
sub r12, 8
mov [r12], rax
ret
word_main:
; push 2
sub r12, 8
mov qword [r12], 2
; push 40
sub r12, 8
mov qword [r12], 40
call word__2b
call word_puts
; push 1
sub r12, 8
mov qword [r12], 1
; push 2
sub r12, 8
mov qword [r12], 2
call word_foo
call word_puts
; push 0
sub r12, 8
mov qword [r12], 0
ret
word_foo:
call word__3er
call word__3er
; push 0
sub r12, 8
mov qword [r12], 0
call word_rpick
; push 1
sub r12, 8
mov qword [r12], 1
call word_rpick
call word__2b
call word_rdrop
call word_rdrop
ret
section .bss
align 16
dstack: resb DSTK_BYTES
dstack_top:
align 16
rstack: resb RSTK_BYTES
rstack_top:
align 16
print_buf: resb PRINT_BUF_BYTES
print_buf_end:

BIN
build/call_syntax_parens.o Normal file

Binary file not shown.

298
build/loops_and_cmp.asm Normal file
View File

@@ -0,0 +1,298 @@
section .text
%define DSTK_BYTES 65536
%define RSTK_BYTES 65536
%define PRINT_BUF_BYTES 128
global _start
_start:
; initialize data/return stack pointers
lea r12, [rel dstack_top]
mov r15, r12
lea r13, [rel rstack_top]
call word_main
mov rax, 0
cmp r12, r15
je .no_exit_value
mov rax, [r12]
add r12, 8
.no_exit_value:
mov rdi, rax
mov rax, 60
syscall
word_puts:
mov rax, [r12]
add r12, 8
mov rbx, rax
mov r8, 0
cmp rbx, 0
jge puts_abs
neg rbx
mov r8, 1
puts_abs:
lea rsi, [rel print_buf_end]
mov rcx, 0
mov r10, 10
cmp rbx, 0
jne puts_digits
dec rsi
mov byte [rsi], '0'
inc rcx
jmp puts_sign
puts_digits:
puts_loop:
xor rdx, rdx
mov rax, rbx
div r10
add dl, '0'
dec rsi
mov [rsi], dl
inc rcx
mov rbx, rax
test rbx, rbx
jne puts_loop
puts_sign:
cmp r8, 0
je puts_finish_digits
dec rsi
mov byte [rsi], '-'
inc rcx
puts_finish_digits:
mov byte [rsi + rcx], 10
inc rcx
mov rax, 1
mov rdi, 1
mov rdx, rcx
mov r9, rsi
mov rsi, r9
syscall
ret
word_dup:
mov rax, [r12]
sub r12, 8
mov [r12], rax
ret
word_drop:
add r12, 8
ret
word_over:
mov rax, [r12 + 8]
sub r12, 8
mov [r12], rax
ret
word_swap:
mov rax, [r12]
mov rbx, [r12 + 8]
mov [r12], rbx
mov [r12 + 8], rax
ret
word__2b:
mov rax, [r12]
add r12, 8
add qword [r12], rax
ret
word__2d:
mov rax, [r12]
add r12, 8
sub qword [r12], rax
ret
word__2a:
mov rax, [r12]
add r12, 8
imul qword [r12]
mov [r12], rax
ret
word__2f:
mov rbx, [r12]
add r12, 8
mov rax, [r12]
cqo
idiv rbx
mov [r12], rax
ret
word__25:
mov rbx, [r12]
add r12, 8
mov rax, [r12]
cqo
idiv rbx
mov [r12], rdx
ret
word__3d_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
sete bl
mov [r12], rbx
ret
word__21_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setne bl
mov [r12], rbx
ret
word__3c:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setl bl
mov [r12], rbx
ret
word__3e:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setg bl
mov [r12], rbx
ret
word__3c_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setle bl
mov [r12], rbx
ret
word__3e_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setge bl
mov [r12], rbx
ret
word__40:
mov rax, [r12]
mov rax, [rax]
mov [r12], rax
ret
word__21:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
mov [rax], rbx
add r12, 8
ret
word_mmap:
mov r9, [r12]
add r12, 8
mov r8, [r12]
add r12, 8
mov r10, [r12]
add r12, 8
mov rdx, [r12]
add r12, 8
mov rsi, [r12]
add r12, 8
mov rdi, [r12]
mov rax, 9
syscall
mov [r12], rax
ret
word_munmap:
mov rsi, [r12]
add r12, 8
mov rdi, [r12]
mov rax, 11
syscall
mov [r12], rax
ret
word_exit:
mov rdi, [r12]
add r12, 8
mov rax, 60
syscall
ret
word__3er:
mov rax, [r12]
add r12, 8
sub r13, 8
mov [r13], rax
ret
word_r_3e:
mov rax, [r13]
add r13, 8
sub r12, 8
mov [r12], rax
ret
word_rdrop:
add r13, 8
ret
word_pick:
mov rcx, [r12]
add r12, 8
mov rax, [r12 + rcx * 8]
sub r12, 8
mov [r12], rax
ret
word_rpick:
mov rcx, [r12]
add r12, 8
mov rax, [r13 + rcx * 8]
sub r12, 8
mov [r12], rax
ret
word_main:
; push 0
sub r12, 8
mov qword [r12], 0
; push 5
sub r12, 8
mov qword [r12], 5
mov rax, [r12]
add r12, 8
cmp rax, 0
jle L_for_end_1
sub r13, 8
mov [r13], rax
L_for_loop_0:
; push 1
sub r12, 8
mov qword [r12], 1
call word__2b
mov rax, [r13]
dec rax
mov [r13], rax
jg L_for_loop_0
add r13, 8
L_for_end_1:
call word_puts
; push 5
sub r12, 8
mov qword [r12], 5
; push 5
sub r12, 8
mov qword [r12], 5
call word__3d_3d
call word_puts
; push 5
sub r12, 8
mov qword [r12], 5
; push 4
sub r12, 8
mov qword [r12], 4
call word__3d_3d
call word_puts
; push 0
sub r12, 8
mov qword [r12], 0
ret
section .bss
align 16
dstack: resb DSTK_BYTES
dstack_top:
align 16
rstack: resb RSTK_BYTES
rstack_top:
align 16
print_buf: resb PRINT_BUF_BYTES
print_buf_end:

BIN
build/loops_and_cmp.o Normal file

Binary file not shown.

289
build/main.asm Normal file
View File

@@ -0,0 +1,289 @@
section .text
%define DSTK_BYTES 65536
%define RSTK_BYTES 65536
%define PRINT_BUF_BYTES 128
global _start
_start:
; initialize data/return stack pointers
lea r12, [rel dstack_top]
mov r15, r12
lea r13, [rel rstack_top]
call word_main
mov rax, 0
cmp r12, r15
je .no_exit_value
mov rax, [r12]
add r12, 8
.no_exit_value:
mov rdi, rax
mov rax, 60
syscall
word_puts:
mov rax, [r12]
add r12, 8
mov rbx, rax
mov r8, 0
cmp rbx, 0
jge puts_abs
neg rbx
mov r8, 1
puts_abs:
lea rsi, [rel print_buf_end]
mov rcx, 0
mov r10, 10
cmp rbx, 0
jne puts_digits
dec rsi
mov byte [rsi], '0'
inc rcx
jmp puts_sign
puts_digits:
puts_loop:
xor rdx, rdx
mov rax, rbx
div r10
add dl, '0'
dec rsi
mov [rsi], dl
inc rcx
mov rbx, rax
test rbx, rbx
jne puts_loop
puts_sign:
cmp r8, 0
je puts_finish_digits
dec rsi
mov byte [rsi], '-'
inc rcx
puts_finish_digits:
mov byte [rsi + rcx], 10
inc rcx
mov rax, 1
mov rdi, 1
mov rdx, rcx
mov r9, rsi
mov rsi, r9
syscall
ret
word_dup:
mov rax, [r12]
sub r12, 8
mov [r12], rax
ret
word_drop:
add r12, 8
ret
word_over:
mov rax, [r12 + 8]
sub r12, 8
mov [r12], rax
ret
word_swap:
mov rax, [r12]
mov rbx, [r12 + 8]
mov [r12], rbx
mov [r12 + 8], rax
ret
word__2b:
mov rax, [r12]
add r12, 8
add qword [r12], rax
ret
word__2d:
mov rax, [r12]
add r12, 8
sub qword [r12], rax
ret
word__2a:
mov rax, [r12]
add r12, 8
imul qword [r12]
mov [r12], rax
ret
word__2f:
mov rbx, [r12]
add r12, 8
mov rax, [r12]
cqo
idiv rbx
mov [r12], rax
ret
word__25:
mov rbx, [r12]
add r12, 8
mov rax, [r12]
cqo
idiv rbx
mov [r12], rdx
ret
word__3d_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
sete bl
mov [r12], rbx
ret
word__21_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setne bl
mov [r12], rbx
ret
word__3c:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setl bl
mov [r12], rbx
ret
word__3e:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setg bl
mov [r12], rbx
ret
word__3c_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setle bl
mov [r12], rbx
ret
word__3e_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setge bl
mov [r12], rbx
ret
word__40:
mov rax, [r12]
mov rax, [rax]
mov [r12], rax
ret
word__21:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
mov [rax], rbx
add r12, 8
ret
word_mmap:
mov r9, [r12]
add r12, 8
mov r8, [r12]
add r12, 8
mov r10, [r12]
add r12, 8
mov rdx, [r12]
add r12, 8
mov rsi, [r12]
add r12, 8
mov rdi, [r12]
mov rax, 9
syscall
mov [r12], rax
ret
word_munmap:
mov rsi, [r12]
add r12, 8
mov rdi, [r12]
mov rax, 11
syscall
mov [r12], rax
ret
word_exit:
mov rdi, [r12]
add r12, 8
mov rax, 60
syscall
ret
word__3er:
mov rax, [r12]
add r12, 8
sub r13, 8
mov [r13], rax
ret
word_r_3e:
mov rax, [r13]
add r13, 8
sub r12, 8
mov [r12], rax
ret
word_rdrop:
add r13, 8
ret
word_pick:
mov rcx, [r12]
add r12, 8
mov rax, [r12 + rcx * 8]
sub r12, 8
mov [r12], rax
ret
word_rpick:
mov rcx, [r12]
add r12, 8
mov rax, [r13 + rcx * 8]
sub r12, 8
mov [r12], rax
ret
word_main:
; push 2
sub r12, 8
mov qword [r12], 2
; push 40
sub r12, 8
mov qword [r12], 40
call word__2b
call word_puts
; push 1
sub r12, 8
mov qword [r12], 1
; push 2
sub r12, 8
mov qword [r12], 2
call word_foo
call word_puts
; push 0
sub r12, 8
mov qword [r12], 0
ret
word_foo:
call word__3er
call word__3er
; push 0
sub r12, 8
mov qword [r12], 0
call word_rpick
; push 1
sub r12, 8
mov qword [r12], 1
call word_rpick
call word__2b
call word_rdrop
call word_rdrop
ret
section .bss
align 16
dstack: resb DSTK_BYTES
dstack_top:
align 16
rstack: resb RSTK_BYTES
rstack_top:
align 16
print_buf: resb PRINT_BUF_BYTES
print_buf_end:

BIN
build/main.o Normal file

Binary file not shown.

View File

@@ -0,0 +1,262 @@
section .text
%define DSTK_BYTES 65536
%define RSTK_BYTES 65536
%define PRINT_BUF_BYTES 128
global _start
_start:
; initialize data/return stack pointers
lea r12, [rel dstack_top]
mov r15, r12
lea r13, [rel rstack_top]
call word_main
mov rax, 0
cmp r12, r15
je .no_exit_value
mov rax, [r12]
add r12, 8
.no_exit_value:
mov rdi, rax
mov rax, 60
syscall
word_puts:
mov rax, [r12]
add r12, 8
mov rbx, rax
mov r8, 0
cmp rbx, 0
jge puts_abs
neg rbx
mov r8, 1
puts_abs:
lea rsi, [rel print_buf_end]
mov rcx, 0
mov r10, 10
cmp rbx, 0
jne puts_digits
dec rsi
mov byte [rsi], '0'
inc rcx
jmp puts_sign
puts_digits:
puts_loop:
xor rdx, rdx
mov rax, rbx
div r10
add dl, '0'
dec rsi
mov [rsi], dl
inc rcx
mov rbx, rax
test rbx, rbx
jne puts_loop
puts_sign:
cmp r8, 0
je puts_finish_digits
dec rsi
mov byte [rsi], '-'
inc rcx
puts_finish_digits:
mov byte [rsi + rcx], 10
inc rcx
mov rax, 1
mov rdi, 1
mov rdx, rcx
mov r9, rsi
mov rsi, r9
syscall
ret
word_dup:
mov rax, [r12]
sub r12, 8
mov [r12], rax
ret
word_drop:
add r12, 8
ret
word_over:
mov rax, [r12 + 8]
sub r12, 8
mov [r12], rax
ret
word_swap:
mov rax, [r12]
mov rbx, [r12 + 8]
mov [r12], rbx
mov [r12 + 8], rax
ret
word__2b:
mov rax, [r12]
add r12, 8
add qword [r12], rax
ret
word__2d:
mov rax, [r12]
add r12, 8
sub qword [r12], rax
ret
word__2a:
mov rax, [r12]
add r12, 8
imul qword [r12]
mov [r12], rax
ret
word__2f:
mov rbx, [r12]
add r12, 8
mov rax, [r12]
cqo
idiv rbx
mov [r12], rax
ret
word__25:
mov rbx, [r12]
add r12, 8
mov rax, [r12]
cqo
idiv rbx
mov [r12], rdx
ret
word__3d_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
sete bl
mov [r12], rbx
ret
word__21_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setne bl
mov [r12], rbx
ret
word__3c:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setl bl
mov [r12], rbx
ret
word__3e:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setg bl
mov [r12], rbx
ret
word__3c_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setle bl
mov [r12], rbx
ret
word__3e_3d:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
cmp rbx, rax
mov rbx, 0
setge bl
mov [r12], rbx
ret
word__40:
mov rax, [r12]
mov rax, [rax]
mov [r12], rax
ret
word__21:
mov rax, [r12]
add r12, 8
mov rbx, [r12]
mov [rax], rbx
add r12, 8
ret
word_mmap:
mov r9, [r12]
add r12, 8
mov r8, [r12]
add r12, 8
mov r10, [r12]
add r12, 8
mov rdx, [r12]
add r12, 8
mov rsi, [r12]
add r12, 8
mov rdi, [r12]
mov rax, 9
syscall
mov [r12], rax
ret
word_munmap:
mov rsi, [r12]
add r12, 8
mov rdi, [r12]
mov rax, 11
syscall
mov [r12], rax
ret
word_exit:
mov rdi, [r12]
add r12, 8
mov rax, 60
syscall
ret
word__3er:
mov rax, [r12]
add r12, 8
sub r13, 8
mov [r13], rax
ret
word_r_3e:
mov rax, [r13]
add r13, 8
sub r12, 8
mov [r12], rax
ret
word_rdrop:
add r13, 8
ret
word_pick:
mov rcx, [r12]
add r12, 8
mov rax, [r12 + rcx * 8]
sub r12, 8
mov [r12], rax
ret
word_rpick:
mov rcx, [r12]
add r12, 8
mov rax, [r13 + rcx * 8]
sub r12, 8
mov [r12], rax
ret
word_main:
; push 6
sub r12, 8
mov qword [r12], 6
call word_puts
; push 0
sub r12, 8
mov qword [r12], 0
ret
section .bss
align 16
dstack: resb DSTK_BYTES
dstack_top:
align 16
rstack: resb RSTK_BYTES
rstack_top:
align 16
print_buf: resb PRINT_BUF_BYTES
print_buf_end:

Binary file not shown.

View File

@@ -73,6 +73,11 @@ word_dup:
word_drop: word_drop:
add r12, 8 add r12, 8
ret ret
word_over:
mov rax, [r12 + 8]
sub r12, 8
mov [r12], rax
ret
word_swap: word_swap:
mov rax, [r12] mov rax, [r12]
mov rbx, [r12 + 8] mov rbx, [r12 + 8]
@@ -222,6 +227,13 @@ word_r_3e:
word_rdrop: word_rdrop:
add r13, 8 add r13, 8
ret ret
word_pick:
mov rcx, [r12]
add r12, 8
mov rax, [r12 + rcx * 8]
sub r12, 8
mov [r12], rax
ret
word_rpick: word_rpick:
mov rcx, [r12] mov rcx, [r12]
add r12, 8 add r12, 8
@@ -461,18 +473,18 @@ word_test_2dif:
mov rax, [r12] mov rax, [r12]
add r12, 8 add r12, 8
test rax, rax test rax, rax
jz L_if_false_0 jz L_if_false_24
; push 111 ; push 111
sub r12, 8 sub r12, 8
mov qword [r12], 111 mov qword [r12], 111
call word_puts call word_puts
jmp L_if_end_1 jmp L_if_end_25
L_if_false_0: L_if_false_24:
; push 222 ; push 222
sub r12, 8 sub r12, 8
mov qword [r12], 222 mov qword [r12], 222
call word_puts call word_puts
L_if_end_1: L_if_end_25:
ret ret
word_test_2delse_2dif: word_test_2delse_2dif:
; push 2 ; push 2
@@ -486,13 +498,13 @@ word_test_2delse_2dif:
mov rax, [r12] mov rax, [r12]
add r12, 8 add r12, 8
test rax, rax test rax, rax
jz L_if_false_2 jz L_if_false_26
; push 50 ; push 50
sub r12, 8 sub r12, 8
mov qword [r12], 50 mov qword [r12], 50
call word_puts call word_puts
jmp L_if_end_3 jmp L_if_end_27
L_if_false_2: L_if_false_26:
call word_dup call word_dup
; push 2 ; push 2
sub r12, 8 sub r12, 8
@@ -501,19 +513,19 @@ L_if_false_2:
mov rax, [r12] mov rax, [r12]
add r12, 8 add r12, 8
test rax, rax test rax, rax
jz L_if_false_4 jz L_if_false_28
; push 60 ; push 60
sub r12, 8 sub r12, 8
mov qword [r12], 60 mov qword [r12], 60
call word_puts call word_puts
jmp L_if_end_5 jmp L_if_end_29
L_if_false_4: L_if_false_28:
; push 70 ; push 70
sub r12, 8 sub r12, 8
mov qword [r12], 70 mov qword [r12], 70
call word_puts call word_puts
L_if_end_5: L_if_end_29:
L_if_end_3: L_if_end_27:
call word_drop call word_drop
ret ret
word_test_2dfor: word_test_2dfor:
@@ -526,10 +538,10 @@ word_test_2dfor:
mov rax, [r12] mov rax, [r12]
add r12, 8 add r12, 8
cmp rax, 0 cmp rax, 0
jle L_for_end_7 jle L_for_end_31
sub r13, 8 sub r13, 8
mov [r13], rax mov [r13], rax
L_for_loop_6: L_for_loop_30:
; push 1 ; push 1
sub r12, 8 sub r12, 8
mov qword [r12], 1 mov qword [r12], 1
@@ -537,9 +549,9 @@ L_for_loop_6:
mov rax, [r13] mov rax, [r13]
dec rax dec rax
mov [r13], rax mov [r13], rax
jg L_for_loop_6 jg L_for_loop_30
add r13, 8 add r13, 8
L_for_end_7: L_for_end_31:
call word_puts call word_puts
ret ret
word_test_2dfor_2dzero: word_test_2dfor_2dzero:
@@ -552,17 +564,17 @@ word_test_2dfor_2dzero:
mov rax, [r12] mov rax, [r12]
add r12, 8 add r12, 8
cmp rax, 0 cmp rax, 0
jle L_for_end_9 jle L_for_end_33
sub r13, 8 sub r13, 8
mov [r13], rax mov [r13], rax
L_for_loop_8: L_for_loop_32:
call word_drop call word_drop
mov rax, [r13] mov rax, [r13]
dec rax dec rax
mov [r13], rax mov [r13], rax
jg L_for_loop_8 jg L_for_loop_32
add r13, 8 add r13, 8
L_for_end_9: L_for_end_33:
call word_puts call word_puts
ret ret
word_test_2dstruct: word_test_2dstruct:

Binary file not shown.

155
fn.sl Normal file
View File

@@ -0,0 +1,155 @@
: call-syntax-rewrite # ( fnameToken -- handled )
dup token-lexeme identifier? 0 == if drop 0 exit then
peek-token dup nil? if drop drop 0 exit then
dup token-lexeme "(" string= 0 == if drop drop 0 exit then
swap >r # stash fnameTok
drop # discard peeked '('
next-token drop # consume '('
list-new # out
list-new # out cur
begin
next-token dup nil? if "unterminated call expression" parse-error then
dup token-lexeme ")" string= if
drop
# flush current arg
list-extend # out'
r> list-append # out''
inject-tokens
1 exit
then
dup token-lexeme "," string= if
drop
list-extend # out'
list-new # out' cur
continue
then
# default: append tok to cur
list-append
again
;
immediate
compile-only
: extend-syntax
"call-syntax-rewrite" set-token-hook
;
immediate
compile-only
: fn-op-prec
dup "+" string= if drop 1 exit then
dup "-" string= if drop 1 exit then
dup "*" string= if drop 2 exit then
dup "/" string= if drop 2 exit then
dup "%" string= if drop 2 exit then
drop 0
;
compile-only
: fn-operator?
fn-op-prec 0 >
;
compile-only
: fn-check-dup
>r # params (r: name)
0 # params idx
begin
over list-length swap >= if # params flag
r> exit
then
dup >r # params idx (r: idx name)
over swap list-get # params elem
1 rpick string= if "duplicate parameter names in fn definition" parse-error then
drop # drop comparison flag when no error
r> 1 + # params idx+1
again
;
compile-only
: fn-params
list-new # lexer params
swap # params lexer
>r # params (r: lexer)
begin
0 rpick lexer-pop token-lexeme # params lex
swap drop # params lex (drop returned lexer)
dup ")" string= if drop r> exit then
dup "int" string= 0 == if "only 'int' parameters are supported in fn definitions" parse-error then
drop # params
0 rpick lexer-pop token-lexeme # params lexer pname
swap drop # params pname
dup identifier? 0 == if "invalid parameter name in fn definition" parse-error then
fn-check-dup # params pname
list-append # params
0 rpick lexer-pop token-lexeme # params lexer sep
swap drop # params sep
dup "," string= if drop continue then
dup ")" string= if drop r> exit then
"expected ',' or ')' in parameter list" parse-error
again
;
compile-only
: fn-collect-body
"{" lexer-expect drop # consume opening brace, keep lexer
lexer-collect-brace # lexer bodyTokens
swap drop # bodyTokens
;
compile-only
: fn-lexemes-from-tokens
list-new >r # tokens (r: acc)
0 # tokens idx
begin
over list-length over swap >= if # stop when idx >= len
drop drop # drop idx and tokens (flag consumed by if)
r> exit # return acc
then
over over list-get token-lexeme # tokens idx lex
r> swap list-append >r # tokens idx
1 + # tokens idx+1
again
;
compile-only
: fn-validate-body
dup list-length 0 == if "empty function body" parse-error then
dup >r 0 r> swap list-get "return" string= 0 == if "function body must start with 'return'" parse-error then
dup list-last ";" string= 0 == if "function body must terminate with ';'" parse-error then
list-clone # work on a copy
list-pop drop # drop trailing ';'
list-pop-front drop # drop leading 'return'
dup list-length 0 == if "missing return expression" parse-error then
;
compile-only
: fn-build-body
fn-translate-postfix # words
;
compile-only
: fn
"(),{};+-*/%," lexer-new # lexer
dup lexer-pop # lexer nameTok
dup >r # save nameTok
token-lexeme # lexer name
dup identifier? 0 == if "invalid function name for 'fn'" parse-error then
>r # save name string
drop # leave lexer only for params
"(" lexer-expect drop # consume '(' keep lexer
fn-params # params lexer
fn-collect-body # params bodyTokens
swap >r # bodyTokens (r: params)
fn-lexemes-from-tokens # lexemes
fn-validate-body # expr
shunt # postfix
r> # postfix params
fn-build-body # body
r> drop # drop name string
r> # name token
swap emit-definition
;
immediate
compile-only

BIN
main Executable file

Binary file not shown.

BIN
main.bin Executable file

Binary file not shown.

398
main.py
View File

@@ -50,6 +50,22 @@ class Reader:
def __init__(self) -> None: def __init__(self) -> None:
self.line = 1 self.line = 1
self.column = 0 self.column = 0
self.custom_tokens: Set[str] = {"(", ")", "{", "}", ";", ",", "[", "]"}
self._token_order: List[str] = sorted(self.custom_tokens, key=len, reverse=True)
def add_tokens(self, tokens: Iterable[str]) -> None:
updated = False
for tok in tokens:
if not tok:
continue
if tok not in self.custom_tokens:
self.custom_tokens.add(tok)
updated = True
if updated:
self._token_order = sorted(self.custom_tokens, key=len, reverse=True)
def add_token_chars(self, chars: str) -> None:
self.add_tokens(chars)
def tokenize(self, source: str) -> Iterable[Token]: def tokenize(self, source: str) -> Iterable[Token]:
self.line = 1 self.line = 1
@@ -62,19 +78,78 @@ class Reader:
source_len = len(source) source_len = len(source)
while index < source_len: while index < source_len:
char = source[index] char = source[index]
if char == '"':
if lexeme:
yield Token("".join(lexeme), token_line, token_column, token_start, index)
lexeme.clear()
token_start = index
token_line = self.line
token_column = self.column
index += 1
self.column += 1
string_parts = ['"']
while True:
if index >= source_len:
raise ParseError("unterminated string literal")
ch = source[index]
string_parts.append(ch)
index += 1
if ch == "\n":
self.line += 1
self.column = 0
else:
self.column += 1
if ch == "\\":
if index >= source_len:
raise ParseError("unterminated string literal")
next_ch = source[index]
string_parts.append(next_ch)
index += 1
if next_ch == "\n":
self.line += 1
self.column = 0
else:
self.column += 1
continue
if ch == '"':
yield Token("".join(string_parts), token_line, token_column, token_start, index)
break
continue
if char == "#": if char == "#":
while index < source_len and source[index] != "\n": while index < source_len and source[index] != "\n":
index += 1 index += 1
continue continue
if char == ";" and index + 1 < source_len and source[index + 1].isalpha():
if not lexeme:
token_start = index
token_line = self.line
token_column = self.column
lexeme.append(";")
index += 1
self.column += 1
continue
matched_token: Optional[str] = None
for tok in self._token_order:
if source.startswith(tok, index):
matched_token = tok
break
if matched_token is not None:
if lexeme:
yield Token("".join(lexeme), token_line, token_column, token_start, index)
lexeme.clear()
token_start = index
token_line = self.line
token_column = self.column
yield Token(matched_token, self.line, self.column, index, index + len(matched_token))
index += len(matched_token)
self.column += len(matched_token)
token_start = index
token_line = self.line
token_column = self.column
continue
if char.isspace(): if char.isspace():
if lexeme: if lexeme:
yield Token( yield Token("".join(lexeme), token_line, token_column, token_start, index)
"".join(lexeme),
token_line,
token_column,
token_start,
index,
)
lexeme.clear() lexeme.clear()
if char == "\n": if char == "\n":
self.line += 1 self.line += 1
@@ -82,6 +157,9 @@ class Reader:
else: else:
self.column += 1 self.column += 1
index += 1 index += 1
token_start = index
token_line = self.line
token_column = self.column
continue continue
if not lexeme: if not lexeme:
token_start = index token_start = index
@@ -91,7 +169,7 @@ class Reader:
self.column += 1 self.column += 1
index += 1 index += 1
if lexeme: if lexeme:
yield Token("".join(lexeme), token_line, token_column, token_start, index) yield Token("".join(lexeme), token_line, token_column, token_start, source_len)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -218,11 +296,8 @@ class MacroContext:
def inject_token_objects(self, tokens: Sequence[Token]) -> None: def inject_token_objects(self, tokens: Sequence[Token]) -> None:
self._parser.tokens[self._parser.pos:self._parser.pos] = list(tokens) self._parser.tokens[self._parser.pos:self._parser.pos] = list(tokens)
def enable_call_syntax(self) -> None: def set_token_hook(self, handler: Optional[str]) -> None:
self._parser.call_syntax_enabled = True self._parser.token_hook = handler
def disable_call_syntax(self) -> None:
self._parser.call_syntax_enabled = False
def new_label(self, prefix: str) -> str: def new_label(self, prefix: str) -> str:
return self._parser._new_label(prefix) return self._parser._new_label(prefix)
@@ -247,6 +322,7 @@ class Word:
macro_params: int = 0 macro_params: int = 0
compile_time_intrinsic: Optional[Callable[["CompileTimeVM"], None]] = None compile_time_intrinsic: Optional[Callable[["CompileTimeVM"], None]] = None
compile_only: bool = False compile_only: bool = False
compile_time_override: bool = False
@dataclass @dataclass
@@ -271,9 +347,12 @@ Context = Union[Module, Definition]
class Parser: class Parser:
def __init__(self, dictionary: Dictionary) -> None: def __init__(self, dictionary: Dictionary, reader: Optional[Reader] = None) -> None:
self.dictionary = dictionary self.dictionary = dictionary
self.reader = reader or Reader()
self.tokens: List[Token] = [] self.tokens: List[Token] = []
self._token_iter: Optional[Iterable[Token]] = None
self._token_iter_exhausted = True
self.pos = 0 self.pos = 0
self.context_stack: List[Context] = [] self.context_stack: List[Context] = []
self.definition_stack: List[Word] = [] self.definition_stack: List[Word] = []
@@ -282,14 +361,20 @@ class Parser:
self.macro_recording: Optional[MacroDefinition] = None self.macro_recording: Optional[MacroDefinition] = None
self.control_stack: List[Dict[str, str]] = [] self.control_stack: List[Dict[str, str]] = []
self.label_counter = 0 self.label_counter = 0
self.call_syntax_enabled = False self.token_hook: Optional[str] = None
self._last_token: Optional[Token] = None
self.compile_time_vm = CompileTimeVM(self) self.compile_time_vm = CompileTimeVM(self)
def inject_token_objects(self, tokens: Sequence[Token]) -> None:
"""Insert tokens at the current parse position."""
self.tokens[self.pos:self.pos] = list(tokens)
# Public helpers for macros ------------------------------------------------ # Public helpers for macros ------------------------------------------------
def next_token(self) -> Token: def next_token(self) -> Token:
return self._consume() return self._consume()
def peek_token(self) -> Optional[Token]: def peek_token(self) -> Optional[Token]:
self._ensure_tokens(self.pos)
return None if self._eof() else self.tokens[self.pos] return None if self._eof() else self.tokens[self.pos]
def emit_node(self, node: ASTNode) -> None: def emit_node(self, node: ASTNode) -> None:
@@ -300,7 +385,9 @@ class Parser:
# Parsing ------------------------------------------------------------------ # Parsing ------------------------------------------------------------------
def parse(self, tokens: Iterable[Token], source: str) -> Module: def parse(self, tokens: Iterable[Token], source: str) -> Module:
self.tokens = list(tokens) self.tokens = []
self._token_iter = iter(tokens)
self._token_iter_exhausted = False
self.source = source self.source = source
self.pos = 0 self.pos = 0
self.context_stack = [Module(forms=[])] self.context_stack = [Module(forms=[])]
@@ -308,10 +395,14 @@ class Parser:
self.last_defined = None self.last_defined = None
self.control_stack = [] self.control_stack = []
self.label_counter = 0 self.label_counter = 0
self.call_syntax_enabled = False self.token_hook = None
self._last_token = None
while not self._eof(): while not self._eof():
token = self._consume() token = self._consume()
self._last_token = token
if self._run_token_hook(token):
continue
if self._handle_macro_recording(token): if self._handle_macro_recording(token):
continue continue
lexeme = token.lexeme lexeme = token.lexeme
@@ -358,11 +449,6 @@ class Parser:
# Internal helpers --------------------------------------------------------- # Internal helpers ---------------------------------------------------------
def _handle_token(self, token: Token) -> None: def _handle_token(self, token: Token) -> None:
if self.call_syntax_enabled:
call_target = self._maybe_call_form(token.lexeme)
if call_target is not None:
self._append_node(WordRef(name=call_target))
return
if self._try_literal(token): if self._try_literal(token):
return return
@@ -444,6 +530,12 @@ class Parser:
self.dictionary.register(word) self.dictionary.register(word)
def _push_control(self, entry: Dict[str, str]) -> None: def _push_control(self, entry: Dict[str, str]) -> None:
if "line" not in entry or "column" not in entry:
tok = self._last_token
if tok is not None:
entry = dict(entry)
entry["line"] = tok.line
entry["column"] = tok.column
self.control_stack.append(entry) self.control_stack.append(entry)
def _pop_control(self, expected: Tuple[str, ...]) -> Dict[str, str]: def _pop_control(self, expected: Tuple[str, ...]) -> Dict[str, str]:
@@ -451,7 +543,14 @@ class Parser:
raise ParseError("control stack underflow") raise ParseError("control stack underflow")
entry = self.control_stack.pop() entry = self.control_stack.pop()
if entry.get("type") not in expected: if entry.get("type") not in expected:
raise ParseError(f"mismatched control word '{entry.get('type')}'") tok = self._last_token
location = ""
if tok is not None:
location = f" at {tok.line}:{tok.column} near '{tok.lexeme}'"
origin = ""
if "line" in entry and "column" in entry:
origin = f" (opened at {entry['line']}:{entry['column']})"
raise ParseError(f"mismatched control word '{entry.get('type')}'" + origin + location)
return entry return entry
def _new_label(self, prefix: str) -> str: def _new_label(self, prefix: str) -> str:
@@ -459,13 +558,16 @@ class Parser:
self.label_counter += 1 self.label_counter += 1
return label return label
def _maybe_call_form(self, lexeme: str) -> Optional[str]: def _run_token_hook(self, token: Token) -> bool:
if len(lexeme) <= 2 or not lexeme.endswith("()"): if not self.token_hook:
return None return False
name = lexeme[:-2] hook_word = self.dictionary.lookup(self.token_hook)
if not name or not _is_identifier(name): if hook_word is None:
return None raise ParseError(f"token hook '{self.token_hook}' not defined")
return name self.compile_time_vm.invoke_with_args(hook_word, [token])
# Convention: hook leaves handled flag on stack (int truthy means consumed)
handled = self.compile_time_vm.pop()
return bool(handled)
def _handle_if_control(self) -> None: def _handle_if_control(self) -> None:
false_label = self._new_label("if_false") false_label = self._new_label("if_false")
@@ -518,6 +620,9 @@ class Parser:
word = self.definition_stack.pop() word = self.definition_stack.pop()
ctx.immediate = word.immediate ctx.immediate = word.immediate
ctx.compile_only = word.compile_only ctx.compile_only = word.compile_only
if word.compile_only or word.immediate:
word.compile_time_override = True
word.compile_time_intrinsic = None
module = self.context_stack[-1] module = self.context_stack[-1]
if not isinstance(module, Module): if not isinstance(module, Module):
raise ParseError("nested definitions are not supported yet") raise ParseError("nested definitions are not supported yet")
@@ -626,6 +731,7 @@ class Parser:
return True return True
def _consume(self) -> Token: def _consume(self) -> Token:
self._ensure_tokens(self.pos)
if self._eof(): if self._eof():
raise ParseError("unexpected EOF") raise ParseError("unexpected EOF")
token = self.tokens[self.pos] token = self.tokens[self.pos]
@@ -633,8 +739,23 @@ class Parser:
return token return token
def _eof(self) -> bool: def _eof(self) -> bool:
self._ensure_tokens(self.pos)
return self.pos >= len(self.tokens) return self.pos >= len(self.tokens)
def _ensure_tokens(self, upto: int) -> None:
if self._token_iter_exhausted:
return
if self._token_iter is None:
self._token_iter_exhausted = True
return
while len(self.tokens) <= upto and not self._token_iter_exhausted:
try:
next_tok = next(self._token_iter)
except StopIteration:
self._token_iter_exhausted = True
break
self.tokens.append(next_tok)
class CompileTimeVM: class CompileTimeVM:
def __init__(self, parser: Parser) -> None: def __init__(self, parser: Parser) -> None:
@@ -642,10 +763,12 @@ class CompileTimeVM:
self.dictionary = parser.dictionary self.dictionary = parser.dictionary
self.stack: List[Any] = [] self.stack: List[Any] = []
self.return_stack: List[Any] = [] self.return_stack: List[Any] = []
self.loop_stack: List[Dict[str, Any]] = []
def reset(self) -> None: def reset(self) -> None:
self.stack.clear() self.stack.clear()
self.return_stack.clear() self.return_stack.clear()
self.loop_stack.clear()
def push(self, value: Any) -> None: def push(self, value: Any) -> None:
self.stack.append(value) self.stack.append(value)
@@ -688,11 +811,18 @@ class CompileTimeVM:
self.reset() self.reset()
self._call_word(word) self._call_word(word)
def invoke_with_args(self, word: Word, args: Sequence[Any]) -> None:
self.reset()
for value in args:
self.push(value)
self._call_word(word)
def _call_word(self, word: Word) -> None: def _call_word(self, word: Word) -> None:
if word.compile_time_intrinsic is not None: definition = word.definition
prefer_definition = word.compile_time_override or (isinstance(definition, Definition) and (word.immediate or word.compile_only))
if not prefer_definition and word.compile_time_intrinsic is not None:
word.compile_time_intrinsic(self) word.compile_time_intrinsic(self)
return return
definition = word.definition
if definition is None: if definition is None:
raise ParseError(f"word '{word.name}' has no compile-time definition") raise ParseError(f"word '{word.name}' has no compile-time definition")
if isinstance(definition, AsmDefinition): if isinstance(definition, AsmDefinition):
@@ -708,7 +838,9 @@ class CompileTimeVM:
def _execute_nodes(self, nodes: Sequence[ASTNode]) -> None: def _execute_nodes(self, nodes: Sequence[ASTNode]) -> None:
label_positions = self._label_positions(nodes) label_positions = self._label_positions(nodes)
loop_pairs = self._for_pairs(nodes) loop_pairs = self._for_pairs(nodes)
loop_stack: List[Dict[str, Any]] = [] begin_pairs = self._begin_pairs(nodes)
self.loop_stack = []
begin_stack: List[Dict[str, int]] = []
ip = 0 ip = 0
while ip < len(nodes): while ip < len(nodes):
node = nodes[ip] node = nodes[ip]
@@ -717,7 +849,31 @@ class CompileTimeVM:
ip += 1 ip += 1
continue continue
if isinstance(node, WordRef): if isinstance(node, WordRef):
self._call_word_by_name(node.name) name = node.name
if name == "begin":
end_idx = begin_pairs.get(ip)
if end_idx is None:
raise ParseError("'begin' without matching 'again'")
begin_stack.append({"begin": ip, "end": end_idx})
ip += 1
continue
if name == "again":
if not begin_stack or begin_stack[-1]["end"] != ip:
raise ParseError("'again' without matching 'begin'")
ip = begin_stack[-1]["begin"] + 1
continue
if name == "continue":
if not begin_stack:
raise ParseError("'continue' outside begin/again loop")
ip = begin_stack[-1]["begin"] + 1
continue
if name == "exit":
if begin_stack:
frame = begin_stack.pop()
ip = frame["end"] + 1
continue
return
self._call_word_by_name(name)
ip += 1 ip += 1
continue continue
if isinstance(node, BranchZero): if isinstance(node, BranchZero):
@@ -748,18 +904,18 @@ class CompileTimeVM:
raise ParseError("internal loop bookkeeping error") raise ParseError("internal loop bookkeeping error")
ip = match + 1 ip = match + 1
continue continue
loop_stack.append({"remaining": count, "begin": ip}) self.loop_stack.append({"remaining": count, "begin": ip, "initial": count})
ip += 1 ip += 1
continue continue
if isinstance(node, ForNext): if isinstance(node, ForNext):
if not loop_stack: if not self.loop_stack:
raise ParseError("'next' without matching 'for'") raise ParseError("'next' without matching 'for'")
frame = loop_stack[-1] frame = self.loop_stack[-1]
frame["remaining"] -= 1 frame["remaining"] -= 1
if frame["remaining"] > 0: if frame["remaining"] > 0:
ip = frame["begin"] + 1 ip = frame["begin"] + 1
continue continue
loop_stack.pop() self.loop_stack.pop()
ip += 1 ip += 1
continue continue
raise ParseError(f"unsupported compile-time AST node {node!r}") raise ParseError(f"unsupported compile-time AST node {node!r}")
@@ -787,6 +943,22 @@ class CompileTimeVM:
raise ParseError("'for' without matching 'next'") raise ParseError("'for' without matching 'next'")
return pairs return pairs
def _begin_pairs(self, nodes: Sequence[ASTNode]) -> Dict[int, int]:
stack: List[int] = []
pairs: Dict[int, int] = {}
for idx, node in enumerate(nodes):
if isinstance(node, WordRef) and node.name == "begin":
stack.append(idx)
elif isinstance(node, WordRef) and node.name == "again":
if not stack:
raise ParseError("'again' without matching 'begin'")
begin_idx = stack.pop()
pairs[begin_idx] = idx
pairs[idx] = begin_idx
if stack:
raise ParseError("'begin' without matching 'again'")
return pairs
def _jump_to_label(self, labels: Dict[str, int], target: str) -> int: def _jump_to_label(self, labels: Dict[str, int], target: str) -> int:
if target not in labels: if target not in labels:
raise ParseError(f"unknown label '{target}' during compile-time execution") raise ParseError(f"unknown label '{target}' during compile-time execution")
@@ -1457,6 +1629,13 @@ def _ct_rpick(vm: CompileTimeVM) -> None:
vm.push(vm.return_stack[-1 - index]) vm.push(vm.return_stack[-1 - index])
def _ct_pick(vm: CompileTimeVM) -> None:
index = vm.pop_int()
if index < 0 or index >= len(vm.stack):
raise ParseError("pick index out of range")
vm.push(vm.stack[-1 - index])
def _ct_nil(vm: CompileTimeVM) -> None: def _ct_nil(vm: CompileTimeVM) -> None:
vm.push(None) vm.push(None)
@@ -1509,6 +1688,14 @@ def _ct_list_empty(vm: CompileTimeVM) -> None:
vm.push(1 if not lst else 0) vm.push(1 if not lst else 0)
def _ct_loop_index(vm: CompileTimeVM) -> None:
if not vm.loop_stack:
raise ParseError("'i' used outside of a for loop")
frame = vm.loop_stack[-1]
idx = frame["initial"] - frame["remaining"]
vm.push(idx)
def _ct_list_get(vm: CompileTimeVM) -> None: def _ct_list_get(vm: CompileTimeVM) -> None:
index = vm.pop_int() index = vm.pop_int()
lst = _ensure_list(vm.pop()) lst = _ensure_list(vm.pop())
@@ -1608,11 +1795,121 @@ def _ct_string_to_number(vm: CompileTimeVM) -> None:
vm.push(0) vm.push(0)
def _ct_set_token_hook(vm: CompileTimeVM) -> None:
hook_name = vm.pop_str()
vm.parser.token_hook = hook_name
def _ct_clear_token_hook(vm: CompileTimeVM) -> None:
vm.parser.token_hook = None
def _ct_use_l2_compile_time(vm: CompileTimeVM) -> None:
if vm.stack:
name = vm.pop_str()
word = vm.dictionary.lookup(name)
else:
word = vm.parser.most_recent_definition()
if word is None:
raise ParseError("use-l2-ct with empty stack and no recent definition")
name = word.name
if word is None:
raise ParseError(f"unknown word '{name}' for use-l2-ct")
word.compile_time_intrinsic = None
word.compile_time_override = True
def _ct_add_token(vm: CompileTimeVM) -> None:
tok = vm.pop_str()
vm.parser.reader.add_tokens([tok])
def _ct_add_token_chars(vm: CompileTimeVM) -> None:
chars = vm.pop_str()
vm.parser.reader.add_token_chars(chars)
def _ct_fn_param_index(vm: CompileTimeVM) -> None:
name = vm.pop_str()
params = _ensure_list(vm.pop())
try:
idx = params.index(name)
vm.push(params)
vm.push(idx)
vm.push(1)
except ValueError:
vm.push(params)
vm.push(-1)
vm.push(0)
def _ct_fn_translate_postfix(vm: CompileTimeVM) -> None:
params = _ensure_list(vm.pop())
postfix = _ensure_list(vm.pop())
prologue: List[Any] = [">r"] * len(params)
translated: List[Any] = []
for tok in postfix:
if isinstance(tok, int):
translated.append(tok)
continue
if isinstance(tok, str):
try:
num_value = int(tok, 0)
translated.append(num_value)
continue
except ValueError:
pass
if isinstance(tok, str) and tok in params:
idx = params.index(tok)
translated.append(idx)
translated.append("rpick")
else:
translated.append(tok)
epilogue: List[Any] = ["rdrop"] * len(params)
out: List[Any] = prologue + translated + epilogue
vm.push(out)
def _ct_shunt(vm: CompileTimeVM) -> None:
"""Convert an infix token list (strings) to postfix using +,-,*,/,%."""
ops: List[str] = []
output: List[str] = []
prec = {"+": 1, "-": 1, "*": 2, "/": 2, "%": 2}
tokens = _ensure_list(vm.pop())
for tok in tokens:
if not isinstance(tok, str):
raise ParseError("shunt expects list of strings")
if tok == "(":
ops.append(tok)
continue
if tok == ")":
while ops and ops[-1] != "(":
output.append(ops.pop())
if not ops:
raise ParseError("mismatched parentheses in expression")
ops.pop()
continue
if tok in prec:
while ops and ops[-1] in prec and prec[ops[-1]] >= prec[tok]:
output.append(ops.pop())
ops.append(tok)
continue
output.append(tok)
while ops:
top = ops.pop()
if top == "(":
raise ParseError("mismatched parentheses in expression")
output.append(top)
vm.push(output)
def _ct_int_to_string(vm: CompileTimeVM) -> None: def _ct_int_to_string(vm: CompileTimeVM) -> None:
value = vm.pop_int() value = vm.pop_int()
vm.push(str(value)) vm.push(str(value))
def _ct_identifier_p(vm: CompileTimeVM) -> None: def _ct_identifier_p(vm: CompileTimeVM) -> None:
value = vm.pop_str() value = vm.pop_str()
vm.push(1 if _is_identifier(value) else 0) vm.push(1 if _is_identifier(value) else 0)
@@ -1677,12 +1974,6 @@ def _ct_parse_error(vm: CompileTimeVM) -> None:
raise ParseError(message) raise ParseError(message)
def _ct_enable_call_syntax(vm: CompileTimeVM) -> None:
vm.parser.call_syntax_enabled = True
def _ct_disable_call_syntax(vm: CompileTimeVM) -> None:
vm.parser.call_syntax_enabled = False
def _ct_lexer_new(vm: CompileTimeVM) -> None: def _ct_lexer_new(vm: CompileTimeVM) -> None:
@@ -1763,6 +2054,7 @@ def _register_compile_time_primitives(dictionary: Dictionary) -> None:
register("r>", _ct_r_from) register("r>", _ct_r_from)
register("rdrop", _ct_rdrop) register("rdrop", _ct_rdrop)
register("rpick", _ct_rpick) register("rpick", _ct_rpick)
register("pick", _ct_pick)
register("nil", _ct_nil, compile_only=True) register("nil", _ct_nil, compile_only=True)
register("nil?", _ct_nil_p, compile_only=True) register("nil?", _ct_nil_p, compile_only=True)
@@ -1778,6 +2070,7 @@ def _register_compile_time_primitives(dictionary: Dictionary) -> None:
register("list-clear", _ct_list_clear, compile_only=True) register("list-clear", _ct_list_clear, compile_only=True)
register("list-extend", _ct_list_extend, compile_only=True) register("list-extend", _ct_list_extend, compile_only=True)
register("list-last", _ct_list_last, compile_only=True) register("list-last", _ct_list_last, compile_only=True)
register("i", _ct_loop_index, compile_only=True)
register("map-new", _ct_map_new, compile_only=True) register("map-new", _ct_map_new, compile_only=True)
register("map-set", _ct_map_set, compile_only=True) register("map-set", _ct_map_set, compile_only=True)
@@ -1788,18 +2081,27 @@ def _register_compile_time_primitives(dictionary: Dictionary) -> None:
register("string-length", _ct_string_length, compile_only=True) register("string-length", _ct_string_length, compile_only=True)
register("string-append", _ct_string_append, compile_only=True) register("string-append", _ct_string_append, compile_only=True)
register("string>number", _ct_string_to_number, compile_only=True) register("string>number", _ct_string_to_number, compile_only=True)
register("fn-param-index", _ct_fn_param_index, compile_only=True)
register("fn-translate-postfix", _ct_fn_translate_postfix, compile_only=True)
register("int>string", _ct_int_to_string, compile_only=True) register("int>string", _ct_int_to_string, compile_only=True)
register("identifier?", _ct_identifier_p, compile_only=True) register("identifier?", _ct_identifier_p, compile_only=True)
register("shunt", _ct_shunt, compile_only=True)
register("token-lexeme", _ct_token_lexeme, compile_only=True) register("token-lexeme", _ct_token_lexeme, compile_only=True)
register("token-from-lexeme", _ct_token_from_lexeme, compile_only=True) register("token-from-lexeme", _ct_token_from_lexeme, compile_only=True)
register("next-token", _ct_next_token, compile_only=True) register("next-token", _ct_next_token, compile_only=True)
register("peek-token", _ct_peek_token, compile_only=True) register("peek-token", _ct_peek_token, compile_only=True)
register("inject-tokens", _ct_inject_tokens, compile_only=True) register("inject-tokens", _ct_inject_tokens, compile_only=True)
register("add-token", _ct_add_token, compile_only=True)
register("add-token-chars", _ct_add_token_chars, compile_only=True)
register("set-token-hook", _ct_set_token_hook, compile_only=True)
register("clear-token-hook", _ct_clear_token_hook, compile_only=True)
register("use-l2-ct", _ct_use_l2_compile_time, compile_only=True)
word_use_l2 = dictionary.lookup("use-l2-ct")
if word_use_l2:
word_use_l2.immediate = True
register("emit-definition", _ct_emit_definition, compile_only=True) register("emit-definition", _ct_emit_definition, compile_only=True)
register("parse-error", _ct_parse_error, compile_only=True) register("parse-error", _ct_parse_error, compile_only=True)
register("enable-call-syntax", _ct_enable_call_syntax, compile_only=True)
register("disable-call-syntax", _ct_disable_call_syntax, compile_only=True)
register("lexer-new", _ct_lexer_new, compile_only=True) register("lexer-new", _ct_lexer_new, compile_only=True)
register("lexer-pop", _ct_lexer_pop, compile_only=True) register("lexer-pop", _ct_lexer_pop, compile_only=True)
@@ -1910,11 +2212,11 @@ class Compiler:
def __init__(self) -> None: def __init__(self) -> None:
self.reader = Reader() self.reader = Reader()
self.dictionary = bootstrap_dictionary() self.dictionary = bootstrap_dictionary()
self.parser = Parser(self.dictionary) self.parser = Parser(self.dictionary, self.reader)
self.assembler = Assembler(self.dictionary) self.assembler = Assembler(self.dictionary)
def compile_source(self, source: str) -> Emission: def compile_source(self, source: str) -> Emission:
tokens = list(self.reader.tokenize(source)) tokens = self.reader.tokenize(source)
module = self.parser.parse(tokens, source) module = self.parser.parse(tokens, source)
return self.assembler.emit(module) return self.assembler.emit(module)

View File

@@ -1,15 +1,15 @@
import stdlib.sl import stdlib.sl
import fn.sl
: main : main
2 40 + 2 40 +
puts puts
extend-syntax extend-syntax
1 foo(1, 2)
2
foo()
puts puts
0 0
; ;
fn foo(int a, int b){ fn foo(int a, int b){
return a + b; return a + b;
} }

221
stdlib.sl
View File

@@ -47,211 +47,6 @@ puts_finish_digits:
} }
; ;
: extend-syntax
enable-call-syntax
;
immediate
compile-only
:py fn {
FN_SPLIT_CHARS = set("(),{};+-*/%,")
def split_token(token):
lex = token.lexeme
parts = []
idx = 0
while idx < len(lex):
char = lex[idx]
if char in FN_SPLIT_CHARS:
parts.append(Token(
lexeme=char,
line=token.line,
column=token.column + idx,
start=token.start + idx,
end=token.start + idx + 1,
))
idx += 1
continue
start_idx = idx
while idx < len(lex) and lex[idx] not in FN_SPLIT_CHARS:
idx += 1
segment = lex[start_idx:idx]
if segment:
parts.append(Token(
lexeme=segment,
line=token.line,
column=token.column + start_idx,
start=token.start + start_idx,
end=token.start + idx,
))
return [part for part in parts if part.lexeme]
class FnLexer:
def __init__(self, parser):
self.parser = parser
self.buffer = []
def _fill(self):
while not self.buffer:
if self.parser._eof():
raise ParseError("unexpected EOF inside fn definition")
token = self.parser.next_token()
split = split_token(token)
if not split:
continue
self.buffer.extend(split)
def peek(self):
self._fill()
return self.buffer[0]
def pop(self):
token = self.peek()
self.buffer.pop(0)
return token
def expect(self, lexeme):
token = self.pop()
if token.lexeme != lexeme:
raise ParseError(f"expected '{lexeme}' but found '{token.lexeme}'")
return token
def push_back_remaining(self):
if not self.buffer:
return
self.parser.tokens[self.parser.pos:self.parser.pos] = self.buffer
self.buffer = []
def collect_block_tokens(self):
depth = 1
collected = []
while depth > 0:
token = self.pop()
if token.lexeme == "{":
depth += 1
collected.append(token)
continue
if token.lexeme == "}":
depth -= 1
if depth == 0:
break
collected.append(token)
continue
collected.append(token)
return collected
OP_PRECEDENCE = {}
OP_PRECEDENCE["+"] = 1
OP_PRECEDENCE["-"] = 1
OP_PRECEDENCE["*"] = 2
OP_PRECEDENCE["/"] = 2
OP_PRECEDENCE["%"] = 2
def parse_fn_body(tokens):
if not tokens:
raise ParseError("empty function body")
lexemes = [tok.lexeme for tok in tokens if tok.lexeme]
if not lexemes or lexemes[0] != "return":
raise ParseError("function body must start with 'return'")
if lexemes[-1] != ";":
raise ParseError("function body must terminate with ';'")
extra = lexemes[1:-1]
if not extra:
raise ParseError("missing return expression")
return extra
def shunting_yard(tokens):
output = []
stack = []
for token in tokens:
if token == "(":
stack.append(token)
continue
if token == ")":
while stack and stack[-1] != "(":
output.append(stack.pop())
if not stack:
raise ParseError("mismatched parentheses in return expression")
stack.pop()
continue
if token in OP_PRECEDENCE:
while stack and stack[-1] in OP_PRECEDENCE and OP_PRECEDENCE[stack[-1]] >= OP_PRECEDENCE[token]:
output.append(stack.pop())
stack.append(token)
continue
output.append(token)
while stack:
top = stack.pop()
if top == "(":
raise ParseError("mismatched parentheses in return expression")
output.append(top)
return output
def is_int_literal(text):
try:
int(text, 0)
return True
except ValueError:
return False
def translate_postfix(postfix, params):
indices = {name: idx for idx, name in enumerate(params)}
translated = []
for token in postfix:
if token in indices:
translated.append(str(indices[token]))
translated.append("rpick")
continue
if is_int_literal(token):
translated.append(token)
continue
translated.append(token)
return translated
def macro(ctx):
parser = ctx.parser
if not isinstance(parser.context_stack[-1], Module):
raise ParseError("'fn' definitions must be top-level")
lexer = FnLexer(parser)
name_token = lexer.pop()
name = name_token.lexeme
if not is_identifier(name):
raise ParseError("invalid function name for 'fn'")
lexer.expect("(")
params = []
if lexer.peek().lexeme != ")":
while True:
type_token = lexer.pop()
if type_token.lexeme != "int":
raise ParseError("only 'int' parameters are supported in fn definitions")
param_token = lexer.pop()
if not is_identifier(param_token.lexeme):
raise ParseError("invalid parameter name in fn definition")
params.append(param_token.lexeme)
if lexer.peek().lexeme == ",":
lexer.pop()
continue
break
lexer.expect(")")
lexer.expect("{")
body_tokens = lexer.collect_block_tokens()
lexer.push_back_remaining()
if len(params) != len(set(params)):
raise ParseError("duplicate parameter names in fn definition")
return_tokens = parse_fn_body(body_tokens)
postfix = shunting_yard(return_tokens)
body_words = []
for _ in reversed(params):
body_words.append(">r")
body_words.extend(translate_postfix(postfix, params))
for _ in params:
body_words.append("rdrop")
generated = []
emit_definition(generated, name_token, name, body_words)
ctx.inject_token_objects(generated)
}
;
:asm dup { :asm dup {
mov rax, [r12] mov rax, [r12]
sub r12, 8 sub r12, 8
@@ -264,6 +59,13 @@ compile-only
} }
; ;
:asm over {
mov rax, [r12 + 8]
sub r12, 8
mov [r12], rax
}
;
:asm swap { :asm swap {
mov rax, [r12] mov rax, [r12]
mov rbx, [r12 + 8] mov rbx, [r12 + 8]
@@ -453,6 +255,15 @@ compile-only
} }
; ;
:asm pick {
mov rcx, [r12]
add r12, 8
mov rax, [r12 + rcx * 8]
sub r12, 8
mov [r12], rax
}
;
:asm rpick { :asm rpick {
mov rcx, [r12] mov rcx, [r12]
add r12, 8 add r12, 8

BIN
test.bin

Binary file not shown.

View File

@@ -1,4 +1,5 @@
import stdlib.sl import stdlib.sl
import fn.sl
:asm mem-slot { :asm mem-slot {
lea rax, [rel print_buf] lea rax, [rel print_buf]
@@ -33,8 +34,8 @@ struct: Point
extend-syntax extend-syntax
fn fancy_add(int left, int right){ fn fancy_add(int a, int b){
return (left + right) * right; return (a + b) * b;
} }
: test-add : test-add

153
tests/run_tests.py Normal file
View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""Simple end-to-end test runner for L2.
Each test case provides an L2 program source and an expected stdout. The runner
invokes the bootstrap compiler on the fly and executes the produced binary.
"""
from __future__ import annotations
import subprocess
import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import List
ROOT = Path(__file__).resolve().parents[1]
COMPILER = ROOT / "main.py"
PYTHON = Path(sys.executable)
@dataclass
class TestCase:
name: str
source: str
expected_stdout: str
CASES: List[TestCase] = [
TestCase(
name="call_syntax_parens",
source=f"""
import {ROOT / 'stdlib.sl'}
import {ROOT / 'fn.sl'}
: main
2 40 +
puts
extend-syntax
foo(1, 2)
puts
0
;
fn foo(int a, int b){{
return a + b;
}}
""",
expected_stdout="42\n3\n",
),
TestCase(
name="loops_and_cmp",
source=f"""
import {ROOT / 'stdlib.sl'}
: main
0
5 for
1 +
next
puts
5 5 == puts
5 4 == puts
0
;
""",
expected_stdout="5\n1\n0\n",
),
TestCase(
name="override_dup_compile_time",
source=f"""
import {ROOT / 'stdlib.sl'}
: dup
6
;
compile-only
: emit-overridden
"dup" use-l2-ct
42
dup
int>string
nil
token-from-lexeme
list-new
swap
list-append
inject-tokens
;
immediate
compile-only
: main
emit-overridden
puts
0
;
""",
expected_stdout="6\n",
),
]
def run_case(case: TestCase) -> None:
print(f"[run] {case.name}")
with tempfile.TemporaryDirectory() as tmp:
src_path = Path(tmp) / f"{case.name}.sl"
exe_path = Path(tmp) / f"{case.name}.out"
src_path.write_text(case.source.strip() + "\n", encoding="utf-8")
compile_cmd = [str(PYTHON), str(COMPILER), str(src_path), "-o", str(exe_path)]
compile_result = subprocess.run(
compile_cmd,
capture_output=True,
text=True,
cwd=ROOT,
)
if compile_result.returncode != 0:
sys.stderr.write("[fail] compile error\n")
sys.stderr.write(compile_result.stdout)
sys.stderr.write(compile_result.stderr)
raise SystemExit(compile_result.returncode)
run_result = subprocess.run(
[str(exe_path)],
capture_output=True,
text=True,
cwd=ROOT,
)
if run_result.returncode != 0:
sys.stderr.write("[fail] execution error\n")
sys.stderr.write(run_result.stdout)
sys.stderr.write(run_result.stderr)
raise SystemExit(run_result.returncode)
if run_result.stdout != case.expected_stdout:
sys.stderr.write(f"[fail] output mismatch for {case.name}\n")
sys.stderr.write("expected:\n" + case.expected_stdout)
sys.stderr.write("got:\n" + run_result.stdout)
raise SystemExit(1)
print(f"[ok] {case.name}")
def main() -> None:
for case in CASES:
run_case(case)
print("[all tests passed]")
if __name__ == "__main__":
main()