mirror of
https://github.com/Gers2017/cpp.js.git
synced 2024-11-25 00:08:41 -06:00
Cpp.js initial commit | cpp club
This commit is contained in:
commit
e50e93e13e
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/bin
|
||||||
|
/**/*.out
|
||||||
|
/ignore
|
||||||
|
main.rs
|
||||||
|
test.cpp
|
||||||
|
test.rs
|
43
README.md
Normal file
43
README.md
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Cpp.js
|
||||||
|
|
||||||
|
> ## C++ compiler written in Vanilla Javascript
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- [x] Compiles c++ to Rust
|
||||||
|
- [ ] Compiles c++ to Php (Not yet)
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Because I had no other choice.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# node main.js <file>.cpp
|
||||||
|
node main.js main.cpp # compiles a main.cpp file to rust
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test rust code
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# rustc <file>.rs -o <file>.out
|
||||||
|
rustc main.rs -o main.out
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- [ ] `//` comments
|
||||||
|
- [ ] `printf("...");`
|
||||||
|
- [ ] `return`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// This is comment
|
||||||
|
printf("Hello from c++\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
11
main.cpp
Normal file
11
main.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// This is comment
|
||||||
|
// return 1
|
||||||
|
printf("Hello from c++\n\n");
|
||||||
|
printf("Hi rust!!!!\n");
|
||||||
|
printf("123123\n");
|
||||||
|
return 0;
|
||||||
|
}
|
332
main.js
Normal file
332
main.js
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
|
import { Parser } from "./parser.js";
|
||||||
|
import { PrintStmt, ReturnStmt } from "./stuff.js";
|
||||||
|
import {
|
||||||
|
is_alpha,
|
||||||
|
is_alphanum,
|
||||||
|
is_digit,
|
||||||
|
is_whitespace,
|
||||||
|
pretty_error,
|
||||||
|
panic,
|
||||||
|
} from "./utils.js";
|
||||||
|
|
||||||
|
// Javascript enums at home
|
||||||
|
export const TokenType = {
|
||||||
|
LEFT_PAREN: 1,
|
||||||
|
RIGHT_PAREN: 2,
|
||||||
|
LEFT_BRACE: 3,
|
||||||
|
RIGHT_BRACE: 4,
|
||||||
|
SEMICOLON: 5,
|
||||||
|
COMMA: 6,
|
||||||
|
IDENTIFIER: 7,
|
||||||
|
STRING: 8,
|
||||||
|
NUMBER: 9,
|
||||||
|
PRINTF: 10,
|
||||||
|
RETURN: 11,
|
||||||
|
IF: 12,
|
||||||
|
ELSE: 13,
|
||||||
|
INT_TYPE: 14,
|
||||||
|
EOF: 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
const LITERAL_TOKENS = {
|
||||||
|
"(": TokenType.LEFT_PAREN,
|
||||||
|
")": TokenType.RIGHT_PAREN,
|
||||||
|
"{": TokenType.LEFT_BRACE,
|
||||||
|
"}": TokenType.RIGHT_BRACE,
|
||||||
|
",": TokenType.COMMA,
|
||||||
|
";": TokenType.SEMICOLON,
|
||||||
|
};
|
||||||
|
|
||||||
|
const KEYWORDS = {
|
||||||
|
printf: TokenType.PRINTF,
|
||||||
|
int: TokenType.INT_TYPE,
|
||||||
|
return: TokenType.RETURN,
|
||||||
|
if: TokenType.IF,
|
||||||
|
else: TokenType.ELSE,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Token {
|
||||||
|
/**
|
||||||
|
* @param {number} token_type
|
||||||
|
* @param {string} value
|
||||||
|
* @param {Location} loc
|
||||||
|
*/
|
||||||
|
constructor(token_type, value, loc) {
|
||||||
|
this.token_type = token_type;
|
||||||
|
this.value = value;
|
||||||
|
this.loc = loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
display() {
|
||||||
|
return `Token { type: ${this.token_type}, value: '${
|
||||||
|
this.value
|
||||||
|
}', location: ${this.loc.display()} }`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Location {
|
||||||
|
/**
|
||||||
|
* @param { number } row
|
||||||
|
* @param { number } column
|
||||||
|
*/
|
||||||
|
constructor(row, column) {
|
||||||
|
this.row = row;
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
display() {
|
||||||
|
// human readable
|
||||||
|
return `(row: ${this.row + 1}, col: ${this.column + 1})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------- Lexer --------
|
||||||
|
|
||||||
|
class Lexer {
|
||||||
|
/**
|
||||||
|
* @param {string} source
|
||||||
|
*/
|
||||||
|
constructor(source) {
|
||||||
|
this.source = source;
|
||||||
|
this.index = 0;
|
||||||
|
this.cursor = 0; // index at the start of new scan
|
||||||
|
this.row = 0;
|
||||||
|
this.line_start = 0;
|
||||||
|
/** @type {Token[]} */
|
||||||
|
this.tokens = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
length() {
|
||||||
|
return this.source.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_end() {
|
||||||
|
return this.index >= this.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
is_not_end() {
|
||||||
|
return this.index < this.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
advance() {
|
||||||
|
if (this.is_not_end()) {
|
||||||
|
let ch = this.peek();
|
||||||
|
this.index++;
|
||||||
|
|
||||||
|
if (ch === "\n") {
|
||||||
|
this.line_start = this.index;
|
||||||
|
this.row++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_column() {
|
||||||
|
return this.index - this.line_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_location() {
|
||||||
|
return new Location(this.row, this.get_column());
|
||||||
|
}
|
||||||
|
|
||||||
|
peek() {
|
||||||
|
return this.source[this.index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns { string | null }
|
||||||
|
*/
|
||||||
|
peek_next() {
|
||||||
|
if (this.index + 1 >= this.length()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.source[this.index + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(ch) {
|
||||||
|
return this.peek() === ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
next_matches(ch) {
|
||||||
|
let next = this.peek_next();
|
||||||
|
if (next === null) return false;
|
||||||
|
return next === ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { (ch: string) => boolean } predicate
|
||||||
|
*/
|
||||||
|
drop_while(predicate) {
|
||||||
|
while (this.is_not_end() && predicate(this.peek())) {
|
||||||
|
this.advance(); // skip ch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trim_left() {
|
||||||
|
this.drop_while((ch) => is_whitespace(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
drop_line() {
|
||||||
|
this.drop_while((ch) => ch !== "\n");
|
||||||
|
if (this.is_not_end()) {
|
||||||
|
this.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_tokens() {
|
||||||
|
while (this.is_not_end()) {
|
||||||
|
this.cursor = this.index;
|
||||||
|
this.scan_token();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.push_token(new Token(TokenType.EOF, "", this.get_location()));
|
||||||
|
return this.tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_token() {
|
||||||
|
const ch = this.peek();
|
||||||
|
const location = this.get_location();
|
||||||
|
|
||||||
|
if (is_whitespace(ch)) {
|
||||||
|
this.advance();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.matches("#") ||
|
||||||
|
(this.matches("/") && this.next_matches("/"))
|
||||||
|
) {
|
||||||
|
this.drop_line();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ch) {
|
||||||
|
case '"':
|
||||||
|
this.string(location);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (ch in LITERAL_TOKENS) {
|
||||||
|
this.push_token(
|
||||||
|
new Token(LITERAL_TOKENS[ch], ch, location)
|
||||||
|
);
|
||||||
|
this.advance();
|
||||||
|
} else if (is_alpha(ch)) {
|
||||||
|
this.identifier(location);
|
||||||
|
} else if (is_digit(ch)) {
|
||||||
|
this.digit(location);
|
||||||
|
} else {
|
||||||
|
panic(`Lexer error: Unknown token '${ch}'`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push_token(token) {
|
||||||
|
this.tokens.push(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
slice(start) {
|
||||||
|
if (!start) {
|
||||||
|
start = this.cursor;
|
||||||
|
}
|
||||||
|
return this.source.slice(start, this.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
identifier(location) {
|
||||||
|
while (this.is_not_end() && is_alphanum(this.peek())) {
|
||||||
|
this.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = this.slice();
|
||||||
|
const type = value in KEYWORDS ? KEYWORDS[value] : TokenType.IDENTIFIER;
|
||||||
|
this.push_token(new Token(type, value, location));
|
||||||
|
}
|
||||||
|
|
||||||
|
digit(location) {
|
||||||
|
while (this.is_not_end() && is_digit(this.peek())) {
|
||||||
|
this.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = this.slice();
|
||||||
|
this.push_token(new Token(TokenType.NUMBER, value, location));
|
||||||
|
}
|
||||||
|
|
||||||
|
string(location) {
|
||||||
|
this.advance(); // skip '"'
|
||||||
|
|
||||||
|
const start_col = this.get_column();
|
||||||
|
const start_row = this.row;
|
||||||
|
const start = this.index;
|
||||||
|
|
||||||
|
while (this.is_not_end() && this.peek() !== '"') {
|
||||||
|
this.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.is_end()) {
|
||||||
|
let err_line = this.source.slice(this.cursor - 1, this.index);
|
||||||
|
let newline_idx = err_line.indexOf("\n");
|
||||||
|
|
||||||
|
if (newline_idx !== -1) {
|
||||||
|
err_line = err_line.substring(0, newline_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty_error(
|
||||||
|
start_row,
|
||||||
|
start_col,
|
||||||
|
err_line,
|
||||||
|
"You forgot to close the string!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = this.slice(start);
|
||||||
|
|
||||||
|
this.advance(); // skip '"'
|
||||||
|
|
||||||
|
this.push_token(new Token(TokenType.STRING, value, location));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_main();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _main() {
|
||||||
|
const CPP_REGEX = /.\.cpp/i;
|
||||||
|
const args = process.argv;
|
||||||
|
let filename = "main.cpp";
|
||||||
|
|
||||||
|
for (const arg of args) {
|
||||||
|
if (CPP_REGEX.test(arg)) {
|
||||||
|
filename = arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = readFileSync(filename, "utf8");
|
||||||
|
const lexer = new Lexer(source);
|
||||||
|
const tokens = lexer.scan_tokens();
|
||||||
|
|
||||||
|
// console.log(lexer.tokens);
|
||||||
|
|
||||||
|
const parser = new Parser(tokens);
|
||||||
|
const statements = parser.parse();
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
output.push("fn main() {");
|
||||||
|
|
||||||
|
for (const stmt of statements) {
|
||||||
|
if (stmt instanceof PrintStmt) {
|
||||||
|
output.push(`\tprint!("${stmt.string}");`);
|
||||||
|
} else if (stmt instanceof ReturnStmt) {
|
||||||
|
output.push(`\tstd::process::exit(${stmt.value});`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push("}\n");
|
||||||
|
const text = output.join("\n");
|
||||||
|
|
||||||
|
filename = filename.replace("cpp", "rs");
|
||||||
|
writeFileSync(filename, text);
|
||||||
|
}
|
18
package.json
Normal file
18
package.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "cpp-js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "C (or maybe not C++) compiler written in Javascript for fun and profit.",
|
||||||
|
"main": "main.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"cpp",
|
||||||
|
"blazingly-fast",
|
||||||
|
"unsafe-rust",
|
||||||
|
"lightspeed"
|
||||||
|
],
|
||||||
|
"author": "The ultimate life form | kirby",
|
||||||
|
"license": "GPL-3.0-or-later"
|
||||||
|
}
|
162
parser.js
Normal file
162
parser.js
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
// ------- Parser --------
|
||||||
|
|
||||||
|
import { Token, TokenType } from "./main.js";
|
||||||
|
import { PrintStmt, ReturnStmt } 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 */
|
||||||
|
constructor(tokens) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
this.index = 0;
|
||||||
|
this.back_index = this.tokens.length - 1;
|
||||||
|
/**
|
||||||
|
* @type { Stmt[] }
|
||||||
|
*/
|
||||||
|
this.statements = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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 '('
|
||||||
|
const 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);
|
||||||
|
|
||||||
|
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()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
run.sh
Executable file
5
run.sh
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
node main.js main.cpp &&
|
||||||
|
rustc main.rs -o main.out &&
|
||||||
|
./main.out
|
59
stuff.js
Normal file
59
stuff.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
export class AST {}
|
||||||
|
export class Stmt {}
|
||||||
|
|
||||||
|
export class Block extends AST {
|
||||||
|
constructor(stmts) {
|
||||||
|
super();
|
||||||
|
this.stmts = stmts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrintStmt extends Stmt {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} string
|
||||||
|
* @param {string[] | undefined} args
|
||||||
|
*/
|
||||||
|
constructor(string, args) {
|
||||||
|
super();
|
||||||
|
this.string = string;
|
||||||
|
this.args = args ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReturnStmt extends Stmt {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {NumberExpr} value
|
||||||
|
*/
|
||||||
|
constructor(value) {
|
||||||
|
super();
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
export class IfStmt extends Stmt {}
|
||||||
|
|
||||||
|
export class ElseStmt extends Stmt {}
|
||||||
|
|
||||||
|
export class Expr {}
|
||||||
|
|
||||||
|
export class EqualsExpr {
|
||||||
|
constructor(a, b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Num {
|
||||||
|
constructor(value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FunctionDeclaration extends Stmt {
|
||||||
|
constructor(name, params, body) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
38
utils.js
Normal file
38
utils.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
const ALPHA_REGEX = /[a-z]/i;
|
||||||
|
const ALPHA_NUM_REGEX = /[a-z0-9]/i;
|
||||||
|
const WHITESPACE_REGEX = /\s+/i;
|
||||||
|
|
||||||
|
export function is_digit(ch) {
|
||||||
|
const code = ch.charCodeAt(0);
|
||||||
|
return code > 47 && code < 58;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function is_alpha(ch) {
|
||||||
|
// return ch.charCodeAt(0) > 64 && ch.charCodeAt() < 123;
|
||||||
|
return ch.match(ALPHA_REGEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function is_alphanum(ch) {
|
||||||
|
return ALPHA_NUM_REGEX.test(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function is_whitespace(ch) {
|
||||||
|
return WHITESPACE_REGEX.test(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function panic(message) {
|
||||||
|
console.error(message);
|
||||||
|
exit_rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pretty_error(row, column, error_line, error_message) {
|
||||||
|
console.error(`\x1b[31mError at line: ${row + 1} column: ${column + 1}`);
|
||||||
|
console.error(`\n\x1b[33m ${error_line}`);
|
||||||
|
console.error(` \x1b[0m^^^${error_message}`);
|
||||||
|
exit_rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exit_rand() {
|
||||||
|
const rand_code = Math.round(42 + Math.random() * 28);
|
||||||
|
process.exit(rand_code);
|
||||||
|
}
|
Loading…
Reference in a new issue