init
This commit is contained in:
commit
792e98b95c
14
.clang-format
Normal file
14
.clang-format
Normal file
@ -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
|
||||||
|
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
main.o
|
||||||
|
ssc
|
||||||
8
Makefile
Normal file
8
Makefile
Normal file
@ -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 $@ $^
|
||||||
|
|
||||||
6
compile_flags.txt
Normal file
6
compile_flags.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-xc++
|
||||||
|
-std=c++23
|
||||||
|
-pedantic
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
|
||||||
5
hello.lang
Normal file
5
hello.lang
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
(fn main () int (
|
||||||
|
(call println "hello")
|
||||||
|
))
|
||||||
|
|
||||||
517
main.cpp
Normal file
517
main.cpp
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <expected>
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <print>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<File, std::string>
|
||||||
|
{
|
||||||
|
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<Tok>
|
||||||
|
{
|
||||||
|
auto& text = file.text;
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
int line = 1;
|
||||||
|
int col = 1;
|
||||||
|
|
||||||
|
auto toks = std::vector<Tok>();
|
||||||
|
|
||||||
|
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<Tok, std::vector<std::unique_ptr<SExpr>>> data)
|
||||||
|
: m_ty(ty)
|
||||||
|
, m_loc(loc)
|
||||||
|
, m_data(std::move(data)) { };
|
||||||
|
|
||||||
|
static auto make_ident(Loc loc, Tok tok) -> std::unique_ptr<SExpr>
|
||||||
|
{
|
||||||
|
return std::make_unique<SExpr>(PrivKey { }, SExprTy::Ident, loc, tok);
|
||||||
|
}
|
||||||
|
static auto make_int(Loc loc, Tok tok) -> std::unique_ptr<SExpr>
|
||||||
|
{
|
||||||
|
return std::make_unique<SExpr>(PrivKey { }, SExprTy::Int, loc, tok);
|
||||||
|
}
|
||||||
|
static auto make_str(Loc loc, Tok tok) -> std::unique_ptr<SExpr>
|
||||||
|
{
|
||||||
|
return std::make_unique<SExpr>(PrivKey { }, SExprTy::Str, loc, tok);
|
||||||
|
}
|
||||||
|
static auto make_list(Loc loc, std::vector<std::unique_ptr<SExpr>> exprs)
|
||||||
|
-> std::unique_ptr<SExpr>
|
||||||
|
{
|
||||||
|
return std::make_unique<SExpr>(
|
||||||
|
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<Tok>(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nodes() const -> const std::vector<std::unique_ptr<SExpr>>&
|
||||||
|
{
|
||||||
|
assert(m_ty == SExprTy::List);
|
||||||
|
return std::get<std::vector<std::unique_ptr<SExpr>>>(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<Tok, std::vector<std::unique_ptr<SExpr>>> m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parse_sexprs(File& file) -> std::unique_ptr<SExpr>
|
||||||
|
{
|
||||||
|
auto toks = tokenize(file);
|
||||||
|
auto tok = toks.begin();
|
||||||
|
|
||||||
|
auto begin_loc = tok->loc;
|
||||||
|
|
||||||
|
auto parse_expr = [&]() -> std::unique_ptr<SExpr> {
|
||||||
|
auto impl = [&](auto& self) -> std::unique_ptr<SExpr> {
|
||||||
|
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<std::unique_ptr<SExpr>>();
|
||||||
|
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<std::unique_ptr<SExpr>>();
|
||||||
|
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<SExpr>;
|
||||||
|
|
||||||
|
explicit SEMatcher(SExpr& node)
|
||||||
|
: m_node(node) { };
|
||||||
|
|
||||||
|
auto match() -> std::optional<std::unordered_map<std::string, SExpr*>>
|
||||||
|
{
|
||||||
|
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<std::string, SExpr*> m_captures = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
enum NodeTy {
|
||||||
|
File,
|
||||||
|
FnStmt,
|
||||||
|
CallExpr,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Node {
|
||||||
|
private:
|
||||||
|
struct PrivKey { };
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Vec = std::vector<std::unique_ptr<Node>>;
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
Vec stmts;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FnStmt {
|
||||||
|
std::string ident;
|
||||||
|
Vec params;
|
||||||
|
std::unique_ptr<Node> ret_ty;
|
||||||
|
Vec body;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Data = std::variant<File>;
|
||||||
|
|
||||||
|
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<Node>
|
||||||
|
{
|
||||||
|
return std::make_unique<Node>(
|
||||||
|
PrivKey { }, NodeTy::File, std::move(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
NodeTy m_ty;
|
||||||
|
Data m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parser {
|
||||||
|
public:
|
||||||
|
using M = SEMatcher;
|
||||||
|
using SEPtr = std::unique_ptr<SExpr>;
|
||||||
|
using NodePtr = std::unique_ptr<Node>;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user