Last active
July 1, 2019 14:58
-
-
Save OverlappingElvis/fa66977abe65742c3286130ba1f50b40 to your computer and use it in GitHub Desktop.
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
const chevrotain = require(`chevrotain`) | |
const { Lexer, Parser } = chevrotain | |
const allTokens = [] | |
const createToken = (options) => { | |
const token = chevrotain.createToken(options) | |
allTokens.push(token) | |
return token | |
} | |
const WhiteSpace = createToken({ | |
name: `WhiteSpace`, | |
pattern: /\s+/, | |
group: Lexer.SKIPPED | |
}) | |
const On = createToken({ | |
name: `On`, | |
pattern: /on/ | |
}) | |
const Off = createToken({ | |
name: `Off`, | |
pattern: /off/ | |
}) | |
const Brightness = createToken({ | |
name: `Brightness`, | |
pattern: /brightness/ | |
}) | |
const Color = createToken({ | |
name: `Color`, | |
pattern: /color/ | |
}) | |
const Wait = createToken({ | |
name: `Wait`, | |
pattern: /wait/ | |
}) | |
const Alias = createToken({ | |
name: `Alias`, | |
pattern: /alias/ | |
}) | |
const NUMBER = createToken({ | |
name: `NUMBER`, | |
pattern: /\d+/ | |
}) | |
const HEX_COLOR = createToken({ | |
name: `HEX_COLOR`, | |
pattern: /#[a-fA-F0-9]{6}/ | |
}) | |
const IDENTIFIER = createToken({ | |
name: `IDENTIFIER`, | |
pattern: /[a-zA-Z]+/ | |
}) | |
const LightLexer = new Lexer(allTokens) | |
class LightParser extends Parser { | |
constructor () { | |
super(allTokens) | |
const $ = this | |
$.RULE(`program`, () => { | |
$.MANY(() => { | |
$.SUBRULE($.statement) | |
}) | |
}) | |
$.RULE(`statement`, () => { | |
$.OR([ | |
{ | |
ALT: () => $.SUBRULE($.onStatement) | |
}, | |
{ | |
ALT: () => $.SUBRULE($.offStatement) | |
}, | |
{ | |
ALT: () => $.SUBRULE($.colorStatement) | |
}, | |
{ | |
ALT: () => $.SUBRULE($.brightnessStatement) | |
}, | |
{ | |
ALT: () => $.SUBRULE($.waitStatement) | |
}, | |
{ | |
ALT: () => $.SUBRULE($.aliasStatement) | |
} | |
]) | |
}) | |
$.RULE(`onStatement`, () => { | |
$.CONSUME(On) | |
}) | |
$.RULE(`offStatement`, () => { | |
$.CONSUME(Off) | |
}) | |
$.RULE(`colorStatement`, () => { | |
$.CONSUME(Color) | |
$.OR([ | |
{ | |
ALT: () => $.CONSUME(HEX_COLOR) | |
}, | |
{ | |
ALT: () => $.CONSUME(IDENTIFIER) | |
} | |
]) | |
}) | |
$.RULE(`brightnessStatement`, () => { | |
$.CONSUME(Brightness) | |
$.OR([ | |
{ | |
ALT: () => $.CONSUME(NUMBER) | |
}, | |
{ | |
ALT: () => $.CONSUME(IDENTIFIER) | |
} | |
]) | |
}) | |
$.RULE(`waitStatement`, () => { | |
$.CONSUME(Wait) | |
$.OR([ | |
{ | |
ALT: () => $.CONSUME(NUMBER) | |
}, | |
{ | |
ALT: () => $.CONSUME(IDENTIFIER) | |
} | |
]) | |
}) | |
$.RULE(`aliasStatement`, () => { | |
$.CONSUME(Alias) | |
$.CONSUME(IDENTIFIER) | |
$.OR([ | |
{ | |
ALT: () => $.CONSUME(HEX_COLOR) | |
}, | |
{ | |
ALT: () => $.CONSUME(NUMBER) | |
} | |
]) | |
}) | |
$.performSelfAnalysis() | |
} | |
} | |
const parser = new LightParser() | |
const lexed = LightLexer.tokenize(`alias green #00ff00 alias blue #0000ff alias delay 600 brightness 75 on wait delay color green wait delay color blue wait delay off`) | |
if (lexed.errors.length) { | |
console.log(`Lexer error!`) | |
console.log(lexed.errors) | |
throw new Error() | |
} | |
parser.input = lexed.tokens | |
const cst = parser.program() | |
if (parser.errors.length) { | |
console.log(`Parser error!`) | |
console.log(parser.errors) | |
throw new Error() | |
} | |
const BaseCstVisitor = parser.getBaseCstVisitorConstructor() | |
class LightInterpreter extends BaseCstVisitor { | |
constructor () { | |
super() | |
this.light = { | |
on: false, | |
brightness: 100, | |
color: `#ffffff` | |
} | |
this.scope = {} | |
this.validateVisitor() | |
} | |
program (context) { | |
for (const statement of context.statement) { | |
this.visit(statement) | |
} | |
} | |
statement (context) { | |
if (context.onStatement) { | |
this.visit(context.onStatement) | |
} else if (context.offStatement) { | |
this.visit(context.offStatement) | |
} else if (context.colorStatement) { | |
this.visit(context.colorStatement) | |
} else if (context.brightnessStatement) { | |
this.visit(context.brightnessStatement) | |
} else if (context.waitStatement) { | |
this.visit(context.waitStatement) | |
} else if (context.aliasStatement) { | |
this.visit(context.aliasStatement) | |
} | |
} | |
onStatement (context) { | |
console.log(`turning light on (light was ${this.light.on ? `on` : `off`})`) | |
this.light.on = true | |
} | |
offStatement (context) { | |
console.log(`turning light off (light was ${this.light.on ? `on` : `off`})`) | |
this.light.on = false | |
} | |
colorStatement (context) { | |
const color = context.IDENTIFIER ? this.scope[context.IDENTIFIER[0].image] : context.HEX_COLOR[0].image | |
console.log(`setting color to ${color} (color was ${this.light.color})`) | |
this.light.color = color | |
} | |
brightnessStatement (context) { | |
const value = context.IDENTIFIER ? this.scope[context.IDENTIFIER[0].image] : parseInt(context.NUMBER[0].image, 10) | |
console.log(`brightness was ${this.light.brightness}`) | |
console.log(`setting brightness to ${value}`) | |
this.light.brightness = value | |
} | |
waitStatement (context) { | |
const value = context.IDENTIFIER ? this.scope[context.IDENTIFIER[0].image] : parseInt(context.NUMBER[0].image, 10) | |
console.log(`waiting ${value}`) | |
} | |
aliasStatement (context) { | |
const identifier = context.IDENTIFIER[0].image | |
console.log(`setting ${identifier} to ${context.NUMBER ? context.NUMBER[0].image : context.HEX_COLOR[0].image}`) | |
if (context.NUMBER) { | |
this.scope[identifier] = parseInt(context.NUMBER[0].image, 10) | |
} else { | |
this.scope[identifier] = context.HEX_COLOR[0].image | |
} | |
} | |
} | |
const interpreter = new LightInterpreter() | |
interpreter.visit(cst) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment