Last active
May 24, 2020 12:46
-
-
Save ghaiklor/a8ac42aded01fd0cae3af873942314e7 to your computer and use it in GitHub Desktop.
Tiny compiler for mathematical expressions written in TypeScript
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
// Feel free to change this constant and Run the playground | |
// It will emit assembly into Logs tab to your right | |
const EXPRESSION_TO_COMPILE = "5 + 10 * 10"; | |
// Tokens | |
enum TokenType { NUMBER, PLUS, MINUS, STAR, SLASH, LEFT_PAREN, RIGHT_PAREN, EOF } | |
interface Token { type: TokenType, value: string } | |
// Lexical Analysis | |
class Scanner { | |
private readonly source: string; | |
private index: number; | |
constructor(source: string) { | |
this.source = source; | |
this.index = 0; | |
} | |
private number(): Token { | |
let char = this.source[this.index]; | |
let buffer = ''; | |
while (char && (char >= '0' && char <= '9')) { | |
buffer += char; | |
char = this.source[++this.index]; | |
} | |
return { type: TokenType.NUMBER, value: buffer }; | |
} | |
public tokens(): Token[] { | |
const tokens: Token[] = []; | |
while (this.index < this.source.length) { | |
const char = this.source[this.index]; | |
if (char === ' ') { | |
this.index++; | |
continue; | |
} | |
if (char >= '0' && char <= '9') { | |
tokens.push(this.number()); | |
continue; | |
} | |
if (['+', '-', '*', '/', '(', ')'].indexOf(char) > -1) { | |
const types: Record<string, TokenType> = { | |
'+': TokenType.PLUS, | |
'-': TokenType.MINUS, | |
'*': TokenType.STAR, | |
'/': TokenType.SLASH, | |
'(': TokenType.LEFT_PAREN, | |
')': TokenType.RIGHT_PAREN | |
} | |
this.index++; | |
tokens.push({ type: types[char], value: char }) | |
continue; | |
} | |
} | |
return tokens; | |
} | |
} | |
// In sake of simplicity I am doing syntax directed translation, no need in IR | |
class Parser { | |
private readonly tokens: Token[]; | |
private currentToken: Token; | |
private currentIndex: number; | |
constructor(source: string) { | |
this.tokens = new Scanner(source).tokens(); | |
this.currentToken = this.tokens[0]; | |
this.currentIndex = 0; | |
} | |
private expect(type: TokenType): void { | |
if (this.currentToken.type !== type) throw new Error(`Unexpected token: ${this.currentToken.value}`); | |
this.currentToken = this.tokens[++this.currentIndex] || { type: TokenType.EOF, value: '' }; | |
} | |
private emit(code: string) { | |
console.log(`${code}`); | |
} | |
private expression() { | |
this.addition(); | |
} | |
private addition() { | |
this.multiplication(); | |
while (['+', '-'].indexOf(this.currentToken.value) > -1) { | |
switch (this.currentToken.value) { | |
case '+': | |
this.expect(TokenType.PLUS); | |
this.multiplication(); | |
this.emit('pop rbx'); | |
this.emit('pop rax'); | |
this.emit('add rax, rbx'); | |
this.emit('push rax'); | |
break; | |
case '-': | |
this.expect(TokenType.MINUS); | |
this.multiplication(); | |
this.emit('pop rbx'); | |
this.emit('pop rax'); | |
this.emit('sub rax, rbx'); | |
this.emit('push rax'); | |
break; | |
} | |
} | |
} | |
private multiplication() { | |
this.terminal(); | |
while (['*', '/'].indexOf(this.currentToken.value) > -1) { | |
switch (this.currentToken.value) { | |
case '*': | |
this.expect(TokenType.STAR); | |
this.terminal(); | |
this.emit('pop rbx'); | |
this.emit('pop rax'); | |
this.emit('mul rbx'); | |
this.emit('push rax'); | |
break; | |
case '/': | |
this.expect(TokenType.SLASH); | |
this.terminal(); | |
this.emit('pop rbx'); | |
this.emit('pop rax'); | |
this.emit('div rbx'); | |
this.emit('push rax'); | |
break; | |
} | |
} | |
} | |
private terminal() { | |
switch (this.currentToken.type) { | |
case TokenType.NUMBER: | |
this.emit(`mov rbx, ${this.currentToken.value}`); | |
this.emit('push rbx'); | |
this.expect(TokenType.NUMBER); | |
break; | |
case TokenType.LEFT_PAREN: | |
this.expect(TokenType.LEFT_PAREN); | |
this.expression(); | |
this.expect(TokenType.RIGHT_PAREN) | |
break; | |
} | |
} | |
private prelude() { | |
this.emit('global _main'); | |
this.emit('_main:'); | |
} | |
private afterlude() { | |
this.emit('pop rbx'); | |
this.emit('mov rax, 0x2000001'); | |
this.emit('mov rdi, rbx'); | |
this.emit('syscall') | |
} | |
public codegen() { | |
this.prelude(); | |
this.expression(); | |
this.afterlude(); | |
} | |
} | |
new Parser(EXPRESSION_TO_COMPILE).codegen(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment