Saturday, March 03, 2007

SubX. Common Lisp Sub-eXpression language.

There are some disadvantages to using common lisp. Some of them I've mentioned in my previous post. Subx tries to improve on these or possibly eliminate some of them.
A grammar that is intuitive enough and covers the usual programming patterns isn't impossible to create. It has been and will continue to be developed in future languages. Macros allow you to extend the existing grammar at various levels. This is the case with the 'loop' macro that defines it's own iteration-language. This is one of the most complex that is offered by the standard implementation of common lisp. There are others of course like 'dotimes', 'dolist' etc. Subx tries to offer structures that are general enough to apply to usual cases and simplify overall the code. It provides a framework for creating grammars. It's based only on a few very basic concepts: operators: unary, binary and parentheses: associativity, context change parens and proper lisp parens. Using these concepts custom macros/functions can be used with a more clear syntax.

Expressions that use the subx-grammar can be used in lisp by surrounding them in []. These are just read as normal paranthesized expressions so you can use them anywhere:

(defun some-function()
[ <subx-expression> ])

(loop for x below 10 collect
[ <subx-expression> ])

Well, you get the idea.
Now, just to start on the safe side subx can be inserted in normal lisp code but also normal lisp code can seamlessly be integrated in subx. So [] can contain (<symbo|form>*) that are interpreted as lisp expressions (s-expr). So writing something like [(list 1 2)] is pretty much the same as (list 1 2) and also [(list 1 [2])] or some other nesting.

As you guessed () act as normal parens. So [(1)] isn't that legal cause (1) is not.

The third kind of parens used are {}. These represent function application. The syntax is function{<param,>*<param>}
Operators have associativity and priorities. Some are already defined: common arithmetic, some logic. Any othe operatator is considered a function.

There are a couple of operators that I've defined already. These include:
- sequencing by progn: ';'
- sequencing by forms enumeration: ':'
- list construction: ','
- function call on a form: '.'
- function call '{}'
- apply function '~'
- map '<-', mapcan '<<-', filter '<->', reduce '<=>'
- arithmetic, logical, bitwise
- assignment: '=', destructuring bind: ':='

Some basic examples:
Writing a function call:

(list 1 2)
[list 1 2]
[list{1,2}]
[apply #'list 1,2]
[funcall #'list 1 2]
[1.(list 2)]
[1.list~2]
[1.list{2}]
==> (1 2)

Applying a map:

[1..5 <- ((x) (inc x))]
[1..5 <- inc]
[1..5 <- #'inc]
[1..5 <- (lambda (x) (inc x))]
==> (2 3 4 5 6)

Filter:

[1..10 <-> oddp]
==> (1 3 5 7 9)

Reduce:

[1..10 <=> #'+]
==> 55

Ifs:

[if 1 + 2 == 3: list{1,2}: list~3]
==> (1 2)

[when 1 == 1: print 1,2,3]
==> (1 2 3)

[unless 1 == 2: print{1,2,3}]
==> 1 2 3

Nesting ifs:

[if 1 == 1:
[if 2 == 1 + 2: print{2}: print{3}]:
print{4}]

Weird add-ing:

[+ 1.+{2}.+~3.(+ 4) + 5 6]
==> 21

Weird equals:

3 == 1 + 2 == 4 - 1
==> 3

Slicing:

[..{10}@..{3}]
[[..10]@[..3]]
[(..10)@(..3)]
[..~10@..~3]
==> (0 1 2)

Loop-ing:

[for x on 1..5 <- list collect:
sum{x@[..2] <- car}] ==> (3 5 7 9 5)

Using macros:

[let ((x 9)): x = print{x + 1} + 2; x,2]
==> 10 10
==> (12 2)

[for x in 1..10 collect: x + 1]
==> (2 3 4 5 6 7 8 9 10 11)

[x = 0; while [x += 1] < 10 collect: x,]
==> ((1) (2) (3) (4) (5) (6) (7) (8) (9))

[defparameter x: 1 + 2, 4]
x
==> (3 4)

[(x &rest y) := [..5]; x, y]
==> (0 (1 2 3 4))

[f = [lambda (x): x + 1]; f{1}]
==> 2

1 comment:

Unknown said...

Why do i have the feeling that we'll get a 'social problems of lisp' post in a few weeks after the author tries to show his 'fixed lisp' to real lispers :).