Last active
May 23, 2016 19:55
-
-
Save PaulMaynard/1e92a3f55844fadf60ce5c2957e44912 to your computer and use it in GitHub Desktop.
Norwest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Scope { | |
constructor(vars, parents) { | |
this.vars = vars || new Map() | |
this.parents = parents || [Scope.global] | |
} | |
get(name) { | |
if(this.vars.has(name)) { | |
return this.vars.get(name) | |
} else if(this.parents.length > 0) { | |
for(let p of this.parents) { | |
if(p.has(name)) { | |
return p.get(name) | |
} | |
} | |
return nil | |
} else { | |
return nil | |
} | |
} | |
set(name, val) { | |
if(this.vars.has(name)) { | |
this.vars.set(name, val) | |
} else if(this.parents.length > 0) { | |
for(let p of this.parents) { | |
if(p.has(name)) { | |
p.set(name) | |
return | |
} | |
} | |
} | |
} | |
has(name) { | |
if(this.vars.has(name)) { | |
return true | |
} else if(this.parents.length > 0) { | |
for(let p of this.parents) { | |
if(p.has(name)) { | |
return true | |
} else { | |
return false | |
} | |
} | |
} else { | |
return false | |
} | |
} | |
def(name, val = nil) { | |
this.vars.set(name, val) | |
} | |
debug() { | |
let o = {} | |
for(let [k, v] of this.vars) { | |
o[k.value] = v.value | |
} | |
//o.Parents = this.parents.map(p => p.debug()) | |
return o | |
} | |
} | |
class Value { | |
constructor(val, type) { | |
this.value = val | |
this.type = type | |
} | |
} | |
let nil = new Value(null, 'Nil') | |
function Name(name) { | |
if(Name.symbols.has(name)) { | |
return Name.symbols.get(name) | |
} else { | |
let val = new Value(name, 'Name') | |
Name.symbols.set(name, val) | |
return val | |
} | |
} | |
Name.symbols = new Map() | |
class Block extends Value { | |
constructor(exprs, lexScope) { | |
super(exprs, 'Block') | |
this.run = (runScope = new Scope()) => { | |
let s = new Scope(new Map(), [runScope, lexScope]) | |
let r = nil | |
for(let e of this.value) { | |
r = e.exec(s) | |
} | |
return r | |
} | |
} | |
} | |
class JSFunc extends Value { | |
constructor(func) { | |
super(func, 'Function') | |
} | |
call(args, scope) { | |
return this.value(...args) | |
} | |
} | |
class ScopeFunc extends JSFunc { | |
constructor(func) { | |
super(func) | |
} | |
call(args, scope) { | |
return this.value(args, scope) | |
} | |
} | |
class Expression { | |
} | |
class Literal extends Expression { | |
constructor(val) { | |
super() | |
this.value = val | |
} | |
exec() { | |
return this.value | |
} | |
} | |
class Ref extends Expression { | |
constructor(name) { | |
super() | |
this.name = name | |
} | |
exec(scope) { | |
return scope.get(this.name) | |
} | |
} | |
class CallExpr extends Expression { | |
constructor(func, args) { | |
super() | |
this.func = func | |
this.args = args | |
} | |
exec(scope) { | |
return this.func.exec(scope).call(this.args.map(a => a.exec(scope)), scope) | |
} | |
} | |
class BlockExpr extends Expression { | |
constructor(exprs) { | |
super() | |
this.exprs = exprs | |
} | |
exec(scope) { | |
return new Block(this.exprs, scope) | |
} | |
} | |
function build(node) { | |
return build.types[node.Type](node) | |
} | |
build.types = { | |
ref({name}) { | |
return new Ref(Name(name)) | |
}, | |
literal({Class, value}) { | |
return new Literal(build.classes[Class](value)) | |
}, | |
block({exprs}) { | |
return new BlockExpr(exprs.map(build)) | |
}, | |
call({func, args}) { | |
return new CallExpr(build(func), args.map(build)) | |
} | |
} | |
build.classes = { | |
number(num) { | |
return new Value(num, 'Number') | |
}, | |
string(str) { | |
return new Value(str, 'String') | |
}, | |
name(n) { | |
return Name(n) | |
}, | |
} | |
Scope.global = new Scope(new Map([ | |
[Name('nil'), nil], | |
[new Name('run'), new JSFunc((block, scope) => { | |
return block.run(scope || new Scope()) | |
})], | |
[new Name('list'), new JSFunc((...elems) => new Value(elems, 'List'))], | |
// Scopes | |
[new Name('scope'), new ScopeFunc(([vars, parents], scope) => { | |
if(vars) { | |
return new Value(new Scope(new Map(vars.value.map(v => v.value)), []), 'Scope') | |
} | |
return new Value(scope, "Scope") | |
})], | |
[new Name('def'), new ScopeFunc(([name, val, scope], defscope) => | |
((scope && scope.value) || defscope).def(name, val))], | |
[new Name('get'), new ScopeFunc(([name, scope], defscope) => | |
((scope && scope.value) || defscope).get(name))], | |
[new Name('set'), new ScopeFunc(([name, val, scope], defscope) => | |
((scope && scope.value) || defscope).set(name, val))], | |
// Math | |
[Name('+'), new JSFunc((...args) => | |
new Value(args.reduce((a, b) => a + b.value, 0), 'Number'))], | |
[Name('-'), new JSFunc((a, b) => new Value(a.value - b.value, 'Number'))], | |
[Name('*'), new JSFunc((a, b) => | |
new Value(args.reduce((a, b) => a * b.value, 1), 'Number'))], | |
[Name('/'), new JSFunc((a, b) => new Value(a.value / b.value, 'Number'))], | |
// Boolean | |
[Name('true'), new Value(true, 'Boolean')], | |
[Name('false'), new Value(true, 'Boolean')], | |
[Name('|'), new JSFunc((a, b) => new Value(a.value || b.value, 'Boolean'))], | |
[Name('&'), new JSFunc((a, b) => new Value(a.value && b.value, 'Boolean'))], | |
[Name('!'), new JSFunc(a => new Value(!a.value, 'Boolean'))], | |
]), []) | |
let scope = new Scope(new Map([ | |
]), [Scope.global]) | |
scope.def(Name('foo'), new Value(123, 'Number')) | |
console.log(build( | |
{ | |
"Type": "call", | |
"func": { | |
"Type": "ref", | |
"name": "run" | |
}, | |
"args": [ | |
{ | |
"Type": "block", | |
"exprs": [ | |
{ | |
"Type": "call", | |
"func": { | |
"Type": "ref", | |
"name": "def" | |
}, | |
"args": [ | |
{ | |
"Type": "literal", | |
"Class": "name", | |
"value": "bar" | |
}, | |
{ | |
"Type": "literal", | |
"Class": "string", | |
"value": "baz" | |
} | |
] | |
} | |
] | |
} | |
] | |
} | |
).exec(scope)) | |
console.log(scope.debug()) | |
console.log('\n\n\n') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Program | |
= _ exprs:(e:Expression _ {return e})* { | |
return { | |
Type: 'call', | |
func: {Type: 'ref', name: 'run'}, | |
args: [ | |
{Type: 'block', exprs} | |
] | |
} | |
} | |
Expression | |
= Call | |
/ Block | |
/ List | |
/ Get | |
/ value:Value { | |
return {Type: 'literal', Class: value.Class, value: value.value} | |
} | |
/ Ref | |
Call | |
= lparen _ func:Expression args:(space e:Expression {return e})* _ rparen { | |
return {Type: 'call', func, args} | |
} | |
Value | |
= Number | |
/ String | |
/ Name | |
Number | |
= num:$([+-]?[0-9]+('.'[0-9]+)?) { | |
return {Class: 'number', value: Number(num, 10)} | |
} | |
String | |
= quote chars:$[^"]+ quote { | |
return {Class: 'string', value: chars} | |
} | |
Name | |
= colon name:Identifier { | |
return {Class: 'name', value: name} | |
} | |
Block | |
= lbrace _ exprs:(e:Expression _ {return e})* rbrace { | |
return {Type: 'block', exprs} | |
} | |
List | |
= lbracket _ first: Expression? rest:(space e:Expression {return e})* _ rbracket { | |
return { | |
Type: 'call', | |
func: {Type: 'ref', name: 'list'}, | |
args: [first].filter(x => !!x).concat(rest) | |
} | |
} | |
Get | |
= scope:Identifier colon name:Identifier { | |
return { | |
Type: 'call', | |
func: {Type: 'ref', name: 'get'}, | |
args: [ | |
{ | |
Type: 'literal', | |
Class: 'name', | |
value: name | |
}, | |
{Type: 'ref', name: scope} | |
] | |
} | |
} | |
Ref | |
= name:Identifier { | |
return {Type: 'ref', name} | |
} | |
Identifier | |
= $[^ \t\n\r:\(\)\{\}\[\]"]+ | |
lparen = '(' | |
rparen = ')' | |
lbrace = '{' | |
rbrace = '}' | |
lbracket = '[' | |
rbracket = ']' | |
quote = '"' | |
colon = ':' | |
space = [ \t\n\r]+ | |
_ "whitespace" = space? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment