summaryrefslogtreecommitdiffstats
path: root/arc.arc
diff options
context:
space:
mode:
Diffstat (limited to 'arc.arc')
-rw-r--r--arc.arc1700
1 files changed, 1700 insertions, 0 deletions
diff --git a/arc.arc b/arc.arc
new file mode 100644
index 0000000..ef11fc9
--- /dev/null
+++ b/arc.arc
@@ -0,0 +1,1700 @@
+; Main Arc lib. Ported to Scheme version Jul 06.
+
+; don't like names of conswhen and consif
+
+; need better way of generating strings; too many calls to string
+; maybe strings with escape char for evaluation
+; make foo~bar equiv of foo:~bar (in expand-ssyntax)
+; add sigs of ops defined in ac.scm
+; get hold of error types within arc
+; does macex have to be defined in scheme instead of using def below?
+; write disp, read, write in arc
+; could I get all of macros up into arc.arc?
+; warn when shadow a global name
+; some simple regexp/parsing plan
+
+; compromises in this implementation:
+; no objs in code
+; (mac testlit args (listtab args)) breaks when called
+; separate string type
+; (= (cdr (cdr str)) "foo") couldn't work because no way to get str tail
+; not sure this is a mistake; strings may be subtly different from
+; lists of chars
+
+
+(assign do (annotate 'mac
+ (fn args `((fn () ,@args)))))
+
+(assign safeset (annotate 'mac
+ (fn (var val)
+ `(do (if (bound ',var)
+ (do (disp "*** redefining " (stderr))
+ (disp ',var (stderr))
+ (disp #\newline (stderr))))
+ (assign ,var ,val)))))
+
+(assign def (annotate 'mac
+ (fn (name parms . body)
+ `(do (sref sig ',parms ',name)
+ (safeset ,name (fn ,parms ,@body))))))
+
+(def caar (xs) (car (car xs)))
+(def cadr (xs) (car (cdr xs)))
+(def cddr (xs) (cdr (cdr xs)))
+
+(def no (x) (is x nil))
+
+(def acons (x) (is (type x) 'cons))
+
+(def atom (x) (no (acons x)))
+
+; Can return to this def once Rtm gets ac to make all rest args
+; nil-terminated lists.
+
+; (def list args args)
+
+(def copylist (xs)
+ (if (no xs)
+ nil
+ (cons (car xs) (copylist (cdr xs)))))
+
+(def list args (copylist args))
+
+(def idfn (x) x)
+
+; Maybe later make this internal. Useful to let xs be a fn?
+
+(def map1 (f xs)
+ (if (no xs)
+ nil
+ (cons (f (car xs)) (map1 f (cdr xs)))))
+
+(def pair (xs (o f list))
+ (if (no xs)
+ nil
+ (no (cdr xs))
+ (list (list (car xs)))
+ (cons (f (car xs) (cadr xs))
+ (pair (cddr xs) f))))
+
+(assign mac (annotate 'mac
+ (fn (name parms . body)
+ `(do (sref sig ',parms ',name)
+ (safeset ,name (annotate 'mac (fn ,parms ,@body)))))))
+
+(mac and args
+ (if args
+ (if (cdr args)
+ `(if ,(car args) (and ,@(cdr args)))
+ (car args))
+ 't))
+
+(def assoc (key al)
+ (if (atom al)
+ nil
+ (and (acons (car al)) (is (caar al) key))
+ (car al)
+ (assoc key (cdr al))))
+
+(def alref (al key) (cadr (assoc key al)))
+
+(mac with (parms . body)
+ `((fn ,(map1 car (pair parms))
+ ,@body)
+ ,@(map1 cadr (pair parms))))
+
+(mac let (var val . body)
+ `(with (,var ,val) ,@body))
+
+(mac withs (parms . body)
+ (if (no parms)
+ `(do ,@body)
+ `(let ,(car parms) ,(cadr parms)
+ (withs ,(cddr parms) ,@body))))
+
+; Rtm prefers to overload + to do this
+
+(def join args
+ (if (no args)
+ nil
+ (let a (car args)
+ (if (no a)
+ (apply join (cdr args))
+ (cons (car a) (apply join (cdr a) (cdr args)))))))
+
+; Need rfn for use in macro expansions.
+
+(mac rfn (name parms . body)
+ `(let ,name nil
+ (assign ,name (fn ,parms ,@body))))
+
+(mac afn (parms . body)
+ `(let self nil
+ (assign self (fn ,parms ,@body))))
+
+; Ac expands x:y:z into (compose x y z), ~x into (complement x)
+
+; Only used when the call to compose doesn't occur in functional position.
+; Composes in functional position are transformed away by ac.
+
+(mac compose args
+ (let g (uniq)
+ `(fn ,g
+ ,((afn (fs)
+ (if (cdr fs)
+ (list (car fs) (self (cdr fs)))
+ `(apply ,(if (car fs) (car fs) 'idfn) ,g)))
+ args))))
+
+; Ditto: complement in functional position optimized by ac.
+
+(mac complement (f)
+ (let g (uniq)
+ `(fn ,g (no (apply ,f ,g)))))
+
+(def rev (xs)
+ ((afn (xs acc)
+ (if (no xs)
+ acc
+ (self (cdr xs) (cons (car xs) acc))))
+ xs nil))
+
+(def isnt (x y) (no (is x y)))
+
+(mac w/uniq (names . body)
+ (if (acons names)
+ `(with ,(apply + nil (map1 (fn (n) (list n '(uniq)))
+ names))
+ ,@body)
+ `(let ,names (uniq) ,@body)))
+
+(mac or args
+ (and args
+ (w/uniq g
+ `(let ,g ,(car args)
+ (if ,g ,g (or ,@(cdr args)))))))
+
+(def alist (x) (or (no x) (is (type x) 'cons)))
+
+(mac in (x . choices)
+ (w/uniq g
+ `(let ,g ,x
+ (or ,@(map1 (fn (c) `(is ,g ,c)) choices)))))
+
+; Could take n args, but have never once needed that.
+
+(def iso (x y)
+ (or (is x y)
+ (and (acons x)
+ (acons y)
+ (iso (car x) (car y))
+ (iso (cdr x) (cdr y)))))
+
+(mac when (test . body)
+ `(if ,test (do ,@body)))
+
+(mac unless (test . body)
+ `(if (no ,test) (do ,@body)))
+
+(mac while (test . body)
+ (w/uniq (gf gp)
+ `((rfn ,gf (,gp)
+ (when ,gp ,@body (,gf ,test)))
+ ,test)))
+
+(def empty (seq)
+ (or (no seq)
+ (and (or (is (type seq) 'string) (is (type seq) 'table))
+ (is (len seq) 0))))
+
+(def reclist (f xs)
+ (and xs (or (f xs) (reclist f (cdr xs)))))
+
+(def recstring (test s (o start 0))
+ ((afn (i)
+ (and (< i (len s))
+ (or (test i)
+ (self (+ i 1)))))
+ start))
+
+(def testify (x)
+ (if (isa x 'fn) x [is _ x]))
+
+; Like keep, seems like some shouldn't testify. But find should,
+; and all probably should.
+
+(def some (test seq)
+ (let f (testify test)
+ (if (alist seq)
+ (reclist f:car seq)
+ (recstring f:seq seq))))
+
+(def all (test seq)
+ (~some (complement (testify test)) seq))
+
+(def mem (test seq)
+ (let f (testify test)
+ (reclist [if (f:car _) _] seq)))
+
+(def find (test seq)
+ (let f (testify test)
+ (if (alist seq)
+ (reclist [if (f:car _) (car _)] seq)
+ (recstring [if (f:seq _) (seq _)] seq))))
+
+(def isa (x y) (is (type x) y))
+
+; Possible to write map without map1, but makes News 3x slower.
+
+;(def map (f . seqs)
+; (if (some1 no seqs)
+; nil
+; (no (cdr seqs))
+; (let s1 (car seqs)
+; (cons (f (car s1))
+; (map f (cdr s1))))
+; (cons (apply f (map car seqs))
+; (apply map f (map cdr seqs)))))
+
+
+(def map (f . seqs)
+ (if (some [isa _ 'string] seqs)
+ (withs (n (apply min (map len seqs))
+ new (newstring n))
+ ((afn (i)
+ (if (is i n)
+ new
+ (do (sref new (apply f (map [_ i] seqs)) i)
+ (self (+ i 1)))))
+ 0))
+ (no (cdr seqs))
+ (map1 f (car seqs))
+ ((afn (seqs)
+ (if (some no seqs)
+ nil
+ (cons (apply f (map1 car seqs))
+ (self (map1 cdr seqs)))))
+ seqs)))
+
+(def mappend (f . args)
+ (apply + nil (apply map f args)))
+
+(def firstn (n xs)
+ (if (no n) xs
+ (and (> n 0) xs) (cons (car xs) (firstn (- n 1) (cdr xs)))
+ nil))
+
+(def nthcdr (n xs)
+ (if (no n) xs
+ (> n 0) (nthcdr (- n 1) (cdr xs))
+ xs))
+
+; Generalization of pair: (tuples x) = (pair x)
+
+(def tuples (xs (o n 2))
+ (if (no xs)
+ nil
+ (cons (firstn n xs)
+ (tuples (nthcdr n xs) n))))
+
+; If ok to do with =, why not with def? But see if use it.
+
+(mac defs args
+ `(do ,@(map [cons 'def _] (tuples args 3))))
+
+(def caris (x val)
+ (and (acons x) (is (car x) val)))
+
+(def warn (msg . args)
+ (disp (+ "Warning: " msg ". "))
+ (map [do (write _) (disp " ")] args)
+ (disp #\newline))
+
+(mac atomic body
+ `(atomic-invoke (fn () ,@body)))
+
+(mac atlet args
+ `(atomic (let ,@args)))
+
+(mac atwith args
+ `(atomic (with ,@args)))
+
+(mac atwiths args
+ `(atomic (withs ,@args)))
+
+
+; setforms returns (vars get set) for a place based on car of an expr
+; vars is a list of gensyms alternating with expressions whose vals they
+; should be bound to, suitable for use as first arg to withs
+; get is an expression returning the current value in the place
+; set is an expression representing a function of one argument
+; that stores a new value in the place
+
+; A bit gross that it works based on the *name* in the car, but maybe
+; wrong to worry. Macros live in expression land.
+
+; seems meaningful to e.g. (push 1 (pop x)) if (car x) is a cons.
+; can't in cl though. could I define a setter for push or pop?
+
+(assign setter (table))
+
+(mac defset (name parms . body)
+ (w/uniq gexpr
+ `(sref setter
+ (fn (,gexpr)
+ (let ,parms (cdr ,gexpr)
+ ,@body))
+ ',name)))
+
+(defset car (x)
+ (w/uniq g
+ (list (list g x)
+ `(car ,g)
+ `(fn (val) (scar ,g val)))))
+
+(defset cdr (x)
+ (w/uniq g
+ (list (list g x)
+ `(cdr ,g)
+ `(fn (val) (scdr ,g val)))))
+
+(defset caar (x)
+ (w/uniq g
+ (list (list g x)
+ `(caar ,g)
+ `(fn (val) (scar (car ,g) val)))))
+
+(defset cadr (x)
+ (w/uniq g
+ (list (list g x)
+ `(cadr ,g)
+ `(fn (val) (scar (cdr ,g) val)))))
+
+(defset cddr (x)
+ (w/uniq g
+ (list (list g x)
+ `(cddr ,g)
+ `(fn (val) (scdr (cdr ,g) val)))))
+
+; Note: if expr0 macroexpands into any expression whose car doesn't
+; have a setter, setforms assumes it's a data structure in functional
+; position. Such bugs will be seen only when the code is executed, when
+; sref complains it can't set a reference to a function.
+
+(def setforms (expr0)
+ (let expr (macex expr0)
+ (if (isa expr 'sym)
+ (if (ssyntax expr)
+ (setforms (ssexpand expr))
+ (w/uniq (g h)
+ (list (list g expr)
+ g
+ `(fn (,h) (assign ,expr ,h)))))
+ ; make it also work for uncompressed calls to compose
+ (and (acons expr) (metafn (car expr)))
+ (setforms (expand-metafn-call (ssexpand (car expr)) (cdr expr)))
+ (and (acons expr) (acons (car expr)) (is (caar expr) 'get))
+ (setforms (list (cadr expr) (cadr (car expr))))
+ (let f (setter (car expr))
+ (if f
+ (f expr)
+ ; assumed to be data structure in fn position
+ (do (when (caris (car expr) 'fn)
+ (warn "Inverting what looks like a function call"
+ expr0 expr))
+ (w/uniq (g h)
+ (let argsyms (map [uniq] (cdr expr))
+ (list (+ (list g (car expr))
+ (mappend list argsyms (cdr expr)))
+ `(,g ,@argsyms)
+ `(fn (,h) (sref ,g ,h ,(car argsyms))))))))))))
+
+(def metafn (x)
+ (or (ssyntax x)
+ (and (acons x) (in (car x) 'compose 'complement))))
+
+(def expand-metafn-call (f args)
+ (if (is (car f) 'compose)
+ ((afn (fs)
+ (if (caris (car fs) 'compose) ; nested compose
+ (self (join (cdr (car fs)) (cdr fs)))
+ (cdr fs)
+ (list (car fs) (self (cdr fs)))
+ (cons (car fs) args)))
+ (cdr f))
+ (is (car f) 'no)
+ (err "Can't invert " (cons f args))
+ (cons f args)))
+
+(def expand= (place val)
+ (if (and (isa place 'sym) (~ssyntax place))
+ `(assign ,place ,val)
+ (let (vars prev setter) (setforms place)
+ (w/uniq g
+ `(atwith ,(+ vars (list g val))
+ (,setter ,g))))))
+
+(def expand=list (terms)
+ `(do ,@(map (fn ((p v)) (expand= p v)) ; [apply expand= _]
+ (pair terms))))
+
+(mac = args
+ (expand=list args))
+
+(mac loop (start test update . body)
+ (w/uniq (gfn gparm)
+ `(do ,start
+ ((rfn ,gfn (,gparm)
+ (if ,gparm
+ (do ,@body ,update (,gfn ,test))))
+ ,test))))
+
+(mac for (v init max . body)
+ (w/uniq (gi gm)
+ `(with (,v nil ,gi ,init ,gm (+ ,max 1))
+ (loop (assign ,v ,gi) (< ,v ,gm) (assign ,v (+ ,v 1))
+ ,@body))))
+
+(mac down (v init min . body)
+ (w/uniq (gi gm)
+ `(with (,v nil ,gi ,init ,gm (- ,min 1))
+ (loop (assign ,v ,gi) (> ,v ,gm) (assign ,v (- ,v 1))
+ ,@body))))
+
+(mac repeat (n . body)
+ `(for ,(uniq) 1 ,n ,@body))
+
+; could bind index instead of gensym
+
+(mac each (var expr . body)
+ (w/uniq (gseq gf gv)
+ `(let ,gseq ,expr
+ (if (alist ,gseq)
+ ((rfn ,gf (,gv)
+ (when (acons ,gv)
+ (let ,var (car ,gv) ,@body)
+ (,gf (cdr ,gv))))
+ ,gseq)
+ (isa ,gseq 'table)
+ (maptable (fn ,var ,@body)
+ ,gseq)
+ (for ,gv 0 (- (len ,gseq) 1)
+ (let ,var (,gseq ,gv) ,@body))))))
+
+; (nthcdr x y) = (cut y x).
+
+(def cut (seq start (o end))
+ (let end (if (no end) (len seq)
+ (< end 0) (+ (len seq) end)
+ end)
+ (if (isa seq 'string)
+ (let s2 (newstring (- end start))
+ (for i 0 (- end start 1)
+ (= (s2 i) (seq (+ start i))))
+ s2)
+ (firstn (- end start) (nthcdr start seq)))))
+
+(mac whilet (var test . body)
+ (w/uniq (gf gp)
+ `((rfn ,gf (,gp)
+ (let ,var ,gp
+ (when ,var ,@body (,gf ,test))))
+ ,test)))
+
+(def last (xs)
+ (if (cdr xs)
+ (last (cdr xs))
+ (car xs)))
+
+(def rem (test seq)
+ (let f (testify test)
+ (if (alist seq)
+ ((afn (s)
+ (if (no s) nil
+ (f (car s)) (self (cdr s))
+ (cons (car s) (self (cdr s)))))
+ seq)
+ (coerce (rem test (coerce seq 'cons)) 'string))))
+
+; Seems like keep doesn't need to testify-- would be better to
+; be able to use tables as fns. But rem does need to, because
+; often want to rem a table from a list. So maybe the right answer
+; is to make keep the more primitive, not rem.
+
+(def keep (test seq)
+ (rem (complement (testify test)) seq))
+
+;(def trues (f seq)
+; (rem nil (map f seq)))
+
+(def trues (f xs)
+ (and xs
+ (let fx (f (car xs))
+ (if fx
+ (cons fx (trues f (cdr xs)))
+ (trues f (cdr xs))))))
+
+(mac do1 args
+ (w/uniq g
+ `(let ,g ,(car args)
+ ,@(cdr args)
+ ,g)))
+
+; Would like to write a faster case based on table generated by a macro,
+; but can't insert objects into expansions in Mzscheme.
+
+(mac caselet (var expr . args)
+ (let ex (afn (args)
+ (if (no (cdr args))
+ (car args)
+ `(if (is ,var ',(car args))
+ ,(cadr args)
+ ,(self (cddr args)))))
+ `(let ,var ,expr ,(ex args))))
+
+(mac case (expr . args)
+ `(caselet ,(uniq) ,expr ,@args))
+
+(mac push (x place)
+ (w/uniq gx
+ (let (binds val setter) (setforms place)
+ `(let ,gx ,x
+ (atwiths ,binds
+ (,setter (cons ,gx ,val)))))))
+
+(mac swap (place1 place2)
+ (w/uniq (g1 g2)
+ (with ((binds1 val1 setter1) (setforms place1)
+ (binds2 val2 setter2) (setforms place2))
+ `(atwiths ,(+ binds1 (list g1 val1) binds2 (list g2 val2))
+ (,setter1 ,g2)
+ (,setter2 ,g1)))))
+
+(mac rotate places
+ (with (vars (map [uniq] places)
+ forms (map setforms places))
+ `(atwiths ,(mappend (fn (g (binds val setter))
+ (+ binds (list g val)))
+ vars
+ forms)
+ ,@(map (fn (g (binds val setter))
+ (list setter g))
+ (+ (cdr vars) (list (car vars)))
+ forms))))
+
+(mac pop (place)
+ (w/uniq g
+ (let (binds val setter) (setforms place)
+ `(atwiths ,(+ binds (list g val))
+ (do1 (car ,g)
+ (,setter (cdr ,g)))))))
+
+(def adjoin (x xs (o test iso))
+ (if (some [test x _] xs)
+ xs
+ (cons x xs)))
+
+(mac pushnew (x place . args)
+ (w/uniq gx
+ (let (binds val setter) (setforms place)
+ `(atwiths ,(+ (list gx x) binds)
+ (,setter (adjoin ,gx ,val ,@args))))))
+
+(mac pull (test place)
+ (w/uniq g
+ (let (binds val setter) (setforms place)
+ `(atwiths ,(+ (list g test) binds)
+ (,setter (rem ,g ,val))))))
+
+(mac togglemem (x place . args)
+ (w/uniq gx
+ (let (binds val setter) (setforms place)
+ `(atwiths ,(+ (list gx x) binds)
+ (,setter (if (mem ,gx ,val)
+ (rem ,gx ,val)
+ (adjoin ,gx ,val ,@args)))))))
+
+(mac ++ (place (o i 1))
+ (if (isa place 'sym)
+ `(= ,place (+ ,place ,i))
+ (w/uniq gi
+ (let (binds val setter) (setforms place)
+ `(atwiths ,(+ binds (list gi i))
+ (,setter (+ ,val ,gi)))))))
+
+(mac -- (place (o i 1))
+ (if (isa place 'sym)
+ `(= ,place (- ,place ,i))
+ (w/uniq gi
+ (let (binds val setter) (setforms place)
+ `(atwiths ,(+ binds (list gi i))
+ (,setter (- ,val ,gi)))))))
+
+; E.g. (++ x) equiv to (zap + x 1)
+
+(mac zap (op place . args)
+ (with (gop (uniq)
+ gargs (map [uniq] args)
+ mix (afn seqs
+ (if (some no seqs)
+ nil
+ (+ (map car seqs)
+ (apply self (map cdr seqs))))))
+ (let (binds val setter) (setforms place)
+ `(atwiths ,(+ binds (list gop op) (mix gargs args))
+ (,setter (,gop ,val ,@gargs))))))
+
+; Can't simply mod pr to print strings represented as lists of chars,
+; because empty string will get printed as nil. Would need to rep strings
+; as lists of chars annotated with 'string, and modify car and cdr to get
+; the rep of these. That would also require hacking the reader.
+
+(def pr args
+ (map1 disp args)
+ (car args))
+
+(def prt args
+ (map1 [if _ (disp _)] args)
+ (car args))
+
+(def prn args
+ (do1 (apply pr args)
+ (writec #\newline)))
+
+(mac wipe args
+ `(do ,@(map (fn (a) `(= ,a nil)) args)))
+
+(mac set args
+ `(do ,@(map (fn (a) `(= ,a t)) args)))
+
+; Destructuring means ambiguity: are pat vars bound in else? (no)
+
+(mac iflet (var expr then . rest)
+ (w/uniq gv
+ `(let ,gv ,expr
+ (if ,gv (let ,var ,gv ,then) ,@rest))))
+
+(mac whenlet (var expr . body)
+ `(iflet ,var ,expr (do ,@body)))
+
+(mac aif (expr . body)
+ `(let it ,expr
+ (if it
+ ,@(if (cddr body)
+ `(,(car body) (aif ,@(cdr body)))
+ body))))
+
+(mac awhen (expr . body)
+ `(let it ,expr (if it (do ,@body))))
+
+(mac aand args
+ (if (no args)
+ 't
+ (no (cdr args))
+ (car args)
+ `(let it ,(car args) (and it (aand ,@(cdr args))))))
+
+(mac accum (accfn . body)
+ (w/uniq gacc
+ `(withs (,gacc nil ,accfn [push _ ,gacc])
+ ,@body
+ (rev ,gacc))))
+
+; Repeatedly evaluates its body till it returns nil, then returns vals.
+
+(mac drain (expr (o eof nil))
+ (w/uniq (gacc gdone gres)
+ `(with (,gacc nil ,gdone nil)
+ (while (no ,gdone)
+ (let ,gres ,expr
+ (if (is ,gres ,eof)
+ (= ,gdone t)
+ (push ,gres ,gacc))))
+ (rev ,gacc))))
+
+; For the common C idiom while x = snarfdata != stopval.
+; Rename this if use it often.
+
+(mac whiler (var expr endval . body)
+ (w/uniq gf
+ `(withs (,var nil ,gf (testify ,endval))
+ (while (no (,gf (= ,var ,expr)))
+ ,@body))))
+
+;(def macex (e)
+; (if (atom e)
+; e
+; (let op (and (atom (car e)) (eval (car e)))
+; (if (isa op 'mac)
+; (apply (rep op) (cdr e))
+; e))))
+
+(def consif (x y) (if x (cons x y) y))
+
+(def string args
+ (apply + "" (map [coerce _ 'string] args)))
+
+(def flat x
+ ((afn (x acc)
+ (if (no x) acc
+ (atom x) (cons x acc)
+ (self (car x) (self (cdr x) acc))))
+ x nil))
+
+(mac check (x test (o alt))
+ (w/uniq gx
+ `(let ,gx ,x
+ (if (,test ,gx) ,gx ,alt))))
+
+(def pos (test seq (o start 0))
+ (let f (testify test)
+ (if (alist seq)
+ ((afn (seq n)
+ (if (no seq)
+ nil
+ (f (car seq))
+ n
+ (self (cdr seq) (+ n 1))))
+ (nthcdr start seq)
+ start)
+ (recstring [if (f (seq _)) _] seq start))))
+
+(def even (n) (is (mod n 2) 0))
+
+(def odd (n) (no (even n)))
+
+(mac after (x . ys)
+ `(protect (fn () ,x) (fn () ,@ys)))
+
+(let expander
+ (fn (f var name body)
+ `(let ,var (,f ,name)
+ (after (do ,@body) (close ,var))))
+
+ (mac w/infile (var name . body)
+ (expander 'infile var name body))
+
+ (mac w/outfile (var name . body)
+ (expander 'outfile var name body))
+
+ (mac w/instring (var str . body)
+ (expander 'instring var str body))
+
+ (mac w/socket (var port . body)
+ (expander 'open-socket var port body))
+ )
+
+(mac w/outstring (var . body)
+ `(let ,var (outstring) ,@body))
+
+; what happens to a file opened for append if arc is killed in
+; the middle of a write?
+
+(mac w/appendfile (var name . body)
+ `(let ,var (outfile ,name 'append)
+ (after (do ,@body) (close ,var))))
+
+; rename this simply "to"? - prob not; rarely use
+
+(mac w/stdout (str . body)
+ `(call-w/stdout ,str (fn () ,@body)))
+
+(mac w/stdin (str . body)
+ `(call-w/stdin ,str (fn () ,@body)))
+
+(mac tostring body
+ (w/uniq gv
+ `(w/outstring ,gv
+ (w/stdout ,gv ,@body)
+ (inside ,gv))))
+
+(mac fromstring (str . body)
+ (w/uniq gv
+ `(w/instring ,gv ,str
+ (w/stdin ,gv ,@body))))
+
+(def readstring1 (s (o eof nil)) (w/instring i s (read i eof)))
+
+(def read ((o x (stdin)) (o eof nil))
+ (if (isa x 'string) (readstring1 x eof) (sread x eof)))
+
+; inconsistency between names of readfile[1] and writefile
+
+(def readfile (name) (w/infile s name (drain (read s))))
+
+(def readfile1 (name) (w/infile s name (read s)))
+
+(def readall (src (o eof nil))
+ ((afn (i)
+ (let x (read i eof)
+ (if (is x eof)
+ nil
+ (cons x (self i)))))
+ (if (isa src 'string) (instring src) src)))
+
+(def allchars (str)
+ (tostring (whiler c (readc str nil) no
+ (writec c))))
+
+(def filechars (name)
+ (w/infile s name (allchars s)))
+
+(def writefile (val file)
+ (let tmpfile (+ file ".tmp")
+ (w/outfile o tmpfile (write val o))
+ (mvfile tmpfile file))
+ val)
+
+(def sym (x) (coerce x 'sym))
+
+(def int (x (o b 10)) (coerce x 'int b))
+
+(mac rand-choice exprs
+ `(case (rand ,(len exprs))
+ ,@(let key -1
+ (mappend [list (++ key) _]
+ exprs))))
+
+(mac n-of (n expr)
+ (w/uniq ga
+ `(let ,ga nil
+ (repeat ,n (push ,expr ,ga))
+ (rev ,ga))))
+
+; rejects bytes >= 248 lest digits be overrepresented
+
+(def rand-string (n)
+ (let c "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ (with (nc 62 s (newstring n) i 0)
+ (w/infile str "/dev/urandom"
+ (while (< i n)
+ (let x (readb str)
+ (unless (> x 247)
+ (= (s i) (c (mod x nc)))
+ (++ i)))))
+ s)))
+
+(mac forlen (var s . body)
+ `(for ,var 0 (- (len ,s) 1) ,@body))
+
+(mac on (var s . body)
+ (if (is var 'index)
+ (err "Can't use index as first arg to on.")
+ (w/uniq gs
+ `(let ,gs ,s
+ (forlen index ,gs
+ (let ,var (,gs index)
+ ,@body))))))
+
+(def best (f seq)
+ (if (no seq)
+ nil
+ (let wins (car seq)
+ (each elt (cdr seq)
+ (if (f elt wins) (= wins elt)))
+ wins)))
+
+(def max args (best > args))
+(def min args (best < args))
+
+; (mac max2 (x y)
+; (w/uniq (a b)
+; `(with (,a ,x ,b ,y) (if (> ,a ,b) ,a ,b))))
+
+(def most (f seq)
+ (unless (no seq)
+ (withs (wins (car seq) topscore (f wins))
+ (each elt (cdr seq)
+ (let score (f elt)
+ (if (> score topscore) (= wins elt topscore score))))
+ wins)))
+
+; Insert so that list remains sorted. Don't really want to expose
+; these but seem to have to because can't include a fn obj in a
+; macroexpansion.
+
+(def insert-sorted (test elt seq)
+ (if (no seq)
+ (list elt)
+ (test elt (car seq))
+ (cons elt seq)
+ (cons (car seq) (insert-sorted test elt (cdr seq)))))
+
+(mac insort (test elt seq)
+ `(zap [insert-sorted ,test ,elt _] ,seq))
+
+(def reinsert-sorted (test elt seq)
+ (if (no seq)
+ (list elt)
+ (is elt (car seq))
+ (reinsert-sorted test elt (cdr seq))
+ (test elt (car seq))
+ (cons elt (rem elt seq))
+ (cons (car seq) (reinsert-sorted test elt (cdr seq)))))
+
+(mac insortnew (test elt seq)
+ `(zap [reinsert-sorted ,test ,elt _] ,seq))
+
+; Could make this look at the sig of f and return a fn that took the
+; right no of args and didn't have to call apply (or list if 1 arg).
+
+(def memo (f)
+ (with (cache (table) nilcache (table))
+ (fn args
+ (or (cache args)
+ (and (no (nilcache args))
+ (aif (apply f args)
+ (= (cache args) it)
+ (do (set (nilcache args))
+ nil)))))))
+
+
+(mac defmemo (name parms . body)
+ `(safeset ,name (memo (fn ,parms ,@body))))
+
+(def <= args
+ (or (no args)
+ (no (cdr args))
+ (and (no (> (car args) (cadr args)))
+ (apply <= (cdr args)))))
+
+(def >= args
+ (or (no args)
+ (no (cdr args))
+ (and (no (< (car args) (cadr args)))
+ (apply >= (cdr args)))))
+
+(def whitec (c)
+ (in c #\space #\newline #\tab #\return))
+
+(def nonwhite (c) (no (whitec c)))
+
+(def letter (c) (or (<= #\a c #\z) (<= #\A c #\Z)))
+
+(def digit (c) (<= #\0 c #\9))
+
+(def alphadig (c) (or (letter c) (digit c)))
+
+(def punc (c)
+ (in c #\. #\, #\; #\: #\! #\?))
+
+(def readline ((o str (stdin)))
+ (awhen (readc str)
+ (tostring
+ (writec it)
+ (whiler c (readc str) [in _ nil #\newline]
+ (writec c)))))
+
+; Don't currently use this but suspect some code could.
+
+(mac summing (sumfn . body)
+ (w/uniq (gc gt)
+ `(let ,gc 0
+ (let ,sumfn (fn (,gt) (if ,gt (++ ,gc)))
+ ,@body)
+ ,gc)))
+
+(def sum (f xs)
+ (let n 0
+ (each x xs (++ n (f x)))
+ n))
+
+(def treewise (f base tree)
+ (if (atom tree)
+ (base tree)
+ (f (treewise f base (car tree))
+ (treewise f base (cdr tree)))))
+
+(def carif (x) (if (atom x) x (car x)))
+
+; Could prob be generalized beyond printing.
+
+(def prall (elts (o init "") (o sep ", "))
+ (when elts
+ (pr init (car elts))
+ (map [pr sep _] (cdr elts))
+ elts))
+
+(def prs args
+ (prall args "" #\space))
+
+(def tree-subst (old new tree)
+ (if (is tree old)
+ new
+ (atom tree)
+ tree
+ (cons (tree-subst old new (car tree))
+ (tree-subst old new (cdr tree)))))
+
+(def ontree (f tree)
+ (f tree)
+ (unless (atom tree)
+ (ontree f (car tree))
+ (ontree f (cdr tree))))
+
+(def dotted (x)
+ (if (atom x)
+ nil
+ (and (cdr x) (or (atom (cdr x))
+ (dotted (cdr x))))))
+
+(def fill-table (table data)
+ (each (k v) (pair data) (= (table k) v))
+ table)
+
+(def keys (h)
+ (accum a (each (k v) h (a k))))
+
+(def vals (h)
+ (accum a (each (k v) h (a v))))
+
+; These two should really be done by coerce. Wrap coerce?
+
+(def tablist (h)
+ (accum a (maptable (fn args (a args)) h)))
+
+(def listtab (al)
+ (let h (table)
+ (map (fn ((k v)) (= (h k) v))
+ al)
+ h))
+
+(mac obj args
+ `(listtab (list ,@(map (fn ((k v))
+ `(list ',k ,v))
+ (pair args)))))
+
+(def load-table (file (o eof))
+ (w/infile i file (read-table i eof)))
+
+(def read-table ((o i (stdin)) (o eof))
+ (let e (read i eof)
+ (if (alist e) (listtab e) e)))
+
+(def load-tables (file)
+ (w/infile i file
+ (w/uniq eof
+ (drain (read-table i eof) eof))))
+
+(def save-table (h file)
+ (writefile (tablist h) file))
+
+(def write-table (h (o o (stdout)))
+ (write (tablist h) o))
+
+(def copy (x . args)
+ (let x2 (case (type x)
+ sym x
+ cons (copylist x) ; (apply (fn args args) x)
+ string (let new (newstring (len x))
+ (forlen i x
+ (= (new i) (x i)))
+ new)
+ table (let new (table)
+ (each (k v) x
+ (= (new k) v))
+ new)
+ (err "Can't copy " x))
+ (map (fn ((k v)) (= (x2 k) v))
+ (pair args))
+ x2))
+
+(def abs (n)
+ (if (< n 0) (- n) n))
+
+; The problem with returning a list instead of multiple values is that
+; you can't act as if the fn didn't return multiple vals in cases where
+; you only want the first. Not a big problem.
+
+(def round (n)
+ (withs (base (trunc n) rem (abs (- n base)))
+ (if (> rem 1/2) ((if (> n 0) + -) base 1)
+ (< rem 1/2) base
+ (odd base) ((if (> n 0) + -) base 1)
+ base)))
+
+(def roundup (n)
+ (withs (base (trunc n) rem (abs (- n base)))
+ (if (>= rem 1/2)
+ ((if (> n 0) + -) base 1)
+ base)))
+
+(def nearest (n quantum)
+ (* (roundup (/ n quantum)) quantum))
+
+(def avg (ns) (/ (apply + ns) (len ns)))
+
+(def med (ns (o test >))
+ ((sort test ns) (round (/ (len ns) 2))))
+
+; Use mergesort on assumption that mostly sorting mostly sorted lists
+; benchmark: (let td (n-of 10000 (rand 100)) (time (sort < td)) 1)
+
+(def sort (test seq)
+ (if (alist seq)
+ (mergesort test (copy seq))
+ (coerce (mergesort test (coerce seq 'cons)) (type seq))))
+
+; Destructive stable merge-sort, adapted from slib and improved
+; by Eli Barzilay for MzLib; re-written in Arc.
+
+(def mergesort (less? lst)
+ (with (n (len lst))
+ (if (<= n 1) lst
+ ; ; check if the list is already sorted
+ ; ; (which can be a common case, eg, directory lists).
+ ; (let loop ([last (car lst)] [next (cdr lst)])
+ ; (or (null? next)
+ ; (and (not (less? (car next) last))
+ ; (loop (car next) (cdr next)))))
+ ; lst
+ ((afn (n)
+ (if (> n 2)
+ ; needs to evaluate L->R
+ (withs (j (/ (if (even n) n (- n 1)) 2) ; faster than round
+ a (self j)
+ b (self (- n j)))
+ (merge less? a b))
+ ; the following case just inlines the length 2 case,
+ ; it can be removed (and use the above case for n>1)
+ ; and the code still works, except a little slower
+ (is n 2)
+ (with (x (car lst) y (cadr lst) p lst)
+ (= lst (cddr lst))
+ (when (less? y x) (scar p y) (scar (cdr p) x))
+ (scdr (cdr p) nil)
+ p)
+ (is n 1)
+ (with (p lst)
+ (= lst (cdr lst))
+ (scdr p nil)
+ p)
+ nil))
+ n))))
+
+; Also by Eli.
+
+(def merge (less? x y)
+ (if (no x) y
+ (no y) x
+ (let lup nil
+ (assign lup
+ (fn (r x y r-x?) ; r-x? for optimization -- is r connected to x?
+ (if (less? (car y) (car x))
+ (do (if r-x? (scdr r y))
+ (if (cdr y) (lup y x (cdr y) nil) (scdr y x)))
+ ; (car x) <= (car y)
+ (do (if (no r-x?) (scdr r x))
+ (if (cdr x) (lup x (cdr x) y t) (scdr x y))))))
+ (if (less? (car y) (car x))
+ (do (if (cdr y) (lup y x (cdr y) nil) (scdr y x))
+ y)
+ ; (car x) <= (car y)
+ (do (if (cdr x) (lup x (cdr x) y t) (scdr x y))
+ x)))))
+
+(def bestn (n f seq)
+ (firstn n (sort f seq)))
+
+(def split (seq pos)
+ (list (cut seq 0 pos) (cut seq pos)))
+
+(mac time (expr)
+ (w/uniq (t1 t2)
+ `(let ,t1 (msec)
+ (do1 ,expr
+ (let ,t2 (msec)
+ (prn "time: " (- ,t2 ,t1) " msec."))))))
+
+(mac jtime (expr)
+ `(do1 'ok (time ,expr)))
+
+(mac time10 (expr)
+ `(time (repeat 10 ,expr)))
+
+(def union (f xs ys)
+ (+ xs (rem (fn (y) (some [f _ y] xs))
+ ys)))
+
+(= templates* (table))
+
+(mac deftem (tem . fields)
+ (withs (name (carif tem) includes (if (acons tem) (cdr tem)))
+ `(= (templates* ',name)
+ (+ (mappend templates* ',(rev includes))
+ (list ,@(map (fn ((k v)) `(list ',k (fn () ,v)))
+ (pair fields)))))))
+
+(mac addtem (name . fields)
+ `(= (templates* ',name)
+ (union (fn (x y) (is (car x) (car y)))
+ (list ,@(map (fn ((k v)) `(list ',k (fn () ,v)))
+ (pair fields)))
+ (templates* ',name))))
+
+(def inst (tem . args)
+ (let x (table)
+ (each (k v) (if (acons tem) tem (templates* tem))
+ (unless (no v) (= (x k) (v))))
+ (each (k v) (pair args)
+ (= (x k) v))
+ x))
+
+; To write something to be read by temread, (write (tablist x))
+
+(def temread (tem (o str (stdin)))
+ (templatize tem (read str)))
+
+; Converts alist to inst; ugly; maybe should make this part of coerce.
+; Note: discards fields not defined by the template.
+
+(def templatize (tem raw)
+ (with (x (inst tem) fields (if (acons tem) tem (templates* tem)))
+ (each (k v) raw
+ (when (assoc k fields)
+ (= (x k) v)))
+ x))
+
+(def temload (tem file)
+ (w/infile i file (temread tem i)))
+
+(def temloadall (tem file)
+ (map (fn (pairs) (templatize tem pairs))
+ (w/infile in file (readall in))))
+
+
+(def number (n) (in (type n) 'int 'num))
+
+(def since (t1) (- (seconds) t1))
+
+(def minutes-since (t1) (/ (since t1) 60))
+(def hours-since (t1) (/ (since t1) 3600))
+(def days-since (t1) (/ (since t1) 86400))
+
+; could use a version for fns of 1 arg at least
+
+(def cache (timef valf)
+ (with (cached nil gentime nil)
+ (fn ()
+ (unless (and cached (< (since gentime) (timef)))
+ (= cached (valf)
+ gentime (seconds)))
+ cached)))
+
+(mac defcache (name lasts . body)
+ `(safeset ,name (cache (fn () ,lasts)
+ (fn () ,@body))))
+
+(mac errsafe (expr)
+ `(on-err (fn (c) nil)
+ (fn () ,expr)))
+
+(def saferead (arg) (errsafe:read arg))
+
+(def safe-load-table (filename)
+ (or (errsafe:load-table filename)
+ (table)))
+
+(def ensure-dir (path)
+ (unless (dir-exists path)
+ (system (string "mkdir -p " path))))
+
+(def date ((o s (seconds)))
+ (rev (nthcdr 3 (timedate s))))
+
+(def datestring ((o s (seconds)))
+ (let (y m d) (date s)
+ (string y "-" (if (< m 10) "0") m "-" (if (< d 10) "0") d)))
+
+(def count (test x)
+ (with (n 0 testf (testify test))
+ (each elt x
+ (if (testf elt) (++ n)))
+ n))
+
+(def ellipsize (str (o limit 80))
+ (if (<= (len str) limit)
+ str
+ (+ (cut str 0 limit) "...")))
+
+(def rand-elt (seq)
+ (seq (rand (len seq))))
+
+(mac until (test . body)
+ `(while (no ,test) ,@body))
+
+(def before (x y seq (o i 0))
+ (with (xp (pos x seq i) yp (pos y seq i))
+ (and xp (or (no yp) (< xp yp)))))
+
+(def orf fns
+ (fn args
+ ((afn (fs)
+ (and fs (or (apply (car fs) args) (self (cdr fs)))))
+ fns)))
+
+(def andf fns
+ (fn args
+ ((afn (fs)
+ (if (no fs) t
+ (no (cdr fs)) (apply (car fs) args)
+ (and (apply (car fs) args) (self (cdr fs)))))
+ fns)))
+
+(def atend (i s)
+ (> i (- (len s) 2)))
+
+(def multiple (x y)
+ (is 0 (mod x y)))
+
+(mac nor args `(no (or ,@args)))
+
+; Consider making the default sort fn take compare's two args (when do
+; you ever have to sort mere lists of numbers?) and rename current sort
+; as prim-sort or something.
+
+; Could simply modify e.g. > so that (> len) returned the same thing
+; as (compare > len).
+
+(def compare (comparer scorer)
+ (fn (x y) (comparer (scorer x) (scorer y))))
+
+; Cleaner thus, but may only ever need in 2 arg case.
+
+;(def compare (comparer scorer)
+; (fn args (apply comparer map scorer args)))
+
+; (def only (f g . args) (aif (apply g args) (f it)))
+
+(def only (f)
+ (fn args (if (car args) (apply f args))))
+
+(mac conswhen (f x y)
+ (w/uniq (gf gx)
+ `(with (,gf ,f ,gx ,x)
+ (if (,gf ,gx) (cons ,gx ,y) ,y))))
+
+; Could combine with firstn if put f arg last, default to (fn (x) t).
+
+(def retrieve (n f xs)
+ (if (no n) (keep f xs)
+ (or (<= n 0) (no xs)) nil
+ (f (car xs)) (cons (car xs) (retrieve (- n 1) f (cdr xs)))
+ (retrieve n f (cdr xs))))
+
+(def dedup (xs)
+ (with (h (table) acc nil)
+ (each x xs
+ (unless (h x)
+ (push x acc)
+ (set (h x))))
+ (rev acc)))
+
+(def single (x) (and (acons x) (no (cdr x))))
+
+(def intersperse (x ys)
+ (and ys (cons (car ys)
+ (mappend [list x _] (cdr ys)))))
+
+(def counts (seq (o c (table)))
+ (if (no seq)
+ c
+ (do (++ (c (car seq) 0))
+ (counts (cdr seq) c))))
+
+(def commonest (seq)
+ (with (winner nil n 0)
+ (each (k v) (counts seq)
+ (when (> v n) (= winner k n v)))
+ (list winner n)))
+
+(def reduce (f xs)
+ (if (cddr xs)
+ (reduce f (cons (f (car xs) (cadr xs)) (cddr xs)))
+ (apply f xs)))
+
+(def rreduce (f xs)
+ (if (cddr xs)
+ (f (car xs) (rreduce f (cdr xs)))
+ (apply f xs)))
+
+(let argsym (uniq)
+
+ (def parse-format (str)
+ (accum a
+ (with (chars nil i -1)
+ (w/instring s str
+ (whilet c (readc s)
+ (case c
+ #\# (do (a (coerce (rev chars) 'string))
+ (wipe chars)
+ (a (read s)))
+ #\~ (do (a (coerce (rev chars) 'string))
+ (wipe chars)
+ (readc s)
+ (a (list argsym (++ i))))
+ (push c chars))))
+ (when chars
+ (a (coerce (rev chars) 'string))))))
+
+ (mac prf (str . args)
+ `(let ,argsym (list ,@args)
+ (pr ,@(parse-format str))))
+)
+
+(def load (file)
+ (w/infile f file
+ (w/uniq eof
+ (whiler e (read f eof) eof
+ (eval e)))))
+
+(def positive (x)
+ (and (number x) (> x 0)))
+
+(mac w/table (var . body)
+ `(let ,var (table) ,@body ,var))
+
+(def ero args
+ (w/stdout (stderr)
+ (each a args
+ (write a)
+ (writec #\space))
+ (writec #\newline))
+ (car args))
+
+(def queue () (list nil nil 0))
+
+; Despite call to atomic, once had some sign this wasn't thread-safe.
+; Keep an eye on it.
+
+(def enq (obj q)
+ (atomic
+ (++ (q 2))
+ (if (no (car q))
+ (= (cadr q) (= (car q) (list obj)))
+ (= (cdr (cadr q)) (list obj)
+ (cadr q) (cdr (cadr q))))
+ (car q)))
+
+(def deq (q)
+ (atomic (unless (is (q 2) 0) (-- (q 2)))
+ (pop (car q))))
+
+; Should redef len to do this, and make queues lists annotated queue.
+
+(def qlen (q) (q 2))
+
+(def qlist (q) (car q))
+
+(def enq-limit (val q (o limit 1000))
+ (atomic
+ (unless (< (qlen q) limit)
+ (deq q))
+ (enq val q)))
+
+(def median (ns)
+ ((sort > ns) (trunc (/ (len ns) 2))))
+
+(mac noisy-each (n var val . body)
+ (w/uniq (gn gc)
+ `(with (,gn ,n ,gc 0)
+ (each ,var ,val
+ (when (multiple (++ ,gc) ,gn)
+ (pr ".")
+ (flushout)
+ )
+ ,@body)
+ (prn)
+ (flushout))))
+
+(mac point (name . body)
+ (w/uniq (g p)
+ `(ccc (fn (,g)
+ (let ,name (fn ((o ,p)) (,g ,p))
+ ,@body)))))
+
+(mac catch body
+ `(point throw ,@body))
+
+(def downcase (x)
+ (let downc (fn (c)
+ (let n (coerce c 'int)
+ (if (or (< 64 n 91) (< 191 n 215) (< 215 n 223))
+ (coerce (+ n 32) 'char)
+ c)))
+ (case (type x)
+ string (map downc x)
+ char (downc x)
+ sym (sym (map downc (coerce x 'string)))
+ (err "Can't downcase" x))))
+
+(def upcase (x)
+ (let upc (fn (c)
+ (let n (coerce c 'int)
+ (if (or (< 96 n 123) (< 223 n 247) (< 247 n 255))
+ (coerce (- n 32) 'char)
+ c)))
+ (case (type x)
+ string (map upc x)
+ char (upc x)
+ sym (sym (map upc (coerce x 'string)))
+ (err "Can't upcase" x))))
+
+(def inc (x (o n 1))
+ (coerce (+ (coerce x 'int) n) (type x)))
+
+(def range (start end)
+ (if (> start end)
+ nil
+ (cons start (range (inc start) end))))
+
+(def mismatch (s1 s2)
+ (catch
+ (on c s1
+ (when (isnt c (s2 index))
+ (throw index)))))
+
+(def memtable (ks)
+ (let h (table)
+ (each k ks (set (h k)))
+ h))
+
+(= bar* " | ")
+
+(mac w/bars body
+ (w/uniq (out needbars)
+ `(let ,needbars nil
+ (do ,@(map (fn (e)
+ `(let ,out (tostring ,e)
+ (unless (is ,out "")
+ (if ,needbars
+ (pr bar* ,out)
+ (do (set ,needbars)
+ (pr ,out))))))
+ body)))))
+
+(def len< (x n) (< (len x) n))
+
+(def len> (x n) (> (len x) n))
+
+(mac thread body
+ `(new-thread (fn () ,@body)))
+
+(mac trav (x . fs)
+ (w/uniq g
+ `((afn (,g)
+ (when ,g
+ ,@(map [list _ g] fs)))
+ ,x)))
+
+(mac or= (place expr)
+ (let (binds val setter) (setforms place)
+ `(atwiths ,binds
+ (or ,val (,setter ,expr)))))
+
+(= hooks* (table))
+
+(def hook (name . args)
+ (aif (hooks* name) (apply it args)))
+
+(mac defhook (name . rest)
+ `(= (hooks* ',name) (fn ,@rest)))
+
+(mac out (expr) `(pr ,(tostring (eval expr))))
+
+; if renamed this would be more natural for (map [_ user] pagefns*)
+
+(def get (index) [_ index])
+
+(= savers* (table))
+
+(mac fromdisk (var file init load save)
+ (w/uniq (gf gv)
+ `(unless (bound ',var)
+ (do1 (= ,var (iflet ,gf (file-exists ,file)
+ (,load ,gf)
+ ,init))
+ (= (savers* ',var) (fn (,gv) (,save ,gv ,file)))))))
+
+(mac diskvar (var file)
+ `(fromdisk ,var ,file nil readfile1 writefile))
+
+(mac disktable (var file)
+ `(fromdisk ,var ,file (table) load-table save-table))
+
+(mac todisk (var (o expr var))
+ `((savers* ',var)
+ ,(if (is var expr) var `(= ,var ,expr))))
+
+
+(mac evtil (expr test)
+ (w/uniq gv
+ `(let ,gv ,expr
+ (while (no (,test ,gv))
+ (= ,gv ,expr))
+ ,gv)))
+
+(def rand-key (h)
+ (if (empty h)
+ nil
+ (let n (rand (len h))
+ (catch
+ (each (k v) h
+ (when (is (-- n) -1)
+ (throw k)))))))
+
+(def ratio (test xs)
+ (if (empty xs)
+ 0
+ (/ (count test xs) (len xs))))
+
+
+; any logical reason I can't say (push x (if foo y z)) ?
+; eval would have to always ret 2 things, the val and where it came from
+; idea: implicit tables of tables; setf empty field, becomes table
+; or should setf on a table just take n args?
+
+; idea: use constants in functional position for currying?
+; (1 foo) would mean (fn args (apply foo 1 args))
+; another solution would be to declare certain symbols curryable, and
+; if > was, >_10 would mean [> _ 10]
+; or just say what the hell and make _ ssyntax for currying
+; idea: make >10 ssyntax for [> _ 10]
+; solution to the "problem" of improper lists: allow any atom as a list
+; terminator, not just nil. means list recursion should terminate on
+; atom rather than nil, (def empty (x) (or (atom x) (is x "")))
+; table should be able to take an optional initial-value. handle in sref.
+; warn about code of form (if (= )) -- probably mean is
+; warn when a fn has a parm that's already defined as a macro.
+; (def foo (after) (after))
+; idea: a fn (nothing) that returns a special gensym which is ignored
+; by map, so can use map in cases when don't want all the vals
+; idea: anaph macro so instead of (aand x y) say (anaph and x y)
+; idea: foo.bar!baz as an abbrev for (foo bar 'baz)
+; or something a bit more semantic?
+; could uniq be (def uniq () (annotate 'symbol (list 'u))) again?
+; idea: use x- for (car x) and -x for (cdr x) (but what about math -?)
+; idea: get rid of strings and just use symbols
+; could a string be (#\a #\b . "") ?
+; better err msg when , outside of a bq
+; idea: parameter (p foo) means in body foo is (pair arg)
+; idea: make ('string x) equiv to (coerce x 'string) ? or isa?
+; quoted atoms in car valuable unused semantic space
+; idea: if (defun foo (x y) ...), make (foo 1) return (fn (y) (foo 1 y))
+; probably would lead to lots of errors when call with missing args
+; but would be really dense with . notation, (foo.1 2)
+; or use special ssyntax for currying: (foo@1 2)
+; remember, can also double; could use foo::bar to mean something
+; wild idea: inline defs for repetitive code
+; same args as fn you're in
+; variant of compose where first fn only applied to first arg?
+; (> (len x) y) means (>+len x y)
+; use ssyntax underscore for a var?
+; foo_bar means [foo _ bar]
+; what does foo:_:bar mean?
+; matchcase
+; idea: atable that binds it to table, assumes input is a list
+; crazy that finding the top 100 nos takes so long:
+; (let bb (n-of 1000 (rand 50)) (time10 (bestn 100 > bb)))
+; time: 2237 msec. -> now down to 850 msec
+