as_lisp is a Lisp dialect implemented in Actionscript.
It's a bytecode compiled language, and comes with its own compiler and a small bytecode interpreter, both written in Actionscript. The language includes the typical Lisp-dialect features you'd expect, like proper closures, tail-call optimization, and macros.
Language implementation should be pretty readable and easy to extend (which also means: it's not particularly optimized). Compiler and bytecode design are heavily influenced by (ie. cribbed from) Quinnec's "Lisp in Small Pieces" and Norvig's "Principles of Artificial Intelligence Programming" . Standing on the shoulders on giants. :)
as_lisp is intended to be used as a library, embedded in another host program, and not a standalone executable. The compiler, bytecode interpreter, and runtime environment, are all easy to access and manipulate from host programs. A simple REPL console host app is included as an example. Since as_lisp can run inside the Adobe Flash runtime, it's compatible with a wide range of environments, including web browsers, and standalone AIR apps (iOS, Android, PC/Mac).
This is very much a work in progress, so please pardon the dust, use at your own risk, and so on. :)
var ctx :Context = new Context(null, true); // make a new vm + compiler
ctx.execute("(+ 1 2)"); // => [ 3 ]
Value types:
- Boolean - #t or #f, backed by Actionscript boolean
- Number - same as Actionscript Number (floating point double)
- String - same as Actionscript String (immutable char sequence in double quotes)
- Symbol - similar to Scheme
- Cons - pair of expressions
- Closure - non-inspectable pair of environment and compiled code sequence
Small set of reserved keywords - everything else is a valid symbol
- quote
- begin
- set!
- if
- if*
- lambda
- defmacro
- .
Tail calls get optimized during compilation, without any language hints
(define (rec x) (if (= x 0) 0 (rec (- x 1))))
(rec 1000000) ;; look ma, no stack overflow!
Quotes, quasiquotes and unquotes are supported in the Lisp fashion:
'x ;; => 'x
`x ;; => 'x
`,x ;; => x
`(1 ,(list 2 3)) ;; => '(1 (2 3))
`(1 ,@(list 2 3)) ;; => '(1 2 3)
Closures
(set! fn (let ((sum 0)) (lambda (delta) (set! sum (+ sum delta)) sum)))
(fn 0) ;; => 0
(fn 100) ;; => 100
(fn 0) ;; => 100
Macros are more like Lisp than Scheme.
;; (let ((x 1) (y 2)) (+ x 1)) =>
;; ((lambda (x y) (+ x y)) 1 2)
(defmacro let (bindings . body)
`((lambda ,(map car bindings) ,@body)
,@(map cadr bindings)))
Macroexpansion - single-step and full
(and 1 (or 2 3)) ;; => 2
(mx1 '(and 1 (or 2 3))) ;; => (if 1 (core:or 2 3) #f)
(mx '(and 1 (or 2 3))) ;; => (if 1 (if* 2 3) #f)
Built-in primitives live in the "core" package and can be redefined
(+ 1 2) ;; => 3
(set! core:+ core:*) ;; => [Closure]
(+ 1 2) ;; => 2
Packages
(package-set "math") ;; => "math"
(package-get) ;; => "math"
(package-import ("core")) ;; => null
(package-export '(sin cos))
Built-in primitives are very bare bones (for now):
- Functions:
-
-
-
- / = != < <= > >=
-
-
- const list append length
- not null? cons? atom? string? number? boolean?
- car cdr cadr cddr caddr cdddr map
- mx mx1 trace gensym
- package-set package-get package-import package-export
- first second third rest
- fold-left fold-right
- Macros
- let let* letrec define
- and or cond case
- Flash interop
- new deref ..
- Fix bugs (hah!)
- Build out the standard library
- Flesh out Flash interop. Right now it's in its infancy:
(new '(flash geom Point) 2 3) ;; => [Native: (x=2, y=3)]
(deref (new '(flash geom Point) 2 3) 'x) ;; => 2
;; also (.. instance '(field1 field2)) == (deref (deref instance field1) field2)
- Peephole optimizer. Also optimize execution of built-in primitives.
- Add better debugging: trace function calls, their args and return values, etc
- Error messages are somewhere between opaque and completely misleading
- Redefining a known macro as a function will fail silently in weird ways
- Symbol / package resolution is buggy - eg. if a symbol "foo" is defined in core but not in the package "bar", then "bar:foo" will resolve to "core:foo" even though it should resolve as undefined.
inputs: (+ 1 2)
parsed: (core:+ 1 2)
ARGS 0
CONST 1
CONST 2
GVAR core:+
CALLJ 2
inputs: (begin (+ (+ 1 2) 3) 4)
parsed: (begin (core:+ (core:+ 1 2) 3) 4)
ARGS 0
SAVE "K0" 11
SAVE "K1" 7
CONST 1
CONST 2
GVAR core:+
CALLJ 2
LABEL "K1"
CONST 3
GVAR core:+
CALLJ 2
LABEL "K0"
POP
CONST 4
RETURN
inputs: ((lambda (a) a) 5)
parsed: ((lambda (a) a) 5)
ARGS 0
CONST 5
FN [Closure] ; (a)
ARGS 1
LVAR 0 0 ; a
RETURN
CALLJ 1
inputs: (begin (set! incf (lambda (x) (+ x 1))) (incf (incf 5)))
parsed: (begin (set! foo:incf (lambda (foo:x) (core:+ foo:x 1))) (foo:incf (foo:incf 5)))
ARGS 0
FN [Closure] ; ((core:+ foo:x 1))
ARGS 1
LVAR 0 0 ; foo:x
CONST 1
GVAR core:+
CALLJ 2
GSET foo:incf
POP
SAVE "K0" 8
CONST 5
GVAR foo:incf
CALLJ 1
LABEL "K0"
GVAR foo:incf
CALLJ 1