Skip to content

Instantly share code, notes, and snippets.

@OrangeBacon
Created May 25, 2017 10:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OrangeBacon/10777fedd052d61cc08163e76d170bbc to your computer and use it in GitHub Desktop.
Save OrangeBacon/10777fedd052d61cc08163e76d170bbc to your computer and use it in GitHub Desktop.
A Programming Language
<div id='editor'></div>
<div id='output'></div>
<div id='run'>run</div>
<div id="example" hidden>
#comment
/*
this is also a comment
*/
# all function arguments are required //TODO
def print-range = fn(start,stop) -> {
if start <= stop then {
print(start);
if start + 1 <= stop then {
print(", ");
sleep(250);
print-range(start + 1, stop);
} else print("\n");
};
};
print-range(1,5);
def max;
/*
everything is an expression
this returns 3.14
*/
def pi = 3.14;
def function = fn(x) -> {
# return = last expression
x;
};
def string = "hello world"
|> capitalise
|> increment
|> reverse;
/*
equivalent to:
String.fromCharCode(...[...string.toUpperCase()].map(x=>x.charCodeAt(0)+1).reverse())
in JavaScript since strings act as arrays
*/
/*
closure
fn -> {} is short for fn() -> {}
variables in closure are not accessible outside
*/
(fn->{
# iife
# code
})();
# or simpler
{
# code
};
/*
To make a vaiable mutable you must add the keyword mut;
by default let is immutable.
*/
def mut level = 0;
def with-yield = fn(mut func) -> {
let (mut yield) -> {
yield = fn(val) -> {
shift(fn(SK) -> {
func = SK;
val; ## return val
});
};
fn(val) -> {
reset( fn() -> func(val || yield) );
};
}
};
def foo = with-yield(fn(yield) -> {
yield(1);
yield(2);
yield(3);
"DONE";
});
time(fn->{
print(foo());
print(foo());
print(foo());
print(foo());
print(foo());
});
</div>
/* jshint esversion:6 */
/**\
|**| TODO
|**|
|**| Macros
|**| Function/operator overloading
|**| arrays
|**| objects
|**| type checking
|**| modules
|**| jscodegen
|**| standard library
|**| rest parameters
|**| proper editor
|**| import/export
|**| optional arguments
\**/
// parsing
function InputStream(input, editor = false) {
let pos = 0;
let line = 1;
let col = 0;
return {
next,
peek,
eof,
err,
peekData
};
function next() {
let ch = input.charAt(pos);
let val = {
value: ch,
line,
col,
pos
};
pos++;
if (ch == "\n") {
line++;
col = 0;
} else {
col++;
}
return val;
}
function peek(x = 0) {
return input.charAt(pos + x);
}
function peekData(x = 0) {
let ch = input.charAt(pos + x);
let val = {
value: ch,
line,
col,
pos
};
for (let i = 0; i < x; i++) {
if (input.charAt(val.pos) == "\n") {
val.line++;
val.col = 0;
} else {
val.col++;
}
val.pos++;
}
return val;
}
function eof() {
return peek() === "";
}
function err(type, msg) {
if (editor) {
let el = document.createElement("span");
el.setAttribute('class', 'cm-error');
el.style.display = 'block';
el.innerText = `${type}: ${msg} at line ${line}:${col}`;
editor.widgets.push(editor.addLineWidget(line - 1, el, {
noHScroll: true
}));
}
throw new Error(`${type}: ${msg} at line ${line}:${col}`);
}
}
let TokenStreamCommon = {
keywords: ["if", "then", "else", "fn", "->", "true", "false", "null", "let", "mut", "fn->", "def"],
is_whitespace(ch) {
return " \t\n".indexOf(ch) >= 0;
},
is_digit(ch) {
return /[0-9]/i.test(ch);
},
is_id_start(ch) {
return /[a-z_]/i.test(ch);
},
is_id(ch) {
return TokenStreamCommon.is_id_start(ch) || "?!-<>=0123456789".indexOf(ch) >= 0;
},
is_keyword(x) {
return TokenStreamCommon.keywords.indexOf(x) >= 0;
},
is_punc(ch) {
return ",;[]{}()".indexOf(ch) >= 0;
},
is_op(ch) {
return "+-*/%=&|<>!^".indexOf(ch) >= 0;
}
};
function TokenStream(input) {
let current = null;
let c = TokenStreamCommon;
return {
next,
peek,
eof,
err: input.err
};
function read_while(pred) {
let str = '';
while (pred(input.peek(), input.peek(1)) && !input.eof()) {
str += input.next().value;
}
return str;
}
function read_comment() {
let next = input.next();
let varient;
if (next.value == "/") {
input.next();
varient = "//";
} else {
varient = "#";
}
let val = read_while(ch => ch !== '\n');
return {
type: "comment",
varient: varient,
value: val,
line: next.line,
col: next.col
};
}
function read_comment_m() {
let next = input.next();
input.next();
let val = read_while((ch1, ch2) => ch1 !== '*' && ch2 !== '/');
input.next();
input.next();
return {
type: "comment",
varient: "multiline",
value: val,
line: next.line,
col: next.col
};
}
function read_escaped(end) {
let escaped = false;
let str = "";
while (!input.eof()) {
let next = input.next();
let ch = next.value;
if (escaped) {
if (ch == "n") {
str += "\n";
} else {
str += ch;
}
escaped = false;
} else if (ch == "\\") {
escaped = true;
} else if (ch == end) {
break;
} else {
str += ch;
}
}
return str;
}
function read_string() {
let next = input.next();
let varient = next.value == "'" ? "'" : '"';
let val = read_escaped(varient);
return {
type: "string",
varient: varient,
value: val,
line: next.line,
col: next.col
};
}
function read_number() {
let next = input.peekData();
let has_dot = false;
let number = read_while(function(ch) {
if (ch == ".") {
if (has_dot) return false;
has_dot = true;
return true;
}
return c.is_digit(ch);
});
return {
type: "number",
value: number,
line: next.line,
col: next.col,
};
}
function read_ident() {
let next = input.peekData();
let id = read_while(c.is_id);
return {
type: c.is_keyword(id) ? "kw" : "var",
value: id,
line: next.line,
col: next.col
};
}
function read_punc() {
let next = input.next();
return {
type: "punc",
value: next.value,
line: next.line,
col: next.col
};
}
function read_op() {
let next = input.peekData();
return {
type: "op",
value: read_while(c.is_op),
line: next.line,
col: next.col
};
}
function read_mod() {
let next = input.next();
return {
type: "mod",
value: next.value,
line: next.line,
col: next.col
};
}
function read_next() {
read_while(c.is_whitespace);
if (input.eof()) return null;
var ch = input.peek();
var next = input.peek(1);
if (ch == "#") {
read_comment();
return read_next();
}
if (ch == "/" && next == "*") {
read_comment_m();
return read_next();
}
if (ch == "/" && next == "/") {
read_comment();
return read_next();
}
if (ch == "'" || ch == '"') return read_string();
if (c.is_digit(ch)) return read_number();
if (c.is_id_start(ch) || (ch == "-" && next == ">")) return read_ident();
if (c.is_punc(ch)) return read_punc();
if (c.is_op(ch)) return read_op();
if (ch == "?") return read_mod();
input.err("Lexing Error", `Unexpected character "${ch}"`);
}
function peek() {
return current || (current = read_next());
}
function next() {
var tok = current;
current = null;
return tok || read_next();
}
function eof() {
return peek() === null;
}
}
function parse(input) {
let FALSE = {
type: "bool",
value: false
};
let PRECEDENCE = {
"=": 1,
"*=": 1,
"/=": 1,
"+=": 1,
"-=": 1,
"||": 2,
"&&": 3,
"<": 7,
">": 7,
"<=": 7,
">=": 7,
"==": 7,
"!=": 7,
"+": 10,
"-": 10,
"*": 20,
"/": 20,
"%": 20,
"|>": 30,
};
let is = type => ch => {
let tok = input.peek();
return tok && tok.type == type && (!ch || tok.value == ch) && tok;
};
let is_punc = is("punc");
let is_kw = is("kw");
let is_op = is("op");
let is_var = is("var");
let skip = type => ch => {
if (is(type)(ch)) {
input.next();
} else {
input.err("Parse Error", `Expecting ${type}: "${ch}"`);
}
};
let skip_punc = skip("punc");
let skip_kw = skip("kw");
let skip_op = skip("op");
let skip_var = skip("var");
function parse_call(func) {
let peek = input.peek();
return {
type: "call",
func: func,
line: peek.line,
col: peek.col,
args: delimited("(", ")", ",", parse_expression),
};
}
function maybe_call(expr) {
expr = expr();
return is_punc("(") ? parse_call(expr) : expr;
}
function delimited(start, stop, separator, parser) {
var a = [],
first = true;
skip_punc(start);
while (!input.eof()) {
if (is_punc(stop)) break;
if (first) first = false;
else skip_punc(separator);
if (is_punc(stop)) break;
a.push(parser());
}
skip_punc(stop);
return a;
}
function parse_block() {
let peek = input.peek();
var prog = delimited("{", "}", ";", parse_expression);
if (prog.length === 0) return FALSE;
if (prog.length == 1) return prog[0];
return {
type: "block",
prog: prog,
line: peek.line,
col: peek.col
};
}
function parse_not(){
let next = input.next();
return {
type: 'not',
value: parse_expression(),
line: next.line,
col: next.col,
};
}
function parse_bool() {
let next = input.next();
return {
type: "bool",
value: next.value == "true",
line: next.line,
col: next.col
};
}
function parse_if() {
let peek = input.peek();
skip_kw("if");
var cond = parse_expression();
if (!is_punc("{")) skip_kw("then");
var then = parse_expression();
var ret = {
type: "if",
cond: cond,
then: then,
line: peek.line,
col: peek.col
};
if (is_kw("else")) {
input.next();
ret.else = parse_expression();
}
return ret;
}
function parse_varname() {
let name = input.next();
let mod = "";
let immutable = true;
if (name.type == "mod") {
mod = '?';
name = input.next();
}
if(name.type =="kw" && name.value == "mut"){
immutable = false;
name = input.next();
}
if (name.type != "var") input.err("Parse Error", "Expecting variable name");
return {
value: name.value,
mod: mod,
immutable:immutable,
line: name.line,
col: name.col
};
}
function parse_fn() {
let next = input.next();
let vars, name, ret;
if (next.value == "fn") {
if (input.peek().type == "var") name = input.next();
vars = delimited("(", ")", ",", parse_varname);
skip_kw("->");
} else {
vars = [];
name = null;
}
return {
type: "function",
name: name,
vars: vars,
body: parse_expression(),
line: next.line,
col: next.col
};
}
function parse_vardef() {
let peek = input.peek();
let name = parse_varname();
let def;
if (is_op("=")) {
skip_op("=");
def = parse_expression();
}
return {
name,
def,
line: peek.line,
col: peek.col
};
}
function parse_let() {
skip_kw("let");
let peek = input.peek();
if (peek.type == "var") {
let name = input.next().value;
let defs = delimited("(", ")", ",", parse_vardef);
skip_kw("->");
return {
type: "call",
func: {
type: "function",
name: name,
vars: defs.map(def => def.name),
body: parse_expression()
},
args: defs.map(def => def.def || FALSE),
line: peek.line,
col: peek.col
};
}
let vars = delimited("(", ")", ",", parse_vardef);
skip_kw("->");
return {
type: "let",
vars: vars,
body: parse_expression(),
line: peek.line,
col: peek.col
};
}
function parse_define() {
let next = input.next();
let varient = "immutable";
if (is_kw("mut")) {
varient = "mutable";
skip_kw("mut");
}
let left = is_var();
if (left) {
skip_var(left.value);
} else {
input.err("Parse Error", `Expecting variable name after ${varient=="immutable"?"def":"def mut"}`);
}
let right = {
type: "number",
value: 0
};
if (is_op("=")) {
skip_op("=");
right = parse_expression();
}
return {
type: "define",
varient,
left,
right,
line: next.line,
col:next.col
};
}
function parse_atom() {
return maybe_call(function() {
if (is_punc("(")) {
input.next();
let exp = parse_expression();
skip_punc(")");
return exp;
}
if (is_punc("{")) return parse_block();
if(is_op("!"))return parse_not();
if (is_kw("if")) return parse_if();
if (is_kw("true") || is_kw("false")) return parse_bool();
if (is_kw("fn") || is_kw("fn->")) return parse_fn();
if (is_kw("def")) return parse_define();
if (is_kw("let")) return parse_let();
let tok = input.next();
if (tok.type == "var" || tok.type == "number" || tok.type == "string")
return tok;
input.err("Parse Error", `Unexpected token: ${JSON.stringify(tok)}`);
});
}
function maybe_binary(left, my_prec) {
let tok = is_op();
if (tok) {
let his_prec = PRECEDENCE[tok.value];
if (his_prec > my_prec) {
input.next();
let type;
let assign = ["=", "*=", "+=", "-=", "/="];
if (assign.indexOf(tok.value) >= 0) {
type = "assign";
} else {
type = "binary";
}
return maybe_binary({
type: type,
operator: tok.value,
left: left,
right: maybe_binary(parse_atom(), his_prec),
line: left.line,
col: left.col
}, my_prec);
}
}
return left;
}
function maybe_pipe(expr){
if(expr.type=='binary'&&expr.operator=='|>'){
let left = expr.left;
let right = expr.right;
if(right.type == "var"){
return {
type:'call',
func: right,
line: right.line,
col: right.col,
args: [maybe_pipe(left)],
};
} else if(right.type == "call"){
return {
type: 'call',
func: {
value: right.func.value,
type: 'var',
line: right.func.line,
col: right.func.col
},
line: right.line,
col: right.col,
args: [left,...right.args],
};
} else {
input.err("Parse Error", "Expected call or variable after '|>'");
}
} else {
return expr;
}
}
function parse_expression() {
return maybe_pipe((function(){
return maybe_call(function() {
return maybe_binary(parse_atom(), 0);
});
})());
}
function parse_prog() {
let prog = [];
let peek = input.peek();
while (!input.eof()) {
prog.push(parse_expression());
if (!input.eof()) skip_punc(';');
}
return {
type: "block",
prog: prog,
global: true,
line: peek.line,
col: peek.col
};
}
return parse_prog();
}
/*
function parse2(input){
let FALSE = { type: "bool", value: false };
let TRUE = { type: "bool", value: true };
let is = type => value => {
let tok = input.peek();
return tok && tok.type==type && (tok.value==value||!value);
}
let is_punc = is("punc");
let is_kw = is("kw");
let is_var = is("var");
let is_op = is("op");
let skip = type => ch => {
if (is(type)(ch)){
input.next()
} else {
input.err("Parse Error",`Expecting ${type}: "${ch}", found ${JSON.stringify(input.peek())}`);
}
}
let skip_op = skip("op");
let skip_punc = skip("punc");
let skip_kw = skip("kw");
function is_assign(){
return is_op() && input.peek().value.endsWith("=");
}
function delimited(start,stop,sep,parser){
let code = [];
let first = true;
skip_punc(start);
while(!input.eof()){
if(is_punc(stop)) break;
if(first) first = false;else skip_punc(sep);
if(is_punc(stop)) break;
code.push(parser());
}
skip_punc(stop);
return code;
}
function parse_define(){
let type = input.next();
let name = input.next();
skip_op("=");
return {
type: type.value,
left: name,
right: parse_expression()
}
}
function parse_block(){
let block = delimited("{","}",";",parse_expression);
if(block.length==0)return FALSE;
if(block.length==1)return block[0];
return {type:"block",value:block};
}
function parse_varname() {
var name = input.next();
if (name.type != "var") input.err("Parse Error","Expecting variable name");
return name.value;
}
function parse_function(){
let next = input.next();
let vars;
if(next.value=="fn"){
vars = delimited("(",")",",",parse_varname);
skip_kw("->");
} else {vars = null}
return {
type: "function",
vars: vars,
value: parse_expression()
};
}
function parse_if(){
skip_kw("if");
let cond = parse_expression();
if(!is_punc("{"))skip_kw("then");
let then = parse_expression();
let ret = {
type: "if",
cond: cond,
then: then
}
if(is_kw("else")) {
input.next();
ret.else = parse_expression();
}
return ret;
}
function parse_bool() {
return {
type : "bool",
value : input.next().value == "true"
};
}
function make_call(func){
return {
type: "call",
func: func,
args: delimited("(",")",",",parse_expression)
};
}
function parse_assign(left){
return {
type: "assign",
left: left,
right: parse_expression()
};
}
function parse_binary(start){
if(!is_op())return start;
function get(){
let cache = [];
while(!input.eof()){
if(is_op()){
cache.push(input.next());
}
}
}
}
function parse_expression(){
if(is_kw("let")||is_kw("const"))return parse_define();
if(is_punc("{"))return parse_block();
if(is_kw("fn")||is_kw("fn->"))return parse_function();
if(is_kw("if"))return parse_if();
if(is_kw("true")||is_kw("false"))return parse_bool();
if(is_punc("(")){
input.next();
let exp = parse_expression();
input.next();
if(is_punc("(")){
return make_call(exp);
} else {
return exp;
}
}
if(is_var()){
let iden = input.next();
if(is_punc("(")){
return make_call(iden);
} else if(is_assign()){
return parse_assign(iden);
} else {
return parse_binary(iden);
}
}
let tok = input.next();
if(is("string")()||is("number")()) return tok;
input.err("Parse Error",`Unexpected token ${JSON.stringify(tok)}`);
}
function parse_prog(){
let prog = [];
while(!input.eof()){
prog.push(parse_expression());
if(!input.eof())skip_punc(";");
}
return prog;
}
return parse_prog();
}
*/
// env
function Environment(parent) {
this.vars = Object.create(parent ? parent.vars : {});
this.parent = parent;
}
Environment.prototype = {
extend: function() {
return new Environment(this);
},
lookup: function(name) {
var scope = this;
while (scope) {
if (Object.prototype.hasOwnProperty.call(scope.vars, name))
return scope;
scope = scope.parent;
}
return false;
},
get: function(name,{line,col}={line:undefined,col:undefined}) {
if (name in this.vars)
return this.vars[name].value;
throw new Error(`Undefined variable "${name}" at ${line}:${col}`);
},
set: function(name, value,{line,col}={line:undefined,col:undefined}) {
let scope = this.lookup(name);
if (!(scope || this).vars.hasOwnProperty(name))
throw new Error(`Undefined variable "${name}" at ${line}:${col}`);
let opt = (scope || this).vars[name];
if (opt.immutable) {
throw new Error(`Cannot write to read-only variable ${name} at ${line}:${col}`);
}
opt.value = value;
return value;
},
def: function(name, value, {
immutable = true,
force = true
} = {
immutable: true,
force: true
},{line,col}={line:undefined,col:undefined}) {
if (this.vars.hasOwnProperty(name) && !force)
throw new Error(`Variable ${name} is already defined at ${line}:${col}`);
if (this.parent && !force)
throw new Error(`Cannot define value when not in global scope at ${line}:${col}`);
this.vars[name] = {
value,
immutable
};
return this.vars[name];
}
};
function makeEnv() {
let globalEnv = new Environment();
globalEnv.def("print", function(callback, txt) {
if (typeof txt !== "string") txt = txt.toString();
output.setValue(output.getValue() + txt);
callback();
});
globalEnv.def("clear", function(callback) {
output.setValue('');
callback();
});
globalEnv.def("time", function(callback, func) {
let start = performance.now();
func(function(){
let end = performance.now();
Execute(callback, [(end - start).toFixed(3) + 'ms']);
});
});
globalEnv.def("sleep", function(callback, milliseconds) {
setTimeout(function() {
Execute(callback, [false]); // continuations expect a value, pass false
}, milliseconds);
});
globalEnv.def("callcc",function(callback,fn, ...args){
fn(callback,function CC(discard,ret){
callback(ret);
}, ...args);
});
//http://lisperator.net/pltut/cps-evaluator/yield#reset-shift
var pstack = [];
function _goto(f) {
f(function KGOTO(r) {
var h = pstack.pop();
h(r);
});
}
function reset(KRESET, th) {
pstack.push(KRESET);
_goto(th);
}
globalEnv.def("reset", reset);
function shift(KSHIFT, f) {
_goto(function(KGOTO) {
f(KGOTO, function SK(k1, v) {
pstack.push(k1);
KSHIFT(v);
});
});
}
globalEnv.def("shift", shift);
// for stdEnv
globalEnv.def("catch",null,{immutable:false});
globalEnv.def("throw",null,{immutable:false});
globalEnv.def("with-yield",null,{immutable:false});
return globalEnv;
}
function stdEnv(ast){
let std = `catch = fn(func)->{
callcc(fn(cont)-> {
func(cont);
});
};
throw = fn(cont)->{
cont();
};
with-yield = fn(func) ->{
let (mut yield) -> {
yield = fn(val) ->{
shift(fn(SK)->{
func = SK;
val; ## return val
});
};
fn(val) -> {
reset( fn-> func(val || yield) );
};
}
};`;
std = parse(TokenStream(InputStream(std)));
ast = JSON.parse(JSON.stringify(ast));
ast.prog = [...std.prog,...ast.prog];
return ast;
}
// evaluating
/* function evaluate(exp, env) {
function apply_op(op, a, b) {
function num(x) {
x = parseFloat(x);
if (Number.isNaN(x))
throw new Error("Expected number but got " + x);
return x;
}
function div(x) {
if (num(x) == 0)
throw new Error("Divide by zero");
return x;
}
switch (op) {
case "+":
return num(a) + num(b);
case "-":
return num(a) - num(b);
case "*":
return num(a) * num(b);
case "/":
return num(a) / div(b);
case "%":
return num(a) % div(b);
case "&&":
return a !== false && b;
case "||":
return a !== false ? a : b;
case "<":
return num(a) < num(b);
case ">":
return num(a) > num(b);
case "<=":
return num(a) <= num(b);
case ">=":
return num(a) >= num(b);
case "==":
return a === b;
case "!=":
return a !== b;
}
throw new Error("Can't apply operator " + op);
}
function make_function(env, exp) {
function fn() {
var names = exp.vars;
var scope = env.extend();
for (var i = 0; i < names.length; ++i)
scope.def(names[i].value, (i < arguments.length ? arguments[i] : false), false);
return evaluate(exp.body, scope);
}
return fn;
}
switch (exp.type) {
case "number":
case "string":
case "bool":
return exp.value;
case "var":
return env.get(exp.value);
case "define":
if (exp.left.type != "var")
throw new Error("Cannot assign to " + JSON.stringify(exp.left));
return env.def(exp.left.value, evaluate(exp.right, env), exp.varient == "immutable");
case "assign":
if (exp.left.type != "var")
throw new Error("Cannot assign to " + JSON.stringify(exp.left));
return env.set(exp.left.value, evaluate(exp.right, env));
case "binary":
return apply_op(exp.operator,
evaluate(exp.left, env),
evaluate(exp.right, env));
case "function":
return make_function(env, exp);
case "if":
let cond = evaluate(exp.cond, env);
if (cond !== false) return evaluate(exp.then, env);
return exp.else ? evaluate(exp.else, env) : false;
case "block":
let val = false;
exp.prog.forEach(function(exp) {
val = evaluate(exp, env)
});
return val;
case "call":
let func = evaluate(exp.func, env);
if(typeof func !== "function")func=func.value;
return func.apply(null, exp.args.map(arg => evaluate(arg, env)));
default:
throw new Error(`Cannot evaluate ${exp.type}`);
}
}
*/
let STACKLEN;
function Continuation(f, args) {
this.f = f;
this.args = args;
}
function GUARD(f, args) {
if (--STACKLEN < 0) throw new Continuation(f, args);
}
function cpsEvaluate(exp, env, callback) {
function apply_op(op, a, b) {
function num(x) {
x = parseFloat(x);
if (Number.isNaN(x))
throw new Error(`Expected number but got "${x}" at ${exp.line}:${exp.col}`);
return x;
}
function div(x) {
if (num(x) === 0)
throw new Error(`Divide by zero at ${exp.line}:${exp.col}`);
return x;
}
switch (op) {
case "+":
return num(a) + num(b);
case "-":
return num(a) - num(b);
case "*":
return num(a) * num(b);
case "/":
return num(a) / div(b);
case "%":
return num(a) % div(b);
case "&&":
return a !== false && b;
case "||":
return a !== false ? a : b;
case "<":
return num(a) < num(b);
case ">":
return num(a) > num(b);
case "<=":
return num(a) <= num(b);
case ">=":
return num(a) >= num(b);
case "==":
return a === b;
case "!=":
return a !== b;
}
throw new Error(`Can't apply operator "${op}" at ${exp.line}:${exp.col}`);
}
function make_function(env, exp) {
function err(arg,exp){
throw new Error(`Argument ${arg} not provided to function at ${exp.line}:${exp.col}`)
}
function fn(callback) {
GUARD(fn, arguments);
let names = exp.vars;
let scope = env.extend();
for (let i = 0; i < names.length; ++i) {
scope.def(names[i].value, i + 1 < arguments.length ? arguments[i + 1] : false , {
immutable: names[i].immutable,
force: true,
});
}
cpsEvaluate(exp.body, scope, callback);
}
fn.len = exp.vars.length + 1;
if (exp.name) {
env = env.extend();
env.def(exp.name.value, fn);
}
return fn;
}
GUARD(cpsEvaluate, arguments);
switch (exp.type) {
case "number":
case "string":
case "bool":
callback(exp.value);
return;
case "var":
callback(env.get(exp.value,exp));
return;
case "define":
if (exp.left.type != "var")
throw new Error(`Cannot assign to ${exp.left.type} at ${exp.line}:${exp.col}`);
cpsEvaluate(exp.right, env, function CC(right) {
GUARD(CC, arguments);
callback(env.def(exp.left.value, right, {
immutable: exp.varient == "immutable",
force: false
},exp));
});
return;
case "let":
(function loop(env, i) {
if (i < exp.vars.length) {
let v = exp.vars[i];
if (v.def) {
cpsEvaluate(v.def, env, function CC(val) {
let scope = env.extend();
scope.def(v.name.value, val, {
immutable: v.name.immutable
},exp);
loop(scope, i + 1);
});
} else {
let scope = env.extend();
scope.def(v.name.value, false, {
immutable: v.name.immutable
},exp);
loop(scope, i + 1);
}
} else {
cpsEvaluate(exp.body, env, callback);
}
})(env, 0);
return;
case "assign":
if (exp.left.type != "var")
throw new Error(`Cannot assign to ${exp.left.type}`);
cpsEvaluate(exp.right, env, function CC(right) {
GUARD(CC, arguments);
callback(env.set(exp.left.value, right,env));
});
return;
case "binary":
cpsEvaluate(exp.left, env, function CC(left) {
GUARD(CC, arguments);
cpsEvaluate(exp.right, env, function CC(right) {
GUARD(CC, arguments);
callback(apply_op(exp.operator, left, right));
});
});
return;
case "function":
callback(make_function(env, exp));
return;
case "if":
cpsEvaluate(exp.cond, env, function CC(cond) {
GUARD(CC, arguments);
if (cond !== false) cpsEvaluate(exp.then, env, callback);
else if (exp.else) cpsEvaluate(exp.else, env, callback);
else callback(false);
});
return;
case "block":
let scope = exp.global ? env : env.extend();
(function loop(last, i) {
GUARD(loop, arguments);
if (i < exp.prog.length) {
cpsEvaluate(exp.prog[i], scope, function CC(val) {
GUARD(CC, arguments);
loop(val, i + 1);
});
} else {
callback(last);
}
})(false, 0);
return;
case "call":
cpsEvaluate(exp.func, env, function CC(func) {
GUARD(CC, arguments);
(function loop(args, i) {
GUARD(loop, arguments);
if (i < exp.args.length) {
cpsEvaluate(exp.args[i], env, function CC(arg) {
GUARD(CC, arguments);
args[i + 1] = arg;
loop(args, i + 1);
});
} else {
console.log(func,func.length)
if(args.length < (func.len || func.length))
throw new Error(`Not enough arguments provided to function at ${exp.line}:${exp.col}`)
func(...args);
}
})([callback], 0);
});
return;
case "not":
cpsEvaluate(exp.value,env,function CC(val){
if(typeof val != 'boolean')
throw new Error(`'!' expects a boolean value, not "${typeof val}" at ${exp.line}:${exp.col}`);
callback(!val);
});
return;
default:
throw new Error(`Cannot evaluate ${JSON.stringify(exp)} at ${exp.line}:${exp.col}`);
}
}
function Execute(f, args) {
while (true) try {
STACKLEN = 200;
return f(...args);
} catch (e) {
if (e instanceof Continuation) {
f = e.f;
args = e.args;
} else {
throw e;
}
}
}
// editor
let editor = document.getElementById('editor');
let output = document.getElementById('output');
CodeMirror.defineMode("lang", function() {
let c = TokenStreamCommon;
function token(stream, state) {
function read_comment(next) {
stream.eatWhile(ch => ch !== '\n');
return "comment";
}
function read_comment_m(next) {
stream.next();
state.comment_m = true;
while (!stream.eol()) {
if (ch == "*" && next == "/") {
state.comment_m = false;
stream.next();
return "comment";
}
ch = stream.next();
next = stream.peek();
}
return "comment";
}
function read_string(ch) {
state.string = ch;
if (stream.peek() == ch) {
stream.next();
state.string = false;
return "string";
}
if (stream.peek() == "\\") {
state.escape = true;
return "string";
}
while (!stream.eol()) {
let ch = stream.next();
let next = stream.peek();
if (ch != "\\" && next == state.string) {
stream.next();
state.string = false;
state.escape = false;
return "string";
}
if (next == "\\") {
state.escape = true;
return "string";
}
}
return "string";
}
function read_number(ch) {
let has_dot = ch == ".";
let number = stream.eatWhile(function(ch) {
if (ch == ".") {
if (has_dot) return false;
has_dot = true;
return true;
}
return c.is_digit(ch);
});
return "number";
}
function read_ident() {
stream.eatWhile(c.is_id);
let curr = stream.current().trim();
let cState = state.varState[state.varState.length - 1];
if (c.is_keyword(curr)) {
if (curr == "def" || (curr == "mut" && state.context === null)) {
state.context = "def";
}
if (curr == "fn") {
state.context = "fn";
state.varState.push(JSON.parse(JSON.stringify(cState)));
cState = state.varState[state.varState.length - 1];
}
if (curr == "->") {
state.context = null;
state.prev = curr;
}
if (curr == "let") {
state.context = "let";
state.varState.push(JSON.parse(JSON.stringify(cState)));
cState = state.varState[state.varState.length - 1];
}
return "keyword";
}
if (state.context == "def") {
state.context = null;
if (!cState.includes(curr)) cState.push(curr);
return "def";
}
if (state.context == "fn" || state.context == "let") {
cState.push(curr);
return "def";
}
if (cState.includes(curr)) return "variable-2";
return "variable-1";
}
function read_punc() {
let cState = state.varState[state.varState.length - 1];
let curr = stream.current().trim();
if (curr == "{") {
if (state.prev != '->') {
state.varState.push(JSON.parse(JSON.stringify(cState)));
} else {
state.prev = null;
state.context = null;
}
}
if (curr == "," && state.context == "letexp") {
state.context = "let";
}
if (curr == "}" && state.varState.length > 1) {
state.varState.pop();
}
if ("(){}[]".indexOf(curr) >= 0) {
return "bracket";
}
return;
}
function read_op() {
stream.eatWhile(c.is_op);
if (state.context == "let") {
state.context = "letexp";
}
return "operator";
}
stream.eatSpace();
let ch = stream.next();
let next = stream.peek();
if (state.comment_m) {
while (!stream.eol()) {
if (ch == "*" && next == "/") {
state.comment_m = false;
stream.next();
return "comment";
}
ch = stream.next();
next = stream.peek();
}
return "comment";
}
if (state.escape) {
stream.next();
state.escape = false;
return "atom";
}
if (state.string) {
if (ch == state.string) {
state.string = false;
return "string";
}
while (!stream.eol()) {
if (ch != "\\" && next == state.string) {
stream.next();
state.string = false;
return "string";
}
if (ch == "\\") {
state.escape = true;
return "string";
}
ch = stream.next();
next = stream.peek();
}
return "string";
}
if (ch == "#") {
return read_comment();
}
if (ch == "/" && next == "*") {
return read_comment_m();
}
if (ch == "/" && next == "/") {
return read_comment();
}
if (ch == "'" || ch == '"') return read_string(ch);
if (c.is_digit(ch)) return read_number(ch);
if (c.is_id_start(ch) || (ch == "-" && next == ">")) return read_ident();
if (c.is_punc(ch)) return read_punc();
if (c.is_op(ch)) return read_op();
if (ch == "?") return "atom";
stream.skipToEnd();
return "error";
}
return {
startState() {
return {
comment_m: false,
string: false,
context: null,
escape: false,
varState: [
Object.keys(makeEnv().vars)
],
prev: null,
};
},
copyState(state) {
return JSON.parse(JSON.stringify(state));
},
indent(state, text) {
let indent = state.varState.length - 1;
if (text.includes("}")) indent = state.varState.length - 2;
return editor.options.indentUnit * indent;
},
token
};
});
editor = CodeMirror(editor, {
value: document.getElementById('example').innerText,
lineNumbers: true,
theme: "railscasts",
lineWrapping: true,
scrollbarStyle: "overlay",
matchBrackets: true,
autoCloseBrackets: true,
styleActiveLine: true,
mode: "lang",
tabSize: 2,
extraKeys: {
["Ctrl-Enter"]() {
display();
},
["Ctrl-L"]() {
output.setValue('');
}
}
});
editor.widgets = [];
CodeMirror.defineMode("console", function() {
return {
token: function(stream) {
if (stream.match("Error: ")) {
stream.skipToEnd();
return "error";
} else if (stream.match("*** Result: ")) {
stream.skipToEnd();
return "variable-2";
}
stream.skipToEnd();
return "comment";
}
};
});
output = CodeMirror(output, {
readOnly: "nocursor",
theme: "railscasts",
lineWrapping: true,
scrollbarStyle: "overlay",
mode: "console"
});
let run = document.getElementById('run');
function display() {
// clear errors
editor.widgets.forEach(w => w.clear());
editor.widgets = [];
// get Token stream
let code = editor.getValue();
code = InputStream(code, editor);
code = TokenStream(code);
// define environment
let globalEnv = makeEnv();
function print(txt) {
if (typeof txt !== "string") txt = txt.toString();
output.setValue(output.getValue() + txt);
}
// make ast and eval
let x = parse(code);
window.x = x;
window.onerror = function(msg, source, line, col, e) {
if (!(output.getValue().endsWith('\n') || output.getValue() === "")) print('\n');
print(e);
window.e = e;
};
if (!(output.getValue().endsWith('\n') || output.getValue() === "")) print('\n');
Execute(cpsEvaluate, [stdEnv(x), globalEnv, function(result) {
if (!(output.getValue().endsWith('\n') || output.getValue() === "")) print('\n');
print(`*** Result: ${result}\n`);
}]);
}
run.addEventListener('click', display);
display();
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.23.0/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.23.0/addon/scroll/simplescrollbars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.23.0/addon/edit/matchbrackets.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.23.0/addon/edit/closebrackets.min.js"></script>
html,body {
padding:0;
height:100%;
width:100%;
}
.CodeMirror {
height:100%;
}
.CodeMirror-overlayscroll-vertical div {
background-color:#8F938F;
}
.CodeMirror div div.CodeMirror-selected { //increase specificity
background-color:black!important;
}
#editor,#output {
width:50%;
float:left;
height:100%;
box-sizing:border-box;
}
#output {
height: calc(100% - 35px);
white-space: pre-wrap;
word-break: break-word;
font-family:monospace;
overflow:auto;
background-color: rgb(44, 40, 39);
border-left: 5px solid #34302f;
box-sizing:border-box;
color:#8F938F;
}
#run {
position:absolute;
width:50%;
height:35px;
right:0;
bottom:0;
box-sizing:border-box;
line-height:30px;
text-align:center;
border-left: 5px solid #34302f;
border-top: 5px solid #34302f;
background-color: rgb(44, 40, 39);
color:#8F938F;
}
.err {
background-color:red;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.23.0/codemirror.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.23.0/addon/scroll/simplescrollbars.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.23.0/theme/railscasts.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment