diff --git a/CMakeLists.txt b/CMakeLists.txt index 92c408a..4d9d70d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,17 @@ else() target_compile_options(finch PRIVATE -Wall -Wextra) endif() +# The official Windows LLVM package names the DIA SDK library at the path of the machine it was +# built on (Visual Studio 2019); point it at the Visual Studio installed here. +if(MSVC AND TARGET LLVMDebugInfoPDB AND DEFINED ENV{VSINSTALLDIR}) + get_target_property(FINCH_PDB_LIBS LLVMDebugInfoPDB INTERFACE_LINK_LIBRARIES) + if(FINCH_PDB_LIBS) + file(TO_CMAKE_PATH "$ENV{VSINSTALLDIR}DIA SDK/lib/amd64/diaguids.lib" FINCH_DIA_LIB) + list(TRANSFORM FINCH_PDB_LIBS REPLACE ".*diaguids\\.lib$" "${FINCH_DIA_LIB}") + set_target_properties(LLVMDebugInfoPDB PROPERTIES INTERFACE_LINK_LIBRARIES "${FINCH_PDB_LIBS}") + endif() +endif() + # Link the one shared libLLVM when available: libclang uses it too, and two copies # of LLVM in one process crash on exit. if(TARGET LLVM) diff --git a/src/ast.h b/src/ast.h index 2cb02fb..b996a50 100644 --- a/src/ast.h +++ b/src/ast.h @@ -202,6 +202,7 @@ using StmtPtr = std::unique_ptr; struct BlockStmt : Stmt { std::vector body; + Pos end; // the closing '}' explicit BlockStmt(Pos p) : Stmt(StmtKind::Block, p) {} }; using BlockPtr = std::unique_ptr; @@ -210,6 +211,7 @@ struct VarDeclStmt : Stmt { bool hasType; // `int x = 5` vs `x := 5` Type type; std::string name; + Pos namePos; ExprPtr init; // may be null only when hasType VarDeclStmt(Pos p) : Stmt(StmtKind::VarDecl, p) {} }; @@ -236,12 +238,14 @@ struct WhileStmt : Stmt { }; struct ForStmt : Stmt { std::string var; + Pos varPos; ExprPtr from, to; // for var in from..to (to is exclusive) BlockPtr body; ForStmt(Pos p) : Stmt(StmtKind::For, p) {} }; struct ForEachStmt : Stmt { std::string var; + Pos varPos; ExprPtr list; // for var in list (an array or a str) BlockPtr body; ForEachStmt(Pos p) : Stmt(StmtKind::ForEach, p) {} @@ -266,11 +270,13 @@ struct DeferStmt : Stmt { // defer : runs when the block ends struct Param { Type type; std::string name; - Pos pos; + Pos pos; // the type + Pos namePos; // the name }; struct FnDecl { Pos pos; + Pos namePos; std::string name; Type ret; // Void when there is no `-> type` std::vector params; @@ -282,10 +288,12 @@ struct Field { std::string name; ExprPtr init; // default value, may be null Pos pos; + Pos namePos; }; struct StructDecl { Pos pos; + Pos namePos; std::string name; std::vector fields; }; diff --git a/src/builtins_doc.h b/src/builtins_doc.h new file mode 100644 index 0000000..6e82aa3 --- /dev/null +++ b/src/builtins_doc.h @@ -0,0 +1,69 @@ +#pragma once +// One description of every built-in function and method, shared by the language server +// (hover, completion, signature help). + +struct BuiltinDoc { + const char *name; + const char *signature; + const char *doc; +}; + +inline const BuiltinDoc kBuiltins[] = { + {"print", "print(a, b, ...)", "Prints the values separated by spaces, then a new line. Works with every type."}, + {"input", "input(str question = \"\") -> str", "Shows the question and reads one line typed by the user, without the newline."}, + {"read_file", "read_file(str path) -> str", "The whole file as text. Stops the program if it can't be read."}, + {"write_file", "write_file(str path, str text) -> bool", "Writes (replaces) the file. true if it worked."}, + {"file_exists", "file_exists(str path) -> bool", "Is there a file at this path?"}, + {"delete_file", "delete_file(str path) -> bool", "Deletes the file. true if it worked."}, + {"shell", "shell(str command) -> int", "Runs a command in the system's shell and gives back its exit code."}, + {"exit", "exit(int code)", "Ends the program right away with this exit code."}, + {"addr", "addr(x) -> ptr[T]", "A pointer to a variable, field or element. addr(function) gives C a callback."}, + {"new", "new(value) -> ptr[T]", "Puts a copy of the value on the heap. Give it back with free(...)."}, + {"free", "free(ptr p)", "Gives back memory from new(...) (or from C), freeing what the value owns."}, + {"str", "str(x) -> str", "Turns a number, bool, char, []u8 or C char pointer into text."}, + {"int", "int(x) -> int", "Converts to a whole number: cuts off fractions, parses text (stops the program if it isn't a number)."}, + {"float", "float(x) -> float", "Converts to a decimal number; parses text."}, + {"char", "char(int n) -> char", "The character with this code."}, + {"bool", "bool(int n) -> bool", "true for any number but 0."}, + {"ptr", "ptr(x) -> ptr", "An untyped pointer: from a typed pointer, or from a number (an address or offset for C)."}, +}; + +inline const BuiltinDoc kArrayMethods[] = { + {"len", ".len -> int", "The number of elements."}, + {"ptr", ".ptr -> ptr[T]", "The address of the first element, for C."}, + {"push", "push(T x)", "Adds x at the end."}, + {"pop", "pop() -> T", "Removes the last element and gives it back."}, + {"insert", "insert(int i, T x)", "Puts x at position i, moving the rest."}, + {"remove", "remove(int i) -> T", "Removes position i and gives it back."}, + {"clear", "clear()", "Removes everything."}, + {"resize", "resize(int n)", "Makes the array n long; new elements are zero."}, + {"find", "find(T x) -> int", "The index of x, or -1."}, + {"contains", "contains(T x) -> bool", "Is x in the array?"}, + {"sort", "sort()", "Sorts in place (numbers, chars, str)."}, + {"reverse", "reverse()", "Reverses in place."}, + {"slice", "slice(int start, int end) -> []T", "A new array with the elements start to end-1."}, + {"join", "join(str sep) -> str", "For []str: one text with sep between the parts."}, +}; + +inline const BuiltinDoc kStrMethods[] = { + {"len", ".len -> int", "The number of bytes."}, + {"ptr", ".ptr -> ptr[char]", "The address of the text, for C (valid while the str lives)."}, + {"sub", "sub(int start, int end) -> str", "The part from start to end-1."}, + {"find", "find(str t) -> int", "Where t starts, or -1."}, + {"contains", "contains(str t) -> bool", "Does the text contain t?"}, + {"starts_with", "starts_with(str t) -> bool", "Does the text start with t?"}, + {"ends_with", "ends_with(str t) -> bool", "Does the text end with t?"}, + {"split", "split(str sep) -> []str", "The parts between each sep; split(\"\") gives single characters."}, + {"trim", "trim() -> str", "Without spaces and newlines at both ends."}, + {"upper", "upper() -> str", "With a-z changed to A-Z."}, + {"lower", "lower() -> str", "With A-Z changed to a-z."}, + {"replace", "replace(str from, str to) -> str", "Every from replaced by to."}, + {"repeat", "repeat(int n) -> str", "The text n times."}, + {"bytes", "bytes() -> []u8", "The bytes of the text."}, +}; + +inline const char *kKeywords[] = {"fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", + "false", "null", "import", "link", "struct", "defer"}; + +inline const char *kTypeNames[] = {"int", "float", "bool", "char", "str", "ptr", "i8", "i16", "i32", "i64", + "u8", "u16", "u32", "u64", "f32", "f64"}; diff --git a/src/codegen.cpp b/src/codegen.cpp index 418cfda..b1df457 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3,6 +3,7 @@ // codegen_builtins.cpp; the C calling convention in abi.cpp. #include "codegen_impl.h" +#include "builtins_doc.h" using namespace llvm; @@ -81,6 +82,21 @@ void Codegen::declareStructs() { structStore.push_back(std::move(s)); } for (auto &s : structStore) resolveStruct(s.get()); + if (g_index) + for (auto &s : structStore) { + StructIndex si{s->module, s->name, {}, -1, 0, 0}; + if (s->decl) { + si.file = s->decl->namePos.file; + si.line = s->decl->namePos.line; + si.col = s->decl->namePos.col; + note(s->decl->namePos, s->name.size(), structHover(s.get()), s->decl->namePos); + } + for (auto &f : s->fields) { + si.fields.push_back({f.name, f.type.show()}); + if (s->decl) note(f.pos, f.name.size(), "```finch\n" + f.type.show() + " " + f.name + "\n```\nfield of `" + s->name + "`", f.pos); + } + g_index->structs.push_back(si); + } } void Codegen::declareFns() { @@ -147,6 +163,12 @@ void Codegen::define(Fn &fn) { const FnDecl &d = *fn.decl; curFn = &fn; curModule = fn.module; + if (g_index) { + FnInfo fi{fn.module, d.name, signature(fn), "", {}, d.namePos.file, d.namePos.line, d.namePos.col, d.body->end.line}; + for (size_t i = 0; i < fn.params.size(); i++) fi.params.push_back(fn.params[i].show() + " " + d.params[i].name); + g_index->fns.push_back(fi); + note(d.namePos, d.name.size(), "```finch\n" + signature(fn) + "\n```", d.namePos); + } Function *f = fn.llvm; b.SetInsertPoint(BasicBlock::Create(ctx, "entry", f)); if (di) { @@ -168,8 +190,8 @@ void Codegen::define(Fn &fn) { const Param &p = d.params[i]; const FType &t = fn.params[i]; arg.setName(p.name); - if (owning(t) && !fn.borrowParam[i]) addVar(p.pos, p.name, t, copyValue(&arg, t), true, false, i + 1); // changed inside: own a copy - else addVar(p.pos, p.name, t, &arg, false, false, i + 1); + if (owning(t) && !fn.borrowParam[i]) addVar(p.namePos, p.name, t, copyValue(&arg, t), true, false, i + 1); // changed inside: own a copy + else addVar(p.namePos, p.name, t, &arg, false, false, i + 1); i++; } blockBody(*d.body); @@ -256,7 +278,13 @@ Var &Codegen::addVar(Pos p, const std::string &name, const FType &t, Value *init v.readonly = readonly; v.owned = owned && owning(t); v.order = ++varOrder; + v.pos = p; if (v.owned) scopes.back().cleanups.push_back({false, name}); + if (g_index && name[0] != '$' && curFn) { + const FnDecl &d = *curFn->decl; + g_index->vars.push_back({name, t.show(), p.file, p.line, p.col, d.pos.file, d.pos.line, d.body->end.line}); + note(p, name.size(), "```finch\n" + t.show() + " " + name + "\n```", p); + } if (di && name[0] != '$') { DILocalVariable *dv = argNo ? di->createParameterVariable(diFn, name, argNo, diFiles[p.file], p.line, diType(t)) : di->createAutoVariable(diFn, name, diFiles[p.file], p.line, diType(t)); @@ -319,13 +347,13 @@ void Codegen::varDecl(const VarDeclStmt &s) { Pos ip = s.init->pos; if (v.type.kind == FType::Void) failAt(ip.file, ip.line, ip.col, "this gives back nothing, so it can't be stored"); if (v.type.kind == FType::Null) failAt(ip.file, ip.line, ip.col, "null needs a type, like: ptr[int] " + s.name + " = null"); - addVar(p, s.name, v.type, own(v), true); + addVar(s.namePos, s.name, v.type, own(v), true); return; } FType t = resolve(s.type, p); if (t.kind == FType::Void) failAt(p.file, p.line, p.col, "a variable can't have the type 'nothing'"); Value_ v = s.init ? coerce(exprWant(*s.init, t), t, s.init->pos, "'" + s.name + "'") : defaultValue(t, p); - addVar(p, s.name, t, own(v), true); + addVar(s.namePos, s.name, t, own(v), true); } void Codegen::assign(const AssignStmt &s) { @@ -431,7 +459,7 @@ void Codegen::forStmt(const ForStmt &s) { Value *to = coerce(expr(*s.to), FType::I64, s.to->pos, "the end of a range").v; pushScope(); - Var &iv = addVar(s.pos, s.var, FType::I64, from, false, true); + Var &iv = addVar(s.varPos, s.var, FType::I64, from, false, true); iv.readonlyWhy = "the loop variable '" + s.var + "' can't be changed"; Value *i = iv.slot; @@ -503,7 +531,7 @@ void Codegen::forEachStmt(const ForEachStmt &s) { Value *elem = b.CreateLoad(ty(et), elemAddr); loops.push_back({step, end, scopes.size()}); pushScope(); - Var &xv = addVar(s.pos, s.var, et, copyElems ? copyValue(elem, et) : elem, copyElems, true); + Var &xv = addVar(s.varPos, s.var, et, copyElems ? copyValue(elem, et) : elem, copyElems, true); xv.readonlyWhy = "the loop variable '" + s.var + "' can't be changed (to change the list, loop with for i in 0..list.len and use list[i])"; block(*s.body); popScope(); @@ -655,6 +683,8 @@ Value_ Codegen::expr(const Expr &e) { if (!lookup(v.name)) { auto c = cimports.consts.find(v.name); if (c != cimports.consts.end()) { + note(e.pos, v.name.size(), "C constant `" + v.name + " = " + + (c->second.type.isFloat() ? std::to_string(c->second.f) : std::to_string(c->second.i)) + "`"); if (c->second.type.isFloat()) return {ConstantFP::get(b.getDoubleTy(), c->second.f), FType::F64, true}; return {b.getInt64(c->second.i), FType::I64, true}; } @@ -704,7 +734,10 @@ LRef Codegen::ref(const Expr &e, bool forWrite) { switch (e.kind) { case ExprKind::Var: { auto &v = static_cast(e); - if (Var *var = lookup(v.name)) return {true, {var->slot, var->type}, {}}; + if (Var *var = lookup(v.name)) { + note(e.pos, v.name.size(), "```finch\n" + var->type.show() + " " + v.name + "\n```", var->pos); + return {true, {var->slot, var->type}, {}}; + } if (cimports.globals.count(v.name)) return {true, {cGlobal(v.name), resolve(cimports.globals.at(v.name).type, e.pos)}, {}}; return {false, {}, expr(e)}; } @@ -753,6 +786,10 @@ LRef Codegen::member(const MemberExpr &m, bool forWrite) { } if (t.kind == FType::Array || t.kind == FType::Str || t.kind == FType::Fixed) { + if (m.field == "len" || m.field == "ptr") + note(Pos{p.line, p.col + 1, p.file}, m.field.size(), + m.field == "len" ? "`.len -> int`: the number of " + std::string(t.kind == FType::Str ? "bytes" : "elements") + : "`.ptr`: the address of the first element, for C"); if (m.field != "len" && m.field != "ptr") failAt(p.file, p.line, p.col, (t.kind == FType::Str ? "a str" : "an array") + std::string(" has .len and .ptr, not '.") + m.field + "'"); if (forWrite) failAt(p.file, p.line, p.col, "." + m.field + " can't be changed directly" + (t.kind == FType::Array ? " (use resize, push or pop)" : "")); @@ -786,6 +823,8 @@ LRef Codegen::member(const MemberExpr &m, bool forWrite) { failAt(p.file, p.line, p.col, "the struct " + t.show() + " has no field '" + m.field + "' (it has: " + names + ")"); } const StructInfo::F &f = s->fields[idx]; + note(Pos{p.line, p.col + 1, p.file}, m.field.size(), + "```finch\n" + f.type.show() + " " + m.field + "\n```\nfield of `" + s->name + "`", f.pos); unsigned llvmIdx = f.llvmIndex; if (obj.isPlace) return {true, {b.CreateStructGEP(s->llvm, obj.pl.addr, llvmIdx), f.type}, {}}; // a field of a temporary struct: take it (copying if it owns memory), then drop the rest @@ -1086,6 +1125,33 @@ Value_ Codegen::binary(const BinaryExpr &e) { } } +// ---------- language server index ---------- + +void Codegen::note(Pos at, size_t len, const std::string &hover, Pos def) { + if (!g_index || at.line <= 0) return; + SymRef r{at.file, at.line, at.col, (int)len, hover}; + if (def.line > 0) { + r.defFile = def.file; + r.defLine = def.line; + r.defCol = def.col; + } + g_index->refs.push_back(r); +} + +std::string Codegen::signature(const Fn &fn) { + std::string s = "fn " + (fn.module.empty() ? "" : fn.module + ".") + fn.decl->name + "("; + for (size_t i = 0; i < fn.params.size(); i++) s += (i ? ", " : "") + fn.params[i].show() + " " + fn.decl->params[i].name; + s += ")"; + if (fn.ret.kind != FType::Void) s += " -> " + fn.ret.show(); + return s; +} + +std::string Codegen::structHover(StructInfo *s) { + std::string h = "```finch\nstruct " + s->name + " {\n"; + for (auto &f : s->fields) h += " " + f.type.show() + " " + f.name + "\n"; + return h + "}\n```" + (s->isC ? "\nfrom C" : ""); +} + // ---------- debug info ---------- void Codegen::setLoc(Pos p) { diff --git a/src/codegen_builtins.cpp b/src/codegen_builtins.cpp index b6c8c25..2895dfd 100644 --- a/src/codegen_builtins.cpp +++ b/src/codegen_builtins.cpp @@ -1,6 +1,7 @@ // Code generation, part 3: calls, built-in functions, conversions and methods. #include "codegen_impl.h" +#include "builtins_doc.h" using namespace llvm; @@ -161,9 +162,23 @@ Value *Codegen::cGlobal(const std::string &name) { // ---------- calls ---------- +static std::string docHover(const BuiltinDoc &d) { + return "```finch\n" + std::string(d.signature) + "\n```\n" + d.doc; +} + +static std::string cSignature(const CFunc &f) { + std::string s = f.ret.show() + " " + f.name + "("; + for (size_t i = 0; i < f.params.size(); i++) s += (i ? ", " : "") + f.params[i].show(); + if (f.variadic) s += f.params.empty() ? "..." : ", ..."; + return s + ")"; +} + Value_ Codegen::call(const CallExpr &c) { const std::string &n = c.callee; Pos p = c.pos; + if (g_index) + for (const BuiltinDoc &d : kBuiltins) + if (n == d.name) note(p, n.size(), docHover(d)); auto one = [&](const char *what) -> const Expr & { checkArgs(c.args, c.argNames, 1, p, n); (void)what; @@ -266,9 +281,16 @@ Value_ Codegen::call(const CallExpr &c) { } ModuleScope &m = modules[curModule]; - if (auto it = m.fns.find(n); it != m.fns.end()) return callFinch(it->second, c.args, c.argNames, p, n); - if (StructInfo *s = findStruct("", n, false)) return construct(s, c.args, c.argNames, p); + if (auto it = m.fns.find(n); it != m.fns.end()) { + note(p, n.size(), "```finch\n" + signature(it->second) + "\n```", it->second.decl->namePos); + return callFinch(it->second, c.args, c.argNames, p, n); + } + if (StructInfo *s = findStruct("", n, false)) { + note(p, n.size(), structHover(s), s->decl ? s->decl->namePos : Pos{}); + return construct(s, c.args, c.argNames, p); + } if (auto cf = cimports.fns.find(n); cf != cimports.fns.end()) { + note(p, n.size(), "C function from `" + cf->second.header + "`\n```c\n" + cSignature(cf->second) + "\n```"); checkArgs(c.args, c.argNames, cf->second.params.size(), p, n, cf->second.variadic); return callC(cf->second, c.args, p); } @@ -468,10 +490,16 @@ Value_ Codegen::method(const MethodExpr &m) { if (!lookup(mn) && modules[curModule].imports.count(mn)) { auto mi = modules.find(mn); if (mi == modules.end()) failAt(p.file, p.line, p.col, "the module '" + mn + "' was not found"); - if (auto it = mi->second.fns.find(m.name); it != mi->second.fns.end()) + note(m.obj->pos, mn.size(), "module `" + mn + "`"); + Pos namePos{p.line, p.col + 1, p.file}; + if (auto it = mi->second.fns.find(m.name); it != mi->second.fns.end()) { + note(namePos, m.name.size(), "```finch\n" + signature(it->second) + "\n```", it->second.decl->namePos); return callFinch(it->second, m.args, m.argNames, p, mn + "." + m.name); - if (auto it = mi->second.structs.find(m.name); it != mi->second.structs.end()) + } + if (auto it = mi->second.structs.find(m.name); it != mi->second.structs.end()) { + note(namePos, m.name.size(), structHover(it->second), it->second->decl->namePos); return construct(it->second, m.args, m.argNames, p); + } failAt(p.file, p.line, p.col, "the module '" + mn + "' has no function or struct named '" + m.name + "'"); } if (!lookup(mn) && !cimports.globals.count(mn) && !cimports.consts.count(mn)) @@ -481,6 +509,14 @@ Value_ Codegen::method(const MethodExpr &m) { static const std::set changing = {"push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"}; LRef r = ref(*m.obj, changing.count(m.name) > 0); FType t = r.isPlace ? r.pl.type : r.val.type; + if (g_index && (t.kind == FType::Array || t.kind == FType::Str)) { + auto look = [&](const BuiltinDoc *first, const BuiltinDoc *last) { + for (; first != last; ++first) + if (m.name == first->name) note(Pos{p.line, p.col + 1, p.file}, m.name.size(), docHover(*first)); + }; + if (t.kind == FType::Str) look(std::begin(kStrMethods), std::end(kStrMethods)); + else look(std::begin(kArrayMethods), std::end(kArrayMethods)); + } if (t.kind == FType::Array) { Value_ recv = r.isPlace ? Value_{r.pl.addr, t} : r.val; if (!r.isPlace) { diff --git a/src/codegen_impl.h b/src/codegen_impl.h index ebe3bf0..c8928b0 100644 --- a/src/codegen_impl.h +++ b/src/codegen_impl.h @@ -3,6 +3,7 @@ #include "codegen.h" #include "error.h" +#include "index.h" #include #include @@ -29,6 +30,7 @@ struct StructInfo { const Expr *init = nullptr; // default value bool hidden = false; // C union/bitfield padding: has no name in Finch unsigned llvmIndex = 0; // C structs carry explicit padding between fields + Pos pos; // Finch structs: where the field is declared }; std::vector fields; llvm::StructType *llvm = nullptr; @@ -55,6 +57,7 @@ struct Var { bool owned = false; // dropped when its scope ends int order = 0; // declaration order, for defer bool moved = false; // returned out of the function: not dropped on that path + Pos pos; // where it was declared }; struct Cleanup { @@ -259,6 +262,11 @@ public: Value_ emitCCall(const CFunc &f, llvm::Function *fn, std::vector args, Pos p); llvm::Function *cThunk(Fn &fn, Pos p); + // ---------- language server index ---------- + void note(Pos at, size_t len, const std::string &hover, Pos def = Pos{}); + std::string signature(const Fn &fn); + std::string structHover(StructInfo *s); + // ---------- debug info ---------- void setLoc(Pos p); llvm::DIType *diType(const FType &t); diff --git a/src/codegen_types.cpp b/src/codegen_types.cpp index 5de7e4e..dd0a236 100644 --- a/src/codegen_types.cpp +++ b/src/codegen_types.cpp @@ -99,6 +99,7 @@ void Codegen::resolveStruct(StructInfo *s) { nf.type = resolveT(f.type, f.pos, true); if (nf.type.kind == FType::Void) failAt(f.pos.file, f.pos.line, f.pos.col, "a field can't have the type 'nothing'"); nf.init = f.init.get(); + nf.pos = f.namePos; nf.llvmIndex = elems.size(); elems.push_back(ty(nf.type)); s->owning |= owning(nf.type); diff --git a/src/error.h b/src/error.h index ee71c4d..c848f0c 100644 --- a/src/error.h +++ b/src/error.h @@ -13,8 +13,16 @@ struct SourceFile { inline std::vector g_files; inline int g_curFile = 0; // the file the lexer/parser is working on +// The language server needs errors as values: it shows them in the editor and keeps running. +struct FinchError { + int file, line, col; + std::string msg; +}; +inline bool g_throwErrors = false; + // Finch stops at the first error and shows exactly where it is. [[noreturn]] inline void failAt(int file, int line, int col, const std::string &msg) { + if (g_throwErrors) throw FinchError{file, line, col, msg}; const SourceFile &f = g_files.at(file); std::fprintf(stderr, "%s:%d:%d: error: %s\n", f.path.c_str(), line, col, msg.c_str()); diff --git a/src/index.h b/src/index.h new file mode 100644 index 0000000..9e12b25 --- /dev/null +++ b/src/index.h @@ -0,0 +1,39 @@ +#pragma once +// What the compiler learns about a program while checking it, for the language server: +// what each name is, its type, and where it was defined. + +#include +#include + +struct SymRef { // a name in the source + int file, line, col, len; + std::string hover; // markdown shown on mouse-over + int defFile = -1, defLine = 0, defCol = 0; +}; + +struct VarInfo { // a variable or parameter, for completion + std::string name, type; + int file, line, col; + int fnFile, fnLine, fnEnd; // the function it lives in +}; + +struct FnInfo { + std::string module, name, signature, doc; + std::vector params; + int file, line, col, endLine; +}; + +struct StructIndex { + std::string module, name; + std::vector> fields; // name, type + int file, line, col; +}; + +struct SemIndex { + std::vector refs; + std::vector vars; + std::vector fns; + std::vector structs; +}; + +inline SemIndex *g_index = nullptr; // set by the language server; null when compiling diff --git a/src/parser.cpp b/src/parser.cpp index ccd21bc..58e7c45 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -147,6 +147,7 @@ private: 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(); @@ -157,6 +158,7 @@ private: 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"); @@ -175,6 +177,7 @@ private: 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++; @@ -183,6 +186,7 @@ private: 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)); @@ -207,6 +211,7 @@ private: endOfStatement(); } parenDepth = saved; + b->end = pos(); next(); return b; } @@ -229,12 +234,14 @@ private: } 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(); @@ -242,6 +249,7 @@ private: } 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; @@ -272,6 +280,7 @@ private: 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; @@ -281,6 +290,7 @@ private: 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();