From 8a1dd20bd9591ed31f42161d9e4a84506556e146 Mon Sep 17 00:00:00 2001 From: Xircon Date: Thu, 30 Jul 2026 22:31:39 -0400 Subject: [PATCH] screaming over here... anyhow you can add yo shit now --- .ccls | 2 +- CMakeLists.txt | 1 + documentation/lang/Grammar.md | 28 +++++- examples/compact/exit.x | 1 + examples/compact/exitadd.x | 2 + examples/compact/exitvar.x | 3 + examples/exitcompact.x | 1 - src/arena.hxx | 33 ++++++ src/error.hxx | 42 ++++++++ src/generation.hxx | 112 +++++++++++++++++++-- src/main.cxx | 9 +- src/parser.hxx | 183 +++++++++++++++++++++++++++++----- src/tokenization.hxx | 56 +++++++++-- 13 files changed, 422 insertions(+), 51 deletions(-) create mode 100644 examples/compact/exit.x create mode 100644 examples/compact/exitadd.x create mode 100644 examples/compact/exitvar.x delete mode 100644 examples/exitcompact.x create mode 100644 src/arena.hxx create mode 100644 src/error.hxx diff --git a/.ccls b/.ccls index 551a1af..92c9764 100644 --- a/.ccls +++ b/.ccls @@ -1,2 +1,2 @@ clang -%c -std=c++23 \ No newline at end of file +%cpp -std=c++23 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index fb7bbf2..125bfbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ project(xlang enable_language(CXX) set(CMAKE_CXX_COMPILER /bin/clang++) set(CXX_STANDARD 23) +set(CMAKE_CXX_FLAGS "-std=c++${CXX_STANDARD}") include_directories(BEFORE SYSTEM /usr/include ./) link_directories(BEFORE $ENV{LD_LIBRARY_PATH} ${CMAKE_BINARY_DIR}/ ) diff --git a/documentation/lang/Grammar.md b/documentation/lang/Grammar.md index 4b5be7c..a61599a 100644 --- a/documentation/lang/Grammar.md +++ b/documentation/lang/Grammar.md @@ -2,9 +2,29 @@ Basically just an outline of how to type things out and how things work syntactically. $$ \begin{align} -[\text{exit}] &\to exit([\text{expr}]); -\\ -[\text{expr}] &\to \text{int\_lit} + [\text{prog}] &\to [\text{stmt}]^* \\ + [\text{stmt}]^* &\to + \begin{cases} + \text{exit}([\text{expr}]); \\ + \text{var}\space\text{ident} = [\text{expr}]; \\ + \end{cases} \\ + [\text{expr}] &\to + \begin{cases} + \text{[Term]} + \text{[BinExpr]} \\ + \end{cases} \\ + [\text{BinExpr}] + \begin{cases} + [\text[{Expr}] * \text{[Expr]}] &prec=3\\ + [\text[{Expr}] \div \text{[Expr]}] &prec=2\\ + [\text[{Expr}] + \text{[Expr]}] &prec=1\\ + [\text[{Expr}] - \text{[Expr]}] &prec=0\\ + \end{cases} \\ + [\text{Term}] &\to + \begin{cases} + \text{int\_lit} \\ + \text{ident} \\ + \end{cases} \\ \end{align} $$ -### Note: i hate laytec or however you spell it. Its hell. \ No newline at end of file +### Note: i hate katex or however you spell it. Its hell. \ No newline at end of file diff --git a/examples/compact/exit.x b/examples/compact/exit.x new file mode 100644 index 0000000..06233f6 --- /dev/null +++ b/examples/compact/exit.x @@ -0,0 +1 @@ +exit(69); \ No newline at end of file diff --git a/examples/compact/exitadd.x b/examples/compact/exitadd.x new file mode 100644 index 0000000..048a9ae --- /dev/null +++ b/examples/compact/exitadd.x @@ -0,0 +1,2 @@ +var x = 69 + 69 + 42; +exit(x + 2); \ No newline at end of file diff --git a/examples/compact/exitvar.x b/examples/compact/exitvar.x new file mode 100644 index 0000000..d483fb9 --- /dev/null +++ b/examples/compact/exitvar.x @@ -0,0 +1,3 @@ +var x = 7; +var y = 8; +exit(x); \ No newline at end of file diff --git a/examples/exitcompact.x b/examples/exitcompact.x deleted file mode 100644 index fe99dd5..0000000 --- a/examples/exitcompact.x +++ /dev/null @@ -1 +0,0 @@ -exit 11; \ No newline at end of file diff --git a/src/arena.hxx b/src/arena.hxx new file mode 100644 index 0000000..2ff6033 --- /dev/null +++ b/src/arena.hxx @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +class ArenaAllocator { +public: + inline explicit ArenaAllocator(size_t bytes) : m_size(bytes) { + m_buffer = static_cast(malloc(m_size)); + m_offset = m_buffer; + } + + template + inline T* alloc() { + void* offset = m_offset; + m_offset += sizeof(T); + return static_cast(offset); + } + + inline ArenaAllocator(const ArenaAllocator& other) = delete; + + inline ArenaAllocator operator=(const ArenaAllocator& other) = delete; + + inline ~ArenaAllocator() { + free(m_buffer); + } + +private: + size_t m_size; + std::byte* m_buffer; + std::byte* m_offset; +}; \ No newline at end of file diff --git a/src/error.hxx b/src/error.hxx new file mode 100644 index 0000000..c0e5da2 --- /dev/null +++ b/src/error.hxx @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +namespace err { + inline void syntax(std::optional err) { + std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37mexpected '" << err.value() << "'\033[0m" << std::endl; + } + + inline void expression(std::optional err) { + if (err.has_value()) { + std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37minvalid expression `" << err.value() << "'\033[0m" << std::endl; + } else { + std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37minvalid expression\033[0m" << std::endl; + } + } + + inline void unknowntype(std::optional err) { + if (err.has_value()) { + std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37munknown type name '" << err.value() << "'\033[0m" << std::endl; + } else { + std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37munknown type name\033[0m" << std::endl; + } + } + + inline void statement(std::optional err) { + if (err.has_value()) { + std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37minvalid statement '" << err.value() << "'\033[0m" << std::endl; + } else { + std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37minvalid statement\033[0m" << std::endl; + } + } + + inline void redefine(std::optional err) { + std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37mredefinition of '" << err.value() << "'\033[0m" << std::endl; + } + + inline void undeclared(std::optional err) { + std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37mundeclared varible '" << err.value() << "'\033[0m" << std::endl; + } +} \ No newline at end of file diff --git a/src/generation.hxx b/src/generation.hxx index 49f6724..aecb825 100644 --- a/src/generation.hxx +++ b/src/generation.hxx @@ -2,23 +2,117 @@ #include #include +#include +#include #include "parser.hxx" class Generator { public: - inline explicit Generator(node::Exit root) : m_root(std::move(root)) { + inline explicit Generator(node::Prog prog) : m_prog(std::move(prog)) { // Erm!!! } - [[nodiscard]] inline std::string generate() const { - std::stringstream output; - output << "global _start\n_start:\n"; - output << " mov rax, 60\n"; - output << " mov rdi, " << m_root.expr.int_lit.value.value() << "\n"; - output << " syscall\n"; - return output.str(); + void gen_stmt(const node::Stmt* stmt) { + struct StmtVisitor { // Visiting the homies + Generator* gen; + void operator()(const node::StmtExit* stmt_exit) const { + gen->gen_expr(stmt_exit->expr); + gen->m_output << " mov rax, 60\n"; + gen->pop("rdi"); + gen->m_output << " syscall\n"; + } + + void operator()(const node::StmtVar* stmt_var) { + if (gen->m_vars.contains(stmt_var->ident.value.value())) { + err::redefine(stmt_var->ident.value.value()); + exit(EXIT_FAILURE); + } + gen->m_vars.insert({stmt_var->ident.value.value(), Var {.stack_loc = gen->m_stack_size}}); + gen->gen_expr(stmt_var->expr); + } + }; + + StmtVisitor visitor {.gen = this}; + std::visit(visitor, stmt->var); + } + + void gen_term(const node::Term* term) { + struct TermVisitor { + Generator* gen; + + void operator()(const node::TermIntLit* term_int_lit) const { + gen->m_output << " mov rax, " << term_int_lit->int_lit.value.value() << "\n"; + gen->push("rax"); + } + + void operator()(const node::TermIdent* term_ident) const { + if (!gen->m_vars.contains(term_ident->ident.value.value())) { + err::undeclared(term_ident->ident.value.value()); + exit(EXIT_FAILURE); + } + const auto& var = gen->m_vars.at(term_ident->ident.value.value()); + std::stringstream offset; + offset << "QWORD [rsp + " << (gen->m_stack_size - var.stack_loc - 1) * 8 << "]"; + gen->push(offset.str()); + } + }; + TermVisitor visitor ({.gen = this}); + std::visit(visitor, term->var); + } + + void gen_expr(const node::Expr* expr) { + struct ExprVisitor { + Generator* gen; + + void operator()(const node::Term* term) const { + gen->gen_term(term); + } + + void operator()(const node::BinExpr* bin_expr) const { + gen->gen_expr(bin_expr->var->lhs); + gen->gen_expr(bin_expr->var->rhs); + gen->pop("rax"); + gen->pop("rbx"); + gen->m_output << " add rax, rbx\n"; + gen->push("rax"); + } + }; + + ExprVisitor visitor {.gen = this}; + std::visit(visitor, expr->var); + } + + [[nodiscard]] inline std::string gen_prog() { + m_output << "global _start\n_start:\n"; + + for (const node::Stmt* stmt : m_prog.stmts) { + gen_stmt(stmt); + } + + m_output << " mov rax, 60\n"; + m_output << " mov rdi, 0\n"; + m_output << " syscall\n"; + return m_output.str(); } private: - const node::Exit m_root; + + void push(const std::string& reg) { + m_output << " push " << reg << "\n"; + m_stack_size++; + } + + void pop(const std::string& reg) { + m_output << " pop " << reg << "\n"; + m_stack_size--; + } + + struct Var { + size_t stack_loc; + }; + + const node::Prog m_prog; + std::stringstream m_output; + size_t m_stack_size = 0; + std::unordered_map m_vars {}; }; \ No newline at end of file diff --git a/src/main.cxx b/src/main.cxx index 89c2169..a191ee0 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -4,6 +4,7 @@ #include #include +#include "arena.hxx" #include "parser.hxx" #include "generation.hxx" #include "tokenization.hxx" @@ -26,18 +27,18 @@ int main(int argc, char* argv[]) { std::vector tokens = tokenizer.tokenize(); Parser parser(std::move(tokens)); - std::optional tree = parser.parse(); + std::optional prog = parser.parse_prog(); - if (!tree.has_value()) { + if (!prog.has_value()) { std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37mparse tree has no value, possibly empty file?\033[0m" << std::endl; exit(EXIT_FAILURE); } - Generator generator(tree.value()); + Generator generator(prog.value()); { std::fstream output("out.asm", std::ios::out); - output << generator.generate(); + output << generator.gen_prog(); } system("nasm -felf64 out.asm"); diff --git a/src/parser.hxx b/src/parser.hxx index 8a94430..312195b 100644 --- a/src/parser.hxx +++ b/src/parser.hxx @@ -2,63 +2,176 @@ #include #include +#include #include "tokenization.hxx" +#include "error.hxx" +#include "arena.hxx" namespace node { - struct Expr { + struct TermIntLit { Token int_lit; }; - struct Exit { - Expr expr; + struct TermIdent { + Token ident; + }; + + struct Expr; + + struct BinExprAdd { + Expr* lhs; + Expr* rhs; + }; + + // struct BinExprMul { + // Expr* lhs; + // Expr* rhs; + // }; + + struct BinExpr { + BinExprAdd* var; + }; + + struct Term { + std::variant var; + }; + + struct Expr { + std::variant var; + }; + + struct StmtExit { + Expr* expr; + }; + + struct StmtVar { + Token ident; + Expr* expr; + }; + + struct Stmt { + std::variant var; + }; + + struct Prog { + std::vector stmts; }; } class Parser { public: - inline explicit Parser(std::vector tokens) : m_tokens(std::move(tokens)) { + inline explicit Parser(std::vector tokens) + : m_tokens(std::move(tokens)), + m_allocator(1024 * 1024 * 4) { // 4 MB should be ok..? // Erm! } - std::optional parse_expr() { - if(peek().has_value() && peek().value().type == TokenType::int_lit) { - return node::Expr{.int_lit = consume()}; + std::optional parse_term() { + if (auto int_lit = try_consume(TokenType::int_lit)) { + auto term_int_lit = m_allocator.alloc(); + term_int_lit->int_lit = int_lit.value(); + auto term = m_allocator.alloc(); + term->var = term_int_lit; + return term; + } else if(auto ident = try_consume(TokenType::ident)) { + auto term_ident = m_allocator.alloc(); + term_ident->ident = ident.value(); + auto term = m_allocator.alloc(); + term->var = term_ident; + return term; } else { return {}; } } - std::optional parse() { - std::optional exit_node; - while(peek().has_value()) { - if (peek().value().type == TokenType::exit) { - consume(); - if (auto node_expr = parse_expr()) { - exit_node = node::Exit {.expr = node_expr.value()}; + std::optional parse_expr() { + if (auto term = parse_term()) { + if (try_consume(TokenType::add).has_value()) { + auto bin_expr = m_allocator.alloc(); + auto bin_expr_add = m_allocator.alloc(); + auto lhs_expr = m_allocator.alloc(); + lhs_expr->var = term.value(); + bin_expr_add->lhs = lhs_expr; + if (auto rhs = parse_expr()) { + bin_expr_add->rhs = rhs.value(); + bin_expr->var = bin_expr_add; + auto expr = m_allocator.alloc(); + expr->var = bin_expr; + return expr; } else { - std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37minvalid expression\033[0m" << std::endl; + err::expression({}); exit(EXIT_FAILURE); } + } else { + auto expr = m_allocator.alloc(); + expr->var = term.value(); + return expr; + } + } else { + return {}; + } + } - if (peek().has_value() && peek().value().type == TokenType::semi) { - consume(); - } else { - std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37minvalid syntax\033[0m" << std::endl; - exit(EXIT_FAILURE); - } + std::optional parse_stmt() { + if (peek().has_value() && peek().value().type == TokenType::exit && peek(1).has_value() && peek(1).value().type == TokenType::open_paren) { + consume(); + consume(); + auto stmt_exit = m_allocator.alloc(); + if (auto node_expr = parse_expr()) { + stmt_exit->expr = node_expr.value(); + } else { + err::expression({}); + exit(EXIT_FAILURE); + } + + try_consume(TokenType::close_paren, ")"); + try_consume(TokenType::semi, ";"); + + auto stmt = m_allocator.alloc(); + stmt->var = stmt_exit; + return stmt; + } else if (peek().has_value() && peek().value().type == TokenType::var + && peek(1).has_value() && peek(1).value().type == TokenType::ident + && peek(2).has_value() && peek(2).value().type == TokenType::equals) { // Holy else-if statement + consume(); + auto stmt_var = m_allocator.alloc(); + stmt_var->ident = consume(); + consume(); + if (auto expr = parse_expr()) { + stmt_var->expr = expr.value(); + } else { + err::expression({}); + } + try_consume(TokenType::semi, ";"); + + auto stmt = m_allocator.alloc(); + stmt->var = stmt_var; + return stmt; + } else { + return {}; + } + } + + std::optional parse_prog() { + node::Prog prog; + while (peek().has_value()) { + if (auto stmt = parse_stmt()) { + prog.stmts.push_back(stmt.value()); + } else { + err::statement({}); + exit(EXIT_FAILURE); } } - m_index = 0; - return exit_node; + return prog; } private: - [[nodiscard]] inline std::optional peek(int ahead = 1) const { - if (m_index + ahead > m_tokens.size()) { + [[nodiscard]] inline std::optional peek(int offset = 0) const { + if (m_index + offset >= m_tokens.size()) { return {}; } else { - return m_tokens.at(m_index); + return m_tokens.at(m_index + offset); } } @@ -66,6 +179,24 @@ private: return m_tokens.at(m_index++); } + inline Token try_consume(TokenType type, const std::string& err) { + if (peek().has_value() && peek().value().type == type) { + return consume(); + } else { + err::syntax(err); + exit(EXIT_FAILURE); + } + } + + inline std::optional try_consume(TokenType type) { + if (peek().has_value() && peek().value().type == type) { + return consume(); + } else { + return {}; + } + } + const std::vector m_tokens; size_t m_index = 0; + ArenaAllocator m_allocator; }; \ No newline at end of file diff --git a/src/tokenization.hxx b/src/tokenization.hxx index c62929a..fba07b2 100644 --- a/src/tokenization.hxx +++ b/src/tokenization.hxx @@ -6,10 +6,21 @@ #include #include +#include "error.hxx" + enum class TokenType { exit, int_lit, semi, + open_paren, + close_paren, + ident, + var, + equals, + add, + mul, + div, + sub }; struct Token { @@ -37,9 +48,14 @@ public: tokens.push_back({.type = TokenType::exit}); buffer.clear(); continue; + } else if (buffer == "var") { + tokens.push_back({.type = TokenType::var}); + buffer.clear(); + continue; } else { - std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37munknown type name \'" << buffer << "\'\033[0m" << std::endl; - exit(EXIT_FAILURE); + tokens.push_back({.type = TokenType::ident, .value = buffer}); + buffer.clear(); + continue; } } else if (std::isdigit(peek().value())) { buffer.push_back(consume()); @@ -49,15 +65,43 @@ public: tokens.push_back({.type = TokenType::int_lit, .value = buffer}); buffer.clear(); continue; + } else if (peek().value() == '(') { + consume(); + tokens.push_back({.type = TokenType::open_paren}); + continue; + } else if (peek().value() == ')') { + consume(); + tokens.push_back({.type = TokenType::close_paren}); + continue; } else if (peek().value() == ';') { consume(); tokens.push_back({.type = TokenType::semi}); continue; + } else if (peek().value() == '=') { + consume(); + tokens.push_back({.type = TokenType::equals}); + continue; + } else if (peek().value() == '+') { + consume(); + tokens.push_back({.type = TokenType::add}); + continue; + } else if (peek().value() == '*') { + consume(); + tokens.push_back({.type = TokenType::mul}); + continue; + } else if (peek().value() == '-') { + consume(); + tokens.push_back({.type = TokenType::sub}); + continue; + } else if (peek().value() == '/') { + consume(); + tokens.push_back({.type = TokenType::div}); + continue; } else if (std::isspace(peek().value())) { consume(); continue; } else { - std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37munknown type name \'" << buffer << "\'\033[0m" << std::endl; + err::unknowntype(buffer); exit(EXIT_FAILURE); } } @@ -67,11 +111,11 @@ public: } private: - [[nodiscard]] inline std::optional peek(int ahead = 1) const { - if (m_index + ahead > m_src.length()) { + [[nodiscard]] inline std::optional peek(int offset = 0) const { + if (m_index + offset >= m_src.length()) { return {}; } else { - return m_src.at(m_index); + return m_src.at(m_index + offset); } }