mirror of
https://github.com/Gers2017/cpp.js.git
synced 2024-11-29 10:18:43 -06:00
170 lines
4.7 KiB
JavaScript
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()}`);
|
|
}
|
|
}
|
|
}
|