*/experiments
make/mal.mk
-miniMAL/node_modules
-js/node_modules
+*/node_modules
js/mal.js
js/mal_web.js
-coffee/node_modules
bash/mal.sh
c/*.o
*.pyc
.sbt
groovy/*.class
.crystal
+es6/build
# Settings
#
-IMPLS = bash c clojure coffee cpp crystal cs erlang factor forth fsharp go groovy \
+IMPLS = bash c clojure coffee cpp crystal cs erlang es6 factor forth fsharp go groovy \
haskell java julia js lua make mal ocaml matlab miniMAL nim \
perl php ps python r racket rpython ruby rust scala swift vb guile
crystal_STEP_TO_PROG = crystal/$($(1))
cs_STEP_TO_PROG = cs/$($(1)).exe
erlang_STEP_TO_PROG = erlang/$($(1))
+es6_STEP_TO_PROG = es6/build/$($(1)).js
factor_STEP_TO_PROG = factor/src/$($(1))/$($(1)).factor
forth_STEP_TO_PROG = forth/$($(1)).fs
fsharp_STEP_TO_PROG = fsharp/$($(1)).exe
crystal_RUNSTEP = ../$(2) $(3)
cs_RUNSTEP = mono ../$(2) --raw $(3)
erlang_RUNSTEP = ../$(2) $(3)
+es6_RUNSTEP = node ../$(2) $(3)
factor_RUNSTEP = factor ../$(2) $(3)
forth_RUNSTEP = gforth ../$(2) $(3)
fsharp_RUNSTEP = mono ../$(2) --raw $(3)
--- /dev/null
+
+SOURCES = step0_repl.js step1_read_print.js
+
+all: $(foreach s,$(SOURCES),build/$(s))
+
+build/%.js: %.js
+ babel --source-maps true $< --out-file $@
+
+build/step0_repl.js: step0_repl.js build/node_readline.js
+
+build/step1_read_print.js: step1_read_print.js build/node_readline.js build/types.js build/reader.js build/printer.js
+
+clean:
+ rm -f build/*
--- /dev/null
+// IMPORTANT: choose one
+var RL_LIB = "libreadline"; // NOTE: libreadline is GPL
+//var RL_LIB = "libedit";
+
+var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history');
+
+var ffi = require('ffi'),
+ fs = require('fs');
+
+var rllib = ffi.Library(RL_LIB, {
+ 'readline': [ 'string', [ 'string' ] ],
+ 'add_history': [ 'int', [ 'string' ] ]});
+
+var rl_history_loaded = false;
+
+export function readline(prompt) {
+ prompt = prompt || "user> ";
+
+ if (!rl_history_loaded) {
+ rl_history_loaded = true;
+ var lines = [];
+ if (fs.existsSync(HISTORY_FILE)) {
+ lines = fs.readFileSync(HISTORY_FILE).toString().split("\n");
+ }
+ // Max of 2000 lines
+ lines = lines.slice(Math.max(lines.length - 2000, 0));
+ for (var i=0; i<lines.length; i++) {
+ if (lines[i]) { rllib.add_history(lines[i]); }
+ }
+ }
+
+ var line = rllib.readline(prompt);
+ if (line) {
+ rllib.add_history(line);
+ try {
+ fs.appendFileSync(HISTORY_FILE, line + "\n");
+ } catch (exc) {
+ // ignored
+ }
+ }
+
+ return line;
+};
--- /dev/null
+{
+ "name": "mal",
+ "version": "0.0.1",
+ "description": "Make a Lisp (mal) language implemented in Javascript",
+ "dependencies": {
+ "ffi": "1.3.x"
+ }
+}
--- /dev/null
+import { Sym } from './types';
+
+export function pr_str(obj, print_readably) {
+ if (typeof print_readably === 'undefined') { print_readably = true; }
+ var _r = print_readably;
+ if (obj instanceof Array) {
+ var ret = obj.map(function(e) { return pr_str(e,_r); });
+ return "(" + ret.join(' ') + ")";
+ } else if (typeof obj === "string") {
+ if (obj[0] === '\u029e') {
+ return ':' + obj.slice(1);
+ } else if (_r) {
+ return '"' + obj.replace(/\\/g, "\\\\")
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, "\\n") + '"'; // string
+ } else {
+ return obj;
+ }
+ } else if (obj === null) {
+ return "nil";
+ } else {
+ return obj.toString();
+ }
+}
--- /dev/null
+import * as types from './types';
+
+export class BlankException extends Error {}
+
+class Reader {
+ constructor(tokens) {
+ this.tokens = tokens;
+ this.position = 0;
+ }
+ next() { return this.tokens[this.position++]; }
+ peek() { return this.tokens[this.position]; }
+}
+
+function tokenize(str) {
+ const re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g;
+ let match = null;
+ let results = [];
+ while ((match = re.exec(str)[1]) != '') {
+ if (match[0] === ';') { continue; }
+ results.push(match);
+ }
+ return results;
+}
+
+function read_atom (reader) {
+ const token = reader.next();
+ //console.log("read_atom:", token);
+ if (token.match(/^-?[0-9]+$/)) {
+ return parseInt(token,10) // integer
+ } else if (token.match(/^-?[0-9][0-9.]*$/)) {
+ return parseFloat(token,10); // float
+ } else if (token[0] === "\"") {
+ return token.slice(1,token.length-1)
+ .replace(/\\"/g, '"')
+ .replace(/\\n/g, "\n"); // string
+ } else if (token[0] === ":") {
+ return types._keyword(token.slice(1));
+ } else if (token === "nil") {
+ return null;
+ } else if (token === "true") {
+ return true;
+ } else if (token === "false") {
+ return false;
+ } else {
+ return types._symbol(token); // symbol
+ }
+}
+
+// read list of tokens
+function read_list(reader, start, end) {
+ start = start || '(';
+ end = end || ')';
+ var ast = [];
+ var token = reader.next();
+ if (token !== start) {
+ throw new Error("expected '" + start + "'");
+ }
+ while ((token = reader.peek()) !== end) {
+ if (!token) {
+ throw new Error("expected '" + end + "', got EOF");
+ }
+ ast.push(read_form(reader));
+ }
+ reader.next();
+ return ast;
+}
+
+function read_form(reader) {
+ var token = reader.peek();
+ switch (token) {
+ // reader macros/transforms
+ case ';': return null; // Ignore comments
+ case '\'': reader.next();
+ return [types._symbol('quote'), read_form(reader)];
+ case '`': reader.next();
+ return [types._symbol('quasiquote'), read_form(reader)];
+ case '~': reader.next();
+ return [types._symbol('unquote'), read_form(reader)];
+ case '~@': reader.next();
+ return [types._symbol('splice-unquote'), read_form(reader)];
+ case '^': reader.next();
+ var meta = read_form(reader);
+ return [types._symbol('with-meta'), read_form(reader), meta];
+ case '@': reader.next();
+ return [types._symbol('deref'), read_form(reader)];
+
+ // list
+ case ')': throw new Error("unexpected ')'");
+ case '(': return read_list(reader);
+
+ // atom
+ default: return read_atom(reader);
+ }
+}
+
+export function read_str(str) {
+ var tokens = tokenize(str);
+ if (tokens.length === 0) { throw new BlankException(); }
+ return read_form(new Reader(tokens))
+}
+
--- /dev/null
+import { readline } from './node_readline';
+
+// read
+const READ = (str) => str;
+
+// eval
+const EVAL = (ast, env) => ast;
+
+// print
+const PRINT = (exp) => exp;
+
+// repl
+const REP = (str) => PRINT(EVAL(READ(str), {}));
+
+while (true) {
+ let line = readline("user> ");
+ if (line == null) break;
+ if (line) { console.log(REP(line)); }
+}
--- /dev/null
+import { readline } from './node_readline';
+//import * as types from './types';
+import { BlankException, read_str } from './reader';
+import { pr_str } from './printer';
+
+// read
+const READ = (str) => read_str(str);
+
+// eval
+const EVAL = (ast, env) => ast
+
+// print
+const PRINT = (exp) => pr_str(exp, true);
+
+// repl
+const REP = (str) => PRINT(EVAL(READ(str), {}));
+
+while (true) {
+ let line = readline("user> ");
+ if (line == null) break;
+ try {
+ if (line) { console.log(REP(line)); }
+ } catch (exc) {
+ console.log("exc: ", exc, typeof(exc));
+ if (exc instanceof BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); }
+ else { console.log(exc); }
+ }
+}
--- /dev/null
+// Symbols
+class Sym {
+ constructor(name) { this.name = name; }
+ toString() { return this.name; }
+ name() { return this.name; }
+}
+export function _symbol(name) { return new Sym(name); }
+export function _symbol_Q(obj) { return obj instanceof Sym; }
f = el[0]
return f(*el[1:])
+# print
def PRINT(exp):
return printer._pr_str(exp)