From 2fd7388f294f3cfd95d8723aaf8cc5352da4f8ac Mon Sep 17 00:00:00 2001 From: Daniel Berezin Date: Wed, 1 May 2019 22:21:08 -0700 Subject: [PATCH] Added prettier --- .prettierrc.js | 12 ++++++ dist/poem.min.js | 3 -- dist/poem.min.map | 1 - environment.js | 10 ++--- errors.js | 22 ++++++---- index.js | 34 ++++++++------- interpreter.js | 51 ++++++++++++++-------- interpreter.test.js | 45 ++++++++------------ package-lock.json | 6 +++ package.json | 7 +++- parser.js | 77 +++++++++++++++++++++------------- parser.test.js | 36 +++------------- tokenizer.js | 100 +++++++++++++++++++++++++------------------- tokenizer.test.js | 26 +++++++----- types.js | 21 +++++----- 15 files changed, 249 insertions(+), 202 deletions(-) create mode 100644 .prettierrc.js delete mode 100644 dist/poem.min.js delete mode 100644 dist/poem.min.map diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..02250d2 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,12 @@ +module.exports = { + bracketSpacing: true, + insertPragma: false, + parser: 'babel', + printWidth: 100, + proseWrap: 'preserve', + requirePragma: false, + semi: false, + singleQuote: true, + tabWidth: 2, + useTabs: false +} diff --git a/dist/poem.min.js b/dist/poem.min.js deleted file mode 100644 index 6a60eb7..0000000 --- a/dist/poem.min.js +++ /dev/null @@ -1,3 +0,0 @@ -parcelRequire=function(e,r,n,t){var i="function"==typeof parcelRequire&&parcelRequire,o="function"==typeof require&&require;function u(n,t){if(!r[n]){if(!e[n]){var f="function"==typeof parcelRequire&&parcelRequire;if(!t&&f)return f(n,!0);if(i)return i(n,!0);if(o&&"string"==typeof n)return o(n);var c=new Error("Cannot find module '"+n+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[n][1][r]||r};var l=r[n]=new u.Module(n);e[n][0].call(l.exports,p,l,l.exports,this)}return r[n].exports;function p(e){return u(p.resolve(e))}}u.isParcelRequire=!0,u.Module=function(e){this.id=e,this.bundle=u,this.exports={}},u.modules=e,u.cache=r,u.parent=i,u.register=function(r,n){e[r]=[function(e,r){r.exports=n},{}]};for(var f=0;f(\w+)articles< >nouns< >linkingVerbs< >adjectives<")+"\n",t%5==0&&(e+="\n");return e+"\n By Daniel Berezin and Vivian Nghiem, "+(new Date).getFullYear()};alert(l()); -},{}]},{},["6TDu"], null) \ No newline at end of file diff --git a/dist/poem.min.map b/dist/poem.min.map deleted file mode 100644 index 7ddaaba..0000000 --- a/dist/poem.min.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["poem.js"],"names":["pick","array","Math","floor","random","length","subjects","replace","split","articles","linkingVerbs","adjectives","nouns","grammarGenerator","str","match","output","test","exec","val","parse","generatePoem","i","Date","getFullYear","alert"],"mappings":";AA6CA,IAAA,EAAA,WAAA,OAAA,SAAA,EAAA,GAAA,GAAA,MAAA,QAAA,GAAA,OAAA,EAAA,GAAA,OAAA,YAAA,OAAA,GAAA,OAAA,SAAA,EAAA,GAAA,IAAA,EAAA,GAAA,GAAA,EAAA,GAAA,EAAA,OAAA,EAAA,IAAA,IAAA,IAAA,EAAA,EAAA,EAAA,OAAA,cAAA,GAAA,EAAA,EAAA,QAAA,QAAA,EAAA,KAAA,EAAA,QAAA,GAAA,EAAA,SAAA,GAAA,GAAA,IAAA,MAAA,GAAA,GAAA,EAAA,EAAA,EAAA,QAAA,KAAA,GAAA,EAAA,QAAA,EAAA,SAAA,QAAA,GAAA,EAAA,MAAA,GAAA,OAAA,EAAA,CAAA,EAAA,GAAA,MAAA,IAAA,UAAA,yDAAA,GA3CMA,EAAO,SAAA,GAASC,OAAAA,EAAMC,KAAKC,MAAMD,KAAKE,SAAWH,EAAMI,UACvDC,EAAW,uGACdC,QAAQ,MAAO,IACfC,MAAM,KACHC,EAAW,gGACdF,QAAQ,MAAO,IACfC,MAAM,KACHE,EAAe,sGAClBH,QAAQ,MAAO,IACfC,MAAM,KACHG,EAAa,sPAChBJ,QAAQ,MAAO,IACfC,MAAM,KACHI,EAAQ,8hCACXL,QAAQ,MAAO,IACfC,MAAM,KAEHK,EAAmB,CAAA,SAAA,EAAA,SAAA,EAAA,aAAA,EAAA,WAAA,EAAA,MAAA,EAMhB,MAAA,SAACC,GAGCC,IAFHC,IAAAA,EAASF,EACPC,EAAQ,UACPA,EAAME,KAAKD,IAAS,CACRD,IAAAA,EAAAA,EAAMG,KAAKF,GADH,EAAA,EAAA,EAAA,GACfG,GADe,EAAA,GAAA,EAAA,IAEhBH,EAAAA,EAAOT,QAAQQ,EAAOF,EAAiBO,MAAMpB,EAAKa,EAAiBM,MAEvEH,OAAAA,IAILK,EAAe,WAEd,IADDP,IAAAA,EAAM,GACDQ,EAAI,EAAGA,EAAI,GAAIA,IACfT,GAAAA,EAAiBO,MAAM,kDAAoD,KAC9EE,EAAI,GAAM,IAAGR,GAAO,MAEnBA,OAAAA,EAAkD,4CAAA,IAAIS,MAAQC,eAGvEC,MAAMJ","file":"poem.min.map","sourceRoot":"..","sourcesContent":["// Ref https://codereview.stackexchange.com/questions/156874/node-js-automatic-poem-generator\n\nconst pick = array => array[Math.floor(Math.random() * array.length)]\nconst subjects = `I,You,Daniel,Vivian,Bo,Yuki,Jing,Cotton,Vianna,Vivonda,The Stranger,The Liar,Harry Potter,Dumbledore`\n .replace(/\\n/g, '')\n .split(',')\nconst articles = `>subjects< knew that the,>subjects< could tell that,>subjects< never expected that,All of the`\n .replace(/\\n/g, '')\n .split(',')\nconst linkingVerbs = `was,had been,will be,could be,might be,should have been,would have been,could have been,belonged to`\n .replace(/\\n/g, '')\n .split(',')\nconst adjectives = `abstract,mysterious,permanent,unfortunate,intricate,confusing,true,false,fake,a lie,a stranger,a friend,serene,confusing,an enemy,terrible,enchanting,mine,yours,his,hers,theirs,ours,fortunate,understood,mine,interesting,mutual,artistic,musical`\n .replace(/\\n/g, '')\n .split(',')\nconst nouns = `gate,enthusiasm,hair,theory,truth,security,introduction,requirement,activity,examination,dirt,marketing,town,meal,investment,classroom,sample,poetry,priority,distribution,celebration,refrigerator,employer,basis,health,food,decision,reputation,fortune,manager,poet,woman,secretary,history,potato,reflection,percentage,bath,management,response,temperature,owner,farmer,village,assistant,winner,interaction,product,night,opportunity,cabinet,inspector,protection,area,accident,union,employee,database,entry,knowledge,wealth,error,appearance,platform,competition,football,aspect,speech,device,drawer,movie,preference,inflation,personality,connection,hotel,grandmother,studio,attention,payment,cell,driver,scene,variety,appointment,relationship,penalty,category,lab,actor,community,breath,customer,uncle,revolution,disaster,media,lake,failure,safety,freedom,hearing,charity,boyfriend,effort,computer,member,conclusion,coffee,magazine,perception,beer,ability,power,session,literature,marriage,camera,sir,nation,bird,outcome,difficulty,definition,library,city`\n .replace(/\\n/g, '')\n .split(',')\n\nconst grammarGenerator = {\n subjects,\n articles,\n linkingVerbs,\n adjectives,\n nouns,\n parse: (str) => {\n let output = str\n const match = />(\\w+) {\n let str = ''\n for (let i = 0; i < 14; i++) {\n str += grammarGenerator.parse('>articles< >nouns< >linkingVerbs< >adjectives<') + '\\n'\n if (i % 5 === 0) str += '\\n'\n }\n return str + `\\n By Daniel Berezin and Vivian Nghiem, ${(new Date()).getFullYear()}`\n}\n\nalert(generatePoem())"]} \ No newline at end of file diff --git a/environment.js b/environment.js index 41cc546..22ff1cd 100644 --- a/environment.js +++ b/environment.js @@ -1,12 +1,12 @@ const { runtimeError } = require('./errors') class Environment { - constructor (enclosing) { + constructor(enclosing) { this.map = new Map() this.enclosing = enclosing } - get (varToken) { + get(varToken) { if (this.map.has(varToken.name.lexeme)) { return this.map.get(varToken.name.lexeme) } @@ -14,7 +14,7 @@ class Environment { throw runtimeError(`Undefined variable "${varToken.name.lexeme}"`, varToken.name) } - set (token, value) { + set(token, value) { if (this.map.has(token.lexeme)) { throw runtimeError(`Duplicate variable declaration "${token.lexeme}"`, token) // return this.map.set(token.lexeme, value) @@ -22,7 +22,7 @@ class Environment { return this.map.set(token.lexeme, value) } - assign (token, value) { + assign(token, value) { if (!this.map.has(token.lexeme)) { if (this.enclosing) return this.enclosing.assign(token, value) throw runtimeError(`Undefined variable "${token.lexeme}"`, token) @@ -31,4 +31,4 @@ class Environment { } } -module.exports = Environment \ No newline at end of file +module.exports = Environment diff --git a/errors.js b/errors.js index f479ede..e172e2e 100644 --- a/errors.js +++ b/errors.js @@ -1,37 +1,45 @@ const tokenizer = require('./tokenizer') const token = tokenizer.tokenEnum -const nullable = str => str ? str : '' +const nullable = str => (str ? str : '') class LoxError { - constructor (msg, startCoordinates, endCoordinates) { + constructor(msg, startCoordinates, endCoordinates) { this.msg = msg this.startCoordinates = startCoordinates this.endCoordinates = endCoordinates } - toString () { + toString() { return this.msg } } const error = (msg, startCoordinates, endCoordinates) => - new LoxError(msg, startCoordinates, endCoordinates ) + new LoxError(msg, startCoordinates, endCoordinates) const parseError = (msg, token) => { if (token.type === token.EOF) { return new LoxError(msg, token.startCoordinates, token.endCoordinates) } else { - return new LoxError(`${nullable(token.lexeme && `at "${token.lexeme}": `)}${msg}`, token.startCoordinates, token.endCoordinates) + return new LoxError( + `${nullable(token.lexeme && `at "${token.lexeme}": `)}${msg}`, + token.startCoordinates, + token.endCoordinates + ) } } const runtimeError = (msg, token) => - new LoxError(`${nullable(token.lexeme && `at "${token.lexeme}": `)}${msg}`, token.startCoordinates, token.endCoordinates) + new LoxError( + `${nullable(token.lexeme && `at "${token.lexeme}": `)}${msg}`, + token.startCoordinates, + token.endCoordinates + ) module.exports = { error, LoxError, runtimeError, parseError -} \ No newline at end of file +} diff --git a/index.js b/index.js index 0804ff1..4df9f0d 100755 --- a/index.js +++ b/index.js @@ -30,7 +30,11 @@ const run = (code, environment) => { return lastStatement } catch (e) { if (e instanceof LoxError) { - console.error('Parse Error:', e.toString(), `at ${e.endCoordinates.line}:${e.endCoordinates.col + 1}`) + console.error( + 'Parse Error:', + e.toString(), + `at ${e.endCoordinates.line}:${e.endCoordinates.col + 1}` + ) // Pre Error String const frontIndex = code.lastIndexOf('\n', e.startCoordinates.index) @@ -88,25 +92,27 @@ const runFile = filename => { const optionRegex = /--(\w+)(?:=(.+))?/ const processOptions = args => - args.map(arg => { - const match = optionRegex.exec(arg) - if (match) { - const [_, option, value] = match - if (!value) { - options[option] = !options[option] + args + .map(arg => { + const match = optionRegex.exec(arg) + if (match) { + const [_, option, value] = match + if (!value) { + options[option] = !options[option] + } else { + options[option] = value + } } else { - options[option] = value + return arg } - } else { - return arg - } - }).filter(Boolean) + }) + .filter(Boolean) const main = argv => { const args = processOptions(argv.slice(2)) if (options.debug) console.log(options) if (args.length > 1) { - console.error("Usage: jlox [script]") + console.error('Usage: jlox [script]') return 64 } else if (args.length === 1) { runFile(args[0]) @@ -115,4 +121,4 @@ const main = argv => { } } -return main(process.argv) \ No newline at end of file +return main(process.argv) diff --git a/interpreter.js b/interpreter.js index 09eddae..a07c7a5 100644 --- a/interpreter.js +++ b/interpreter.js @@ -1,5 +1,17 @@ const { runtimeError } = require('./errors') -const { Binary, Unary, Literal, Var, Grouping, Block, ExpressionStatement, VarStatement, PrintStatement, Assignment, Condition } = require('./types') +const { + Binary, + Unary, + Literal, + Var, + Grouping, + Block, + ExpressionStatement, + VarStatement, + PrintStatement, + Assignment, + Condition +} = require('./types') const Environment = require('./environment') const tokenizer = require('./tokenizer') const token = tokenizer.tokenEnum @@ -15,21 +27,22 @@ const checkNumber = (token, ...operands) => { } class Interpreter { - constructor (environment) { + constructor(environment) { this.environment = environment || new Environment() } - interpret (expr) { + interpret(expr) { return this.evaluate(expr) } - evaluate (expr) { + evaluate(expr) { if (expr instanceof Block) return this.visitBlock(expr) else if (expr instanceof Assignment) return this.visitAssignment(expr) else if (expr instanceof Condition) return this.visitCondition(expr) else if (expr instanceof VarStatement) return this.visitVarStatement(expr) else if (expr instanceof PrintStatement) return this.visitPrintStatement(expr) - else if (expr instanceof ExpressionStatement) return this.visitGrouping(expr) // Doesn't need it's own, it can just evaluate like grouping + // Doesn't need it's own, it can just evaluate like grouping + else if (expr instanceof ExpressionStatement) return this.visitGrouping(expr) else if (expr instanceof Grouping) return this.visitGrouping(expr) else if (expr instanceof Var) return this.visitVar(expr) else if (expr instanceof Literal) return this.visitLiteral(expr) @@ -37,17 +50,21 @@ class Interpreter { else if (expr instanceof Binary) return this.visitBinary(expr) } - visitLiteral (expr) { return expr.value } - visitGrouping (expr) { return this.evaluate(expr.expression) } - visitPrintStatement (expr) { + visitLiteral(expr) { + return expr.value + } + visitGrouping(expr) { + return this.evaluate(expr.expression) + } + visitPrintStatement(expr) { const val = this.evaluate(expr.expression) console.log(!val ? 'nil' : val.toString()) return val } - visitVar (variable) { + visitVar(variable) { return this.environment.get(variable) } - visitVarStatement (variable) { + visitVarStatement(variable) { let value = null if (variable.initializer !== null) { value = this.evaluate(variable.initializer) @@ -56,12 +73,12 @@ class Interpreter { return null } - visitBlock (expr) { + visitBlock(expr) { this.interpretBlock(expr.statements, new Environment(this.environment)) return null } - visitCondition (expr) { + visitCondition(expr) { if (isTruthy(this.evaluate(expr.condition))) { this.evaluate(expr.thenBranch) } else if (expr.elseBranch) { @@ -70,7 +87,7 @@ class Interpreter { return null } - interpretBlock (statements, env) { + interpretBlock(statements, env) { const prevEnvironment = this.environment try { this.environment = env @@ -84,13 +101,13 @@ class Interpreter { } } - visitAssignment (expr) { + visitAssignment(expr) { const value = this.evaluate(expr.value) this.environment.assign(expr.name, value) return value } - visitUnary (expr) { + visitUnary(expr) { const right = this.evaluate(expr.right) switch (expr.operator.type) { case token.MINUS: @@ -101,7 +118,7 @@ class Interpreter { } } - visitBinary (expr) { + visitBinary(expr) { const left = this.evaluate(expr.left) const right = this.evaluate(expr.right) switch (expr.operator.type) { @@ -139,4 +156,4 @@ class Interpreter { } } -module.exports = Interpreter \ No newline at end of file +module.exports = Interpreter diff --git a/interpreter.test.js b/interpreter.test.js index 64e8658..c5bc026 100644 --- a/interpreter.test.js +++ b/interpreter.test.js @@ -28,29 +28,27 @@ describe('Interpreter', () => { // Numbers '3': 3, '321': 321, - '.3': .3, - '.321': .321, + '.3': 0.3, + '.321': 0.321, '(321.123)': 321.123, // Strings '""': '', '"hello"': 'hello', - [ - `" + [`" Independent Woman - "` - ]: ` + "`]: ` Independent Woman `, // True - 'true': true, + true: true, '(true)': true, // False - 'false': false, + false: false, // Nil '(nil)': null } @@ -107,7 +105,7 @@ describe('Interpreter', () => { '21312.312312 / 123.21312', // Groupings '(5 - (3 - 1)) + -1', - '(5 - ((3/1) * 23)) + (12 * (2 + 1))', + '(5 - ((3/1) * 23)) + (12 * (2 + 1))' ] for (let eq of math) { expect(interpreter.interpret(parseExpression(eq))).toBe(eval(eq)) @@ -127,7 +125,7 @@ describe('Interpreter', () => { '3 == 2', '2 == 2', '3 != 2', - '2 != 2', + '2 != 2' ] for (let comp of comparisons) { expect(interpreter.interpret(parseExpression(comp))).toBe(eval(comp)) @@ -135,26 +133,19 @@ describe('Interpreter', () => { }) test('catches basic binary type errors', () => { - const comparisons = [ - ['2', '"hello"'], - ['false', '"hello"'] - ] - const operators = [ - '-', - '*', - '/', - '>', - '>=', - '<', - '<=' - ] + const comparisons = [['2', '"hello"'], ['false', '"hello"']] + const operators = ['-', '*', '/', '>', '>=', '<', '<='] for (let operator of operators) { for (let comparison of comparisons) { const [left, right] = comparison const stmt1 = left + operator + right const stmt2 = right + operator + left - expect(() => { interpreter.interpret(parseExpression(stmt1)) }).toThrow('Operand must be a number!') - expect(() => { interpreter.interpret(parseExpression(stmt2)) }).toThrow('Operand must be a number!') + expect(() => { + interpreter.interpret(parseExpression(stmt1)) + }).toThrow('Operand must be a number!') + expect(() => { + interpreter.interpret(parseExpression(stmt2)) + }).toThrow('Operand must be a number!') } } }) @@ -188,7 +179,7 @@ describe('Interpreter', () => { values.push(interpreter.interpret(stmt)) } - expect(values).toEqual([ null, null, null, null, 'global a', 'global b', 'global c' ]) + expect(values).toEqual([null, null, null, null, 'global a', 'global b', 'global c']) }) }) -}) \ No newline at end of file +}) diff --git a/package-lock.json b/package-lock.json index 6ab8414..b449118 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3833,6 +3833,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prettier": { + "version": "1.17.0", + "resolved": "http://registry.prod.auction.local:8081/repository/npm-internal/prettier/-/prettier-1.17.0.tgz", + "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==", + "dev": true + }, "pretty-format": { "version": "24.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", diff --git a/package.json b/package.json index 8ffea32..aa6da64 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "main": "index.js", "bin": "./index.js", "scripts": { - "test": "jest" + "test": "jest", + "format": "prettier --write **/*.js", + "check": "prettier --check **/*.js" }, "repository": { "type": "git", @@ -18,7 +20,8 @@ }, "homepage": "https://github.com/danman113/YALI.js#readme", "devDependencies": { - "jest": "^24.7.1" + "jest": "^24.7.1", + "prettier": "1.17.0" }, "dependencies": { "chalk": "^2.4.2" diff --git a/parser.js b/parser.js index a74092c..58a8188 100644 --- a/parser.js +++ b/parser.js @@ -1,19 +1,31 @@ const tokenizer = require('./tokenizer') -const { Binary, Unary, Var, Literal, Grouping, PrintStatement, ExpressionStatement, VarStatement, Assignment, Block, Condition } = require('./types') +const { + Binary, + Unary, + Var, + Literal, + Grouping, + PrintStatement, + ExpressionStatement, + VarStatement, + Assignment, + Block, + Condition +} = require('./types') const { parseError: ParseError } = require('./errors') const token = tokenizer.tokenEnum class Parser { - constructor (tokens) { + constructor(tokens) { this.tokens = tokens this.current = 0 } - expression () { + expression() { return this.assignment() } - parse () { + parse() { let statements = [] while (!this.isAtEnd) { statements.push(this.declaration()) @@ -22,7 +34,7 @@ class Parser { return statements } - assignment () { + assignment() { const expr = this.equality() if (this.match(token.EQUAL)) { const equalToken = this.previous() @@ -37,13 +49,13 @@ class Parser { return expr } - declaration () { + declaration() { if (this.match(token.VAR)) return this.varDeclaration() return this.statement() } - varDeclaration () { + varDeclaration() { const name = this.consume(token.IDENTIFIER, 'Expected variable name') let initializer = null if (this.match(token.EQUAL)) { @@ -54,7 +66,7 @@ class Parser { return new VarStatement(name, initializer) } - statement () { + statement() { if (this.match(token.IF)) return this.ifStatement() if (this.match(token.PRINT)) return this.printStatement() if (this.match(token.LEFT_BRACE)) return new Block(this.block()) @@ -62,7 +74,7 @@ class Parser { return this.expressionStatement() } - ifStatement () { + ifStatement() { this.consume(token.LEFT_PAREN, 'Expected "(" after "if"') const cond = this.expression() this.consume(token.RIGHT_PAREN, 'Expected ")" after expression') @@ -73,8 +85,7 @@ class Parser { return new Condition(cond, ifBranch, elseBranch) } - - block () { + block() { let statements = [] while (!this.check(token.RIGHT_BRACE) && !this.isAtEnd) { statements.push(this.declaration()) @@ -84,21 +95,21 @@ class Parser { return statements } - expressionStatement () { + expressionStatement() { const val = this.expression() this.consume(token.SEMICOLON, 'Expect ; after value.') return new ExpressionStatement(val) } - printStatement () { + printStatement() { const val = this.expression() this.consume(token.SEMICOLON, 'Expect ; after value.') return new PrintStatement(val) } - matchBinary (method, ...operators) { + matchBinary(method, ...operators) { let expr = this[method]() - while(this.match(...operators)) { + while (this.match(...operators)) { const operator = this.previous() const right = this[method]() expr = new Binary(expr, operator, right) @@ -106,23 +117,29 @@ class Parser { return expr } - equality () { + equality() { return this.matchBinary('comparison', token.BANG_EQUAL, token.EQUAL_EQUAL) } - comparison () { - return this.matchBinary('addition', token.GREATER, token.GREATER_EQUAL, token.LESS, token.LESS_EQUAL) + comparison() { + return this.matchBinary( + 'addition', + token.GREATER, + token.GREATER_EQUAL, + token.LESS, + token.LESS_EQUAL + ) } - addition () { + addition() { return this.matchBinary('multiplication', token.MINUS, token.PLUS) } - multiplication () { + multiplication() { return this.matchBinary('unary', token.SLASH, token.STAR) } - unary () { + unary() { if (this.match(token.BANG, token.MINUS)) { const operator = this.previous() const right = this.unary() @@ -131,7 +148,7 @@ class Parser { return this.primary() } - primary () { + primary() { if (this.match(token.FALSE)) return new Literal(false) if (this.match(token.TRUE)) return new Literal(true) if (this.match(token.NIL)) return new Literal(null) @@ -147,14 +164,14 @@ class Parser { throw ParseError('Expected Expression', this.peek()) } - consume (type, err) { + consume(type, err) { if (this.check(type)) return this.advance() throw ParseError(err, this.peek()) } // Checks if current token is one of the following tokens and advances to next token - match (...tokens) { + match(...tokens) { for (let token of tokens) { if (this.check(token)) { this.advance() @@ -166,30 +183,30 @@ class Parser { } // Verifies current token is equal to type - check (type) { + check(type) { return !this.isAtEnd && this.peek().type === type } - get isAtEnd () { + get isAtEnd() { return this.peek().type === token.EOF } // Gets current token - peek () { + peek() { return this.tokens[this.current] } // Gets previous token - previous () { + previous() { if (this.current <= 0) throw ParseError('Expected previous but found nothing', this.peek()) return this.tokens[this.current - 1] } // Advances parser to the next token - advance () { + advance() { if (!this.isAtEnd) this.current++ return this.previous() } } -module.exports = Parser \ No newline at end of file +module.exports = Parser diff --git a/parser.test.js b/parser.test.js index 1ab2c47..2abc052 100644 --- a/parser.test.js +++ b/parser.test.js @@ -47,56 +47,30 @@ describe('Expression Parsing', () => { }) test('Parses multiplication correctly', () => { - const mult = [ - `3 * 1`, - `-2 * 2`, - `-3 * -3`, - `3 / 1`, - `-2 / 2`, - `-3 / -3`, - ] + const mult = [`3 * 1`, `-2 * 2`, `-3 * -3`, `3 / 1`, `-2 / 2`, `-3 / -3`] for (const u of mult) { expect(parseString(u).expression()).toMatchSnapshot() } }) test('Parses addition correctly', () => { - const add = [ - `3 + 1`, - `-2 + 2`, - `-3 + -3`, - `3 - 1`, - `-2 - 2`, - `-3 - -3`, - ] + const add = [`3 + 1`, `-2 + 2`, `-3 + -3`, `3 - 1`, `-2 - 2`, `-3 - -3`] for (const u of add) { expect(parseString(u).expression()).toMatchSnapshot() } }) test('Parses comparison correctly', () => { - const comp = [ - `3 > 1`, - `2 < 2`, - `3 <= 3`, - `3 >= 1`, - ] + const comp = [`3 > 1`, `2 < 2`, `3 <= 3`, `3 >= 1`] for (const u of comp) { expect(parseString(u).expression()).toMatchSnapshot() } }) test('Parses equality correctly', () => { - const equals = [ - `3 == 3`, - `3 != 2`, - `3 != -3`, - `-3 != -3`, - `!true != true`, - `!true == !true`, - ] + const equals = [`3 == 3`, `3 != 2`, `3 != -3`, `-3 != -3`, `!true != true`, `!true == !true`] for (const u of equals) { expect(parseString(u).expression()).toMatchSnapshot() } }) -}) \ No newline at end of file +}) diff --git a/tokenizer.js b/tokenizer.js index f92fcea..65848ea 100644 --- a/tokenizer.js +++ b/tokenizer.js @@ -16,8 +16,9 @@ const tokens = ` PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, EOF -`.split(',').map(token => token.trim()) - +` + .split(',') + .map(token => token.trim()) let tokenEnum = {} tokens.forEach((token, i) => { @@ -40,26 +41,26 @@ const keywords = { this: tokenEnum.THIS, true: tokenEnum.TRUE, var: tokenEnum.VAR, - while: tokenEnum.WHILE, + while: tokenEnum.WHILE } const tokenMap = { - '(': (tokenizer) => { + '(': tokenizer => { tokenizer.addToken(tokenEnum.LEFT_PAREN) }, - ')': (tokenizer) => { + ')': tokenizer => { tokenizer.addToken(tokenEnum.RIGHT_PAREN) }, - '{': (tokenizer) => { + '{': tokenizer => { tokenizer.addToken(tokenEnum.LEFT_BRACE) }, - '}': (tokenizer) => { + '}': tokenizer => { tokenizer.addToken(tokenEnum.RIGHT_BRACE) }, - ',': (tokenizer) => { + ',': tokenizer => { tokenizer.addToken(tokenEnum.COMMA) }, - '.': (tokenizer) => { + '.': tokenizer => { // Handles leading decimals for number literals if (isDigit(tokenizer.peek())) { tokenizer.handleNumberLiterals() @@ -67,45 +68,45 @@ const tokenMap = { tokenizer.addToken(tokenEnum.DOT) } }, - '-': (tokenizer) => { + '-': tokenizer => { tokenizer.addToken(tokenEnum.MINUS) }, - '+': (tokenizer) => { + '+': tokenizer => { tokenizer.addToken(tokenEnum.PLUS) }, - ';': (tokenizer) => { + ';': tokenizer => { tokenizer.addToken(tokenEnum.SEMICOLON) }, - '/': (tokenizer) => { + '/': tokenizer => { if (tokenizer.nextMatch('/')) { // Eat all those delish comments - while(tokenizer.peek() !== '\n' && tokenizer.peek() !== '') tokenizer.chomp() + while (tokenizer.peek() !== '\n' && tokenizer.peek() !== '') tokenizer.chomp() } else { tokenizer.addToken(tokenEnum.SLASH) } }, - '*': (tokenizer) => { + '*': tokenizer => { tokenizer.addToken(tokenEnum.STAR) }, - '!': (tokenizer) => { + '!': tokenizer => { tokenizer.addToken(tokenizer.nextMatch('=') ? tokenEnum.BANG_EQUAL : tokenEnum.BANG) }, - '=': (tokenizer) => { + '=': tokenizer => { tokenizer.addToken(tokenizer.nextMatch('=') ? tokenEnum.EQUAL_EQUAL : tokenEnum.EQUAL) }, - '>': (tokenizer) => { + '>': tokenizer => { tokenizer.addToken(tokenizer.nextMatch('=') ? tokenEnum.GREATER_EQUAL : tokenEnum.GREATER) }, - '<': (tokenizer) => { + '<': tokenizer => { tokenizer.addToken(tokenizer.nextMatch('=') ? tokenEnum.LESS_EQUAL : tokenEnum.LESS) }, ' ': noop, '\t': noop, '\r': noop, - '\n': (tokenizer) => { + '\n': tokenizer => { tokenizer.newline() }, - '"': (tokenizer) => { + '"': tokenizer => { tokenizer.handleStringLiterals() } } @@ -115,14 +116,14 @@ const isAlpha = str => /[a-zA-Z_]/.test(str) const isAlphaNumeric = str => isAlpha(str) || isDigit(str) class Tokenizer { - static get tokens () { + static get tokens() { return tokens } - static get tokenEnum () { + static get tokenEnum() { return tokenEnum } - constructor (source) { + constructor(source) { this.source = source this.length = source.length this.tokens = [] @@ -133,8 +134,8 @@ class Tokenizer { this.current = 0 } - handleStringLiterals () { - while(this.peek() !== '"' && this.peek() !== '') { + handleStringLiterals() { + while (this.peek() !== '"' && this.peek() !== '') { if (this.peek() === '\n') this.newline() this.chomp() } @@ -144,9 +145,9 @@ class Tokenizer { this.addToken(tokenEnum.STRING, value) } - handleNumberLiterals () { + handleNumberLiterals() { let hasDecimal = false - while(isDigit(this.peek()) || (!hasDecimal && this.peek() === '.')) { + while (isDigit(this.peek()) || (!hasDecimal && this.peek() === '.')) { if (this.peek() === '.') hasDecimal = true this.chomp() } @@ -154,7 +155,7 @@ class Tokenizer { this.addToken(tokenEnum.NUMBER, parseFloat(value)) } - handleIdentifiers () { + handleIdentifiers() { while (isAlphaNumeric(this.peek())) this.chomp() const value = this.source.substring(this.start, this.current) if (keywords[value]) { @@ -164,7 +165,7 @@ class Tokenizer { } } - scanTokens () { + scanTokens() { while (this.current < this.length) { const c = this.chomp() this.startPosition = new Coordinate(this.column - 1, this.line, this.current - 1) @@ -175,7 +176,11 @@ class Tokenizer { this.handleIdentifiers() } else { // Column isn't -1 because we haven't iterated column yet - throw Error(`Unexpected character ${c}`, this.startPosition, new Coordinate(this.column, this.line, this.current)) + throw Error( + `Unexpected character ${c}`, + this.startPosition, + new Coordinate(this.column, this.line, this.current) + ) } } else { tokenMap[c](this) @@ -186,44 +191,51 @@ class Tokenizer { return this.tokens } - get endPosition () { + get endPosition() { return new Coordinate(this.column - 1, this.line, this.current) } - addToken (type, literal = null) { + addToken(type, literal = null) { const text = this.source.substring(this.start, this.current) - this.tokens.push(new Token(type, text, literal, new Coordinate(this.column, this.line, this.current), this.startPosition)) - } - - increment () { + this.tokens.push( + new Token( + type, + text, + literal, + new Coordinate(this.column, this.line, this.current), + this.startPosition + ) + ) + } + + increment() { this.current++ this.column++ } - newline () { + newline() { this.line++ this.column = 0 } - chomp () { + chomp() { this.increment() return this.source.charAt(this.current - 1) } - peek () { + peek() { return this.source.charAt(this.current) } - nextMatch (expected) { + nextMatch(expected) { if (this.peek() !== expected) return false this.increment() return true } - } class Coordinate { - constructor (col, line, index) { + constructor(col, line, index) { this.col = col this.line = line this.index = index @@ -231,7 +243,7 @@ class Coordinate { } class Token { - constructor (type, lexeme, literal, endCoordinates, startCoordinates) { + constructor(type, lexeme, literal, endCoordinates, startCoordinates) { this.type = type this.lexeme = lexeme this.literal = literal diff --git a/tokenizer.test.js b/tokenizer.test.js index 3567c1e..806b7a6 100644 --- a/tokenizer.test.js +++ b/tokenizer.test.js @@ -47,17 +47,17 @@ describe('Tokenizer', () => { }) test('Handles unfinished strings', () => { - let tokenizer = new Tokenizer(`var str = "hello this is an unfinished str-`) - expect(() => tokenizer.scanTokens()).toThrow(/Unfinished string/) + let tokenizer = new Tokenizer(`var str = "hello this is an unfinished str-`) + expect(() => tokenizer.scanTokens()).toThrow(/Unfinished string/) - tokenizer = new Tokenizer(`var str = "hello this is an finished string"`) - expect(() => tokenizer.scanTokens()).not.toThrow(/Unfinished string/) + tokenizer = new Tokenizer(`var str = "hello this is an finished string"`) + expect(() => tokenizer.scanTokens()).not.toThrow(/Unfinished string/) - tokenizer = new Tokenizer(` + tokenizer = new Tokenizer(` var str = "hello this is an finished string" "oh god not another unfinished str- `) - expect(() => tokenizer.scanTokens()).toThrow(/Unfinished string/) + expect(() => tokenizer.scanTokens()).toThrow(/Unfinished string/) }) test('Handles unexpected characters', () => { @@ -83,7 +83,9 @@ describe('Tokenizer', () => { tokenizer.scanTokens() } catch (e) { expect(e.toString()).toBe('Unfinished string') - expect(singleLine.substr(e.startCoordinates.index, e.endCoordinates.index)).toBe('"would suck if something happened to m-') + expect(singleLine.substr(e.startCoordinates.index, e.endCoordinates.index)).toBe( + '"would suck if something happened to m-' + ) expect(e.startCoordinates.line).toBe(1) expect(e.endCoordinates.line).toBe(1) } @@ -104,10 +106,14 @@ describe('Tokenizer', () => { }) test('Returns correct line number when handling unexpected characters', () => { - const unexpectedCharacters = ['🐕', 'var a = 🐕', ` + const unexpectedCharacters = [ + '🐕', + 'var a = 🐕', + ` var cat = "cat" var dog = 🐕 - `] + ` + ] for (let char of unexpectedCharacters) { try { let tokenizer = new Tokenizer(char) @@ -119,4 +125,4 @@ describe('Tokenizer', () => { } } }) -}) \ No newline at end of file +}) diff --git a/types.js b/types.js index b8df86b..01d4032 100644 --- a/types.js +++ b/types.js @@ -1,32 +1,32 @@ class Binary { - constructor (left, operator, right) { + constructor(left, operator, right) { this.left = left this.operator = operator this.right = right } } class Unary { - constructor (operator, right) { + constructor(operator, right) { this.operator = operator this.right = right } } class Literal { - constructor (value) { + constructor(value) { this.value = value } } class Var { - constructor (name) { + constructor(name) { this.name = name } } // @TODO: Can we express this in a less type-dependant way? class Grouping { - constructor (expression) { + constructor(expression) { this.expression = expression } } @@ -35,34 +35,33 @@ class ExpressionStatement extends Grouping {} class PrintStatement extends Grouping {} class VarStatement { - constructor (name, initializer) { + constructor(name, initializer) { this.name = name this.initializer = initializer } } class Assignment { - constructor (name, value) { + constructor(name, value) { this.name = name this.value = value } } class Block { - constructor (statements) { + constructor(statements) { this.statements = statements } } class Condition { - constructor (condition, thenBranch, elseBranch) { + constructor(condition, thenBranch, elseBranch) { this.condition = condition this.thenBranch = thenBranch this.elseBranch = elseBranch } } - module.exports = { Var, Binary, @@ -75,4 +74,4 @@ module.exports = { PrintStatement, VarStatement, Assignment -} \ No newline at end of file +}