Macros
Elfenben is not a dialect of Lisp. There is no list processing in Elfenben. Elfenben is Javascript using a Lispy syntax (a tree syntax). This is so that we can manipulate the syntax tree while compiling, in order to support macros.
Macro syntax
The base form for macros is:
(macro name (arguments expression) (template expression))
The ~ symbol is used to expand/dereference arguments and expressions. For example say we defined a simple macro:
(macro method (methodParams)
(function ~methodParams
(console.log "hello from method!")))
And called it with the folllowing:
(method (x y z))
It would produce the following: (function (x y z) (console.log "hello from method!"))
The ~@ symbol is used to unwrap arguments and expressions. For example say we defined a macro:
(macro make-vars (varNamesAndValues)
(var ~@varNamesAndValues))
And called it with the following:
(make-vars (x 5 y 6 z 10))
It would produce the following: (var x 5 y 6 z 10)
You can also produce raw javascript effectively giving you access to the transpiler output using the javascript keyword. For example:
(macro raw-script ()
`(do`
`(javascript "let ___result = 'raw script!';")`
`___result`))
And called it with the following:
(console.log (raw-script))
It would output the following: 'raw script!'
This is because the raw javascript was used during transpiling and produces the resulting javascript:
console.log((function(){
let ___result = 'raw script!';
return ___result;
})());
Examples
You can define a macro:
(macro array? (obj)
(= (Object.prototype.toString.call ~obj) "[object Array]"))
The 'array?' conditional is defined as a macro in Elfenben. The 'macro'
expression takes a name as its second element, a parameters list in the third element,
and the fourth element is the template to which the macro will expand.
Now let us create a Lisp like 'let' macro in Elfenben:
(macro lisp-let (names vals rest...)
((function ~names ~rest...) ~@vals))
(lisp-let (name email tel) ("John" "[email protected]" "555-555-5555")
(console.log name)
(console.log email)
(console.log tel))
The 'lisp-let' macro creates lexically scoped variables with initial values. It does this by creating an anonymous function whose argument names are the required variable names, sets the variables to their initial values by calling the function immediately with the values. The macro also wraps the required code inside the function.Now lets look at the call to the 'lisp-let' macro. 'names' will correspond to '(name email tel)'. 'rest...' corresponds to '(console.log name) (console.log email) (console.log tel)', which is the rest of the expressions after vals. We want to dereference these values in the macro template, and we do that with '~names', '~rest...'. However 'vals' corresponds to ("John" "[email protected]" "555-555-5555"). But thats not the way we want to dereference it. We need to dereference it without the parenthesis. For that we use '~@vals'.We don't really need 'lisp-let' in Elfenben. We have 'let'. But if you need it, you can extend Elfenben by adding this macro to your code. Thats the power of macros. You can extend the language itself or create your own domain specific language.