Skip to content

Instantly share code, notes, and snippets.

@mariusGundersen
Created December 30, 2015 02:09
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 mariusGundersen/0f390c538ae78abc8ff6 to your computer and use it in GitHub Desktop.
Save mariusGundersen/0f390c538ae78abc8ff6 to your computer and use it in GitHub Desktop.
DSL with operator overloading in ES2015
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}
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