├── static ├── robots.txt ├── s.gif ├── arc.png ├── grayarrow.gif └── graydown.gif ├── copyright ├── libs.arc ├── as.scm ├── README.md ├── how-to-run-news ├── brackets.scm ├── code.arc ├── blog.arc ├── pprint.arc ├── prompt.arc ├── strings.arc ├── html.arc ├── srv.arc ├── app.arc ├── ac.scm └── arc.arc /static/robots.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/s.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wting/hackernews/HEAD/static/s.gif -------------------------------------------------------------------------------- /static/arc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wting/hackernews/HEAD/static/arc.png -------------------------------------------------------------------------------- /static/grayarrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wting/hackernews/HEAD/static/grayarrow.gif -------------------------------------------------------------------------------- /static/graydown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wting/hackernews/HEAD/static/graydown.gif -------------------------------------------------------------------------------- /copyright: -------------------------------------------------------------------------------- 1 | This software is copyright (c) Paul Graham and Robert Morris. Permission 2 | to use it is granted under the Perl Foundations' Artistic License 2.0. 3 | -------------------------------------------------------------------------------- /libs.arc: -------------------------------------------------------------------------------- 1 | (map load '("strings.arc" 2 | "pprint.arc" 3 | "code.arc" 4 | "html.arc" 5 | "srv.arc" 6 | "app.arc" 7 | "prompt.arc")) 8 | -------------------------------------------------------------------------------- /as.scm: -------------------------------------------------------------------------------- 1 | ; mzscheme -m -f as.scm 2 | ; (tl) 3 | ; (asv) 4 | ; http://localhost:8080 5 | 6 | (require mzscheme) ; promise we won't redefine mzscheme bindings 7 | 8 | (require "ac.scm") 9 | (require "brackets.scm") 10 | (use-bracket-readtable) 11 | 12 | (aload "arc.arc") 13 | (aload "libs.arc") 14 | 15 | (tl) 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Note 2 | 3 | - Instructions copied from the original download [site][arc-ins]. 4 | - The files from the tarball in step 2 have been copied into this repository. 5 | 6 | ## Original Instructions 7 | 8 | 1. Install version 372 of MzScheme. (Don't use the latest version. Versions after 372 made lists immutable.) 9 | 2. Get http://ycombinator.com/arc/arc3.tar and untar it. 10 | 3. Type mzscheme -m -f as.scm and you should get an Arc prompt. 11 | 4. If you ^C an Arc program, you'll get the Scheme REPL. Use (tl) to get back to the Arc REPL. 12 | 5. If you have questions or suggestions, post them on the [forum][arc-forum]. 13 | 14 | [arc-ins]: http://arclanguage.org/install 15 | [arc-forum]: http://arclanguage.org/forum 16 | -------------------------------------------------------------------------------- /how-to-run-news: -------------------------------------------------------------------------------- 1 | To run News: 2 | 3 | tar xvf arc3.tar 4 | 5 | cd arc3 6 | 7 | mkdir arc 8 | 9 | echo "myname" > arc/admins 10 | 11 | mzscheme -f as.scm 12 | 13 | at the arc prompt: 14 | 15 | (load "news.arc") 16 | 17 | (nsv) 18 | 19 | go to http://localhost:8080 20 | 21 | click on login, and create an account called myname 22 | 23 | you should now be logged in as an admin 24 | 25 | manually give at least 10 karma to your initial set of users 26 | 27 | don't worry about "user break" messages when restarting News 28 | 29 | 30 | 31 | To customize News: 32 | 33 | change the variables at the top of news.arc 34 | 35 | 36 | 37 | To improve performance: 38 | 39 | (= static-max-age* 7200) ; browsers can cache static files for 7200 sec 40 | 41 | (declare 'direct-calls t) ; you promise not to redefine fns as tables 42 | 43 | (declare 'explicit-flush t) ; you take responsibility for flushing output 44 | ; (all existing news code already does) 45 | -------------------------------------------------------------------------------- /brackets.scm: -------------------------------------------------------------------------------- 1 | ; From Eli Barzilay, eli@barzilay.org 2 | 3 | ;> (require "brackets.scm") 4 | ;> (use-bracket-readtable) 5 | ;> ([+ _ 1] 10) 6 | ;11 7 | 8 | (module brackets mzscheme 9 | 10 | ; main reader function for []s 11 | ; recursive read starts with default readtable's [ parser, 12 | ; but nested reads still use the curent readtable: 13 | 14 | (define (read-square-brackets ch port src line col pos) 15 | `(fn (_) 16 | ,(read/recursive port #\[ #f))) 17 | 18 | ; a readtable that is just like the builtin except for []s 19 | 20 | (define bracket-readtable 21 | (make-readtable #f #\[ 'terminating-macro read-square-brackets)) 22 | 23 | ; call this to set the global readtable 24 | 25 | (provide use-bracket-readtable) 26 | 27 | (define (use-bracket-readtable) 28 | (current-readtable bracket-readtable)) 29 | 30 | ; these two implement the required functionality for #reader 31 | 32 | ;(define (*read inp) 33 | ; (parameterize ((current-readtable bracket-readtable)) 34 | ; (read inp))) 35 | 36 | (define (*read . args) 37 | (parameterize ((current-readtable bracket-readtable)) 38 | (read (if (null? args) (current-input-port) (car args))))) 39 | 40 | (define (*read-syntax src port) 41 | (parameterize ((current-readtable bracket-readtable)) 42 | (read-syntax src port))) 43 | 44 | ; and the need to be provided as `read' and `read-syntax' 45 | 46 | (provide (rename *read read) (rename *read-syntax read-syntax)) 47 | 48 | ) 49 | -------------------------------------------------------------------------------- /code.arc: -------------------------------------------------------------------------------- 1 | ; Code analysis. Spun off 21 Dec 07. 2 | 3 | ; Ought to do more of this in Arc. One of the biggest advantages 4 | ; of Lisp is messing with code. 5 | 6 | (def codelines (file) 7 | (w/infile in file 8 | (summing test 9 | (whilet line (readline in) 10 | (test (aand (find nonwhite line) (isnt it #\;))))))) 11 | 12 | (def codeflat (file) 13 | (len (flat (readall (infile file))))) 14 | 15 | (def codetree (file) 16 | (treewise + (fn (x) 1) (readall (infile file)))) 17 | 18 | (def code-density (file) 19 | (/ (codetree file) (codelines file))) 20 | 21 | (def tokcount (files) 22 | (let counts (table) 23 | (each f files 24 | (each token (flat (readall (infile f))) 25 | (++ (counts token 0)))) 26 | counts)) 27 | 28 | (def common-tokens (files) 29 | (let counts (tokcount files) 30 | (let ranking nil 31 | (maptable (fn (k v) 32 | (unless (nonop k) 33 | (insort (compare > cadr) (list k v) ranking))) 34 | counts) 35 | ranking))) 36 | 37 | (def nonop (x) 38 | (in x 'quote 'unquote 'quasiquote 'unquote-splicing)) 39 | 40 | (def common-operators (files) 41 | (keep [and (isa (car _) 'sym) (bound (car _))] (common-tokens files))) 42 | 43 | (def top40 (xs) 44 | (map prn (firstn 40 xs)) 45 | t) 46 | 47 | (def space-eaters (files) 48 | (let counts (tokcount files) 49 | (let ranking nil 50 | (maptable (fn (k v) 51 | (when (and (isa k 'sym) (bound k)) 52 | (insort (compare > [* (len (string (car _))) 53 | (cadr _)]) 54 | (list k v (* (len (string k)) v)) 55 | ranking))) 56 | counts) 57 | ranking))) 58 | 59 | ;(top40 (space-eaters allfiles*)) 60 | 61 | (mac flatlen args `(len (flat ',args))) 62 | -------------------------------------------------------------------------------- /blog.arc: -------------------------------------------------------------------------------- 1 | ; Blog tool example. 20 Jan 08, rev 21 May 09. 2 | 3 | ; To run: 4 | ; arc> (load "blog.arc") 5 | ; arc> (bsv) 6 | ; go to http://localhost:8080/blog 7 | 8 | (= postdir* "arc/posts/" maxid* 0 posts* (table)) 9 | 10 | (= blogtitle* "A Blog") 11 | 12 | (deftem post id nil title nil text nil) 13 | 14 | (def load-posts () 15 | (each id (map int (dir postdir*)) 16 | (= maxid* (max maxid* id) 17 | (posts* id) (temload 'post (string postdir* id))))) 18 | 19 | (def save-post (p) (save-table p (string postdir* p!id))) 20 | 21 | (def post (id) (posts* (errsafe:int id))) 22 | 23 | (mac blogpage body 24 | `(whitepage 25 | (center 26 | (widtable 600 27 | (tag b (link blogtitle* "blog")) 28 | (br 3) 29 | ,@body 30 | (br 3) 31 | (w/bars (link "archive") 32 | (link "new post" "newpost")))))) 33 | 34 | (defop viewpost req (blogop post-page req)) 35 | 36 | (def blogop (f req) 37 | (aif (post (arg req "id")) 38 | (f (get-user req) it) 39 | (blogpage (pr "No such post.")))) 40 | 41 | (def permalink (p) (string "viewpost?id=" p!id)) 42 | 43 | (def post-page (user p) (blogpage (display-post user p))) 44 | 45 | (def display-post (user p) 46 | (tag b (link p!title (permalink p))) 47 | (when user 48 | (sp) 49 | (link "[edit]" (string "editpost?id=" p!id))) 50 | (br2) 51 | (pr p!text)) 52 | 53 | (defopl newpost req 54 | (whitepage 55 | (aform [let u (get-user _) 56 | (post-page u (addpost u (arg _ "t") (arg _ "b")))] 57 | (tab (row "title" (input "t" "" 60)) 58 | (row "text" (textarea "b" 10 80)) 59 | (row "" (submit)))))) 60 | 61 | (def addpost (user title text) 62 | (let p (inst 'post 'id (++ maxid*) 'title title 'text text) 63 | (save-post p) 64 | (= (posts* p!id) p))) 65 | 66 | (defopl editpost req (blogop edit-page req)) 67 | 68 | (def edit-page (user p) 69 | (whitepage 70 | (vars-form user 71 | `((string title ,p!title t t) (text text ,p!text t t)) 72 | (fn (name val) (= (p name) val)) 73 | (fn () (save-post p) 74 | (post-page user p))))) 75 | 76 | (defop archive req 77 | (blogpage 78 | (tag ul 79 | (each p (map post (rev (range 1 maxid*))) 80 | (tag li (link p!title (permalink p))))))) 81 | 82 | (defop blog req 83 | (let user (get-user req) 84 | (blogpage 85 | (for i 0 4 86 | (awhen (posts* (- maxid* i)) 87 | (display-post user it) 88 | (br 3)))))) 89 | 90 | (def bsv () 91 | (ensure-dir postdir*) 92 | (load-posts) 93 | (asv)) 94 | 95 | 96 | -------------------------------------------------------------------------------- /pprint.arc: -------------------------------------------------------------------------------- 1 | ; Pretty-Printing. Spun off 4 Aug 06. 2 | 3 | ; todo: indentation of long ifs; quasiquote, unquote, unquote-splicing 4 | 5 | (= bodops* (fill-table (table) 6 | '(let 2 with 1 while 1 def 2 fn 1 rfn 2 afn 1 7 | when 1 unless 1 after 1 whilet 2 for 3 each 2 whenlet 2 awhen 1 8 | whitepage 0 tag 1 form 1 aform 1 aformh 1 w/link 1 textarea 3 9 | ))) 10 | 11 | (= oneline* 35) ; print exprs less than this long on one line 12 | 13 | ; If returns nil, can assume it didn't have to break expr. 14 | 15 | (def ppr (expr (o col 0) (o noindent nil)) 16 | (if (or (atom expr) (dotted expr)) 17 | (do (unless noindent (sp col)) 18 | (write expr) 19 | nil) 20 | (is (car expr) 'quote) 21 | (do (unless noindent (sp col)) 22 | (pr "'") 23 | (ppr (cadr expr) (+ col 1) t)) 24 | (bodops* (car expr)) 25 | (do (unless noindent (sp col)) 26 | (let whole (tostring (write expr)) 27 | (if (< (len whole) oneline*) 28 | (do (pr whole) nil) 29 | (ppr-progn expr col noindent)))) 30 | (do (unless noindent (sp col)) 31 | (let whole (tostring (write expr)) 32 | (if (< (len whole) oneline*) 33 | (do (pr whole) nil) 34 | (ppr-call expr col noindent)))))) 35 | 36 | (def ppr-progn (expr col noindent) 37 | (lpar) 38 | (let n (bodops* (car expr)) 39 | (let str (tostring (write-spaced (firstn n expr))) 40 | (unless (is n 0) (pr str) (sp)) 41 | (ppr (expr n) (+ col (len str) 2) t)) 42 | (map (fn (e) (prn) (ppr e (+ col 2))) 43 | (nthcdr (+ n 1) expr))) 44 | (rpar) 45 | t) 46 | 47 | (def ppr-call (expr col noindent) 48 | (lpar) 49 | (let carstr (tostring (write (car expr))) 50 | (pr carstr) 51 | (if (cdr expr) 52 | (do (sp) 53 | (let broke (ppr (cadr expr) (+ col (len carstr) 2) t) 54 | (pprest (cddr expr) 55 | (+ col (len carstr) 2) 56 | (no broke))) 57 | t) 58 | (do (rpar) t)))) 59 | 60 | (def pprest (exprs col (o oneline t)) 61 | (if (and oneline 62 | (all (fn (e) 63 | (or (atom e) (and (is (car e) 'quote) (atom (cadr e))))) 64 | exprs)) 65 | (do (map (fn (e) (pr " ") (write e)) 66 | exprs) 67 | (rpar)) 68 | (do (when exprs 69 | (each e exprs (prn) (ppr e col))) 70 | (rpar)))) 71 | 72 | (def write-spaced (xs) 73 | (when xs 74 | (write (car xs)) 75 | (each x (cdr xs) (pr " ") (write x)))) 76 | 77 | (def sp ((o n 1)) (repeat n (pr " "))) 78 | (def lpar () (pr "(")) 79 | (def rpar () (pr ")")) 80 | 81 | -------------------------------------------------------------------------------- /prompt.arc: -------------------------------------------------------------------------------- 1 | ; Prompt: Web-based programming application. 4 Aug 06. 2 | 3 | (= appdir* "arc/apps/") 4 | 5 | (defop prompt req 6 | (let user (get-user req) 7 | (if (admin user) 8 | (prompt-page user) 9 | (pr "Sorry.")))) 10 | 11 | (def prompt-page (user . msg) 12 | (ensure-dir appdir*) 13 | (ensure-dir (string appdir* user)) 14 | (whitepage 15 | (prbold "Prompt") 16 | (hspace 20) 17 | (pr user " | ") 18 | (link "logout") 19 | (when msg (hspace 10) (apply pr msg)) 20 | (br2) 21 | (tag (table border 0 cellspacing 10) 22 | (each app (dir (+ appdir* user)) 23 | (tr (td app) 24 | (td (ulink user 'edit (edit-app user app))) 25 | (td (ulink user 'run (run-app user app))) 26 | (td (hspace 40) 27 | (ulink user 'delete (rem-app user app)))))) 28 | (br2) 29 | (aform (fn (req) 30 | (when-umatch user req 31 | (aif (goodname (arg req "app")) 32 | (edit-app user it) 33 | (prompt-page user "Bad name.")))) 34 | (tab (row "name:" (input "app") (submit "create app")))))) 35 | 36 | (def app-path (user app) 37 | (and user app (+ appdir* user "/" app))) 38 | 39 | (def read-app (user app) 40 | (aand (app-path user app) 41 | (file-exists it) 42 | (readfile it))) 43 | 44 | (def write-app (user app exprs) 45 | (awhen (app-path user app) 46 | (w/outfile o it 47 | (each e exprs (write e o))))) 48 | 49 | (def rem-app (user app) 50 | (let file (app-path user app) 51 | (if (file-exists file) 52 | (do (rmfile (app-path user app)) 53 | (prompt-page user "Program " app " deleted.")) 54 | (prompt-page user "No such app.")))) 55 | 56 | (def edit-app (user app) 57 | (whitepage 58 | (pr "user: " user " app: " app) 59 | (br2) 60 | (aform (fn (req) 61 | (let u2 (get-user req) 62 | (if (is u2 user) 63 | (do (when (is (arg req "cmd") "save") 64 | (write-app user app (readall (arg req "exprs")))) 65 | (prompt-page user)) 66 | (login-page 'both nil 67 | (fn (u ip) (prompt-page u)))))) 68 | (textarea "exprs" 10 82 69 | (pprcode (read-app user app))) 70 | (br2) 71 | (buts 'cmd "save" "cancel")))) 72 | 73 | (def pprcode (exprs) 74 | (each e exprs 75 | (ppr e) 76 | (pr "\n\n"))) 77 | 78 | (def view-app (user app) 79 | (whitepage 80 | (pr "user: " user " app: " app) 81 | (br2) 82 | (tag xmp (pprcode (read-app user app))))) 83 | 84 | (def run-app (user app) 85 | (let exprs (read-app user app) 86 | (if exprs 87 | (on-err (fn (c) (pr "Error: " (details c))) 88 | (fn () (map eval exprs))) 89 | (prompt-page user "Error: No application " app " for user " user)))) 90 | 91 | (wipe repl-history*) 92 | 93 | (defop repl req 94 | (if (admin (get-user req)) 95 | (replpage req) 96 | (pr "Sorry."))) 97 | 98 | (def replpage (req) 99 | (whitepage 100 | (repl (readall (or (arg req "expr") "")) "repl"))) 101 | 102 | (def repl (exprs url) 103 | (each expr exprs 104 | (on-err (fn (c) (push (list expr c t) repl-history*)) 105 | (fn () 106 | (= that (eval expr) thatexpr expr) 107 | (push (list expr that) repl-history*)))) 108 | (form url 109 | (textarea "expr" 8 60) 110 | (sp) 111 | (submit)) 112 | (tag xmp 113 | (each (expr val err) (firstn 20 repl-history*) 114 | (pr "> ") 115 | (ppr expr) 116 | (prn) 117 | (prn (if err "Error: " "") 118 | (ellipsize (tostring (write val)) 800))))) 119 | 120 | -------------------------------------------------------------------------------- /strings.arc: -------------------------------------------------------------------------------- 1 | ; Matching. Spun off 29 Jul 06. 2 | 3 | ; arc> (tostring (writec (coerce 133 'char))) 4 | ; 5 | ;> (define ss (open-output-string)) 6 | ;> (write-char (integer->char 133) ss) 7 | ;> (get-output-string ss) 8 | ;"\u0085" 9 | 10 | (def tokens (s (o sep whitec)) 11 | (let test (testify sep) 12 | (let rec (afn (cs toks tok) 13 | (if (no cs) (consif tok toks) 14 | (test (car cs)) (self (cdr cs) (consif tok toks) nil) 15 | (self (cdr cs) toks (cons (car cs) tok)))) 16 | (rev (map [coerce _ 'string] 17 | (map rev (rec (coerce s 'cons) nil nil))))))) 18 | 19 | ; names of cut, split, halve not optimal 20 | 21 | (def halve (s (o sep whitec)) 22 | (let test (testify sep) 23 | (let rec (afn (cs tok) 24 | (if (no cs) (list (rev tok)) 25 | (test (car cs)) (list cs (rev tok)) 26 | (self (cdr cs) (cons (car cs) tok)))) 27 | (rev (map [coerce _ 'string] 28 | (rec (coerce s 'cons) nil)))))) 29 | 30 | ; maybe promote to arc.arc, but if so include a list clause 31 | 32 | (def positions (test seq) 33 | (accum a 34 | (let f (testify test) 35 | (forlen i seq 36 | (if (f (seq i)) (a i)))))) 37 | 38 | (def lines (s) 39 | (accum a 40 | ((afn ((p . ps)) 41 | (if ps 42 | (do (a (rem #\return (cut s (+ p 1) (car ps)))) 43 | (self ps)) 44 | (a (cut s (+ p 1))))) 45 | (cons -1 (positions #\newline s))))) 46 | 47 | ; > (require (lib "uri-codec.ss" "net")) 48 | ;> (form-urlencoded-decode "x%ce%bbx") 49 | ;"xλx" 50 | 51 | ; first byte: 0-7F, 1 char; c2-df 2; e0-ef 3, f0-f4 4. 52 | 53 | ; Fixed for utf8 by pc. 54 | 55 | (def urldecode (s) 56 | (tostring 57 | (forlen i s 58 | (caselet c (s i) 59 | #\+ (writec #\space) 60 | #\% (do (when (> (- (len s) i) 2) 61 | (writeb (int (cut s (+ i 1) (+ i 3)) 16))) 62 | (++ i 2)) 63 | (writec c))))) 64 | 65 | (def urlencode (s) 66 | (tostring 67 | (each c s 68 | (writec #\%) 69 | (let i (int c) 70 | (if (< i 16) (writec #\0)) 71 | (pr (coerce i 'string 16)))))) 72 | 73 | (mac litmatch (pat string (o start 0)) 74 | (w/uniq (gstring gstart) 75 | `(with (,gstring ,string ,gstart ,start) 76 | (unless (> (+ ,gstart ,(len pat)) (len ,gstring)) 77 | (and ,@(let acc nil 78 | (forlen i pat 79 | (push `(is ,(pat i) (,gstring (+ ,gstart ,i))) 80 | acc)) 81 | (rev acc))))))) 82 | 83 | ; litmatch would be cleaner if map worked for string and integer args: 84 | 85 | ; ,@(map (fn (n c) 86 | ; `(is ,c (,gstring (+ ,gstart ,n)))) 87 | ; (len pat) 88 | ; pat) 89 | 90 | (mac endmatch (pat string) 91 | (w/uniq (gstring glen) 92 | `(withs (,gstring ,string ,glen (len ,gstring)) 93 | (unless (> ,(len pat) (len ,gstring)) 94 | (and ,@(let acc nil 95 | (forlen i pat 96 | (push `(is ,(pat (- (len pat) 1 i)) 97 | (,gstring (- ,glen 1 ,i))) 98 | acc)) 99 | (rev acc))))))) 100 | 101 | (def posmatch (pat seq (o start 0)) 102 | (catch 103 | (if (isa pat 'fn) 104 | (for i start (- (len seq) 1) 105 | (when (pat (seq i)) (throw i))) 106 | (for i start (- (len seq) (len pat)) 107 | (when (headmatch pat seq i) (throw i)))) 108 | nil)) 109 | 110 | (def headmatch (pat seq (o start 0)) 111 | (let p (len pat) 112 | ((afn (i) 113 | (or (is i p) 114 | (and (is (pat i) (seq (+ i start))) 115 | (self (+ i 1))))) 116 | 0))) 117 | 118 | (def begins (seq pat (o start 0)) 119 | (unless (len> pat (- (len seq) start)) 120 | (headmatch pat seq start))) 121 | 122 | (def subst (new old seq) 123 | (let boundary (+ (- (len seq) (len old)) 1) 124 | (tostring 125 | (forlen i seq 126 | (if (and (< i boundary) (headmatch old seq i)) 127 | (do (++ i (- (len old) 1)) 128 | (pr new)) 129 | (pr (seq i))))))) 130 | 131 | (def multisubst (pairs seq) 132 | (tostring 133 | (forlen i seq 134 | (iflet (old new) (find [begins seq (car _) i] pairs) 135 | (do (++ i (- (len old) 1)) 136 | (pr new)) 137 | (pr (seq i)))))) 138 | 139 | ; not a good name 140 | 141 | (def findsubseq (pat seq (o start 0)) 142 | (if (< (- (len seq) start) (len pat)) 143 | nil 144 | (if (headmatch pat seq start) 145 | start 146 | (findsubseq pat seq (+ start 1))))) 147 | 148 | (def blank (s) (~find ~whitec s)) 149 | 150 | (def nonblank (s) (unless (blank s) s)) 151 | 152 | (def trim (s where (o test whitec)) 153 | (withs (f (testify test) 154 | p1 (pos ~f s)) 155 | (if p1 156 | (cut s 157 | (if (in where 'front 'both) p1 0) 158 | (when (in where 'end 'both) 159 | (let i (- (len s) 1) 160 | (while (and (> i p1) (f (s i))) 161 | (-- i)) 162 | (+ i 1)))) 163 | ""))) 164 | 165 | (def num (n (o digits 2) (o trail-zeros nil) (o init-zero nil)) 166 | (withs (comma 167 | (fn (i) 168 | (tostring 169 | (map [apply pr (rev _)] 170 | (rev (intersperse '(#\,) 171 | (tuples (rev (coerce (string i) 'cons)) 172 | 3)))))) 173 | abrep 174 | (let a (abs n) 175 | (if (< digits 1) 176 | (comma (roundup a)) 177 | (exact a) 178 | (string (comma a) 179 | (when (and trail-zeros (> digits 0)) 180 | (string "." (newstring digits #\0)))) 181 | (withs (d (expt 10 digits) 182 | m (/ (roundup (* a d)) d) 183 | i (trunc m) 184 | r (abs (trunc (- (* m d) (* i d))))) 185 | (+ (if (is i 0) 186 | (if (or init-zero (is r 0)) "0" "") 187 | (comma i)) 188 | (withs (rest (string r) 189 | padded (+ (newstring (- digits (len rest)) #\0) 190 | rest) 191 | final (if trail-zeros 192 | padded 193 | (trim padded 'end [is _ #\0]))) 194 | (string (unless (empty final) ".") 195 | final))))))) 196 | (if (and (< n 0) (find [and (digit _) (isnt _ #\0)] abrep)) 197 | (+ "-" abrep) 198 | abrep))) 199 | 200 | 201 | ; English 202 | 203 | (def pluralize (n str) 204 | (if (or (is n 1) (single n)) 205 | str 206 | (string str "s"))) 207 | 208 | (def plural (n x) 209 | (string n #\ (pluralize n x))) 210 | 211 | 212 | ; http://www.eki.ee/letter/chardata.cgi?HTML4=1 213 | ; http://jrgraphix.net/research/unicode_blocks.php?block=1 214 | ; http://home.tiscali.nl/t876506/utf8tbl.html 215 | ; http://www.fileformat.info/info/unicode/block/latin_supplement/utf8test.htm 216 | ; http://en.wikipedia.org/wiki/Utf-8 217 | ; http://unicode.org/charts/charindex2.html 218 | -------------------------------------------------------------------------------- /html.arc: -------------------------------------------------------------------------------- 1 | ; HTML Utils. 2 | 3 | 4 | (def color (r g b) 5 | (with (c (table) 6 | f (fn (x) (if (< x 0) 0 (> x 255) 255 x))) 7 | (= (c 'r) (f r) (c 'g) (f g) (c 'b) (f b)) 8 | c)) 9 | 10 | (def dehex (str) (errsafe (coerce str 'int 16))) 11 | 12 | (defmemo hex>color (str) 13 | (and (is (len str) 6) 14 | (with (r (dehex (cut str 0 2)) 15 | g (dehex (cut str 2 4)) 16 | b (dehex (cut str 4 6))) 17 | (and r g b 18 | (color r g b))))) 19 | 20 | (defmemo gray (n) (color n n n)) 21 | 22 | (= white (gray 255) 23 | black (gray 0) 24 | linkblue (color 0 0 190) 25 | orange (color 255 102 0) 26 | darkred (color 180 0 0) 27 | darkblue (color 0 0 120) 28 | ) 29 | 30 | (= opmeths* (table)) 31 | 32 | (mac opmeth args 33 | `(opmeths* (list ,@args))) 34 | 35 | (mac attribute (tag opt f) 36 | `(= (opmeths* (list ',tag ',opt)) ,f)) 37 | 38 | (= hexreps (table)) 39 | 40 | (for i 0 255 (= (hexreps i) 41 | (let s (coerce i 'string 16) 42 | (if (is (len s) 1) (+ "0" s) s)))) 43 | 44 | (defmemo hexrep (col) 45 | (+ (hexreps (col 'r)) (hexreps (col 'g)) (hexreps (col 'b)))) 46 | 47 | (def opcolor (key val) 48 | (w/uniq gv 49 | `(whenlet ,gv ,val 50 | (pr ,(string " " key "=#") (hexrep ,gv))))) 51 | 52 | (def opstring (key val) 53 | `(aif ,val (pr ,(+ " " key "=\"") it #\"))) 54 | 55 | (def opnum (key val) 56 | `(aif ,val (pr ,(+ " " key "=") it))) 57 | 58 | (def opsym (key val) 59 | `(pr ,(+ " " key "=") ,val)) 60 | 61 | (def opsel (key val) 62 | `(if ,val (pr " selected"))) 63 | 64 | (def opesc (key val) 65 | `(awhen ,val 66 | (pr ,(string " " key "=\"")) 67 | (if (isa it 'string) (pr-escaped it) (pr it)) 68 | (pr #\"))) 69 | 70 | ; need to escape more? =? 71 | 72 | (def pr-escaped (x) 73 | (each c x 74 | (pr (case c #\< "<" 75 | #\> ">" 76 | #\" """ 77 | #\& "&" 78 | c)))) 79 | 80 | (attribute a href opstring) 81 | (attribute a rel opstring) 82 | (attribute a class opstring) 83 | (attribute a id opsym) 84 | (attribute a onclick opstring) 85 | (attribute body alink opcolor) 86 | (attribute body bgcolor opcolor) 87 | (attribute body leftmargin opnum) 88 | (attribute body link opcolor) 89 | (attribute body marginheight opnum) 90 | (attribute body marginwidth opnum) 91 | (attribute body topmargin opnum) 92 | (attribute body vlink opcolor) 93 | (attribute font color opcolor) 94 | (attribute font face opstring) 95 | (attribute font size opnum) 96 | (attribute form action opstring) 97 | (attribute form method opsym) 98 | (attribute img align opsym) 99 | (attribute img border opnum) 100 | (attribute img height opnum) 101 | (attribute img width opnum) 102 | (attribute img vspace opnum) 103 | (attribute img hspace opnum) 104 | (attribute img src opstring) 105 | (attribute input name opstring) 106 | (attribute input size opnum) 107 | (attribute input type opsym) 108 | (attribute input value opesc) 109 | (attribute option selected opsel) 110 | (attribute select name opstring) 111 | (attribute table bgcolor opcolor) 112 | (attribute table border opnum) 113 | (attribute table cellpadding opnum) 114 | (attribute table cellspacing opnum) 115 | (attribute table width opstring) 116 | (attribute textarea cols opnum) 117 | (attribute textarea name opstring) 118 | (attribute textarea rows opnum) 119 | (attribute textarea wrap opsym) 120 | (attribute td align opsym) 121 | (attribute td bgcolor opcolor) 122 | (attribute td colspan opnum) 123 | (attribute td width opnum) 124 | (attribute td valign opsym) 125 | (attribute td class opstring) 126 | (attribute tr bgcolor opcolor) 127 | (attribute hr color opcolor) 128 | (attribute span class opstring) 129 | (attribute span align opstring) 130 | (attribute span id opsym) 131 | (attribute rss version opstring) 132 | 133 | 134 | (mac gentag args (start-tag args)) 135 | 136 | (mac tag (spec . body) 137 | `(do ,(start-tag spec) 138 | ,@body 139 | ,(end-tag spec))) 140 | 141 | (mac tag-if (test spec . body) 142 | `(if ,test 143 | (tag ,spec ,@body) 144 | (do ,@body))) 145 | 146 | (def start-tag (spec) 147 | (if (atom spec) 148 | `(pr ,(string "<" spec ">")) 149 | (let opts (tag-options (car spec) (pair (cdr spec))) 150 | (if (all [isa _ 'string] opts) 151 | `(pr ,(string "<" (car spec) (apply string opts) ">")) 152 | `(do (pr ,(string "<" (car spec))) 153 | ,@(map (fn (opt) 154 | (if (isa opt 'string) 155 | `(pr ,opt) 156 | opt)) 157 | opts) 158 | (pr ">")))))) 159 | 160 | (def end-tag (spec) 161 | `(pr ,(string ""))) 162 | 163 | (def literal (x) 164 | (case (type x) 165 | sym (in x nil t) 166 | cons (caris x 'quote) 167 | t)) 168 | 169 | ; Returns a list whose elements are either strings, which can 170 | ; simply be printed out, or expressions, which when evaluated 171 | ; generate output. 172 | 173 | (def tag-options (spec options) 174 | (if (no options) 175 | '() 176 | (let ((opt val) . rest) options 177 | (let meth (if (is opt 'style) opstring (opmeth spec opt)) 178 | (if meth 179 | (if val 180 | (cons (if (precomputable-tagopt val) 181 | (tostring (eval (meth opt val))) 182 | (meth opt val)) 183 | (tag-options spec rest)) 184 | (tag-options spec rest)) 185 | (do 186 | (pr "") 187 | (tag-options spec rest))))))) 188 | 189 | (def precomputable-tagopt (val) 190 | (and (literal val) 191 | (no (and (is (type val) 'string) (find #\@ val))))) 192 | 193 | (def br ((o n 1)) 194 | (repeat n (pr "
")) 195 | (prn)) 196 | 197 | (def br2 () (prn "

")) 198 | 199 | (mac center body `(tag center ,@body)) 200 | (mac underline body `(tag u ,@body)) 201 | (mac tab body `(tag (table border 0) ,@body)) 202 | (mac tr body `(tag tr ,@body)) 203 | 204 | (let pratoms (fn (body) 205 | (if (or (no body) 206 | (all [and (acons _) (isnt (car _) 'quote)] 207 | body)) 208 | body 209 | `((pr ,@body)))) 210 | 211 | (mac td body `(tag td ,@(pratoms body))) 212 | (mac trtd body `(tr (td ,@(pratoms body)))) 213 | (mac tdr body `(tag (td align 'right) ,@(pratoms body))) 214 | (mac tdcolor (col . body) `(tag (td bgcolor ,col) ,@(pratoms body))) 215 | ) 216 | 217 | (mac row args 218 | `(tr ,@(map [list 'td _] args))) 219 | 220 | (mac prrow args 221 | (w/uniq g 222 | `(tr ,@(map (fn (a) 223 | `(let ,g ,a 224 | (if (number ,g) 225 | (tdr (pr ,g)) 226 | (td (pr ,g))))) 227 | args)))) 228 | 229 | (mac prbold body `(tag b (pr ,@body))) 230 | 231 | (def para args 232 | (gentag p) 233 | (when args (apply pr args))) 234 | 235 | (def menu (name items (o sel nil)) 236 | (tag (select name name) 237 | (each i items 238 | (tag (option selected (is i sel)) 239 | (pr i))))) 240 | 241 | (mac whitepage body 242 | `(tag html 243 | (tag (body bgcolor white alink linkblue) ,@body))) 244 | 245 | (def errpage args (whitepage (apply prn args))) 246 | 247 | (def blank-url () "s.gif") 248 | 249 | ; Could memoize these. 250 | 251 | ; If h = 0, doesn't affect table column widths in some Netscapes. 252 | 253 | (def hspace (n) (gentag img src (blank-url) height 1 width n)) 254 | (def vspace (n) (gentag img src (blank-url) height n width 0)) 255 | (def vhspace (h w) (gentag img src (blank-url) height h width w)) 256 | 257 | (mac new-hspace (n) 258 | (if (number n) 259 | `(pr ,(string "")) 260 | `(pr ""))) 261 | 262 | ;(def spacerow (h) (tr (td (vspace h)))) 263 | 264 | (def spacerow (h) (pr "")) 265 | 266 | ; For use as nested table. 267 | 268 | (mac zerotable body 269 | `(tag (table border 0 cellpadding 0 cellspacing 0) 270 | ,@body)) 271 | 272 | ; was `(tag (table border 0 cellpadding 0 cellspacing 7) ,@body) 273 | 274 | (mac sptab body 275 | `(tag (table style "border-spacing: 7px 0px;") ,@body)) 276 | 277 | (mac widtable (w . body) 278 | `(tag (table width ,w) (tr (td ,@body)))) 279 | 280 | (def cellpr (x) (pr (or x " "))) 281 | 282 | (def but ((o text "submit") (o name nil)) 283 | (gentag input type 'submit name name value text)) 284 | 285 | (def submit ((o val "submit")) 286 | (gentag input type 'submit value val)) 287 | 288 | (def buts (name . texts) 289 | (if (no texts) 290 | (but) 291 | (do (but (car texts) name) 292 | (each text (cdr texts) 293 | (pr " ") 294 | (but text name))))) 295 | 296 | (mac spanrow (n . body) 297 | `(tr (tag (td colspan ,n) ,@body))) 298 | 299 | (mac form (action . body) 300 | `(tag (form method "post" action ,action) ,@body)) 301 | 302 | (mac textarea (name rows cols . body) 303 | `(tag (textarea name ,name rows ,rows cols ,cols) ,@body)) 304 | 305 | (def input (name (o val "") (o size 10)) 306 | (gentag input type 'text name name value val size size)) 307 | 308 | (mac inputs args 309 | `(tag (table border 0) 310 | ,@(map (fn ((name label len text)) 311 | (w/uniq (gl gt) 312 | `(let ,gl ,len 313 | (tr (td (pr ',label ":")) 314 | (if (isa ,gl 'cons) 315 | (td (textarea ',name (car ,gl) (cadr ,gl) 316 | (let ,gt ,text (if ,gt (pr ,gt))))) 317 | (td (gentag input type ',(if (is label 'password) 318 | 'password 319 | 'text) 320 | name ',name 321 | size ,len 322 | value ,text))))))) 323 | (tuples args 4)))) 324 | 325 | (def single-input (label name chars btext (o pwd)) 326 | (pr label) 327 | (gentag input type (if pwd 'password 'text) name name size chars) 328 | (sp) 329 | (submit btext)) 330 | 331 | (mac cdata body 332 | `(do (pr ""))) 335 | 336 | (def eschtml (str) 337 | (tostring 338 | (each c str 339 | (pr (case c #\< "<" 340 | #\> ">" 341 | #\" """ 342 | #\' "'" 343 | #\& "&" 344 | c))))) 345 | 346 | (def esc<>& (str) 347 | (tostring 348 | (each c str 349 | (pr (case c #\< "<" 350 | #\> ">" 351 | #\& "&" 352 | c))))) 353 | 354 | (def nbsp () (pr " ")) 355 | 356 | (def link (text (o dest text) (o color)) 357 | (tag (a href dest) 358 | (tag-if color (font color color) 359 | (pr text)))) 360 | 361 | (def underlink (text (o dest text)) 362 | (tag (a href dest) (tag u (pr text)))) 363 | 364 | (def striptags (s) 365 | (let intag nil 366 | (tostring 367 | (each c s 368 | (if (is c #\<) (set intag) 369 | (is c #\>) (wipe intag) 370 | (no intag) (pr c)))))) 371 | 372 | (def clean-url (u) 373 | (rem [in _ #\" #\' #\< #\>] u)) 374 | 375 | (def shortlink (url) 376 | (unless (or (no url) (< (len url) 7)) 377 | (link (cut url 7) url))) 378 | 379 | ; this should be one regexp 380 | 381 | (def parafy (str) 382 | (let ink nil 383 | (tostring 384 | (each c str 385 | (pr c) 386 | (unless (whitec c) (set ink)) 387 | (when (is c #\newline) 388 | (unless ink (pr "

")) 389 | (wipe ink)))))) 390 | 391 | (mac spanclass (name . body) 392 | `(tag (span class ',name) ,@body)) 393 | 394 | (def pagemessage (text) 395 | (when text (prn text) (br2))) 396 | 397 | ; Could be stricter. Memoized because looking for chars in Unicode 398 | ; strings is terribly inefficient in Mzscheme. 399 | 400 | (defmemo valid-url (url) 401 | (and (len> url 10) 402 | (or (begins url "http://") 403 | (begins url "https://")) 404 | (~find [in _ #\< #\> #\" #\'] url))) 405 | 406 | (mac fontcolor (c . body) 407 | (w/uniq g 408 | `(let ,g ,c 409 | (if ,g 410 | (tag (font color ,g) ,@body) 411 | (do ,@body))))) 412 | -------------------------------------------------------------------------------- /srv.arc: -------------------------------------------------------------------------------- 1 | ; HTTP Server. 2 | 3 | ; To improve performance with static files, set static-max-age*. 4 | 5 | (= arcdir* "arc/" logdir* "arc/logs/" staticdir* "static/") 6 | 7 | (= quitsrv* nil breaksrv* nil) 8 | 9 | (def serve ((o port 8080)) 10 | (wipe quitsrv*) 11 | (ensure-srvdirs) 12 | (map [apply new-bgthread _] pending-bgthreads*) 13 | (w/socket s port 14 | (prn "ready to serve port " port) 15 | (flushout) 16 | (= currsock* s) 17 | (until quitsrv* 18 | (handle-request s breaksrv*))) 19 | (prn "quit server")) 20 | 21 | (def serve1 ((o port 8080)) 22 | (w/socket s port (handle-request s t))) 23 | 24 | (def ensure-srvdirs () 25 | (map ensure-dir (list arcdir* logdir* staticdir*))) 26 | 27 | (= srv-noisy* nil) 28 | 29 | ; http requests currently capped at 2 meg by socket-accept 30 | 31 | ; should threads process requests one at a time? no, then 32 | ; a browser that's slow consuming the data could hang the 33 | ; whole server. 34 | 35 | ; wait for a connection from a browser and start a thread 36 | ; to handle it. also arrange to kill that thread if it 37 | ; has not completed in threadlife* seconds. 38 | 39 | (= threadlife* 30 requests* 0 requests/ip* (table) 40 | throttle-ips* (table) ignore-ips* (table) spurned* (table)) 41 | 42 | (def handle-request (s breaksrv) 43 | (if breaksrv 44 | (handle-request-1 s) 45 | (errsafe (handle-request-1 s)))) 46 | 47 | (def handle-request-1 (s) 48 | (let (i o ip) (socket-accept s) 49 | (if (and (or (ignore-ips* ip) (abusive-ip ip)) 50 | (++ (spurned* ip 0))) 51 | (force-close i o) 52 | (do (++ requests*) 53 | (++ (requests/ip* ip 0)) 54 | (with (th1 nil th2 nil) 55 | (= th1 (thread 56 | (after (handle-request-thread i o ip) 57 | (close i o) 58 | (kill-thread th2)))) 59 | (= th2 (thread 60 | (sleep threadlife*) 61 | (unless (dead th1) 62 | (prn "srv thread took too long for " ip)) 63 | (break-thread th1) 64 | (force-close i o)))))))) 65 | 66 | ; Returns true if ip has made req-limit* requests in less than 67 | ; req-window* seconds. If an ip is throttled, only 1 request is 68 | ; allowed per req-window* seconds. If an ip makes req-limit* 69 | ; requests in less than dos-window* seconds, it is a treated as a DoS 70 | ; attack and put in ignore-ips* (for this server invocation). 71 | 72 | ; To adjust this while running, adjust the req-window* time, not 73 | ; req-limit*, because algorithm doesn't enforce decreases in the latter. 74 | 75 | (= req-times* (table) req-limit* 30 req-window* 10 dos-window* 2) 76 | 77 | (def abusive-ip (ip) 78 | (and (only.> (requests/ip* ip) 250) 79 | (let now (seconds) 80 | (do1 (if (req-times* ip) 81 | (and (>= (qlen (req-times* ip)) 82 | (if (throttle-ips* ip) 1 req-limit*)) 83 | (let dt (- now (deq (req-times* ip))) 84 | (if (< dt dos-window*) (set (ignore-ips* ip))) 85 | (< dt req-window*))) 86 | (do (= (req-times* ip) (queue)) 87 | nil)) 88 | (enq now (req-times* ip)))))) 89 | 90 | (def handle-request-thread (i o ip) 91 | (with (nls 0 lines nil line nil responded nil t0 (msec)) 92 | (after 93 | (whilet c (unless responded (readc i)) 94 | (if srv-noisy* (pr c)) 95 | (if (is c #\newline) 96 | (if (is (++ nls) 2) 97 | (let (type op args n cooks) (parseheader (rev lines)) 98 | (let t1 (msec) 99 | (case type 100 | get (respond o op args cooks ip) 101 | post (handle-post i o op args n cooks ip) 102 | (respond-err o "Unknown request: " (car lines))) 103 | (log-request type op args cooks ip t0 t1) 104 | (set responded))) 105 | (do (push (string (rev line)) lines) 106 | (wipe line))) 107 | (unless (is c #\return) 108 | (push c line) 109 | (= nls 0)))) 110 | (close i o))) 111 | (harvest-fnids)) 112 | 113 | (def log-request (type op args cooks ip t0 t1) 114 | (with (parsetime (- t1 t0) respondtime (- (msec) t1)) 115 | (srvlog 'srv ip 116 | parsetime 117 | respondtime 118 | (if (> (+ parsetime respondtime) 1000) "***" "") 119 | type 120 | op 121 | (let arg1 (car args) 122 | (if (caris arg1 "fnid") "" arg1)) 123 | cooks))) 124 | 125 | ; Could ignore return chars (which come from textarea fields) here by 126 | ; (unless (is c #\return) (push c line)) 127 | 128 | (def handle-post (i o op args n cooks ip) 129 | (if srv-noisy* (pr "Post Contents: ")) 130 | (if (no n) 131 | (respond-err o "Post request without Content-Length.") 132 | (let line nil 133 | (whilet c (and (> n 0) (readc i)) 134 | (if srv-noisy* (pr c)) 135 | (-- n) 136 | (push c line)) 137 | (if srv-noisy* (pr "\n\n")) 138 | (respond o op (+ (parseargs (string (rev line))) args) cooks ip)))) 139 | 140 | (= header* "HTTP/1.1 200 OK 141 | Content-Type: text/html; charset=utf-8 142 | Connection: close") 143 | 144 | (= type-header* (table)) 145 | 146 | (def gen-type-header (ctype) 147 | (+ "HTTP/1.0 200 OK 148 | Content-Type: " 149 | ctype 150 | " 151 | Connection: close")) 152 | 153 | (map (fn ((k v)) (= (type-header* k) (gen-type-header v))) 154 | '((gif "image/gif") 155 | (jpg "image/jpeg") 156 | (png "image/png") 157 | (text/html "text/html; charset=utf-8"))) 158 | 159 | (= rdheader* "HTTP/1.0 302 Moved") 160 | 161 | (= srvops* (table) redirector* (table) optimes* (table) opcounts* (table)) 162 | 163 | (def save-optime (name elapsed) 164 | ; this is the place to put a/b testing 165 | ; toggle a flag and push elapsed into one of two lists 166 | (++ (opcounts* name 0)) 167 | (unless (optimes* name) (= (optimes* name) (queue))) 168 | (enq-limit elapsed (optimes* name) 1000)) 169 | 170 | ; For ops that want to add their own headers. They must thus remember 171 | ; to prn a blank line before anything meant to be part of the page. 172 | 173 | (mac defop-raw (name parms . body) 174 | (w/uniq t1 175 | `(= (srvops* ',name) 176 | (fn ,parms 177 | (let ,t1 (msec) 178 | (do1 (do ,@body) 179 | (save-optime ',name (- (msec) ,t1)))))))) 180 | 181 | (mac defopr-raw (name parms . body) 182 | `(= (redirector* ',name) t 183 | (srvops* ',name) (fn ,parms ,@body))) 184 | 185 | (mac defop (name parm . body) 186 | (w/uniq gs 187 | `(do (wipe (redirector* ',name)) 188 | (defop-raw ,name (,gs ,parm) 189 | (w/stdout ,gs (prn) ,@body))))) 190 | 191 | ; Defines op as a redirector. Its retval is new location. 192 | 193 | (mac defopr (name parm . body) 194 | (w/uniq gs 195 | `(do (set (redirector* ',name)) 196 | (defop-raw ,name (,gs ,parm) 197 | ,@body)))) 198 | 199 | ;(mac testop (name . args) `((srvops* ',name) ,@args)) 200 | 201 | (deftem request 202 | args nil 203 | cooks nil 204 | ip nil) 205 | 206 | (= unknown-msg* "Unknown." max-age* (table) static-max-age* nil) 207 | 208 | (def respond (str op args cooks ip) 209 | (w/stdout str 210 | (iflet f (srvops* op) 211 | (let req (inst 'request 'args args 'cooks cooks 'ip ip) 212 | (if (redirector* op) 213 | (do (prn rdheader*) 214 | (prn "Location: " (f str req)) 215 | (prn)) 216 | (do (prn header*) 217 | (awhen (max-age* op) 218 | (prn "Cache-Control: max-age=" it)) 219 | (f str req)))) 220 | (let filetype (static-filetype op) 221 | (aif (and filetype (file-exists (string staticdir* op))) 222 | (do (prn (type-header* filetype)) 223 | (awhen static-max-age* 224 | (prn "Cache-Control: max-age=" it)) 225 | (prn) 226 | (w/infile i it 227 | (whilet b (readb i) 228 | (writeb b str)))) 229 | (respond-err str unknown-msg*)))))) 230 | 231 | (def static-filetype (sym) 232 | (let fname (coerce sym 'string) 233 | (and (~find #\/ fname) 234 | (case (downcase (last (check (tokens fname #\.) ~single))) 235 | "gif" 'gif 236 | "jpg" 'jpg 237 | "jpeg" 'jpg 238 | "png" 'png 239 | "css" 'text/html 240 | "txt" 'text/html 241 | "htm" 'text/html 242 | "html" 'text/html 243 | "arc" 'text/html 244 | )))) 245 | 246 | (def respond-err (str msg . args) 247 | (w/stdout str 248 | (prn header*) 249 | (prn) 250 | (apply pr msg args))) 251 | 252 | (def parseheader (lines) 253 | (let (type op args) (parseurl (car lines)) 254 | (list type 255 | op 256 | args 257 | (and (is type 'post) 258 | (some (fn (s) 259 | (and (begins s "Content-Length:") 260 | (errsafe:coerce (cadr (tokens s)) 'int))) 261 | (cdr lines))) 262 | (some (fn (s) 263 | (and (begins s "Cookie:") 264 | (parsecookies s))) 265 | (cdr lines))))) 266 | 267 | ; (parseurl "GET /p1?foo=bar&ug etc") -> (get p1 (("foo" "bar") ("ug"))) 268 | 269 | (def parseurl (s) 270 | (let (type url) (tokens s) 271 | (let (base args) (tokens url #\?) 272 | (list (sym (downcase type)) 273 | (sym (cut base 1)) 274 | (if args 275 | (parseargs args) 276 | nil))))) 277 | 278 | ; I don't urldecode field names or anything in cookies; correct? 279 | 280 | (def parseargs (s) 281 | (map (fn ((k v)) (list k (urldecode v))) 282 | (map [tokens _ #\=] (tokens s #\&)))) 283 | 284 | (def parsecookies (s) 285 | (map [tokens _ #\=] 286 | (cdr (tokens s [or (whitec _) (is _ #\;)])))) 287 | 288 | (def arg (req key) (alref req!args key)) 289 | 290 | ; *** Warning: does not currently urlencode args, so if need to do 291 | ; that replace v with (urlencode v). 292 | 293 | (def reassemble-args (req) 294 | (aif req!args 295 | (apply string "?" (intersperse '& 296 | (map (fn ((k v)) 297 | (string k '= v)) 298 | it))) 299 | "")) 300 | 301 | (= fns* (table) fnids* nil timed-fnids* nil) 302 | 303 | ; count on huge (expt 64 10) size of fnid space to avoid clashes 304 | 305 | (def new-fnid () 306 | (check (sym (rand-string 10)) ~fns* (new-fnid))) 307 | 308 | (def fnid (f) 309 | (atlet key (new-fnid) 310 | (= (fns* key) f) 311 | (push key fnids*) 312 | key)) 313 | 314 | (def timed-fnid (lasts f) 315 | (atlet key (new-fnid) 316 | (= (fns* key) f) 317 | (push (list key (seconds) lasts) timed-fnids*) 318 | key)) 319 | 320 | ; Within f, it will be bound to the fn's own fnid. Remember that this is 321 | ; so low-level that need to generate the newline to separate from the headers 322 | ; within the body of f. 323 | 324 | (mac afnid (f) 325 | `(atlet it (new-fnid) 326 | (= (fns* it) ,f) 327 | (push it fnids*) 328 | it)) 329 | 330 | ;(defop test-afnid req 331 | ; (tag (a href (url-for (afnid (fn (req) (prn) (pr "my fnid is " it))))) 332 | ; (pr "click here"))) 333 | 334 | ; To be more sophisticated, instead of killing fnids, could first 335 | ; replace them with fns that tell the server it's harvesting too 336 | ; aggressively if they start to get called. But the right thing to 337 | ; do is estimate what the max no of fnids can be and set the harvest 338 | ; limit there-- beyond that the only solution is to buy more memory. 339 | 340 | (def harvest-fnids ((o n 50000)) ; was 20000 341 | (when (len> fns* n) 342 | (pull (fn ((id created lasts)) 343 | (when (> (since created) lasts) 344 | (wipe (fns* id)) 345 | t)) 346 | timed-fnids*) 347 | (atlet nharvest (trunc (/ n 10)) 348 | (let (kill keep) (split (rev fnids*) nharvest) 349 | (= fnids* (rev keep)) 350 | (each id kill 351 | (wipe (fns* id))))))) 352 | 353 | (= fnurl* "/x" rfnurl* "/r" rfnurl2* "/y" jfnurl* "/a") 354 | 355 | (= dead-msg* "\nUnknown or expired link.") 356 | 357 | (defop-raw x (str req) 358 | (w/stdout str 359 | (aif (fns* (sym (arg req "fnid"))) 360 | (it req) 361 | (pr dead-msg*)))) 362 | 363 | (defopr-raw y (str req) 364 | (aif (fns* (sym (arg req "fnid"))) 365 | (w/stdout str (it req)) 366 | "deadlink")) 367 | 368 | ; For asynchronous calls; discards the page. Would be better to tell 369 | ; the fn not to generate it. 370 | 371 | (defop-raw a (str req) 372 | (aif (fns* (sym (arg req "fnid"))) 373 | (tostring (it req)))) 374 | 375 | (defopr r req 376 | (aif (fns* (sym (arg req "fnid"))) 377 | (it req) 378 | "deadlink")) 379 | 380 | (defop deadlink req 381 | (pr dead-msg*)) 382 | 383 | (def url-for (fnid) 384 | (string fnurl* "?fnid=" fnid)) 385 | 386 | (def flink (f) 387 | (string fnurl* "?fnid=" (fnid (fn (req) (prn) (f req))))) 388 | 389 | (def rflink (f) 390 | (string rfnurl* "?fnid=" (fnid f))) 391 | 392 | ; Since it's just an expr, gensym a parm for (ignored) args. 393 | 394 | (mac w/link (expr . body) 395 | `(tag (a href (flink (fn (,(uniq)) ,expr))) 396 | ,@body)) 397 | 398 | (mac w/rlink (expr . body) 399 | `(tag (a href (rflink (fn (,(uniq)) ,expr))) 400 | ,@body)) 401 | 402 | (mac onlink (text . body) 403 | `(w/link (do ,@body) (pr ,text))) 404 | 405 | (mac onrlink (text . body) 406 | `(w/rlink (do ,@body) (pr ,text))) 407 | 408 | ; bad to have both flink and linkf; rename flink something like fnid-link 409 | 410 | (mac linkf (text parms . body) 411 | `(tag (a href (flink (fn ,parms ,@body))) (pr ,text))) 412 | 413 | (mac rlinkf (text parms . body) 414 | `(tag (a href (rflink (fn ,parms ,@body))) (pr ,text))) 415 | 416 | ;(defop top req (linkf 'whoami? (req) (pr "I am " (get-user req)))) 417 | 418 | ;(defop testf req (w/link (pr "ha ha ha") (pr "laugh"))) 419 | 420 | (mac w/link-if (test expr . body) 421 | `(tag-if ,test (a href (flink (fn (,(uniq)) ,expr))) 422 | ,@body)) 423 | 424 | (def fnid-field (id) 425 | (gentag input type 'hidden name 'fnid value id)) 426 | 427 | ; f should be a fn of one arg, which will be http request args. 428 | 429 | (def fnform (f bodyfn (o redir)) 430 | (tag (form method 'post action (if redir rfnurl2* fnurl*)) 431 | (fnid-field (fnid f)) 432 | (bodyfn))) 433 | 434 | ; Could also make a version that uses just an expr, and var capture. 435 | ; Is there a way to ensure user doesn't use "fnid" as a key? 436 | 437 | (mac aform (f . body) 438 | (w/uniq ga 439 | `(tag (form method 'post action fnurl*) 440 | (fnid-field (fnid (fn (,ga) 441 | (prn) 442 | (,f ,ga)))) 443 | ,@body))) 444 | 445 | ;(defop test1 req 446 | ; (fnform (fn (req) (prn) (pr req)) 447 | ; (fn () (single-input "" 'foo 20 "submit")))) 448 | 449 | ;(defop test2 req 450 | ; (aform (fn (req) (pr req)) 451 | ; (single-input "" 'foo 20 "submit"))) 452 | 453 | ; Like aform except creates a fnid that will last for lasts seconds 454 | ; (unless the server is restarted). 455 | 456 | (mac timed-aform (lasts f . body) 457 | (w/uniq (gl gf gi ga) 458 | `(withs (,gl ,lasts 459 | ,gf (fn (,ga) (prn) (,f ,ga))) 460 | (tag (form method 'post action fnurl*) 461 | (fnid-field (if ,gl (timed-fnid ,gl ,gf) (fnid ,gf))) 462 | ,@body)))) 463 | 464 | (mac arform (f . body) 465 | `(tag (form method 'post action rfnurl*) 466 | (fnid-field (fnid ,f)) 467 | ,@body)) 468 | 469 | ; overlong 470 | 471 | (mac tarform (lasts f . body) 472 | (w/uniq (gl gf) 473 | `(withs (,gl ,lasts ,gf ,f) 474 | (tag (form method 'post action rfnurl*) 475 | (fnid-field (if ,gl (timed-fnid ,gl ,gf) (fnid ,gf))) 476 | ,@body)))) 477 | 478 | (mac aformh (f . body) 479 | `(tag (form method 'post action fnurl*) 480 | (fnid-field (fnid ,f)) 481 | ,@body)) 482 | 483 | (mac arformh (f . body) 484 | `(tag (form method 'post action rfnurl2*) 485 | (fnid-field (fnid ,f)) 486 | ,@body)) 487 | 488 | ; only unique per server invocation 489 | 490 | (= unique-ids* (table)) 491 | 492 | (def unique-id ((o len 8)) 493 | (let id (sym (rand-string (max 5 len))) 494 | (if (unique-ids* id) 495 | (unique-id) 496 | (= (unique-ids* id) id)))) 497 | 498 | (def srvlog (type . args) 499 | (w/appendfile o (logfile-name type) 500 | (w/stdout o (atomic (apply prs (seconds) args) (prn))))) 501 | 502 | (def logfile-name (type) 503 | (string logdir* type "-" (memodate))) 504 | 505 | (with (lastasked nil lastval nil) 506 | 507 | (def memodate () 508 | (let now (seconds) 509 | (if (or (no lastasked) (> (- now lastasked) 60)) 510 | (= lastasked now lastval (datestring)) 511 | lastval))) 512 | 513 | ) 514 | 515 | (defop || req (pr "It's alive.")) 516 | 517 | (defop topips req 518 | (when (admin (get-user req)) 519 | (whitepage 520 | (sptab 521 | (each ip (let leaders nil 522 | (maptable (fn (ip n) 523 | (when (> n 100) 524 | (insort (compare > requests/ip*) 525 | ip 526 | leaders))) 527 | requests/ip*) 528 | leaders) 529 | (let n (requests/ip* ip) 530 | (row ip n (pr (num (* 100 (/ n requests*)) 1))))))))) 531 | 532 | (defop spurned req 533 | (when (admin (get-user req)) 534 | (whitepage 535 | (sptab 536 | (map (fn ((ip n)) (row ip n)) 537 | (sortable spurned*)))))) 538 | 539 | ; eventually promote to general util 540 | 541 | (def sortable (ht (o f >)) 542 | (let res nil 543 | (maptable (fn kv 544 | (insort (compare f cadr) kv res)) 545 | ht) 546 | res)) 547 | 548 | 549 | ; Background Threads 550 | 551 | (= bgthreads* (table) pending-bgthreads* nil) 552 | 553 | (def new-bgthread (id f sec) 554 | (aif (bgthreads* id) (break-thread it)) 555 | (= (bgthreads* id) (new-thread (fn () 556 | (while t 557 | (sleep sec) 558 | (f)))))) 559 | 560 | ; should be a macro for this? 561 | 562 | (mac defbg (id sec . body) 563 | `(do (pull [caris _ ',id] pending-bgthreads*) 564 | (push (list ',id (fn () ,@body) ,sec) 565 | pending-bgthreads*))) 566 | 567 | 568 | 569 | ; Idea: make form fields that know their value type because of 570 | ; gensymed names, and so the receiving fn gets args that are not 571 | ; strings but parsed values. 572 | 573 | -------------------------------------------------------------------------------- /app.arc: -------------------------------------------------------------------------------- 1 | ; Application Server. Layer inserted 2 Sep 06. 2 | 3 | ; ideas: 4 | ; def a general notion of apps of which prompt is one, news another 5 | ; give each user a place to store data? A home dir? 6 | 7 | ; A user is simply a string: "pg". Use /whoami to test user cookie. 8 | 9 | (= hpwfile* "arc/hpw" 10 | oidfile* "arc/openids" 11 | adminfile* "arc/admins" 12 | cookfile* "arc/cooks") 13 | 14 | (def asv ((o port 8080)) 15 | (load-userinfo) 16 | (serve port)) 17 | 18 | (def load-userinfo () 19 | (= hpasswords* (safe-load-table hpwfile*) 20 | openids* (safe-load-table oidfile*) 21 | admins* (map string (errsafe (readfile adminfile*))) 22 | cookie->user* (safe-load-table cookfile*)) 23 | (maptable (fn (k v) (= (user->cookie* v) k)) 24 | cookie->user*)) 25 | 26 | ; idea: a bidirectional table, so don't need two vars (and sets) 27 | 28 | (= cookie->user* (table) user->cookie* (table) logins* (table)) 29 | 30 | (def get-user (req) 31 | (let u (aand (alref req!cooks "user") (cookie->user* (sym it))) 32 | (when u (= (logins* u) req!ip)) 33 | u)) 34 | 35 | (mac when-umatch (user req . body) 36 | `(if (is ,user (get-user ,req)) 37 | (do ,@body) 38 | (mismatch-message))) 39 | 40 | (def mismatch-message () 41 | (prn "Dead link: users don't match.")) 42 | 43 | (mac when-umatch/r (user req . body) 44 | `(if (is ,user (get-user ,req)) 45 | (do ,@body) 46 | "mismatch")) 47 | 48 | (defop mismatch req (mismatch-message)) 49 | 50 | (mac uform (user req after . body) 51 | `(aform (fn (,req) 52 | (when-umatch ,user ,req 53 | ,after)) 54 | ,@body)) 55 | 56 | (mac urform (user req after . body) 57 | `(arform (fn (,req) 58 | (when-umatch/r ,user ,req 59 | ,after)) 60 | ,@body)) 61 | 62 | ; Like onlink, but checks that user submitting the request is the 63 | ; same it was generated for. For extra protection could log the 64 | ; username and ip addr of every genlink, and check if they match. 65 | 66 | (mac ulink (user text . body) 67 | (w/uniq req 68 | `(linkf ,text (,req) 69 | (when-umatch ,user ,req ,@body)))) 70 | 71 | 72 | (defop admin req (admin-gate (get-user req))) 73 | 74 | (def admin-gate (u) 75 | (if (admin u) 76 | (admin-page u) 77 | (login-page 'login nil 78 | (fn (u ip) (admin-gate u))))) 79 | 80 | (def admin (u) (and u (mem u admins*))) 81 | 82 | (def user-exists (u) (and u (hpasswords* u) u)) 83 | 84 | (def admin-page (user . msg) 85 | (whitepage 86 | (prbold "Admin: ") 87 | (hspace 20) 88 | (pr user " | ") 89 | (w/link (do (logout-user user) 90 | (whitepage (pr "Bye " user "."))) 91 | (pr "logout")) 92 | (when msg (hspace 10) (map pr msg)) 93 | (br2) 94 | (aform (fn (req) 95 | (when-umatch user req 96 | (with (u (arg req "u") p (arg req "p")) 97 | (if (or (no u) (no p) (is u "") (is p "")) 98 | (pr "Bad data.") 99 | (user-exists u) 100 | (admin-page user "User already exists: " u) 101 | (do (create-acct u p) 102 | (admin-page user)))))) 103 | (pwfields "create (server) account")))) 104 | 105 | (def cook-user (user) 106 | (let id (new-user-cookie) 107 | (= (cookie->user* id) user 108 | (user->cookie* user) id) 109 | (save-table cookie->user* cookfile*) 110 | id)) 111 | 112 | ; Unique-ids are only unique per server invocation. 113 | 114 | (def new-user-cookie () 115 | (let id (unique-id) 116 | (if (cookie->user* id) (new-user-cookie) id))) 117 | 118 | (def logout-user (user) 119 | (wipe (logins* user)) 120 | (wipe (cookie->user* (user->cookie* user)) (user->cookie* user)) 121 | (save-table cookie->user* cookfile*)) 122 | 123 | (def create-acct (user pw) 124 | (set (dc-usernames* (downcase user))) 125 | (set-pw user pw)) 126 | 127 | (def disable-acct (user) 128 | (set-pw user (rand-string 20)) 129 | (logout-user user)) 130 | 131 | (def set-pw (user pw) 132 | (= (hpasswords* user) (and pw (shash pw))) 133 | (save-table hpasswords* hpwfile*)) 134 | 135 | (def hello-page (user ip) 136 | (whitepage (prs "hello" user "at" ip))) 137 | 138 | (defop login req (login-page 'login)) 139 | 140 | ; switch is one of: register, login, both 141 | 142 | ; afterward is either a function on the newly created username and 143 | ; ip address, in which case it is called to generate the next page 144 | ; after a successful login, or a pair of (function url), which means 145 | ; call the function, then redirect to the url. 146 | 147 | ; classic example of something that should just "return" a val 148 | ; via a continuation rather than going to a new page. 149 | 150 | (def login-page (switch (o msg nil) (o afterward hello-page)) 151 | (whitepage 152 | (pagemessage msg) 153 | (when (in switch 'login 'both) 154 | (login-form "Login" switch login-handler afterward) 155 | (hook 'login-form afterward) 156 | (br2)) 157 | (when (in switch 'register 'both) 158 | (login-form "Create Account" switch create-handler afterward)))) 159 | 160 | (def login-form (label switch handler afterward) 161 | (prbold label) 162 | (br2) 163 | (fnform (fn (req) (handler req switch afterward)) 164 | (fn () (pwfields (downcase label))) 165 | (acons afterward))) 166 | 167 | (def login-handler (req switch afterward) 168 | (logout-user (get-user req)) 169 | (aif (good-login (arg req "u") (arg req "p") req!ip) 170 | (login it req!ip (user->cookie* it) afterward) 171 | (failed-login switch "Bad login." afterward))) 172 | 173 | (def create-handler (req switch afterward) 174 | (logout-user (get-user req)) 175 | (with (user (arg req "u") pw (arg req "p")) 176 | (aif (bad-newacct user pw) 177 | (failed-login switch it afterward) 178 | (do (create-acct user pw) 179 | (login user req!ip (cook-user user) afterward))))) 180 | 181 | (def login (user ip cookie afterward) 182 | (= (logins* user) ip) 183 | (prcookie cookie) 184 | (if (acons afterward) 185 | (let (f url) afterward 186 | (f user ip) 187 | url) 188 | (do (prn) 189 | (afterward user ip)))) 190 | 191 | (def failed-login (switch msg afterward) 192 | (if (acons afterward) 193 | (flink (fn ignore (login-page switch msg afterward))) 194 | (do (prn) 195 | (login-page switch msg afterward)))) 196 | 197 | (def prcookie (cook) 198 | (prn "Set-Cookie: user=" cook "; expires=Sun, 17-Jan-2038 19:14:07 GMT")) 199 | 200 | (def pwfields ((o label "login")) 201 | (inputs u username 20 nil 202 | p password 20 nil) 203 | (br) 204 | (submit label)) 205 | 206 | (= good-logins* (queue) bad-logins* (queue)) 207 | 208 | (def good-login (user pw ip) 209 | (let record (list (seconds) ip user) 210 | (if (and user pw (aand (shash pw) (is it (hpasswords* user)))) 211 | (do (unless (user->cookie* user) (cook-user user)) 212 | (enq-limit record good-logins*) 213 | user) 214 | (do (enq-limit record bad-logins*) 215 | nil)))) 216 | 217 | ; Create a file in case people have quote chars in their pws. I can't 218 | ; believe there's no way to just send the chars. 219 | 220 | (def shash (str) 221 | (let fname (+ "/tmp/shash" (rand-string 10)) 222 | (w/outfile f fname (disp str f)) 223 | (let res (tostring (system (+ "openssl dgst -sha1 <" fname))) 224 | (do1 (cut res 0 (- (len res) 1)) 225 | (rmfile fname))))) 226 | 227 | (= dc-usernames* (table)) 228 | 229 | (def username-taken (user) 230 | (when (empty dc-usernames*) 231 | (each (k v) hpasswords* 232 | (set (dc-usernames* (downcase k))))) 233 | (dc-usernames* (downcase user))) 234 | 235 | (def bad-newacct (user pw) 236 | (if (no (goodname user 2 15)) 237 | "Usernames can only contain letters, digits, dashes and 238 | underscores, and should be between 2 and 15 characters long. 239 | Please choose another." 240 | (username-taken user) 241 | "That username is taken. Please choose another." 242 | (or (no pw) (< (len pw) 4)) 243 | "Passwords should be a least 4 characters long. Please 244 | choose another." 245 | nil)) 246 | 247 | (def goodname (str (o min 1) (o max nil)) 248 | (and (isa str 'string) 249 | (>= (len str) min) 250 | (~find (fn (c) (no (or (alphadig c) (in c #\- #\_)))) 251 | str) 252 | (isnt (str 0) #\-) 253 | (or (no max) (<= (len str) max)) 254 | str)) 255 | 256 | (defop logout req 257 | (aif (get-user req) 258 | (do (logout-user it) 259 | (pr "Logged out.")) 260 | (pr "You were not logged in."))) 261 | 262 | (defop whoami req 263 | (aif (get-user req) 264 | (prs it 'at req!ip) 265 | (do (pr "You are not logged in. ") 266 | (w/link (login-page 'both) (pr "Log in")) 267 | (pr ".")))) 268 | 269 | 270 | (= formwid* 60 bigformwid* 80 numwid* 16 formatdoc-url* nil) 271 | 272 | ; Eventually figure out a way to separate type name from format of 273 | ; input field, instead of having e.g. toks and bigtoks 274 | 275 | (def varfield (typ id val) 276 | (if (in typ 'string 'string1 'url) 277 | (gentag input type 'text name id value val size formwid*) 278 | (in typ 'num 'int 'posint 'sym) 279 | (gentag input type 'text name id value val size numwid*) 280 | (in typ 'users 'toks) 281 | (gentag input type 'text name id value (tostring (apply prs val)) 282 | size formwid*) 283 | (is typ 'sexpr) 284 | (gentag input type 'text name id 285 | value (tostring (map [do (write _) (sp)] val)) 286 | size formwid*) 287 | (in typ 'syms 'text 'doc 'mdtext 'mdtext2 'lines 'bigtoks) 288 | (let text (if (in typ 'syms 'bigtoks) 289 | (tostring (apply prs val)) 290 | (is typ 'lines) 291 | (tostring (apply pr (intersperse #\newline val))) 292 | (in typ 'mdtext 'mdtext2) 293 | (unmarkdown val) 294 | (no val) 295 | "" 296 | val) 297 | (tag (textarea cols (if (is typ 'doc) bigformwid* formwid*) 298 | rows (needrows text formwid* 4) 299 | wrap 'virtual 300 | style (if (is typ 'doc) "font-size:8.5pt") 301 | name id) 302 | (prn) ; needed or 1 initial newline gets chopped off 303 | (pr text)) 304 | (when (and formatdoc-url* (in typ 'mdtext 'mdtext2)) 305 | (pr " ") 306 | (tag (font size -2) 307 | (link "help" formatdoc-url* (gray 175))))) 308 | (caris typ 'choice) 309 | (menu id (cddr typ) val) 310 | (is typ 'yesno) 311 | (menu id '("yes" "no") (if val "yes" "no")) 312 | (is typ 'hexcol) 313 | (gentag input type 'text name id value val) 314 | (is typ 'time) 315 | (gentag input type 'text name id value (if val (english-time val) "")) 316 | (is typ 'date) 317 | (gentag input type 'text name id value (if val (english-date val) "")) 318 | (err "unknown varfield type" typ))) 319 | 320 | (def text-rows (text wid (o pad 3)) 321 | (+ (trunc (/ (len text) (* wid .8))) pad)) 322 | 323 | (def needrows (text cols (o pad 0)) 324 | (+ pad (max (+ 1 (count #\newline text)) 325 | (roundup (/ (len text) (- cols 5)))))) 326 | 327 | (def varline (typ id val (o liveurls)) 328 | (if (in typ 'users 'syms 'toks 'bigtoks) (apply prs val) 329 | (is typ 'lines) (map prn val) 330 | (is typ 'yesno) (pr (if val 'yes 'no)) 331 | (caris typ 'choice) (varline (cadr typ) nil val) 332 | (is typ 'url) (if (and liveurls (valid-url val)) 333 | (link val val) 334 | (pr val)) 335 | (text-type typ) (pr (or val "")) 336 | (pr val))) 337 | 338 | (def text-type (typ) (in typ 'string 'string1 'url 'text 'mdtext 'mdtext2)) 339 | 340 | ; Newlines in forms come back as /r/n. Only want the /ns. Currently 341 | ; remove the /rs in individual cases below. Could do it in aform or 342 | ; even in the parsing of http requests, in the server. 343 | 344 | ; Need the calls to striptags so that news users can't get html 345 | ; into a title or comment by editing it. If want a form that 346 | ; can take html, just create another typ for it. 347 | 348 | (def readvar (typ str (o fail nil)) 349 | (case (carif typ) 350 | string (striptags str) 351 | string1 (if (blank str) fail (striptags str)) 352 | url (if (blank str) "" (valid-url str) (clean-url str) fail) 353 | num (let n (saferead str) (if (number n) n fail)) 354 | int (let n (saferead str) 355 | (if (number n) (round n) fail)) 356 | posint (let n (saferead str) 357 | (if (and (number n) (> n 0)) (round n) fail)) 358 | text (striptags str) 359 | doc (striptags str) 360 | mdtext (md-from-form str) 361 | mdtext2 (md-from-form str t) ; for md with no links 362 | sym (or (sym:car:tokens str) fail) 363 | syms (map sym (tokens str)) 364 | sexpr (errsafe (readall str)) 365 | users (rem [no (goodname _)] (tokens str)) 366 | toks (tokens str) 367 | bigtoks (tokens str) 368 | lines (lines str) 369 | choice (readvar (cadr typ) str) 370 | yesno (is str "yes") 371 | hexcol (if (hex>color str) str fail) 372 | time (or (errsafe (parse-time str)) fail) 373 | date (or (errsafe (parse-date str)) fail) 374 | (err "unknown readvar type" typ))) 375 | 376 | (= fail* (uniq)) 377 | 378 | ; Takes a list of fields of the form (type label value view modify) and 379 | ; a fn f and generates a form such that when submitted (f label newval) 380 | ; will be called for each valid value. Finally done is called. 381 | 382 | (def vars-form (user fields f done (o button "update") (o lasts)) 383 | (timed-aform lasts 384 | (if (all [no (_ 4)] fields) 385 | (fn (req)) 386 | (fn (req) 387 | (when-umatch user req 388 | (each (k v) req!args 389 | (let name (sym k) 390 | (awhen (find [is (cadr _) name] fields) 391 | ; added sho to fix bug 392 | (let (typ id val sho mod) it 393 | (when (and mod v) 394 | (let newval (readvar typ v fail*) 395 | (unless (is newval fail*) 396 | (f name newval)))))))) 397 | (done)))) 398 | (tab 399 | (showvars fields)) 400 | (unless (all [no (_ 4)] fields) ; no modifiable fields 401 | (br) 402 | (submit button)))) 403 | 404 | (def showvars (fields (o liveurls)) 405 | (each (typ id val view mod question) fields 406 | (when view 407 | (when question 408 | (tr (td (prn question)))) 409 | (tr (unless question (tag (td valign 'top) (pr id ":"))) 410 | (td (if mod 411 | (varfield typ id val) 412 | (varline typ id val liveurls)))) 413 | (prn)))) 414 | 415 | ; http://daringfireball.net/projects/markdown/syntax 416 | 417 | (def md-from-form (str (o nolinks)) 418 | (markdown (trim (rem #\return (esc<>& str)) 'end) 60 nolinks)) 419 | 420 | (def markdown (s (o maxurl) (o nolinks)) 421 | (let ital nil 422 | (tostring 423 | (forlen i s 424 | (iflet (newi spaces) (indented-code s i (if (is i 0) 2 0)) 425 | (do (pr "

