corascript
Version:
cora is a lisp dialect compile to javascript
551 lines (492 loc) • 16.4 kB
JavaScript
var fs = require('fs');
var primitive = {};
var symbols = {};
function Symbol(name) {
this.name = name;
}
function isSymbol(s) {
return s instanceof Symbol;
}
function err(x) { throw new Error(x); };
function Cons(x, y) {
this.hd = x;
this.tl = y;
}
Cons.prototype = {
car : function() {
return this.hd;
},
cdr : function() {
return this.tl;
}
};
function cons(x, y) {
return new Cons(x, y);
}
function consLength(x) {
let length = 0;
while (isCons(x)) {
x = x.tl;
length++;
}
if (x !== null) err('not a valid list');
return length;
}
function isCons(x) {
return x instanceof Cons;
}
function isFunction(x) {
return x instanceof Function;
}
function isError(x) {
return x instanceof Error;
}
function isString(x) {
return typeof x === 'string';
}
function isArray(x) {
return x instanceof Array;
}
function isSymbol(x) {
return x instanceof Symbol;
}
function isNumber(x) {
return typeof x === 'number';
}
function hd(o) {
return mustCons(o).car();
}
function tl(o) {
return mustCons(o).cdr();
}
function cadr(x) {
return hd(tl(x));
}
function caddr(x) {
return hd(tl(tl(x)));
}
function intern(str) {
if (str === "true") return true;
if (str === "false") return false;
return new Symbol(mustString(str));
}
function escape(s) {
if (isSymbol(s)) s = s.name;
let result = '';
for (let i = 0; i < s.length; ++i) {
switch (s[i]) {
case '\\': { result += '\\\\'; break; }
case '\0': { result += '\\0'; break; }
case '\b': { result += '\\b'; break; }
case '\f': { result += '\\f'; break; }
case '\n': { result += '\\n'; break; }
case '\r': { result += '\\r'; break; }
case '\t': { result += '\\t'; break; }
case '\v': { result += '\\v'; break; }
case '\"': { result += '\\"'; break; }
case '\'': { result += "\\'"; break; }
default: { result += s[i]; break; }
}
}
return result;
}
function rename(name) {
if (isSymbol(name)) name = name.name;
let result = '';
for (let i = 0; i < name.length; ++i) {
switch (name[i]) {
case '-': { result += '_'; break; }
case '_': { result += '$un'; break; }
case '$': { result += '$dl'; break; }
case '.': { result += '$do'; break; }
case ',': { result += '$cm'; break; }
case '`': { result += '$bt'; break; }
case "'": { result += '$ap'; break; }
case '+': { result += '$pl'; break; }
case '*': { result += '$st'; break; }
case '<': { result += '$lt'; break; }
case '>': { result += '$gt'; break; }
case '%': { result += '$pe'; break; }
case '&': { result += '$am'; break; }
case '^': { result += '$ca'; break; }
case '=': { result += '$eq'; break; }
case '!': { result += '$ex'; break; }
case '?': { result += '$qu'; break; }
case '@': { result += '$at'; break; }
case '~': { result += '$ti'; break; }
case '#': { result += '$ha'; break; }
case '|': { result += '$pi'; break; }
case ':': { result += '$co'; break; }
case ';': { result += '$sc'; break; }
case '/': { result += '$sl'; break; }
case '{': { result += '$lc'; break; }
case '}': { result += '$rc'; break; }
case '[': { result += '$ls'; break; }
case ']': { result += '$rs'; break; }
case '\\': { result += '$bs'; break; }
default: { result += name[i]; break; }
}
}
return result;
}
function consToArray(x) {
const array = [];
while (x instanceof Cons && x !== null) {
array.push(hd(x));
x = tl(x);
}
return array;
}
function Env(locals, parent) {
if (parent == null) {
this.idx = 0;
} else {
this.idx = parent.idx + 1;
}
var arr = new Array();
for (let i = 0; i<locals.length; i++) {
let exists = parent.findVariable(locals[i]);
if (exists !== null) {
arr[i] = {name: locals[i], rename: rename(locals[i]) + '$' + this.idx};
} else {
arr[i] = {name: locals[i], rename: rename(locals[i])};
}
}
this.locals = arr;
this.parent = parent;
}
Env.prototype.findVariable = function(name) {
let ptr = this;
let cmp = function(obj) {
return obj.name === name;
};
while(ptr != null) {
let obj = ptr.locals.find(cmp);
if (obj !== undefined) {
return obj;
}
ptr = ptr.parent;
};
return null;
};
function kl2js(kl, env, tail) {
// literal
if (kl === null) return 'null';
if (kl === true) return 'true';
if (kl === false) return 'false';
if (isNumber(kl)) return kl.toString();
if (isString(kl)) return '"' + escape(kl) + '"';
// symbol
if (isSymbol(kl)) {
if (kl.name === "true") return "true";
if (kl.name === "false") return "false";
let varItem = env.findVariable(kl.name);
if (varItem !== null) {
return varItem.rename;
}
return "new Symbol(\"" + kl.name + "\")";
}
if (isCons(kl)) {
let car = kl.car();
let remain = kl.cdr();
// special form
if (car instanceof Symbol) {
if (car.name === "if" && consLength(remain) == 3) {
return ifExpr2js(hd(remain), cadr(remain), caddr(remain), env, tail);
} else if (car.name === "cond") {
if (remain === null) {
return 'err("no cond match")';
}
return ifExpr2js(hd(hd(remain)), cadr(hd(remain)), cons(car, tl(remain)), env, tail);
} else if (car.name === "let") {
return letExpr2js(hd(remain), cadr(remain), caddr(remain), env, tail);
} else if (car.name === "defun") {
return defunExpr2js(hd(remain), cadr(remain), caddr(remain), env);
} else if (car.name === "lambda") {
return lambdaExpr2js(hd(remain), cadr(remain), env);
} else if (car.name === "freeze") {
return freezeExpr2js(hd(remain), env);
} else if (car.name === "and") {
return andExpr2js(hd(remain), cadr(remain), env);
} else if (car.name === "or") {
return orExpr2js(hd(remain), cadr(remain), env);
} else if (car.name === "trap-error") {
return trapErrorExpr2js(hd(remain), cadr(remain), env, tail);
} else if (car.name === "do") {
return doExpr2js(hd(remain), cadr(remain), env, tail);
}
// apply symbol function
return applyExpr2js(car, remain, env, tail);
} else if (car instanceof Cons) {
// complex apply
return applyExpr2js(car, remain, env, tail);
}
}
}
function ifExpr2js(testExpr, succExpr, failExpr, env, tail) {
let test = kl2js(testExpr, env, false);
let succ = kl2js(succExpr, env, tail);
let fail = kl2js(failExpr, env, tail);
return "(mustBoolean(" + test + ") === true) ? ("+ succ + ") : (" + fail + ")";
}
function andExpr2js(x, y, env) {
return "(mustBoolean(" + kl2js(x, env, false) + ") && mustBoolean(" + kl2js(y, env, false) + "))";
}
function orExpr2js(x, y, env) {
return "(mustBoolean(" + kl2js(x, env, false) + ") || mustBoolean(" + kl2js(y, env, false) + "))";
}
function letExpr2js(varExpr, valExpr, bodyExpr, env, tail) {
let valStr = kl2js(valExpr, env, false);
let env1 = new Env([mustSymbol(varExpr).name], env);
let name = env1.locals[0].rename;
let body = kl2js(bodyExpr, env1, tail);
return "(function(){var " + name + " = " + valStr + "; return " + body + ";})()";
}
function defunExpr2js(fun, args, bodyExpr, env) {
let locals = consToArray(args).map(function(s){return s.name;});
let env1 = new Env(locals, env);
let localsString = env1.locals.map(function(varItem){return varItem.rename;}).join(', ');
let body = kl2js(bodyExpr, env1, true);
return "defun(\"" + fun.name + "\", " + "function("+ localsString + ") { return " + body + " ;}, " + locals.length.toString() + ")";
}
var startTime = new Date().getTime();
function defun(name, f, arity) {
f.arity = arity;
primitive[name] = f;
return new Symbol(name);
}
function eq(x, y) {
if (x === y) return true;
if (isSymbol(x) && isSymbol(y)) return x.name === y.name;
if (isCons(x) && isCons(y)) return eq(x.hd, y.hd) && eq(x.tl, y.tl);
if (isArray(x) && isArray(y)) {
if (x.length !== y.length) return false;
for (let i = 0; i < x.length; ++i) {
if (!eq(x[i], y[i])) return false;
}
return true;
}
return false;
}
function klSet(x, y) {
symbols[mustSymbol(x).name] = y;
return y;
}
function toStr(x) {
if (x === null) return '[]';
if (isSymbol(x)) return x.name;
if (isString(x)) return `"${x}"`;
if (isCons(x)) return `[${consToArray(x).map(toStr).join(' ')}]`;
if (isFunction(x)) return `<Function ${x.klName}>`;
if (isArray(x)) return `<Vector ${x.length}>`;
if (isError(x)) return `<Error "${x.message}">`;
if (x === undefined) return err("str(undefined)");
return '' + x;
}
function mustNumber(x) {
if (typeof x !== 'number') {
return err("mustNumber");
}
return x;
}
function mustBoolean(x) {
if (typeof x !== 'boolean') {
return err("mustBoolean");
}
return x;
}
function mustString(x) {
if (typeof x !== 'string') {
return err("mustString");
}
return x;
}
function mustSymbol(x) {
if (x instanceof Symbol === false) {
return err("mustSymbol");
}
return x;
}
function mustCons(x) {
if (x instanceof Cons === false) {
return err("mustCons");
}
return x;
}
defun("if", function(c, x, y) {return mustBoolean(c) ? x : y;}, 3);
defun("and", function(x, y) {return mustBoolean(x) && mustBoolean(y);}, 2);
defun("or", function(x, y) {return mustBoolean(x) || mustBoolean(y);}, 2);
defun("not", function(x) {return !mustBoolean(x);}, 1);
defun("set", klSet, 2);
defun("value", function(x, y) {
let ret = symbols[mustSymbol(x).name];
if (ret === undefined) {
return err(x.name + ' is not bound');
}
return ret;
}, 1);
defun("+", function(x, y) {return mustNumber(x) + mustNumber(y);}, 2);
defun("-", function(x, y) {return mustNumber(x) - mustNumber(y);}, 2);
defun("*", function(x, y) {return mustNumber(x) * mustNumber(y);}, 2);
defun("/", function(x, y) {return mustNumber(x) / mustNumber(y);}, 2);
defun("<", function(x, y) {return mustNumber(x) < mustNumber(y);}, 2);
defun(">", function(x, y) {return mustNumber(x) > mustNumber(y);}, 2);
defun("<=", function(x, y) {return mustNumber(x) <= mustNumber(y);}, 2);
defun(">=", function(x, y) {return mustNumber(x) >= mustNumber(y);}, 2);
defun("=", eq, 2);
defun("number?", isNumber, 1);
defun("cons", cons, 2);
defun("cons?", isCons, 1);
defun("hd", hd, 1);
defun("tl", tl, 1);
defun("intern", intern, 1);
defun("string?", isString, 1);
defun("str", toStr, 1);
defun("pos", function(s, x) {return s[x];}, 2);
defun("tlstr", function(s) {return mustString(s).slice(1);}, 1);
defun("cn", function(x, y) {return mustString(x) + mustString(y);}, 2);
defun("string->n", function(x) {return mustString(x).charCodeAt(0);}, 1);
defun("n->string", function(n) {return String.fromCharCode(mustNumber(n));}, 1);
defun("absvector", function(n) {return new Array(n).fill(null);}, 1);
defun("<-address", function(a, i) {
if (a instanceof Array === false) {
return err('address need an vector');
}
if (mustNumber(i) >= a.length) {
return err('index out of range for vector');
}
return a[i];
}, 2);
defun("address->", function(a, i, x) {a[i] = x; return a;}, 3);
defun("absvector?", isArray, 1);
defun("eval-kl", function(kl) {return eval(kl2js(kl, new Env([], null)), true);}, 1);
defun("simple-error", function(e) {return err(mustString(e));}, 1);
defun("error-to-string", function(e) {return e.message;}, 1);
defun("get-time", function(mode) {
if (mode.name === 'unix') return new Date().getTime();
if (mode.name === 'run') return new Date().getTime() - startTime;
return err("get-time only accepts 'unix or 'run");
}, 1);
defun("symbol?", function(x) {return x instanceof Symbol;}, 1);
defun("integer?", function(x) {return typeof x === 'number';}, 1);
defun("read-file-as-charlist", function(filePath) {
let data = fs.readFileSync(filePath);
let ret = null;
for (let i=data.length-1; i>=0; i--) {
ret = cons(data[i], ret);
}
return ret;
}, 1);
defun("write-to-file", function(File, Text) {
fs.writeFileSync(File, Text);
return Text;
}, 2);
klSet(new Symbol("*stinput*"), process.stdin);
klSet(new Symbol("*stoutput*"), process.stdout);
klSet(new Symbol("*home-directory*"), "/home/genius");
klSet(new Symbol("*language*"), "javascript");
klSet(new Symbol("*implementation*"), "compiler");
klSet(new Symbol("*relase*"), "0.0.1");
klSet(new Symbol("*os*"), "linux");
klSet(new Symbol("*porters*"), "Arthur Mao");
klSet(new Symbol("*port*"), "0.0.1");
function klFun(f, arity) {
f.arity = arity;
return f;
}
function klApply(fn, ...args) {
if (fn.arity === undefined) {
return err("can't klApply invalid function");
}
if (fn.arity == args.length) {
return fn(...args);
}
if (fn.arity < args.length) {
return klApply(fn(...args.slice(0, fn.arity)), ...args.slice(fn.arity));
}
return klFun(function(...more) {
return klApply(fn, ...args.concat(more));
}, fn.arity - args.length);
}
function Trampoline(f, ...args) {
this.f = f;
this.args = args;
}
function klTailApply(fn, ...args) {
let ret = klApply(fn, ...args);
while(ret instanceof Trampoline) {
ret = klApply(ret.f, ...ret.args);
};
return ret;
}
function call(name, ...args) {
let prim = primitive[name];
if (prim === undefined || prim.arity == undefined) {
return err('primitive not defined:' + name);
}
return klTailApply(prim, ...args);
}
function applyExpr2js(fn, args, env, tail) {
let argsArray = consToArray(args).map(function(x) {return kl2js(x, env, false);}, args);
let argsStr = "";
let fnStr;
if (fn instanceof Symbol) {
let varItem = env.findVariable(fn.name);
if (varItem !== null) {
fnStr = varItem.rename;
} else {
fnStr = "primitive[\"" + fn.name + "\"]";
}
} else {
fnStr = kl2js(fn, env, false);
}
for (let i=0; i<argsArray.length; i++) {
argsStr = argsStr + ", " + argsArray[i];
}
if (tail) {
return "new Trampoline(" + fnStr + argsStr + ")";
}
return "klTailApply(" + fnStr + argsStr + ")";
}
function lambdaExpr2js(varExpr, bodyExpr, env) {
let env1 = new Env([mustSymbol(varExpr).name], env);
let name = env1.locals[0].rename;
let body = kl2js(bodyExpr, env1, true);
return "klFun(function(" + name + ") { return " + body + ";}, 1)";
}
function doExpr2js(exp1, exp2, env, tail) {
var str1 = kl2js(exp1, env, false);
var str2 = kl2js(exp2, env, tail);
return "(function(){" + str1 + "; return " + str2 + ";})()";
}
function freezeExpr2js(body, env) {
return "klFun(function() { return " + kl2js(body, env, true) + "}, 0)";
}
function trapErrorExpr2js(body, handle, env, tail) {
let bodyStr = kl2js(body, env, false);
let handleStr = kl2js(handle, env, tail);
return "(function(){ try { return " + bodyStr + ";} catch (err) { return klTailApply(" + handleStr + ", err);} })()";
}
defun("kl->js", function(kl) {return kl2js(kl, new Env([], null), false);}, 1);
// This module is for internal usage, all the variables in this file could be exported.
module.exports = {
Symbol: Symbol,
isSymbol: isSymbol,
Cons: Cons,
isCons: isCons,
Trampoline: Trampoline,
klTailApply: klTailApply,
Env: Env,
kl2js: kl2js,
mustBoolean: mustBoolean,
hd: hd,
tl: tl,
eq: eq,
cons: cons,
intern: intern,
defun: defun,
call: call,
err: err,
primitive: primitive,
klFun: klFun,
};