diff --git a/jig/ast.rkt b/jig/ast.rkt new file mode 100644 index 0000000..204e32f --- /dev/null +++ b/jig/ast.rkt @@ -0,0 +1,53 @@ +#lang racket +(provide Lit Prim0 Prim1 Prim2 Prim3 If Eof Begin + Let Var Prog Defn App) + +;; type Prog = (Prog (Listof Defn) Expr) +(struct Prog (ds e) #:prefab) + +;; type Defn = (Defn Id (Listof Id) Expr) +(struct Defn (f xs e) #:prefab) +;; type Expr = (Lit Datum) +;; | (Eof) +;; | (Prim0 Op0) +;; | (Prim1 Op1 Expr) +;; | (Prim2 Op2 Expr Expr) +;; | (Prim3 Op3 Expr Expr Expr) +;; | (If Expr Expr Expr) +;; | (Begin Expr Expr) +;; | (Let Id Expr Expr) +;; | (Var Id) +;; | (App Id (Listof Expr)) + +;; type Id = Symbol +;; type Datum = Integer +;; | Boolean +;; | Character +;; | String +;; type Op0 = 'read-byte | 'peek-byte | 'void +;; type Op1 = 'add1 | 'sub1 +;; | 'zero? +;; | 'char? | 'integer->char | 'char->integer +;; | 'write-byte | 'eof-object? +;; | 'box | 'car | 'cdr | 'unbox +;; | 'empty? | 'cons? | 'box? +;; | 'vector? | 'vector-length +;; | 'string? | 'string-length +;; type Op2 = '+ | '- | '< | '= +;; | 'eq? | 'cons +;; | 'make-vector | 'vector-ref +;; | 'make-string | 'string-ref +;; type Op3 = 'vector-set! + +(struct Eof () #:prefab) +(struct Lit (d) #:prefab) +(struct Prim0 (p) #:prefab) +(struct Prim1 (p e) #:prefab) +(struct Prim2 (p e1 e2) #:prefab) +(struct Prim3 (p e1 e2 e3) #:prefab) +(struct If (e1 e2 e3) #:prefab) +(struct Begin (e1 e2) #:prefab) +(struct Let (x e1 e2) #:prefab) +(struct Var (x) #:prefab) +(struct App (f es) #:prefab) + diff --git a/jig/build-runtime.rkt b/jig/build-runtime.rkt new file mode 100644 index 0000000..7278350 --- /dev/null +++ b/jig/build-runtime.rkt @@ -0,0 +1,14 @@ +#lang racket +(provide runtime-path) + +(require racket/runtime-path) +(define-runtime-path here ".") + +(unless (system (string-append "make -C '" + (path->string (normalize-path here)) + "' -s runtime.o")) + (error 'build-runtime "could not build runtime")) + +(define runtime-path + (normalize-path (build-path here "runtime.o"))) + diff --git a/jig/compile-ops.rkt b/jig/compile-ops.rkt new file mode 100644 index 0000000..1a8fbed --- /dev/null +++ b/jig/compile-ops.rkt @@ -0,0 +1,350 @@ +#lang racket +(provide compile-op0 compile-op1 compile-op2 compile-op3 pad-stack) +(require "ast.rkt") +(require "types.rkt") +(require a86/ast) + +(define rax 'rax) +(define eax 'eax) ; 32-bit load/store +(define rbx 'rbx) ; heap +(define rdi 'rdi) ; arg +(define r8 'r8) ; scratch in op2 +(define r9 'r9) ; scratch +(define r10 'r10) ; scratch + +(define r15 'r15) ; stack pad (non-volatile) +(define rsp 'rsp) ; stack + +;; Op0 -> Asm +(define (compile-op0 p) + (match p + ['void (seq (Mov rax (value->bits (void))))] + ['read-byte (seq pad-stack (Call 'read_byte) unpad-stack)] + ['peek-byte (seq pad-stack (Call 'peek_byte) unpad-stack)])) + +;; Op1 -> Asm +(define (compile-op1 p) + (match p + ['add1 + (seq (assert-integer rax) + (Add rax (value->bits 1)))] + ['sub1 + (seq (assert-integer rax) + (Sub rax (value->bits 1)))] + ['zero? + (seq (assert-integer rax) + (Cmp rax 0) + if-equal)] + ['char? + (seq (And rax mask-char) + (Cmp rax type-char) + if-equal)] + ['char->integer + (seq (assert-char rax) + (Sar rax char-shift) + (Sal rax int-shift))] + ['integer->char + (seq (assert-codepoint) + (Sar rax int-shift) + (Sal rax char-shift) + (Xor rax type-char))] + ['eof-object? + (seq (Cmp rax (value->bits eof)) + if-equal)] + ['write-byte + (seq assert-byte + pad-stack + (Mov rdi rax) + (Call 'write_byte) + unpad-stack)] + + ['box + (seq (Mov (Offset rbx 0) rax) ; memory write + (Mov rax rbx) ; put box in rax + (Or rax type-box) ; tag as a box + (Add rbx 8))] + + ['unbox + (seq (assert-box rax) + (Xor rax type-box) + (Mov rax (Offset rax 0)))] + ['car + (seq (assert-cons rax) + (Xor rax type-cons) + (Mov rax (Offset rax 8)))] + ['cdr + (seq (assert-cons rax) + (Xor rax type-cons) + (Mov rax (Offset rax 0)))] + + ['empty? (seq (Cmp rax (value->bits '())) if-equal)] + ['cons? (type-pred ptr-mask type-cons)] + ['box? (type-pred ptr-mask type-box)] + ['vector? (type-pred ptr-mask type-vect)] + ['string? (type-pred ptr-mask type-str)] + ['vector-length + (let ((zero (gensym)) + (done (gensym))) + (seq (assert-vector rax) + (Xor rax type-vect) + (Cmp rax 0) + (Je zero) + (Mov rax (Offset rax 0)) + (Sal rax int-shift) + (Jmp done) + (Label zero) + (Mov rax 0) + (Label done)))] + ['string-length + (let ((zero (gensym)) + (done (gensym))) + (seq (assert-string rax) + (Xor rax type-str) + (Cmp rax 0) + (Je zero) + (Mov rax (Offset rax 0)) + (Sal rax int-shift) + (Jmp done) + (Label zero) + (Mov rax 0) + (Label done)))])) + + +;; Op2 -> Asm +(define (compile-op2 p) + (match p + ['+ + (seq (Pop r8) + (assert-integer r8) + (assert-integer rax) + (Add rax r8))] + ['- + (seq (Pop r8) + (assert-integer r8) + (assert-integer rax) + (Sub r8 rax) + (Mov rax r8))] + ['< + (seq (Pop r8) + (assert-integer r8) + (assert-integer rax) + (Cmp r8 rax) + if-lt)] + ['= + (seq (Pop r8) + (assert-integer r8) + (assert-integer rax) + (Cmp r8 rax) + if-equal)] + ['cons + (seq (Mov (Offset rbx 0) rax) + (Pop rax) + (Mov (Offset rbx 8) rax) + (Mov rax rbx) + (Or rax type-cons) + (Add rbx 16))] + ['eq? + (seq (Pop r8) + (Cmp rax r8) + if-equal)] + ['make-vector ;; size value + (let ((loop (gensym)) + (done (gensym)) + (empty (gensym))) + (seq (Pop r8) ;; r8 = size + (assert-natural r8) + (Cmp r8 0) ; special case empty vector + (Je empty) + + (Mov r9 rbx) + (Or r9 type-vect) + + (Sar r8 int-shift) + (Mov (Offset rbx 0) r8) + (Add rbx 8) + + (Label loop) + (Mov (Offset rbx 0) rax) + (Add rbx 8) + (Sub r8 1) + (Cmp r8 0) + (Jne loop) + + (Mov rax r9) + (Jmp done) + + (Label empty) + (Mov rax type-vect) + (Label done)))] + ['vector-ref ; vector index + (seq (Pop r8) + (assert-vector r8) + (assert-integer rax) + (Cmp r8 type-vect) + (Je 'err) ; special case for empty vector + (Cmp rax 0) + (Jl 'err) + (Xor r8 type-vect) ; r8 = ptr + (Mov r9 (Offset r8 0)) ; r9 = len + (Sar rax int-shift) ; rax = index + (Sub r9 1) + (Cmp r9 rax) + (Jl 'err) + (Sal rax 3) + (Add r8 rax) + (Mov rax (Offset r8 8)))] + ['make-string + (let ((loop (gensym)) + (done (gensym)) + (empty (gensym))) + (seq (Pop r8) + (assert-natural r8) + (assert-char rax) + (Cmp r8 0) ; special case empty string + (Je empty) + + (Mov r9 rbx) + (Or r9 type-str) + + (Sar r8 int-shift) + (Mov (Offset rbx 0) r8) + (Add rbx 8) + + (Sar rax char-shift) + + (Add r8 1) ; adds 1 + (Sar r8 1) ; when + (Sal r8 1) ; len is odd + + (Label loop) + (Mov (Offset rbx 0) eax) + (Add rbx 4) + (Sub r8 1) + (Cmp r8 0) + (Jne loop) + + (Mov rax r9) + (Jmp done) + + (Label empty) + (Mov rax type-str) + (Label done)))] + ['string-ref + (seq (Pop r8) + (assert-string r8) + (assert-integer rax) + (Cmp r8 type-str) + (Je 'err) ; special case for empty string + (Cmp rax 0) + (Jl 'err) + (Xor r8 type-str) ; r8 = ptr + (Mov r9 (Offset r8 0)) ; r9 = len + (Sar rax int-shift) ; rax = index + (Sub r9 1) + (Cmp r9 rax) + (Jl 'err) + (Sal rax 2) + (Add r8 rax) + (Mov 'eax (Offset r8 8)) + (Sal rax char-shift) + (Or rax type-char))])) + +;; Op3 -> Asm +(define (compile-op3 p) + (match p + ['vector-set! + (seq (Pop r10) + (Pop r8) + (assert-vector r8) + (assert-integer r10) + (Cmp r10 0) + (Jl 'err) + (Xor r8 type-vect) ; r8 = ptr + (Mov r9 (Offset r8 0)) ; r9 = len + (Sar r10 int-shift) ; r10 = index + (Sub r9 1) + (Cmp r9 r10) + (Jl 'err) + (Sal r10 3) + (Add r8 r10) + (Mov (Offset r8 8) rax) + (Mov rax (value->bits (void))))])) + + +;; -> Asm +;; set rax to #t or #f if comparison flag is equal +(define if-equal + (seq (Mov rax (value->bits #f)) + (Mov r9 (value->bits #t)) + (Cmove rax r9))) + +;; -> Asm +;; set rax to #t or #f if comparison flag is less than +(define if-lt + (seq (Mov rax (value->bits #f)) + (Mov r9 (value->bits #t)) + (Cmovl rax r9))) + +(define (assert-type mask type) + (λ (arg) + (seq (Mov r9 arg) + (And r9 mask) + (Cmp r9 type) + (Jne 'err)))) + +(define (type-pred mask type) + (seq (And rax mask) + (Cmp rax type) + if-equal)) + +(define assert-integer + (assert-type mask-int type-int)) +(define assert-char + (assert-type mask-char type-char)) +(define assert-box + (assert-type ptr-mask type-box)) +(define assert-cons + (assert-type ptr-mask type-cons)) +(define assert-vector + (assert-type ptr-mask type-vect)) +(define assert-string + (assert-type ptr-mask type-str)) + +(define (assert-codepoint) + (let ((ok (gensym))) + (seq (assert-integer rax) + (Cmp rax (value->bits 0)) + (Jl 'err) + (Cmp rax (value->bits 1114111)) + (Jg 'err) + (Cmp rax (value->bits 55295)) + (Jl ok) + (Cmp rax (value->bits 57344)) + (Jg ok) + (Jmp 'err) + (Label ok)))) + +(define assert-byte + (seq (assert-integer rax) + (Cmp rax (value->bits 0)) + (Jl 'err) + (Cmp rax (value->bits 255)) + (Jg 'err))) + +(define (assert-natural r) + (seq (assert-integer r) + (Cmp r (value->bits 0)) + (Jl 'err))) + +;; Asm +;; Dynamically pad the stack to be aligned for a call +(define pad-stack + (seq (Mov r15 rsp) + (And r15 #b1000) + (Sub rsp r15))) + +;; Asm +;; Undo the stack alignment after a call +(define unpad-stack + (seq (Add rsp r15))) + diff --git a/jig/compile-stdin.rkt b/jig/compile-stdin.rkt new file mode 100644 index 0000000..82d4583 --- /dev/null +++ b/jig/compile-stdin.rkt @@ -0,0 +1,14 @@ +#lang racket +(provide main) +(require "parse.rkt") +(require "compile.rkt") +(require "read-all.rkt") +(require a86/printer) + +;; -> Void +;; Compile contents of stdin, +;; emit asm code on stdout +(define (main) + (read-line) ; ignore #lang racket line + (asm-display (compile (apply parse (read-all))))) + diff --git a/jig/compile.rkt b/jig/compile.rkt new file mode 100644 index 0000000..ca70003 --- /dev/null +++ b/jig/compile.rkt @@ -0,0 +1,204 @@ +#lang racket +(provide (all-defined-out)) +(require "ast.rkt") +(require "compile-ops.rkt") +(require "types.rkt") +(require a86/ast) + +(define rax 'rax) +(define rbx 'rbx) ; heap +(define rsp 'rsp) ; stack +(define rdi 'rdi) ; arg +(define r8 'r8) ; scratch +(define r15 'r15) ; stack pad (non-volatile) + +;; Prog -> Asm +(define (compile p) + (match p + [(Prog ds e) + (prog (Global 'entry) + (Extern 'peek_byte) + (Extern 'read_byte) + (Extern 'write_byte) + (Extern 'raise_error) + (Label 'entry) + (Push rbx) ; save callee-saved register + (Push r15) + (Mov rbx rdi) ; recv heap pointer + + (compile-e e '() #f) + (Pop r15) ; restore callee-save register + (Pop rbx) + (Ret) + (compile-defines ds) + (Label 'err) + pad-stack + (Call 'raise_error))])) + +;; [Listof Defn] -> Asm +(define (compile-defines ds) + (match ds + ['() (seq)] + [(cons d ds) + (seq (compile-define d) + (compile-defines ds))])) + +;; Defn -> Asm +(define (compile-define d) + (match d + [(Defn f xs e) + (seq (Label (symbol->label f)) + (compile-e e (reverse xs) #t) + (Add rsp (* 8 (length xs))) ; pop args + (Ret))])) + +;; type CEnv = (Listof [Maybe Id]) +;; Expr CEnv Boolean -> Asm +(define (compile-e e c t?) + (match e + [(Lit d) (compile-value d)] + [(Eof) (compile-value eof)] + [(Var x) (compile-variable x c)] + [(Prim0 p) (compile-prim0 p)] + [(Prim1 p e) (compile-prim1 p e c)] + [(Prim2 p e1 e2) (compile-prim2 p e1 e2 c)] + [(Prim3 p e1 e2 e3) (compile-prim3 p e1 e2 e3 c)] + [(If e1 e2 e3) + (compile-if e1 e2 e3 c t?)] + [(Begin e1 e2) + (compile-begin e1 e2 c t?)] + [(Let x e1 e2) + (compile-let x e1 e2 c t?)] + [(App f es) + (compile-app f es c t?)])) + +;; Value -> Asm +(define (compile-value v) + (cond [(string? v) (compile-string v)] + [else (Mov rax (value->bits v))])) + +;; Id CEnv -> Asm +(define (compile-variable x c) + (let ((i (lookup x c))) + (seq (Mov rax (Offset rsp i))))) + +;; String -> Asm +(define (compile-string s) + (let ((len (string-length s))) + (if (zero? len) + (seq (Mov rax type-str)) + (seq (Mov rax len) + (Mov (Offset rbx 0) rax) + (compile-string-chars (string->list s) 8) + (Mov rax rbx) + (Or rax type-str) + (Add rbx + (+ 8 (* 4 (if (odd? len) (add1 len) len)))))))) + +;; [Listof Char] Integer -> Asm +(define (compile-string-chars cs i) + (match cs + ['() (seq)] + [(cons c cs) + (seq (Mov rax (char->integer c)) + (Mov (Offset rbx i) 'eax) + (compile-string-chars cs (+ 4 i)))])) + +;; Op0 -> Asm +(define (compile-prim0 p) + (compile-op0 p)) + +(define (compile-prim1 p e c) + (seq (compile-e e c #f) + (compile-op1 p))) + +;; Op2 Expr Expr CEnv -> Asm +(define (compile-prim2 p e1 e2 c) + (seq (compile-e e1 c #f) + (Push rax) + (compile-e e2 (cons #f c) #f) + (compile-op2 p))) + +;; Op3 Expr Expr Expr CEnv -> Asm +(define (compile-prim3 p e1 e2 e3 c) + (seq (compile-e e1 c #f) + (Push rax) + (compile-e e2 (cons #f c) #f) + (Push rax) + (compile-e e3 (cons #f (cons #f c)) #f) + (compile-op3 p))) +;; Expr Expr Expr CEnv Boolean -> Asm +(define (compile-if e1 e2 e3 c t?) + (let ((l1 (gensym 'if)) + (l2 (gensym 'if))) + (seq (compile-e e1 c #f) + (Cmp rax (value->bits #f)) + (Je l1) + (compile-e e2 c t?) + (Jmp l2) + (Label l1) + (compile-e e3 c t?) + (Label l2)))) +;; Expr Expr CEnv Boolean -> Asm +(define (compile-begin e1 e2 c t?) + (seq (compile-e e1 c #f) + (compile-e e2 c t?))) +;; Id Expr Expr CEnv Boolean -> Asm +(define (compile-let x e1 e2 c t?) + (seq (compile-e e1 c #f) + (Push rax) + (compile-e e2 (cons x c) t?) + (Add rsp 8))) + +;; Id [Listof Expr] CEnv -> Asm +;; The return address is placed above the arguments, so callee pops +;; arguments and return address is next frame + +;; Id [Listof Expr] CEnv Boolean -> Asm +(define (compile-app f es c t?) + (if t? + (compile-app-tail f es c) + (compile-app-nontail f es c))) + +;; Id [Listof Expr] CEnv -> Asm +(define (compile-app-tail f es c) + (seq (compile-es es c) + (move-args (length es) (length c)) + (Add rsp (* 8 (length c))) + (Jmp (symbol->label f)))) + +;; Integer Integer -> Asm +(define (move-args i off) + (cond [(zero? off) (seq)] + [(zero? i) (seq)] + [else + (seq (Mov r8 (Offset rsp (* 8 (sub1 i)))) + (Mov (Offset rsp (* 8 (+ off (sub1 i)))) r8) + (move-args (sub1 i) off))])) +;; Id [Listof Expr] CEnv -> Asm +(define (compile-app-nontail f es c) + (let ((r (gensym 'ret))) + (seq (Lea rax r) + (Push rax) + (compile-es es (cons #f c)) + (Jmp (symbol->label f)) + (Label r)))) + +;; [Listof Expr] CEnv -> Asm +(define (compile-es es c) + (match es + ['() '()] + [(cons e es) + (seq (compile-e e c #f) + (Push rax) + (compile-es es (cons #f c)))])) + +;; Id CEnv -> Integer +(define (lookup x cenv) + (match cenv + ['() (error "undefined variable:" x)] + [(cons y rest) + (match (eq? x y) + [#t 0] + [#f (+ 8 (lookup x rest))])])) + diff --git a/jig/interp-io.rkt b/jig/interp-io.rkt new file mode 100644 index 0000000..f763754 --- /dev/null +++ b/jig/interp-io.rkt @@ -0,0 +1,12 @@ +#lang racket +(provide interp/io) +(require "interp.rkt") +;; String Prog -> (Cons Value String) +;; Interpret p with given string as input, +;; return value and collected output as string +(define (interp/io p input) + (parameterize ((current-output-port (open-output-string)) + (current-input-port (open-input-string input))) + (cons (interp p) + (get-output-string (current-output-port))))) + diff --git a/jig/interp-prim.rkt b/jig/interp-prim.rkt new file mode 100644 index 0000000..aa73685 --- /dev/null +++ b/jig/interp-prim.rkt @@ -0,0 +1,76 @@ +#lang racket +(provide interp-prim0 interp-prim1 interp-prim2 interp-prim3) + +;; Op0 -> Value +(define (interp-prim0 op) + (match op + ['read-byte (read-byte)] + ['peek-byte (peek-byte)] + ['void (void)])) + +;; Op1 Value -> Answer +(define (interp-prim1 op v) + (match (list op v) + [(list 'add1 (? integer?)) (add1 v)] + [(list 'sub1 (? integer?)) (sub1 v)] + [(list 'zero? (? integer?)) (zero? v)] + [(list 'char? v) (char? v)] + [(list 'integer->char (? codepoint?)) (integer->char v)] + [(list 'char->integer (? char?)) (char->integer v)] + [(list 'write-byte (? byte?)) (write-byte v)] + [(list 'eof-object? v) (eof-object? v)] + [(list 'box v) (box v)] + [(list 'unbox (? box?)) (unbox v)] + [(list 'car (? pair?)) (car v)] + [(list 'cdr (? pair?)) (cdr v)] + [(list 'empty? v) (empty? v)] + [(list 'cons? v) (cons? v)] + [(list 'box? v) (box? v)] + [(list 'vector? v) (vector? v)] + [(list 'vector-length (? vector?)) (vector-length v)] + [(list 'string? v) (string? v)] + [(list 'string-length (? string?)) (string-length v)] + [_ 'err])) + +;; Op2 Value Value -> Answer +(define (interp-prim2 op v1 v2) + (match (list op v1 v2) + [(list '+ (? integer?) (? integer?)) (+ v1 v2)] + [(list '- (? integer?) (? integer?)) (- v1 v2)] + [(list '< (? integer?) (? integer?)) (< v1 v2)] + [(list '= (? integer?) (? integer?)) (= v1 v2)] + [(list 'eq? v1 v2) (eq? v1 v2)] + [(list 'cons v1 v2) (cons v1 v2)] + [(list 'make-vector (? integer?) _) + (if (<= 0 v1) + (make-vector v1 v2) + 'err)] + [(list 'vector-ref (? vector?) (? integer?)) + (if (<= 0 v2 (sub1 (vector-length v1))) + (vector-ref v1 v2) + 'err)] + [(list 'make-string (? integer?) (? char?)) + (if (<= 0 v1) + (make-string v1 v2) + 'err)] + [(list 'string-ref (? string?) (? integer?)) + (if (<= 0 v2 (sub1 (string-length v1))) + (string-ref v1 v2) + 'err)] + [_ 'err])) + +;; Op3 Value Value Value -> Answer +(define (interp-prim3 p v1 v2 v3) + (match (list p v1 v2 v3) + [(list 'vector-set! (? vector?) (? integer?) _) + (if (<= 0 v2 (sub1 (vector-length v1))) + (vector-set! v1 v2 v3) + 'err)] + [_ 'err])) + +;; Any -> Boolean +(define (codepoint? v) + (and (integer? v) + (or (<= 0 v 55295) + (<= 57344 v 1114111)))) + diff --git a/jig/interp-stdin.rkt b/jig/interp-stdin.rkt new file mode 100644 index 0000000..ae13b54 --- /dev/null +++ b/jig/interp-stdin.rkt @@ -0,0 +1,13 @@ +#lang racket +(provide main) +(require "parse.rkt") +(require "interp.rkt") +(require "read-all.rkt") + +;; -> Void +;; Parse and interpret contents of stdin, +;; print result on stdout +(define (main) + (read-line) ; ignore #lang racket line + (println (interp (apply parse (read-all))))) + diff --git a/jig/interp.rkt b/jig/interp.rkt new file mode 100644 index 0000000..2d50bdd --- /dev/null +++ b/jig/interp.rkt @@ -0,0 +1,111 @@ +#lang racket +(provide interp) +(provide interp-env) +(require "ast.rkt") +(require "interp-prim.rkt") + +;; type Value = +;; | Integer +;; | Boolean +;; | Character +;; | Eof +;; | Void +;; | '() +;; | (cons Value Value) +;; | (box Value) +;; | (string Character ...) +;; | (vector Value ...) + +;; type Env = (Listof (List Id Value)) +;; Prog -> Answer +(define (interp p) + (match p + [(Prog ds e) + (interp-env e '() ds)])) + +;; Expr Env -> Answer +(define (interp-env e r ds) + (match e + [(Lit d) d] + [(Eof) eof] + [(Var x) (lookup r x)] + [(Prim0 p) (interp-prim0 p)] + [(Prim1 p e) + (match (interp-env e r ds) + ['err 'err] + [v (interp-prim1 p v)])] + [(Prim2 p e1 e2) + (match (interp-env e1 r ds) + ['err 'err] + [v1 (match (interp-env e2 r ds) + ['err 'err] + [v2 (interp-prim2 p v1 v2)])])] + [(Prim3 p e1 e2 e3) + (match (interp-env e1 r ds) + ['err 'err] + [v1 (match (interp-env e2 r ds) + ['err 'err] + [v2 (match (interp-env e3 r ds) + ['err 'err] + [v3 (interp-prim3 p v1 v2 v3)])])])] + [(If e0 e1 e2) + (match (interp-env e0 r ds) + ['err 'err] + [v + (if v + (interp-env e1 r ds) + (interp-env e2 r ds))])] + [(Begin e1 e2) + (match (interp-env e1 r ds) + ['err 'err] + [v (interp-env e2 r ds)])] + [(Let x e1 e2) + (match (interp-env e1 r ds) + ['err 'err] + [v (interp-env e2 (ext r x v) ds)])] + [(App f es) + (match (interp-env* es r ds) + ['err 'err] + [vs + (match (defns-lookup ds f) + [(Defn f xs e) + ; check arity matches + (if (= (length xs) (length vs)) + (interp-env e (zip xs vs) ds) + 'err)])])])) + +;; (Listof Expr) REnv Defns -> (Listof Value) | 'err +(define (interp-env* es r ds) + (match es + ['() '()] + [(cons e es) + (match (interp-env e r ds) + ['err 'err] + [v (match (interp-env* es r ds) + ['err 'err] + [vs (cons v vs)])])])) + +;; Defns Symbol -> Defn +(define (defns-lookup ds f) + (findf (match-lambda [(Defn g _ _) (eq? f g)]) + ds)) + +(define (zip xs ys) + (match* (xs ys) + [('() '()) '()] + [((cons x xs) (cons y ys)) + (cons (list x y) + (zip xs ys))])) + +;; Env Id -> Value +(define (lookup r x) + (match r + [(cons (list y val) r) + (if (symbol=? x y) + val + (lookup r x))])) + +;; Env Id Value -> Env +(define (ext r x v) + (cons (list x v) r)) + diff --git a/jig/main.rkt b/jig/main.rkt new file mode 100644 index 0000000..e0e3892 --- /dev/null +++ b/jig/main.rkt @@ -0,0 +1,13 @@ +#lang racket +(require "ast.rkt") +(require "parse.rkt") +(require "interp.rkt") +(require "compile.rkt") +(require "run.rkt") +(provide (all-from-out "ast.rkt")) +(provide (all-from-out "parse.rkt")) +(provide (all-from-out "interp.rkt")) +(provide (all-from-out "compile.rkt")) +(provide (all-from-out "run.rkt")) + + diff --git a/jig/parse.rkt b/jig/parse.rkt new file mode 100644 index 0000000..6f93714 --- /dev/null +++ b/jig/parse.rkt @@ -0,0 +1,70 @@ +#lang racket +(provide parse parse-e parse-define) +(require "ast.rkt") + +;; S-Expr ... -> Prog +(define (parse . s) + (match s + [(cons (and (cons 'define _) d) s) + (match (apply parse s) + [(Prog ds e) + (Prog (cons (parse-define d) ds) e)])] + [(cons e '()) (Prog '() (parse-e e))] + [_ (error "program parse error")])) + +;; S-Expr -> Defn +(define (parse-define s) + (match s + [(list 'define (list-rest (? symbol? f) xs) e) + (if (andmap symbol? xs) + (Defn f xs (parse-e e)) + (error "parse definition error"))] + [_ (error "Parse defn error" s)])) + +;; S-Expr -> Expr +(define (parse-e s) + (match s + [(? datum?) (Lit s)] + ['eof (Eof)] + [(? symbol?) (Var s)] + [(list 'quote (list)) (Lit '())] + [(list (? op0? p0)) (Prim0 p0)] + [(list (? op1? p1) e) (Prim1 p1 (parse-e e))] + [(list (? op2? p2) e1 e2) (Prim2 p2 (parse-e e1) (parse-e e2))] + [(list (? op3? p3) e1 e2 e3) + (Prim3 p3 (parse-e e1) (parse-e e2) (parse-e e3))] + [(list 'begin e1 e2) + (Begin (parse-e e1) (parse-e e2))] + [(list 'if e1 e2 e3) + (If (parse-e e1) (parse-e e2) (parse-e e3))] + [(list 'let (list (list (? symbol? x) e1)) e2) + (Let x (parse-e e1) (parse-e e2))] + [(cons (? symbol? f) es) + (App f (map parse-e es))] + [_ (error "Parse error" s)])) + + +;; Any -> Boolean +(define (datum? x) + (or (exact-integer? x) + (boolean? x) + (char? x) + (string? x))) + +;; Any -> Boolean +(define (op0? x) + (memq x '(read-byte peek-byte void))) + +(define (op1? x) + (memq x '(add1 sub1 zero? char? integer->char char->integer + write-byte eof-object? + box unbox empty? cons? box? car cdr + vector? vector-length string? string-length))) + +(define (op2? x) + (memq x '(+ - < = eq? cons + make-vector vector-ref make-string string-ref))) + +(define (op3? x) + (memq x '(vector-set!))) + diff --git a/jig/read-all.rkt b/jig/read-all.rkt new file mode 100644 index 0000000..a83fe69 --- /dev/null +++ b/jig/read-all.rkt @@ -0,0 +1,9 @@ +#lang racket +(provide read-all) +;; read all s-expression until eof +(define (read-all) + (let ((r (read))) + (if (eof-object? r) + '() + (cons r (read-all))))) + diff --git a/jig/run-stdin.rkt b/jig/run-stdin.rkt new file mode 100644 index 0000000..16cf99e --- /dev/null +++ b/jig/run-stdin.rkt @@ -0,0 +1,12 @@ +#lang racket +(provide main) +(require "parse.rkt") +(require "compile.rkt") +(require "run.rkt") + +;; -> Void +;; Compile contents of stdin and use asm-interp to run +(define (main) + (read-line) ; ignore #lang racket line + (run (compile (parse (read))))) + diff --git a/jig/run.rkt b/jig/run.rkt new file mode 100644 index 0000000..3448bba --- /dev/null +++ b/jig/run.rkt @@ -0,0 +1,19 @@ +#lang racket +(require a86/interp) +(require "types.rkt") +(require "build-runtime.rkt") +(provide run run/io) +;; Asm -> Answer +(define (run is) + (parameterize ((current-objs (list (path->string runtime-path)))) + (match (asm-interp is) + ['err 'err] + [b (bits->value b)]))) +;; Asm String -> (cons Answer String) +(define (run/io is in) + (parameterize ((current-objs (list (path->string runtime-path)))) + (match (asm-interp/io is in) + [(cons 'err out) (cons 'err out)] + [(cons b out) + (cons (bits->value b) out)]))) + diff --git a/jig/test/compile.rkt b/jig/test/compile.rkt new file mode 100644 index 0000000..48ea7ad --- /dev/null +++ b/jig/test/compile.rkt @@ -0,0 +1,8 @@ +#lang racket +(require "../compile.rkt") +(require "../parse.rkt") +(require "../run.rkt") +(require "test-runner.rkt") +(test (λ p (run (compile (apply parse p))))) +(test/io (λ (in . p) (run/io (compile (apply parse p)) in))) + diff --git a/jig/test/interp.rkt b/jig/test/interp.rkt new file mode 100644 index 0000000..c6b3641 --- /dev/null +++ b/jig/test/interp.rkt @@ -0,0 +1,8 @@ +#lang racket +(require "../interp.rkt") +(require "../interp-io.rkt") +(require "../parse.rkt") +(require "test-runner.rkt") +(test (λ p (interp (apply parse p)))) +(test/io (λ (in . p) (interp/io (apply parse p) in))) + diff --git a/jig/test/test-runner.rkt b/jig/test/test-runner.rkt new file mode 100644 index 0000000..88ad438 --- /dev/null +++ b/jig/test/test-runner.rkt @@ -0,0 +1,280 @@ +#lang racket +(provide test test/io) +(require rackunit) + +(define (test run) + (begin ;; Abscond + (check-equal? (run 7) 7) + (check-equal? (run -8) -8)) + + (begin ;; Blackmail + (check-equal? (run '(add1 (add1 7))) 9) + (check-equal? (run '(add1 (sub1 7))) 7)) + + (begin ;; Con + (check-equal? (run '(if (zero? 0) 1 2)) 1) + (check-equal? (run '(if (zero? 1) 1 2)) 2) + (check-equal? (run '(if (zero? -7) 1 2)) 2) + (check-equal? (run '(if (zero? 0) + (if (zero? 1) 1 2) + 7)) + 2) + (check-equal? (run '(if (zero? (if (zero? 0) 1 0)) + (if (zero? 1) 1 2) + 7)) + 7)) + + (begin ;; Dupe + (check-equal? (run #t) #t) + (check-equal? (run #f) #f) + (check-equal? (run '(if #t 1 2)) 1) + (check-equal? (run '(if #f 1 2)) 2) + (check-equal? (run '(if 0 1 2)) 1) + (check-equal? (run '(if #t 3 4)) 3) + (check-equal? (run '(if #f 3 4)) 4) + (check-equal? (run '(if 0 3 4)) 3) + (check-equal? (run '(zero? 4)) #f) + (check-equal? (run '(zero? 0)) #t)) + + (begin ;; Dodger + (check-equal? (run #\a) #\a) + (check-equal? (run #\b) #\b) + (check-equal? (run '(char? #\a)) #t) + (check-equal? (run '(char? #t)) #f) + (check-equal? (run '(char? 8)) #f) + (check-equal? (run '(char->integer #\a)) (char->integer #\a)) + (check-equal? (run '(integer->char 955)) #\λ)) + + (begin ;; Evildoer + (check-equal? (run '(void)) (void)) + (check-equal? (run '(begin 1 2)) 2) + (check-equal? (run '(eof-object? (void))) #f)) + + (begin ;; Extort + (check-equal? (run '(add1 #f)) 'err) + (check-equal? (run '(sub1 #f)) 'err) + (check-equal? (run '(zero? #f)) 'err) + (check-equal? (run '(char->integer #f)) 'err) + (check-equal? (run '(integer->char #f)) 'err) + (check-equal? (run '(integer->char -1)) 'err) + (check-equal? (run '(write-byte #f)) 'err) + (check-equal? (run '(write-byte -1)) 'err) + (check-equal? (run '(write-byte 256)) 'err) + (check-equal? (run '(begin (integer->char 97) + (integer->char 98))) + #\b)) + + (begin ;; Fraud + (check-equal? (run '(let ((x 7)) x)) 7) + (check-equal? (run '(let ((x 7)) 2)) 2) + (check-equal? (run '(let ((x 7)) (add1 x))) 8) + (check-equal? (run '(let ((x (add1 7))) x)) 8) + (check-equal? (run '(let ((x 7)) (let ((y 2)) x))) 7) + (check-equal? (run '(let ((x 7)) (let ((x 2)) x))) 2) + (check-equal? (run '(let ((x 7)) (let ((x (add1 x))) x))) 8) + + (check-equal? (run '(let ((x 0)) + (if (zero? x) 7 8))) + 7) + (check-equal? (run '(let ((x 1)) + (add1 (if (zero? x) 7 8)))) + 9) + (check-equal? (run '(+ 3 4)) 7) + (check-equal? (run '(- 3 4)) -1) + (check-equal? (run '(+ (+ 2 1) 4)) 7) + (check-equal? (run '(+ (+ 2 1) (+ 2 2))) 7) + (check-equal? (run '(let ((x (+ 1 2))) + (let ((z (- 4 x))) + (+ (+ x x) z)))) + 7) + + (check-equal? (run '(= 5 5)) #t) + (check-equal? (run '(= 4 5)) #f) + (check-equal? (run '(= (add1 4) 5)) #t) + (check-equal? (run '(< 5 5)) #f) + (check-equal? (run '(< 4 5)) #t) + (check-equal? (run '(< (add1 4) 5)) #f)) + + (begin ;; Hustle + (check-equal? (run ''()) '()) + (check-equal? (run '(empty? '())) #t) + (check-equal? (run '(empty? 3)) #f) + (check-equal? (run '(empty? (cons 1 2))) #f) + (check-equal? (run '(box 1)) (box 1)) + (check-equal? (run '(box -1)) (box -1)) + (check-equal? (run '(cons 1 2)) (cons 1 2)) + (check-equal? (run '(unbox (box 1))) 1) + (check-equal? (run '(car (cons 1 2))) 1) + (check-equal? (run '(cdr (cons 1 2))) 2) + (check-equal? (run '(cons 1 '())) (list 1)) + (check-equal? (run '(let ((x (cons 1 2))) + (begin (cdr x) + (car x)))) + 1) + (check-equal? (run '(let ((x (cons 1 2))) + (let ((y (box 3))) + (unbox y)))) + 3) + (check-equal? (run '(eq? 1 1)) #t) + (check-equal? (run '(eq? 1 2)) #f) + (check-equal? (run '(eq? (cons 1 2) (cons 1 2))) #f) + (check-equal? (run '(let ((x (cons 1 2))) (eq? x x))) #t)) + + (begin ;; Hoax + (check-equal? (run '(make-vector 0 0)) #()) + (check-equal? (run '(make-vector 1 0)) #(0)) + (check-equal? (run '(make-vector 3 0)) #(0 0 0)) + (check-equal? (run '(make-vector 3 5)) #(5 5 5)) + (check-equal? (run '(vector? (make-vector 0 0))) #t) + (check-equal? (run '(vector? (cons 0 0))) #f) + (check-equal? (run '(vector-ref (make-vector 0 #f) 0)) 'err) + (check-equal? (run '(vector-ref (make-vector 3 5) -1)) 'err) + (check-equal? (run '(vector-ref (make-vector 3 5) 0)) 5) + (check-equal? (run '(vector-ref (make-vector 3 5) 1)) 5) + (check-equal? (run '(vector-ref (make-vector 3 5) 2)) 5) + (check-equal? (run '(vector-ref (make-vector 3 5) 3)) 'err) + (check-equal? (run '(let ((x (make-vector 3 5))) + (begin (vector-set! x 0 4) + x))) + #(4 5 5)) + (check-equal? (run '(let ((x (make-vector 3 5))) + (begin (vector-set! x 1 4) + x))) + #(5 4 5)) + (check-equal? (run '(vector-length (make-vector 3 #f))) 3) + (check-equal? (run '(vector-length (make-vector 0 #f))) 0) + (check-equal? (run '"") "") + (check-equal? (run '"fred") "fred") + (check-equal? (run '"wilma") "wilma") + (check-equal? (run '(make-string 0 #\f)) "") + (check-equal? (run '(make-string 3 #\f)) "fff") + (check-equal? (run '(make-string 3 #\g)) "ggg") + (check-equal? (run '(string-length "")) 0) + (check-equal? (run '(string-length "fred")) 4) + (check-equal? (run '(string-ref "" 0)) 'err) + (check-equal? (run '(string-ref (make-string 0 #\a) 0)) 'err) + (check-equal? (run '(string-ref "fred" 0)) #\f) + (check-equal? (run '(string-ref "fred" 1)) #\r) + (check-equal? (run '(string-ref "fred" 2)) #\e) + (check-equal? (run '(string-ref "fred" 4)) 'err) + (check-equal? (run '(string? "fred")) #t) + (check-equal? (run '(string? (cons 1 2))) #f) + (check-equal? (run '(begin (make-string 3 #\f) + (make-string 3 #\f))) + "fff")) + + (begin ;; Iniquity + (check-equal? (run + '(define (f x) x) + '(f 5)) + 5) + (check-equal? (run + '(define (tri x) + (if (zero? x) + 0 + (+ x (tri (sub1 x))))) + '(tri 9)) + 45) + + (check-equal? (run + '(define (even? x) + (if (zero? x) + #t + (odd? (sub1 x)))) + '(define (odd? x) + (if (zero? x) + #f + (even? (sub1 x)))) + '(even? 101)) + #f) + + (check-equal? (run + '(define (map-add1 xs) + (if (empty? xs) + '() + (cons (add1 (car xs)) + (map-add1 (cdr xs))))) + '(map-add1 (cons 1 (cons 2 (cons 3 '()))))) + '(2 3 4)) + (check-equal? (run '(define (f x y) y) + '(f 1 (add1 #f))) + 'err))) + +(define (test/io run) + (begin ;; Evildoer + (check-equal? (run "" 7) (cons 7 "")) + (check-equal? (run "" '(write-byte 97)) (cons (void) "a")) + (check-equal? (run "a" '(read-byte)) (cons 97 "")) + (check-equal? (run "b" '(begin (write-byte 97) (read-byte))) + (cons 98 "a")) + (check-equal? (run "" '(read-byte)) (cons eof "")) + (check-equal? (run "" '(eof-object? (read-byte))) (cons #t "")) + (check-equal? (run "a" '(eof-object? (read-byte))) (cons #f "")) + (check-equal? (run "" '(begin (write-byte 97) (write-byte 98))) + (cons (void) "ab")) + + (check-equal? (run "ab" '(peek-byte)) (cons 97 "")) + (check-equal? (run "ab" '(begin (peek-byte) (read-byte))) (cons 97 "")) + (check-equal? (run "†" '(read-byte)) (cons 226 "")) + (check-equal? (run "†" '(peek-byte)) (cons 226 ""))) + + (begin ;; Extort + (check-equal? (run "" '(write-byte #t)) (cons 'err ""))) + + (begin ;; Fraud + (check-equal? (run "" '(let ((x 97)) (write-byte x))) (cons (void) "a")) + (check-equal? (run "" + '(let ((x 97)) + (begin (write-byte x) + x))) + (cons 97 "a")) + (check-equal? (run "b" '(let ((x 97)) (begin (read-byte) x))) + (cons 97 "")) + (check-equal? (run "b" '(let ((x 97)) (begin (peek-byte) x))) + (cons 97 ""))) + + (begin ;; Iniquity + (check-equal? (run "" + '(define (print-alphabet i) + (if (zero? i) + (void) + (begin (write-byte (- 123 i)) + (print-alphabet (sub1 i))))) + '(print-alphabet 26)) + (cons (void) "abcdefghijklmnopqrstuvwxyz")) + + (check-equal? (run "" + '(define (f x) + (write-byte x)) + '(f 97)) + (cons (void) "a")) + (check-equal? (run "" + '(define (f x y) + (write-byte x)) + '(f 97 98)) + (cons (void) "a")) + (check-equal? (run "" + '(define (f x) + (let ((y x)) + (write-byte y))) + '(f 97)) + (cons (void) "a")) + (check-equal? (run "" + '(define (f x y) + (let ((y x)) + (write-byte y))) + '(f 97 98)) + (cons (void) "a")) + (check-equal? (run "" + '(define (f x) + (write-byte x)) + '(let ((z 97)) + (f z))) + (cons (void) "a")) + (check-equal? (run "" + '(define (f x y) + (write-byte x)) + '(let ((z 97)) + (f z 98))) + (cons (void) "a")))) + diff --git a/jig/types.rkt b/jig/types.rkt new file mode 100644 index 0000000..e0da50d --- /dev/null +++ b/jig/types.rkt @@ -0,0 +1,90 @@ +#lang racket +(provide (all-defined-out)) +(require ffi/unsafe) + +(define imm-shift 3) +(define imm-mask #b111) +(define ptr-mask #b111) +(define type-box #b001) +(define type-cons #b010) +(define type-vect #b011) +(define type-str #b100) +(define int-shift (+ 1 imm-shift)) +(define mask-int #b1111) +(define char-shift (+ 2 imm-shift)) +(define type-int #b0000) +(define type-char #b01000) +(define mask-char #b11111) + +(define (bits->value b) + (cond [(= b (value->bits #t)) #t] + [(= b (value->bits #f)) #f] + [(= b (value->bits eof)) eof] + [(= b (value->bits (void))) (void)] + [(= b (value->bits '())) '()] + [(int-bits? b) + (arithmetic-shift b (- int-shift))] + [(char-bits? b) + (integer->char (arithmetic-shift b (- char-shift)))] + [(box-bits? b) + (box (bits->value (heap-ref b)))] + [(cons-bits? b) + (cons (bits->value (heap-ref (+ b 8))) + (bits->value (heap-ref b)))] + [(vect-bits? b) + (if (zero? (untag b)) + (vector) + (build-vector (heap-ref b) + (lambda (j) + (bits->value (heap-ref (+ b (* 8 (add1 j))))))))] + [(str-bits? b) + (if (zero? (untag b)) + (string) + (build-string (heap-ref b) + (lambda (j) + (char-ref (+ b 8) j))))] + [else (error "invalid bits")])) + +(define (value->bits v) + (cond [(eq? v #t) #b00011000] + [(eq? v #f) #b00111000] + [(integer? v) (arithmetic-shift v int-shift)] + [(eof-object? v) #b01011000] + [(void? v) #b01111000] + [(empty? v) #b10011000] + [(char? v) + (bitwise-ior type-char + (arithmetic-shift (char->integer v) char-shift))] + [else (error "not an immediate value" v)])) + +(define (int-bits? v) + (= type-int (bitwise-and v mask-int))) + +(define (char-bits? v) + (= type-char (bitwise-and v mask-char))) + +(define (imm-bits? v) + (zero? (bitwise-and v imm-mask))) + +(define (cons-bits? v) + (= type-cons (bitwise-and v imm-mask))) + +(define (box-bits? v) + (= type-box (bitwise-and v imm-mask))) + +(define (vect-bits? v) + (= type-vect (bitwise-and v imm-mask))) + +(define (str-bits? v) + (= type-str (bitwise-and v imm-mask))) + +(define (untag i) + (arithmetic-shift (arithmetic-shift i (- (integer-length ptr-mask))) + (integer-length ptr-mask))) + +(define (heap-ref i) + (ptr-ref (cast (untag i) _int64 _pointer) _int64)) + +(define (char-ref i j) + (integer->char (ptr-ref (cast (untag i) _int64 _pointer) _uint32 j))) +