")
426 |                  (let cb (code-block s (- newi spaces 1))
427 |                    (pr cb)
428 |                    (= i (+ (- newi spaces 1) (len cb))))
429 |                  (pr "
")) 430 | (iflet newi (parabreak s i (if (is i 0) 1 0)) 431 | (do (unless (is i 0) (pr "

")) 432 | (= i (- newi 1))) 433 | (and (is (s i) #\*) 434 | (or ital 435 | (atend i s) 436 | (and (~whitec (s (+ i 1))) 437 | (pos #\* s (+ i 1))))) 438 | (do (pr (if ital "" "")) 439 | (= ital (no ital))) 440 | (and (no nolinks) 441 | (or (litmatch "http://" s i) 442 | (litmatch "https://" s i))) 443 | (withs (n (urlend s i) 444 | url (clean-url (cut s i n))) 445 | (tag (a href url rel 'nofollow) 446 | (pr (if (no maxurl) url (ellipsize url maxurl)))) 447 | (= i (- n 1))) 448 | (writec (s i)))))))) 449 | 450 | (def indented-code (s i (o newlines 0) (o spaces 0)) 451 | (let c (s i) 452 | (if (nonwhite c) 453 | (if (and (> newlines 1) (> spaces 1)) 454 | (list i spaces) 455 | nil) 456 | (atend i s) 457 | nil 458 | (is c #\newline) 459 | (indented-code s (+ i 1) (+ newlines 1) 0) 460 | (indented-code s (+ i 1) newlines (+ spaces 1))))) 461 | 462 | ; If i is start a paragraph break, returns index of start of next para. 463 | 464 | (def parabreak (s i (o newlines 0)) 465 | (let c (s i) 466 | (if (or (nonwhite c) (atend i s)) 467 | (if (> newlines 1) i nil) 468 | (parabreak s (+ i 1) (+ newlines (if (is c #\newline) 1 0)))))) 469 | 470 | ; Returns the indices of the next paragraph break in s, if any. 471 | 472 | (def next-parabreak (s i) 473 | (unless (atend i s) 474 | (aif (parabreak s i) 475 | (list i it) 476 | (next-parabreak s (+ i 1))))) 477 | 478 | (def paras (s (o i 0)) 479 | (if (atend i s) 480 | nil 481 | (iflet (endthis startnext) (next-parabreak s i) 482 | (cons (cut s i endthis) 483 | (paras s startnext)) 484 | (list (trim (cut s i) 'end))))) 485 | 486 | 487 | ; Returns the index of the first char not part of the url beginning 488 | ; at i, or len of string if url goes all the way to the end. 489 | 490 | ; Note that > immediately after a url (http://foo.com>) will cause 491 | ; an odd result, because the > gets escaped to something beginning 492 | ; with &, which is treated as part of the url. Perhaps the answer 493 | ; is just to esc<>& after markdown instead of before. 494 | 495 | ; Treats a delimiter as part of a url if it is (a) an open delimiter 496 | ; not followed by whitespace or eos, or (b) a close delimiter 497 | ; balancing a previous open delimiter. 498 | 499 | (def urlend (s i (o indelim)) 500 | (let c (s i) 501 | (if (atend i s) 502 | (if ((orf punc whitec opendelim) c) 503 | i 504 | (closedelim c) 505 | (if indelim (+ i 1) i) 506 | (+ i 1)) 507 | (if (or (whitec c) 508 | (and (punc c) (whitec (s (+ i 1)))) 509 | (and ((orf whitec punc) (s (+ i 1))) 510 | (or (opendelim c) 511 | (and (closedelim c) (no indelim))))) 512 | i 513 | (urlend s (+ i 1) (or (opendelim c) 514 | (and indelim (no (closedelim c))))))))) 515 | 516 | (def opendelim (c) (in c #\< #\( #\[ #\{)) 517 | 518 | (def closedelim (c) (in c #\> #\) #\] #\})) 519 | 520 | 521 | (def code-block (s i) 522 | (tostring 523 | (until (let left (- (len s) i 1) 524 | (or (is left 0) 525 | (and (> left 2) 526 | (is (s (+ i 1)) #\newline) 527 | (nonwhite (s (+ i 2)))))) 528 | (writec (s (++ i)))))) 529 | 530 | (def unmarkdown (s) 531 | (tostring 532 | (forlen i s 533 | (if (litmatch "

" s i) 534 | (do (++ i 2) 535 | (unless (is i 2) (pr "\n\n"))) 536 | (litmatch "" s i) 537 | (do (++ i 2) (pr #\*)) 538 | (litmatch "" s i) 539 | (do (++ i 3) (pr #\*)) 540 | (litmatch "" s endurl) 545 | (+ it 3) 546 | endurl))) 547 | (writec (s i)))) 548 | (litmatch "

" s i)
549 |            (awhen (findsubseq "
" s (+ i 12)) 550 | (pr (cut s (+ i 11) it)) 551 | (= i (+ it 12))) 552 | (writec (s i)))))) 553 | 554 | 555 | (def english-time (min) 556 | (let n (mod min 720) 557 | (string (let h (trunc (/ n 60)) (if (is h 0) "12" h)) 558 | ":" 559 | (let m (mod n 60) 560 | (if (is m 0) "00" 561 | (< m 10) (string "0" m) 562 | m)) 563 | (if (is min 0) " midnight" 564 | (is min 720) " noon" 565 | (>= min 720) " pm" 566 | " am")))) 567 | 568 | (def parse-time (s) 569 | (let (nums (o label "")) (halve s letter) 570 | (with ((h (o m 0)) (map int (tokens nums ~digit)) 571 | cleanlabel (downcase (rem ~alphadig label))) 572 | (+ (* (if (is h 12) 573 | (if (in cleanlabel "am" "midnight") 574 | 0 575 | 12) 576 | (is cleanlabel "pm") 577 | (+ h 12) 578 | h) 579 | 60) 580 | m)))) 581 | 582 | 583 | (= months* '("January" "February" "March" "April" "May" "June" "July" 584 | "August" "September" "October" "November" "December")) 585 | 586 | (def english-date ((y m d)) 587 | (string d " " (months* (- m 1)) " " y)) 588 | 589 | (= month-names* (obj "january" 1 "jan" 1 590 | "february" 2 "feb" 2 591 | "march" 3 "mar" 3 592 | "april" 4 "apr" 4 593 | "may" 5 594 | "june" 6 "jun" 6 595 | "july" 7 "jul" 7 596 | "august" 8 "aug" 8 597 | "september" 9 "sept" 9 "sep" 9 598 | "october" 10 "oct" 10 599 | "november" 11 "nov" 11 600 | "december" 12 "dec" 12)) 601 | 602 | (def monthnum (s) (month-names* (downcase s))) 603 | 604 | ; Doesn't work for BC dates. 605 | 606 | (def parse-date (s) 607 | (let nums (date-nums s) 608 | (if (valid-date nums) 609 | nums 610 | (err (string "Invalid date: " s))))) 611 | 612 | (def date-nums (s) 613 | (with ((ynow mnow dnow) (date) 614 | toks (tokens s ~alphadig)) 615 | (if (all [all digit _] toks) 616 | (let nums (map int toks) 617 | (case (len nums) 618 | 1 (list ynow mnow (car nums)) 619 | 2 (iflet d (find [> _ 12] nums) 620 | (list ynow (find [isnt _ d] nums) d) 621 | (cons ynow nums)) 622 | (if (> (car nums) 31) 623 | (firstn 3 nums) 624 | (rev (firstn 3 nums))))) 625 | ([all digit _] (car toks)) 626 | (withs ((ds ms ys) toks 627 | d (int ds)) 628 | (aif (monthnum ms) 629 | (list (or (errsafe (int ys)) ynow) 630 | it 631 | d) 632 | nil)) 633 | (monthnum (car toks)) 634 | (let (ms ds ys) toks 635 | (aif (errsafe (int ds)) 636 | (list (or (errsafe (int ys)) ynow) 637 | (monthnum (car toks)) 638 | it) 639 | nil)) 640 | nil))) 641 | 642 | ; To be correct needs to know days per month, and about leap years 643 | 644 | (def valid-date ((y m d)) 645 | (and y m d 646 | (< 0 m 13) 647 | (< 0 d 32))) 648 | 649 | (mac defopl (name parm . body) 650 | `(defop ,name ,parm 651 | (if (get-user ,parm) 652 | (do ,@body) 653 | (login-page 'both 654 | "You need to be logged in to do that." 655 | (list (fn (u ip)) 656 | (string ',name (reassemble-args ,parm))))))) 657 | 658 | -------------------------------------------------------------------------------- /ac.scm: -------------------------------------------------------------------------------- 1 | ; Arc Compiler. 2 | 3 | (module ac mzscheme 4 | 5 | (provide (all-defined)) 6 | ; uncomment the following require for mzscheme-4.x 7 | ; much of Arc will work, but not mutable pairs. 8 | ; (require rnrs/mutable-pairs-6) 9 | (require (lib "port.ss")) 10 | (require (lib "process.ss")) 11 | (require (lib "pretty.ss")) 12 | 13 | ; compile an Arc expression into a Scheme expression, 14 | ; both represented as s-expressions. 15 | ; env is a list of lexically bound variables, which we 16 | ; need in order to decide whether set should create a global. 17 | 18 | (define (ac s env) 19 | (cond ((string? s) (ac-string s env)) 20 | ((literal? s) s) 21 | ((eqv? s 'nil) (list 'quote 'nil)) 22 | ((ssyntax? s) (ac (expand-ssyntax s) env)) 23 | ((symbol? s) (ac-var-ref s env)) 24 | ((ssyntax? (xcar s)) (ac (cons (expand-ssyntax (car s)) (cdr s)) env)) 25 | ((eq? (xcar s) 'quote) (list 'quote (ac-niltree (cadr s)))) 26 | ((eq? (xcar s) 'quasiquote) (ac-qq (cadr s) env)) 27 | ((eq? (xcar s) 'if) (ac-if (cdr s) env)) 28 | ((eq? (xcar s) 'fn) (ac-fn (cadr s) (cddr s) env)) 29 | ((eq? (xcar s) 'assign) (ac-set (cdr s) env)) 30 | ; the next three clauses could be removed without changing semantics 31 | ; ... except that they work for macros (so prob should do this for 32 | ; every elt of s, not just the car) 33 | ((eq? (xcar (xcar s)) 'compose) (ac (decompose (cdar s) (cdr s)) env)) 34 | ((eq? (xcar (xcar s)) 'complement) 35 | (ac (list 'no (cons (cadar s) (cdr s))) env)) 36 | ((eq? (xcar (xcar s)) 'andf) (ac-andf s env)) 37 | ((pair? s) (ac-call (car s) (cdr s) env)) 38 | (#t (err "Bad object in expression" s)))) 39 | 40 | (define atstrings #f) 41 | 42 | (define (ac-string s env) 43 | (if atstrings 44 | (if (atpos s 0) 45 | (ac (cons 'string (map (lambda (x) 46 | (if (string? x) 47 | (unescape-ats x) 48 | x)) 49 | (codestring s))) 50 | env) 51 | (unescape-ats s)) 52 | (string-copy s))) ; avoid immutable strings 53 | 54 | (define (literal? x) 55 | (or (boolean? x) 56 | (char? x) 57 | (string? x) 58 | (number? x) 59 | (eq? x '()))) 60 | 61 | (define (ssyntax? x) 62 | (and (symbol? x) 63 | (not (or (eqv? x '+) (eqv? x '++) (eqv? x '_))) 64 | (let ((name (symbol->string x))) 65 | (has-ssyntax-char? name (- (string-length name) 1))))) 66 | 67 | (define (has-ssyntax-char? string i) 68 | (and (>= i 0) 69 | (or (let ((c (string-ref string i))) 70 | (or (eqv? c #\:) (eqv? c #\~) 71 | (eqv? c #\+) 72 | ;(eqv? c #\_) 73 | (eqv? c #\.) (eqv? c #\!))) 74 | (has-ssyntax-char? string (- i 1))))) 75 | 76 | (define (read-from-string str) 77 | (let ((port (open-input-string str))) 78 | (let ((val (read port))) 79 | (close-input-port port) 80 | val))) 81 | 82 | ; Though graphically the right choice, can't use _ for currying 83 | ; because then _!foo becomes a function. Maybe use <>. For now 84 | ; leave this off and see how often it would have been useful. 85 | 86 | ; Might want to make ~ have less precedence than +, because 87 | ; ~foo+bar prob should mean (andf (complement foo) bar), not 88 | ; (complement (andf foo bar)). 89 | 90 | (define (expand-ssyntax sym) 91 | ((cond ((or (insym? #\: sym) (insym? #\~ sym)) expand-compose) 92 | ((insym? #\+ sym) expand-and) 93 | ; ((insym? #\_ sym) expand-curry) 94 | ((or (insym? #\. sym) (insym? #\! sym)) expand-sexpr) 95 | (#t (error "Unknown ssyntax" sym))) 96 | sym)) 97 | 98 | (define (expand-compose sym) 99 | (let ((elts (map (lambda (tok) 100 | (if (eqv? (car tok) #\~) 101 | (if (null? (cdr tok)) 102 | 'no 103 | `(complement ,(chars->value (cdr tok)))) 104 | (chars->value tok))) 105 | (tokens (lambda (c) (eqv? c #\:)) 106 | (symbol->chars sym) 107 | '() 108 | '() 109 | #f)))) 110 | (if (null? (cdr elts)) 111 | (car elts) 112 | (cons 'compose elts)))) 113 | 114 | (define (expand-and sym) 115 | (let ((elts (map chars->value 116 | (tokens (lambda (c) (eqv? c #\+)) 117 | (symbol->chars sym) 118 | '() 119 | '() 120 | #f)))) 121 | (if (null? (cdr elts)) 122 | (car elts) 123 | (cons 'andf elts)))) 124 | 125 | ; How to include quoted arguments? Can't treat all as quoted, because 126 | ; never want to quote fn given as first. Do we want to allow quote chars 127 | ; within symbols? Could be ugly. 128 | 129 | ; If release, fix the fact that this simply uses v0... as vars. Should 130 | ; make these vars gensyms. 131 | 132 | (define (expand-curry sym) 133 | (let ((expr (exc (map (lambda (x) 134 | (if (pair? x) (chars->value x) x)) 135 | (tokens (lambda (c) (eqv? c #\_)) 136 | (symbol->chars sym) 137 | '() 138 | '() 139 | #t)) 140 | 0))) 141 | (list 'fn 142 | (keep (lambda (s) 143 | (and (symbol? s) 144 | (eqv? (string-ref (symbol->string s) 0) 145 | #\v))) 146 | expr) 147 | expr))) 148 | 149 | (define (keep f xs) 150 | (cond ((null? xs) '()) 151 | ((f (car xs)) (cons (car xs) (keep f (cdr xs)))) 152 | (#t (keep f (cdr xs))))) 153 | 154 | (define (exc elts n) 155 | (cond ((null? elts) 156 | '()) 157 | ((eqv? (car elts) #\_) 158 | (cons (string->symbol (string-append "v" (number->string n))) 159 | (exc (cdr elts) (+ n 1)))) 160 | (#t 161 | (cons (car elts) (exc (cdr elts) n))))) 162 | 163 | (define (expand-sexpr sym) 164 | (build-sexpr (reverse (tokens (lambda (c) (or (eqv? c #\.) (eqv? c #\!))) 165 | (symbol->chars sym) 166 | '() 167 | '() 168 | #t)) 169 | sym)) 170 | 171 | (define (build-sexpr toks orig) 172 | (cond ((null? toks) 173 | 'get) 174 | ((null? (cdr toks)) 175 | (chars->value (car toks))) 176 | (#t 177 | (list (build-sexpr (cddr toks) orig) 178 | (if (eqv? (cadr toks) #\!) 179 | (list 'quote (chars->value (car toks))) 180 | (if (or (eqv? (car toks) #\.) (eqv? (car toks) #\!)) 181 | (err "Bad ssyntax" orig) 182 | (chars->value (car toks)))))))) 183 | 184 | (define (insym? char sym) (member char (symbol->chars sym))) 185 | 186 | (define (symbol->chars x) (string->list (symbol->string x))) 187 | 188 | (define (chars->value chars) (read-from-string (list->string chars))) 189 | 190 | (define (tokens test source token acc keepsep?) 191 | (cond ((null? source) 192 | (reverse (if (pair? token) 193 | (cons (reverse token) acc) 194 | acc))) 195 | ((test (car source)) 196 | (tokens test 197 | (cdr source) 198 | '() 199 | (let ((rec (if (null? token) 200 | acc 201 | (cons (reverse token) acc)))) 202 | (if keepsep? 203 | (cons (car source) rec) 204 | rec)) 205 | keepsep?)) 206 | (#t 207 | (tokens test 208 | (cdr source) 209 | (cons (car source) token) 210 | acc 211 | keepsep?)))) 212 | 213 | (define (ac-global-name s) 214 | (string->symbol (string-append "_" (symbol->string s)))) 215 | 216 | (define (ac-var-ref s env) 217 | (if (lex? s env) 218 | s 219 | (ac-global-name s))) 220 | 221 | ; quasiquote 222 | 223 | (define (ac-qq args env) 224 | (list 'quasiquote (ac-qq1 1 args env))) 225 | 226 | ; process the argument of a quasiquote. keep track of 227 | ; depth of nesting. handle unquote only at top level (level = 1). 228 | ; complete form, e.g. x or (fn x) or (unquote (fn x)) 229 | 230 | (define (ac-qq1 level x env) 231 | (cond ((= level 0) 232 | (ac x env)) 233 | ((and (pair? x) (eqv? (car x) 'unquote)) 234 | (list 'unquote (ac-qq1 (- level 1) (cadr x) env))) 235 | ((and (pair? x) (eqv? (car x) 'unquote-splicing) (= level 1)) 236 | (list 'unquote-splicing 237 | (list 'ar-nil-terminate (ac-qq1 (- level 1) (cadr x) env)))) 238 | ((and (pair? x) (eqv? (car x) 'quasiquote)) 239 | (list 'quasiquote (ac-qq1 (+ level 1) (cadr x) env))) 240 | ((pair? x) 241 | (map (lambda (x) (ac-qq1 level x env)) x)) 242 | (#t x))) 243 | 244 | ; (if) -> nil 245 | ; (if x) -> x 246 | ; (if t a ...) -> a 247 | ; (if nil a b) -> b 248 | ; (if nil a b c) -> (if b c) 249 | 250 | (define (ac-if args env) 251 | (cond ((null? args) ''nil) 252 | ((null? (cdr args)) (ac (car args) env)) 253 | (#t `(if (not (ar-false? ,(ac (car args) env))) 254 | ,(ac (cadr args) env) 255 | ,(ac-if (cddr args) env))))) 256 | 257 | (define (ac-dbname! name env) 258 | (if (symbol? name) 259 | (cons (list name) env) 260 | env)) 261 | 262 | (define (ac-dbname env) 263 | (cond ((null? env) #f) 264 | ((pair? (car env)) (caar env)) 265 | (#t (ac-dbname (cdr env))))) 266 | 267 | ; translate fn directly into a lambda if it has ordinary 268 | ; parameters, otherwise use a rest parameter and parse it. 269 | 270 | (define (ac-fn args body env) 271 | (if (ac-complex-args? args) 272 | (ac-complex-fn args body env) 273 | (ac-nameit 274 | (ac-dbname env) 275 | `(lambda ,(let ((a (ac-denil args))) (if (eqv? a 'nil) '() a)) 276 | ,@(ac-body* body (append (ac-arglist args) env)))))) 277 | 278 | ; does an fn arg list use optional parameters or destructuring? 279 | ; a rest parameter is not complex 280 | 281 | (define (ac-complex-args? args) 282 | (cond ((eqv? args '()) #f) 283 | ((symbol? args) #f) 284 | ((and (pair? args) (symbol? (car args))) 285 | (ac-complex-args? (cdr args))) 286 | (#t #t))) 287 | 288 | ; translate a fn with optional or destructuring args 289 | ; (fn (x (o y x) (o z 21) (x1 x2) . rest) ...) 290 | ; arguments in top-level list are mandatory (unless optional), 291 | ; but it's OK for parts of a list you're destructuring to 292 | ; be missing. 293 | 294 | (define (ac-complex-fn args body env) 295 | (let* ((ra (ar-gensym)) 296 | (z (ac-complex-args args env ra #t))) 297 | `(lambda ,ra 298 | (let* ,z 299 | ,@(ac-body* body (append (ac-complex-getargs z) env)))))) 300 | 301 | ; returns a list of two-element lists, first is variable name, 302 | ; second is (compiled) expression. to be used in a let. 303 | ; caller should extract variables and add to env. 304 | ; ra is the rest argument to the fn. 305 | ; is-params indicates that args are function arguments 306 | ; (not destructuring), so they must be passed or be optional. 307 | 308 | (define (ac-complex-args args env ra is-params) 309 | (cond ((or (eqv? args '()) (eqv? args 'nil)) '()) 310 | ((symbol? args) (list (list args ra))) 311 | ((pair? args) 312 | (let* ((x (if (and (pair? (car args)) (eqv? (caar args) 'o)) 313 | (ac-complex-opt (cadar args) 314 | (if (pair? (cddar args)) 315 | (caddar args) 316 | 'nil) 317 | env 318 | ra) 319 | (ac-complex-args 320 | (car args) 321 | env 322 | (if is-params 323 | `(car ,ra) 324 | `(ar-xcar ,ra)) 325 | #f))) 326 | (xa (ac-complex-getargs x))) 327 | (append x (ac-complex-args (cdr args) 328 | (append xa env) 329 | `(ar-xcdr ,ra) 330 | is-params)))) 331 | (#t (err "Can't understand fn arg list" args)))) 332 | 333 | ; (car ra) is the argument 334 | ; so it's not present if ra is nil or '() 335 | 336 | (define (ac-complex-opt var expr env ra) 337 | (list (list var `(if (pair? ,ra) (car ,ra) ,(ac expr env))))) 338 | 339 | ; extract list of variables from list of two-element lists. 340 | 341 | (define (ac-complex-getargs a) 342 | (map (lambda (x) (car x)) a)) 343 | 344 | ; (a b . c) -> (a b c) 345 | ; a -> (a) 346 | 347 | (define (ac-arglist a) 348 | (cond ((null? a) '()) 349 | ((symbol? a) (list a)) 350 | ((symbol? (cdr a)) (list (car a) (cdr a))) 351 | (#t (cons (car a) (ac-arglist (cdr a)))))) 352 | 353 | (define (ac-body body env) 354 | (map (lambda (x) (ac x env)) body)) 355 | 356 | ; like ac-body, but spits out a nil expression if empty 357 | 358 | (define (ac-body* body env) 359 | (if (null? body) 360 | (list (list 'quote 'nil)) 361 | (ac-body body env))) 362 | 363 | ; (set v1 expr1 v2 expr2 ...) 364 | 365 | (define (ac-set x env) 366 | `(begin ,@(ac-setn x env))) 367 | 368 | (define (ac-setn x env) 369 | (if (null? x) 370 | '() 371 | (cons (ac-set1 (ac-macex (car x)) (cadr x) env) 372 | (ac-setn (cddr x) env)))) 373 | 374 | ; trick to tell Scheme the name of something, so Scheme 375 | ; debugging and profiling make more sense. 376 | 377 | (define (ac-nameit name v) 378 | (if (symbol? name) 379 | (let ((n (string->symbol (string-append " " (symbol->string name))))) 380 | (list 'let `((,n ,v)) n)) 381 | v)) 382 | 383 | ; = replaced by set, which is only for vars 384 | ; = now defined in arc (is it?) 385 | ; name is to cause fns to have their arc names for debugging 386 | 387 | (define (ac-set1 a b1 env) 388 | (if (symbol? a) 389 | (let ((b (ac b1 (ac-dbname! a env)))) 390 | (list 'let `((zz ,b)) 391 | (cond ((eqv? a 'nil) (err "Can't rebind nil")) 392 | ((eqv? a 't) (err "Can't rebind t")) 393 | ((lex? a env) `(set! ,a zz)) 394 | (#t `(namespace-set-variable-value! ',(ac-global-name a) 395 | zz))) 396 | 'zz)) 397 | (err "First arg to set must be a symbol" a))) 398 | 399 | ; given a list of Arc expressions, return a list of Scheme expressions. 400 | ; for compiling passed arguments. 401 | 402 | (define (ac-args names exprs env) 403 | (if (null? exprs) 404 | '() 405 | (cons (ac (car exprs) 406 | (ac-dbname! (if (pair? names) (car names) #f) env)) 407 | (ac-args (if (pair? names) (cdr names) '()) 408 | (cdr exprs) 409 | env)))) 410 | 411 | ; generate special fast code for ordinary two-operand 412 | ; calls to the following functions. this is to avoid 413 | ; calling e.g. ar-is with its &rest and apply. 414 | 415 | (define ac-binaries 416 | '((is ar-is2) 417 | (< ar-<2) 418 | (> ar->2) 419 | (+ ar-+2))) 420 | 421 | ; (foo bar) where foo is a global variable bound to a procedure. 422 | 423 | (define (ac-global-call fn args env) 424 | (cond ((and (assoc fn ac-binaries) (= (length args) 2)) 425 | `(,(cadr (assoc fn ac-binaries)) ,@(ac-args '() args env))) 426 | (#t 427 | `(,(ac-global-name fn) ,@(ac-args '() args env))))) 428 | 429 | ; compile a function call 430 | ; special cases for speed, to avoid compiled output like 431 | ; (ar-apply _pr (list 1 2)) 432 | ; which results in 1/2 the CPU time going to GC. Instead: 433 | ; (ar-funcall2 _pr 1 2) 434 | ; and for (foo bar), if foo is a reference to a global variable, 435 | ; and it's bound to a function, generate (foo bar) instead of 436 | ; (ar-funcall1 foo bar) 437 | 438 | (define direct-calls #f) 439 | 440 | (define (ac-call fn args env) 441 | (let ((macfn (ac-macro? fn))) 442 | (cond (macfn 443 | (ac-mac-call macfn args env)) 444 | ((and (pair? fn) (eqv? (car fn) 'fn)) 445 | `(,(ac fn env) ,@(ac-args (cadr fn) args env))) 446 | ((and direct-calls (symbol? fn) (not (lex? fn env)) (bound? fn) 447 | (procedure? (namespace-variable-value (ac-global-name fn)))) 448 | (ac-global-call fn args env)) 449 | ((= (length args) 0) 450 | `(ar-funcall0 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 451 | ((= (length args) 1) 452 | `(ar-funcall1 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 453 | ((= (length args) 2) 454 | `(ar-funcall2 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 455 | ((= (length args) 3) 456 | `(ar-funcall3 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 457 | ((= (length args) 4) 458 | `(ar-funcall4 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 459 | (#t 460 | `(ar-apply ,(ac fn env) 461 | (list ,@(map (lambda (x) (ac x env)) args))))))) 462 | 463 | (define (ac-mac-call m args env) 464 | (let ((x1 (apply m (map ac-niltree args)))) 465 | (let ((x2 (ac (ac-denil x1) env))) 466 | x2))) 467 | 468 | ; returns #f or the macro function 469 | 470 | (define (ac-macro? fn) 471 | (if (symbol? fn) 472 | (let ((v (namespace-variable-value (ac-global-name fn) 473 | #t 474 | (lambda () #f)))) 475 | (if (and v 476 | (ar-tagged? v) 477 | (eq? (ar-type v) 'mac)) 478 | (ar-rep v) 479 | #f)) 480 | #f)) 481 | 482 | ; macroexpand the outer call of a form as much as possible 483 | 484 | (define (ac-macex e . once) 485 | (if (pair? e) 486 | (let ((m (ac-macro? (car e)))) 487 | (if m 488 | (let ((expansion (ac-denil (apply m (map ac-niltree (cdr e)))))) 489 | (if (null? once) (ac-macex expansion) expansion)) 490 | e)) 491 | e)) 492 | 493 | ; macros return Arc lists, ending with NIL. 494 | ; but the Arc compiler expects Scheme lists, ending with '(). 495 | ; what to do with (is x nil . nil) ? 496 | ; the first nil ought to be replaced with 'NIL 497 | ; the second with '() 498 | ; so the rule is: NIL in the car -> 'NIL, NIL in the cdr -> '(). 499 | ; NIL by itself -> NIL 500 | 501 | (define (ac-denil x) 502 | (cond ((pair? x) (cons (ac-denil-car (car x)) (ac-denil-cdr (cdr x)))) 503 | (#t x))) 504 | 505 | (define (ac-denil-car x) 506 | (if (eq? x 'nil) 507 | 'nil 508 | (ac-denil x))) 509 | 510 | (define (ac-denil-cdr x) 511 | (if (eq? x 'nil) 512 | '() 513 | (ac-denil x))) 514 | 515 | ; is v lexically bound? 516 | 517 | (define (lex? v env) 518 | (memq v env)) 519 | 520 | (define (xcar x) 521 | (and (pair? x) (car x))) 522 | 523 | ; #f and '() -> nil for a whole quoted list/tree. 524 | 525 | ; Arc primitives written in Scheme should look like: 526 | 527 | ; (xdef foo (lambda (lst) 528 | ; (ac-niltree (scheme-foo (ar-nil-terminate lst))))) 529 | 530 | ; That is, Arc lists are NIL-terminated. When calling a Scheme 531 | ; function that treats an argument as a list, call ar-nil-terminate 532 | ; to change NIL to '(). When returning any data created by Scheme 533 | ; to Arc, call ac-niltree to turn all '() into NIL. 534 | ; (hash-table-get doesn't use its argument as a list, so it doesn't 535 | ; need ar-nil-terminate). 536 | 537 | (define (ac-niltree x) 538 | (cond ((pair? x) (cons (ac-niltree (car x)) (ac-niltree (cdr x)))) 539 | ((or (eq? x #f) (eq? x '())) 'nil) 540 | (#t x))) 541 | 542 | ; The next two are optimizations, except work for macros. 543 | 544 | (define (decompose fns args) 545 | (cond ((null? fns) `((fn vals (car vals)) ,@args)) 546 | ((null? (cdr fns)) (cons (car fns) args)) 547 | (#t (list (car fns) (decompose (cdr fns) args))))) 548 | 549 | (define (ac-andf s env) 550 | (ac (let ((gs (map (lambda (x) (ar-gensym)) (cdr s)))) 551 | `((fn ,gs 552 | (and ,@(map (lambda (f) `(,f ,@gs)) 553 | (cdar s)))) 554 | ,@(cdr s))) 555 | env)) 556 | 557 | (define err error) 558 | 559 | ; run-time primitive procedures 560 | 561 | ;(define (xdef a b) 562 | ; (namespace-set-variable-value! (ac-global-name a) b) 563 | ; b) 564 | 565 | (define-syntax xdef 566 | (syntax-rules () 567 | ((xxdef a b) 568 | (let ((nm (ac-global-name 'a)) 569 | (a b)) 570 | (namespace-set-variable-value! nm a) 571 | a)))) 572 | 573 | (define fn-signatures (make-hash-table 'equal)) 574 | 575 | ; This is a replacement for xdef that stores opeator signatures. 576 | ; Haven't started using it yet. 577 | 578 | (define (odef a parms b) 579 | (namespace-set-variable-value! (ac-global-name a) b) 580 | (hash-table-put! fn-signatures a (list parms)) 581 | b) 582 | 583 | (xdef sig fn-signatures) 584 | 585 | ; versions of car and cdr for parsing arguments for optional 586 | ; parameters, that yield nil for nil. maybe we should use 587 | ; full Arc car and cdr, so we can destructure more things 588 | 589 | (define (ar-xcar x) 590 | (if (or (eqv? x 'nil) (eqv? x '())) 591 | 'nil 592 | (car x))) 593 | 594 | (define (ar-xcdr x) 595 | (if (or (eqv? x 'nil) (eqv? x '())) 596 | 'nil 597 | (cdr x))) 598 | 599 | ; convert #f from a Scheme predicate to NIL. 600 | 601 | (define (ar-nill x) 602 | (if (or (eq? x '()) (eq? x #f)) 603 | 'nil 604 | x)) 605 | 606 | ; definition of falseness for Arc if. 607 | ; must include '() since sometimes Arc functions see 608 | ; Scheme lists (e.g. . body of a macro). 609 | 610 | (define (ar-false? x) 611 | (or (eq? x 'nil) (eq? x '()) (eq? x #f))) 612 | 613 | ; call a function or perform an array ref, hash ref, &c 614 | 615 | ; Non-fn constants in functional position are valuable real estate, so 616 | ; should figure out the best way to exploit it. What could (1 foo) or 617 | ; ('a foo) mean? Maybe it should mean currying. 618 | 619 | ; For now the way to make the default val of a hash table be other than 620 | ; nil is to supply the val when doing the lookup. Later may also let 621 | ; defaults be supplied as an arg to table. To implement this, need: an 622 | ; eq table within scheme mapping tables to defaults, and to adapt the 623 | ; code in arc.arc that reads and writes tables to read and write their 624 | ; default vals with them. To make compatible with existing written tables, 625 | ; just use an atom or 3-elt list to keep the default. 626 | 627 | (define (ar-apply fn args) 628 | (cond ((procedure? fn) 629 | (apply fn args)) 630 | ((pair? fn) 631 | (list-ref fn (car args))) 632 | ((string? fn) 633 | (string-ref fn (car args))) 634 | ((hash-table? fn) 635 | (ar-nill (hash-table-get fn 636 | (car args) 637 | (if (pair? (cdr args)) (cadr args) #f)))) 638 | ; experiment: means e.g. [1] is a constant fn 639 | ; ((or (number? fn) (symbol? fn)) fn) 640 | ; another possibility: constant in functional pos means it gets 641 | ; passed to the first arg, i.e. ('kids item) means (item 'kids). 642 | (#t (err "Function call on inappropriate object" fn args)))) 643 | 644 | (xdef apply (lambda (fn . args) 645 | (ar-apply fn (ar-apply-args args)))) 646 | 647 | ; special cases of ar-apply for speed and to avoid consing arg lists 648 | 649 | (define (ar-funcall0 fn) 650 | (if (procedure? fn) 651 | (fn) 652 | (ar-apply fn (list)))) 653 | 654 | (define (ar-funcall1 fn arg1) 655 | (if (procedure? fn) 656 | (fn arg1) 657 | (ar-apply fn (list arg1)))) 658 | 659 | (define (ar-funcall2 fn arg1 arg2) 660 | (if (procedure? fn) 661 | (fn arg1 arg2) 662 | (ar-apply fn (list arg1 arg2)))) 663 | 664 | (define (ar-funcall3 fn arg1 arg2 arg3) 665 | (if (procedure? fn) 666 | (fn arg1 arg2 arg3) 667 | (ar-apply fn (list arg1 arg2 arg3)))) 668 | 669 | (define (ar-funcall4 fn arg1 arg2 arg3 arg4) 670 | (if (procedure? fn) 671 | (fn arg1 arg2 arg3 arg4) 672 | (ar-apply fn (list arg1 arg2 arg3 arg4)))) 673 | 674 | ; replace the nil at the end of a list with a '() 675 | 676 | (define (ar-nil-terminate l) 677 | (if (or (eqv? l '()) (eqv? l 'nil)) 678 | '() 679 | (cons (car l) (ar-nil-terminate (cdr l))))) 680 | 681 | ; turn the arguments to Arc apply into a list. 682 | ; if you call (apply fn 1 2 '(3 4)) 683 | ; then args is '(1 2 (3 4 . nil) . ()) 684 | ; that is, the main list is a scheme list. 685 | ; and we should return '(1 2 3 4 . ()) 686 | ; was once (apply apply list (ac-denil args)) 687 | ; but that didn't work for (apply fn nil) 688 | 689 | (define (ar-apply-args args) 690 | (cond ((null? args) '()) 691 | ((null? (cdr args)) (ar-nil-terminate (car args))) 692 | (#t (cons (car args) (ar-apply-args (cdr args)))))) 693 | 694 | 695 | 696 | 697 | 698 | (xdef cons cons) 699 | 700 | (xdef car (lambda (x) 701 | (cond ((pair? x) (car x)) 702 | ((eqv? x 'nil) 'nil) 703 | ((eqv? x '()) 'nil) 704 | (#t (err "Can't take car of" x))))) 705 | 706 | (xdef cdr (lambda (x) 707 | (cond ((pair? x) (cdr x)) 708 | ((eqv? x 'nil) 'nil) 709 | ((eqv? x '()) 'nil) 710 | (#t (err "Can't take cdr of" x))))) 711 | 712 | (define (tnil x) (if x 't 'nil)) 713 | 714 | ; (pairwise pred '(a b c d)) => 715 | ; (and (pred a b) (pred b c) (pred c d)) 716 | ; pred returns t/nil, as does pairwise 717 | ; reduce? 718 | 719 | (define (pairwise pred lst) 720 | (cond ((null? lst) 't) 721 | ((null? (cdr lst)) 't) 722 | ((not (eqv? (pred (car lst) (cadr lst)) 'nil)) 723 | (pairwise pred (cdr lst))) 724 | (#t 'nil))) 725 | 726 | ; not quite right, because behavior of underlying eqv unspecified 727 | ; in many cases according to r5rs 728 | ; do we really want is to ret t for distinct strings? 729 | 730 | ; for (is x y) 731 | 732 | (define (ar-is2 a b) 733 | (tnil (or (eqv? a b) 734 | (and (string? a) (string? b) (string=? a b)) 735 | (and (ar-false? a) (ar-false? b))))) 736 | 737 | ; for all other uses of is 738 | 739 | (xdef is (lambda args (pairwise ar-is2 args))) 740 | 741 | (xdef err err) 742 | (xdef nil 'nil) 743 | (xdef t 't) 744 | 745 | (define (all test seq) 746 | (or (null? seq) 747 | (and (test (car seq)) (all test (cdr seq))))) 748 | 749 | (define (arc-list? x) (or (pair? x) (eqv? x 'nil) (eqv? x '()))) 750 | 751 | ; Generic +: strings, lists, numbers. 752 | ; Return val has same type as first argument. 753 | 754 | (xdef + (lambda args 755 | (cond ((null? args) 0) 756 | ((char-or-string? (car args)) 757 | (apply string-append 758 | (map (lambda (a) (ar-coerce a 'string)) 759 | args))) 760 | ((arc-list? (car args)) 761 | (ac-niltree (apply append (map ar-nil-terminate args)))) 762 | (#t (apply + args))))) 763 | 764 | (define (char-or-string? x) (or (string? x) (char? x))) 765 | 766 | (define (ar-+2 x y) 767 | (cond ((char-or-string? x) 768 | (string-append (ar-coerce x 'string) (ar-coerce y 'string))) 769 | ((and (arc-list? x) (arc-list? y)) 770 | (ac-niltree (append (ar-nil-terminate x) (ar-nil-terminate y)))) 771 | (#t (+ x y)))) 772 | 773 | (xdef - -) 774 | (xdef * *) 775 | (xdef / /) 776 | (xdef mod modulo) 777 | (xdef expt expt) 778 | (xdef sqrt sqrt) 779 | 780 | ; generic comparison 781 | 782 | (define (ar->2 x y) 783 | (tnil (cond ((and (number? x) (number? y)) (> x y)) 784 | ((and (string? x) (string? y)) (string>? x y)) 785 | ((and (symbol? x) (symbol? y)) (string>? (symbol->string x) 786 | (symbol->string y))) 787 | ((and (char? x) (char? y)) (char>? x y)) 788 | (#t (> x y))))) 789 | 790 | (xdef > (lambda args (pairwise ar->2 args))) 791 | 792 | (define (ar-<2 x y) 793 | (tnil (cond ((and (number? x) (number? y)) (< x y)) 794 | ((and (string? x) (string? y)) (stringstring x) 796 | (symbol->string y))) 797 | ((and (char? x) (char? y)) (char sym 817 | 818 | (define (ar-type x) 819 | (cond ((ar-tagged? x) (vector-ref x 1)) 820 | ((pair? x) 'cons) 821 | ((symbol? x) 'sym) 822 | ((null? x) 'sym) 823 | ((procedure? x) 'fn) 824 | ((char? x) 'char) 825 | ((string? x) 'string) 826 | ((integer? x) 'int) 827 | ((number? x) 'num) ; unsure about this 828 | ((hash-table? x) 'table) 829 | ((output-port? x) 'output) 830 | ((input-port? x) 'input) 831 | ((tcp-listener? x) 'socket) 832 | ((exn? x) 'exception) 833 | ((thread? x) 'thread) 834 | (#t (err "Type: unknown type" x)))) 835 | (xdef type ar-type) 836 | 837 | (define (ar-rep x) 838 | (if (ar-tagged? x) 839 | (vector-ref x 2) 840 | x)) 841 | 842 | (xdef rep ar-rep) 843 | 844 | ; currently rather a joke: returns interned symbols 845 | 846 | (define ar-gensym-count 0) 847 | 848 | (define (ar-gensym) 849 | (set! ar-gensym-count (+ ar-gensym-count 1)) 850 | (string->symbol (string-append "gs" (number->string ar-gensym-count)))) 851 | 852 | (xdef uniq ar-gensym) 853 | 854 | (xdef ccc call-with-current-continuation) 855 | 856 | (xdef infile open-input-file) 857 | 858 | (xdef outfile (lambda (f . args) 859 | (open-output-file f 860 | 'text 861 | (if (equal? args '(append)) 862 | 'append 863 | 'truncate)))) 864 | 865 | (xdef instring open-input-string) 866 | (xdef outstring open-output-string) 867 | 868 | ; use as general fn for looking inside things 869 | 870 | (xdef inside get-output-string) 871 | 872 | (xdef stdout current-output-port) ; should be a vars 873 | (xdef stdin current-input-port) 874 | (xdef stderr current-error-port) 875 | 876 | (xdef call-w/stdout 877 | (lambda (port thunk) 878 | (parameterize ((current-output-port port)) (thunk)))) 879 | 880 | (xdef call-w/stdin 881 | (lambda (port thunk) 882 | (parameterize ((current-input-port port)) (thunk)))) 883 | 884 | (xdef readc (lambda str 885 | (let ((c (read-char (if (pair? str) 886 | (car str) 887 | (current-input-port))))) 888 | (if (eof-object? c) 'nil c)))) 889 | 890 | 891 | (xdef readb (lambda str 892 | (let ((c (read-byte (if (pair? str) 893 | (car str) 894 | (current-input-port))))) 895 | (if (eof-object? c) 'nil c)))) 896 | 897 | (xdef peekc (lambda str 898 | (let ((c (peek-char (if (pair? str) 899 | (car str) 900 | (current-input-port))))) 901 | (if (eof-object? c) 'nil c)))) 902 | 903 | (xdef writec (lambda (c . args) 904 | (write-char c 905 | (if (pair? args) 906 | (car args) 907 | (current-output-port))) 908 | c)) 909 | 910 | (xdef writeb (lambda (b . args) 911 | (write-byte b 912 | (if (pair? args) 913 | (car args) 914 | (current-output-port))) 915 | b)) 916 | 917 | (define explicit-flush #f) 918 | 919 | (define (printwith f args) 920 | (let ((port (if (> (length args) 1) 921 | (cadr args) 922 | (current-output-port)))) 923 | (when (pair? args) 924 | (f (ac-denil (car args)) port)) 925 | (unless explicit-flush (flush-output port))) 926 | 'nil) 927 | 928 | (xdef write (lambda args (printwith write args))) 929 | (xdef disp (lambda args (printwith display args))) 930 | 931 | ; sread = scheme read. eventually replace by writing read 932 | 933 | (xdef sread (lambda (p eof) 934 | (let ((expr (read p))) 935 | (if (eof-object? expr) eof expr)))) 936 | 937 | ; these work in PLT but not scheme48 938 | 939 | (define char->ascii char->integer) 940 | (define ascii->char integer->char) 941 | 942 | (define (iround x) (inexact->exact (round x))) 943 | 944 | (define (ar-coerce x type . args) 945 | (cond 946 | ((ar-tagged? x) (err "Can't coerce annotated object")) 947 | ((eqv? type (ar-type x)) x) 948 | ((char? x) (case type 949 | ((int) (char->ascii x)) 950 | ((string) (string x)) 951 | ((sym) (string->symbol (string x))) 952 | (else (err "Can't coerce" x type)))) 953 | ((integer? x) (case type 954 | ((num) x) 955 | ((char) (ascii->char x)) 956 | ((string) (apply number->string x args)) 957 | (else (err "Can't coerce" x type)))) 958 | ((number? x) (case type 959 | ((int) (iround x)) 960 | ((char) (ascii->char (iround x))) 961 | ((string) (apply number->string x args)) 962 | (else (err "Can't coerce" x type)))) 963 | ((string? x) (case type 964 | ((sym) (string->symbol x)) 965 | ((cons) (ac-niltree (string->list x))) 966 | ((num) (or (apply string->number x args) 967 | (err "Can't coerce" x type))) 968 | ((int) (let ((n (apply string->number x args))) 969 | (if n 970 | (iround n) 971 | (err "Can't coerce" x type)))) 972 | (else (err "Can't coerce" x type)))) 973 | ((pair? x) (case type 974 | ((string) (apply string-append 975 | (map (lambda (y) (ar-coerce y 'string)) 976 | (ar-nil-terminate x)))) 977 | (else (err "Can't coerce" x type)))) 978 | ((eqv? x 'nil) (case type 979 | ((string) "") 980 | (else (err "Can't coerce" x type)))) 981 | ((null? x) (case type 982 | ((string) "") 983 | (else (err "Can't coerce" x type)))) 984 | ((symbol? x) (case type 985 | ((string) (symbol->string x)) 986 | (else (err "Can't coerce" x type)))) 987 | (#t x))) 988 | 989 | (xdef coerce ar-coerce) 990 | 991 | (xdef open-socket (lambda (num) (tcp-listen num 50 #t))) 992 | 993 | ; the 2050 means http requests currently capped at 2 meg 994 | ; http://list.cs.brown.edu/pipermail/plt-scheme/2005-August/009414.html 995 | 996 | (xdef socket-accept (lambda (s) 997 | (let ((oc (current-custodian)) 998 | (nc (make-custodian))) 999 | (current-custodian nc) 1000 | (call-with-values 1001 | (lambda () (tcp-accept s)) 1002 | (lambda (in out) 1003 | (let ((in1 (make-limited-input-port in 100000 #t))) 1004 | (current-custodian oc) 1005 | (associate-custodian nc in1 out) 1006 | (list in1 1007 | out 1008 | (let-values (((us them) (tcp-addresses out))) 1009 | them)))))))) 1010 | 1011 | (xdef new-thread thread) 1012 | (xdef kill-thread kill-thread) 1013 | (xdef break-thread break-thread) 1014 | (xdef current-thread current-thread) 1015 | 1016 | (define (wrapnil f) (lambda args (apply f args) 'nil)) 1017 | 1018 | (xdef sleep (wrapnil sleep)) 1019 | 1020 | ; Will system "execute" a half-finished string if thread killed 1021 | ; in the middle of generating it? 1022 | 1023 | (xdef system (wrapnil system)) 1024 | 1025 | (xdef pipe-from (lambda (cmd) 1026 | (let ((tf (ar-tmpname))) 1027 | (system (string-append cmd " > " tf)) 1028 | (let ((str (open-input-file tf))) 1029 | (system (string-append "rm -f " tf)) 1030 | str)))) 1031 | 1032 | (define (ar-tmpname) 1033 | (call-with-input-file "/dev/urandom" 1034 | (lambda (rstr) 1035 | (do ((s "/tmp/") 1036 | (c (read-char rstr) (read-char rstr)) 1037 | (i 0 (+ i 1))) 1038 | ((>= i 16) s) 1039 | (set! s (string-append s 1040 | (string 1041 | (integer->char 1042 | (+ (char->integer #\a) 1043 | (modulo 1044 | (char->integer (read-char rstr)) 1045 | 26)))))))))) 1046 | 1047 | ; PLT scheme provides only eq? and equal? hash tables, 1048 | ; we need the latter for strings. 1049 | 1050 | (xdef table (lambda args 1051 | (let ((h (make-hash-table 'equal))) 1052 | (if (pair? args) ((car args) h)) 1053 | h))) 1054 | 1055 | ;(xdef table (lambda args 1056 | ; (fill-table (make-hash-table 'equal) 1057 | ; (if (pair? args) (ac-denil (car args)) '())))) 1058 | 1059 | (define (fill-table h pairs) 1060 | (if (eq? pairs '()) 1061 | h 1062 | (let ((pair (car pairs))) 1063 | (begin (hash-table-put! h (car pair) (cadr pair)) 1064 | (fill-table h (cdr pairs)))))) 1065 | 1066 | (xdef maptable (lambda (fn table) ; arg is (fn (key value) ...) 1067 | (hash-table-for-each table fn) 1068 | table)) 1069 | 1070 | (define (protect during after) 1071 | (dynamic-wind (lambda () #t) during after)) 1072 | 1073 | (xdef protect protect) 1074 | 1075 | ; need to use a better seed 1076 | 1077 | (xdef rand random) 1078 | 1079 | (xdef dir (lambda (name) 1080 | (ac-niltree (map path->string (directory-list name))))) 1081 | 1082 | ; Would def mkdir in terms of make-directory and call that instead 1083 | ; of system in ensure-dir, but make-directory is too weak: it doesn't 1084 | ; create intermediate directories like mkdir -p. 1085 | 1086 | (xdef file-exists (lambda (name) 1087 | (if (file-exists? name) name 'nil))) 1088 | 1089 | (xdef dir-exists (lambda (name) 1090 | (if (directory-exists? name) name 'nil))) 1091 | 1092 | (xdef rmfile (wrapnil delete-file)) 1093 | 1094 | (xdef mvfile (lambda (old new) 1095 | (rename-file-or-directory old new #t) 1096 | 'nil)) 1097 | 1098 | ; top level read-eval-print 1099 | ; tle kept as a way to get a break loop when a scheme err 1100 | 1101 | (define (arc-eval expr) 1102 | (eval (ac expr '()))) 1103 | 1104 | (define (tle) 1105 | (display "Arc> ") 1106 | (let ((expr (read))) 1107 | (when (not (eqv? expr ':a)) 1108 | (write (arc-eval expr)) 1109 | (newline) 1110 | (tle)))) 1111 | 1112 | (define last-condition* #f) 1113 | 1114 | (define (tl) 1115 | (display "Use (quit) to quit, (tl) to return here after an interrupt.\n") 1116 | (tl2)) 1117 | 1118 | (define (tl2) 1119 | (display "arc> ") 1120 | (on-err (lambda (c) 1121 | (set! last-condition* c) 1122 | (display "Error: ") 1123 | (write (exn-message c)) 1124 | (newline) 1125 | (tl2)) 1126 | (lambda () 1127 | (let ((expr (read))) 1128 | (if (eqv? expr ':a) 1129 | 'done 1130 | (let ((val (arc-eval expr))) 1131 | (write (ac-denil val)) 1132 | (namespace-set-variable-value! '_that val) 1133 | (namespace-set-variable-value! '_thatexpr expr) 1134 | (newline) 1135 | (tl2))))))) 1136 | 1137 | (define (aload1 p) 1138 | (let ((x (read p))) 1139 | (if (eof-object? x) 1140 | #t 1141 | (begin 1142 | (arc-eval x) 1143 | (aload1 p))))) 1144 | 1145 | (define (atests1 p) 1146 | (let ((x (read p))) 1147 | (if (eof-object? x) 1148 | #t 1149 | (begin 1150 | (write x) 1151 | (newline) 1152 | (let ((v (arc-eval x))) 1153 | (if (ar-false? v) 1154 | (begin 1155 | (display " FAILED") 1156 | (newline)))) 1157 | (atests1 p))))) 1158 | 1159 | (define (aload filename) 1160 | (call-with-input-file filename aload1)) 1161 | 1162 | (define (test filename) 1163 | (call-with-input-file filename atests1)) 1164 | 1165 | (define (acompile1 ip op) 1166 | (let ((x (read ip))) 1167 | (if (eof-object? x) 1168 | #t 1169 | (let ((scm (ac x '()))) 1170 | (eval scm) 1171 | (pretty-print scm op) 1172 | (newline op) 1173 | (newline op) 1174 | (acompile1 ip op))))) 1175 | 1176 | ; compile xx.arc to xx.arc.scm 1177 | ; useful to examine the Arc compiler output 1178 | (define (acompile inname) 1179 | (let ((outname (string-append inname ".scm"))) 1180 | (if (file-exists? outname) 1181 | (delete-file outname)) 1182 | (call-with-input-file inname 1183 | (lambda (ip) 1184 | (call-with-output-file outname 1185 | (lambda (op) 1186 | (acompile1 ip op))))))) 1187 | 1188 | (xdef macex (lambda (e) (ac-macex (ac-denil e)))) 1189 | 1190 | (xdef macex1 (lambda (e) (ac-macex (ac-denil e) 'once))) 1191 | 1192 | (xdef eval (lambda (e) 1193 | (eval (ac (ac-denil e) '())))) 1194 | 1195 | ; If an err occurs in an on-err expr, no val is returned and code 1196 | ; after it doesn't get executed. Not quite what I had in mind. 1197 | 1198 | (define (on-err errfn f) 1199 | ((call-with-current-continuation 1200 | (lambda (k) 1201 | (lambda () 1202 | (with-handlers ((exn:fail? (lambda (c) 1203 | (k (lambda () (errfn c)))))) 1204 | (f))))))) 1205 | (xdef on-err on-err) 1206 | 1207 | (define (disp-to-string x) 1208 | (let ((o (open-output-string))) 1209 | (display x o) 1210 | (close-output-port o) 1211 | (get-output-string o))) 1212 | 1213 | (xdef details (lambda (c) 1214 | (disp-to-string (exn-message c)))) 1215 | 1216 | (xdef scar (lambda (x val) 1217 | (if (string? x) 1218 | (string-set! x 0 val) 1219 | (set-car! x val)) 1220 | val)) 1221 | 1222 | (xdef scdr (lambda (x val) 1223 | (if (string? x) 1224 | (err "Can't set cdr of a string" x) 1225 | (set-cdr! x val)) 1226 | val)) 1227 | 1228 | ; When and if cdr of a string returned an actual (eq) tail, could 1229 | ; say (if (string? x) (string-replace! x val 1) ...) in scdr, but 1230 | ; for now would be misleading to allow this, because fails for cddr. 1231 | 1232 | (define (string-replace! str val index) 1233 | (if (eqv? (string-length val) (- (string-length str) index)) 1234 | (do ((i index (+ i 1))) 1235 | ((= i (string-length str)) str) 1236 | (string-set! str i (string-ref val (- i index)))) 1237 | (err "Length mismatch between strings" str val index))) 1238 | 1239 | ; Later may want to have multiple indices. 1240 | 1241 | (xdef sref 1242 | (lambda (com val ind) 1243 | (cond ((hash-table? com) (if (eqv? val 'nil) 1244 | (hash-table-remove! com ind) 1245 | (hash-table-put! com ind val))) 1246 | ((string? com) (string-set! com ind val)) 1247 | ((pair? com) (nth-set! com ind val)) 1248 | (#t (err "Can't set reference " com ind val))) 1249 | val)) 1250 | 1251 | (define (nth-set! lst n val) 1252 | (set-car! (list-tail lst n) val)) 1253 | 1254 | ; rewrite to pass a (true) gensym instead of #f in case var bound to #f 1255 | 1256 | (define (bound? arcname) 1257 | (namespace-variable-value (ac-global-name arcname) 1258 | #t 1259 | (lambda () #f))) 1260 | 1261 | (xdef bound (lambda (x) (tnil (bound? x)))) 1262 | 1263 | (xdef newstring make-string) 1264 | 1265 | (xdef trunc (lambda (x) (inexact->exact (truncate x)))) 1266 | 1267 | (xdef exact (lambda (x) 1268 | (tnil (and (integer? x) (exact? x))))) 1269 | 1270 | (xdef msec current-milliseconds) 1271 | (xdef current-process-milliseconds current-process-milliseconds) 1272 | (xdef current-gc-milliseconds current-gc-milliseconds) 1273 | 1274 | (xdef seconds current-seconds) 1275 | 1276 | (print-hash-table #t) 1277 | 1278 | (xdef client-ip (lambda (port) 1279 | (let-values (((x y) (tcp-addresses port))) 1280 | y))) 1281 | 1282 | ; make sure only one thread at a time executes anything 1283 | ; inside an atomic-invoke. atomic-invoke is allowed to 1284 | ; nest within a thread; the thread-cell keeps track of 1285 | ; whether this thread already holds the lock. 1286 | 1287 | (define ar-the-sema (make-semaphore 1)) 1288 | 1289 | (define ar-sema-cell (make-thread-cell #f)) 1290 | 1291 | (xdef atomic-invoke (lambda (f) 1292 | (if (thread-cell-ref ar-sema-cell) 1293 | (ar-apply f '()) 1294 | (begin 1295 | (thread-cell-set! ar-sema-cell #t) 1296 | (protect 1297 | (lambda () 1298 | (call-with-semaphore 1299 | ar-the-sema 1300 | (lambda () (ar-apply f '())))) 1301 | (lambda () 1302 | (thread-cell-set! ar-sema-cell #f))))))) 1303 | 1304 | (xdef dead (lambda (x) (tnil (thread-dead? x)))) 1305 | 1306 | ; Added because Mzscheme buffers output. Not a permanent part of Arc. 1307 | ; Only need to use when declare explicit-flush optimization. 1308 | 1309 | (xdef flushout (lambda () (flush-output) 't)) 1310 | 1311 | (xdef ssyntax (lambda (x) (tnil (ssyntax? x)))) 1312 | 1313 | (xdef ssexpand (lambda (x) 1314 | (if (symbol? x) (expand-ssyntax x) x))) 1315 | 1316 | (xdef quit exit) 1317 | 1318 | ; there are two ways to close a TCP output port. 1319 | ; (close o) waits for output to drain, then closes UNIX descriptor. 1320 | ; (force-close o) discards buffered output, then closes UNIX desc. 1321 | ; web servers need the latter to get rid of connections to 1322 | ; clients that are not reading data. 1323 | ; mzscheme close-output-port doesn't work (just raises an error) 1324 | ; if there is buffered output for a non-responsive socket. 1325 | ; must use custodian-shutdown-all instead. 1326 | 1327 | (define custodians (make-hash-table 'equal)) 1328 | 1329 | (define (associate-custodian c i o) 1330 | (hash-table-put! custodians i c) 1331 | (hash-table-put! custodians o c)) 1332 | 1333 | ; if a port has a custodian, use it to close the port forcefully. 1334 | ; also get rid of the reference to the custodian. 1335 | ; sadly doing this to the input port also kills the output port. 1336 | 1337 | (define (try-custodian p) 1338 | (let ((c (hash-table-get custodians p #f))) 1339 | (if c 1340 | (begin 1341 | (custodian-shutdown-all c) 1342 | (hash-table-remove! custodians p) 1343 | #t) 1344 | #f))) 1345 | 1346 | (define (ar-close . args) 1347 | (map (lambda (p) 1348 | (cond ((input-port? p) (close-input-port p)) 1349 | ((output-port? p) (close-output-port p)) 1350 | ((tcp-listener? p) (tcp-close p)) 1351 | (#t (err "Can't close " p)))) 1352 | args) 1353 | (map (lambda (p) (try-custodian p)) args) ; free any custodian 1354 | 'nil) 1355 | 1356 | (xdef close ar-close) 1357 | 1358 | (xdef force-close (lambda args 1359 | (map (lambda (p) 1360 | (if (not (try-custodian p)) 1361 | (ar-close p))) 1362 | args) 1363 | 'nil)) 1364 | 1365 | (xdef memory current-memory-use) 1366 | 1367 | (xdef declare (lambda (key val) 1368 | (let ((flag (not (ar-false? val)))) 1369 | (case key 1370 | ((atstrings) (set! atstrings flag)) 1371 | ((direct-calls) (set! direct-calls flag)) 1372 | ((explicit-flush) (set! explicit-flush flag))) 1373 | val))) 1374 | 1375 | (putenv "TZ" ":GMT") 1376 | 1377 | (define (gmt-date sec) (seconds->date sec)) 1378 | 1379 | (xdef timedate 1380 | (lambda args 1381 | (let ((d (gmt-date (if (pair? args) (car args) (current-seconds))))) 1382 | (ac-niltree (list (date-second d) 1383 | (date-minute d) 1384 | (date-hour d) 1385 | (date-day d) 1386 | (date-month d) 1387 | (date-year d)))))) 1388 | 1389 | (xdef sin sin) 1390 | (xdef cos cos) 1391 | (xdef tan tan) 1392 | (xdef asin asin) 1393 | (xdef acos acos) 1394 | (xdef atan atan) 1395 | (xdef log log) 1396 | 1397 | (define (codestring s) 1398 | (let ((i (atpos s 0))) 1399 | (if i 1400 | (cons (substring s 0 i) 1401 | (let* ((rest (substring s (+ i 1))) 1402 | (in (open-input-string rest)) 1403 | (expr (read in)) 1404 | (i2 (let-values (((x y z) (port-next-location in))) z))) 1405 | (close-input-port in) 1406 | (cons expr (codestring (substring rest (- i2 1)))))) 1407 | (list s)))) 1408 | 1409 | ; First unescaped @ in s, if any. Escape by doubling. 1410 | 1411 | (define (atpos s i) 1412 | (cond ((eqv? i (string-length s)) 1413 | #f) 1414 | ((eqv? (string-ref s i) #\@) 1415 | (if (and (< (+ i 1) (string-length s)) 1416 | (not (eqv? (string-ref s (+ i 1)) #\@))) 1417 | i 1418 | (atpos s (+ i 2)))) 1419 | (#t 1420 | (atpos s (+ i 1))))) 1421 | 1422 | (define (unescape-ats s) 1423 | (list->string (letrec ((unesc (lambda (cs) 1424 | (cond 1425 | ((null? cs) 1426 | '()) 1427 | ((and (eqv? (car cs) #\@) 1428 | (not (null? (cdr cs))) 1429 | (eqv? (cadr cs) #\@)) 1430 | (unesc (cdr cs))) 1431 | (#t 1432 | (cons (car cs) (unesc (cdr cs)))))))) 1433 | (unesc (string->list s))))) 1434 | 1435 | ) 1436 | 1437 | -------------------------------------------------------------------------------- /arc.arc: -------------------------------------------------------------------------------- 1 | ; Main Arc lib. Ported to Scheme version Jul 06. 2 | 3 | ; don't like names of conswhen and consif 4 | 5 | ; need better way of generating strings; too many calls to string 6 | ; maybe strings with escape char for evaluation 7 | ; make foo~bar equiv of foo:~bar (in expand-ssyntax) 8 | ; add sigs of ops defined in ac.scm 9 | ; get hold of error types within arc 10 | ; does macex have to be defined in scheme instead of using def below? 11 | ; write disp, read, write in arc 12 | ; could I get all of macros up into arc.arc? 13 | ; warn when shadow a global name 14 | ; some simple regexp/parsing plan 15 | 16 | ; compromises in this implementation: 17 | ; no objs in code 18 | ; (mac testlit args (listtab args)) breaks when called 19 | ; separate string type 20 | ; (= (cdr (cdr str)) "foo") couldn't work because no way to get str tail 21 | ; not sure this is a mistake; strings may be subtly different from 22 | ; lists of chars 23 | 24 | 25 | (assign do (annotate 'mac 26 | (fn args `((fn () ,@args))))) 27 | 28 | (assign safeset (annotate 'mac 29 | (fn (var val) 30 | `(do (if (bound ',var) 31 | (do (disp "*** redefining " (stderr)) 32 | (disp ',var (stderr)) 33 | (disp #\newline (stderr)))) 34 | (assign ,var ,val))))) 35 | 36 | (assign def (annotate 'mac 37 | (fn (name parms . body) 38 | `(do (sref sig ',parms ',name) 39 | (safeset ,name (fn ,parms ,@body)))))) 40 | 41 | (def caar (xs) (car (car xs))) 42 | (def cadr (xs) (car (cdr xs))) 43 | (def cddr (xs) (cdr (cdr xs))) 44 | 45 | (def no (x) (is x nil)) 46 | 47 | (def acons (x) (is (type x) 'cons)) 48 | 49 | (def atom (x) (no (acons x))) 50 | 51 | ; Can return to this def once Rtm gets ac to make all rest args 52 | ; nil-terminated lists. 53 | 54 | ; (def list args args) 55 | 56 | (def copylist (xs) 57 | (if (no xs) 58 | nil 59 | (cons (car xs) (copylist (cdr xs))))) 60 | 61 | (def list args (copylist args)) 62 | 63 | (def idfn (x) x) 64 | 65 | ; Maybe later make this internal. Useful to let xs be a fn? 66 | 67 | (def map1 (f xs) 68 | (if (no xs) 69 | nil 70 | (cons (f (car xs)) (map1 f (cdr xs))))) 71 | 72 | (def pair (xs (o f list)) 73 | (if (no xs) 74 | nil 75 | (no (cdr xs)) 76 | (list (list (car xs))) 77 | (cons (f (car xs) (cadr xs)) 78 | (pair (cddr xs) f)))) 79 | 80 | (assign mac (annotate 'mac 81 | (fn (name parms . body) 82 | `(do (sref sig ',parms ',name) 83 | (safeset ,name (annotate 'mac (fn ,parms ,@body))))))) 84 | 85 | (mac and args 86 | (if args 87 | (if (cdr args) 88 | `(if ,(car args) (and ,@(cdr args))) 89 | (car args)) 90 | 't)) 91 | 92 | (def assoc (key al) 93 | (if (atom al) 94 | nil 95 | (and (acons (car al)) (is (caar al) key)) 96 | (car al) 97 | (assoc key (cdr al)))) 98 | 99 | (def alref (al key) (cadr (assoc key al))) 100 | 101 | (mac with (parms . body) 102 | `((fn ,(map1 car (pair parms)) 103 | ,@body) 104 | ,@(map1 cadr (pair parms)))) 105 | 106 | (mac let (var val . body) 107 | `(with (,var ,val) ,@body)) 108 | 109 | (mac withs (parms . body) 110 | (if (no parms) 111 | `(do ,@body) 112 | `(let ,(car parms) ,(cadr parms) 113 | (withs ,(cddr parms) ,@body)))) 114 | 115 | ; Rtm prefers to overload + to do this 116 | 117 | (def join args 118 | (if (no args) 119 | nil 120 | (let a (car args) 121 | (if (no a) 122 | (apply join (cdr args)) 123 | (cons (car a) (apply join (cdr a) (cdr args))))))) 124 | 125 | ; Need rfn for use in macro expansions. 126 | 127 | (mac rfn (name parms . body) 128 | `(let ,name nil 129 | (assign ,name (fn ,parms ,@body)))) 130 | 131 | (mac afn (parms . body) 132 | `(let self nil 133 | (assign self (fn ,parms ,@body)))) 134 | 135 | ; Ac expands x:y:z into (compose x y z), ~x into (complement x) 136 | 137 | ; Only used when the call to compose doesn't occur in functional position. 138 | ; Composes in functional position are transformed away by ac. 139 | 140 | (mac compose args 141 | (let g (uniq) 142 | `(fn ,g 143 | ,((afn (fs) 144 | (if (cdr fs) 145 | (list (car fs) (self (cdr fs))) 146 | `(apply ,(if (car fs) (car fs) 'idfn) ,g))) 147 | args)))) 148 | 149 | ; Ditto: complement in functional position optimized by ac. 150 | 151 | (mac complement (f) 152 | (let g (uniq) 153 | `(fn ,g (no (apply ,f ,g))))) 154 | 155 | (def rev (xs) 156 | ((afn (xs acc) 157 | (if (no xs) 158 | acc 159 | (self (cdr xs) (cons (car xs) acc)))) 160 | xs nil)) 161 | 162 | (def isnt (x y) (no (is x y))) 163 | 164 | (mac w/uniq (names . body) 165 | (if (acons names) 166 | `(with ,(apply + nil (map1 (fn (n) (list n '(uniq))) 167 | names)) 168 | ,@body) 169 | `(let ,names (uniq) ,@body))) 170 | 171 | (mac or args 172 | (and args 173 | (w/uniq g 174 | `(let ,g ,(car args) 175 | (if ,g ,g (or ,@(cdr args))))))) 176 | 177 | (def alist (x) (or (no x) (is (type x) 'cons))) 178 | 179 | (mac in (x . choices) 180 | (w/uniq g 181 | `(let ,g ,x 182 | (or ,@(map1 (fn (c) `(is ,g ,c)) choices))))) 183 | 184 | ; Could take n args, but have never once needed that. 185 | 186 | (def iso (x y) 187 | (or (is x y) 188 | (and (acons x) 189 | (acons y) 190 | (iso (car x) (car y)) 191 | (iso (cdr x) (cdr y))))) 192 | 193 | (mac when (test . body) 194 | `(if ,test (do ,@body))) 195 | 196 | (mac unless (test . body) 197 | `(if (no ,test) (do ,@body))) 198 | 199 | (mac while (test . body) 200 | (w/uniq (gf gp) 201 | `((rfn ,gf (,gp) 202 | (when ,gp ,@body (,gf ,test))) 203 | ,test))) 204 | 205 | (def empty (seq) 206 | (or (no seq) 207 | (and (no (acons seq)) (is (len seq) 0)))) 208 | 209 | (def reclist (f xs) 210 | (and xs (or (f xs) (reclist f (cdr xs))))) 211 | 212 | (def recstring (test s (o start 0)) 213 | ((afn (i) 214 | (and (< i (len s)) 215 | (or (test i) 216 | (self (+ i 1))))) 217 | start)) 218 | 219 | (def testify (x) 220 | (if (isa x 'fn) x [is _ x])) 221 | 222 | ; Like keep, seems like some shouldn't testify. But find should, 223 | ; and all probably should. 224 | 225 | (def some (test seq) 226 | (let f (testify test) 227 | (if (alist seq) 228 | (reclist f:car seq) 229 | (recstring f:seq seq)))) 230 | 231 | (def all (test seq) 232 | (~some (complement (testify test)) seq)) 233 | 234 | (def mem (test seq) 235 | (let f (testify test) 236 | (reclist [if (f:car _) _] seq))) 237 | 238 | (def find (test seq) 239 | (let f (testify test) 240 | (if (alist seq) 241 | (reclist [if (f:car _) (car _)] seq) 242 | (recstring [if (f:seq _) (seq _)] seq)))) 243 | 244 | (def isa (x y) (is (type x) y)) 245 | 246 | ; Possible to write map without map1, but makes News 3x slower. 247 | 248 | ;(def map (f . seqs) 249 | ; (if (some1 no seqs) 250 | ; nil 251 | ; (no (cdr seqs)) 252 | ; (let s1 (car seqs) 253 | ; (cons (f (car s1)) 254 | ; (map f (cdr s1)))) 255 | ; (cons (apply f (map car seqs)) 256 | ; (apply map f (map cdr seqs))))) 257 | 258 | 259 | (def map (f . seqs) 260 | (if (some [isa _ 'string] seqs) 261 | (withs (n (apply min (map len seqs)) 262 | new (newstring n)) 263 | ((afn (i) 264 | (if (is i n) 265 | new 266 | (do (sref new (apply f (map [_ i] seqs)) i) 267 | (self (+ i 1))))) 268 | 0)) 269 | (no (cdr seqs)) 270 | (map1 f (car seqs)) 271 | ((afn (seqs) 272 | (if (some no seqs) 273 | nil 274 | (cons (apply f (map1 car seqs)) 275 | (self (map1 cdr seqs))))) 276 | seqs))) 277 | 278 | (def mappend (f . args) 279 | (apply + nil (apply map f args))) 280 | 281 | (def firstn (n xs) 282 | (if (no n) xs 283 | (and (> n 0) xs) (cons (car xs) (firstn (- n 1) (cdr xs))) 284 | nil)) 285 | 286 | (def nthcdr (n xs) 287 | (if (no n) xs 288 | (> n 0) (nthcdr (- n 1) (cdr xs)) 289 | xs)) 290 | 291 | ; Generalization of pair: (tuples x) = (pair x) 292 | 293 | (def tuples (xs (o n 2)) 294 | (if (no xs) 295 | nil 296 | (cons (firstn n xs) 297 | (tuples (nthcdr n xs) n)))) 298 | 299 | ; If ok to do with =, why not with def? But see if use it. 300 | 301 | (mac defs args 302 | `(do ,@(map [cons 'def _] (tuples args 3)))) 303 | 304 | (def caris (x val) 305 | (and (acons x) (is (car x) val))) 306 | 307 | (def warn (msg . args) 308 | (disp (+ "Warning: " msg ". ")) 309 | (map [do (write _) (disp " ")] args) 310 | (disp #\newline)) 311 | 312 | (mac atomic body 313 | `(atomic-invoke (fn () ,@body))) 314 | 315 | (mac atlet args 316 | `(atomic (let ,@args))) 317 | 318 | (mac atwith args 319 | `(atomic (with ,@args))) 320 | 321 | (mac atwiths args 322 | `(atomic (withs ,@args))) 323 | 324 | 325 | ; setforms returns (vars get set) for a place based on car of an expr 326 | ; vars is a list of gensyms alternating with expressions whose vals they 327 | ; should be bound to, suitable for use as first arg to withs 328 | ; get is an expression returning the current value in the place 329 | ; set is an expression representing a function of one argument 330 | ; that stores a new value in the place 331 | 332 | ; A bit gross that it works based on the *name* in the car, but maybe 333 | ; wrong to worry. Macros live in expression land. 334 | 335 | ; seems meaningful to e.g. (push 1 (pop x)) if (car x) is a cons. 336 | ; can't in cl though. could I define a setter for push or pop? 337 | 338 | (assign setter (table)) 339 | 340 | (mac defset (name parms . body) 341 | (w/uniq gexpr 342 | `(sref setter 343 | (fn (,gexpr) 344 | (let ,parms (cdr ,gexpr) 345 | ,@body)) 346 | ',name))) 347 | 348 | (defset car (x) 349 | (w/uniq g 350 | (list (list g x) 351 | `(car ,g) 352 | `(fn (val) (scar ,g val))))) 353 | 354 | (defset cdr (x) 355 | (w/uniq g 356 | (list (list g x) 357 | `(cdr ,g) 358 | `(fn (val) (scdr ,g val))))) 359 | 360 | (defset caar (x) 361 | (w/uniq g 362 | (list (list g x) 363 | `(caar ,g) 364 | `(fn (val) (scar (car ,g) val))))) 365 | 366 | (defset cadr (x) 367 | (w/uniq g 368 | (list (list g x) 369 | `(cadr ,g) 370 | `(fn (val) (scar (cdr ,g) val))))) 371 | 372 | (defset cddr (x) 373 | (w/uniq g 374 | (list (list g x) 375 | `(cddr ,g) 376 | `(fn (val) (scdr (cdr ,g) val))))) 377 | 378 | ; Note: if expr0 macroexpands into any expression whose car doesn't 379 | ; have a setter, setforms assumes it's a data structure in functional 380 | ; position. Such bugs will be seen only when the code is executed, when 381 | ; sref complains it can't set a reference to a function. 382 | 383 | (def setforms (expr0) 384 | (let expr (macex expr0) 385 | (if (isa expr 'sym) 386 | (if (ssyntax expr) 387 | (setforms (ssexpand expr)) 388 | (w/uniq (g h) 389 | (list (list g expr) 390 | g 391 | `(fn (,h) (assign ,expr ,h))))) 392 | ; make it also work for uncompressed calls to compose 393 | (and (acons expr) (metafn (car expr))) 394 | (setforms (expand-metafn-call (ssexpand (car expr)) (cdr expr))) 395 | (and (acons expr) (acons (car expr)) (is (caar expr) 'get)) 396 | (setforms (list (cadr expr) (cadr (car expr)))) 397 | (let f (setter (car expr)) 398 | (if f 399 | (f expr) 400 | ; assumed to be data structure in fn position 401 | (do (when (caris (car expr) 'fn) 402 | (warn "Inverting what looks like a function call" 403 | expr0 expr)) 404 | (w/uniq (g h) 405 | (let argsyms (map [uniq] (cdr expr)) 406 | (list (+ (list g (car expr)) 407 | (mappend list argsyms (cdr expr))) 408 | `(,g ,@argsyms) 409 | `(fn (,h) (sref ,g ,h ,(car argsyms)))))))))))) 410 | 411 | (def metafn (x) 412 | (or (ssyntax x) 413 | (and (acons x) (in (car x) 'compose 'complement)))) 414 | 415 | (def expand-metafn-call (f args) 416 | (if (is (car f) 'compose) 417 | ((afn (fs) 418 | (if (caris (car fs) 'compose) ; nested compose 419 | (self (join (cdr (car fs)) (cdr fs))) 420 | (cdr fs) 421 | (list (car fs) (self (cdr fs))) 422 | (cons (car fs) args))) 423 | (cdr f)) 424 | (is (car f) 'no) 425 | (err "Can't invert " (cons f args)) 426 | (cons f args))) 427 | 428 | (def expand= (place val) 429 | (if (and (isa place 'sym) (~ssyntax place)) 430 | `(assign ,place ,val) 431 | (let (vars prev setter) (setforms place) 432 | (w/uniq g 433 | `(atwith ,(+ vars (list g val)) 434 | (,setter ,g)))))) 435 | 436 | (def expand=list (terms) 437 | `(do ,@(map (fn ((p v)) (expand= p v)) ; [apply expand= _] 438 | (pair terms)))) 439 | 440 | (mac = args 441 | (expand=list args)) 442 | 443 | (mac loop (start test update . body) 444 | (w/uniq (gfn gparm) 445 | `(do ,start 446 | ((rfn ,gfn (,gparm) 447 | (if ,gparm 448 | (do ,@body ,update (,gfn ,test)))) 449 | ,test)))) 450 | 451 | (mac for (v init max . body) 452 | (w/uniq (gi gm) 453 | `(with (,v nil ,gi ,init ,gm (+ ,max 1)) 454 | (loop (assign ,v ,gi) (< ,v ,gm) (assign ,v (+ ,v 1)) 455 | ,@body)))) 456 | 457 | (mac down (v init min . body) 458 | (w/uniq (gi gm) 459 | `(with (,v nil ,gi ,init ,gm (- ,min 1)) 460 | (loop (assign ,v ,gi) (> ,v ,gm) (assign ,v (- ,v 1)) 461 | ,@body)))) 462 | 463 | (mac repeat (n . body) 464 | `(for ,(uniq) 1 ,n ,@body)) 465 | 466 | ; could bind index instead of gensym 467 | 468 | (mac each (var expr . body) 469 | (w/uniq (gseq gf gv) 470 | `(let ,gseq ,expr 471 | (if (alist ,gseq) 472 | ((rfn ,gf (,gv) 473 | (when (acons ,gv) 474 | (let ,var (car ,gv) ,@body) 475 | (,gf (cdr ,gv)))) 476 | ,gseq) 477 | (isa ,gseq 'table) 478 | (maptable (fn ,var ,@body) 479 | ,gseq) 480 | (for ,gv 0 (- (len ,gseq) 1) 481 | (let ,var (,gseq ,gv) ,@body)))))) 482 | 483 | ; (nthcdr x y) = (cut y x). 484 | 485 | (def cut (seq start (o end)) 486 | (let end (if (no end) (len seq) 487 | (< end 0) (+ (len seq) end) 488 | end) 489 | (if (isa seq 'string) 490 | (let s2 (newstring (- end start)) 491 | (for i 0 (- end start 1) 492 | (= (s2 i) (seq (+ start i)))) 493 | s2) 494 | (firstn (- end start) (nthcdr start seq))))) 495 | 496 | (mac whilet (var test . body) 497 | (w/uniq (gf gp) 498 | `((rfn ,gf (,gp) 499 | (let ,var ,gp 500 | (when ,var ,@body (,gf ,test)))) 501 | ,test))) 502 | 503 | (def last (xs) 504 | (if (cdr xs) 505 | (last (cdr xs)) 506 | (car xs))) 507 | 508 | (def rem (test seq) 509 | (let f (testify test) 510 | (if (alist seq) 511 | ((afn (s) 512 | (if (no s) nil 513 | (f (car s)) (self (cdr s)) 514 | (cons (car s) (self (cdr s))))) 515 | seq) 516 | (coerce (rem test (coerce seq 'cons)) 'string)))) 517 | 518 | ; Seems like keep doesn't need to testify-- would be better to 519 | ; be able to use tables as fns. But rem does need to, because 520 | ; often want to rem a table from a list. So maybe the right answer 521 | ; is to make keep the more primitive, not rem. 522 | 523 | (def keep (test seq) 524 | (rem (complement (testify test)) seq)) 525 | 526 | ;(def trues (f seq) 527 | ; (rem nil (map f seq))) 528 | 529 | (def trues (f xs) 530 | (and xs 531 | (let fx (f (car xs)) 532 | (if fx 533 | (cons fx (trues f (cdr xs))) 534 | (trues f (cdr xs)))))) 535 | 536 | (mac do1 args 537 | (w/uniq g 538 | `(let ,g ,(car args) 539 | ,@(cdr args) 540 | ,g))) 541 | 542 | ; Would like to write a faster case based on table generated by a macro, 543 | ; but can't insert objects into expansions in Mzscheme. 544 | 545 | (mac caselet (var expr . args) 546 | (let ex (afn (args) 547 | (if (no (cdr args)) 548 | (car args) 549 | `(if (is ,var ',(car args)) 550 | ,(cadr args) 551 | ,(self (cddr args))))) 552 | `(let ,var ,expr ,(ex args)))) 553 | 554 | (mac case (expr . args) 555 | `(caselet ,(uniq) ,expr ,@args)) 556 | 557 | (mac push (x place) 558 | (w/uniq gx 559 | (let (binds val setter) (setforms place) 560 | `(let ,gx ,x 561 | (atwiths ,binds 562 | (,setter (cons ,gx ,val))))))) 563 | 564 | (mac swap (place1 place2) 565 | (w/uniq (g1 g2) 566 | (with ((binds1 val1 setter1) (setforms place1) 567 | (binds2 val2 setter2) (setforms place2)) 568 | `(atwiths ,(+ binds1 (list g1 val1) binds2 (list g2 val2)) 569 | (,setter1 ,g2) 570 | (,setter2 ,g1))))) 571 | 572 | (mac rotate places 573 | (with (vars (map [uniq] places) 574 | forms (map setforms places)) 575 | `(atwiths ,(mappend (fn (g (binds val setter)) 576 | (+ binds (list g val))) 577 | vars 578 | forms) 579 | ,@(map (fn (g (binds val setter)) 580 | (list setter g)) 581 | (+ (cdr vars) (list (car vars))) 582 | forms)))) 583 | 584 | (mac pop (place) 585 | (w/uniq g 586 | (let (binds val setter) (setforms place) 587 | `(atwiths ,(+ binds (list g val)) 588 | (do1 (car ,g) 589 | (,setter (cdr ,g))))))) 590 | 591 | (def adjoin (x xs (o test iso)) 592 | (if (some [test x _] xs) 593 | xs 594 | (cons x xs))) 595 | 596 | (mac pushnew (x place . args) 597 | (w/uniq gx 598 | (let (binds val setter) (setforms place) 599 | `(atwiths ,(+ (list gx x) binds) 600 | (,setter (adjoin ,gx ,val ,@args)))))) 601 | 602 | (mac pull (test place) 603 | (w/uniq g 604 | (let (binds val setter) (setforms place) 605 | `(atwiths ,(+ (list g test) binds) 606 | (,setter (rem ,g ,val)))))) 607 | 608 | (mac togglemem (x place . args) 609 | (w/uniq gx 610 | (let (binds val setter) (setforms place) 611 | `(atwiths ,(+ (list gx x) binds) 612 | (,setter (if (mem ,gx ,val) 613 | (rem ,gx ,val) 614 | (adjoin ,gx ,val ,@args))))))) 615 | 616 | (mac ++ (place (o i 1)) 617 | (if (isa place 'sym) 618 | `(= ,place (+ ,place ,i)) 619 | (w/uniq gi 620 | (let (binds val setter) (setforms place) 621 | `(atwiths ,(+ binds (list gi i)) 622 | (,setter (+ ,val ,gi))))))) 623 | 624 | (mac -- (place (o i 1)) 625 | (if (isa place 'sym) 626 | `(= ,place (- ,place ,i)) 627 | (w/uniq gi 628 | (let (binds val setter) (setforms place) 629 | `(atwiths ,(+ binds (list gi i)) 630 | (,setter (- ,val ,gi))))))) 631 | 632 | ; E.g. (++ x) equiv to (zap + x 1) 633 | 634 | (mac zap (op place . args) 635 | (with (gop (uniq) 636 | gargs (map [uniq] args) 637 | mix (afn seqs 638 | (if (some no seqs) 639 | nil 640 | (+ (map car seqs) 641 | (apply self (map cdr seqs)))))) 642 | (let (binds val setter) (setforms place) 643 | `(atwiths ,(+ binds (list gop op) (mix gargs args)) 644 | (,setter (,gop ,val ,@gargs)))))) 645 | 646 | ; Can't simply mod pr to print strings represented as lists of chars, 647 | ; because empty string will get printed as nil. Would need to rep strings 648 | ; as lists of chars annotated with 'string, and modify car and cdr to get 649 | ; the rep of these. That would also require hacking the reader. 650 | 651 | (def pr args 652 | (map1 disp args) 653 | (car args)) 654 | 655 | (def prt args 656 | (map1 [if _ (disp _)] args) 657 | (car args)) 658 | 659 | (def prn args 660 | (do1 (apply pr args) 661 | (writec #\newline))) 662 | 663 | (mac wipe args 664 | `(do ,@(map (fn (a) `(= ,a nil)) args))) 665 | 666 | (mac set args 667 | `(do ,@(map (fn (a) `(= ,a t)) args))) 668 | 669 | ; Destructuring means ambiguity: are pat vars bound in else? (no) 670 | 671 | (mac iflet (var expr then . rest) 672 | (w/uniq gv 673 | `(let ,gv ,expr 674 | (if ,gv (let ,var ,gv ,then) ,@rest)))) 675 | 676 | (mac whenlet (var expr . body) 677 | `(iflet ,var ,expr (do ,@body))) 678 | 679 | (mac aif (expr . body) 680 | `(let it ,expr 681 | (if it 682 | ,@(if (cddr body) 683 | `(,(car body) (aif ,@(cdr body))) 684 | body)))) 685 | 686 | (mac awhen (expr . body) 687 | `(let it ,expr (if it (do ,@body)))) 688 | 689 | (mac aand args 690 | (if (no args) 691 | 't 692 | (no (cdr args)) 693 | (car args) 694 | `(let it ,(car args) (and it (aand ,@(cdr args)))))) 695 | 696 | (mac accum (accfn . body) 697 | (w/uniq gacc 698 | `(withs (,gacc nil ,accfn [push _ ,gacc]) 699 | ,@body 700 | (rev ,gacc)))) 701 | 702 | ; Repeatedly evaluates its body till it returns nil, then returns vals. 703 | 704 | (mac drain (expr (o eof nil)) 705 | (w/uniq (gacc gdone gres) 706 | `(with (,gacc nil ,gdone nil) 707 | (while (no ,gdone) 708 | (let ,gres ,expr 709 | (if (is ,gres ,eof) 710 | (= ,gdone t) 711 | (push ,gres ,gacc)))) 712 | (rev ,gacc)))) 713 | 714 | ; For the common C idiom while x = snarfdata != stopval. 715 | ; Rename this if use it often. 716 | 717 | (mac whiler (var expr endval . body) 718 | (w/uniq gf 719 | `(withs (,var nil ,gf (testify ,endval)) 720 | (while (no (,gf (= ,var ,expr))) 721 | ,@body)))) 722 | 723 | ;(def macex (e) 724 | ; (if (atom e) 725 | ; e 726 | ; (let op (and (atom (car e)) (eval (car e))) 727 | ; (if (isa op 'mac) 728 | ; (apply (rep op) (cdr e)) 729 | ; e)))) 730 | 731 | (def consif (x y) (if x (cons x y) y)) 732 | 733 | (def string args 734 | (apply + "" (map [coerce _ 'string] args))) 735 | 736 | (def flat x 737 | ((afn (x acc) 738 | (if (no x) acc 739 | (atom x) (cons x acc) 740 | (self (car x) (self (cdr x) acc)))) 741 | x nil)) 742 | 743 | (mac check (x test (o alt)) 744 | (w/uniq gx 745 | `(let ,gx ,x 746 | (if (,test ,gx) ,gx ,alt)))) 747 | 748 | (def pos (test seq (o start 0)) 749 | (let f (testify test) 750 | (if (alist seq) 751 | ((afn (seq n) 752 | (if (no seq) 753 | nil 754 | (f (car seq)) 755 | n 756 | (self (cdr seq) (+ n 1)))) 757 | (nthcdr start seq) 758 | start) 759 | (recstring [if (f (seq _)) _] seq start)))) 760 | 761 | (def even (n) (is (mod n 2) 0)) 762 | 763 | (def odd (n) (no (even n))) 764 | 765 | (mac after (x . ys) 766 | `(protect (fn () ,x) (fn () ,@ys))) 767 | 768 | (let expander 769 | (fn (f var name body) 770 | `(let ,var (,f ,name) 771 | (after (do ,@body) (close ,var)))) 772 | 773 | (mac w/infile (var name . body) 774 | (expander 'infile var name body)) 775 | 776 | (mac w/outfile (var name . body) 777 | (expander 'outfile var name body)) 778 | 779 | (mac w/instring (var str . body) 780 | (expander 'instring var str body)) 781 | 782 | (mac w/socket (var port . body) 783 | (expander 'open-socket var port body)) 784 | ) 785 | 786 | (mac w/outstring (var . body) 787 | `(let ,var (outstring) ,@body)) 788 | 789 | ; what happens to a file opened for append if arc is killed in 790 | ; the middle of a write? 791 | 792 | (mac w/appendfile (var name . body) 793 | `(let ,var (outfile ,name 'append) 794 | (after (do ,@body) (close ,var)))) 795 | 796 | ; rename this simply "to"? - prob not; rarely use 797 | 798 | (mac w/stdout (str . body) 799 | `(call-w/stdout ,str (fn () ,@body))) 800 | 801 | (mac w/stdin (str . body) 802 | `(call-w/stdin ,str (fn () ,@body))) 803 | 804 | (mac tostring body 805 | (w/uniq gv 806 | `(w/outstring ,gv 807 | (w/stdout ,gv ,@body) 808 | (inside ,gv)))) 809 | 810 | (mac fromstring (str . body) 811 | (w/uniq gv 812 | `(w/instring ,gv ,str 813 | (w/stdin ,gv ,@body)))) 814 | 815 | (def readstring1 (s (o eof nil)) (w/instring i s (read i eof))) 816 | 817 | (def read ((o x (stdin)) (o eof nil)) 818 | (if (isa x 'string) (readstring1 x eof) (sread x eof))) 819 | 820 | ; inconsistency between names of readfile[1] and writefile 821 | 822 | (def readfile (name) (w/infile s name (drain (read s)))) 823 | 824 | (def readfile1 (name) (w/infile s name (read s))) 825 | 826 | (def readall (src (o eof nil)) 827 | ((afn (i) 828 | (let x (read i eof) 829 | (if (is x eof) 830 | nil 831 | (cons x (self i))))) 832 | (if (isa src 'string) (instring src) src))) 833 | 834 | (def allchars (str) 835 | (tostring (whiler c (readc str nil) no 836 | (writec c)))) 837 | 838 | (def filechars (name) 839 | (w/infile s name (allchars s))) 840 | 841 | (def writefile (val file) 842 | (let tmpfile (+ file ".tmp") 843 | (w/outfile o tmpfile (write val o)) 844 | (mvfile tmpfile file)) 845 | val) 846 | 847 | (def sym (x) (coerce x 'sym)) 848 | 849 | (def int (x (o b 10)) (coerce x 'int b)) 850 | 851 | (mac rand-choice exprs 852 | `(case (rand ,(len exprs)) 853 | ,@(let key -1 854 | (mappend [list (++ key) _] 855 | exprs)))) 856 | 857 | (mac n-of (n expr) 858 | (w/uniq ga 859 | `(let ,ga nil 860 | (repeat ,n (push ,expr ,ga)) 861 | (rev ,ga)))) 862 | 863 | ; rejects bytes >= 248 lest digits be overrepresented 864 | 865 | (def rand-string (n) 866 | (let c "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 867 | (with (nc 62 s (newstring n) i 0) 868 | (w/infile str "/dev/urandom" 869 | (while (< i n) 870 | (let x (readb str) 871 | (unless (> x 247) 872 | (= (s i) (c (mod x nc))) 873 | (++ i))))) 874 | s))) 875 | 876 | (mac forlen (var s . body) 877 | `(for ,var 0 (- (len ,s) 1) ,@body)) 878 | 879 | (mac on (var s . body) 880 | (if (is var 'index) 881 | (err "Can't use index as first arg to on.") 882 | (w/uniq gs 883 | `(let ,gs ,s 884 | (forlen index ,gs 885 | (let ,var (,gs index) 886 | ,@body)))))) 887 | 888 | (def best (f seq) 889 | (if (no seq) 890 | nil 891 | (let wins (car seq) 892 | (each elt (cdr seq) 893 | (if (f elt wins) (= wins elt))) 894 | wins))) 895 | 896 | (def max args (best > args)) 897 | (def min args (best < args)) 898 | 899 | ; (mac max2 (x y) 900 | ; (w/uniq (a b) 901 | ; `(with (,a ,x ,b ,y) (if (> ,a ,b) ,a ,b)))) 902 | 903 | (def most (f seq) 904 | (unless (no seq) 905 | (withs (wins (car seq) topscore (f wins)) 906 | (each elt (cdr seq) 907 | (let score (f elt) 908 | (if (> score topscore) (= wins elt topscore score)))) 909 | wins))) 910 | 911 | ; Insert so that list remains sorted. Don't really want to expose 912 | ; these but seem to have to because can't include a fn obj in a 913 | ; macroexpansion. 914 | 915 | (def insert-sorted (test elt seq) 916 | (if (no seq) 917 | (list elt) 918 | (test elt (car seq)) 919 | (cons elt seq) 920 | (cons (car seq) (insert-sorted test elt (cdr seq))))) 921 | 922 | (mac insort (test elt seq) 923 | `(zap [insert-sorted ,test ,elt _] ,seq)) 924 | 925 | (def reinsert-sorted (test elt seq) 926 | (if (no seq) 927 | (list elt) 928 | (is elt (car seq)) 929 | (reinsert-sorted test elt (cdr seq)) 930 | (test elt (car seq)) 931 | (cons elt (rem elt seq)) 932 | (cons (car seq) (reinsert-sorted test elt (cdr seq))))) 933 | 934 | (mac insortnew (test elt seq) 935 | `(zap [reinsert-sorted ,test ,elt _] ,seq)) 936 | 937 | ; Could make this look at the sig of f and return a fn that took the 938 | ; right no of args and didn't have to call apply (or list if 1 arg). 939 | 940 | (def memo (f) 941 | (with (cache (table) nilcache (table)) 942 | (fn args 943 | (or (cache args) 944 | (and (no (nilcache args)) 945 | (aif (apply f args) 946 | (= (cache args) it) 947 | (do (set (nilcache args)) 948 | nil))))))) 949 | 950 | 951 | (mac defmemo (name parms . body) 952 | `(safeset ,name (memo (fn ,parms ,@body)))) 953 | 954 | (def <= args 955 | (or (no args) 956 | (no (cdr args)) 957 | (and (no (> (car args) (cadr args))) 958 | (apply <= (cdr args))))) 959 | 960 | (def >= args 961 | (or (no args) 962 | (no (cdr args)) 963 | (and (no (< (car args) (cadr args))) 964 | (apply >= (cdr args))))) 965 | 966 | (def whitec (c) 967 | (in c #\space #\newline #\tab #\return)) 968 | 969 | (def nonwhite (c) (no (whitec c))) 970 | 971 | (def letter (c) (or (<= #\a c #\z) (<= #\A c #\Z))) 972 | 973 | (def digit (c) (<= #\0 c #\9)) 974 | 975 | (def alphadig (c) (or (letter c) (digit c))) 976 | 977 | (def punc (c) 978 | (in c #\. #\, #\; #\: #\! #\?)) 979 | 980 | (def readline ((o str (stdin))) 981 | (awhen (readc str) 982 | (tostring 983 | (writec it) 984 | (whiler c (readc str) [in _ nil #\newline] 985 | (writec c))))) 986 | 987 | ; Don't currently use this but suspect some code could. 988 | 989 | (mac summing (sumfn . body) 990 | (w/uniq (gc gt) 991 | `(let ,gc 0 992 | (let ,sumfn (fn (,gt) (if ,gt (++ ,gc))) 993 | ,@body) 994 | ,gc))) 995 | 996 | (def sum (f xs) 997 | (let n 0 998 | (each x xs (++ n (f x))) 999 | n)) 1000 | 1001 | (def treewise (f base tree) 1002 | (if (atom tree) 1003 | (base tree) 1004 | (f (treewise f base (car tree)) 1005 | (treewise f base (cdr tree))))) 1006 | 1007 | (def carif (x) (if (atom x) x (car x))) 1008 | 1009 | ; Could prob be generalized beyond printing. 1010 | 1011 | (def prall (elts (o init "") (o sep ", ")) 1012 | (when elts 1013 | (pr init (car elts)) 1014 | (map [pr sep _] (cdr elts)) 1015 | elts)) 1016 | 1017 | (def prs args 1018 | (prall args "" #\space)) 1019 | 1020 | (def tree-subst (old new tree) 1021 | (if (is tree old) 1022 | new 1023 | (atom tree) 1024 | tree 1025 | (cons (tree-subst old new (car tree)) 1026 | (tree-subst old new (cdr tree))))) 1027 | 1028 | (def ontree (f tree) 1029 | (f tree) 1030 | (unless (atom tree) 1031 | (ontree f (car tree)) 1032 | (ontree f (cdr tree)))) 1033 | 1034 | (def dotted (x) 1035 | (if (atom x) 1036 | nil 1037 | (and (cdr x) (or (atom (cdr x)) 1038 | (dotted (cdr x)))))) 1039 | 1040 | (def fill-table (table data) 1041 | (each (k v) (pair data) (= (table k) v)) 1042 | table) 1043 | 1044 | (def keys (h) 1045 | (accum a (each (k v) h (a k)))) 1046 | 1047 | (def vals (h) 1048 | (accum a (each (k v) h (a v)))) 1049 | 1050 | ; These two should really be done by coerce. Wrap coerce? 1051 | 1052 | (def tablist (h) 1053 | (accum a (maptable (fn args (a args)) h))) 1054 | 1055 | (def listtab (al) 1056 | (let h (table) 1057 | (map (fn ((k v)) (= (h k) v)) 1058 | al) 1059 | h)) 1060 | 1061 | (mac obj args 1062 | `(listtab (list ,@(map (fn ((k v)) 1063 | `(list ',k ,v)) 1064 | (pair args))))) 1065 | 1066 | (def load-table (file (o eof)) 1067 | (w/infile i file (read-table i eof))) 1068 | 1069 | (def read-table ((o i (stdin)) (o eof)) 1070 | (let e (read i eof) 1071 | (if (alist e) (listtab e) e))) 1072 | 1073 | (def load-tables (file) 1074 | (w/infile i file 1075 | (w/uniq eof 1076 | (drain (read-table i eof) eof)))) 1077 | 1078 | (def save-table (h file) 1079 | (writefile (tablist h) file)) 1080 | 1081 | (def write-table (h (o o (stdout))) 1082 | (write (tablist h) o)) 1083 | 1084 | (def copy (x . args) 1085 | (let x2 (case (type x) 1086 | sym x 1087 | cons (copylist x) ; (apply (fn args args) x) 1088 | string (let new (newstring (len x)) 1089 | (forlen i x 1090 | (= (new i) (x i))) 1091 | new) 1092 | table (let new (table) 1093 | (each (k v) x 1094 | (= (new k) v)) 1095 | new) 1096 | (err "Can't copy " x)) 1097 | (map (fn ((k v)) (= (x2 k) v)) 1098 | (pair args)) 1099 | x2)) 1100 | 1101 | (def abs (n) 1102 | (if (< n 0) (- n) n)) 1103 | 1104 | ; The problem with returning a list instead of multiple values is that 1105 | ; you can't act as if the fn didn't return multiple vals in cases where 1106 | ; you only want the first. Not a big problem. 1107 | 1108 | (def round (n) 1109 | (withs (base (trunc n) rem (abs (- n base))) 1110 | (if (> rem 1/2) ((if (> n 0) + -) base 1) 1111 | (< rem 1/2) base 1112 | (odd base) ((if (> n 0) + -) base 1) 1113 | base))) 1114 | 1115 | (def roundup (n) 1116 | (withs (base (trunc n) rem (abs (- n base))) 1117 | (if (>= rem 1/2) 1118 | ((if (> n 0) + -) base 1) 1119 | base))) 1120 | 1121 | (def nearest (n quantum) 1122 | (* (roundup (/ n quantum)) quantum)) 1123 | 1124 | (def avg (ns) (/ (apply + ns) (len ns))) 1125 | 1126 | (def med (ns (o test >)) 1127 | ((sort test ns) (round (/ (len ns) 2)))) 1128 | 1129 | ; Use mergesort on assumption that mostly sorting mostly sorted lists 1130 | ; benchmark: (let td (n-of 10000 (rand 100)) (time (sort < td)) 1) 1131 | 1132 | (def sort (test seq) 1133 | (if (alist seq) 1134 | (mergesort test (copy seq)) 1135 | (coerce (mergesort test (coerce seq 'cons)) (type seq)))) 1136 | 1137 | ; Destructive stable merge-sort, adapted from slib and improved 1138 | ; by Eli Barzilay for MzLib; re-written in Arc. 1139 | 1140 | (def mergesort (less? lst) 1141 | (with (n (len lst)) 1142 | (if (<= n 1) lst 1143 | ; ; check if the list is already sorted 1144 | ; ; (which can be a common case, eg, directory lists). 1145 | ; (let loop ([last (car lst)] [next (cdr lst)]) 1146 | ; (or (null? next) 1147 | ; (and (not (less? (car next) last)) 1148 | ; (loop (car next) (cdr next))))) 1149 | ; lst 1150 | ((afn (n) 1151 | (if (> n 2) 1152 | ; needs to evaluate L->R 1153 | (withs (j (/ (if (even n) n (- n 1)) 2) ; faster than round 1154 | a (self j) 1155 | b (self (- n j))) 1156 | (merge less? a b)) 1157 | ; the following case just inlines the length 2 case, 1158 | ; it can be removed (and use the above case for n>1) 1159 | ; and the code still works, except a little slower 1160 | (is n 2) 1161 | (with (x (car lst) y (cadr lst) p lst) 1162 | (= lst (cddr lst)) 1163 | (when (less? y x) (scar p y) (scar (cdr p) x)) 1164 | (scdr (cdr p) nil) 1165 | p) 1166 | (is n 1) 1167 | (with (p lst) 1168 | (= lst (cdr lst)) 1169 | (scdr p nil) 1170 | p) 1171 | nil)) 1172 | n)))) 1173 | 1174 | ; Also by Eli. 1175 | 1176 | (def merge (less? x y) 1177 | (if (no x) y 1178 | (no y) x 1179 | (let lup nil 1180 | (assign lup 1181 | (fn (r x y r-x?) ; r-x? for optimization -- is r connected to x? 1182 | (if (less? (car y) (car x)) 1183 | (do (if r-x? (scdr r y)) 1184 | (if (cdr y) (lup y x (cdr y) nil) (scdr y x))) 1185 | ; (car x) <= (car y) 1186 | (do (if (no r-x?) (scdr r x)) 1187 | (if (cdr x) (lup x (cdr x) y t) (scdr x y)))))) 1188 | (if (less? (car y) (car x)) 1189 | (do (if (cdr y) (lup y x (cdr y) nil) (scdr y x)) 1190 | y) 1191 | ; (car x) <= (car y) 1192 | (do (if (cdr x) (lup x (cdr x) y t) (scdr x y)) 1193 | x))))) 1194 | 1195 | (def bestn (n f seq) 1196 | (firstn n (sort f seq))) 1197 | 1198 | (def split (seq pos) 1199 | (list (cut seq 0 pos) (cut seq pos))) 1200 | 1201 | (mac time (expr) 1202 | (w/uniq (t1 t2) 1203 | `(let ,t1 (msec) 1204 | (do1 ,expr 1205 | (let ,t2 (msec) 1206 | (prn "time: " (- ,t2 ,t1) " msec.")))))) 1207 | 1208 | (mac jtime (expr) 1209 | `(do1 'ok (time ,expr))) 1210 | 1211 | (mac time10 (expr) 1212 | `(time (repeat 10 ,expr))) 1213 | 1214 | (def union (f xs ys) 1215 | (+ xs (rem (fn (y) (some [f _ y] xs)) 1216 | ys))) 1217 | 1218 | (= templates* (table)) 1219 | 1220 | (mac deftem (tem . fields) 1221 | (withs (name (carif tem) includes (if (acons tem) (cdr tem))) 1222 | `(= (templates* ',name) 1223 | (+ (mappend templates* ',(rev includes)) 1224 | (list ,@(map (fn ((k v)) `(list ',k (fn () ,v))) 1225 | (pair fields))))))) 1226 | 1227 | (mac addtem (name . fields) 1228 | `(= (templates* ',name) 1229 | (union (fn (x y) (is (car x) (car y))) 1230 | (list ,@(map (fn ((k v)) `(list ',k (fn () ,v))) 1231 | (pair fields))) 1232 | (templates* ',name)))) 1233 | 1234 | (def inst (tem . args) 1235 | (let x (table) 1236 | (each (k v) (templates* tem) 1237 | (unless (no v) (= (x k) (v)))) 1238 | (each (k v) (pair args) 1239 | (= (x k) v)) 1240 | x)) 1241 | 1242 | ; To write something to be read by temread, (write (tablist x)) 1243 | 1244 | (def temread (tem (o str (stdin))) 1245 | (templatize tem (read str))) 1246 | 1247 | ; Converts alist to inst; ugly; maybe should make this part of coerce. 1248 | ; Note: discards fields not defined by the template. 1249 | 1250 | (def templatize (tem raw) 1251 | (with (x (inst tem) fields (templates* tem)) 1252 | (each (k v) raw 1253 | (when (assoc k fields) 1254 | (= (x k) v))) 1255 | x)) 1256 | 1257 | (def temload (tem file) 1258 | (w/infile i file (temread tem i))) 1259 | 1260 | (def temloadall (tem file) 1261 | (map (fn (pairs) (templatize tem pairs)) 1262 | (w/infile in file (readall in)))) 1263 | 1264 | 1265 | (def number (n) (in (type n) 'int 'num)) 1266 | 1267 | (def since (t1) (- (seconds) t1)) 1268 | 1269 | (def minutes-since (t1) (/ (since t1) 60)) 1270 | (def hours-since (t1) (/ (since t1) 3600)) 1271 | (def days-since (t1) (/ (since t1) 86400)) 1272 | 1273 | ; could use a version for fns of 1 arg at least 1274 | 1275 | (def cache (timef valf) 1276 | (with (cached nil gentime nil) 1277 | (fn () 1278 | (unless (and cached (< (since gentime) (timef))) 1279 | (= cached (valf) 1280 | gentime (seconds))) 1281 | cached))) 1282 | 1283 | (mac defcache (name lasts . body) 1284 | `(safeset ,name (cache (fn () ,lasts) 1285 | (fn () ,@body)))) 1286 | 1287 | (mac errsafe (expr) 1288 | `(on-err (fn (c) nil) 1289 | (fn () ,expr))) 1290 | 1291 | (def saferead (arg) (errsafe:read arg)) 1292 | 1293 | (def safe-load-table (filename) 1294 | (or (errsafe:load-table filename) 1295 | (table))) 1296 | 1297 | (def ensure-dir (path) 1298 | (unless (dir-exists path) 1299 | (system (string "mkdir -p " path)))) 1300 | 1301 | (def date ((o s (seconds))) 1302 | (rev (nthcdr 3 (timedate s)))) 1303 | 1304 | (def datestring ((o s (seconds))) 1305 | (let (y m d) (date s) 1306 | (string y "-" (if (< m 10) "0") m "-" (if (< d 10) "0") d))) 1307 | 1308 | (def count (test x) 1309 | (with (n 0 testf (testify test)) 1310 | (each elt x 1311 | (if (testf elt) (++ n))) 1312 | n)) 1313 | 1314 | (def ellipsize (str (o limit 80)) 1315 | (if (<= (len str) limit) 1316 | str 1317 | (+ (cut str 0 limit) "..."))) 1318 | 1319 | (def rand-elt (seq) 1320 | (seq (rand (len seq)))) 1321 | 1322 | (mac until (test . body) 1323 | `(while (no ,test) ,@body)) 1324 | 1325 | (def before (x y seq (o i 0)) 1326 | (with (xp (pos x seq i) yp (pos y seq i)) 1327 | (and xp (or (no yp) (< xp yp))))) 1328 | 1329 | (def orf fns 1330 | (fn args 1331 | ((afn (fs) 1332 | (and fs (or (apply (car fs) args) (self (cdr fs))))) 1333 | fns))) 1334 | 1335 | (def andf fns 1336 | (fn args 1337 | ((afn (fs) 1338 | (if (no fs) t 1339 | (no (cdr fs)) (apply (car fs) args) 1340 | (and (apply (car fs) args) (self (cdr fs))))) 1341 | fns))) 1342 | 1343 | (def atend (i s) 1344 | (> i (- (len s) 2))) 1345 | 1346 | (def multiple (x y) 1347 | (is 0 (mod x y))) 1348 | 1349 | (mac nor args `(no (or ,@args))) 1350 | 1351 | ; Consider making the default sort fn take compare's two args (when do 1352 | ; you ever have to sort mere lists of numbers?) and rename current sort 1353 | ; as prim-sort or something. 1354 | 1355 | ; Could simply modify e.g. > so that (> len) returned the same thing 1356 | ; as (compare > len). 1357 | 1358 | (def compare (comparer scorer) 1359 | (fn (x y) (comparer (scorer x) (scorer y)))) 1360 | 1361 | ; Cleaner thus, but may only ever need in 2 arg case. 1362 | 1363 | ;(def compare (comparer scorer) 1364 | ; (fn args (apply comparer map scorer args))) 1365 | 1366 | ; (def only (f g . args) (aif (apply g args) (f it))) 1367 | 1368 | (def only (f) 1369 | (fn args (if (car args) (apply f args)))) 1370 | 1371 | (mac conswhen (f x y) 1372 | (w/uniq (gf gx) 1373 | `(with (,gf ,f ,gx ,x) 1374 | (if (,gf ,gx) (cons ,gx ,y) ,y)))) 1375 | 1376 | ; Could combine with firstn if put f arg last, default to (fn (x) t). 1377 | 1378 | (def retrieve (n f xs) 1379 | (if (no n) (keep f xs) 1380 | (or (<= n 0) (no xs)) nil 1381 | (f (car xs)) (cons (car xs) (retrieve (- n 1) f (cdr xs))) 1382 | (retrieve n f (cdr xs)))) 1383 | 1384 | (def dedup (xs) 1385 | (with (h (table) acc nil) 1386 | (each x xs 1387 | (unless (h x) 1388 | (push x acc) 1389 | (set (h x)))) 1390 | (rev acc))) 1391 | 1392 | (def single (x) (and (acons x) (no (cdr x)))) 1393 | 1394 | (def intersperse (x ys) 1395 | (and ys (cons (car ys) 1396 | (mappend [list x _] (cdr ys))))) 1397 | 1398 | (def counts (seq (o c (table))) 1399 | (if (no seq) 1400 | c 1401 | (do (++ (c (car seq) 0)) 1402 | (counts (cdr seq) c)))) 1403 | 1404 | (def commonest (seq) 1405 | (with (winner nil n 0) 1406 | (each (k v) (counts seq) 1407 | (when (> v n) (= winner k n v))) 1408 | (list winner n))) 1409 | 1410 | (def reduce (f xs) 1411 | (if (cddr xs) 1412 | (reduce f (cons (f (car xs) (cadr xs)) (cddr xs))) 1413 | (apply f xs))) 1414 | 1415 | (def rreduce (f xs) 1416 | (if (cddr xs) 1417 | (f (car xs) (rreduce f (cdr xs))) 1418 | (apply f xs))) 1419 | 1420 | (let argsym (uniq) 1421 | 1422 | (def parse-format (str) 1423 | (accum a 1424 | (with (chars nil i -1) 1425 | (w/instring s str 1426 | (whilet c (readc s) 1427 | (case c 1428 | #\# (do (a (coerce (rev chars) 'string)) 1429 | (wipe chars) 1430 | (a (read s))) 1431 | #\~ (do (a (coerce (rev chars) 'string)) 1432 | (wipe chars) 1433 | (readc s) 1434 | (a (list argsym (++ i)))) 1435 | (push c chars)))) 1436 | (when chars 1437 | (a (coerce (rev chars) 'string)))))) 1438 | 1439 | (mac prf (str . args) 1440 | `(let ,argsym (list ,@args) 1441 | (pr ,@(parse-format str)))) 1442 | ) 1443 | 1444 | (def load (file) 1445 | (w/infile f file 1446 | (w/uniq eof 1447 | (whiler e (read f eof) eof 1448 | (eval e))))) 1449 | 1450 | (def positive (x) 1451 | (and (number x) (> x 0))) 1452 | 1453 | (mac w/table (var . body) 1454 | `(let ,var (table) ,@body ,var)) 1455 | 1456 | (def ero args 1457 | (w/stdout (stderr) 1458 | (each a args 1459 | (write a) 1460 | (writec #\space)) 1461 | (writec #\newline)) 1462 | (car args)) 1463 | 1464 | (def queue () (list nil nil 0)) 1465 | 1466 | ; Despite call to atomic, once had some sign this wasn't thread-safe. 1467 | ; Keep an eye on it. 1468 | 1469 | (def enq (obj q) 1470 | (atomic 1471 | (++ (q 2)) 1472 | (if (no (car q)) 1473 | (= (cadr q) (= (car q) (list obj))) 1474 | (= (cdr (cadr q)) (list obj) 1475 | (cadr q) (cdr (cadr q)))) 1476 | (car q))) 1477 | 1478 | (def deq (q) 1479 | (atomic (unless (is (q 2) 0) (-- (q 2))) 1480 | (pop (car q)))) 1481 | 1482 | ; Should redef len to do this, and make queues lists annotated queue. 1483 | 1484 | (def qlen (q) (q 2)) 1485 | 1486 | (def qlist (q) (car q)) 1487 | 1488 | (def enq-limit (val q (o limit 1000)) 1489 | (atomic 1490 | (unless (< (qlen q) limit) 1491 | (deq q)) 1492 | (enq val q))) 1493 | 1494 | (def median (ns) 1495 | ((sort > ns) (trunc (/ (len ns) 2)))) 1496 | 1497 | (mac noisy-each (n var val . body) 1498 | (w/uniq (gn gc) 1499 | `(with (,gn ,n ,gc 0) 1500 | (each ,var ,val 1501 | (when (multiple (++ ,gc) ,gn) 1502 | (pr ".") 1503 | (flushout) 1504 | ) 1505 | ,@body) 1506 | (prn) 1507 | (flushout)))) 1508 | 1509 | (mac point (name . body) 1510 | (w/uniq (g p) 1511 | `(ccc (fn (,g) 1512 | (let ,name (fn ((o ,p)) (,g ,p)) 1513 | ,@body))))) 1514 | 1515 | (mac catch body 1516 | `(point throw ,@body)) 1517 | 1518 | (def downcase (x) 1519 | (let downc (fn (c) 1520 | (let n (coerce c 'int) 1521 | (if (or (< 64 n 91) (< 191 n 215) (< 215 n 223)) 1522 | (coerce (+ n 32) 'char) 1523 | c))) 1524 | (case (type x) 1525 | string (map downc x) 1526 | char (downc x) 1527 | sym (sym (map downc (coerce x 'string))) 1528 | (err "Can't downcase" x)))) 1529 | 1530 | (def upcase (x) 1531 | (let upc (fn (c) 1532 | (let n (coerce c 'int) 1533 | (if (or (< 96 n 123) (< 223 n 247) (< 247 n 255)) 1534 | (coerce (- n 32) 'char) 1535 | c))) 1536 | (case (type x) 1537 | string (map upc x) 1538 | char (upc x) 1539 | sym (sym (map upc (coerce x 'string))) 1540 | (err "Can't upcase" x)))) 1541 | 1542 | (def inc (x (o n 1)) 1543 | (coerce (+ (coerce x 'int) n) (type x))) 1544 | 1545 | (def range (start end) 1546 | (if (> start end) 1547 | nil 1548 | (cons start (range (inc start) end)))) 1549 | 1550 | (def mismatch (s1 s2) 1551 | (catch 1552 | (on c s1 1553 | (when (isnt c (s2 index)) 1554 | (throw index))))) 1555 | 1556 | (def memtable (ks) 1557 | (let h (table) 1558 | (each k ks (set (h k))) 1559 | h)) 1560 | 1561 | (= bar* " | ") 1562 | 1563 | (mac w/bars body 1564 | (w/uniq (out needbars) 1565 | `(let ,needbars nil 1566 | (do ,@(map (fn (e) 1567 | `(let ,out (tostring ,e) 1568 | (unless (is ,out "") 1569 | (if ,needbars 1570 | (pr bar* ,out) 1571 | (do (set ,needbars) 1572 | (pr ,out)))))) 1573 | body))))) 1574 | 1575 | (def len< (x n) (< (len x) n)) 1576 | 1577 | (def len> (x n) (> (len x) n)) 1578 | 1579 | (mac thread body 1580 | `(new-thread (fn () ,@body))) 1581 | 1582 | (mac trav (x . fs) 1583 | (w/uniq g 1584 | `((afn (,g) 1585 | (when ,g 1586 | ,@(map [list _ g] fs))) 1587 | ,x))) 1588 | 1589 | (mac or= (place expr) 1590 | (let (binds val setter) (setforms place) 1591 | `(atwiths ,binds 1592 | (or ,val (,setter ,expr))))) 1593 | 1594 | (= hooks* (table)) 1595 | 1596 | (def hook (name . args) 1597 | (aif (hooks* name) (apply it args))) 1598 | 1599 | (mac defhook (name . rest) 1600 | `(= (hooks* ',name) (fn ,@rest))) 1601 | 1602 | (mac out (expr) `(pr ,(tostring (eval expr)))) 1603 | 1604 | ; if renamed this would be more natural for (map [_ user] pagefns*) 1605 | 1606 | (def get (index) [_ index]) 1607 | 1608 | (= savers* (table)) 1609 | 1610 | (mac fromdisk (var file init load save) 1611 | (w/uniq (gf gv) 1612 | `(unless (bound ',var) 1613 | (do1 (= ,var (iflet ,gf (file-exists ,file) 1614 | (,load ,gf) 1615 | ,init)) 1616 | (= (savers* ',var) (fn (,gv) (,save ,gv ,file))))))) 1617 | 1618 | (mac diskvar (var file) 1619 | `(fromdisk ,var ,file nil readfile1 writefile)) 1620 | 1621 | (mac disktable (var file) 1622 | `(fromdisk ,var ,file (table) load-table save-table)) 1623 | 1624 | (mac todisk (var (o expr var)) 1625 | `((savers* ',var) 1626 | ,(if (is var expr) var `(= ,var ,expr)))) 1627 | 1628 | 1629 | (mac evtil (expr test) 1630 | (w/uniq gv 1631 | `(let ,gv ,expr 1632 | (while (no (,test ,gv)) 1633 | (= ,gv ,expr)) 1634 | ,gv))) 1635 | 1636 | (def rand-key (h) 1637 | (if (empty h) 1638 | nil 1639 | (let n (rand (len h)) 1640 | (catch 1641 | (each (k v) h 1642 | (when (is (-- n) -1) 1643 | (throw k))))))) 1644 | 1645 | (def ratio (test xs) 1646 | (if (empty xs) 1647 | 0 1648 | (/ (count test xs) (len xs)))) 1649 | 1650 | 1651 | ; any logical reason I can't say (push x (if foo y z)) ? 1652 | ; eval would have to always ret 2 things, the val and where it came from 1653 | ; idea: implicit tables of tables; setf empty field, becomes table 1654 | ; or should setf on a table just take n args? 1655 | 1656 | ; idea: use constants in functional position for currying? 1657 | ; (1 foo) would mean (fn args (apply foo 1 args)) 1658 | ; another solution would be to declare certain symbols curryable, and 1659 | ; if > was, >_10 would mean [> _ 10] 1660 | ; or just say what the hell and make _ ssyntax for currying 1661 | ; idea: make >10 ssyntax for [> _ 10] 1662 | ; solution to the "problem" of improper lists: allow any atom as a list 1663 | ; terminator, not just nil. means list recursion should terminate on 1664 | ; atom rather than nil, (def empty (x) (or (atom x) (is x ""))) 1665 | ; table should be able to take an optional initial-value. handle in sref. 1666 | ; warn about code of form (if (= )) -- probably mean is 1667 | ; warn when a fn has a parm that's already defined as a macro. 1668 | ; (def foo (after) (after)) 1669 | ; idea: a fn (nothing) that returns a special gensym which is ignored 1670 | ; by map, so can use map in cases when don't want all the vals 1671 | ; idea: anaph macro so instead of (aand x y) say (anaph and x y) 1672 | ; idea: foo.bar!baz as an abbrev for (foo bar 'baz) 1673 | ; or something a bit more semantic? 1674 | ; could uniq be (def uniq () (annotate 'symbol (list 'u))) again? 1675 | ; idea: use x- for (car x) and -x for (cdr x) (but what about math -?) 1676 | ; idea: get rid of strings and just use symbols 1677 | ; could a string be (#\a #\b . "") ? 1678 | ; better err msg when , outside of a bq 1679 | ; idea: parameter (p foo) means in body foo is (pair arg) 1680 | ; idea: make ('string x) equiv to (coerce x 'string) ? or isa? 1681 | ; quoted atoms in car valuable unused semantic space 1682 | ; idea: if (defun foo (x y) ...), make (foo 1) return (fn (y) (foo 1 y)) 1683 | ; probably would lead to lots of errors when call with missing args 1684 | ; but would be really dense with . notation, (foo.1 2) 1685 | ; or use special ssyntax for currying: (foo@1 2) 1686 | ; remember, can also double; could use foo::bar to mean something 1687 | ; wild idea: inline defs for repetitive code 1688 | ; same args as fn you're in 1689 | ; variant of compose where first fn only applied to first arg? 1690 | ; (> (len x) y) means (>+len x y) 1691 | ; use ssyntax underscore for a var? 1692 | ; foo_bar means [foo _ bar] 1693 | ; what does foo:_:bar mean? 1694 | ; matchcase 1695 | ; idea: atable that binds it to table, assumes input is a list 1696 | ; crazy that finding the top 100 nos takes so long: 1697 | ; (let bb (n-of 1000 (rand 50)) (time10 (bestn 100 > bb))) 1698 | ; time: 2237 msec. -> now down to 850 msec 1699 | 1700 | --------------------------------------------------------------------------------