arc0 vs arc3.1 lines with < are in arc0, aren't in arc3.1 lines with > aren't in arc0, are in arc3.1 lines with ========... are 80 chars wide ================================================================================ ac.scm ================================================================================ 2 < ; scheme48 3 < ; ,open tables sockets extended-ports c-system-function ascii i/o-internal 4 < ; ,open posix-files handle random pp simple-conditions 5 < 6 < ; to do: 7 < ; select, perhaps with threads, or pltt events 8 < ; check argument count for complex arguments 2 > ; Arc Compiler. 9 3 10 < ; refs.arc, first 300 lines of x, total CPU time including startup. on powerbook. 11 < ; scheme48: 31.944u 0.518s 2:13.65 24.2% 0+0k 5+7io 0pf+0w 12 < ; mzscheme: 16.425u 0.489s 0:52.61 32.1% 0+0k 26+22io 0pf+0w 13 < 14 < ; dynamic creation of local variables with = 15 < ; can you create globals inside a procedure? 16 < ; does action of = depend on whether, at run time, 17 < ; the variable has a global definition? 18 < ; what's the scope of such a variable? 19 < ; though a.lisp seems to create a global, not a local! 20 < ; run-time expansion of macros 21 < ; how do I know if something is a macro at compile time? 22 < ; macros have lexical scope. so how do i know if a lexical 23 < ; variable is going to be bound to a procedure? or to a macro? 24 < ; what is annotate doing to symbols? 25 < ; tests.arc implies that '(a b) produces a mutable list 26 < ; so (fn () '(a)) produces a list that can be modified, 27 < ; and future calls will reflect the modifications 28 < ; oy. clisp works this way too. 29 < ; it's not that easy to simulate this. 30 < ; what is this? (def foo1 (x (o y x)) (list x y)) 31 < 32 < ; it's not clear I translate NILs in the outputs of macros correctly. 33 < ; I translate (NIL . NIL) to ('NIL . '()) 34 < ; I use Scheme apply to call macros. 35 < ; Scheme apply demands a '()-terminated list. 36 < ; most macros have a . body argument. 37 < ; so body is '()-terminated, not NIL-terminated. 38 < ; solution: ar-false? knows about '() 39 < ; this doesn't work, since var isn't a variable name: 40 < ; (mac or args 41 < ; (and args 42 < ; (let var (tag 'symbol (list 'or)) 43 < ; (list 'let var (car args) 44 < ; (list 'if var var (cons 'or (cdr args))))))) 45 < 46 4 (module ac mzscheme 47 5 48 6 (provide (all-defined)) 49 7 (require (lib "port.ss")) 50 8 (require (lib "process.ss")) 51 9 (require (lib "pretty.ss")) 10 > (require (lib "foreign.ss")) 11 > (unsafe!) 52 12 53 13 ; compile an Arc expression into a Scheme expression, 54 14 ; both represented as s-expressions. 55 15 ; env is a list of lexically bound variables, which we 56 16 ; need in order to decide whether set should create a global. 57 17 58 18 (define (ac s env) 59 < (cond ((string? s) (string-copy s)) ; to avoid immutable strings 19 > (cond ((string? s) (ac-string s env)) 60 20 ((literal? s) s) 61 21 ((eqv? s 'nil) (list 'quote 'nil)) 62 22 ((ssyntax? s) (ac (expand-ssyntax s) env)) 63 23 ((symbol? s) (ac-var-ref s env)) 64 24 ((ssyntax? (xcar s)) (ac (cons (expand-ssyntax (car s)) (cdr s)) env)) 65 25 ((eq? (xcar s) 'quote) (list 'quote (ac-niltree (cadr s)))) 66 26 ((eq? (xcar s) 'quasiquote) (ac-qq (cadr s) env)) 67 27 ((eq? (xcar s) 'if) (ac-if (cdr s) env)) 68 28 ((eq? (xcar s) 'fn) (ac-fn (cadr s) (cddr s) env)) 69 < ((eq? (xcar s) 'set) (ac-set (cdr s) env)) 70 < ; this line could be removed without changing semantics 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) 71 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)) 72 37 ((pair? s) (ac-call (car s) (cdr s) env)) 73 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 74 53 75 54 (define (literal? x) 76 55 (or (boolean? x) 77 56 (char? x) 78 57 (string? x) 79 58 (number? x) 80 59 (eq? x '()))) 81 60 82 61 (define (ssyntax? x) 83 62 (and (symbol? x) 84 < (not (or (eqv? x '+) (eqv? x '++))) 63 > (not (or (eqv? x '+) (eqv? x '++) (eqv? x '_))) 85 64 (let ((name (symbol->string x))) 86 65 (has-ssyntax-char? name (- (string-length name) 1))))) 87 66 88 67 (define (has-ssyntax-char? string i) 89 68 (and (>= i 0) 90 69 (or (let ((c (string-ref string i))) 91 < (or (eqv? c #\:) (eqv? c #\~))) 70 > (or (eqv? c #\:) (eqv? c #\~) 71 > (eqv? c #\&) 72 > ;(eqv? c #\_) 73 > (eqv? c #\.) (eqv? c #\!))) 92 74 (has-ssyntax-char? string (- i 1))))) 93 75 94 76 (define (read-from-string str) 95 77 (let ((port (open-input-string str))) 96 78 (let ((val (read port))) 97 79 (close-input-port port) 98 80 val))) 99 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 > 100 90 (define (expand-ssyntax sym) 91 > ((cond ((or (insym? #\: sym) (insym? #\~ sym)) expand-compose) 92 > ((or (insym? #\. sym) (insym? #\! sym)) expand-sexpr) 93 > ((insym? #\& sym) expand-and) 94 > ; ((insym? #\_ sym) expand-curry) 95 > (#t (error "Unknown ssyntax" sym))) 96 > sym)) 97 > 98 > (define (expand-compose sym) 101 99 (let ((elts (map (lambda (tok) 102 100 (if (eqv? (car tok) #\~) 103 < `(complement ,(chars->value (cdr tok))) 101 > (if (null? (cdr tok)) 102 > 'no 103 > `(complement ,(chars->value (cdr tok)))) 104 104 (chars->value tok))) 105 < (tokens #\: (symbol->chars sym) '() '())))) 105 > (tokens (lambda (c) (eqv? c #\:)) 106 > (symbol->chars sym) 107 > '() 108 > '() 109 > #f)))) 106 110 (if (null? (cdr elts)) 107 111 (car elts) 108 112 (cons 'compose elts)))) 109 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 > 110 186 (define (symbol->chars x) (string->list (symbol->string x))) 111 187 112 188 (define (chars->value chars) (read-from-string (list->string chars))) 113 189 114 < ; result will contain || if separator at end of symbol; could use 115 < ; that to mean something 116 < 117 < (define (tokens separator source token acc) 190 > (define (tokens test source token acc keepsep?) 118 191 (cond ((null? source) 119 < (reverse (cons (reverse token) acc))) 120 < ((eqv? (car source) separator) 121 < (tokens separator 192 > (reverse (if (pair? token) 193 > (cons (reverse token) acc) 194 > acc))) 195 > ((test (car source)) 196 > (tokens test 122 197 (cdr source) 123 198 '() 124 < (cons (reverse token) acc))) 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?)) 125 206 (#t 126 < (tokens separator 207 > (tokens test 127 208 (cdr source) 128 209 (cons (car source) token) 129 < acc)))) 130 < 131 < ; Purely an optimization. Could in principle do it with a preprocessor 132 < ; instead of adding a line to ac, but only want to do it for evaluated 133 < ; subtrees, and much easier to figure those out in ac. 134 < 135 < (define (decompose fns args) 136 < (cond ((null? fns) `((fn vals (car vals)) ,@args)) 137 < ((null? (cdr fns)) (cons (car fns) args)) 138 < (#t (list (car fns) (decompose (cdr fns) args))))) 139 < 210 > acc 211 > keepsep?)))) 140 212 141 213 (define (ac-global-name s) 142 214 (string->symbol (string-append "_" (symbol->string s)))) 143 215 144 216 (define (ac-var-ref s env) 145 217 (if (lex? s env) 146 218 s 147 219 (ac-global-name s))) 148 220 149 221 ; quasiquote 150 222 151 223 (define (ac-qq args env) 152 224 (list 'quasiquote (ac-qq1 1 args env))) 153 225 154 226 ; process the argument of a quasiquote. keep track of 155 227 ; depth of nesting. handle unquote only at top level (level = 1). 156 228 ; complete form, e.g. x or (fn x) or (unquote (fn x)) 229 > 157 230 (define (ac-qq1 level x env) 158 231 (cond ((= level 0) 159 232 (ac x env)) 160 233 ((and (pair? x) (eqv? (car x) 'unquote)) 161 234 (list 'unquote (ac-qq1 (- level 1) (cadr x) env))) 162 235 ((and (pair? x) (eqv? (car x) 'unquote-splicing) (= level 1)) 163 236 (list 'unquote-splicing 164 237 (list 'ar-nil-terminate (ac-qq1 (- level 1) (cadr x) env)))) 165 238 ((and (pair? x) (eqv? (car x) 'quasiquote)) 166 239 (list 'quasiquote (ac-qq1 (+ level 1) (cadr x) env))) 167 240 ((pair? x) 168 < (map (lambda (x) (ac-qq1 level x env)) x)) 241 > (imap (lambda (x) (ac-qq1 level x env)) x)) 169 242 (#t x))) 243 > 244 > ; like map, but don't demand '()-terminated list 245 > 246 > (define (imap f l) 247 > (cond ((pair? l) 248 > (cons (f (car l)) (imap f (cdr l)))) 249 > ((null? l) 250 > '()) 251 > (#t (f l)))) 170 252 171 253 ; (if) -> nil 172 254 ; (if x) -> x 173 255 ; (if t a ...) -> a 174 256 ; (if nil a b) -> b 175 257 ; (if nil a b c) -> (if b c) 176 258 177 259 (define (ac-if args env) 178 260 (cond ((null? args) ''nil) 179 261 ((null? (cdr args)) (ac (car args) env)) 180 262 (#t `(if (not (ar-false? ,(ac (car args) env))) 181 < ;(not (eq? 'nil ,(ac (car args) env))) 182 263 ,(ac (cadr args) env) 183 264 ,(ac-if (cddr args) env))))) 184 265 266 > (define (ac-dbname! name env) 267 > (if (symbol? name) 268 > (cons (list name) env) 269 > env)) 270 > 271 > (define (ac-dbname env) 272 > (cond ((null? env) #f) 273 > ((pair? (car env)) (caar env)) 274 > (#t (ac-dbname (cdr env))))) 275 > 185 276 ; translate fn directly into a lambda if it has ordinary 186 277 ; parameters, otherwise use a rest parameter and parse it. 278 > 187 279 (define (ac-fn args body env) 188 280 (if (ac-complex-args? args) 189 281 (ac-complex-fn args body env) 190 < `(lambda ,(let ((a (ac-denil args))) (if (eqv? a 'nil) '() a)) 191 < 'nil 192 < ,@(ac-body body (append (ac-arglist args) env))))) 282 > (ac-nameit 283 > (ac-dbname env) 284 > `(lambda ,(let ((a (ac-denil args))) (if (eqv? a 'nil) '() a)) 285 > ,@(ac-body* body (append (ac-arglist args) env)))))) 193 286 194 287 ; does an fn arg list use optional parameters or destructuring? 195 288 ; a rest parameter is not complex 289 > 196 290 (define (ac-complex-args? args) 197 291 (cond ((eqv? args '()) #f) 198 292 ((symbol? args) #f) 199 293 ((and (pair? args) (symbol? (car args))) 200 294 (ac-complex-args? (cdr args))) 201 295 (#t #t))) 202 296 203 297 ; translate a fn with optional or destructuring args 204 298 ; (fn (x (o y x) (o z 21) (x1 x2) . rest) ...) 205 299 ; arguments in top-level list are mandatory (unless optional), 206 300 ; but it's OK for parts of a list you're destructuring to 207 301 ; be missing. 302 > 208 303 (define (ac-complex-fn args body env) 209 304 (let* ((ra (ar-gensym)) 210 305 (z (ac-complex-args args env ra #t))) 211 306 `(lambda ,ra 212 307 (let* ,z 213 < 'nil 214 < ,@(ac-body body (append (ac-complex-getargs z) env)))))) 308 > ,@(ac-body* body (append (ac-complex-getargs z) env)))))) 215 309 216 310 ; returns a list of two-element lists, first is variable name, 217 311 ; second is (compiled) expression. to be used in a let. 218 312 ; caller should extract variables and add to env. 219 313 ; ra is the rest argument to the fn. 220 314 ; is-params indicates that args are function arguments 221 315 ; (not destructuring), so they must be passed or be optional. 316 > 222 317 (define (ac-complex-args args env ra is-params) 223 318 (cond ((or (eqv? args '()) (eqv? args 'nil)) '()) 224 319 ((symbol? args) (list (list args ra))) 225 320 ((pair? args) 226 321 (let* ((x (if (and (pair? (car args)) (eqv? (caar args) 'o)) 227 322 (ac-complex-opt (cadar args) 228 323 (if (pair? (cddar args)) 229 324 (caddar args) 230 325 'nil) 231 326 env 232 327 ra) 233 328 (ac-complex-args 234 329 (car args) 235 330 env 236 331 (if is-params 237 332 `(car ,ra) 238 333 `(ar-xcar ,ra)) 239 334 #f))) 240 335 (xa (ac-complex-getargs x))) 241 336 (append x (ac-complex-args (cdr args) 242 337 (append xa env) 243 338 `(ar-xcdr ,ra) 244 339 is-params)))) 245 340 (#t (err "Can't understand fn arg list" args)))) 246 341 247 342 ; (car ra) is the argument 248 343 ; so it's not present if ra is nil or '() 344 > 249 345 (define (ac-complex-opt var expr env ra) 250 346 (list (list var `(if (pair? ,ra) (car ,ra) ,(ac expr env))))) 251 347 252 348 ; extract list of variables from list of two-element lists. 349 > 253 350 (define (ac-complex-getargs a) 254 351 (map (lambda (x) (car x)) a)) 255 352 256 353 ; (a b . c) -> (a b c) 257 354 ; a -> (a) 355 > 258 356 (define (ac-arglist a) 259 357 (cond ((null? a) '()) 260 358 ((symbol? a) (list a)) 261 359 ((symbol? (cdr a)) (list (car a) (cdr a))) 262 360 (#t (cons (car a) (ac-arglist (cdr a)))))) 263 361 264 362 (define (ac-body body env) 363 > (map (lambda (x) (ac x env)) body)) 364 > 365 > ; like ac-body, but spits out a nil expression if empty 366 > 367 > (define (ac-body* body env) 265 368 (if (null? body) 266 < '() 267 < (cons (ac (car body) env) (ac-body (cdr body) env)))) 369 > (list (list 'quote 'nil)) 370 > (ac-body body env))) 268 371 269 372 ; (set v1 expr1 v2 expr2 ...) 270 373 271 374 (define (ac-set x env) 272 375 `(begin ,@(ac-setn x env))) 273 376 274 377 (define (ac-setn x env) 275 378 (if (null? x) 276 379 '() 277 < (cons (ac-set1 (ac-macex (car x)) (ac (cadr x) env) env) 380 > (cons (ac-set1 (ac-macex (car x)) (cadr x) env) 278 381 (ac-setn (cddr x) env)))) 279 382 383 > ; trick to tell Scheme the name of something, so Scheme 384 > ; debugging and profiling make more sense. 385 > 386 > (define (ac-nameit name v) 387 > (if (symbol? name) 388 > (let ((n (string->symbol (string-append " " (symbol->string name))))) 389 > (list 'let `((,n ,v)) n)) 390 > v)) 391 > 280 392 ; = replaced by set, which is only for vars 281 393 ; = now defined in arc (is it?) 282 394 ; name is to cause fns to have their arc names for debugging 283 395 284 < (define (ac-set1 a b env) 396 > (define (ac-set1 a b1 env) 285 397 (if (symbol? a) 286 < (let ((name (string->symbol (string-append " " (symbol->string a))))) 287 < (list 'let `((,name ,b)) 398 > (let ((b (ac b1 (ac-dbname! a env)))) 399 > (list 'let `((zz ,b)) 288 400 (cond ((eqv? a 'nil) (err "Can't rebind nil")) 289 401 ((eqv? a 't) (err "Can't rebind t")) 290 < ((lex? a env) `(set! ,a ,name)) 402 > ((lex? a env) `(set! ,a zz)) 291 403 (#t `(namespace-set-variable-value! ',(ac-global-name a) 292 < ,name))) 293 < name)) 404 > zz))) 405 > 'zz)) 294 406 (err "First arg to set must be a symbol" a))) 407 > 408 > ; given a list of Arc expressions, return a list of Scheme expressions. 409 > ; for compiling passed arguments. 410 > 411 > (define (ac-args names exprs env) 412 > (if (null? exprs) 413 > '() 414 > (cons (ac (car exprs) 415 > (ac-dbname! (if (pair? names) (car names) #f) env)) 416 > (ac-args (if (pair? names) (cdr names) '()) 417 > (cdr exprs) 418 > env)))) 419 > 420 > ; generate special fast code for ordinary two-operand 421 > ; calls to the following functions. this is to avoid 422 > ; calling e.g. ar-is with its &rest and apply. 423 > 424 > (define ac-binaries 425 > '((is ar-is2) 426 > (< ar-<2) 427 > (> ar->2) 428 > (+ ar-+2))) 429 > 430 > ; (foo bar) where foo is a global variable bound to a procedure. 431 > 432 > (define (ac-global-call fn args env) 433 > (cond ((and (assoc fn ac-binaries) (= (length args) 2)) 434 > `(,(cadr (assoc fn ac-binaries)) ,@(ac-args '() args env))) 435 > (#t 436 > `(,(ac-global-name fn) ,@(ac-args '() args env))))) 295 437 296 438 ; compile a function call 297 439 ; special cases for speed, to avoid compiled output like 298 440 ; (ar-apply _pr (list 1 2)) 299 441 ; which results in 1/2 the CPU time going to GC. Instead: 300 442 ; (ar-funcall2 _pr 1 2) 443 > ; and for (foo bar), if foo is a reference to a global variable, 444 > ; and it's bound to a function, generate (foo bar) instead of 445 > ; (ar-funcall1 foo bar) 446 > 447 > (define direct-calls #f) 448 > 301 449 (define (ac-call fn args env) 302 450 (let ((macfn (ac-macro? fn))) 303 451 (cond (macfn 304 452 (ac-mac-call macfn args env)) 305 453 ((and (pair? fn) (eqv? (car fn) 'fn)) 306 < `(,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 454 > `(,(ac fn env) ,@(ac-args (cadr fn) args env))) 455 > ((and direct-calls (symbol? fn) (not (lex? fn env)) (bound? fn) 456 > (procedure? (namespace-variable-value (ac-global-name fn)))) 457 > (ac-global-call fn args env)) 307 458 ((= (length args) 0) 308 459 `(ar-funcall0 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 309 460 ((= (length args) 1) 310 461 `(ar-funcall1 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 311 462 ((= (length args) 2) 312 463 `(ar-funcall2 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 313 464 ((= (length args) 3) 314 465 `(ar-funcall3 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 315 466 ((= (length args) 4) 316 467 `(ar-funcall4 ,(ac fn env) ,@(map (lambda (x) (ac x env)) args))) 317 468 (#t 318 469 `(ar-apply ,(ac fn env) 319 470 (list ,@(map (lambda (x) (ac x env)) args))))))) 320 471 321 472 (define (ac-mac-call m args env) 322 473 (let ((x1 (apply m (map ac-niltree args)))) 323 474 (let ((x2 (ac (ac-denil x1) env))) 324 475 x2))) 325 476 326 477 ; returns #f or the macro function 327 478 328 479 (define (ac-macro? fn) 329 480 (if (symbol? fn) 330 481 (let ((v (namespace-variable-value (ac-global-name fn) 331 482 #t 332 483 (lambda () #f)))) 333 484 (if (and v 334 485 (ar-tagged? v) 335 486 (eq? (ar-type v) 'mac)) 336 487 (ar-rep v) 337 488 #f)) 338 489 #f)) 339 490 340 491 ; macroexpand the outer call of a form as much as possible 341 492 342 493 (define (ac-macex e . once) 343 494 (if (pair? e) 344 495 (let ((m (ac-macro? (car e)))) 345 496 (if m 346 497 (let ((expansion (ac-denil (apply m (map ac-niltree (cdr e)))))) 347 498 (if (null? once) (ac-macex expansion) expansion)) 348 499 e)) 349 500 e)) 350 501 351 502 ; macros return Arc lists, ending with NIL. 352 503 ; but the Arc compiler expects Scheme lists, ending with '(). 353 504 ; what to do with (is x nil . nil) ? 354 505 ; the first nil ought to be replaced with 'NIL 355 506 ; the second with '() 356 507 ; so the rule is: NIL in the car -> 'NIL, NIL in the cdr -> '(). 357 508 ; NIL by itself -> NIL 358 509 359 510 (define (ac-denil x) 360 511 (cond ((pair? x) (cons (ac-denil-car (car x)) (ac-denil-cdr (cdr x)))) 361 512 (#t x))) 362 513 363 514 (define (ac-denil-car x) 364 515 (if (eq? x 'nil) 365 516 'nil 366 517 (ac-denil x))) 367 518 368 519 (define (ac-denil-cdr x) 369 520 (if (eq? x 'nil) 370 521 '() 371 522 (ac-denil x))) 372 523 373 524 ; is v lexically bound? 525 > 374 526 (define (lex? v env) 375 527 (memq v env)) 376 528 377 529 (define (xcar x) 378 530 (and (pair? x) (car x))) 379 531 380 532 ; #f and '() -> nil for a whole quoted list/tree. 381 533 534 > ; Arc primitives written in Scheme should look like: 535 > 536 > ; (xdef foo (lambda (lst) 537 > ; (ac-niltree (scheme-foo (ar-nil-terminate lst))))) 538 > 539 > ; That is, Arc lists are NIL-terminated. When calling a Scheme 540 > ; function that treats an argument as a list, call ar-nil-terminate 541 > ; to change NIL to '(). When returning any data created by Scheme 542 > ; to Arc, call ac-niltree to turn all '() into NIL. 543 > ; (hash-table-get doesn't use its argument as a list, so it doesn't 544 > ; need ar-nil-terminate). 545 > 382 546 (define (ac-niltree x) 383 547 (cond ((pair? x) (cons (ac-niltree (car x)) (ac-niltree (cdr x)))) 384 548 ((or (eq? x #f) (eq? x '())) 'nil) 385 549 (#t x))) 386 550 387 < ;(define (err msg . args) 388 < ; (display msg) 389 < ; (map (lambda (a) (display " ") (write a)) args) 390 < ; (newline) 391 < ; (xxundefined)) 392 < 393 < (define err error) ; eli says need to remove xxundefined for speed 551 > ; The next two are optimizations, except work for macros. 552 > 553 > (define (decompose fns args) 554 > (cond ((null? fns) `((fn vals (car vals)) ,@args)) 555 > ((null? (cdr fns)) (cons (car fns) args)) 556 > (#t (list (car fns) (decompose (cdr fns) args))))) 557 > 558 > (define (ac-andf s env) 559 > (ac (let ((gs (map (lambda (x) (ar-gensym)) (cdr s)))) 560 > `((fn ,gs 561 > (and ,@(map (lambda (f) `(,f ,@gs)) 562 > (cdar s)))) 563 > ,@(cdr s))) 564 > env)) 565 > 566 > (define err error) 394 567 395 568 ; run-time primitive procedures 396 569 397 < (define (xdef a b) 398 < (namespace-set-variable-value! (ac-global-name a) b) 399 < b) 570 > ;(define (xdef a b) 571 > ; (namespace-set-variable-value! (ac-global-name a) b) 572 > ; b) 400 573 574 > (define-syntax xdef 575 > (syntax-rules () 576 > ((xxdef a b) 577 > (let ((nm (ac-global-name 'a)) 578 > (a b)) 579 > (namespace-set-variable-value! nm a) 580 > a)))) 581 > 401 582 (define fn-signatures (make-hash-table 'equal)) 402 583 403 584 ; This is a replacement for xdef that stores opeator signatures. 404 585 ; Haven't started using it yet. 405 586 406 587 (define (odef a parms b) 407 588 (namespace-set-variable-value! (ac-global-name a) b) 408 589 (hash-table-put! fn-signatures a (list parms)) 409 590 b) 410 591 411 < (xdef 'sig fn-signatures) 592 > (xdef sig fn-signatures) 412 593 413 594 ; versions of car and cdr for parsing arguments for optional 414 595 ; parameters, that yield nil for nil. maybe we should use 415 596 ; full Arc car and cdr, so we can destructure more things 416 597 417 598 (define (ar-xcar x) 418 599 (if (or (eqv? x 'nil) (eqv? x '())) 419 600 'nil 420 601 (car x))) 421 602 422 603 (define (ar-xcdr x) 423 604 (if (or (eqv? x 'nil) (eqv? x '())) 424 605 'nil 425 606 (cdr x))) 426 607 427 608 ; convert #f from a Scheme predicate to NIL. 428 609 429 610 (define (ar-nill x) 430 611 (if (or (eq? x '()) (eq? x #f)) 431 612 'nil 432 613 x)) 433 614 434 615 ; definition of falseness for Arc if. 435 616 ; must include '() since sometimes Arc functions see 436 617 ; Scheme lists (e.g. . body of a macro). 437 618 438 619 (define (ar-false? x) 439 620 (or (eq? x 'nil) (eq? x '()) (eq? x #f))) 440 621 441 < #| 442 < (if (eq? x 'nil) #t 443 < (if (eq? x '()) #t 444 < (not x))) 445 < |# 446 < 447 622 ; call a function or perform an array ref, hash ref, &c 448 623 449 < ; Non-fn donstants in functional position are valuable real estate, so 450 < ; should figure out the best way to exploit it. 624 > ; Non-fn constants in functional position are valuable real estate, so 625 > ; should figure out the best way to exploit it. What could (1 foo) or 626 > ; ('a foo) mean? Maybe it should mean currying. 451 627 628 > ; For now the way to make the default val of a hash table be other than 629 > ; nil is to supply the val when doing the lookup. Later may also let 630 > ; defaults be supplied as an arg to table. To implement this, need: an 631 > ; eq table within scheme mapping tables to defaults, and to adapt the 632 > ; code in arc.arc that reads and writes tables to read and write their 633 > ; default vals with them. To make compatible with existing written tables, 634 > ; just use an atom or 3-elt list to keep the default. 635 > 452 636 (define (ar-apply fn args) 453 < (cond ((procedure? fn) (apply fn args)) 454 < ((pair? fn) (list-ref fn (car args))) 455 < ((string? fn) (string-ref fn (car args))) 456 < ((hash-table? fn) (ar-nill (hash-table-get fn (car args) #f))) 637 > (cond ((procedure? fn) 638 > (apply fn args)) 639 > ((pair? fn) 640 > (list-ref fn (car args))) 641 > ((string? fn) 642 > (string-ref fn (car args))) 643 > ((hash-table? fn) 644 > (ar-nill (hash-table-get fn 645 > (car args) 646 > (if (pair? (cdr args)) (cadr args) #f)))) 457 647 ; experiment: means e.g. [1] is a constant fn 458 648 ; ((or (number? fn) (symbol? fn)) fn) 459 649 ; another possibility: constant in functional pos means it gets 460 650 ; passed to the first arg, i.e. ('kids item) means (item 'kids). 461 651 (#t (err "Function call on inappropriate object" fn args)))) 462 652 463 < (xdef 'apply (lambda (fn . args) 653 > (xdef apply (lambda (fn . args) 464 654 (ar-apply fn (ar-apply-args args)))) 465 655 466 656 ; special cases of ar-apply for speed and to avoid consing arg lists 657 > 467 658 (define (ar-funcall0 fn) 468 659 (if (procedure? fn) 469 660 (fn) 470 661 (ar-apply fn (list)))) 471 662 472 663 (define (ar-funcall1 fn arg1) 473 664 (if (procedure? fn) 474 665 (fn arg1) 475 666 (ar-apply fn (list arg1)))) 476 667 477 668 (define (ar-funcall2 fn arg1 arg2) 478 669 (if (procedure? fn) 479 670 (fn arg1 arg2) 480 671 (ar-apply fn (list arg1 arg2)))) 481 672 482 673 (define (ar-funcall3 fn arg1 arg2 arg3) 483 674 (if (procedure? fn) 484 675 (fn arg1 arg2 arg3) 485 676 (ar-apply fn (list arg1 arg2 arg3)))) 486 677 487 678 (define (ar-funcall4 fn arg1 arg2 arg3 arg4) 488 679 (if (procedure? fn) 489 680 (fn arg1 arg2 arg3 arg4) 490 681 (ar-apply fn (list arg1 arg2 arg3 arg4)))) 491 682 492 683 ; replace the nil at the end of a list with a '() 493 684 494 685 (define (ar-nil-terminate l) 495 686 (if (or (eqv? l '()) (eqv? l 'nil)) 496 687 '() 497 688 (cons (car l) (ar-nil-terminate (cdr l))))) 498 689 499 690 ; turn the arguments to Arc apply into a list. 500 691 ; if you call (apply fn 1 2 '(3 4)) 501 692 ; then args is '(1 2 (3 4 . nil) . ()) 502 693 ; that is, the main list is a scheme list. 503 694 ; and we should return '(1 2 3 4 . ()) 504 695 ; was once (apply apply list (ac-denil args)) 505 696 ; but that didn't work for (apply fn nil) 506 697 507 698 (define (ar-apply-args args) 508 699 (cond ((null? args) '()) 509 700 ((null? (cdr args)) (ar-nil-terminate (car args))) 510 701 (#t (cons (car args) (ar-apply-args (cdr args)))))) 511 702 512 < (xdef 'cons cons) 703 > 704 > 705 > 706 > 707 > (xdef cons cons) 513 708 514 < (xdef 'car (lambda (x) 709 > (xdef car (lambda (x) 515 710 (cond ((pair? x) (car x)) 516 711 ((eqv? x 'nil) 'nil) 517 712 ((eqv? x '()) 'nil) 518 713 (#t (err "Can't take car of" x))))) 519 714 520 < (xdef 'cdr (lambda (x) 715 > (xdef cdr (lambda (x) 521 716 (cond ((pair? x) (cdr x)) 522 717 ((eqv? x 'nil) 'nil) 523 718 ((eqv? x '()) 'nil) 524 719 (#t (err "Can't take cdr of" x))))) 525 720 721 > (define (tnil x) (if x 't 'nil)) 722 > 723 > ; (pairwise pred '(a b c d)) => 724 > ; (and (pred a b) (pred b c) (pred c d)) 725 > ; pred returns t/nil, as does pairwise 526 726 ; reduce? 527 727 528 < (define (pairwise pred args base) 529 < (let ((n (length args))) 530 < (cond ((< n 2) base) 531 < ((= n 2) (apply pred args)) 532 < (#t (and (pred (car args) (cadr args)) 533 < (pairwise pred (cdr args) base)))))) 728 > (define (pairwise pred lst) 729 > (cond ((null? lst) 't) 730 > ((null? (cdr lst)) 't) 731 > ((not (eqv? (pred (car lst) (cadr lst)) 'nil)) 732 > (pairwise pred (cdr lst))) 733 > (#t 'nil))) 534 734 535 735 ; not quite right, because behavior of underlying eqv unspecified 536 736 ; in many cases according to r5rs 537 737 ; do we really want is to ret t for distinct strings? 538 738 539 < (xdef 'is (lambda args 540 < (if (or (all (lambda (a) (eqv? (car args) a)) (cdr args)) 541 < (and (all string? args) 542 < (apply string=? args)) 543 < (all ar-false? args)) 544 < 't 'nil))) 739 > ; for (is x y) 740 > 741 > (define (ar-is2 a b) 742 > (tnil (or (eqv? a b) 743 > (and (string? a) (string? b) (string=? a b)) 744 > (and (ar-false? a) (ar-false? b))))) 745 > 746 > ; for all other uses of is 545 747 546 < (xdef 'err err) 547 < (xdef 'nil 'nil) 548 < (xdef 't 't) 748 > (xdef is (lambda args (pairwise ar-is2 args))) 749 > 750 > (xdef err err) 751 > (xdef nil 'nil) 752 > (xdef t 't) 549 753 550 754 (define (all test seq) 551 755 (or (null? seq) 552 756 (and (test (car seq)) (all test (cdr seq))))) 553 757 554 < ; rather strictly excludes () 555 < 556 < (define (arc-list? x) (or (pair? x) (eqv? x 'nil))) 758 > (define (arc-list? x) (or (pair? x) (eqv? x 'nil) (eqv? x '()))) 557 759 558 < ; generic +: strings, lists, numbers. 559 < ; problem with generic +: what to return when no args? 560 < ; could even coerce based on type of first arg... 760 > ; Generic +: strings, lists, numbers. 761 > ; Return val has same type as first argument. 561 762 562 < (xdef '+ (lambda args 763 > (xdef + (lambda args 563 764 (cond ((null? args) 0) 564 < ((all string? args) 565 < (apply string-append args)) 566 < ((all arc-list? args) 765 > ((char-or-string? (car args)) 766 > (apply string-append 767 > (map (lambda (a) (ar-coerce a 'string)) 768 > args))) 769 > ((arc-list? (car args)) 567 770 (ac-niltree (apply append (map ar-nil-terminate args)))) 568 771 (#t (apply + args))))) 569 772 570 < (xdef '- -) 571 < (xdef '* *) 572 < (xdef '/ /) 573 < (xdef 'mod modulo) 574 < (xdef 'expt expt) 575 < (xdef 'sqrt sqrt) 773 > (define (char-or-string? x) (or (string? x) (char? x))) 774 > 775 > (define (ar-+2 x y) 776 > (cond ((char-or-string? x) 777 > (string-append (ar-coerce x 'string) (ar-coerce y 'string))) 778 > ((and (arc-list? x) (arc-list? y)) 779 > (ac-niltree (append (ar-nil-terminate x) (ar-nil-terminate y)))) 780 > (#t (+ x y)))) 781 > 782 > (xdef - -) 783 > (xdef * *) 784 > (xdef / /) 785 > (xdef mod modulo) 786 > (xdef expt expt) 787 > (xdef sqrt sqrt) 576 788 577 789 ; generic comparison 578 790 579 < (define (arc> . args) 580 < (cond ((all number? args) (apply > args)) 581 < ((all string? args) (pairwise string>? args #f)) 582 < ((all symbol? args) (pairwise (lambda (x y) 583 < (string>? (symbol->string x) 584 < (symbol->string y))) 585 < args 586 < #f)) 587 < ((all char? args) (pairwise char>? args #f)) 588 < (#t (apply > args)))) 589 < (xdef '> (lambda args (if (apply arc> args) 't 'nil))) 791 > (define (ar->2 x y) 792 > (tnil (cond ((and (number? x) (number? y)) (> x y)) 793 > ((and (string? x) (string? y)) (string>? x y)) 794 > ((and (symbol? x) (symbol? y)) (string>? (symbol->string x) 795 > (symbol->string y))) 796 > ((and (char? x) (char? y)) (char>? x y)) 797 > (#t (> x y))))) 798 > 799 > (xdef > (lambda args (pairwise ar->2 args))) 590 800 591 < (define (arc< . args) 592 < (cond ((all number? args) (apply < args)) 593 < ((all string? args) (pairwise stringstring x) 596 < (symbol->string y))) 597 < args 598 < #f)) 599 < ((all char? args) (pairwise char (define (ar-<2 x y) 802 > (tnil (cond ((and (number? x) (number? y)) (< x y)) 803 > ((and (string? x) (string? y)) (string ((and (symbol? x) (symbol? y)) (stringstring x) 805 > (symbol->string y))) 806 > ((and (char? x) (char? y)) (char (#t (< x y))))) 808 > 809 > (xdef < (lambda args (pairwise ar-<2 args))) 602 810 603 < (xdef 'len (lambda (x) 811 > (xdef len (lambda (x) 604 812 (cond ((string? x) (string-length x)) 605 813 ((hash-table? x) (hash-table-count x)) 606 814 (#t (length (ar-nil-terminate x)))))) 607 815 608 816 (define (ar-tagged? x) 609 817 (and (vector? x) (eq? (vector-ref x 0) 'tagged))) 610 818 611 819 (define (ar-tag type rep) 612 820 (cond ((eqv? (ar-type rep) type) rep) 613 821 (#t (vector 'tagged type rep)))) 614 < (xdef 'annotate ar-tag) 822 > 823 > (xdef annotate ar-tag) 615 824 616 825 ; (type nil) -> sym 826 > 827 > (define (exint? x) (and (integer? x) (exact? x))) 617 828 618 829 (define (ar-type x) 619 830 (cond ((ar-tagged? x) (vector-ref x 1)) 620 831 ((pair? x) 'cons) 621 832 ((symbol? x) 'sym) 622 833 ((null? x) 'sym) 623 834 ((procedure? x) 'fn) 624 835 ((char? x) 'char) 625 836 ((string? x) 'string) 626 < ((integer? x) 'int) 837 > ((exint? x) 'int) 627 838 ((number? x) 'num) ; unsure about this 628 839 ((hash-table? x) 'table) 629 840 ((output-port? x) 'output) 630 841 ((input-port? x) 'input) 631 842 ((tcp-listener? x) 'socket) 632 843 ((exn? x) 'exception) 844 > ((thread? x) 'thread) 633 845 (#t (err "Type: unknown type" x)))) 634 < (xdef 'type ar-type) 846 > (xdef type ar-type) 635 847 636 848 (define (ar-rep x) 637 849 (if (ar-tagged? x) 638 850 (vector-ref x 2) 639 851 x)) 640 < (xdef 'rep ar-rep) 852 > 853 > (xdef rep ar-rep) 641 854 642 855 ; currently rather a joke: returns interned symbols 643 856 644 857 (define ar-gensym-count 0) 858 > 645 859 (define (ar-gensym) 646 860 (set! ar-gensym-count (+ ar-gensym-count 1)) 647 861 (string->symbol (string-append "gs" (number->string ar-gensym-count)))) 648 < (xdef 'uniq ar-gensym) 862 > 863 > (xdef uniq ar-gensym) 649 864 650 < (xdef 'ccc call-with-current-continuation) 865 > (xdef ccc call-with-current-continuation) 651 866 652 < (xdef 'infile open-input-file) 653 < (xdef 'outfile (lambda (f . args) 867 > (xdef infile open-input-file) 868 > 869 > (xdef outfile (lambda (f . args) 654 870 (open-output-file f 655 871 'text 656 872 (if (equal? args '(append)) 657 873 'append 658 874 'truncate)))) 659 875 660 < (xdef 'instring open-input-string) 661 < (xdef 'outstring open-output-string) 876 > (xdef instring open-input-string) 877 > (xdef outstring open-output-string) 662 878 663 879 ; use as general fn for looking inside things 664 880 665 < (xdef 'inside get-output-string) 881 > (xdef inside get-output-string) 666 882 667 < (xdef 'close (lambda (p) 668 < (cond ((input-port? p) (close-input-port p)) 669 < ((output-port? p) (close-output-port p)) 670 < ((tcp-listener? p) (tcp-close p)) 671 < (#t (err "Can't close " p))) 672 < 'nil)) 673 < 674 < (xdef 'stdout current-output-port) ; should be a vars 675 < (xdef 'stdin current-input-port) 676 < (xdef 'stderr current-error-port) 883 > (xdef stdout current-output-port) ; should be a vars 884 > (xdef stdin current-input-port) 885 > (xdef stderr current-error-port) 677 886 678 < (xdef 'call-w/stdout 887 > (xdef call-w/stdout 679 888 (lambda (port thunk) 680 889 (parameterize ((current-output-port port)) (thunk)))) 681 890 682 < (xdef 'call-w/stdin 891 > (xdef call-w/stdin 683 892 (lambda (port thunk) 684 893 (parameterize ((current-input-port port)) (thunk)))) 685 894 686 < ; (readc stream) 687 < ; nil stream means stdout 688 < ; returns nil on eof 689 < 690 < (xdef 'readc (lambda (str) 691 < (let ((p (if (ar-false? str) 692 < (current-input-port) 693 < str))) 694 < (let ((c (read-char p))) 695 < (if (eof-object? c) 'nil c))))) 895 > (xdef readc (lambda str 896 > (let ((c (read-char (if (pair? str) 897 > (car str) 898 > (current-input-port))))) 899 > (if (eof-object? c) 'nil c)))) 696 900 697 < (xdef 'readb (lambda (str) 698 < (let ((p (if (ar-false? str) 699 < (current-input-port) 700 < str))) 701 < (let ((c (read-byte p))) 702 < (if (eof-object? c) 'nil c))))) 901 > 902 > (xdef readb (lambda str 903 > (let ((c (read-byte (if (pair? str) 904 > (car str) 905 > (current-input-port))))) 906 > (if (eof-object? c) 'nil c)))) 703 907 704 < (xdef 'peekc (lambda (str) 705 < (let ((p (if (ar-false? str) 706 < (current-input-port) 707 < str))) 708 < (let ((c (peek-char p))) 709 < (if (eof-object? c) 'nil c))))) 908 > (xdef peekc (lambda str 909 > (let ((c (peek-char (if (pair? str) 910 > (car str) 911 > (current-input-port))))) 912 > (if (eof-object? c) 'nil c)))) 710 913 711 < (xdef 'writec (lambda (c . args) 914 > (xdef writec (lambda (c . args) 712 915 (write-char c 713 916 (if (pair? args) 714 917 (car args) 715 918 (current-output-port))) 716 919 c)) 717 920 718 < (xdef 'writeb (lambda (b . args) 921 > (xdef writeb (lambda (b . args) 719 922 (write-byte b 720 923 (if (pair? args) 721 924 (car args) 722 925 (current-output-port))) 723 926 b)) 724 927 725 < (xdef 'write (lambda args 726 < (if (pair? args) 727 < (write (ac-denil (car args)) 728 < (if (pair? (cdr args)) 729 < (cadr args) 730 < (current-output-port)))) 731 < (flush-output) 732 < 'nil)) 733 < 734 < (xdef 'disp (lambda args 735 < (if (pair? args) 736 < (display (ac-denil (car args)) 737 < (if (pair? (cdr args)) 738 < (cadr args) 739 < (current-output-port)))) 740 < (flush-output) 741 < 'nil)) 928 > (define explicit-flush #f) 929 > 930 > (define (printwith f args) 931 > (let ((port (if (> (length args) 1) 932 > (cadr args) 933 > (current-output-port)))) 934 > (when (pair? args) 935 > (f (ac-denil (car args)) port)) 936 > (unless explicit-flush (flush-output port))) 937 > 'nil) 742 938 939 > (xdef write (lambda args (printwith write args))) 940 > (xdef disp (lambda args (printwith display args))) 941 > 743 942 ; sread = scheme read. eventually replace by writing read 744 943 745 < (xdef 'sread (lambda (p eof) 944 > (xdef sread (lambda (p eof) 746 945 (let ((expr (read p))) 747 946 (if (eof-object? expr) eof expr)))) 748 947 749 948 ; these work in PLT but not scheme48 750 949 751 950 (define char->ascii char->integer) 752 951 (define ascii->char integer->char) 753 952 754 < (xdef 'coerce (lambda (x type . args) 755 < (cond 756 < ((ar-tagged? x) (err "Can't coerce annotated object")) 757 < ((eqv? type (ar-type x)) x) 758 < 759 < ((char? x) (case type 760 < ((int) (char->ascii x)) 761 < ((string) (string x)) 762 < ((sym) (string->symbol (string x))) 763 < (else (err "Can't coerce" x type)))) 764 < ((integer? x) (case type 765 < ((char) (ascii->char x)) 766 < ((string) (apply number->string x args)) 767 < (else (err "Can't coerce" x type)))) 768 < ((number? x) (case type 769 < ((int) (round x)) 770 < ((char) (ascii->char (round x))) 771 < ((string) (apply number->string x args)) 772 < (else (err "Can't coerce" x type)))) 773 < ((string? x) (case type 774 < ((sym) (string->symbol x)) 775 < ((cons) (ac-niltree (string->list x))) 776 < ((int) (or (apply string->number x args) 777 < (err "Can't coerce" x type))) 778 < (else (err "Can't coerce" x type)))) 779 < ((pair? x) (case type 780 < ((string) (list->string 781 < (ar-nil-terminate x))) 782 < (else (err "Can't coerce" x type)))) 783 < ((eqv? x 'nil) (case type 784 < ((string) "") 785 < (else (err "Can't coerce" x type)))) 786 < ((symbol? x) (case type 787 < ((string) (symbol->string x)) 788 < (else (err "Can't coerce" x type)))) 789 < (#t x)))) 953 > (define (iround x) (inexact->exact (round x))) 954 > 955 > (define (ar-coerce x type . args) 956 > (cond 957 > ((ar-tagged? x) (err "Can't coerce annotated object")) 958 > ((eqv? type (ar-type x)) x) 959 > ((char? x) (case type 960 > ((int) (char->ascii x)) 961 > ((string) (string x)) 962 > ((sym) (string->symbol (string x))) 963 > (else (err "Can't coerce" x type)))) 964 > ((exint? x) (case type 965 > ((num) x) 966 > ((char) (ascii->char x)) 967 > ((string) (apply number->string x args)) 968 > (else (err "Can't coerce" x type)))) 969 > ((number? x) (case type 970 > ((int) (iround x)) 971 > ((char) (ascii->char (iround x))) 972 > ((string) (apply number->string x args)) 973 > (else (err "Can't coerce" x type)))) 974 > ((string? x) (case type 975 > ((sym) (string->symbol x)) 976 > ((cons) (ac-niltree (string->list x))) 977 > ((num) (or (apply string->number x args) 978 > (err "Can't coerce" x type))) 979 > ((int) (let ((n (apply string->number x args))) 980 > (if n 981 > (iround n) 982 > (err "Can't coerce" x type)))) 983 > (else (err "Can't coerce" x type)))) 984 > ((pair? x) (case type 985 > ((string) (apply string-append 986 > (map (lambda (y) (ar-coerce y 'string)) 987 > (ar-nil-terminate x)))) 988 > (else (err "Can't coerce" x type)))) 989 > ((eqv? x 'nil) (case type 990 > ((string) "") 991 > (else (err "Can't coerce" x type)))) 992 > ((null? x) (case type 993 > ((string) "") 994 > (else (err "Can't coerce" x type)))) 995 > ((symbol? x) (case type 996 > ((string) (symbol->string x)) 997 > (else (err "Can't coerce" x type)))) 998 > (#t x))) 790 999 791 < (xdef 'open-socket (lambda (num) (tcp-listen num 50 #t))) 1000 > (xdef coerce ar-coerce) 1001 > 1002 > (xdef open-socket (lambda (num) (tcp-listen num 50 #t))) 792 1003 793 1004 ; the 2050 means http requests currently capped at 2 meg 794 1005 ; http://list.cs.brown.edu/pipermail/plt-scheme/2005-August/009414.html 795 1006 796 < (xdef 'socket-accept (lambda (s) 797 < (call-with-values 1007 > (xdef socket-accept (lambda (s) 1008 > (let ((oc (current-custodian)) 1009 > (nc (make-custodian))) 1010 > (current-custodian nc) 1011 > (call-with-values 798 1012 (lambda () (tcp-accept s)) 799 1013 (lambda (in out) 800 < (list (make-limited-input-port in 100000 #t) 801 < out 802 < (let-values (((us them) (tcp-addresses out))) 803 < them)))))) 1014 > (let ((in1 (make-limited-input-port in 100000 #t))) 1015 > (current-custodian oc) 1016 > (associate-custodian nc in1 out) 1017 > (list in1 1018 > out 1019 > (let-values (((us them) (tcp-addresses out))) 1020 > them)))))))) 804 1021 805 < (xdef 'thread thread) 806 < (xdef 'kill-thread kill-thread) 807 < (xdef 'break-thread break-thread) 1022 > ; allow Arc to give up root privileges after it 1023 > ; calls open-socket. thanks, Eli! 1024 > (define setuid (get-ffi-obj 'setuid #f (_fun _int -> _int))) 1025 > (xdef setuid setuid) 1026 > 1027 > (xdef new-thread thread) 1028 > (xdef kill-thread kill-thread) 1029 > (xdef break-thread break-thread) 1030 > (xdef current-thread current-thread) 808 1031 809 1032 (define (wrapnil f) (lambda args (apply f args) 'nil)) 810 1033 811 < (xdef 'sleep (wrapnil sleep)) 1034 > (xdef sleep (wrapnil sleep)) 812 1035 813 1036 ; Will system "execute" a half-finished string if thread killed 814 1037 ; in the middle of generating it? 815 1038 816 < (xdef 'system (wrapnil system)) 1039 > (xdef system (wrapnil system)) 817 1040 818 < (xdef 'pipe-from (lambda (cmd) 1041 > (xdef pipe-from (lambda (cmd) 819 1042 (let ((tf (ar-tmpname))) 820 1043 (system (string-append cmd " > " tf)) 821 1044 (let ((str (open-input-file tf))) 822 1045 (system (string-append "rm -f " tf)) 823 1046 str)))) 824 1047 825 1048 (define (ar-tmpname) 826 1049 (call-with-input-file "/dev/urandom" 827 1050 (lambda (rstr) 828 1051 (do ((s "/tmp/") 829 1052 (c (read-char rstr) (read-char rstr)) 830 1053 (i 0 (+ i 1))) 831 1054 ((>= i 16) s) 832 1055 (set! s (string-append s 833 1056 (string 834 1057 (integer->char 835 1058 (+ (char->integer #\a) 836 1059 (modulo 837 1060 (char->integer (read-char rstr)) 838 1061 26)))))))))) 839 1062 840 1063 ; PLT scheme provides only eq? and equal? hash tables, 841 1064 ; we need the latter for strings. 842 1065 843 < (xdef 'table (lambda () (make-hash-table 'equal))) 1066 > (xdef table (lambda args 1067 > (let ((h (make-hash-table 'equal))) 1068 > (if (pair? args) ((car args) h)) 1069 > h))) 844 1070 845 < ;(xdef 'table (lambda args 1071 > ;(xdef table (lambda args 846 1072 ; (fill-table (make-hash-table 'equal) 847 1073 ; (if (pair? args) (ac-denil (car args)) '())))) 848 1074 849 1075 (define (fill-table h pairs) 850 1076 (if (eq? pairs '()) 851 1077 h 852 1078 (let ((pair (car pairs))) 853 1079 (begin (hash-table-put! h (car pair) (cadr pair)) 854 1080 (fill-table h (cdr pairs)))))) 855 1081 856 < (xdef 'maptable (lambda (fn table) ; arg is (fn (key value) ...) 1082 > (xdef maptable (lambda (fn table) ; arg is (fn (key value) ...) 857 1083 (hash-table-for-each table fn) 858 1084 table)) 859 1085 860 < (xdef 'protect (lambda (during after) 861 < (dynamic-wind (lambda () #t) during after))) 1086 > (define (protect during after) 1087 > (dynamic-wind (lambda () #t) during after)) 862 1088 1089 > (xdef protect protect) 1090 > 863 1091 ; need to use a better seed 864 1092 865 < (xdef 'rand random) 1093 > (xdef rand random) 866 1094 867 < (xdef 'dir (lambda (name) (map path->string (directory-list name)))) 1095 > (xdef dir (lambda (name) 1096 > (ac-niltree (map path->string (directory-list name))))) 868 1097 869 < (xdef 'file-exists (lambda (name) 1098 > ; Would def mkdir in terms of make-directory and call that instead 1099 > ; of system in ensure-dir, but make-directory is too weak: it doesn't 1100 > ; create intermediate directories like mkdir -p. 1101 > 1102 > (xdef file-exists (lambda (name) 870 1103 (if (file-exists? name) name 'nil))) 871 1104 872 < (xdef 'dir-exists (lambda (name) 1105 > (xdef dir-exists (lambda (name) 873 1106 (if (directory-exists? name) name 'nil))) 874 1107 875 < (xdef 'rmfile (wrapnil delete-file)) 1108 > (xdef rmfile (wrapnil delete-file)) 876 1109 1110 > (xdef mvfile (lambda (old new) 1111 > (rename-file-or-directory old new #t) 1112 > 'nil)) 1113 > 877 1114 ; top level read-eval-print 878 1115 ; tle kept as a way to get a break loop when a scheme err 879 1116 880 1117 (define (arc-eval expr) 881 < (eval (ac expr '()) (interaction-environment))) 1118 > (eval (ac expr '()))) 882 1119 883 1120 (define (tle) 884 1121 (display "Arc> ") 885 1122 (let ((expr (read))) 886 1123 (when (not (eqv? expr ':a)) 887 1124 (write (arc-eval expr)) 888 1125 (newline) 889 1126 (tle)))) 890 1127 891 1128 (define last-condition* #f) 892 1129 893 1130 (define (tl) 894 1131 (display "Use (quit) to quit, (tl) to return here after an interrupt.\n") 895 1132 (tl2)) 896 1133 897 1134 (define (tl2) 898 1135 (display "arc> ") 899 1136 (on-err (lambda (c) 900 1137 (set! last-condition* c) 901 1138 (display "Error: ") 902 1139 (write (exn-message c)) 903 1140 (newline) 904 1141 (tl2)) 905 1142 (lambda () 906 1143 (let ((expr (read))) 907 1144 (if (eqv? expr ':a) 908 1145 'done 909 1146 (let ((val (arc-eval expr))) 910 1147 (write (ac-denil val)) 911 1148 (namespace-set-variable-value! '_that val) 912 1149 (namespace-set-variable-value! '_thatexpr expr) 913 1150 (newline) 914 1151 (tl2))))))) 915 1152 916 1153 (define (aload1 p) 917 1154 (let ((x (read p))) 918 1155 (if (eof-object? x) 919 1156 #t 920 1157 (begin 921 1158 (arc-eval x) 922 1159 (aload1 p))))) 923 1160 924 1161 (define (atests1 p) 925 1162 (let ((x (read p))) 926 1163 (if (eof-object? x) 927 1164 #t 928 1165 (begin 929 1166 (write x) 930 1167 (newline) 931 1168 (let ((v (arc-eval x))) 932 1169 (if (ar-false? v) 933 1170 (begin 934 1171 (display " FAILED") 935 1172 (newline)))) 936 1173 (atests1 p))))) 937 1174 938 1175 (define (aload filename) 939 1176 (call-with-input-file filename aload1)) 940 1177 941 1178 (define (test filename) 942 1179 (call-with-input-file filename atests1)) 943 1180 944 1181 (define (acompile1 ip op) 945 1182 (let ((x (read ip))) 946 1183 (if (eof-object? x) 947 1184 #t 948 1185 (let ((scm (ac x '()))) 949 < (eval scm (interaction-environment)) 1186 > (eval scm) 950 1187 (pretty-print scm op) 951 1188 (newline op) 952 1189 (newline op) 953 1190 (acompile1 ip op))))) 954 1191 955 1192 ; compile xx.arc to xx.arc.scm 956 1193 ; useful to examine the Arc compiler output 957 1194 (define (acompile inname) 958 1195 (let ((outname (string-append inname ".scm"))) 959 1196 (if (file-exists? outname) 960 1197 (delete-file outname)) 961 1198 (call-with-input-file inname 962 1199 (lambda (ip) 963 1200 (call-with-output-file outname 964 1201 (lambda (op) 965 1202 (acompile1 ip op))))))) 966 1203 967 < (xdef 'macex (lambda (e) (ac-macex (ac-denil e)))) 1204 > (xdef macex (lambda (e) (ac-macex (ac-denil e)))) 968 1205 969 < (xdef 'macex1 (lambda (e) (ac-macex (ac-denil e) 'once))) 1206 > (xdef macex1 (lambda (e) (ac-macex (ac-denil e) 'once))) 970 1207 971 < (xdef 'eval (lambda (e) 972 < (eval (ac (ac-denil e) '()) (interaction-environment)))) 1208 > (xdef eval (lambda (e) 1209 > (eval (ac (ac-denil e) '())))) 973 1210 974 1211 ; If an err occurs in an on-err expr, no val is returned and code 975 1212 ; after it doesn't get executed. Not quite what I had in mind. 976 1213 977 1214 (define (on-err errfn f) 978 1215 ((call-with-current-continuation 979 1216 (lambda (k) 980 1217 (lambda () 981 1218 (with-handlers ((exn:fail? (lambda (c) 982 1219 (k (lambda () (errfn c)))))) 983 1220 (f))))))) 984 < (xdef 'on-err on-err) 1221 > (xdef on-err on-err) 985 1222 986 < (define (write-to-string x) 1223 > (define (disp-to-string x) 987 1224 (let ((o (open-output-string))) 988 < (write x o) 1225 > (display x o) 989 1226 (close-output-port o) 990 1227 (get-output-string o))) 991 1228 992 < (xdef 'details (lambda (c) 993 < (write-to-string (exn-message c)))) 1229 > (xdef details (lambda (c) 1230 > (disp-to-string (exn-message c)))) 994 1231 995 < (xdef 'scar (lambda (x val) 1232 > (xdef scar (lambda (x val) 996 1233 (if (string? x) 997 1234 (string-set! x 0 val) 998 < (set-car! x val)) 1235 > (x-set-car! x val)) 999 1236 val)) 1000 1237 1001 < (xdef 'scdr (lambda (x val) 1238 > (xdef scdr (lambda (x val) 1002 1239 (if (string? x) 1003 1240 (err "Can't set cdr of a string" x) 1004 < (set-cdr! x val)) 1241 > (x-set-cdr! x val)) 1005 1242 val)) 1243 > 1244 > ; decide at run-time whether the underlying mzscheme supports 1245 > ; set-car! and set-cdr!, since I can't figure out how to do it 1246 > ; at compile time. 1247 > 1248 > (define (x-set-car! p v) 1249 > (let ((fn (namespace-variable-value 'set-car! #t (lambda () #f)))) 1250 > (if (procedure? fn) 1251 > (fn p v) 1252 > (n-set-car! p v)))) 1253 > 1254 > (define (x-set-cdr! p v) 1255 > (let ((fn (namespace-variable-value 'set-cdr! #t (lambda () #f)))) 1256 > (if (procedure? fn) 1257 > (fn p v) 1258 > (n-set-cdr! p v)))) 1259 > 1260 > ; Eli's code to modify mzscheme-4's immutable pairs. 1261 > 1262 > ;; to avoid a malloc on every call, reuse a single pointer, but make 1263 > ;; it thread-local to avoid races 1264 > (define ptr (make-thread-cell #f)) 1265 > (define (get-ptr) 1266 > (or (thread-cell-ref ptr) 1267 > (let ([p (malloc _scheme 1)]) (thread-cell-set! ptr p) p))) 1268 > 1269 > ;; set a pointer to the cons cell, then dereference it as a pointer, 1270 > ;; and bang the new value in the given offset 1271 > (define (set-ca/dr! offset who p x) 1272 > (if (pair? p) 1273 > (let ([p* (get-ptr)]) 1274 > (ptr-set! p* _scheme p) 1275 > (ptr-set! (ptr-ref p* _pointer 0) _scheme offset x)) 1276 > (raise-type-error who "pair" p))) 1277 > 1278 > (define (n-set-car! p x) (set-ca/dr! 1 'set-car! p x)) 1279 > (define (n-set-cdr! p x) (set-ca/dr! 2 'set-cdr! p x)) 1006 1280 1007 1281 ; When and if cdr of a string returned an actual (eq) tail, could 1008 1282 ; say (if (string? x) (string-replace! x val 1) ...) in scdr, but 1009 1283 ; for now would be misleading to allow this, because fails for cddr. 1010 1284 1011 1285 (define (string-replace! str val index) 1012 1286 (if (eqv? (string-length val) (- (string-length str) index)) 1013 1287 (do ((i index (+ i 1))) 1014 1288 ((= i (string-length str)) str) 1015 1289 (string-set! str i (string-ref val (- i index)))) 1016 1290 (err "Length mismatch between strings" str val index))) 1017 1291 1018 < (xdef 'sref (lambda (com val ind) ; later make ind rest arg 1019 < (cond ((hash-table? com) (if (eqv? val 'nil) 1020 < (hash-table-remove! com ind) 1021 < (hash-table-put! com ind val))) 1022 < ((string? com) (string-set! com ind val)) 1023 < ((pair? com) (nth-set! com ind val)) 1024 < (#t (err "Can't set reference " com ind val))) 1025 < val)) 1292 > ; Later may want to have multiple indices. 1293 > 1294 > (xdef sref 1295 > (lambda (com val ind) 1296 > (cond ((hash-table? com) (if (eqv? val 'nil) 1297 > (hash-table-remove! com ind) 1298 > (hash-table-put! com ind val))) 1299 > ((string? com) (string-set! com ind val)) 1300 > ((pair? com) (nth-set! com ind val)) 1301 > (#t (err "Can't set reference " com ind val))) 1302 > val)) 1026 1303 1027 1304 (define (nth-set! lst n val) 1028 < (set-car! (list-tail lst n) val)) 1305 > (x-set-car! (list-tail lst n) val)) 1029 1306 1030 1307 ; rewrite to pass a (true) gensym instead of #f in case var bound to #f 1031 1308 1032 1309 (define (bound? arcname) 1033 1310 (namespace-variable-value (ac-global-name arcname) 1034 1311 #t 1035 1312 (lambda () #f))) 1036 1313 1037 < (xdef 'bound (lambda (x) (if (bound? x) 't 'nil))) 1314 > (xdef bound (lambda (x) (tnil (bound? x)))) 1038 1315 1039 < (xdef 'newstring make-string) 1316 > (xdef newstring make-string) 1040 1317 1041 < (xdef 'truncate (lambda (x) (inexact->exact (truncate x)))) 1318 > (xdef trunc (lambda (x) (inexact->exact (truncate x)))) 1042 1319 1043 < (xdef 'exact (lambda (x) (and (integer? x) (exact? x)))) 1320 > ; bad name 1321 > 1322 > (xdef exact (lambda (x) (tnil (exint? x)))) 1044 1323 1045 < (xdef 'msec current-milliseconds) 1046 < (xdef 'current-process-milliseconds current-process-milliseconds) 1047 < (xdef 'current-gc-milliseconds current-gc-milliseconds) 1324 > (xdef msec current-milliseconds) 1325 > (xdef current-process-milliseconds current-process-milliseconds) 1326 > (xdef current-gc-milliseconds current-gc-milliseconds) 1048 1327 1049 < (xdef 'seconds current-seconds) 1328 > (xdef seconds current-seconds) 1050 1329 1051 1330 (print-hash-table #t) 1052 1331 1053 < (xdef 'client-ip (lambda (port) 1332 > (xdef client-ip (lambda (port) 1054 1333 (let-values (((x y) (tcp-addresses port))) 1055 1334 y))) 1056 1335 1057 1336 ; make sure only one thread at a time executes anything 1058 1337 ; inside an atomic-invoke. atomic-invoke is allowed to 1059 1338 ; nest within a thread; the thread-cell keeps track of 1060 1339 ; whether this thread already holds the lock. 1061 < ; XXX make sure cell is set #f after an exception? 1062 < ; maybe it doesn't matter since thread will die? 1340 > 1063 1341 (define ar-the-sema (make-semaphore 1)) 1342 > 1064 1343 (define ar-sema-cell (make-thread-cell #f)) 1065 < (xdef 'atomic-invoke (lambda (f) 1344 > 1345 > (xdef atomic-invoke (lambda (f) 1066 1346 (if (thread-cell-ref ar-sema-cell) 1067 1347 (ar-apply f '()) 1068 1348 (begin 1069 1349 (thread-cell-set! ar-sema-cell #t) 1070 < (let ((ret 1071 < (call-with-semaphore 1072 < ar-the-sema 1073 < (lambda () (ar-apply f '()))))) 1074 < (thread-cell-set! ar-sema-cell #f) 1075 < ret))))) 1076 < 1077 < (xdef 'dead thread-dead?) 1078 < 1079 < ; Added because Mzscheme buffers output. Not sure if want as official 1080 < ; part of Arc. 1350 > (protect 1351 > (lambda () 1352 > (call-with-semaphore 1353 > ar-the-sema 1354 > (lambda () (ar-apply f '())))) 1355 > (lambda () 1356 > (thread-cell-set! ar-sema-cell #f))))))) 1357 > 1358 > (xdef dead (lambda (x) (tnil (thread-dead? x)))) 1359 > 1360 > ; Added because Mzscheme buffers output. Not a permanent part of Arc. 1361 > ; Only need to use when declare explicit-flush optimization. 1081 1362 1082 < ;(xdef 'flushout (lambda () (flush-output) 't)) 1363 > (xdef flushout (lambda () (flush-output) 't)) 1083 1364 1084 < (xdef 'ssyntax (lambda (x) (if (ssyntax? x) 't 'nil))) 1365 > (xdef ssyntax (lambda (x) (tnil (ssyntax? x)))) 1085 1366 1086 < (xdef 'ssexpand (lambda (x) 1367 > (xdef ssexpand (lambda (x) 1087 1368 (if (symbol? x) (expand-ssyntax x) x))) 1088 1369 1089 < (xdef 'quit exit) 1370 > (xdef quit exit) 1090 1371 1372 > ; there are two ways to close a TCP output port. 1373 > ; (close o) waits for output to drain, then closes UNIX descriptor. 1374 > ; (force-close o) discards buffered output, then closes UNIX desc. 1375 > ; web servers need the latter to get rid of connections to 1376 > ; clients that are not reading data. 1377 > ; mzscheme close-output-port doesn't work (just raises an error) 1378 > ; if there is buffered output for a non-responsive socket. 1379 > ; must use custodian-shutdown-all instead. 1091 1380 1381 > (define custodians (make-hash-table 'equal)) 1382 > 1383 > (define (associate-custodian c i o) 1384 > (hash-table-put! custodians i c) 1385 > (hash-table-put! custodians o c)) 1386 > 1387 > ; if a port has a custodian, use it to close the port forcefully. 1388 > ; also get rid of the reference to the custodian. 1389 > ; sadly doing this to the input port also kills the output port. 1390 > 1391 > (define (try-custodian p) 1392 > (let ((c (hash-table-get custodians p #f))) 1393 > (if c 1394 > (begin 1395 > (custodian-shutdown-all c) 1396 > (hash-table-remove! custodians p) 1397 > #t) 1398 > #f))) 1399 > 1400 > (define (ar-close . args) 1401 > (map (lambda (p) 1402 > (cond ((input-port? p) (close-input-port p)) 1403 > ((output-port? p) (close-output-port p)) 1404 > ((tcp-listener? p) (tcp-close p)) 1405 > (#t (err "Can't close " p)))) 1406 > args) 1407 > (map (lambda (p) (try-custodian p)) args) ; free any custodian 1408 > 'nil) 1409 > 1410 > (xdef close ar-close) 1411 > 1412 > (xdef force-close (lambda args 1413 > (map (lambda (p) 1414 > (if (not (try-custodian p)) 1415 > (ar-close p))) 1416 > args) 1417 > 'nil)) 1418 > 1419 > (xdef memory current-memory-use) 1420 > 1421 > (xdef declare (lambda (key val) 1422 > (let ((flag (not (ar-false? val)))) 1423 > (case key 1424 > ((atstrings) (set! atstrings flag)) 1425 > ((direct-calls) (set! direct-calls flag)) 1426 > ((explicit-flush) (set! explicit-flush flag))) 1427 > val))) 1428 > 1429 > (putenv "TZ" ":GMT") 1430 > 1431 > (define (gmt-date sec) (seconds->date sec)) 1432 > 1433 > (xdef timedate 1434 > (lambda args 1435 > (let ((d (gmt-date (if (pair? args) (car args) (current-seconds))))) 1436 > (ac-niltree (list (date-second d) 1437 > (date-minute d) 1438 > (date-hour d) 1439 > (date-day d) 1440 > (date-month d) 1441 > (date-year d)))))) 1442 > 1443 > (xdef sin sin) 1444 > (xdef cos cos) 1445 > (xdef tan tan) 1446 > (xdef asin asin) 1447 > (xdef acos acos) 1448 > (xdef atan atan) 1449 > (xdef log log) 1450 > 1451 > (define (codestring s) 1452 > (let ((i (atpos s 0))) 1453 > (if i 1454 > (cons (substring s 0 i) 1455 > (let* ((rest (substring s (+ i 1))) 1456 > (in (open-input-string rest)) 1457 > (expr (read in)) 1458 > (i2 (let-values (((x y z) (port-next-location in))) z))) 1459 > (close-input-port in) 1460 > (cons expr (codestring (substring rest (- i2 1)))))) 1461 > (list s)))) 1462 > 1463 > ; First unescaped @ in s, if any. Escape by doubling. 1464 > 1465 > (define (atpos s i) 1466 > (cond ((eqv? i (string-length s)) 1467 > #f) 1468 > ((eqv? (string-ref s i) #\@) 1469 > (if (and (< (+ i 1) (string-length s)) 1470 > (not (eqv? (string-ref s (+ i 1)) #\@))) 1471 > i 1472 > (atpos s (+ i 2)))) 1473 > (#t 1474 > (atpos s (+ i 1))))) 1475 > 1476 > (define (unescape-ats s) 1477 > (list->string (letrec ((unesc (lambda (cs) 1478 > (cond 1479 > ((null? cs) 1480 > '()) 1481 > ((and (eqv? (car cs) #\@) 1482 > (not (null? (cdr cs))) 1483 > (eqv? (cadr cs) #\@)) 1484 > (unesc (cdr cs))) 1485 > (#t 1486 > (cons (car cs) (unesc (cdr cs)))))))) 1487 > (unesc (string->list s))))) 1488 > 1092 1489 ) 1093 1490 1094 < (require ac) 1095 1491 ================================================================================ app.arc ================================================================================ 1097 1493 ; Application Server. Layer inserted 2 Sep 06. 1098 1494 1099 < ; todo: def a general notion of apps of which the programming app is 1100 < ; one and the news site another. 1495 > ; ideas: 1496 > ; def a general notion of apps of which prompt is one, news another 1101 1497 ; give each user a place to store data? A home dir? 1102 1498 1103 1499 ; A user is simply a string: "pg". Use /whoami to test user cookie. 1104 1500 1105 1501 (= hpwfile* "arc/hpw" 1502 > oidfile* "arc/openids" 1106 1503 adminfile* "arc/admins" 1107 1504 cookfile* "arc/cooks") 1108 1505 1109 1506 (def asv ((o port 8080)) 1110 1507 (load-userinfo) 1111 1508 (serve port)) 1112 1509 1113 1510 (def load-userinfo () 1114 1511 (= hpasswords* (safe-load-table hpwfile*) 1512 > openids* (safe-load-table oidfile*) 1115 1513 admins* (map string (errsafe (readfile adminfile*))) 1116 1514 cookie->user* (safe-load-table cookfile*)) 1117 1515 (maptable (fn (k v) (= (user->cookie* v) k)) 1118 1516 cookie->user*)) 1119 1517 1120 1518 ; idea: a bidirectional table, so don't need two vars (and sets) 1121 1519 1122 1520 (= cookie->user* (table) user->cookie* (table) logins* (table)) 1123 1521 1124 1522 (def get-user (req) 1125 < (let u (aand (alref (req 'cooks) "user") (cookie->user* (sym it))) 1126 < (when u (= (logins* u) (req 'ip))) 1523 > (let u (aand (alref req!cooks "user") (cookie->user* (sym it))) 1524 > (when u (= (logins* u) req!ip)) 1127 1525 u)) 1128 1526 1129 < (mac when-usermatch (user req . body) 1527 > (mac when-umatch (user req . body) 1130 1528 `(if (is ,user (get-user ,req)) 1131 1529 (do ,@body) 1132 1530 (mismatch-message))) 1133 1531 1134 < (def mismatch-message () (prn "Dead link: users don't match.")) 1532 > (def mismatch-message () 1533 > (prn "Dead link: users don't match.")) 1135 1534 1136 < (mac when-usermatchr (user req . body) 1535 > (mac when-umatch/r (user req . body) 1137 1536 `(if (is ,user (get-user ,req)) 1138 1537 (do ,@body) 1139 1538 "mismatch")) 1140 1539 1141 1540 (defop mismatch req (mismatch-message)) 1142 1541 1143 < (mac matchform (user req after . body) 1542 > (mac uform (user req after . body) 1144 1543 `(aform (fn (,req) 1145 < (when-usermatch ,user ,req 1544 > (when-umatch ,user ,req 1146 1545 ,after)) 1147 1546 ,@body)) 1148 1547 1149 < (mac matchrform (user req after . body) 1548 > (mac urform (user req after . body) 1150 1549 `(arform (fn (,req) 1151 < (when-usermatchr ,user ,req 1550 > (when-umatch/r ,user ,req 1152 1551 ,after)) 1153 1552 ,@body)) 1154 1553 1155 1554 ; Like onlink, but checks that user submitting the request is the 1156 < ; same it was generated for. Really should log the username and 1157 < ; ip addr of every genlink, and check if they match. 1555 > ; same it was generated for. For extra protection could log the 1556 > ; username and ip addr of every genlink, and check if they match. 1158 1557 1159 < (mac userlink (user text . body) 1558 > (mac ulink (user text . body) 1160 1559 (w/uniq req 1161 1560 `(linkf ,text (,req) 1162 < (when-usermatch ,user ,req ,@body)))) 1561 > (when-umatch ,user ,req ,@body)))) 1163 1562 1164 1563 1165 1564 (defop admin req (admin-gate (get-user req))) 1166 1565 1167 1566 (def admin-gate (u) 1168 1567 (if (admin u) 1169 1568 (admin-page u) 1170 1569 (login-page 'login nil 1171 1570 (fn (u ip) (admin-gate u))))) 1172 1571 1173 1572 (def admin (u) (and u (mem u admins*))) 1174 1573 1175 1574 (def user-exists (u) (and u (hpasswords* u) u)) 1176 1575 1177 1576 (def admin-page (user . msg) 1178 1577 (whitepage 1179 1578 (prbold "Admin: ") 1180 1579 (hspace 20) 1181 1580 (pr user " | ") 1182 1581 (w/link (do (logout-user user) 1183 1582 (whitepage (pr "Bye " user "."))) 1184 1583 (pr "logout")) 1185 1584 (when msg (hspace 10) (map pr msg)) 1186 1585 (br2) 1187 1586 (aform (fn (req) 1188 < (when-usermatch user req 1587 > (when-umatch user req 1189 1588 (with (u (arg req "u") p (arg req "p")) 1190 1589 (if (or (no u) (no p) (is u "") (is p "")) 1191 1590 (pr "Bad data.") 1192 1591 (user-exists u) 1193 1592 (admin-page user "User already exists: " u) 1194 1593 (do (create-acct u p) 1195 1594 (admin-page user)))))) 1196 1595 (pwfields "create (server) account")))) 1197 < 1198 < ; need to define a notion of a hashtable that's always written 1199 < ; to a file when modified 1200 1596 1201 1597 (def cook-user (user) 1202 1598 (let id (new-user-cookie) 1203 1599 (= (cookie->user* id) user 1204 1600 (user->cookie* user) id) 1205 1601 (save-table cookie->user* cookfile*) 1206 1602 id)) 1207 1603 1208 1604 ; Unique-ids are only unique per server invocation. 1209 1605 1210 1606 (def new-user-cookie () 1211 1607 (let id (unique-id) 1212 1608 (if (cookie->user* id) (new-user-cookie) id))) 1213 1609 1214 1610 (def logout-user (user) 1215 < (nil! (logins* user)) 1216 < (nil! (cookie->user* (user->cookie* user)) (user->cookie* user)) 1611 > (wipe (logins* user)) 1612 > (wipe (cookie->user* (user->cookie* user)) (user->cookie* user)) 1217 1613 (save-table cookie->user* cookfile*)) 1218 1614 1219 1615 (def create-acct (user pw) 1616 > (set (dc-usernames* (downcase user))) 1220 1617 (set-pw user pw)) 1221 1618 1222 1619 (def disable-acct (user) 1223 1620 (set-pw user (rand-string 20)) 1224 1621 (logout-user user)) 1225 1622 1226 1623 (def set-pw (user pw) 1227 1624 (= (hpasswords* user) (and pw (shash pw))) 1228 1625 (save-table hpasswords* hpwfile*)) 1229 1626 1230 1627 (def hello-page (user ip) 1231 1628 (whitepage (prs "hello" user "at" ip))) 1232 1629 1233 1630 (defop login req (login-page 'login)) 1234 1631 1235 1632 ; switch is one of: register, login, both 1236 < ; afterward is a function on the newly created user, ip addr 1237 < ; or can be a list of such a fn and a string, in which case call fn 1238 < ; then redirect to string 1633 > 1634 > ; afterward is either a function on the newly created username and 1635 > ; ip address, in which case it is called to generate the next page 1636 > ; after a successful login, or a pair of (function url), which means 1637 > ; call the function, then redirect to the url. 1239 1638 1240 1639 ; classic example of something that should just "return" a val 1241 1640 ; via a continuation rather than going to a new page. 1242 1641 1243 < ; ugly code-- too much duplication 1244 < 1245 1642 (def login-page (switch (o msg nil) (o afterward hello-page)) 1246 1643 (whitepage 1247 1644 (pagemessage msg) 1248 1645 (when (in switch 'login 'both) 1249 < (prbold "Login") 1250 < (br2) 1251 < (if (acons afterward) 1252 < (let (f url) afterward 1253 < (arformh (fn (req) 1254 < (logout-user (get-user req)) 1255 < (aif (good-login (arg req "u") (arg req "p") (req 'ip)) 1256 < (do (= (logins* it) (req 'ip)) 1257 < (prcookie (user->cookie* it)) 1258 < (f it (req 'ip)) 1259 < url) 1260 < (flink (fn ignore (login-page switch 1261 < "Bad login." 1262 < afterward))))) 1263 < (pwfields))) 1264 < (aformh (fn (req) 1265 < (logout-user (get-user req)) 1266 < (aif (good-login (arg req "u") (arg req "p") (req 'ip)) 1267 < (do (= (logins* it) (req 'ip)) 1268 < (prcookie (user->cookie* it)) 1269 < (prn) 1270 < (afterward it (req 'ip))) 1271 < (do (prn) 1272 < (login-page switch "Bad login." afterward)))) 1273 < (pwfields))) 1646 > (login-form "Login" switch login-handler afterward) 1647 > (hook 'login-form afterward) 1274 1648 (br2)) 1275 1649 (when (in switch 'register 'both) 1276 < (prbold "Create Account") 1277 < (br2) 1278 < (if (acons afterward) 1279 < (let (f url) afterward 1280 < (arformh (fn (req) 1281 < (logout-user (get-user req)) 1282 < (with (user (arg req "u") pw (arg req "p")) 1283 < (aif (bad-newacct user pw) 1284 < (flink (fn ignore 1285 < (login-page switch it afterward))) 1286 < (do (create-acct user pw) 1287 < (= (logins* user) (req 'ip)) 1288 < (prcookie (cook-user user)) 1289 < (f user (req 'ip)) 1290 < url)))) 1291 < (pwfields "create account"))) 1292 < (aformh (fn (req) 1293 < (logout-user (get-user req)) 1294 < (with (user (arg req "u") pw (arg req "p")) 1295 < (aif (bad-newacct user pw) 1296 < (do (prn) 1297 < (login-page switch it afterward)) 1298 < (do (create-acct user pw) 1299 < (= (logins* user) (req 'ip)) 1300 < (prcookie (cook-user user)) 1301 < (prn) 1302 < (afterward user (req 'ip)))))) 1303 < (pwfields "create account")))))) 1304 < 1650 > (login-form "Create Account" switch create-handler afterward)))) 1651 > 1652 > (def login-form (label switch handler afterward) 1653 > (prbold label) 1654 > (br2) 1655 > (fnform (fn (req) (handler req switch afterward)) 1656 > (fn () (pwfields (downcase label))) 1657 > (acons afterward))) 1658 > 1659 > (def login-handler (req switch afterward) 1660 > (logout-user (get-user req)) 1661 > (aif (good-login (arg req "u") (arg req "p") req!ip) 1662 > (login it req!ip (user->cookie* it) afterward) 1663 > (failed-login switch "Bad login." afterward))) 1664 > 1665 > (def create-handler (req switch afterward) 1666 > (logout-user (get-user req)) 1667 > (with (user (arg req "u") pw (arg req "p")) 1668 > (aif (bad-newacct user pw) 1669 > (failed-login switch it afterward) 1670 > (do (create-acct user pw) 1671 > (login user req!ip (cook-user user) afterward))))) 1672 > 1673 > (def login (user ip cookie afterward) 1674 > (= (logins* user) ip) 1675 > (prcookie cookie) 1676 > (if (acons afterward) 1677 > (let (f url) afterward 1678 > (f user ip) 1679 > url) 1680 > (do (prn) 1681 > (afterward user ip)))) 1682 > 1683 > (def failed-login (switch msg afterward) 1684 > (if (acons afterward) 1685 > (flink (fn ignore (login-page switch msg afterward))) 1686 > (do (prn) 1687 > (login-page switch msg afterward)))) 1688 > 1305 1689 (def prcookie (cook) 1306 1690 (prn "Set-Cookie: user=" cook "; expires=Sun, 17-Jan-2038 19:14:07 GMT")) 1307 1691 1308 1692 (def pwfields ((o label "login")) 1309 1693 (inputs u username 20 nil 1310 1694 p password 20 nil) 1311 1695 (br) 1312 1696 (submit label)) 1313 1697 1314 1698 (= good-logins* (queue) bad-logins* (queue)) 1315 1699 1316 1700 (def good-login (user pw ip) 1317 < (let record (list (seconds) ip user pw) 1701 > (let record (list (seconds) ip user) 1318 1702 (if (and user pw (aand (shash pw) (is it (hpasswords* user)))) 1319 1703 (do (unless (user->cookie* user) (cook-user user)) 1320 1704 (enq-limit record good-logins*) 1321 1705 user) 1322 1706 (do (enq-limit record bad-logins*) 1323 1707 nil)))) 1324 1708 1325 < ; can remove this once sha1 installed on pi 1326 < 1327 1709 ; Create a file in case people have quote chars in their pws. I can't 1328 1710 ; believe there's no way to just send the chars. 1329 1711 1330 1712 (def shash (str) 1331 1713 (let fname (+ "/tmp/shash" (rand-string 10)) 1332 1714 (w/outfile f fname (disp str f)) 1333 1715 (let res (tostring (system (+ "openssl dgst -sha1 <" fname))) 1334 < (do1 (subseq res 0 (- (len res) 1)) 1716 > (do1 (cut res 0 (- (len res) 1)) 1335 1717 (rmfile fname))))) 1336 1718 1719 > (= dc-usernames* (table)) 1720 > 1721 > (def username-taken (user) 1722 > (when (empty dc-usernames*) 1723 > (each (k v) hpasswords* 1724 > (set (dc-usernames* (downcase k))))) 1725 > (dc-usernames* (downcase user))) 1726 > 1337 1727 (def bad-newacct (user pw) 1338 1728 (if (no (goodname user 2 15)) 1339 1729 "Usernames can only contain letters, digits, dashes and 1340 1730 underscores, and should be between 2 and 15 characters long. 1341 1731 Please choose another." 1342 < (let dcuser (downcase user) 1343 < (some [is dcuser (downcase _)] (keys hpasswords*))) 1732 > (username-taken user) 1344 1733 "That username is taken. Please choose another." 1345 1734 (or (no pw) (< (len pw) 4)) 1346 1735 "Passwords should be a least 4 characters long. Please 1347 1736 choose another." 1348 1737 nil)) 1349 1738 1350 1739 (def goodname (str (o min 1) (o max nil)) 1351 1740 (and (isa str 'string) 1352 1741 (>= (len str) min) 1353 1742 (~find (fn (c) (no (or (alphadig c) (in c #\- #\_)))) 1354 1743 str) 1355 1744 (isnt (str 0) #\-) 1356 1745 (or (no max) (<= (len str) max)) 1357 1746 str)) 1358 1747 1359 < 1360 1748 (defop logout req 1361 1749 (aif (get-user req) 1362 1750 (do (logout-user it) 1363 1751 (pr "Logged out.")) 1364 1752 (pr "You were not logged in."))) 1365 1753 1366 1754 (defop whoami req 1367 1755 (aif (get-user req) 1368 < (prs it 'at (req 'ip)) 1756 > (prs it 'at req!ip) 1369 1757 (do (pr "You are not logged in. ") 1370 1758 (w/link (login-page 'both) (pr "Log in")) 1371 1759 (pr ".")))) 1372 1760 1373 1761 1374 < 1375 < (= formwid* 60 bigformwid* 80 numwid* 8 formatdoc-url* nil) 1762 > (= formwid* 60 bigformwid* 80 numwid* 16 formatdoc-url* nil) 1376 1763 1377 1764 ; Eventually figure out a way to separate type name from format of 1378 1765 ; input field, instead of having e.g. toks and bigtoks 1379 1766 1380 1767 (def varfield (typ id val) 1381 1768 (if (in typ 'string 'string1 'url) 1382 1769 (gentag input type 'text name id value val size formwid*) 1383 < (in typ 'num 'int 'posint) 1770 > (in typ 'num 'int 'posint 'sym) 1384 1771 (gentag input type 'text name id value val size numwid*) 1385 1772 (in typ 'users 'toks) 1386 1773 (gentag input type 'text name id value (tostring (apply prs val)) 1387 1774 size formwid*) 1388 1775 (is typ 'sexpr) 1389 1776 (gentag input type 'text name id 1390 1777 value (tostring (map [do (write _) (sp)] val)) 1391 1778 size formwid*) 1392 1779 (in typ 'syms 'text 'doc 'mdtext 'mdtext2 'lines 'bigtoks) 1393 1780 (let text (if (in typ 'syms 'bigtoks) 1394 1781 (tostring (apply prs val)) 1782 > (is typ 'lines) 1783 > (tostring (apply pr (intersperse #\newline val))) 1395 1784 (in typ 'mdtext 'mdtext2) 1396 1785 (unmarkdown val) 1397 1786 (no val) 1398 1787 "" 1399 1788 val) 1400 1789 (tag (textarea cols (if (is typ 'doc) bigformwid* formwid*) 1401 1790 rows (needrows text formwid* 4) 1402 1791 wrap 'virtual 1403 1792 style (if (is typ 'doc) "font-size:8.5pt") 1404 1793 name id) 1405 1794 (prn) ; needed or 1 initial newline gets chopped off 1406 1795 (pr text)) 1407 1796 (when (and formatdoc-url* (in typ 'mdtext 'mdtext2)) 1408 1797 (pr " ") 1409 1798 (tag (font size -2) 1410 1799 (link "help" formatdoc-url* (gray 175))))) 1411 < (and (acons typ) (is (car typ) 'choice)) 1800 > (caris typ 'choice) 1412 1801 (menu id (cddr typ) val) 1413 1802 (is typ 'yesno) 1414 1803 (menu id '("yes" "no") (if val "yes" "no")) 1415 1804 (is typ 'hexcol) 1416 < (gentag input type 'text name id value val); was (hexrep val) 1805 > (gentag input type 'text name id value val) 1806 > (is typ 'time) 1807 > (gentag input type 'text name id value (if val (english-time val) "")) 1808 > (is typ 'date) 1809 > (gentag input type 'text name id value (if val (english-date val) "")) 1417 1810 (err "unknown varfield type" typ))) 1418 1811 1419 1812 (def text-rows (text wid (o pad 3)) 1420 < (+ (truncate (/ (len text) (* wid .8))) pad)) 1813 > (+ (trunc (/ (len text) (* wid .8))) pad)) 1421 1814 1422 1815 (def needrows (text cols (o pad 0)) 1423 1816 (+ pad (max (+ 1 (count #\newline text)) 1424 1817 (roundup (/ (len text) (- cols 5)))))) 1425 1818 1426 < (def varline (typ id val) 1819 > (def varline (typ id val (o liveurls)) 1427 1820 (if (in typ 'users 'syms 'toks 'bigtoks) (apply prs val) 1428 1821 (is typ 'lines) (map prn val) 1429 1822 (is typ 'yesno) (pr (if val 'yes 'no)) 1430 < (is typ 'choice) (varline (cadr typ) nil val) 1823 > (caris typ 'choice) (varline (cadr typ) nil val) 1824 > (is typ 'url) (if (and liveurls (valid-url val)) 1825 > (link val val) 1826 > (pr val)) 1431 1827 (text-type typ) (pr (or val "")) 1432 1828 (pr val))) 1433 1829 1434 1830 (def text-type (typ) (in typ 'string 'string1 'url 'text 'mdtext 'mdtext2)) 1435 1831 1436 1832 ; Newlines in forms come back as /r/n. Only want the /ns. Currently 1437 1833 ; remove the /rs in individual cases below. Could do it in aform or 1438 1834 ; even in the parsing of http requests, in the server. 1439 1835 1440 1836 ; Need the calls to striptags so that news users can't get html 1441 1837 ; into a title or comment by editing it. If want a form that 1442 1838 ; can take html, just create another typ for it. 1443 1839 1444 1840 (def readvar (typ str (o fail nil)) 1445 1841 (case (carif typ) 1446 1842 string (striptags str) 1447 < string1 (if (is str "") fail (striptags str)) 1448 < url (if (is str "") str (valid-url str) (striptags str) fail) 1843 > string1 (if (blank str) fail (striptags str)) 1844 > url (if (blank str) "" (valid-url str) (clean-url str) fail) 1449 1845 num (let n (saferead str) (if (number n) n fail)) 1450 1846 int (let n (saferead str) 1451 1847 (if (number n) (round n) fail)) 1452 1848 posint (let n (saferead str) 1453 1849 (if (and (number n) (> n 0)) (round n) fail)) 1454 1850 text (striptags str) 1455 1851 doc (striptags str) 1456 1852 mdtext (md-from-form str) 1457 1853 mdtext2 (md-from-form str t) ; for md with no links 1458 < ; sym (aif (tokens str) (sym (car it)) fail) 1459 < ; syms (map sym (tokens str)) 1854 > sym (or (sym:car:tokens str) fail) 1855 > syms (map sym (tokens str)) 1460 1856 sexpr (errsafe (readall str)) 1461 1857 users (rem [no (goodname _)] (tokens str)) 1462 1858 toks (tokens str) 1463 1859 bigtoks (tokens str) 1464 < ; lines (or (splitlines (= sss str)) fail) 1860 > lines (lines str) 1465 1861 choice (readvar (cadr typ) str) 1466 1862 yesno (is str "yes") 1467 < hexcol (if (hex>color str) str fail) ; was (or (hex>color str) fail) 1863 > hexcol (if (hex>color str) str fail) 1864 > time (or (errsafe (parse-time str)) fail) 1865 > date (or (errsafe (parse-date str)) fail) 1468 1866 (err "unknown readvar type" typ))) 1469 1867 1470 < (def splitlines (str) 1471 < (map [rem #\return _] (split (cons #\newline "") str))) 1868 > ; dates should be tagged date, and just redefine < 1869 > 1870 > (def varcompare (typ) 1871 > (if (in typ 'syms 'sexpr 'users 'toks 'bigtoks 'lines 'hexcol) 1872 > (fn (x y) (> (len x) (len y))) 1873 > (is typ 'date) 1874 > (fn (x y) 1875 > (or (no y) (and x (date< x y)))) 1876 > (fn (x y) 1877 > (or (empty y) (and (~empty x) (< x y)))))) 1878 > 1472 1879 1473 < (= fail* (uniq)) 1880 > ; (= fail* (uniq)) 1881 > 1882 > (def fail* ()) ; coudn't possibly come back from a form 1474 1883 1475 1884 ; Takes a list of fields of the form (type label value view modify) and 1476 1885 ; a fn f and generates a form such that when submitted (f label newval) 1477 1886 ; will be called for each valid value. Finally done is called. 1478 1887 1479 1888 (def vars-form (user fields f done (o button "update") (o lasts)) 1480 < (timed-aform lasts 1481 < (fn (req) 1482 < (when-usermatch user req 1483 < (each (k v) (req 'args) 1484 < (let name (sym k) 1485 < (awhen (find [is (cadr _) name] fields) 1486 < (let (typ id val mod) it 1487 < (when (and mod v) 1488 < (let newval (readvar typ v fail*) 1489 < (unless (is newval fail*) 1490 < (f name newval)))))))) 1491 < (done))) 1889 > (taform lasts 1890 > (if (all [no (_ 4)] fields) 1891 > (fn (req)) 1892 > (fn (req) 1893 > (when-umatch user req 1894 > (each (k v) req!args 1895 > (let name (sym k) 1896 > (awhen (find [is (cadr _) name] fields) 1897 > ; added sho to fix bug 1898 > (let (typ id val sho mod) it 1899 > (when (and mod v) 1900 > (let newval (readvar typ v fail*) 1901 > (unless (is newval fail*) 1902 > (f name newval)))))))) 1903 > (done)))) 1492 1904 (tab 1493 1905 (showvars fields)) 1494 1906 (unless (all [no (_ 4)] fields) ; no modifiable fields 1495 1907 (br) 1496 1908 (submit button)))) 1497 1909 1498 < (def showvars (fields) 1910 > (def showvars (fields (o liveurls)) 1499 1911 (each (typ id val view mod question) fields 1500 1912 (when view 1501 1913 (when question 1502 1914 (tr (td (prn question)))) 1503 1915 (tr (unless question (tag (td valign 'top) (pr id ":"))) 1504 < (td ((if mod varfield varline) typ id val))) 1916 > (td (if mod 1917 > (varfield typ id val) 1918 > (varline typ id val liveurls)))) 1505 1919 (prn)))) 1506 1920 1507 1921 ; http://daringfireball.net/projects/markdown/syntax 1508 1922 1509 1923 (def md-from-form (str (o nolinks)) 1510 < (markdown (trim (rem #\return (esc<>& str)) 'end) 60 nolinks)) 1924 > (markdown (trim (rem #\return (esc-tags str)) 'end) 60 nolinks)) 1511 1925 1512 1926 (def markdown (s (o maxurl) (o nolinks)) 1513 1927 (let ital nil 1514 1928 (tostring 1515 1929 (forlen i s 1516 1930 (iflet (newi spaces) (indented-code s i (if (is i 0) 2 0)) 1517 1931 (do (pr "

")
1518 1932                      (let cb (code-block s (- newi spaces 1))
1519 1933                        (pr cb)
1520 1934                        (= i (+ (- newi spaces 1) (len cb))))
1521 1935                      (pr "
")) 1522 1936 (iflet newi (parabreak s i (if (is i 0) 1 0)) 1523 1937 (do (unless (is i 0) (pr "

")) 1524 1938 (= i (- newi 1))) 1525 1939 (and (is (s i) #\*) 1526 1940 (or ital 1527 1941 (atend i s) 1528 1942 (and (~whitec (s (+ i 1))) 1529 1943 (pos #\* s (+ i 1))))) 1530 1944 (do (pr (if ital "" "")) 1531 1945 (= ital (no ital))) 1532 1946 (and (no nolinks) 1533 < (t! gotthere) 1534 1947 (or (litmatch "http://" s i) 1535 1948 (litmatch "https://" s i))) 1536 1949 (withs (n (urlend s i) 1537 < url (subseq s i n)) 1950 > url (clean-url (cut s i n))) 1538 1951 (tag (a href url rel 'nofollow) 1539 1952 (pr (if (no maxurl) url (ellipsize url maxurl)))) 1540 1953 (= i (- n 1))) 1541 1954 (writec (s i)))))))) 1542 1955 1543 1956 (def indented-code (s i (o newlines 0) (o spaces 0)) 1544 1957 (let c (s i) 1545 1958 (if (nonwhite c) 1546 1959 (if (and (> newlines 1) (> spaces 1)) 1547 1960 (list i spaces) 1548 1961 nil) 1549 1962 (atend i s) 1550 1963 nil 1551 1964 (is c #\newline) 1552 1965 (indented-code s (+ i 1) (+ newlines 1) 0) 1553 1966 (indented-code s (+ i 1) newlines (+ spaces 1))))) 1554 1967 1968 > ; If i is start a paragraph break, returns index of start of next para. 1969 > 1555 1970 (def parabreak (s i (o newlines 0)) 1556 1971 (let c (s i) 1557 1972 (if (or (nonwhite c) (atend i s)) 1558 1973 (if (> newlines 1) i nil) 1559 1974 (parabreak s (+ i 1) (+ newlines (if (is c #\newline) 1 0)))))) 1560 < 1975 > 1976 > ; Returns the indices of the next paragraph break in s, if any. 1561 1977 1978 > (def next-parabreak (s i) 1979 > (unless (atend i s) 1980 > (aif (parabreak s i) 1981 > (list i it) 1982 > (next-parabreak s (+ i 1))))) 1983 > 1984 > (def paras (s (o i 0)) 1985 > (if (atend i s) 1986 > nil 1987 > (iflet (endthis startnext) (next-parabreak s i) 1988 > (cons (cut s i endthis) 1989 > (paras s startnext)) 1990 > (list (trim (cut s i) 'end))))) 1991 > 1992 > 1562 1993 ; Returns the index of the first char not part of the url beginning 1563 1994 ; at i, or len of string if url goes all the way to the end. 1564 1995 1565 1996 ; Note that > immediately after a url (http://foo.com>) will cause 1566 1997 ; an odd result, because the > gets escaped to something beginning 1567 1998 ; with &, which is treated as part of the url. Perhaps the answer 1568 < ; is just to esc<>& after markdown instead of before. 1999 > ; is just to esc-tags after markdown instead of before. 1569 2000 1570 < (def urlend (s i) 2001 > ; Treats a delimiter as part of a url if it is (a) an open delimiter 2002 > ; not followed by whitespace or eos, or (b) a close delimiter 2003 > ; balancing a previous open delimiter. 2004 > 2005 > (def urlend (s i (o indelim)) 1571 2006 (let c (s i) 1572 2007 (if (atend i s) 1573 < (if ((orf punc delimc whitec) c) i (+ i 1)) 2008 > (if ((orf punc whitec opendelim) c) 2009 > i 2010 > (closedelim c) 2011 > (if indelim (+ i 1) i) 2012 > (+ i 1)) 1574 2013 (if (or (whitec c) 1575 < (delimc c) 1576 < (and (punc c) 1577 < ((orf whitec delimc) (s (+ i 1))))) 2014 > (and (punc c) (whitec (s (+ i 1)))) 2015 > (and ((orf whitec punc) (s (+ i 1))) 2016 > (or (opendelim c) 2017 > (and (closedelim c) (no indelim))))) 1578 2018 i 1579 < (urlend s (+ i 1)))))) 1580 < 1581 < (def delimc (c) 1582 < (in c #\( #\) #\[ #\] #\{ #\} #\")) 2019 > (urlend s (+ i 1) (or (opendelim c) 2020 > (and indelim (no (closedelim c))))))))) 2021 > 2022 > (def opendelim (c) (in c #\< #\( #\[ #\{)) 2023 > 2024 > (def closedelim (c) (in c #\> #\) #\] #\})) 1583 2025 1584 2026 1585 2027 (def code-block (s i) 1586 2028 (tostring 1587 2029 (until (let left (- (len s) i 1) 1588 2030 (or (is left 0) 1589 2031 (and (> left 2) 1590 2032 (is (s (+ i 1)) #\newline) 1591 2033 (nonwhite (s (+ i 2)))))) 1592 2034 (writec (s (++ i)))))) 1593 2035 1594 2036 (def unmarkdown (s) 1595 2037 (tostring 1596 2038 (forlen i s 1597 2039 (if (litmatch "

" s i) 1598 2040 (do (++ i 2) 1599 2041 (unless (is i 2) (pr "\n\n"))) 1600 2042 (litmatch "" s i) 1601 2043 (do (++ i 2) (pr #\*)) 1602 2044 (litmatch "" s i) 1603 2045 (do (++ i 3) (pr #\*)) 1604 2046 (litmatch "" s endurl) 1609 2051 (+ it 3) 1610 2052 endurl))) 1611 2053 (writec (s i)))) 1612 2054 (litmatch "

" s i)
1613 2055                (awhen (findsubseq "
" s (+ i 12)) 1614 < (pr (subseq s (+ i 11) it)) 2056 > (pr (cut s (+ i 11) it)) 1615 2057 (= i (+ it 12))) 1616 < (litmatch "
" s i)
1617      <              (awhen (findsubseq "
" s (+ i 12)) 1618 < (pr (subseq s (+ i 11) it)) 1619 < (= i (+ it 12))) 1620 2058 (writec (s i)))))) 1621 2059 2060 > 2061 > (def english-time (min) 2062 > (let n (mod min 720) 2063 > (string (let h (trunc (/ n 60)) (if (is h 0) "12" h)) 2064 > ":" 2065 > (let m (mod n 60) 2066 > (if (is m 0) "00" 2067 > (< m 10) (string "0" m) 2068 > m)) 2069 > (if (is min 0) " midnight" 2070 > (is min 720) " noon" 2071 > (>= min 720) " pm" 2072 > " am")))) 2073 > 2074 > (def parse-time (s) 2075 > (let (nums (o label "")) (halve s letter) 2076 > (with ((h (o m 0)) (map int (tokens nums ~digit)) 2077 > cleanlabel (downcase (rem ~alphadig label))) 2078 > (+ (* (if (is h 12) 2079 > (if (in cleanlabel "am" "midnight") 2080 > 0 2081 > 12) 2082 > (is cleanlabel "am") 2083 > h 2084 > (+ h 12)) 2085 > 60) 2086 > m)))) 2087 > 2088 > 2089 > (= months* '("January" "February" "March" "April" "May" "June" "July" 2090 > "August" "September" "October" "November" "December")) 2091 > 2092 > (def english-date ((y m d)) 2093 > (string d " " (months* (- m 1)) " " y)) 2094 > 2095 > (= month-names* (obj "january" 1 "jan" 1 2096 > "february" 2 "feb" 2 2097 > "march" 3 "mar" 3 2098 > "april" 4 "apr" 4 2099 > "may" 5 2100 > "june" 6 "jun" 6 2101 > "july" 7 "jul" 7 2102 > "august" 8 "aug" 8 2103 > "september" 9 "sept" 9 "sep" 9 2104 > "october" 10 "oct" 10 2105 > "november" 11 "nov" 11 2106 > "december" 12 "dec" 12)) 2107 > 2108 > (def monthnum (s) (month-names* (downcase s))) 2109 > 2110 > ; Doesn't work for BC dates. 2111 > 2112 > (def parse-date (s) 2113 > (let nums (date-nums s) 2114 > (if (valid-date nums) 2115 > nums 2116 > (err (string "Invalid date: " s))))) 2117 > 2118 > (def date-nums (s) 2119 > (with ((ynow mnow dnow) (date) 2120 > toks (tokens s ~alphadig)) 2121 > (if (all [all digit _] toks) 2122 > (let nums (map int toks) 2123 > (case (len nums) 2124 > 1 (list ynow mnow (car nums)) 2125 > 2 (iflet d (find [> _ 12] nums) 2126 > (list ynow (find [isnt _ d] nums) d) 2127 > (cons ynow nums)) 2128 > (if (> (car nums) 31) 2129 > (firstn 3 nums) 2130 > (rev (firstn 3 nums))))) 2131 > ([all digit _] (car toks)) 2132 > (withs ((ds ms ys) toks 2133 > d (int ds)) 2134 > (aif (monthnum ms) 2135 > (list (or (errsafe (int ys)) ynow) 2136 > it 2137 > d) 2138 > nil)) 2139 > (monthnum (car toks)) 2140 > (let (ms ds ys) toks 2141 > (aif (errsafe (int ds)) 2142 > (list (or (errsafe (int ys)) ynow) 2143 > (monthnum (car toks)) 2144 > it) 2145 > nil)) 2146 > nil))) 2147 > 2148 > ; To be correct needs to know days per month, and about leap years 2149 > 2150 > (def valid-date ((y m d)) 2151 > (and y m d 2152 > (< 0 m 13) 2153 > (< 0 d 32))) 1622 2154 1623 2155 (mac defopl (name parm . body) 1624 2156 `(defop ,name ,parm 1625 2157 (if (get-user ,parm) 1626 2158 (do ,@body) 1627 2159 (login-page 'both 1628 2160 "You need to be logged in to do that." 1629 2161 (list (fn (u ip)) 1630 2162 (string ',name (reassemble-args ,parm))))))) 1631 2163 1632 2164 ================================================================================ arc.arc ================================================================================ 1634 2166 ; Main Arc lib. Ported to Scheme version Jul 06. 1635 2167 1636 < ; optimize ~foo in functional position in ac, like compose 1637 < ; rename: string, into-string (shorter). could call intos string, 1638 < ; but then what to call string? 2168 > ; don't like names of conswhen and consif 2169 > 2170 > ; need better way of generating strings; too many calls to string 2171 > ; maybe strings with escape char for evaluation 2172 > ; make foo~bar equiv of foo:~bar (in expand-ssyntax) 2173 > ; add sigs of ops defined in ac.scm 1639 2174 ; get hold of error types within arc 1640 < ; why is macex defined in scheme instead of using def below? 2175 > ; does macex have to be defined in scheme instead of using def below? 1641 2176 ; write disp, read, write in arc 1642 < ; could prob write rmfile and dir in terms of system 1643 < ; could I get all of macros up into lib.arc? 1644 < 1645 < ; any logical reason I can't say (push x (if foo y z)) ? 1646 < ; eval would have to always ret 2 things, the val and where it came from 1647 < ; idea: implicit tables of tables; setf empty field, becomes table 1648 < ; or should setf on a table just take n args? 1649 < ; idea: permanent objs that live on disk and are updated when modified 2177 > ; could I get all of macros up into arc.arc? 2178 > ; warn when shadow a global name 2179 > ; some simple regexp/parsing plan 1650 2180 1651 2181 ; compromises in this implementation: 1652 2182 ; no objs in code 1653 2183 ; (mac testlit args (listtab args)) breaks when called 1654 2184 ; separate string type 1655 2185 ; (= (cdr (cdr str)) "foo") couldn't work because no way to get str tail 2186 > ; not sure this is a mistake; strings may be subtly different from 2187 > ; lists of chars 1656 2188 1657 2189 1658 < (set do (annotate 'mac 1659 < (fn args `((fn () ,@args))))) 2190 > (assign do (annotate 'mac 2191 > (fn args `((fn () ,@args))))) 1660 2192 1661 < (set safeset (annotate 'mac 1662 < (fn (var val) 1663 < `(do (if (bound ',var) 1664 < (do (disp "*** redefining ") 1665 < (disp ',var) 1666 < (writec #\newline))) 1667 < (set ,var ,val))))) 2193 > (assign safeset (annotate 'mac 2194 > (fn (var val) 2195 > `(do (if (bound ',var) 2196 > (do (disp "*** redefining " (stderr)) 2197 > (disp ',var (stderr)) 2198 > (disp #\newline (stderr)))) 2199 > (assign ,var ,val))))) 1668 2200 1669 < (set def (annotate 'mac 1670 < (fn (name parms . body) 1671 < `(do (sref sig ',parms ',name) 1672 < (safeset ,name (fn ,parms ,@body)))))) 2201 > (assign def (annotate 'mac 2202 > (fn (name parms . body) 2203 > `(do (sref sig ',parms ',name) 2204 > (safeset ,name (fn ,parms ,@body)))))) 1673 2205 1674 2206 (def caar (xs) (car (car xs))) 1675 2207 (def cadr (xs) (car (cdr xs))) 1676 2208 (def cddr (xs) (cdr (cdr xs))) 1677 2209 1678 2210 (def no (x) (is x nil)) 1679 2211 1680 2212 (def acons (x) (is (type x) 'cons)) 1681 2213 1682 2214 (def atom (x) (no (acons x))) 1683 2215 1684 < (def list args args) 2216 > ; Can return to this def once Rtm gets ac to make all rest args 2217 > ; nil-terminated lists. 2218 > 2219 > ; (def list args args) 1685 2220 2221 > (def copylist (xs) 2222 > (if (no xs) 2223 > nil 2224 > (cons (car xs) (copylist (cdr xs))))) 2225 > 2226 > (def list args (copylist args)) 2227 > 1686 2228 (def idfn (x) x) 1687 2229 1688 < ; Maybe later make this internal. 2230 > ; Maybe later make this internal. Useful to let xs be a fn? 1689 2231 1690 2232 (def map1 (f xs) 1691 2233 (if (no xs) 1692 2234 nil 1693 2235 (cons (f (car xs)) (map1 f (cdr xs))))) 1694 2236 1695 2237 (def pair (xs (o f list)) 1696 2238 (if (no xs) 1697 2239 nil 1698 2240 (no (cdr xs)) 1699 2241 (list (list (car xs))) 1700 2242 (cons (f (car xs) (cadr xs)) 1701 2243 (pair (cddr xs) f)))) 1702 2244 1703 < (set mac (annotate 'mac 1704 < (fn (name parms . body) 1705 < `(do (sref sig ',parms ',name) 1706 < (safeset ,name (annotate 'mac (fn ,parms ,@body))))))) 2245 > (assign mac (annotate 'mac 2246 > (fn (name parms . body) 2247 > `(do (sref sig ',parms ',name) 2248 > (safeset ,name (annotate 'mac (fn ,parms ,@body))))))) 1707 2249 1708 2250 (mac and args 1709 2251 (if args 1710 2252 (if (cdr args) 1711 2253 `(if ,(car args) (and ,@(cdr args))) 1712 2254 (car args)) 1713 2255 't)) 1714 2256 1715 2257 (def assoc (key al) 1716 2258 (if (atom al) 1717 2259 nil 1718 2260 (and (acons (car al)) (is (caar al) key)) 1719 2261 (car al) 1720 2262 (assoc key (cdr al)))) 1721 2263 1722 2264 (def alref (al key) (cadr (assoc key al))) 1723 2265 1724 2266 (mac with (parms . body) 1725 2267 `((fn ,(map1 car (pair parms)) 1726 2268 ,@body) 1727 2269 ,@(map1 cadr (pair parms)))) 1728 2270 1729 2271 (mac let (var val . body) 1730 2272 `(with (,var ,val) ,@body)) 1731 2273 1732 2274 (mac withs (parms . body) 1733 2275 (if (no parms) 1734 2276 `(do ,@body) 1735 2277 `(let ,(car parms) ,(cadr parms) 1736 2278 (withs ,(cddr parms) ,@body)))) 1737 2279 1738 2280 ; Rtm prefers to overload + to do this 1739 2281 1740 2282 (def join args 1741 2283 (if (no args) 1742 2284 nil 1743 2285 (let a (car args) 1744 2286 (if (no a) 1745 2287 (apply join (cdr args)) 1746 2288 (cons (car a) (apply join (cdr a) (cdr args))))))) 1747 2289 1748 2290 ; Need rfn for use in macro expansions. 1749 2291 1750 2292 (mac rfn (name parms . body) 1751 2293 `(let ,name nil 1752 < (set ,name (fn ,parms ,@body)))) 2294 > (assign ,name (fn ,parms ,@body)))) 1753 2295 1754 2296 (mac afn (parms . body) 1755 2297 `(let self nil 1756 < (set self (fn ,parms ,@body)))) 2298 > (assign self (fn ,parms ,@body)))) 1757 2299 1758 2300 ; Ac expands x:y:z into (compose x y z), ~x into (complement x) 1759 2301 1760 2302 ; Only used when the call to compose doesn't occur in functional position. 1761 2303 ; Composes in functional position are transformed away by ac. 1762 2304 1763 2305 (mac compose args 1764 2306 (let g (uniq) 1765 2307 `(fn ,g 1766 2308 ,((afn (fs) 1767 2309 (if (cdr fs) 1768 2310 (list (car fs) (self (cdr fs))) 1769 2311 `(apply ,(if (car fs) (car fs) 'idfn) ,g))) 1770 2312 args)))) 2313 > 2314 > ; Ditto: complement in functional position optimized by ac. 1771 2315 1772 2316 (mac complement (f) 1773 2317 (let g (uniq) 1774 2318 `(fn ,g (no (apply ,f ,g))))) 1775 2319 1776 2320 (def rev (xs) 1777 2321 ((afn (xs acc) 1778 2322 (if (no xs) 1779 2323 acc 1780 2324 (self (cdr xs) (cons (car xs) acc)))) 1781 2325 xs nil)) 1782 2326 1783 2327 (def isnt (x y) (no (is x y))) 1784 2328 1785 2329 (mac w/uniq (names . body) 1786 2330 (if (acons names) 1787 2331 `(with ,(apply + nil (map1 (fn (n) (list n '(uniq))) 1788 2332 names)) 1789 2333 ,@body) 1790 2334 `(let ,names (uniq) ,@body))) 1791 2335 1792 2336 (mac or args 1793 2337 (and args 1794 2338 (w/uniq g 1795 2339 `(let ,g ,(car args) 1796 2340 (if ,g ,g (or ,@(cdr args))))))) 1797 2341 1798 2342 (def alist (x) (or (no x) (is (type x) 'cons))) 1799 2343 1800 2344 (mac in (x . choices) 1801 2345 (w/uniq g 1802 2346 `(let ,g ,x 1803 2347 (or ,@(map1 (fn (c) `(is ,g ,c)) choices))))) 1804 2348 1805 < ; should take n args 2349 > ; Could take n args, but have never once needed that. 1806 2350 1807 2351 (def iso (x y) 1808 2352 (or (is x y) 1809 2353 (and (acons x) 1810 2354 (acons y) 1811 2355 (iso (car x) (car y)) 1812 2356 (iso (cdr x) (cdr y))))) 1813 2357 1814 2358 (mac when (test . body) 1815 2359 `(if ,test (do ,@body))) 1816 2360 1817 2361 (mac unless (test . body) 1818 2362 `(if (no ,test) (do ,@body))) 1819 2363 1820 2364 (mac while (test . body) 1821 2365 (w/uniq (gf gp) 1822 2366 `((rfn ,gf (,gp) 1823 2367 (when ,gp ,@body (,gf ,test))) 1824 2368 ,test))) 1825 2369 1826 2370 (def empty (seq) 1827 2371 (or (no seq) 1828 < (and (no (acons seq)) (is (len seq) 0)))) 2372 > (and (or (is (type seq) 'string) (is (type seq) 'table)) 2373 > (is (len seq) 0)))) 1829 2374 1830 2375 (def reclist (f xs) 1831 2376 (and xs (or (f xs) (reclist f (cdr xs))))) 1832 2377 1833 2378 (def recstring (test s (o start 0)) 1834 < (let n (len s) 1835 < ((afn (i) 1836 < (and (< i (len s)) 1837 < (or (test i) 1838 < (self (+ i 1))))) 1839 < start))) 2379 > ((afn (i) 2380 > (and (< i (len s)) 2381 > (or (test i) 2382 > (self (+ i 1))))) 2383 > start)) 1840 2384 1841 2385 (def testify (x) 1842 2386 (if (isa x 'fn) x [is _ x])) 2387 > 2388 > ; Like keep, seems like some shouldn't testify. But find should, 2389 > ; and all probably should. 1843 2390 1844 2391 (def some (test seq) 1845 2392 (let f (testify test) 1846 2393 (if (alist seq) 1847 2394 (reclist f:car seq) 1848 2395 (recstring f:seq seq)))) 1849 2396 1850 2397 (def all (test seq) 1851 2398 (~some (complement (testify test)) seq)) 1852 2399 1853 2400 (def mem (test seq) 1854 2401 (let f (testify test) 1855 2402 (reclist [if (f:car _) _] seq))) 1856 2403 1857 2404 (def find (test seq) 1858 2405 (let f (testify test) 1859 2406 (if (alist seq) 1860 2407 (reclist [if (f:car _) (car _)] seq) 1861 2408 (recstring [if (f:seq _) (seq _)] seq)))) 1862 2409 1863 2410 (def isa (x y) (is (type x) y)) 1864 2411 1865 2412 ; Possible to write map without map1, but makes News 3x slower. 1866 2413 1867 2414 ;(def map (f . seqs) 1868 2415 ; (if (some1 no seqs) 1869 2416 ; nil 1870 2417 ; (no (cdr seqs)) 1871 2418 ; (let s1 (car seqs) 1872 2419 ; (cons (f (car s1)) 1873 2420 ; (map f (cdr s1)))) 1874 2421 ; (cons (apply f (map car seqs)) 1875 2422 ; (apply map f (map cdr seqs))))) 1876 2423 1877 2424 1878 2425 (def map (f . seqs) 1879 2426 (if (some [isa _ 'string] seqs) 1880 2427 (withs (n (apply min (map len seqs)) 1881 2428 new (newstring n)) 1882 2429 ((afn (i) 1883 2430 (if (is i n) 1884 2431 new 1885 2432 (do (sref new (apply f (map [_ i] seqs)) i) 1886 2433 (self (+ i 1))))) 1887 2434 0)) 1888 2435 (no (cdr seqs)) 1889 2436 (map1 f (car seqs)) 1890 2437 ((afn (seqs) 1891 2438 (if (some no seqs) 1892 2439 nil 1893 2440 (cons (apply f (map1 car seqs)) 1894 2441 (self (map1 cdr seqs))))) 1895 2442 seqs))) 1896 2443 1897 2444 (def mappend (f . args) 1898 2445 (apply + nil (apply map f args))) 1899 2446 1900 2447 (def firstn (n xs) 1901 < (if (and (> n 0) xs) 1902 < (cons (car xs) (firstn (- n 1) (cdr xs))) 1903 < nil)) 2448 > (if (no n) xs 2449 > (and (> n 0) xs) (cons (car xs) (firstn (- n 1) (cdr xs))) 2450 > nil)) 1904 2451 1905 2452 (def nthcdr (n xs) 1906 < (if (> n 0) 1907 < (nthcdr (- n 1) (cdr xs)) 1908 < xs)) 2453 > (if (no n) xs 2454 > (> n 0) (nthcdr (- n 1) (cdr xs)) 2455 > xs)) 1909 2456 1910 2457 ; Generalization of pair: (tuples x) = (pair x) 1911 2458 1912 2459 (def tuples (xs (o n 2)) 1913 2460 (if (no xs) 1914 2461 nil 1915 2462 (cons (firstn n xs) 1916 2463 (tuples (nthcdr n xs) n)))) 1917 2464 1918 < (def caris (x val) (and (acons x) (is (car x) val))) 2465 > ; If ok to do with =, why not with def? But see if use it. 2466 > 2467 > (mac defs args 2468 > `(do ,@(map [cons 'def _] (tuples args 3)))) 2469 > 2470 > (def caris (x val) 2471 > (and (acons x) (is (car x) val))) 1919 2472 1920 2473 (def warn (msg . args) 1921 2474 (disp (+ "Warning: " msg ". ")) 1922 2475 (map [do (write _) (disp " ")] args) 1923 2476 (disp #\newline)) 1924 2477 1925 2478 (mac atomic body 1926 2479 `(atomic-invoke (fn () ,@body))) 1927 2480 1928 2481 (mac atlet args 1929 2482 `(atomic (let ,@args))) 1930 2483 1931 2484 (mac atwith args 1932 2485 `(atomic (with ,@args))) 1933 2486 1934 2487 (mac atwiths args 1935 2488 `(atomic (withs ,@args))) 1936 2489 2490 > 1937 2491 ; setforms returns (vars get set) for a place based on car of an expr 1938 2492 ; vars is a list of gensyms alternating with expressions whose vals they 1939 2493 ; should be bound to, suitable for use as first arg to withs 1940 2494 ; get is an expression returning the current value in the place 1941 2495 ; set is an expression representing a function of one argument 1942 2496 ; that stores a new value in the place 1943 2497 1944 2498 ; A bit gross that it works based on the *name* in the car, but maybe 1945 2499 ; wrong to worry. Macros live in expression land. 1946 2500 1947 2501 ; seems meaningful to e.g. (push 1 (pop x)) if (car x) is a cons. 1948 2502 ; can't in cl though. could I define a setter for push or pop? 1949 2503 1950 < (set setter (table)) 2504 > (assign setter (table)) 1951 2505 1952 2506 (mac defset (name parms . body) 1953 2507 (w/uniq gexpr 1954 2508 `(sref setter 1955 2509 (fn (,gexpr) 1956 2510 (let ,parms (cdr ,gexpr) 1957 2511 ,@body)) 1958 2512 ',name))) 1959 2513 1960 2514 (defset car (x) 1961 2515 (w/uniq g 1962 2516 (list (list g x) 1963 2517 `(car ,g) 1964 2518 `(fn (val) (scar ,g val))))) 1965 2519 1966 2520 (defset cdr (x) 1967 2521 (w/uniq g 1968 2522 (list (list g x) 1969 2523 `(cdr ,g) 1970 2524 `(fn (val) (scdr ,g val))))) 1971 2525 1972 2526 (defset caar (x) 1973 2527 (w/uniq g 1974 2528 (list (list g x) 1975 2529 `(caar ,g) 1976 2530 `(fn (val) (scar (car ,g) val))))) 1977 2531 1978 2532 (defset cadr (x) 1979 2533 (w/uniq g 1980 2534 (list (list g x) 1981 2535 `(cadr ,g) 1982 2536 `(fn (val) (scar (cdr ,g) val))))) 1983 2537 1984 2538 (defset cddr (x) 1985 2539 (w/uniq g 1986 2540 (list (list g x) 1987 2541 `(cddr ,g) 1988 2542 `(fn (val) (scdr (cdr ,g) val))))) 1989 2543 1990 2544 ; Note: if expr0 macroexpands into any expression whose car doesn't 1991 2545 ; have a setter, setforms assumes it's a data structure in functional 1992 2546 ; position. Such bugs will be seen only when the code is executed, when 1993 2547 ; sref complains it can't set a reference to a function. 1994 2548 1995 2549 (def setforms (expr0) 1996 2550 (let expr (macex expr0) 1997 2551 (if (isa expr 'sym) 1998 < (w/uniq (g h) 1999 < (list (list g expr) 2000 < g 2001 < `(fn (,h) (set ,expr ,h)))) 2552 > (if (ssyntax expr) 2553 > (setforms (ssexpand expr)) 2554 > (w/uniq (g h) 2555 > (list (list g expr) 2556 > g 2557 > `(fn (,h) (assign ,expr ,h))))) 2002 2558 ; make it also work for uncompressed calls to compose 2003 2559 (and (acons expr) (metafn (car expr))) 2004 2560 (setforms (expand-metafn-call (ssexpand (car expr)) (cdr expr))) 2561 > (and (acons expr) (acons (car expr)) (is (caar expr) 'get)) 2562 > (setforms (list (cadr expr) (cadr (car expr)))) 2005 2563 (let f (setter (car expr)) 2006 2564 (if f 2007 2565 (f expr) 2008 2566 ; assumed to be data structure in fn position 2009 2567 (do (when (caris (car expr) 'fn) 2010 < (warn "Inverting what looks like a function call" 2568 > (warn "Inverting what looks like a function call" 2011 2569 expr0 expr)) 2012 2570 (w/uniq (g h) 2013 2571 (let argsyms (map [uniq] (cdr expr)) 2014 2572 (list (+ (list g (car expr)) 2015 2573 (mappend list argsyms (cdr expr))) 2016 2574 `(,g ,@argsyms) 2017 < `(fn (,h) (sref ,g ,h ,@argsyms))))))))))) 2575 > `(fn (,h) (sref ,g ,h ,(car argsyms)))))))))))) 2018 2576 2019 2577 (def metafn (x) 2020 2578 (or (ssyntax x) 2021 2579 (and (acons x) (in (car x) 'compose 'complement)))) 2022 2580 2023 2581 (def expand-metafn-call (f args) 2024 2582 (if (is (car f) 'compose) 2025 < ((afn (fs) 2026 < (if (caris (car fs) 'compose) ; nested compose 2027 < (self (join (cdr (car fs)) (cdr fs))) 2028 < (cdr fs) 2029 < (list (car fs) (self (cdr fs))) 2030 < (cons (car fs) args))) 2031 < (cdr f)) 2032 < (err "Can't invert " (cons f args)))) 2583 > ((afn (fs) 2584 > (if (caris (car fs) 'compose) ; nested compose 2585 > (self (join (cdr (car fs)) (cdr fs))) 2586 > (cdr fs) 2587 > (list (car fs) (self (cdr fs))) 2588 > (cons (car fs) args))) 2589 > (cdr f)) 2590 > (is (car f) 'no) 2591 > (err "Can't invert " (cons f args)) 2592 > (cons f args))) 2033 2593 2034 2594 (def expand= (place val) 2035 < (if (isa place 'sym) 2036 < `(set ,place ,val) 2595 > (if (and (isa place 'sym) (~ssyntax place)) 2596 > `(assign ,place ,val) 2037 2597 (let (vars prev setter) (setforms place) 2038 2598 (w/uniq g 2039 2599 `(atwith ,(+ vars (list g val)) 2040 2600 (,setter ,g)))))) 2041 2601 2042 2602 (def expand=list (terms) 2043 2603 `(do ,@(map (fn ((p v)) (expand= p v)) ; [apply expand= _] 2044 2604 (pair terms)))) 2045 2605 2046 2606 (mac = args 2047 2607 (expand=list args)) 2048 2608 2049 2609 (mac loop (start test update . body) 2050 2610 (w/uniq (gfn gparm) 2051 2611 `(do ,start 2052 2612 ((rfn ,gfn (,gparm) 2053 2613 (if ,gparm 2054 2614 (do ,@body ,update (,gfn ,test)))) 2055 2615 ,test)))) 2056 2616 2057 2617 (mac for (v init max . body) 2058 2618 (w/uniq (gi gm) 2059 2619 `(with (,v nil ,gi ,init ,gm (+ ,max 1)) 2060 < (loop (set ,v ,gi) (< ,v ,gm) (set ,v (+ ,v 1)) 2620 > (loop (assign ,v ,gi) (< ,v ,gm) (assign ,v (+ ,v 1)) 2061 2621 ,@body)))) 2062 2622 2623 > (mac down (v init min . body) 2624 > (w/uniq (gi gm) 2625 > `(with (,v nil ,gi ,init ,gm (- ,min 1)) 2626 > (loop (assign ,v ,gi) (> ,v ,gm) (assign ,v (- ,v 1)) 2627 > ,@body)))) 2628 > 2063 2629 (mac repeat (n . body) 2064 2630 `(for ,(uniq) 1 ,n ,@body)) 2065 2631 2066 2632 ; could bind index instead of gensym 2067 2633 2068 2634 (mac each (var expr . body) 2069 < (w/uniq (gseq g) 2635 > (w/uniq (gseq gf gv) 2070 2636 `(let ,gseq ,expr 2071 2637 (if (alist ,gseq) 2072 < ((afn (,g) 2073 < (when (acons ,g) 2074 < (let ,var (car ,g) ,@body) 2075 < (self (cdr ,g)))) 2638 > ((rfn ,gf (,gv) 2639 > (when (acons ,gv) 2640 > (let ,var (car ,gv) ,@body) 2641 > (,gf (cdr ,gv)))) 2076 2642 ,gseq) 2077 2643 (isa ,gseq 'table) 2078 < (maptable (fn (,g ,var) ,@body) 2644 > (maptable (fn ,var ,@body) 2079 2645 ,gseq) 2080 < (for ,g 0 (- (len ,gseq) 1) 2081 < (let ,var (,gseq ,g) ,@body)))))) 2646 > (for ,gv 0 (- (len ,gseq) 1) 2647 > (let ,var (,gseq ,gv) ,@body)))))) 2082 2648 2083 < ; (nthcdr x y) = (subseq y x). 2649 > ; (nthcdr x y) = (cut y x). 2084 2650 2085 < (def subseq (seq start (o end (len seq))) 2086 < (if (isa seq 'string) 2087 < (let s2 (newstring (- end start)) 2088 < (for i 0 (- end start 1) 2089 < (= (s2 i) (seq (+ start i)))) 2090 < s2) 2091 < (firstn (- end start) (nthcdr start seq)))) 2651 > (def cut (seq start (o end)) 2652 > (let end (if (no end) (len seq) 2653 > (< end 0) (+ (len seq) end) 2654 > end) 2655 > (if (isa seq 'string) 2656 > (let s2 (newstring (- end start)) 2657 > (for i 0 (- end start 1) 2658 > (= (s2 i) (seq (+ start i)))) 2659 > s2) 2660 > (firstn (- end start) (nthcdr start seq))))) 2092 2661 2093 < (mac ontable (k v h . body) 2094 < `(maptable (fn (,k ,v) ,@body) ,h)) 2095 < 2096 2662 (mac whilet (var test . body) 2097 2663 (w/uniq (gf gp) 2098 2664 `((rfn ,gf (,gp) 2099 2665 (let ,var ,gp 2100 2666 (when ,var ,@body (,gf ,test)))) 2101 2667 ,test))) 2102 2668 2103 < (def last (seq) 2104 < (if (no (cdr seq)) 2105 < (car seq) 2106 < (last (cdr seq)))) 2669 > (def last (xs) 2670 > (if (cdr xs) 2671 > (last (cdr xs)) 2672 > (car xs))) 2107 2673 2108 2674 (def rem (test seq) 2109 2675 (let f (testify test) 2110 2676 (if (alist seq) 2111 2677 ((afn (s) 2112 2678 (if (no s) nil 2113 2679 (f (car s)) (self (cdr s)) 2114 2680 (cons (car s) (self (cdr s))))) 2115 2681 seq) 2116 2682 (coerce (rem test (coerce seq 'cons)) 'string)))) 2117 2683 2684 > ; Seems like keep doesn't need to testify-- would be better to 2685 > ; be able to use tables as fns. But rem does need to, because 2686 > ; often want to rem a table from a list. So maybe the right answer 2687 > ; is to make keep the more primitive, not rem. 2688 > 2118 2689 (def keep (test seq) 2119 2690 (rem (complement (testify test)) seq)) 2120 2691 2121 < (def trues (f seq) (rem nil (map f seq))) 2692 > ;(def trues (f seq) 2693 > ; (rem nil (map f seq))) 2694 > 2695 > (def trues (f xs) 2696 > (and xs 2697 > (let fx (f (car xs)) 2698 > (if fx 2699 > (cons fx (trues f (cdr xs))) 2700 > (trues f (cdr xs)))))) 2122 2701 2123 2702 (mac do1 args 2124 2703 (w/uniq g 2125 2704 `(let ,g ,(car args) 2126 2705 ,@(cdr args) 2127 2706 ,g))) 2128 2707 2129 2708 ; Would like to write a faster case based on table generated by a macro, 2130 2709 ; but can't insert objects into expansions in Mzscheme. 2131 2710 2132 2711 (mac caselet (var expr . args) 2133 2712 (let ex (afn (args) 2134 2713 (if (no (cdr args)) 2135 2714 (car args) 2136 2715 `(if (is ,var ',(car args)) 2137 2716 ,(cadr args) 2138 2717 ,(self (cddr args))))) 2139 2718 `(let ,var ,expr ,(ex args)))) 2140 2719 2141 2720 (mac case (expr . args) 2142 2721 `(caselet ,(uniq) ,expr ,@args)) 2143 2722 2144 2723 (mac push (x place) 2145 2724 (w/uniq gx 2146 2725 (let (binds val setter) (setforms place) 2147 2726 `(let ,gx ,x 2148 2727 (atwiths ,binds 2149 2728 (,setter (cons ,gx ,val))))))) 2150 2729 2151 2730 (mac swap (place1 place2) 2152 2731 (w/uniq (g1 g2) 2153 2732 (with ((binds1 val1 setter1) (setforms place1) 2154 2733 (binds2 val2 setter2) (setforms place2)) 2155 2734 `(atwiths ,(+ binds1 (list g1 val1) binds2 (list g2 val2)) 2156 2735 (,setter1 ,g2) 2157 2736 (,setter2 ,g1))))) 2158 2737 2159 2738 (mac rotate places 2160 2739 (with (vars (map [uniq] places) 2161 2740 forms (map setforms places)) 2162 2741 `(atwiths ,(mappend (fn (g (binds val setter)) 2163 2742 (+ binds (list g val))) 2164 2743 vars 2165 2744 forms) 2166 2745 ,@(map (fn (g (binds val setter)) 2167 2746 (list setter g)) 2168 2747 (+ (cdr vars) (list (car vars))) 2169 2748 forms)))) 2170 2749 2171 2750 (mac pop (place) 2172 2751 (w/uniq g 2173 2752 (let (binds val setter) (setforms place) 2174 2753 `(atwiths ,(+ binds (list g val)) 2175 2754 (do1 (car ,g) 2176 2755 (,setter (cdr ,g))))))) 2177 2756 2178 2757 (def adjoin (x xs (o test iso)) 2179 2758 (if (some [test x _] xs) 2180 2759 xs 2181 2760 (cons x xs))) 2182 2761 2183 2762 (mac pushnew (x place . args) 2184 2763 (w/uniq gx 2185 2764 (let (binds val setter) (setforms place) 2186 2765 `(atwiths ,(+ (list gx x) binds) 2187 2766 (,setter (adjoin ,gx ,val ,@args)))))) 2188 2767 2189 2768 (mac pull (test place) 2190 2769 (w/uniq g 2191 2770 (let (binds val setter) (setforms place) 2192 2771 `(atwiths ,(+ (list g test) binds) 2193 2772 (,setter (rem ,g ,val)))))) 2194 2773 2774 > (mac togglemem (x place . args) 2775 > (w/uniq gx 2776 > (let (binds val setter) (setforms place) 2777 > `(atwiths ,(+ (list gx x) binds) 2778 > (,setter (if (mem ,gx ,val) 2779 > (rem ,gx ,val) 2780 > (adjoin ,gx ,val ,@args))))))) 2781 > 2195 2782 (mac ++ (place (o i 1)) 2196 2783 (if (isa place 'sym) 2197 2784 `(= ,place (+ ,place ,i)) 2198 2785 (w/uniq gi 2199 2786 (let (binds val setter) (setforms place) 2200 2787 `(atwiths ,(+ binds (list gi i)) 2201 2788 (,setter (+ ,val ,gi))))))) 2202 2789 2203 2790 (mac -- (place (o i 1)) 2204 2791 (if (isa place 'sym) 2205 2792 `(= ,place (- ,place ,i)) 2206 2793 (w/uniq gi 2207 2794 (let (binds val setter) (setforms place) 2208 2795 `(atwiths ,(+ binds (list gi i)) 2209 2796 (,setter (- ,val ,gi))))))) 2210 2797 2211 < ; E.g. (inc x) equiv to (zap + x 1) 2798 > ; E.g. (++ x) equiv to (zap + x 1) 2212 2799 2213 2800 (mac zap (op place . args) 2214 2801 (with (gop (uniq) 2215 2802 gargs (map [uniq] args) 2216 2803 mix (afn seqs 2217 2804 (if (some no seqs) 2218 2805 nil 2219 2806 (+ (map car seqs) 2220 2807 (apply self (map cdr seqs)))))) 2221 2808 (let (binds val setter) (setforms place) 2222 2809 `(atwiths ,(+ binds (list gop op) (mix gargs args)) 2223 2810 (,setter (,gop ,val ,@gargs)))))) 2224 2811 2225 2812 ; Can't simply mod pr to print strings represented as lists of chars, 2226 2813 ; because empty string will get printed as nil. Would need to rep strings 2227 2814 ; as lists of chars annotated with 'string, and modify car and cdr to get 2228 2815 ; the rep of these. That would also require hacking the reader. 2229 2816 2230 < ;(def pr args 2231 < ; (if (isa (car args) 'output) 2232 < ; (do (error "stream arg!" args) 2233 < ; (map1 [disp _ (car args)] (cdr args)) 2234 < ; (cadr args)) 2235 < ; (do (map1 disp args) 2236 < ; (car args)))) 2237 < 2238 2817 (def pr args 2239 2818 (map1 disp args) 2240 2819 (car args)) 2241 2820 2242 < ; Rtm says this version should make the server 20% faster because map1 2243 < ; generates so much garbage; in fact makes slower; maybe rewrite map1? 2244 < 2245 < ;(def newpr args 2246 < ; (if (isa (car args) 'output) 2247 < ; (do (each a (cdr args) (disp a (car args))) 2248 < ; (cadr args)) 2249 < ; (do (each a args (disp a)) 2250 < ; (car args)))) 2821 > (def prt args 2822 > (map1 [if _ (disp _)] args) 2823 > (car args)) 2251 2824 2252 2825 (def prn args 2253 2826 (do1 (apply pr args) 2254 < (writec #\newline 2255 < (if (isa (car args) 'output) (car args) (stdout))))) 2827 > (writec #\newline))) 2256 2828 2257 < (mac nil! args 2829 > (mac wipe args 2258 2830 `(do ,@(map (fn (a) `(= ,a nil)) args))) 2259 2831 2260 < (mac t! args 2832 > (mac set args 2261 2833 `(do ,@(map (fn (a) `(= ,a t)) args))) 2262 2834 2263 < ; Destructing means ambiguity: are pat vars bound in else? (no) 2835 > ; Destructuring means ambiguity: are pat vars bound in else? (no) 2264 2836 2265 2837 (mac iflet (var expr then . rest) 2266 2838 (w/uniq gv 2267 2839 `(let ,gv ,expr 2268 2840 (if ,gv (let ,var ,gv ,then) ,@rest)))) 2269 2841 2270 2842 (mac whenlet (var expr . body) 2271 2843 `(iflet ,var ,expr (do ,@body))) 2272 2844 2273 2845 (mac aif (expr . body) 2274 < `(let it ,expr (if it ,@body))) 2846 > `(let it ,expr 2847 > (if it 2848 > ,@(if (cddr body) 2849 > `(,(car body) (aif ,@(cdr body))) 2850 > body)))) 2275 2851 2276 2852 (mac awhen (expr . body) 2277 2853 `(let it ,expr (if it (do ,@body)))) 2278 2854 2279 2855 (mac aand args 2280 2856 (if (no args) 2281 2857 't 2282 2858 (no (cdr args)) 2283 2859 (car args) 2284 2860 `(let it ,(car args) (and it (aand ,@(cdr args)))))) 2285 2861 2286 2862 (mac accum (accfn . body) 2287 2863 (w/uniq gacc 2288 2864 `(withs (,gacc nil ,accfn [push _ ,gacc]) 2289 2865 ,@body 2290 < ,gacc))) 2866 > (rev ,gacc)))) 2291 2867 2292 2868 ; Repeatedly evaluates its body till it returns nil, then returns vals. 2293 2869 2294 2870 (mac drain (expr (o eof nil)) 2295 2871 (w/uniq (gacc gdone gres) 2296 2872 `(with (,gacc nil ,gdone nil) 2297 2873 (while (no ,gdone) 2298 2874 (let ,gres ,expr 2299 2875 (if (is ,gres ,eof) 2300 2876 (= ,gdone t) 2301 2877 (push ,gres ,gacc)))) 2302 2878 (rev ,gacc)))) 2303 2879 2304 2880 ; For the common C idiom while x = snarfdata != stopval. 2305 2881 ; Rename this if use it often. 2306 2882 2307 2883 (mac whiler (var expr endval . body) 2308 2884 (w/uniq gf 2309 < `((rfn ,gf (,var) 2310 < (when (and ,var (no (is ,var ,endval))) 2311 < ,@body 2312 < (,gf ,expr))) 2313 < ,expr))) 2885 > `(withs (,var nil ,gf (testify ,endval)) 2886 > (while (no (,gf (= ,var ,expr))) 2887 > ,@body)))) 2314 2888 2315 2889 ;(def macex (e) 2316 2890 ; (if (atom e) 2317 2891 ; e 2318 2892 ; (let op (and (atom (car e)) (eval (car e))) 2319 2893 ; (if (isa op 'mac) 2320 2894 ; (apply (rep op) (cdr e)) 2321 2895 ; e)))) 2322 2896 2323 2897 (def consif (x y) (if x (cons x y) y)) 2324 2898 2325 2899 (def string args 2326 2900 (apply + "" (map [coerce _ 'string] args))) 2327 2901 2328 < (def flat (x (o stringstoo)) 2329 < ((rfn f (x acc) 2330 < (if (or (no x) (and stringstoo (is x ""))) 2331 < acc 2332 < (and (atom x) (no (and stringstoo (isa x 'string)))) 2333 < (cons x acc) 2334 < (f (car x) (f (cdr x) acc)))) 2902 > (def flat x 2903 > ((afn (x acc) 2904 > (if (no x) acc 2905 > (atom x) (cons x acc) 2906 > (self (car x) (self (cdr x) acc)))) 2335 2907 x nil)) 2336 2908 2337 < ; Perhaps not the final idea, or at least final name 2338 < 2339 < (mac default (x test alt) 2909 > (mac check (x test (o alt)) 2340 2910 (w/uniq gx 2341 2911 `(let ,gx ,x 2342 2912 (if (,test ,gx) ,gx ,alt)))) 2343 2913 2344 2914 (def pos (test seq (o start 0)) 2345 2915 (let f (testify test) 2346 2916 (if (alist seq) 2347 2917 ((afn (seq n) 2348 2918 (if (no seq) 2349 2919 nil 2350 2920 (f (car seq)) 2351 2921 n 2352 2922 (self (cdr seq) (+ n 1)))) 2353 2923 (nthcdr start seq) 2354 2924 start) 2355 2925 (recstring [if (f (seq _)) _] seq start)))) 2356 2926 2357 2927 (def even (n) (is (mod n 2) 0)) 2358 2928 2359 2929 (def odd (n) (no (even n))) 2360 2930 2361 2931 (mac after (x . ys) 2362 2932 `(protect (fn () ,x) (fn () ,@ys))) 2363 2933 2364 2934 (let expander 2365 2935 (fn (f var name body) 2366 2936 `(let ,var (,f ,name) 2367 2937 (after (do ,@body) (close ,var)))) 2368 2938 2369 2939 (mac w/infile (var name . body) 2370 2940 (expander 'infile var name body)) 2371 2941 2372 2942 (mac w/outfile (var name . body) 2373 2943 (expander 'outfile var name body)) 2374 2944 2375 2945 (mac w/instring (var str . body) 2376 2946 (expander 'instring var str body)) 2947 > 2948 > (mac w/socket (var port . body) 2949 > (expander 'open-socket var port body)) 2377 2950 ) 2378 2951 2379 2952 (mac w/outstring (var . body) 2380 2953 `(let ,var (outstring) ,@body)) 2954 > 2955 > ; what happens to a file opened for append if arc is killed in 2956 > ; the middle of a write? 2381 2957 2382 2958 (mac w/appendfile (var name . body) 2383 2959 `(let ,var (outfile ,name 'append) 2384 2960 (after (do ,@body) (close ,var)))) 2385 2961 2386 2962 ; rename this simply "to"? - prob not; rarely use 2387 2963 2388 2964 (mac w/stdout (str . body) 2389 2965 `(call-w/stdout ,str (fn () ,@body))) 2390 2966 2391 2967 (mac w/stdin (str . body) 2392 2968 `(call-w/stdin ,str (fn () ,@body))) 2393 2969 2394 2970 (mac tostring body 2395 2971 (w/uniq gv 2396 2972 `(w/outstring ,gv 2397 2973 (w/stdout ,gv ,@body) 2398 2974 (inside ,gv)))) 2399 2975 2400 2976 (mac fromstring (str . body) 2401 2977 (w/uniq gv 2402 2978 `(w/instring ,gv ,str 2403 2979 (w/stdin ,gv ,@body)))) 2404 2980 2405 2981 (def readstring1 (s (o eof nil)) (w/instring i s (read i eof))) 2406 2982 2407 2983 (def read ((o x (stdin)) (o eof nil)) 2408 2984 (if (isa x 'string) (readstring1 x eof) (sread x eof))) 2409 2985 2986 > ; inconsistency between names of readfile[1] and writefile 2987 > 2410 2988 (def readfile (name) (w/infile s name (drain (read s)))) 2411 2989 2412 2990 (def readfile1 (name) (w/infile s name (read s))) 2413 2991 2414 < (def writefile1 (val name) (w/outfile s name (write val s)) val) 2415 < 2416 2992 (def readall (src (o eof nil)) 2417 2993 ((afn (i) 2418 2994 (let x (read i eof) 2419 2995 (if (is x eof) 2420 2996 nil 2421 2997 (cons x (self i))))) 2422 2998 (if (isa src 'string) (instring src) src))) 2423 2999 3000 > (def allchars (str) 3001 > (tostring (whiler c (readc str nil) no 3002 > (writec c)))) 3003 > 3004 > (def filechars (name) 3005 > (w/infile s name (allchars s))) 3006 > 3007 > (def writefile (val file) 3008 > (let tmpfile (+ file ".tmp") 3009 > (w/outfile o tmpfile (write val o)) 3010 > (mvfile tmpfile file)) 3011 > val) 3012 > 2424 3013 (def sym (x) (coerce x 'sym)) 3014 > 3015 > (def int (x (o b 10)) (coerce x 'int b)) 2425 3016 2426 3017 (mac rand-choice exprs 2427 3018 `(case (rand ,(len exprs)) 2428 3019 ,@(let key -1 2429 3020 (mappend [list (++ key) _] 2430 3021 exprs)))) 2431 3022 2432 3023 (mac n-of (n expr) 2433 3024 (w/uniq ga 2434 3025 `(let ,ga nil 2435 3026 (repeat ,n (push ,expr ,ga)) 2436 3027 (rev ,ga)))) 2437 3028 3029 > ; rejects bytes >= 248 lest digits be overrepresented 3030 > 2438 3031 (def rand-string (n) 2439 < (with (cap (fn () (+ 65 (rand 26))) 2440 < sm (fn () (+ 97 (rand 26))) 2441 < dig (fn () (+ 48 (rand 10)))) 2442 < (coerce (map [coerce _ 'char] 2443 < (cons (rand-choice (cap) (sm)) 2444 < (n-of (- n 1) (rand-choice (cap) (sm) (dig))))) 2445 < 'string))) 3032 > (let c "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 3033 > (with (nc 62 s (newstring n) i 0) 3034 > (w/infile str "/dev/urandom" 3035 > (while (< i n) 3036 > (let x (readb str) 3037 > (unless (> x 247) 3038 > (= (s i) (c (mod x nc))) 3039 > (++ i))))) 3040 > s))) 2446 3041 2447 3042 (mac forlen (var s . body) 2448 3043 `(for ,var 0 (- (len ,s) 1) ,@body)) 2449 3044 2450 3045 (mac on (var s . body) 2451 3046 (if (is var 'index) 2452 3047 (err "Can't use index as first arg to on.") 2453 3048 (w/uniq gs 2454 3049 `(let ,gs ,s 2455 3050 (forlen index ,gs 2456 3051 (let ,var (,gs index) 2457 3052 ,@body)))))) 2458 3053 2459 3054 (def best (f seq) 2460 3055 (if (no seq) 2461 3056 nil 2462 3057 (let wins (car seq) 2463 3058 (each elt (cdr seq) 2464 3059 (if (f elt wins) (= wins elt))) 2465 3060 wins))) 2466 3061 2467 3062 (def max args (best > args)) 2468 3063 (def min args (best < args)) 2469 3064 2470 3065 ; (mac max2 (x y) 2471 3066 ; (w/uniq (a b) 2472 3067 ; `(with (,a ,x ,b ,y) (if (> ,a ,b) ,a ,b)))) 2473 3068 2474 3069 (def most (f seq) 2475 3070 (unless (no seq) 2476 3071 (withs (wins (car seq) topscore (f wins)) 2477 3072 (each elt (cdr seq) 2478 3073 (let score (f elt) 2479 3074 (if (> score topscore) (= wins elt topscore score)))) 2480 3075 wins))) 2481 3076 2482 3077 ; Insert so that list remains sorted. Don't really want to expose 2483 3078 ; these but seem to have to because can't include a fn obj in a 2484 3079 ; macroexpansion. 2485 3080 2486 3081 (def insert-sorted (test elt seq) 2487 3082 (if (no seq) 2488 3083 (list elt) 2489 3084 (test elt (car seq)) 2490 3085 (cons elt seq) 2491 3086 (cons (car seq) (insert-sorted test elt (cdr seq))))) 2492 3087 2493 3088 (mac insort (test elt seq) 2494 3089 `(zap [insert-sorted ,test ,elt _] ,seq)) 2495 3090 2496 3091 (def reinsert-sorted (test elt seq) 2497 3092 (if (no seq) 2498 3093 (list elt) 2499 3094 (is elt (car seq)) 2500 3095 (reinsert-sorted test elt (cdr seq)) 2501 3096 (test elt (car seq)) 2502 3097 (cons elt (rem elt seq)) 2503 3098 (cons (car seq) (reinsert-sorted test elt (cdr seq))))) 2504 3099 2505 3100 (mac insortnew (test elt seq) 2506 3101 `(zap [reinsert-sorted ,test ,elt _] ,seq)) 2507 3102 2508 3103 ; Could make this look at the sig of f and return a fn that took the 2509 3104 ; right no of args and didn't have to call apply (or list if 1 arg). 2510 3105 2511 3106 (def memo (f) 2512 < (let cache (table) 3107 > (with (cache (table) nilcache (table)) 2513 3108 (fn args 2514 3109 (or (cache args) 2515 < (= (cache args) (apply f args)))))) 3110 > (and (no (nilcache args)) 3111 > (aif (apply f args) 3112 > (= (cache args) it) 3113 > (do (set (nilcache args)) 3114 > nil))))))) 3115 > 2516 3116 2517 3117 (mac defmemo (name parms . body) 2518 3118 `(safeset ,name (memo (fn ,parms ,@body)))) 2519 3119 2520 3120 (def <= args 2521 < (or (no args) 3121 > (or (no args) 2522 3122 (no (cdr args)) 2523 3123 (and (no (> (car args) (cadr args))) 2524 3124 (apply <= (cdr args))))) 2525 3125 2526 3126 (def >= args 2527 < (or (no args) 3127 > (or (no args) 2528 3128 (no (cdr args)) 2529 3129 (and (no (< (car args) (cadr args))) 2530 3130 (apply >= (cdr args))))) 2531 < 3131 > 2532 3132 (def whitec (c) 2533 3133 (in c #\space #\newline #\tab #\return)) 2534 3134 2535 3135 (def nonwhite (c) (no (whitec c))) 2536 3136 2537 < (def alphadig (c) 2538 < (or (<= #\a c #\z) (<= #\A c #\Z) (<= #\0 c #\9))) 3137 > (def letter (c) (or (<= #\a c #\z) (<= #\A c #\Z))) 3138 > 3139 > (def digit (c) (<= #\0 c #\9)) 3140 > 3141 > (def alphadig (c) (or (letter c) (digit c))) 2539 3142 2540 3143 (def punc (c) 2541 3144 (in c #\. #\, #\; #\: #\! #\?)) 2542 3145 2543 3146 (def readline ((o str (stdin))) 2544 3147 (awhen (readc str) 2545 3148 (tostring 2546 3149 (writec it) 2547 < (whiler c (readc str) #\newline 3150 > (whiler c (readc str) [in _ nil #\newline] 2548 3151 (writec c))))) 2549 3152 2550 3153 ; Don't currently use this but suspect some code could. 2551 3154 2552 3155 (mac summing (sumfn . body) 2553 3156 (w/uniq (gc gt) 2554 3157 `(let ,gc 0 2555 3158 (let ,sumfn (fn (,gt) (if ,gt (++ ,gc))) 2556 3159 ,@body) 2557 3160 ,gc))) 2558 3161 2559 < (def trav (f base tree) 3162 > (def sum (f xs) 3163 > (let n 0 3164 > (each x xs (++ n (f x))) 3165 > n)) 3166 > 3167 > (def treewise (f base tree) 2560 3168 (if (atom tree) 2561 3169 (base tree) 2562 < (f (trav f base (car tree)) (trav f base (cdr tree))))) 3170 > (f (treewise f base (car tree)) 3171 > (treewise f base (cdr tree))))) 2563 3172 2564 3173 (def carif (x) (if (atom x) x (car x))) 2565 3174 2566 3175 ; Could prob be generalized beyond printing. 2567 3176 2568 3177 (def prall (elts (o init "") (o sep ", ")) 2569 3178 (when elts 2570 3179 (pr init (car elts)) 2571 3180 (map [pr sep _] (cdr elts)) 2572 3181 elts)) 2573 3182 2574 3183 (def prs args 2575 3184 (prall args "" #\space)) 2576 3185 2577 3186 (def tree-subst (old new tree) 2578 3187 (if (is tree old) 2579 3188 new 2580 3189 (atom tree) 2581 3190 tree 2582 3191 (cons (tree-subst old new (car tree)) 2583 3192 (tree-subst old new (cdr tree))))) 2584 3193 2585 3194 (def ontree (f tree) 2586 3195 (f tree) 2587 3196 (unless (atom tree) 2588 3197 (ontree f (car tree)) 2589 3198 (ontree f (cdr tree)))) 2590 3199 2591 3200 (def dotted (x) 2592 3201 (if (atom x) 2593 3202 nil 2594 3203 (and (cdr x) (or (atom (cdr x)) 2595 3204 (dotted (cdr x)))))) 2596 3205 2597 3206 (def fill-table (table data) 2598 3207 (each (k v) (pair data) (= (table k) v)) 2599 3208 table) 2600 3209 2601 < (mac obj args 2602 < (w/uniq g 2603 < `(let ,g (table) 2604 < ,@(map (fn ((k v)) `(= (,g ',k) ,v)) 2605 < (pair args)) 2606 < ,g))) 2607 < 2608 3210 (def keys (h) 2609 < (accum a (ontable k v h (a k)))) 3211 > (accum a (each (k v) h (a k)))) 2610 3212 2611 3213 (def vals (h) 2612 < (accum a (ontable k v h (a v)))) 3214 > (accum a (each (k v) h (a v)))) 2613 3215 2614 3216 ; These two should really be done by coerce. Wrap coerce? 2615 3217 2616 3218 (def tablist (h) 2617 3219 (accum a (maptable (fn args (a args)) h))) 2618 3220 2619 3221 (def listtab (al) 2620 3222 (let h (table) 2621 3223 (map (fn ((k v)) (= (h k) v)) 2622 3224 al) 2623 3225 h)) 3226 > 3227 > (mac obj args 3228 > `(listtab (list ,@(map (fn ((k v)) 3229 > `(list ',k ,v)) 3230 > (pair args))))) 2624 3231 2625 3232 (def load-table (file (o eof)) 2626 3233 (w/infile i file (read-table i eof))) 2627 3234 2628 3235 (def read-table ((o i (stdin)) (o eof)) 2629 3236 (let e (read i eof) 2630 3237 (if (alist e) (listtab e) e))) 2631 3238 2632 3239 (def load-tables (file) 2633 3240 (w/infile i file 2634 3241 (w/uniq eof 2635 3242 (drain (read-table i eof) eof)))) 2636 3243 2637 3244 (def save-table (h file) 2638 < (w/outfile o file (write-table h o))) 3245 > (writefile (tablist h) file)) 2639 3246 2640 3247 (def write-table (h (o o (stdout))) 2641 3248 (write (tablist h) o)) 2642 3249 2643 < (def copy (x) 2644 < (case (type x) 2645 < sym x 2646 < cons (apply (fn args args) x) 2647 < string (let new (newstring (len x)) 2648 < (forlen i x 2649 < (= (new i) (x i))) 2650 < new) 2651 < table (let new (table) 2652 < (ontable k v x 2653 < (= (new k) v)) 2654 < new) 2655 < (err "Can't copy " x))) 3250 > (def copy (x . args) 3251 > (let x2 (case (type x) 3252 > sym x 3253 > cons (copylist x) ; (apply (fn args args) x) 3254 > string (let new (newstring (len x)) 3255 > (forlen i x 3256 > (= (new i) (x i))) 3257 > new) 3258 > table (let new (table) 3259 > (each (k v) x 3260 > (= (new k) v)) 3261 > new) 3262 > (err "Can't copy " x)) 3263 > (map (fn ((k v)) (= (x2 k) v)) 3264 > (pair args)) 3265 > x2)) 2656 3266 2657 3267 (def abs (n) 2658 3268 (if (< n 0) (- n) n)) 2659 3269 2660 3270 ; The problem with returning a list instead of multiple values is that 2661 3271 ; you can't act as if the fn didn't return multiple vals in cases where 2662 3272 ; you only want the first. Not a big problem. 2663 3273 2664 3274 (def round (n) 2665 < (withs (base (truncate n) rem (abs (- n base))) 3275 > (withs (base (trunc n) rem (abs (- n base))) 2666 3276 (if (> rem 1/2) ((if (> n 0) + -) base 1) 2667 3277 (< rem 1/2) base 2668 3278 (odd base) ((if (> n 0) + -) base 1) 2669 3279 base))) 2670 3280 2671 3281 (def roundup (n) 2672 < (withs (base (truncate n) rem (abs (- n base))) 3282 > (withs (base (trunc n) rem (abs (- n base))) 2673 3283 (if (>= rem 1/2) 2674 3284 ((if (> n 0) + -) base 1) 2675 3285 base))) 2676 3286 2677 < (def to-nearest (n quantum) 3287 > (def nearest (n quantum) 2678 3288 (* (roundup (/ n quantum)) quantum)) 2679 3289 2680 3290 (def avg (ns) (/ (apply + ns) (len ns))) 3291 > 3292 > (def med (ns (o test >)) 3293 > ((sort test ns) (round (/ (len ns) 2)))) 2681 3294 2682 3295 ; Use mergesort on assumption that mostly sorting mostly sorted lists 2683 3296 ; benchmark: (let td (n-of 10000 (rand 100)) (time (sort < td)) 1) 2684 3297 2685 3298 (def sort (test seq) 2686 3299 (if (alist seq) 2687 3300 (mergesort test (copy seq)) 2688 3301 (coerce (mergesort test (coerce seq 'cons)) (type seq)))) 2689 3302 2690 3303 ; Destructive stable merge-sort, adapted from slib and improved 2691 3304 ; by Eli Barzilay for MzLib; re-written in Arc. 2692 3305 2693 3306 (def mergesort (less? lst) 2694 3307 (with (n (len lst)) 2695 3308 (if (<= n 1) lst 2696 3309 ; ; check if the list is already sorted 2697 3310 ; ; (which can be a common case, eg, directory lists). 2698 3311 ; (let loop ([last (car lst)] [next (cdr lst)]) 2699 3312 ; (or (null? next) 2700 3313 ; (and (not (less? (car next) last)) 2701 3314 ; (loop (car next) (cdr next))))) 2702 3315 ; lst 2703 3316 ((afn (n) 2704 3317 (if (> n 2) 2705 3318 ; needs to evaluate L->R 2706 3319 (withs (j (/ (if (even n) n (- n 1)) 2) ; faster than round 2707 3320 a (self j) 2708 3321 b (self (- n j))) 2709 3322 (merge less? a b)) 2710 3323 ; the following case just inlines the length 2 case, 2711 3324 ; it can be removed (and use the above case for n>1) 2712 3325 ; and the code still works, except a little slower 2713 3326 (is n 2) 2714 3327 (with (x (car lst) y (cadr lst) p lst) 2715 3328 (= lst (cddr lst)) 2716 3329 (when (less? y x) (scar p y) (scar (cdr p) x)) 2717 3330 (scdr (cdr p) nil) 2718 3331 p) 2719 3332 (is n 1) 2720 3333 (with (p lst) 2721 3334 (= lst (cdr lst)) 2722 3335 (scdr p nil) 2723 3336 p) 2724 3337 nil)) 2725 3338 n)))) 2726 3339 2727 3340 ; Also by Eli. 2728 3341 2729 3342 (def merge (less? x y) 2730 3343 (if (no x) y 2731 3344 (no y) x 2732 3345 (let lup nil 2733 < (set lup 2734 < (fn (r x y r-x?) ; r-x? for optimization -- is r connected to x? 2735 < (if (less? (car y) (car x)) 2736 < (do (if r-x? (scdr r y)) 2737 < (if (cdr y) (lup y x (cdr y) nil) (scdr y x))) 2738 < ; (car x) <= (car y) 2739 < (do (if (no r-x?) (scdr r x)) 2740 < (if (cdr x) (lup x (cdr x) y t) (scdr x y)))))) 3346 > (assign lup 3347 > (fn (r x y r-x?) ; r-x? for optimization -- is r connected to x? 3348 > (if (less? (car y) (car x)) 3349 > (do (if r-x? (scdr r y)) 3350 > (if (cdr y) (lup y x (cdr y) nil) (scdr y x))) 3351 > ; (car x) <= (car y) 3352 > (do (if (no r-x?) (scdr r x)) 3353 > (if (cdr x) (lup x (cdr x) y t) (scdr x y)))))) 2741 3354 (if (less? (car y) (car x)) 2742 3355 (do (if (cdr y) (lup y x (cdr y) nil) (scdr y x)) 2743 3356 y) 2744 3357 ; (car x) <= (car y) 2745 3358 (do (if (cdr x) (lup x (cdr x) y t) (scdr x y)) 2746 3359 x))))) 2747 3360 2748 3361 (def bestn (n f seq) 2749 3362 (firstn n (sort f seq))) 2750 3363 2751 3364 (def split (seq pos) 2752 < (withs (mid (nthcdr (- pos 1) seq) 2753 < s2 (cdr mid)) 2754 < (nil! (cdr mid)) 2755 < (list seq s2))) 3365 > (list (cut seq 0 pos) (cut seq pos))) 2756 3366 2757 3367 (mac time (expr) 2758 3368 (w/uniq (t1 t2) 2759 3369 `(let ,t1 (msec) 2760 3370 (do1 ,expr 2761 3371 (let ,t2 (msec) 2762 3372 (prn "time: " (- ,t2 ,t1) " msec.")))))) 2763 3373 2764 3374 (mac jtime (expr) 2765 3375 `(do1 'ok (time ,expr))) 2766 3376 2767 3377 (mac time10 (expr) 2768 3378 `(time (repeat 10 ,expr))) 2769 3379 3380 > (def union (f xs ys) 3381 > (+ xs (rem (fn (y) (some [f _ y] xs)) 3382 > ys))) 3383 > 2770 3384 (= templates* (table)) 2771 3385 2772 < (def maps (fn . args) 2773 < (apply join (apply map fn args))) 2774 < 2775 3386 (mac deftem (tem . fields) 2776 3387 (withs (name (carif tem) includes (if (acons tem) (cdr tem))) 2777 3388 `(= (templates* ',name) 2778 < (+ (maps templates* ',(rev includes)) 3389 > (+ (mappend templates* ',(rev includes)) 2779 3390 (list ,@(map (fn ((k v)) `(list ',k (fn () ,v))) 2780 3391 (pair fields))))))) 2781 3392 3393 > (mac addtem (name . fields) 3394 > `(= (templates* ',name) 3395 > (union (fn (x y) (is (car x) (car y))) 3396 > (list ,@(map (fn ((k v)) `(list ',k (fn () ,v))) 3397 > (pair fields))) 3398 > (templates* ',name)))) 3399 > 2782 3400 (def inst (tem . args) 2783 3401 (let x (table) 2784 < (each (k v) (templates* tem) 3402 > (each (k v) (if (acons tem) tem (templates* tem)) 2785 3403 (unless (no v) (= (x k) (v)))) 2786 3404 (each (k v) (pair args) 2787 3405 (= (x k) v)) 2788 3406 x)) 2789 3407 2790 3408 ; To write something to be read by temread, (write (tablist x)) 2791 3409 2792 3410 (def temread (tem (o str (stdin))) 2793 3411 (templatize tem (read str))) 2794 3412 2795 3413 ; Converts alist to inst; ugly; maybe should make this part of coerce. 2796 3414 ; Note: discards fields not defined by the template. 2797 3415 2798 3416 (def templatize (tem raw) 2799 < (with (x (inst tem) fields (templates* tem)) 3417 > (with (x (inst tem) fields (if (acons tem) tem (templates* tem))) 2800 3418 (each (k v) raw 2801 3419 (when (assoc k fields) 2802 3420 (= (x k) v))) 2803 3421 x)) 2804 3422 2805 3423 (def temload (tem file) 2806 3424 (w/infile i file (temread tem i))) 2807 3425 2808 3426 (def temloadall (tem file) 2809 3427 (map (fn (pairs) (templatize tem pairs)) 2810 3428 (w/infile in file (readall in)))) 2811 3429 2812 3430 2813 3431 (def number (n) (in (type n) 'int 'num)) 2814 3432 3433 > (def since (t1) (- (seconds) t1)) 3434 > 3435 > (def minutes-since (t1) (/ (since t1) 60)) 3436 > (def hours-since (t1) (/ (since t1) 3600)) 3437 > (def days-since (t1) (/ (since t1) 86400)) 3438 > 3439 > ; could use a version for fns of 1 arg at least 3440 > 2815 3441 (def cache (timef valf) 2816 3442 (with (cached nil gentime nil) 2817 3443 (fn () 2818 < (unless (and cached (< (- (seconds) gentime) (timef))) 3444 > (unless (and cached (< (since gentime) (timef))) 2819 3445 (= cached (valf) 2820 3446 gentime (seconds))) 2821 3447 cached))) 2822 3448 3449 > (mac defcache (name lasts . body) 3450 > `(safeset ,name (cache (fn () ,lasts) 3451 > (fn () ,@body)))) 3452 > 2823 3453 (mac errsafe (expr) 2824 3454 `(on-err (fn (c) nil) 2825 3455 (fn () ,expr))) 2826 3456 2827 < (def saferead (arg) (errsafe (read arg))) 3457 > (def saferead (arg) (errsafe:read arg)) 2828 3458 2829 3459 (def safe-load-table (filename) 2830 < (or (errsafe (load-table filename)) 3460 > (or (errsafe:load-table filename) 2831 3461 (table))) 2832 3462 2833 3463 (def ensure-dir (path) 2834 3464 (unless (dir-exists path) 2835 < (system (string "mkdir " path)))) 3465 > (system (string "mkdir -p " path)))) 2836 3466 2837 < (def date ((o time (seconds))) 2838 < (let val (tostring (system (string "date -u -r " time " \"+%Y-%m-%d\""))) 2839 < (subseq val 0 (- (len val) 1)))) 3467 > (def date ((o s (seconds))) 3468 > (rev (nthcdr 3 (timedate s)))) 2840 3469 2841 < (def since (t1) (- (seconds) t1)) 3470 > (def datestring ((o s (seconds))) 3471 > (let (y m d) (date s) 3472 > (string y "-" (if (< m 10) "0") m "-" (if (< d 10) "0") d))) 2842 3473 2843 3474 (def count (test x) 2844 3475 (with (n 0 testf (testify test)) 2845 3476 (each elt x 2846 3477 (if (testf elt) (++ n))) 2847 3478 n)) 2848 3479 2849 3480 (def ellipsize (str (o limit 80)) 2850 3481 (if (<= (len str) limit) 2851 3482 str 2852 < (+ (subseq str 0 limit) "..."))) 3483 > (+ (cut str 0 limit) "..."))) 2853 3484 2854 < (def random-elt (seq) (seq (rand (len seq)))) 3485 > (def rand-elt (seq) 3486 > (seq (rand (len seq)))) 2855 3487 2856 3488 (mac until (test . body) 2857 3489 `(while (no ,test) ,@body)) 2858 3490 2859 3491 (def before (x y seq (o i 0)) 2860 3492 (with (xp (pos x seq i) yp (pos y seq i)) 2861 3493 (and xp (or (no yp) (< xp yp))))) 2862 3494 2863 3495 (def orf fns 2864 < (fn (x) (some [_ x] fns))) 3496 > (fn args 3497 > ((afn (fs) 3498 > (and fs (or (apply (car fs) args) (self (cdr fs))))) 3499 > fns))) 2865 3500 2866 3501 (def andf fns 2867 < (fn (x) (all [_ x] fns))) 3502 > (fn args 3503 > ((afn (fs) 3504 > (if (no fs) t 3505 > (no (cdr fs)) (apply (car fs) args) 3506 > (and (apply (car fs) args) (self (cdr fs))))) 3507 > fns))) 2868 3508 2869 3509 (def atend (i s) 2870 < (>= i (- (len s) 1))) 3510 > (> i (- (len s) 2))) 2871 3511 2872 3512 (def multiple (x y) 2873 3513 (is 0 (mod x y))) 2874 3514 2875 3515 (mac nor args `(no (or ,@args))) 2876 3516 2877 3517 ; Consider making the default sort fn take compare's two args (when do 2878 3518 ; you ever have to sort mere lists of numbers?) and rename current sort 2879 3519 ; as prim-sort or something. 2880 3520 2881 3521 ; Could simply modify e.g. > so that (> len) returned the same thing 2882 3522 ; as (compare > len). 2883 3523 2884 3524 (def compare (comparer scorer) 2885 3525 (fn (x y) (comparer (scorer x) (scorer y)))) 2886 3526 2887 3527 ; Cleaner thus, but may only ever need in 2 arg case. 2888 3528 2889 3529 ;(def compare (comparer scorer) 2890 3530 ; (fn args (apply comparer map scorer args))) 2891 3531 2892 < (def only (f g . args) 2893 < (aif (apply g args) (f it))) 3532 > ; (def only (f g . args) (aif (apply g args) (f it))) 3533 > 3534 > (def only (f) 3535 > (fn args (if (car args) (apply f args)))) 2894 3536 2895 3537 (mac conswhen (f x y) 2896 3538 (w/uniq (gf gx) 2897 3539 `(with (,gf ,f ,gx ,x) 2898 3540 (if (,gf ,gx) (cons ,gx ,y) ,y)))) 2899 3541 2900 < ; Could rename this get, but don't unless it's frequently used. 3542 > ; Could combine with firstn if put f arg last, default to (fn (x) t). 2901 3543 2902 < (def firstn-that (n f xs) 2903 < (if (or (<= n 0) (no xs)) 2904 < nil 2905 < (f (car xs)) 2906 < (cons (car xs) (firstn-that (- n 1) f (cdr xs))) 2907 < (firstn-that n f (cdr xs)))) 3544 > (def retrieve (n f xs) 3545 > (if (no n) (keep f xs) 3546 > (or (<= n 0) (no xs)) nil 3547 > (f (car xs)) (cons (car xs) (retrieve (- n 1) f (cdr xs))) 3548 > (retrieve n f (cdr xs)))) 2908 3549 2909 3550 (def dedup (xs) 2910 3551 (with (h (table) acc nil) 2911 3552 (each x xs 2912 3553 (unless (h x) 2913 3554 (push x acc) 2914 < (t! (h x)))) 3555 > (set (h x)))) 2915 3556 (rev acc))) 2916 3557 2917 3558 (def single (x) (and (acons x) (no (cdr x)))) 2918 3559 2919 < (def plural (n str) 2920 < (if (or (is n 1) (single n)) 2921 < str 2922 < (string str "s"))) 2923 < 2924 3560 (def intersperse (x ys) 2925 < (cons (car ys) 2926 < (mappend [list x _] (cdr ys)))) 3561 > (and ys (cons (car ys) 3562 > (mappend [list x _] (cdr ys))))) 2927 3563 2928 3564 (def counts (seq (o c (table))) 2929 3565 (if (no seq) 2930 3566 c 2931 < (do (zap [if _ (+ _ 1) 1] (c (car seq))) 3567 > (do (++ (c (car seq) 0)) 2932 3568 (counts (cdr seq) c)))) 2933 3569 2934 3570 (def commonest (seq) 2935 3571 (with (winner nil n 0) 2936 < (ontable k v (counts seq) 3572 > (each (k v) (counts seq) 2937 3573 (when (> v n) (= winner k n v))) 2938 3574 (list winner n))) 2939 < 2940 < (def splitn (n xs) 2941 < (let acc nil 2942 < ((afn (n xs) 2943 < (if (or (no xs) (<= n 0)) 2944 < (list (rev acc) xs) 2945 < (do (push (car xs) acc) 2946 < (self (- n 1) (cdr xs))))) 2947 < n xs))) 2948 3575 2949 3576 (def reduce (f xs) 2950 3577 (if (cddr xs) 2951 3578 (reduce f (cons (f (car xs) (cadr xs)) (cddr xs))) 2952 3579 (apply f xs))) 2953 3580 2954 3581 (def rreduce (f xs) 2955 3582 (if (cddr xs) 2956 3583 (f (car xs) (rreduce f (cdr xs))) 2957 3584 (apply f xs))) 2958 3585 2959 3586 (let argsym (uniq) 2960 3587 2961 3588 (def parse-format (str) 2962 < (rev (accum a 2963 < (with (chars nil i -1) 2964 < (w/instring s str 2965 < (whilet c (readc s) 2966 < (case c 2967 < #\# (do (a (coerce (rev chars) 'string)) 2968 < (nil! chars) 2969 < (a (read s))) 2970 < #\~ (do (a (coerce (rev chars) 'string)) 2971 < (nil! chars) 2972 < (readc s) 2973 < (a (list argsym (++ i)))) 2974 < (push c chars)))) 2975 < (when chars 2976 < (a (coerce (rev chars) 'string))))))) 3589 > (accum a 3590 > (with (chars nil i -1) 3591 > (w/instring s str 3592 > (whilet c (readc s) 3593 > (case c 3594 > #\# (do (a (coerce (rev chars) 'string)) 3595 > (wipe chars) 3596 > (a (read s))) 3597 > #\~ (do (a (coerce (rev chars) 'string)) 3598 > (wipe chars) 3599 > (readc s) 3600 > (a (list argsym (++ i)))) 3601 > (push c chars)))) 3602 > (when chars 3603 > (a (coerce (rev chars) 'string)))))) 2977 3604 2978 3605 (mac prf (str . args) 2979 3606 `(let ,argsym (list ,@args) 2980 3607 (pr ,@(parse-format str)))) 2981 3608 ) 2982 3609 2983 3610 (def load (file) 2984 3611 (w/infile f file 2985 < (whilet e (read f) 2986 < (eval e)))) 3612 > (w/uniq eof 3613 > (whiler e (read f eof) eof 3614 > (eval e))))) 2987 3615 2988 3616 (def positive (x) 2989 3617 (and (number x) (> x 0))) 2990 3618 2991 3619 (mac w/table (var . body) 2992 3620 `(let ,var (table) ,@body ,var)) 2993 3621 2994 < (def ero args 2995 < (each a args 2996 < (write a (stderr)) 2997 < (writec #\space (stderr)))) 3622 > (def ero args 3623 > (w/stdout (stderr) 3624 > (each a args 3625 > (write a) 3626 > (writec #\space)) 3627 > (writec #\newline)) 3628 > (car args)) 2998 3629 2999 3630 (def queue () (list nil nil 0)) 3000 3631 3001 3632 ; Despite call to atomic, once had some sign this wasn't thread-safe. 3633 > ; Keep an eye on it. 3002 3634 3003 3635 (def enq (obj q) 3004 3636 (atomic 3005 3637 (++ (q 2)) 3006 3638 (if (no (car q)) 3007 3639 (= (cadr q) (= (car q) (list obj))) 3008 3640 (= (cdr (cadr q)) (list obj) 3009 3641 (cadr q) (cdr (cadr q)))) 3010 3642 (car q))) 3011 3643 3012 3644 (def deq (q) 3013 3645 (atomic (unless (is (q 2) 0) (-- (q 2))) 3014 3646 (pop (car q)))) 3015 3647 3016 3648 ; Should redef len to do this, and make queues lists annotated queue. 3017 3649 3018 3650 (def qlen (q) (q 2)) 3019 3651 3020 3652 (def qlist (q) (car q)) 3021 3653 3022 3654 (def enq-limit (val q (o limit 1000)) 3023 3655 (atomic 3024 3656 (unless (< (qlen q) limit) 3025 3657 (deq q)) 3026 3658 (enq val q))) 3027 3659 3028 3660 (def median (ns) 3029 < ((sort > ns) (truncate (/ (len ns) 2)))) 3661 > ((sort > ns) (trunc (/ (len ns) 2)))) 3030 3662 3031 3663 (mac noisy-each (n var val . body) 3032 3664 (w/uniq (gn gc) 3033 3665 `(with (,gn ,n ,gc 0) 3034 3666 (each ,var ,val 3035 3667 (when (multiple (++ ,gc) ,gn) 3036 3668 (pr ".") 3037 < ;(flushout) 3669 > (flushout) 3038 3670 ) 3039 3671 ,@body) 3040 3672 (prn) 3041 < ;(flushout) 3042 < ))) 3673 > (flushout)))) 3043 3674 3044 3675 (mac point (name . body) 3045 < (w/uniq g 3676 > (w/uniq (g p) 3046 3677 `(ccc (fn (,g) 3047 < (let ,name [,g _] 3678 > (let ,name (fn ((o ,p)) (,g ,p)) 3048 3679 ,@body))))) 3049 3680 3050 3681 (mac catch body 3051 3682 `(point throw ,@body)) 3052 3683 3053 3684 (def downcase (x) 3054 3685 (let downc (fn (c) 3055 3686 (let n (coerce c 'int) 3056 3687 (if (or (< 64 n 91) (< 191 n 215) (< 215 n 223)) 3057 3688 (coerce (+ n 32) 'char) 3058 3689 c))) 3059 3690 (case (type x) 3060 3691 string (map downc x) 3061 3692 char (downc x) 3062 3693 sym (sym (map downc (coerce x 'string))) 3063 3694 (err "Can't downcase" x)))) 3064 3695 3065 3696 (def upcase (x) 3066 3697 (let upc (fn (c) 3067 3698 (let n (coerce c 'int) 3068 3699 (if (or (< 96 n 123) (< 223 n 247) (< 247 n 255)) 3069 3700 (coerce (- n 32) 'char) 3070 3701 c))) 3071 3702 (case (type x) 3072 3703 string (map upc x) 3073 3704 char (upc x) 3074 3705 sym (sym (map upc (coerce x 'string))) 3075 3706 (err "Can't upcase" x)))) 3076 3707 3708 > (def inc (x (o n 1)) 3709 > (coerce (+ (coerce x 'int) n) (type x))) 3710 > 3077 3711 (def range (start end) 3078 3712 (if (> start end) 3079 3713 nil 3080 < (cons start (range (+ start 1) end)))) 3714 > (cons start (range (inc start) end)))) 3081 3715 3082 3716 (def mismatch (s1 s2) 3083 3717 (catch 3084 3718 (on c s1 3085 3719 (when (isnt c (s2 index)) 3086 3720 (throw index))))) 3087 3721 3088 3722 (def memtable (ks) 3089 3723 (let h (table) 3090 < (each k ks (t! (h k))) 3724 > (each k ks (set (h k))) 3091 3725 h)) 3092 3726 3093 3727 (= bar* " | ") 3094 3728 3095 3729 (mac w/bars body 3096 3730 (w/uniq (out needbars) 3097 3731 `(let ,needbars nil 3098 3732 (do ,@(map (fn (e) 3099 3733 `(let ,out (tostring ,e) 3100 3734 (unless (is ,out "") 3101 3735 (if ,needbars 3102 3736 (pr bar* ,out) 3103 < (do (t! ,needbars) 3737 > (do (set ,needbars) 3104 3738 (pr ,out)))))) 3105 3739 body))))) 3106 3740 3741 > (def len< (x n) (< (len x) n)) 3107 3742 3108 < ; Lower priority ideas 3743 > (def len> (x n) (> (len x) n)) 3744 > 3745 > (mac thread body 3746 > `(new-thread (fn () ,@body))) 3109 3747 3748 > (mac trav (x . fs) 3749 > (w/uniq g 3750 > `((afn (,g) 3751 > (when ,g 3752 > ,@(map [list _ g] fs))) 3753 > ,x))) 3754 > 3755 > (mac or= (place expr) 3756 > (let (binds val setter) (setforms place) 3757 > `(atwiths ,binds 3758 > (or ,val (,setter ,expr))))) 3759 > 3760 > (= hooks* (table)) 3761 > 3762 > (def hook (name . args) 3763 > (aif (hooks* name) (apply it args))) 3764 > 3765 > (mac defhook (name . rest) 3766 > `(= (hooks* ',name) (fn ,@rest))) 3767 > 3768 > (mac out (expr) `(pr ,(tostring (eval expr)))) 3769 > 3770 > ; if renamed this would be more natural for (map [_ user] pagefns*) 3771 > 3772 > (def get (index) [_ index]) 3773 > 3774 > (= savers* (table)) 3775 > 3776 > (mac fromdisk (var file init load save) 3777 > (w/uniq (gf gv) 3778 > `(unless (bound ',var) 3779 > (do1 (= ,var (iflet ,gf (file-exists ,file) 3780 > (,load ,gf) 3781 > ,init)) 3782 > (= (savers* ',var) (fn (,gv) (,save ,gv ,file))))))) 3783 > 3784 > (mac diskvar (var file) 3785 > `(fromdisk ,var ,file nil readfile1 writefile)) 3786 > 3787 > (mac disktable (var file) 3788 > `(fromdisk ,var ,file (table) load-table save-table)) 3789 > 3790 > (mac todisk (var (o expr var)) 3791 > `((savers* ',var) 3792 > ,(if (is var expr) var `(= ,var ,expr)))) 3793 > 3794 > 3795 > (mac evtil (expr test) 3796 > (w/uniq gv 3797 > `(let ,gv ,expr 3798 > (while (no (,test ,gv)) 3799 > (= ,gv ,expr)) 3800 > ,gv))) 3801 > 3802 > (def rand-key (h) 3803 > (if (empty h) 3804 > nil 3805 > (let n (rand (len h)) 3806 > (catch 3807 > (each (k v) h 3808 > (when (is (-- n) -1) 3809 > (throw k))))))) 3810 > 3811 > (def ratio (test xs) 3812 > (if (empty xs) 3813 > 0 3814 > (/ (count test xs) (len xs)))) 3815 > 3816 > 3817 > ; any logical reason I can't say (push x (if foo y z)) ? 3818 > ; eval would have to always ret 2 things, the val and where it came from 3819 > ; idea: implicit tables of tables; setf empty field, becomes table 3820 > ; or should setf on a table just take n args? 3821 > 3822 > ; idea: use constants in functional position for currying? 3823 > ; (1 foo) would mean (fn args (apply foo 1 args)) 3824 > ; another solution would be to declare certain symbols curryable, and 3825 > ; if > was, >_10 would mean [> _ 10] 3826 > ; or just say what the hell and make _ ssyntax for currying 3827 > ; idea: make >10 ssyntax for [> _ 10] 3110 3828 ; solution to the "problem" of improper lists: allow any atom as a list 3111 3829 ; terminator, not just nil. means list recursion should terminate on 3112 3830 ; atom rather than nil, (def empty (x) (or (atom x) (is x ""))) 3113 3831 ; table should be able to take an optional initial-value. handle in sref. 3114 3832 ; warn about code of form (if (= )) -- probably mean is 3115 3833 ; warn when a fn has a parm that's already defined as a macro. 3116 3834 ; (def foo (after) (after)) 3117 3835 ; idea: a fn (nothing) that returns a special gensym which is ignored 3118 3836 ; by map, so can use map in cases when don't want all the vals 3119 3837 ; idea: anaph macro so instead of (aand x y) say (anaph and x y) 3120 3838 ; idea: foo.bar!baz as an abbrev for (foo bar 'baz) 3839 > ; or something a bit more semantic? 3121 3840 ; could uniq be (def uniq () (annotate 'symbol (list 'u))) again? 3122 3841 ; idea: use x- for (car x) and -x for (cdr x) (but what about math -?) 3123 3842 ; idea: get rid of strings and just use symbols 3124 3843 ; could a string be (#\a #\b . "") ? 3125 3844 ; better err msg when , outside of a bq 3126 3845 ; idea: parameter (p foo) means in body foo is (pair arg) 3127 3846 ; idea: make ('string x) equiv to (coerce x 'string) ? or isa? 3128 3847 ; quoted atoms in car valuable unused semantic space 3848 > ; idea: if (defun foo (x y) ...), make (foo 1) return (fn (y) (foo 1 y)) 3849 > ; probably would lead to lots of errors when call with missing args 3850 > ; but would be really dense with . notation, (foo.1 2) 3851 > ; or use special ssyntax for currying: (foo@1 2) 3852 > ; remember, can also double; could use foo::bar to mean something 3853 > ; wild idea: inline defs for repetitive code 3854 > ; same args as fn you're in 3855 > ; variant of compose where first fn only applied to first arg? 3856 > ; (> (len x) y) means (>+len x y) 3857 > ; use ssyntax underscore for a var? 3858 > ; foo_bar means [foo _ bar] 3859 > ; what does foo:_:bar mean? 3860 > ; matchcase 3861 > ; idea: atable that binds it to table, assumes input is a list 3862 > ; crazy that finding the top 100 nos takes so long: 3863 > ; (let bb (n-of 1000 (rand 50)) (time10 (bestn 100 > bb))) 3864 > ; time: 2237 msec. -> now down to 850 msec 3129 3865 3130 3866 ================================================================================ as.scm ================================================================================ 3132 3868 ; mzscheme -m -f as.scm 3133 3869 ; (tl) 3134 3870 ; (asv) 3135 3871 ; http://localhost:8080 3136 3872 3137 3873 (require mzscheme) ; promise we won't redefine mzscheme bindings 3138 3874 3139 < (load "ac.scm") 3875 > (require "ac.scm") 3140 3876 (require "brackets.scm") 3141 3877 (use-bracket-readtable) 3142 3878 3143 3879 (aload "arc.arc") 3144 3880 (aload "libs.arc") 3145 3881 3146 3882 (tl) 3147 3883 3148 3884 ================================================================================ blog.arc ================================================================================ 3150 < ; Blog tool example. 20 Jan 08. 3886 > ; Blog tool example. 20 Jan 08, rev 21 May 09. 3151 3887 3152 3888 ; To run: 3153 3889 ; arc> (load "blog.arc") 3154 3890 ; arc> (bsv) 3155 3891 ; go to http://localhost:8080/blog 3156 3892 3157 3893 (= postdir* "arc/posts/" maxid* 0 posts* (table)) 3158 3894 3159 3895 (= blogtitle* "A Blog") 3160 3896 3161 < (deftem post 3162 < id nil 3163 < title nil 3164 < text nil) 3897 > (deftem post id nil title nil text nil) 3165 3898 3166 3899 (def load-posts () 3167 < (each id (map [coerce _ 'int] (dir postdir*)) 3900 > (each id (map int (dir postdir*)) 3168 3901 (= maxid* (max maxid* id) 3169 3902 (posts* id) (temload 'post (string postdir* id))))) 3170 3903 3171 < (def save-post (p) 3172 < (save-table p (string postdir* (p 'id)))) 3904 > (def save-post (p) (save-table p (string postdir* p!id))) 3173 3905 3174 < (def post (id) (posts* (errsafe (coerce id 'int)))) 3906 > (def post (id) (posts* (errsafe:int id))) 3175 3907 3176 3908 (mac blogpage body 3177 3909 `(whitepage 3178 3910 (center 3179 3911 (widtable 600 3180 3912 (tag b (link blogtitle* "blog")) 3181 3913 (br 3) 3182 3914 ,@body 3183 3915 (br 3) 3184 3916 (w/bars (link "archive") 3185 3917 (link "new post" "newpost")))))) 3186 3918 3187 < (defop viewpost req 3919 > (defop viewpost req (blogop post-page req)) 3920 > 3921 > (def blogop (f req) 3188 3922 (aif (post (arg req "id")) 3189 < (post-page (get-user req) it) 3190 < (notfound))) 3923 > (f (get-user req) it) 3924 > (blogpage (pr "No such post.")))) 3191 3925 3192 < (def permalink (p) (string "viewpost?id=" (p 'id))) 3926 > (def permalink (p) (string "viewpost?id=" p!id)) 3193 3927 3194 3928 (def post-page (user p) (blogpage (display-post user p))) 3195 3929 3196 3930 (def display-post (user p) 3197 < (tag b (link (p 'title) (permalink p))) 3931 > (tag b (link p!title (permalink p))) 3198 3932 (when user 3199 3933 (sp) 3200 < (link "[edit]" (string "editpost?id=" (p 'id)))) 3934 > (link "[edit]" (string "editpost?id=" p!id))) 3201 3935 (br2) 3202 < (pr (p 'text))) 3936 > (pr p!text)) 3203 3937 3204 < (def notfound () 3205 < (blogpage (pr "No such post."))) 3206 < 3207 3938 (defopl newpost req 3208 3939 (whitepage 3209 < (aform (fn (req) 3210 < (let user (get-user req) 3211 < (post-page user 3212 < (addpost user (arg req "t") (arg req "b"))))) 3213 < (tab 3214 < (row "title" (input "t" "" 60)) 3215 < (row "text" (textarea "b" 10 80)) 3216 < (row "" (submit)))))) 3940 > (aform [let u (get-user _) 3941 > (post-page u (addpost u (arg _ "t") (arg _ "b")))] 3942 > (tab (row "title" (input "t" "" 60)) 3943 > (row "text" (textarea "b" 10 80)) 3944 > (row "" (submit)))))) 3217 3945 3218 3946 (def addpost (user title text) 3219 3947 (let p (inst 'post 'id (++ maxid*) 'title title 'text text) 3220 3948 (save-post p) 3221 < (= (posts* (p 'id)) p))) 3949 > (= (posts* p!id) p))) 3222 3950 3223 < (defopl editpost req 3224 < (aif (post (arg req "id")) 3225 < (edit-page (get-user req) it) 3226 < (notfound))) 3951 > (defopl editpost req (blogop edit-page req)) 3227 3952 3228 3953 (def edit-page (user p) 3229 3954 (whitepage 3230 3955 (vars-form user 3231 < `((string title ,(p 'title) t t) 3232 < (text text ,(p 'text) t t)) 3956 > `((string title ,p!title t t) (text text ,p!text t t)) 3233 3957 (fn (name val) (= (p name) val)) 3234 3958 (fn () (save-post p) 3235 3959 (post-page user p))))) 3236 3960 3237 3961 (defop archive req 3238 3962 (blogpage 3239 3963 (tag ul 3240 3964 (each p (map post (rev (range 1 maxid*))) 3241 < (tag li (link (p 'title) (permalink p))))))) 3965 > (tag li (link p!title (permalink p))))))) 3242 3966 3243 3967 (defop blog req 3244 3968 (let user (get-user req) 3245 3969 (blogpage 3246 3970 (for i 0 4 3247 3971 (awhen (posts* (- maxid* i)) 3248 3972 (display-post user it) 3249 3973 (br 3)))))) 3250 3974 3251 3975 (def bsv () 3252 3976 (ensure-dir postdir*) 3253 3977 (load-posts) 3254 3978 (asv)) 3255 3979 3256 3980 3257 3981 ================================================================================ brackets.scm ================================================================================ 3259 3983 ; From Eli Barzilay, eli@barzilay.org 3260 3984 3261 3985 ;> (require "brackets.scm") 3262 3986 ;> (use-bracket-readtable) 3263 3987 ;> ([+ _ 1] 10) 3264 3988 ;11 3265 3989 3266 3990 (module brackets mzscheme 3267 3991 3268 3992 ; main reader function for []s 3269 3993 ; recursive read starts with default readtable's [ parser, 3270 3994 ; but nested reads still use the curent readtable: 3271 3995 3272 3996 (define (read-square-brackets ch port src line col pos) 3273 3997 `(fn (_) 3274 3998 ,(read/recursive port #\[ #f))) 3275 3999 3276 4000 ; a readtable that is just like the builtin except for []s 3277 4001 3278 4002 (define bracket-readtable 3279 4003 (make-readtable #f #\[ 'terminating-macro read-square-brackets)) 3280 4004 3281 4005 ; call this to set the global readtable 3282 4006 3283 4007 (provide use-bracket-readtable) 3284 4008 3285 4009 (define (use-bracket-readtable) 3286 4010 (current-readtable bracket-readtable)) 3287 4011 3288 4012 ; these two implement the required functionality for #reader 3289 4013 3290 4014 ;(define (*read inp) 3291 4015 ; (parameterize ((current-readtable bracket-readtable)) 3292 4016 ; (read inp))) 3293 4017 3294 4018 (define (*read . args) 3295 4019 (parameterize ((current-readtable bracket-readtable)) 3296 4020 (read (if (null? args) (current-input-port) (car args))))) 3297 4021 3298 4022 (define (*read-syntax src port) 3299 4023 (parameterize ((current-readtable bracket-readtable)) 3300 4024 (read-syntax src port))) 3301 4025 3302 4026 ; and the need to be provided as `read' and `read-syntax' 3303 4027 3304 4028 (provide (rename *read read) (rename *read-syntax read-syntax)) 3305 4029 3306 4030 ) 3307 4031 ================================================================================ code.arc ================================================================================ 3309 4033 ; Code analysis. Spun off 21 Dec 07. 3310 4034 3311 4035 ; Ought to do more of this in Arc. One of the biggest advantages 3312 4036 ; of Lisp is messing with code. 3313 4037 3314 4038 (def codelines (file) 3315 4039 (w/infile in file 3316 4040 (summing test 3317 4041 (whilet line (readline in) 3318 < (test (aand (pos nonwhite line) (isnt it #\;))))))) 4042 > (test (aand (find nonwhite line) (isnt it #\;))))))) 3319 4043 3320 4044 (def codeflat (file) 3321 4045 (len (flat (readall (infile file))))) 3322 4046 3323 4047 (def codetree (file) 3324 < (trav + (fn (x) 1) (readall (infile file)))) 4048 > (treewise + (fn (x) 1) (readall (infile file)))) 3325 4049 3326 4050 (def code-density (file) 3327 4051 (/ (codetree file) (codelines file))) 3328 4052 3329 4053 (def tokcount (files) 3330 4054 (let counts (table) 3331 4055 (each f files 3332 4056 (each token (flat (readall (infile f))) 3333 < (= (counts token) 3334 < (+ 1 (or (counts token) 0))))) 4057 > (++ (counts token 0)))) 3335 4058 counts)) 3336 4059 3337 4060 (def common-tokens (files) 3338 4061 (let counts (tokcount files) 3339 4062 (let ranking nil 3340 4063 (maptable (fn (k v) 3341 4064 (unless (nonop k) 3342 4065 (insort (compare > cadr) (list k v) ranking))) 3343 4066 counts) 3344 4067 ranking))) 3345 4068 3346 4069 (def nonop (x) 3347 4070 (in x 'quote 'unquote 'quasiquote 'unquote-splicing)) 3348 4071 3349 4072 (def common-operators (files) 3350 4073 (keep [and (isa (car _) 'sym) (bound (car _))] (common-tokens files))) 3351 4074 3352 4075 (def top40 (xs) 3353 4076 (map prn (firstn 40 xs)) 3354 4077 t) 3355 4078 3356 4079 (def space-eaters (files) 3357 4080 (let counts (tokcount files) 3358 4081 (let ranking nil 3359 4082 (maptable (fn (k v) 3360 4083 (when (and (isa k 'sym) (bound k)) 3361 < (insort (compare > [* (len (coerce (car _) 'string)) 4084 > (insort (compare > [* (len (string (car _))) 3362 4085 (cadr _)]) 3363 4086 (list k v (* (len (string k)) v)) 3364 4087 ranking))) 3365 4088 counts) 3366 4089 ranking))) 3367 4090 3368 4091 ;(top40 (space-eaters allfiles*)) 3369 4092 4093 > (mac flatlen args `(len (flat ',args))) 3370 4094 ================================================================================ html.arc ================================================================================ 3372 4096 ; HTML Utils. 3373 4097 3374 4098 3375 4099 (def color (r g b) 3376 4100 (with (c (table) 3377 4101 f (fn (x) (if (< x 0) 0 (> x 255) 255 x))) 3378 4102 (= (c 'r) (f r) (c 'g) (f g) (c 'b) (f b)) 3379 4103 c)) 3380 4104 3381 4105 (def dehex (str) (errsafe (coerce str 'int 16))) 3382 4106 3383 4107 (defmemo hex>color (str) 3384 4108 (and (is (len str) 6) 3385 < (with (r (dehex (subseq str 0 2)) 3386 < g (dehex (subseq str 2 4)) 3387 < b (dehex (subseq str 4 6))) 4109 > (with (r (dehex (cut str 0 2)) 4110 > g (dehex (cut str 2 4)) 4111 > b (dehex (cut str 4 6))) 3388 4112 (and r g b 3389 4113 (color r g b))))) 3390 4114 3391 4115 (defmemo gray (n) (color n n n)) 3392 4116 3393 4117 (= white (gray 255) 3394 4118 black (gray 0) 3395 < linkblue (color 0 0 190)) 4119 > linkblue (color 0 0 190) 4120 > orange (color 255 102 0) 4121 > darkred (color 180 0 0) 4122 > darkblue (color 0 0 120) 4123 > ) 3396 4124 3397 4125 (= opmeths* (table)) 3398 4126 3399 < ; hack: intern key pair till have implicit tables of tables 3400 < 3401 < (mac opmeth (tag opt) 3402 < `(opmeths* (sym (+ (coerce ,tag 'string) "." (coerce ,opt 'string))))) 4127 > (mac opmeth args 4128 > `(opmeths* (list ,@args))) 3403 4129 3404 4130 (mac attribute (tag opt f) 3405 < ; `(= (opmeth ',tag ',opt) ,f) 3406 < `(= (opmeths* ',(sym (+ (coerce tag 'string) "." (coerce opt 'string)))) 3407 < ,f)) 4131 > `(= (opmeths* (list ',tag ',opt)) ,f)) 3408 4132 3409 4133 (= hexreps (table)) 3410 4134 3411 4135 (for i 0 255 (= (hexreps i) 3412 4136 (let s (coerce i 'string 16) 3413 4137 (if (is (len s) 1) (+ "0" s) s)))) 3414 4138 3415 4139 (defmemo hexrep (col) 3416 4140 (+ (hexreps (col 'r)) (hexreps (col 'g)) (hexreps (col 'b)))) 3417 4141 3418 4142 (def opcolor (key val) 3419 4143 (w/uniq gv 3420 4144 `(whenlet ,gv ,val 3421 < (pr " " ',key "=#" (hexrep ,gv))))) 4145 > (pr ,(string " " key "=#") (hexrep ,gv))))) 3422 4146 3423 4147 (def opstring (key val) 3424 < `(aif ,val (pr " " ',key "=\"" it #\"))) 4148 > `(aif ,val (pr ,(+ " " key "=\"") it #\"))) 3425 4149 3426 4150 (def opnum (key val) 3427 < `(aif ,val (pr " " ',key "=" it))) 4151 > `(aif ,val (pr ,(+ " " key "=") it))) 3428 4152 3429 4153 (def opsym (key val) 3430 < `(pr " " ',key "=" ,val)) 4154 > `(pr ,(+ " " key "=") ,val)) 3431 4155 3432 4156 (def opsel (key val) 3433 4157 `(if ,val (pr " selected"))) 3434 4158 4159 > (def opcheck (key val) 4160 > `(if ,val (pr " checked"))) 4161 > 3435 4162 (def opesc (key val) 3436 4163 `(awhen ,val 3437 < (pr " " ',key "=\"") 4164 > (pr ,(string " " key "=\"")) 3438 4165 (if (isa it 'string) (pr-escaped it) (pr it)) 3439 4166 (pr #\"))) 3440 4167 3441 4168 ; need to escape more? =? 3442 4169 3443 4170 (def pr-escaped (x) 3444 4171 (each c x 3445 4172 (pr (case c #\< "<" 3446 4173 #\> ">" 3447 4174 #\" """ 3448 4175 #\& "&" 3449 4176 c)))) 3450 4177 3451 4178 (attribute a href opstring) 3452 4179 (attribute a rel opstring) 3453 4180 (attribute a class opstring) 3454 4181 (attribute a id opsym) 3455 4182 (attribute a onclick opstring) 3456 4183 (attribute body alink opcolor) 3457 4184 (attribute body bgcolor opcolor) 3458 4185 (attribute body leftmargin opnum) 3459 4186 (attribute body link opcolor) 3460 4187 (attribute body marginheight opnum) 3461 4188 (attribute body marginwidth opnum) 3462 4189 (attribute body topmargin opnum) 3463 4190 (attribute body vlink opcolor) 3464 4191 (attribute font color opcolor) 3465 4192 (attribute font face opstring) 3466 4193 (attribute font size opnum) 3467 4194 (attribute form action opstring) 3468 4195 (attribute form method opsym) 3469 4196 (attribute img align opsym) 3470 4197 (attribute img border opnum) 3471 4198 (attribute img height opnum) 3472 4199 (attribute img width opnum) 3473 4200 (attribute img vspace opnum) 3474 4201 (attribute img hspace opnum) 3475 4202 (attribute img src opstring) 3476 4203 (attribute input name opstring) 3477 4204 (attribute input size opnum) 3478 4205 (attribute input type opsym) 3479 4206 (attribute input value opesc) 3480 < (attribute option selected opsel) 4207 > (attribute input checked opcheck) 3481 4208 (attribute select name opstring) 4209 > (attribute option selected opsel) 3482 4210 (attribute table bgcolor opcolor) 3483 4211 (attribute table border opnum) 3484 4212 (attribute table cellpadding opnum) 3485 4213 (attribute table cellspacing opnum) 3486 4214 (attribute table width opstring) 3487 4215 (attribute textarea cols opnum) 3488 4216 (attribute textarea name opstring) 3489 4217 (attribute textarea rows opnum) 3490 4218 (attribute textarea wrap opsym) 3491 4219 (attribute td align opsym) 3492 4220 (attribute td bgcolor opcolor) 3493 4221 (attribute td colspan opnum) 3494 4222 (attribute td width opnum) 3495 4223 (attribute td valign opsym) 3496 4224 (attribute td class opstring) 3497 4225 (attribute tr bgcolor opcolor) 3498 4226 (attribute hr color opcolor) 3499 4227 (attribute span class opstring) 3500 4228 (attribute span align opstring) 3501 4229 (attribute span id opsym) 3502 4230 (attribute rss version opstring) 3503 4231 3504 4232 3505 4233 (mac gentag args (start-tag args)) 3506 4234 3507 4235 (mac tag (spec . body) 3508 4236 `(do ,(start-tag spec) 3509 4237 ,@body 3510 4238 ,(end-tag spec))) 3511 4239 3512 4240 (mac tag-if (test spec . body) 3513 4241 `(if ,test 3514 4242 (tag ,spec ,@body) 3515 4243 (do ,@body))) 3516 4244 3517 4245 (def start-tag (spec) 3518 4246 (if (atom spec) 3519 < `(pr "<" ',spec ">") 3520 < `(do (pr "<" ',(car spec)) 3521 < ,@(tag-options (car spec) (pair (cdr spec))) 3522 < (pr ">")))) 3523 < 4247 > `(pr ,(string "<" spec ">")) 4248 > (let opts (tag-options (car spec) (pair (cdr spec))) 4249 > (if (all [isa _ 'string] opts) 4250 > `(pr ,(string "<" (car spec) (apply string opts) ">")) 4251 > `(do (pr ,(string "<" (car spec))) 4252 > ,@(map (fn (opt) 4253 > (if (isa opt 'string) 4254 > `(pr ,opt) 4255 > opt)) 4256 > opts) 4257 > (pr ">")))))) 4258 > 3524 4259 (def end-tag (spec) 3525 < `(pr "")) 4260 > `(pr ,(string ""))) 3526 4261 4262 > (def literal (x) 4263 > (case (type x) 4264 > sym (in x nil t) 4265 > cons (caris x 'quote) 4266 > t)) 4267 > 4268 > ; Returns a list whose elements are either strings, which can 4269 > ; simply be printed out, or expressions, which when evaluated 4270 > ; generate output. 4271 > 3527 4272 (def tag-options (spec options) 3528 4273 (if (no options) 3529 4274 '() 3530 4275 (let ((opt val) . rest) options 3531 4276 (let meth (if (is opt 'style) opstring (opmeth spec opt)) 3532 4277 (if meth 3533 4278 (if val 3534 < (cons (meth opt val) 4279 > (cons (if (precomputable-tagopt val) 4280 > (tostring (eval (meth opt val))) 4281 > (meth opt val)) 3535 4282 (tag-options spec rest)) 3536 4283 (tag-options spec rest)) 3537 4284 (do 3538 4285 (pr "") 3539 4286 (tag-options spec rest))))))) 4287 > 4288 > (def precomputable-tagopt (val) 4289 > (and (literal val) 4290 > (no (and (is (type val) 'string) (find #\@ val))))) 3540 4291 3541 4292 (def br ((o n 1)) 3542 4293 (repeat n (pr "
")) 3543 4294 (prn)) 3544 4295 3545 4296 (def br2 () (prn "

")) 3546 4297 3547 4298 (mac center body `(tag center ,@body)) 3548 4299 (mac underline body `(tag u ,@body)) 3549 4300 (mac tab body `(tag (table border 0) ,@body)) 3550 4301 (mac tr body `(tag tr ,@body)) 3551 4302 3552 4303 (let pratoms (fn (body) 3553 4304 (if (or (no body) 3554 4305 (all [and (acons _) (isnt (car _) 'quote)] 3555 4306 body)) 3556 4307 body 3557 4308 `((pr ,@body)))) 3558 4309 3559 4310 (mac td body `(tag td ,@(pratoms body))) 3560 4311 (mac trtd body `(tr (td ,@(pratoms body)))) 3561 < (mac tdright body `(tag (td align 'right) ,@(pratoms body))) 4312 > (mac tdr body `(tag (td align 'right) ,@(pratoms body))) 3562 4313 (mac tdcolor (col . body) `(tag (td bgcolor ,col) ,@(pratoms body))) 3563 4314 ) 3564 4315 3565 4316 (mac row args 3566 4317 `(tr ,@(map [list 'td _] args))) 3567 4318 3568 4319 (mac prrow args 3569 4320 (w/uniq g 3570 4321 `(tr ,@(map (fn (a) 3571 4322 `(let ,g ,a 3572 4323 (if (number ,g) 3573 < (tdright (pr ,g)) 4324 > (tdr (pr ,g)) 3574 4325 (td (pr ,g))))) 3575 4326 args)))) 3576 4327 3577 4328 (mac prbold body `(tag b (pr ,@body))) 3578 4329 3579 < (def para () (gentag p)) 4330 > (def para args 4331 > (gentag p) 4332 > (when args (apply pr args))) 3580 4333 3581 4334 (def menu (name items (o sel nil)) 3582 4335 (tag (select name name) 3583 4336 (each i items 3584 4337 (tag (option selected (is i sel)) 3585 4338 (pr i))))) 3586 4339 3587 4340 (mac whitepage body 3588 4341 `(tag html 3589 4342 (tag (body bgcolor white alink linkblue) ,@body))) 3590 4343 3591 4344 (def errpage args (whitepage (apply prn args))) 3592 4345 3593 < (= local-images* nil) ; set to t when developing offline 3594 < 3595 < (def img-url (file) 3596 < (string (unless local-images* "http://ycombinator.com/images/") file)) 3597 < 3598 < (def blank-url () 3599 < (if local-images* "s.gif" "http://ycombinator.com/images/s.gif")) 4346 > (def blank-url () "s.gif") 3600 4347 3601 4348 ; Could memoize these. 3602 4349 3603 4350 ; If h = 0, doesn't affect table column widths in some Netscapes. 3604 4351 3605 4352 (def hspace (n) (gentag img src (blank-url) height 1 width n)) 3606 4353 (def vspace (n) (gentag img src (blank-url) height n width 0)) 3607 4354 (def vhspace (h w) (gentag img src (blank-url) height h width w)) 3608 4355 3609 4356 (mac new-hspace (n) 3610 4357 (if (number n) 3611 4358 `(pr ,(string "")) 3612 4359 `(pr ""))) 3613 4360 3614 4361 ;(def spacerow (h) (tr (td (vspace h)))) 3615 4362 3616 4363 (def spacerow (h) (pr "")) 3617 4364 3618 4365 ; For use as nested table. 3619 4366 3620 4367 (mac zerotable body 3621 4368 `(tag (table border 0 cellpadding 0 cellspacing 0) 3622 4369 ,@body)) 3623 4370 3624 < (mac spacetable body 3625 < `(tag (table border 0 cellpadding 0 cellspacing 7) ,@body)) 4371 > ; was `(tag (table border 0 cellpadding 0 cellspacing 7) ,@body) 4372 > 4373 > (mac sptab body 4374 > `(tag (table style "border-spacing: 7px 0px;") ,@body)) 3626 4375 3627 4376 (mac widtable (w . body) 3628 4377 `(tag (table width ,w) (tr (td ,@body)))) 3629 4378 3630 4379 (def cellpr (x) (pr (or x " "))) 3631 4380 3632 4381 (def but ((o text "submit") (o name nil)) 3633 4382 (gentag input type 'submit name name value text)) 3634 4383 3635 4384 (def submit ((o val "submit")) 3636 4385 (gentag input type 'submit value val)) 3637 4386 3638 4387 (def buts (name . texts) 3639 4388 (if (no texts) 3640 4389 (but) 3641 4390 (do (but (car texts) name) 3642 4391 (each text (cdr texts) 3643 4392 (pr " ") 3644 4393 (but text name))))) 3645 4394 3646 4395 (mac spanrow (n . body) 3647 4396 `(tr (tag (td colspan ,n) ,@body))) 3648 4397 3649 4398 (mac form (action . body) 3650 4399 `(tag (form method "post" action ,action) ,@body)) 3651 4400 3652 4401 (mac textarea (name rows cols . body) 3653 4402 `(tag (textarea name ,name rows ,rows cols ,cols) ,@body)) 3654 4403 3655 4404 (def input (name (o val "") (o size 10)) 3656 4405 (gentag input type 'text name name value val size size)) 3657 4406 3658 4407 (mac inputs args 3659 4408 `(tag (table border 0) 3660 4409 ,@(map (fn ((name label len text)) 3661 4410 (w/uniq (gl gt) 3662 4411 `(let ,gl ,len 3663 4412 (tr (td (pr ',label ":")) 3664 4413 (if (isa ,gl 'cons) 3665 4414 (td (textarea ',name (car ,gl) (cadr ,gl) 3666 4415 (let ,gt ,text (if ,gt (pr ,gt))))) 3667 4416 (td (gentag input type ',(if (is label 'password) 3668 4417 'password 3669 4418 'text) 3670 4419 name ',name 3671 4420 size ,len 3672 4421 value ,text))))))) 3673 4422 (tuples args 4)))) 3674 4423 4424 > (def single-input (label name chars btext (o pwd)) 4425 > (pr label) 4426 > (gentag input type (if pwd 'password 'text) name name size chars) 4427 > (sp) 4428 > (submit btext)) 4429 > 4430 > (mac cdata body 4431 > `(do (pr " ,@body 4433 > (pr "]]>"))) 4434 > 3675 4435 (def eschtml (str) 3676 4436 (tostring 3677 4437 (each c str 3678 4438 (pr (case c #\< "<" 3679 4439 #\> ">" 3680 4440 #\" """ 3681 4441 #\' "'" 3682 4442 #\& "&" 3683 4443 c))))) 3684 4444 3685 < (def esc<>& (str) 4445 > (def esc-tags (str) 3686 4446 (tostring 3687 4447 (each c str 3688 4448 (pr (case c #\< "<" 3689 4449 #\> ">" 3690 4450 #\& "&" 3691 4451 c))))) 3692 4452 3693 4453 (def nbsp () (pr " ")) 3694 4454 3695 4455 (def link (text (o dest text) (o color)) 3696 4456 (tag (a href dest) 3697 4457 (tag-if color (font color color) 3698 4458 (pr text)))) 3699 4459 3700 4460 (def underlink (text (o dest text)) 3701 4461 (tag (a href dest) (tag u (pr text)))) 3702 4462 3703 4463 (def striptags (s) 3704 4464 (let intag nil 3705 4465 (tostring 3706 4466 (each c s 3707 < (if (is c #\<) (t! intag) 3708 < (is c #\>) (nil! intag) 4467 > (if (is c #\<) (set intag) 4468 > (is c #\>) (wipe intag) 3709 4469 (no intag) (pr c)))))) 3710 4470 4471 > (def clean-url (u) 4472 > (rem [in _ #\" #\' #\< #\>] u)) 4473 > 3711 4474 (def shortlink (url) 3712 4475 (unless (or (no url) (< (len url) 7)) 3713 < (link (subseq url 7) url))) 4476 > (link (cut url 7) url))) 3714 4477 3715 4478 ; this should be one regexp 3716 4479 3717 4480 (def parafy (str) 3718 4481 (let ink nil 3719 4482 (tostring 3720 4483 (each c str 3721 4484 (pr c) 3722 < (unless (whitec c) (t! ink)) 4485 > (unless (whitec c) (set ink)) 3723 4486 (when (is c #\newline) 3724 4487 (unless ink (pr "

")) 3725 < (nil! ink)))))) 4488 > (wipe ink)))))) 3726 4489 3727 4490 (mac spanclass (name . body) 3728 4491 `(tag (span class ',name) ,@body)) 3729 4492 3730 4493 (def pagemessage (text) 3731 4494 (when text (prn text) (br2))) 4495 > 4496 > ; Could be stricter. Memoized because looking for chars in Unicode 4497 > ; strings is terribly inefficient in Mzscheme. 4498 > 4499 > (defmemo valid-url (url) 4500 > (and (len> url 10) 4501 > (or (begins url "http://") 4502 > (begins url "https://")) 4503 > (~find [in _ #\< #\> #\" #\'] url))) 4504 > 4505 > (mac fontcolor (c . body) 4506 > (w/uniq g 4507 > `(let ,g ,c 4508 > (if ,g 4509 > (tag (font color ,g) ,@body) 4510 > (do ,@body))))) 3732 4511 ================================================================================ libs.arc ================================================================================ 3734 4513 (map load '("strings.arc" 3735 4514 "pprint.arc" 3736 4515 "code.arc" 3737 4516 "html.arc" 3738 4517 "srv.arc" 3739 4518 "app.arc" 3740 4519 "prompt.arc")) 3741 4520 ================================================================================ pprint.arc ================================================================================ 3743 4522 ; Pretty-Printing. Spun off 4 Aug 06. 3744 4523 3745 4524 ; todo: indentation of long ifs; quasiquote, unquote, unquote-splicing 3746 4525 3747 4526 (= bodops* (fill-table (table) 3748 4527 '(let 2 with 1 while 1 def 2 fn 1 rfn 2 afn 1 3749 4528 when 1 unless 1 after 1 whilet 2 for 3 each 2 whenlet 2 awhen 1 3750 4529 whitepage 0 tag 1 form 1 aform 1 aformh 1 w/link 1 textarea 3 3751 4530 ))) 3752 4531 3753 4532 (= oneline* 35) ; print exprs less than this long on one line 3754 4533 3755 4534 ; If returns nil, can assume it didn't have to break expr. 3756 4535 3757 4536 (def ppr (expr (o col 0) (o noindent nil)) 3758 4537 (if (or (atom expr) (dotted expr)) 3759 4538 (do (unless noindent (sp col)) 3760 4539 (write expr) 3761 4540 nil) 3762 4541 (is (car expr) 'quote) 3763 4542 (do (unless noindent (sp col)) 3764 4543 (pr "'") 3765 4544 (ppr (cadr expr) (+ col 1) t)) 3766 4545 (bodops* (car expr)) 3767 4546 (do (unless noindent (sp col)) 3768 4547 (let whole (tostring (write expr)) 3769 4548 (if (< (len whole) oneline*) 3770 4549 (do (pr whole) nil) 3771 4550 (ppr-progn expr col noindent)))) 3772 4551 (do (unless noindent (sp col)) 3773 4552 (let whole (tostring (write expr)) 3774 4553 (if (< (len whole) oneline*) 3775 4554 (do (pr whole) nil) 3776 4555 (ppr-call expr col noindent)))))) 3777 4556 3778 4557 (def ppr-progn (expr col noindent) 3779 4558 (lpar) 3780 4559 (let n (bodops* (car expr)) 3781 4560 (let str (tostring (write-spaced (firstn n expr))) 3782 4561 (unless (is n 0) (pr str) (sp)) 3783 4562 (ppr (expr n) (+ col (len str) 2) t)) 3784 4563 (map (fn (e) (prn) (ppr e (+ col 2))) 3785 4564 (nthcdr (+ n 1) expr))) 3786 4565 (rpar) 3787 4566 t) 3788 4567 3789 4568 (def ppr-call (expr col noindent) 3790 4569 (lpar) 3791 4570 (let carstr (tostring (write (car expr))) 3792 4571 (pr carstr) 3793 4572 (if (cdr expr) 3794 4573 (do (sp) 3795 4574 (let broke (ppr (cadr expr) (+ col (len carstr) 2) t) 3796 4575 (pprest (cddr expr) 3797 4576 (+ col (len carstr) 2) 3798 4577 (no broke))) 3799 4578 t) 3800 4579 (do (rpar) t)))) 3801 4580 3802 4581 (def pprest (exprs col (o oneline t)) 3803 4582 (if (and oneline 3804 4583 (all (fn (e) 3805 4584 (or (atom e) (and (is (car e) 'quote) (atom (cadr e))))) 3806 4585 exprs)) 3807 4586 (do (map (fn (e) (pr " ") (write e)) 3808 4587 exprs) 3809 4588 (rpar)) 3810 4589 (do (when exprs 3811 4590 (each e exprs (prn) (ppr e col))) 3812 4591 (rpar)))) 3813 4592 3814 4593 (def write-spaced (xs) 3815 4594 (when xs 3816 4595 (write (car xs)) 3817 4596 (each x (cdr xs) (pr " ") (write x)))) 3818 4597 3819 4598 (def sp ((o n 1)) (repeat n (pr " "))) 3820 4599 (def lpar () (pr "(")) 3821 4600 (def rpar () (pr ")")) 3822 4601 3823 4602 ================================================================================ prompt.arc ================================================================================ 3825 4604 ; Prompt: Web-based programming application. 4 Aug 06. 3826 4605 3827 4606 (= appdir* "arc/apps/") 3828 4607 3829 4608 (defop prompt req 3830 4609 (let user (get-user req) 3831 4610 (if (admin user) 3832 4611 (prompt-page user) 3833 4612 (pr "Sorry.")))) 3834 4613 3835 4614 (def prompt-page (user . msg) 3836 4615 (ensure-dir appdir*) 3837 4616 (ensure-dir (string appdir* user)) 3838 4617 (whitepage 3839 4618 (prbold "Prompt") 3840 4619 (hspace 20) 3841 4620 (pr user " | ") 3842 4621 (link "logout") 3843 4622 (when msg (hspace 10) (apply pr msg)) 3844 4623 (br2) 3845 4624 (tag (table border 0 cellspacing 10) 3846 4625 (each app (dir (+ appdir* user)) 3847 4626 (tr (td app) 3848 < (td (userlink user 'edit (edit-app user app))) 3849 < (td (userlink user 'run (run-app user app))) 4627 > (td (ulink user 'edit (edit-app user app))) 4628 > (td (ulink user 'run (run-app user app))) 3850 4629 (td (hspace 40) 3851 < (userlink user 'delete (rem-app user app)))))) 4630 > (ulink user 'delete (rem-app user app)))))) 3852 4631 (br2) 3853 4632 (aform (fn (req) 3854 < (when-usermatch user req 4633 > (when-umatch user req 3855 4634 (aif (goodname (arg req "app")) 3856 4635 (edit-app user it) 3857 4636 (prompt-page user "Bad name.")))) 3858 4637 (tab (row "name:" (input "app") (submit "create app")))))) 3859 4638 3860 4639 (def app-path (user app) 3861 4640 (and user app (+ appdir* user "/" app))) 3862 4641 3863 4642 (def read-app (user app) 3864 4643 (aand (app-path user app) 3865 4644 (file-exists it) 3866 < (w/infile i it (readall i)))) 4645 > (readfile it))) 3867 4646 3868 4647 (def write-app (user app exprs) 3869 4648 (awhen (app-path user app) 3870 4649 (w/outfile o it 3871 4650 (each e exprs (write e o))))) 3872 4651 3873 4652 (def rem-app (user app) 3874 4653 (let file (app-path user app) 3875 4654 (if (file-exists file) 3876 4655 (do (rmfile (app-path user app)) 3877 4656 (prompt-page user "Program " app " deleted.")) 3878 4657 (prompt-page user "No such app.")))) 3879 4658 3880 4659 (def edit-app (user app) 3881 4660 (whitepage 3882 4661 (pr "user: " user " app: " app) 3883 4662 (br2) 3884 4663 (aform (fn (req) 3885 4664 (let u2 (get-user req) 3886 4665 (if (is u2 user) 3887 4666 (do (when (is (arg req "cmd") "save") 3888 4667 (write-app user app (readall (arg req "exprs")))) 3889 4668 (prompt-page user)) 3890 4669 (login-page 'both nil 3891 4670 (fn (u ip) (prompt-page u)))))) 3892 4671 (textarea "exprs" 10 82 3893 4672 (pprcode (read-app user app))) 3894 4673 (br2) 3895 4674 (buts 'cmd "save" "cancel")))) 3896 4675 3897 4676 (def pprcode (exprs) 3898 4677 (each e exprs 3899 4678 (ppr e) 3900 4679 (pr "\n\n"))) 3901 4680 3902 4681 (def view-app (user app) 3903 4682 (whitepage 3904 4683 (pr "user: " user " app: " app) 3905 4684 (br2) 3906 4685 (tag xmp (pprcode (read-app user app))))) 3907 4686 3908 4687 (def run-app (user app) 3909 4688 (let exprs (read-app user app) 3910 4689 (if exprs 3911 4690 (on-err (fn (c) (pr "Error: " (details c))) 3912 4691 (fn () (map eval exprs))) 3913 4692 (prompt-page user "Error: No application " app " for user " user)))) 3914 4693 3915 < (nil! repl-history*) 4694 > (wipe repl-history*) 3916 4695 3917 4696 (defop repl req 3918 4697 (if (admin (get-user req)) 3919 4698 (replpage req) 3920 4699 (pr "Sorry."))) 3921 4700 3922 4701 (def replpage (req) 3923 4702 (whitepage 3924 4703 (repl (readall (or (arg req "expr") "")) "repl"))) 3925 4704 3926 4705 (def repl (exprs url) 3927 4706 (each expr exprs 3928 4707 (on-err (fn (c) (push (list expr c t) repl-history*)) 3929 4708 (fn () 3930 4709 (= that (eval expr) thatexpr expr) 3931 4710 (push (list expr that) repl-history*)))) 3932 4711 (form url 3933 4712 (textarea "expr" 8 60) 3934 4713 (sp) 3935 4714 (submit)) 3936 4715 (tag xmp 3937 4716 (each (expr val err) (firstn 20 repl-history*) 3938 4717 (pr "> ") 3939 4718 (ppr expr) 3940 4719 (prn) 3941 4720 (prn (if err "Error: " "") 3942 4721 (ellipsize (tostring (write val)) 800))))) 3943 4722 3944 4723 ================================================================================ srv.arc ================================================================================ 3946 < ; (server) then http://tintin.archub.org:8080/foo 3947 < 3948 < ; could make form fields that know their value type because of 3949 < ; gensymed names, and so the receiving fn gets args that are not 3950 < ; strings but parsed values. 3951 < 3952 < ; write w/socket 4725 > ; HTTP Server. 4726 > 4727 > ; To improve performance with static files, set static-max-age*. 4728 > 4729 > (= arcdir* "arc/" logdir* "arc/logs/" staticdir* "static/") 3953 4730 3954 < ; set breaksrv* to t to be able to ^c the server 3955 < 3956 < (= arcdir* "arc/" logdir* "arc/logs/" quitsrv* nil breaksrv* nil) 4731 > (= quitsrv* nil breaksrv* nil) 3957 4732 3958 4733 (def serve ((o port 8080)) 3959 < (nil! quitsrv*) 3960 < (ensure-install) 3961 < (let s (open-socket port) 3962 < (prn "ready to serve port " port) ; (flushout) 4734 > (wipe quitsrv*) 4735 > (ensure-srvdirs) 4736 > (map [apply new-bgthread _] pending-bgthreads*) 4737 > (w/socket s port 4738 > (setuid 2) ; XXX switch from root to pg 4739 > (prn "ready to serve port " port) 4740 > (flushout) 3963 4741 (= currsock* s) 3964 < (after (while (no quitsrv*) 3965 < (if breaksrv* 3966 < (handle-request s) 3967 < (errsafe (handle-request s)))) 3968 < (close s) 3969 < (prn "quit server")))) 4742 > (until quitsrv* 4743 > (handle-request s breaksrv*))) 4744 > (prn "quit server")) 3970 4745 3971 4746 (def serve1 ((o port 8080)) 3972 < (let s (open-socket port) 3973 < (after (handle-request s) (close s)))) 4747 > (w/socket s port (handle-request s t))) 4748 > 4749 > (def ensure-srvdirs () 4750 > (map ensure-dir (list arcdir* logdir* staticdir*))) 3974 4751 3975 4752 (= srv-noisy* nil) 3976 4753 3977 4754 ; http requests currently capped at 2 meg by socket-accept 3978 4755 3979 4756 ; should threads process requests one at a time? no, then 3980 4757 ; a browser that's slow consuming the data could hang the 3981 4758 ; whole server. 3982 4759 3983 4760 ; wait for a connection from a browser and start a thread 3984 4761 ; to handle it. also arrange to kill that thread if it 3985 4762 ; has not completed in threadlife* seconds. 3986 4763 3987 < (= srvthreads* nil threadlimit* 50 threadlife* 30) 3988 < 3989 < ; Could auto-throttle ips, e.g. if one has more than x% of recent requests. 3990 < 3991 < (= requests* 0 requests/ip* (table) throttle-ips* (table) throttle-time* 60) 3992 < 3993 < (def handle-request (s (o life threadlife*)) 3994 < (if (< (len (= srvthreads* (rem dead srvthreads*))) 3995 < threadlimit*) 3996 < (with ((i o ip) (socket-accept s)) 3997 < (++ requests*) 3998 < (= (requests/ip* ip) (+ 1 (or (requests/ip* ip) 0))) 3999 < (let th (thread (fn () 4000 < (if (throttle-ips* ip) (sleep (rand throttle-time*))) 4001 < (handle-request-thread i o ip))) 4002 < (push th srvthreads*) 4003 < (thread (fn () 4004 < (sleep life) 4005 < (unless (dead th) (prn "srv thread took too long")) 4006 < (break-thread th) 4007 < (close o) 4008 < (close i))))) 4009 < (sleep .2))) 4764 > (= threadlife* 30 requests* 0 requests/ip* (table) 4765 > throttle-ips* (table) ignore-ips* (table) spurned* (table)) 4766 > 4767 > (def handle-request (s breaksrv) 4768 > (if breaksrv 4769 > (handle-request-1 s) 4770 > (errsafe (handle-request-1 s)))) 4771 > 4772 > (def handle-request-1 (s) 4773 > (let (i o ip) (socket-accept s) 4774 > (if (and (or (ignore-ips* ip) (abusive-ip ip)) 4775 > (++ (spurned* ip 0))) 4776 > (force-close i o) 4777 > (do (++ requests*) 4778 > (++ (requests/ip* ip 0)) 4779 > (with (th1 nil th2 nil) 4780 > (= th1 (thread 4781 > (after (handle-request-thread i o ip) 4782 > (close i o) 4783 > (kill-thread th2)))) 4784 > (= th2 (thread 4785 > (sleep threadlife*) 4786 > (unless (dead th1) 4787 > (prn "srv thread took too long for " ip)) 4788 > (break-thread th1) 4789 > (force-close i o)))))))) 4790 > 4791 > ; Returns true if ip has made req-limit* requests in less than 4792 > ; req-window* seconds. If an ip is throttled, only 1 request is 4793 > ; allowed per req-window* seconds. If an ip makes req-limit* 4794 > ; requests in less than dos-window* seconds, it is a treated as a DoS 4795 > ; attack and put in ignore-ips* (for this server invocation). 4796 > 4797 > ; To adjust this while running, adjust the req-window* time, not 4798 > ; req-limit*, because algorithm doesn't enforce decreases in the latter. 4799 > 4800 > (= req-times* (table) req-limit* 30 req-window* 10 dos-window* 2) 4010 4801 4802 > (def abusive-ip (ip) 4803 > (and (only.> (requests/ip* ip) 250) 4804 > (let now (seconds) 4805 > (do1 (if (req-times* ip) 4806 > (and (>= (qlen (req-times* ip)) 4807 > (if (throttle-ips* ip) 1 req-limit*)) 4808 > (let dt (- now (deq (req-times* ip))) 4809 > (if (< dt dos-window*) (set (ignore-ips* ip))) 4810 > (< dt req-window*))) 4811 > (do (= (req-times* ip) (queue)) 4812 > nil)) 4813 > (enq now (req-times* ip)))))) 4814 > 4011 4815 (def handle-request-thread (i o ip) 4012 < (with (nls 0 lines nil line nil responded nil) 4816 > (with (nls 0 lines nil line nil responded nil t0 (msec)) 4013 4817 (after 4014 < (whilet c (and (no responded) (readc i)) 4818 > (whilet c (unless responded (readc i)) 4015 4819 (if srv-noisy* (pr c)) 4016 4820 (if (is c #\newline) 4017 4821 (if (is (++ nls) 2) 4018 < (do (let (type op args n cooks) (parseheader (rev lines)) 4019 < (srvlog 'srv ip type op cooks) 4020 < (case type 4021 < get (respond o op args cooks ip) 4022 < post (handle-post i o op n cooks ip) 4023 < (respond-err o "Unknown request: " (car lines)))) 4024 < (= responded t)) 4025 < (do (push (coerce (rev line) 'string) lines) 4026 < (= line nil))) 4822 > (let (type op args n cooks) (parseheader (rev lines)) 4823 > (let t1 (msec) 4824 > (case type 4825 > get (respond o op args cooks ip) 4826 > post (handle-post i o op args n cooks ip) 4827 > (respond-err o "Unknown request: " (car lines))) 4828 > (log-request type op args cooks ip t0 t1) 4829 > (set responded))) 4830 > (do (push (string (rev line)) lines) 4831 > (wipe line))) 4027 4832 (unless (is c #\return) 4028 4833 (push c line) 4029 4834 (= nls 0)))) 4030 < (close o) 4031 < (close i))) 4835 > (close i o))) 4032 4836 (harvest-fnids)) 4033 4837 4838 > (def log-request (type op args cooks ip t0 t1) 4839 > (with (parsetime (- t1 t0) respondtime (- (msec) t1)) 4840 > (srvlog 'srv ip 4841 > parsetime 4842 > respondtime 4843 > (if (> (+ parsetime respondtime) 1000) "***" "") 4844 > type 4845 > op 4846 > (let arg1 (car args) 4847 > (if (caris arg1 "fnid") "" arg1)) 4848 > cooks))) 4849 > 4034 4850 ; Could ignore return chars (which come from textarea fields) here by 4035 4851 ; (unless (is c #\return) (push c line)) 4036 4852 4037 < (def handle-post (i o op n cooks ip) 4853 > (def handle-post (i o op args n cooks ip) 4038 4854 (if srv-noisy* (pr "Post Contents: ")) 4039 4855 (if (no n) 4040 4856 (respond-err o "Post request without Content-Length.") 4041 4857 (let line nil 4042 4858 (whilet c (and (> n 0) (readc i)) 4043 4859 (if srv-noisy* (pr c)) 4044 4860 (-- n) 4045 4861 (push c line)) 4046 4862 (if srv-noisy* (pr "\n\n")) 4047 < (respond o op (parseargs (coerce (rev line) 'string)) cooks ip)))) 4863 > (respond o op (+ (parseargs (string (rev line))) args) cooks ip)))) 4048 4864 4049 < (= header* "HTTP/1.0 200 OK 4050 < Content-Type: text/html 4865 > (= header* "HTTP/1.1 200 OK 4866 > Content-Type: text/html; charset=utf-8 4051 4867 Connection: close") 4052 4868 4053 < (= gif-header* "HTTP/1.0 200 OK 4054 < Content-Type: image/gif 4055 < Connection: close") 4869 > (= type-header* (table)) 4870 > 4871 > (def gen-type-header (ctype) 4872 > (+ "HTTP/1.0 200 OK 4873 > Content-Type: " 4874 > ctype 4875 > " 4876 > Connection: close")) 4056 4877 4878 > (map (fn ((k v)) (= (type-header* k) (gen-type-header v))) 4879 > '((gif "image/gif") 4880 > (jpg "image/jpeg") 4881 > (png "image/png") 4882 > (text/html "text/html; charset=utf-8"))) 4883 > 4057 4884 (= rdheader* "HTTP/1.0 302 Moved") 4058 4885 4059 < (= srvops* (table) redirectors* (table) optimes* (table)) 4886 > (= srvops* (table) redirector* (table) optimes* (table) opcounts* (table)) 4060 4887 4061 4888 (def save-optime (name elapsed) 4889 > ; this is the place to put a/b testing 4890 > ; toggle a flag and push elapsed into one of two lists 4891 > (++ (opcounts* name 0)) 4062 4892 (unless (optimes* name) (= (optimes* name) (queue))) 4063 4893 (enq-limit elapsed (optimes* name) 1000)) 4064 4894 4065 4895 ; For ops that want to add their own headers. They must thus remember 4066 4896 ; to prn a blank line before anything meant to be part of the page. 4067 4897 4068 4898 (mac defop-raw (name parms . body) 4069 4899 (w/uniq t1 4070 4900 `(= (srvops* ',name) 4071 4901 (fn ,parms 4072 4902 (let ,t1 (msec) 4073 4903 (do1 (do ,@body) 4074 4904 (save-optime ',name (- (msec) ,t1)))))))) 4075 4905 4076 4906 (mac defopr-raw (name parms . body) 4077 < `(= (redirectors* ',name) t 4078 < (srvops* ',name) (fn ,parms ,@body))) 4907 > `(= (redirector* ',name) t 4908 > (srvops* ',name) (fn ,parms ,@body))) 4079 4909 4080 4910 (mac defop (name parm . body) 4081 4911 (w/uniq gs 4082 < `(defop-raw ,name (,gs ,parm) 4083 < (w/stdout ,gs (prn) ,@body)))) 4912 > `(do (wipe (redirector* ',name)) 4913 > (defop-raw ,name (,gs ,parm) 4914 > (w/stdout ,gs (prn) ,@body))))) 4084 4915 4085 4916 ; Defines op as a redirector. Its retval is new location. 4086 4917 4087 4918 (mac defopr (name parm . body) 4088 4919 (w/uniq gs 4089 < `(do (t! (redirectors* ',name)) 4920 > `(do (set (redirector* ',name)) 4090 4921 (defop-raw ,name (,gs ,parm) 4091 4922 ,@body)))) 4092 4923 4093 4924 ;(mac testop (name . args) `((srvops* ',name) ,@args)) 4094 4925 4095 4926 (deftem request 4096 4927 args nil 4097 4928 cooks nil 4098 4929 ip nil) 4099 4930 4100 < (= unknown-msg* "Unknown operator.") 4931 > (= unknown-msg* "Unknown." max-age* (table) static-max-age* nil) 4101 4932 4102 4933 (def respond (str op args cooks ip) 4103 4934 (w/stdout str 4104 < (if (gifname op) 4105 < (do (prn gif-header*) 4106 < (prn) 4107 < (w/infile i (coerce op 'string) 4108 < (whilet b (readb i) 4109 < (writeb b str)))) 4110 < (aif (srvops* op) 4111 < (let req (inst 'request 'args args 'cooks cooks 'ip ip) 4112 < (if (redirectors* op) 4113 < (do (prn rdheader*) 4114 < (let loc (it str req) ; may write to str, e.g. cookies 4115 < (prn "Location: " loc)) 4116 < (prn)) 4117 < (do (prn header*) 4118 < (it str req)))) 4119 < (respond-err str unknown-msg*))))) 4935 > (iflet f (srvops* op) 4936 > (let req (inst 'request 'args args 'cooks cooks 'ip ip) 4937 > (if (redirector* op) 4938 > (do (prn rdheader*) 4939 > (prn "Location: " (f str req)) 4940 > (prn)) 4941 > (do (prn header*) 4942 > (awhen (max-age* op) 4943 > (prn "Cache-Control: max-age=" it)) 4944 > (f str req)))) 4945 > (let filetype (static-filetype op) 4946 > (aif (and filetype (file-exists (string staticdir* op))) 4947 > (do (prn (type-header* filetype)) 4948 > (awhen static-max-age* 4949 > (prn "Cache-Control: max-age=" it)) 4950 > (prn) 4951 > (w/infile i it 4952 > (whilet b (readb i) 4953 > (writeb b str)))) 4954 > (respond-err str unknown-msg*)))))) 4120 4955 4121 < (def gifname (sym) 4122 < (let str (coerce sym 'string) 4123 < (and (endmatch ".gif" str) (~find #\/ str)))) 4956 > (def static-filetype (sym) 4957 > (let fname (coerce sym 'string) 4958 > (and (~find #\/ fname) 4959 > (case (downcase (last (check (tokens fname #\.) ~single))) 4960 > "gif" 'gif 4961 > "jpg" 'jpg 4962 > "jpeg" 'jpg 4963 > "png" 'png 4964 > "css" 'text/html 4965 > "txt" 'text/html 4966 > "htm" 'text/html 4967 > "html" 'text/html 4968 > "arc" 'text/html 4969 > )))) 4124 4970 4125 4971 (def respond-err (str msg . args) 4126 4972 (w/stdout str 4127 4973 (prn header*) 4128 4974 (prn) 4129 4975 (apply pr msg args))) 4130 4976 4131 4977 (def parseheader (lines) 4132 4978 (let (type op args) (parseurl (car lines)) 4133 4979 (list type 4134 4980 op 4135 4981 args 4136 4982 (and (is type 'post) 4137 4983 (some (fn (s) 4138 4984 (and (begins s "Content-Length:") 4139 < (coerce (cadr (tokens s)) 'int))) 4985 > (errsafe:coerce (cadr (tokens s)) 'int))) 4140 4986 (cdr lines))) 4141 4987 (some (fn (s) 4142 4988 (and (begins s "Cookie:") 4143 4989 (parsecookies s))) 4144 4990 (cdr lines))))) 4145 4991 4146 4992 ; (parseurl "GET /p1?foo=bar&ug etc") -> (get p1 (("foo" "bar") ("ug"))) 4147 4993 4148 4994 (def parseurl (s) 4149 4995 (let (type url) (tokens s) 4150 4996 (let (base args) (tokens url #\?) 4151 < (list (coerce (downcase type) 'sym) 4152 < (coerce (subseq base 1) 'sym) 4997 > (list (sym (downcase type)) 4998 > (sym (cut base 1)) 4153 4999 (if args 4154 5000 (parseargs args) 4155 5001 nil))))) 4156 5002 4157 < ; don't urldecode field names or anything in cookies; correct? 5003 > ; I don't urldecode field names or anything in cookies; correct? 4158 5004 4159 5005 (def parseargs (s) 4160 5006 (map (fn ((k v)) (list k (urldecode v))) 4161 5007 (map [tokens _ #\=] (tokens s #\&)))) 4162 5008 4163 5009 (def parsecookies (s) 4164 5010 (map [tokens _ #\=] 4165 5011 (cdr (tokens s [or (whitec _) (is _ #\;)])))) 4166 5012 4167 < (def arg (req key) (alref (req 'args) key)) 5013 > (def arg (req key) (alref req!args key)) 4168 5014 4169 5015 ; *** Warning: does not currently urlencode args, so if need to do 4170 5016 ; that replace v with (urlencode v). 4171 5017 4172 5018 (def reassemble-args (req) 4173 < (aif (req 'args) 5019 > (aif req!args 4174 5020 (apply string "?" (intersperse '& 4175 < (map (fn (pair) 4176 < (let (k v) pair 4177 < (string k '= v))) 5021 > (map (fn ((k v)) 5022 > (string k '= v)) 4178 5023 it))) 4179 5024 "")) 4180 5025 4181 5026 (= fns* (table) fnids* nil timed-fnids* nil) 4182 5027 4183 5028 ; count on huge (expt 64 10) size of fnid space to avoid clashes 4184 5029 4185 5030 (def new-fnid () 4186 < (let key (sym (rand-string 10)) 4187 < (if (fns* key) 4188 < (new-fnid) 4189 < key))) 5031 > (check (sym (rand-string 10)) ~fns* (new-fnid))) 4190 5032 4191 5033 (def fnid (f) 4192 5034 (atlet key (new-fnid) 4193 5035 (= (fns* key) f) 4194 5036 (push key fnids*) 4195 5037 key)) 4196 5038 4197 5039 (def timed-fnid (lasts f) 4198 5040 (atlet key (new-fnid) 4199 5041 (= (fns* key) f) 4200 5042 (push (list key (seconds) lasts) timed-fnids*) 4201 5043 key)) 4202 5044 4203 5045 ; Within f, it will be bound to the fn's own fnid. Remember that this is 4204 5046 ; so low-level that need to generate the newline to separate from the headers 4205 5047 ; within the body of f. 4206 5048 4207 5049 (mac afnid (f) 4208 5050 `(atlet it (new-fnid) 4209 5051 (= (fns* it) ,f) 4210 5052 (push it fnids*) 4211 5053 it)) 4212 5054 4213 5055 ;(defop test-afnid req 4214 5056 ; (tag (a href (url-for (afnid (fn (req) (prn) (pr "my fnid is " it))))) 4215 5057 ; (pr "click here"))) 4216 5058 4217 5059 ; To be more sophisticated, instead of killing fnids, could first 4218 5060 ; replace them with fns that tell the server it's harvesting too 4219 5061 ; aggressively if they start to get called. But the right thing to 4220 5062 ; do is estimate what the max no of fnids can be and set the harvest 4221 5063 ; limit there-- beyond that the only solution is to buy more memory. 4222 5064 4223 < (def harvest-fnids ((o n 20000)) 4224 < (when (> (len fns*) n) 4225 < (atomic 4226 < (pull (fn ((id created lasts)) 4227 < (when (> (- (seconds) created) lasts) 4228 < (nil! (fns* id)) 4229 < t)) 4230 < timed-fnids*)) 4231 < (atlet nharvest (truncate (/ n 10)) 4232 < (let (kill keep) (splitn nharvest (rev fnids*)) 5065 > (def harvest-fnids ((o n 50000)) ; was 20000 5066 > (when (len> fns* n) 5067 > (pull (fn ((id created lasts)) 5068 > (when (> (since created) lasts) 5069 > (wipe (fns* id)) 5070 > t)) 5071 > timed-fnids*) 5072 > (atlet nharvest (trunc (/ n 10)) 5073 > (let (kill keep) (split (rev fnids*) nharvest) 4233 5074 (= fnids* (rev keep)) 4234 5075 (each id kill 4235 < (nil! (fns* id))))))) 5076 > (wipe (fns* id))))))) 4236 5077 4237 < (= fnurl* "x" rfnurl* "r" rfnurl2* "y" jfnurl* "a") 5078 > (= fnurl* "/x" rfnurl* "/r" rfnurl2* "/y" jfnurl* "/a") 5079 > 5080 > (= dead-msg* "\nUnknown or expired link.") 4238 5081 4239 5082 (defop-raw x (str req) 4240 < (let id (sym (arg req "fnid")) 4241 < (aif (fns* id) 4242 < (w/stdout str (it req)) 4243 < (w/stdout str (prn) (pr "unknown or expired link"))))) 5083 > (w/stdout str 5084 > (aif (fns* (sym (arg req "fnid"))) 5085 > (it req) 5086 > (pr dead-msg*)))) 4244 5087 4245 5088 (defopr-raw y (str req) 4246 < (let id (sym (arg req "fnid")) 4247 < (aif (fns* id) 4248 < (w/stdout str (it req)) 4249 < "deadlink"))) 5089 > (aif (fns* (sym (arg req "fnid"))) 5090 > (w/stdout str (it req)) 5091 > "deadlink")) 4250 5092 4251 5093 ; For asynchronous calls; discards the page. Would be better to tell 4252 5094 ; the fn not to generate it. 4253 5095 4254 5096 (defop-raw a (str req) 4255 < (let id (sym (arg req "fnid")) 4256 < (aif (fns* id) (tostring (it req))))) 5097 > (aif (fns* (sym (arg req "fnid"))) 5098 > (tostring (it req)))) 4257 5099 4258 5100 (defopr r req 4259 < (let id (sym (arg req "fnid")) 4260 < (aif (fns* id) 4261 < (it req) 4262 < "deadlink"))) 5101 > (aif (fns* (sym (arg req "fnid"))) 5102 > (it req) 5103 > "deadlink")) 4263 5104 4264 5105 (defop deadlink req 4265 < (pr "unknown or expired link")) 5106 > (pr dead-msg*)) 4266 5107 4267 5108 (def url-for (fnid) 4268 5109 (string fnurl* "?fnid=" fnid)) 4269 5110 4270 5111 (def flink (f) 4271 5112 (string fnurl* "?fnid=" (fnid (fn (req) (prn) (f req))))) 4272 5113 4273 < ; couldn't I just say (fnid f) here? 4274 < 4275 5114 (def rflink (f) 4276 < (string rfnurl* "?fnid=" (fnid (fn (req) (f req))))) 5115 > (string rfnurl* "?fnid=" (fnid f))) 4277 5116 4278 5117 ; Since it's just an expr, gensym a parm for (ignored) args. 4279 5118 4280 5119 (mac w/link (expr . body) 4281 < (w/uniq g 4282 < `(tag (a href (flink (fn (,g) ,expr))) 4283 < ,@body))) 5120 > `(tag (a href (flink (fn (,(uniq)) ,expr))) 5121 > ,@body)) 4284 5122 4285 5123 (mac w/rlink (expr . body) 4286 < (w/uniq g 4287 < `(tag (a href (rflink (fn (,g) ,expr))) 4288 < ,@body))) 5124 > `(tag (a href (rflink (fn (,(uniq)) ,expr))) 5125 > ,@body)) 4289 5126 4290 5127 (mac onlink (text . body) 4291 5128 `(w/link (do ,@body) (pr ,text))) 5129 > 5130 > (mac onrlink (text . body) 5131 > `(w/rlink (do ,@body) (pr ,text))) 4292 5132 4293 5133 ; bad to have both flink and linkf; rename flink something like fnid-link 4294 5134 4295 5135 (mac linkf (text parms . body) 4296 5136 `(tag (a href (flink (fn ,parms ,@body))) (pr ,text))) 4297 5137 4298 5138 (mac rlinkf (text parms . body) 4299 5139 `(tag (a href (rflink (fn ,parms ,@body))) (pr ,text))) 4300 5140 4301 5141 ;(defop top req (linkf 'whoami? (req) (pr "I am " (get-user req)))) 4302 5142 4303 5143 ;(defop testf req (w/link (pr "ha ha ha") (pr "laugh"))) 4304 5144 4305 5145 (mac w/link-if (test expr . body) 4306 < (w/uniq g 4307 < `(tag-if ,test (a href (flink (fn (,g) ,expr))) 4308 < ,@body))) 5146 > `(tag-if ,test (a href (flink (fn (,(uniq)) ,expr))) 5147 > ,@body)) 4309 5148 5149 > (def fnid-field (id) 5150 > (gentag input type 'hidden name 'fnid value id)) 5151 > 4310 5152 ; f should be a fn of one arg, which will be http request args. 5153 > 5154 > (def fnform (f bodyfn (o redir)) 5155 > (tag (form method 'post action (if redir rfnurl2* fnurl*)) 5156 > (fnid-field (fnid f)) 5157 > (bodyfn))) 5158 > 4311 5159 ; Could also make a version that uses just an expr, and var capture. 4312 5160 ; Is there a way to ensure user doesn't use "fnid" as a key? 4313 5161 4314 5162 (mac aform (f . body) 4315 < (w/uniq (gi ga) 4316 < `(let ,gi (fnid (fn (,ga) 4317 < (prn) 4318 < (,f ,ga))) 4319 < (tag (form method 'post action fnurl*) 4320 < (gentag input type 'hidden name 'fnid value ,gi) 4321 < ,@body)))) 5163 > (w/uniq ga 5164 > `(tag (form method 'post action fnurl*) 5165 > (fnid-field (fnid (fn (,ga) 5166 > (prn) 5167 > (,f ,ga)))) 5168 > ,@body))) 4322 5169 5170 > ;(defop test1 req 5171 > ; (fnform (fn (req) (prn) (pr req)) 5172 > ; (fn () (single-input "" 'foo 20 "submit")))) 5173 > 5174 > ;(defop test2 req 5175 > ; (aform (fn (req) (pr req)) 5176 > ; (single-input "" 'foo 20 "submit"))) 5177 > 4323 5178 ; Like aform except creates a fnid that will last for lasts seconds 4324 5179 ; (unless the server is restarted). 4325 5180 4326 < (mac timed-aform (lasts f . body) 5181 > (mac taform (lasts f . body) 4327 5182 (w/uniq (gl gf gi ga) 4328 5183 `(withs (,gl ,lasts 4329 < ,gf (fn (,ga) (prn) (,f ,ga)) 4330 < ,gi (if ,gl (timed-fnid ,lasts ,gf) (fnid ,gf))) 5184 > ,gf (fn (,ga) (prn) (,f ,ga))) 4331 5185 (tag (form method 'post action fnurl*) 4332 < (gentag input type 'hidden name 'fnid value ,gi) 5186 > (fnid-field (if ,gl (timed-fnid ,gl ,gf) (fnid ,gf))) 4333 5187 ,@body)))) 4334 5188 4335 5189 (mac arform (f . body) 4336 < (w/uniq gi 4337 < `(let ,gi (fnid ,f) 5190 > `(tag (form method 'post action rfnurl*) 5191 > (fnid-field (fnid ,f)) 5192 > ,@body)) 5193 > 5194 > ; overlong 5195 > 5196 > (mac tarform (lasts f . body) 5197 > (w/uniq (gl gf) 5198 > `(withs (,gl ,lasts ,gf ,f) 4338 5199 (tag (form method 'post action rfnurl*) 4339 < (gentag input type 'hidden name 'fnid value ,gi) 5200 > (fnid-field (if ,gl (timed-fnid ,gl ,gf) (fnid ,gf))) 4340 5201 ,@body)))) 4341 5202 4342 5203 (mac aformh (f . body) 4343 < (w/uniq gi 4344 < `(let ,gi (fnid ,f) 4345 < (tag (form method 'post action fnurl*) 4346 < (gentag input type 'hidden name 'fnid value ,gi) 4347 < ,@body)))) 5204 > `(tag (form method 'post action fnurl*) 5205 > (fnid-field (fnid ,f)) 5206 > ,@body)) 4348 5207 4349 5208 (mac arformh (f . body) 4350 < (w/uniq gi 4351 < `(let ,gi (fnid ,f) 4352 < (tag (form method 'post action rfnurl2*) 4353 < (gentag input type 'hidden name 'fnid value ,gi) 4354 < ,@body)))) 5209 > `(tag (form method 'post action rfnurl2*) 5210 > (fnid-field (fnid ,f)) 5211 > ,@body)) 4355 5212 4356 5213 ; only unique per server invocation 4357 5214 4358 5215 (= unique-ids* (table)) 4359 5216 4360 5217 (def unique-id ((o len 8)) 4361 5218 (let id (sym (rand-string (max 5 len))) 4362 5219 (if (unique-ids* id) 4363 5220 (unique-id) 4364 5221 (= (unique-ids* id) id)))) 4365 5222 4366 < 4367 5223 (def srvlog (type . args) 4368 < (w/appendfile o (string logdir* type "-" (memodate)) 4369 < (w/stdout o (apply prs (seconds) args) (prn)))) 5224 > (w/appendfile o (logfile-name type) 5225 > (w/stdout o (atomic (apply prs (seconds) args) (prn))))) 4370 5226 5227 > (def logfile-name (type) 5228 > (string logdir* type "-" (memodate))) 5229 > 4371 5230 (with (lastasked nil lastval nil) 4372 5231 4373 5232 (def memodate () 4374 5233 (let now (seconds) 4375 5234 (if (or (no lastasked) (> (- now lastasked) 60)) 4376 < (= lastasked now lastval (date)) 5235 > (= lastasked now lastval (datestring)) 4377 5236 lastval))) 4378 5237 4379 5238 ) 4380 5239 4381 < (defop || req 4382 < (pr "It's alive.")) 5240 > (defop || req (pr "It's alive.")) 4383 5241 4384 5242 (defop topips req 4385 5243 (when (admin (get-user req)) 4386 5244 (whitepage 4387 < (spacetable 5245 > (sptab 4388 5246 (each ip (let leaders nil 4389 5247 (maptable (fn (ip n) 4390 5248 (when (> n 100) 4391 5249 (insort (compare > requests/ip*) 4392 5250 ip 4393 5251 leaders))) 4394 5252 requests/ip*) 4395 5253 leaders) 4396 5254 (let n (requests/ip* ip) 4397 < (row ip n (num (* 100 (/ n requests*)) 1)))))))) 5255 > (row ip n (pr (num (* 100 (/ n requests*)) 1))))))))) 4398 5256 4399 < (def ensure-install () 4400 < (ensure-dir arcdir*) 4401 < (ensure-dir logdir*) 4402 < (when (empty hpasswords*) 4403 < (create-acct "frug" "frug") 4404 < (writefile1 'frug adminfile*)) 4405 < (load-userinfo)) 5257 > (defop spurned req 5258 > (when (admin (get-user req)) 5259 > (whitepage 5260 > (sptab 5261 > (map (fn ((ip n)) (row ip n)) 5262 > (sortable spurned*)))))) 5263 > 5264 > ; eventually promote to general util 5265 > 5266 > (def sortable (ht (o f >)) 5267 > (let res nil 5268 > (maptable (fn kv 5269 > (insort (compare f cadr) kv res)) 5270 > ht) 5271 > res)) 5272 > 5273 > 5274 > ; Background Threads 5275 > 5276 > (= bgthreads* (table) pending-bgthreads* nil) 5277 > 5278 > (def new-bgthread (id f sec) 5279 > (aif (bgthreads* id) (break-thread it)) 5280 > (= (bgthreads* id) (new-thread (fn () 5281 > (while t 5282 > (sleep sec) 5283 > (f)))))) 4406 5284 5285 > ; should be a macro for this? 5286 > 5287 > (mac defbg (id sec . body) 5288 > `(do (pull [caris _ ',id] pending-bgthreads*) 5289 > (push (list ',id (fn () ,@body) ,sec) 5290 > pending-bgthreads*))) 5291 > 5292 > 5293 > 5294 > ; Idea: make form fields that know their value type because of 5295 > ; gensymed names, and so the receiving fn gets args that are not 5296 > ; strings but parsed values. 4407 5297 4408 5298 ================================================================================ strings.arc ================================================================================ 4410 5300 ; Matching. Spun off 29 Jul 06. 4411 5301 4412 < ; Arc> (tostring (writec (coerce 133 'char))) 5302 > ; arc> (tostring (writec (coerce 133 'char))) 4413 5303 ; 4414 5304 ;> (define ss (open-output-string)) 4415 5305 ;> (write-char (integer->char 133) ss) 4416 5306 ;> (get-output-string ss) 4417 5307 ;"\u0085" 4418 5308 4419 5309 (def tokens (s (o sep whitec)) 4420 < (let test (if (isa sep 'fn) sep (fn (c) (is c sep))) 5310 > (let test (testify sep) 4421 5311 (let rec (afn (cs toks tok) 4422 5312 (if (no cs) (consif tok toks) 4423 5313 (test (car cs)) (self (cdr cs) (consif tok toks) nil) 4424 5314 (self (cdr cs) toks (cons (car cs) tok)))) 4425 < (rev (map [coerce _ 'string] 4426 < (map rev (rec (coerce s 'cons) nil nil))))))) 5315 > (rev (map [coerce _ 'string] 5316 > (map rev (rec (coerce s 'cons) nil nil))))))) 4427 5317 5318 > ; names of cut, split, halve not optimal 5319 > 5320 > (def halve (s (o sep whitec)) 5321 > (let test (testify sep) 5322 > (let rec (afn (cs tok) 5323 > (if (no cs) (list (rev tok)) 5324 > (test (car cs)) (list cs (rev tok)) 5325 > (self (cdr cs) (cons (car cs) tok)))) 5326 > (rev (map [coerce _ 'string] 5327 > (rec (coerce s 'cons) nil)))))) 5328 > 5329 > ; maybe promote to arc.arc, but if so include a list clause 5330 > 5331 > (def positions (test seq) 5332 > (accum a 5333 > (let f (testify test) 5334 > (forlen i seq 5335 > (if (f (seq i)) (a i)))))) 5336 > 5337 > (def lines (s) 5338 > (accum a 5339 > ((afn ((p . ps)) 5340 > (if ps 5341 > (do (a (rem #\return (cut s (+ p 1) (car ps)))) 5342 > (self ps)) 5343 > (a (cut s (+ p 1))))) 5344 > (cons -1 (positions #\newline s))))) 5345 > 5346 > (def slices (s test) 5347 > (accum a 5348 > ((afn ((p . ps)) 5349 > (if ps 5350 > (do (a (cut s (+ p 1) (car ps))) 5351 > (self ps)) 5352 > (a (cut s (+ p 1))))) 5353 > (cons -1 (positions test s))))) 5354 > 4428 5355 ; > (require (lib "uri-codec.ss" "net")) 4429 5356 ;> (form-urlencoded-decode "x%ce%bbx") 4430 5357 ;"xλx" 4431 5358 4432 5359 ; first byte: 0-7F, 1 char; c2-df 2; e0-ef 3, f0-f4 4. 4433 5360 5361 > ; Fixed for utf8 by pc. 5362 > 4434 5363 (def urldecode (s) 4435 < (tostring 4436 < (forlen i s 4437 < (caselet c (s i) 4438 < #\+ (writec #\space) 4439 < #\% (do (when (> (- (len s) i) 2) 4440 < (let code (coerce (subseq s (+ i 1) (+ i 3)) 4441 < 'int 16) 4442 < (if (> code 126) 4443 < (pr (latin1-hack code)) 4444 < (writec (coerce code 'char))))) 4445 < (++ i 2)) 4446 < (writec c))))) 5364 > (tostring 5365 > (forlen i s 5366 > (caselet c (s i) 5367 > #\+ (writec #\space) 5368 > #\% (do (when (> (- (len s) i) 2) 5369 > (writeb (int (cut s (+ i 1) (+ i 3)) 16))) 5370 > (++ i 2)) 5371 > (writec c))))) 4447 5372 4448 < 4449 < ; Converts utf8 chars between 128 and 255 to ascii char or string. 4450 < ; Not able to convert every char; generates X if can't. Probably not 4451 < ; the ultimate solution. The Right Thing would be to preserve these 4452 < ; chars, instead of downgrading them to ascii. To do that, would have 4453 < ; to convert them to unicode. E.g. ellipsis in unicode is #x2026, 4454 < ; euro sign is #x20ac. 4455 < 4456 < ; In Mzscheme: (display (integer->char #xE9)) 4457 < 4458 < ; Then have to figure out how to print them back out, both in forms 4459 < ; and in pages. Presumably do the reverse translation and &-escape them. 4460 < 4461 < ; For much of this range, unicode and utf8 agree. 233 is e-acute in 4462 < ; both. It's chars like 133 that are a problem. So perhaps as a 4463 < ; start try preserving e.g. 233. 4464 < 4465 < ; This would be faster if I made a macro that translated it into 4466 < ; a hashtable or even string. 4467 < 4468 < (def latin1-hack (i) 4469 < (if (is i 128) "EUR" ; euros 4470 < (is i 133) "..." 4471 < (<= 145 i 146) #\' 4472 < (<= 147 i 148) #\" 4473 < (is i 151) "--" ; long dash 4474 < (is i 154) #\S 4475 < (is i 155) #\> 4476 < (is i 156) "oe" 4477 < (is i 158) #\z 4478 < (is i 159) #\Y 4479 < (is i 162) #\c ; cents 4480 < (is i 163) "GBP" ; pounds 4481 < (is i 165) "JPY" ; yen 4482 < (is i 166) #\| 4483 < (is i 171) "<<" 4484 < (is i 187) ">>" 4485 < (<= 192 i 197) #\A 4486 < (is i 198) "AE" 4487 < (is i 199) #\C 4488 < (<= 200 i 203) #\E 4489 < (<= 204 i 207) #\I 4490 < (is i 209) #\N 4491 < (<= 210 i 214) #\O 4492 < (is i 215) #\x 4493 < (is i 216) #\O 4494 < (<= 217 i 220) #\U 4495 < (is i 221) #\Y 4496 < (is i 223) "ss" 4497 < (<= 224 i 229) #\a 4498 < (is i 230) "ae" 4499 < (is i 231) #\c 4500 < (<= 232 i 235) #\e 4501 < (<= 236 i 239) #\i 4502 < (is i 241) #\n 4503 < (<= 242 i 246) #\o 4504 < (is i 247) #\/ 4505 < (is i 248) #\o 4506 < (<= 249 i 252) #\u 4507 < (in i 253 255) #\y 4508 < #\X 4509 < )) 5373 > (def urlencode (s) 5374 > (tostring 5375 > (each c s 5376 > (writec #\%) 5377 > (let i (int c) 5378 > (if (< i 16) (writec #\0)) 5379 > (pr (coerce i 'string 16)))))) 4510 5380 4511 5381 (mac litmatch (pat string (o start 0)) 4512 5382 (w/uniq (gstring gstart) 4513 5383 `(with (,gstring ,string ,gstart ,start) 4514 5384 (unless (> (+ ,gstart ,(len pat)) (len ,gstring)) 4515 5385 (and ,@(let acc nil 4516 5386 (forlen i pat 4517 5387 (push `(is ,(pat i) (,gstring (+ ,gstart ,i))) 4518 5388 acc)) 4519 5389 (rev acc))))))) 4520 5390 4521 5391 ; litmatch would be cleaner if map worked for string and integer args: 4522 5392 4523 5393 ; ,@(map (fn (n c) 4524 5394 ; `(is ,c (,gstring (+ ,gstart ,n)))) 4525 5395 ; (len pat) 4526 5396 ; pat) 4527 5397 4528 5398 (mac endmatch (pat string) 4529 5399 (w/uniq (gstring glen) 4530 5400 `(withs (,gstring ,string ,glen (len ,gstring)) 4531 5401 (unless (> ,(len pat) (len ,gstring)) 4532 5402 (and ,@(let acc nil 4533 5403 (forlen i pat 4534 5404 (push `(is ,(pat (- (len pat) 1 i)) 4535 5405 (,gstring (- ,glen 1 ,i))) 4536 5406 acc)) 4537 5407 (rev acc))))))) 4538 5408 4539 5409 (def posmatch (pat seq (o start 0)) 4540 5410 (catch 4541 5411 (if (isa pat 'fn) 4542 5412 (for i start (- (len seq) 1) 4543 5413 (when (pat (seq i)) (throw i))) 4544 < (for i start (- (len seq) (- (len pat) 2)) 5414 > (for i start (- (len seq) (len pat)) 4545 5415 (when (headmatch pat seq i) (throw i)))) 4546 5416 nil)) 4547 5417 4548 5418 (def headmatch (pat seq (o start 0)) 4549 5419 (let p (len pat) 4550 5420 ((afn (i) 4551 5421 (or (is i p) 4552 5422 (and (is (pat i) (seq (+ i start))) 4553 5423 (self (+ i 1))))) 4554 5424 0))) 4555 5425 4556 5426 (def begins (seq pat (o start 0)) 4557 < (unless (> (len pat) (- (len seq) start)) 5427 > (unless (len> pat (- (len seq) start)) 4558 5428 (headmatch pat seq start))) 4559 5429 4560 5430 (def subst (new old seq) 4561 5431 (let boundary (+ (- (len seq) (len old)) 1) 4562 5432 (tostring 4563 5433 (forlen i seq 4564 5434 (if (and (< i boundary) (headmatch old seq i)) 4565 5435 (do (++ i (- (len old) 1)) 4566 5436 (pr new)) 4567 5437 (pr (seq i))))))) 4568 5438 4569 5439 (def multisubst (pairs seq) 4570 5440 (tostring 4571 5441 (forlen i seq 4572 5442 (iflet (old new) (find [begins seq (car _) i] pairs) 4573 5443 (do (++ i (- (len old) 1)) 4574 5444 (pr new)) 4575 5445 (pr (seq i)))))) 4576 5446 5447 > ; not a good name 5448 > 4577 5449 (def findsubseq (pat seq (o start 0)) 4578 5450 (if (< (- (len seq) start) (len pat)) 4579 5451 nil 4580 5452 (if (headmatch pat seq start) 4581 5453 start 4582 5454 (findsubseq pat seq (+ start 1))))) 4583 5455 4584 5456 (def blank (s) (~find ~whitec s)) 4585 5457 4586 < ; should make it possible for test to be a literal as well 4587 < 4588 < (def trim (s where (o test whitec)) 4589 < (let p1 (pos [no (test _)] s) 5458 > (def nonblank (s) (unless (blank s) s)) 5459 > 5460 > (def trim (s (o where 'both) (o test whitec)) 5461 > (withs (f (testify test) 5462 > p1 (pos ~f s)) 4590 5463 (if p1 4591 < (subseq s 4592 < (if (in where 'front 'both) p1 0) 4593 < (when (in where 'end 'both) 4594 < (let i (- (len s) 1) 4595 < (while (and (> i p1) (test (s i))) 4596 < (-- i)) 4597 < (+ i 1)))) 5464 > (cut s 5465 > (if (in where 'front 'both) p1 0) 5466 > (when (in where 'end 'both) 5467 > (let i (- (len s) 1) 5468 > (while (and (> i p1) (f (s i))) 5469 > (-- i)) 5470 > (+ i 1)))) 4598 5471 ""))) 4599 5472 4600 < (def num (m (o digits 2) (o trail-zeros nil) (o init-zero nil)) 4601 < (let comma 4602 < (fn (n) 4603 < (tostring 4604 < (map [apply pr (rev _)] 4605 < (rev (intersperse '(#\,) 4606 < (tuples (rev (coerce (string n) 'cons)) 4607 < 3)))))) 4608 < (if (< digits 1) 4609 < (comma (roundup m)) 4610 < (exact m) 4611 < (string (comma m) 4612 < (when (and trail-zeros (> digits 0)) 4613 < (string "." (newstring digits #\0)))) 4614 < (let n (truncate m) 4615 < (string (if (is n 0) (if init-zero 0 "") (comma n)) 4616 < "." 4617 < (withs (rest (string (abs (roundup 4618 < (- (* m (expt 10 digits)) 4619 < (* n (expt 10 digits)))))) 4620 < v2 (string (newstring (- digits (len rest)) #\0) 4621 < rest)) 4622 < (if trail-zeros 4623 < v2 4624 < (trim v2 'end [is _ #\0])))))))) 5473 > (def num (n (o digits 2) (o trail-zeros nil) (o init-zero nil)) 5474 > (withs (comma 5475 > (fn (i) 5476 > (tostring 5477 > (map [apply pr (rev _)] 5478 > (rev (intersperse '(#\,) 5479 > (tuples (rev (coerce (string i) 'cons)) 5480 > 3)))))) 5481 > abrep 5482 > (let a (abs n) 5483 > (if (< digits 1) 5484 > (comma (roundup a)) 5485 > (exact a) 5486 > (string (comma a) 5487 > (when (and trail-zeros (> digits 0)) 5488 > (string "." (newstring digits #\0)))) 5489 > (withs (d (expt 10 digits) 5490 > m (/ (roundup (* a d)) d) 5491 > i (trunc m) 5492 > r (abs (trunc (- (* m d) (* i d))))) 5493 > (+ (if (is i 0) 5494 > (if (or init-zero (is r 0)) "0" "") 5495 > (comma i)) 5496 > (withs (rest (string r) 5497 > padded (+ (newstring (- digits (len rest)) #\0) 5498 > rest) 5499 > final (if trail-zeros 5500 > padded 5501 > (trim padded 'end [is _ #\0]))) 5502 > (string (unless (empty final) ".") 5503 > final))))))) 5504 > (if (and (< n 0) (find [and (digit _) (isnt _ #\0)] abrep)) 5505 > (+ "-" abrep) 5506 > abrep))) 5507 > 5508 > 5509 > ; English 5510 > 5511 > (def pluralize (n str) 5512 > (if (or (is n 1) (single n)) 5513 > str 5514 > (string str "s"))) 4625 5515 5516 > (def plural (n x) 5517 > (string n #\ (pluralize n x))) 4626 5518 5519 > 4627 5520 ; http://www.eki.ee/letter/chardata.cgi?HTML4=1 4628 5521 ; http://jrgraphix.net/research/unicode_blocks.php?block=1 4629 5522 ; http://home.tiscali.nl/t876506/utf8tbl.html 4630 5523 ; http://www.fileformat.info/info/unicode/block/latin_supplement/utf8test.htm 4631 5524 ; http://en.wikipedia.org/wiki/Utf-8 4632 5525 ; http://unicode.org/charts/charindex2.html 4633 5526 ================================================================================ news.arc ================================================================================ 5528 > ; News. 2 Sep 06. 5529 > 5530 > ; to run news: (nsv), then go to http://localhost:8080 5531 > ; put usernames of admins, separated by whitespace, in arc/admins 5532 > 5533 > ; bug: somehow (+ votedir* nil) is getting evaluated. 5534 > 5535 > (declare 'atstrings t) 5536 > 5537 > (= this-site* "My Forum" 5538 > site-url* "http://news.yourdomain.com/" 5539 > parent-url* "http://www.yourdomain.com" 5540 > favicon-url* "" 5541 > site-desc* "What this site is about." ; for rss feed 5542 > site-color* (color 180 180 180) 5543 > border-color* (color 180 180 180) 5544 > prefer-url* t) 5545 > 5546 > 5547 > ; Structures 5548 > 5549 > ; Could add (html) types like choice, yesno to profile fields. But not 5550 > ; as part of deftem, which is defstruct. Need another mac on top of 5551 > ; deftem. Should not need the type specs in user-fields. 5552 > 5553 > (deftem profile 5554 > id nil 5555 > name nil 5556 > created (seconds) 5557 > auth 0 5558 > member nil 5559 > submitted nil 5560 > votes nil ; for now just recent, elts each (time id by sitename dir) 5561 > karma 1 5562 > avg nil 5563 > weight .5 5564 > ignore nil 5565 > email nil 5566 > about nil 5567 > showdead nil 5568 > noprocrast nil 5569 > firstview nil 5570 > lastview nil 5571 > maxvisit 20 5572 > minaway 180 5573 > topcolor nil 5574 > keys nil 5575 > delay 0) 5576 > 5577 > (deftem item 5578 > id nil 5579 > type nil 5580 > by nil 5581 > ip nil 5582 > time (seconds) 5583 > url nil 5584 > title nil 5585 > text nil 5586 > votes nil ; elts each (time ip user type score) 5587 > score 0 5588 > sockvotes 0 5589 > flags nil 5590 > dead nil 5591 > deleted nil 5592 > parts nil 5593 > parent nil 5594 > kids nil 5595 > keys nil) 5596 > 5597 > 5598 > ; Load and Save 5599 > 5600 > (= newsdir* "arc/news/" 5601 > storydir* "arc/news/story/" 5602 > profdir* "arc/news/profile/" 5603 > votedir* "arc/news/vote/") 5604 > 5605 > (= votes* (table) profs* (table)) 5606 > 5607 > (= initload-users* nil) 5608 > 5609 > (def nsv ((o port 8080)) 5610 > (map ensure-dir (list arcdir* newsdir* storydir* votedir* profdir*)) 5611 > (unless stories* (load-items)) 5612 > (if (and initload-users* (empty profs*)) (load-users)) 5613 > (asv port)) 5614 > 5615 > (def load-users () 5616 > (pr "load users: ") 5617 > (noisy-each 100 id (dir profdir*) 5618 > (load-user id))) 5619 > 5620 > ; For some reason vote files occasionally get written out in a 5621 > ; broken way. The nature of the errors (random missing or extra 5622 > ; chars) suggests the bug is lower-level than anything in Arc. 5623 > ; Which unfortunately means all lists written to disk are probably 5624 > ; vulnerable to it, since that's all save-table does. 5625 > 5626 > (def load-user (u) 5627 > (= (votes* u) (load-table (+ votedir* u)) 5628 > (profs* u) (temload 'profile (+ profdir* u))) 5629 > u) 5630 > 5631 > ; Have to check goodname because some user ids come from http requests. 5632 > ; So this is like safe-item. Don't need a sep fn there though. 5633 > 5634 > (def profile (u) 5635 > (or (profs* u) 5636 > (aand (goodname u) 5637 > (file-exists (+ profdir* u)) 5638 > (= (profs* u) (temload 'profile it))))) 5639 > 5640 > (def votes (u) 5641 > (or (votes* u) 5642 > (aand (file-exists (+ votedir* u)) 5643 > (= (votes* u) (load-table it))))) 5644 > 5645 > (def init-user (u) 5646 > (= (votes* u) (table) 5647 > (profs* u) (inst 'profile 'id u)) 5648 > (save-votes u) 5649 > (save-prof u) 5650 > u) 5651 > 5652 > ; Need this because can create users on the server (for other apps) 5653 > ; without setting up places to store their state as news users. 5654 > ; See the admin op in app.arc. So all calls to login-page from the 5655 > ; news app need to call this in the after-login fn. 5656 > 5657 > (def ensure-news-user (u) 5658 > (if (profile u) u (init-user u))) 5659 > 5660 > (def save-votes (u) (save-table (votes* u) (+ votedir* u))) 5661 > 5662 > (def save-prof (u) (save-table (profs* u) (+ profdir* u))) 5663 > 5664 > (mac uvar (u k) `((profile ,u) ',k)) 5665 > 5666 > (mac karma (u) `(uvar ,u karma)) 5667 > (mac ignored (u) `(uvar ,u ignore)) 5668 > 5669 > ; Note that users will now only consider currently loaded users. 5670 > 5671 > (def users ((o f idfn)) 5672 > (keep f (keys profs*))) 5673 > 5674 > (def check-key (u k) 5675 > (and u (mem k (uvar u keys)))) 5676 > 5677 > (def author (u i) (is u i!by)) 5678 > 5679 > 5680 > (= stories* nil comments* nil 5681 > items* (table) url->story* (table) 5682 > maxid* 0 initload* 15000) 5683 > 5684 > ; The dir expression yields stories in order of file creation time 5685 > ; (because arc infile truncates), so could just rev the list instead of 5686 > ; sorting, but sort anyway. 5687 > 5688 > ; Note that stories* etc only include the initloaded (i.e. recent) 5689 > ; ones, plus those created since this server process started. 5690 > 5691 > ; Could be smarter about preloading by keeping track of popular pages. 5692 > 5693 > (def load-items () 5694 > (system (+ "rm " storydir* "*.tmp")) 5695 > (pr "load items: ") 5696 > (with (items (table) 5697 > ids (sort > (map int (dir storydir*)))) 5698 > (if ids (= maxid* (car ids))) 5699 > (noisy-each 100 id (firstn initload* ids) 5700 > (let i (load-item id) 5701 > (push i (items i!type)))) 5702 > (= stories* (rev (merge (compare < !id) items!story items!poll)) 5703 > comments* (rev items!comment)) 5704 > (hook 'initload items)) 5705 > (ensure-topstories)) 5706 > 5707 > (def ensure-topstories () 5708 > (aif (errsafe (readfile1 (+ newsdir* "topstories"))) 5709 > (= ranked-stories* (map item it)) 5710 > (do (prn "ranking stories.") 5711 > (flushout) 5712 > (gen-topstories)))) 5713 > 5714 > (def astory (i) (is i!type 'story)) 5715 > (def acomment (i) (is i!type 'comment)) 5716 > (def apoll (i) (is i!type 'poll)) 5717 > 5718 > (def load-item (id) 5719 > (let i (temload 'item (+ storydir* id)) 5720 > (= (items* id) i) 5721 > (awhen (and (astory&live i) (check i!url ~blank)) 5722 > (register-url i it)) 5723 > i)) 5724 > 5725 > ; Note that duplicates are only prevented of items that have at some 5726 > ; point been loaded. 5727 > 5728 > (def register-url (i url) 5729 > (= (url->story* (canonical-url url)) i!id)) 5730 > 5731 > ; redefined later 5732 > 5733 > (= stemmable-sites* (table)) 5734 > 5735 > (def canonical-url (url) 5736 > (if (stemmable-sites* (sitename url)) 5737 > (cut url 0 (pos #\? url)) 5738 > url)) 5739 > 5740 > (def new-item-id () 5741 > (evtil (++ maxid*) [~file-exists (+ storydir* _)])) 5742 > 5743 > (def item (id) 5744 > (or (items* id) (errsafe:load-item id))) 5745 > 5746 > (def kids (i) (map item i!kids)) 5747 > 5748 > ; For use on external item references (from urls). Checks id is int 5749 > ; because people try e.g. item?id=363/blank.php 5750 > 5751 > (def safe-item (id) 5752 > (ok-id&item (if (isa id 'string) (saferead id) id))) 5753 > 5754 > (def ok-id (id) 5755 > (and (exact id) (<= 1 id maxid*))) 5756 > 5757 > (def arg->item (req key) 5758 > (safe-item:saferead (arg req key))) 5759 > 5760 > (def live (i) (nor i!dead i!deleted)) 5761 > 5762 > (def save-item (i) (save-table i (+ storydir* i!id))) 5763 > 5764 > (def kill (i how) 5765 > (unless i!dead 5766 > (log-kill i how) 5767 > (wipe (comment-cache* i!id)) 5768 > (set i!dead) 5769 > (save-item i))) 5770 > 5771 > (= kill-log* nil) 5772 > 5773 > (def log-kill (i how) 5774 > (push (list i!id how) kill-log*)) 5775 > 5776 > (mac each-loaded-item (var . body) 5777 > (w/uniq g 5778 > `(let ,g nil 5779 > (loop (= ,g maxid*) (> ,g 0) (-- ,g) 5780 > (whenlet ,var (items* ,g) 5781 > ,@body))))) 5782 > 5783 > (def loaded-items (test) 5784 > (accum a (each-loaded-item i (test&a i)))) 5785 > 5786 > (def newslog args (apply srvlog 'news args)) 5787 > 5788 > 5789 > ; Ranking 5790 > 5791 > ; Votes divided by the age in hours to the gravityth power. 5792 > ; Would be interesting to scale gravity in a slider. 5793 > 5794 > (= gravity* 1.8 timebase* 120 front-threshold* 1 5795 > nourl-factor* .4 lightweight-factor* .3 ) 5796 > 5797 > (def frontpage-rank (s (o scorefn realscore) (o gravity gravity*)) 5798 > (* (/ (let base (- (scorefn s) 1) 5799 > (if (> base 0) (expt base .8) base)) 5800 > (expt (/ (+ (item-age s) timebase*) 60) gravity)) 5801 > (if (no (in s!type 'story 'poll)) .5 5802 > (blank s!url) nourl-factor* 5803 > (lightweight s) (min lightweight-factor* 5804 > (contro-factor s)) 5805 > (contro-factor s)))) 5806 > 5807 > (def contro-factor (s) 5808 > (aif (check (visible-family nil s) [> _ 20]) 5809 > (min 1 (expt (/ (realscore s) it) 2)) 5810 > 1)) 5811 > 5812 > (def realscore (i) (- i!score i!sockvotes)) 5813 > 5814 > (disktable lightweights* (+ newsdir* "lightweights")) 5815 > 5816 > (def lightweight (s) 5817 > (or s!dead 5818 > (mem 'rally s!keys) ; title is a rallying cry 5819 > (mem 'image s!keys) ; post is mainly image(s) 5820 > (lightweights* (sitename s!url)) 5821 > (lightweight-url s!url))) 5822 > 5823 > (defmemo lightweight-url (url) 5824 > (in (downcase (last (tokens url #\.))) "png" "jpg" "jpeg")) 5825 > 5826 > (def item-age (i) (minutes-since i!time)) 5827 > 5828 > (def user-age (u) (minutes-since (uvar u created))) 5829 > 5830 > ; Only looks at the 1000 most recent stories, which might one day be a 5831 > ; problem if there is massive spam. 5832 > 5833 > (def gen-topstories () 5834 > (= ranked-stories* (rank-stories 180 1000 (memo frontpage-rank)))) 5835 > 5836 > (def save-topstories () 5837 > (writefile (map !id (firstn 180 ranked-stories*)) 5838 > (+ newsdir* "topstories"))) 5839 > 5840 > (def rank-stories (n consider scorefn) 5841 > (bestn n (compare > scorefn) (latest-items metastory nil consider))) 5842 > 5843 > ; With virtual lists the above call to latest-items could be simply: 5844 > ; (map item (retrieve consider metastory:item (gen maxid* [- _ 1]))) 5845 > 5846 > (def latest-items (test (o stop) (o n)) 5847 > (accum a 5848 > (catch 5849 > (down id maxid* 1 5850 > (let i (item id) 5851 > (if (or (and stop (stop i)) (and n (<= n 0))) 5852 > (throw)) 5853 > (when (test i) 5854 > (a i) 5855 > (if n (-- n)))))))) 5856 > 5857 > ; redefined later 5858 > 5859 > (def metastory (i) (and i (in i!type 'story 'poll))) 5860 > 5861 > (def adjust-rank (s (o scorefn frontpage-rank)) 5862 > (insortnew (compare > (memo scorefn)) s ranked-stories*) 5863 > (save-topstories)) 5864 > 5865 > ; If something rose high then stopped getting votes, its score would 5866 > ; decline but it would stay near the top. Newly inserted stories would 5867 > ; thus get stuck in front of it. I avoid this by regularly adjusting 5868 > ; the rank of a random top story. 5869 > 5870 > (defbg rerank-random 30 (rerank-random)) 5871 > 5872 > (def rerank-random () 5873 > (when ranked-stories* 5874 > (adjust-rank (ranked-stories* (rand (min 50 (len ranked-stories*))))))) 5875 > 5876 > (def topstories (user n (o threshold front-threshold*)) 5877 > (retrieve n 5878 > [and (>= (realscore _) threshold) (cansee user _)] 5879 > ranked-stories*)) 5880 > 5881 > (= max-delay* 10) 5882 > 5883 > (def cansee (user i) 5884 > (if i!deleted (admin user) 5885 > i!dead (or (author user i) (seesdead user)) 5886 > (delayed i) (author user i) 5887 > t)) 5888 > 5889 > (let mature (table) 5890 > (def delayed (i) 5891 > (and (no (mature i!id)) 5892 > (acomment i) 5893 > (or (< (item-age i) (min max-delay* (uvar i!by delay))) 5894 > (do (set (mature i!id)) 5895 > nil))))) 5896 > 5897 > (def seesdead (user) 5898 > (or (and user (uvar user showdead) (no (ignored user))) 5899 > (editor user))) 5900 > 5901 > (def visible (user is) 5902 > (keep [cansee user _] is)) 5903 > 5904 > (def cansee-descendant (user c) 5905 > (or (cansee user c) 5906 > (some [cansee-descendant user (item _)] 5907 > c!kids))) 5908 > 5909 > (def editor (u) 5910 > (and u (or (admin u) (> (uvar u auth) 0)))) 5911 > 5912 > (def member (u) 5913 > (and u (or (admin u) (uvar u member)))) 5914 > 5915 > 5916 > ; Page Layout 5917 > 5918 > (= up-url* "grayarrow.gif" down-url* "graydown.gif" logo-url* "arc.png") 5919 > 5920 > (defopr favicon.ico req favicon-url*) 5921 > 5922 > ; redefined later 5923 > 5924 > (def gen-css-url () 5925 > (prn "")) 5926 > 5927 > (mac npage (title . body) 5928 > `(tag html 5929 > (tag head 5930 > (gen-css-url) 5931 > (prn "") 5932 > (tag script (pr votejs*)) 5933 > (tag title (pr ,title))) 5934 > (tag body 5935 > (center 5936 > (tag (table border 0 cellpadding 0 cellspacing 0 width "85%" 5937 > bgcolor sand) 5938 > ,@body))))) 5939 > 5940 > (= pagefns* nil) 5941 > 5942 > (mac fulltop (user lid label title whence . body) 5943 > (w/uniq (gu gi gl gt gw) 5944 > `(with (,gu ,user ,gi ,lid ,gl ,label ,gt ,title ,gw ,whence) 5945 > (npage (+ this-site* (if ,gt (+ bar* ,gt) "")) 5946 > (if (check-procrast ,gu) 5947 > (do (pagetop 'full ,gi ,gl ,gt ,gu ,gw) 5948 > (hook 'page ,gu ,gl) 5949 > ,@body) 5950 > (row (procrast-msg ,gu ,gw))))))) 5951 > 5952 > (mac longpage (user t1 lid label title whence . body) 5953 > (w/uniq (gu gt gi) 5954 > `(with (,gu ,user ,gt ,t1 ,gi ,lid) 5955 > (fulltop ,gu ,gi ,label ,title ,whence 5956 > (trtd ,@body) 5957 > (trtd (vspace 10) 5958 > (color-stripe (main-color ,gu)) 5959 > (br) 5960 > (center 5961 > (hook 'longfoot) 5962 > (admin-bar ,gu (- (msec) ,gt) ,whence))))))) 5963 > 5964 > (def admin-bar (user elapsed whence) 5965 > (when (admin user) 5966 > (br2) 5967 > (w/bars 5968 > (pr (len items*) "/" maxid* " loaded") 5969 > (pr (round (/ (memory) 1000000)) " mb") 5970 > (pr elapsed " msec") 5971 > (link "settings" "newsadmin") 5972 > (hook 'admin-bar user whence)))) 5973 > 5974 > (def color-stripe (c) 5975 > (tag (table width "100%" cellspacing 0 cellpadding 1) 5976 > (tr (tdcolor c)))) 5977 > 5978 > (mac shortpage (user lid label title whence . body) 5979 > `(fulltop ,user ,lid ,label ,title ,whence 5980 > (trtd ,@body))) 5981 > 5982 > (mac minipage (label . body) 5983 > `(npage (+ this-site* bar* ,label) 5984 > (pagetop nil nil ,label) 5985 > (trtd ,@body))) 5986 > 5987 > (def msgpage (user msg (o title)) 5988 > (minipage (or title "Message") 5989 > (spanclass admin 5990 > (center (if (len> msg 80) 5991 > (widtable 500 msg) 5992 > (pr msg)))) 5993 > (br2))) 5994 > 5995 > (= (max-age* 'news.css) 86400) ; cache css in browser for 1 day 5996 > 5997 > ; turn off server caching via (= caching* 0) or won't see changes 5998 > 5999 > (defop news.css req 6000 > (pr " 6001 > body { font-family:Verdana; font-size:10pt; color:#828282; } 6002 > td { font-family:Verdana; font-size:10pt; color:#828282; } 6003 > 6004 > .admin td { font-family:Verdana; font-size:8.5pt; color:#000000; } 6005 > .subtext td { font-family:Verdana; font-size: 7pt; color:#828282; } 6006 > 6007 > input { font-family:Courier; font-size:10pt; color:#000000; } 6008 > input[type=\"submit\"] { font-family:Verdana; } 6009 > textarea { font-family:Courier; font-size:10pt; color:#000000; } 6010 > 6011 > a:link { color:#000000; text-decoration:none; } 6012 > a:visited { color:#828282; text-decoration:none; } 6013 > 6014 > .default { font-family:Verdana; font-size: 10pt; color:#828282; } 6015 > .admin { font-family:Verdana; font-size:8.5pt; color:#000000; } 6016 > .title { font-family:Verdana; font-size: 10pt; color:#828282; } 6017 > .adtitle { font-family:Verdana; font-size: 9pt; color:#828282; } 6018 > .subtext { font-family:Verdana; font-size: 7pt; color:#828282; } 6019 > .yclinks { font-family:Verdana; font-size: 8pt; color:#828282; } 6020 > .pagetop { font-family:Verdana; font-size: 10pt; color:#222222; } 6021 > .comhead { font-family:Verdana; font-size: 8pt; color:#828282; } 6022 > .comment { font-family:Verdana; font-size: 9pt; } 6023 > .dead { font-family:Verdana; font-size: 9pt; color:#dddddd; } 6024 > 6025 > .comment a:link, .comment a:visited { text-decoration:underline;} 6026 > .dead a:link, .dead a:visited { color:#dddddd; } 6027 > .pagetop a:visited { color:#000000;} 6028 > .topsel a:link, .topsel a:visited { color:#ffffff; } 6029 > 6030 > .subtext a:link, .subtext a:visited { color:#828282; } 6031 > .subtext a:hover { text-decoration:underline; } 6032 > 6033 > .comhead a:link, .subtext a:visited { color:#828282; } 6034 > .comhead a:hover { text-decoration:underline; } 6035 > 6036 > .default p { margin-top: 8px; margin-bottom: 0px; } 6037 > 6038 > .pagebreak {page-break-before:always} 6039 > 6040 > pre { overflow: auto; padding: 2px; max-width:600px; } 6041 > pre:hover {overflow:auto} ")) 6042 > 6043 > ; only need pre padding because of a bug in Mac Firefox 6044 > 6045 > ; Without setting the bottom margin of p tags to 0, 1- and n-para comments 6046 > ; have different space at the bottom. This solution suggested by Devin. 6047 > ; Really am using p tags wrong (as separators rather than wrappers) and the 6048 > ; correct thing to do would be to wrap each para in

. Then whatever 6049 > ; I set the bottom spacing to, it would be the same no matter how many paras 6050 > ; in a comment. In this case by setting the bottom spacing of p to 0, I'm 6051 > ; making it the same as no p, which is what the first para has. 6052 > 6053 > ; supplied by pb 6054 > ;.vote { padding-left:2px; vertical-align:top; } 6055 > ;.comment { margin-top:1ex; margin-bottom:1ex; color:black; } 6056 > ;.vote IMG { border:0; margin: 3px 2px 3px 2px; } 6057 > ;.reply { font-size:smaller; text-decoration:underline !important; } 6058 > 6059 > (= votejs* " 6060 > function byId(id) { 6061 > return document.getElementById(id); 6062 > } 6063 > 6064 > function vote(node) { 6065 > var v = node.id.split(/_/); // {'up', '123'} 6066 > var item = v[1]; 6067 > 6068 > // adjust score 6069 > var score = byId('score_' + item); 6070 > var newscore = parseInt(score.innerHTML) + (v[0] == 'up' ? 1 : -1); 6071 > score.innerHTML = newscore + (newscore == 1 ? ' point' : ' points'); 6072 > 6073 > // hide arrows 6074 > byId('up_' + item).style.visibility = 'hidden'; 6075 > byId('down_' + item).style.visibility = 'hidden'; 6076 > 6077 > // ping server 6078 > var ping = new Image(); 6079 > ping.src = node.href; 6080 > 6081 > return false; // cancel browser nav 6082 > } ") 6083 > 6084 > 6085 > ; Page top 6086 > 6087 > (= sand (color 246 246 239) textgray (gray 130)) 6088 > 6089 > (def main-color (user) 6090 > (aif (and user (uvar user topcolor)) 6091 > (hex>color it) 6092 > site-color*)) 6093 > 6094 > (def pagetop (switch lid label (o title) (o user) (o whence)) 6095 > ; (tr (tdcolor black (vspace 5))) 6096 > (tr (tdcolor (main-color user) 6097 > (tag (table border 0 cellpadding 0 cellspacing 0 width "100%" 6098 > style "padding:2px") 6099 > (tr (gen-logo) 6100 > (when (is switch 'full) 6101 > (tag (td style "line-height:12pt; height:10px;") 6102 > (spanclass pagetop 6103 > (tag b (link this-site* "news")) 6104 > (hspace 10) 6105 > (toprow user label)))) 6106 > (if (is switch 'full) 6107 > (tag (td style "text-align:right;padding-right:4px;") 6108 > (spanclass pagetop (topright user whence))) 6109 > (tag (td style "line-height:12pt; height:10px;") 6110 > (spanclass pagetop (prbold label)))))))) 6111 > (map [_ user] pagefns*) 6112 > (spacerow 10)) 6113 > 6114 > (def gen-logo () 6115 > (tag (td style "width:18px;padding-right:4px") 6116 > (tag (a href parent-url*) 6117 > (tag (img src logo-url* width 18 height 18 6118 > style "border:1px #@(hexrep border-color*) solid;"))))) 6119 > 6120 > (= toplabels* '(nil "welcome" "new" "threads" "comments" "leaders" "*")) 6121 > 6122 > ; redefined later 6123 > 6124 > (= welcome-url* "welcome") 6125 > 6126 > (def toprow (user label) 6127 > (w/bars 6128 > (when (noob user) 6129 > (toplink "welcome" welcome-url* label)) 6130 > (toplink "new" "newest" label) 6131 > (when user 6132 > (toplink "threads" (threads-url user) label)) 6133 > (toplink "comments" "newcomments" label) 6134 > (toplink "leaders" "leaders" label) 6135 > (hook 'toprow user label) 6136 > (link "submit") 6137 > (unless (mem label toplabels*) 6138 > (fontcolor white (pr label))))) 6139 > 6140 > (def toplink (name dest label) 6141 > (tag-if (is name label) (span class 'topsel) 6142 > (link name dest))) 6143 > 6144 > (def topright (user whence (o showkarma t)) 6145 > (when user 6146 > (userlink user user nil) 6147 > (when showkarma (pr " (@(karma user))")) 6148 > (pr " | ")) 6149 > (if user 6150 > (rlinkf 'logout (req) 6151 > (when-umatch/r user req 6152 > (logout-user user) 6153 > whence)) 6154 > (onlink "login" 6155 > (login-page 'both nil 6156 > (list (fn (u ip) 6157 > (ensure-news-user u) 6158 > (newslog ip u 'top-login)) 6159 > whence))))) 6160 > 6161 > (def noob (user) 6162 > (and user (< (days-since (uvar user created)) 1))) 6163 > 6164 > 6165 > ; News-Specific Defop Variants 6166 > 6167 > (mac defopt (name parm test msg . body) 6168 > `(defop ,name ,parm 6169 > (if (,test (get-user ,parm)) 6170 > (do ,@body) 6171 > (login-page 'both (+ "Please log in" ,msg ".") 6172 > (list (fn (u ip) (ensure-news-user u)) 6173 > (string ',name (reassemble-args ,parm))))))) 6174 > 6175 > (mac defopg (name parm . body) 6176 > `(defopt ,name ,parm idfn "" ,@body)) 6177 > 6178 > (mac defope (name parm . body) 6179 > `(defopt ,name ,parm editor " as an editor" ,@body)) 6180 > 6181 > (mac defopa (name parm . body) 6182 > `(defopt ,name ,parm admin " as an administrator" ,@body)) 6183 > 6184 > (mac opexpand (definer name parms . body) 6185 > (w/uniq gr 6186 > `(,definer ,name ,gr 6187 > (with (user (get-user ,gr) ip (,gr 'ip)) 6188 > (with ,(and parms (mappend [list _ (list 'arg gr (string _))] 6189 > parms)) 6190 > (newslog ip user ',name ,@parms) 6191 > ,@body))))) 6192 > 6193 > (= newsop-names* nil) 6194 > 6195 > (mac newsop args 6196 > `(do (pushnew ',(car args) newsop-names*) 6197 > (opexpand defop ,@args))) 6198 > 6199 > (mac adop (name parms . body) 6200 > (w/uniq g 6201 > `(opexpand defopa ,name ,parms 6202 > (let ,g (string ',name) 6203 > (shortpage user nil ,g ,g ,g 6204 > ,@body))))) 6205 > 6206 > (mac edop (name parms . body) 6207 > (w/uniq g 6208 > `(opexpand defope ,name ,parms 6209 > (let ,g (string ',name) 6210 > (shortpage user nil ,g ,g ,g 6211 > ,@body))))) 6212 > 6213 > 6214 > ; News Admin 6215 > 6216 > (defopa newsadmin req 6217 > (let user (get-user req) 6218 > (newslog req!ip user 'newsadmin) 6219 > (newsadmin-page user))) 6220 > 6221 > ; Note that caching* is reset to val in source when restart server. 6222 > 6223 > (def nad-fields () 6224 > `((num caching ,caching* t t) 6225 > (bigtoks comment-kill ,comment-kill* t t) 6226 > (bigtoks comment-ignore ,comment-ignore* t t) 6227 > (bigtoks lightweights ,(sort < (keys lightweights*)) t t))) 6228 > 6229 > ; Need a util like vars-form for a collection of variables. 6230 > ; Or could generalize vars-form to think of places (in the setf sense). 6231 > 6232 > (def newsadmin-page (user) 6233 > (shortpage user nil nil "newsadmin" "newsadmin" 6234 > (vars-form user 6235 > (nad-fields) 6236 > (fn (name val) 6237 > (case name 6238 > caching (= caching* val) 6239 > comment-kill (todisk comment-kill* val) 6240 > comment-ignore (todisk comment-ignore* val) 6241 > lightweights (todisk lightweights* (memtable val)) 6242 > )) 6243 > (fn () (newsadmin-page user))) 6244 > (br2) 6245 > (aform (fn (req) 6246 > (with (user (get-user req) subject (arg req "id")) 6247 > (if (profile subject) 6248 > (do (killallby subject) 6249 > (submitted-page user subject)) 6250 > (admin&newsadmin-page user)))) 6251 > (single-input "" 'id 20 "kill all by")) 6252 > (br2) 6253 > (aform (fn (req) 6254 > (let user (get-user req) 6255 > (set-ip-ban user (arg req "ip") t) 6256 > (admin&newsadmin-page user))) 6257 > (single-input "" 'ip 20 "ban ip")))) 6258 > 6259 > 6260 > ; Users 6261 > 6262 > (newsop user (id) 6263 > (if (only.profile id) 6264 > (user-page user id) 6265 > (pr "No such user."))) 6266 > 6267 > (def user-page (user subject) 6268 > (let here (user-url subject) 6269 > (shortpage user nil nil (+ "Profile: " subject) here 6270 > (profile-form user subject) 6271 > (br2) 6272 > (when (some astory:item (uvar subject submitted)) 6273 > (underlink "submissions" (submitted-url subject))) 6274 > (when (some acomment:item (uvar subject submitted)) 6275 > (sp) 6276 > (underlink "comments" (threads-url subject))) 6277 > (hook 'user user subject)))) 6278 > 6279 > (def profile-form (user subject) 6280 > (let prof (profile subject) 6281 > (vars-form user 6282 > (user-fields user subject) 6283 > (fn (name val) 6284 > (when (and (is name 'ignore) val (no prof!ignore)) 6285 > (log-ignore user subject 'profile)) 6286 > (= (prof name) val)) 6287 > (fn () (save-prof subject) 6288 > (user-page user subject))))) 6289 > 6290 > (= topcolor-threshold* 250) 6291 > 6292 > (def user-fields (user subject) 6293 > (withs (e (editor user) 6294 > a (admin user) 6295 > w (is user subject) 6296 > k (and w (> (karma user) topcolor-threshold*)) 6297 > u (or a w) 6298 > m (or a (and (member user) w)) 6299 > p (profile subject)) 6300 > `((string user ,subject t nil) 6301 > (string name ,(p 'name) ,m ,m) 6302 > (string created ,(text-age:user-age subject) t nil) 6303 > (string password ,(resetpw-link) ,w nil) 6304 > (string saved ,(saved-link user subject) ,u nil) 6305 > (int auth ,(p 'auth) ,e ,a) 6306 > (yesno member ,(p 'member) ,a ,a) 6307 > (posint karma ,(p 'karma) t ,a) 6308 > (num avg ,(p 'avg) ,a nil) 6309 > (yesno ignore ,(p 'ignore) ,e ,e) 6310 > (num weight ,(p 'weight) ,a ,a) 6311 > (mdtext2 about ,(p 'about) t ,u) 6312 > (string email ,(p 'email) ,u ,u) 6313 > (yesno showdead ,(p 'showdead) ,u ,u) 6314 > (yesno noprocrast ,(p 'noprocrast) ,u ,u) 6315 > (string firstview ,(p 'firstview) ,a nil) 6316 > (string lastview ,(p 'lastview) ,a nil) 6317 > (posint maxvisit ,(p 'maxvisit) ,u ,u) 6318 > (posint minaway ,(p 'minaway) ,u ,u) 6319 > (sexpr keys ,(p 'keys) ,a ,a) 6320 > (hexcol topcolor ,(or (p 'topcolor) (hexrep site-color*)) ,k ,k) 6321 > (int delay ,(p 'delay) ,u ,u)))) 6322 > 6323 > (def saved-link (user subject) 6324 > (when (or (admin user) (is user subject)) 6325 > (let n (if (len> (votes subject) 500) 6326 > "many" 6327 > (len (voted-stories user subject))) 6328 > (if (is n 0) 6329 > "" 6330 > (tostring (underlink n (saved-url subject))))))) 6331 > 6332 > (def resetpw-link () 6333 > (tostring (underlink "reset password" "resetpw"))) 6334 > 6335 > (newsop welcome () 6336 > (pr "Welcome to " this-site* ", " user "!")) 6337 > 6338 > 6339 > ; Main Operators 6340 > 6341 > ; remember to set caching to 0 when testing non-logged-in 6342 > 6343 > (= caching* 1 perpage* 30 threads-perpage* 10 maxend* 210) 6344 > 6345 > ; Limiting that newscache can't take any arguments except the user. 6346 > ; To allow other arguments, would have to turn the cache from a single 6347 > ; stored value to a hash table whose keys were lists of arguments. 6348 > 6349 > (mac newscache (name user time . body) 6350 > (w/uniq gc 6351 > `(let ,gc (cache (fn () (* caching* ,time)) 6352 > (fn () (tostring (let ,user nil ,@body)))) 6353 > (def ,name (,user) 6354 > (if ,user 6355 > (do ,@body) 6356 > (pr (,gc))))))) 6357 > 6358 > 6359 > (newsop news () (newspage user)) 6360 > 6361 > (newsop || () (newspage user)) 6362 > 6363 > ;(newsop index.html () (newspage user)) 6364 > 6365 > (newscache newspage user 90 6366 > (listpage user (msec) (topstories user maxend*) nil nil "news")) 6367 > 6368 > (def listpage (user t1 items label title (o url label) (o number t)) 6369 > (hook 'listpage user) 6370 > (longpage user t1 nil label title url 6371 > (display-items user items label title url 0 perpage* number))) 6372 > 6373 > 6374 > (newsop newest () (newestpage user)) 6375 > 6376 > ; Note: dead/deleted items will persist for the remaining life of the 6377 > ; cached page. If this were a prob, could make deletion clear caches. 6378 > 6379 > (newscache newestpage user 40 6380 > (listpage user (msec) (newstories user maxend*) "new" "New Links" "newest")) 6381 > 6382 > (def newstories (user n) 6383 > (retrieve n [cansee user _] stories*)) 6384 > 6385 > 6386 > (newsop best () (bestpage user)) 6387 > 6388 > (newscache bestpage user 1000 6389 > (listpage user (msec) (beststories user maxend*) "best" "Top Links")) 6390 > 6391 > ; As no of stories gets huge, could test visibility in fn sent to best. 6392 > 6393 > (def beststories (user n) 6394 > (bestn n (compare > realscore) (visible user stories*))) 6395 > 6396 > 6397 > (newsop noobstories () (noobspage user stories*)) 6398 > (newsop noobcomments () (noobspage user comments*)) 6399 > 6400 > (def noobspage (user source) 6401 > (listpage user (msec) (noobs user maxend* source) "noobs" "New Accounts")) 6402 > 6403 > (def noobs (user n source) 6404 > (retrieve n [and (cansee user _) (bynoob _)] source)) 6405 > 6406 > (def bynoob (i) 6407 > (< (- (user-age i!by) (item-age i)) 2880)) 6408 > 6409 > 6410 > (newsop bestcomments () (bestcpage user)) 6411 > 6412 > (newscache bestcpage user 1000 6413 > (listpage user (msec) (bestcomments user maxend*) 6414 > "best comments" "Best Comments" "bestcomments" nil)) 6415 > 6416 > (def bestcomments (user n) 6417 > (bestn n (compare > realscore) (visible user comments*))) 6418 > 6419 > 6420 > (newsop lists () 6421 > (longpage user (msec) nil "lists" "Lists" "lists" 6422 > (sptab 6423 > (row (link "best") "Highest voted recent links.") 6424 > (row (link "active") "Most active current discussions.") 6425 > (row (link "bestcomments") "Highest voted recent comments.") 6426 > (row (link "noobstories") "Submissions from new accounts.") 6427 > (row (link "noobcomments") "Comments from new accounts.") 6428 > (when (admin user) 6429 > (map row:link 6430 > '(optimes topips flagged killed badguys badlogins goodlogins))) 6431 > (hook 'listspage user)))) 6432 > 6433 > 6434 > (def saved-url (user) (+ "saved?id=" user)) 6435 > 6436 > (newsop saved (id) 6437 > (if (only.profile id) 6438 > (savedpage user id) 6439 > (pr "No such user."))) 6440 > 6441 > (def savedpage (user subject) 6442 > (if (or (is user subject) (admin user)) 6443 > (listpage user (msec) 6444 > (sort (compare < item-age) (voted-stories user subject)) 6445 > "saved" "Saved Links" (saved-url subject)) 6446 > (pr "Can't display that."))) 6447 > 6448 > (def voted-stories (user subject) 6449 > (keep [and (astory _) (cansee user _)] 6450 > (map item (keys:votes subject)))) 6451 > 6452 > 6453 > ; Story Display 6454 > 6455 > (def display-items (user items label title whence 6456 > (o start 0) (o end perpage*) (o number)) 6457 > (zerotable 6458 > (let n start 6459 > (each i (cut items start end) 6460 > (display-item (and number (++ n)) i user whence t) 6461 > (spacerow (if (acomment i) 15 5)))) 6462 > (when end 6463 > (let newend (+ end perpage*) 6464 > (when (and (<= newend maxend*) (< end (len items))) 6465 > (spacerow 10) 6466 > (tr (tag (td colspan (if number 2 1))) 6467 > (tag (td class 'title) 6468 > (morelink display-items 6469 > items label title end newend number)))))))) 6470 > 6471 > ; This code is inevitably complex because the More fn needs to know 6472 > ; its own fnid in order to supply a correct whence arg to stuff on 6473 > ; the page it generates, like logout and delete links. 6474 > 6475 > (def morelink (f items label title . args) 6476 > (tag (a href 6477 > (url-for 6478 > (afnid (fn (req) 6479 > (prn) 6480 > (with (url (url-for it) ; it bound by afnid 6481 > user (get-user req)) 6482 > (newslog req!ip user 'more label) 6483 > (longpage user (msec) nil label title url 6484 > (apply f user items label title url args)))))) 6485 > rel 'nofollow) 6486 > (pr "More"))) 6487 > 6488 > (def display-story (i s user whence) 6489 > (when (or (cansee user s) (s 'kids)) 6490 > (tr (display-item-number i) 6491 > (td (votelinks s user whence)) 6492 > (titleline s s!url user whence)) 6493 > (tr (tag (td colspan (if i 2 1))) 6494 > (tag (td class 'subtext) 6495 > (hook 'itemline s user) 6496 > (itemline s user) 6497 > (when (in s!type 'story 'poll) (commentlink s user)) 6498 > (editlink s user) 6499 > (when (apoll s) (addoptlink s user)) 6500 > (unless i (flaglink s user whence)) 6501 > (killlink s user whence) 6502 > (blastlink s user whence) 6503 > (blastlink s user whence t) 6504 > (deletelink s user whence))))) 6505 > 6506 > (def display-item-number (i) 6507 > (when i (tag (td align 'right valign 'top class 'title) 6508 > (pr i ".")))) 6509 > 6510 > (= follow-threshold* 5) 6511 > 6512 > (def titleline (s url user whence) 6513 > (tag (td class 'title) 6514 > (if (cansee user s) 6515 > (do (deadmark s user) 6516 > (titlelink s url user) 6517 > (pdflink url) 6518 > (awhen (sitename url) 6519 > (spanclass comhead 6520 > (pr " (" ) 6521 > (if (admin user) 6522 > (w/rlink (do (set-site-ban user 6523 > it 6524 > (case (car (banned-sites* it)) 6525 > nil 'ignore 6526 > ignore 'kill 6527 > kill nil)) 6528 > whence) 6529 > (let ban (car (banned-sites* it)) 6530 > (tag-if ban (font color (case ban 6531 > ignore darkred 6532 > kill darkblue)) 6533 > (pr it)))) 6534 > (pr it)) 6535 > (pr ") ")))) 6536 > (pr (pseudo-text s))))) 6537 > 6538 > (def titlelink (s url user) 6539 > (let toself (blank url) 6540 > (tag (a href (if toself 6541 > (item-url s!id) 6542 > (or (live s) (author user s) (editor user)) 6543 > url 6544 > nil) 6545 > rel (unless (or toself (> (realscore s) follow-threshold*)) 6546 > 'nofollow)) 6547 > (pr s!title)))) 6548 > 6549 > (def pdflink (url) 6550 > (awhen (vacuumize url) 6551 > (pr " [") 6552 > (link "scribd" it) 6553 > (pr "]"))) 6554 > 6555 > (defmemo vacuumize (url) 6556 > (and (or (endmatch ".pdf" url) (endmatch ".PDF" url)) 6557 > (+ "http://www.scribd.com/vacuum?url=" url))) 6558 > 6559 > (def pseudo-text (i) 6560 > (if i!deleted "[deleted]" "[dead]")) 6561 > 6562 > (def deadmark (i user) 6563 > (when (and i!dead (seesdead user)) 6564 > (pr " [dead] ")) 6565 > (when (and i!deleted (admin user)) 6566 > (pr " [deleted] "))) 6567 > 6568 > (= downvote-threshold* 200 downvote-time* 1440) 6569 > 6570 > (= votewid* 14) 6571 > 6572 > (def votelinks (i user whence (o downtoo)) 6573 > (center 6574 > (if (and (cansee user i) 6575 > (or (no user) 6576 > (no ((votes user) i!id)))) 6577 > (do (votelink i user whence 'up) 6578 > (if (and downtoo 6579 > (or (admin user) 6580 > (< (item-age i) downvote-time*)) 6581 > (canvote user i 'down)) 6582 > (do (br) 6583 > (votelink i user whence 'down)) 6584 > ; don't understand why needed, but is, or a new 6585 > ; page is generated on voting 6586 > (tag (span id (+ "down_" i!id))))) 6587 > (author user i) 6588 > (do (fontcolor orange (pr "*")) 6589 > (br) 6590 > (hspace votewid*)) 6591 > (hspace votewid*)))) 6592 > 6593 > ; could memoize votelink more, esp for non-logged in users, 6594 > ; since only uparrow is shown; could straight memoize 6595 > 6596 > ; redefined later (identically) so the outs catch new vals of up-url, etc. 6597 > 6598 > (def votelink (i user whence dir) 6599 > (tag (a id (if user (string dir '_ i!id)) 6600 > onclick (if user "return vote(this)") 6601 > href (vote-url user i dir whence)) 6602 > (if (is dir 'up) 6603 > (out (gentag img src up-url* border 0 vspace 3 hspace 2)) 6604 > (out (gentag img src down-url* border 0 vspace 3 hspace 2))))) 6605 > 6606 > (def vote-url (user i dir whence) 6607 > (+ "vote?" "for=" i!id 6608 > "&dir=" dir 6609 > (if user (+ "&by=" user "&auth=" (user->cookie* user))) 6610 > "&whence=" (urlencode whence))) 6611 > 6612 > (= lowest-score* -4) 6613 > 6614 > ; Not much stricter than whether to generate the arrow. Further tests 6615 > ; applied in vote-for. 6616 > 6617 > (def canvote (user i dir) 6618 > (and user 6619 > (news-type&live i) 6620 > (or (is dir 'up) (> i!score lowest-score*)) 6621 > (no ((votes user) i!id)) 6622 > (or (is dir 'up) 6623 > (and (acomment i) 6624 > (> (karma user) downvote-threshold*) 6625 > (no (aand i!parent (author user (item it)))))))) 6626 > 6627 > ; Need the by argument or someone could trick logged in users into 6628 > ; voting something up by clicking on a link. But a bad guy doesn't 6629 > ; know how to generate an auth arg that matches each user's cookie. 6630 > 6631 > (newsop vote (by for dir auth whence) 6632 > (with (i (safe-item for) 6633 > dir (saferead dir) 6634 > whence (if whence (urldecode whence) "news")) 6635 > (if (no i) 6636 > (pr "No such item.") 6637 > (no (in dir 'up 'down)) 6638 > (pr "Can't make that vote.") 6639 > (and by (or (isnt by user) (isnt (sym auth) (user->cookie* user)))) 6640 > (pr "User mismatch.") 6641 > (no user) 6642 > (login-page 'both "You have to be logged in to vote." 6643 > (list (fn (u ip) 6644 > (ensure-news-user u) 6645 > (newslog ip u 'vote-login) 6646 > (when (canvote u i dir) 6647 > (vote-for u i dir) 6648 > (logvote ip u i))) 6649 > whence)) 6650 > (canvote user i dir) 6651 > (do (vote-for by i dir) 6652 > (logvote ip by i)) 6653 > (pr "Can't make that vote.")))) 6654 > 6655 > (def itemline (i user) 6656 > (when (cansee user i) 6657 > (when (news-type i) (itemscore i user)) 6658 > (byline i user))) 6659 > 6660 > (def itemscore (i (o user)) 6661 > (tag (span id (+ "score_" i!id)) 6662 > (pr (plural (if (is i!type 'pollopt) (realscore i) i!score) 6663 > "point"))) 6664 > (hook 'itemscore i user)) 6665 > 6666 > ; redefined later 6667 > 6668 > (def byline (i user) 6669 > (pr " by @(tostring (userlink user i!by)) @(text-age:item-age i) ")) 6670 > 6671 > (def user-url (user) (+ "user?id=" user)) 6672 > 6673 > (= show-avg* nil) 6674 > 6675 > (def userlink (user subject (o show-avg t)) 6676 > (link (user-name user subject) (user-url subject)) 6677 > (awhen (and show-avg* (admin user) show-avg (uvar subject avg)) 6678 > (pr " (@(num it 1 t t))"))) 6679 > 6680 > (= noob-color* (color 60 150 60)) 6681 > 6682 > (def user-name (user subject) 6683 > (if (and (editor user) (ignored subject)) 6684 > (tostring (fontcolor darkred (pr subject))) 6685 > (and (editor user) (< (user-age subject) 1440)) 6686 > (tostring (fontcolor noob-color* (pr subject))) 6687 > subject)) 6688 > 6689 > (= show-threadavg* nil) 6690 > 6691 > (def commentlink (i user) 6692 > (when (cansee user i) 6693 > (pr bar*) 6694 > (tag (a href (item-url i!id)) 6695 > (let n (- (visible-family user i) 1) 6696 > (if (> n 0) 6697 > (do (pr (plural n "comment")) 6698 > (awhen (and show-threadavg* (admin user) (threadavg i)) 6699 > (pr " (@(num it 1 t t))"))) 6700 > (pr "discuss")))))) 6701 > 6702 > (def visible-family (user i) 6703 > (+ (if (cansee user i) 1 0) 6704 > (sum [visible-family user (item _)] i!kids))) 6705 > 6706 > (def threadavg (i) 6707 > (only.avg (map [or (uvar _ avg) 1] 6708 > (rem admin (dedup (map !by (keep live (family i)))))))) 6709 > 6710 > (= user-changetime* 120 editor-changetime* 1440) 6711 > 6712 > (= everchange* (table) noedit* (table)) 6713 > 6714 > (def canedit (user i) 6715 > (or (admin user) 6716 > (and (~noedit* i!type) 6717 > (editor user) 6718 > (< (item-age i) editor-changetime*)) 6719 > (own-changeable-item user i))) 6720 > 6721 > (def own-changeable-item (user i) 6722 > (and (author user i) 6723 > (~mem 'locked i!keys) 6724 > (no i!deleted) 6725 > (or (everchange* i!type) 6726 > (< (item-age i) user-changetime*)))) 6727 > 6728 > (def editlink (i user) 6729 > (when (canedit user i) 6730 > (pr bar*) 6731 > (link "edit" (edit-url i)))) 6732 > 6733 > (def addoptlink (p user) 6734 > (when (or (admin user) (author user p)) 6735 > (pr bar*) 6736 > (onlink "add choice" (add-pollopt-page p user)))) 6737 > 6738 > ; reset later 6739 > 6740 > (= flag-threshold* 30 flag-kill-threshold* 7 many-flags* 1) 6741 > 6742 > ; Un-flagging something doesn't unkill it, if it's now no longer 6743 > ; over flag-kill-threshold. Ok, since arbitrary threshold anyway. 6744 > 6745 > (def flaglink (i user whence) 6746 > (when (and user 6747 > (isnt user i!by) 6748 > (or (admin user) (> (karma user) flag-threshold*))) 6749 > (pr bar*) 6750 > (w/rlink (do (togglemem user i!flags) 6751 > (when (and (~mem 'nokill i!keys) 6752 > (len> i!flags flag-kill-threshold*) 6753 > (< (realscore i) 10) 6754 > (~find admin:!2 i!vote)) 6755 > (kill i 'flags)) 6756 > whence) 6757 > (pr "@(if (mem user i!flags) 'un)flag")) 6758 > (when (and (admin user) (len> i!flags many-flags*)) 6759 > (pr bar* (plural (len i!flags) "flag") " ") 6760 > (w/rlink (do (togglemem 'nokill i!keys) 6761 > (save-item i) 6762 > whence) 6763 > (pr (if (mem 'nokill i!keys) "un-notice" "noted")))))) 6764 > 6765 > (def killlink (i user whence) 6766 > (when (admin user) 6767 > (pr bar*) 6768 > (w/rlink (do (zap no i!dead) 6769 > (if i!dead 6770 > (do (pull 'nokill i!keys) 6771 > (log-kill i user)) 6772 > (pushnew 'nokill i!keys)) 6773 > (save-item i) 6774 > whence) 6775 > (pr "@(if i!dead 'un)kill")))) 6776 > 6777 > ; Blast kills the submission and bans the user. Nuke also bans the 6778 > ; site, so that all future submitters will be ignored. Does not ban 6779 > ; the ip address, but that will eventually get banned by maybe-ban-ip. 6780 > 6781 > (def blastlink (i user whence (o nuke)) 6782 > (when (and (admin user) 6783 > (or (no nuke) (~empty i!url))) 6784 > (pr bar*) 6785 > (w/rlink (do (toggle-blast i user nuke) 6786 > whence) 6787 > (prt (if (ignored i!by) "un-") (if nuke "nuke" "blast"))))) 6788 > 6789 > (def toggle-blast (i user (o nuke)) 6790 > (atomic 6791 > (if (ignored i!by) 6792 > (do (wipe i!dead (ignored i!by)) 6793 > (awhen (and nuke (sitename i!url)) 6794 > (set-site-ban user it nil))) 6795 > (do (set i!dead) 6796 > (ignore user i!by (if nuke 'nuke 'blast)) 6797 > (awhen (and nuke (sitename i!url)) 6798 > (set-site-ban user it 'ignore)))) 6799 > (if i!dead (log-kill i user)) 6800 > (save-item i) 6801 > (save-prof i!by))) 6802 > 6803 > (def candelete (user i) 6804 > (or (admin user) (own-changeable-item user i))) 6805 > 6806 > (def deletelink (i user whence) 6807 > (when (candelete user i) 6808 > (pr bar*) 6809 > (linkf (if i!deleted "undelete" "delete") (req) 6810 > (let user (get-user req) 6811 > (if (candelete user i) 6812 > (del-confirm-page user i whence) 6813 > (prn "You can't delete that.")))))) 6814 > 6815 > ; Undeleting stories could cause a slight inconsistency. If a story 6816 > ; linking to x gets deleted, another submission can take its place in 6817 > ; url->story. If the original is then undeleted, there will be two 6818 > ; stories with equal claim to be in url->story. (The more recent will 6819 > ; win because it happens to get loaded later.) Not a big problem. 6820 > 6821 > (def del-confirm-page (user i whence) 6822 > (minipage "Confirm" 6823 > (tab 6824 > ; link never used so not testable but think correct 6825 > (display-item nil i user (flink [del-confirm-page (get-user _) i whence])) 6826 > (spacerow 20) 6827 > (tr (td) 6828 > (td (urform user req 6829 > (do (when (candelete user i) 6830 > (= i!deleted (is (arg req "b") "Yes")) 6831 > (save-item i)) 6832 > whence) 6833 > (prn "Do you want this to @(if i!deleted 'stay 'be) deleted?") 6834 > (br2) 6835 > (but "Yes" "b") (sp) (but "No" "b"))))))) 6836 > 6837 > (def permalink (story user) 6838 > (when (cansee user story) 6839 > (pr bar*) 6840 > (link "link" (item-url story!id)))) 6841 > 6842 > (def logvote (ip user story) 6843 > (newslog ip user 'vote (story 'id) (list (story 'title)))) 6844 > 6845 > (def text-age (a) 6846 > (tostring 6847 > (if (>= a 1440) (pr (plural (trunc (/ a 1440)) "day") " ago") 6848 > (>= a 60) (pr (plural (trunc (/ a 60)) "hour") " ago") 6849 > (pr (plural (trunc a) "minute") " ago")))) 6850 > 6851 > 6852 > ; Voting 6853 > 6854 > ; A user needs legit-threshold karma for a vote to count if there has 6855 > ; already been a vote from the same IP address. A new account below both 6856 > ; new- thresholds won't affect rankings, though such votes still affect 6857 > ; scores unless not a legit-user. 6858 > 6859 > (= legit-threshold* 0 new-age-threshold* 0 new-karma-threshold* 2) 6860 > 6861 > (def legit-user (user) 6862 > (or (editor user) 6863 > (> (karma user) legit-threshold*))) 6864 > 6865 > (def possible-sockpuppet (user) 6866 > (or (ignored user) 6867 > (< (uvar user weight) .5) 6868 > (and (< (user-age user) new-age-threshold*) 6869 > (< (karma user) new-karma-threshold*)))) 6870 > 6871 > (= downvote-ratio-limit* .65 recent-votes* nil votewindow* 100) 6872 > 6873 > ; Note: if vote-for by one user changes (s 'score) while s is being 6874 > ; edited by another, the save after the edit will overwrite the change. 6875 > ; Actual votes can't be lost because that field is not editable. Not a 6876 > ; big enough problem to drag in locking. 6877 > 6878 > (def vote-for (user i (o dir 'up)) 6879 > (unless (or ((votes user) i!id) 6880 > (and (~live i) (isnt user i!by))) 6881 > (withs (ip (logins* user) 6882 > vote (list (seconds) ip user dir i!score)) 6883 > (unless (or (and (or (ignored user) (check-key user 'novote)) 6884 > (isnt user i!by)) 6885 > (and (is dir 'down) 6886 > (~editor user) 6887 > (or (check-key user 'nodowns) 6888 > (> (downvote-ratio user) downvote-ratio-limit*) 6889 > ; prevention of karma-bombing 6890 > (just-downvoted user i!by))) 6891 > (and (~legit-user user) 6892 > (isnt user i!by) 6893 > (find [is (cadr _) ip] i!votes)) 6894 > (and (isnt i!type 'pollopt) 6895 > (biased-voter i vote))) 6896 > (++ i!score (case dir up 1 down -1)) 6897 > ; canvote protects against sockpuppet downvote of comments 6898 > (when (and (is dir 'up) (possible-sockpuppet user)) 6899 > (++ i!sockvotes)) 6900 > (metastory&adjust-rank i) 6901 > (unless (or (author user i) 6902 > (and (is ip i!ip) (~editor user)) 6903 > (is i!type 'pollopt)) 6904 > (++ (karma i!by) (case dir up 1 down -1)) 6905 > (save-prof i!by)) 6906 > (wipe (comment-cache* i!id))) 6907 > (if (admin user) (pushnew 'nokill i!keys)) 6908 > (push vote i!votes) 6909 > (save-item i) 6910 > (push (list (seconds) i!id i!by (sitename i!url) dir) 6911 > (uvar user votes)) 6912 > (= ((votes* user) i!id) vote) 6913 > (save-votes user) 6914 > (zap [firstn votewindow* _] (uvar user votes)) 6915 > (save-prof user) 6916 > (push (cons i!id vote) recent-votes*)))) 6917 > 6918 > ; redefined later 6919 > 6920 > (def biased-voter (i vote) nil) 6921 > 6922 > ; ugly to access vote fields by position number 6923 > 6924 > (def downvote-ratio (user (o sample 20)) 6925 > (ratio [is _.1.3 'down] 6926 > (keep [let by ((item (car _)) 'by) 6927 > (nor (is by user) (ignored by))] 6928 > (bestn sample (compare > car:cadr) (tablist (votes user)))))) 6929 > 6930 > (def just-downvoted (user victim (o n 3)) 6931 > (let prev (firstn n (recent-votes-by user)) 6932 > (and (is (len prev) n) 6933 > (all (fn ((id sec ip voter dir score)) 6934 > (and (author victim (item id)) (is dir 'down))) 6935 > prev)))) 6936 > 6937 > ; Ugly to pluck out fourth element. Should read votes into a vote 6938 > ; template. They're stored slightly differently in two diff places: 6939 > ; in one with the voter in the car and the other without. 6940 > 6941 > (def recent-votes-by (user) 6942 > (keep [is _.3 user] recent-votes*)) 6943 > 6944 > 6945 > ; Story Submission 6946 > 6947 > (newsop submit () 6948 > (if user 6949 > (submit-page user "" "" t) 6950 > (submit-login-warning "" "" t))) 6951 > 6952 > (def submit-login-warning ((o url) (o title) (o showtext) (o text)) 6953 > (login-page 'both "You have to be logged in to submit." 6954 > (fn (user ip) 6955 > (ensure-news-user user) 6956 > (newslog ip user 'submit-login) 6957 > (submit-page user url title showtext text)))) 6958 > 6959 > (def submit-page (user (o url) (o title) (o showtext) (o text "") (o msg)) 6960 > (minipage "Submit" 6961 > (pagemessage msg) 6962 > (urform user req 6963 > (process-story (get-user req) 6964 > (clean-url (arg req "u")) 6965 > (striptags (arg req "t")) 6966 > showtext 6967 > (and showtext (md-from-form (arg req "x") t)) 6968 > req!ip) 6969 > (tab 6970 > (row "title" (input "t" title 50)) 6971 > (if prefer-url* 6972 > (do (row "url" (input "u" url 50)) 6973 > (when showtext 6974 > (row "" "or") 6975 > (row "text" (textarea "x" 4 50 (only.pr text))))) 6976 > (do (row "text" (textarea "x" 4 50 (only.pr text))) 6977 > (row "" "or") 6978 > (row "url" (input "u" url 50)))) 6979 > (row "" (submit)) 6980 > (spacerow 20) 6981 > (row "" submit-instructions*))))) 6982 > 6983 > (= submit-instructions* 6984 > "Leave url blank to submit a question for discussion. If there is 6985 > no url, the text (if any) will appear at the top of the comments 6986 > page. If there is a url, the text will be ignored.") 6987 > 6988 > ; For use by outside code like bookmarklet. 6989 > ; http://news.domain.com/submitlink?u=http://foo.com&t=Foo 6990 > ; Added a confirm step to avoid xss hacks. 6991 > 6992 > (newsop submitlink (u t) 6993 > (if user 6994 > (submit-page user u t) 6995 > (submit-login-warning u t))) 6996 > 6997 > (= title-limit* 80 6998 > retry* "Please try again." 6999 > toolong* "Please make title < @title-limit* characters." 7000 > bothblank* "The url and text fields can't both be blank. Please 7001 > either supply a url, or if you're asking a question, 7002 > put it in the text field." 7003 > toofast* "You're submitting too fast. Please slow down. Thanks." 7004 > spammage* "Stop spamming us. You're wasting your time.") 7005 > 7006 > ; Only for annoyingly high-volume spammers. For ordinary spammers it's 7007 > ; enough to ban their sites and ip addresses. 7008 > 7009 > (disktable big-spamsites* (+ newsdir* "big-spamsites")) 7010 > 7011 > (def process-story (user url title showtext text ip) 7012 > (aif (and (~blank url) (live-story-w/url url)) 7013 > (do (vote-for user it) 7014 > (item-url it!id)) 7015 > (if (no user) 7016 > (flink [submit-login-warning url title showtext text]) 7017 > (no (and (or (blank url) (valid-url url)) 7018 > (~blank title))) 7019 > (flink [submit-page user url title showtext text retry*]) 7020 > (len> title title-limit*) 7021 > (flink [submit-page user url title showtext text toolong*]) 7022 > (and (blank url) (blank text)) 7023 > (flink [submit-page user url title showtext text bothblank*]) 7024 > (let site (sitename url) 7025 > (or (big-spamsites* site) (recent-spam site))) 7026 > (flink [msgpage user spammage*]) 7027 > (oversubmitting user ip 'story url) 7028 > (flink [msgpage user toofast*]) 7029 > (let s (create-story url (process-title title) text user ip) 7030 > (story-ban-test user s ip url) 7031 > (when (ignored user) (kill s 'ignored)) 7032 > (submit-item user s) 7033 > (maybe-ban-ip s) 7034 > "newest")))) 7035 > 7036 > (def submit-item (user i) 7037 > (push i!id (uvar user submitted)) 7038 > (save-prof user) 7039 > (vote-for user i)) 7040 > 7041 > (def recent-spam (site) 7042 > (and (caris (banned-sites* site) 'ignore) 7043 > (recent-items [is (sitename _!url) site] 720))) 7044 > 7045 > (def recent-items (test minutes) 7046 > (let cutoff (- (seconds) (* 60 minutes)) 7047 > (latest-items test [< _!time cutoff]))) 7048 > 7049 > ; Turn this on when spam becomes a problem. 7050 > 7051 > (= enforce-oversubmit* nil) 7052 > 7053 > ; New user can't submit more than 2 stories in a 2 hour period. 7054 > ; Give overeager users the key toofast to make limit permanent. 7055 > 7056 > (def oversubmitting (user ip kind (o url)) 7057 > (and enforce-oversubmit* 7058 > (or (check-key user 'toofast) 7059 > (ignored user) 7060 > (< (user-age user) new-age-threshold*) 7061 > (< (karma user) new-karma-threshold*)) 7062 > (len> (recent-items [or (author user _) (is _!ip ip)] 180) 7063 > (if (is kind 'story) 7064 > (if (bad-user user) 0 1) 7065 > (if (bad-user user) 1 10))))) 7066 > 7067 > ; Note that by deliberate tricks, someone could submit a story with a 7068 > ; blank title. 7069 > 7070 > (diskvar scrubrules* (+ newsdir* "scrubrules")) 7071 > 7072 > (def process-title (s) 7073 > (let s2 (multisubst scrubrules* s) 7074 > (zap upcase (s2 0)) 7075 > s2)) 7076 > 7077 > (def live-story-w/url (url) 7078 > (aand (url->story* (canonical-url url)) (check (item it) live))) 7079 > 7080 > (def parse-site (url) 7081 > (rev (tokens (cadr (tokens url [in _ #\/ #\?])) #\.))) 7082 > 7083 > (defmemo sitename (url) 7084 > (and (valid-url url) 7085 > (let toks (parse-site (rem #\space url)) 7086 > (if (isa (saferead (car toks)) 'int) 7087 > (tostring (prall toks "" ".")) 7088 > (let (t1 t2 t3 . rest) toks 7089 > (if (and (~in t3 nil "www") 7090 > (or (mem t1 multi-tld-countries*) 7091 > (mem t2 long-domains*))) 7092 > (+ t3 "." t2 "." t1) 7093 > (and t2 (+ t2 "." t1)))))))) 7094 > 7095 > (= multi-tld-countries* '("uk" "jp" "au" "in" "ph" "tr" "za" "my" "nz" "br" 7096 > "mx" "th" "sg" "id" "pk" "eg" "il" "at" "pl")) 7097 > 7098 > (= long-domains* '("blogspot" "wordpress" "livejournal" "blogs" "typepad" 7099 > "weebly" "posterous" "blog-city" "supersized" "dreamhosters" 7100 > ; "sampasite" "multiply" "wetpaint" ; all spam, just ban 7101 > "eurekster" "blogsome" "edogo" "blog" "com")) 7102 > 7103 > (def create-story (url title text user ip) 7104 > (newslog ip user 'create url (list title)) 7105 > (let s (inst 'item 'type 'story 'id (new-item-id) 7106 > 'url url 'title title 'text text 'by user 'ip ip) 7107 > (save-item s) 7108 > (= (items* s!id) s) 7109 > (unless (blank url) (register-url s url)) 7110 > (push s stories*) 7111 > s)) 7112 > 7113 > 7114 > ; Bans 7115 > 7116 > (def ignore (user subject cause) 7117 > (set (ignored subject)) 7118 > (save-prof subject) 7119 > (log-ignore user subject cause)) 7120 > 7121 > (diskvar ignore-log* (+ newsdir* "ignore-log")) 7122 > 7123 > (def log-ignore (user subject cause) 7124 > (todisk ignore-log* (cons (list subject user cause) ignore-log*))) 7125 > 7126 > ; Kill means stuff with this substring gets killed. Ignore is stronger, 7127 > ; means that user will be auto-ignored. Eventually this info should 7128 > ; be stored on disk and not in the source code. 7129 > 7130 > (disktable banned-ips* (+ newsdir* "banned-ips")) ; was ips 7131 > (disktable banned-sites* (+ newsdir* "banned-sites")) ; was sites 7132 > 7133 > (diskvar comment-kill* (+ newsdir* "comment-kill")) 7134 > (diskvar comment-ignore* (+ newsdir* "comment-ignore")) 7135 > 7136 > (= comment-kill* nil ip-ban-threshold* 3) 7137 > 7138 > (def set-ip-ban (user ip yesno (o info)) 7139 > (= (banned-ips* ip) (and yesno (list user (seconds) info))) 7140 > (todisk banned-ips*)) 7141 > 7142 > (def set-site-ban (user site ban (o info)) 7143 > (= (banned-sites* site) (and ban (list ban user (seconds) info))) 7144 > (todisk banned-sites*)) 7145 > 7146 > ; Kill submissions from banned ips, but don't auto-ignore users from 7147 > ; them, because eventually ips will become legit again. 7148 > 7149 > ; Note that ban tests are only applied when a link or comment is 7150 > ; submitted, not each time it's edited. This will do for now. 7151 > 7152 > (def story-ban-test (user i ip url) 7153 > (site-ban-test user i url) 7154 > (ip-ban-test i ip) 7155 > (hook 'story-ban-test user i ip url)) 7156 > 7157 > (def site-ban-test (user i url) 7158 > (whenlet ban (banned-sites* (sitename url)) 7159 > (if (caris ban 'ignore) (ignore nil user 'site-ban)) 7160 > (kill i 'site-ban))) 7161 > 7162 > (def ip-ban-test (i ip) 7163 > (if (banned-ips* ip) (kill i 'banned-ip))) 7164 > 7165 > (def comment-ban-test (user i ip string kill-list ignore-list) 7166 > (when (some [posmatch _ string] ignore-list) 7167 > (ignore nil user 'comment-ban)) 7168 > (when (or (banned-ips* ip) (some [posmatch _ string] kill-list)) 7169 > (kill i 'comment-ban))) 7170 > 7171 > ; An IP is banned when multiple ignored users have submitted over 7172 > ; ban-threshold* (currently loaded) dead stories from it. 7173 > 7174 > ; Can consider comments too if that later starts to be a problem, 7175 > ; but the threshold may start to be higher because then you'd be 7176 > ; dealing with trolls rather than spammers. 7177 > 7178 > (def maybe-ban-ip (s) 7179 > (when (and s!dead (ignored s!by)) 7180 > (let bads (loaded-items [and _!dead (astory _) (is _!ip s!ip)]) 7181 > (when (and (len> bads ip-ban-threshold*) 7182 > (some [and (ignored _!by) (isnt _!by s!by)] bads)) 7183 > (set-ip-ban nil s!ip t))))) 7184 > 7185 > (def killallby (user) 7186 > (map [kill _ 'all] (submissions user))) 7187 > 7188 > ; Only called from repl. 7189 > 7190 > (def kill-whole-thread (c) 7191 > (kill c 'thread) 7192 > (map kill-whole-thread:item c!kids)) 7193 > 7194 > 7195 > ; Polls 7196 > 7197 > ; a way to add a karma threshold for voting in a poll 7198 > ; or better still an arbitrary test fn, or at least pair of name/threshold. 7199 > ; option to sort the elements of a poll when displaying 7200 > ; exclusive field? (means only allow one vote per poll) 7201 > 7202 > (= poll-threshold* 20) 7203 > 7204 > (newsop newpoll () 7205 > (if (and user (> (karma user) poll-threshold*)) 7206 > (newpoll-page user) 7207 > (pr "Sorry, you need @poll-threshold* karma to create a poll."))) 7208 > 7209 > (def newpoll-page (user (o title "Poll: ") (o text "") (o opts "") (o msg)) 7210 > (minipage "New Poll" 7211 > (pagemessage msg) 7212 > (urform user req 7213 > (process-poll (get-user req) 7214 > (striptags (arg req "t")) 7215 > (md-from-form (arg req "x") t) 7216 > (striptags (arg req "o")) 7217 > req!ip) 7218 > (tab 7219 > (row "title" (input "t" title 50)) 7220 > (row "text" (textarea "x" 4 50 (only.pr text))) 7221 > (row "" "Use blank lines to separate choices:") 7222 > (row "choices" (textarea "o" 7 50 (only.pr opts))) 7223 > (row "" (submit)))))) 7224 > 7225 > (= fewopts* "A poll must have at least two options.") 7226 > 7227 > (def process-poll (user title text opts ip) 7228 > (if (or (blank title) (blank opts)) 7229 > (flink [newpoll-page user title text opts retry*]) 7230 > (len> title title-limit*) 7231 > (flink [newpoll-page user title text opts toolong*]) 7232 > (len< (paras opts) 2) 7233 > (flink [newpoll-page user title text opts fewopts*]) 7234 > (atlet p (create-poll (multisubst scrubrules* title) text opts user ip) 7235 > (ip-ban-test p ip) 7236 > (when (ignored user) (kill p 'ignored)) 7237 > (submit-item user p) 7238 > (maybe-ban-ip p) 7239 > "newest"))) 7240 > 7241 > (def create-poll (title text opts user ip) 7242 > (newslog ip user 'create-poll title) 7243 > (let p (inst 'item 'type 'poll 'id (new-item-id) 7244 > 'title title 'text text 'by user 'ip ip) 7245 > (= p!parts (map get!id (map [create-pollopt p nil nil _ user ip] 7246 > (paras opts)))) 7247 > (save-item p) 7248 > (= (items* p!id) p) 7249 > (push p stories*) 7250 > p)) 7251 > 7252 > (def create-pollopt (p url title text user ip) 7253 > (let o (inst 'item 'type 'pollopt 'id (new-item-id) 7254 > 'url url 'title title 'text text 'parent p!id 7255 > 'by user 'ip ip) 7256 > (save-item o) 7257 > (= (items* o!id) o) 7258 > o)) 7259 > 7260 > (def add-pollopt-page (p user) 7261 > (minipage "Add Poll Choice" 7262 > (urform user req 7263 > (do (add-pollopt user p (striptags (arg req "x")) req!ip) 7264 > (item-url p!id)) 7265 > (tab 7266 > (row "text" (textarea "x" 4 50)) 7267 > (row "" (submit)))))) 7268 > 7269 > (def add-pollopt (user p text ip) 7270 > (unless (blank text) 7271 > (atlet o (create-pollopt p nil nil text user ip) 7272 > (++ p!parts (list o!id)) 7273 > (save-item p)))) 7274 > 7275 > (def display-pollopts (p user whence) 7276 > (each o (visible user (map item p!parts)) 7277 > (display-pollopt nil o user whence) 7278 > (spacerow 7))) 7279 > 7280 > (def display-pollopt (n o user whence) 7281 > (tr (display-item-number n) 7282 > (tag (td valign 'top) 7283 > (votelinks o user whence)) 7284 > (tag (td class 'comment) 7285 > (tag (div style "margin-top:1px;margin-bottom:0px") 7286 > (if (~cansee user o) (pr (pseudo-text o)) 7287 > (~live o) (spanclass dead 7288 > (pr (if (~blank o!title) o!title o!text))) 7289 > (if (and (~blank o!title) (~blank o!url)) 7290 > (link o!title o!url) 7291 > (fontcolor black (pr o!text))))))) 7292 > (tr (if n (td)) 7293 > (td) 7294 > (tag (td class 'default) 7295 > (spanclass comhead 7296 > (itemscore o) 7297 > (editlink o user) 7298 > (killlink o user whence) 7299 > (deletelink o user whence) 7300 > (deadmark o user))))) 7301 > 7302 > 7303 > ; Individual Item Page (= Comments Page of Stories) 7304 > 7305 > (defmemo item-url (id) (+ "item?id=" id)) 7306 > 7307 > (newsop item (id) 7308 > (let s (safe-item id) 7309 > (if (news-type s) 7310 > (do (if s!deleted (note-baditem user ip)) 7311 > (item-page user s)) 7312 > (do (note-baditem user ip) 7313 > (pr "No such item."))))) 7314 > 7315 > (= baditemreqs* (table) baditem-threshold* 1/100) 7316 > 7317 > ; Something looking at a lot of deleted items is probably the bad sort 7318 > ; of crawler. Throttle it for this server invocation. 7319 > 7320 > (def note-baditem (user ip) 7321 > (unless (admin user) 7322 > (++ (baditemreqs* ip 0)) 7323 > (with (r (requests/ip* ip) b (baditemreqs* ip)) 7324 > (when (and (> r 500) (> (/ b r) baditem-threshold*)) 7325 > (set (throttle-ips* ip)))))) 7326 > 7327 > ; redefined later 7328 > 7329 > (def news-type (i) (and i (in i!type 'story 'comment 'poll 'pollopt))) 7330 > 7331 > (def item-page (user i) 7332 > (with (title (and (cansee user i) 7333 > (or i!title (aand i!text (ellipsize (striptags it))))) 7334 > here (item-url i!id)) 7335 > (longpage user (msec) nil nil title here 7336 > (tab (display-item nil i user here) 7337 > (display-item-text i user) 7338 > (when (apoll i) 7339 > (spacerow 10) 7340 > (tr (td) 7341 > (td (tab (display-pollopts i user here))))) 7342 > (when (and (cansee user i) (comments-active i)) 7343 > (spacerow 10) 7344 > (row "" (comment-form i user here)))) 7345 > (br2) 7346 > (when (and i!kids (commentable i)) 7347 > (tab (display-subcomments i user here)) 7348 > (br2))))) 7349 > 7350 > (def commentable (i) (in i!type 'story 'comment 'poll)) 7351 > 7352 > ; By default the ability to comment on an item is turned off after 7353 > ; 45 days, but this can be overriden with commentable key. 7354 > 7355 > (= commentable-threshold* (* 60 24 45)) 7356 > 7357 > (def comments-active (i) 7358 > (and (live&commentable i) 7359 > (live (superparent i)) 7360 > (or (< (item-age i) commentable-threshold*) 7361 > (mem 'commentable i!keys)))) 7362 > 7363 > 7364 > (= displayfn* (table)) 7365 > 7366 > (= (displayfn* 'story) (fn (n i user here inlist) 7367 > (display-story n i user here))) 7368 > 7369 > (= (displayfn* 'comment) (fn (n i user here inlist) 7370 > (display-comment n i user here nil 0 nil inlist))) 7371 > 7372 > (= (displayfn* 'poll) (displayfn* 'story)) 7373 > 7374 > (= (displayfn* 'pollopt) (fn (n i user here inlist) 7375 > (display-pollopt n i user here))) 7376 > 7377 > (def display-item (n i user here (o inlist)) 7378 > ((displayfn* (i 'type)) n i user here inlist)) 7379 > 7380 > (def superparent (i) 7381 > (aif i!parent (superparent:item it) i)) 7382 > 7383 > (def display-item-text (s user) 7384 > (when (and (cansee user s) 7385 > (in s!type 'story 'poll) 7386 > (blank s!url) 7387 > (~blank s!text)) 7388 > (spacerow 2) 7389 > (row "" s!text))) 7390 > 7391 > 7392 > ; Edit Item 7393 > 7394 > (def edit-url (i) (+ "edit?id=" i!id)) 7395 > 7396 > (newsop edit (id) 7397 > (let i (safe-item id) 7398 > (if (and i 7399 > (cansee user i) 7400 > (editable-type i) 7401 > (or (news-type i) (admin user) (author user i))) 7402 > (edit-page user i) 7403 > (pr "No such item.")))) 7404 > 7405 > (def editable-type (i) (fieldfn* i!type)) 7406 > 7407 > (= fieldfn* (table)) 7408 > 7409 > (= (fieldfn* 'story) 7410 > (fn (user s) 7411 > (with (a (admin user) e (editor user) x (canedit user s)) 7412 > `((string1 title ,s!title t ,x) 7413 > (url url ,s!url t ,e) 7414 > (mdtext2 text ,s!text t ,x) 7415 > ,@(standard-item-fields s a e x))))) 7416 > 7417 > (= (fieldfn* 'comment) 7418 > (fn (user c) 7419 > (with (a (admin user) e (editor user) x (canedit user c)) 7420 > `((mdtext text ,c!text t ,x) 7421 > ,@(standard-item-fields c a e x))))) 7422 > 7423 > (= (fieldfn* 'poll) 7424 > (fn (user p) 7425 > (with (a (admin user) e (editor user) x (canedit user p)) 7426 > `((string1 title ,p!title t ,x) 7427 > (mdtext2 text ,p!text t ,x) 7428 > ,@(standard-item-fields p a e x))))) 7429 > 7430 > (= (fieldfn* 'pollopt) 7431 > (fn (user p) 7432 > (with (a (admin user) e (editor user) x (canedit user p)) 7433 > `((string title ,p!title t ,x) 7434 > (url url ,p!url t ,x) 7435 > (mdtext2 text ,p!text t ,x) 7436 > ,@(standard-item-fields p a e x))))) 7437 > 7438 > (def standard-item-fields (i a e x) 7439 > `((int votes ,(len i!votes) ,a nil) 7440 > (int score ,i!score t ,a) 7441 > (int sockvotes ,i!sockvotes ,a ,a) 7442 > (yesno dead ,i!dead ,e ,e) 7443 > (yesno deleted ,i!deleted ,a ,a) 7444 > (sexpr flags ,i!flags ,a nil) 7445 > (sexpr keys ,i!keys ,a ,a) 7446 > (string ip ,i!ip ,e nil))) 7447 > 7448 > ; Should check valid-url etc here too. In fact make a fn that 7449 > ; does everything that has to happen after submitting a story, 7450 > ; and call it both there and here. 7451 > 7452 > (def edit-page (user i) 7453 > (let here (edit-url i) 7454 > (shortpage user nil nil "Edit" here 7455 > (tab (display-item nil i user here) 7456 > (display-item-text i user)) 7457 > (br2) 7458 > (vars-form user 7459 > ((fieldfn* i!type) user i) 7460 > (fn (name val) 7461 > (unless (ignore-edit user i name val) 7462 > (when (and (is name 'dead) val (no i!dead)) 7463 > (log-kill i user)) 7464 > (= (i name) val))) 7465 > (fn () (if (admin user) (pushnew 'locked i!keys)) 7466 > (save-item i) 7467 > (metastory&adjust-rank i) 7468 > (wipe (comment-cache* i!id)) 7469 > (edit-page user i))) 7470 > (hook 'edit user i)))) 7471 > 7472 > (def ignore-edit (user i name val) 7473 > (case name title (len> val title-limit*) 7474 > dead (and (mem 'nokill i!keys) (~admin user)))) 7475 > 7476 > 7477 > ; Comment Submission 7478 > 7479 > (def comment-login-warning (parent whence (o text)) 7480 > (login-page 'both "You have to be logged in to comment." 7481 > (fn (u ip) 7482 > (ensure-news-user u) 7483 > (newslog ip u 'comment-login) 7484 > (addcomment-page parent u whence text)))) 7485 > 7486 > (def addcomment-page (parent user whence (o text) (o msg)) 7487 > (minipage "Add Comment" 7488 > (pagemessage msg) 7489 > (tab 7490 > (let here (flink [addcomment-page parent (get-user _) whence text msg]) 7491 > (display-item nil parent user here)) 7492 > (spacerow 10) 7493 > (row "" (comment-form parent user whence text))))) 7494 > 7495 > (= noob-comment-msg* nil) 7496 > 7497 > ; Comment forms last for 30 min (- cache time) 7498 > 7499 > (def comment-form (parent user whence (o text)) 7500 > (tarform 1800 7501 > (fn (req) 7502 > (when-umatch/r user req 7503 > (process-comment user parent (arg req "text") req!ip whence))) 7504 > (textarea "text" 6 60 7505 > (aif text (prn (unmarkdown it)))) 7506 > (when (and noob-comment-msg* (noob user)) 7507 > (br2) 7508 > (spanclass subtext (pr noob-comment-msg*))) 7509 > (br2) 7510 > (submit (if (acomment parent) "reply" "add comment")))) 7511 > 7512 > (= comment-threshold* -20) 7513 > 7514 > ; Have to remove #\returns because a form gives you back "a\r\nb" 7515 > ; instead of just "a\nb". Maybe should just remove returns from 7516 > ; the vals coming in from any form, e.g. in aform. 7517 > 7518 > (def process-comment (user parent text ip whence) 7519 > (if (no user) 7520 > (flink [comment-login-warning parent whence text]) 7521 > (empty text) 7522 > (flink [addcomment-page parent (get-user _) whence text retry*]) 7523 > (oversubmitting user ip 'comment) 7524 > (flink [msgpage user toofast*]) 7525 > (atlet c (create-comment parent (md-from-form text) user ip) 7526 > (comment-ban-test user c ip text comment-kill* comment-ignore*) 7527 > (if (bad-user user) (kill c 'ignored/karma)) 7528 > (submit-item user c) 7529 > whence))) 7530 > 7531 > (def bad-user (u) 7532 > (or (ignored u) (< (karma u) comment-threshold*))) 7533 > 7534 > (def create-comment (parent text user ip) 7535 > (newslog ip user 'comment (parent 'id)) 7536 > (let c (inst 'item 'type 'comment 'id (new-item-id) 7537 > 'text text 'parent parent!id 'by user 'ip ip) 7538 > (save-item c) 7539 > (= (items* c!id) c) 7540 > (push c!id parent!kids) 7541 > (save-item parent) 7542 > (push c comments*) 7543 > c)) 7544 > 7545 > 7546 > ; Comment Display 7547 > 7548 > (def display-comment-tree (c user whence (o indent 0) (o initialpar)) 7549 > (when (cansee-descendant user c) 7550 > (display-1comment c user whence indent initialpar) 7551 > (display-subcomments c user whence (+ indent 1)))) 7552 > 7553 > (def display-1comment (c user whence indent showpar) 7554 > (row (tab (display-comment nil c user whence t indent showpar showpar)))) 7555 > 7556 > (def display-subcomments (c user whence (o indent 0)) 7557 > (each k (sort (compare > frontpage-rank:item) c!kids) 7558 > (display-comment-tree (item k) user whence indent))) 7559 > 7560 > (def display-comment (n c user whence (o astree) (o indent 0) 7561 > (o showpar) (o showon)) 7562 > (tr (display-item-number n) 7563 > (when astree (td (hspace (* indent 40)))) 7564 > (tag (td valign 'top) (votelinks c user whence t)) 7565 > (display-comment-body c user whence astree indent showpar showon))) 7566 > 7567 > ; Comment caching doesn't make generation of comments significantly 7568 > ; faster, but may speed up everything else by generating less garbage. 7569 > 7570 > ; It might solve the same problem more generally to make html code 7571 > ; more efficient. 7572 > 7573 > (= comment-cache* (table) comment-cache-timeout* (table) cc-window* 10000) 7574 > 7575 > (= comments-printed* 0 cc-hits* 0) 7576 > 7577 > (= comment-caching* t) 7578 > 7579 > ; Cache comments generated for nil user that are over an hour old. 7580 > ; Only try to cache most recent 10k items. But this window moves, 7581 > ; so if server is running a long time could have more than that in 7582 > ; cache. Probably should actively gc expired cache entries. 7583 > 7584 > (def display-comment-body (c user whence astree indent showpar showon) 7585 > (++ comments-printed*) 7586 > (if (and comment-caching* 7587 > astree (no showpar) (no showon) 7588 > (live c) 7589 > (nor (admin user) (editor user) (author user c)) 7590 > (< (- maxid* c!id) cc-window*) 7591 > (> (- (seconds) c!time) 60)) ; was 3600 7592 > (pr (cached-comment-body c user whence indent)) 7593 > (gen-comment-body c user whence astree indent showpar showon))) 7594 > 7595 > (def cached-comment-body (c user whence indent) 7596 > (or (and (> (or (comment-cache-timeout* c!id) 0) (seconds)) 7597 > (awhen (comment-cache* c!id) 7598 > (++ cc-hits*) 7599 > it)) 7600 > (= (comment-cache-timeout* c!id) 7601 > (cc-timeout c!time) 7602 > (comment-cache* c!id) 7603 > (tostring (gen-comment-body c user whence t indent nil nil))))) 7604 > 7605 > ; Cache for the remainder of the current minute, hour, or day. 7606 > 7607 > (def cc-timeout (t0) 7608 > (let age (- (seconds) t0) 7609 > (+ t0 (if (< age 3600) 7610 > (* (+ (trunc (/ age 60)) 1) 60) 7611 > (< age 86400) 7612 > (* (+ (trunc (/ age 3600)) 1) 3600) 7613 > (* (+ (trunc (/ age 86400)) 1) 86400))))) 7614 > 7615 > (def gen-comment-body (c user whence astree indent showpar showon) 7616 > (tag (td class 'default) 7617 > (let parent (and (or (no astree) showpar) (c 'parent)) 7618 > (tag (div style "margin-top:2px; margin-bottom:-10px; ") 7619 > (spanclass comhead 7620 > (itemline c user) 7621 > (permalink c user) 7622 > (when parent 7623 > (when (cansee user c) (pr bar*)) 7624 > (link "parent" (item-url ((item parent) 'id)))) 7625 > (editlink c user) 7626 > (killlink c user whence) 7627 > (blastlink c user whence) 7628 > (deletelink c user whence) 7629 > ; a hack to check whence but otherwise need an arg just for this 7630 > (unless (or astree (is whence "newcomments")) 7631 > (flaglink c user whence)) 7632 > (deadmark c user) 7633 > (when showon 7634 > (pr " | on: ") 7635 > (let s (superparent c) 7636 > (link (ellipsize s!title 50) (item-url s!id)))))) 7637 > (when (or parent (cansee user c)) 7638 > (br)) 7639 > (spanclass comment 7640 > (if (~cansee user c) (pr (pseudo-text c)) 7641 > (nor (live c) (author user c)) (spanclass dead (pr c!text)) 7642 > (fontcolor (comment-color c) 7643 > (pr c!text)))) 7644 > (when (and astree (cansee user c) (live c)) 7645 > (para) 7646 > (tag (font size 1) 7647 > (if (and (~mem 'neutered c!keys) 7648 > (replyable c indent) 7649 > (comments-active c)) 7650 > (underline (replylink c whence)) 7651 > (fontcolor sand (pr "-----")))))))) 7652 > 7653 > ; For really deeply nested comments, caching could add another reply 7654 > ; delay, but that's ok. 7655 > 7656 > ; People could beat this by going to the link url or manually entering 7657 > ; the reply url, but deal with that if they do. 7658 > 7659 > (= reply-decay* 1.8) ; delays: (0 0 1 3 7 12 18 25 33 42 52 63) 7660 > 7661 > (def replyable (c indent) 7662 > (or (< indent 2) 7663 > (> (item-age c) (expt (- indent 1) reply-decay*)))) 7664 > 7665 > (def replylink (i whence (o title 'reply)) 7666 > (link title (+ "reply?id=" i!id "&whence=" (urlencode whence)))) 7667 > 7668 > (newsop reply (id whence) 7669 > (with (i (safe-item id) 7670 > whence (or (only.urldecode whence) "news")) 7671 > (if (only.comments-active i) 7672 > (if user 7673 > (addcomment-page i user whence) 7674 > (login-page 'both "You have to be logged in to comment." 7675 > (fn (u ip) 7676 > (ensure-news-user u) 7677 > (newslog ip u 'comment-login) 7678 > (addcomment-page i u whence)))) 7679 > (pr "No such item.")))) 7680 > 7681 > (def comment-color (c) 7682 > (if (> c!score 0) black (grayrange c!score))) 7683 > 7684 > (defmemo grayrange (s) 7685 > (gray (min 230 (round (expt (* (+ (abs s) 2) 900) .6))))) 7686 > 7687 > 7688 > ; Threads 7689 > 7690 > (def threads-url (user) (+ "threads?id=" user)) 7691 > 7692 > (newsop threads (id) 7693 > (if id 7694 > (threads-page user id) 7695 > (pr "No user specified."))) 7696 > 7697 > (def threads-page (user subject) 7698 > (if (profile subject) 7699 > (withs (title (+ subject "'s comments") 7700 > label (if (is user subject) "threads" title) 7701 > here (threads-url subject)) 7702 > (longpage user (msec) nil label title here 7703 > (awhen (keep [and (cansee user _) (~subcomment _)] 7704 > (comments subject maxend*)) 7705 > (display-threads user it label title here)))) 7706 > (prn "No such user."))) 7707 > 7708 > (def display-threads (user comments label title whence 7709 > (o start 0) (o end threads-perpage*)) 7710 > (tab 7711 > (each c (cut comments start end) 7712 > (display-comment-tree c user whence 0 t)) 7713 > (when end 7714 > (let newend (+ end threads-perpage*) 7715 > (when (and (<= newend maxend*) (< end (len comments))) 7716 > (spacerow 10) 7717 > (row (tab (tr (td (hspace 0)) 7718 > (td (hspace votewid*)) 7719 > (tag (td class 'title) 7720 > (morelink display-threads 7721 > comments label title end newend)))))))))) 7722 > 7723 > (def submissions (user (o limit)) 7724 > (map item (firstn limit (uvar user submitted)))) 7725 > 7726 > (def comments (user (o limit)) 7727 > (map item (retrieve limit acomment:item (uvar user submitted)))) 7728 > 7729 > (def subcomment (c) 7730 > (some [and (acomment _) (is _!by c!by) (no _!deleted)] 7731 > (ancestors c))) 7732 > 7733 > (def ancestors (i) 7734 > (accum a (trav i!parent a:item self:!parent:item))) 7735 > 7736 > 7737 > ; Submitted 7738 > 7739 > (def submitted-url (user) (+ "submitted?id=" user)) 7740 > 7741 > (newsop submitted (id) 7742 > (if id 7743 > (submitted-page user id) 7744 > (pr "No user specified."))) 7745 > 7746 > (def submitted-page (user subject) 7747 > (if (profile subject) 7748 > (with (label (+ subject "'s submissions") 7749 > here (submitted-url subject)) 7750 > (longpage user (msec) nil label label here 7751 > (if (or (no (ignored subject)) 7752 > (is user subject) 7753 > (seesdead user)) 7754 > (aif (keep [and (metastory _) (cansee user _)] 7755 > (submissions subject)) 7756 > (display-items user it label label here 0 perpage* t))))) 7757 > (pr "No such user."))) 7758 > 7759 > 7760 > ; RSS 7761 > 7762 > (newsop rss () (rsspage nil)) 7763 > 7764 > (newscache rsspage user 90 7765 > (rss-stories (retrieve perpage* live ranked-stories*))) 7766 > 7767 > (def rss-stories (stories) 7768 > (tag (rss version "2.0") 7769 > (tag channel 7770 > (tag title (pr this-site*)) 7771 > (tag link (pr site-url*)) 7772 > (tag description (pr site-desc*)) 7773 > (each s stories 7774 > (tag item 7775 > (let comurl (+ site-url* (item-url s!id)) 7776 > (tag title (pr (eschtml s!title))) 7777 > (tag link (pr (if (blank s!url) comurl (eschtml s!url)))) 7778 > (tag comments (pr comurl)) 7779 > (tag description 7780 > (cdata (link "Comments" comurl))))))))) 7781 > 7782 > 7783 > ; User Stats 7784 > 7785 > (newsop leaders () (leaderspage user)) 7786 > 7787 > (= nleaders* 20) 7788 > 7789 > (newscache leaderspage user 1000 7790 > (longpage user (msec) nil "leaders" "Leaders" "leaders" 7791 > (sptab 7792 > (let i 0 7793 > (each u (firstn nleaders* (leading-users)) 7794 > (tr (tdr:pr (++ i) ".") 7795 > (td (userlink user u nil)) 7796 > (tdr:pr (karma u)) 7797 > (when (admin user) 7798 > (tdr:prt (only.num (uvar u avg) 2 t t)))) 7799 > (if (is i 10) (spacerow 30))))))) 7800 > 7801 > (= leader-threshold* 1) ; redefined later 7802 > 7803 > (def leading-users () 7804 > (sort (compare > [karma _]) 7805 > (users [and (> (karma _) leader-threshold*) (~admin _)]))) 7806 > 7807 > (adop editors () 7808 > (tab (each u (users [is (uvar _ auth) 1]) 7809 > (row (userlink user u))))) 7810 > 7811 > 7812 > (= update-avg-threshold* 0) ; redefined later 7813 > 7814 > (defbg update-avg 45 7815 > (unless (or (empty profs*) (no stories*)) 7816 > (update-avg (rand-user [and (only.> (car (uvar _ submitted)) 7817 > (- maxid* initload*)) 7818 > (len> (uvar _ submitted) 7819 > update-avg-threshold*)])))) 7820 > 7821 > (def update-avg (user) 7822 > (= (uvar user avg) (comment-score user)) 7823 > (save-prof user)) 7824 > 7825 > (def rand-user ((o test idfn)) 7826 > (evtil (rand-key profs*) test)) 7827 > 7828 > ; Ignore the most recent 5 comments since they may still be gaining votes. 7829 > ; Also ignore the highest-scoring comment, since possibly a fluff outlier. 7830 > 7831 > (def comment-score (user) 7832 > (aif (check (nthcdr 5 (comments user 50)) [len> _ 10]) 7833 > (avg (cdr (sort > (map !score (rem !deleted it))))) 7834 > nil)) 7835 > 7836 > 7837 > ; Comment Analysis 7838 > 7839 > ; Instead of a separate active op, should probably display this info 7840 > ; implicitly by e.g. changing color of commentlink or by showing the 7841 > ; no of comments since that user last looked. 7842 > 7843 > (newsop active () (active-page user)) 7844 > 7845 > (newscache active-page user 600 7846 > (listpage user (msec) (actives user) "active" "Active Threads")) 7847 > 7848 > (def actives (user (o n maxend*) (o consider 2000)) 7849 > (visible user (rank-stories n consider (memo active-rank)))) 7850 > 7851 > (= active-threshold* 1500) 7852 > 7853 > (def active-rank (s) 7854 > (sum [max 0 (- active-threshold* (item-age _))] 7855 > (cdr (family s)))) 7856 > 7857 > (def family (i) (cons i (mappend family:item i!kids))) 7858 > 7859 > 7860 > (newsop newcomments () (newcomments-page user)) 7861 > 7862 > (newscache newcomments-page user 60 7863 > (listpage user (msec) (visible user (firstn maxend* comments*)) 7864 > "comments" "New Comments" "newcomments" nil)) 7865 > 7866 > 7867 > ; Doc 7868 > 7869 > (defop formatdoc req 7870 > (msgpage (get-user req) formatdoc* "Formatting Options")) 7871 > 7872 > (= formatdoc-url* "formatdoc") 7873 > 7874 > (= formatdoc* 7875 > "Blank lines separate paragraphs. 7876 >

Text after a blank line that is indented by two or more spaces is 7877 > reproduced verbatim. (This is intended for code.) 7878 >

Text surrounded by asterisks is italicized, if the character after the 7879 > first asterisk isn't whitespace. 7880 >

Urls become links, except in the text field of a submission.

") 7881 > 7882 > 7883 > ; Noprocrast 7884 > 7885 > (def check-procrast (user) 7886 > (or (no user) 7887 > (no (uvar user noprocrast)) 7888 > (let now (seconds) 7889 > (unless (uvar user firstview) 7890 > (reset-procrast user)) 7891 > (or (when (< (/ (- now (uvar user firstview)) 60) 7892 > (uvar user maxvisit)) 7893 > (= (uvar user lastview) now) 7894 > (save-prof user) 7895 > t) 7896 > (when (> (/ (- now (uvar user lastview)) 60) 7897 > (uvar user minaway)) 7898 > (reset-procrast user) 7899 > t))))) 7900 > 7901 > (def reset-procrast (user) 7902 > (= (uvar user lastview) (= (uvar user firstview) (seconds))) 7903 > (save-prof user)) 7904 > 7905 > (def procrast-msg (user whence) 7906 > (let m (+ 1 (trunc (- (uvar user minaway) 7907 > (minutes-since (uvar user lastview))))) 7908 > (pr "Get back to work!") 7909 > (para "Sorry, you can't see this page. Based on the anti-procrastination 7910 > parameters you set in your profile, you'll be able to use the site 7911 > again in " (plural m "minute") ".") 7912 > (para "(If you got this message after submitting something, don't worry, 7913 > the submission was processed.)") 7914 > (para "To change your anti-procrastination settings, go to your profile 7915 > by clicking on your username. If noprocrast is set to 7916 > yes, you'll be limited to sessions of maxvisit 7917 > minutes, with minaway minutes between them.") 7918 > (para) 7919 > (w/rlink whence (underline (pr "retry"))) 7920 > ; (hspace 20) 7921 > ; (w/rlink (do (reset-procrast user) whence) (underline (pr "override"))) 7922 > (br2))) 7923 > 7924 > 7925 > ; Reset PW 7926 > 7927 > (defopg resetpw req (resetpw-page (get-user req))) 7928 > 7929 > (def resetpw-page (user (o msg)) 7930 > (minipage "Reset Password" 7931 > (if msg 7932 > (pr msg) 7933 > (blank (uvar user email)) 7934 > (do (pr "Before you do this, please add your email address to your ") 7935 > (underlink "profile" (user-url user)) 7936 > (pr ". Otherwise you could lose your account if you mistype 7937 > your new password."))) 7938 > (br2) 7939 > (uform user req (try-resetpw user (arg req "p")) 7940 > (single-input "New password: " 'p 20 "reset" t)))) 7941 > 7942 > (def try-resetpw (user newpw) 7943 > (if (len< newpw 4) 7944 > (resetpw-page user "Passwords should be a least 4 characters long. 7945 > Please choose another.") 7946 > (do (set-pw user newpw) 7947 > (newspage user)))) 7948 > 7949 > 7950 > ; Scrubrules 7951 > 7952 > (defopa scrubrules req 7953 > (scrub-page (get-user req) scrubrules*)) 7954 > 7955 > ; If have other global alists, generalize an alist edit page. 7956 > ; Or better still generalize vars-form. 7957 > 7958 > (def scrub-page (user rules (o msg nil)) 7959 > (minipage "Scrubrules" 7960 > (when msg (pr msg) (br2)) 7961 > (uform user req 7962 > (with (froms (lines (arg req "from")) 7963 > tos (lines (arg req "to"))) 7964 > (if (is (len froms) (len tos)) 7965 > (do (todisk scrubrules* (map list froms tos)) 7966 > (scrub-page user scrubrules* "Changes saved.")) 7967 > (scrub-page user rules "To and from should be same length."))) 7968 > (pr "From: ") 7969 > (tag (textarea name 'from 7970 > cols (apply max 20 (map len (map car rules))) 7971 > rows (+ (len rules) 3)) 7972 > (apply pr #\newline (intersperse #\newline (map car rules)))) 7973 > (pr " To: ") 7974 > (tag (textarea name 'to 7975 > cols (apply max 20 (map len (map cadr rules))) 7976 > rows (+ (len rules) 3)) 7977 > (apply pr #\newline (intersperse #\newline (map cadr rules)))) 7978 > (br2) 7979 > (submit "update")))) 7980 > 7981 > 7982 > ; Abuse Analysis 7983 > 7984 > (adop badsites () 7985 > (sptab 7986 > (row "Dead" "Days" "Site" "O" "K" "I" "Users") 7987 > (each (site deads) (with (banned (banned-site-items) 7988 > pairs (killedsites)) 7989 > (+ pairs (map [list _ (banned _)] 7990 > (rem (fn (d) 7991 > (some [caris _ d] pairs)) 7992 > (keys banned-sites*))))) 7993 > (let ban (car (banned-sites* site)) 7994 > (tr (tdr (when deads 7995 > (onlink (len deads) 7996 > (listpage user (msec) deads 7997 > nil (+ "killed at " site) "badsites")))) 7998 > (tdr (when deads (pr (round (days-since ((car deads) 'time)))))) 7999 > (td site) 8000 > (td (w/rlink (do (set-site-ban user site nil) "badsites") 8001 > (fontcolor (if ban gray.220 black) (pr "x")))) 8002 > (td (w/rlink (do (set-site-ban user site 'kill) "badsites") 8003 > (fontcolor (case ban kill darkred gray.220) (pr "x")))) 8004 > (td (w/rlink (do (set-site-ban user site 'ignore) "badsites") 8005 > (fontcolor (case ban ignore darkred gray.220) (pr "x")))) 8006 > (td (each u (dedup (map !by deads)) 8007 > (userlink user u nil) 8008 > (pr " ")))))))) 8009 > 8010 > (defcache killedsites 300 8011 > (let bads (table [each-loaded-item i 8012 > (awhen (and i!dead (sitename i!url)) 8013 > (push i (_ it)))]) 8014 > (with (acc nil deadcount (table)) 8015 > (each (site items) bads 8016 > (let n (len items) 8017 > (when (> n 2) 8018 > (= (deadcount site) n) 8019 > (insort (compare > deadcount:car) 8020 > (list site (rev items)) 8021 > acc)))) 8022 > acc))) 8023 > 8024 > (defcache banned-site-items 300 8025 > (table [each-loaded-item i 8026 > (awhen (and i!dead (check (sitename i!url) banned-sites*)) 8027 > (push i (_ it)))])) 8028 > 8029 > ; Would be nice to auto unban ips whose most recent submission is > n 8030 > ; days old, but hard to do because of lazy loading. Would have to keep 8031 > ; a table of most recent submission per ip, and only enforce bannnedness 8032 > ; if < n days ago. 8033 > 8034 > (adop badips () 8035 > (withs ((bads goods) (badips) 8036 > (subs ips) (sorted-badips bads goods)) 8037 > (sptab 8038 > (row "IP" "Days" "Dead" "Live" "Users") 8039 > (each ip ips 8040 > (tr (td (let banned (banned-ips* ip) 8041 > (w/rlink (do (set-ip-ban user ip (no banned)) 8042 > "badips") 8043 > (fontcolor (if banned darkred) (pr ip))))) 8044 > (tdr (when (or (goods ip) (bads ip)) 8045 > (pr (round (days-since 8046 > (max (aif (car (goods ip)) it!time 0) 8047 > (aif (car (bads ip)) it!time 0))))))) 8048 > (tdr (onlink (len (bads ip)) 8049 > (listpage user (msec) (bads ip) 8050 > nil (+ "dead from " ip) "badips"))) 8051 > (tdr (onlink (len (goods ip)) 8052 > (listpage user (msec) (goods ip) 8053 > nil (+ "live from " ip) "badips"))) 8054 > (td (each u (subs ip) 8055 > (userlink user u nil) 8056 > (pr " ")))))))) 8057 > 8058 > (defcache badips 300 8059 > (with (bads (table) goods (table)) 8060 > (each-loaded-item s 8061 > (if (and s!dead (commentable s)) 8062 > (push s (bads s!ip)) 8063 > (push s (goods s!ip)))) 8064 > (each (k v) bads (zap rev (bads k))) 8065 > (each (k v) goods (zap rev (goods k))) 8066 > (list bads goods))) 8067 > 8068 > (def sorted-badips (bads goods) 8069 > (withs (ips (let ips (rem [len< (bads _) 2] (keys bads)) 8070 > (+ ips (rem [mem _ ips] (keys banned-ips*)))) 8071 > subs (table 8072 > [each ip ips 8073 > (= (_ ip) (dedup (map !by (+ (bads ip) (goods ip)))))])) 8074 > (list subs 8075 > (sort (compare > (memo [badness (subs _) (bads _) (goods _)])) 8076 > ips)))) 8077 > 8078 > (def badness (subs bads goods) 8079 > (* (/ (len bads) 8080 > (max .9 (expt (len goods) 2)) 8081 > (expt (+ (days-since (aif (car bads) it!time 0)) 8082 > 1) 8083 > 2)) 8084 > (if (len> subs 1) 20 1))) 8085 > 8086 > 8087 > (edop flagged () 8088 > (display-selected-items user [retrieve maxend* flagged _] "flagged")) 8089 > 8090 > (def flagged (i) 8091 > (and (live i) 8092 > (~mem 'nokill i!keys) 8093 > (len> i!flags many-flags*))) 8094 > 8095 > 8096 > (edop killed () 8097 > (display-selected-items user [retrieve maxend* !dead _] "killed")) 8098 > 8099 > (def display-selected-items (user f whence) 8100 > (display-items user (f stories*) nil nil whence) 8101 > (vspace 35) 8102 > (color-stripe textgray) 8103 > (vspace 35) 8104 > (display-items user (f comments*) nil nil whence)) 8105 > 8106 > 8107 > ; Rather useless thus; should add more data. 8108 > 8109 > (adop badguys () 8110 > (tab (each u (sort (compare > [uvar _ created]) 8111 > (users [ignored _])) 8112 > (row (userlink user u nil))))) 8113 > 8114 > (adop badlogins () (logins-page bad-logins*)) 8115 > 8116 > (adop goodlogins () (logins-page good-logins*)) 8117 > 8118 > (def logins-page (source) 8119 > (sptab (each (time ip user) (firstn 100 (rev (qlist source))) 8120 > (row time ip user)))) 8121 > 8122 > 8123 > ; Stats 8124 > 8125 > (adop optimes () 8126 > (sptab 8127 > (tr (td "op") (tdr "avg") (tdr "med") (tdr "req") (tdr "total")) 8128 > (spacerow 10) 8129 > (each name (sort < newsop-names*) 8130 > (tr (td name) 8131 > (let ms (only.avg (qlist (optimes* name))) 8132 > (tdr:prt (only.round ms)) 8133 > (tdr:prt (only.med (qlist (optimes* name)))) 8134 > (let n (opcounts* name) 8135 > (tdr:prt n) 8136 > (tdr:prt (and n (round (/ (* n ms) 1000)))))))))) 8137 > 8138 > (defop topcolors req 8139 > (minipage "Custom Colors" 8140 > (tab 8141 > (each c (dedup (map downcase (trues [uvar _ topcolor] (users)))) 8142 > (tr (td c) (tdcolor (hex>color c) (hspace 30))))))) 8143 > 8144 > 8145 >