cpp.js/parser.js

170 lines
4.7 KiB
JavaScript

// ------- Parser --------
import { Token, TokenType } from "./main.js";
import { PrintStmt, ReturnStmt, Stmt } from "./stuff.js";
import { panic } from "./utils.js";
const Errors = {
EXPECT_INT_ERR: "Expected type annotation for main function",
LEFT_PAREN_ERR: "Expected '('",
RIGHT_PAREN_ERR: "Expected ')'",
LEFT_BRACE_ERR: "Expected '{'",
RIGHT_BRACE_ERR: "Expected '}'",
PRINTF_ERR: 'Expected "printf" statement',
SEMI_COLON_ERR: "Expected ';'",
STRING_ERR: "Expected string literal",
RETURN_ERR: "Expected return statement",
RETURN_CODE_ERR: "Expected return code",
NO_MORE_TOKENS_ERR: "No more tokens to parse!",
EOF_ERR: "Expected EOF token at the end",
};
/**
* @typedef { { token_type: number, error_message: string } } Pattern
*/
export class Parser {
/** @param { Token[] } tokens */
/** @param { { true_newline: boolean } | undefined } options */
constructor(tokens, options) {
this.tokens = tokens;
this.index = 0;
this.back_index = this.tokens.length - 1;
/**
* @type { Stmt[] }
*/
this.statements = [];
this.options = options ?? {
// By default use '\\n'. Useful for transpiling to rust
true_newline: false,
};
}
is_empty() {
return this.tokens.length == 0;
}
is_not_empty() {
return this.tokens.length > 0;
}
peek() {
if (this.is_empty()) {
panic(Errors.NO_MORE_TOKENS_ERR);
}
return this.tokens[0];
}
peek_back() {
return this.tokens[this.tokens.length - 1];
}
pop_front() {
if (this.is_empty()) {
return null;
}
return this.tokens.shift();
}
pop_back() {
if (this.is_empty()) {
return null;
}
return this.tokens.pop();
}
parse() {
// start main
this.expect(TokenType.INT_TYPE, Errors.EXPECT_INT_ERR);
const fn_name = this.expect(TokenType.IDENTIFIER).value;
if (fn_name !== "main") {
panic(`Invalid function name: "${fn_name}". Expected "main"`);
}
this.expect(TokenType.LEFT_PAREN, Errors.LEFT_PAREN_ERR);
this.expect(TokenType.RIGHT_PAREN, Errors.RIGHT_PAREN_ERR);
this.expect(TokenType.LEFT_BRACE, Errors.LEFT_BRACE_ERR);
this.expect_back(TokenType.EOF, Errors.EOF_ERR);
this.expect_back(TokenType.RIGHT_BRACE, Errors.RIGHT_BRACE_ERR);
// end main
while (this.is_not_empty()) {
this.statements.push(this.get_next_stmt());
}
return this.statements;
}
/**
* @param {number} expected_type
* @param {string} error_message
* @returns { Token }
*/
expect(expected_type, error_message) {
if (this.is_empty()) {
panic(error_message);
} else if (this.peek().token_type !== expected_type) {
panic(`${error_message} at ${this.peek().loc.display()}`);
}
return this.pop_front();
}
/**
* @param { number } expected_type
* @param { string } error_message
* @returns { Token }
*/
expect_back(expected_type, error_message) {
if (this.is_empty()) {
panic(error_message);
} else if (this.peek_back().token_type !== expected_type) {
panic(`${error_message} at ${this.peek().loc.display()}`);
}
return this.pop_back();
}
get_next_stmt() {
/**
* @type { Token }
*/
const token = this.peek();
if (token.token_type === TokenType.PRINTF) {
this.expect(TokenType.PRINTF, Errors.PRINTF_ERR); // skip print
this.expect(TokenType.LEFT_PAREN, Errors.LEFT_PAREN_ERR); // skip '('
let value = this.expect(TokenType.STRING, Errors.STRING_ERR).value; // get the string!
this.expect(TokenType.RIGHT_PAREN, Errors.RIGHT_PAREN_ERR); // skip ')'
this.expect(TokenType.SEMICOLON, Errors.SEMI_COLON_ERR);
if (this.options.true_newline) {
const regex = /\\n/gim;
value = value.replace(regex, "\n");
}
return new PrintStmt(value);
} else if (token.token_type === TokenType.RETURN) {
// console.log("current:", this.peek()?.display());
this.expect(TokenType.RETURN, Errors.RETURN_ERR); // skip return
const value = this.expect(
TokenType.NUMBER,
Errors.RETURN_CODE_ERR
).value; // skip number
this.expect(TokenType.SEMICOLON, Errors.SEMI_COLON_ERR);
return new ReturnStmt(value);
} else {
panic(`Unexpected '${token.value}' ${token.display()}`);
}
}
}