ES6: step0 and step1 basics.
authorJoel Martin <github@martintribe.org>
Fri, 31 Jul 2015 02:15:55 +0000 (21:15 -0500)
committerJoel Martin <github@martintribe.org>
Fri, 31 Jul 2015 02:15:55 +0000 (21:15 -0500)
.gitignore
Makefile
es6/Makefile [new file with mode: 0644]
es6/node_readline.js [new file with mode: 0644]
es6/package.json [new file with mode: 0644]
es6/printer.js [new file with mode: 0644]
es6/reader.js [new file with mode: 0644]
es6/step0_repl.js [new file with mode: 0644]
es6/step1_read_print.js [new file with mode: 0644]
es6/types.js [new file with mode: 0644]
python/step2_eval.py

index 2186198..66fa371 100644 (file)
@@ -1,10 +1,8 @@
 */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
@@ -61,3 +59,4 @@ nim/nimcache
 .sbt
 groovy/*.class
 .crystal
+es6/build
index 1e36854..1d0ed15 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ PYTHON = python
 # 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
 
@@ -62,6 +62,7 @@ cpp_STEP_TO_PROG =     cpp/$($(1))
 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
@@ -106,6 +107,7 @@ cpp_RUNSTEP =     ../$(2) $(3)
 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)
diff --git a/es6/Makefile b/es6/Makefile
new file mode 100644 (file)
index 0000000..2d0c58c
--- /dev/null
@@ -0,0 +1,14 @@
+
+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/*
diff --git a/es6/node_readline.js b/es6/node_readline.js
new file mode 100644 (file)
index 0000000..dd5db0b
--- /dev/null
@@ -0,0 +1,43 @@
+// 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;
+};
diff --git a/es6/package.json b/es6/package.json
new file mode 100644 (file)
index 0000000..802cf22
--- /dev/null
@@ -0,0 +1,8 @@
+{
+    "name": "mal",
+    "version": "0.0.1",
+    "description": "Make a Lisp (mal) language implemented in Javascript",
+    "dependencies": {
+        "ffi": "1.3.x"
+    }
+}
diff --git a/es6/printer.js b/es6/printer.js
new file mode 100644 (file)
index 0000000..3db0eac
--- /dev/null
@@ -0,0 +1,24 @@
+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();
+    }
+}
diff --git a/es6/reader.js b/es6/reader.js
new file mode 100644 (file)
index 0000000..01f5eb4
--- /dev/null
@@ -0,0 +1,101 @@
+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))
+}
+
diff --git a/es6/step0_repl.js b/es6/step0_repl.js
new file mode 100644 (file)
index 0000000..e5f3d84
--- /dev/null
@@ -0,0 +1,19 @@
+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)); }
+}
diff --git a/es6/step1_read_print.js b/es6/step1_read_print.js
new file mode 100644 (file)
index 0000000..7b630dd
--- /dev/null
@@ -0,0 +1,29 @@
+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); }
+    }
+}
diff --git a/es6/types.js b/es6/types.js
new file mode 100644 (file)
index 0000000..68f88d0
--- /dev/null
@@ -0,0 +1,8 @@
+// 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; }
index 8af09b1..5b69810 100644 (file)
@@ -37,6 +37,7 @@ def EVAL(ast, env):
         f = el[0]
         return f(*el[1:])
 
+# print
 def PRINT(exp):
     return printer._pr_str(exp)