from-scratch/stages/lisp0/lisp.asm
2026-06-30 20:27:06 +02:00

2116 lines
49 KiB
NASM

section .bss
buf resb 0x100
align 8,db 0
atoms times 24 resb 8
global env
env resq 1
env_tail resq 1
;;
section .text
global get_env
get_env:
mov rax, qword [rel env]
ret
;; converts char $dil to a digit with radix $rsi, returning it in $edx. $al is set to 1 if the char is a valid digit, and 0 otherwise.
to_digit:
lea eax, [rsi - 2]
cmp eax, 35
jae .invalid
movzx rdi, dil
lea edx, [rdi - 65] ; 'A' = 65
and edx, -33 ; convert to uppercase
add edx, 10 ; 'A' should map to 10
lea eax, [rdi - 48] ; '0' = 48
cmp esi, 11
cmovb edx, eax ; if radix <= 10, then take the difference from '0'
cmp edi, 58
cmovb edx, eax ; or if char < '9', then take the difference from '0'
xor eax, eax
cmp edx, esi
setb al ; al = edx < radix
ret
.invalid:
xor eax, eax
ret
;; rdi: *u8
strlen:
xor rax, rax
.strlen_loop:
cmp byte [rdi + rax], 0
je .strlen_done
inc rax
jmp .strlen_loop
.strlen_done:
ret
;; @param lhs: (rdi, rsi)
;; @param rhs: (rdx, rcx)
;; @return al
strcmp:
cmp rcx, rsi
cmovb rsi, rcx ; if rhs is shorter, use its length for the loop
xor eax, eax
.strcmp_loop:
cmp rsi, rax
jz .strcmp_equal
movzx ecx, byte [rdx + rax]
cmp byte [rdi + rax], cl
lea rax, [rax + 1]
je .strcmp_loop
seta al ; al = lhs > rhs
sbb al, 0 ; al = al - CF
ret
.strcmp_equal:
xor eax, eax
ret
;; rdi: src
;; rsi: dst
;; rdx: len
memcpy:
.loop:
test rdx, rdx
jz .done
mov al, byte [rdi]
mov byte [rsi], al
inc rsi
inc rdi
dec rdx
jmp .loop
.done:
ret
;; fn getc(*const opaque) -> u8
extern getc
;; fn peekc(*const opaque) -> u8
extern peekc
;; fn panic(msg: *const u8) -> !
extern panic_abort
;; fn alloc(size: usize, align: usize) -> *mut u8
extern alloc
;; fn dealloc(ptr: *mut u8, size: usize, align: usize)
extern dealloc
;; fn print(obj: Obj) -> ()
extern print
do_panic_abort:
xor rdi, rdi
xor rsi, rsi
call panic_abort
int 3
align 8,db 0
global nil
nil dq 1 ; the nil object, with refcount = 1
OBJ_BYTE equ 0
OBJ_CONS equ 1
OBJ_CLOS equ 2
OBJ_ATOM equ 3
OBJ_NUM equ 4
OBJ_PRIM equ 5
OBJ_STR equ 6
OBJ_ARR equ 7
ATOM_QUOTE equ 0
ATOM_TRUE equ 1
ATOM_PLUS equ 2
ATOM_MINUS equ 3
ATOM_MUL equ 4
ATOM_DIV equ 5
ATOM_REM equ 6
ATOM_LT equ 7
ATOM_EQ equ 8
ATOM_CAR equ 9
ATOM_CDR equ 10
ATOM_NOT equ 11
ATOM_LAMBDA equ 12
ATOM_DEFINE equ 13
ATOM_EVAL equ 14
ATOM_IF equ 15
ATOM_LET equ 16
ATOM_LET_STAR equ 17
ATOM_STR_LEN equ 18
ATOM_LIST_LEN equ 19
ATOM_STR_PARTS equ 20
ATOM_SYSCALL equ 21
ATOM_PRINT_ENV equ 22
QUOTE_STR db "quote"
QUOTE_STR_LEN equ $ - QUOTE_STR
TRUE_STR db "t"
TRUE_STR_LEN equ $ - TRUE_STR
PLUS_STR db "+"
PLUS_STR_LEN equ $ - PLUS_STR
MINUS_STR db "-"
MINUS_STR_LEN equ $ - MINUS_STR
MUL_STR db "*"
MUL_STR_LEN equ $ - MUL_STR
DIV_STR db "/"
DIV_STR_LEN equ $ - DIV_STR
REM_STR db "%"
REM_STR_LEN equ $ - REM_STR
LT_STR db "<"
LT_STR_LEN equ $ - LT_STR
EQ_STR db "="
EQ_STR_LEN equ $ - EQ_STR
CAR_STR db "car"
CAR_STR_LEN equ $ - CAR_STR
CDR_STR db "cdr"
CDR_STR_LEN equ $ - CDR_STR
NOT_STR db "not"
NOT_STR_LEN equ $ - NOT_STR
LAMBDA_STR db "lambda"
LAMBDA_STR_LEN equ $ - LAMBDA_STR
DEFINE_STR db "define"
DEFINE_STR_LEN equ $ - DEFINE_STR
EVAL_STR db "eval"
EVAL_STR_LEN equ $ - EVAL_STR
IF_STR db "if"
IF_STR_LEN equ $ - IF_STR
LET_STR db "let"
LET_STR_LEN equ $ - LET_STR
LET_STAR_STR db "let*"
LET_STAR_STR_LEN equ $ - LET_STAR_STR
STR_LEN_STR db "str-len"
STR_LEN_STR_LEN equ $ - STR_LEN_STR
LIST_LEN_STR db "list-len"
LIST_LEN_STR_LEN equ $ - LIST_LEN_STR
STR_PARTS_STR db "str-parts"
STR_PARTS_STR_LEN equ $ - STR_PARTS_STR
SYSCALL_STR db "syscall"
SYSCALL_STR_LEN equ $ - SYSCALL_STR
PRINT_ENV_STR db "print-env"
PRINT_ENV_STR_LEN equ $ - PRINT_ENV_STR
SPACE_CHAR db "\Space"
SPACE_CHAR_LEN equ $ - SPACE_CHAR
NL_CHAR db "\NL"
NL_CHAR_LEN equ $ - NL_CHAR
TAB_CHAR db "\Tab"
TAB_CHAR_LEN equ $ - TAB_CHAR
;; folds all elements in the list $rdi using the binary function $rdx in the environment $rsi with an accumulator value of $rcx, returning the result in $rax
;; fn fold_list(List<T>, Env, (U, T, Env) -> U, U) -> U
fold_list:
sub rsp, 32
mov qword [rsp], rdi ; rest
mov qword [rsp + 8], rsi ; env
mov qword [rsp + 16], rcx ; acc
mov qword [rsp + 24], rdx ; func
.loop:
mov rdi, qword [rsp]
call car_cdr
mov qword [rsp], rdx ; rest = cdr
mov rsi, rax
mov rdi, qword [rsp + 16] ; $rdi = acc
mov rdx, qword [rsp + 8] ; $rdx = env
mov rax, qword [rsp + 24] ; $rax = func
call rax
mov qword [rsp + 16], rax ; update acc to result of func(acc, car)
mov rdi, qword [rsp] ; $rdi = rest
lea rsi, [rel nil]
cmp rdi, rsi
jne .loop
mov rax, qword [rsp + 16] ; return acc
add rsp, 32
ret
;; like fold_list, but requires that the list is non-empty
;; fn reduce_list(List<T>, Env, (T, T, Env) -> T) -> T
reduce_list:
mov rcx, rdx ; func
call car_cdr
lea rdi, [rel nil]
cmp rdi, rdx
je .done
mov rdi, rdx ; list
mov rdx, rcx ; func
mov rcx, rax ; acc
call fold_list
ret
.done:
mov rax, rdx
ret
;; like reduce_list, calls a predicate instead of a binary function, and returns "t" if the predicate returns non-zero for any pair of adjacent elements, and nil otherwise
;; predicate: fn (T, T, Env) -> bool
any2_list:
sub rsp, 24
mov qword [rsp], rdi ; rest
mov qword [rsp + 8], rsi ; env
mov qword [rsp + 16], rdx ; pred
.loop:
call car_cdr
mov rsi, rax
mov qword [rsp], rdx
mov rdi, rdx
lea rdx, [rel nil]
cmp rdi, rdx
je .false
call get_car ; car(rest)
mov rdi, rax
mov rdx, qword [rsp + 8] ; env
mov rax, qword [rsp + 16] ; pred
call rax
; func(car(rest), car(cdr(rest)), env)
test al, al
jnz .true
jmp .loop
.true:
mov al, 1
add rsp, 24
ret
.false:
xor al, al
add rsp, 24
ret
unwrap_num_ptr:
call obj_addr_part
sub rdi, rax
cmp rdi, OBJ_NUM
jne do_panic_abort
add rax, 8 ; point to the number value
ret
unwrap_num:
call unwrap_num_ptr
mov rax, qword [rax] ; unwrap the number value
ret
;; |a, b| {a += b; a}
p_add_inner:
push rdi
call unwrap_num_ptr
mov rdx, rax
mov rdi, rsi
call unwrap_num
add qword [rdx], rax
pop rax
ret
;; |a, b| {a -= b; a}
p_sub_inner:
push rdi
call unwrap_num_ptr
mov rdx, rax
mov rdi, rsi
call unwrap_num
sub qword [rdx], rax
pop rax
ret
;; |a, b| {a *= b; a}
p_imul_inner:
push rdi
call unwrap_num_ptr
mov rcx, rax
mov rdi, rsi
call unwrap_num
mov rdi, qword [rcx]
imul rdi
mov qword [rcx], rax
pop rax
ret
;; |a, b| {a /= b; a}
p_idiv_inner:
push rdi
call unwrap_num_ptr
mov rcx, rax
mov rdi, rsi
call unwrap_num
mov rdi, rax
mov rax, qword [rcx]
xor rdx, rdx
idiv rdi
mov qword [rcx], rax
pop rax
ret
;; |a, b| {a %= b; a}
p_irem_inner:
push rdi
call unwrap_num_ptr
mov rcx, rax
mov rdi, rsi
call unwrap_num
mov rdi, rax
mov rax, qword [rcx]
xor rdx, rdx
idiv rdi
mov qword [rcx], rdx
pop rax
ret
;; |a, b| {a < b}
p_lt_inner:
call unwrap_num
mov rdx, rax
mov rdi, rsi
call unwrap_num
cmp rdx, rax
lea rax, [rel nil]
lea rdx, [rel atoms]
mov rdx, [rdx + ATOM_TRUE * 8] ; get the "t" atom
cmovl rax, rdx ; if a < b, return "t", else return nil
ret
p_neq_inner:
call obj_eq
ret
p_lt:
call eval_list
mov rdi, rax
call try_car_cdar
mov rdi, rax
mov rsi, rdx
call p_lt_inner
ret
p_eq:
push rsi
call eval_list
mov rdi, rax
pop rsi
lea rdx, [rel p_neq_inner]
call any2_list ; list.any(|a, b| a != b)
test al, al ; 1 if any pair is unequual
lea rax, [rel nil]
lea rsi, [rel atoms]
cmovz rax, [rsi + ATOM_TRUE * 8] ; if no pair is unequal, return "t", else return nil
ret
make_num:
push rdi
mov rdi, 16
mov rsi, 8
call alloc
mov dword [rax], 1 ; refcount = 1
pop rdi
mov qword [rax + 8], rdi ; value = number
mov rdi, rax
mov rsi, OBJ_NUM
call obj_set_tag
ret
;; sum list $rdi in env $rsi
p_add:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, 0
call make_num
mov rcx, rax ; acc
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_add_inner]
call fold_list
add rsp, 24
ret
p_sub:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, 0
call make_num
mov rcx, rax ; acc
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_sub_inner]
call fold_list
add rsp, 24
ret
p_mul:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, 1
call make_num
mov rcx, rax ; acc
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_imul_inner]
call fold_list
add rsp, 24
ret
p_div:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_idiv_inner]
call reduce_list
add rsp, 24
ret
p_rem:
sub rsp, 24
mov qword [rsp + 8], rsi ; env
call eval_list
mov qword [rsp], rax ; evaled list
mov rdi, qword [rsp] ; $rdi = evaled list
mov rsi, qword [rsp + 8] ; $rsi = env
lea rdx, [rel p_irem_inner]
call reduce_list
add rsp, 24
ret
p_car:
call eval_list
mov rdi, rax
call get_caar
ret
p_cdr:
call eval_list
mov rdi, rax
call get_cadr
ret
p_not:
call eval_list
mov rdi, rax
call get_car
lea rsi, [rel nil]
cmp rsi, rax
lea rax, qword [rel atoms]
mov rax, [rax + ATOM_TRUE * 8] ; get the "t" atom
cmovne rax, rsi
ret
p_quote:
call get_car
ret
p_lambda:
push rsi ; save env
call get_car
push rax ; params
call get_cdr
mov rdi, rax
call get_car
mov rsi, rax ; body
pop rdi ; params
pop rdx
cmp rdx, qword [rel env] ; if env is global env, then use nil as closure env
lea rcx, [rel nil]
cmove rdx, rcx
call clos
ret
p_define:
call get_car
push rax ; params
call get_cdr
mov rdi, rax
call get_car
mov rdi, rax
call eval
mov rsi, rax ; evaled value
mov rdi, qword [rsp] ; name
call genv_append
pop rax ; name
ret
p_eval:
push rsi
call eval_list
mov rdi, rax
call get_car
mov rdi, rax
pop rsi
call eval
ret
;; (if cond then else)
;; cond = car($rdi)
;; then = car(cdr($rdi))
;; else = car(cdr(cdr($rdi)))
;; result = eval(cond) ? eval(then) : eval(else)
p_if:
; eval(car(cdr(not(eval(car($rdi), $rsi)) ? cdr(rdi) : $rdi)) $rsi)
sub rsp, 16
mov qword [rsp], rdi ; save $rdi = arg
mov qword [rsp + 8], rsi ; save $rsi = env
call get_car
mov rdi, rax
call eval
mov rdi, rax
call is_nil
mov cl, al
mov rdi, qword [rsp] ; restore $rdi = arg
call get_cdr
mov rdi, rax
call get_car
mov rsi, rax
call get_cdr
mov rdi, rax
call get_car
test cl, cl
cmovnz rsi, rax
mov rdi, rsi
mov rsi, qword [rsp + 8] ; restore $rsi = env
call eval
add rsp, 16
ret
;; (Env, (var val), Env) -> Env
;; $rdi = acc-env, $rsi = (var val), $rdx = genv
p_let_inner:
sub rsp, 16
mov qword [rsp], rdi ; acc-env
mov qword [rsp + 8], rsi ; (var val)
mov rdi, rsi
call get_cdr
mov rdi, rax ; (val . nil)
call get_car
mov rdi, rax ; val
mov rsi, rdx ; genv
call eval
mov rsi, rax ; val
mov rdi, qword [rsp + 8] ; (var val)
call get_car
mov rdi, rax ; var
mov rdx, qword [rsp] ; acc-env
call prepend ; ((var . val) . acc-env)
add rsp, 16
ret
;; (Env, (var val), Env) -> Env
;; $rdi = acc-env, $rsi = (var val), $rdx = genv
p_let_star_inner:
sub rsp, 16
mov qword [rsp], rdi ; acc-env
mov qword [rsp + 8], rsi ; (var val)
mov rdi, rsi
call get_cdar
mov rdi, rax
mov rsi, qword [rsp] ; acc-env
call eval
mov rsi, rax ; val
mov rdi, qword [rsp + 8] ; (var val)
call get_car
mov rdi, rax ; var
mov rdx, qword [rsp] ; acc-env
call prepend ; ((var . val) . acc-env)
add rsp, 16
ret
;; (let ((var1 val1) (var2 val2) ...) body)
;; $rdi = (bindings expr) where bindings = ((var1 val1) (var2 val2) ...)
p_let:
sub rsp, 24
call get_car
mov qword [rsp], rax ; bindings
call get_cdar
mov qword [rsp + 8], rax ; body
mov qword [rsp + 16], rsi ; in_env
mov rdi, qword [rsp] ; $rdi = bindings
mov rsi, qword [rsp + 16] ; $rsi = in_env
mov rcx, rsi
lea rdx, [rel p_let_inner]
call fold_list
mov rsi, rax ; body_env = fold_list(...)
mov rdi, qword [rsp + 8] ; $rdi =
call eval
add rsp, 24
ret
p_let_star:
sub rsp, 24
call get_car
mov qword [rsp], rax ; bindings
call get_cdar
mov qword [rsp + 8], rax ; body
mov qword [rsp + 16], rsi ; in_env
mov rdi, qword [rsp] ; $rdi = bindings
mov rsi, qword [rsp + 16] ; $rsi = in_env
mov rcx, rsi
lea rdx, [rel p_let_star_inner]
call fold_list
mov rsi, rax ; body_env = fold_list(...)
mov rdi, qword [rsp + 8] ; $rdi =
call eval
add rsp, 24
ret
; p_let_rec:
;; returns the length of the input list as a number
p_list_len:
call eval_list
xor rcx, rcx ; acc = 0
.len_loop:
mov rdi, rax ; list
call is_nil
test al, al
jnz .len_done
inc rcx
call get_cdr
jmp .len_loop
.len_done:
push rcx
mov rdi, 16
mov rsi, 8
call alloc
mov dword [rax], 1 ; refcount = 1
pop rcx
mov qword [rax + 8], rcx ; value = length
mov rdi, rax
mov rsi, OBJ_NUM
call obj_set_tag
ret
;; concatenates a list of strings into a single string
;; (str_concat "Hello," " my" " name is" " Alice") -> "Hello, my name is Alice"
p_str_concat:
;; appends a list of chars to a string
;; (str_append "hello" \Space \w \o \r \l \d) -> "hello world"
p_str_append:
;; return the nth element of a string
p_str_nth:
;; sets the nth element of a string to a char
p_set_nth:
;; returns a new string that is a substring from a a+b of the input string
;; (substr "hello world" 0 5) -> "hello"
p_str_substr:
;; returns 't if the haystack-string contains the needle-char , and nil otherwise
p_str_contains_char:
;; returns 't if the haystack-string contains the needle-string, and nil otherwise
;; (contains_str? "hello world" "lo wo") -> 't
p_str_contains_str:
;; returns the length of the input string as a number
p_str_len:
call eval_list
mov rdi, rax
call get_car
mov rdi, rax
call obj_tag_part
cmp al, OBJ_STR
jne do_panic_abort
call obj_addr_part
movzx rax, dword [rax + 4]
push rax
mov rdi, 16
mov rsi, 8
call alloc
mov rdi, rax
mov dword [rdi], 1 ; refcount = 1
pop rax
mov qword [rdi + 8], rax ; value = string length
mov rsi, OBJ_NUM
call obj_set_tag
ret
;; returns the pointer and length part of the string object as a pair of numbers
p_str_decompose:
call eval_list
mov rdi, rax
call get_car
mov rdi, rax
call obj_tag_part
cmp al, OBJ_STR
jne do_panic_abort
sub rsp, 8
call obj_addr_part
movzx rdi, dword [rax + 4] ; length
mov rsi, qword [rax + 8] ; ptr
mov qword [rsp], rsi
call make_num
mov rdi, qword [rsp] ; ptr
mov qword [rsp], rax ; length number
call make_num
mov rdi, rax ; ptr number
mov rsi, qword [rsp] ; length number
call cons
add rsp, 8
ret
;; returns the ascii value of the char
p_char_code:
call eval_list
mov rdi, rax
call get_car
mov rdi, rax
call obj_tag_part
cmp al, OBJ_BYTE
jne do_panic_abort
shr rax, 8
and rax, 0xFF
mov rdi, rax
call make_num
ret
;; returns a char object representing the ascii value
p_make_char:
call eval_list
mov rdi, rax
call get_car
mov rdi, rax
call unwrap_num
xor rax, rax
mov ah, cl
mov al, 0 ; OBJ_BYTE
ret
;; performs a system call, returning the result as a number
;; (syscall num arg1 arg2 arg3) -> syscall(num, arg1, arg2, arg3)
p_syscall:
push r12
push r13
call eval_list
mov rdi, rax
; r11 = syscall num
; r12 = arg0
xor r11, r11
xor r12, r12
xor r13, r13
call ._next_arg ; syacall num
jnz .do_syscall
mov r11, rcx
call ._next_arg ; arg0
jnz .do_syscall
mov r12, rcx
call ._next_arg ; arg1
jnz .do_syscall
mov rsi, rcx
call ._next_arg ; arg2
jnz .do_syscall
mov r13, rcx
call ._next_arg ; arg3
jnz .do_syscall
mov r10, rcx
call ._next_arg ; arg4
jnz .do_syscall
mov r8, rcx
call ._next_arg ; arg5
jnz .do_syscall
mov r9, rcx
.do_syscall:
mov rax, r11 ; syscall num
mov rdi, r12 ; arg0
mov rdx, r13 ; arg2
syscall
mov rdi, rax
call make_num
pop r13
pop r12
ret
;; fn _next_arg(list) -> ($rcx=arg, $rdi=rest)
._next_arg:
call is_nil
test al, al
jnz ._next_arg_done
call car_cdr
mov rdi, rax ; arg
call unwrap_num
mov rdi, rdx ; rest
mov rcx, rax ; arg value
mov al, 0
test al, al
._next_arg_done:
ret
p_print_env:
mov rdi, rsi
push rdi
call print
pop rdi
lea rax, [rel nil]
ret
global init_env
;; appends var=$rdi bound to val=$rsi to the global environment
genv_append:
call cons
mov rdi, rax
lea rsi, [rel nil]
call cons
mov rsi, rax
mov rdi, qword [rel env_tail]
call set_cdr
mov qword [rel env_tail], rsi
ret
;; appends var=atoms[$rdi] bound to val=prim($rsi) to the global environment
genv_append_atom_prim_by_idx:
lea rax, [rel atoms]
mov rdi, [rax + rdi * 8] ; get the atom pointer
call make_prim_pair
mov rdi, rax
lea rsi, [rel nil]
call cons
mov rsi, rax
mov rdi, qword [rel env_tail]
call set_cdr
mov qword [rel env_tail], rsi
ret
init_env:
call init_atoms
lea rdi, [rel atoms]
mov rsi, [rdi + ATOM_TRUE * 8] ; get the "t" atom
mov rdi, rsi
call cons ; (t . t)
mov rdi, rax ; $rdi = (t . t)
lea rsi, [rel nil]
call cons ; ((t . t) . nil)
mov qword [rel env], rax ; env = ((t . t) . nil)
mov qword [rel env_tail], rax ; env_tail = ((t . t) . nil)
mov rdi, ATOM_QUOTE
lea rsi, [rel p_quote]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_PLUS
lea rsi, [rel p_add]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_MINUS
lea rsi, [rel p_sub]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_MUL
lea rsi, [rel p_mul]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_DIV
lea rsi, [rel p_div]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_REM
lea rsi, [rel p_rem]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_LT
lea rsi, [rel p_lt]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_EQ
lea rsi, [rel p_eq]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_CAR
lea rsi, [rel p_car]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_CDR
lea rsi, [rel p_cdr]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_NOT
lea rsi, [rel p_not]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_LAMBDA
lea rsi, [rel p_lambda]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_DEFINE
lea rsi, [rel p_define]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_EVAL
lea rsi, [rel p_eval]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_IF
lea rsi, [rel p_if]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_LET
lea rsi, [rel p_let]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_LET_STAR
lea rsi, [rel p_let_star]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_STR_LEN
lea rsi, [rel p_str_len]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_LIST_LEN
lea rsi, [rel p_list_len]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_STR_PARTS
lea rsi, [rel p_str_decompose]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_SYSCALL
lea rsi, [rel p_syscall]
call genv_append_atom_prim_by_idx
mov rdi, ATOM_PRINT_ENV
lea rsi, [rel p_print_env]
call genv_append_atom_prim_by_idx
mov rax, qword [rel env]
ret
global init_atoms
;; makes a (atom . prim) cons cell out of an atom symbol and a primitive function pointer
make_prim_pair:
push rdi
push rsi
mov rdi, 16
mov rsi, 8
call alloc
mov dword [rax], 1 ; refcount = 1
pop rsi
mov qword [rax + 8], rsi ; pointer to primitive function
mov rdi, rax
mov rsi, OBJ_PRIM
call obj_set_tag
mov rsi, rax
pop rdi
call obj_inc_ref
call cons
ret
make_atom:
push rdi
push rsi
mov rdi, 16
mov rsi, 8
call alloc
pop rsi
pop rdi
mov dword [rax], 1 ; refcount = 1
mov dword [rax + 4], esi ; length of string
mov qword [rax + 8], rdi ; pointer to string
mov rdi, rax
mov rsi, OBJ_ATOM
call obj_set_tag
ret
init_atoms:
mov rdi, QUOTE_STR
mov rsi, QUOTE_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx], rax ; atoms[0] = "quote"
mov rdi, TRUE_STR
mov rsi, TRUE_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + 8], rax ; atoms[1] = "t"
mov rdi, PLUS_STR
mov rsi, PLUS_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_PLUS * 8], rax ; atoms[2] = "+"
mov rdi, MINUS_STR
mov rsi, MINUS_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_MINUS * 8], rax ; atoms[3] = "-"
mov rdi, MUL_STR
mov rsi, MUL_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_MUL * 8], rax ; atoms[4] = "*"
mov rdi, DIV_STR
mov rsi, DIV_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_DIV * 8], rax ; atoms[5] = "/"
mov rdi, REM_STR
mov rsi, REM_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_REM * 8], rax ; atoms[6] = "%"
mov rdi, LT_STR
mov rsi, LT_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_LT * 8], rax ; atoms[7] = "<"
mov rdi, EQ_STR
mov rsi, EQ_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_EQ * 8], rax ; atoms[8] = "="
mov rdi, CAR_STR
mov rsi, CAR_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_CAR * 8], rax ; atoms[9] = "car"
mov rdi, CDR_STR
mov rsi, CDR_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_CDR * 8], rax ; atoms[10] = "cdr"
mov rdi, NOT_STR
mov rsi, NOT_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_NOT * 8], rax ; atoms[11] = "not"
mov rdi, LAMBDA_STR
mov rsi, LAMBDA_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_LAMBDA * 8], rax ; atoms[12] = "lambda"
mov rdi, DEFINE_STR
mov rsi, DEFINE_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_DEFINE * 8], rax ; atoms[13] = "define"
mov rdi, EVAL_STR
mov rsi, EVAL_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_EVAL * 8], rax ; atoms[14] = "eval"
mov rdi, IF_STR
mov rsi, IF_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_IF * 8], rax ; atoms[15] = "if"
mov rdi, LET_STR
mov rsi, LET_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_LET * 8], rax ; atoms[16] = "let"
mov rdi, LET_STAR_STR
mov rsi, LET_STAR_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_LET_STAR * 8], rax ; atoms[17] = "let*"
mov rdi, STR_LEN_STR
mov rsi, STR_LEN_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_STR_LEN * 8], rax ; atoms[18] = "str-len"
mov rdi, LIST_LEN_STR
mov rsi, LIST_LEN_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_LIST_LEN * 8], rax ; atoms[21] = "list-len"
mov rdi, STR_PARTS_STR
mov rsi, STR_PARTS_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_STR_PARTS * 8], rax ; atoms[19] = "str-parts"
mov rdi, SYSCALL_STR
mov rsi, SYSCALL_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_SYSCALL * 8], rax ; atoms[20] = "syscall"
mov rdi, PRINT_ENV_STR
mov rsi, PRINT_ENV_STR_LEN
call make_atom
lea rcx, [rel atoms]
mov qword [rcx + ATOM_PRINT_ENV * 8], rax ; atoms[22] = "print-env"
ret
;; returns 0 if the objects pointed at by $rdi and $rsi are equal, and 1 otherwise.
obj_eq:
sub rsp, 24
cmp rdi, rsi
je .equal
mov qword [rsp], rdi ; save obj1
mov qword [rsp + 8], rsi ; save obj2
call obj_tag_part
mov byte [rsp + 16], al ; save tag1
mov rdi, qword [rsp + 8] ; restore obj2
call obj_tag_part
cmp al, byte [rsp + 16] ; compare tag2 with tag1
jne .not_equal
cmp al, OBJ_BYTE
je .compare_bytes
cmp al, OBJ_ATOM
je .compare_atoms
cmp al, OBJ_STR
je .compare_strs
cmp al, OBJ_NUM
je .compare_nums
cmp al, OBJ_CONS
je .compare_cons
cmp al, OBJ_CLOS
je .compare_clos
cmp al, OBJ_PRIM
je .compare_prims
jmp .equal
.compare_bytes:
jmp do_panic_abort
.compare_atoms:
.compare_strs:
call obj_addr_part
mov qword [rsp + 8], rax
mov rdi, qword [rsp] ; restore obj1
call obj_addr_part
movzx rdx, dword [rax + 4] ; len1
mov rcx, qword [rsp + 8] ; ptr2
movzx rbx, dword [rcx + 4] ; len2
cmp rdx, rbx
jne .not_equal
mov rdx, qword [rax + 8] ; ptr1
mov rsi, qword [rcx + 8] ; ptr2
cmp rdx, rsi
je .equal
mov rdi, rdx
mov rdx, rsi
mov rsi, rbx
mov rcx, rbx
call strcmp
test al, al
je .equal
jmp .not_equal
.compare_nums:
call obj_addr_part
mov qword [rsp + 8], rax
mov rdi, qword [rsp] ; restore obj1
call obj_addr_part
mov rdx, qword [rax + 8] ; num1
mov rcx, qword [rsp + 8]
mov rcx, qword [rcx + 8] ; num2
cmp rdx, rcx
je .equal
jmp .not_equal
.compare_cons:
.compare_clos:
mov rdi, qword [rsp] ; restore obj1
call get_cdr
mov qword [rsp + 8], rax ; save cdr1
call get_car
mov rcx, rax ; $rcx = car1
mov rdi, qword [rsp + 16] ; $rdi = obj2
call get_cdr
mov qword [rsp + 16], rax ; save cdr2
call get_car
mov rdi, rcx ; $rdi = car1
mov rsi, rax ; $rsi = car2
call obj_eq
test al, al
jne .not_equal
mov rdi, qword [rsp + 8] ; $rdi = cdr1
mov rsi, qword [rsp + 16] ; $rsi = cdr2
call obj_eq
test al, al
jne .not_equal
jmp .equal
.compare_prims:
call obj_addr_part
mov rax, qword [rax + 8] ; prim1
mov qword [rsp + 16], rax
mov rdi, qword [rsp] ; restore obj1
call obj_addr_part
mov rdx, qword [rax + 8] ; prim1
mov rcx, qword [rsp + 16] ; prim2
cmp rdx, rcx
je .equal
jmp .not_equal
.equal:
xor eax, eax
add rsp, 24
ret
.not_equal:
mov eax, 1
add rsp, 24
ret
dtor_table:
dd 0
dd dtor_table - dtor_cons
dd dtor_table - dtor_clos
dd dtor_table - dtor_atom
dd dtor_table - dtor_num
dd 0
dd dtor_table - dtor_str
dd 0
dtor_nil:
dtor_prim:
ret
dtor_cons:
dtor_clos:
push rdi
mov rdi, qword [rdi + 8] ; car
call obj_dec_ref
mov rdi, qword [rsp]
mov rdi, qword [rdi + 16] ; cdr
call obj_dec_ref
pop rdi
mov rsi, 24
mov rdx, 8
call dealloc
ret
dtor_atom:
dtor_str:
push rdi
mov rax, rdi
mov rdi, qword [rax + 8] ; pointer to string
movzx rsi, dword [rax + 4] ; length of string
mov rdx, 1
call dealloc
pop rdi
dtor_num:
mov rsi, 16
mov rdx, 8
call dealloc
ret
obj_inc_ref:
call is_nil
test al, al
jnz .done ; nil is immortal, so we're done
call obj_addr_part
inc dword [rax] ; increment the refcount
.done:
ret
obj_dec_ref:
call is_nil
test al, al
jnz .done ; nil is immortal, so we're done
call obj_tag_part
mov rsi, rax
call obj_addr_part
dec dword [rax] ; decrement the refcount
jnz .done ; if refcount != 0, we're done
lea rcx, qword [rel dtor_table]
movsxd rsi, [rcx + rsi * 4] ; get the offset of the destructor
test rsi, rsi
jz .done ; if there's no destructor, we're done
add rcx, rsi ; p_dtor = &dtor_table + dtor_table[tag]
mov rdi, rax ; set the argument for the destructor (the object pointer)
call rcx ; call the destructor
.done:
ret
obj_assert_tag:
push rax
call obj_tag_part
cmp al, sil
jne do_panic_abort
pop rax
ret
;; returns the tag part of the object handle in $rdi
obj_tag_part:
mov rax, rdi
and al, 0x7
movzx rax, al
ret
obj_into_addr_part:
and rdi, -8
ret
;; returns the address part of the object handle in $rdi
obj_addr_part:
mov rax, rdi
and rax, -8
ret
obj_set_tag_in_place:
and rdi, -8 ; clear the tag bits
or rdi, rsi ; set the new tag bits
ret
obj_set_tag:
mov rax, rdi
and rax, -8 ; clear the tag bits
or rax, rsi ; set the new tag bits
ret
;; construcst a closure object with params $rdi, body $rsi, and env $rdx
clos:
push rdx ; env
call cons ; (params . body)
mov rdi, rax
pop rsi ; env
call cons ; ((params . body) . env)
mov rdi, rax
mov rsi, OBJ_CLOS
call obj_set_tag
ret
;; constructs a cons cell ($rdi . $rsi) and returns an object pointer to it
cons:
push rdi
push rsi
mov rdi, 24 ; struct { refcount: usize, car: *const Object, cdr: *const Object }
mov rsi, 8
call alloc
pop rsi
pop rdi
mov dword [rax], 1 ; refcount = 1
mov qword [rax + 8], rdi ; car = a
mov qword [rax + 16], rsi ; cdr = b
mov rdi, rax
mov rsi, OBJ_CONS
call obj_set_tag
ret
;; returns the car and cdr of the cons cell $rdi in $rax and $rcx
;; clobers $rax, $rdx, preserves $rdi
car_cdr:
call obj_tag_part
cmp al, OBJ_CONS
jne do_panic_abort
call obj_addr_part
mov rdx, qword [rax + 16] ; cdr
mov rax, qword [rax + 8] ; car
ret
;; (a . (b . _)) => $rax = a, $rdx = b
;; (a . nil) => $rax = a, $rdx = nil
try_car_cdar:
call car_cdr
lea rdi, [rel nil]
cmp rdi, rdx
je .done
mov rdi, rdx
and rdx, 7
cmp dl, OBJ_CONS
jne do_panic_abort
call obj_into_addr_part
mov rdx, qword [rdi + 8] ; cdar
.done:
ret
get_car:
call obj_tag_part
cmp al, OBJ_CONS
jne .invalid
call obj_addr_part
mov rax, qword [rax + 8] ; return the car
ret
.invalid:
xor rdi, rdi
call panic_abort
get_cdr:
call obj_tag_part
cmp al, OBJ_CONS
jne .invalid
call obj_addr_part
mov rax, qword [rax + 16] ; return the cdr
ret
.invalid:
xor rdi, rdi
call panic_abort
get_caar:
call get_car
mov rdi, rax
call get_car
ret
get_cadr:
call get_car
mov rdi, rax
call get_cdr
ret
get_cdar:
call get_cdr
mov rdi, rax
call get_car
ret
get_cddr:
call get_cdr
mov rdi, rax
call get_cdr
ret
set_cdr:
call obj_tag_part
cmp al, OBJ_CONS
jne .invalid
call obj_addr_part
mov qword [rax + 16], rsi ; set the cdr to
ret
.invalid:
xor rdi, rdi
call panic_abort
get_clos_env:
call obj_tag_part
cmp al, OBJ_CLOS
jne do_panic_abort
call obj_addr_part
mov rax, qword [rax + 16] ; return the env
ret
get_clos_params:
call obj_tag_part
cmp al, OBJ_CLOS
jne do_panic_abort
call obj_addr_part
mov rdi, qword [rax + 8] ; return the (params . body)
call get_car
ret
get_clos_body:
call obj_tag_part
cmp al, OBJ_CLOS
jne do_panic_abort
call obj_addr_part
mov rdi, qword [rax + 8] ; return the (params . body)
call get_cdr
ret
;; returns 1 if the result of `peekc($rdi)` is $sil
;; treats all characters less than ' ' as spaces.
is_ch:
push rsi
call peekc
pop rsi
cmp al, ' '
setbe cl ; cl = al <= ' '
movzx ecx, cl
mov edx, ' '
mul ecx, edx ; cl = (al < ' ') ? ' ' : 0
cmp sil, ' '
cmove ax, cx ; al = (sil < ' ') ? cl : al
cmp al, sil
setz al ; al = al == sil
ret
next_token:
push r14
xor r14, r14
sub rsp, 16
mov qword [rsp + 8], 0 ; flags
mov qword [rsp], rdi ; source iterator
.skip_spaces:
mov rsi, ' '
call is_ch
test al, al
mov rdi, qword [rsp]
jz .skip_spaces_done
call getc
mov rdi, qword [rsp]
jmp .skip_spaces
.skip_spaces_done:
call peekc
cmp al, '('
je .leading_kw
cmp al, ')'
je .leading_kw
cmp al, "'"
je .leading_kw
cmp al, `"`
sete al
mov byte [rsp + 8], al ; set string-ness if the next char is a quote
jmp .eat
.leading_kw:
mov rdi, qword [rsp]
call getc
lea rcx, [rel buf]
lea rcx, [rcx + r14]
mov byte [rcx], al
inc r14
jmp .done
.eat:
mov rdi, qword [rsp]
call getc
; handle escapes if we're in an escape sequence
cmp byte [rsp + 9], 1
jne .skip_escape
movzx ecx, al
cmp al, `n`
mov edi, `\n`
cmove ecx, edi
cmp al, `t`
mov edi, `\t`
cmove ecx, edi
cmp al, `r`
mov edi, `\r`
cmove ecx, edi
lea rdi, [rel buf]
mov byte [rdi + r14], cl
mov byte [rsp + 9], 0
inc r14
jmp .eat
.skip_escape:
cmp al, `\\`
sete cl
mov byte [rsp + 9], cl
je .eat
lea rcx, [rel buf]
lea rcx, [rcx + r14]
mov byte [rcx], al
inc r14
cmp al, `"`
sete dl
mov cl, byte [rsp + 8] ; get string-ness
xchg cl, dl
shl dl, cl ; string-ness = 2^(num_quotes_eaten)
mov byte [rsp + 8], dl
cmp dl, 2
jg .done
cmp dl, 0
jne .eat
mov rdi, qword [rsp]
mov rsi, ' '
call is_ch
test al, al
jnz .done
mov rdi, qword [rsp]
mov rsi, '('
call is_ch
test al, al
jnz .done
mov rdi, qword [rsp]
mov rsi, ')'
call is_ch
test al, al
jnz .done
jmp .eat
.done:
lea rcx, [rel buf]
lea rax, [rcx + r14]
mov byte [rax], 0
add rsp, 16
pop r14
mov al, byte [rel buf]
ret
parse_cur_token:
push rdi
cmp byte [rel buf], '('
je .list
cmp byte [rel buf], "'"
je .quote
cmp byte [rel buf], `"`
je .string
cmp byte [rel buf], `\\`
je .char
pop rdi
call parse_atom
ret
.list:
pop rdi
call parse_list
ret
.quote:
pop rdi
call parse_quote
ret
.string:
pop rdi
call parse_string
ret
.char:
pop rdi
call parse_char
ret
;; fn parse_string(&mut iter) -> Char
parse_char:
sub rsp, 8
lea rdi, [rel buf]
call strlen
mov qword [rsp], rax ; len
lea rdi, [rel buf]
mov rsi, rax
lea rdx, [rel SPACE_CHAR]
mov rcx, SPACE_CHAR_LEN
call strcmp
test al, al
je .is_space
lea rdi, [rel buf]
mov rsi, qword [rsp] ; len
lea rdx, [rel NL_CHAR]
mov rcx, NL_CHAR_LEN
call strcmp
test al, al
je .is_newline
lea rdi, [rel buf]
mov rsi, qword [rsp] ; len
lea rdx, [rel TAB_CHAR]
mov rcx, TAB_CHAR_LEN
call strcmp
test al, al
je .is_tab
lea rdi, [rel buf]
mov ah, byte [rdi + 1] ; get the char after the backslash
.done:
add rsp, 8
mov al, 0 ; OBJ_BYTE
ret
.is_space:
mov ah, ' '
jmp .done
.is_newline:
mov ah, `\n`
jmp .done
.is_tab:
mov ah, `\t`
jmp .done
;; fn parse_string(&mut iter) -> Box<[u8]>
parse_string:
push r12
xor r12, r12
.loop:
; scan for closing quote
lea rax, [rel buf]
mov al, byte [rax + r12 + 1]
; cmp al, `\\`
; je .escape
cmp al, `"`
je .done
test al, al
jz .invalid
inc r12
jmp .loop
.done:
mov rdi, r12
mov rsi, 1
call alloc
lea rdi, [rel buf]
inc rdi
mov rsi, rax
mov rdx, r12
push rax
call memcpy
mov rdi, 16
mov rsi, 8
call alloc
mov dword [rax], 1 ; refcount = 1
mov dword [rax + 4], r12d ; length of string
pop rdi
mov qword [rax + 8], rdi ; pointer to string
mov rdi, rax
mov rsi, OBJ_STR
call obj_set_tag
pop r12
ret
.invalid:
xor rdi, rdi
call panic_abort
parse_num:
push r12
sub rsp, 16
mov qword [rsp], 0 ; acc = 0
mov dword [rsp + 8], 10 ; radix = 10
lea r12, [rel buf]
cmp byte [r12], '-' ; check for optional leading '-'
jne .skip_sign
mov qword [rsp], -1
inc r12 ; index++
.skip_sign:
cmp byte [r12], '0'
jne .skip_radix
inc r12 ; index++
mov dil, byte [r12]
cmp dil, 'x'
jne .skip_radix
inc r12 ; index++
mov qword [rsp + 4], 16 ; radix = 16
.skip_radix:
mov dil, byte [r12]
test dil, dil
jz .done
movzx rsi, dword [rsp + 8] ; radix
call to_digit
test al, al
jz .done
mov rax, qword [rsp] ; acc
mov rcx, rdx ; digit
imul rax, rsi ; acc *= radix
add rax, rcx ; acc += digit
mov qword [rsp], rax
inc r12 ; index++
jmp .skip_radix
.done:
cmp byte [r12], 0
setz al
lea rcx, [rel buf]
sub r12, rcx ; r12 = count
mul rax, r12
mov rdx, qword [rsp] ; acc
add rsp, 16
pop r12
ret
parse_atom:
call parse_num
test al, al
jz .not_num
push rdx
mov rdi, 16 ; struct { refcount: usize, value: i64 }
mov rsi, 8
call alloc
pop rdx
mov dword [rax], 1 ; refcount = 1
mov qword [rax + 8], rdx ; value = acc
mov rdi, rax
mov rsi, OBJ_NUM
call obj_set_tag
ret
.not_num:
lea rdi, [rel buf]
call strlen
push rax ; len
mov rdi, rax
mov rsi, 1
call alloc ; allocate memory for the string
pop rdx ; len
push rax ; ptr
push rdx ; len
lea rdi, [rel buf]
mov rsi, rax
call memcpy ; memcpy(ptr, buf, len)
mov rdi, 16 ; struct { refcount: u32, length: u32, ptr: *const u8 }
mov rsi, 8
call alloc ; allocate the atom object
pop rdx ; length of string
pop rcx ; pointer to string
mov dword [rax], 1 ; refcount = 1
mov dword [rax + 4], edx ; length of string
mov qword [rax + 8], rcx ; pointer to string
mov rdi, rax
mov rsi, OBJ_ATOM
call obj_set_tag
ret
parse_quote:
call parse_next_token
mov rdi, rax
lea rsi, [rel nil]
call cons ; (t . nil)
push rax
lea rdi, [rel atoms]
mov rdi, [rdi + ATOM_QUOTE * 8] ; get the "quote" atom
push rdi
call obj_inc_ref
pop rdi ; $rdi = "quote" atom
pop rsi ; $rsi = (t . nil)
call cons ; ("quote" . (t . nil))
ret
parse_list:
sub rsp, 24
mov qword [rsp], rdi
lea rax, [rel nil]
mov qword [rsp + 8], rax ; head = nil
mov qword [rsp + 16], rax ; tail = nil
.tailcall:
mov rdi, qword [rsp]
call next_token
cmp al, ')'
je .finish
cmp al, '.'
mov rdi, qword [rsp]
jnz .list
; dotted_pair:
call parse_next_token ; if (a . b), return b since we're in the recursive call for (cons a b)
mov rsi, rax ; $rsi = b
mov rdi, qword [rsp + 8] ; $rdi = (a . nil)
call set_cdr ; set_cdr((a . nil), b) => (a . b)
mov rdi, qword [rsp]
call next_token
cmp al, ')'
jnz .invalid
jmp .finish
.list:
call parse_cur_token
mov rdi, rax
lea rsi, [rel nil]
call cons ; (t . nil)
xchg rax, qword [rsp + 16] ; replace(&mut tail, new_tail)
lea rsi, [rel nil]
cmp rax, rsi
je .init_tail ; if tail was nil, set head = tail = (t . nil)
mov rdi, rax ; $rdi = old tail
mov rsi, qword [rsp + 16] ; $rsi = new tail
call set_cdr ; set_cdr(old_tail, new_tail) => (old_tail . (t . nil))
jmp .tailcall
.init_tail:
mov rax, qword [rsp + 16] ; $rax = (t . nil)
mov qword [rsp + 8], rax ; head = (t . nil)
jmp .tailcall
.invalid:
xor rdi, rdi
call panic_abort
.finish:
mov rax, [rsp + 8] ; the list we've been building up
add rsp, 24
ret
global parse_next_token
parse_next_token:
push rdi
call next_token
pop rdi
call parse_cur_token
ret
global eval
;; evaluates the expression in $rdi in the environment $rsi and returns the result in $rax
eval:
call obj_tag_part
cmp al, OBJ_ATOM
je .eval_atom
cmp al, OBJ_CONS
jne .uneval
; eval_list($rdi, $rsi)
sub rsp, 16
mov qword [rsp], rsi ; env
call get_cdr
mov qword [rsp + 8], rax ; cdr($rdi)
call get_car
mov rdi, rax ; $rdi = car($rdi)
mov rsi, qword [rsp] ; $rsi = env
call eval ; $rax = eval(car($rdi), $rsi)
mov rdi, rax ; $rdi = eval(car($rdi),
mov rsi, qword [rsp + 8] ; $rsi = cdr($rdi)
mov rdx, qword [rsp] ; $rdx = env
call apply
add rsp, 16
ret
.eval_atom:
call assoc
ret
.uneval:
mov rax, rdi
ret
;; returns a list of the evaluated elements in the list in $rdi in the environment $rsi
eval_list:
sub rsp, 32
mov qword [rsp], rsi ; save the environment on the stack
lea rax, [rel nil]
mov qword [rsp + 8], rax ; tail = nil
mov qword [rsp + 16], rax ; head = nil
.tailcall:
call obj_tag_part
cmp al, OBJ_CONS
jne .not_cons
; cons(eval(car($rdi), $rsi), eval_list(cdr($rdi), $rsi))
call get_car
mov rcx, rax
call get_cdr
mov qword [rsp + 24], rax ; cdr($rdi)
mov rdi, rcx ; $rdi = car($rdi)
mov rsi, qword [rsp] ; $rsi = environment
call eval
mov rdi, rax ; $rdi = eval(car($rdi), $rsi)
lea rsi, [rel nil] ; nil
call cons ; $rax = cons(eval(car($rdi), $rsi), 'nil)
xchg rax, qword [rsp + 8] ; replace(&mut tail, new_tail)
lea rsi, [rel nil]
cmp rax, rsi
jne .has_init_head
mov rax, qword [rsp + 8] ; $rax = new tail
mov qword [rsp + 16], rax ; head = new tail
mov rdi, qword [rsp + 24] ; $rdi = cdr($rdi)
jmp .tailcall
.has_init_head:
mov rdi, rax
mov rsi, qword [rsp + 8]
call set_cdr
mov rdi, qword [rsp + 24] ; $rdi = cdr($rdi)
jmp .tailcall
.not_cons:
call is_nil
test al, al
jnz .nil
mov rsi, qword [rsp] ; $rsi = environment
call eval
mov rsi, rax
mov rdi, qword [rsp + 8] ; $rdi = tail
; cdr of tail starts as nil, in the case of a cons cell as a list, we want to update it to be the eval of the cdr of the list
call set_cdr ; set_cdr(tail, eval($rdi, $rsi))
.nil:
; the tail starts as nil
mov rax, qword [rsp + 16] ; $rax = head
add rsp, 32
ret
;; returns the value associated with the symbol in $rdi in the environment $rsi, or nil if not found
assoc:
sub rsp, 16
mov qword [rsp], rsi ; save the environment on the
mov qword [rsp + 8], rdi ; save the symbol on the stack
.assoc_loop:
mov rdi, qword [rsp] ; $rdi = environment
call is_nil
test al, al
jnz .not_found ; if we've reached the end of the environment, return nil
call obj_tag_part
cmp al, OBJ_CONS
jne .invalid_env
call get_car
mov rdi, rax
call get_car
mov rdi, rax ; $rdi = key
mov rsi, qword [rsp + 8] ; $rsi = sym
call obj_eq
test al, al
je .found
mov rdi, qword [rsp] ; $rdi = environment
call get_cdr
mov qword [rsp], rax ; environment = cdr(environment)
jmp .assoc_loop
.found:
mov rdi, qword [rsp] ; $rdi = environment
call get_car
mov rdi, rax
call get_cdr
add rsp, 16
ret
.invalid_env:
.not_found:
lea rax, [rel nil]
add rsp, 16
ret
;; applies the function in $rdi to the argument list in $rsi in the environment $rdx and returns the result in $rax
apply:
call obj_tag_part
cmp al, OBJ_PRIM
je .apply_prim
cmp al, OBJ_CLOS
jne .invalid_func
; closure application:
call reduce
ret
.apply_prim:
call obj_addr_part
mov rax, qword [rax + 8] ; get the function pointer
mov rdi, rsi ; $rdi = argument list
mov rsi, rdx ; $rsi = environment
jmp rax
.invalid_func:
xor rdi, rdi
call panic_abort
;; recursively binds the symbols in $rdi to the values in $rsi in the environment $rdx and returns the new environment in $rax
bind_list:
sub rsp, 16
.tailcall:
call is_nil
test al, al
jnz .done ; if there are no more symbols to bind, return the old environment
call get_car
mov rcx, rax ; key
call get_cdr
mov qword [rsp + 8], rax ; keys
mov rdi, rsi ; values
call get_car
mov rsi, rax ; val
call get_cdr
mov qword [rsp], rax ; vals
mov rdi, rcx ; $rdi = key
call prepend ; new_env = ((key . val) . old_env)
mov rdx, rax
mov rdi, qword [rsp + 8] ; $rdi = keys
mov rsi, qword [rsp] ; $rsi = vals
jmp .tailcall ; recursively bind the rest of the keys and vals in the new environment
.done:
add rsp, 16
mov rax, rdx
ret
;; applies closure $rdi to arguments $rsi and environment $rdx, returning the result in $rax
reduce:
; a closure has the following form: ((params . body) . env)
sub rsp, 32
mov qword [rsp], rdx ; eval_env
mov qword [rsp + 8], rdi ; closure
call get_clos_env
mov rdi, rax
call is_nil
test al, al
mov rax, qword [rel env] ; g_env
cmovz rax, rdi ; base_env = closure_env == nil ? g_env : closure_env
mov qword [rsp + 16], rax ; save base env at rsp + 16
mov rdi, rsi ; $rdi = args
mov rsi, qword [rsp] ; $rsi = eval_env
call eval_list ; evaled_args = eval_list(args, eval_env)
mov rsi, rax ; $rsi = evaled args
mov rdi, qword [rsp + 8] ; $rdi = closure
call get_clos_params
mov rdi, rax ; params
mov rdx, qword [rsp + 16] ; $rdx = base_env
call bind_list ; closure_env = bind_list(params, evaled_args, base_env)
mov rsi, rax ; $rsi = closure_env
mov rdi, qword [rsp + 8] ; $rdi = closure
call get_clos_body
mov rdi, rax ; $rdi = body
call eval ; eval(body, closure_env)
add rsp, 32
ret
;; k: $rdi, v: $rsi, e: $rdx -> ((k . v) . e)
prepend:
push rdx
call cons
mov rdi, rax
pop rsi
call cons
ret
;; PRIMITIVES
;; returns 1 if the object in $rdi is nil, 0 otherwise
is_nil:
lea rax, [rel nil]
cmp rdi, rax
sete al
ret