boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

commit 9d8abb8d6a2cd646c041021c86a3337af964f64c
parent 641a2e87e7b4efa2fe6f4589a65f2d6c3a4531d3
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 27 Apr 2026 14:02:08 -0700

scheme1: quasiquote, variadic prims

Diffstat:
Mdocs/LISP.md | 5+++++
Mdocs/SCHEME1-R7RS-TODO.md | 2+-
Mscheme1/prelude.scm | 6++----
Mscheme1/scheme1.P1pp | 373++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Atests/scheme1/107-quasiquote.expected-exit | 1+
Atests/scheme1/107-quasiquote.scm | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/scheme1/108-variadic-arith.expected-exit | 1+
Atests/scheme1/108-variadic-arith.scm | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 461 insertions(+), 56 deletions(-)

diff --git a/docs/LISP.md b/docs/LISP.md @@ -142,6 +142,11 @@ Flat namespace — no library carving for v1. is a pure op, not a syscall, so the normal `(ok . val)` convention doesn't apply). +Arities follow R7RS: `+ * bit-and bit-or bit-xor` accept 0+ args +(identities `0 1 -1 0 0`); `-` accepts 1+ (`(- x)` is unary negate); +`= < > <= >=` accept 2+ and chain pairwise. `quotient` / `remainder` / +`modulo` / `arithmetic-shift` are binary; `bit-not` is unary. + Division rounding (R7RS semantics) — worth stating since target ISAs differ at the instruction level: diff --git a/docs/SCHEME1-R7RS-TODO.md b/docs/SCHEME1-R7RS-TODO.md @@ -21,7 +21,7 @@ and rough implementation notes. - [ ] **`let-values`, `let*-values`, `define-values`.** Depend on the MV protocol. -- [ ] **Flat (one-level) quasiquote.** `(quasiquote ...)`, +- [x] **Flat (one-level) quasiquote.** `(quasiquote ...)`, `(unquote x)`, `(unquote-splicing xs)` evaluated at the single template depth. Nested quasiquote is unsupported. Works on lists today; extend to vectors when those land. diff --git a/scheme1/prelude.scm b/scheme1/prelude.scm @@ -474,16 +474,14 @@ (cons #t (%port (cdr r) (make-bytevector BUFSIZE) 0 0)) r))) -;; bit-or is 2-arg only in scheme1 today, so the 3-flag combinations -;; are written as nested 2-arg calls. (define (open-output path) (let ((r (sys-openat AT_FDCWD path - (bit-or O_WRONLY (bit-or O_CREAT O_TRUNC)) MODE_644))) + (bit-or O_WRONLY O_CREAT O_TRUNC) MODE_644))) (if (car r) (cons #t (%port (cdr r) #f 0 0)) r))) (define (open-append path) (let ((r (sys-openat AT_FDCWD path - (bit-or O_WRONLY (bit-or O_CREAT O_APPEND)) MODE_644))) + (bit-or O_WRONLY O_CREAT O_APPEND) MODE_644))) (if (car r) (cons #t (%port (cdr r) #f 0 0)) r))) (define (close p) (sys-close (port-fd p))) diff --git a/scheme1/scheme1.P1pp b/scheme1/scheme1.P1pp @@ -309,6 +309,8 @@ %beqz(a1, &::quote) %addi(a1, a0, -44) ; ',' %beqz(a1, &::comma) + %addi(a1, a0, -96) ; '`' + %beqz(a1, &::quasiquote) %addi(a1, a0, -34) ; '"' %beqz(a1, &::string) @@ -413,15 +415,33 @@ %tail(&cons) ::comma - # Mirror of ::quote: consume the leading ','; recurse into parse_one - # for the inner datum; build (unquote <datum>). The comma sugar exists - # purely so pmatch patterns can be written as `,ident`; there is no - # quasiquote evaluator. Outside a pmatch pattern this list reaches - # eval as an application of the (unbound) `unquote` and dies through - # the standard unbound-variable path. + # Consume the leading ','. If the next byte is '@', this is `,@` + # (unquote-splicing); otherwise it's `,` (unquote). Recurse into + # parse_one for the inner datum and build the appropriate pair. + # The comma sugar predates quasiquote -- it was added so pmatch + # patterns could be written as `,ident`. Outside a quasiquote + # template (and outside pmatch) `(unquote x)` still reaches eval as + # an application of the unbound `unquote` and dies through the + # standard unbound-variable path. %lda_global(t0, t2, &readbuf_pos) %addi(t0, t0, 1) %st(t0, t2, 0) + %ld_global(t1, &readbuf_len) + %beq(t0, t1, &::comma_atom) + %readbuf_byte(a0, t0) + %addi(a1, a0, -64) ; '@' + %bnez(a1, &::comma_atom) + %addi(t0, t0, 1) + %st(t0, t2, 0) + %call(&parse_one) + %li(a1, %imm_val(%IMM.NIL)) + %call(&cons) + %ld_global(t0, &sym_unquote_splicing) + %mov(a1, a0) + %mov(a0, t0) + %tail(&cons) + + ::comma_atom %call(&parse_one) %li(a1, %imm_val(%IMM.NIL)) %call(&cons) @@ -430,6 +450,23 @@ %mov(a0, t0) %tail(&cons) + ::quasiquote + # Consume the leading '`'; recurse into parse_one for the datum; + # build (quasiquote <datum>). The evaluator implements one-level + # template walking with `,` and `,@` substitution; nested + # quasiquote forms are preserved literally rather than recursed + # into. + %lda_global(t0, t2, &readbuf_pos) + %addi(t0, t0, 1) + %st(t0, t2, 0) + %call(&parse_one) + %li(a1, %imm_val(%IMM.NIL)) + %call(&cons) + %ld_global(t0, &sym_quasiquote) + %mov(a1, a0) + %mov(a0, t0) + %tail(&cons) + ::char_lit # Cursor is already past '#\\'; parse_char scans the body and returns # a tagged fixnum (the u8 char value). @@ -1154,6 +1191,7 @@ %ldl(t0, expr) %car(t0, t0) ; t0 = head %dispatch_form(&sym_quote, &::do_quote) + %dispatch_form(&sym_quasiquote, &::do_quasiquote) %dispatch_form(&sym_if, &::do_if) %dispatch_form(&sym_lambda, &::do_lambda) %dispatch_form(&sym_define, &::do_define) @@ -1199,6 +1237,9 @@ %car(a0, a0) %eret + ::do_quasiquote + %tail_to_handler(&eval_quasiquote) + ::do_if %tail_to_handler(&eval_if) ::do_lambda @@ -1287,6 +1328,150 @@ %ldl(a0, head) }) +# qq_walk(t=a0, env=a1) -> result (a0). One-level quasiquote walker +# (R7RS §4.2.8 minus the nested-template recursion). Atoms and any +# `(quasiquote ...)` form are returned literally; an `(unquote x)` head +# evaluates x; otherwise we walk the spine, splicing `(unquote-splicing +# xs)` elements and recursing on every other element. Improper tails +# are qq'd and stitched back onto the result. +# +# Locals: +# t the template +# env evaluation environment for `,` and `,@` +# head accumulator head (NIL until first cell appended) +# tail most recently appended cell +# walk spine cursor (advances) +# spliced inner cursor used during `,@` expansion +%fn2(qq_walk, {t env head tail walk spliced}, { + %stl(a0, t) + %stl(a1, env) + %li(t0, %imm_val(%IMM.NIL)) + %stl(t0, head) + %stl(t0, tail) + + # Atom -> return as-is. + %tagof(t0, a0) + %li(t1, %TAG.PAIR) + %bne(t0, t1, &::atom_ret) + + # (unquote x) at the top -> tail eval(cadr t, env). + %ldl(t0, t) + %car(t1, t0) + %ld_global(t2, &sym_unquote) + %beq(t1, t2, &::do_unquote) + # Nested (quasiquote ...) -> literal; do not recurse. + %ld_global(t2, &sym_quasiquote) + %beq(t1, t2, &::atom_ret) + + # Walk the spine. + %ldl(t0, t) + %stl(t0, walk) + + ::loop + %ldl(t0, walk) + %if_nil(t1, t0, &::done) + %tagof(t1, t0) + %li(t2, %TAG.PAIR) + %bne(t1, t2, &::improper) + + # elem = car(walk). Detect (unquote-splicing ...) shape. + %car(a0, t0) + %tagof(t1, a0) + %li(t2, %TAG.PAIR) + %bne(t1, t2, &::recurse_elem) + %car(t1, a0) + %ld_global(t2, &sym_unquote_splicing) + %bne(t1, t2, &::recurse_elem) + + # spliced = eval(cadr elem, env) + %cdr(a0, a0) + %car(a0, a0) + %ldl(a1, env) + %call(&eval) + %stl(a0, spliced) + + ::splice_loop + %ldl(t0, spliced) + %if_nil(t1, t0, &::splice_advance_walk) + %tagof(t1, t0) + %li(t2, %TAG.PAIR) + %bne(t1, t2, &::splice_advance_walk) + %car(a0, t0) + %li(a1, %imm_val(%IMM.NIL)) + %call(&cons) + %ldl(t0, head) + %if_nil(t1, t0, &::splice_first) + %ldl(t0, tail) + %set_cdr(a0, t0) + %stl(a0, tail) + %b(&::splice_step) + ::splice_first + %stl(a0, head) + %stl(a0, tail) + ::splice_step + %ldl(t0, spliced) + %cdr(t0, t0) + %stl(t0, spliced) + %b(&::splice_loop) + + ::splice_advance_walk + %ldl(t0, walk) + %cdr(t0, t0) + %stl(t0, walk) + %b(&::loop) + + ::recurse_elem + # new_elem = qq_walk(elem, env); elem already in a0. + %ldl(a1, env) + %call(&qq_walk) + %li(a1, %imm_val(%IMM.NIL)) + %call(&cons) + %ldl(t0, head) + %if_nil(t1, t0, &::elem_first) + %ldl(t0, tail) + %set_cdr(a0, t0) + %stl(a0, tail) + %b(&::elem_step) + ::elem_first + %stl(a0, head) + %stl(a0, tail) + ::elem_step + %ldl(t0, walk) + %cdr(t0, t0) + %stl(t0, walk) + %b(&::loop) + + ::improper + # walk holds a non-pair non-NIL cdr; qq it and stitch onto tail. + %ldl(a0, walk) + %ldl(a1, env) + %call(&qq_walk) + %ldl(t0, tail) + %set_cdr(a0, t0) + + ::done + %ldl(a0, head) + %eret + + ::atom_ret + %ldl(a0, t) + %eret + + ::do_unquote + %ldl(t0, t) + %cdr(t0, t0) + %car(a0, t0) + %ldl(a1, env) + %tail(&eval) +}) + +# eval_quasiquote(rest=a0, env=a1) -> result (a0). rest = (template . _); +# tail-walks the template through qq_walk. +%fn(eval_quasiquote, 0, { + %car(a0, a0) + %tail(&qq_walk) +}) + # apply(fn=a0, args=a1) -> result (a0) # # Locals: @@ -1365,6 +1550,8 @@ %fn(intern_special_forms, 0, { %intern_form(&name_quote, 5, &sym_quote) + %intern_form(&name_quasiquote, 10, &sym_quasiquote) + %intern_form(&name_unquote_splicing, 16, &sym_unquote_splicing) %intern_form(&name_if, 2, &sym_if) %intern_form(&name_lambda, 6, &sym_lambda) %intern_form(&name_define, 6, &sym_define) @@ -3266,10 +3453,11 @@ # and the result goes back in a0. Most use no frame at all; the few that # need recursion (apply) carry a small one via %fn. # -# Two-arg arithmetic on tagged fixnums takes advantage of the (n << 3) -# representation: + / - / signed compare / bit-and / bit-or all work -# directly on the tagged words. Only * needs to untag one operand to -# avoid a stray <<6. +# Arithmetic / compare / bitwise primitives on tagged fixnums take +# advantage of the (n << 3) representation: + / - / signed compare / +# bit-and / bit-or / bit-xor all work directly on the tagged words, so +# the variadic fold loop preserves the tag at every step. Only * has to +# untag each incoming operand to avoid a stray <<6. # (sys-exit code) -- libp1pp's sys_exit doesn't return; %b, not %call. :prim_sys_exit_entry @@ -3612,63 +3800,118 @@ %ret %endscope -# Two-arg arithmetic. +# Variadic arithmetic. (+ ...) folds with identity 0; (* ...) folds with +# identity 1; (- x) is unary negate, (- x y z ...) folds left. :prim_plus_entry - %car(t0, a0) - %cdr(t1, a0) - %car(t1, t1) - %add(a0, t0, t1) +%scope prim_plus + %li(t0, 0) ; tagged 0; tag bits stay 0 across %add + ::loop + %if_nil(t1, a0, &::done) + %car(t1, a0) + %add(t0, t0, t1) + %cdr(a0, a0) + %b(&::loop) + ::done + %mov(a0, t0) %ret +%endscope +# (- x) -> -x; (- x y ...) -> x - y - ... . (-) is undefined behavior +# per the primitive-failure policy. :prim_minus_entry - %car(t0, a0) - %cdr(t1, a0) - %car(t1, t1) - %sub(a0, t0, t1) +%scope prim_minus + %car(t0, a0) ; seed = first arg (tagged) + %cdr(a0, a0) + %if_nil(t1, a0, &::neg) + ::loop + %if_nil(t1, a0, &::done) + %car(t1, a0) + %sub(t0, t0, t1) + %cdr(a0, a0) + %b(&::loop) + ::neg + %li(t1, 0) ; unary: 0 - seed + %sub(t0, t1, t0) + ::done + %mov(a0, t0) %ret +%endscope +# Multiply keeps the accumulator tagged and untags each incoming arg: +# (a<<3) * b == (a*b)<<3, so the loop preserves the fixnum tag. :prim_mult_entry - %car(t0, a0) - %cdr(t1, a0) - %car(t1, t1) +%scope prim_mult + %li(t0, 8) ; tagged 1 = mkfix(1) + ::loop + %if_nil(t1, a0, &::done) + %car(t1, a0) %untag_fix(t1, t1) - %mul(a0, t0, t1) + %mul(t0, t0, t1) + %cdr(a0, a0) + %b(&::loop) + ::done + %mov(a0, t0) %ret +%endscope +# Variadic chained comparisons: (op a b c ...) ⇔ (a op b) ∧ (b op c) ∧ ... +# Walks the tail with a single live `prev` register; a0 is reused as the +# args cursor and finally as the result. <2 args is undefined behavior. :prim_eq_entry %scope prim_eq - %car(t0, a0) - %cdr(t1, a0) - %car(t1, t1) - %li(a0, %imm_val(%IMM.FALSE)) - %bne(t0, t1, &::end) + %car(t0, a0) ; prev = first + %cdr(a0, a0) + ::loop + %if_nil(t1, a0, &::true) + %car(t1, a0) ; curr + %bne(t0, t1, &::false) + %mov(t0, t1) + %cdr(a0, a0) + %b(&::loop) + ::true %li(a0, %imm_val(%IMM.TRUE)) - ::end + %ret + ::false + %li(a0, %imm_val(%IMM.FALSE)) %ret %endscope :prim_lt_entry %scope prim_lt %car(t0, a0) - %cdr(t1, a0) - %car(t1, t1) - %li(a0, %imm_val(%IMM.TRUE)) - %blt(t0, t1, &::end) + %cdr(a0, a0) + ::loop + %if_nil(t1, a0, &::true) + %car(t1, a0) + %blt(t0, t1, &::ok) ; prev < curr -> continue %li(a0, %imm_val(%IMM.FALSE)) - ::end + %ret + ::ok + %mov(t0, t1) + %cdr(a0, a0) + %b(&::loop) + ::true + %li(a0, %imm_val(%IMM.TRUE)) %ret %endscope :prim_gt_entry %scope prim_gt %car(t0, a0) - %cdr(t1, a0) - %car(t1, t1) - %li(a0, %imm_val(%IMM.TRUE)) - %blt(t1, t0, &::end) + %cdr(a0, a0) + ::loop + %if_nil(t1, a0, &::true) + %car(t1, a0) + %blt(t1, t0, &::ok) ; curr < prev <=> prev > curr -> continue %li(a0, %imm_val(%IMM.FALSE)) - ::end + %ret + ::ok + %mov(t0, t1) + %cdr(a0, a0) + %b(&::loop) + ::true + %li(a0, %imm_val(%IMM.TRUE)) %ret %endscope @@ -3688,26 +3931,50 @@ %rem(a0, t0, t1) %ret +# Variadic bitwise folds. Tagged fixnums have low 3 bits = 0, so AND/OR/ +# XOR with another tagged fixnum preserves the tag in the accumulator. +# Identities: bit-and -> -1 (tagged -8), bit-or -> 0, bit-xor -> 0. :prim_bit_and_entry - %car(t0, a0) - %cdr(t1, a0) - %car(t1, t1) - %and(a0, t0, t1) +%scope prim_bit_and + %li(t0, -8) ; tagged -1; AND-identity preserves the tag + ::loop + %if_nil(t1, a0, &::done) + %car(t1, a0) + %and(t0, t0, t1) + %cdr(a0, a0) + %b(&::loop) + ::done + %mov(a0, t0) %ret +%endscope :prim_bit_or_entry - %car(t0, a0) - %cdr(t1, a0) - %car(t1, t1) - %or(a0, t0, t1) +%scope prim_bit_or + %li(t0, 0) + ::loop + %if_nil(t1, a0, &::done) + %car(t1, a0) + %or(t0, t0, t1) + %cdr(a0, a0) + %b(&::loop) + ::done + %mov(a0, t0) %ret +%endscope -# (bit-xor x y) -- both operands have low 3 tag bits == 0 (fixnums), so -# bitwise XOR preserves zero tag bits in the result. :prim_bit_xor_entry - %args2(t0, t1, a0) - %xor(a0, t0, t1) +%scope prim_bit_xor + %li(t0, 0) + ::loop + %if_nil(t1, a0, &::done) + %car(t1, a0) + %xor(t0, t0, t1) + %cdr(a0, a0) + %b(&::loop) + ::done + %mov(a0, t0) %ret +%endscope # (bit-not n) -- bitwise complement. Untag, XOR with -1 (= ~n), retag. # Can't XOR the tagged value directly: that would flip the low 3 tag bits. @@ -5682,6 +5949,8 @@ :name_do "do" 00 00 00 00 00 :name_case_lambda "case-lambda" 00 00 00 00 :name_unquote "unquote" +:name_quasiquote "quasiquote" 00 00 00 00 00 +:name_unquote_splicing "unquote-splicing" 00 00 00 00 00 00 00 :name_guard "guard" 00 00 :name_underscore "_" 00 00 00 00 00 00 :name_dollar "$" 00 00 00 00 00 00 @@ -5951,6 +6220,8 @@ :sym_do $(0) :sym_case_lambda $(0) :sym_unquote $(0) +:sym_quasiquote $(0) +:sym_unquote_splicing $(0) :sym_guard $(0) :sym_underscore $(0) :sym_dollar $(0) diff --git a/tests/scheme1/107-quasiquote.expected-exit b/tests/scheme1/107-quasiquote.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/scheme1/107-quasiquote.scm b/tests/scheme1/107-quasiquote.scm @@ -0,0 +1,56 @@ +; One-level quasiquote: backtick reads as (quasiquote ...); inside that +; template, (unquote x) splices a single evaluated value, and +; (unquote-splicing xs) splices the elements of xs in place. Atoms in +; the template are returned literally; nested quasiquote forms are not +; supported -- we don't recurse into them, so a `(quasiquote ...) form +; sitting inside the template is preserved as-is. + +;; --- Atom template: returns the atom ------------------------------------- +(if (= 7 `7) 0 (sys-exit 1)) +(if (eq? 'foo `foo) 0 (sys-exit 2)) +(if (equal? '() `()) 0 (sys-exit 3)) + +;; --- All-literal list template ------------------------------------------ +(if (equal? (list 1 2 3) `(1 2 3)) 0 (sys-exit 4)) +(if (equal? '(a b c) `(a b c)) 0 (sys-exit 5)) + +;; --- ,x evaluates one element in place ---------------------------------- +(define x 10) +(if (equal? (list 1 10 3) `(1 ,x 3)) 0 (sys-exit 6)) +(if (equal? (list 'a 11 'b) `(a ,(+ x 1) b)) 0 (sys-exit 7)) + +;; --- ,x at head and tail positions -------------------------------------- +(if (equal? (list 10 'b) `(,x b)) 0 (sys-exit 8)) +(if (equal? (list 'a 10) `(a ,x)) 0 (sys-exit 9)) + +;; --- ,@xs splices a list in place --------------------------------------- +(define xs (list 2 3 4)) +(if (equal? (list 1 2 3 4 5) `(1 ,@xs 5)) 0 (sys-exit 10)) + +;; --- ,@ at the head of the list ----------------------------------------- +(if (equal? (list 2 3 4 'tail) `(,@xs tail)) 0 (sys-exit 11)) + +;; --- ,@ at the tail of the list ----------------------------------------- +(if (equal? (list 'head 2 3 4) `(head ,@xs)) 0 (sys-exit 12)) + +;; --- ,@'() splices nothing ---------------------------------------------- +(if (equal? (list 'a 'b) `(a ,@'() b)) 0 (sys-exit 13)) + +;; --- Mixed: literals, ,, ,@ in one template ----------------------------- +(if (equal? (list 'h 10 2 3 4 'mid 11 't) + `(h ,x ,@xs mid ,(+ x 1) t)) + 0 (sys-exit 14)) + +;; --- Nested-list template (no ,) is preserved literally ----------------- +(if (equal? '((a b) (c d)) `((a b) (c d))) 0 (sys-exit 15)) + +;; --- , inside a nested list still evaluates at this depth --------------- +(if (equal? (list (list 1 10) 'k) `((1 ,x) k)) 0 (sys-exit 16)) + +;; --- ,@ inside a nested list splices into that nested list -------------- +(if (equal? (list (list 1 2 3 4) 'k) `((1 ,@xs) k)) 0 (sys-exit 17)) + +;; --- (quasiquote x) prefix form parses identically to `x ---------------- +(if (equal? (list 1 10 3) (quasiquote (1 (unquote x) 3))) 0 (sys-exit 18)) + +(sys-exit 0) diff --git a/tests/scheme1/108-variadic-arith.expected-exit b/tests/scheme1/108-variadic-arith.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/scheme1/108-variadic-arith.scm b/tests/scheme1/108-variadic-arith.scm @@ -0,0 +1,73 @@ +; Variadic arity for the arithmetic / comparison / bitwise primitives. +; + * bit-and bit-or bit-xor accept 0+ args; - accepts 1+ (unary +; negate); = < > accept 2+ args and chain pairwise. + +;; --- + (0+) ------------------------------------------------------------- +(if (= 0 (+)) 0 (sys-exit 1)) +(if (= 7 (+ 7)) 0 (sys-exit 2)) +(if (= 5 (+ 2 3)) 0 (sys-exit 3)) +(if (= 10 (+ 1 2 3 4)) 0 (sys-exit 4)) +(if (= 0 (+ 1 -1 2 -2)) 0 (sys-exit 5)) + +;; --- - (1+) ------------------------------------------------------------- +(if (= -5 (- 5)) 0 (sys-exit 6)) +(if (= 5 (- -5)) 0 (sys-exit 7)) +(if (= 1 (- 3 2)) 0 (sys-exit 8)) +(if (= 4 (- 10 1 2 3)) 0 (sys-exit 9)) +(if (= -6 (- 0 1 2 3)) 0 (sys-exit 10)) + +;; --- * (0+) ------------------------------------------------------------- +(if (= 1 (*)) 0 (sys-exit 11)) +(if (= 9 (* 9)) 0 (sys-exit 12)) +(if (= 6 (* 2 3)) 0 (sys-exit 13)) +(if (= 24 (* 2 3 4)) 0 (sys-exit 14)) +(if (= 0 (* 1 2 0 3)) 0 (sys-exit 15)) +(if (= -24 (* -1 2 3 4)) 0 (sys-exit 16)) + +;; --- = (2+) ------------------------------------------------------------- +(if (= 1 1) 0 (sys-exit 17)) +(if (not (= 1 2)) 0 (sys-exit 18)) +(if (= 7 7 7) 0 (sys-exit 19)) +(if (not (= 7 7 8)) 0 (sys-exit 20)) +(if (not (= 7 8 7)) 0 (sys-exit 21)) +(if (= 0 0 0 0) 0 (sys-exit 22)) + +;; --- < (2+) ------------------------------------------------------------- +(if (< 1 2) 0 (sys-exit 23)) +(if (not (< 2 1)) 0 (sys-exit 24)) +(if (not (< 1 1)) 0 (sys-exit 25)) +(if (< 1 2 3) 0 (sys-exit 26)) +(if (not (< 1 2 2)) 0 (sys-exit 27)) +(if (not (< 1 3 2)) 0 (sys-exit 28)) +(if (< -3 -2 -1 0 1) 0 (sys-exit 29)) + +;; --- > (2+) ------------------------------------------------------------- +(if (> 2 1) 0 (sys-exit 30)) +(if (not (> 1 2)) 0 (sys-exit 31)) +(if (> 3 2 1) 0 (sys-exit 32)) +(if (not (> 3 2 2)) 0 (sys-exit 33)) +(if (not (> 3 1 2)) 0 (sys-exit 34)) +(if (> 5 4 3 2 1 0 -1) 0 (sys-exit 35)) + +;; --- bit-and (0+, identity -1) ------------------------------------------ +(if (= -1 (bit-and)) 0 (sys-exit 36)) +(if (= #x33 (bit-and #x33)) 0 (sys-exit 37)) +(if (= #x03 (bit-and #x33 #x0f)) 0 (sys-exit 38)) +(if (= #x01 (bit-and #x33 #x0f #x05)) 0 (sys-exit 39)) +(if (= 0 (bit-and #x0f #xf0)) 0 (sys-exit 40)) + +;; --- bit-or (0+, identity 0) -------------------------------------------- +(if (= 0 (bit-or)) 0 (sys-exit 41)) +(if (= #x21 (bit-or #x21)) 0 (sys-exit 42)) +(if (= #x3f (bit-or #x33 #x0f)) 0 (sys-exit 43)) +(if (= #x37 (bit-or #x33 #x04 #x10)) 0 (sys-exit 44)) +(if (= 7 (bit-or 1 2 4)) 0 (sys-exit 45)) + +;; --- bit-xor (0+, identity 0) ------------------------------------------- +(if (= 0 (bit-xor)) 0 (sys-exit 46)) +(if (= #x55 (bit-xor #x55)) 0 (sys-exit 47)) +(if (= #x33 (bit-xor #x55 #x66)) 0 (sys-exit 48)) +(if (= 0 (bit-xor #x0f #x0f)) 0 (sys-exit 49)) +(if (= #x55 (bit-xor #x0f #x33 #x66 #x0f)) 0 (sys-exit 50)) + +(sys-exit 0)