boot2

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

commit 14a2a38c5247dd571ff4bb68491008f085cb86fe
parent 64fe1f05c2f3c3f5bb2bd94d993f909fd8a20a88
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 27 Apr 2026 10:57:51 -0700

scheme1: when, unless, case

Diffstat:
Mscheme1/scheme1.P1pp | 364+++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Atests/scheme1/94-when.expected-exit | 1+
Atests/scheme1/94-when.scm | 24++++++++++++++++++++++++
Atests/scheme1/95-unless.expected-exit | 1+
Atests/scheme1/95-unless.scm | 24++++++++++++++++++++++++
Atests/scheme1/96-case.expected-exit | 1+
Atests/scheme1/96-case.scm | 48++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 351 insertions(+), 112 deletions(-)

diff --git a/scheme1/scheme1.P1pp b/scheme1/scheme1.P1pp @@ -1163,6 +1163,9 @@ %dispatch_form(&sym_letrec, &::do_letrec) %dispatch_form(&sym_and, &::do_and) %dispatch_form(&sym_or, &::do_or) + %dispatch_form(&sym_when, &::do_when) + %dispatch_form(&sym_unless, &::do_unless) + %dispatch_form(&sym_case, &::do_case) %dispatch_form(&sym_setbang, &::do_setbang) %dispatch_form(&sym_define_record_type, &::do_define_record_type) %dispatch_form(&sym_pmatch, &::do_pmatch) @@ -1212,6 +1215,12 @@ %tail_to_handler(&eval_and) ::do_or %tail_to_handler(&eval_or) + ::do_when + %tail_to_handler(&eval_when) + ::do_unless + %tail_to_handler(&eval_unless) + ::do_case + %tail_to_handler(&eval_case) ::do_setbang %tail_to_handler(&eval_setbang) ::do_define_record_type @@ -1351,6 +1360,9 @@ %intern_form(&name_letrec, 6, &sym_letrec) %intern_form(&name_and, 3, &sym_and) %intern_form(&name_or, 2, &sym_or) + %intern_form(&name_when, 4, &sym_when) + %intern_form(&name_unless, 6, &sym_unless) + %intern_form(&name_case, 4, &sym_case) %intern_form(&name_setbang, 4, &sym_setbang) %intern_form(&name_define_record_type, 18, &sym_define_record_type) %intern_form(&name_pmatch, 6, &sym_pmatch) @@ -1894,6 +1906,131 @@ %mov(a0, t0) }) +# eval_when(rest=a0, env=a1) -> value (a0). +# (when test body...) -- if test evaluates non-#f, tail-eval body and +# return its last value; otherwise return UNSPEC. Body never enters a +# new scope. +# +# Locals: +# rest +# env +%fn2(eval_when, {rest env}, { + %stl(a0, rest) + %stl(a1, env) + + %car(a0, a0) ; test + %call(&eval) + + %li(t0, %imm_val(%IMM.FALSE)) + %beq(a0, t0, &::skip) + + %ldl(a0, rest) + %cdr(a0, a0) ; body + %ldl(a1, env) + %tail(&eval_body) + + ::skip + %li(a0, %imm_val(%IMM.UNSPEC)) +}) + +# eval_unless(rest=a0, env=a1) -> value (a0). +# Mirror of eval_when: tail-eval body iff test is #f, else UNSPEC. +# +# Locals: +# rest +# env +%fn2(eval_unless, {rest env}, { + %stl(a0, rest) + %stl(a1, env) + + %car(a0, a0) ; test + %call(&eval) + + %li(t0, %imm_val(%IMM.FALSE)) + %bne(a0, t0, &::skip) + + %ldl(a0, rest) + %cdr(a0, a0) ; body + %ldl(a1, env) + %tail(&eval_body) + + ::skip + %li(a0, %imm_val(%IMM.UNSPEC)) +}) + +# eval_case(rest=a0, env=a1) -> value (a0). +# rest is (key-expr clause...). The key is evaluated once; clauses are +# tried in order. Clause shape: +# ((datum...) body...) ; datums are literal, eq?-compared to key +# (else body...) ; matches unconditionally +# Matching uses pointer equality (eq?), which is correct for fixnums, +# symbols, chars, and booleans -- the values case is meant for. The +# matched clause's body is tail-evaluated via eval_body. No-match (and +# no else) returns UNSPEC, mirroring eval_cond's no-match policy. +# +# Locals: +# subject (evaluated key) +# env +# clauses (advances) +# datums (advances within a clause) +%fn2(eval_case, {subject env clauses datums}, { + %stl(a1, env) + + # subject = eval(car(rest), env); clauses = cdr(rest). + %mov(t0, a0) + %cdr(t1, t0) + %stl(t1, clauses) + %car(a0, t0) + %ldl(a1, env) + %call(&eval) + %stl(a0, subject) + + ::loop + %ldl(t0, clauses) + %if_nil(t1, t0, &::no_match) + + %car(t1, t0) ; clause + %car(t2, t1) ; head: datum-list or `else` + + %ld_global(a3, &sym_else) + %beq(t2, a3, &::do_else) + + # Walk the datum list, eq?-compare each against subject. + %stl(t2, datums) + %ldl(a0, subject) + ::scan + %ldl(t0, datums) + %if_nil(t1, t0, &::next_clause) + %car(t1, t0) ; datum + %beq(t1, a0, &::do_body) + %cdr(t0, t0) + %stl(t0, datums) + %b(&::scan) + + ::do_body + %ldl(t0, clauses) + %car(t0, t0) + %cdr(a0, t0) ; body + %ldl(a1, env) + %tail(&eval_body) + + ::do_else + %ldl(t0, clauses) + %car(t0, t0) + %cdr(a0, t0) ; body + %ldl(a1, env) + %tail(&eval_body) + + ::next_clause + %ldl(t0, clauses) + %cdr(t0, t0) + %stl(t0, clauses) + %b(&::loop) + + ::no_match + %li(a0, %imm_val(%IMM.UNSPEC)) +}) + # eval_pmatch(rest=a0, env=a1) -> value (a0). # rest is (subject-expr . clauses). The subject is evaluated once; each # clause is then tried in order against the same subject value, restarting @@ -4968,118 +5105,118 @@ # Surface names. Length is hard-coded at the call site; no NUL needed # because intern takes (ptr, len). Aligned padding via "\0" bytes is # fine -- M0 emits ASCII verbatim. -:name_quote "quote" -:name_if "if" -:name_lambda "lambda" -:name_define "define" -:name_begin "begin" -:name_cond "cond" -:name_else "else" -:name_arrow "=>" -:name_let "let" -:name_letstar "let*" -:name_letrec "letrec" -:name_and "and" -:name_or "or" -:name_setbang "set!" -:name_define_record_type "define-record-type" -:name_pmatch "pmatch" +:name_quote "quote" 00 00 +:name_if "if" 00 00 00 00 00 +:name_lambda "lambda" 00 +:name_define "define" 00 +:name_begin "begin" 00 00 +:name_cond "cond" 00 00 00 +:name_else "else" 00 00 00 +:name_arrow "=>" 00 00 00 00 00 +:name_let "let" 00 00 00 00 +:name_letstar "let*" 00 00 00 +:name_letrec "letrec" 00 +:name_and "and" 00 00 00 00 +:name_or "or" 00 00 00 00 00 +:name_when "when" 00 00 00 +:name_unless "unless" 00 +:name_case "case" 00 00 00 +:name_setbang "set!" 00 00 00 +:name_define_record_type "define-record-type" 00 00 00 00 00 +:name_pmatch "pmatch" 00 :name_unquote "unquote" -:name_guard "guard" -:name_underscore "_" +:name_guard "guard" 00 00 +:name_underscore "_" 00 00 00 00 00 00 # Primitive surface names. -:name_sys_exit "sys-exit" -:name_cons "cons" -:name_car "car" -:name_cdr "cdr" -:name_nullq "null?" -:name_pairq "pair?" +:name_sys_exit "sys-exit" 00 00 00 00 00 00 00 +:name_cons "cons" 00 00 00 +:name_car "car" 00 00 00 00 +:name_cdr "cdr" 00 00 00 00 +:name_nullq "null?" 00 00 +:name_pairq "pair?" 00 00 :name_stringq "string?" -:name_set_car "set-car!" -:name_set_cdr "set-cdr!" -:name_length "length" -:name_list_ref "list-ref" -:name_str_to_sym "string->symbol" -:name_sym_to_str "symbol->string" -:name_num_to_str "number->string" -:name_str_to_num "string->number" -:name_bv_append "bytevector-append" -:name_booleanq "boolean?" -:name_integerq "integer?" +:name_set_car "set-car!" 00 00 00 00 00 00 00 +:name_set_cdr "set-cdr!" 00 00 00 00 00 00 00 +:name_length "length" 00 +:name_list_ref "list-ref" 00 00 00 00 00 00 00 +:name_str_to_sym "string->symbol" 00 +:name_sym_to_str "symbol->string" 00 +:name_num_to_str "number->string" 00 +:name_str_to_num "string->number" 00 +:name_bv_append "bytevector-append" 00 00 00 00 00 00 +:name_booleanq "boolean?" 00 00 00 00 00 00 00 +:name_integerq "integer?" 00 00 00 00 00 00 00 :name_symbolq "symbol?" -:name_procedureq "procedure?" -:name_zeroq "zero?" -:name_not "not" -:name_eqq "eq?" -:name_equal "equal?" -:name_plus "+" -:name_minus "-" -:name_mult "*" -:name_eq "=" -:name_lt "<" -:name_gt ">" -:name_quotient "quotient" -:name_remainder "remainder" +:name_procedureq "procedure?" 00 00 00 00 00 +:name_zeroq "zero?" 00 00 +:name_not "not" 00 00 00 00 +:name_eqq "eq?" 00 00 00 00 +:name_equal "equal?" 00 +:name_plus "+" 00 00 00 00 00 00 +:name_minus "-" 00 00 00 00 00 00 +:name_mult "*" 00 00 00 00 00 00 +:name_eq "=" 00 00 00 00 00 00 +:name_lt "<" 00 00 00 00 00 00 +:name_gt ">" 00 00 00 00 00 00 +:name_quotient "quotient" 00 00 00 00 00 00 00 +:name_remainder "remainder" 00 00 00 00 00 00 :name_bit_and "bit-and" -:name_bit_or "bit-or" +:name_bit_or "bit-or" 00 :name_bit_xor "bit-xor" :name_bit_not "bit-not" -:name_arith_shift "arithmetic-shift" -:name_apply "apply" +:name_arith_shift "arithmetic-shift" 00 00 00 00 00 00 00 +:name_apply "apply" 00 00 :name_make_bv "make-bytevector" -:name_bv_length "bytevector-length" -;; "string-length" + NUL = 14 bytes; pad with 2 '00' to 16 (multiple of 8) -;; so the sum of all name strings stays 8-aligned for the prim_table $() -;; rows that follow. -:name_string_length "string-length" '00' '00' -:name_bv_u8_ref "bytevector-u8-ref" -:name_bv_u8_set "bytevector-u8-set!" +:name_bv_length "bytevector-length" 00 00 00 00 00 00 +:name_string_length "string-length" 00 00 +:name_bv_u8_ref "bytevector-u8-ref" 00 00 00 00 00 00 +:name_bv_u8_set "bytevector-u8-set!" 00 00 00 00 00 :name_bv_copy "bytevector-copy" -:name_bv_copy_b "bytevector-copy!" -:name_bv_eq "bytevector=?" - -:name_sys_read "sys-read" -:name_sys_write "sys-write" -:name_sys_close "sys-close" -:name_sys_openat "sys-openat" -:name_sys_clone "sys-clone" -:name_sys_execve "sys-execve" -:name_sys_waitid "sys-waitid" -:name_sys_argv "sys-argv" -:name_eof_object "eof-object" -:name_eof_objectq "eof-object?" +:name_bv_copy_b "bytevector-copy!" 00 00 00 00 00 00 00 +:name_bv_eq "bytevector=?" 00 00 00 + +:name_sys_read "sys-read" 00 00 00 00 00 00 00 +:name_sys_write "sys-write" 00 00 00 00 00 00 +:name_sys_close "sys-close" 00 00 00 00 00 00 +:name_sys_openat "sys-openat" 00 00 00 00 00 +:name_sys_clone "sys-clone" 00 00 00 00 00 00 +:name_sys_execve "sys-execve" 00 00 00 00 00 +:name_sys_waitid "sys-waitid" 00 00 00 00 00 +:name_sys_argv "sys-argv" 00 00 00 00 00 00 00 +:name_eof_object "eof-object" 00 00 00 00 00 +:name_eof_objectq "eof-object?" 00 00 00 00 :name_display "display" -:name_write "write" -:name_error "error" -:name_format "format" +:name_write "write" 00 00 +:name_error "error" 00 00 +:name_format "format" 00 ;; The last three names are padded individually to 16 bytes (multiple ;; of 8) so subsequent 8-aligned data slots (prim_table $() rows, the ;; bss pointer slots) stay aligned. M0 appends a NUL to every "..." ;; string, so the listed length below counts the trailing NUL plus the -;; explicit '00' bytes. -;; "heap-mark" + NUL = 10 bytes; pad with 6 '00' to reach 16. -;; "heap-rewind!"+ NUL = 13 bytes; pad with 3 '00' to reach 16. -;; "heap-usage" + NUL = 11 bytes; pad with 5 '00' to reach 16. -:name_heap_mark "heap-mark" '00' '00' '00' '00' '00' '00' -:name_heap_rewind_bang "heap-rewind!" '00' '00' '00' -:name_heap_usage "heap-usage" '00' '00' '00' '00' '00' +;; explicit 00 bytes. +;; "heap-mark" + NUL = 10 bytes; pad with 6 00 to reach 16. +;; "heap-rewind!"+ NUL = 13 bytes; pad with 3 00 to reach 16. +;; "heap-usage" + NUL = 11 bytes; pad with 5 00 to reach 16. +:name_heap_mark "heap-mark" 00 00 00 00 00 00 +:name_heap_rewind_bang "heap-rewind!" 00 00 00 +:name_heap_usage "heap-usage" 00 00 00 00 00 # Writer string constants. Lengths are hard-coded at the str_putn call # sites (write_to_bv branches). No NUL needed in the source bytes -- # str_putn takes (ptr, n). -:str_false "#f" -:str_true "#t" -:str_nil "()" -:str_unspec "#!unspec" -:str_unbound "#!unbound" -:str_eof "#!eof" -:str_closure "#<closure>" -:str_prim "#<prim>" -:str_td "#<rec-type>" -:str_rec "#<record>" -:str_unknown "#<unknown>" +:str_false "#f" 00 00 00 00 00 00 +:str_true "#t" 00 00 00 00 00 00 +:str_nil "()" 00 00 00 00 00 00 +:str_unspec "#!unspec" 00 00 00 00 00 00 00 00 +:str_unbound "#!unbound" 00 00 00 00 00 00 00 +:str_eof "#!eof" 00 00 00 +:str_closure "#<closure>" 00 00 00 00 00 00 +:str_prim "#<prim>" 00 +:str_td "#<rec-type>" 00 00 00 00 00 +:str_rec "#<record>" 00 00 00 00 00 00 00 +:str_unknown "#<unknown>" 00 00 00 00 00 00 :str_error_prefix "scheme1: error: " # Primitive registration table. Each entry: 8-byte name_ptr (4-byte label @@ -5150,26 +5287,26 @@ &name_heap_rewind_bang %(0) $(12) &prim_heap_rewind_bang_entry %(0) :prim_table_end -:msg_usage "scheme1: usage: scheme1 SOURCE.scm" '0a' '00' -:msg_load_fail "scheme1: failed to read source" '0a' '00' -:msg_symtab_full "scheme1: symbol table full" '0a' '00' -:msg_unexp_rparen "scheme1: unexpected ')'" '0a' '00' -:msg_bad_hash "scheme1: bad #-syntax" '0a' '00' -:msg_unexp_eof "scheme1: unexpected EOF in form" '0a' '00' -:msg_unterm_list "scheme1: unterminated list" '0a' '00' -:msg_unbound "scheme1: unbound variable" '0a' '00' -:msg_not_proc "scheme1: not a procedure" '0a' '00' -:msg_heap_full "scheme1: heap exhausted" '0a' '00' -:msg_readbuf_full "scheme1: source buffer overflow" '0a' '00' -:msg_bv_oob "scheme1: bytevector index out of range" '0a' '00' -:msg_unterm_string "scheme1: unterminated string literal" '0a' '00' -:msg_bad_escape "scheme1: bad string escape" '0a' '00' -:msg_bad_char "scheme1: bad #\\ character literal" '0a' '00' -:msg_bad_number "scheme1: bad number literal" '0a' '00' -:msg_bad_ident "scheme1: bad identifier" '0a' '00' -:msg_internal_define "scheme1: internal define is not supported (use letrec)" '0a' '00' -:msg_pmatch_no_match "scheme1: pmatch: no clause matched" '0a' '00' -:msg_bad_unquote_pattern "scheme1: pmatch: malformed ,-pattern" '0a' '00' +:msg_usage "scheme1: usage: scheme1 SOURCE.scm" '0a' 00 +:msg_load_fail "scheme1: failed to read source" '0a' 00 +:msg_symtab_full "scheme1: symbol table full" '0a' 00 +:msg_unexp_rparen "scheme1: unexpected ')'" '0a' 00 +:msg_bad_hash "scheme1: bad #-syntax" '0a' 00 +:msg_unexp_eof "scheme1: unexpected EOF in form" '0a' 00 +:msg_unterm_list "scheme1: unterminated list" '0a' 00 +:msg_unbound "scheme1: unbound variable" '0a' 00 +:msg_not_proc "scheme1: not a procedure" '0a' 00 +:msg_heap_full "scheme1: heap exhausted" '0a' 00 +:msg_readbuf_full "scheme1: source buffer overflow" '0a' 00 +:msg_bv_oob "scheme1: bytevector index out of range" '0a' 00 +:msg_unterm_string "scheme1: unterminated string literal" '0a' 00 +:msg_bad_escape "scheme1: bad string escape" '0a' 00 +:msg_bad_char "scheme1: bad #\\ character literal" '0a' 00 +:msg_bad_number "scheme1: bad number literal" '0a' 00 +:msg_bad_ident "scheme1: bad identifier" '0a' 00 +:msg_internal_define "scheme1: internal define is not supported (use letrec)" '0a' 00 +:msg_pmatch_no_match "scheme1: pmatch: no clause matched" '0a' 00 +:msg_bad_unquote_pattern "scheme1: pmatch: malformed ,-pattern" '0a' 00 :name_ch_tab "tab" :name_ch_null "null" @@ -5245,6 +5382,9 @@ :sym_letrec $(0) :sym_and $(0) :sym_or $(0) +:sym_when $(0) +:sym_unless $(0) +:sym_case $(0) :sym_setbang $(0) :sym_define_record_type $(0) :sym_pmatch $(0) diff --git a/tests/scheme1/94-when.expected-exit b/tests/scheme1/94-when.expected-exit @@ -0,0 +1 @@ +42 diff --git a/tests/scheme1/94-when.scm b/tests/scheme1/94-when.scm @@ -0,0 +1,24 @@ +; (when test body...) -- if test is non-#f, eval body and return last; +; otherwise return UNSPEC. The body forms execute left-to-right. + +;; --- Truthy test: body runs, last form is the value ------------------ +(if (= 5 (when #t 1 2 3 4 5)) 0 (sys-exit 1)) + +;; --- Falsy test: body skipped; result is UNSPEC (eq? to other UNSPECs) +(if (eq? (when #f (sys-exit 99)) (when #f 42)) 0 (sys-exit 2)) +(if (not (eq? (when #f 1) #t)) 0 (sys-exit 3)) +(if (not (eq? (when #f 1) #f)) 0 (sys-exit 4)) +(if (not (eq? (when #f 1) '())) 0 (sys-exit 5)) + +;; --- Body side-effects fire only when test is truthy ----------------- +(define counter 0) +(when #f (set! counter 100)) +(if (= counter 0) 0 (sys-exit 6)) +(when #t (set! counter (+ counter 1)) (set! counter (+ counter 2))) +(if (= counter 3) 0 (sys-exit 7)) + +;; --- Truthy non-#t value (any non-#f counts as true) ----------------- +(if (= 7 (when 0 7)) 0 (sys-exit 8)) +(if (= 7 (when '() 7)) 0 (sys-exit 9)) + +(sys-exit 42) diff --git a/tests/scheme1/95-unless.expected-exit b/tests/scheme1/95-unless.expected-exit @@ -0,0 +1 @@ +17 diff --git a/tests/scheme1/95-unless.scm b/tests/scheme1/95-unless.scm @@ -0,0 +1,24 @@ +; (unless test body...) -- mirror of `when`. Body runs (and last value +; is returned) when test is #f; otherwise UNSPEC. + +;; --- Falsy test: body runs, last form is the value ------------------- +(if (= 5 (unless #f 1 2 3 4 5)) 0 (sys-exit 1)) + +;; --- Truthy test: body skipped; result is UNSPEC --------------------- +(if (eq? (unless #t (sys-exit 99)) (unless 0 42)) 0 (sys-exit 2)) +(if (not (eq? (unless #t 1) #t)) 0 (sys-exit 3)) +(if (not (eq? (unless #t 1) #f)) 0 (sys-exit 4)) +(if (not (eq? (unless #t 1) '())) 0 (sys-exit 5)) + +;; --- Body side-effects fire only when test is falsy ------------------ +(define counter 0) +(unless #t (set! counter 100)) +(if (= counter 0) 0 (sys-exit 6)) +(unless #f (set! counter (+ counter 1)) (set! counter (+ counter 2))) +(if (= counter 3) 0 (sys-exit 7)) + +;; --- Any non-#f counts as true (so body is skipped) ------------------ +(if (eq? (unless 0 1) (unless #t 1)) 0 (sys-exit 8)) +(if (eq? (unless '() 1) (unless #t 1)) 0 (sys-exit 9)) + +(sys-exit 17) diff --git a/tests/scheme1/96-case.expected-exit b/tests/scheme1/96-case.expected-exit @@ -0,0 +1 @@ +23 diff --git a/tests/scheme1/96-case.scm b/tests/scheme1/96-case.scm @@ -0,0 +1,48 @@ +; (case key clause...) -- evaluates key once, then walks clauses. +; Each clause is ((datum...) body...) or (else body...). Datums are +; literal (not evaluated) and matched eq?-style; body of the matched +; clause is tail-evaluated. No-match (no else) returns UNSPEC. + +;; --- Basic match against a single datum ------------------------------ +(if (= 11 (case 1 ((1) 11) ((2) 22) (else 99))) 0 (sys-exit 1)) +(if (= 22 (case 2 ((1) 11) ((2) 22) (else 99))) 0 (sys-exit 2)) + +;; --- Multiple datums per clause -------------------------------------- +(if (= 7 (case 3 ((1 2 3) 7) ((4 5) 8) (else 9))) 0 (sys-exit 3)) +(if (= 8 (case 5 ((1 2 3) 7) ((4 5) 8) (else 9))) 0 (sys-exit 4)) + +;; --- else fires when no clause matches ------------------------------- +(if (= 99 (case 42 ((1) 1) ((2 3) 2) (else 99))) 0 (sys-exit 5)) + +;; --- No match, no else: UNSPEC (eq? to other UNSPECs) ---------------- +(if (eq? (case 7 ((1) 11)) (case 9 ((2) 22))) 0 (sys-exit 6)) +(if (not (eq? (case 7 ((1) 11)) #f)) 0 (sys-exit 7)) + +;; --- Symbols match by eq? -------------------------------------------- +(if (eq? 'red (case 'apple + ((banana) 'yellow) + ((apple cherry) 'red) + (else 'gray))) + 0 (sys-exit 8)) + +;; --- Booleans match by eq? ------------------------------------------- +(if (= 1 (case #t ((#t) 1) ((#f) 2) (else 3))) 0 (sys-exit 9)) +(if (= 2 (case #f ((#t) 1) ((#f) 2) (else 3))) 0 (sys-exit 10)) + +;; --- Body has multi-form sequencing; last form is the value --------- +(define counter 0) +(if (= 9 (case 1 + ((1) (set! counter (+ counter 5)) + (set! counter (+ counter 3)) + (+ counter 1)) + (else 0))) + 0 (sys-exit 11)) +(if (= counter 8) 0 (sys-exit 12)) + +;; --- Key expression is evaluated only once --------------------------- +(define seen 0) +(define (bump) (set! seen (+ seen 1)) 2) +(if (= 22 (case (bump) ((1) 11) ((2) 22) (else 99))) 0 (sys-exit 13)) +(if (= seen 1) 0 (sys-exit 14)) + +(sys-exit 23)