865 lines
21 KiB
NASM
865 lines
21 KiB
NASM
|
|
|
|
section .bss
|
|
buf resb 0x100
|
|
align 8,db 0
|
|
atoms times 2 resb 8
|
|
env resq 1
|
|
;;
|
|
section .text
|
|
|
|
;; 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
|
|
je .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:
|
|
.memcpy_loop:
|
|
test rdx, rdx
|
|
jz .memcpy_done
|
|
mov al, byte [rdi]
|
|
mov byte [rsi], al
|
|
inc rsi
|
|
inc rdi
|
|
dec rdx
|
|
jmp .memcpy_loop
|
|
|
|
.memcpy_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
|
|
|
|
align 8,db 0
|
|
nil dq 1 ; the nil object, with refcount = 1
|
|
OBJ_NIL 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
|
|
|
|
ATOM_QUOTE equ 0
|
|
ATOM_TRUE equ 1
|
|
|
|
QUOTE_STR db "quote", 0
|
|
QUOTE_STR_LEN equ $ - QUOTE_STR
|
|
TRUE_STR db "t", 0
|
|
TRUE_STR_LEN equ $ - TRUE_STR
|
|
|
|
global init_env
|
|
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)
|
|
ret
|
|
|
|
global init_atoms
|
|
init_atoms:
|
|
mov rdi, 16
|
|
mov rsi, 8
|
|
call alloc
|
|
mov dword [rax], 1 ; refcount = 1
|
|
mov dword [rax + 4], QUOTE_STR_LEN ; length of "quote
|
|
lea rcx, [rel QUOTE_STR]
|
|
mov qword [rax + 8], rcx ; pointer to "quote"
|
|
mov rdi, rax
|
|
mov rsi, OBJ_ATOM
|
|
call obj_set_tag
|
|
lea rcx, [rel atoms]
|
|
mov qword [rcx], rax ; atoms[0] = "quote"
|
|
|
|
mov rdi, 16
|
|
mov rsi, 8
|
|
call alloc
|
|
mov dword [rax], 1 ; refcount = 1
|
|
mov dword [rax + 4], TRUE_STR_LEN ; length of "t"
|
|
lea rcx, [rel TRUE_STR]
|
|
mov qword [rax + 8], rcx ; pointer to "t"
|
|
mov rdi, rax
|
|
mov rsi, OBJ_ATOM
|
|
call obj_set_tag
|
|
lea rcx, [rel atoms]
|
|
mov qword [rcx + 8], rax ; atoms[1] = "t"
|
|
ret
|
|
|
|
|
|
|
|
;; returns 0 if the objects pointed at by $rdi and $rsi are equal, and 1 otherwise.
|
|
obj_eq:
|
|
sub rsp, 24
|
|
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_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_atoms:
|
|
.compare_strs:
|
|
call obj_addr_part
|
|
mov qword [rsp + 8], rax
|
|
mov rdi, qword [rsp] ; restore obj1
|
|
call obj_addr_part
|
|
mov rdx, dword [rax + 4] ; len1
|
|
mov rcx, qword [rsp + 8] ; ptr2
|
|
mov 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 ; $rdi = ptr1
|
|
mov rsi, rsi ; $rsi = ptr2
|
|
mov rdx, rbx ; $rdx = len1 = len2
|
|
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 dtor_table - dtor_nil
|
|
dd dtor_table - dtor_cons
|
|
dd dtor_table - dtor_clos
|
|
dd dtor_table - dtor_atom
|
|
dd dtor_table - dtor_num
|
|
dd dtor_table - dtor_prim
|
|
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 obj_tag_part
|
|
cmp al, OBJ_NIL
|
|
je .done ; nil is immortal, so we're done
|
|
call obj_addr_part
|
|
inc dword [rax] ; increment the refcount
|
|
.done:
|
|
ret
|
|
|
|
obj_dec_ref:
|
|
call obj_tag_part
|
|
cmp al, OBJ_NIL
|
|
je .done ; nil is immortal, so we're done
|
|
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
|
|
add rcx, rsi ; calculate the address of the destructor
|
|
mov rdi, rax ; set the argument for the destructor (the object pointer)
|
|
call rcx ; call the destructor
|
|
.done:
|
|
ret
|
|
|
|
;; returns the tag part of the object handle in $rdi
|
|
obj_tag_part:
|
|
mov rax, rdi
|
|
and al, 0x7
|
|
movzx rax, al
|
|
ret
|
|
|
|
;; returns the address part of the object handle in $rdi
|
|
obj_addr_part:
|
|
mov rax, rdi
|
|
and rax, -8
|
|
ret
|
|
|
|
obj_set_tag:
|
|
mov rax, rdi
|
|
and rax, -8 ; clear the tag bits
|
|
or rax, rsi ; set the new tag bits
|
|
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
|
|
|
|
|
|
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
|
|
|
|
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
|
|
|
|
;; 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
|
|
push rdi
|
|
xor r14, r14
|
|
.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, "'"
|
|
jne .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
|
|
lea rcx, [rel buf]
|
|
lea rcx, [rcx + r14]
|
|
mov byte [rcx], al
|
|
inc r14
|
|
|
|
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
|
|
pop rdi
|
|
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
|
|
pop rdi
|
|
call parse_atom
|
|
ret
|
|
.list:
|
|
pop rdi
|
|
call parse_list
|
|
ret
|
|
.quote:
|
|
pop rdi
|
|
call parse_quote
|
|
ret
|
|
|
|
parse_num:
|
|
push r12
|
|
sub rsp, 16
|
|
mov qword [rsp], 0 ; acc = 0
|
|
mov dword [rsp + 4], 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 + 4] ; radix
|
|
call to_digit
|
|
test al, al
|
|
jz .done
|
|
mov rax, [rsp] ; acc
|
|
mov rcx, rdx ; digit
|
|
imul rax, rsi ; acc *= radix
|
|
add rax, rcx ; acc += digit
|
|
mov [rsp], rcx
|
|
inc r12 ; index++
|
|
jmp .skip_radix
|
|
.done:
|
|
cmp byte [r12 + 1], 0
|
|
setz al
|
|
lea rcx, [rel buf]
|
|
sub r12, rcx ; r12 = count
|
|
mul rax, r12
|
|
mov rdx, [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 qword [rsp + 8], rax ; update the tail of the list to (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:
|
|
cmp al, OBJ_NIL
|
|
je .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
|
|
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:
|
|
mov rax, rdx
|
|
call is_nil
|
|
test al, al
|
|
jnz .done ; if there are no more symbols to bind, return the old environment
|
|
sub rsp, 32
|
|
mov qword [rsp], rax ; save the old environment on the stack
|
|
mov qword [rsp + 16], rsi ; tmp_values
|
|
call get_car
|
|
mov qword [rsp + 8],rax ; key
|
|
call get_cdr
|
|
mov qword [rsp + 16], rax ; keys
|
|
mov rdi, qword [rsp + 16] ; values
|
|
call get_car
|
|
mov qword [rsp + 16], rax ; val
|
|
call get_cdr
|
|
mov qword [rsp + 24], rax ; vals
|
|
|
|
mov rdi, qword [rsp + 8] ; $rdi = key
|
|
mov rsi, qword [rsp + 16] ; $rsi = val
|
|
mov rdx, qword [rsp] ; $rdx = old environment
|
|
call prepend ; new_env = ((key . val) . old_env)
|
|
mov rdx, rax
|
|
mov rdi, qword [rsp + 16] ; $rdi = keys
|
|
mov rsi, qword [rsp + 24] ; $rsi = vals
|
|
add rsp, 32
|
|
jmp bind ; recursively bind the rest of the keys and vals in the new environment
|
|
.done:
|
|
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_cdr
|
|
mov rdi, rax
|
|
call is_nil
|
|
test al, al
|
|
mov rax, qword [rsp] ; eval_env
|
|
xchg qword [rsp + 8], rax ;
|
|
cmovz qword [rsp + 8], rdi ; env = closure_env == nil ? eval_env : closure_env
|
|
mov rdi, rax ; restore closure
|
|
call get_car
|
|
mov rdi, rax ; (params . body)
|
|
call get_car
|
|
mov qword [rsp + 16], rax ; params
|
|
call get_cdr
|
|
mov qword [rsp + 24], rax ; body
|
|
|
|
|
|
|
|
;; k: $rdi, v: $rsi, e: $rdx -> ((k . v) . e)
|
|
prepend:
|
|
push rdx
|
|
call cons
|
|
mov rsi, rax
|
|
pop rdi
|
|
call cons
|
|
ret
|
|
|
|
|
|
|
|
;; PRIMITIVES
|
|
|
|
;; returns 1 if the object in $rdi is nil, 0 otherwise
|
|
is_nil:
|
|
call obj_tag_part
|
|
cmp al, OBJ_NIL
|
|
setz al
|
|
ret
|