diff --git a/.babelrc b/.babelrc
new file mode 100644
index 00000000..002b4aa0
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["env"]
+}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..2ca0133b
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+* text eol=lf
+.* text eol=lf
+dist/* binary
diff --git a/.gitignore b/.gitignore
index b25c15b8..95ea5187 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,8 @@
*~
+node_modules
+coverage
+npm-debug.log
+\#*
+.\#*
+.DEV
+.[0-9]*
diff --git a/.npmignore b/.npmignore
index d29dc586..fdca3a9e 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,2 +1,7 @@
spec
*~
+covarage
+Makefile
+jest.config.js
+version
+templates
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..75affb3c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+node_js:
+ - "node"
+install:
+ - npm install
+script:
+ - make lint
+ - make test
+after_script:
+ - make coveralls
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..20f9c5a2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,18 @@
+## 0.2.0
+### Features
+* Add reduce and set functions
+* add while, ++ and -- macros
+* ignore comments everything after ; but not inside strings and regexes
+* gensym and load functions
+* better string function
+* Pair methods for working with ALists + Pair::reduce
+* throw exception on car/cdr with non list
+
+### Bugs
+* fix parsing empty strings
+* fix various errors catch by lint
+* fix parsing ALists with list as keys and values
+* fix parsing quasiquote that evaluate to single pair out if unquote
+
+## 0.1.0
+* Initial version
diff --git a/Makefile b/Makefile
index 0f29c3e1..c601b875 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,58 @@
-.PHONY publish
+.PHONY: publish test coveralls lint
+
+VERSION=0.1.0
+BRANCH=`git branch | grep '^*' | sed 's/* //'`
+DATE=`date -uR`
+SPEC_CHECKSUM=`md5sum spec/lips.spec.js | cut -d' ' -f 1`
+COMMIT=`git log -n 1 | grep commit | sed 's/commit //'`
+
+GIT=git
+SED=sed
+RM=rm
+TEST=test
+CAT=cat
+NPM=npm
+ESLINT=./node_modules/.bin/eslint
+COVERALLS=./node_modules/.bin/coveralls
+JEST=./node_modules/.bin/jest
+UGLIFY=./node_modules/.bin/uglifyjs
+BABEL=./node_modules/.bin/babel
+
+
+ALL: Makefile .$(VERSION) dist/lips.js dist/lips.min.js README.md package.json
+
+dist/lips.js: src/lips.js .$(VERSION)
+ $(GIT) branch | grep '* devel' > /dev/null && $(SED) -e "s/{{VER}}/DEV/g" -e "s/{{DATE}}/$(DATE)/g" src/lips.js > dist/lips.tmp.js || $(SED) -e "s/{{VER}}/$(VERSION)/g" -e "s/{{DATE}}/$(DATE)/g" src/lips.js > dist/lips.tmp.js
+ $(BABEL) dist/lips.tmp.js > dist/lips.js
+ $(RM) dist/lips.tmp.js
+
+dist/lips.min.js: dist/lips.js .$(VERSION)
+ $(UGLIFY) -o dist/lips.min.js --comments --mangle -- dist/lips.js
+
+Makefile: templates/Makefile
+ $(SED) -e "s/{{VER""SION}}/"$(VERSION)"/" templates/Makefile > Makefile
+
+package.json: templates/package.json .$(VERSION)
+ $(SED) -e "s/{{VER}}/"$(VERSION)"/" templates/package.json > package.json || true
+
+README.md: templates/README.md
+ $(GIT) branch | grep '* devel' > /dev/null && $(SED) -e "s/{{VER}}/DEV/g" -e \
+ "s/{{BRANCH}}/$(BRANCH)/g" -e "s/{{CHECKSUM}}/$(SPEC_CHECKSUM)/g" \
+ -e "s/{{COMMIT}}/$(COMMIT)/g" < templates/README.md > README.md || \
+ $(SED) -e "s/{{VER}}/$(VERSION)/g" -e "s/{{BRANCH}}/$(BRANCH)/g" -e \
+ "s/{{CHECKSUM}}/$(SPEC_CHECKSUM)/g" -e "s/{{COMMIT}}/$(COMMIT)/g" < templates/README.md > README.md
+
+.$(VERSION): Makefile
+ touch .$(VERSION)
publish:
- npm publish --access=public
+ $(NPM) publish --access=public
+
+test:
+ $(JEST)
+
+coveralls:
+ $(CAT) ./coverage/lcov.info | $(COVERALLS)
+
+lint:
+ $(ESLINT) src/lips.js spec/lips.spec.js
diff --git a/README.md b/README.md
index e0a0cca1..df8c9884 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,12 @@
-## Lips is Pretty Simple
+## LIPS is Pretty Simple
-[![npm](https://img.shields.io/badge/npm-0.1.0-blue.svg)](https://www.npmjs.com/package/@jcubic/lips)
+[![npm](https://img.shields.io/badge/npm-DEV-blue.svg)](https://www.npmjs.com/package/@jcubic/lips)
+[![travis](https://travis-ci.org/jcubic/jquery.terminal.svg?branch=devel&acf06f4b5cae4b8fb9ca088cda6a89e805b48569)](https://travis-ci.org/jcubic/jquery.terminal)
+[![Coverage Status](https://coveralls.io/repos/github/jcubic/lips/badge.svg?branch=devel&)](https://coveralls.io/github/jcubic/lips?branch=devel)
-Lips is very simple Lisp, similar to Scheme writen in JavaScript.
+
+
+LIPS is very simple Lisp, similar to Scheme writen in JavaScript.
[Demo](https://codepen.io/jcubic/full/LQBaaV/)
@@ -31,8 +35,8 @@ https://cdn.rawgit.com/jcubic/lips/master/index.js
```javascript
var {parse, tokenize, evaluate} = require('@jcubic/lips');
-parse(tokenize(code)).forEach(function(code) {
- evalute(code);
+parse(tokenize(string)).forEach(function(code) {
+ evaluate(code);
});
```
@@ -46,7 +50,9 @@ You can create new environment using:
var env = new Environment({}, lips.global_environment);
```
-You need to use global environment otherwise you will not have any functions.
+First argument is an object with functions, macros and varibles (see Extending LIPS at the end).
+Second argument is parent environment, you need to use global environment (or other that extend global)
+otherwise you will not have any functions.
## What's in
@@ -71,7 +77,7 @@ You need to use global environment otherwise you will not have any functions.
(print (cadaddr lst)))
```
-all functions that match this regex `c[ad]{2,5}r`
+all functions that match this regex `c[ad]{2,5}r` are defined.
### ALists
@@ -124,7 +130,7 @@ then type S-Expression like `(print 10)`. If function return Promise
the execution is paused and restored when Promise is resolved
-### Access JavaScript functions
+### Access JavaScript functions and objects
```scheme
((. window "alert") "hello")
@@ -145,14 +151,14 @@ function `$` is available because it's in window object.
or operate on strings
-```scheme
+```
((. "foo bar baz" "replace") /^[a-z]+/g "baz")
(let ((match (. "foo bar baz" "match")))
(array->list (match /([a-z]+)/g)))
```
-### Mapping and filtering
+### Mapping, filtering and reducing
```scheme
(map car (list
@@ -164,13 +170,47 @@ or operate on strings
(filter (lambda (x)
(== (% x 2) 0))
(list 1 2 3 4 5))
+
+(define (reverse list)
+ (reduce (lambda (list x) (cons x list)) list))
+
+(reverse '(1 2 3 4))
+```
+
+### Working with arrays
+
+You can modify array with `set` function and to get the value of the array you can use `.` dot function.
+
+```scheme
+(let ((arr (list->array '(1 2 3 4))))
+ (set arr 0 2)
+ (print (array->list arr)))
+
+(let* ((div ((. document "querySelectorAll") ".terminal-output > div"))
+ (len (. div "length"))
+ (i 0))
+ (while (< i len)
+ (print (. (. div i) "innerHTML"))
+ (++ i)))
+```
+
+this equivalent of JavaScript code:
+
+```javascript
+var div = document.querySelectorAll(".terminal div");
+var len = div.length;
+var i = 0;
+while (i < len) {
+ console.log(div[i].innerHTML);
+ ++i;
+}
```
### Math and boolean operators
`< > => <= ++ -- + - * / % and or`
-## Extending Lips
+## Extending LIPS
to create new function from JavaScript you can use:
@@ -180,9 +220,9 @@ env.set('replace', function(re, sub, string) {
});
```
-then you can use it in lips:
+then you can use it in LIPS:
-```scheme
+```
(replace /(foo|bar)/g "hello" "foo bar baz")
```
@@ -194,14 +234,11 @@ single function argument, that should return lisp code (instance of Pair)
var {Macro, Pair, Symbol, nil} = lips;
env.set('quote-car', new Macro(function(code) {
- return new Pair(
- new Symbol("quote"),
- new Pair(code.car.car, nil)
- );
+ return Pair.fromArray([new Symbol('quote'), code.car.car]);
}));
```
-and you can execute this macro in lips:
+and you can execute this macro in LIPS:
```scheme
(quote-car (foo bar baz))
diff --git a/demo.html b/demo.html
index f87c73a0..b369d899 100644
--- a/demo.html
+++ b/demo.html
@@ -2,14 +2,33 @@
- Lips Demo
+ LIPS Demo
-
+
+
diff --git a/dist/lips.js b/dist/lips.js
new file mode 100644
index 00000000..bda784f2
--- /dev/null
+++ b/dist/lips.js
@@ -0,0 +1,1185 @@
+/**@license
+ * LIPS is Pretty Simple - version DEV
+ *
+ * Copyright (c) 2018 Jakub Jankiewicz
+ * Released under the MIT license
+ *
+ * build: Sun, 04 Mar 2018 08:09:12 +0000
+ */
+/*
+ * TODO: Pair.prototype.toObject = alist to Object
+ */
+"use strict";
+/* global define, module, setTimeout, jQuery */
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define([], function () {
+ return root.lips = factory();
+ });
+ } else if ((typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object' && module.exports) {
+ // Node/CommonJS
+ module.exports = factory();
+ } else {
+ root.lips = factory();
+ }
+})(typeof self !== 'undefined' ? self : undefined, function (undefined) {
+ // parse_argument based on function from jQuery Terminal
+ var re_re = /^\/((?:\\\/|[^/]|\[[^\]]*\/[^\]]*\])+)\/([gimy]*)$/;
+ var float_re = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/;
+ // ----------------------------------------------------------------------
+ function parse_argument(arg) {
+ function parse_string(string) {
+ // remove quotes if before are even number of slashes
+ // we don't remove slases becuase they are handled by JSON.parse
+ //string = string.replace(/([^\\])['"]$/, '$1');
+ if (string.match(/^['"]/)) {
+ if (string === '""' || string === "''") {
+ return '';
+ }
+ var quote = string[0];
+ var re = new RegExp("((^|[^\\\\])(?:\\\\\\\\)*)" + quote, "g");
+ string = string.replace(re, "$1");
+ }
+ // use build in function to parse rest of escaped characters
+ return JSON.parse('"' + string + '"');
+ }
+ var regex = arg.match(re_re);
+ if (regex) {
+ return new RegExp(regex[1], regex[2]);
+ } else if (arg.match(/['"]/)) {
+ return parse_string(arg);
+ } else if (arg.match(/^-?[0-9]+$/)) {
+ return parseInt(arg, 10);
+ } else if (arg.match(float_re)) {
+ return parseFloat(arg);
+ } else if (arg === 'nil') {
+ return nil;
+ } else {
+ return new _Symbol(arg);
+ }
+ }
+ // ----------------------------------------------------------------------
+ /* eslint-disable */
+ var tokens_re = /("[^"\\]*(?:\\[\S\s][^"\\]*)*"|\/[^\/\\]*(?:\\[\S\s][^\/\\]*)*\/[gimy]*(?=\s|\(|\)|$)|;.*|\(|\)|'|\.|,@|,|`|[^(\s)]+)/gi;
+ /* eslint-enable */
+ // ----------------------------------------------------------------------
+ function tokenize(str) {
+ return str.split('\n').map(function (line) {
+ return line.split(tokens_re).map(function (token) {
+ if (token.match(/^;/)) {
+ return null;
+ }
+ return token.trim();
+ }).filter(Boolean);
+ }).reduce(function (arr, tokens) {
+ return arr.concat(tokens);
+ }, []);
+ }
+ // ----------------------------------------------------------------------
+ var specials = {
+ "'": new _Symbol('quote'),
+ '`': new _Symbol('quasiquote'),
+ ',': new _Symbol('unquote'),
+ ',@': new _Symbol('unquote-splicing')
+ };
+ // ----------------------------------------------------------------------
+ // :: tokens are the array of strings from tokenizer
+ // :: the return value is lisp code created out of Pair class
+ // ----------------------------------------------------------------------
+ function parse(tokens) {
+ var stack = [];
+ var result = [];
+ var special = null;
+ var special_tokens = Object.keys(specials);
+ var special_forms = special_tokens.map(function (s) {
+ return specials[s].name;
+ });
+ var parents = 0;
+ var first_value = false;
+ tokens.forEach(function (token) {
+ var top = stack[stack.length - 1];
+ if (special_tokens.indexOf(token) !== -1) {
+ special = token;
+ } else if (token === '(') {
+ first_value = true;
+ parents++;
+ if (special) {
+ stack.push([specials[special]]);
+ special = null;
+ }
+ stack.push([]);
+ } else if (token === '.' && !first_value) {
+ stack[stack.length - 1] = Pair.fromArray(top);
+ } else if (token === ')') {
+ parents--;
+ if (!stack.length) {
+ throw new Error('Unbalanced parenthesis');
+ }
+ if (stack.length === 1) {
+ result.push(stack.pop());
+ } else if (stack.length > 1) {
+ var list = stack.pop();
+ top = stack[stack.length - 1];
+ if (top instanceof Array) {
+ top.push(list);
+ } else if (top instanceof Pair) {
+ top.append(Pair.fromArray(list));
+ }
+ if (top instanceof Array && top[0] instanceof _Symbol && special_forms.includes(top[0].name) && stack.length > 1) {
+ stack.pop();
+ if (stack[stack.length - 1].length === 0) {
+ stack[stack.length - 1] = top;
+ } else if (stack[stack.length - 1] instanceof Pair) {
+ if (stack[stack.length - 1].cdr instanceof Pair) {
+ stack[stack.length - 1] = new Pair(stack[stack.length - 1], Pair.fromArray(top));
+ } else {
+ stack[stack.length - 1].cdr = Pair.fromArray(top);
+ }
+ } else {
+ stack[stack.length - 1].push(top);
+ }
+ }
+ }
+ if (parents === 0 && stack.length) {
+ result.push(stack.pop());
+ }
+ } else {
+ first_value = false;
+ var value = parse_argument(token);
+ if (special) {
+ value = [specials[special], value];
+ special = false;
+ }
+ if (top instanceof Pair) {
+ var node = top;
+ while (true) {
+ if (node.cdr === nil) {
+ node.cdr = value;
+ break;
+ } else {
+ node = node.cdr;
+ }
+ }
+ } else if (!stack.length) {
+ result.push(value);
+ } else {
+ top.push(value);
+ }
+ }
+ });
+ if (stack.length) {
+ throw new Error('Unbalanced parenthesis');
+ }
+ return result.map(function (arg) {
+ if (arg instanceof Array) {
+ return Pair.fromArray(arg);
+ }
+ return arg;
+ });
+ }
+ // ----------------------------------------------------------------------
+ // :: Symbol constructor
+ // ----------------------------------------------------------------------
+ function _Symbol(name) {
+ this.name = name;
+ }
+ _Symbol.is = function (symbol, name) {
+ return symbol instanceof _Symbol && typeof name === 'string' && symbol.name === name;
+ };
+ _Symbol.prototype.toJSON = _Symbol.prototype.toString = function () {
+ //return '<#symbol \'' + this.name + '\'>';
+ return this.name;
+ };
+ // ----------------------------------------------------------------------
+ // :: Nil constructor with only once instance
+ // ----------------------------------------------------------------------
+ function Nil() {}
+ Nil.prototype.toString = function () {
+ return 'nil';
+ };
+ var nil = new Nil();
+ // ----------------------------------------------------------------------
+ // :: Pair constructor
+ // ----------------------------------------------------------------------
+ function Pair(car, cdr) {
+ this.car = car;
+ this.cdr = cdr;
+ }
+ Pair.prototype.length = function () {
+ var len = 0;
+ var node = this;
+ while (true) {
+ if (node === nil) {
+ break;
+ }
+ len++;
+ node = node.cdr;
+ }
+ return len;
+ };
+ Pair.prototype.clone = function () {
+ var cdr;
+ if (this.cdr === nil) {
+ cdr = nil;
+ } else {
+ cdr = this.cdr.clone();
+ }
+ return new Pair(this.car, cdr);
+ };
+ Pair.prototype.toArray = function () {
+ if (this.cdr === nil && this.car === nil) {
+ return [];
+ }
+ var result = [];
+ if (this.car instanceof Pair) {
+ result.push(this.car.toArray());
+ } else {
+ result.push(this.car);
+ }
+ if (this.cdr instanceof Pair) {
+ result = result.concat(this.cdr.toArray());
+ }
+ return result;
+ };
+ Pair.fromArray = function (array) {
+ if (array instanceof Pair) {
+ return array;
+ }
+ if (array.length && !array instanceof Array) {
+ array = [].concat(_toConsumableArray(array));
+ }
+ if (array.length === 0) {
+ return new Pair(nil, nil);
+ } else {
+ var car;
+ if (array[0] instanceof Array) {
+ car = Pair.fromArray(array[0]);
+ } else {
+ car = array[0];
+ }
+ if (array.length === 1) {
+ return new Pair(car, nil);
+ } else {
+ return new Pair(car, Pair.fromArray(array.slice(1)));
+ }
+ }
+ };
+ Pair.prototype.toObject = function () {
+ var node = this;
+ var result = {};
+ while (true) {
+ if (node instanceof Pair && node.car instanceof Pair) {
+ var pair = node.car;
+ var name = pair.car;
+ if (name instanceof _Symbol) {
+ name = name.name;
+ }
+ result[name] = pair.cdr;
+ node = node.cdr;
+ } else {
+ break;
+ }
+ }
+ return result;
+ };
+ Pair.fromPairs = function (array) {
+ return array.reduce(function (list, pair) {
+ return new Pair(new Pair(new _Symbol(pair[0]), pair[1]), list);
+ }, nil);
+ };
+ Pair.fromObject = function (obj) {
+ var array = Object.keys(obj).map(function (key) {
+ return [key, obj[key]];
+ });
+ return Pair.fromPairs(array);
+ };
+ Pair.prototype.reduce = function (fn) {
+ var node = this;
+ var result = nil;
+ while (true) {
+ if (node !== nil) {
+ result = fn(result, node.car);
+ node = node.cdr;
+ } else {
+ break;
+ }
+ }
+ return result;
+ };
+ Pair.prototype.reverse = function () {
+ var node = this;
+ var prev = nil;
+ while (node !== nil) {
+ var next = node.cdr;
+ node.cdr = prev;
+ prev = node;
+ node = next;
+ }
+ return prev;
+ };
+ Pair.prototype.transform = function (fn) {
+ var visited = [];
+ function recur(pair) {
+ if (pair instanceof Pair) {
+ if (pair.replace) {
+ delete pair.replace;
+ return pair;
+ }
+ var car = fn(pair.car);
+ if (car instanceof Pair) {
+ car = recur(car);
+ visited.push(car);
+ }
+ var cdr = fn(pair.cdr);
+ if (cdr instanceof Pair) {
+ cdr = recur(cdr);
+ visited.push(cdr);
+ }
+ return new Pair(car, cdr);
+ }
+ return pair;
+ }
+ return recur(this);
+ };
+ Pair.prototype.toString = function () {
+ var arr = ['('];
+ if (typeof this.car === 'string') {
+ arr.push(JSON.stringify(this.car));
+ } else if (typeof this.car !== 'undefined') {
+ arr.push(this.car);
+ }
+ if (this.cdr instanceof Pair) {
+ arr.push(' ');
+ arr.push(this.cdr.toString().replace(/^\(|\)$/g, ''));
+ } else if (typeof this.cdr !== 'undefined' && this.cdr !== nil) {
+ if (typeof this.cdr === 'string') {
+ arr = arr.concat([' . ', JSON.stringify(this.cdr)]);
+ } else {
+ arr = arr.concat([' . ', this.cdr]);
+ }
+ }
+ arr.push(')');
+ return arr.join('');
+ };
+ Pair.prototype.append = function (pair) {
+ if (pair instanceof Array) {
+ return this.append(Pair.fromArray(pair));
+ }
+ var p = this;
+ while (true) {
+ if (p instanceof Pair && p.cdr !== nil) {
+ p = p.cdr;
+ } else {
+ break;
+ }
+ }
+ p.cdr = pair;
+ return this;
+ };
+
+ // ----------------------------------------------------------------------
+ // :: Macro constructor
+ // ----------------------------------------------------------------------
+ function Macro(fn) {
+ this.fn = fn;
+ }
+ Macro.prototype.invoke = function (code, env) {
+ return this.fn.call(env, code);
+ };
+
+ // ----------------------------------------------------------------------
+ // :: Environment constructor (parent argument is optional)
+ // ----------------------------------------------------------------------
+ function Environment(obj, parent) {
+ this.env = obj;
+ this.parent = parent;
+ }
+ Environment.prototype.get = function (symbol) {
+ if (symbol instanceof _Symbol) {
+ if (typeof this.env[symbol.name] !== 'undefined') {
+ return this.env[symbol.name];
+ }
+ } else if (typeof symbol === 'string') {
+ if (typeof this.env[symbol] !== 'undefined') {
+ return this.env[symbol];
+ }
+ }
+
+ if (this.parent instanceof Environment) {
+ return this.parent.get(symbol);
+ } else if (symbol instanceof _Symbol) {
+ if (typeof window[symbol.name] !== 'undefined') {
+ return window[symbol.name];
+ }
+ } else if (typeof symbol === 'string') {
+ if (typeof window[symbol] !== 'undefined') {
+ return window[symbol];
+ }
+ }
+ };
+ Environment.prototype.set = function (name, value) {
+ this.env[name] = value;
+ };
+ // ----------------------------------------------------------------------
+ // :: Quote constructor used to pause evaluation from Macro
+ // ----------------------------------------------------------------------
+ function Quote(value) {
+ this.value = value;
+ }
+ // ----------------------------------------------------------------------
+ // :: function that return macro for let and let*
+ // ----------------------------------------------------------------------
+ function let_macro(asterisk) {
+ return new Macro(function (code) {
+ var _this = this;
+
+ var args = this.get('list->array')(code.car);
+ var env = new Environment({}, this);
+ args.forEach(function (pair) {
+ env.set(pair.car, evaluate(pair.cdr.car, asterisk ? env : _this));
+ });
+ var output = new Pair(new _Symbol('begin'), code.cdr);
+ return new Quote(evaluate(output, env));
+ });
+ }
+ var gensym = function () {
+ var count = 0;
+ return function () {
+ count++;
+ return new _Symbol('#' + count);
+ };
+ }();
+ function request(url) {
+ var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'GET';
+ var headers = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
+ var data = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
+
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+ Object.keys(headers).forEach(function (name) {
+ xhr.setRequestHeader(name, headers[name]);
+ });
+ return new Promise(function (resolve) {
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4 && xhr.status === 200) {
+ resolve(xhr.responseText);
+ }
+ };
+ if (data !== null) {
+ xhr.send(data);
+ } else {
+ xhr.send();
+ }
+ });
+ }
+ var global_env = new Environment({
+ nil: nil,
+ window: window,
+ 'true': true,
+ 'false': false,
+ stdout: {
+ write: function write() {
+ var _console;
+
+ (_console = console).log.apply(_console, arguments);
+ }
+ },
+ stdin: {
+ read: function read() {
+ return new Promise(function (resolve) {
+ resolve(prompt(''));
+ });
+ }
+ },
+ cons: function cons(car, cdr) {
+ return new Pair(car, cdr);
+ },
+ car: function car(list) {
+ if (list instanceof Pair) {
+ return list.car;
+ } else {
+ throw new Error('argument to car need to be a list');
+ }
+ },
+ cdr: function cdr(list) {
+ if (list instanceof Pair) {
+ return list.cdr;
+ } else {
+ throw new Error('argument to cdr need to be a list');
+ }
+ },
+ 'set-car': function setCar(slot, value) {
+ slot.car = value;
+ },
+ 'set-cdr': function setCdr(slot, value) {
+ slot.cdr = value;
+ },
+ assoc: function assoc(list, key) {
+ var node = list;
+ var name = key instanceof _Symbol ? key.name : key;
+ while (true) {
+ var car = node.car.car;
+ if (car instanceof _Symbol && car.name === name || car.name === name) {
+ return node.car;
+ } else {
+ node = node.cdr;
+ }
+ }
+ },
+ gensym: gensym,
+ load: function load(file) {
+ var _this2 = this;
+
+ request(file).then(function (code) {
+ _this2.get('eval')(_this2.get('read')(code));
+ });
+ },
+ 'while': new Macro(function (code) {
+ var self = this;
+ var begin = new Pair(new _Symbol('begin'), code.cdr);
+ return new Promise(function (resolve) {
+ var result;
+ (function loop() {
+ function next(cond) {
+ if (cond) {
+ var value = evaluate(begin, self);
+ if (value instanceof Promise) {
+ value.then(function (value) {
+ result = value;
+ loop();
+ });
+ } else {
+ result = value;
+ loop();
+ }
+ } else {
+ resolve(result);
+ }
+ }
+ var cond = evaluate(code.car, self);
+ if (cond instanceof Promise) {
+ cond.then(next);
+ } else {
+ next(cond);
+ }
+ })();
+ });
+ }),
+ 'if': new Macro(function (code) {
+ var _this3 = this;
+
+ var resolve = function resolve(cond) {
+ if (cond) {
+ var true_value = evaluate(code.cdr.car, _this3);
+ if (typeof true_value === 'undefined') {
+ return;
+ }
+ return true_value;
+ } else if (code.cdr.cdr.car instanceof Pair) {
+ var false_value = evaluate(code.cdr.cdr.car, _this3);
+ if (typeof false_value === 'undefined') {
+ return false;
+ }
+ return false_value;
+ } else {
+ return false;
+ }
+ };
+ var cond = evaluate(code.car, this);
+ if (cond instanceof Promise) {
+ return cond.then(resolve);
+ } else {
+ return resolve(cond);
+ }
+ }),
+ 'let*': let_macro(true),
+ 'let': let_macro(false),
+ 'begin': new Macro(function (code) {
+ var _this4 = this;
+
+ var arr = this.get('list->array')(code);
+ return arr.reduce(function (_, code) {
+ return evaluate(code, _this4);
+ }, 0);
+ }),
+ timer: new Macro(function (code) {
+ var _this5 = this;
+
+ return new Promise(function (resolve) {
+ setTimeout(function () {
+ resolve(new Quote(evaluate(code.cdr, _this5)));
+ }, code.car);
+ });
+ }),
+ define: new Macro(function (code) {
+ if (code.car instanceof Pair && code.car.car instanceof _Symbol) {
+ var new_code = new Pair(new _Symbol("define"), new Pair(code.car.car, new Pair(new Pair(new _Symbol("lambda"), new Pair(code.car.cdr, code.cdr)))));
+ return new_code;
+ }
+ var value = code.cdr.car;
+ if (value instanceof Pair) {
+ value = evaluate(value, this);
+ }
+ if (code.car instanceof _Symbol) {
+ this.env[code.car.name] = value;
+ }
+ }),
+ set: function set(obj, key, value) {
+ obj[key] = value;
+ },
+ 'eval': function _eval(code) {
+ var _this6 = this;
+
+ if (code instanceof Pair) {
+ return evaluate(code, this);
+ }
+ if (code instanceof Array) {
+ var result;
+ code.forEach(function (code) {
+ result = evaluate(code, _this6);
+ });
+ return result;
+ }
+ },
+ lambda: new Macro(function (code) {
+ var _this7 = this;
+
+ return function () {
+ var env = new Environment({}, _this7);
+ var name = code.car;
+ var i = 0;
+ var value;
+ while (true) {
+ if (name.car !== nil) {
+ if (typeof (arguments.length <= i ? undefined : arguments[i]) === 'undefined') {
+ value = nil;
+ } else {
+ value = arguments.length <= i ? undefined : arguments[i];
+ }
+ env.env[name.car.name] = value;
+ }
+ if (name.cdr === nil) {
+ break;
+ }
+ i++;
+ name = name.cdr;
+ }
+ return evaluate(code.cdr.car, env);
+ };
+ }),
+ defmacro: new Macro(function (macro) {
+ if (macro.car.car instanceof _Symbol) {
+ this.env[macro.car.car.name] = new Macro(function (code) {
+ var env = new Environment({}, this);
+ var name = macro.car.cdr;
+ var arg = code;
+ while (true) {
+ if (name.car !== nil && arg.car !== nil) {
+ env.env[name.car.name] = arg.car;
+ }
+ if (name.cdr === nil) {
+ break;
+ }
+ arg = arg.cdr;
+ name = name.cdr;
+ }
+ return evaluate(macro.cdr.car, env);
+ });
+ }
+ }),
+ quote: new Macro(function (arg) {
+ return new Quote(arg.car);
+ }),
+ quasiquote: new Macro(function (arg) {
+ var self = this;
+ function recur(pair) {
+ if (pair instanceof Pair) {
+ var eval_pair;
+ if (_Symbol.is(pair.car.car, 'unquote-splicing')) {
+ eval_pair = evaluate(pair.car.cdr.car, self);
+ if (!eval_pair instanceof Pair) {
+ throw new Error('Value of unquote-splicing need' + ' to be pair');
+ }
+ if (pair.cdr instanceof Pair) {
+ if (eval_pair instanceof Pair) {
+ eval_pair.cdr.append(recur(pair.cdr));
+ } else {
+ eval_pair = new Pair(eval_pair, recur(pair.cdr));
+ }
+ }
+ return eval_pair;
+ }
+ if (_Symbol.is(pair.car, 'unquote-splicing')) {
+ eval_pair = evaluate(pair.cdr.car, self);
+ if (!eval_pair instanceof Pair) {
+ throw new Error('Value of unquote-splicing' + ' need to be pair');
+ }
+ return eval_pair;
+ }
+ if (_Symbol.is(pair.car, 'unquote')) {
+ if (pair.cdr.cdr !== nil) {
+ return new Pair(evaluate(pair.cdr.car, self), pair.cdr.cdr);
+ } else {
+ return evaluate(pair.cdr.car, self);
+ }
+ }
+ var car = pair.car;
+ if (car instanceof Pair) {
+ car = recur(car);
+ }
+ var cdr = pair.cdr;
+ if (cdr instanceof Pair) {
+ cdr = recur(cdr);
+ }
+ return new Pair(car, cdr);
+ }
+ return pair;
+ }
+ return new Quote(recur(arg.car));
+ }),
+ clone: function clone(list) {
+ return list.clone();
+ },
+ append: function append(list, item) {
+ return this.get('append!')(list.clone(), item);
+ },
+ 'append!': function append(list, item) {
+ var node = list;
+ while (true) {
+ if (node.cdr === nil) {
+ node.cdr = item;
+ break;
+ }
+ node = node.cdr;
+ }
+ return list;
+ },
+ list: function list() {
+ return Pair.fromArray([].slice.call(arguments));
+ },
+ concat: function concat() {
+ return [].join.call(arguments, '');
+ },
+ string: function string(obj) {
+ if (typeof jQuery !== 'undefined' && obj instanceof jQuery.fn.init) {
+ return '<#jQuery>';
+ }
+ if (obj instanceof Macro) {
+ //return '<#Macro>';
+ }
+ if (typeof obj === 'undefined') {
+ return '<#undefined>';
+ }
+ if (typeof obj === 'function') {
+ return '<#function>';
+ }
+ if (obj === nil) {
+ return 'nil';
+ }
+ if (obj instanceof Array || obj === null) {
+ return JSON.stringify(obj);
+ }
+ if (obj instanceof Pair) {
+ return obj.toString();
+ }
+ if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object') {
+ var name = obj.constructor.name;
+ if (name !== '') {
+ return '<#' + name + '>';
+ }
+ return '<#Object>';
+ }
+ if (typeof obj !== 'string') {
+ return obj.toString();
+ }
+ return obj;
+ },
+ env: function env(_env) {
+ _env = _env || this;
+ var names = Object.keys(_env.env);
+ var result;
+ if (names.length) {
+ result = Pair.fromArray(names);
+ } else {
+ result = nil;
+ }
+ if (_env.parent !== undefined) {
+ return this.get('env').call(this, _env.parent).append(result);
+ }
+ return result;
+ },
+ '.': function _(obj, arg) {
+ var name = arg instanceof _Symbol ? arg.name : arg;
+ var value = obj[name];
+ if (typeof value === 'function') {
+ return value.bind(obj);
+ }
+ return value;
+ },
+ read: function read(arg) {
+ var _this8 = this;
+
+ if (typeof arg === 'string') {
+ return parse(tokenize(arg));
+ }
+ return this.get('stdin').read().then(function (text) {
+ return _this8.get('read').call(_this8, text);
+ });
+ },
+ print: function print() {
+ var _get,
+ _this9 = this;
+
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ (_get = this.get('stdout')).write.apply(_get, _toConsumableArray(args.map(function (arg) {
+ return _this9.get('string')(arg);
+ })));
+ },
+ 'array->list': function arrayList(array) {
+ return Pair.fromArray(array);
+ },
+ 'list->array': function listArray(list) {
+ var result = [];
+ var node = list;
+ while (true) {
+ if (node instanceof Pair) {
+ result.push(node.car);
+ node = node.cdr;
+ } else {
+ break;
+ }
+ }
+ return result;
+ },
+ filter: function filter(fn, list) {
+ return Pair.fromArray(this.get('list->array')(list).filter(fn));
+ },
+ odd: function odd(num) {
+ return num % 2 === 1;
+ },
+ even: function even(num) {
+ return num % 2 === 0;
+ },
+ apply: function apply(fn, list) {
+ var args = this.get('list->array')(list);
+ return fn.apply(null, args);
+ },
+ map: function map(fn, list) {
+ var result = this.get('list->array')(list).map(fn);
+ if (result.length) {
+ return Pair.fromArray(result);
+ } else {
+ return nil;
+ }
+ },
+ reduce: function reduce(fn, list) {
+ var arr = this.get('list->array')(list);
+ return arr.reduce(function (list, item) {
+ return fn(list, item);
+ }, nil);
+ },
+ // math functions
+ '*': function _() {
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ return args.reduce(function (a, b) {
+ return a * b;
+ });
+ },
+ '+': function _() {
+ for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ args[_key3] = arguments[_key3];
+ }
+
+ return args.reduce(function (a, b) {
+ return a + b;
+ });
+ },
+ '-': function _() {
+ for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+
+ return args.reduce(function (a, b) {
+ return a - b;
+ });
+ },
+ '/': function _() {
+ for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
+ args[_key5] = arguments[_key5];
+ }
+
+ return args.reduce(function (a, b) {
+ return a / b;
+ });
+ },
+ '%': function _(a, b) {
+ return a % b;
+ },
+ // Booleans
+ "==": function _(a, b) {
+ return a === b;
+ },
+ '>': function _(a, b) {
+ return a > b;
+ },
+ '<': function _(a, b) {
+ return a < b;
+ },
+ '<=': function _(a, b) {
+ return a <= b;
+ },
+ '>=': function _(a, b) {
+ return a >= b;
+ },
+ or: new Macro(function (code) {
+ var args = this.get('list->array')(code);
+ var self = this;
+ return new Promise(function (resolve) {
+ var result;
+ (function loop() {
+ function next(value) {
+ result = value;
+ if (result) {
+ resolve(value);
+ }
+ loop();
+ }
+ var arg = args.shift();
+ if (typeof arg === 'undefined') {
+ if (result) {
+ resolve(result);
+ } else {
+ resolve(false);
+ }
+ } else {
+ var value = evaluate(arg, self);
+ if (value instanceof Promise) {
+ value.then(next);
+ } else {
+ next(value);
+ }
+ }
+ })();
+ });
+ }),
+ and: new Macro(function (code) {
+ var args = this.get('list->array')(code);
+ var self = this;
+ return new Promise(function (resolve) {
+ var result;
+ (function loop() {
+ function next(value) {
+ result = value;
+ if (!result) {
+ resolve(false);
+ }
+ loop();
+ }
+ var arg = args.shift();
+ if (typeof arg === 'undefined') {
+ if (result) {
+ resolve(result);
+ } else {
+ resolve(false);
+ }
+ } else {
+ var value = evaluate(arg, self);
+ if (value instanceof Promise) {
+ value.then(next);
+ } else {
+ next(value);
+ }
+ }
+ })();
+ });
+ }),
+ '++': new Macro(function (code) {
+ var value = this.get(code.car) + 1;
+ this.set(code.car, value);
+ return value;
+ }),
+ '--': new Macro(function (code) {
+ var value = this.get(code.car) - 1;
+ this.set(code.car, value);
+ return value;
+ })
+ });
+
+ // ----------------------------------------------------------------------
+ // source: https://stackoverflow.com/a/4331218/387194
+ function allPossibleCases(arr) {
+ if (arr.length === 1) {
+ return arr[0];
+ } else {
+ var result = [];
+ // recur with the rest of array
+ var allCasesOfRest = allPossibleCases(arr.slice(1));
+ for (var i = 0; i < allCasesOfRest.length; i++) {
+ for (var j = 0; j < arr[0].length; j++) {
+ result.push(arr[0][j] + allCasesOfRest[i]);
+ }
+ }
+ return result;
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ function combinations(input, start, end) {
+ var result = [];
+ for (var i = start; i <= end; ++i) {
+ var input_arr = [];
+ for (var j = 0; j < i; ++j) {
+ input_arr.push(input);
+ }
+ result = result.concat(allPossibleCases(input_arr));
+ }
+ return result;
+ }
+ // ----------------------------------------------------------------------
+ // cadr caddr cadadr etc.
+ combinations(['d', 'a'], 2, 5).forEach(function (spec) {
+ var chars = spec.split('').reverse();
+ global_env.set('c' + spec + 'r', function (arg) {
+ return chars.reduce(function (list, type) {
+ if (type === 'a') {
+ return list.car;
+ } else {
+ return list.cdr;
+ }
+ }, arg);
+ });
+ });
+
+ // ----------------------------------------------------------------------
+ function evaluate(code, env) {
+ env = env || global_env;
+ var value;
+ if (typeof code === 'undefined') {
+ return;
+ }
+ var first = code.car;
+ var rest = code.cdr;
+ if (first instanceof Pair) {
+ value = evaluate(first, env);
+ if (typeof value !== 'function') {
+ throw new Error(env.get('string')(value) + ' is not a function');
+ }
+ }
+ if (typeof first === 'function') {
+ value = first;
+ }
+ if (first instanceof _Symbol) {
+ value = env.get(first);
+ if (value instanceof Macro) {
+ value = value.invoke(rest, env);
+ if (value instanceof Quote) {
+ return value.value;
+ }
+ return evaluate(value, env);
+ } else if (typeof value !== 'function') {
+ throw new Error('Unknown function `' + first.name + '\'');
+ }
+ }
+ if (typeof value === 'function') {
+ var args = [];
+ var node = rest;
+ while (true) {
+ if (node instanceof Pair) {
+ args.push(evaluate(node.car, env));
+ node = node.cdr;
+ } else {
+ break;
+ }
+ }
+ var promises = args.filter(function (arg) {
+ return arg instanceof Promise;
+ });
+ if (promises.length) {
+ return Promise.all(args).then(function (args) {
+ return value.apply(env, args);
+ });
+ }
+ return value.apply(env, args);
+ } else if (code instanceof _Symbol) {
+ value = env.get(code);
+ if (value === 'undefined') {
+ throw new Error('Unbound variable `' + code.name + '\'');
+ }
+ return value;
+ } else {
+ return code;
+ }
+ }
+ // ----------------------------------------------------------------------
+
+ function balanced(code) {
+ var re = /[()]/;
+ var parenthesis = tokenize(code).filter(function (token) {
+ return token.match(re);
+ });
+ var open = parenthesis.filter(function (p) {
+ return p === ')';
+ });
+ var close = parenthesis.filter(function (p) {
+ return p === '(';
+ });
+ return open.length === close.length;
+ }
+ // --------------------------------------
+ Pair.unDry = function (value) {
+ return new Pair(value.car, value.cdr);
+ };
+ Pair.prototype.toDry = function () {
+ return {
+ value: {
+ car: this.car,
+ cdr: this.cdr
+ }
+ };
+ };
+ Nil.prototype.toDry = function () {
+ return {
+ value: null
+ };
+ };
+ Nil.unDry = function () {
+ return nil;
+ };
+ _Symbol.prototype.toDry = function () {
+ return {
+ value: {
+ name: this.name
+ }
+ };
+ };
+ _Symbol.unDry = function (value) {
+ return new _Symbol(value.name);
+ };
+ return {
+ version: 'DEV',
+ parse: parse,
+ tokenize: tokenize,
+ evaluate: evaluate,
+ Environment: Environment,
+ global_environment: global_env,
+ balanced_parenthesis: balanced,
+ Macro: Macro,
+ Quote: Quote,
+ Pair: Pair,
+ nil: nil,
+ Symbol: _Symbol
+ };
+});
+
diff --git a/dist/lips.min.js b/dist/lips.min.js
new file mode 100644
index 00000000..89bf112a
--- /dev/null
+++ b/dist/lips.min.js
@@ -0,0 +1,9 @@
+/**@license
+ * LIPS is Pretty Simple - version DEV
+ *
+ * Copyright (c) 2018 Jakub Jankiewicz
+ * Released under the MIT license
+ *
+ * build: Sun, 04 Mar 2018 08:09:12 +0000
+ */
+"use strict";var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(n){return typeof n}:function(n){return n&&typeof Symbol==="function"&&n.constructor===Symbol&&n!==Symbol.prototype?"symbol":typeof n};function _toConsumableArray(n){if(Array.isArray(n)){for(var r=0,e=Array(n.length);r1){var p=r.pop();d=r[r.length-1];if(d instanceof Array){d.push(p)}else if(d instanceof l){d.append(l.fromArray(p))}if(d instanceof Array&&d[0]instanceof c&&o.includes(d[0].name)&&r.length>1){r.pop();if(r[r.length-1].length===0){r[r.length-1]=d}else if(r[r.length-1]instanceof l){if(r[r.length-1].cdr instanceof l){r[r.length-1]=new l(r[r.length-1],l.fromArray(d))}else{r[r.length-1].cdr=l.fromArray(d)}}else{r[r.length-1].push(d)}}}if(u===0&&r.length){e.push(r.pop())}}else{h=false;var v=t(n);if(i){v=[f[i],v];i=false}if(d instanceof l){var y=d;while(true){if(y.cdr===s){y.cdr=v;break}else{y=y.cdr}}}else if(!r.length){e.push(v)}else{d.push(v)}}});if(r.length){throw new Error("Unbalanced parenthesis")}return e.map(function(n){if(n instanceof Array){return l.fromArray(n)}return n})}function c(n){this.name=n}c.is=function(n,r){return n instanceof c&&typeof r==="string"&&n.name===r};c.prototype.toJSON=c.prototype.toString=function(){return this.name};function u(){}u.prototype.toString=function(){return"nil"};var s=new u;function l(n,r){this.car=n;this.cdr=r}l.prototype.length=function(){var n=0;var r=this;while(true){if(r===s){break}n++;r=r.cdr}return n};l.prototype.clone=function(){var n;if(this.cdr===s){n=s}else{n=this.cdr.clone()}return new l(this.car,n)};l.prototype.toArray=function(){if(this.cdr===s&&this.car===s){return[]}var n=[];if(this.car instanceof l){n.push(this.car.toArray())}else{n.push(this.car)}if(this.cdr instanceof l){n=n.concat(this.cdr.toArray())}return n};l.fromArray=function(n){if(n instanceof l){return n}if(n.length&&!n instanceof Array){n=[].concat(_toConsumableArray(n))}if(n.length===0){return new l(s,s)}else{var r;if(n[0]instanceof Array){r=l.fromArray(n[0])}else{r=n[0]}if(n.length===1){return new l(r,s)}else{return new l(r,l.fromArray(n.slice(1)))}}};l.prototype.toObject=function(){var n=this;var r={};while(true){if(n instanceof l&&n.car instanceof l){var e=n.car;var t=e.car;if(t instanceof c){t=t.name}r[t]=e.cdr;n=n.cdr}else{break}}return r};l.fromPairs=function(n){return n.reduce(function(n,r){return new l(new l(new c(r[0]),r[1]),n)},s)};l.fromObject=function(n){var r=Object.keys(n).map(function(r){return[r,n[r]]});return l.fromPairs(r)};l.prototype.reduce=function(n){var r=this;var e=s;while(true){if(r!==s){e=n(e,r.car);r=r.cdr}else{break}}return e};l.prototype.reverse=function(){var n=this;var r=s;while(n!==s){var e=n.cdr;n.cdr=r;r=n;n=e}return r};l.prototype.transform=function(n){var r=[];function e(t){if(t instanceof l){if(t.replace){delete t.replace;return t}var i=n(t.car);if(i instanceof l){i=e(i);r.push(i)}var a=n(t.cdr);if(a instanceof l){a=e(a);r.push(a)}return new l(i,a)}return t}return e(this)};l.prototype.toString=function(){var n=["("];if(typeof this.car==="string"){n.push(JSON.stringify(this.car))}else if(typeof this.car!=="undefined"){n.push(this.car)}if(this.cdr instanceof l){n.push(" ");n.push(this.cdr.toString().replace(/^\(|\)$/g,""))}else if(typeof this.cdr!=="undefined"&&this.cdr!==s){if(typeof this.cdr==="string"){n=n.concat([" . ",JSON.stringify(this.cdr)])}else{n=n.concat([" . ",this.cdr])}}n.push(")");return n.join("")};l.prototype.append=function(n){if(n instanceof Array){return this.append(l.fromArray(n))}var r=this;while(true){if(r instanceof l&&r.cdr!==s){r=r.cdr}else{break}}r.cdr=n;return this};function h(n){this.fn=n}h.prototype.invoke=function(n,r){return this.fn.call(r,n)};function d(n,r){this.env=n;this.parent=r}d.prototype.get=function(n){if(n instanceof c){if(typeof this.env[n.name]!=="undefined"){return this.env[n.name]}}else if(typeof n==="string"){if(typeof this.env[n]!=="undefined"){return this.env[n]}}if(this.parent instanceof d){return this.parent.get(n)}else if(n instanceof c){if(typeof window[n.name]!=="undefined"){return window[n.name]}}else if(typeof n==="string"){if(typeof window[n]!=="undefined"){return window[n]}}};d.prototype.set=function(n,r){this.env[n]=r};function p(n){this.value=n}function v(n){return new h(function(r){var e=this;var t=this.get("list->array")(r.car);var i=new d({},this);t.forEach(function(r){i.set(r.car,A(r.cdr.car,n?i:e))});var a=new l(new c("begin"),r.cdr);return new p(A(a,i))})}var y=function(){var n=0;return function(){n++;return new c("#"+n)}}();function w(r){var e=arguments.length>1&&arguments[1]!==n?arguments[1]:"GET";var t=arguments.length>2&&arguments[2]!==n?arguments[2]:{};var i=arguments.length>3&&arguments[3]!==n?arguments[3]:null;var a=new XMLHttpRequest;a.open(e,r,true);Object.keys(t).forEach(function(n){a.setRequestHeader(n,t[n])});return new Promise(function(n){a.onreadystatechange=function(){if(a.readyState===4&&a.status===200){n(a.responseText)}};if(i!==null){a.send(i)}else{a.send()}})}var g=new d({nil:s,window:window,true:true,false:false,stdout:{write:function n(){var r;(r=console).log.apply(r,arguments)}},stdin:{read:function n(){return new Promise(function(n){n(prompt(""))})}},cons:function n(r,e){return new l(r,e)},car:function n(r){if(r instanceof l){return r.car}else{throw new Error("argument to car need to be a list")}},cdr:function n(r){if(r instanceof l){return r.cdr}else{throw new Error("argument to cdr need to be a list")}},"set-car":function n(r,e){r.car=e},"set-cdr":function n(r,e){r.cdr=e},assoc:function n(r,e){var t=r;var i=e instanceof c?e.name:e;while(true){var a=t.car.car;if(a instanceof c&&a.name===i||a.name===i){return t.car}else{t=t.cdr}}},gensym:y,load:function n(r){var e=this;w(r).then(function(n){e.get("eval")(e.get("read")(n))})},while:new h(function(n){var r=this;var e=new l(new c("begin"),n.cdr);return new Promise(function(t){var i;(function a(){function f(n){if(n){var f=A(e,r);if(f instanceof Promise){f.then(function(n){i=n;a()})}else{i=f;a()}}else{t(i)}}var o=A(n.car,r);if(o instanceof Promise){o.then(f)}else{f(o)}})()})}),if:new h(function(n){var r=this;var e=function e(t){if(t){var i=A(n.cdr.car,r);if(typeof i==="undefined"){return}return i}else if(n.cdr.cdr.car instanceof l){var a=A(n.cdr.cdr.car,r);if(typeof a==="undefined"){return false}return a}else{return false}};var t=A(n.car,this);if(t instanceof Promise){return t.then(e)}else{return e(t)}}),"let*":v(true),let:v(false),begin:new h(function(n){var r=this;var e=this.get("list->array")(n);return e.reduce(function(n,e){return A(e,r)},0)}),timer:new h(function(n){var r=this;return new Promise(function(e){setTimeout(function(){e(new p(A(n.cdr,r)))},n.car)})}),define:new h(function(n){if(n.car instanceof l&&n.car.car instanceof c){var r=new l(new c("define"),new l(n.car.car,new l(new l(new c("lambda"),new l(n.car.cdr,n.cdr)))));return r}var e=n.cdr.car;if(e instanceof l){e=A(e,this)}if(n.car instanceof c){this.env[n.car.name]=e}}),set:function n(r,e,t){r[e]=t},eval:function n(r){var e=this;if(r instanceof l){return A(r,this)}if(r instanceof Array){var t;r.forEach(function(n){t=A(n,e)});return t}},lambda:new h(function(r){var e=this;return function(){var t=new d({},e);var i=r.car;var a=0;var f;while(true){if(i.car!==s){if(typeof(arguments.length<=a?n:arguments[a])==="undefined"){f=s}else{f=arguments.length<=a?n:arguments[a]}t.env[i.car.name]=f}if(i.cdr===s){break}a++;i=i.cdr}return A(r.cdr.car,t)}}),defmacro:new h(function(n){if(n.car.car instanceof c){this.env[n.car.car.name]=new h(function(r){var e=new d({},this);var t=n.car.cdr;var i=r;while(true){if(t.car!==s&&i.car!==s){e.env[t.car.name]=i.car}if(t.cdr===s){break}i=i.cdr;t=t.cdr}return A(n.cdr.car,e)})}}),quote:new h(function(n){return new p(n.car)}),quasiquote:new h(function(n){var r=this;function e(n){if(n instanceof l){var t;if(c.is(n.car.car,"unquote-splicing")){t=A(n.car.cdr.car,r);if(!t instanceof l){throw new Error("Value of unquote-splicing need"+" to be pair")}if(n.cdr instanceof l){if(t instanceof l){t.cdr.append(e(n.cdr))}else{t=new l(t,e(n.cdr))}}return t}if(c.is(n.car,"unquote-splicing")){t=A(n.cdr.car,r);if(!t instanceof l){throw new Error("Value of unquote-splicing"+" need to be pair")}return t}if(c.is(n.car,"unquote")){if(n.cdr.cdr!==s){return new l(A(n.cdr.car,r),n.cdr.cdr)}else{return A(n.cdr.car,r)}}var i=n.car;if(i instanceof l){i=e(i)}var a=n.cdr;if(a instanceof l){a=e(a)}return new l(i,a)}return n}return new p(e(n.car))}),clone:function n(r){return r.clone()},append:function n(r,e){return this.get("append!")(r.clone(),e)},"append!":function n(r,e){var t=r;while(true){if(t.cdr===s){t.cdr=e;break}t=t.cdr}return r},list:function n(){return l.fromArray([].slice.call(arguments))},concat:function n(){return[].join.call(arguments,"")},string:function n(r){if(typeof jQuery!=="undefined"&&r instanceof jQuery.fn.init){return"<#jQuery>"}if(r instanceof h){}if(typeof r==="undefined"){return"<#undefined>"}if(typeof r==="function"){return"<#function>"}if(r===s){return"nil"}if(r instanceof Array||r===null){return JSON.stringify(r)}if(r instanceof l){return r.toString()}if((typeof r==="undefined"?"undefined":_typeof(r))==="object"){var e=r.constructor.name;if(e!==""){return"<#"+e+">"}return"<#Object>"}if(typeof r!=="string"){return r.toString()}return r},env:function r(e){e=e||this;var t=Object.keys(e.env);var i;if(t.length){i=l.fromArray(t)}else{i=s}if(e.parent!==n){return this.get("env").call(this,e.parent).append(i)}return i},".":function n(r,e){var t=e instanceof c?e.name:e;var i=r[t];if(typeof i==="function"){return i.bind(r)}return i},read:function n(r){var e=this;if(typeof r==="string"){return o(a(r))}return this.get("stdin").read().then(function(n){return e.get("read").call(e,n)})},print:function n(){var r,e=this;for(var t=arguments.length,i=Array(t),a=0;alist":function n(r){return l.fromArray(r)},"list->array":function n(r){var e=[];var t=r;while(true){if(t instanceof l){e.push(t.car);t=t.cdr}else{break}}return e},filter:function n(r,e){return l.fromArray(this.get("list->array")(e).filter(r))},odd:function n(r){return r%2===1},even:function n(r){return r%2===0},apply:function n(r,e){var t=this.get("list->array")(e);return r.apply(null,t)},map:function n(r,e){var t=this.get("list->array")(e).map(r);if(t.length){return l.fromArray(t)}else{return s}},reduce:function n(r,e){var t=this.get("list->array")(e);return t.reduce(function(n,e){return r(n,e)},s)},"*":function n(){for(var r=arguments.length,e=Array(r),t=0;t":function n(r,e){return r>e},"<":function n(r,e){return r=":function n(r,e){return r>=e},or:new h(function(n){var r=this.get("list->array")(n);var e=this;return new Promise(function(n){var t;(function i(){function a(r){t=r;if(t){n(r)}i()}var f=r.shift();if(typeof f==="undefined"){if(t){n(t)}else{n(false)}}else{var o=A(f,e);if(o instanceof Promise){o.then(a)}else{a(o)}}})()})}),and:new h(function(n){var r=this.get("list->array")(n);var e=this;return new Promise(function(n){var t;(function i(){function a(r){t=r;if(!t){n(false)}i()}var f=r.shift();if(typeof f==="undefined"){if(t){n(t)}else{n(false)}}else{var o=A(f,e);if(o instanceof Promise){o.then(a)}else{a(o)}}})()})}),"++":new h(function(n){var r=this.get(n.car)+1;this.set(n.car,r);return r}),"--":new h(function(n){var r=this.get(n.car)-1;this.set(n.car,r);return r})});function m(n){if(n.length===1){return n[0]}else{var r=[];var e=m(n.slice(1));for(var t=0;t a + b,
+ f2: (a, b) => new Pair(a, new Pair(b, nil))
+ }, global_environment);
+ it('should return value', function() {
+ expect(exec('value', env)).toEqual(rand);
+ });
+ it('should call function', function() {
+ expect(exec('(fun 1 2)', env)).toEqual(3);
+ expect(exec('(fun "foo" "bar")', env)).toEqual("foobar");
+ });
+ it('should set environment', function() {
+ exec('(define x "foobar")', env);
+ expect(exec('x', env)).toEqual("foobar");
+ expect(exec('x')).toEqual(undefined);
+ });
+ it('should create list', function() {
+ expect(exec('(cons 1 (cons 2 (cons 3 nil)))'))
+ .toEqual(Pair.fromArray([1, 2, 3]));
+ });
+ describe('quote', function() {
+ it('should return literal list', function() {
+ expect(exec(`'(1 2 3 (4 5))`)).toEqual(
+ Pair.fromArray([1, 2, 3, [4, 5]])
+ );
+ });
+ it('should return alist', function() {
+ expect(exec(`'((foo . 1)
+ (bar . 2.1)
+ (baz . "string")
+ (quux . /foo./g))`)).toEqual(
+ new Pair(
+ new Pair(
+ new Symbol('foo'),
+ 1
+ ),
+ new Pair(
+ new Pair(
+ new Symbol('bar'),
+ 2.1
+ ),
+ new Pair(
+ new Pair(
+ new Symbol('baz'),
+ "string"
+ ),
+ new Pair(
+ new Pair(
+ new Symbol('quux'),
+ /foo./g
+ ),
+ nil
+ )
+ )
+ )
+ )
+ );
+ });
+ });
+ describe('quasiquote', function() {
+ it('should create list with function call', function() {
+ expect(exec('`(1 2 3 ,(fun 2 2) 5)', env)).toEqual(
+ Pair.fromArray([1, 2, 3, 4, 5])
+ );
+ });
+ it('should create list with value', function() {
+ expect(exec('`(1 2 3 ,value 4)', env)).toEqual(
+ Pair.fromArray([1, 2, 3, rand, 4])
+ );
+ });
+ it('should create single list using uquote-splice', function() {
+ expect(exec('`(1 2 3 ,@(f2 4 5) 6)', env)).toEqual(
+ Pair.fromArray([1, 2, 3, 4, 5, 6])
+ );
+ });
+ it('should create single pair', function() {
+ [
+ '`(1 . 2)',
+ '`(,(car (list 1 2 3)) . 2)',
+ '`(1 . ,(cadr (list 1 2 3)))',
+ '`(,(car (list 1 2 3)) . ,(cadr (list 1 2 3)))'
+ ].forEach((code) => {
+ expect(exec(code)).toEqual(new Pair(1, 2));
+ });
+ });
+ it('should create list from pair syntax', function() {
+ expect(exec('`(,(car (list 1 2 3)) . (1 2 3))')).toEqual(
+ Pair.fromArray([1, 1, 2, 3])
+ );
+ });
+ it('should create alist with values', function() {
+ expect(exec(`\`((1 . ,(car (list 1 2)))
+ (2 . ,(cadr (list 1 "foo"))))`)).toEqual(
+ new Pair(
+ new Pair(1, 1),
+ new Pair(new Pair(2, "foo"), nil))
+ );
+ expect(exec(`\`((,(car (list "foo")) . ,(car (list 1 2)))
+ (2 . ,(cadr (list 1 "foo"))))`))
+ .toEqual(new Pair(
+ new Pair("foo", 1),
+ new Pair(
+ new Pair(2, "foo"),
+ nil
+ )));
+ });
+ it('should process nested backquote', function() {
+ expect(exec('`(1 2 3 ,(cadr `(1 ,(+ "foo" "bar") 3)) 4)')).toEqual(
+ Pair.fromArray([1, 2, 3, "foobar", 4])
+ );
+ });
+ });
+});
+
+
+/*
+
+var code = parse(tokenize(`
+ (print (cons 1 (cons 2 (cons 3 nil))))
+ (print (list 1 2 3 4))
+ (print (car (list 1 2 3)))
+ (print (concat "hello" " " "world"))
+ (print (append (list 1 2 3) (list 10)))
+ (print nil)
+ (define x 10)
+ (print (* x x))
+ (print (/ 1 2))
+ (define l1 (list 1 2 3 4))
+ (define l2 (append l1 (list 5 6)))
+ (print l1)
+ (print l2)
+ (defmacro (foo code) \`(print ,(string (car code))))
+ (foo (name baz))
+ (print \`(,(car (list "a" "b" "c")) 2 3))
+ (print \`(,@(list 1 2 3)))
+ (print \`(1 2 3 ,@(list 4 5) 6))
+ (defmacro (xxx code) \`(list 1 ,(car (cdr code)) 2))
+ (print (xxx ("10" "20")))
+ (if (== 10 20) (print "1 true") (print "1 false"))
+ (if (== 10 10) (print "2 true") (print "2 false"))
+ (print (concat "if " (if (== x 10) "10" "20")))
+`));
+
+(function() {
+ var env = new Environment({}, global_environment);
+ var c = parse(tokenize([
+ "(define x '(1 2 3))",
+ "(print x)",
+ "(print `(1 2 ,@x 4 5))",
+ "(print `(foo bar ,@x 4 5))"
+ ].join(' ')));
+ c.forEach(function(code) {
+ console.log(code.toString());
+ try {
+ evaluate(code, env);
+ } catch (e) {
+ console.error(e.message);
+ }
+ })
+})();
+*/
diff --git a/index.js b/src/lips.js
similarity index 74%
rename from index.js
rename to src/lips.js
index 8fcb9f50..4d7342f0 100644
--- a/index.js
+++ b/src/lips.js
@@ -1,15 +1,20 @@
-/**
- * Lips is Pretty Simple - version 0.1.0
+/**@license
+ * LIPS is Pretty Simple - version {{VER}}
*
* Copyright (c) 2018 Jakub Jankiewicz
* Released under the MIT license
*
+ * build: {{DATE}}
*/
+/*
+ * TODO: Pair.prototype.toObject = alist to Object
+ */
+"use strict";
/* global define, module, setTimeout, jQuery */
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
- define([], function () {
+ define([], function() {
return (root.lips = factory());
});
} else if (typeof module === 'object' && module.exports) {
@@ -25,7 +30,6 @@
// ----------------------------------------------------------------------
function parse_argument(arg) {
function parse_string(string) {
- console.log(JSON.stringify(string));
// remove quotes if before are even number of slashes
// we don't remove slases becuase they are handled by JSON.parse
//string = string.replace(/([^\\])['"]$/, '$1');
@@ -49,7 +53,7 @@
return parseInt(arg, 10);
} else if (arg.match(float_re)) {
return parseFloat(arg);
- } else if (arg == 'nil') {
+ } else if (arg === 'nil') {
return nil;
} else {
return new Symbol(arg);
@@ -57,13 +61,20 @@
}
// ----------------------------------------------------------------------
/* eslint-disable */
- var tokens_re = /("[^"\\]*(?:\\[\S\s][^"\\]*)*"|\/[^\/\\]*(?:\\[\S\s][^\/\\]*)*\/[gimy]*(?=\s|\(|\)|$)|\(|\)|'|\.|,@|,|`|[^(\s)]+)/gi;
+ var tokens_re = /("[^"\\]*(?:\\[\S\s][^"\\]*)*"|\/[^\/\\]*(?:\\[\S\s][^\/\\]*)*\/[gimy]*(?=\s|\(|\)|$)|;.*|\(|\)|'|\.|,@|,|`|[^(\s)]+)/gi;
/* eslint-enable */
// ----------------------------------------------------------------------
function tokenize(str) {
- return str.split(tokens_re).map(function(token) {
- return token.trim();
- }).filter(Boolean);
+ return str.split('\n').map(function(line) {
+ return line.split(tokens_re).map(function(token) {
+ if (token.match(/^;/)) {
+ return null;
+ }
+ return token.trim();
+ }).filter(Boolean);
+ }).reduce(function(arr, tokens) {
+ return arr.concat(tokens);
+ }, []);
}
// ----------------------------------------------------------------------
var specials = {
@@ -79,15 +90,14 @@
function parse(tokens) {
var stack = [];
var result = [];
- var list;
var special = null;
var special_tokens = Object.keys(specials);
var special_forms = special_tokens.map(s => specials[s].name);
var parents = 0;
var first_value = false;
- tokens.forEach(function(token, i) {
- var top = stack[stack.length-1];
- if (special_tokens.indexOf(token) != -1) {
+ tokens.forEach(function(token) {
+ var top = stack[stack.length - 1];
+ if (special_tokens.indexOf(token) !== -1) {
special = token;
} else if (token === '(') {
first_value = true;
@@ -98,30 +108,43 @@
}
stack.push([]);
} else if (token === '.' && !first_value) {
- stack[stack.length-1] = Pair.fromArray(top);
+ stack[stack.length - 1] = Pair.fromArray(top);
} else if (token === ')') {
parents--;
if (!stack.length) {
- throw new Error('Unbalanced parenthesis 1');
+ throw new Error('Unbalanced parenthesis');
}
if (stack.length === 1) {
result.push(stack.pop());
} else if (stack.length > 1) {
var list = stack.pop();
- top = stack[stack.length-1];
- top.push(list);
+ top = stack[stack.length - 1];
+ if (top instanceof Array) {
+ top.push(list);
+ } else if (top instanceof Pair) {
+ top.append(Pair.fromArray(list));
+ }
if (top instanceof Array && top[0] instanceof Symbol &&
special_forms.includes(top[0].name) &&
stack.length > 1) {
stack.pop();
- if (stack[stack.length-1].length == 0) {
- stack[stack.length-1] = top;
+ if (stack[stack.length - 1].length === 0) {
+ stack[stack.length - 1] = top;
+ } else if (stack[stack.length - 1] instanceof Pair) {
+ if (stack[stack.length - 1].cdr instanceof Pair) {
+ stack[stack.length - 1] = new Pair(
+ stack[stack.length - 1],
+ Pair.fromArray(top)
+ );
+ } else {
+ stack[stack.length - 1].cdr = Pair.fromArray(top);
+ }
} else {
- stack[stack.length-1].push(top);
+ stack[stack.length - 1].push(top);
}
}
}
- if (parents == 0 && stack.length) {
+ if (parents === 0 && stack.length) {
result.push(stack.pop());
}
} else {
@@ -133,7 +156,7 @@
}
if (top instanceof Pair) {
var node = top;
- while(true) {
+ while (true) {
if (node.cdr === nil) {
node.cdr = value;
break;
@@ -149,7 +172,7 @@
}
});
if (stack.length) {
- throw new Error('Unbalanced parenthesis 2');
+ throw new Error('Unbalanced parenthesis');
}
return result.map((arg) => {
if (arg instanceof Array) {
@@ -167,19 +190,39 @@
Symbol.is = function(symbol, name) {
return symbol instanceof Symbol &&
typeof name === 'string' &&
- symbol.name == name;
+ symbol.name === name;
};
Symbol.prototype.toJSON = Symbol.prototype.toString = function() {
//return '<#symbol \'' + this.name + '\'>';
return this.name;
};
// ----------------------------------------------------------------------
+ // :: Nil constructor with only once instance
+ // ----------------------------------------------------------------------
+ function Nil() {}
+ Nil.prototype.toString = function() {
+ return 'nil';
+ };
+ var nil = new Nil();
+ // ----------------------------------------------------------------------
// :: Pair constructor
// ----------------------------------------------------------------------
function Pair(car, cdr) {
this.car = car;
this.cdr = cdr;
}
+ Pair.prototype.length = function() {
+ var len = 0;
+ var node = this;
+ while (true) {
+ if (node === nil) {
+ break;
+ }
+ len++;
+ node = node.cdr;
+ }
+ return len;
+ };
Pair.prototype.clone = function() {
var cdr;
if (this.cdr === nil) {
@@ -190,7 +233,7 @@
return new Pair(this.car, cdr);
};
Pair.prototype.toArray = function() {
- if (this.cdr === nil && this.car == nil) {
+ if (this.cdr === nil && this.car === nil) {
return [];
}
var result = [];
@@ -208,7 +251,10 @@
if (array instanceof Pair) {
return array;
}
- if (array.length == 0) {
+ if (array.length && !array instanceof Array) {
+ array = [...array];
+ }
+ if (array.length === 0) {
return new Pair(nil, nil);
} else {
var car;
@@ -224,6 +270,63 @@
}
}
};
+ Pair.prototype.toObject = function() {
+ var node = this;
+ var result = {};
+ while (true) {
+ if (node instanceof Pair && node.car instanceof Pair) {
+ var pair = node.car;
+ var name = pair.car;
+ if (name instanceof Symbol) {
+ name = name.name;
+ }
+ result[name] = pair.cdr;
+ node = node.cdr;
+ } else {
+ break;
+ }
+ }
+ return result;
+ };
+ Pair.fromPairs = function(array) {
+ return array.reduce((list, pair) => {
+ return new Pair(
+ new Pair(
+ new Symbol(pair[0]),
+ pair[1]
+ ),
+ list
+ );
+ }, nil);
+ };
+ Pair.fromObject = function(obj) {
+ var array = Object.keys(obj).map((key) => [key, obj[key]]);
+ return Pair.fromPairs(array);
+ };
+ Pair.prototype.reduce = function(fn) {
+ var node = this;
+ var result = nil;
+ while (true) {
+ if (node !== nil) {
+ result = fn(result, node.car);
+ node = node.cdr;
+ } else {
+ break;
+ }
+ }
+ return result;
+ };
+ Pair.prototype.reverse = function() {
+ var node = this;
+ var prev = nil;
+ while (node !== nil) {
+ var next = node.cdr;
+ node.cdr = prev;
+ prev = node;
+ node = next;
+ }
+ return prev;
+ };
Pair.prototype.transform = function(fn) {
var visited = [];
function recur(pair) {
@@ -250,7 +353,7 @@
};
Pair.prototype.toString = function() {
var arr = ['('];
- if (typeof this.car == 'string') {
+ if (typeof this.car === 'string') {
arr.push(JSON.stringify(this.car));
} else if (typeof this.car !== 'undefined') {
arr.push(this.car);
@@ -259,14 +362,21 @@
arr.push(' ');
arr.push(this.cdr.toString().replace(/^\(|\)$/g, ''));
} else if (typeof this.cdr !== 'undefined' && this.cdr !== nil) {
- arr = arr.concat([' . ', this.cdr]);
+ if (typeof this.cdr === 'string') {
+ arr = arr.concat([' . ', JSON.stringify(this.cdr)]);
+ } else {
+ arr = arr.concat([' . ', this.cdr]);
+ }
}
arr.push(')');
return arr.join('');
};
Pair.prototype.append = function(pair) {
+ if (pair instanceof Array) {
+ return this.append(Pair.fromArray(pair));
+ }
var p = this;
- while(true) {
+ while (true) {
if (p instanceof Pair && p.cdr !== nil) {
p = p.cdr;
} else {
@@ -277,13 +387,6 @@
return this;
};
- // ----------------------------------------------------------------------
- // :: Nil constructor with only once instance
- // ----------------------------------------------------------------------
- function Nil() {}
- Nil.prototype.toString = function() { return 'nil'; };
- var nil = new Nil();
-
// ----------------------------------------------------------------------
// :: Macro constructor
// ----------------------------------------------------------------------
@@ -306,7 +409,7 @@
if (typeof this.env[symbol.name] !== 'undefined') {
return this.env[symbol.name];
}
- } else if (typeof symbol == 'string') {
+ } else if (typeof symbol === 'string') {
if (typeof this.env[symbol] !== 'undefined') {
return this.env[symbol];
}
@@ -318,7 +421,7 @@
if (typeof window[symbol.name] !== 'undefined') {
return window[symbol.name];
}
- } else if (typeof symbol == 'string') {
+ } else if (typeof symbol === 'string') {
if (typeof window[symbol] !== 'undefined') {
return window[symbol];
}
@@ -347,6 +450,32 @@
return new Quote(evaluate(output, env));
});
}
+ var gensym = (function() {
+ var count = 0;
+ return function() {
+ count++;
+ return new Symbol('#' + count);
+ };
+ })();
+ function request(url, method = 'GET', headers = {}, data = null) {
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+ Object.keys(headers).forEach(name => {
+ xhr.setRequestHeader(name, headers[name]);
+ });
+ return new Promise((resolve) => {
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4 && xhr.status === 200) {
+ resolve(xhr.responseText);
+ }
+ };
+ if (data !== null) {
+ xhr.send(data);
+ } else {
+ xhr.send();
+ }
+ });
+ }
var global_env = new Environment({
nil: nil,
window: window,
@@ -358,7 +487,7 @@
}
},
stdin: {
- read: function(arg) {
+ read: function() {
return new Promise((resolve) => {
resolve(prompt(''));
});
@@ -370,14 +499,17 @@
car: function(list) {
if (list instanceof Pair) {
return list.car;
+ } else {
+ throw new Error('argument to car need to be a list');
}
},
cdr: function(list) {
if (list instanceof Pair) {
return list.cdr;
+ } else {
+ throw new Error('argument to cdr need to be a list');
}
},
-
'set-car': function(slot, value) {
slot.car = value;
},
@@ -387,7 +519,7 @@
assoc: function(list, key) {
var node = list;
var name = key instanceof Symbol ? key.name : key;
- while(true) {
+ while (true) {
var car = node.car.car;
if (car instanceof Symbol &&
car.name === name || car.name === name) {
@@ -397,24 +529,62 @@
}
}
},
+ gensym: gensym,
+ load: function(file) {
+ request(file).then((code) => {
+ this.get('eval')(this.get('read')(code));
+ });
+ },
+ 'while': new Macro(function(code) {
+ var self = this;
+ var begin = new Pair(
+ new Symbol('begin'),
+ code.cdr
+ );
+ return new Promise((resolve) => {
+ var result;
+ (function loop() {
+ function next(cond) {
+ if (cond) {
+ var value = evaluate(begin, self);
+ if (value instanceof Promise) {
+ value.then((value) => {
+ result = value;
+ loop();
+ });
+ } else {
+ result = value;
+ loop();
+ }
+ } else {
+ resolve(result);
+ }
+ }
+ var cond = evaluate(code.car, self);
+ if (cond instanceof Promise) {
+ cond.then(next);
+ } else {
+ next(cond);
+ }
+ })();
+ });
+ }),
'if': new Macro(function(code) {
var resolve = (cond) => {
if (cond) {
var true_value = evaluate(code.cdr.car, this);
- if (typeof true_value === 'undefiend') {
+ if (typeof true_value === 'undefined') {
return;
}
return true_value;
- } else {
- if (code.cdr.cdr.car instanceof Pair) {
- var false_value = evaluate(code.cdr.cdr.car, this);
- if (typeof false_value === 'udefined') {
- return false;
- }
- return false_value;
- } else {
+ } else if (code.cdr.cdr.car instanceof Pair) {
+ var false_value = evaluate(code.cdr.cdr.car, this);
+ if (typeof false_value === 'undefined') {
return false;
}
+ return false_value;
+ } else {
+ return false;
}
};
var cond = evaluate(code.car, this);
@@ -465,6 +635,9 @@
this.env[code.car.name] = value;
}
}),
+ set: function(obj, key, value) {
+ obj[key] = value;
+ },
'eval': function(code) {
if (code instanceof Pair) {
return evaluate(code, this);
@@ -481,7 +654,6 @@
return (...args) => {
var env = new Environment({}, this);
var name = code.car;
- var arg = code;
var i = 0;
var value;
while (true) {
@@ -504,13 +676,12 @@
}),
defmacro: new Macro(function(macro) {
if (macro.car.car instanceof Symbol) {
- var this_env = this;
this.env[macro.car.car.name] = new Macro(function(code) {
var env = new Environment({}, this);
var name = macro.car.cdr;
var arg = code;
while (true) {
- if (name.car !== nil && arg.car != nil) {
+ if (name.car !== nil && arg.car !== nil) {
env.env[name.car.name] = arg.car;
}
if (name.cdr === nil) {
@@ -524,30 +695,15 @@
}
}),
quote: new Macro(function(arg) {
- var env = this;
- function recur(pair) {
- if (pair instanceof Pair) {
- var car = pair.car;
- if (car instanceof Pair) {
- car = recur(car);
- }
- var cdr = pair.cdr;
- if (cdr instanceof Pair) {
- cdr = recur(cdr);
- }
- return new Pair(car, cdr);
- }
- return pair;
- }
return new Quote(arg.car);
}),
quasiquote: new Macro(function(arg) {
- var env = this;
+ var self = this;
function recur(pair) {
if (pair instanceof Pair) {
var eval_pair;
if (Symbol.is(pair.car.car, 'unquote-splicing')) {
- eval_pair = evaluate(pair.car.cdr.car, env);
+ eval_pair = evaluate(pair.car.cdr.car, self);
if (!eval_pair instanceof Pair) {
throw new Error('Value of unquote-splicing need' +
' to be pair');
@@ -565,7 +721,7 @@
return eval_pair;
}
if (Symbol.is(pair.car, 'unquote-splicing')) {
- eval_pair = evaluate(pair.cdr.car, env);
+ eval_pair = evaluate(pair.cdr.car, self);
if (!eval_pair instanceof Pair) {
throw new Error('Value of unquote-splicing' +
' need to be pair');
@@ -573,7 +729,14 @@
return eval_pair;
}
if (Symbol.is(pair.car, 'unquote')) {
- return evaluate(pair.cdr.car, env);
+ if (pair.cdr.cdr !== nil) {
+ return new Pair(
+ evaluate(pair.cdr.car, self),
+ pair.cdr.cdr
+ );
+ } else {
+ return evaluate(pair.cdr.car, self);
+ }
}
var car = pair.car;
if (car instanceof Pair) {
@@ -596,7 +759,6 @@
return this.get('append!')(list.clone(), item);
},
'append!': function(list, item) {
- var parent;
var node = list;
while (true) {
if (node.cdr === nil) {
@@ -618,12 +780,31 @@
obj instanceof jQuery.fn.init) {
return '<#jQuery>';
}
- if (typeof obj == 'undefined') {
+ if (obj instanceof Macro) {
+ //return '<#Macro>';
+ }
+ if (typeof obj === 'undefined') {
return '<#undefined>';
}
- if (typeof obj == 'function') {
+ if (typeof obj === 'function') {
return '<#function>';
}
+ if (obj === nil) {
+ return 'nil';
+ }
+ if (obj instanceof Array || obj === null) {
+ return JSON.stringify(obj);
+ }
+ if (obj instanceof Pair) {
+ return obj.toString();
+ }
+ if (typeof obj === 'object') {
+ var name = obj.constructor.name;
+ if (name !== '') {
+ return '<#' + name + '>';
+ }
+ return '<#Object>';
+ }
if (typeof obj !== 'string') {
return obj.toString();
}
@@ -645,7 +826,7 @@
},
'.': function(obj, arg) {
var name = arg instanceof Symbol ? arg.name : arg;
- var value = obj[arg];
+ var value = obj[name];
if (typeof value === 'function') {
return value.bind(obj);
}
@@ -701,24 +882,30 @@
return nil;
}
},
+ reduce: function(fn, list) {
+ var arr = this.get('list->array')(list);
+ return arr.reduce((list, item) => {
+ return fn(list, item);
+ }, nil);
+ },
// math functions
- '*': function() {
- return [].reduce.call(arguments, function(a, b) {
+ '*': function(...args) {
+ return args.reduce(function(a, b) {
return a * b;
- }, 1);
+ });
},
- '+': function() {
- return [].reduce.call(arguments, function(a, b) {
+ '+': function(...args) {
+ return args.reduce(function(a, b) {
return a + b;
- }, 0);
+ });
},
- '-': function() {
- return [].reduce.call(arguments, function(a, b) {
+ '-': function(...args) {
+ return args.reduce(function(a, b) {
return a - b;
});
},
- '/': function() {
- return [].reduce.call(arguments, function(a, b) {
+ '/': function(...args) {
+ return args.reduce(function(a, b) {
return a / b;
});
},
@@ -727,7 +914,7 @@
},
// Booleans
"==": function(a, b) {
- return a == b;
+ return a === b;
},
'>': function(a, b) {
return a > b;
@@ -743,7 +930,7 @@
},
or: new Macro(function(code) {
var args = this.get('list->array')(code);
- var env = this;
+ var self = this;
return new Promise(function(resolve) {
var result;
(function loop() {
@@ -762,7 +949,7 @@
resolve(false);
}
} else {
- var value = evaluate(arg, env);
+ var value = evaluate(arg, self);
if (value instanceof Promise) {
value.then(next);
} else {
@@ -774,7 +961,7 @@
}),
and: new Macro(function(code) {
var args = this.get('list->array')(code);
- var env = this;
+ var self = this;
return new Promise(function(resolve) {
var result;
(function loop() {
@@ -793,7 +980,7 @@
resolve(false);
}
} else {
- var value = evaluate(arg, env);
+ var value = evaluate(arg, self);
if (value instanceof Promise) {
value.then(next);
} else {
@@ -802,13 +989,23 @@
}
})();
});
+ }),
+ '++': new Macro(function(code) {
+ var value = this.get(code.car) + 1;
+ this.set(code.car, value);
+ return value;
+ }),
+ '--': new Macro(function(code) {
+ var value = this.get(code.car) - 1;
+ this.set(code.car, value);
+ return value;
})
});
// ----------------------------------------------------------------------
// source: https://stackoverflow.com/a/4331218/387194
function allPossibleCases(arr) {
- if (arr.length == 1) {
+ if (arr.length === 1) {
return arr[0];
} else {
var result = [];
@@ -840,11 +1037,10 @@
combinations(['d', 'a'], 2, 5).forEach((spec) => {
var chars = spec.split('').reverse();
global_env.set('c' + spec + 'r', function(arg) {
- var result = arg;
return chars.reduce(function(list, type) {
if (type === 'a') {
return list.car;
- } else if (type === 'd') {
+ } else {
return list.cdr;
}
}, arg);
@@ -916,9 +1112,9 @@
function balanced(code) {
var re = /[()]/;
var parenthesis = tokenize(code).filter((token) => token.match(re));
- var open = parenthesis.filter(p => p == ')');
- var close = parenthesis.filter(p => p == '(');
- return open.length == close.length;
+ var open = parenthesis.filter(p => p === ')');
+ var close = parenthesis.filter(p => p === '(');
+ return open.length === close.length;
}
// --------------------------------------
Pair.unDry = function(value) {
@@ -927,8 +1123,8 @@
Pair.prototype.toDry = function() {
return {
value: {
- car : this.car,
- cdr : this.cdr
+ car: this.car,
+ cdr: this.cdr
}
};
};
@@ -951,6 +1147,7 @@
return new Symbol(value.name);
};
return {
+ version: '{{VER}}',
parse: parse,
tokenize: tokenize,
evaluate: evaluate,
diff --git a/templates/Makefile b/templates/Makefile
new file mode 100644
index 00000000..d1ca57da
--- /dev/null
+++ b/templates/Makefile
@@ -0,0 +1,58 @@
+.PHONY: publish test coveralls lint
+
+VERSION={{VERSION}}
+BRANCH=`git branch | grep '^*' | sed 's/* //'`
+DATE=`date -uR`
+SPEC_CHECKSUM=`md5sum spec/lips.spec.js | cut -d' ' -f 1`
+COMMIT=`git log -n 1 | grep commit | sed 's/commit //'`
+
+GIT=git
+SED=sed
+RM=rm
+TEST=test
+CAT=cat
+NPM=npm
+ESLINT=./node_modules/.bin/eslint
+COVERALLS=./node_modules/.bin/coveralls
+JEST=./node_modules/.bin/jest
+UGLIFY=./node_modules/.bin/uglifyjs
+BABEL=./node_modules/.bin/babel
+
+
+ALL: Makefile .$(VERSION) dist/lips.js dist/lips.min.js README.md package.json
+
+dist/lips.js: src/lips.js .$(VERSION)
+ $(GIT) branch | grep '* devel' > /dev/null && $(SED) -e "s/{{VER}}/DEV/g" -e "s/{{DATE}}/$(DATE)/g" src/lips.js > dist/lips.tmp.js || $(SED) -e "s/{{VER}}/$(VERSION)/g" -e "s/{{DATE}}/$(DATE)/g" src/lips.js > dist/lips.tmp.js
+ $(BABEL) dist/lips.tmp.js > dist/lips.js
+ $(RM) dist/lips.tmp.js
+
+dist/lips.min.js: dist/lips.js .$(VERSION)
+ $(UGLIFY) -o dist/lips.min.js --comments --mangle -- dist/lips.js
+
+Makefile: templates/Makefile
+ $(SED) -e "s/{{VER""SION}}/"$(VERSION)"/" templates/Makefile > Makefile
+
+package.json: templates/package.json .$(VERSION)
+ $(SED) -e "s/{{VER}}/"$(VERSION)"/" templates/package.json > package.json || true
+
+README.md: templates/README.md
+ $(GIT) branch | grep '* devel' > /dev/null && $(SED) -e "s/{{VER}}/DEV/g" -e \
+ "s/{{BRANCH}}/$(BRANCH)/g" -e "s/{{CHECKSUM}}/$(SPEC_CHECKSUM)/g" \
+ -e "s/{{COMMIT}}/$(COMMIT)/g" < templates/README.md > README.md || \
+ $(SED) -e "s/{{VER}}/$(VERSION)/g" -e "s/{{BRANCH}}/$(BRANCH)/g" -e \
+ "s/{{CHECKSUM}}/$(SPEC_CHECKSUM)/g" -e "s/{{COMMIT}}/$(COMMIT)/g" < templates/README.md > README.md
+
+.$(VERSION): Makefile
+ touch .$(VERSION)
+
+publish:
+ $(NPM) publish --access=public
+
+test:
+ $(JEST)
+
+coveralls:
+ $(CAT) ./coverage/lcov.info | $(COVERALLS)
+
+lint:
+ $(ESLINT) src/lips.js spec/lips.spec.js
diff --git a/templates/README.md b/templates/README.md
new file mode 100644
index 00000000..3f7300a5
--- /dev/null
+++ b/templates/README.md
@@ -0,0 +1,259 @@
+## LIPS is Pretty Simple
+
+[![npm](https://img.shields.io/badge/npm-{{VER}}-blue.svg)](https://www.npmjs.com/package/@jcubic/lips)
+[![travis](https://travis-ci.org/jcubic/jquery.terminal.svg?branch={{BRANCH}}&{{COMMIT}})](https://travis-ci.org/jcubic/jquery.terminal)
+[![Coverage Status](https://coveralls.io/repos/github/jcubic/lips/badge.svg?branch={{BRANCH}}&{{CHECKSUM}})](https://coveralls.io/github/jcubic/lips?branch={{BRANCH}})
+
+
+
+LIPS is very simple Lisp, similar to Scheme writen in JavaScript.
+
+[Demo](https://codepen.io/jcubic/full/LQBaaV/)
+
+## Installation
+
+use npm
+
+```
+npm install @jcubic/lips
+```
+
+then include the file in script tag, You can grab the version from unpkg.com
+
+```
+https://unpkg.com/@jcubic/lips
+```
+
+or from rawgit
+
+```
+https://cdn.rawgit.com/jcubic/lips/master/index.js
+```
+
+## Usage
+
+```javascript
+var {parse, tokenize, evaluate} = require('@jcubic/lips');
+
+parse(tokenize(string)).forEach(function(code) {
+ evaluate(code);
+});
+```
+
+`evaluate` function also accept second argument, which is Environment.
+By default it's `lips.global_environment`. You can use it if you want to
+have separated instances of the interpreter.
+
+You can create new environment using:
+
+```javascript
+var env = new Environment({}, lips.global_environment);
+```
+
+First argument is an object with functions, macros and varibles (see Extending LIPS at the end).
+Second argument is parent environment, you need to use global environment (or other that extend global)
+otherwise you will not have any functions.
+
+## What's in
+
+### variables and functions
+
+```scheme
+(define x 10)
+(define square (lambda (x) (* x x)))
+(define (square x) (* x x))
+```
+
+### List operications
+
+```scheme
+(cons 1 2)
+(cons 1 (cons 2 nil))
+(list 1 2 3 4)
+'(1 2 3 4)
+
+(let ((lst '(1 2 (3 4 5 6))))
+ (print (car lst))
+ (print (cadaddr lst)))
+```
+
+all functions that match this regex `c[ad]{2,5}r` are defined.
+
+### ALists
+
+```scheme
+(let ((l '((foo . "lorem") (bar "ipsum"))))
+ (set-cdr (assoc l 'foo) "hello")
+ (set-cdr (assoc l 'bar) "world")
+ (print l))
+```
+
+### Flow constructs
+
+```scheme
+(let ((x 5))
+ (while (> (-- x) 0) (print x)))
+```
+
+same as in JS
+
+```scheme
+(if (== "10" 10)
+ (print "equal"))
+
+(let ((x 10))
+ (if (and (> x 1) (< x 20))
+ (begin
+ (print "this is x > 1")
+ (print "and x < 20"))))
+```
+
+### eval
+
+```scheme
+(eval (read "(print \"hello\")"))
+```
+
+### Lisp Macros
+
+```scheme
+(defmacro (foo x) `(1 2 ,@(car x) 3 4))
+```
+
+### Async code
+
+```scheme
+(eval (read))
+```
+
+then type S-Expression like `(print 10)`. If function return Promise
+the execution is paused and restored when Promise is resolved
+
+
+### Access JavaScript functions and objects
+
+```scheme
+((. window "alert") "hello")
+((. console "log") "hello")
+```
+
+If object is not found in environment, then window object is tested for
+presense of the element.
+
+You can execute jQuery functions
+
+```scheme
+(let* ((term ($ ".terminal")))
+ ((. term "css") "background" "red"))
+```
+
+function `$` is available because it's in window object.
+
+or operate on strings
+
+```
+((. "foo bar baz" "replace") /^[a-z]+/g "baz")
+
+(let ((match (. "foo bar baz" "match")))
+ (array->list (match /([a-z]+)/g)))
+```
+
+### Mapping, filtering and reducing
+
+```scheme
+(map car (list
+ (cons "a" 2)
+ (cons "b" 3)))
+
+(filter odd (list 1 2 3 4 5))
+
+(filter (lambda (x)
+ (== (% x 2) 0))
+ (list 1 2 3 4 5))
+
+(define (reverse list)
+ (reduce (lambda (list x) (cons x list)) list))
+
+(reverse '(1 2 3 4))
+```
+
+### Working with arrays
+
+You can modify array with `set` function and to get the value of the array you can use `.` dot function.
+
+```scheme
+(let ((arr (list->array '(1 2 3 4))))
+ (set arr 0 2)
+ (print (array->list arr)))
+
+(let* ((div ((. document "querySelectorAll") ".terminal-output > div"))
+ (len (. div "length"))
+ (i 0))
+ (while (< i len)
+ (print (. (. div i) "innerHTML"))
+ (++ i)))
+```
+
+this equivalent of JavaScript code:
+
+```javascript
+var div = document.querySelectorAll(".terminal div");
+var len = div.length;
+var i = 0;
+while (i < len) {
+ console.log(div[i].innerHTML);
+ ++i;
+}
+```
+
+### Math and boolean operators
+
+`< > => <= ++ -- + - * / % and or`
+
+## Extending LIPS
+
+to create new function from JavaScript you can use:
+
+```javascript
+env.set('replace', function(re, sub, string) {
+ return string.replace(re, sub);
+});
+```
+
+then you can use it in LIPS:
+
+```
+(replace /(foo|bar)/g "hello" "foo bar baz")
+```
+
+To define a macro in javascript you can use Macro constructor that accept
+single function argument, that should return lisp code (instance of Pair)
+
+```javascript
+
+var {Macro, Pair, Symbol, nil} = lips;
+
+env.set('quote-car', new Macro(function(code) {
+ return Pair.fromArray([new Symbol('quote'), code.car.car]);
+}));
+```
+
+and you can execute this macro in LIPS:
+
+```scheme
+(quote-car (foo bar baz))
+```
+
+it will return first symbol and not execute it as function foo.
+
+if you want to create macro like quasiquote, the returned code need to be wrapped with
+Quote instance.
+
+When creating macros in JavaScript you can use helper `Pair.fromArray()`
+and `code.toArray()`.
+
+## License
+
+Released under [MIT](http://opensource.org/licenses/MIT) license
+
+Copyright (c) 2018 [Jakub Jankiewicz](http://jcubic.pl/jakub-jankiewicz)
diff --git a/templates/package.json b/templates/package.json
new file mode 100644
index 00000000..a5e0c2b2
--- /dev/null
+++ b/templates/package.json
@@ -0,0 +1,195 @@
+{
+ "name": "@jcubic/lips",
+ "version": "{{VER}}",
+ "description": "Simple Scheme Like Lisp in JavaScript",
+ "main": "dist/lips.js",
+ "scripts": {},
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/jcubic/lips.git"
+ },
+ "keywords": [
+ "lisp",
+ "scheme",
+ "language",
+ "interpreter"
+ ],
+ "author": "Jakub Jankiewicz (http://jcubic.pl/jakub-jankiewicz/)",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/jcubic/lips/issues"
+ },
+ "homepage": "https://github.com/jcubic/lips#readme",
+ "eslintConfig": {
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "script",
+ "ecmaFeatures": {}
+ },
+ "env": {
+ "browser": true,
+ "jest": true,
+ "node": true
+ },
+ "globals": {
+ "Promise": true
+ },
+ "rules": {
+ "eqeqeq": "error",
+ "curly": "error",
+ "no-unreachable": "error",
+ "valid-typeof": "error",
+ "no-unexpected-multiline": "error",
+ "no-regex-spaces": "error",
+ "no-irregular-whitespace": "error",
+ "no-invalid-regexp": "error",
+ "no-inner-declarations": "error",
+ "no-func-assign": "error",
+ "no-extra-semi": "error",
+ "no-extra-boolean-cast": "error",
+ "no-debugger": "error",
+ "no-dupe-args": "error",
+ "no-dupe-keys": "error",
+ "no-duplicate-case": "error",
+ "no-empty-character-class": "error",
+ "no-ex-assign": "error",
+ "array-callback-return": "error",
+ "no-case-declarations": "error",
+ "guard-for-in": "error",
+ "no-caller": "error",
+ "no-eval": "error",
+ "no-extend-native": "error",
+ "no-extra-bind": "error",
+ "no-fallthrough": "error",
+ "no-global-assign": "error",
+ "no-implicit-globals": "error",
+ "no-implied-eval": "error",
+ "no-multi-spaces": "error",
+ "no-new-wrappers": "error",
+ "no-redeclare": "error",
+ "no-self-assign": "error",
+ "no-return-assign": "error",
+ "no-self-compare": "error",
+ "no-throw-literal": "error",
+ "no-unused-labels": "error",
+ "no-useless-call": "error",
+ "no-useless-escape": "error",
+ "no-void": "error",
+ "no-with": "error",
+ "radix": "error",
+ "wrap-iife": [
+ "error",
+ "inside"
+ ],
+ "yoda": [
+ "error",
+ "never"
+ ],
+ "no-catch-shadow": "error",
+ "no-delete-var": "error",
+ "no-label-var": "error",
+ "no-undef-init": "error",
+ "no-unused-vars": "error",
+ "no-undef": "error",
+ "comma-style": [
+ "error",
+ "last"
+ ],
+ "comma-dangle": [
+ "error",
+ "never"
+ ],
+ "comma-spacing": [
+ "error",
+ {
+ "before": false,
+ "after": true
+ }
+ ],
+ "computed-property-spacing": [
+ "error",
+ "never"
+ ],
+ "eol-last": [
+ "error",
+ "always"
+ ],
+ "func-call-spacing": [
+ "error",
+ "never"
+ ],
+ "key-spacing": [
+ "error",
+ {
+ "beforeColon": false,
+ "afterColon": true,
+ "mode": "strict"
+ }
+ ],
+ "max-len": [
+ "error",
+ 85
+ ],
+ "max-statements-per-line": "error",
+ "new-parens": "error",
+ "no-array-constructor": "error",
+ "no-lonely-if": "error",
+ "no-mixed-spaces-and-tabs": "error",
+ "no-multiple-empty-lines": "error",
+ "no-new-object": "error",
+ "no-tabs": "error",
+ "no-trailing-spaces": "error",
+ "no-whitespace-before-property": "error",
+ "object-curly-spacing": [
+ "error",
+ "never"
+ ],
+ "space-before-blocks": "error",
+ "keyword-spacing": [
+ "error",
+ {
+ "before": true,
+ "after": true
+ }
+ ],
+ "space-in-parens": [
+ "error",
+ "never"
+ ],
+ "space-infix-ops": "error",
+ "space-before-function-paren": [
+ "error",
+ "never"
+ ],
+ "complexity": [
+ "error",
+ {
+ "max": 26
+ }
+ ],
+ "indent": [
+ "error",
+ 4,
+ {
+ "SwitchCase": 1
+ }
+ ],
+ "linebreak-style": [
+ "error",
+ "unix"
+ ],
+ "semi": [
+ "error",
+ "always"
+ ]
+ }
+ },
+ "devDependencies": {
+ "babel-cli": "^6.26.0",
+ "babel-preset-env": "^1.6.1",
+ "coveralls": "^3.0.0",
+ "eslint": "^4.18.1",
+ "jest": "^22.4.2",
+ "uglify-js": "^3.3.12"
+ }
+}
diff --git a/version b/version
new file mode 100755
index 00000000..c0677b4b
--- /dev/null
+++ b/version
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# Display current version or update version if used with version as argument
+
+VERSION=`grep VERSION= Makefile | sed -e 's/VERSION=\(.*\)/\1/'`
+if [ -z "$1" ]; then
+ echo $VERSION
+elif [ "$1" != "$VERSION" ]; then
+ sed -e "s/{{VERSION}}/"$1"/" templates/Makefile > Makefile
+fi