Adder language spec

(This is a really early version, which assumes you know some Lisp.)

Table of Contents

Overview

Adder is a Lisp-1, meaning that function names are variable names, as in Scheme. By contrast, in a Lisp-2, such as Common Lisp or Emacs Lisp, function names are a separate namespace, so that, in the expression (foo foo), the two foos are unrelated. (Python also has a single namespace, while Ruby has two. I sometimes think of Python as a Perl-1, and Ruby as a Perl-2.) All names must be defined before being used; setting a variable without defining it is an error. There are no Common Lisp-style packages; a symbol is purely a name.

Adder supports object field access via Python syntax: foo.bar.baz, for example. There is also a Lispy syntax, as in (. foo bar baz), which you'll need when you want to access a field of an object that's not in a variable. In addition, .bar.baz is a function, so that (.bar.baz foo) is equivalent to foo.bar.baz. This is so you can write functional code without having to write quite so many trivial lambdas. The equivalent Lispy syntax is (.. bar baz).

All Python builtins are available as members of the object named python; so, for example, (python.sum '(1 2 3)) is legal Adder.

Adder supports Common Lisp-style (defmacro).

Builtins

Special forms

quote
import
As the Python version, although there's no equivalent of as or from. For example, (import foo bar) is equivalent to Python's import foo,bar. Note that modules written in Adder can be imported, too—in fact, they can even be imported into Python. See Modules, below.
if
while
break
continue
begin
yield
return
lambda
and
or
:=
.
class
Syntax is (class ClassName (...ParentNames...) ...code...). As in Python, the class definition encloses a block of code which is executed in class scope. For example:
(import math)

(class Point ()
  (define __init__ (self x y)
    (:= (. self x) x)
    (:= (. self y) y))

  (define (dist self other)
    (math.sqrt (+ (* (- other.x self.x) (- other.x self.x))
                  (* (- other.y self.y) (- other.y self.y)))))
 )
defconst
defvar
defun
Define a function. The argument syntax supports &rest, &keys, and &optional, which correspond to the Python equivalent syntax. Note that keyword arguments are the new style, introduced in Python 3, which cannot be passed as positional arguments.
defmacro
Define a macro, as in Common Lisp. (Although it doesn't have the super-powered argument lists of Common Lisp; its argument list syntax is the same as for (defun).)
scope
Like (begin), but introduces a new lexical scope. You probably want (let) instead.
extern
(extern foo) declares that the runtime environment will contain a variable named foo. Used for code which is run dynamically, relying on variables set externally (e.g., HTML templates). Returns the value of foo.

Functions

==
!=
<=
<
>=
>
+
-
*
/
//
%
in
raise
print
gensym
[]
getattr
slice
list
tuple
set
dict
isinstance
mk-list
mk-tuple
mk-set
mk-dict
mk-symbol
reverse
eval
stdenv
exec-py
Somewhat limited, because the Adder compiler renames vars to keep them unambiguous (so that we can have multiple lexical scopes within the same function). As a result, something like (begin (defvar x 9) (exec-py "x=7") x) will return 9, not 7, since the x in Adder is actually x-1, which gets translated into __adder__x_002d1.
apply
load
TODO: make it work as a first-order function. The problem at the moment seems to be that it loads into the wrong environment. See test-compiler.py, CompileAndEvalTestCase._testOpFuncLoad().

Constants

true
false
None

Prelude

Constants

stdin
stdout
stderr
type-list
type-tuple
type-set
type-dict
type-symbol
type-int

Macros

cond
case
ecase
..
A function generator. (.. bar baz) is equivalent to (lambda (x) (. x bar baz)).
let
Yes, it can be defined in terms of lambda; but that depends on having cheap function calls. I don't know that Python is cheap enough. Instead (let) expands into (scope)
let*
define
Scheme-type syntax: (define (f x) (* x x)) or (define y 9).
delay, force
As in Scheme.
when, unless
As in Common Lisp.
with-macro-vars
(with-macro-vars (v1 ... vn) &rest body) binds v1 through vn to gensyms and evaluates body.
for
(for (var sequence) &rest body)
if-bind
(if-bind (var condition) then else): binds var to condition and then uses it in an if.
when-bind
(when-bind (var condition) then): binds var to condition and then uses it in an when.
yield*
My original reason for creating Adder. It's fairly common in Python to have to write a loop such as:
for bar in foo:
  yield bar
(For example, if you're recursing down a tree.) In Adder, this would be (yield* foo): yield all the elements which foo would yield. In Adder, yield* is a simple macro; in Python, it's impossible.

Functions

error
reverse!
cons
list?
tuple?
set?
dict?
symbol?
int?
head
tail
eval-py
map
zip
take
Input: an iterator, i, and an integer, n. Output: a generator which yields the next n elements of i.
str
len

Modules

Modules can be written in Adder, and can be imported into Adder and into Python. To make sure the Adder modules in the directory /foo/bar/baz are available, put /foo/bar/+baz+ into sys.path (aka the environment variable PYTHONPATH). If the directory /foo/bar/+baz+ actually exists, this won't work.

If you have a Python program, and you want to import an Adder module, you'll need to import adder before you import the first Adder module; that inserts the import hook used to load Adder modules.

When you import an Adder module, it gets compiled to Python; if the Adder module's source was /foo/bar/baz/quux.+, the Python code is cached in /foo/bar/baz/quux.py. At this time, /foo/bar/baz/quux.pyc does not get created; that might be a nice little speedup.