diff --git a/.gitignore b/.gitignore index 3f8bde6..7cfa7f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /bin -/**/*.out /ignore +/**/*.out *.rs +*.asm +*.elf test.cpp test.rs \ No newline at end of file diff --git a/Makefile b/Makefile index 619abc7..30d961d 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,20 @@ CC=rustc - +ASM=fasm default: build-rs build-rs: - node main.js main.cpp && $(CC) main.rs -o main.out + node main.js main.cpp --target rust && $(CC) main.rs -o main.out + +build-fasm: + node main.js main.cpp --target x86_64-fasm-linux-gnu && $(ASM) main.asm main.elf && chmod +x main.elf run: ./main.out +run-fasm: + ./main.elf + clean: rm *.out diff --git a/interpreter.js b/interpreter.js index f000b4f..1ed8b8f 100644 --- a/interpreter.js +++ b/interpreter.js @@ -2,7 +2,7 @@ import { writeFileSync } from "fs"; import { Stmt, PrintStmt, ReturnStmt } from "./stuff.js"; /** - * + * @param {string} filename * @param {Stmt[]} statements */ export function generate_rust_code(filename, statements) { @@ -22,3 +22,49 @@ export function generate_rust_code(filename, statements) { writeFileSync(filename, text); } + +/** + * @param {string} filename + * @param {Stmt[]} statements + */ +export function generate_fasm_linux(filename, statements) { + const output = []; + output.push("format ELF executable 3"); + output.push("entry start"); + output.push("segment readable executable"); + output.push("start:"); + + const push_syscall = () => output.push("int 0x80"); + + for (let i = 0; i < statements.length; i++) { + const stmt = statements[i]; + + if (stmt instanceof PrintStmt) { + output.push("mov eax, 4"); // write syscall + output.push("mov ebx, 1"); // stdout (fd) + output.push(`mov ecx, str${i}`); + output.push(`mov edx, ${stmt.string.length}`); + push_syscall(); + } else if (stmt instanceof ReturnStmt) { + output.push("mov eax, 1"); // exit syscall + output.push(`mov ebx, ${stmt.value}`); // return code + push_syscall(); + } + } + + output.push("segment readable writeable"); + + for (let i = 0; i < statements.length; i++) { + const stmt = statements[i]; + if (stmt instanceof PrintStmt) { + const message = [...stmt.string] + .map((ch) => ch.charCodeAt(0)) + .join(","); + + output.push(`str${i} db ${message}`); + } + } + + const text = output.join("\n"); + writeFileSync(filename, text); +} diff --git a/main.js b/main.js index 4960734..30f4fff 100644 --- a/main.js +++ b/main.js @@ -1,6 +1,6 @@ import { readFileSync } from "fs"; import { Parser } from "./parser.js"; -import { generate_rust_code } from "./interpreter.js"; +import { generate_fasm_linux, generate_rust_code } from "./interpreter.js"; import { is_alpha, @@ -366,7 +366,8 @@ function _main() { const lexer = new Lexer(source); const tokens = lexer.scan_tokens(); - const parser = new Parser(tokens); + const is_asm = target == TARGETS[1]; + const parser = new Parser(tokens, { true_newline: is_asm }); const statements = parser.parse(); switch (target) { @@ -377,7 +378,7 @@ function _main() { case TARGETS[1]: // x86_64-fasm-linux-gnu filename = filename.replace(".cpp", ".asm"); - // TODO GENERATE ASM! + generate_fasm_linux(filename, statements); default: break; } diff --git a/parser.js b/parser.js index d7e70a7..7dd96aa 100644 --- a/parser.js +++ b/parser.js @@ -25,7 +25,8 @@ const Errors = { export class Parser { /** @param { Token[] } tokens */ - constructor(tokens) { + /** @param { { true_newline: boolean } | undefined } options */ + constructor(tokens, options) { this.tokens = tokens; this.index = 0; this.back_index = this.tokens.length - 1; @@ -33,6 +34,10 @@ export class Parser { * @type { Stmt[] } */ this.statements = []; + this.options = options ?? { + // By default use '\\n'. Useful for transpiling to rust + true_newline: false, + }; } is_empty() { @@ -135,13 +140,15 @@ export class Parser { 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! + 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());