Created
December 30, 2015 02:09
-
-
Save mariusGundersen/0f390c538ae78abc8ff6 to your computer and use it in GitHub Desktop.
DSL with operator overloading in ES2015
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 Vector{ | |
constructor(x, y){ | |
this.x = x; | |
this.y = y; | |
} | |
[Symbol['+']](that){ | |
return new Vector(this.x + that.x, this.y + that.y); | |
} | |
[Symbol['-']](that){ | |
return new Vector(this.x - that.x, this.y - that.y); | |
} | |
[Symbol['*']](that){ | |
return new Vector(this.x * that, this.y * that); | |
} | |
[Symbol['/']](that){ | |
return new Vector(this.x / that, this.y / that); | |
} | |
static math(chunks, ...operands){ | |
const tokens = lexer(chunks, operands); | |
const astRoot = new Expression(tokens); | |
return astRoot.calculate(); | |
} | |
} | |
let a = new Vector(1,2); | |
let b = new Vector(1,3); | |
let k = 5; | |
let c = Vector.math`(${a} + ${b}) * ${k} - ${b}`; | |
console.log(c); //{"x":9,"y":22} |
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
Symbol['+'] = Symbol('+'); | |
Symbol['-'] = Symbol('-'); | |
Symbol['*'] = Symbol('*'); | |
Symbol['/'] = Symbol('/'); | |
function*lexer(chunks, operands){ | |
for(let chunk of chunks){ | |
for(let token of chunk.split('').map(token => token.trim()).filter(token => token)){ | |
while((yield token) === 'revert') yield null; | |
} | |
if(operands.length){ | |
const operand = operands.shift(); | |
while((yield operand) === 'revert') yield null; | |
} | |
} | |
} | |
class AstNode{ | |
constructor(tokens){ | |
this.tokens = tokens; | |
} | |
get nextToken(){ | |
const token = this.tokens.next(); | |
if(token.done) throw new Error('Unexpected end of expression'); | |
return token.value; | |
} | |
assertIs(...expected){ | |
const token = this.tokens.next(); | |
if(token.done) throw new Error(`Unexpected end of expression, expected ${expected}`); | |
if(!expected.includes(token.value)) throw new Error(`Unexpected token ${JSON.stringify(token.value)}, expected ${expected}`); | |
return token.value; | |
} | |
maybeIs(...expected){ | |
const token = this.tokens.next(); | |
if(token.done) return {ok:false}; | |
if(expected.includes(token.value)) return {ok:true, operator:token.value}; | |
this.tokens.next('revert'); | |
return {ok:false}; | |
} | |
} | |
class Expression extends AstNode{ | |
constructor(tokens){ | |
super(tokens); | |
const factor = new Factor(tokens); | |
const {operator, ok} = this.maybeIs('+', '-'); | |
if(!ok){ | |
return factor; | |
} | |
this.left = factor; | |
this.operator = Symbol[operator]; | |
this.right = new Factor(tokens); | |
} | |
calculate(){ | |
return this.left.calculate()[this.operator](this.right.calculate()); | |
} | |
} | |
class Factor extends AstNode{ | |
constructor(tokens){ | |
super(tokens); | |
const value = new Value(tokens); | |
const {operator, ok} = this.maybeIs('*', '/'); | |
if(!ok){ | |
return value; | |
} | |
this.left = value; | |
this.operator = Symbol[operator]; | |
this.right = new Value(tokens); | |
} | |
calculate(){ | |
return this.left.calculate()[this.operator](this.right.calculate()); | |
} | |
} | |
class Value extends AstNode{ | |
constructor(tokens){ | |
super(tokens) | |
const token = this.nextToken; | |
if(token == '('){ | |
const value = new Expression(tokens); | |
this.assertIs(')'); | |
return value; | |
}else if(token instanceof Vector){ | |
this.value = token | |
}else if(typeof token == 'number'){ | |
this.value = token | |
}else{ | |
throw new Error('unexpected token '+token); | |
} | |
} | |
calculate(){ | |
return this.value; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment