diff --git a/langs/hoax/ast.rkt b/langs/hoax/ast.rkt index e0505716..f6aa3f63 100644 --- a/langs/hoax/ast.rkt +++ b/langs/hoax/ast.rkt @@ -1,15 +1,14 @@ #lang racket -(provide Lit Prim0 Prim1 Prim2 Prim3 If Eof Begin Let - Var Empty) -;; +(provide Lit Prim0 Prim1 Prim2 Prim3 If Eof Begin + Let Var) ;; type Expr = (Lit Datum) ;; | (Eof) -;; | (Empty) ;; | (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) @@ -25,8 +24,8 @@ ;; | 'write-byte | 'eof-object? ;; | 'box | 'car | 'cdr | 'unbox ;; | 'empty? | 'cons? | 'box? -;; | 'vector? | vector-length -;; | 'string? | string-length +;; | 'vector? | 'vector-length +;; | 'string? | 'string-length ;; type Op2 = '+ | '- | '< | '= ;; | 'eq? | 'cons ;; | 'make-vector | 'vector-ref @@ -34,7 +33,6 @@ ;; type Op3 = 'vector-set! (struct Eof () #:prefab) -(struct Empty () #:prefab) (struct Lit (d) #:prefab) (struct Prim0 (p) #:prefab) (struct Prim1 (p e) #:prefab) diff --git a/langs/hoax/build-runtime.rkt b/langs/hoax/build-runtime.rkt new file mode 100644 index 00000000..72783506 --- /dev/null +++ b/langs/hoax/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/langs/hoax/compile-ops.rkt b/langs/hoax/compile-ops.rkt new file mode 100644 index 00000000..ab32441e --- /dev/null +++ b/langs/hoax/compile-ops.rkt @@ -0,0 +1,239 @@ +#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)] + + ;; TODO + ['make-vector (seq)] + ['vector-ref (seq)] + ['make-string (seq)] + ['string-ref (seq)])) + +;; Op3 -> Asm +(define (compile-op3 p) + (match p + ['vector-set! (seq)])) + + +;; -> 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/langs/hoax/compile-stdin.rkt b/langs/hoax/compile-stdin.rkt new file mode 100644 index 00000000..532ee0eb --- /dev/null +++ b/langs/hoax/compile-stdin.rkt @@ -0,0 +1,13 @@ +#lang racket +(provide main) +(require "parse.rkt") +(require "compile.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 (parse (read))))) + diff --git a/langs/hoax/compile.rkt b/langs/hoax/compile.rkt new file mode 100644 index 00000000..81386f03 --- /dev/null +++ b/langs/hoax/compile.rkt @@ -0,0 +1,135 @@ +#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 r15 'r15) ; stack pad (non-volatile) + +;; Expr -> Asm +(define (compile e) + (prog (Global 'entry) + (Extern 'peek_byte) + (Extern 'read_byte) + (Extern 'write_byte) + (Extern 'raise_error) + (Label 'entry) + ;; save callee-saved register + (Push r15) + (Push rbx) + ;; recv heap pointer + (Mov rbx rdi) + (compile-e e '()) + (Pop rbx) + ;; restore callee-save register + (Pop r15) + (Ret) + ;; Error handler + (Label 'err) + pad-stack + (Call 'raise_error))) + +;; type CEnv = (Listof [Maybe Id]) +;; Expr CEnv -> Asm +(define (compile-e e c) + (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)] + [(Begin e1 e2) + (compile-begin e1 e2 c)] + [(Let x e1 e2) + (compile-let x e1 e2 c)])) + +;; 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) + ;; TODO + (seq)) + +;; [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)) + +;; Op1 Expr CEnv -> Asm +(define (compile-prim1 p e c) + (seq (compile-e e c) + (compile-op1 p))) + +;; Op2 Expr Expr CEnv -> Asm +(define (compile-prim2 p e1 e2 c) + (seq (compile-e e1 c) + (Push rax) + (compile-e e2 (cons #f c)) + (compile-op2 p))) + +;; Op3 Expr Expr Expr CEnv -> Asm +(define (compile-prim3 p e1 e2 e3 c) + (seq (compile-e e1 c) + (Push rax) + (compile-e e2 (cons #f c)) + (Push rax) + (compile-e e3 (cons #f (cons #f c))) + (compile-op3 p))) +;; Expr Expr Expr CEnv -> Asm +(define (compile-if e1 e2 e3 c) + (let ((l1 (gensym 'if)) + (l2 (gensym 'if))) + (seq (compile-e e1 c) + (Cmp rax (value->bits #f)) + (Je l1) + (compile-e e2 c) + (Jmp l2) + (Label l1) + (compile-e e3 c) + (Label l2)))) +;; Expr Expr CEnv -> Asm +(define (compile-begin e1 e2 c) + (seq (compile-e e1 c) + (compile-e e2 c))) + +;; Id Expr Expr CEnv -> Asm +(define (compile-let x e1 e2 c) + (seq (compile-e e1 c) + (Push rax) + (compile-e e2 (cons x c)) + (Add rsp 8))) + +;; 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/langs/hoax/interp-io.rkt b/langs/hoax/interp-io.rkt new file mode 100644 index 00000000..29a82d0b --- /dev/null +++ b/langs/hoax/interp-io.rkt @@ -0,0 +1,13 @@ +#lang racket +(provide interp/io) +(require "interp.rkt") + +;; String Expr -> (Cons Value String) +;; Interpret e with given string as input, +;; return value and collected output as string +(define (interp/io e input) + (parameterize ((current-output-port (open-output-string)) + (current-input-port (open-input-string input))) + (cons (interp e) + (get-output-string (current-output-port))))) + diff --git a/langs/hoax/interp-prim.rkt b/langs/hoax/interp-prim.rkt new file mode 100644 index 00000000..aa73685d --- /dev/null +++ b/langs/hoax/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/langs/hoax/interp-stdin.rkt b/langs/hoax/interp-stdin.rkt new file mode 100644 index 00000000..ce4885f7 --- /dev/null +++ b/langs/hoax/interp-stdin.rkt @@ -0,0 +1,12 @@ +#lang racket +(provide main) +(require "parse.rkt") +(require "interp.rkt") + +;; -> Void +;; Parse and interpret contents of stdin, +;; print result on stdout +(define (main) + (read-line) ; ignore #lang racket line + (println (interp (parse (read))))) + diff --git a/langs/hoax/interp.rkt b/langs/hoax/interp.rkt new file mode 100644 index 00000000..48c5413b --- /dev/null +++ b/langs/hoax/interp.rkt @@ -0,0 +1,76 @@ +#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)) +;; Expr -> Answer +(define (interp e) + (interp-env e '())) + +;; Expr Env -> Answer +(define (interp-env e r) + (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) + ['err 'err] + [v (interp-prim1 p v)])] + [(Prim2 p e1 e2) + (match (interp-env e1 r) + ['err 'err] + [v1 (match (interp-env e2 r) + ['err 'err] + [v2 (interp-prim2 p v1 v2)])])] + [(Prim3 p e1 e2 e3) + (match (interp-env e1 r) + ['err 'err] + [v1 (match (interp-env e2 r) + ['err 'err] + [v2 (match (interp-env e3 r) + ['err 'err] + [v3 (interp-prim3 p v1 v2 v3)])])])] + [(If e0 e1 e2) + (match (interp-env e0 r) + ['err 'err] + [v + (if v + (interp-env e1 r) + (interp-env e2 r))])] + [(Begin e1 e2) + (match (interp-env e1 r) + ['err 'err] + [v (interp-env e2 r)])] + [(Let x e1 e2) + (match (interp-env e1 r) + ['err 'err] + [v (interp-env e2 (ext r x v))])])) + +;; 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/langs/hoax/main.rkt b/langs/hoax/main.rkt new file mode 100644 index 00000000..e0e38924 --- /dev/null +++ b/langs/hoax/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/langs/hoax/parse.rkt b/langs/hoax/parse.rkt new file mode 100644 index 00000000..cc60ab44 --- /dev/null +++ b/langs/hoax/parse.rkt @@ -0,0 +1,47 @@ +#lang racket +(provide parse) +(require "ast.rkt") + +;; S-Expr -> Expr +(define (parse s) + (match s + ['eof (Eof)] + [(? datum?) (Lit s)] + [(? symbol?) (Var s)] + [(list 'quote (list)) (Lit '())] + [(list (? op0? o)) (Prim0 o)] + [(list (? op1? o) e) (Prim1 o (parse e))] + [(list (? op2? o) e1 e2) (Prim2 o (parse e1) (parse e2))] + [(list (? op3? o) e1 e2 e3) (Prim3 o (parse e1) (parse e2) (parse e3))] + [(list 'begin e1 e2) (Begin (parse e1) (parse e2))] + [(list 'if e1 e2 e3) + (If (parse e1) (parse e2) (parse e3))] + [(list 'let (list (list (? symbol? x) e1)) e2) + (Let x (parse e1) (parse e2))] + [_ (error "Parse error")])) + + +;; 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/langs/hoax/run-stdin.rkt b/langs/hoax/run-stdin.rkt new file mode 100644 index 00000000..16cf99e0 --- /dev/null +++ b/langs/hoax/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/langs/hoax/run.rkt b/langs/hoax/run.rkt new file mode 100644 index 00000000..3448bbaf --- /dev/null +++ b/langs/hoax/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/langs/hoax/test/compile.rkt b/langs/hoax/test/compile.rkt new file mode 100644 index 00000000..d52b46dd --- /dev/null +++ b/langs/hoax/test/compile.rkt @@ -0,0 +1,10 @@ +#lang racket +(require "../compile.rkt") +(require "../parse.rkt") +(require "../run.rkt") +(require "test-runner.rkt") + +(test (λ (e) (run (compile (parse e))))) + +(test/io (λ (in e) (run/io (compile (parse e)) in))) + diff --git a/langs/hoax/test/interp.rkt b/langs/hoax/test/interp.rkt new file mode 100644 index 00000000..74d4a050 --- /dev/null +++ b/langs/hoax/test/interp.rkt @@ -0,0 +1,10 @@ +#lang racket +(require "../interp.rkt") +(require "../interp-io.rkt") +(require "../parse.rkt") +(require "test-runner.rkt") + +(test (λ (e) (interp (parse e)))) + +(test/io (λ (in e) (interp/io (parse e) in))) + diff --git a/langs/hoax/test/test-runner.rkt b/langs/hoax/test/test-runner.rkt new file mode 100644 index 00000000..ecb9c11c --- /dev/null +++ b/langs/hoax/test/test-runner.rkt @@ -0,0 +1,198 @@ +#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"))) + +(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 "")))) + diff --git a/langs/hoax/types.rkt b/langs/hoax/types.rkt new file mode 100644 index 00000000..e0da50df --- /dev/null +++ b/langs/hoax/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))) +