-
-
Notifications
You must be signed in to change notification settings - Fork 37
Getting Started
You can use LIPS from unpkg
<script src="https://unpkg.com/@jcubic/lips"></script>
or download from npm:
npm install @jcubic/lips
You can also use webpack to bundle your dependencies together with LIPS and include that one bundle.
After you included the LIPS script into your page. You can put your LIPS code into script tag:
<script type="text/x-lips">
(load "examples/helpers.lips") ;; helpers file have definition of --> macro
(--> console (log "hello world!"))
<script>
You can also use src attribute to run your script:
<script type="text/x-lips" src="script.lips"></script>
If you use emacs you can put comment at the beginning
;; -*- scheme -*-
so you will have Scheme syntax highlighting.
If you already know scheme here are the differences:
- no big Standard library,
-
print
instead ofdisplay
, - no hygienic macros only pure lisp macros with define-macro syntax and
quasi quotation
, - no tail call eliminations,
- no call/cc (continuations).
to define a function use define macro:
(define (square x)
(* x x))
This is equivalent of:
(define square (lambda (x)
(* x x)))
and you will get this code if you run:
(macroexpand (define (square x)
(* x x)))
Some of the internal macros written in JavaScript are expanding like define
.
To define macros use:
(define-macro (when x . body)
`(if ,x
(begin
,@body)))
this will create macro when that is like if but you can put more then one expression and it don't have else case.
To learn more about macros I suggest book let over lambda.
You can access JavaScript objects from LIPS, if object is global you can use it like normal variable:
(. document 'getElementById)
(. document "getElementById")
Those two calls will return function getElementById taken from document object. Dot is function that can be used to get the property of an object. You can't use it in every place since it's also special token in LIPS (to mark pair (foo . bar)
, if you want to use in different place, for instance as variable you can use get
function that is alias for dot function).
When you get function property from object (method) the function is bound to that object so you can call it without any issues for example:
(define log (. console 'log))
(log "hello world")
There are also two helper macros that make working with JS easier ..
and -->
(macros defined in examples/helpers.lips):
(.. document.querySelector)
this is useful for grabbing nested field:
(.. $.terminal.active)
and:
(--> document (querySelector "div"))
you can also use
(--> $.terminal (active))
You can also set Properties on object using set-obj!
function:
(set-obj! window "name" "This came from LIPS")
this will set property name on object so you can use:
console.log(window.name);
If you want to generate array from LIPS list you can use function:
((. (list->array '(1 2 3)) 'forEach) (lambda (x) (print x)))
This will convert list (1 2 3)
into array get forEach function from that array and call LIPS function as JavaScript in forEach.
(array->list ((. (list->array '(1 2 3)) 'map) (lambda (x) (+ 1 x))))
this will run [].map function call LIPS code to increase the number return array and convert that array into LIPS list (2 3 4)
(new Promise (lambda (resolve) (setTimeout (lambda () (resolve 10)) 1000)))
this will create new promise that will resolve after 1 second to number 10.
lips.LNumber is special class/constructor that wrap all the numbers in LIPS so if you interact with JavaScript you need to convert those numbers to real numbers, some functions will do this automatically like setTimeout that call valueOf
on non numbers. For other cases you need to call this yourself. You can use helper value
function from examples/helper.lips file. that will convert numbers to real numbers.
This is done in this way because LNumber wrap multiple behaviors it use bigint native numbers or BN.js library. So you can create really big results. Like factorial of 1000 see test spec file.
LIPS will process promises and wait untill they are resolved before it start evaluating rest of the expressions. So your async code is always like using async/await
.
Example:
(define-macro (delay time . expr)
`(new Promise (lambda (resolve) (setTimeout (lambda () (resolve (begin ,@expr))) ,time))))
(let ((x 10) (y 20))
(+ 10 (delay 1000 (+ x y))))
Expression will evaluate to 40 after delay. If any of the expression inside is a promise the whole expression will be a promise. You need to know about this and handle properly if you interact with JavaScript.
Consider this code:
(--> (fetch "__browserfs__/foo/app.lips") (text))
fetch is function (defined in JavaScript) that return a promise and it can be called with text function (--> helper macro is defined in examples/helpers.lips file).
NOTE: In LIPS demo by default there is loaded jQuery library.
In examples/helper.lips file there is helper macro -->
that you can use to interact better with jQuery, you can use chaining:
(--> ($ "body")
(on "click" (lambda (e) (print "click event")))
(find "ul")
(append "<li>Hello</li>")
(next)
(css "color" "red"))
It's equivalent of:
$("body")
.on("click", function(e) { console.log("click event"); })
.find("ul")
.append("<li>Hello</li>")
.next()
.css("color", "red");
NOTE: you don't need to use chaining you can use --> to call one function.
in helpers.lips file there is also one more macro make-object
that allow to create object from key like syntax, so you can use it to create JavaScript objects (e.g. to be used with css()
jQuery function or any other JavaScript function):
(let ((css (. ($ ".terminal") 'css)))
(css (make-object :color "red" :border "1px solid red")))
this function use built in function dot and make-object macro. You can shorten this code using:
(--> ($ ".terminal")
(css (make-object :color "red" :border "1px solid blue")))
You can call LIPS directly inside your JavaScript code (this is how demo was created):
ar {exec} = require('@jcubic/lips'); // node
// or
var {exec} = lips; // browser
exec(string).then(function(results) {
results.forEach(function(result) {
console.log(result.toString());
});
});
With exec you can pass 2 more arguments second argument is environment. You can create new Environment using:
var local = lisp.env.inherit('name');
exec(string, local);
3rd option is optional dynamic scope environment, you can also use true to have dynamic the same as your original environment:
exec(string, local, local); // dynamic scope with local env
exec(string, local, true); // same
exec(string, true); // dynamic scope with LIPS global env
lips.env.set('sum', function(...args) {
return args.reduce((acc, i) => acc + i);
});
You can use this function in LIPS:
(sum 1 2 3 4 5)
;; 15
const {env, Macro, Pair, evaluate, Symbol: _Symbol } = lips;
// we use _Symbol so we don't overwrite native Symbol (it would break the code).
env.set('delay', new Macro('delay', function(code, {dynamic_scope, error}) {
var args = {error, env: this};
// this is needed only if you want your function to work with dynamic scope
args.dynamic_scope = dynamic_scope === true ? this : dynamic_scope;
return new Promise(function(resolve) {
setTimeout(function() {
resolve(evaluate(new Pair(new _Symbol('begin'), code.cdr), args));
}, code.car);
});
}));
and you can use this code the same as in one of the previous sections:
(let ((x 10) (y 20))
(+ 10 (delay 1000 (+ x y))))
Copyright (C) 2019-2022 Jakub T. Jankiewicz (Wiki content under Creative Commons (CC BY-SA 4.0) License, but only if you try to publish whole or big part of the content).