screaming over here... anyhow you can add yo shit now

This commit is contained in:
Xircon 2026-07-30 22:31:39 -04:00
parent 5cd0ea9206
commit 8a1dd20bd9
13 changed files with 422 additions and 51 deletions

2
.ccls
View File

@ -1,2 +1,2 @@
clang
%c -std=c++23
%cpp -std=c++23

View File

@ -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}/ )

View File

@ -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.
### Note: i hate katex or however you spell it. Its hell.

1
examples/compact/exit.x Normal file
View File

@ -0,0 +1 @@
exit(69);

View File

@ -0,0 +1,2 @@
var x = 69 + 69 + 42;
exit(x + 2);

View File

@ -0,0 +1,3 @@
var x = 7;
var y = 8;
exit(x);

View File

@ -1 +0,0 @@
exit 11;

33
src/arena.hxx Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <optional>
#include <iostream>
#include <stdlib.h>
class ArenaAllocator {
public:
inline explicit ArenaAllocator(size_t bytes) : m_size(bytes) {
m_buffer = static_cast<std::byte*>(malloc(m_size));
m_offset = m_buffer;
}
template<typename T>
inline T* alloc() {
void* offset = m_offset;
m_offset += sizeof(T);
return static_cast<T*>(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;
};

42
src/error.hxx Normal file
View File

@ -0,0 +1,42 @@
#pragma once
#include <iostream>
#include <optional>
namespace err {
inline void syntax(std::optional<std::string> err) {
std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37mexpected '" << err.value() << "'\033[0m" << std::endl;
}
inline void expression(std::optional<std::string> 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<std::string> 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<std::string> 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<std::string> err) {
std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37mredefinition of '" << err.value() << "'\033[0m" << std::endl;
}
inline void undeclared(std::optional<std::string> err) {
std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37mundeclared varible '" << err.value() << "'\033[0m" << std::endl;
}
}

View File

@ -2,23 +2,117 @@
#include <optional>
#include <vector>
#include <cassert>
#include <unordered_map>
#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<std::string, Var> m_vars {};
};

View File

@ -4,6 +4,7 @@
#include <optional>
#include <vector>
#include "arena.hxx"
#include "parser.hxx"
#include "generation.hxx"
#include "tokenization.hxx"
@ -26,18 +27,18 @@ int main(int argc, char* argv[]) {
std::vector<Token> tokens = tokenizer.tokenize();
Parser parser(std::move(tokens));
std::optional<node::Exit> tree = parser.parse();
std::optional<node::Prog> 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");

View File

@ -2,63 +2,176 @@
#include <optional>
#include <vector>
#include <variant>
#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<TermIntLit*, TermIdent*> var;
};
struct Expr {
std::variant<Term*, BinExpr*> var;
};
struct StmtExit {
Expr* expr;
};
struct StmtVar {
Token ident;
Expr* expr;
};
struct Stmt {
std::variant<StmtExit*, StmtVar*> var;
};
struct Prog {
std::vector<Stmt*> stmts;
};
}
class Parser {
public:
inline explicit Parser(std::vector<Token> tokens) : m_tokens(std::move(tokens)) {
inline explicit Parser(std::vector<Token> tokens)
: m_tokens(std::move(tokens)),
m_allocator(1024 * 1024 * 4) { // 4 MB should be ok..?
// Erm!
}
std::optional<node::Expr> parse_expr() {
if(peek().has_value() && peek().value().type == TokenType::int_lit) {
return node::Expr{.int_lit = consume()};
std::optional<node::Term*> parse_term() {
if (auto int_lit = try_consume(TokenType::int_lit)) {
auto term_int_lit = m_allocator.alloc<node::TermIntLit>();
term_int_lit->int_lit = int_lit.value();
auto term = m_allocator.alloc<node::Term>();
term->var = term_int_lit;
return term;
} else if(auto ident = try_consume(TokenType::ident)) {
auto term_ident = m_allocator.alloc<node::TermIdent>();
term_ident->ident = ident.value();
auto term = m_allocator.alloc<node::Term>();
term->var = term_ident;
return term;
} else {
return {};
}
}
std::optional<node::Exit> parse() {
std::optional<node::Exit> 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<node::Expr*> parse_expr() {
if (auto term = parse_term()) {
if (try_consume(TokenType::add).has_value()) {
auto bin_expr = m_allocator.alloc<node::BinExpr>();
auto bin_expr_add = m_allocator.alloc<node::BinExprAdd>();
auto lhs_expr = m_allocator.alloc<node::Expr>();
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<node::Expr>();
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<node::Expr>();
expr->var = term.value();
return expr;
}
} else {
return {};
}
}
std::optional<node::Stmt*> 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<node::StmtExit>();
if (auto node_expr = parse_expr()) {
stmt_exit->expr = node_expr.value();
} else {
err::expression({});
exit(EXIT_FAILURE);
}
if (peek().has_value() && peek().value().type == TokenType::semi) {
try_consume(TokenType::close_paren, ")");
try_consume(TokenType::semi, ";");
auto stmt = m_allocator.alloc<node::Stmt>();
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<node::StmtVar>();
stmt_var->ident = consume();
consume();
if (auto expr = parse_expr()) {
stmt_var->expr = expr.value();
} else {
std::cerr << "xlang: \033[1;31merror:\033[0m \033[1;37minvalid syntax\033[0m" << std::endl;
err::expression({});
}
try_consume(TokenType::semi, ";");
auto stmt = m_allocator.alloc<node::Stmt>();
stmt->var = stmt_var;
return stmt;
} else {
return {};
}
}
std::optional<node::Prog> 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<Token> peek(int ahead = 1) const {
if (m_index + ahead > m_tokens.size()) {
[[nodiscard]] inline std::optional<Token> 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<Token> try_consume(TokenType type) {
if (peek().has_value() && peek().value().type == type) {
return consume();
} else {
return {};
}
}
const std::vector<Token> m_tokens;
size_t m_index = 0;
ArenaAllocator m_allocator;
};

View File

@ -6,10 +6,21 @@
#include <optional>
#include <vector>
#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<char> peek(int ahead = 1) const {
if (m_index + ahead > m_src.length()) {
[[nodiscard]] inline std::optional<char> 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);
}
}