diff options
Diffstat (limited to 'archive/0.vm.arc/mu.arc')
-rw-r--r-- | archive/0.vm.arc/mu.arc | 3259 |
1 files changed, 3259 insertions, 0 deletions
diff --git a/archive/0.vm.arc/mu.arc b/archive/0.vm.arc/mu.arc new file mode 100644 index 00000000..2aebd3d5 --- /dev/null +++ b/archive/0.vm.arc/mu.arc @@ -0,0 +1,3259 @@ +(ero "initializing mu.. (takes ~5s)") +;; profiler (http://arclanguage.org/item?id=11556) +; Keeping this right on top as a reminder to profile before guessing at why my +; program is slow. +(mac proc (name params . body) + `(def ,name ,params ,@body nil)) + +(mac filter-log (msg f x) + `(ret x@ ,x + (prn ,msg (,f x@)))) + +(= times* (table)) + +(mac deftimed (name args . body) + `(do + (def ,(sym (string name "_core")) ,args + ,@body) + (def ,name ,args + (let t0 (msec) + (ret ans ,(cons (sym (string name "_core")) args) + (update-time ,(string name) t0)))))) + +(proc update-time(name t0) ; call directly in recursive functions + (or= times*.name (list 0 0)) + (with ((a b) times*.name + timing (- (msec) t0)) + (= times*.name + (list + (+ a timing) + (+ b 1))))) + +(def print-times() + (prn (current-process-milliseconds)) + (prn "gc " (current-gc-milliseconds)) + (each (name time) (tablist times*) + (prn name " " time))) + +;; what happens when our virtual machine starts up +(= initialization-fns* (queue)) +(def reset () + (each f (as cons initialization-fns*) + (f))) + +(mac on-init body + `(enq (fn () ,@body) + initialization-fns*)) + +;; persisting and checking traces for each test +(= traces* (queue)) +(= trace-dir* ".traces/") +(ensure-dir trace-dir*) +(= curr-trace-file* nil) +(on-init + (awhen curr-trace-file* + (tofile (+ trace-dir* it) + (each (label trace) (as cons traces*) + (pr label ": " trace)))) + (= curr-trace-file* nil) + (= traces* (queue))) + +(def new-trace (filename) + (prn "== @filename") +;? ) + (= curr-trace-file* filename)) + +(= dump-trace* nil) +(def trace (label . args) + (when (or (is dump-trace* t) + (and dump-trace* (is label "-")) + (and dump-trace* (pos label dump-trace*!whitelist)) + (and dump-trace* (no dump-trace*!whitelist) (~pos label dump-trace*!blacklist))) + (apply prn label ": " args)) + (enq (list label (apply tostring:prn args)) + traces*) + (car args)) + +(on-init + (wipe dump-trace*)) + +(redef tr args ; why am I still returning to prn when debugging? Will this help? + (do1 nil + (apply trace "-" args))) + +(def tr2 (msg arg) + (tr msg arg) + arg) + +(def check-trace-contents (msg expected-contents) + (unless (trace-contents-match expected-contents) + (prn "F - " msg) + (prn " trace contents") + (print-trace-contents-mismatch expected-contents))) + +(def trace-contents-match (expected-contents) + (each (label msg) (as cons traces*) + (when (and expected-contents + (is label expected-contents.0.0) + (posmatch expected-contents.0.1 msg)) + (pop expected-contents))) + (no expected-contents)) + +(def print-trace-contents-mismatch (expected-contents) + (each (label msg) (as cons traces*) + (whenlet (expected-label expected-msg) expected-contents.0 + (if (and (is label expected-label) + (posmatch expected-msg msg)) + (do (pr " * ") + (pop expected-contents)) + (pr " ")) + (pr label ": " msg))) + (prn " couldn't find") + (each (expected-label expected-msg) expected-contents + (prn " ! " expected-label ": " expected-msg))) + +(def check-trace-doesnt-contain (msg (label unexpected-contents)) + (when (some (fn ((l s)) + (and (is l label) (posmatch unexpected-contents msg))) + (as cons traces*)) + (prn "F - " msg) + (prn " trace contents") + (each (l msg) (as cons traces*) + (if (and (is l label) + (posmatch unexpected-contents msg)) + (pr " X ") + (pr " ")) + (pr label ": " msg)))) + +;; virtual machine state + +; things that a future assembler will need separate memory for: +; code; types; args channel +; at compile time: mapping names to locations +(on-init + (= type* (table)) ; name -> type info + (= memory* (table)) ; address -> value (make this a vector?) + (= function* (table)) ; name -> [instructions] + ; transforming mu programs + (= location* (table)) ; function -> {name -> index into default-space} + (= next-space-generator* (table)) ; function -> name of function generating next space + ; each function's next space will usually always come from a single function + (= next-routine-id* 0) + (= continuation* (table)) + ) + +(on-init + (= type* (obj + ; Each type must be scalar or array, sum or product or primitive + type (obj size 1) ; implicitly scalar and primitive + type-address (obj size 1 address t elem '(type)) + type-array (obj array t elem '(type)) + type-array-address (obj size 1 address t elem '(type-array)) + location (obj size 1 address t elem '(location)) ; assume it points to an atom + integer (obj size 1) + boolean (obj size 1) + boolean-address (obj size 1 address t elem '(boolean)) + byte (obj size 1) + byte-address (obj size 1 address t elem '(byte)) + string (obj array t elem '(byte)) ; inspired by Go + ; an address contains the location of a specific type + string-address (obj size 1 address t elem '(string)) + string-address-address (obj size 1 address t elem '(string-address)) + string-address-array (obj array t elem '(string-address)) + string-address-array-address (obj size 1 address t elem '(string-address-array)) + string-address-array-address-address (obj size 1 address t elem '(string-address-array-address)) + ; 'character' will be of larger size when mu supports unicode + ; we're currently undisciplined about mixing 'byte' and 'character' + ; realistic test of indiscipline in general + character (obj size 1) ; int32 like a Go rune + character-address (obj size 1 address t elem '(character)) + ; a buffer makes it easy to append to a string/array + ; todo: make this generic + ; data isn't a 'real' array: its length is stored outside it, + ; so for example, 'print-string' won't work on it. + buffer (obj size 2 and-record t elems '((integer) (string-address)) fields '(length data)) + buffer-address (obj size 1 address t elem '(buffer)) + ; a stream makes it easy to read from a string/array + stream (obj size 2 and-record t elems '((integer) (string-address)) fields '(pointer data)) + stream-address (obj size 1 address t elem '(stream)) + ; isolating function calls + space (obj array t elem '(location)) ; by convention index 0 points to outer space + space-address (obj size 1 address t elem '(space)) + ; arrays consist of an integer length followed by that many + ; elements, all of the same type + integer-array (obj array t elem '(integer)) + integer-array-address (obj size 1 address t elem '(integer-array)) + integer-array-address-address (obj size 1 address t elem '(integer-array-address)) + integer-address (obj size 1 address t elem '(integer)) ; pointer to int + integer-address-address (obj size 1 address t elem '(integer-address)) + ; and-records consist of a multiple fields of different types + integer-boolean-pair (obj size 2 and-record t elems '((integer) (boolean)) fields '(int bool)) + integer-boolean-pair-address (obj size 1 address t elem '(integer-boolean-pair)) + integer-boolean-pair-array (obj array t elem '(integer-boolean-pair)) + integer-boolean-pair-array-address (obj size 1 address t elem '(integer-boolean-pair-array)) + integer-integer-pair (obj size 2 and-record t elems '((integer) (integer))) + integer-integer-pair-address (obj size 1 address t elem '(integer-integer-pair)) + integer-point-pair (obj size 2 and-record t elems '((integer) (integer-integer-pair))) + integer-point-pair-address (obj size 1 address t elem '(integer-point-pair)) + integer-point-pair-address-address (obj size 1 address t elem '(integer-point-pair-address)) + ; tagged-values are the foundation of dynamic types + tagged-value (obj size 2 and-record t elems '((type) (location)) fields '(type payload)) + tagged-value-address (obj size 1 address t elem '(tagged-value)) + tagged-value-array (obj array t elem '(tagged-value)) + tagged-value-array-address (obj size 1 address t elem '(tagged-value-array)) + tagged-value-array-address-address (obj size 1 address t elem '(tagged-value-array-address)) + ; heterogeneous lists + list (obj size 2 and-record t elems '((tagged-value) (list-address)) fields '(car cdr)) + list-address (obj size 1 address t elem '(list)) + list-address-address (obj size 1 address t elem '(list-address)) + ; parallel routines use channels to synchronize + channel (obj size 3 and-record t elems '((integer) (integer) (tagged-value-array-address)) fields '(first-full first-free circular-buffer)) + ; be careful of accidental copies to channels + channel-address (obj size 1 address t elem '(channel)) + ; opaque pointer to a call stack + ; todo: save properly in allocated memory + continuation (obj size 1) + ; editor + line (obj array t elem '(character)) + line-address (obj size 1 address t elem '(line)) + line-address-address (obj size 1 address t elem '(line-address)) + screen (obj array t elem '(line-address)) + screen-address (obj size 1 address t elem '(screen)) + ; fake screen + terminal (obj size 5 and-record t elems '((integer) (integer) (integer) (integer) (string-address)) fields '(num-rows num-cols cursor-row cursor-col data)) + terminal-address (obj size 1 address t elem '(terminal)) + ; fake keyboard + keyboard (obj size 2 and-record t elems '((integer) (string-address)) fields '(index data)) + keyboard-address (obj size 1 address t elem '(keyboard)) + ))) + +;; managing concurrent routines + +(on-init +;? (prn "-- resetting memory allocation") + (= Memory-allocated-until 1000) + (= Allocation-chunk 100000)) + +; routine = runtime state for a serial thread of execution +(def make-routine (fn-name . args) + (let curr-alloc Memory-allocated-until +;? (prn "-- allocating routine: @curr-alloc") + (++ Memory-allocated-until Allocation-chunk) + (annotate 'routine (obj alloc curr-alloc alloc-max Memory-allocated-until + call-stack + (list (obj fn-name fn-name pc 0 args args caller-arg-idx 0)))) + ; other fields we use in routine: + ; sleep: conditions + ; limit: number of cycles this routine can use + ; running-since: start of the clock for counting cycles this routine has used + + ; todo: do memory management in mu + )) + +(defextend empty (x) (isa x 'routine) + (no rep.x!call-stack)) + +(def stack (routine) + ((rep routine) 'call-stack)) + +(def push-stack (routine op) + (push (obj fn-name op pc 0 caller-arg-idx 0 t0 (msec)) + rep.routine!call-stack)) + +(def pop-stack (routine) +;? (update-time label.routine (msec)) ;? 1 + (pop rep.routine!call-stack)) + +(def top (routine) + stack.routine.0) + +(def label (routine) + (whenlet stack stack.routine + (or= stack.0!label + (label2 stack)))) +(def label2 (stack) + (string:intersperse "/" (map [_ 'fn-name] stack)));)) + +(def body (routine) + (function* stack.routine.0!fn-name)) + +(mac pc (routine (o idx 0)) ; assignable + `((((rep ,routine) 'call-stack) ,idx) 'pc)) + +(mac caller-arg-idx (routine (o idx 0)) ; assignable + `((((rep ,routine) 'call-stack) ,idx) 'caller-arg-idx)) + +(mac caller-args (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'args)) +(mac caller-operands (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'caller-operands)) +(mac caller-results (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'caller-results)) + +(mac results (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'results)) +(mac reply-args (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'reply-args)) + +(def waiting-for-exact-cycle? (routine) + (is 'until rep.routine!sleep.0)) + +(def ready-to-wake-up (routine) + (assert no.routine*) + (case rep.routine!sleep.0 + until + (> curr-cycle* rep.routine!sleep.1) + until-location-changes + (~is rep.routine!sleep.2 (memory* rep.routine!sleep.1)) + until-routine-done + (find [and _ (is rep._!id rep.routine!sleep.1)] + completed-routines*) + )) + +(on-init + (= running-routines* (queue)) ; simple round-robin scheduler + ; set of sleeping routines; don't modify routines while they're in this table + (= sleeping-routines* (table)) + (= completed-routines* nil) ; audit trail + (= routine* nil) + (= abort-routine* (parameter nil)) + (= curr-cycle* 0) + (= scheduling-interval* 500) + (= scheduler-switch-table* nil) ; hook into scheduler for debugging + ) + +; like arc's 'point' but you can also call ((abort-routine*)) in nested calls +(mac routine-mark body + (w/uniq (g p) + `(ccc (fn (,g) + (parameterize abort-routine* (fn ((o ,p)) (,g ,p)) + ,@body))))) + +(def run fn-names + (freeze function*) +;? (prn function*!main) ;? 1 + (load-system-functions) + (apply run-more fn-names)) + +; assume we've already frozen; throw on a few more routines and continue scheduling +(def run-more fn-names + (each it fn-names + (enq make-routine.it running-routines*)) + (while (~empty running-routines*) + (= routine* deq.running-routines*) + (when rep.routine*!limit + ; start the clock if it wasn't already running + (or= rep.routine*!running-since curr-cycle*)) + (trace "schedule" label.routine*) + (routine-mark + (run-for-time-slice scheduling-interval*)) + (update-scheduler-state))) + +; prepare next iteration of round-robin scheduler +; +; state before: routine* running-routines* sleeping-routines* +; state after: running-routines* (with next routine to run at head) sleeping-routines* +; +; responsibilities: +; add routine* to either running-routines* or sleeping-routines* or completed-routines* +; wake up any necessary sleeping routines (which might be waiting for a +; particular time or for a particular memory location to change) +; detect termination: all non-helper routines completed +; detect deadlock: kill all sleeping routines when none can be woken +(def update-scheduler-state () + (when routine* +;? (prn "update scheduler state: " routine*) + (if + rep.routine*!sleep + (do (trace "schedule" "pushing " label.routine* " to sleep queue") + ; keep the clock ticking at rep.routine*!running-since + (set sleeping-routines*.routine*)) + rep.routine*!error + (do (trace "schedule" "done with dead routine " label.routine*) +;? (tr rep.routine*) + (push routine* completed-routines*)) + empty.routine* + (do (trace "schedule" "done with routine " label.routine*) + (push routine* completed-routines*)) + (no rep.routine*!limit) + (do (trace "schedule" "scheduling " label.routine* " for further processing") + (enq routine* running-routines*)) + (> rep.routine*!limit 0) + (do (trace "schedule" "scheduling " label.routine* " for further processing (limit)") + ; stop the clock and debit the time on it from the routine + (-- rep.routine*!limit (- curr-cycle* rep.routine*!running-since)) + (wipe rep.routine*!running-since) + (if (<= rep.routine*!limit 0) + (do (trace "schedule" "routine ran out of time") + (push routine* completed-routines*)) + (enq routine* running-routines*))) + :else + (err "illegal scheduler state")) + (= routine* nil)) + (each (routine _) routine-canon.sleeping-routines* + (when (aand rep.routine!limit (<= it (- curr-cycle* rep.routine!running-since))) + (trace "schedule" "routine timed out") + (wipe sleeping-routines*.routine) + (push routine completed-routines*) +;? (tr completed-routines*) + )) + (each (routine _) routine-canon.sleeping-routines* + (when (ready-to-wake-up routine) + (trace "schedule" "waking up " label.routine) + (wipe sleeping-routines*.routine) ; do this before modifying routine + (wipe rep.routine!sleep) + (++ pc.routine) + (enq routine running-routines*))) + ; optimization for simulated time + (when (empty running-routines*) + (whenlet exact-sleeping-routines (keep waiting-for-exact-cycle? keys.sleeping-routines*) + (let next-wakeup-cycle (apply min (map [rep._!sleep 1] exact-sleeping-routines)) + (= curr-cycle* (+ 1 next-wakeup-cycle))) + (trace "schedule" "skipping to cycle " curr-cycle*) + (update-scheduler-state))) + (when (and (or (~empty running-routines*) + (~empty sleeping-routines*)) + (all [rep._ 'helper] (as cons running-routines*)) + (all [rep._ 'helper] keys.sleeping-routines*)) + (trace "schedule" "just helpers left; stopping everything") + (until (empty running-routines*) + (push (deq running-routines*) completed-routines*)) + (each (routine _) sleeping-routines* +;? (prn " " label.routine) ;? 0 + (wipe sleeping-routines*.routine) + (push routine completed-routines*))) + (detect-deadlock) + ) + +(def detect-deadlock () + (when (and (empty running-routines*) + (~empty sleeping-routines*) + (~some 'literal (map (fn(_) rep._!sleep.1) + keys.sleeping-routines*))) + (each (routine _) sleeping-routines* + (wipe sleeping-routines*.routine) + (= rep.routine!error "deadlock detected") + (push routine completed-routines*)))) + +(def die (msg) + (tr "die: " msg) + (= rep.routine*!error msg) + (iflet abort-continuation (abort-routine*) + (abort-continuation))) + +;; running a single routine + +; value of an arg or oarg, stripping away all metadata +; wish I could have this flag an error when arg is incorrectly formed +(mac v (operand) ; for value + `((,operand 0) 0)) + +; routines consist of instrs +; instrs consist of oargs, op and args +(def parse-instr (instr) + (iflet delim (pos '<- instr) + (do (when (atom (instr (+ delim 1))) + (err "operator not tokenized in @instr; maybe you need to freeze functions*?")) + (list (cut instr 0 delim) ; oargs + (v (instr (+ delim 1))) ; op + (cut instr (+ delim 2)))) ; args + (list nil (v car.instr) cdr.instr))) + +(def metadata (operand) + cdr.operand) + +(def ty (operand) + (cdr operand.0)) + +(def literal? (operand) + (unless (acons ty.operand) + (err "no type in operand @operand")) + (in ty.operand.0 'literal 'offset 'fn)) + +(def typeinfo (operand) + (or (type* ty.operand.0) + (err "unknown type @(tostring prn.operand)"))) + +; operand accessors +(def nondummy (operand) ; precondition for helpers below + (~is '_ operand)) + +; just for convenience, 'new' instruction sometimes takes a raw string and +; allocates just enough space to store it +(def not-raw-string (operand) + (~isa operand 'string)) + +(def address? (operand) + (or (is ty.operand.0 'location) + typeinfo.operand!address)) + +($:require "charterm/main.rkt") +($:require graphics/graphics) +;? ($:require "terminal-color/terminal-color/main.rkt") ;? 1 +(= Viewport nil) +; http://rosettacode.org/wiki/Terminal_control/Coloured_text#Racket +($:define (tput . xs) (system (apply ~a 'tput " " (add-between xs " "))) (void)) +($:define (foreground color) (tput 'setaf color)) +($:define (background color) (tput 'setab color)) +($:define (reset) (tput 'sgr0)) + +(= new-string-foo* nil) +(= last-print* 0) + +; run instructions from 'routine*' for 'time-slice' +(def run-for-time-slice (time-slice) + (point return + (for ninstrs 0 (< ninstrs time-slice) (++ ninstrs) + (if (empty body.routine*) (err "@stack.routine*.0!fn-name not defined")) + ; falling out of end of function = implicit reply + (while (>= pc.routine* (len body.routine*)) + (pop-stack routine*) + (if empty.routine* (return ninstrs)) + (when (pos '<- (body.routine* pc.routine*)) + (die "No results returned: @(tostring:pr (body.routine* pc.routine*))")) + (++ pc.routine*)) + (++ curr-cycle*) + (when (no ($.current-charterm)) + (let curr (seconds) + (when (~is curr last-print*) + (prn curr " " curr-cycle* " " len.running-routines*) + (= last-print* curr)))) +;? (trace "run" "-- " int-canon.memory*) ;? 1 +;? (trace "run" curr-cycle*) + (trace "run" label.routine* " " pc.routine* ": " (body.routine* pc.routine*)) +;? (trace "run" routine*) + (when (atom (body.routine* pc.routine*)) ; label +;? (tr "label") ;? 1 + (when (aand scheduler-switch-table* + (alref it (body.routine* pc.routine*))) + (++ pc.routine*) + (trace "run" label.routine* " " pc.routine* ": " "context-switch forced " abort-routine*) + ((abort-routine*))) + (++ pc.routine*) + (continue)) + (let (oarg op arg) (parse-instr (body.routine* pc.routine*)) +;? (tr op) ;? 1 + (let results + (case op + ; arithmetic + add + (+ (m arg.0) (m arg.1)) + subtract + (- (m arg.0) (m arg.1)) + multiply + (* (m arg.0) (m arg.1)) + divide + (/ (real (m arg.0)) (m arg.1)) + divide-with-remainder + (list (trunc:/ (m arg.0) (m arg.1)) + (mod (m arg.0) (m arg.1))) + + ; boolean + and + (and (m arg.0) (m arg.1)) + or + (or (m arg.0) (m arg.1)) + not + (not (m arg.0)) + + ; comparison + equal +;? (do (prn (m arg.0) " vs " (m arg.1)) + (is (m arg.0) (m arg.1)) +;? ) + not-equal + (~is (m arg.0) (m arg.1)) + less-than + (< (m arg.0) (m arg.1)) + greater-than + (> (m arg.0) (m arg.1)) + lesser-or-equal + (<= (m arg.0) (m arg.1)) + greater-or-equal + (>= (m arg.0) (m arg.1)) + + ; control flow + jump + (do (= pc.routine* (+ 1 pc.routine* (v arg.0))) + (continue)) + jump-if + (when (m arg.0) + (= pc.routine* (+ 1 pc.routine* (v arg.1))) + (continue)) + jump-unless ; convenient helper + (unless (m arg.0) + (= pc.routine* (+ 1 pc.routine* (v arg.1))) + (continue)) + + ; data management: scalars, arrays, and-records (structs) + copy + (m arg.0) + get + (with (operand (canonize arg.0) + idx (v arg.1)) + (assert (iso '(offset) (ty arg.1)) "record index @arg.1 must have type 'offset'") + (assert (< -1 idx (len typeinfo.operand!elems)) "@idx is out of bounds of record @operand") + (m `((,(apply + v.operand + (map (fn(x) (sizeof `((_ ,@x)))) + (firstn idx typeinfo.operand!elems))) + ,@typeinfo.operand!elems.idx) + (raw)))) + get-address + (with (operand (canonize arg.0) + idx (v arg.1)) + (assert (iso '(offset) (ty arg.1)) "record index @arg.1 must have type 'offset'") + (assert (< -1 idx (len typeinfo.operand!elems)) "@idx is out of bounds of record @operand") + (apply + v.operand + (map (fn(x) (sizeof `((_ ,@x)))) + (firstn idx typeinfo.operand!elems)))) + index + (withs (operand (canonize arg.0) + elemtype typeinfo.operand!elem + idx (m arg.1)) +;? (write arg.0) +;? (pr " => ") +;? (write operand) +;? (prn) + (unless (< -1 idx array-len.operand) + (die "@idx is out of bounds of array @operand")) + (m `((,(+ v.operand + 1 ; for array size + (* idx (sizeof `((_ ,@elemtype))))) + ,@elemtype) + (raw)))) + index-address + (withs (operand (canonize arg.0) + elemtype typeinfo.operand!elem + idx (m arg.1)) + (unless (< -1 idx array-len.operand) + (die "@idx is out of bounds of array @operand")) + (+ v.operand + 1 ; for array size + (* idx (sizeof `((_ ,@elemtype)))))) + new + (if (isa arg.0 'string) + ; special-case: allocate space for a literal string + (new-string arg.0) + (let type (v arg.0) + (assert (iso '(literal) (ty arg.0)) "new: second arg @arg.0 must be literal") + (if (no type*.type) (err "no such type @type")) + ; todo: initialize memory. currently racket does it for us + (if type*.type!array + (new-array type (m arg.1)) + (new-scalar type)))) + sizeof + (sizeof `((_ ,(m arg.0)))) + length + (let base arg.0 + (if (or typeinfo.base!array address?.base) + array-len.base + -1)) + + ; tagged-values require one primitive + save-type + (annotate 'record `(,((ty arg.0) 0) ,(m arg.0))) + + ; code points for characters + character-to-integer + ($.char->integer (m arg.0)) + integer-to-character + ($.integer->char (m arg.0)) + + ; multiprocessing + fork + ; args: fn globals-table args ... + (let routine (apply make-routine (m arg.0) (map m (nthcdr 3 arg))) + (= rep.routine!id ++.next-routine-id*) + (= rep.routine!globals (when (len> arg 1) (m arg.1))) + (= rep.routine!limit (when (len> arg 2) (m arg.2))) + (enq routine running-routines*) + rep.routine!id) + fork-helper + ; args: fn globals-table args ... + (let routine (apply make-routine (m arg.0) (map m (nthcdr 3 arg))) + (= rep.routine!id ++.next-routine-id*) + (set rep.routine!helper) + (= rep.routine!globals (when (len> arg 1) (m arg.1))) + (= rep.routine!limit (when (len> arg 2) (m arg.2))) + (enq routine running-routines*) + rep.routine!id) + sleep + (do + (case (v arg.0) + for-some-cycles + (let wakeup-time (+ curr-cycle* (v arg.1)) + (trace "run" label.routine* " " pc.routine* ": " "sleeping until " wakeup-time) + (= rep.routine*!sleep `(until ,wakeup-time))) + until-location-changes + (= rep.routine*!sleep `(until-location-changes ,(addr arg.1) ,(m arg.1))) + until-routine-done + (= rep.routine*!sleep `(until-routine-done ,(m arg.1))) + ; else + (die "badly formed 'sleep' call @(tostring:prn (body.routine* pc.routine*))") + ) + ((abort-routine*))) + assert + (unless (m arg.0) + (die (v arg.1))) ; other routines will be able to look at the error status + assert-false + (when (m arg.0) + (die (v arg.1))) + + ; cursor-based (text mode) interaction + cursor-mode + ;(do1 nil (system "/bin/stty -F /dev/tty raw")) + (do1 nil (if (no ($.current-charterm)) ($.open-charterm))) + retro-mode + ;(do1 nil (system "/bin/stty -F /dev/tty sane")) + (do1 nil (if ($.current-charterm) ($.close-charterm))) + clear-host-screen + (do1 nil (pr "\e[m\e[2J\e[;H")) + clear-line-on-host + (do1 nil (pr "\e[2K")) + cursor-on-host + (do1 nil (pr (+ "\e[" (m arg.0) ";" (m arg.1) "H"))) + cursor-on-host-to-next-line + (do1 nil (pr "\r\n")) + cursor-up-on-host + (do1 nil (pr (+ "\e[" (aif (len> arg 0) (or m arg.0) 1) "A"))) + cursor-down-on-host + (do1 nil (pr (+ "\e[" (aif (len> arg 0) (or m arg.0) 1) "B"))) + cursor-right-on-host + (do1 nil (pr (+ "\e[" (aif (len> arg 0) (or m arg.0) 1) "C"))) + cursor-left-on-host + (do1 nil (pr (+ "\e[" (aif (len> arg 0) (or m arg.0) 1) "D"))) + print-character-to-host + (do1 nil + (assert (in (type:m arg.0) 'char 'sym) (rep (m arg.0))) +;? (write (m arg.0)) (pr " => ") (prn (type (m arg.0))) + (if (no ($.current-charterm)) + (pr (m arg.0)) + (caselet x (m arg.0) + ; todo: test these exceptions + #\newline + (pr "\r\n") + #\backspace + ; backspace doesn't clear after moving the cursor + (pr "\b \b") + ctrl-c + (do ($.close-charterm) + (die "interrupted")) + ;else + (if (and (len> arg 2) + (m arg.2)) + (do + ($.foreground (m arg.1)) + ($.background (m arg.2)) + (pr x) + ($.reset)) + (and (len> arg 1) + (m arg.1)) + (do + ($.foreground (m arg.1)) + (pr x) + ($.reset)) + :else + (pr x)))) + ) + read-key-from-host + (if ($.current-charterm) + (and ($.charterm-byte-ready?) + (ret result ($.charterm-read-key) + (case result + ; charterm exceptions + return + (= result #\newline) + backspace + (= result #\backspace) + ))) + ($.graphics-open?) + ($.ready-key-press Viewport)) + + ; graphics + window-on + (do1 nil + ($.open-graphics) + (= Viewport ($.open-viewport (m arg.0) ; name + (m arg.1) (m arg.2)))) ; width height + window-off + (do1 nil + ($.close-viewport Viewport) ; why doesn't this close the window? works in naked racket. not racket vs arc. + ($.close-graphics) + (= Viewport nil)) + mouse-position + (aif ($.ready-mouse-click Viewport) + (let posn ($.mouse-click-posn it) + (list (annotate 'record (list ($.posn-x posn) ($.posn-y posn))) t)) + (list nil nil)) + wait-for-mouse + (let posn ($.mouse-click-posn ($.get-mouse-click Viewport)) + (list (annotate 'record (list ($.posn-x posn) ($.posn-y posn))) t)) + ; clear-screen in cursor mode above + rectangle + (do1 nil + (($.draw-solid-rectangle Viewport) + ($.make-posn (m arg.0) (m arg.1)) ; origin + (m arg.2) (m arg.3) ; width height + (m arg.4))) ; color + point + (do1 nil + (($.draw-pixel Viewport) ($.make-posn (m arg.0) (m arg.1)) + (m arg.2))) ; color + + image + (do1 nil + (($.draw-pixmap Viewport) (m arg.0) ; filename + ($.make-posn (m arg.1) (m arg.2)))) + color-at + (let pixel (($.get-color-pixel Viewport) ($.make-posn (m arg.0) (m arg.1))) + (prn ($.rgb-red pixel) " " ($.rgb-blue pixel) " " ($.rgb-green pixel)) + ($:rgb-red pixel)) + + ; debugging aides + $dump-memory + (do1 nil + (prn:repr int-canon.memory*)) + $dump-trace + (tofile arg.0 + (each (label trace) (as cons traces*) + (pr label ": " trace))) + $start-tracing + (do1 nil + (set dump-trace*)) + $stop-tracing + (do1 nil + (wipe dump-trace*)) + $dump-routine + (do1 nil + ($.close-charterm) + (prn routine*) + ($.open-charterm) + ) + $dump-channel + (do1 nil + ($.close-charterm) + (withs (x (m arg.0) + y (memory* (+ x 2))) + (prn label.routine* " -- " x " -- " (list (memory* x) + (memory* (+ x 1)) + (memory* (+ x 2))) + " -- " (list (memory* y) + (memory* (+ y 1)) + (repr:memory* (+ y 2)) + (memory* (+ y 3)) + (repr:memory* (+ y 4))))) + ($.open-charterm) + ) + $quit + (quit) + $wait-for-key-from-host + (when ($.current-charterm) + (ret result ($.charterm-read-key) + (case result + ; charterm exceptions + return + (= result #\newline) + backspace + (= result #\backspace) + ))) + $print + (do1 nil +;? (write (m arg.0)) (pr " => ") (prn (type (m arg.0))) + (if (no ($.current-charterm)) + (pr (m arg.0)) + (unless disable-debug-prints-in-console-mode* + (caselet x (m arg.0) + #\newline + (pr "\r\n") + #\backspace + ; backspace doesn't clear after moving the cursor + (pr "\b \b") + ctrl-c + (do ($.close-charterm) + (die "interrupted")) + ;else + (pr x))) + )) + $write + (do1 nil + (write (m arg.0))) + $eval + (new-string:repr:eval:read:to-arc-string (m arg.0)) +;? (let x (to-arc-string (m arg.0)) ;? 1 +;? (prn x) ;? 1 +;? (new-string:repr:eval x)) ;? 1 + + $clear-trace + (do1 nil (wipe interactive-traces*)) + $save-trace + (let x (filter-log "CCC: " len + (string + (filter-log "BBB: " len + (map [string:intersperse ": " _] + (filter-log "AAA: " len + (as cons (interactive-traces* (m arg.0))))) + ))) +;? (let x (string:map [string:intersperse ": " _] +;? (apply join +;? (map [as cons _] rev.interactive-traces*))) + (prn "computed trace; now saving to memory\n") +;? (write x)(write #\newline) ;? 1 +;? (prn x) ;? 1 + (set new-string-foo*) + (do1 (new-string x) + (wipe new-string-foo*))) + + ; first-class continuations + current-continuation + (w/uniq continuation-name + (trace "continuation" "saving @(repr rep.routine*!call-stack) to @continuation-name") + (= continuation*.continuation-name (copy rep.routine*!call-stack)) + continuation-name) + continue-from + (let continuation-name (m arg.0) + (trace "continuation" "restoring @continuation-name") + (trace "continuation" continuation*.continuation-name) + (= rep.routine*!call-stack continuation*.continuation-name) + (trace "continuation" "call stack is now @(repr rep.routine*!call-stack)") +;? (++ pc.routine*) ;? 1 + (continue)) +;? ((abort-routine*))) ;? 1 + + ; user-defined functions + next-input + (let idx caller-arg-idx.routine* + (++ caller-arg-idx.routine*) + (trace "arg" repr.arg " " idx " " (repr caller-args.routine*)) + (if (len> caller-args.routine* idx) + (list caller-args.routine*.idx t) + (list nil nil))) + input + (do (assert (iso '(literal) (ty arg.0))) + (= caller-arg-idx.routine* (v arg.0)) + (let idx caller-arg-idx.routine* + (++ caller-arg-idx.routine*) + (trace "arg" repr.arg " " idx " " (repr caller-args.routine*)) + (if (len> caller-args.routine* idx) + (list caller-args.routine*.idx t) + (list nil nil)))) + rewind-inputs + (do1 nil + (= caller-arg-idx.routine* 0)) + ; type and otype won't always easily compile. be careful. + type + (ty (caller-operands.routine* (v arg.0))) + otype + (ty (caller-results.routine* (v arg.0))) + prepare-reply + (prepare-reply arg) + reply + (do (when arg + (prepare-reply arg)) + (with (results results.routine* + reply-args reply-args.routine*) + (pop-stack routine*) + (if empty.routine* (return ninstrs)) + (let (call-oargs _ call-args) (parse-instr (body.routine* pc.routine*)) +;? (trace "reply" repr.arg " " repr.call-oargs) ;? 1 + (each (dest reply-arg val) (zip call-oargs reply-args results) + (trace "run" label.routine* " " pc.routine* ": " repr.val " => " dest) + (when nondummy.dest + (whenlet argidx (alref metadata.reply-arg 'same-as-arg) + (unless (is v.dest (v call-args.argidx)) + (die "'same-as-arg' output arg in @repr.reply-args can't bind to @repr.call-oargs"))) + (setm dest val)))) + (++ pc.routine*) + (while (>= pc.routine* (len body.routine*)) + (pop-stack routine*) + (when empty.routine* (return ninstrs)) + (++ pc.routine*)) + (continue))) + ; else try to call as a user-defined function + (do (if function*.op + (with (callee-args (accum yield + (each a arg + (yield (m a)))) + callee-operands (accum yield + (each a arg + (yield a))) + callee-results (accum yield + (each a oarg + (yield a)))) + (push-stack routine* op) + (= caller-args.routine* callee-args) + (= caller-operands.routine* callee-operands) + (= caller-results.routine* callee-results)) + (err "no such op @op")) + (continue)) + ) + ; opcode generated some 'results' + ; copy to output args + (if (acons results) + (each (dest val) (zip oarg results) + (unless (is dest '_) + (trace "run" label.routine* " " pc.routine* ": " repr.val " => " dest) + (setm dest val))) + (when oarg ; must be a list + (trace "run" label.routine* " " pc.routine* ": " repr.results " => " oarg.0) + (setm oarg.0 results))) + ) + (++ pc.routine*))) + (return time-slice))) + +(def prepare-reply (args) + (= results.routine* + (accum yield + (each a args + (yield (m a))))) + (= reply-args.routine* args)) + +; helpers for memory access respecting +; immediate addressing - 'literal' and 'offset' +; direct addressing - default +; indirect addressing - 'deref' +; relative addressing - if routine* has 'default-space' + +(def m (loc) ; read memory, respecting metadata + (point return + (when (literal? loc) + (return v.loc)) + (when (is v.loc 'default-space) + (return rep.routine*!call-stack.0!default-space)) +;? (trace "mem" loc) ;? 1 + (assert (isa v.loc 'int) "addresses must be numeric (problem in convert-names?): @repr.loc") + (ret result + (with (n sizeof.loc + addr addr.loc) +;? (trace "mem" "reading " n " locations starting at " addr) ;? 1 + (if (is 1 n) + memory*.addr + :else + (annotate 'record + (map memory* (addrs addr n))))) + (trace "mem" loc " => " result)))) + +(def setm (loc val) ; set memory, respecting metadata +;? (tr 111) + (point return +;? (tr 112) + (when (is v.loc 'default-space) + (assert (is 1 sizeof.loc) "can't store compounds in default-space @loc") + (= rep.routine*!call-stack.0!default-space val) + (return)) +;? (tr 120) + (assert (isa v.loc 'int) "can't store to non-numeric address (problem in convert-names?)") +;? (trace "mem" loc " <= " repr.val) ;? 1 + (with (n (if (isa val 'record) (len rep.val) 1) + addr addr.loc + typ typeof.loc) +;? (trace "mem" "size of " loc " is " n) ;? 1 + (assert n "setm: can't compute type of @loc") + (assert addr "setm: null pointer @loc") + (if (is 1 n) + (do (assert (~isa val 'record) "setm: record of size 1 @(tostring prn.val)") + (trace "mem" loc ": " addr " <= " repr.val) + (= memory*.addr val)) + (do (if type*.typ!array + ; size check for arrays + (when (~is n + (+ 1 ; array length + (* rep.val.0 (sizeof `((_ ,@type*.typ!elem)))))) + (die "writing invalid array @(tostring prn.val)")) + ; size check for non-arrays + (when (~is sizeof.loc n) + (die "writing to incorrect size @(tostring pr.val) => @loc"))) + (let addrs (addrs addr n) + (each (dest src) (zip addrs rep.val) + (trace "mem" loc ": " dest " <= " repr.src) + (= memory*.dest src)))))))) + +(def typeof (operand) + (let loc absolutize.operand + (while (pos '(deref) metadata.loc) + (zap deref loc)) + ty.loc.0)) + +(def addr (operand) + (v canonize.operand)) + +(def addrs (n sz) + (accum yield + (repeat sz + (yield n) + (++ n)))) + +(def canonize (operand) +;? (tr "0: @operand") + (ret operand +;? (prn "1: " operand) +;? (tr "1: " operand) ; todo: why does this die? + (zap absolutize operand) +;? (tr "2: @repr.operand") + (while (pos '(deref) metadata.operand) + (zap deref operand) +;? (tr "3: @repr.operand") + ))) + +(def array-len (operand) + (trace "array-len" operand) + (zap canonize operand) + (if typeinfo.operand!array + (m `((,v.operand integer) ,@metadata.operand)) + :else + (err "can't take len of non-array @operand"))) + +(def sizeof (x) +;? (trace "sizeof" x) ;? 1 + (assert acons.x) + (zap canonize x) + (point return +;? (tr "sizeof: checking @x for array") + (when typeinfo.x!array +;? (tr "sizeof: @x is an array") + (assert (~is '_ v.x) "sizeof: arrays require a specific variable") + (return (+ 1 (* array-len.x (sizeof `((_ ,@typeinfo.x!elem))))))) +;? (tr "sizeof: not an array") + (when typeinfo.x!and-record +;? (tr "sizeof: @x is an and-record") + (return (sum idfn + (accum yield + (each elem typeinfo.x!elems + (yield (sizeof `((_ ,@elem))))))))) +;? (tr "sizeof: @x is a primitive") + (return typeinfo.x!size))) + +(def absolutize (operand) + (if (no routine*) + operand + (in v.operand '_ 'default-space) + operand + (pos '(raw) metadata.operand) + operand + (is 'global space.operand) + (aif rep.routine*!globals + `((,(+ it 1 v.operand) ,@(cdr operand.0)) + ,@(rem [caris _ 'space] metadata.operand) + (raw)) + (die "routine has no globals: @operand")) + :else + (iflet base rep.routine*!call-stack.0!default-space + (space-base (rem [caris _ 'space] operand) + base + space.operand) + operand))) + +(def space-base (operand base space) +;? (prn operand " " base) ;? 1 + (if (is 0 space) + ; base case + (if (< v.operand memory*.base) + `((,(+ base 1 v.operand) ,@(cdr operand.0)) + ,@metadata.operand + (raw)) + (die "no room for var @operand in routine of size @memory*.base")) + ; recursive case + (space-base operand (memory* (+ base 1)) ; location 0 points to next space + (- space 1)))) + +(def space (operand) + (or (alref metadata.operand 'space) + 0)) + +(def deref (operand) + (assert (pos '(deref) metadata.operand)) + (assert address?.operand) + (cons `(,(memory* v.operand) ,@typeinfo.operand!elem) + (drop-one '(deref) metadata.operand))) + +(def drop-one (f x) + (when acons.x ; proper lists only + (if (testify.f car.x) + cdr.x + (cons car.x (drop-one f cdr.x))))) + +; memory allocation + +(def alloc (sz) + (when (> sz (- rep.routine*!alloc-max rep.routine*!alloc)) + (let curr-alloc Memory-allocated-until + (= rep.routine*!alloc curr-alloc) + (++ Memory-allocated-until Allocation-chunk) + (= rep.routine*!alloc-max Memory-allocated-until))) + (ret result rep.routine*!alloc + (++ rep.routine*!alloc sz))) + +(def new-scalar (type) +;? (tr "new scalar: @type") + (alloc (sizeof `((_ ,type))))) + +(def new-array (type size) +;? (tr "new array: @type @size") + (ret result (alloc (+ 1 (* (sizeof `((_ ,@type*.type!elem))) size))) + (= memory*.result size))) + +(def new-string (literal-string) +;? (tr "new string: @literal-string") + (ret result (alloc (+ 1 len.literal-string)) + (= memory*.result len.literal-string) + (on c literal-string + (when (and new-string-foo* (is 0 (mod index 100))) + (prn index " " repr.c)) + (= (memory* (+ result 1 index)) c)))) + +(def to-arc-string (string-address) + (let len (memory* string-address) + (string:map memory* (range (+ string-address 1) + (+ string-address len))))) + +;; desugar structured assembly based on blocks + +(def convert-braces (instrs) +;? (prn "convert-braces " instrs) + (let locs () ; list of information on each brace: (open/close pc) + (let pc 0 + (loop (instrs instrs) + (each instr instrs +;? (tr instr) + (if (or atom.instr (~is 'begin instr.0)) ; label or regular instruction + (do + (trace "c{0" pc " " instr " -- " locs) + (++ pc)) + ; hack: racket replaces braces with parens, so we need the + ; keyword 'begin' to delimit blocks. + ; ultimately there'll be no nesting and braces will just be + ; in an instr by themselves. + :else ; brace + (do + (push `(open ,pc) locs) + (recur cdr.instr) + (push `(close ,pc) locs)))))) + (zap rev locs) +;? (tr "-") + (with (pc 0 + stack ()) ; elems are pcs + (accum yield + (loop (instrs instrs) + (each instr instrs +;? (tr "- " instr) + (point continue + (when (atom instr) ; label + (yield instr) + (++ pc) + (continue)) + (when (is car.instr 'begin) + (push pc stack) + (recur cdr.instr) + (pop stack) + (continue)) + (with ((oarg op arg) (parse-instr instr) + yield-new-instr (fn (new-instr) + (trace "c{1" "@pc X " instr " => " new-instr) + (yield new-instr)) + yield-unchanged (fn () + (trace "c{1" "@pc ✓ " instr) + (yield instr))) + (when (in op 'break 'break-if 'break-unless 'loop 'loop-if 'loop-unless) + (assert (is oarg nil) "@op: can't take oarg in @instr")) + (case op + break + (yield-new-instr `(((jump)) ((,(close-offset pc locs (and arg (v arg.0))) offset)))) + break-if + (yield-new-instr `(((jump-if)) ,arg.0 ((,(close-offset pc locs (and cdr.arg (v arg.1))) offset)))) + break-unless + (yield-new-instr `(((jump-unless)) ,arg.0 ((,(close-offset pc locs (and cdr.arg (v arg.1))) offset)))) + loop + (yield-new-instr `(((jump)) ((,(open-offset pc stack (and arg (v arg.0))) offset)))) + loop-if + (yield-new-instr `(((jump-if)) ,arg.0 ((,(open-offset pc stack (and cdr.arg (v arg.1))) offset)))) + loop-unless + (yield-new-instr `(((jump-unless)) ,arg.0 ((,(open-offset pc stack (and cdr.arg (v arg.1))) offset)))) + ;else + (yield-unchanged))) + (++ pc)))))))) + +(def close-offset (pc locs nblocks) + (or= nblocks 1) +;? (tr nblocks) + (point return +;? (tr "close " pc " " locs) + (let stacksize 0 + (each (state loc) locs + (point continue +;? (tr stacksize "/" done " " state " " loc) + (when (<= loc pc) + (continue)) +;? (tr "process " stacksize loc) + (if (is 'open state) (++ stacksize) (-- stacksize)) + ; last time +;? (tr "process2 " stacksize loc) + (when (is stacksize (* -1 nblocks)) +;? (tr "close now " loc) + (return (- loc pc 1)))))))) + +(def open-offset (pc stack nblocks) + (or= nblocks 1) + (- (stack (- nblocks 1)) 1 pc)) + +;; convert jump targets to offsets + +(def convert-labels (instrs) +;? (tr "convert-labels " instrs) + (let labels (table) + (let pc 0 + (each instr instrs + (when (~acons instr) +;? (tr "label " pc) + (= labels.instr pc)) + (++ pc))) + (let pc 0 + (each instr instrs + (when (and acons.instr + (acons car.instr) + (in (v car.instr) 'jump 'jump-if 'jump-unless)) + (each arg cdr.instr +;? (tr "trying " arg " " ty.arg ": " v.arg " => " (labels v.arg)) + (when (and (is ty.arg.0 'offset) + (isa v.arg 'sym) + (labels v.arg)) + (= v.arg (- (labels v.arg) pc 1))))) + (++ pc)))) + instrs) + +;; convert symbolic names to raw memory locations + +(def add-next-space-generator (instrs name) +;? (prn "== @name") + (each instr instrs + (when acons.instr + (let (oargs op args) (parse-instr instr) + (each oarg oargs + (when (and (nondummy oarg) + (is v.oarg 0) + (iso ty.oarg '(space-address))) + (assert (or (no next-space-generator*.name) + (is next-space-generator*.name (alref oarg 'names))) + "function can have only one next-space-generator environment") + (tr "next-space-generator of @name is @(alref oarg 'names)") + (= next-space-generator*.name (alref oarg 'names)))))))) + +; just a helper for testing; in practice we unbundle assign-names-to-location +; and replace-names-with-location. +(def convert-names (instrs (o name)) +;? (tr "convert-names " instrs) + (= location*.name (assign-names-to-location instrs name)) +;? (tr "save names for function @name: @(tostring:pr location*.name)") ;? 1 + (replace-names-with-location instrs name)) + +(def assign-names-to-location (instrs name (o init-locations)) + (trace "cn0" "convert-names in @name") +;? (prn name ": " location*) ;? 1 + (point return + (ret location (or init-locations (table)) + ; if default-space in first instruction has a name, begin with its bindings + (when (acons instrs.0) ; not a label + (let first-oarg-of-first-instr instrs.0.0 ; hack: assumes the standard default-space boilerplate + (when (and (nondummy first-oarg-of-first-instr) + (is 'default-space (v first-oarg-of-first-instr)) + (assoc 'names metadata.first-oarg-of-first-instr)) + (let old-names (location*:alref metadata.first-oarg-of-first-instr 'names) + (unless old-names +;? (prn "@name requires bindings for @(alref metadata.first-oarg-of-first-instr 'names) which aren't computed yet. Waiting.") ;? 1 + (return nil)) + (= location copy.old-names))))) ; assumption: we've already converted names for 'it' +;? (unless empty.location (prn location)) ;? 2 + (with (isa-field (table) + idx (+ 1 ; 0 always reserved for next space + (or (apply max vals.location) ; skip past bindings already shared from elsewhere + 0)) + already-location (copy location) + ) + (each instr instrs + (point continue + (when atom.instr + (continue)) + (trace "cn0" instr " " canon.location " " canon.isa-field) + (let (oargs op args) (parse-instr instr) +;? (tr "about to rename args: @op") + (when (in op 'get 'get-address) + ; special case: map field offset by looking up type table + (with (basetype (typeof args.0) + field (v args.1)) +;? (tr 111 " " args.0 " " basetype) + (assert type*.basetype!and-record "get on non-record @args.0") +;? (tr 112) + (trace "cn0" "field-access @field in @args.0 of type @basetype") + (when (isa field 'sym) + (unless (already-location field) + (assert (or (~location field) isa-field.field) "field @args.1 is also a variable")) + (when (~location field) + (trace "cn0" "new field; computing location") +;? (tr "aa " type*.basetype) + (assert type*.basetype!fields "no field names available for @instr") +;? (tr "bb") + (iflet idx (pos field type*.basetype!fields) + (do (set isa-field.field) + (trace "cn0" "field location @idx") + (= location.field idx)) + (assert nil "couldn't find field in @instr")))))) + ; map args to location indices + (each arg args + (trace "cn0" "checking arg " arg) + (when (and nondummy.arg not-raw-string.arg (~literal? arg)) + (assert (~isa-field v.arg) "arg @arg is also a field name") + (when (maybe-add arg location idx) + ; todo: test this + (err "use before set: @arg")))) +;? (tr "about to rename oargs") + ; map oargs to location indices + (each arg oargs + (trace "cn0" "checking oarg " arg) + (when (and nondummy.arg not-raw-string.arg) + (assert (~isa-field v.arg) "oarg @arg is also a field name") + (when (maybe-add arg location idx) + (trace "cn0" "location for oarg " arg ": " idx) + ; todo: can't allocate arrays on the stack + (++ idx (sizeof `((_ ,@ty.arg)))))))))))))) + +(def replace-names-with-location (instrs name) + (each instr instrs + (when (acons instr) + (let (oargs op args) (parse-instr instr) + (each arg args + (convert-name arg name)) + (each arg oargs + (convert-name arg name))))) + (each instr instrs + (trace "cn1" instr)) + instrs) + +(= allow-raw-addresses* nil) +(def check-default-space (instrs name) + (unless allow-raw-addresses* + (let oarg-names (accum yield + (each (oargs _ _) (map parse-instr (keep acons ; non-label + instrs)) + (each oarg oargs + (when nondummy.oarg + (yield v.oarg))))) + (when (~pos 'default-space oarg-names) + (prn "function @name has no default-space"))))) + +; assign an index to an arg +(def maybe-add (arg location idx) + (trace "maybe-add" arg) + (when (and nondummy.arg +;? (prn arg " " (assoc 'space arg)) + (~assoc 'space arg) + (~literal? arg) + (~location v.arg) + (isa v.arg 'sym) + (~in v.arg 'nil 'default-space) + (~pos '(raw) metadata.arg)) + (= (location v.arg) idx))) + +; convert the arg to corresponding index +(def convert-name (arg default-name) +;? (prn "111 @arg @default-name") + (when (and nondummy.arg not-raw-string.arg + (~is ty.arg.0 'literal)) ; can't use 'literal?' because we want to rename offsets +;? (prn "112 @arg") + (let name (space-to-name arg default-name) +;? (prn "113 @arg @name @keys.location* @(tostring:pr location*.name)") +;? (when (is arg '((y integer) (space 1))) +;? (prn "@arg => @name")) + (when (aand location*.name (it v.arg)) +;? (prn 114) + (zap location*.name v.arg)) +;? (prn 115) + ))) + +(def space-to-name (arg default-name) + (ret name default-name + (when (~is space.arg 'global) + (repeat space.arg + (zap next-space-generator* name))))) + +(proc check-numeric-address (instrs name) + (unless allow-raw-addresses* + (on instr instrs + (when acons.instr ; not a label + (let (oargs op args) (parse-instr instr) + (each arg oargs + (when (and acons.arg ; not dummy _ or raw string + (isa v.arg 'int) + (~is v.arg 0) + (~pos '(raw) metadata.arg) + (~literal? arg)) + (prn "using a raw integer address @repr.arg in @name (instruction #@index)"))) + (each arg args + (when (and acons.arg ; not dummy _ or raw string + (isa v.arg 'int) + (~is v.arg 0) + (~pos '(raw) metadata.arg) + (~literal? arg)) + (prn "using a raw integer address @repr.arg in @name (instruction #@index)")))))))) + +;; literate tangling system for reordering code + +(def convert-quotes (instrs) + (let deferred (queue) + (each instr instrs + (when (acons instr) + (case instr.0 + defer + (let (q qinstrs) instr.1 + (assert (is 'make-br-fn q) "defer: first arg must be [quoted]") + (each qinstr qinstrs + (enq qinstr deferred)))))) + (accum yield + (each instr instrs + (if atom.instr ; label + (yield instr) + (is instr.0 'defer) + nil ; skip + (is instr.0 'reply) + (do + (when cdr.instr ; return values + (= instr.0 'prepare-reply) + (yield instr)) + (each instr (as cons deferred) + (yield instr)) + (yield '(reply))) + :else + (yield instr))) + (each instr (as cons deferred) + (yield instr))))) + +(on-init + (= before* (table)) ; label -> queue of fragments + (= after* (table))) ; label -> list of fragments + +; see add-code below for adding to before* and after* + +(def insert-code (instrs (o name)) +;? (tr "insert-code " instrs) + (loop (instrs instrs) + (accum yield + (each instr instrs + (if (and (acons instr) (~is 'begin car.instr)) + ; simple instruction + (yield instr) + (and (acons instr) (is 'begin car.instr)) + ; block + (yield `{begin ,@(recur cdr.instr)}) + (atom instr) + ; label + (do +;? (prn "tangling " instr) + (each fragment (as cons (or (and name (before* (sym:string name '/ instr))) + before*.instr)) + (each instr fragment + (yield instr))) + (yield instr) + (each fragment (or (and name (after* (sym:string name '/ instr))) + after*.instr) + (each instr fragment + (yield instr))))))))) + +;; loading code into the virtual machine + +(def add-code (forms) + (each (op . rest) forms + (case op + ; function <name> [ <instructions> ] + ; don't apply our lightweight tools just yet + function! + (let (name (_make-br-fn body)) rest + (assert (is 'make-br-fn _make-br-fn)) + (= name (v tokenize-arg.name)) + (= function*.name body)) + function + (let (name (_make-br-fn body)) rest + (assert (is 'make-br-fn _make-br-fn)) + (= name (v tokenize-arg.name)) + (when function*.name + (prn "adding new clause to @name")) + (= function*.name (join body function*.name))) + + ; and-record <type> [ <name:types> ] + and-record + (let (name (_make-br-fn fields)) rest + (assert (is 'make-br-fn _make-br-fn)) + (= name (v tokenize-arg.name)) + (let fields (map tokenize-arg fields) + (= type*.name (obj size len.fields + and-record t + ; dump all metadata for now except field name and type + elems (map cdar fields) + fields (map caar fields))))) + + ; primitive <type> + primitive + (let (name) rest + (= name (v tokenize-arg.name)) + (= type*.name (obj size 1))) + + ; address <type> <elem-type> + address + (let (name types) rest + (= name (v tokenize-arg.name)) + (= type*.name (obj size 1 + address t + elem types))) + + ; array <type> <elem-type> + array + (let (name types) rest + (= name (v tokenize-arg.name)) + (= type*.name (obj array t + elem types))) + + ; before <label> [ <instructions> ] + ; + ; multiple before directives => code in order + before + (let (label (_make-br-fn fragment)) rest + (assert (is 'make-br-fn _make-br-fn)) + ; todo: stop using '/' in non-standard manner + ;(= label (v tokenize-arg.label)) + (or= before*.label (queue)) + (enq fragment before*.label)) + + ; after <label> [ <instructions> ] + ; + ; multiple after directives => code in *reverse* order + ; (if initialization order in a function is A B, corresponding + ; finalization order should be B A) + after + (let (label (_make-br-fn fragment)) rest + (assert (is 'make-br-fn _make-br-fn)) + ; todo: stop using '/' in non-standard manner + ;(= label (v tokenize-arg.label)) + (push fragment after*.label)) + + ;else + (prn "unrecognized top-level " (cons op rest)) + ))) + +(def freeze (function-table) + (each (name body) canon.function-table +;? (prn "freeze " name) + (= function-table.name (convert-labels:convert-braces:tokenize-args:insert-code body name))) + (each (name body) canon.function-table + (check-default-space body name)) + (each (name body) canon.function-table + (check-numeric-address body name)) + (each (name body) canon.function-table + (add-next-space-generator body name)) + ; keep converting names until none remain + ; (we need to skip unrecognized spaces) + (let change t + (while change + (= change nil) + (each (name body) canon.function-table +;? (prn name) ;? 1 + (when (no location*.name) + (= change t)) + (or= location*.name (assign-names-to-location body name))))) +;? (each (name body) canon.function-table ;? 1 +;? (or= location*.name (assign-names-to-location body name))) ;? 1 + (each (name body) canon.function-table + (= function-table.name (replace-names-with-location body name))) + ; we could clear location* at this point, but maybe we'll find a use for it + ) + +(def freeze-another (fn-name) + (= function*.fn-name (convert-labels:convert-braces:tokenize-args:insert-code function*.fn-name fn-name)) + (check-default-space function*.fn-name fn-name) + (add-next-space-generator function*.fn-name fn-name) + (= location*.fn-name (assign-names-to-location function*.fn-name fn-name location*.fn-name)) + (replace-names-with-location function*.fn-name fn-name)) + +(def tokenize-arg (arg) +;? (tr "tokenize-arg " arg) + (if (in arg '<- '_) + arg + (isa arg 'sym) + (map [map [fromstring _ (read)] _] + (map [tokens _ #\:] + (tokens string.arg #\/))) + :else + arg)) + +(def tokenize-args (instrs) +;? (tr "tokenize-args " instrs) +;? (prn2 "@(tostring prn.instrs) => " + (accum yield + (each instr instrs + (if atom.instr + (yield instr) + (is 'begin instr.0) + (yield `{begin ,@(tokenize-args cdr.instr)}) + :else + (yield (map tokenize-arg instr)))))) +;? ) + +(def prn2 (msg . args) + (pr msg) + (apply prn args)) + +(def canon (table) + (sort (compare < [tostring (prn:car _)]) (as cons table))) + +(def int-canon (table) + (sort (compare < car) (as cons table))) + +(def routine-canon (routine-table) + (sort (compare < label:car) (as cons routine-table))) + +(def repr (val) + (tostring write.val)) + +;; test helpers + +(def memory-contains (addr value) +;? (prn "Looking for @value starting at @addr") + (loop (addr addr + idx 0) +;? (prn "@idx vs @addr") + (if (>= idx len.value) + t + (~is memory*.addr value.idx) + (do1 nil + (prn "@addr should contain @value.idx but contains @memory*.addr")) + :else + (recur (+ addr 1) (+ idx 1))))) + +(def memory-contains-array (addr value) + (and (>= memory*.addr len.value) + (loop (addr (+ addr 1) ; skip count + idx 0) + (if (>= idx len.value) + t + (~is memory*.addr value.idx) + nil + :else + (recur (+ addr 1) (+ idx 1)))))) + +; like memory-contains-array but shows diffs +(def memory-contains-array-verbose (addr value) + (prn "Mismatch when looking at @addr, size @memory*.addr vs @len.value") + (and (>= memory*.addr len.value) + (loop (addr (+ addr 1) ; skip count + idx 0) + (and (< idx len.value) (prn "comparing @idx: @memory*.addr and @value.idx")) + (if (>= idx len.value) + t + (~is memory*.addr value.idx) + (do1 nil + (prn "@addr should contain @(repr value.idx) but contains @(repr memory*.addr)") + (recur (+ addr 1) (+ idx 1))) + :else + (recur (+ addr 1) (+ idx 1)))))) + +; like memory-contains-array but shows diffs in 2D +(def screen-contains (addr width value) + (or (memory-contains-array addr value) + (do1 nil + (prn "Mismatch detected. Screen contents:") + (with (row-start-addr (+ addr 1) ; skip count + idx 0) + (for row 0 (< row (/ len.value width)) (do ++.row (++ row-start-addr width)) + (pr ". ") + (for col 0 (< col width) ++.col + (with (expected value.idx + got (memory* (+ col row-start-addr))) + (pr got) + (pr (if (is expected got) " " "X"))) + ++.idx) + (prn " .") + ))))) + +; run code in tests +(mac run-code (name . body) + ; careful to avoid re-processing functions and adding noise to traces + `(do + (prn "-- " ',name) + (trace "===" ',name) + (wipe (function* ',name)) + (add-code '((function ,name [ ,@body ]))) + (freeze-another ',name) +;? (set dump-trace*) ;? 1 + (run-more ',name))) + +; kludge to prevent reloading functions in .mu files for every test +(def reset2 () + (= memory* (table)) + (= Memory-allocated-until 1000) + (awhen curr-trace-file* + (tofile (+ trace-dir* it) + (each (label trace) (as cons traces*) + (pr label ": " trace)))) + (= curr-trace-file* nil) + (= traces* (queue)) + (wipe dump-trace*) + (wipe function*!main) + (wipe location*!main) + (= running-routines* (queue)) + (= sleeping-routines* (table)) + (wipe completed-routines*) + (wipe routine*) + (= abort-routine* (parameter nil)) + (= curr-cycle* 0) + (= scheduling-interval* 500) + (= scheduler-switch-table* nil) + ) + +(= disable-debug-prints-in-console-mode* nil) +(def test-only-settings () + (set allow-raw-addresses*) + (set disable-debug-prints-in-console-mode*)) + +(def routine-that-ran (f) + (find [some [is f _!fn-name] stack._] + completed-routines*)) + +(def routine-running (f) + (or + (find [some [is f _!fn-name] stack._] + completed-routines*) + (find [some [is f _!fn-name] stack._] + (as cons running-routines*)) + (find [some [is f _!fn-name] stack._] + (keys sleeping-routines*)) + (and routine* + (some [is f _!fn-name] stack.routine*) + routine*))) + +(def ran-to-completion (f) + ; if a routine calling f ran to completion there'll be no sign of it in any + ; completed call-stacks. + (~routine-that-ran f)) + +(def restart (routine) + (while (in top.routine!fn-name 'read 'write) + (pop-stack routine)) + (wipe rep.routine!sleep) + (wipe rep.routine!error) + (enq routine running-routines*)) + +(def dump (msg routine) + (prn "= @msg " rep.routine!sleep) + (prn:rem [in car._ 'sleep 'call-stack] (as cons rep.routine)) + (each frame rep.routine!call-stack + (prn " @frame!fn-name") + (each (key val) frame + (unless (is key 'fn-name) + (prn " " key " " val))))) + +;; system software +; create once, load before every test + +(reset) +(= system-function* (table)) + +(mac init-fn (name . body) + (let real-name (v tokenize-arg.name) + `(= (system-function* ',real-name) ',body))) + +(def load-system-functions () + (each (name f) system-function* + (= (function* name) + (system-function* name)))) + +; allow running mu.arc without load.arc +(unless bound!section (= section do)) + +(section 100 + +(init-fn maybe-coerce + (default-space:space-address <- new space:literal 30:literal) + (x:tagged-value-address <- new tagged-value:literal) + (x:tagged-value-address/deref <- next-input) + (p:type <- next-input) + (xtype:type <- get x:tagged-value-address/deref type:offset) + (match?:boolean <- equal xtype:type p:type) + { begin + (break-if match?:boolean) + (reply 0:literal nil:literal) + } + (xvalue:location <- get x:tagged-value-address/deref payload:offset) + (reply xvalue:location match?:boolean)) + +(init-fn init-tagged-value + (default-space:space-address <- new space:literal 30:literal) + ; assert sizeof:arg.0 == 1 + (xtype:type <- next-input) + (xtypesize:integer <- sizeof xtype:type) + (xcheck:boolean <- equal xtypesize:integer 1:literal) + (assert xcheck:boolean) + ; todo: check that arg 0 matches the type? or is that for the future typechecker? + (result:tagged-value-address <- new tagged-value:literal) + ; result->type = arg 0 + (resulttype:location <- get-address result:tagged-value-address/deref type:offset) + (resulttype:location/deref <- copy xtype:type) + ; result->payload = arg 1 + (locaddr:location <- get-address result:tagged-value-address/deref payload:offset) + (locaddr:location/deref <- next-input) + (reply result:tagged-value-address)) + +(init-fn list-next ; list-address -> list-address + (default-space:space-address <- new space:literal 30:literal) + (base:list-address <- next-input) + (result:list-address <- get base:list-address/deref cdr:offset) + (reply result:list-address)) + +(init-fn list-value-address ; list-address -> tagged-value-address + (default-space:space-address <- new space:literal 30:literal) + (base:list-address <- next-input) + (result:tagged-value-address <- get-address base:list-address/deref car:offset) + (reply result:tagged-value-address)) + +; create a list out of a list of args +; only integers for now +(init-fn init-list + (default-space:space-address <- new space:literal 30:literal) + ; new-list = curr = new list + (result:list-address <- new list:literal) + (curr:list-address <- copy result:list-address) + { begin + ; while read curr-value + (curr-value:integer exists?:boolean <- next-input) + (break-unless exists?:boolean) + ; curr.cdr = new list + (next:list-address-address <- get-address curr:list-address/deref cdr:offset) + (next:list-address-address/deref <- new list:literal) + ; curr = curr.cdr + (curr:list-address <- list-next curr:list-address) + ; curr.car = type:curr-value + (dest:tagged-value-address <- list-value-address curr:list-address) + (dest:tagged-value-address/deref <- save-type curr-value:integer) + (loop) + } + ; return new-list.cdr + (result:list-address <- list-next result:list-address) ; memory leak + (reply result:list-address)) + +; create an array out of a list of scalar args +; only integers for now +(init-fn init-array + (default-space:space-address <- new space:literal 30:literal) + (capacity:integer <- copy 0:literal) + { begin + ; while read curr-value + (curr-value:integer exists?:boolean <- next-input) + (break-unless exists?:boolean) + (capacity:integer <- add capacity:integer 1:literal) + (loop) + } + (result:integer-array-address <- new integer-array:literal capacity:integer) + (rewind-inputs) +;? (xxx:integer <- next-input) ;? 1 +;? ($print xxx:integer) ;? 1 +;? (rewind-inputs) ;? 1 + (i:integer <- copy 0:literal) + { begin + ; while read curr-value + (done?:boolean <- greater-or-equal i:integer capacity:integer) + (break-if done?:boolean) + (curr-value:integer exists?:boolean <- next-input) + (assert exists?:boolean) + (tmp:integer-address <- index-address result:integer-array-address/deref i:integer) + (tmp:integer-address/deref <- copy curr-value:integer) + (i:integer <- add i:integer 1:literal) + (loop) + } + (reply result:integer-array-address)) + +(init-fn list-length + (default-space:space-address <- new space:literal 30:literal) + (curr:list-address <- next-input) +;? ; recursive +;? { begin +;? ; if empty list return 0 +;? (t1:tagged-value-address <- list-value-address curr:list-address) +;? (break-if t1:tagged-value-address) +;? (reply 0:literal) +;? } +;? ; else return 1+length(curr.cdr) +;? ;? ($print (("recurse\n" literal))) +;? (next:list-address <- list-next curr:list-address) +;? (sub:integer <- list-length next:list-address) +;? (result:integer <- add sub:integer 1:literal) +;? (reply result:integer)) + ; iterative solution + (result:integer <- copy 0:literal) + { begin + ; while curr + (t1:tagged-value-address <- list-value-address curr:list-address) + (break-unless t1:tagged-value-address) + ; ++result + (result:integer <- add result:integer 1:literal) +;? ($print result:integer) +;? ($print (("\n" literal))) + ; curr = curr.cdr + (curr:list-address <- list-next curr:list-address) + (loop) + } + (reply result:integer)) + +(init-fn init-channel + (default-space:space-address <- new space:literal 30:literal) + ; result = new channel + (result:channel-address <- new channel:literal) + ; result.first-full = 0 + (full:integer-address <- get-address result:channel-address/deref first-full:offset) + (full:integer-address/deref <- copy 0:literal) + ; result.first-free = 0 + (free:integer-address <- get-address result:channel-address/deref first-free:offset) + (free:integer-address/deref <- copy 0:literal) + ; result.circular-buffer = new tagged-value[arg+1] + (capacity:integer <- next-input) + (capacity:integer <- add capacity:integer 1:literal) ; unused slot for full? below + (channel-buffer-address:tagged-value-array-address-address <- get-address result:channel-address/deref circular-buffer:offset) + (channel-buffer-address:tagged-value-array-address-address/deref <- new tagged-value-array:literal capacity:integer) + (reply result:channel-address)) + +(init-fn capacity + (default-space:space-address <- new space:literal 30:literal) + (chan:channel <- next-input) + (q:tagged-value-array-address <- get chan:channel circular-buffer:offset) + (qlen:integer <- length q:tagged-value-array-address/deref) + (reply qlen:integer)) + +(init-fn write + (default-space:space-address <- new space:literal 30:literal) + (chan:channel-address <- next-input) + (val:tagged-value <- next-input) + { begin + ; block if chan is full + (full:boolean <- full? chan:channel-address/deref) + (break-unless full:boolean) + (full-address:integer-address <- get-address chan:channel-address/deref first-full:offset) + (sleep until-location-changes:literal full-address:integer-address/deref) + } + ; store val + (q:tagged-value-array-address <- get chan:channel-address/deref circular-buffer:offset) + (free:integer-address <- get-address chan:channel-address/deref first-free:offset) + (dest:tagged-value-address <- index-address q:tagged-value-array-address/deref free:integer-address/deref) + (dest:tagged-value-address/deref <- copy val:tagged-value) + ; increment free + (free:integer-address/deref <- add free:integer-address/deref 1:literal) + { begin + ; wrap free around to 0 if necessary + (qlen:integer <- length q:tagged-value-array-address/deref) + (remaining?:boolean <- less-than free:integer-address/deref qlen:integer) + (break-if remaining?:boolean) + (free:integer-address/deref <- copy 0:literal) + } + (reply chan:channel-address/deref/same-as-arg:0)) + +(init-fn read + (default-space:space-address <- new space:literal 30:literal) + (chan:channel-address <- next-input) +;? ($dump-channel chan:channel-address) ;? 2 + { begin + ; block if chan is empty + (empty:boolean <- empty? chan:channel-address/deref) + (break-unless empty:boolean) + (free-address:integer-address <- get-address chan:channel-address/deref first-free:offset) + (sleep until-location-changes:literal free-address:integer-address/deref) + } + ; read result + (full:integer-address <- get-address chan:channel-address/deref first-full:offset) + (q:tagged-value-array-address <- get chan:channel-address/deref circular-buffer:offset) + (result:tagged-value <- index q:tagged-value-array-address/deref full:integer-address/deref) + ; increment full + (full:integer-address/deref <- add full:integer-address/deref 1:literal) + { begin + ; wrap full around to 0 if necessary + (qlen:integer <- length q:tagged-value-array-address/deref) + (remaining?:boolean <- less-than full:integer-address/deref qlen:integer) + (break-if remaining?:boolean) + (full:integer-address/deref <- copy 0:literal) + } + (reply result:tagged-value chan:channel-address/deref/same-as-arg:0)) + +; An empty channel has first-empty and first-full both at the same value. +(init-fn empty? + (default-space:space-address <- new space:literal 30:literal) + ; return arg.first-full == arg.first-free + (chan:channel <- next-input) + (full:integer <- get chan:channel first-full:offset) + (free:integer <- get chan:channel first-free:offset) + (result:boolean <- equal full:integer free:integer) + (reply result:boolean)) + +; A full channel has first-empty just before first-full, wasting one slot. +; (Other alternatives: https://en.wikipedia.org/wiki/Circular_buffer#Full_.2F_Empty_Buffer_Distinction) +(init-fn full? + (default-space:space-address <- new space:literal 30:literal) + (chan:channel <- next-input) + ; curr = chan.first-free + 1 + (curr:integer <- get chan:channel first-free:offset) + (curr:integer <- add curr:integer 1:literal) + { begin + ; if (curr == chan.capacity) curr = 0 + (qlen:integer <- capacity chan:channel) + (remaining?:boolean <- less-than curr:integer qlen:integer) + (break-if remaining?:boolean) + (curr:integer <- copy 0:literal) + } + ; return chan.first-full == curr + (full:integer <- get chan:channel first-full:offset) + (result:boolean <- equal full:integer curr:integer) + (reply result:boolean)) + +(init-fn string-equal + (default-space:space-address <- new space:literal 30:literal) + (a:string-address <- next-input) + (a-len:integer <- length a:string-address/deref) + (b:string-address <- next-input) + (b-len:integer <- length b:string-address/deref) + ; compare lengths + { begin + (length-equal?:boolean <- equal a-len:integer b-len:integer) + (break-if length-equal?:boolean) + (reply nil:literal) + } + ; compare each corresponding byte + (i:integer <- copy 0:literal) + { begin + (done?:boolean <- greater-or-equal i:integer a-len:integer) + (break-if done?:boolean) + (a2:byte <- index a:string-address/deref i:integer) + (b2:byte <- index b:string-address/deref i:integer) + { begin + (chars-match?:boolean <- equal a2:byte b2:byte) + (break-if chars-match?:boolean) + (reply nil:literal) + } + (i:integer <- add i:integer 1:literal) + (loop) + } + (reply t:literal) +) + +(init-fn strcat + (default-space:space-address <- new space:literal 30:literal) + ; result = new string[a.length + b.length] + (a:string-address <- next-input) + (a-len:integer <- length a:string-address/deref) + (b:string-address <- next-input) + (b-len:integer <- length b:string-address/deref) + (result-len:integer <- add a-len:integer b-len:integer) + (result:string-address <- new string:literal result-len:integer) + ; copy a into result + (result-idx:integer <- copy 0:literal) + (i:integer <- copy 0:literal) + { begin + ; while (i < a.length) + (a-done?:boolean <- greater-or-equal i:integer a-len:integer) + (break-if a-done?:boolean) + ; result[result-idx] = a[i] + (out:byte-address <- index-address result:string-address/deref result-idx:integer) + (in:byte <- index a:string-address/deref i:integer) + (out:byte-address/deref <- copy in:byte) + ; ++i + (i:integer <- add i:integer 1:literal) + ; ++result-idx + (result-idx:integer <- add result-idx:integer 1:literal) + (loop) + } + ; copy b into result + (i:integer <- copy 0:literal) + { begin + ; while (i < b.length) + (b-done?:boolean <- greater-or-equal i:integer b-len:integer) + (break-if b-done?:boolean) + ; result[result-idx] = a[i] + (out:byte-address <- index-address result:string-address/deref result-idx:integer) + (in:byte <- index b:string-address/deref i:integer) + (out:byte-address/deref <- copy in:byte) + ; ++i + (i:integer <- add i:integer 1:literal) + ; ++result-idx + (result-idx:integer <- add result-idx:integer 1:literal) + (loop) + } + (reply result:string-address)) + +; replace underscores in first with remaining args +(init-fn interpolate ; string-address template, string-address a.. + (default-space:space-address <- new space:literal 60:literal) + (template:string-address <- next-input) + ; compute result-len, space to allocate for result + (tem-len:integer <- length template:string-address/deref) + (result-len:integer <- copy tem-len:integer) + { begin + ; while arg received + (a:string-address arg-received?:boolean <- next-input) + (break-unless arg-received?:boolean) +;? ($print ("arg now: " literal)) +;? ($print a:string-address) +;? ($print "@":literal) +;? ($print a:string-address/deref) ; todo: test (m on scoped array) +;? ($print "\n":literal) +;? ;? (assert nil:literal) + ; result-len = result-len + arg.length - 1 (for the 'underscore' being replaced) + (a-len:integer <- length a:string-address/deref) + (result-len:integer <- add result-len:integer a-len:integer) + (result-len:integer <- subtract result-len:integer 1:literal) +;? ($print ("result-len now: " literal)) +;? ($print result-len:integer) +;? ($print "\n":literal) + (loop) + } + ; rewind to start of non-template args + (_ <- input 0:literal) + ; result = new string[result-len] + (result:string-address <- new string:literal result-len:integer) + ; repeatedly copy sections of template and 'holes' into result + (result-idx:integer <- copy 0:literal) + (i:integer <- copy 0:literal) + { begin + ; while arg received + (a:string-address arg-received?:boolean <- next-input) + (break-unless arg-received?:boolean) + ; copy template into result until '_' + { begin + ; while (i < template.length) + (tem-done?:boolean <- greater-or-equal i:integer tem-len:integer) + (break-if tem-done?:boolean 2:blocks) + ; while template[i] != '_' + (in:byte <- index template:string-address/deref i:integer) + (underscore?:boolean <- equal in:byte ((#\_ literal))) + (break-if underscore?:boolean) + ; result[result-idx] = template[i] + (out:byte-address <- index-address result:string-address/deref result-idx:integer) + (out:byte-address/deref <- copy in:byte) + ; ++i + (i:integer <- add i:integer 1:literal) + ; ++result-idx + (result-idx:integer <- add result-idx:integer 1:literal) + (loop) + } +;? ($print ("i now: " literal)) +;? ($print i:integer) +;? ($print "\n":literal) + ; copy 'a' into result + (j:integer <- copy 0:literal) + { begin + ; while (j < a.length) + (arg-done?:boolean <- greater-or-equal j:integer a-len:integer) + (break-if arg-done?:boolean) + ; result[result-idx] = a[j] + (in:byte <- index a:string-address/deref j:integer) +;? ($print ("copying: " literal)) +;? ($print in:byte) +;? ($print (" at: " literal)) +;? ($print result-idx:integer) +;? ($print "\n":literal) + (out:byte-address <- index-address result:string-address/deref result-idx:integer) + (out:byte-address/deref <- copy in:byte) + ; ++j + (j:integer <- add j:integer 1:literal) + ; ++result-idx + (result-idx:integer <- add result-idx:integer 1:literal) + (loop) + } + ; skip '_' in template + (i:integer <- add i:integer 1:literal) +;? ($print ("i now: " literal)) +;? ($print i:integer) +;? ($print "\n":literal) + (loop) ; interpolate next arg + } + ; done with holes; copy rest of template directly into result + { begin + ; while (i < template.length) + (tem-done?:boolean <- greater-or-equal i:integer tem-len:integer) + (break-if tem-done?:boolean) + ; result[result-idx] = template[i] + (in:byte <- index template:string-address/deref i:integer) +;? ($print ("copying: " literal)) +;? ($print in:byte) +;? ($print (" at: " literal)) +;? ($print result-idx:integer) +;? ($print "\n":literal) + (out:byte-address <- index-address result:string-address/deref result-idx:integer) + (out:byte-address/deref <- copy in:byte) + ; ++i + (i:integer <- add i:integer 1:literal) + ; ++result-idx + (result-idx:integer <- add result-idx:integer 1:literal) + (loop) + } + (reply result:string-address)) + +(init-fn find-next ; string, character, index -> next index + (default-space:space-address <- new space:literal 30:literal) + (text:string-address <- next-input) + (pattern:character <- next-input) + (idx:integer <- next-input) + (len:integer <- length text:string-address/deref) + { begin + (eof?:boolean <- greater-or-equal idx:integer len:integer) + (break-if eof?:boolean) + (curr:byte <- index text:string-address/deref idx:integer) + (found?:boolean <- equal curr:byte pattern:character) + (break-if found?:boolean) + (idx:integer <- add idx:integer 1:literal) + (loop) + } + (reply idx:integer)) + +(init-fn find-substring/variant:find-next + (default-space:space-address <- new space:literal 30:literal) + ; fairly dumb algorithm; used for parsing code and traces + (text:string-address <- next-input) + (pattern:string-address <- next-input) + (idx:integer <- next-input) + (first:character <- index pattern:string-address/deref 0:literal) + ; repeatedly check for match at current idx + (len:integer <- length text:string-address/deref) + { begin + ; does some unnecessary work checking for substrings even when there isn't enough of text left + (eof?:boolean <- greater-or-equal idx:integer len:integer) + (break-if eof?:boolean) + (found?:boolean <- match-at text:string-address pattern:string-address idx:integer) + (break-if found?:boolean) + (idx:integer <- add idx:integer 1:literal) + ; optimization: skip past indices that definitely won't match + (idx:integer <- find-next text:string-address first:character idx:integer) + (loop) + } + (reply idx:integer) +) + +(init-fn match-at + (default-space:space-address <- new space:literal 30:literal) + ; fairly dumb algorithm; used for parsing code and traces + (text:string-address <- next-input) + (pattern:string-address <- next-input) + (idx:integer <- next-input) + (pattern-len:integer <- length pattern:string-address/deref) + ; check that there's space left for the pattern + { begin + (x:integer <- length text:string-address/deref) + (x:integer <- subtract x:integer pattern-len:integer) + (enough-room?:boolean <- lesser-or-equal idx:integer x:integer) + (break-if enough-room?:boolean) + (reply nil:literal) + } + ; check each character of pattern + (pattern-idx:integer <- copy 0:literal) + { begin + (done?:boolean <- greater-or-equal pattern-idx:integer pattern-len:integer) + (break-if done?:boolean) + (c:character <- index text:string-address/deref idx:integer) + (exp:character <- index pattern:string-address/deref pattern-idx:integer) + { begin + (match?:boolean <- equal c:character exp:character) + (break-if match?:boolean) + (reply nil:literal) + } + (idx:integer <- add idx:integer 1:literal) + (pattern-idx:integer <- add pattern-idx:integer 1:literal) + (loop) + } + (reply t:literal) +) + +(init-fn split ; string, character -> string-address-array-address + (default-space:space-address <- new space:literal 30:literal) + (s:string-address <- next-input) + (delim:character <- next-input) + ; empty string? return empty array + (len:integer <- length s:string-address/deref) + { begin + (empty?:boolean <- equal len:integer 0:literal) + (break-unless empty?:boolean) + (result:string-address-array-address <- new string-address-array:literal 0:literal) + (reply result:string-address-array-address) + } + ; count #pieces we need room for + (count:integer <- copy 1:literal) ; n delimiters = n+1 pieces + (idx:integer <- copy 0:literal) + { begin + (idx:integer <- find-next s:string-address delim:character idx:integer) + (done?:boolean <- greater-or-equal idx:integer len:integer) + (break-if done?:boolean) + (idx:integer <- add idx:integer 1:literal) + (count:integer <- add count:integer 1:literal) + (loop) + } + ; allocate space +;? ($print (("alloc: " literal))) +;? ($print count:integer) +;? ($print (("\n" literal))) + (result:string-address-array-address <- new string-address-array:literal count:integer) + ; repeatedly copy slices (start..end) until delimiter into result[curr-result] + (curr-result:integer <- copy 0:literal) + (start:integer <- copy 0:literal) + { begin + ; while next delim exists + (done?:boolean <- greater-or-equal start:integer len:integer) + (break-if done?:boolean) + (end:integer <- find-next s:string-address delim:character start:integer) +;? ($print start:integer) ;? 1 +;? ($print ((" " literal))) ;? 1 +;? ($print end:integer) ;? 1 +;? ($print (("\n" literal))) ;? 1 + ; copy start..end into result[curr-result] + (dest:string-address-address <- index-address result:string-address-array-address/deref curr-result:integer) + (dest:string-address-address/deref <- string-copy s:string-address start:integer end:integer) + ; slide over to next slice + (start:integer <- add end:integer 1:literal) + (curr-result:integer <- add curr-result:integer 1:literal) + (loop) + } + (reply result:string-address-array-address) +) + +(init-fn split-first-at-substring/variant:split-first ; string text, string delim -> string first, string rest + (default-space:space-address <- new space:literal 30:literal) + (text:string-address <- next-input) + (delim:string-address <- next-input) + ; empty string? return empty strings + (len:integer <- length text:string-address/deref) + { begin + (empty?:boolean <- equal len:integer 0:literal) + (break-unless empty?:boolean) + (x:string-address <- new "") + (y:string-address <- new "") + (reply x:string-address y:string-address) + } + (idx:integer <- find-substring text:string-address delim:string-address 0:literal) + (x:string-address <- string-copy text:string-address 0:literal idx:integer) + (k:integer <- length delim:string-address/deref) + (idx:integer <- add idx:integer k:integer) + (y:string-address <- string-copy text:string-address idx:integer len:integer) + (reply x:string-address y:string-address) +) + +(init-fn split-first ; string text, character delim -> string first, string rest + (default-space:space-address <- new space:literal 30:literal) + (text:string-address <- next-input) + (delim:character <- next-input) + ; empty string? return empty strings + (len:integer <- length text:string-address/deref) + { begin + (empty?:boolean <- equal len:integer 0:literal) + (break-unless empty?:boolean) + (x:string-address <- new "") + (y:string-address <- new "") + (reply x:string-address y:string-address) + } + (idx:integer <- find-next text:string-address delim:character 0:literal) + (x:string-address <- string-copy text:string-address 0:literal idx:integer) + (idx:integer <- add idx:integer 1:literal) + (y:string-address <- string-copy text:string-address idx:integer len:integer) + (reply x:string-address y:string-address) +) + +; todo: make this generic +(init-fn string-copy ; buf start end -> address of new array + (default-space:space-address <- new space:literal 30:literal) + (buf:string-address <- next-input) + (start:integer <- next-input) + (end:integer <- next-input) +;? ($print ((" copy: " literal))) ;? 1 +;? ($print start:integer) ;? 1 +;? ($print (("-" literal))) ;? 1 +;? ($print end:integer) ;? 1 +;? ($print (("\n" literal))) ;? 1 + ; if end is out of bounds, trim it + (len:integer <- length buf:string-address/deref) + (end:integer <- min len:integer end:integer) + ; allocate space for result + (len:integer <- subtract end:integer start:integer) + (result:string-address <- new string:literal len:integer) + ; copy start..end into result[curr-result] + (src-idx:integer <- copy start:integer) + (dest-idx:integer <- copy 0:literal) + { begin + (done?:boolean <- greater-or-equal src-idx:integer end:integer) + (break-if done?:boolean) + (src:character <- index buf:string-address/deref src-idx:integer) +;? ($print ((" copying " literal))) ;? 1 +;? ($print src:character) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (dest:character-address <- index-address result:string-address/deref dest-idx:integer) + (dest:character-address/deref <- copy src:character) + (src-idx:integer <- add src-idx:integer 1:literal) + (dest-idx:integer <- add dest-idx:integer 1:literal) + (loop) + } + (reply result:string-address) +) + +(init-fn min + (default-space:space-address <- new space:literal 30:literal) + (x:integer <- next-input) + (y:integer <- next-input) + { begin + (return-x?:boolean <- less-than x:integer y:integer) + (break-if return-x?:boolean) + (reply y:integer) + } + (reply x:integer) +) + +(init-fn max + (default-space:space-address <- new space:literal 30:literal) + (x:integer <- next-input) + (y:integer <- next-input) + { begin + (return-x?:boolean <- greater-than x:integer y:integer) + (break-if return-x?:boolean) + (reply y:integer) + } + (reply x:integer) +) + +(init-fn init-stream + (default-space:space-address <- new space:literal 30:literal) + (in:string-address <- next-input) + (result:stream-address <- new stream:literal) + (x:integer-address <- get-address result:stream-address/deref pointer:offset) + (x:integer-address/deref <- copy 0:literal) + (y:string-address-address <- get-address result:stream-address/deref data:offset) + (y:string-address-address/deref <- copy in:string-address) + (reply result:stream-address) +) + +(init-fn rewind-stream + (default-space:space-address <- new space:literal 30:literal) + (in:stream-address <- next-input) + (x:integer-address <- get-address in:stream-address/deref pointer:offset) + (x:integer-address/deref <- copy 0:literal) + (reply in:stream-address/same-as-arg:0) +) + +(init-fn read-line + (default-space:space-address <- new space:literal 30:literal) + (in:stream-address <- next-input) + (idx:integer-address <- get-address in:stream-address/deref pointer:offset) + (s:string-address <- get in:stream-address/deref data:offset) +;? ($print (("idx before: " literal))) ;? 1 +;? ($print idx:integer-address/deref) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (next-idx:integer <- find-next s:string-address ((#\newline literal)) idx:integer-address/deref) +;? ($print (("next-idx: " literal))) ;? 1 +;? ($print next-idx:integer) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (result:string-address <- string-copy s:string-address idx:integer-address/deref next-idx:integer) + (idx:integer-address/deref <- add next-idx:integer 1:literal) ; skip newline +;? ($print (("idx now: " literal))) ;? 1 +;? ($print idx:integer-address/deref) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (reply result:string-address) +) + +(init-fn read-character + (default-space:space-address <- new space:literal 30:literal) + (in:stream-address <- next-input) + (idx:integer-address <- get-address in:stream-address/deref pointer:offset) + (s:string-address <- get in:stream-address/deref data:offset) + (c:character <- index s:string-address/deref idx:integer-address/deref) + (idx:integer-address/deref <- add idx:integer-address/deref 1:literal) + (reply c:character) +) + +(init-fn end-of-stream? + (default-space:space-address <- new space:literal 30:literal) + (in:stream-address <- next-input) + (idx:integer <- get in:stream-address/deref pointer:offset) + (s:string-address <- get in:stream-address/deref data:offset) + (len:integer <- length s:string-address/deref) +;? ($print (("eos: " literal))) ;? 1 +;? ($print len:integer) ;? 1 +;? ($print (("\n" literal))) ;? 1 +;? ($print (("idx: " literal))) ;? 1 +;? ($print idx:integer) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (result:boolean <- greater-or-equal idx:integer len:integer) + (reply result:boolean) +) + +(init-fn init-keyboard + (default-space:space-address <- new space:literal 30:literal) + (result:keyboard-address <- new keyboard:literal) + (buf:string-address-address <- get-address result:keyboard-address/deref data:offset) + (buf:string-address-address/deref <- next-input) + (idx:integer-address <- get-address result:keyboard-address/deref index:offset) + (idx:integer-address/deref <- copy 0:literal) + (reply result:keyboard-address) +) + +(init-fn read-key + (default-space:space-address <- new space:literal 30:literal) + (x:keyboard-address <- next-input) + (screen:terminal-address <- next-input) + { begin + (break-unless x:keyboard-address) + (idx:integer-address <- get-address x:keyboard-address/deref index:offset) + (buf:string-address <- get x:keyboard-address/deref data:offset) + (max:integer <- length buf:string-address/deref) + { begin + (done?:boolean <- greater-or-equal idx:integer-address/deref max:integer) + (break-unless done?:boolean) + (reply ((#\null literal))) + } + (c:character <- index buf:string-address/deref idx:integer-address/deref) + (idx:integer-address/deref <- add idx:integer-address/deref 1:literal) + (reply c:character) + } + ; real keyboard input is infrequent; avoid polling it too much + (sleep for-some-cycles:literal 1:literal) + (c:character <- read-key-from-host) + ; when we read from a real keyboard we print to screen as well + { begin + (break-unless c:character) + (silent?:boolean <- equal screen:terminal-address ((silent literal))) + (break-if silent?:boolean) +;? ($print (("aaaa\n" literal))) ;? 1 + (print-character-to-host c:character) + } + (reply c:character) +) + +(init-fn wait-for-key + (default-space:space-address <- new space:literal 30:literal) + (k:keyboard-address <- next-input) + (screen:terminal-address <- next-input) + { begin + (result:character <- read-key k:keyboard-address screen:terminal-address) + (loop-unless result:character) + } + (reply result:character) +) + +(init-fn send-keys-to-stdin + (default-space:space-address <- new space:literal 30:literal) + (k:keyboard-address <- next-input) + (stdin:channel-address <- next-input) +;? (c:character <- copy ((#\a literal))) ;? 1 +;? (curr:tagged-value <- save-type c:character) ;? 1 +;? (stdin:channel-address/deref <- write stdin:channel-address curr:tagged-value) ;? 1 +;? (c:character <- copy ((#\newline literal))) ;? 1 +;? (curr:tagged-value <- save-type c:character) ;? 1 +;? (stdin:channel-address/deref <- write stdin:channel-address curr:tagged-value) ;? 1 + { begin + (c:character <- read-key k:keyboard-address) + (loop-unless c:character) + (curr:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address curr:tagged-value) + (eof?:boolean <- equal c:character ((#\null literal))) + (break-if eof?:boolean) + (loop) + } +) + +; collect characters until newline before sending out +(init-fn buffer-lines + (default-space:space-address <- new space:literal 30:literal) + (stdin:channel-address <- next-input) + (buffered-stdin:channel-address <- next-input) + ; repeat forever + { begin + (line:buffer-address <- init-buffer 30:literal) +;? ($dump-channel 1093:literal) ;? 1 + ; read characters from stdin until newline, copy into line + { begin + (x:tagged-value stdin:channel-address/deref <- read stdin:channel-address) + (c:character <- maybe-coerce x:tagged-value character:literal) + (assert c:character) +;? ($print line:buffer-address) ;? 2 +;? ($print (("\n" literal))) ;? 2 +;? ($print c:character) ;? 2 +;? ($print (("\n" literal))) ;? 2 + ; handle backspace + { begin + (backspace?:boolean <- equal c:character ((#\backspace literal))) + (break-unless backspace?:boolean) + (len:integer-address <- get-address line:buffer-address/deref length:offset) + ; but only if we need to + { begin +;? ($print (("backspace: " literal))) ;? 1 +;? ($print len:integer-address/deref) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (zero?:boolean <- lesser-or-equal len:integer-address/deref 0:literal) + (break-if zero?:boolean) + (len:integer-address/deref <- subtract len:integer-address/deref 1:literal) + } + (loop 2:blocks) + } + (line:buffer-address <- append line:buffer-address c:character) + (line-done?:boolean <- equal c:character ((#\newline literal))) + (break-if line-done?:boolean) + (eof?:boolean <- equal c:character ((#\null literal))) + (break-if eof?:boolean 2:blocks) + (loop) + } + ; copy line into buffered-stdout + (i:integer <- copy 0:literal) + (line-contents:string-address <- get line:buffer-address/deref data:offset) + (max:integer <- get line:buffer-address/deref length:offset) +;? ($print (("len: " literal))) ;? 1 +;? ($print max:integer) ;? 1 +;? ($print (("\n" literal))) ;? 1 + { begin + (done?:boolean <- greater-or-equal i:integer max:integer) + (break-if done?:boolean) + (c:character <- index line-contents:string-address/deref i:integer) + (curr:tagged-value <- save-type c:character) +;? ($dump-channel 1093:literal) ;? 1 +;? ($start-tracing) ;? 1 +;? ($print (("bufferout: " literal))) ;? 2 +;? ($print c:character) ;? 1 +;? (x:integer <- character-to-integer c:character) ;? 1 +;? ($print x:integer) ;? 1 +;? ($print (("\n" literal))) ;? 2 + (buffered-stdin:channel-address/deref <- write buffered-stdin:channel-address curr:tagged-value) +;? ($stop-tracing) ;? 1 +;? ($dump-channel 1093:literal) ;? 1 +;? ($quit) ;? 1 + (i:integer <- add i:integer 1:literal) + (loop) + } + (loop) + } +) + +(init-fn clear-screen + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + { begin + (break-unless x:terminal-address) +;? ($print (("AAA" literal))) + (buf:string-address <- get x:terminal-address/deref data:offset) + (max:integer <- length buf:string-address/deref) + (i:integer <- copy 0:literal) + { begin + (done?:boolean <- greater-or-equal i:integer max:integer) + (break-if done?:boolean) + (x:byte-address <- index-address buf:string-address/deref i:integer) + (x:byte-address/deref <- copy ((#\space literal))) + (i:integer <- add i:integer 1:literal) + (loop) + } + (reply) + } + (clear-host-screen) +) + +(init-fn cursor + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + (newrow:integer <- next-input) + (newcol:integer <- next-input) + { begin + (break-unless x:terminal-address) + (row:integer-address <- get-address x:terminal-address/deref cursor-row:offset) + (row:integer-address/deref <- copy newrow:integer) + (col:integer-address <- get-address x:terminal-address/deref cursor-col:offset) + (col:integer-address/deref <- copy newcol:integer) + (reply) + } + (cursor-on-host row:integer col:integer) +) + +(init-fn cursor-to-next-line + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + { begin + (break-unless x:terminal-address) + (row:integer-address <- get-address x:terminal-address/deref cursor-row:offset) +;? ($print row:integer-address/deref) +;? ($print (("\n" literal))) + (row:integer-address/deref <- add row:integer-address/deref 1:literal) + (col:integer-address <- get-address x:terminal-address/deref cursor-col:offset) +;? ($print col:integer-address/deref) +;? ($print (("\n" literal))) + (col:integer-address/deref <- copy 0:literal) + (reply) + } + (cursor-on-host-to-next-line) +) + +(init-fn cursor-down + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) +;? ($print ((#\# literal))) ;? 1 + (height:integer-address <- get-address x:terminal-address/deref num-rows:offset) +;? ($print height:integer-address/deref) ;? 1 + { begin + (break-unless x:terminal-address) +;? ($print ((#\% literal))) ;? 1 + (row:integer-address <- get-address x:terminal-address/deref cursor-row:offset) +;? ($print (("cursor down: " literal))) ;? 1 +;? ($print row:integer-address/deref) ;? 1 +;? ($print (("\n" literal))) ;? 1 + { begin + (bottom?:boolean <- greater-or-equal row:integer-address/deref height:integer-address/deref) + (break-if bottom?:boolean) + (row:integer-address/deref <- add row:integer-address/deref 1:literal) +;? ($print ((#\* literal))) ;? 1 +;? ($print row:integer-address/deref) ;? 1 + } + (reply) + } + (cursor-down-on-host) +) + +(init-fn cursor-up + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + { begin + (break-unless x:terminal-address) + (row:integer-address <- get-address x:terminal-address/deref cursor-row:offset) +;? ($print (("cursor up: " literal))) ;? 1 +;? ($print row:integer-address/deref) ;? 1 +;? ($print (("\n" literal))) ;? 1 + { begin + (top?:boolean <- lesser-or-equal row:integer-address/deref 0:literal) + (break-if top?:boolean) + (row:integer-address/deref <- subtract row:integer-address/deref 1:literal) + } + (reply) + } + (cursor-up-on-host) +) + +(init-fn cursor-left + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + { begin + (break-unless x:terminal-address) + (col:integer-address <- get-address x:terminal-address/deref cursor-col:offset) + { begin + (edge?:boolean <- lesser-or-equal col:integer-address/deref 0:literal) + (break-if edge?:boolean) + (col:integer-address/deref <- subtract col:integer-address/deref 1:literal) + } + (reply) + } + (cursor-left-on-host) +) + +(init-fn cursor-right + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + (width:integer-address <- get-address x:terminal-address/deref num-cols:offset) + { begin + (break-unless x:terminal-address) + (col:integer-address <- get-address x:terminal-address/deref cursor-col:offset) + { begin + (edge?:boolean <- lesser-or-equal col:integer-address/deref width:integer-address/deref) + (break-if edge?:boolean) + (col:integer-address/deref <- add col:integer-address/deref 1:literal) + } + (reply) + } + (cursor-right-on-host) +) + +(init-fn replace-character + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + (c:character <- next-input) + (print-character x:terminal-address c:character) + (cursor-left x:terminal-address) +) + +(init-fn clear-line + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + { begin + (break-unless x:terminal-address) + (n:integer <- get x:terminal-address/deref num-cols:offset) + (col:integer-address <- get-address x:terminal-address/deref cursor-col:offset) + (orig-col:integer <- copy col:integer-address/deref) + ; space over the entire line + { begin + (done?:boolean <- greater-or-equal col:integer-address/deref n:integer) + (break-if done?:boolean) + (print-character x:terminal-address ((#\space literal))) ; implicitly updates 'col' + (loop) + } + ; now back to where the cursor was + (col:integer-address/deref <- copy orig-col:integer) + (reply) + } + (clear-line-on-host) +) + +(init-fn print-character + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + (c:character <- next-input) + (fg:integer/color <- next-input) + (bg:integer/color <- next-input) +;? ($print (("printing character to screen " literal))) +;? ($print c:character) +;? (reply) +;? ($print (("\n" literal))) + { begin + (break-unless x:terminal-address) + (row:integer-address <- get-address x:terminal-address/deref cursor-row:offset) +;? ($print row:integer-address/deref) ;? 2 +;? ($print ((", " literal))) ;? 1 + (col:integer-address <- get-address x:terminal-address/deref cursor-col:offset) +;? ($print col:integer-address/deref) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (width:integer <- get x:terminal-address/deref num-cols:offset) + (t1:integer <- multiply row:integer-address/deref width:integer) + (idx:integer <- add t1:integer col:integer-address/deref) + (buf:string-address <- get x:terminal-address/deref data:offset) + (cursor:byte-address <- index-address buf:string-address/deref idx:integer) + (cursor:byte-address/deref <- copy c:character) ; todo: newline, etc. + (col:integer-address/deref <- add col:integer-address/deref 1:literal) + ; we don't rely on any auto-wrap functionality + ; maybe die if we go out of screen bounds? + (reply) + } + (print-character-to-host c:character fg:integer/color bg:integer/color) +) + +(init-fn print-string + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + (s:string-address <- next-input) + (len:integer <- length s:string-address/deref) +;? ($print (("print/string: len: " literal))) +;? ($print len:integer) +;? ($print (("\n" literal))) + (i:integer <- copy 0:literal) + { begin + (done?:boolean <- greater-or-equal i:integer len:integer) + (break-if done?:boolean) + (c:character <- index s:string-address/deref i:integer) + (print-character x:terminal-address c:character) + (i:integer <- add i:integer 1:literal) + (loop) + } +) + +(init-fn print-integer + (default-space:space-address <- new space:literal 30:literal) + (x:terminal-address <- next-input) + (n:integer <- next-input) + ; todo: other bases besides decimal +;? ($print (("AAA " literal))) +;? ($print n:integer) + (s:string-address <- integer-to-decimal-string n:integer) +;? ($print s:string-address) + (print-string x:terminal-address s:string-address) +) + +(init-fn init-buffer + (default-space:space-address <- new space:literal 30:literal) + (result:buffer-address <- new buffer:literal) + (len:integer-address <- get-address result:buffer-address/deref length:offset) + (len:integer-address/deref <- copy 0:literal) + (s:string-address-address <- get-address result:buffer-address/deref data:offset) + (capacity:integer <- next-input) + (s:string-address-address/deref <- new string:literal capacity:integer) + (reply result:buffer-address) +) + +(init-fn grow-buffer + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + ; double buffer size + (x:string-address-address <- get-address in:buffer-address/deref data:offset) + (oldlen:integer <- length x:string-address-address/deref/deref) +;? ($print oldlen:integer) ;? 1 + (newlen:integer <- multiply oldlen:integer 2:literal) +;? ($print newlen:integer) ;? 1 + (olddata:string-address <- copy x:string-address-address/deref) + (x:string-address-address/deref <- new string:literal newlen:integer) + ; copy old contents + (i:integer <- copy 0:literal) + { begin + (done?:boolean <- greater-or-equal i:integer oldlen:integer) + (break-if done?:boolean) + (src:byte <- index olddata:string-address/deref i:integer) + (dest:byte-address <- index-address x:string-address-address/deref/deref i:integer) + (dest:byte-address/deref <- copy src:byte) + (i:integer <- add i:integer 1:literal) + (loop) + } + (reply in:buffer-address) +) + +(init-fn buffer-full? + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (len:integer <- get in:buffer-address/deref length:offset) + (s:string-address <- get in:buffer-address/deref data:offset) + (capacity:integer <- length s:string-address/deref) + (result:boolean <- greater-or-equal len:integer capacity:integer) + (reply result:boolean) +) + +(init-fn buffer-index + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (idx:integer <- next-input) + { begin + (len:integer <- get in:buffer-address/deref length:offset) + (not-too-high?:boolean <- less-than idx:integer len:integer) + (not-too-low?:boolean <- greater-or-equal idx:integer 0:literal) + (in-bounds?:boolean <- and not-too-low?:boolean not-too-high?:boolean) + (break-if in-bounds?:boolean) + (assert nil:literal (("buffer-index out of bounds" literal))) + } + (s:string-address <- get in:buffer-address/deref data:offset) + (result:character <- index s:string-address/deref idx:integer) + (reply result:character) +) + +(init-fn to-array ; from buffer + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (len:integer <- get in:buffer-address/deref length:offset) + (s:string-address <- get in:buffer-address/deref data:offset) + { begin + ; test: ctrl-d -> s is nil -> to-array returns nil -> read-expression returns t -> exit repl + (break-if s:string-address) + (reply nil:literal) + } + ; we can't just return s because it is usually the wrong length + (result:string-address <- new string:literal len:integer) + (i:integer <- copy 0:literal) + { begin + (done?:boolean <- greater-or-equal i:integer len:integer) + (break-if done?:boolean) + (src:byte <- index s:string-address/deref i:integer) +;? (foo:integer <- character-to-integer src:byte) ;? 1 +;? ($print (("a: " literal))) ;? 1 +;? ($print foo:integer) ;? 1 +;? ($print ((#\newline literal))) ;? 1 + (dest:byte-address <- index-address result:string-address/deref i:integer) + (dest:byte-address/deref <- copy src:byte) + (i:integer <- add i:integer 1:literal) + (loop) + } + (reply result:string-address) +) + +(init-fn append + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (c:character <- next-input) +;? ($print c:character) ;? 1 + { begin + ; grow buffer if necessary + (full?:boolean <- buffer-full? in:buffer-address) +;? ($print (("aa\n" literal))) ;? 1 + (break-unless full?:boolean) +;? ($print (("bb\n" literal))) ;? 1 + (in:buffer-address <- grow-buffer in:buffer-address) +;? ($print (("cc\n" literal))) ;? 1 + } + (len:integer-address <- get-address in:buffer-address/deref length:offset) + (s:string-address <- get in:buffer-address/deref data:offset) + (dest:byte-address <- index-address s:string-address/deref len:integer-address/deref) + (dest:byte-address/deref <- copy c:character) + (len:integer-address/deref <- add len:integer-address/deref 1:literal) + (reply in:buffer-address/same-as-arg:0) +) + +(init-fn last + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (n:integer <- get in:buffer-address/deref length:offset) + { begin + ; if empty return nil + (empty?:boolean <- equal n:integer 0:literal) + (break-unless empty?:boolean) + (reply nil:literal) + } + (n:integer <- subtract n:integer 1:literal) + (s:string-address <- get in:buffer-address/deref data:offset) + (result:character <- index s:string-address/deref n:integer) + (reply result:character) +) + +(init-fn integer-to-decimal-string + (default-space:space-address <- new space:literal 30:literal) + (n:integer <- next-input) + ; is it zero? + { begin + (zero?:boolean <- equal n:integer 0:literal) + (break-unless zero?:boolean) + (s:string-address <- new "0") + (reply s:string-address) + } + ; save sign + (negate-result:boolean <- copy nil:literal) + { begin + (negative?:boolean <- less-than n:integer 0:literal) + (break-unless negative?:boolean) +;? ($print (("is negative " literal))) + (negate-result:boolean <- copy t:literal) + (n:integer <- multiply n:integer -1:literal) + } + ; add digits from right to left into intermediate buffer + (tmp:buffer-address <- init-buffer 30:literal) + (zero:character <- copy ((#\0 literal))) + (digit-base:integer <- character-to-integer zero:character) + { begin + (done?:boolean <- equal n:integer 0:literal) + (break-if done?:boolean) + (n:integer digit:integer <- divide-with-remainder n:integer 10:literal) + (digit-codepoint:integer <- add digit-base:integer digit:integer) + (c:character <- integer-to-character digit-codepoint:integer) + (tmp:buffer-address <- append tmp:buffer-address c:character) + (loop) + } + ; add sign + { begin + (break-unless negate-result:boolean) + (tmp:buffer-address <- append tmp:buffer-address ((#\- literal))) + } + ; reverse buffer into string result + (len:integer <- get tmp:buffer-address/deref length:offset) + (buf:string-address <- get tmp:buffer-address/deref data:offset) + (result:string-address <- new string:literal len:integer) + (i:integer <- subtract len:integer 1:literal) + (j:integer <- copy 0:literal) + { begin + ; while (i >= 0) + (done?:boolean <- less-than i:integer 0:literal) + (break-if done?:boolean) + ; result[j] = tmp[i] + (src:byte <- index buf:string-address/deref i:integer) + (dest:byte-address <- index-address result:string-address/deref j:integer) + (dest:byte-address/deref <- copy src:byte) + ; ++i + (i:integer <- subtract i:integer 1:literal) + ; --j + (j:integer <- add j:integer 1:literal) + (loop) + } + (reply result:string-address) +) + +(init-fn send-prints-to-stdout + (default-space:space-address <- new space:literal 30:literal) + (screen:terminal-address <- next-input) + (stdout:channel-address <- next-input) +;? (i:integer <- copy 0:literal) ;? 1 + { begin + (x:tagged-value stdout:channel-address/deref <- read stdout:channel-address) + (c:character <- maybe-coerce x:tagged-value character:literal) + (done?:boolean <- equal c:character ((#\null literal))) + (break-if done?:boolean) +;? ($print (("printing " literal))) ;? 1 +;? ($print i:integer) ;? 1 +;? ($print ((" -- " literal))) ;? 1 +;? (x:integer <- character-to-integer c:character) ;? 1 +;? ($print x:integer) ;? 1 +;? ($print (("\n" literal))) ;? 1 +;? (i:integer <- add i:integer 1:literal) ;? 1 + (print-character screen:terminal-address c:character) + (loop) + } +) + +; remember to call this before you clear the screen or at any other milestone +; in an interactive program +(init-fn flush-stdout + (default-space:boolean <- copy nil:literal) ; silence warning, but die if locals used + (sleep for-some-cycles:literal 1:literal) +) + +(init-fn init-fake-terminal + (default-space:space-address <- new space:literal 30:literal/capacity) + (result:terminal-address <- new terminal:literal) + (width:integer-address <- get-address result:terminal-address/deref num-cols:offset) + (width:integer-address/deref <- next-input) + (height:integer-address <- get-address result:terminal-address/deref num-rows:offset) + (height:integer-address/deref <- next-input) + (row:integer-address <- get-address result:terminal-address/deref cursor-row:offset) + (row:integer-address/deref <- copy 0:literal) + (col:integer-address <- get-address result:terminal-address/deref cursor-col:offset) + (col:integer-address/deref <- copy 0:literal) + (bufsize:integer <- multiply width:integer-address/deref height:integer-address/deref) + (buf:string-address-address <- get-address result:terminal-address/deref data:offset) + (buf:string-address-address/deref <- new string:literal bufsize:integer) + (clear-screen result:terminal-address) + (reply result:terminal-address) +) + +(init-fn divides? + (default-space:space-address <- new space:literal 30:literal/capacity) + (x:integer <- next-input) + (y:integer <- next-input) + (_ remainder:integer <- divide-with-remainder x:integer y:integer) + (result:boolean <- equal remainder:integer 0:literal) + (reply result:boolean) +) + +; after all system software is loaded: +;? (= dump-trace* (obj whitelist '("cn0" "cn1"))) +(freeze system-function*) +) ; section 100 for system software + +;; initialization + +(reset) +(awhen (pos "--" argv) + ; batch mode: load all provided files and start at 'main' + (map add-code:readfile (cut argv (+ it 1))) +;? (set dump-trace*) + (run 'main) + (if ($.current-charterm) ($.close-charterm)) + (when ($.graphics-open?) ($.close-viewport Viewport) ($.close-graphics)) +;? (pr "\nmemory: ") +;? (write int-canon.memory*) + (prn) + (each routine completed-routines* + (awhen rep.routine!error + (prn "error - " it) +;? (prn routine) + )) +) + +; repl +(def run-interactive (stmt) + ; careful to avoid re-processing functions and adding noise to traces + (= function*!interactive (convert-labels:convert-braces:tokenize-args (list stmt))) + (add-next-space-generator function*!interactive 'interactive) + (= location*!interactive (assign-names-to-location function*!interactive 'interactive location*!interactive)) + (replace-names-with-location function*!interactive 'interactive) + (= traces* (queue)) ; skip preprocessing + (run-more 'interactive)) + +(when (no cdr.argv) + (add-code:readfile "trace.mu") + (wipe function*!main) + (add-code:readfile "factorial.mu") +;? (add-code:readfile "chessboard.mu") ; takes too long + (wipe function*!main) + (freeze function*) + (load-system-functions) + (wipe interactive-commands*) + (wipe interactive-traces*) + (= interactive-cmdidx* 0) + (= traces* (queue)) +;? (set dump-trace*) ;? 2 + ; interactive mode + (point break + (while t + (pr interactive-cmdidx*)(pr "> ") + (let expr (read) + (unless expr (break)) + (push expr interactive-commands*) + (run-interactive expr)) + (push traces* interactive-traces*) + (++ interactive-cmdidx*) + ))) + +(if ($.current-charterm) ($.close-charterm)) +(reset) +;? (print-times) |