Skip to content

Instantly share code, notes, and snippets.

@ghaiklor
Last active May 24, 2020 12:46
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 ghaiklor/a8ac42aded01fd0cae3af873942314e7 to your computer and use it in GitHub Desktop.
Save ghaiklor/a8ac42aded01fd0cae3af873942314e7 to your computer and use it in GitHub Desktop.
Tiny compiler for mathematical expressions written in TypeScript
// 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