2009-03-28 17:39:04 -04:00
|
|
|
; -*- scheme -*-
|
|
|
|
|
2009-05-31 17:06:04 -04:00
|
|
|
(define Instructions
|
|
|
|
(let ((e (table))
|
2019-08-09 10:18:36 -04:00
|
|
|
(keys
|
|
|
|
[nop dup pop call tcall jmp brf brt jmp.l brf.l brt.l ret
|
|
|
|
|
|
|
|
eq? eqv? equal? atom? not null? boolean? symbol?
|
|
|
|
number? bound? pair? builtin? vector? fixnum? function?
|
|
|
|
|
|
|
|
cons list car cdr set-car! set-cdr!
|
|
|
|
apply
|
|
|
|
|
|
|
|
+ - * / div0 = < compare
|
|
|
|
|
|
|
|
vector aref aset!
|
|
|
|
|
|
|
|
loadt loadf loadnil load0 load1 loadi8
|
|
|
|
loadv loadv.l
|
|
|
|
loadg loadg.l
|
|
|
|
loada loada.l loadc loadc.l
|
|
|
|
setg setg.l
|
|
|
|
seta seta.l setc setc.l
|
|
|
|
|
|
|
|
closure argc vargc trycatch for tapply
|
|
|
|
add2 sub2 neg largc lvargc
|
|
|
|
loada0 loada1 loadc00 loadc01 call.l tcall.l
|
|
|
|
brne brne.l cadr brnn brnn.l brn brn.l
|
|
|
|
optargs brbound keyargs
|
|
|
|
|
|
|
|
dummy_t dummy_f dummy_nil]))
|
2009-03-28 17:39:04 -04:00
|
|
|
(for 0 (1- (length keys))
|
2019-08-09 10:18:36 -04:00
|
|
|
(lambda (i)
|
|
|
|
(put! e (aref keys i) i)))))
|
2009-04-01 22:22:38 -04:00
|
|
|
|
|
|
|
(define arg-counts
|
2009-07-28 00:16:20 -04:00
|
|
|
(table eq? 2 eqv? 2
|
2019-08-09 10:18:36 -04:00
|
|
|
equal? 2 atom? 1
|
|
|
|
not 1 null? 1
|
|
|
|
boolean? 1 symbol? 1
|
|
|
|
number? 1 bound? 1
|
|
|
|
pair? 1 builtin? 1
|
|
|
|
vector? 1 fixnum? 1
|
|
|
|
cons 2 car 1
|
|
|
|
cdr 1 set-car! 2
|
|
|
|
set-cdr! 2 = 2
|
2009-07-28 00:16:20 -04:00
|
|
|
< 2 compare 2
|
|
|
|
aref 2 aset! 3
|
2019-08-09 10:18:36 -04:00
|
|
|
div0 2))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-07-16 21:30:26 -04:00
|
|
|
(define (make-code-emitter) (vector () (table) 0 +inf.0))
|
2009-06-06 17:15:54 -04:00
|
|
|
(define (bcode:code b) (aref b 0))
|
|
|
|
(define (bcode:ctable b) (aref b 1))
|
|
|
|
(define (bcode:nconst b) (aref b 2))
|
2009-07-16 21:30:26 -04:00
|
|
|
(define (bcode:cdepth b d) (aset! b 3 (min (aref b 3) d)))
|
2009-06-06 17:15:54 -04:00
|
|
|
; get an index for a referenced value in a bytecode object
|
|
|
|
(define (bcode:indexfor b v)
|
|
|
|
(let ((const-to-idx (bcode:ctable b))
|
2019-08-09 10:18:36 -04:00
|
|
|
(nconst (bcode:nconst b)))
|
2009-06-06 17:15:54 -04:00
|
|
|
(if (has? const-to-idx v)
|
2019-08-09 10:18:36 -04:00
|
|
|
(get const-to-idx v)
|
|
|
|
(begin (put! const-to-idx v nconst)
|
|
|
|
(prog1 nconst
|
|
|
|
(aset! b 2 (+ nconst 1)))))))
|
2009-03-28 17:39:04 -04:00
|
|
|
(define (emit e inst . args)
|
2009-06-10 20:34:50 -04:00
|
|
|
(if (null? args)
|
2009-07-28 00:16:20 -04:00
|
|
|
(if (and (eq? inst 'car) (pair? (aref e 0))
|
2019-08-09 10:18:36 -04:00
|
|
|
(eq? (car (aref e 0)) 'cdr))
|
|
|
|
(set-car! (aref e 0) 'cadr)
|
|
|
|
(aset! e 0 (cons inst (aref e 0))))
|
2009-06-10 20:34:50 -04:00
|
|
|
(begin
|
2019-08-09 10:18:36 -04:00
|
|
|
(if (memq inst '(loadv loadg setg))
|
|
|
|
(set! args (list (bcode:indexfor e (car args)))))
|
|
|
|
(let ((longform
|
|
|
|
(assq inst '((loadv loadv.l) (loadg loadg.l) (setg setg.l)
|
|
|
|
(loada loada.l) (seta seta.l)))))
|
|
|
|
(if (and longform
|
|
|
|
(> (car args) 255))
|
|
|
|
(set! inst (cadr longform))))
|
|
|
|
(let ((longform
|
|
|
|
(assq inst '((loadc loadc.l) (setc setc.l)))))
|
|
|
|
(if (and longform
|
|
|
|
(or (> (car args) 255)
|
|
|
|
(> (cadr args) 255)))
|
|
|
|
(set! inst (cadr longform))))
|
|
|
|
(if (eq? inst 'loada)
|
|
|
|
(cond ((equal? args '(0))
|
|
|
|
(set! inst 'loada0)
|
|
|
|
(set! args ()))
|
|
|
|
((equal? args '(1))
|
|
|
|
(set! inst 'loada1)
|
|
|
|
(set! args ()))))
|
|
|
|
(if (eq? inst 'loadc)
|
|
|
|
(cond ((equal? args '(0 0))
|
|
|
|
(set! inst 'loadc00)
|
|
|
|
(set! args ()))
|
|
|
|
((equal? args '(0 1))
|
|
|
|
(set! inst 'loadc01)
|
|
|
|
(set! args ()))))
|
|
|
|
|
|
|
|
(let ((lasti (if (pair? (aref e 0))
|
|
|
|
(car (aref e 0)) ()))
|
|
|
|
(bc (aref e 0)))
|
|
|
|
(cond ((and
|
|
|
|
(eq? inst 'brf)
|
|
|
|
(cond ((and (eq? lasti 'not)
|
|
|
|
(eq? (cadr bc) 'null?))
|
|
|
|
(aset! e 0 (cons (car args) (cons 'brn (cddr bc)))))
|
|
|
|
((eq? lasti 'not)
|
|
|
|
(aset! e 0 (cons (car args) (cons 'brt (cdr bc)))))
|
|
|
|
((eq? lasti 'eq?)
|
|
|
|
(aset! e 0 (cons (car args) (cons 'brne (cdr bc)))))
|
|
|
|
((eq? lasti 'null?)
|
|
|
|
(aset! e 0 (cons (car args) (cons 'brnn (cdr bc)))))
|
|
|
|
(else #f))))
|
|
|
|
((and (eq? inst 'brt) (eq? lasti 'null?))
|
|
|
|
(aset! e 0 (cons (car args) (cons 'brn (cdr bc)))))
|
|
|
|
(else
|
|
|
|
(aset! e 0 (nreconc (cons inst args) bc)))))))
|
2009-03-28 17:39:04 -04:00
|
|
|
e)
|
|
|
|
|
|
|
|
(define (make-label e) (gensym))
|
2009-07-28 00:16:20 -04:00
|
|
|
(define (mark-label e l) (emit e 'label l))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
|
|
|
; convert symbolic bytecode representation to a byte array.
|
|
|
|
; labels are fixed-up.
|
|
|
|
(define (encode-byte-code e)
|
2009-05-31 17:06:04 -04:00
|
|
|
(let* ((cl (reverse! e))
|
2019-08-09 10:18:36 -04:00
|
|
|
(v (list->vector cl))
|
|
|
|
(long? (>= (+ (length v) ; 1 byte for each entry, plus...
|
|
|
|
; at most half the entries in this vector can be
|
|
|
|
; instructions accepting 32-bit arguments
|
|
|
|
(* 3 (div0 (length v) 2)))
|
|
|
|
65536)))
|
2009-03-28 17:39:04 -04:00
|
|
|
(let ((n (length v))
|
2019-08-09 10:18:36 -04:00
|
|
|
(i 0)
|
|
|
|
(label-to-loc (table))
|
|
|
|
(fixup-to-label (table))
|
|
|
|
(bcode (buffer))
|
|
|
|
(vi #f)
|
|
|
|
(nxt #f))
|
2009-06-27 19:07:22 -04:00
|
|
|
(io.write bcode #int32(0))
|
2009-03-28 17:39:04 -04:00
|
|
|
(while (< i n)
|
2019-08-09 10:18:36 -04:00
|
|
|
(begin
|
|
|
|
(set! vi (aref v i))
|
|
|
|
(if (eq? vi 'label)
|
|
|
|
(begin (put! label-to-loc (aref v (+ i 1)) (sizeof bcode))
|
|
|
|
(set! i (+ i 2)))
|
|
|
|
(begin
|
|
|
|
(io.write bcode
|
|
|
|
(byte
|
|
|
|
(get Instructions
|
|
|
|
(if long?
|
|
|
|
(case vi
|
|
|
|
(jmp 'jmp.l)
|
|
|
|
(brt 'brt.l)
|
|
|
|
(brf 'brf.l)
|
|
|
|
(brne 'brne.l)
|
|
|
|
(brnn 'brnn.l)
|
|
|
|
(brn 'brn.l)
|
|
|
|
(else vi))
|
|
|
|
vi))))
|
|
|
|
(set! i (+ i 1))
|
|
|
|
(set! nxt (if (< i n) (aref v i) #f))
|
|
|
|
(cond ((memq vi '(jmp brf brt brne brnn brn))
|
|
|
|
(put! fixup-to-label (sizeof bcode) nxt)
|
|
|
|
(io.write bcode ((if long? int32 int16) 0))
|
|
|
|
(set! i (+ i 1)))
|
|
|
|
((eq? vi 'brbound)
|
|
|
|
(io.write bcode (int32 nxt))
|
|
|
|
(set! i (+ i 1)))
|
|
|
|
((number? nxt)
|
|
|
|
(case vi
|
|
|
|
((loadv.l loadg.l setg.l loada.l seta.l
|
|
|
|
largc lvargc call.l tcall.l)
|
|
|
|
(io.write bcode (int32 nxt))
|
|
|
|
(set! i (+ i 1)))
|
|
|
|
|
|
|
|
((loadc setc) ; 2 uint8 args
|
|
|
|
(io.write bcode (uint8 nxt))
|
|
|
|
(set! i (+ i 1))
|
|
|
|
(io.write bcode (uint8 (aref v i)))
|
|
|
|
(set! i (+ i 1)))
|
|
|
|
|
|
|
|
((loadc.l setc.l optargs keyargs) ; 2 int32 args
|
|
|
|
(io.write bcode (int32 nxt))
|
|
|
|
(set! i (+ i 1))
|
|
|
|
(io.write bcode (int32 (aref v i)))
|
|
|
|
(set! i (+ i 1))
|
|
|
|
(if (eq? vi 'keyargs)
|
|
|
|
(begin (io.write bcode (int32 (aref v i)))
|
|
|
|
(set! i (+ i 1)))))
|
|
|
|
|
|
|
|
(else
|
|
|
|
; other number arguments are always uint8
|
|
|
|
(io.write bcode (uint8 nxt))
|
|
|
|
(set! i (+ i 1)))))
|
|
|
|
(else #f))))))
|
2009-06-06 17:15:54 -04:00
|
|
|
|
2009-03-28 17:39:04 -04:00
|
|
|
(table.foreach
|
|
|
|
(lambda (addr labl)
|
2019-08-09 10:18:36 -04:00
|
|
|
(begin (io.seek bcode addr)
|
|
|
|
(io.write bcode ((if long? int32 int16)
|
|
|
|
(- (get label-to-loc labl)
|
|
|
|
addr)))))
|
2009-03-28 17:39:04 -04:00
|
|
|
fixup-to-label)
|
|
|
|
(io.tostring! bcode))))
|
|
|
|
|
|
|
|
(define (const-to-idx-vec e)
|
2009-06-06 17:15:54 -04:00
|
|
|
(let ((cvec (vector.alloc (bcode:nconst e))))
|
2009-04-26 18:19:32 -04:00
|
|
|
(table.foreach (lambda (val idx) (aset! cvec idx val))
|
2019-08-09 10:18:36 -04:00
|
|
|
(bcode:ctable e))
|
2009-04-26 18:19:32 -04:00
|
|
|
cvec))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
|
|
|
(define (index-of item lst start)
|
|
|
|
(cond ((null? lst) #f)
|
2019-08-09 10:18:36 -04:00
|
|
|
((eq? item (car lst)) start)
|
|
|
|
(else (index-of item (cdr lst) (+ start 1)))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-08-12 00:56:32 -04:00
|
|
|
(define (in-env? s env)
|
|
|
|
(and (pair? env)
|
|
|
|
(or (memq s (car env))
|
2019-08-09 10:18:36 -04:00
|
|
|
(in-env? s (cdr env)))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
|
|
|
(define (lookup-sym s env lev arg?)
|
|
|
|
(if (null? env)
|
|
|
|
'(global)
|
|
|
|
(let* ((curr (car env))
|
2019-08-09 10:18:36 -04:00
|
|
|
(i (index-of s curr 0)))
|
|
|
|
(if i
|
|
|
|
(if arg?
|
|
|
|
i
|
|
|
|
(cons lev i))
|
|
|
|
(lookup-sym s
|
|
|
|
(cdr env)
|
|
|
|
(if (or arg? (null? curr)) lev (+ lev 1))
|
|
|
|
#f)))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-07-16 21:30:26 -04:00
|
|
|
; number of non-nulls
|
|
|
|
(define (nnn e) (count (lambda (x) (not (null? x))) e))
|
|
|
|
|
2009-08-12 00:56:32 -04:00
|
|
|
(define (printable? x) (not (or (iostream? x)
|
2019-08-09 10:18:36 -04:00
|
|
|
(eof-object? x))))
|
2009-07-20 00:57:17 -04:00
|
|
|
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-sym g env s Is)
|
2009-04-14 20:12:01 -04:00
|
|
|
(let ((loc (lookup-sym s env 0 #t)))
|
2019-08-09 10:18:36 -04:00
|
|
|
(cond ((number? loc)
|
|
|
|
(emit g (aref Is 0) loc))
|
|
|
|
((number? (car loc))
|
|
|
|
(emit g (aref Is 1) (car loc) (cdr loc))
|
|
|
|
;; update index of most distant captured frame
|
|
|
|
(bcode:cdepth
|
|
|
|
g (- (nnn (cdr env)) 1 (car loc))))
|
|
|
|
(else
|
|
|
|
(if (and (constant? s)
|
|
|
|
(printable? (top-level-value s)))
|
|
|
|
(emit g 'loadv (top-level-value s))
|
|
|
|
(emit g (aref Is 2) s))))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-if g env tail? x)
|
2009-03-28 17:39:04 -04:00
|
|
|
(let ((elsel (make-label g))
|
2019-08-09 10:18:36 -04:00
|
|
|
(endl (make-label g))
|
|
|
|
(test (cadr x))
|
|
|
|
(then (caddr x))
|
|
|
|
(else (if (pair? (cdddr x))
|
|
|
|
(cadddr x)
|
|
|
|
(void))))
|
2009-05-30 13:04:34 -04:00
|
|
|
(cond ((eq? test #t)
|
2019-08-09 10:18:36 -04:00
|
|
|
(compile-in g env tail? then))
|
|
|
|
((eq? test #f)
|
|
|
|
(compile-in g env tail? else))
|
|
|
|
(else
|
|
|
|
(compile-in g env #f test)
|
|
|
|
(emit g 'brf elsel)
|
|
|
|
(compile-in g env tail? then)
|
|
|
|
(if tail?
|
|
|
|
(emit g 'ret)
|
|
|
|
(emit g 'jmp endl))
|
|
|
|
(mark-label g elsel)
|
|
|
|
(compile-in g env tail? else)
|
|
|
|
(mark-label g endl)))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-begin g env tail? forms)
|
2010-01-06 13:27:28 -05:00
|
|
|
(cond ((atom? forms) (compile-in g env tail? (void)))
|
2019-08-09 10:18:36 -04:00
|
|
|
((atom? (cdr forms))
|
|
|
|
(compile-in g env tail? (car forms)))
|
|
|
|
(else
|
|
|
|
(compile-in g env #f (car forms))
|
|
|
|
(emit g 'pop)
|
|
|
|
(compile-begin g env tail? (cdr forms)))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-prog1 g env x)
|
|
|
|
(compile-in g env #f (cadr x))
|
2009-03-28 17:39:04 -04:00
|
|
|
(if (pair? (cddr x))
|
2009-04-01 22:22:38 -04:00
|
|
|
(begin (compile-begin g env #f (cddr x))
|
2019-08-09 10:18:36 -04:00
|
|
|
(emit g 'pop))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-while g env cond body)
|
2009-03-28 17:39:04 -04:00
|
|
|
(let ((top (make-label g))
|
2019-08-09 10:18:36 -04:00
|
|
|
(end (make-label g)))
|
2010-01-06 13:27:28 -05:00
|
|
|
(compile-in g env #f (void))
|
2009-03-28 17:39:04 -04:00
|
|
|
(mark-label g top)
|
2009-04-01 22:22:38 -04:00
|
|
|
(compile-in g env #f cond)
|
2009-07-28 00:16:20 -04:00
|
|
|
(emit g 'brf end)
|
|
|
|
(emit g 'pop)
|
2009-04-14 20:12:01 -04:00
|
|
|
(compile-in g env #f body)
|
2009-07-28 00:16:20 -04:00
|
|
|
(emit g 'jmp top)
|
2009-03-28 17:39:04 -04:00
|
|
|
(mark-label g end)))
|
|
|
|
|
2009-04-15 23:05:38 -04:00
|
|
|
(define (1arg-lambda? func)
|
|
|
|
(and (pair? func)
|
|
|
|
(eq? (car func) 'lambda)
|
|
|
|
(pair? (cdr func))
|
|
|
|
(pair? (cadr func))
|
|
|
|
(length= (cadr func) 1)))
|
|
|
|
|
|
|
|
(define (compile-for g env lo hi func)
|
|
|
|
(if (1arg-lambda? func)
|
|
|
|
(begin (compile-in g env #f lo)
|
2019-08-09 10:18:36 -04:00
|
|
|
(compile-in g env #f hi)
|
|
|
|
(compile-in g env #f func)
|
|
|
|
(emit g 'for))
|
2009-04-15 23:05:38 -04:00
|
|
|
(error "for: third form must be a 1-argument lambda")))
|
|
|
|
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-short-circuit g env tail? forms default branch)
|
|
|
|
(cond ((atom? forms) (compile-in g env tail? default))
|
2019-08-09 10:18:36 -04:00
|
|
|
((atom? (cdr forms)) (compile-in g env tail? (car forms)))
|
|
|
|
(else
|
|
|
|
(let ((end (make-label g)))
|
|
|
|
(compile-in g env #f (car forms))
|
|
|
|
(emit g 'dup)
|
|
|
|
(emit g branch end)
|
|
|
|
(emit g 'pop)
|
|
|
|
(compile-short-circuit g env tail? (cdr forms) default branch)
|
|
|
|
(mark-label g end)))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-and g env tail? forms)
|
2009-07-28 00:16:20 -04:00
|
|
|
(compile-short-circuit g env tail? forms #t 'brf))
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-or g env tail? forms)
|
2009-07-28 00:16:20 -04:00
|
|
|
(compile-short-circuit g env tail? forms #f 'brt))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-arglist g env lst)
|
2009-07-20 00:57:17 -04:00
|
|
|
(for-each (lambda (a)
|
2019-08-09 10:18:36 -04:00
|
|
|
(compile-in g env #f a))
|
|
|
|
lst)
|
2009-07-20 00:57:17 -04:00
|
|
|
(length lst))
|
2009-04-01 00:31:49 -04:00
|
|
|
|
2009-04-09 00:04:27 -04:00
|
|
|
(define (argc-error head count)
|
2009-07-28 00:16:20 -04:00
|
|
|
(error "compile error: " head " expects " count
|
2019-08-09 10:18:36 -04:00
|
|
|
(if (= count 1)
|
|
|
|
" argument."
|
|
|
|
" arguments.")))
|
2009-04-16 17:20:15 -04:00
|
|
|
|
2009-06-14 22:25:21 -04:00
|
|
|
(define builtin->instruction
|
2009-07-28 00:16:20 -04:00
|
|
|
(let ((b2i (table number? 'number? cons 'cons
|
2019-08-09 10:18:36 -04:00
|
|
|
fixnum? 'fixnum? equal? 'equal?
|
|
|
|
eq? 'eq? symbol? 'symbol?
|
|
|
|
div0 'div0 builtin? 'builtin?
|
|
|
|
aset! 'aset! - '- boolean? 'boolean? not 'not
|
|
|
|
apply 'apply atom? 'atom?
|
|
|
|
set-cdr! 'set-cdr! / '/
|
|
|
|
function? 'function? vector 'vector
|
|
|
|
list 'list bound? 'bound?
|
|
|
|
< '< * '* cdr 'cdr null? 'null?
|
|
|
|
+ '+ eqv? 'eqv? compare 'compare aref 'aref
|
|
|
|
set-car! 'set-car! car 'car
|
|
|
|
pair? 'pair? = '= vector? 'vector?)))
|
2009-06-14 22:25:21 -04:00
|
|
|
(lambda (b)
|
|
|
|
(get b2i b #f))))
|
2009-04-26 18:19:32 -04:00
|
|
|
|
2009-07-20 00:57:17 -04:00
|
|
|
(define (compile-builtin-call g env tail? x head b nargs)
|
2013-08-26 16:55:17 -04:00
|
|
|
(let ((count (get arg-counts head #f)))
|
2009-07-20 00:57:17 -04:00
|
|
|
(if (and count
|
2019-08-09 10:18:36 -04:00
|
|
|
(not (length= (cdr x) count)))
|
|
|
|
(argc-error b count))
|
2009-07-20 00:57:17 -04:00
|
|
|
(case b ; handle special cases of vararg builtins
|
2009-07-28 00:16:20 -04:00
|
|
|
(list (if (= nargs 0) (emit g 'loadnil) (emit g b nargs)))
|
|
|
|
(+ (cond ((= nargs 0) (emit g 'load0))
|
2019-08-09 10:18:36 -04:00
|
|
|
((= nargs 2) (emit g 'add2))
|
|
|
|
(else (emit g b nargs))))
|
2013-08-26 16:55:17 -04:00
|
|
|
(- (cond ((= nargs 0) (argc-error b 1))
|
2019-08-09 10:18:36 -04:00
|
|
|
((= nargs 1) (emit g 'neg))
|
|
|
|
((= nargs 2) (emit g 'sub2))
|
|
|
|
(else (emit g b nargs))))
|
2009-07-28 00:16:20 -04:00
|
|
|
(* (if (= nargs 0) (emit g 'load1)
|
2019-08-09 10:18:36 -04:00
|
|
|
(emit g b nargs)))
|
2009-07-28 00:16:20 -04:00
|
|
|
(/ (if (= nargs 0)
|
2019-08-09 10:18:36 -04:00
|
|
|
(argc-error b 1)
|
|
|
|
(emit g b nargs)))
|
2009-07-28 00:16:20 -04:00
|
|
|
(vector (if (= nargs 0)
|
2019-08-09 10:18:36 -04:00
|
|
|
(emit g 'loadv [])
|
|
|
|
(emit g b nargs)))
|
2009-07-28 00:16:20 -04:00
|
|
|
(apply (if (< nargs 2)
|
2019-08-09 10:18:36 -04:00
|
|
|
(argc-error b 2)
|
|
|
|
(emit g (if tail? 'tapply 'apply) nargs)))
|
2009-07-20 00:57:17 -04:00
|
|
|
(else (emit g b)))))
|
|
|
|
|
2009-08-03 01:00:44 -04:00
|
|
|
(define (compile-app g env tail? x)
|
2009-04-01 00:31:49 -04:00
|
|
|
(let ((head (car x)))
|
2009-03-28 17:39:04 -04:00
|
|
|
(let ((head
|
2019-08-09 10:18:36 -04:00
|
|
|
(if (and (symbol? head)
|
|
|
|
(not (in-env? head env))
|
|
|
|
(bound? head)
|
|
|
|
(constant? head)
|
|
|
|
(builtin? (top-level-value head)))
|
|
|
|
(top-level-value head)
|
|
|
|
head)))
|
2009-07-20 00:57:17 -04:00
|
|
|
(if (length> (cdr x) 255)
|
2019-08-09 10:18:36 -04:00
|
|
|
; more than 255 arguments, need long versions of instructions
|
|
|
|
(begin (compile-in g env #f head)
|
|
|
|
(let ((nargs (compile-arglist g env (cdr x))))
|
|
|
|
(emit g (if tail? 'tcall.l 'call.l) nargs)))
|
|
|
|
(let ((b (and (builtin? head)
|
|
|
|
(builtin->instruction head))))
|
|
|
|
(if (and (eq? head 'cadr)
|
|
|
|
(not (in-env? head env))
|
|
|
|
(equal? (top-level-value 'cadr) cadr)
|
|
|
|
(length= x 2))
|
|
|
|
(begin (compile-in g env #f (cadr x))
|
|
|
|
(emit g 'cadr))
|
|
|
|
(begin
|
|
|
|
(if (not b)
|
|
|
|
(compile-in g env #f head))
|
|
|
|
(let ((nargs (compile-arglist g env (cdr x))))
|
|
|
|
(if b
|
|
|
|
(compile-builtin-call g env tail? x head b nargs)
|
|
|
|
(emit g (if tail? 'tcall 'call) nargs))))))))))
|
2009-04-01 22:22:38 -04:00
|
|
|
|
2009-08-12 01:15:21 -04:00
|
|
|
(define (expand-define x)
|
|
|
|
(let ((form (cadr x))
|
2019-08-09 10:18:36 -04:00
|
|
|
(body (if (pair? (cddr x))
|
|
|
|
(cddr x)
|
|
|
|
(if (symbol? (cadr x))
|
|
|
|
`(,(void))
|
|
|
|
(error "compile error: invalid syntax "
|
|
|
|
(print-to-string x))))))
|
2009-08-12 01:15:21 -04:00
|
|
|
(if (symbol? form)
|
2019-08-09 10:18:36 -04:00
|
|
|
`(set! ,form ,(car body))
|
|
|
|
`(set! ,(car form)
|
|
|
|
(lambda ,(cdr form) ,@body . ,(car form))))))
|
2009-07-17 22:16:18 -04:00
|
|
|
|
2009-04-26 23:21:53 -04:00
|
|
|
(define (fits-i8 x) (and (fixnum? x) (>= x -128) (<= x 127)))
|
|
|
|
|
2009-04-01 22:22:38 -04:00
|
|
|
(define (compile-in g env tail? x)
|
2009-07-28 00:16:20 -04:00
|
|
|
(cond ((symbol? x) (compile-sym g env x [loada loadc loadg]))
|
2019-08-09 10:18:36 -04:00
|
|
|
((atom? x)
|
|
|
|
(cond ((eq? x 0) (emit g 'load0))
|
|
|
|
((eq? x 1) (emit g 'load1))
|
|
|
|
((eq? x #t) (emit g 'loadt))
|
|
|
|
((eq? x #f) (emit g 'loadf))
|
|
|
|
((eq? x ()) (emit g 'loadnil))
|
|
|
|
((fits-i8 x) (emit g 'loadi8 x))
|
|
|
|
((eof-object? x)
|
|
|
|
(compile-in g env tail? (list (top-level-value 'eof-object))))
|
|
|
|
(else (emit g 'loadv x))))
|
|
|
|
((or (not (symbol? (car x))) (bound? (car x)) (in-env? (car x) env))
|
|
|
|
(compile-app g env tail? x))
|
|
|
|
(else
|
|
|
|
(case (car x)
|
|
|
|
(quote (if (self-evaluating? (cadr x))
|
|
|
|
(compile-in g env tail? (cadr x))
|
|
|
|
(emit g 'loadv (cadr x))))
|
|
|
|
(if (compile-if g env tail? x))
|
|
|
|
(begin (compile-begin g env tail? (cdr x)))
|
|
|
|
(prog1 (compile-prog1 g env x))
|
|
|
|
(lambda (receive (the-f dept) (compile-f- env x)
|
|
|
|
(begin (emit g 'loadv the-f)
|
|
|
|
(bcode:cdepth g dept)
|
|
|
|
(if (< dept (nnn env))
|
|
|
|
(emit g 'closure)))))
|
|
|
|
(and (compile-and g env tail? (cdr x)))
|
|
|
|
(or (compile-or g env tail? (cdr x)))
|
|
|
|
(while (compile-while g env (cadr x) (cons 'begin (cddr x))))
|
|
|
|
(for (compile-for g env (cadr x) (caddr x) (cadddr x)))
|
|
|
|
(return (compile-in g env #t (cadr x))
|
|
|
|
(emit g 'ret))
|
|
|
|
(set! (compile-in g env #f (caddr x))
|
|
|
|
(or (symbol? (cadr x))
|
|
|
|
(error "set!: second argument must be a symbol"))
|
|
|
|
(compile-sym g env (cadr x) [seta setc setg]))
|
|
|
|
(define (compile-in g env tail?
|
|
|
|
(expand-define x)))
|
|
|
|
(trycatch (compile-in g env #f `(lambda () ,(cadr x)))
|
|
|
|
(unless (1arg-lambda? (caddr x))
|
|
|
|
(error
|
|
|
|
"trycatch: second form must be a 1-argument lambda"))
|
|
|
|
(compile-in g env #f (caddr x))
|
|
|
|
(emit g 'trycatch))
|
|
|
|
(else (compile-app g env tail? x))))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-08-02 00:06:07 -04:00
|
|
|
(define (compile-f env f)
|
2009-07-16 21:30:26 -04:00
|
|
|
(receive (ff ignore)
|
2019-08-09 10:18:36 -04:00
|
|
|
(compile-f- env f)
|
|
|
|
ff))
|
2009-07-16 21:30:26 -04:00
|
|
|
|
2009-07-17 22:16:18 -04:00
|
|
|
(define get-defined-vars
|
|
|
|
(letrec ((get-defined-vars-
|
2019-08-09 10:18:36 -04:00
|
|
|
(lambda (expr)
|
|
|
|
(cond ((atom? expr) ())
|
|
|
|
((and (eq? (car expr) 'define)
|
|
|
|
(pair? (cdr expr)))
|
|
|
|
(or (and (symbol? (cadr expr))
|
|
|
|
(list (cadr expr)))
|
|
|
|
(and (pair? (cadr expr))
|
|
|
|
(symbol? (caadr expr))
|
|
|
|
(list (caadr expr)))
|
|
|
|
()))
|
|
|
|
((eq? (car expr) 'begin)
|
|
|
|
(apply nconc (map get-defined-vars- (cdr expr))))
|
|
|
|
(else ())))))
|
2009-07-17 22:16:18 -04:00
|
|
|
(lambda (expr) (delete-duplicates (get-defined-vars- expr)))))
|
|
|
|
|
2009-08-02 00:06:07 -04:00
|
|
|
(define (keyword-arg? x) (and (pair? x) (keyword? (car x))))
|
|
|
|
(define (keyword->symbol k)
|
|
|
|
(if (keyword? k)
|
|
|
|
(symbol (let ((s (string k)))
|
2019-08-09 10:18:36 -04:00
|
|
|
(string.sub s 0 (string.dec s (length s)))))
|
2009-08-02 00:06:07 -04:00
|
|
|
k))
|
|
|
|
|
2010-05-08 20:42:37 -04:00
|
|
|
(define (lambda-arg-names argl)
|
|
|
|
(map! (lambda (s) (if (pair? s) (keyword->symbol (car s)) s))
|
2019-08-09 10:18:36 -04:00
|
|
|
(to-proper argl)))
|
2010-05-08 20:42:37 -04:00
|
|
|
|
2009-07-26 23:34:33 -04:00
|
|
|
(define (lambda-vars l)
|
2009-08-03 01:00:44 -04:00
|
|
|
(define (check-formals l o opt kw)
|
|
|
|
(cond ((or (null? l) (symbol? l)) #t)
|
2019-08-09 10:18:36 -04:00
|
|
|
((and (pair? l) (symbol? (car l)))
|
|
|
|
(if (or opt kw)
|
|
|
|
(error "compile error: invalid argument list "
|
|
|
|
o ". optional arguments must come after required.")
|
|
|
|
(check-formals (cdr l) o opt kw)))
|
|
|
|
((and (pair? l) (pair? (car l)))
|
|
|
|
(unless (and (length= (car l) 2)
|
|
|
|
(symbol? (caar l)))
|
|
|
|
(error "compile error: invalid optional argument " (car l)
|
|
|
|
" in list " o))
|
|
|
|
(if (keyword? (caar l))
|
|
|
|
(check-formals (cdr l) o opt #t)
|
|
|
|
(if kw
|
|
|
|
(error "compile error: invalid argument list "
|
|
|
|
o ". keyword arguments must come last.")
|
|
|
|
(check-formals (cdr l) o #t kw))))
|
|
|
|
((pair? l)
|
|
|
|
(error "compile error: invalid formal argument " (car l)
|
|
|
|
" in list " o))
|
|
|
|
(else
|
|
|
|
(if (eq? l o)
|
|
|
|
(error "compile error: invalid argument list " o)
|
|
|
|
(error "compile error: invalid formal argument " l
|
|
|
|
" in list " o)))))
|
2009-08-03 01:00:44 -04:00
|
|
|
(check-formals l l #f #f)
|
2010-05-08 20:42:37 -04:00
|
|
|
(lambda-arg-names l))
|
2009-07-26 23:34:33 -04:00
|
|
|
|
2009-07-28 00:16:20 -04:00
|
|
|
(define (emit-optional-arg-inits g env opta vars i)
|
|
|
|
; i is the lexical var index of the opt arg to process next
|
|
|
|
(if (pair? opta)
|
|
|
|
(let ((nxt (make-label g)))
|
2019-08-09 10:18:36 -04:00
|
|
|
(emit g 'brbound i)
|
|
|
|
(emit g 'brt nxt)
|
|
|
|
(compile-in g (cons (list-head vars i) env) #f (cadar opta))
|
|
|
|
(emit g 'seta i)
|
|
|
|
(emit g 'pop)
|
|
|
|
(mark-label g nxt)
|
|
|
|
(emit-optional-arg-inits g env (cdr opta) vars (+ i 1)))))
|
2009-07-28 00:16:20 -04:00
|
|
|
|
2010-12-23 01:49:37 -05:00
|
|
|
#;(define (free-vars e)
|
2010-05-08 20:42:37 -04:00
|
|
|
(cond ((symbol? e) (list e))
|
2019-08-09 10:18:36 -04:00
|
|
|
((or (atom? e) (eq? (car e) 'quote)) ())
|
|
|
|
((eq? (car e) 'lambda)
|
|
|
|
(diff (free-vars (cddr e))
|
|
|
|
(nconc (get-defined-vars (cons 'begin (cddr e)))
|
|
|
|
(lambda-arg-names (cadr e)))))
|
|
|
|
(else (delete-duplicates (apply nconc (map free-vars (cdr e)))))))
|
2010-05-08 20:42:37 -04:00
|
|
|
|
2009-07-17 22:16:18 -04:00
|
|
|
(define compile-f-
|
|
|
|
(let ((*defines-processed-token* (gensym)))
|
|
|
|
; to eval a top-level expression we need to avoid internal define
|
|
|
|
(set-top-level-value!
|
|
|
|
'compile-thunk
|
|
|
|
(lambda (expr)
|
|
|
|
(compile `(lambda () ,expr . ,*defines-processed-token*))))
|
|
|
|
|
2009-08-02 00:06:07 -04:00
|
|
|
(lambda (env f)
|
2009-07-17 22:16:18 -04:00
|
|
|
; convert lambda to one body expression and process internal defines
|
|
|
|
(define (lambda-body e)
|
2019-08-09 10:18:36 -04:00
|
|
|
(let ((B (if (pair? (cddr e))
|
|
|
|
(if (pair? (cdddr e))
|
|
|
|
(cons 'begin (cddr e))
|
|
|
|
(caddr e))
|
|
|
|
(void))))
|
|
|
|
(let ((V (get-defined-vars B)))
|
|
|
|
(if (null? V)
|
|
|
|
B
|
|
|
|
(cons (list* 'lambda V B *defines-processed-token*)
|
|
|
|
(map (lambda (x) (void)) V))))))
|
2010-05-08 20:42:37 -04:00
|
|
|
(define (lam:body f)
|
2019-08-09 10:18:36 -04:00
|
|
|
(if (eq? (lastcdr f) *defines-processed-token*)
|
|
|
|
(caddr f)
|
|
|
|
(lambda-body f)))
|
|
|
|
|
2009-07-17 22:16:18 -04:00
|
|
|
(let ((g (make-code-emitter))
|
2019-08-09 10:18:36 -04:00
|
|
|
(args (cadr f))
|
|
|
|
(atail (lastcdr (cadr f)))
|
|
|
|
(vars (lambda-vars (cadr f)))
|
|
|
|
(opta (filter pair? (cadr f)))
|
|
|
|
(name (if (eq? (lastcdr f) *defines-processed-token*)
|
|
|
|
'lambda
|
|
|
|
(lastcdr f))))
|
|
|
|
(let* ((nargs (if (atom? args) 0 (length args)))
|
|
|
|
(nreq (- nargs (length opta)))
|
|
|
|
(kwa (filter keyword-arg? opta)))
|
|
|
|
|
|
|
|
; emit argument checking prologue
|
|
|
|
(if (not (null? opta))
|
|
|
|
(begin
|
|
|
|
(if (null? kwa)
|
|
|
|
(emit g 'optargs nreq
|
|
|
|
(if (null? atail) nargs (- nargs)))
|
|
|
|
(begin
|
|
|
|
(bcode:indexfor g (make-perfect-hash-table
|
|
|
|
(map cons
|
|
|
|
(map car kwa)
|
|
|
|
(iota (length kwa)))))
|
|
|
|
(emit g 'keyargs nreq (length kwa)
|
|
|
|
(if (null? atail) nargs (- nargs)))))
|
|
|
|
(emit-optional-arg-inits g env opta vars nreq)))
|
|
|
|
|
|
|
|
(cond ((> nargs 255) (emit g (if (null? atail)
|
|
|
|
'largc 'lvargc)
|
|
|
|
nargs))
|
|
|
|
((not (null? atail)) (emit g 'vargc nargs))
|
|
|
|
((null? opta) (emit g 'argc nargs)))
|
|
|
|
|
|
|
|
; compile body and return
|
|
|
|
(compile-in g (cons vars env) #t (lam:body f))
|
|
|
|
(emit g 'ret)
|
|
|
|
(values (function (encode-byte-code (bcode:code g))
|
|
|
|
(const-to-idx-vec g) name)
|
|
|
|
(aref g 3)))))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-04-14 20:12:01 -04:00
|
|
|
(define (compile f) (compile-f () f))
|
|
|
|
|
2009-06-14 22:25:21 -04:00
|
|
|
(define (ref-int32-LE a i)
|
|
|
|
(int32 (+ (ash (aref a (+ i 0)) 0)
|
2019-08-09 10:18:36 -04:00
|
|
|
(ash (aref a (+ i 1)) 8)
|
|
|
|
(ash (aref a (+ i 2)) 16)
|
|
|
|
(ash (aref a (+ i 3)) 24))))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-06-14 22:25:21 -04:00
|
|
|
(define (ref-int16-LE a i)
|
|
|
|
(int16 (+ (ash (aref a (+ i 0)) 0)
|
2019-08-09 10:18:36 -04:00
|
|
|
(ash (aref a (+ i 1)) 8))))
|
2009-03-28 19:46:02 -04:00
|
|
|
|
2009-03-28 17:39:04 -04:00
|
|
|
(define (hex5 n)
|
2009-05-06 22:10:52 -04:00
|
|
|
(string.lpad (number->string n 16) 5 #\0))
|
2009-03-28 17:39:04 -04:00
|
|
|
|
2009-04-26 18:19:32 -04:00
|
|
|
(define (disassemble f . lev?)
|
|
|
|
(if (null? lev?)
|
|
|
|
(begin (disassemble f 0)
|
2019-08-25 15:07:38 -04:00
|
|
|
(xnewline)
|
2019-08-09 10:18:36 -04:00
|
|
|
(return #t)))
|
2009-05-12 21:13:40 -04:00
|
|
|
(let ((lev (car lev?))
|
2019-08-09 10:18:36 -04:00
|
|
|
(code (function:code f))
|
|
|
|
(vals (function:vals f)))
|
2009-05-12 21:13:40 -04:00
|
|
|
(define (print-val v)
|
|
|
|
(if (and (function? v) (not (builtin? v)))
|
2019-08-25 15:07:38 -04:00
|
|
|
(begin (xdisplay "\n")
|
2019-08-09 10:18:36 -04:00
|
|
|
(disassemble v (+ lev 1)))
|
2019-08-25 15:07:38 -04:00
|
|
|
(xwrite v)))
|
|
|
|
(dotimes (xx lev) (xdisplay " "))
|
|
|
|
(xdisplay "maxstack ")
|
|
|
|
(xdisplay (ref-int32-LE code 0))
|
|
|
|
(xnewline)
|
2009-06-27 19:07:22 -04:00
|
|
|
(let ((i 4)
|
2019-08-09 10:18:36 -04:00
|
|
|
(N (length code)))
|
2009-05-12 21:13:40 -04:00
|
|
|
(while (< i N)
|
2019-08-09 10:18:36 -04:00
|
|
|
; find key whose value matches the current byte
|
|
|
|
(let ((inst (table.foldl (lambda (k v z)
|
|
|
|
(or z (and (eq? v (aref code i))
|
|
|
|
k)))
|
|
|
|
#f Instructions)))
|
2019-08-25 15:07:38 -04:00
|
|
|
(if (> i 4) (xnewline))
|
|
|
|
(dotimes (xx lev) (xdisplay " "))
|
|
|
|
(xdisplay (hex5 (- i 4)))
|
|
|
|
(xdisplay ": ")
|
|
|
|
(xdisplay (string inst))
|
|
|
|
(xdisplay " ")
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 1))
|
|
|
|
(case inst
|
|
|
|
((loadv.l loadg.l setg.l)
|
|
|
|
(print-val (aref vals (ref-int32-LE code i)))
|
|
|
|
(set! i (+ i 4)))
|
|
|
|
|
|
|
|
((loadv loadg setg)
|
|
|
|
(print-val (aref vals (aref code i)))
|
|
|
|
(set! i (+ i 1)))
|
|
|
|
|
|
|
|
((loada seta call tcall list + - * / vector
|
|
|
|
argc vargc loadi8 apply tapply)
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay (number->string (aref code i)))
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 1)))
|
|
|
|
|
|
|
|
((loada.l seta.l largc lvargc call.l tcall.l)
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay (number->string (ref-int32-LE code i)))
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 4)))
|
|
|
|
|
|
|
|
((loadc setc)
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay (number->string (aref code i)))
|
|
|
|
(xdisplay " ")
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 1))
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay (number->string (aref code i)))
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 1)))
|
|
|
|
|
|
|
|
((loadc.l setc.l optargs keyargs)
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay (number->string (ref-int32-LE code i)))
|
|
|
|
(xdisplay " ")
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 4))
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay (number->string (ref-int32-LE code i)))
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 4))
|
|
|
|
(if (eq? inst 'keyargs)
|
|
|
|
(begin
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay " ")
|
|
|
|
(xdisplay (number->string (ref-int32-LE code i)))
|
|
|
|
(xdisplay " ")
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 4)))))
|
|
|
|
|
|
|
|
((brbound)
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay (number->string (ref-int32-LE code i)))
|
|
|
|
(xdisplay " ")
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 4)))
|
|
|
|
|
|
|
|
((jmp brf brt brne brnn brn)
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay "@")
|
|
|
|
(xdisplay (hex5 (+ i -4 (ref-int16-LE code i))))
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 2)))
|
|
|
|
|
|
|
|
((jmp.l brf.l brt.l brne.l brnn.l brn.l)
|
2019-08-25 15:07:38 -04:00
|
|
|
(xdisplay "@")
|
|
|
|
(xdisplay (hex5 (+ i -4 (ref-int32-LE code i))))
|
2019-08-09 10:18:36 -04:00
|
|
|
(set! i (+ i 4)))
|
|
|
|
|
|
|
|
(else #f)))))))
|
2009-04-19 18:22:17 -04:00
|
|
|
|
2009-08-02 00:06:07 -04:00
|
|
|
; From SRFI 89 by Marc Feeley (http://srfi.schemers.org/srfi-89/srfi-89.html)
|
|
|
|
; Copyright (C) Marc Feeley 2006. All Rights Reserved.
|
|
|
|
;
|
|
|
|
; "alist" is a list of pairs of the form "(keyword . value)"
|
|
|
|
; The result is a perfect hash-table represented as a vector of
|
|
|
|
; length 2*N, where N is the hash modulus. If the keyword K is in
|
|
|
|
; the hash-table it is at index
|
|
|
|
;
|
|
|
|
; X = (* 2 ($hash-keyword K N))
|
|
|
|
;
|
|
|
|
; and the associated value is at index X+1.
|
|
|
|
(define (make-perfect-hash-table alist)
|
|
|
|
(define ($hash-keyword key n) (mod0 (abs (hash key)) n))
|
|
|
|
(let loop1 ((n (length alist)))
|
|
|
|
(let ((v (vector.alloc (* 2 n) #f)))
|
|
|
|
(let loop2 ((lst alist))
|
|
|
|
(if (pair? lst)
|
|
|
|
(let ((key (caar lst)))
|
|
|
|
(let ((x (* 2 ($hash-keyword key n))))
|
|
|
|
(if (aref v x)
|
|
|
|
(loop1 (+ n 1))
|
|
|
|
(begin
|
|
|
|
(aset! v x key)
|
|
|
|
(aset! v (+ x 1) (cdar lst))
|
|
|
|
(loop2 (cdr lst))))))
|
|
|
|
v)))))
|
|
|
|
|
2009-03-28 17:39:04 -04:00
|
|
|
#t
|