Lispyscript is Javascript using a 'Lispy' syntax, and compiles to Javascript.
An inherent problem with Javascript is that it has no macro support, like other Lisp like languages. That's because macros manipulate the syntax tree while compiling. And this is next to impossible in a language like Javascript. In LispyScript we write Javascript in a tree structure. If you know Javascript and a Lisp like language, then using LispyScript will be a breeze. Even if you don't know a Lispy Language, all you need to learn is to write code in a tree structure.
(.log console "Hello LispyScript!")
A LispyScript program is made up of expressions in parenthesis. The first element in the expression is a function or a LispyScript keyword. The rest of the elements (separated by space characters) are the arguments to the function. As you can see above we can directly access javascript functions from LispyScript.
A more intricate Hello World!
(if (undefined? window)
(.log console "Hello LispyScript!")
(alert "Hello LispyScript!"))
You can have expressions within expressions.
An anonymous function in LispyScript.
(fn [x] (* x x))
The first element in an expression can be an anonymous function.
((fn [x] (* x x)) 2)
That was the anonymous function above evaluated immediately with argument 2. Functions return the last expression evaluated within the function.
You can set a variable name to a function.
(def square
(fn [x]
(* x x)))
(.log console (square 10))
The 'def' expression takes a variable name as the second element and sets its value to the third.
All Javascript functions, objects and literals can be used in LispyScript.
(.call Array.prototype.forEach (Array 1 2 3)
(function [item index list]
(.log console item)))
If you noticed we passed a Javascript literal array as the first argument to
forEach
. You could just as well have passed in { one: 1, two: 2, three: 3}
instead.
You can access object methods and properties using the ".-" notation.
(.log console (.-greet { greet: "hello" }))
ie. If the first element of an expression starts with a .-
, it's considered
as a property of the second element, and the expresion evaluates to the
property.
You can also use the 'get' expression to access a property of an object.
(.log console (get "greet" { greet: "hello" }))
(.log console (get (Array 1 2 3) 1))
You can 'set' variables too.
(set! window.onload (fn [] (alert "Page Loaded")))
The node server example in LispyScript.
(def http (require "http"))
(def server
;; Note: Conventional dash-dilimited lispy style traslates to
;; convetional camelCase style in JS (P.S. you can use cameCase too).
(.create-server http
(fn [request response]
(.write-head response 200 { "Content-Type": "text/plain" })
(.end response "Hello World\n"))))
(.listen server 1337 "127.0.0.1")
(.log console "Server running at http://127.0.0.1:1337/")
LispyScript is not a dialect of Lisp. There is no list processing in LispyScript. LispyScript 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.
You can define a macro.
(defmacro array? [value]
`(= (.call Object.prototype.toString ~value) "[object Array]"))
The 'array?' conditional is defined as a macro in LispyScript. The 'defmacro' 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 LispyScript:
(defmacro let (names values & body)
`((function ~names ~@body) ~@values))
(let (name email tel) ("John" "john@example.org" "555-555-5555")
(.log console name)
(.log console email)
(.log console tel))
The 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 let
macro. names
will correspond to
(name email tel)
. body
corresponds to
(.log console name) (.log console email) (.log console tel)
, which is the
body of the expressions after vals
. We want to dereference these values in
the macro template, and we do that with ~names
, ~@body
. However vals
corresponds to ("John" "john@example.org" "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 let
in LispyScript. We have var
. But if you need it,
you can extend LispyScript 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.
The compiler requires nodejs and npm installation. However the compiled code is standalone javascript that will run anywhere. To install use npm:
npm install lispyscript
-
Typing
lispy
into the command prompt will open a simple REPL. -
Typing
lispy program.ls
will compileprogram.ls
intoprogram.js
in the same directory. -
Type
lispy src/program.ls lib/program.js
to be more explicit. -
Type
cat program.ls | lispy
will pipe conten ofprogram.ls
to the lispy and will wirte compiled JS to the output.
null?, undefined?, boolean?, number?, string?, object?, array?, function?,
=, !=, !, >, <, <=, >=, +, -, *, /, %, &&, ||.
Note: =
and !=
work like ===
and !==
in Javascript.
Adds up all the strings.
(var title "My Home Page")
(console.log
(str "<!DOCTYPE html>
<html>
<head>
<title>" title "</title>
</head>
<body class=\"test\">
Hello World
</body>
</html>"))
In LispyScript double quoted strings are multiline strings. As you can see above they span multiple lines. If you need a double quote inside the string, escape it with ".
If takes a conditional expression and evaluates the true expression if the condition is true, or the false expression otherwise.
The do statement evaluates a set of expressions passed as it arguments.
The when statement evaluates a set of expressions passed as it arguments when the condition is true.
The unless statement evaluates a set of expressions passed as it arguments when the condition is false.
each is just a macro that expands to the native 'forEach' function. So it will not work in old browsers. For backwards compatibility use a library like 'underscore.js'.
(each (Array 1 2 3)
(fn [elem index list]
(.log console elem)))
The above example using underscore.js.
(def _ (require 'underscore'))
(.each _ (Array 1 2 3)
(fn (elem index list)
(.log console elem)))
map is just a macro that expands to the native map
function. So it will not
work in old browsers. For backwards compatibility use a library like
'underscore.js'.
reduce is just a macro that expands to the native reduce
function. So it will
not work in old browsers. For backwards compatibility use a library like
'underscore.js'.
Creates an anonymous function.
Try takes a set of expressions and evaluates them. The last expression must be a function, that will be called in case an exception is thrown. The function is called with the error object.
(def fs (require "fs"))
(def outfile "text.txt")
(try
(.writeFileSync fs outfile "Hello World")
(catch Error e
(.log console (+ "Cannot write file " outfile))
(.exit console 1)))
(var link
(template [data]
"<li><a href=" (.href data) ">" (.text data) "</a></li>\n"))
(var page
(template (title links)
"<!DOCTYPE html>
<html>
<head>
<title>" title "</title>
</head>
<body>
<ul class='nav'>"
(reduce links (function [memo elem] (+ memo (link elem))) "")
"</ul>
</body>
</html>"))
(.log console
(page "My Home Page"
[{ href: "/about", text: "About" },
{ href:" /products", text: "Products" },
{ href: "/contact", text: "Contact" }]))
Includes a file to be compiled with this compilation unit.
Comments in LispyScript start with a ;
and span the rest of the line.