#include "parser.h" #include "error.h" #include #include #include #include #include #include bool typeFromName(const std::string &name, Type &out) { static const std::unordered_map names = { {"int", Type::I64}, {"float", Type::F64}, {"bool", Type::Bool}, {"char", Type::Char}, {"str", Type::Str}, {"ptr", Type::Ptr}, {"i8", Type::I8}, {"i16", Type::I16}, {"i32", Type::I32}, {"i64", Type::I64}, {"u8", Type::U8}, {"u16", Type::U16}, {"u32", Type::U32}, {"u64", Type::U64}, {"f32", Type::F32}, {"f64", Type::F64}, }; auto it = names.find(name); if (it == names.end()) return false; out = Type(it->second); return true; } namespace { bool isTypeName(const std::string &name) { Type t; return typeFromName(name, t); } class Parser { public: Parser(const std::vector &t, int f) : toks(t), file(f) {} Program program() { Program p; while (!at(Tok::End)) { switch (cur().kind) { case Tok::Import: p.imports.push_back(import()); break; case Tok::Link: p.links.push_back(link()); break; case Tok::Struct: p.structs.push_back(structDecl()); break; default: p.fns.push_back(function()); break; } } return p; } private: const std::vector &toks; int file; size_t i = 0; int parenDepth = 0; // inside ( ) and [ ] newlines don't end anything const Token &cur() const { return toks[i]; } const Token &peekTok(size_t off = 1) const { return toks[std::min(i + off, toks.size() - 1)]; } const Token &next() { return toks[i++]; } bool at(Tok k) const { return cur().kind == k; } Pos pos() const { return {cur().line, cur().col, file}; } std::string describe(const Token &t) const { return t.kind == Tok::Ident ? "'" + t.text + "'" : tokName(t.kind); } bool accept(Tok k) { if (!at(k)) return false; i++; return true; } const Token &expect(Tok k, const char *what = nullptr) { if (!at(k)) fail(cur().line, cur().col, std::string("expected ") + (what ? what : tokName(k)) + ", found " + describe(cur())); return next(); } [[noreturn]] void unexpected(const char *ctx) { fail(cur().line, cur().col, "unexpected " + describe(cur()) + " " + ctx); } // A token can continue an expression only if it is on the same line (or we're inside brackets). bool sameLine() const { return parenDepth > 0 || !cur().newlineBefore; } // int, u8, ptr, ptr[int], []str, Point, math.Vec ... Type type() { if (accept(Tok::LBracket)) { expect(Tok::RBracket, "']' (an array type looks like []int)"); return Type::arrayOf(type()); } if (!at(Tok::Ident)) unexpected("(expected a type like int, str, []int, ptr[int] or a struct name)"); std::string name = next().text; Type t; if (typeFromName(name, t)) { if (t.kind == Type::Ptr && accept(Tok::LBracket)) { t = Type::ptrTo(type()); expect(Tok::RBracket); } return t; } t = Type(Type::Named); t.name = name; if (at(Tok::Dot) && peekTok().kind == Tok::Ident) { // module.Struct next(); t.module = name; t.name = next().text; } return t; } // Does a declaration like `int x`, `ptr[int] p`, `[]int a`, `Point p` or `math.Vec v` start here? bool atDeclaration() const { if (at(Tok::LBracket)) return peekTok().kind == Tok::RBracket; if (!at(Tok::Ident)) return false; Tok n = peekTok().kind; if (n == Tok::Ident) return !peekTok().newlineBefore; if (cur().text == "ptr" && n == Tok::LBracket) return true; return n == Tok::Dot && peekTok(2).kind == Tok::Ident && peekTok(3).kind == Tok::Ident && !peekTok(3).newlineBefore; } // ---------- top level ---------- Import import() { Import im; im.pos = pos(); expect(Tok::Import); if (at(Tok::Ident)) { im.isC = false; im.path = next().text; } else { im.path = expect(Tok::String, "a module name (import math) or a C header in quotes (import \"stdio.h\")").text; } return im; } Link link() { Link l; l.pos = pos(); expect(Tok::Link); l.lib = expect(Tok::String, "a library name in quotes, like link \"glfw\"").text; auto ends = [&](const char *x) { size_t n = std::strlen(x); return l.lib.size() > n && l.lib.compare(l.lib.size() - n, n, x) == 0; }; if (ends(".c") || ends(".o") || ends(".a")) return l; // your own C code, next to the .fch file if (l.lib.rfind("lib", 0) == 0 || l.lib.find(".so") != std::string::npos || l.lib.find('/') != std::string::npos) fail(l.pos.line, l.pos.col, "write just the library's name: for libglfw.so that's link \"glfw\""); return l; } StructDecl structDecl() { StructDecl s; s.pos = pos(); expect(Tok::Struct); s.namePos = pos(); s.name = expect(Tok::Ident, "a struct name").text; if (isTypeName(s.name)) fail(s.pos.line, s.pos.col, "'" + s.name + "' is a built-in type name, pick another"); Pos open = pos(); expect(Tok::LBrace); while (!at(Tok::RBrace)) { if (at(Tok::End)) fail(open.line, open.col, "this '{' is never closed (missing '}')"); Field f; f.pos = pos(); if (!atDeclaration()) unexpected("(a field looks like: int x or str name = \"default\")"); f.type = type(); f.namePos = pos(); f.name = expect(Tok::Ident, "a field name").text; for (const Field &other : s.fields) if (other.name == f.name) fail(f.pos.line, f.pos.col, "the field '" + f.name + "' is listed twice"); if (accept(Tok::Assign)) f.init = expr(); s.fields.push_back(std::move(f)); endOfStatement(); } next(); return s; } FnDecl function() { FnDecl fn; fn.pos = pos(); if (at(Tok::Ident) && isTypeName(cur().text)) fail(fn.pos.line, fn.pos.col, "functions start with 'fn', like: fn add(int a, int b) -> int { ... }"); expect(Tok::Fn, "'fn', 'struct', 'import' or 'link' (every function starts with fn)"); fn.namePos = pos(); fn.name = expect(Tok::Ident, "a function name").text; expect(Tok::LParen); parenDepth++; if (!at(Tok::RParen)) { do { Param p; p.pos = pos(); p.type = type(); p.namePos = pos(); p.name = expect(Tok::Ident, "a parameter name").text; fn.params.push_back(p); } while (accept(Tok::Comma)); } parenDepth--; expect(Tok::RParen); fn.ret = accept(Tok::Arrow) ? type() : Type(Type::Void); fn.body = block(); return fn; } // ---------- statements ---------- BlockPtr block() { auto b = std::make_unique(pos()); expect(Tok::LBrace); int saved = parenDepth; parenDepth = 0; while (!at(Tok::RBrace)) { if (at(Tok::End)) fail(b->pos.line, b->pos.col, "this '{' is never closed (missing '}')"); b->body.push_back(statement()); endOfStatement(); } parenDepth = saved; b->end = pos(); next(); return b; } void endOfStatement() { if (at(Tok::RBrace) || at(Tok::End) || cur().newlineBefore) return; unexpected("(put each statement on its own line)"); } StmtPtr statement() { Pos p = pos(); switch (cur().kind) { case Tok::If: return ifStmt(); case Tok::While: { next(); auto s = std::make_unique(p); s->cond = expr(); s->body = block(); return s; } case Tok::For: { next(); Pos varPos = pos(); std::string var = expect(Tok::Ident, "a loop variable name").text; expect(Tok::In); ExprPtr first = expr(); if (accept(Tok::DotDot)) { // for i in 0..10 auto s = std::make_unique(p); s->var = var; s->varPos = varPos; s->from = std::move(first); s->to = expr(); s->body = block(); return s; } auto s = std::make_unique(p); // for x in list s->var = var; s->varPos = varPos; s->list = std::move(first); s->body = block(); return s; } case Tok::Return: { next(); auto s = std::make_unique(p); if (!at(Tok::RBrace) && !cur().newlineBefore) s->value = expr(); return s; } case Tok::Break: next(); return std::make_unique(p); case Tok::Continue: next(); return std::make_unique(p); case Tok::Defer: { next(); if (at(Tok::Defer)) fail(p.line, p.col, "defer inside defer is not allowed"); return std::make_unique(p, statement()); } case Tok::LBrace: return block(); case Tok::Fn: fail(p.line, p.col, "functions can't be inside other functions (move it out)"); case Tok::Struct: fail(p.line, p.col, "structs go outside functions, at the top level of the file"); case Tok::Import: case Tok::Link: fail(p.line, p.col, "import and link go at the top of the file, outside functions"); default: break; } // int x = 5, Point p, []int a if (atDeclaration()) { auto s = std::make_unique(p); s->hasType = true; s->type = type(); s->namePos = pos(); s->name = expect(Tok::Ident, "a variable name").text; if (accept(Tok::Assign)) s->init = expr(); return s; } // x := 5 if (at(Tok::Ident) && peekTok().kind == Tok::Declare) { auto s = std::make_unique(p); s->hasType = false; s->namePos = pos(); s->name = next().text; next(); s->init = expr(); return s; } ExprPtr e = expr(); // x = 5, p.value += 1, a[i] = 2, ... char op = 0; switch (cur().kind) { case Tok::Assign: op = '='; break; case Tok::PlusEq: op = '+'; break; case Tok::MinusEq: op = '-'; break; case Tok::StarEq: op = '*'; break; case Tok::SlashEq: op = '/'; break; case Tok::PercentEq: op = '%'; break; case Tok::Declare: fail(cur().line, cur().col, "':=' creates a new variable, so the left side must be a plain name"); default: break; } if (op && sameLine()) { if (e->kind != ExprKind::Var && e->kind != ExprKind::Member && e->kind != ExprKind::Index) fail(p.line, p.col, "only a variable, a field, an element or p.value can be assigned to"); auto s = std::make_unique(p); s->target = std::move(e); s->op = op; next(); s->value = expr(); return s; } if (e->kind != ExprKind::Call && e->kind != ExprKind::Method) fail(p.line, p.col, "this value is computed but never used"); return std::make_unique(p, std::move(e)); } StmtPtr ifStmt() { auto s = std::make_unique(pos()); expect(Tok::If); s->cond = expr(); s->then = block(); if (accept(Tok::Else)) { if (at(Tok::If)) s->otherwise = ifStmt(); else s->otherwise = block(); } return s; } // ---------- expressions (lowest to highest precedence) ---------- ExprPtr expr() { return orExpr(); } ExprPtr orExpr() { ExprPtr l = andExpr(); while (sameLine() && at(Tok::Or)) { Pos p = pos(); next(); l = std::make_unique(p, BinOp::Or, std::move(l), andExpr()); } return l; } ExprPtr andExpr() { ExprPtr l = compare(); while (sameLine() && at(Tok::And)) { Pos p = pos(); next(); l = std::make_unique(p, BinOp::And, std::move(l), compare()); } return l; } ExprPtr compare() { ExprPtr l = additive(); while (sameLine()) { BinOp op; switch (cur().kind) { case Tok::Eq: op = BinOp::Eq; break; case Tok::NotEq: op = BinOp::NotEq; break; case Tok::Less: op = BinOp::Less; break; case Tok::LessEq: op = BinOp::LessEq; break; case Tok::Greater: op = BinOp::Greater; break; case Tok::GreaterEq: op = BinOp::GreaterEq; break; default: return l; } Pos p = pos(); next(); l = std::make_unique(p, op, std::move(l), additive()); } return l; } // Precedence is Go's, not C's: bit operators bind tighter than comparisons, // so `x & 1 == 0` means `(x & 1) == 0`. ExprPtr additive() { // + - | ^ ExprPtr l = multiplicative(); while (sameLine()) { BinOp op; switch (cur().kind) { case Tok::Plus: op = BinOp::Add; break; case Tok::Minus: op = BinOp::Sub; break; case Tok::Pipe: op = BinOp::BitOr; break; case Tok::Caret: op = BinOp::BitXor; break; default: return l; } Pos p = pos(); next(); l = std::make_unique(p, op, std::move(l), multiplicative()); } return l; } ExprPtr multiplicative() { // * / % << >> & ExprPtr l = unary(); while (sameLine()) { BinOp op; switch (cur().kind) { case Tok::Star: op = BinOp::Mul; break; case Tok::Slash: op = BinOp::Div; break; case Tok::Percent: op = BinOp::Mod; break; case Tok::Shl: op = BinOp::Shl; break; case Tok::Shr: op = BinOp::Shr; break; case Tok::Amp: op = BinOp::BitAnd; break; default: return l; } Pos p = pos(); next(); l = std::make_unique(p, op, std::move(l), unary()); } return l; } ExprPtr unary() { Pos p = pos(); if (accept(Tok::Minus)) return std::make_unique(p, '-', unary()); if (accept(Tok::Not)) return std::make_unique(p, '!', unary()); if (accept(Tok::Tilde)) return std::make_unique(p, '~', unary()); return postfix(); } // p.value, a.len, a[i], a.push(x), math.sqrt(2) ExprPtr postfix() { ExprPtr e = primary(); while (sameLine()) { Pos p = pos(); if (accept(Tok::Dot)) { std::string name = expect(Tok::Ident, "a name after '.'").text; if (at(Tok::LParen) && !cur().newlineBefore) { auto m = std::make_unique(p, std::move(e), name); args(m->args, m->argNames); e = std::move(m); } else { e = std::make_unique(p, std::move(e), name); } } else if (at(Tok::LBracket)) { next(); parenDepth++; ExprPtr index = expr(); parenDepth--; expect(Tok::RBracket); e = std::make_unique(p, std::move(e), std::move(index)); } else { return e; } } return e; } // ( arg, name: arg, ... ) -- we are at the '(' void args(std::vector &out, std::vector &names) { expect(Tok::LParen); parenDepth++; if (!at(Tok::RParen)) { do { if (at(Tok::RParen)) break; // a trailing comma is fine std::string name; if (at(Tok::Ident) && peekTok().kind == Tok::Colon) { name = next().text; next(); } names.push_back(name); out.push_back(expr()); } while (accept(Tok::Comma)); } parenDepth--; expect(Tok::RParen); } ExprPtr primary() { Pos p = pos(); const Token &t = cur(); switch (t.kind) { case Tok::Int: { next(); errno = 0; unsigned long long v = std::strtoull(t.text.c_str(), nullptr, t.text.size() > 1 && (t.text[1] == 'x' || t.text[1] == 'X') ? 16 : 10); if (errno == ERANGE || v > (unsigned long long)LLONG_MAX) fail(p.line, p.col, "number is too big for int"); return std::make_unique(p, (long long)v); } case Tok::Float: next(); return std::make_unique(p, std::strtod(t.text.c_str(), nullptr)); case Tok::True: next(); return std::make_unique(p, true); case Tok::False: next(); return std::make_unique(p, false); case Tok::Null: next(); return std::make_unique(p); case Tok::Char: next(); return std::make_unique(p, t.text[0]); case Tok::String: next(); return std::make_unique(p, t.text); case Tok::LParen: { next(); parenDepth++; ExprPtr e = expr(); parenDepth--; expect(Tok::RParen); return e; } case Tok::LBracket: { // [1, 2, 3] next(); auto a = std::make_unique(p); parenDepth++; while (!at(Tok::RBracket)) { a->elems.push_back(expr()); if (!accept(Tok::Comma)) break; // a trailing comma is fine } parenDepth--; expect(Tok::RBracket); return a; } case Tok::Ident: { next(); if (at(Tok::LParen) && !cur().newlineBefore) { auto call = std::make_unique(p, t.text); args(call->args, call->argNames); return call; } if (isTypeName(t.text)) fail(p.line, p.col, "a type name alone is not a value (did you mean " + t.text + "(...)?)"); return std::make_unique(p, t.text); } default: unexpected("(expected a value)"); } } }; } // namespace Program parse(const std::vector &tokens, int file) { return Parser(tokens, file).program(); }