├── .gitignore ├── .vscode └── launch.json ├── example └── fibonacci.tjs ├── il.js ├── img └── ops.jpg ├── package.json ├── readme.md ├── src ├── Symbols.js ├── ast │ ├── Expr.js │ ├── LexicalScope.js │ ├── Stmt.js │ └── Terminal.js ├── exprParser.js ├── gen │ ├── ILGen.js │ └── OpcodeCompiler.js ├── lexer.js ├── lexical │ ├── literal.js │ ├── number.js │ ├── op.js │ └── util.js ├── parser.js ├── simple_math_parser.js └── tests │ ├── assign_stmt.test.js │ └── lexer.test.js ├── test_lexer.js ├── test_recursive_function.js └── test_simple_math.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | *.tsi 4 | *.tss 5 | *.tso -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${file}" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /example/fibonacci.tjs: -------------------------------------------------------------------------------- 1 | function fibonacci(n) { 2 | if(n == 1 || n == 2) { 3 | return n 4 | } 5 | return fibonacci(n-1) + fibonacci(n-2) 6 | } 7 | 8 | print( fibonacci(5) ) -------------------------------------------------------------------------------- /il.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const Parser = require('./src/parser') 3 | const parser = new Parser() 4 | 5 | const ipt = process.argv[2] 6 | const opt = process.argv[3] 7 | 8 | if(!(ipt && opt)) { 9 | console.log('用法: node il.js <输入文件> <输出名称(不需要扩展名)>') 10 | process.exit(-1) 11 | } 12 | 13 | const sourceCode = fs.readFileSync(ipt, 'utf-8') 14 | const ast = parser.parse(sourceCode) 15 | const symbols = ast.lexicalScope.toJSON() 16 | 17 | ast.gen() 18 | const ils = ast.ilGen.toText() 19 | fs.writeFileSync(opt + '.tss', JSON.stringify(symbols, null, 2), 'utf-8') 20 | fs.writeFileSync(opt + '.tsi', ils, 'utf-8') 21 | console.log('完成') -------------------------------------------------------------------------------- /img/ops.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyjm/compiler-in-js/6247b6089725740dd33ab82cf4a85fe35417a80a/img/ops.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compiler-in", 3 | "version": "1.0.0", 4 | "description": "``` javascript\r node ./test_lexer.js\r ```", 5 | "main": "test_lexer.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/kyjm/compiler-in-js.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/kyjm/compiler-in-js/issues" 17 | }, 18 | "homepage": "https://github.com/kyjm/compiler-in-js#readme", 19 | "dependencies": { 20 | "mocha": "^6.2.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## 词法分析部分 2 | 3 | ``` javascript 4 | node ./test_lexer.js 5 | ``` 6 | 7 | 8 | ### 符号部分状态机 9 | ![image](./img/ops.jpg) 10 | 11 | ## 语法分析 12 | ``` javascript 13 | node ./test_simple_math.js 14 | ``` 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Symbols.js: -------------------------------------------------------------------------------- 1 | class Symbols { 2 | 3 | constructor(){ 4 | this.var_id = 1 5 | } 6 | 7 | assign_temp_var () { 8 | return 't' + this.var_id++ 9 | } 10 | 11 | } 12 | 13 | module.exports = Symbols -------------------------------------------------------------------------------- /src/ast/Expr.js: -------------------------------------------------------------------------------- 1 | const { Terminal, Id } = require('./Terminal') 2 | 3 | class Expr { 4 | constructor(op, left, right){ 5 | this.op = op 6 | this.left = left 7 | this.right = right 8 | } 9 | 10 | print(level = 0) { 11 | const pad = ''.padStart(level * 2) 12 | console.log(pad + this.op) 13 | this.left && this.left.print(level + 1) 14 | this.right && this.right .print(level + 1) 15 | 16 | } 17 | 18 | gen(il,scope){ 19 | // console.log(this.left) 20 | this.left && this.left.gen && this.left.gen(il,scope) 21 | this.right && this.right.gen && this.right.gen(il,scope) 22 | const tempVar = scope.bindTempVar() 23 | il.add(`set ${tempVar} ${this.left.rvalue()} ${this.op} ${this.right.rvalue()}`) 24 | this._rval = tempVar; 25 | } 26 | 27 | rvalue() { 28 | return this._rval; 29 | } 30 | 31 | bindLexicalScope(scope) { 32 | this.left && this.left.bindLexicalScope && this.left.bindLexicalScope(scope) 33 | this.right && this.right.bindLexicalScope && this.right.bindLexicalScope(scope) 34 | } 35 | } 36 | 37 | class FunctionCallExpr extends Expr{ 38 | constructor(id, args){ 39 | super('call', id, args) 40 | } 41 | 42 | gen(il,scope) { 43 | this.right.gen(il,scope) 44 | const tempVar = scope.bindTempVar() 45 | il.add(`call ${scope.getLexemeName(this.left.lvalue())}`) 46 | this._rval = tempVar 47 | } 48 | } 49 | 50 | class AssignExpr extends Expr { 51 | constructor(id, expr) { 52 | super('=', id, expr) 53 | } 54 | 55 | gen(il,scope) { 56 | il.add(`declare ${scope.getLexemeName(id.lvalue())}`) 57 | expr.gen(il,scope) 58 | il.add(`${scope.getLexemeName(id.lvalue())}=${expr.rvalue()}`) 59 | 60 | } 61 | } 62 | 63 | class Args{ 64 | constructor(args, type = 'call') { 65 | this.args = args 66 | this.type = type 67 | } 68 | 69 | print(level) { 70 | this.args.forEach(x => { 71 | x.print(level) 72 | }) 73 | } 74 | 75 | size(){ 76 | return this.args.length 77 | } 78 | 79 | bindLexicalScope(scope) { 80 | for (let i = 0; i < this.args.length; i++) { 81 | if (this.type === 'function') { 82 | scope.bind(this.args[i].value) 83 | this.args[i].bindLexicalScope(scope) 84 | } else { 85 | this.args[i].bindLexicalScope && this.args[i].bindLexicalScope(scope) 86 | } 87 | } 88 | } 89 | 90 | gen(il,scope) { 91 | if(this.type == 'call') { 92 | for (let i = 0; i < this.args.length; i++) { 93 | const expr = this.args[i] 94 | expr.gen && expr.gen(il,scope) 95 | il.add(`pass ${expr.rvalue()}`) 96 | } 97 | } 98 | } 99 | 100 | } 101 | 102 | module.exports = { 103 | Expr, 104 | FunctionCallExpr, 105 | AssignExpr, 106 | Args 107 | } -------------------------------------------------------------------------------- /src/ast/LexicalScope.js: -------------------------------------------------------------------------------- 1 | let scopeId = 1 2 | class LexicalScope { 3 | constructor(parent, others){ 4 | this.parent = parent 5 | 6 | this.others = others 7 | if(!this.parent){ 8 | this.globalHash = {} 9 | } 10 | else { 11 | this.globalHash = parent.globalHash 12 | this.parent.add(this) 13 | } 14 | this.id = scopeId ++ 15 | this.table = {} 16 | this.children = [] 17 | this.index = 0; 18 | 19 | 20 | } 21 | 22 | add(subScope) { 23 | this.children.push(subScope) 24 | } 25 | 26 | lookup(id) { 27 | if(id.indexOf('$') !== -1) { 28 | return this.globalHash[id] 29 | } 30 | let p = this 31 | while(p) { 32 | if( p.table[id] ) { 33 | return p 34 | } 35 | p = p.parent 36 | } 37 | return null 38 | } 39 | 40 | bindTempVar(type = 'number'){ 41 | const varName = `$t`+this.index 42 | this.bind(varName, type) 43 | return varName+'@'+this.id 44 | } 45 | 46 | bind(id, type = 'number', others) { 47 | this.globalHash[id+'@'+this.id]=this 48 | this.table[id] = { 49 | type, 50 | index : this.index++, 51 | ...others 52 | } 53 | } 54 | 55 | getLexemeName(id){ 56 | const scope = this.lookup(id) 57 | if(scope) { 58 | return id+'@'+scope.id 59 | } else { 60 | throw `syntax error: lexeme ${id} not found.` 61 | } 62 | } 63 | 64 | print(level = 0) { 65 | 66 | const pad = ''.padStart(level * 2) 67 | console.log(`${pad}scope ${this.id}\n${pad}{`) 68 | for(let key in this.table) { 69 | console.log(`${pad} ${key} : ${this.table[key].type}`) 70 | } 71 | this.children.forEach(child => { 72 | child.print(level + 1) 73 | }) 74 | console.log(`${pad}}`) 75 | } 76 | 77 | toJSON(){ 78 | const obj = { 79 | id:this.id, 80 | table : this.table, 81 | children : this.children.map(child => child.toJSON()), 82 | ...this.others 83 | } 84 | return obj 85 | } 86 | 87 | } 88 | 89 | module.exports = LexicalScope -------------------------------------------------------------------------------- /src/ast/Stmt.js: -------------------------------------------------------------------------------- 1 | const Symbols = require('../Symbols') 2 | const LexicalScope = require('./LexicalScope') 3 | const ILGen = require('../gen/ILGen') 4 | 5 | class Stmt { 6 | buildLexicalScope(parent) { 7 | this.lexicalScope = parent 8 | } 9 | } 10 | 11 | class DeclareStmt extends Stmt{ 12 | constructor(left, right, isCreate = false){ 13 | super() 14 | this.left = left 15 | this.right = right 16 | this.isCreate = isCreate 17 | } 18 | 19 | buildLexicalScope(parent) { 20 | this.lexicalScope = parent 21 | this.lexicalScope.bind(this.left.value, 'number') 22 | } 23 | 24 | 25 | print(level) { 26 | const pad = ''.padStart(level * 2) 27 | console.log(pad + '=') 28 | this.left && this.left.print(level + 1) 29 | this.right && this.right.print(level + 1) 30 | } 31 | 32 | *gen(il,scope) { 33 | if(this.right.gen) { 34 | yield * this.right.gen(il, this.lexicalScope) 35 | } 36 | yield `${this.lexicalScope.getLexemeName(this.left.value)} = ${this.right.rvalue()}` 37 | } 38 | 39 | } 40 | 41 | class Block{ 42 | 43 | constructor(stmts){ 44 | this.stmts = stmts 45 | } 46 | 47 | buildLexicalScope(parent, create = true) { 48 | if(create) { 49 | this.lexicalScope = new LexicalScope(parent) 50 | }else { 51 | this.lexicalScope = parent 52 | } 53 | 54 | this.stmts.forEach(stmt => { 55 | if (stmt instanceof Stmt) { 56 | stmt.buildLexicalScope(this.lexicalScope) 57 | } else { 58 | stmt.bindLexicalScope(this.lexicalScope) 59 | } 60 | }) 61 | } 62 | 63 | print() { 64 | for(let i = 0; i < this.stmts.length; i++) { 65 | this.stmts[i].print(0) 66 | } 67 | } 68 | gen(il){ 69 | for(let i = 0; i < this.stmts.length; i++) { 70 | this.stmts[i].gen(il, this.lexicalScope) 71 | } 72 | } 73 | } 74 | 75 | class Program extends Block{ 76 | constructor(stmts){ 77 | super(stmts) 78 | this.ilGen = new ILGen() 79 | } 80 | 81 | registerGlobals(scope){ 82 | scope.bind('print', 'function') 83 | } 84 | 85 | buildLexicalScope() { 86 | this.lexicalScope = new LexicalScope() 87 | this.registerGlobals(this.lexicalScope) 88 | this.stmts.forEach(stmt => { 89 | if(stmt instanceof Stmt) { 90 | stmt.buildLexicalScope(this.lexicalScope) 91 | }else { 92 | stmt.bindLexicalScope(this.lexicalScope) 93 | } 94 | }) 95 | } 96 | 97 | gen(){ 98 | this.ilGen.beginSection('main@1') 99 | this.ilGen.add('set %TOP% %SP%') 100 | for(let i = 0; i < this.stmts.length; i++) { 101 | this.stmts[i].gen(this.ilGen, this.lexicalScope) 102 | } 103 | this.ilGen.endSection() 104 | } 105 | } 106 | 107 | 108 | class IfStmt extends Stmt{ 109 | /** 110 | * @param {*} expr if 后面的表达式 111 | * @param {*} ifBlock if 后面的紧跟着的 Block 112 | * @param {*} elseIfStmt 如果有else if, 相当于else后面跟着的If语句 113 | * @param {*} elseBlock 如果没有else if 相当于else后面跟着的Block 114 | */ 115 | constructor(expr, ifBlock, elseIfStmt, elseBlock) { 116 | super() 117 | this.expr = expr 118 | this.ifBlock = ifBlock 119 | this.elseIfStmt = elseIfStmt 120 | this.elseBlock = elseBlock 121 | } 122 | 123 | buildLexicalScope(parent) { 124 | super.buildLexicalScope(parent) 125 | this.expr.bindLexicalScope(this.lexicalScope) 126 | this.ifBlock.buildLexicalScope(this.lexicalScope) 127 | this.elseIfStmt && this.elseIfStmt.buildLexicalScope(this.lexicalScope) 128 | this.elseBlock && this.elseBlock.buildLexicalScope(this.lexicalScope) 129 | } 130 | 131 | print(level) { 132 | const pad = ''.padStart(level * 2) 133 | console.log(pad + 'if') 134 | this.expr.print(level+1) 135 | this.ifBlock.print(level + 1) 136 | } 137 | 138 | gen(il) { 139 | this.expr.gen(il,this.lexicalScope) 140 | 141 | const ifCodeLine = il.add('', true) 142 | let ifBlockNextLineNo = null 143 | this.ifBlock.gen(il,this.lexicalScope) 144 | 145 | if(this.elseIfStmt) { 146 | if(!ifBlockNextLineNo){ 147 | ifBlockNextLineNo = il.current().lineno 148 | } 149 | this.elseIfStmt.gen(il,this.lexicalScope) 150 | } 151 | else if(this.elseBlock) { 152 | if(!ifBlockNextLineNo){ 153 | ifBlockNextLineNo = il.current().lineno 154 | } 155 | this.elseBlock.gen(il,this.lexicalScope) 156 | } 157 | 158 | // const nextLine = il.current().lines[ifCodeLine.lineno+1] 159 | const currentLine = il.currentLine() 160 | const l1 = il.genLabel() 161 | // il.bindLabel(nextLine.lineno, l1) 162 | il.bindLabel(currentLine.lineno+1, l1) 163 | // currentLine.label = l2 164 | // nextLine.label = l1 165 | 166 | ifCodeLine.code = `branch ${this.expr.rvalue()} ${l1}` 167 | } 168 | } 169 | 170 | class ReturnStmt extends Stmt{ 171 | constructor(expr){ 172 | super() 173 | this.expr = expr 174 | } 175 | 176 | buildLexicalScope(parent) { 177 | super.buildLexicalScope(parent) 178 | this.expr.bindLexicalScope(this.lexicalScope) 179 | } 180 | 181 | print(level) { 182 | const pad = ''.padStart(level * 2) 183 | console.log(pad + 'return' ) 184 | this.expr.print(level+1) 185 | } 186 | 187 | gen(il){ 188 | this.expr && this.expr.gen && this.expr.gen(il,this.lexicalScope) 189 | il.add(`return ${this.lexicalScope.getLexemeName(this.expr.rvalue())}`) 190 | } 191 | } 192 | 193 | class Function extends Stmt{ 194 | constructor(id, args, block) { 195 | super() 196 | this.id = id 197 | this.args = args 198 | this.block = block 199 | } 200 | 201 | buildLexicalScope(parent) { 202 | this.lexicalScope = new LexicalScope(parent, { 203 | type :'function', 204 | argc :this.args.size() 205 | }) 206 | parent.bind(this.id.value, 'function') 207 | this.args.bindLexicalScope(this.lexicalScope) 208 | this.block.buildLexicalScope(this.lexicalScope, false) 209 | } 210 | 211 | print(level) { 212 | const pad = ''.padStart(level * 2) 213 | console.log(pad + 'function:' + this.id) 214 | this.args.print(level+1) 215 | this.block.print(level + 1) 216 | } 217 | 218 | gen(il) { 219 | il.add(`declare function ${this.lexicalScope.getLexemeName(this.id.lvalue())}`) 220 | il.beginSection(this.id.value+'@' + this.lexicalScope.id) 221 | il.add(`set %TOP% %SP%`) 222 | this.args.gen(il, this.lexicalScope) 223 | this.block.gen(il, this.lexicalScope) 224 | il.endSection() 225 | } 226 | } 227 | 228 | 229 | module.exports = { 230 | DeclareStmt, 231 | Program, 232 | Block, 233 | Function, 234 | IfStmt, 235 | ReturnStmt 236 | } -------------------------------------------------------------------------------- /src/ast/Terminal.js: -------------------------------------------------------------------------------- 1 | class Factor { 2 | constructor(value) { 3 | this.value = value 4 | } 5 | 6 | lvalue() { 7 | return this.value 8 | } 9 | 10 | rvalue() { 11 | return this.value 12 | } 13 | 14 | print(level) { 15 | console.log(''.padStart(level * 2) + this.value) 16 | } 17 | 18 | } 19 | 20 | 21 | class Id extends Factor{ 22 | bindLexicalScope(scope) { 23 | this.scope = scope.lookup(this.value) 24 | if(this.scope === null) { 25 | throw `sytnax error: ${this.value} is not defined` 26 | } 27 | } 28 | } 29 | 30 | class Numeral extends Factor{} 31 | 32 | module.exports = { 33 | Id, 34 | Numeral 35 | } -------------------------------------------------------------------------------- /src/exprParser.js: -------------------------------------------------------------------------------- 1 | const { Expr } = require('./ast/Expr') 2 | 3 | /** 4 | * 操作符优先级列表 5 | */ 6 | const PRIORITY_TABLE = { 7 | '+' : 60, 8 | '-' : 60, 9 | '*' : 70, 10 | '/' : 70, 11 | '>=' : 80, 12 | '<=' : 80, 13 | '>' : 80, 14 | '<' : 80, 15 | '&&' : 90, 16 | '||' : 90, 17 | '==' : 100, 18 | '!=' : 100, 19 | '(' : 1000, // 用不到 20 | ')' : 1000 21 | } 22 | 23 | /** 24 | * 帮助Pop Stack直到Prediction满足 25 | * @param {*} stack 26 | * @param {Lambda} prediction 27 | * @param {*} callback 28 | */ 29 | function popUntil(stack, prediction, callback) { 30 | let v = null 31 | while( v = stack.pop() ) { 32 | if(prediction(v)) { 33 | stack.push(v) 34 | break 35 | } 36 | callback(v) 37 | } 38 | } 39 | 40 | /** 41 | * 主程序 42 | * @param {*} parser 43 | */ 44 | function parseExpr(parser) { 45 | if(parser.lookahead.value === ')') { 46 | return null 47 | } 48 | // PreOrder : 前序 49 | // inOrder : 中序 50 | // PostOrder : 后序 51 | const postOrderOutput = inOrderToPostOrder.call(parser) 52 | return constructAST(postOrderOutput) 53 | 54 | } 55 | 56 | function constructAST(postOrderOutput) { 57 | 58 | let c = null 59 | const stack = [] 60 | for(let i = 0; i < postOrderOutput.length; i++) { 61 | const c = postOrderOutput[i] 62 | if(c.type === 'op') { 63 | const r = stack.pop() 64 | const l = stack.pop() 65 | const expr = new Expr(c.value, l, r) 66 | stack.push(expr) 67 | } else { 68 | stack.push(c) 69 | } 70 | } 71 | return stack[0] 72 | } 73 | 74 | 75 | function inOrderToPostOrder() { 76 | const opStack = [] 77 | const output = [] 78 | 79 | while(this.lookahead.value != 'eof' && this.lookahead.value !== '}') { 80 | 81 | if(this.lookahead.value === '(') { 82 | opStack.push(this.lookahead) 83 | this.match('(') 84 | } 85 | else if(this.lookahead.value === ')') { 86 | popUntil(opStack, x => x.value === '(', x => { 87 | output.push(x) 88 | }) 89 | const op = opStack.pop() 90 | // 遇到没有左括号匹配的情况意味着需要停止处理 91 | if(!op || op.value !== '(') { 92 | break 93 | } 94 | 95 | this.match(')') 96 | 97 | if(this.lookahead.type != 'op'){ 98 | break 99 | } 100 | } else if(this.lookahead.type === 'op') { 101 | const op = this.lookahead 102 | 103 | if( !(op.value in PRIORITY_TABLE) ) { 104 | throw `An operator expected in @line ${this.lookahead.lineno} but ${this.lookahead.value} found` 105 | } 106 | this.match(op.value) 107 | const lastOp = opStack[opStack.length - 1] 108 | if(!lastOp) { // opStack是空的 109 | opStack.push(op) 110 | } 111 | else { 112 | if(PRIORITY_TABLE[op.value] <= PRIORITY_TABLE[lastOp.value]) { 113 | popUntil(opStack, x => !x || x.value === '(', x => { 114 | output.push(x) 115 | }) 116 | } 117 | opStack.push(op) 118 | } 119 | 120 | }else { 121 | const factor = this.parseFactor() 122 | output.push(factor) 123 | 124 | if(this.lookahead.type != 'op' || this.lookahead.value === '=') { 125 | break 126 | } 127 | } 128 | } 129 | 130 | if(opStack.length > 0) { 131 | while(opStack.length > 0) { 132 | output.push(opStack.pop()) 133 | } 134 | } 135 | 136 | return output 137 | } 138 | 139 | 140 | module.exports = parseExpr -------------------------------------------------------------------------------- /src/gen/ILGen.js: -------------------------------------------------------------------------------- 1 | class ILGen { 2 | 3 | static labelCounter = 1 4 | 5 | constructor(){ 6 | this.stack = [] 7 | this.sections = [] 8 | } 9 | 10 | beginSection(mark){ 11 | const section = new Section(mark) 12 | this.sections.push(section) 13 | this.stack.push(section) 14 | } 15 | 16 | genLabel(){ 17 | return `LB${ILGen.labelCounter++}` 18 | } 19 | 20 | endSection(){ 21 | this.stack.pop() 22 | } 23 | 24 | add(code){ 25 | return this.current().add(code) 26 | } 27 | 28 | current(){ 29 | return this.stack[this.stack.length - 1] 30 | } 31 | 32 | currentLine(){ 33 | const section = this.current() 34 | return section.lines[section.lines.length-1] 35 | 36 | } 37 | 38 | bindLabel(index, label) { 39 | const section = this.current() 40 | section.bindLabel(index, label) 41 | } 42 | 43 | print(){ 44 | 45 | for(let i = this.sections.length-1; i>=0; i--){ 46 | const section = this.sections[i] 47 | console.log('section:' + section.mark) 48 | for(let line of section.lines) { 49 | console.log(`${line.lineno}:${line.code}`) 50 | } 51 | } 52 | } 53 | 54 | toText(){ 55 | let text = '' 56 | for(let i = this.sections.length-1; i>=0; i--){ 57 | const section = this.sections[i] 58 | text += 'section ' + section.mark + '\n' 59 | for(let line of section.lines) { 60 | if(section.labels[line.lineno]) { 61 | text += section.labels[line.lineno]+":" + line.code + '\n' 62 | } else { 63 | text += line.code + '\n' 64 | } 65 | } 66 | } 67 | return text 68 | } 69 | 70 | } 71 | 72 | class Section{ 73 | constructor(mark){ 74 | this.mark = mark 75 | this.lines = [] 76 | this.lineno = 0 77 | this.labels = [] 78 | } 79 | 80 | bindLabel(index, label) { 81 | this.labels[index] = label 82 | 83 | } 84 | 85 | add(code){ 86 | const line = { 87 | code, 88 | lineno : this.lineno++ 89 | } 90 | this.lines.push(line) 91 | return line 92 | } 93 | } 94 | 95 | module.exports = ILGen 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/gen/OpcodeCompiler.js: -------------------------------------------------------------------------------- 1 | class OpcodeCompiler { 2 | 3 | constructor(){ 4 | this.symbolsHash = {} 5 | this.scopeHash = {} 6 | } 7 | 8 | offset(register, lexeme, argc = 0) { 9 | 10 | if(lexeme.index < argc) { 11 | return `${register}+${4 * (argc - lexeme.index + 1)}` 12 | } 13 | else if(lexeme.index > argc) { 14 | return `${register}-${4 * (lexeme.index-1)}` 15 | } 16 | else { 17 | return `${register}` 18 | } 19 | } 20 | 21 | addr(curScope, x, register = 'R0') { 22 | 23 | // 数字 24 | if(x[0].match(/[0-9]/)) { 25 | return '#'+x 26 | } 27 | else if(x.indexOf('%') !== -1) { 28 | return x.replace(/%/g, '') 29 | } 30 | else { 31 | // const argc = currentScop 32 | // console.log(curScope, curScope.children[0].table) 33 | const argc = curScope.type === 'function' ? 34 | curScope.argc : 0 35 | 36 | const lexeme = curScope.find(x) 37 | // 分case讨论 38 | // case1: 变量在当前作用域 39 | // case2:变量不在当前作用域 40 | // case3: 当前作用域是变量作用域的子节点 41 | // case4: 当前作用域不是变量作用域的子节点 42 | if (curScope.id === lexeme.scopeId) { 43 | return `${this.offset('TOP', lexeme, argc)}` 44 | } else { 45 | if (curScope.isParent(lexeme.scopeId)) { 46 | const levelDiff = curScope.level - lexeme.level 47 | this.lines.push(`mov TOP ${register}`) 48 | for (let i = 0; i < levelDiff; i++) { 49 | this.lines.push(`mov @${register} ${register}`) 50 | } 51 | this.lines.push(`mov ${this.offset(register, lexeme, argc)} ${register}`) 52 | } else { 53 | // 这种情况下TOP肯定指向父作用域 54 | this.lines.push(`mov TOP ${register}`) 55 | this.lines.push(`mov @${register} ${register}`) 56 | this.lines.push(`mov ${this.offset(register, lexeme, argc)} ${register}`) 57 | } 58 | } 59 | } 60 | } 61 | 62 | translateArg() { 63 | 64 | } 65 | 66 | translatePass(curScope, params) { 67 | const v = params[0] 68 | this.lines.push(`push ${this.addr(curScope, v, 'TOP')}`) 69 | } 70 | 71 | translateBranch(curScope, params) { 72 | const v = params[0] 73 | const lb1 = params[1] 74 | this.lines.push(`jz ${lb1}`) 75 | } 76 | 77 | translateCall(curScope, params) { 78 | const func = params[0] 79 | this.lines.push(`push PC`) 80 | this.lines.push(`jump ${func}`) 81 | } 82 | 83 | translateSet(curScope, params) { 84 | const assignee = params[0] 85 | const op = params[2] 86 | const l = params[1] 87 | const r = params[3] 88 | 89 | 90 | 91 | if(op) { 92 | const a = this.addr(curScope, l, 'R0') 93 | const b = this.addr(curScope, r, 'R1') 94 | switch(op) { 95 | 96 | case "==": { 97 | 98 | this.lines.push(`cmp ${a} ${b}`) 99 | this.lines.push(`mov ZF ${this.addr(curScope, assignee, 'TOP')}`) 100 | break 101 | } 102 | case "||" : { 103 | this.lines.push(`mov ${a} R0`) 104 | this.lines.push(`or R0 ${b}`) 105 | this.lines.push(`mov R0 ${this.addr(curScope, assignee, 'TOP')}`) 106 | break 107 | } 108 | case '-' : { 109 | this.lines.push(`mov ${a} R0`) 110 | this.lines.push(`sub R0 ${b}`) 111 | this.lines.push(`mov R0 ${this.addr(curScope, assignee, 'TOP')}`) 112 | break 113 | } 114 | case '+': { 115 | this.lines.push(`mov ${a} R0`) 116 | this.lines.push(`add R0 ${b}`) 117 | this.lines.push(`mov R0 ${this.addr(curScope, assignee, 'TOP')}`) 118 | } 119 | } 120 | 121 | } else { 122 | const a = this.addr(curScope, assignee, 'R0') 123 | const b = this.addr(curScope, l, 'R1') 124 | this.lines.push(`mov ${b} ${a}`) 125 | if(assignee.indexOf('%') === -1) { 126 | this.lines.push(`sub #-4 SP`) 127 | } 128 | } 129 | } 130 | 131 | parse(sourceCode, symbols){ 132 | this.lines = [] 133 | this.symbolTable = new SymbolTable(symbols) 134 | const ilLines = sourceCode.split('\n') 135 | let sectionScope = null 136 | 137 | for(let iline of ilLines) { 138 | if(iline.trim()) { 139 | let label = '' 140 | if(iline.indexOf(':') !== -1) { 141 | [label, iline] = iline.split(':') 142 | } 143 | 144 | 145 | const prts = iline.split(' ').filter(x => x) 146 | const [codeName, ...params] = prts 147 | switch(codeName) { 148 | case 'section': { 149 | const [name, id] = params[0].split('@') 150 | sectionScope = this.symbolTable.findScope(id) 151 | 152 | break 153 | } 154 | case 'set' : { 155 | this.translateSet(sectionScope, params) 156 | break 157 | } 158 | 159 | case 'branch' : { 160 | this.translateBranch(sectionScope, params) 161 | break 162 | } 163 | case 'pass' : { 164 | this.translatePass(sectionScope, params) 165 | break 166 | } 167 | 168 | case 'call' : { 169 | this.translateCall(sectionScope, params) 170 | break 171 | } 172 | } 173 | 174 | } 175 | } 176 | 177 | 178 | } 179 | 180 | print() { 181 | for(let line of this.lines) { 182 | console.log(line) 183 | } 184 | } 185 | } 186 | 187 | class SymbolTable { 188 | constructor(symbols, parent, level = 0) { 189 | if(level === 0) { 190 | this.hash = {} 191 | } 192 | this.parent = parent 193 | if(this.parent){ 194 | this.hash = this.parent.hash 195 | } 196 | 197 | for(let key in symbols) { 198 | this[key] = symbols[key] 199 | } 200 | // this.id = symbols.id 201 | // this.table = symbols.table 202 | this.level = level 203 | 204 | 205 | 206 | this.hash[this.id] = this 207 | for(let key in this.table) { 208 | this.table[key].level = this.level 209 | this.table[key].scopeId = this.id 210 | } 211 | 212 | this.children = symbols.children ? 213 | symbols.children.map(x => new SymbolTable(x, this, level +1)) 214 | : null 215 | } 216 | 217 | findScope(id){ 218 | return this.hash[id] 219 | } 220 | 221 | find(id) { 222 | if(id.indexOf('@') !== -1) { 223 | const [vid,scopeId] = id.split('@') 224 | const scope = this.hash[scopeId] 225 | return scope.table[vid] 226 | } 227 | else { 228 | if(this.table[id]) { 229 | return this.table[id] 230 | } 231 | return this.parent.find(id) 232 | } 233 | } 234 | 235 | 236 | } 237 | 238 | const sourceCode = ` 239 | section fibonacci@2 240 | set %TOP% %SP% 241 | set $t1@2 n == 1 242 | set $t2@2 n == 2 243 | set $t3@2 $t1@2 || $t2@2 244 | branch $t3@2 LB1 245 | return n@2 246 | LB1:set $t4@2 n - 1 247 | pass $t4@2 248 | call fibonacci@1 $t5@2 249 | set $t6@2 n - 2 250 | pass $t6@2 251 | call fibonacci@1 $t7@2 252 | set $t8@2 $t5@2 + $t7@2 253 | return $t8@2@2 254 | section main@1 255 | set %TOP% %SP% 256 | declare function fibonacci@1 257 | pass 5 258 | call fibonacci@1 $t2@1 259 | pass $t2@1 260 | call print@1 $t3@1 261 | ` 262 | 263 | const symbols = `{ 264 | "id": 1, 265 | "table": { 266 | "print": { 267 | "type": "function", 268 | "index": 0 269 | }, 270 | "fibonacci": { 271 | "type": "function", 272 | "index": 1 273 | }, 274 | "$t2": { 275 | "type": "number", 276 | "index": 2 277 | }, 278 | "$t3": { 279 | "type": "number", 280 | "index": 3 281 | } 282 | }, 283 | "children": [ 284 | { 285 | "id": 2, 286 | "table": { 287 | "n": { 288 | "type": "number", 289 | "index": 0 290 | }, 291 | "$t1": { 292 | "type": "number", 293 | "index": 1 294 | }, 295 | "$t2": { 296 | "type": "number", 297 | "index": 2 298 | }, 299 | "$t3": { 300 | "type": "number", 301 | "index": 3 302 | }, 303 | "$t4": { 304 | "type": "number", 305 | "index": 4 306 | }, 307 | "$t5": { 308 | "type": "number", 309 | "index": 5 310 | }, 311 | "$t6": { 312 | "type": "number", 313 | "index": 6 314 | }, 315 | "$t7": { 316 | "type": "number", 317 | "index": 7 318 | }, 319 | "$t8": { 320 | "type": "number", 321 | "index": 8 322 | } 323 | }, 324 | "children": [ 325 | { 326 | "id": 3, 327 | "table": {}, 328 | "children": [] 329 | } 330 | ], 331 | "type": "function", 332 | "argc": 1 333 | } 334 | ] 335 | }` 336 | 337 | const compiler = new OpcodeCompiler() 338 | compiler.parse(sourceCode, JSON.parse(symbols)) 339 | compiler.print() -------------------------------------------------------------------------------- /src/lexer.js: -------------------------------------------------------------------------------- 1 | const op = require('./lexical/op') 2 | const literal = require('./lexical/literal') 3 | const number = require('./lexical/number') 4 | const { makeToken, LexicalError } = require('./lexical/util') 5 | 6 | /** 7 | * 8 | * @param {*} sourceCode 9 | * @returns [Token] {type, value} 10 | */ 11 | function lexer(sourceCode) { 12 | 13 | const tokens = [] 14 | let i = 0 15 | let lineno = 0 16 | 17 | function wrapper(automation_func){ 18 | return (...args) => { 19 | 20 | const token = automation_func(...args) 21 | i += token.value.length 22 | token.lineno = lineno 23 | tokens.push(token) 24 | } 25 | } 26 | const getTokenLiteral = wrapper(literal) 27 | const getTokenNumber = wrapper(number) 28 | const getTokenOp = wrapper(op) 29 | 30 | while(i Token ({type, value}) */ 5 | /* throws LexicalError */ 6 | 7 | /* 8 | * 词语类型 9 | */ 10 | function literal( 11 | sourceCode, 12 | index 13 | ) { 14 | let state = 0 15 | let str = '' 16 | 17 | function getNextChar() { 18 | return sourceCode[index++] 19 | } 20 | 21 | while (true) { 22 | switch (state) { 23 | case 0: { 24 | const c = getNextChar() 25 | if (c.match(/[A-Za-z]/)) { 26 | str += c 27 | state = 1 28 | } else { 29 | throw new LexicalError('not a illegal operator') 30 | } 31 | break 32 | } 33 | case 1: { 34 | const c = getNextChar() 35 | if (c.match(/[A-Za-z0-9]/)) { 36 | str += c 37 | } else { 38 | if (KEYWORDS.includes(str)) { 39 | return makeToken('keyword', str) 40 | } else { 41 | return makeToken('id', str) 42 | } 43 | } 44 | break 45 | } 46 | 47 | } 48 | } 49 | } 50 | 51 | module.exports = literal -------------------------------------------------------------------------------- /src/lexical/number.js: -------------------------------------------------------------------------------- 1 | const { LexicalError, makeToken } = require('./util') 2 | /** 3 | * 4 | * @param {*} sourceCode 5 | * @param {*} index 6 | */ 7 | function number(sourceCode, index) { 8 | let state = 0 9 | let number = '' 10 | while(true){ 11 | const c = sourceCode[index++] 12 | switch(state) { 13 | case 0:{ 14 | if(c === '0'){ 15 | number += c 16 | state = 2 17 | } 18 | else if(c.match(/^[0-9]$/)){ 19 | number += c 20 | state = 1 21 | } 22 | else if(c === '.'){ 23 | number += c 24 | state = 3 25 | }else { 26 | throw new LexicalError('not a number') 27 | } 28 | break 29 | } 30 | case 1:{ 31 | if(c.match(/[0-9]/)){ 32 | number += c 33 | }else { 34 | return makeToken('number', number) 35 | } 36 | break 37 | } 38 | case 2:{ 39 | if(c.match(/[1-9]/)){ 40 | number += c 41 | state = 1 42 | }else if(c === '.'){ 43 | number += c 44 | state = 4 45 | }else { 46 | return makeToken('number', number) 47 | } 48 | break 49 | } 50 | case 3:{ 51 | if(c.match(/[0-9]/)){ 52 | state = 5 53 | number += c 54 | }else { 55 | throw new LexicalError('not a number') 56 | } 57 | } 58 | case 4:{ 59 | if(c.match(/[0-9]/)){ 60 | state = 5 61 | number += c 62 | }else { 63 | return makeToken('number', number) 64 | } 65 | break 66 | } 67 | case 5:{ 68 | if(c.match(/[0-9]/)){ 69 | number += c 70 | }else { 71 | return makeToken('number', number) 72 | } 73 | } 74 | } 75 | } 76 | 77 | } 78 | 79 | module.exports = number -------------------------------------------------------------------------------- /src/lexical/op.js: -------------------------------------------------------------------------------- 1 | const { makeToken, LexicalError } = require('./util') 2 | function op(sourceCode, index){ 3 | let state = 0 4 | let op = '' 5 | 6 | while(true){ 7 | 8 | const c = sourceCode[index++] 9 | op += c 10 | 11 | switch(state) { 12 | case 0:{ 13 | switch(c) { 14 | case '+': 15 | state = 1 16 | break 17 | case '-': 18 | state = 2 19 | break 20 | case '*': 21 | case '/': 22 | return makeToken('op', op) 23 | 24 | case '=': 25 | state = 5 26 | break 27 | case '&': 28 | state = 6 29 | break 30 | case '|': 31 | state = 7 32 | break 33 | case '>': 34 | state = 8 35 | break 36 | case '<': 37 | state = 9 38 | break 39 | case '!': 40 | state = 10 41 | break 42 | case '(': 43 | case ')': 44 | case ';': 45 | return makeToken('op', op) 46 | default: 47 | throw new LexicalError('not an op') 48 | } 49 | break 50 | 51 | } 52 | case 1:{ 53 | if(c === '+') { 54 | return makeToken('op', '++') 55 | } 56 | return makeToken('op', '+') 57 | } 58 | case 2:{ 59 | if(c === '-') { 60 | return makeToken('op', '--') 61 | } 62 | return makeToken('op', '-') 63 | } 64 | 65 | case 5:{ 66 | if(c === '=') { 67 | return makeToken('op', '==') 68 | } 69 | return makeToken('op', '=') 70 | } 71 | case 6:{ 72 | if(c === '&') { 73 | return makeToken('op', '&&') 74 | } 75 | return makeToken('op', '&') 76 | 77 | } 78 | case 7:{ 79 | if(c === '|') { 80 | return makeToken('op', '||') 81 | } 82 | return makeToken('op', '|') 83 | } 84 | case 8:{ 85 | if(c === '=') { 86 | return makeToken('op', '>=') 87 | } 88 | return makeToken('op', '>') 89 | } 90 | case 9:{ 91 | if(c === '=') { 92 | return makeToken('op', '<=') 93 | } 94 | return makeToken('op', '<') 95 | } 96 | case 10: { 97 | if(c === '=') { 98 | return makeToken('op', '!=') 99 | } 100 | return makeToken('op', '!') 101 | } 102 | default: 103 | throw new LexicalError('not an op') 104 | } 105 | 106 | } 107 | 108 | } 109 | 110 | module.exports = op -------------------------------------------------------------------------------- /src/lexical/util.js: -------------------------------------------------------------------------------- 1 | function makeToken(type, value, lineno) { 2 | return { type, value, lineno } 3 | } 4 | 5 | class LexicalError extends Error { 6 | constructor(msg) { 7 | super(msg) 8 | } 9 | } 10 | 11 | 12 | const KEYWORDS = [ 13 | 'if', 14 | 'else', 15 | 'while', 16 | 'for', 17 | 'break', 18 | 'continue', 19 | 'function', 20 | 'return', 21 | 'auto' 22 | ] 23 | 24 | module.exports = { 25 | makeToken, 26 | LexicalError, 27 | KEYWORDS 28 | } -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | const lexer = require('./lexer') 2 | 3 | const { Program, DeclareStmt, Function, Block, IfStmt, ReturnStmt } = require('./ast/Stmt') 4 | const { Args, AssignExpr, FunctionCallExpr } = require('./ast/Expr') 5 | const { Id, Numeral } = require('./ast/Terminal') 6 | const exprParser = require('./exprParser') 7 | 8 | /** 9 | * 自顶部向下递归+lookahead一个token的parser 10 | * Program -> Stmts 11 | * Stmts -> Stmt Stmts | ϵ 12 | * S 13 | */ 14 | class Parser { 15 | parse(sourceCode){ 16 | this.tokens = lexer(sourceCode) 17 | 18 | // 增加一个哨兵,用于判断结尾 19 | this.tokens.push({type : 'eof', value : null}) 20 | this.index = 0 21 | this.lookahead = this.tokens[this.index++] 22 | 23 | const program = this.parseProgram() 24 | program.buildLexicalScope() 25 | return program 26 | } 27 | 28 | read(){ 29 | 30 | if(this.lookahead.type !== 'eof') { 31 | this.lookahead = this.tokens[this.index++] 32 | } 33 | } 34 | 35 | match(value) { 36 | if(this.lookahead.value === value) { 37 | this.read() 38 | return value 39 | } 40 | throw `syntax error @line ${this.lookahead.lineno} : expect ${value} here but ${this.lookahead.value} found.` 41 | } 42 | 43 | matchType(type) { 44 | if(this.lookahead.type === type) { 45 | this.read() 46 | } 47 | throw 'syntax error' 48 | } 49 | 50 | /** 51 | * Program -> Stmts 52 | */ 53 | parseProgram() { 54 | return new Program(this.parseStmts()) 55 | } 56 | 57 | /** 58 | * Stmts -> Stmt Stmts | ϵ 59 | */ 60 | parseStmts() { 61 | const stmts = [] 62 | while(this.lookahead.type !== 'eof' && this.lookahead.value !== '}') { 63 | stmts.push ( this.parseStmt() ) 64 | } 65 | return stmts 66 | } 67 | 68 | /** 69 | * Stmt -> DeclareStmt | IfStmt | WhileStmt | Function | Block | ... 70 | * DeclareStmt -> auto = Expr 71 | * IfStmt -> if Expr Block else IfStmt | if Expr Block | Stmt 72 | * 73 | */ 74 | parseStmt() { 75 | 76 | if(this.lookahead.type === 'id' || this.lookahead.type === 'number') { 77 | return this.parseExpr() 78 | } 79 | 80 | // if(this.lookahead.type === 'number') { // 表达式 81 | // return this.parseExpr() 82 | // } 83 | 84 | switch(this.lookahead.value) { 85 | case 'auto' : 86 | return this.parseDeclareStmt() 87 | case 'function': 88 | return this.parseFunctionStmt() 89 | case 'if': 90 | return this.parseIfStmt() 91 | case 'return': 92 | return this.parseReturnStmt() 93 | default : 94 | console.log(this.lookahead) 95 | throw `syntax error @line ${this.lookahead.lineno} : not impl. ${this.lookahead.value}` 96 | } 97 | 98 | } 99 | 100 | parseBlock() { 101 | this.match('{') 102 | const stmts = this.parseStmts() 103 | this.match('}') 104 | return new Block(stmts) 105 | } 106 | 107 | 108 | 109 | 110 | /** 111 | * FunctionStmt -> function {id}(...ARGS) BLOCK 112 | */ 113 | parseFunctionStmt(){ 114 | this.match('function') 115 | if(this.lookahead.type !== 'id') { 116 | throw 'syntax error' 117 | } 118 | const id = this.lookahead.value 119 | this.match(id) 120 | 121 | this.match('(') 122 | const args = this.parseFuncArguments() 123 | this.match(')') 124 | 125 | const block = this.parseBlock() 126 | return new Function(new Id(id), args, block) 127 | } 128 | 129 | /** 130 | * ReturnStmt -> return Expr 131 | */ 132 | parseReturnStmt() { 133 | this.match('return') 134 | const expr = this.parseExpr() 135 | return new ReturnStmt(expr) 136 | } 137 | 138 | /** 139 | * Args -> | ,Args | ϵ 140 | */ 141 | parseFuncArguments() { 142 | let list = [] 143 | if(this.lookahead.type === 'id') { 144 | const id = this.lookahead.value 145 | this.match(id) 146 | list.push(new Id(id)) 147 | if(this.lookahead.value === ',') { 148 | this.match(',') 149 | list = list.concat(this.parseFuncArguments()) 150 | } 151 | } else { 152 | return [] 153 | } 154 | return new Args(list, 'function') 155 | } 156 | 157 | parseArguments() { 158 | let list = [] 159 | let expr = null 160 | while( (expr = this.parseExpr()) ) { 161 | list.push(expr) 162 | } 163 | return new Args(list) 164 | } 165 | 166 | /** 167 | * IfStmt -> if Expr Block | if Expr Block else IfStmt | if Expr Block else Block 168 | */ 169 | parseIfStmt() { 170 | this.match('if') 171 | const expr = this.parseExpr() 172 | const ifBlock = this.parseBlock() 173 | 174 | if(this.lookahead.value === 'else') { 175 | this.match('else') 176 | 177 | if(this.lookahead.value === 'if') { 178 | const ifStmt = this.parseIfStmt() 179 | return new IfStmt(expr, ifBlock, ifStmt) 180 | } 181 | else { 182 | const elseBlock = this.parseBlock() 183 | return new IfStmt(expr, ifBlock, null, elseBlock) 184 | } 185 | }else { 186 | return new IfStmt(expr, ifBlock) 187 | } 188 | } 189 | 190 | /** 191 | * DeclareStmt -> auto id = expr 192 | */ 193 | parseDeclareStmt() { 194 | 195 | 196 | this.match('auto') 197 | if(this.lookahead.type !== 'id'){ 198 | throw 'syntax error' 199 | } 200 | const id = new Id(this.lookahead.value) 201 | 202 | this.match(this.lookahead.value) 203 | this.match('=') 204 | const right = this.parseExpr() 205 | return new DeclareStmt(id, right) 206 | } 207 | 208 | parseExpr() { 209 | return exprParser(this) 210 | } 211 | 212 | /** 213 | * factor -> number | string | id 214 | */ 215 | parseFactor() { 216 | if(this.lookahead.type === 'number') { 217 | const value = this.match(this.lookahead.value) 218 | return new Numeral(value) 219 | } 220 | else if(this.lookahead.type === 'id') { 221 | const value = this.match(this.lookahead.value) 222 | 223 | if(this.lookahead.value=== '(') { 224 | this.match('(') 225 | const args = this.parseArguments() 226 | this.match(')') 227 | return new FunctionCallExpr(new Id(value), args) 228 | } 229 | else if(this.lookahead.value === '=') { 230 | this.match('=') 231 | const expr = this.parseExpr() 232 | return new AssignExpr(new Id(value), expr) 233 | } 234 | 235 | return new Id(value) 236 | }else if (this.lookahead.type === 'string') { 237 | throw 'not impl.' 238 | }else{ 239 | throw `syntax error, expect a factor but ${this.lookahead.value} found` 240 | } 241 | } 242 | 243 | } 244 | 245 | module.exports = Parser -------------------------------------------------------------------------------- /src/simple_math_parser.js: -------------------------------------------------------------------------------- 1 | const lexer = require('./lexer') 2 | // E -> E + T | E - T | T 3 | // E -> TE' 4 | // E' -> +E | -E | e 5 | 6 | // T -> (E) | T * L | T / L | L 7 | // T -> LT' | (E) 8 | // T' -> *T | /T | e 9 | 10 | class Parser{ 11 | constructor(){ 12 | this.index = 0 13 | } 14 | 15 | eat(value) { 16 | const token = this.tokens[this.index] 17 | if(token.value !== value) { 18 | throw 'syntax error' 19 | } 20 | this.index ++ 21 | } 22 | 23 | parse(sourceCode){ 24 | this.tokens = lexer(sourceCode) 25 | this.index = 0 26 | return this.parseExpr() 27 | } 28 | 29 | parseExpr() { 30 | const term = this.parseTerm() 31 | const expr = this.parseExprR() 32 | 33 | if (expr) { 34 | return { 35 | left: term, 36 | ...expr 37 | } 38 | } else { 39 | return term 40 | } 41 | } 42 | 43 | parseExprR(){ 44 | const token = this.tokens[this.index] 45 | if( token && (token.value === '+' || token.value === '-')){ 46 | this.eat(token.value) 47 | const expr = this.parseExpr() 48 | return { 49 | op : token.value, 50 | right : expr 51 | } 52 | } 53 | return null 54 | } 55 | 56 | parseTerm(){ 57 | const token = this.tokens[this.index] 58 | if(token.value === '(') { 59 | console.log('here') 60 | this.eat('(') 61 | const expr = this.parseExpr() 62 | this.eat(')') 63 | return expr 64 | } 65 | else { 66 | const literal = this.parseLiteral() 67 | const term = this.parseTermR() 68 | if(term) { 69 | return { 70 | left: literal, 71 | ...term 72 | } 73 | } else { 74 | return literal 75 | } 76 | } 77 | } 78 | 79 | parseTermR(){ 80 | const token = this.tokens[this.index] 81 | 82 | if(token && (token.value === '*' || token.value === '/')){ 83 | this.eat(token.value) 84 | const term = this.parseTerm() 85 | return { 86 | op : token.value, 87 | right : term 88 | } 89 | } 90 | return null 91 | } 92 | 93 | parseLiteral() { 94 | const token = this.tokens[this.index] 95 | 96 | if(token.type === 'id' ){ 97 | this.eat(token.value) 98 | return { 99 | type : 'id', 100 | id : token.value 101 | } 102 | }else if(token.type === 'number'){ 103 | this.eat(token.value) 104 | return { 105 | type : 'number', 106 | value : token.value 107 | } 108 | }else { 109 | throw 'syntax error' 110 | } 111 | } 112 | 113 | 114 | } 115 | 116 | 117 | 118 | module.exports = Parser -------------------------------------------------------------------------------- /src/tests/assign_stmt.test.js: -------------------------------------------------------------------------------- 1 | const Parser = require('../parser') 2 | 3 | const parser = new Parser() 4 | 5 | const ast = parser.parse(` 6 | auto x = 100 7 | auto y = 200 8 | auto z = x * (y + 100) / 10 9 | `) 10 | 11 | ast.print() 12 | 13 | const ilcode = [...ast.gen()] 14 | ilcode.forEach(item => { 15 | console.log(item) 16 | }) 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/tests/lexer.test.js: -------------------------------------------------------------------------------- 1 | const lexer = require('../lexer') 2 | 3 | const tokens = lexer(` 4 | auto x = 100.00 5 | auto y = 200.00 6 | auto z = x + y * (100 + x) 7 | `) 8 | 9 | console.log(tokens) -------------------------------------------------------------------------------- /test_lexer.js: -------------------------------------------------------------------------------- 1 | const lexer = require('./src/lexer') 2 | const sourceCode = ` 3 | x+y*(5+6) 4 | ` 5 | 6 | console.log( JSON.stringify(lexer(sourceCode))) -------------------------------------------------------------------------------- /test_recursive_function.js: -------------------------------------------------------------------------------- 1 | 2 | const Parser = require('./src/parser') 3 | const sourceCode = ` 4 | function febonacci(n) { 5 | if(n == 1 || n == 2) { 6 | return n 7 | } 8 | return febonacci(n-1) + febonacci(n-2) 9 | } 10 | 11 | print( febonacci(5) ) 12 | ` 13 | 14 | 15 | const parser = new Parser() 16 | const ast = parser.parse(sourceCode) 17 | ast.print() 18 | console.log('-----SYMBOL TABLE--------') 19 | ast.lexicalScope.print() 20 | console.log('-----il------') 21 | ast.gen() 22 | ast.ilGen.print() 23 | -------------------------------------------------------------------------------- /test_simple_math.js: -------------------------------------------------------------------------------- 1 | const Parser = require('./src/simple_math_parser') 2 | 3 | const sourceCode = ` 4 | x+y*(5+6) 5 | ` 6 | const parser = new Parser() 7 | const ast = parser.parse(sourceCode) 8 | console.log( JSON.stringify(ast, null, 2) ) --------------------------------------------------------------------------------