From 792e98b95c6d3e14cde2266d15eae3fb259ca124 Mon Sep 17 00:00:00 2001 From: sfja Date: Thu, 20 Aug 2026 02:58:34 +0200 Subject: [PATCH] init --- .clang-format | 14 ++ .gitignore | 2 + Makefile | 8 + compile_flags.txt | 6 + hello.lang | 5 + main.cpp | 517 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 552 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 compile_flags.txt create mode 100644 hello.lang create mode 100644 main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a56cbd0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +Language: Cpp +BasedOnStyle: WebKit +IndentWidth: 4 +ColumnLimit: 80 +IndentCaseLabels: true +InsertNewlineAtEOF: true +AllowShortFunctionsOnASingleLine: None + +BinPackArguments: false +AllowAllArgumentsOnNextLine: true + +BinPackParameters: false +AllowAllParametersOfDeclarationOnNextLine: true + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7823f75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +main.o +ssc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ed70a6c --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ + +CXXFLAGS = -std=c++23 -pedantic -Wall -Wextra -O2 -g -fsanitize=address + +cpp_files := $(shell find . -name *.cpp) + +ssc: $(cpp_files:%.cpp=%.o) + g++ $(CXXFLAGS) -o $@ $^ + diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..950bc96 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,6 @@ +-xc++ +-std=c++23 +-pedantic +-Wall +-Wextra + diff --git a/hello.lang b/hello.lang new file mode 100644 index 0000000..f0ecf23 --- /dev/null +++ b/hello.lang @@ -0,0 +1,5 @@ + +(fn main () int ( + (call println "hello") +)) + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..a4fcc3a --- /dev/null +++ b/main.cpp @@ -0,0 +1,517 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::size_t; +using namespace std::literals; + +struct Loc { + size_t idx; + int line; + int col; +}; + +struct File { + std::string filename; + std::string text; + + static auto read_text_file(std::string_view filename) + -> std::expected + { + auto file_stream = std::ifstream(filename.data()); + if (!file_stream) { + return std::unexpected("could not open file"); + } + + auto text_stream = std::stringstream(); + text_stream << file_stream.rdbuf(); + auto text = text_stream.str(); + + return File { + .filename = std::string(filename), + .text = std::move(text), + }; + } +}; + +void print_error(File& file, Loc loc, std::string_view message) +{ + auto& text = file.text; + auto [idx, line, col] = loc; + + constexpr auto type = "error"sv; + + constexpr auto clear = "\x1b[0m"sv; + constexpr auto bold_red = "\x1b[1;91m"sv; + constexpr auto bold_white = "\x1b[1;97m"sv; + constexpr auto cyan = "\x1b[0;36m"sv; + constexpr auto gray = "\x1b[0;90m"sv; + constexpr auto light_gray = "\x1b[0;37m"sv; + constexpr auto green = "\x1b[0;32m"sv; + + auto start = text.find_last_of('\n', idx) + 1; + auto end = text.find_first_of('\n', idx); + + if (end == std::string_view::npos) { + end = text.size(); + } + + auto line_text = text.substr(start, end - start); + + auto linenr_str = std::to_string(line); + + // clang-format off + std::println("" + "{0}{1}{2}: {3}\n" + " {4}--> {5}:{6}:{7}\n" + " {8: <{9}}{10}|\n" + " {11}{12}{13}|{14}{15}\n" + " {16: <{17}}" + "{18}|{19: <{20}}{21}^ {22}{23}{24}\n", + bold_red, type, bold_white, + message, cyan, file.filename, + line, col, "", + linenr_str.size(), gray, light_gray, + linenr_str, gray, green, + line_text, "", linenr_str.size(), + gray, "", col - 1, + bold_red, bold_white, message, + clear); + // clang-format on +} + +enum class TokTy { + Ident, + Str, + Int = '0', + LParen = '(', + RParen = ')', +}; + +struct Tok { + TokTy ty; + Loc loc; + std::string_view text; +}; + +auto tokenize(File& file) -> std::vector +{ + auto& text = file.text; + + size_t i = 0; + int line = 1; + int col = 1; + + auto toks = std::vector(); + + bool error_occured = false; + + auto step = [&]() { + if (text[i] == '\n') { + line += 1; + col = 1; + } else { + col += 1; + } + i += 1; + }; + + auto push = [&](Loc loc, TokTy ty) { + toks.push_back(Tok { + .ty = ty, + .loc = loc, + .text = std::string_view(&text[loc.idx], i - loc.idx), + }); + }; + + while (i < text.size()) { + auto loc = Loc { i, line, col }; + + bool matched = false; + while (i < text.size() && std::strchr(" \t\r\n", text[i])) { + matched = true; + step(); + } + if (matched) + continue; + while (i < text.size() + && ((text[i] >= 'a' && text[i] <= 'z') + || (text[i] >= 'A' && text[i] <= 'Z') + || (matched && text[i] >= '0' && text[i] <= '9') + || std::strchr("_+-*/%=<>", text[i]))) { + matched = true; + step(); + } + if (matched) { + push(loc, TokTy::Ident); + continue; + } + while (i < text.size() + && ((text[i] >= '1' && text[i] <= '9') + || (matched && text[i] == '0'))) { + matched = true; + step(); + } + if (matched) { + push(loc, TokTy::Int); + continue; + } + if (std::strchr("()0", text[i])) { + auto ty = TokTy(text[i]); + step(); + push(loc, ty); + continue; + } + if (text[i] == '"') { + step(); + while (i < text.size() && text[i] != '"') { + if (text[i] == '\\') { + step(); + if (i >= text.size()) + break; + } + step(); + } + if (i >= text.size() || text[i] != '"') { + print_error(file, loc, "malformed string"); + error_occured = true; + continue; + } + step(); + push(loc, TokTy::Str); + continue; + } + print_error(file, loc, "illegal character"); + step(); + error_occured = true; + } + + if (error_occured) { + std::println(stderr, "error(s) occured. aborting."); + std::exit(1); + } + + return toks; +} + +enum class SExprTy { + Ident, + Int, + Str, + List, +}; + +class SExpr { +private: + struct PrivKey { }; + +public: + explicit SExpr([[maybe_unused]] PrivKey key, + SExprTy ty, + Loc loc, + std::variant>> data) + : m_ty(ty) + , m_loc(loc) + , m_data(std::move(data)) { }; + + static auto make_ident(Loc loc, Tok tok) -> std::unique_ptr + { + return std::make_unique(PrivKey { }, SExprTy::Ident, loc, tok); + } + static auto make_int(Loc loc, Tok tok) -> std::unique_ptr + { + return std::make_unique(PrivKey { }, SExprTy::Int, loc, tok); + } + static auto make_str(Loc loc, Tok tok) -> std::unique_ptr + { + return std::make_unique(PrivKey { }, SExprTy::Str, loc, tok); + } + static auto make_list(Loc loc, std::vector> exprs) + -> std::unique_ptr + { + return std::make_unique( + PrivKey { }, SExprTy::List, loc, std::move(exprs)); + } + + auto ty() const -> SExprTy + { + return m_ty; + } + + auto tok() const -> Tok + { + assert(m_ty == SExprTy::Ident || m_ty == SExprTy::Int + || m_ty == SExprTy::Str); + return std::get(m_data); + } + + auto nodes() const -> const std::vector>& + { + assert(m_ty == SExprTy::List); + return std::get>>(m_data); + } + + void print() const + { + if (m_ty == SExprTy::List) { + std::print("("); + bool first = true; + for (auto& node : nodes()) { + if (not first) { + std::print(" "); + } + first = false; + node->print(); + } + std::print(")"); + } else { + std::print("{}", tok().text); + } + } + +private: + SExprTy m_ty; + Loc m_loc; + std::variant>> m_data; +}; + +auto parse_sexprs(File& file) -> std::unique_ptr +{ + auto toks = tokenize(file); + auto tok = toks.begin(); + + auto begin_loc = tok->loc; + + auto parse_expr = [&]() -> std::unique_ptr { + auto impl = [&](auto& self) -> std::unique_ptr { + auto loc = tok->loc; + if (tok->ty == TokTy::Ident) { + auto t = *tok; + ++tok; + return SExpr::make_ident(loc, t); + } else if (tok->ty == TokTy::Int) { + auto t = *tok; + ++tok; + return SExpr::make_int(loc, t); + } else if (tok->ty == TokTy::Str) { + auto t = *tok; + ++tok; + return SExpr::make_str(loc, t); + } else if (tok->ty == TokTy::LParen) { + ++tok; + auto exprs = std::vector>(); + while (tok != toks.end() && tok->ty != TokTy::RParen) { + auto expr = self(self); + if (!expr) + return nullptr; + exprs.push_back(std::move(expr)); + } + if (tok == toks.end() || tok->ty != TokTy::RParen) { + print_error(file, + tok != toks.end() ? tok->loc : loc, + "expected ')'"); + return nullptr; + } + ++tok; + return SExpr::make_list(loc, std::move(exprs)); + } else { + print_error(file, loc, "expected expression"); + return nullptr; + } + }; + return impl(impl); + }; + + auto exprs = std::vector>(); + while (tok != toks.end()) { + auto expr = parse_expr(); + if (!expr) + return nullptr; + exprs.push_back(std::move(expr)); + } + return SExpr::make_list(begin_loc, std::move(exprs)); +} + +class SEMatcher { +public: + using SEPtr = std::unique_ptr; + + explicit SEMatcher(SExpr& node) + : m_node(node) { }; + + auto match() -> std::optional> + { + return not m_failed ? std::optional(std::move(m_captures)) + : std::nullopt; + } + + auto list() -> SEMatcher& + { + if (m_node.ty() != SExprTy::List) { + m_failed = true; + } + return *this; + } + + auto list(size_t count) -> SEMatcher& + { + if (m_node.ty() != SExprTy::List || m_node.nodes().size() != count) { + m_failed = true; + } + return *this; + } + + auto at(size_t idx, auto func) -> SEMatcher& + { + if (m_node.ty() != SExprTy::List || idx >= m_node.nodes().size()) { + m_failed = true; + return *this; + } + auto inner = SEMatcher(*m_node.nodes().at(idx)); + func(inner); + m_captures.merge(inner.m_captures); + m_failed |= inner.m_failed; + return *this; + } + + auto all(auto func) -> SEMatcher& + { + if (m_node.ty() != SExprTy::List) { + m_failed = true; + return *this; + } + for (auto& inner_node : m_node.nodes()) { + auto inner = SEMatcher(*inner_node); + func(inner); + m_captures.merge(inner.m_captures); + m_failed |= inner.m_failed; + } + return *this; + } + + auto capture(std::string id) -> SEMatcher& + { + m_captures[id] = &m_node; + return *this; + } + + auto ident() -> SEMatcher& + { + if (m_node.ty() != SExprTy::Ident) { + m_failed = true; + } + return *this; + } + + auto ident(std::string val) -> SEMatcher& + { + if (m_node.ty() != SExprTy::Ident || m_node.tok().text != val) { + m_failed = true; + } + return *this; + } + +private: + SExpr& m_node; + bool m_failed = false; + std::unordered_map m_captures = { }; +}; + +enum NodeTy { + File, + FnStmt, + CallExpr, +}; + +class Node { +private: + struct PrivKey { }; + +public: + using Vec = std::vector>; + + struct File { + Vec stmts; + }; + + struct FnStmt { + std::string ident; + Vec params; + std::unique_ptr ret_ty; + Vec body; + }; + + using Data = std::variant; + + explicit Node([[maybe_unused]] PrivKey key, NodeTy ty, Data data) + : m_ty(ty) + , m_data(std::move(data)) { }; + + static auto make_file(File file) -> std::unique_ptr + { + return std::make_unique( + PrivKey { }, NodeTy::File, std::move(file)); + } + +private: + NodeTy m_ty; + Data m_data; +}; + +class Parser { +public: + using M = SEMatcher; + using SEPtr = std::unique_ptr; + using NodePtr = std::unique_ptr; + + auto parse_file(SExpr& node) -> NodePtr + { + auto stmts = Node::Vec(); + for (auto& inner : node.nodes()) { + stmts.push_back(parse_stmt(*inner)); + } + return Node::make_file(Node::File { std::move(stmts) }); + } + + auto parse_stmt(SExpr& node) -> NodePtr + { + auto fn_stmt_match + = M(node) + .list(5) + .at(0, [](M& m) { return m.ident("fn"); }) + .at(1, [](M& m) { return m.ident().capture("ident"); }) + .at(2, [](M& m) { return m.list().capture("params"); }) + .at(3, [](M& m) { return m.capture("retty"); }) + .at(4, [](M& m) { return m.list().capture("body"); }) + .match(); + if (fn_stmt_match) { + auto m = std::move(*fn_stmt_match); + std::println("matched!, ident = {}", m["ident"]->tok().text); + } + std::println("not matched :("); + return nullptr; + } +}; + +int main(int argc, char* argv[]) +{ + assert(argc >= 2); + auto file = File::read_text_file(argv[1]).value(); + auto sexpr_ast = parse_sexprs(file); + std::println(); + + auto file_ast = Parser().parse_file(*sexpr_ast); +}