mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
Finch 2.4.0: methods, maps, errors as values; str() of any value; macOS and ARM64 docs; Kate support
- fn Struct.method(...) with self by reference, also on C structs
- map[K]V: insertion-ordered hash maps, literals, m[k] += 1, has/get/remove/keys/values
- -> T! / -> !, return error(...), or <value>, or { err }, try; fallible int/float/read_file/write_file/delete_file
- for i, x in list / for k, v in map
- print and str() share one text formatter
- LSP: method and map completion, method signatures; grammar and snippets for VS Code and Kate
- tests: 74 (methods, errors, maps + 11 failure cases); docs at all three levels, en and pl
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fd86eb885c
commit
b872be3176
55 changed files with 3400 additions and 336 deletions
44
src/ast.h
44
src/ast.h
|
|
@ -11,12 +11,14 @@ struct Type {
|
|||
Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64,
|
||||
Str, Ptr, Null,
|
||||
Array, // []T: growable, owns its elements
|
||||
Map, // map[K]V: a hash map that keeps insertion order, owns its keys and values
|
||||
Fixed, // [N]T: only inside C structs
|
||||
Struct, // a resolved struct
|
||||
Named, // a struct name as written, before codegen resolves it
|
||||
};
|
||||
Kind kind = Void;
|
||||
std::shared_ptr<Type> elem; // Ptr (null = untyped `ptr`), Array, Fixed
|
||||
std::shared_ptr<Type> elem; // Ptr (null = untyped `ptr`), Array, Fixed; Map: the value
|
||||
std::shared_ptr<Type> key; // Map
|
||||
long long count = 0; // Fixed
|
||||
std::string name; // Named / Struct
|
||||
std::string module; // Named: written qualifier ("" = none); Struct: defining module
|
||||
|
|
@ -34,6 +36,12 @@ struct Type {
|
|||
a.elem = std::make_shared<Type>(t);
|
||||
return a;
|
||||
}
|
||||
static Type mapOf(const Type &k, const Type &v) {
|
||||
Type m(Map);
|
||||
m.key = std::make_shared<Type>(k);
|
||||
m.elem = std::make_shared<Type>(v);
|
||||
return m;
|
||||
}
|
||||
|
||||
bool isInt() const { return kind >= I8 && kind <= U64; }
|
||||
bool isSigned() const { return kind >= I8 && kind <= I64; }
|
||||
|
|
@ -58,6 +66,7 @@ struct Type {
|
|||
if (!elem || !o.elem) return !elem && !o.elem;
|
||||
return *elem == *o.elem;
|
||||
case Array: return *elem == *o.elem;
|
||||
case Map: return *key == *o.key && *elem == *o.elem;
|
||||
case Fixed: return count == o.count && *elem == *o.elem;
|
||||
case Struct: return info == o.info;
|
||||
case Named: return name == o.name && module == o.module;
|
||||
|
|
@ -86,6 +95,7 @@ struct Type {
|
|||
case Ptr: return elem ? "ptr[" + elem->show() + "]" : "ptr";
|
||||
case Null: return "null";
|
||||
case Array: return "[]" + elem->show();
|
||||
case Map: return "map[" + key->show() + "]" + elem->show();
|
||||
case Fixed: return "[" + std::to_string(count) + "]" + elem->show();
|
||||
case Struct:
|
||||
case Named: return module.empty() ? name : module + "." + name;
|
||||
|
|
@ -104,7 +114,8 @@ struct Pos {
|
|||
|
||||
// ---------- expressions ----------
|
||||
|
||||
enum class ExprKind { Int, Float, Bool, Char, Str, Null, Var, Unary, Binary, Call, Member, Index, ArrayLit, Method };
|
||||
enum class ExprKind { Int, Float, Bool, Char, Str, Null, Var, Unary, Binary, Call, Member, Index, ArrayLit, Method,
|
||||
MapLit, Try, OrElse };
|
||||
|
||||
struct Expr {
|
||||
ExprKind kind;
|
||||
|
|
@ -179,7 +190,11 @@ struct ArrayLitExpr : Expr { // [1, 2, 3]
|
|||
std::vector<ExprPtr> elems;
|
||||
explicit ArrayLitExpr(Pos p) : Expr(ExprKind::ArrayLit, p) {}
|
||||
};
|
||||
// obj.name(args): a.push(x), s.find("x"), and math.sqrt(2) / math.Vec(1, 2) for modules
|
||||
struct MapLitExpr : Expr { // ["a": 1, "b": 2], or [:] for an empty map
|
||||
std::vector<ExprPtr> keys, values;
|
||||
explicit MapLitExpr(Pos p) : Expr(ExprKind::MapLit, p) {}
|
||||
};
|
||||
// obj.name(args): a.push(x), s.find("x"), p.move(1, 2), and math.sqrt(2) / math.Vec(1, 2) for modules
|
||||
struct MethodExpr : Expr {
|
||||
ExprPtr obj;
|
||||
std::string name;
|
||||
|
|
@ -188,6 +203,21 @@ struct MethodExpr : Expr {
|
|||
MethodExpr(Pos p, ExprPtr o, std::string n) : Expr(ExprKind::Method, p), obj(std::move(o)), name(std::move(n)) {}
|
||||
};
|
||||
|
||||
struct BlockStmt;
|
||||
// try f(x): if f fails, the current function fails with the same error
|
||||
struct TryExpr : Expr {
|
||||
ExprPtr call;
|
||||
TryExpr(Pos p, ExprPtr c) : Expr(ExprKind::Try, p), call(std::move(c)) {}
|
||||
};
|
||||
// f(x) or fallback, f(x) or { ...err... }
|
||||
struct OrElseExpr : Expr {
|
||||
ExprPtr call;
|
||||
ExprPtr fallback; // or a value
|
||||
std::unique_ptr<BlockStmt> block; // or a block that sees `err`
|
||||
OrElseExpr(Pos p, ExprPtr c) : Expr(ExprKind::OrElse, p), call(std::move(c)) {}
|
||||
~OrElseExpr() override;
|
||||
};
|
||||
|
||||
// ---------- statements ----------
|
||||
|
||||
enum class StmtKind { VarDecl, Assign, Expr, If, While, For, ForEach, Return, Break, Continue, Block, Defer };
|
||||
|
|
@ -206,6 +236,7 @@ struct BlockStmt : Stmt {
|
|||
explicit BlockStmt(Pos p) : Stmt(StmtKind::Block, p) {}
|
||||
};
|
||||
using BlockPtr = std::unique_ptr<BlockStmt>;
|
||||
inline OrElseExpr::~OrElseExpr() = default;
|
||||
|
||||
struct VarDeclStmt : Stmt {
|
||||
bool hasType; // `int x = 5` vs `x := 5`
|
||||
|
|
@ -246,7 +277,9 @@ struct ForStmt : Stmt {
|
|||
struct ForEachStmt : Stmt {
|
||||
std::string var;
|
||||
Pos varPos;
|
||||
ExprPtr list; // for var in list (an array or a str)
|
||||
std::string var2; // for i, x in list / for k, v in map ("" = one variable)
|
||||
Pos var2Pos;
|
||||
ExprPtr list; // for var in list (an array, a str or a map)
|
||||
BlockPtr body;
|
||||
ForEachStmt(Pos p) : Stmt(StmtKind::ForEach, p) {}
|
||||
};
|
||||
|
|
@ -278,7 +311,10 @@ struct FnDecl {
|
|||
Pos pos;
|
||||
Pos namePos;
|
||||
std::string name;
|
||||
std::string recv; // fn Point.move(...): "Point" (a method; `self` is the Point it is called on)
|
||||
Pos recvPos;
|
||||
Type ret; // Void when there is no `-> type`
|
||||
bool fallible = false; // -> int! / -> ! : can fail with an error
|
||||
std::vector<Param> params;
|
||||
BlockPtr body;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,18 +11,19 @@ struct BuiltinDoc {
|
|||
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."},
|
||||
{"read_file", "read_file(str path) -> str", "The whole file as text. Stops the program if it can't be read, unless handled: read_file(p) or { ... }, try read_file(p)."},
|
||||
{"write_file", "write_file(str path, str text) -> bool", "Writes (replaces) the file. true if it worked; with or / try, the reason it didn't."},
|
||||
{"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."},
|
||||
{"delete_file", "delete_file(str path) -> bool", "Deletes the file. true if it worked; with or / try, the reason it didn't."},
|
||||
{"error", "error(str message)", "The failure a fallible function returns: return error(\"...\"). The caller handles it with or / try."},
|
||||
{"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."},
|
||||
{"int", "int(x) -> int", "Converts to a whole number: cuts off fractions, parses text (stops the program if it isn't a number, unless handled: int(s) or 0)."},
|
||||
{"float", "float(x) -> float", "Converts to a decimal number; parses text (int(s) or 0.0 handles bad 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)."},
|
||||
|
|
@ -62,8 +63,18 @@ inline const BuiltinDoc kStrMethods[] = {
|
|||
{"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 BuiltinDoc kMapMethods[] = {
|
||||
{"len", ".len -> int", "The number of keys."},
|
||||
{"has", "has(K key) -> bool", "Is the key in the map?"},
|
||||
{"get", "get(K key, V default) -> V", "The value for key, or default if the key isn't there."},
|
||||
{"remove", "remove(K key) -> bool", "Removes the key and its value; true if it was there."},
|
||||
{"clear", "clear()", "Removes everything."},
|
||||
{"keys", "keys() -> []K", "The keys, in the order they were added."},
|
||||
{"values", "values() -> []V", "The values, in the order their keys were added."},
|
||||
};
|
||||
|
||||
inline const char *kTypeNames[] = {"int", "float", "bool", "char", "str", "ptr", "i8", "i16", "i32", "i64",
|
||||
inline const char *kKeywords[] = {"fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true",
|
||||
"false", "null", "import", "link", "struct", "defer", "or", "try", "self"};
|
||||
|
||||
inline const char *kTypeNames[] = {"int", "float", "bool", "char", "str", "ptr", "map", "i8", "i16", "i32", "i64",
|
||||
"u8", "u16", "u32", "u64", "f32", "f64"};
|
||||
|
|
|
|||
346
src/codegen.cpp
346
src/codegen.cpp
|
|
@ -39,6 +39,8 @@ std::unique_ptr<Module> Codegen::run() {
|
|||
if (!modules[""].fns.count("main")) failAt(0, 1, 1, "there is no main function (add 'fn main() { ... }')");
|
||||
for (auto &[name, m] : modules)
|
||||
for (auto &[fname, fn] : m.fns) define(fn);
|
||||
for (auto &[s, ms] : methods)
|
||||
for (auto &[mname, fn] : ms) define(fn);
|
||||
defineMainWrapper();
|
||||
if (di) di->finalize();
|
||||
|
||||
|
|
@ -102,19 +104,72 @@ void Codegen::declareStructs() {
|
|||
void Codegen::declareFns() {
|
||||
for (const Program &p : progs)
|
||||
for (const FnDecl &fn : p.fns) declare(p, fn);
|
||||
|
||||
// Which methods change `self`? Calling one of them on something changes it too, so repeat until stable.
|
||||
for (bool changed = true; changed;) {
|
||||
changed = false;
|
||||
for (auto &[s, ms] : methods)
|
||||
for (auto &[name, f] : ms)
|
||||
if (!f.mutatesSelf && mutates("self", *f.decl->body)) {
|
||||
f.mutatesSelf = true;
|
||||
mutatingMethods.insert(name);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
// Owning parameters the function never changes are lent to it instead of copied.
|
||||
auto borrow = [&](Fn &f) {
|
||||
for (size_t i = 0; i < f.params.size(); i++)
|
||||
f.borrowParam.push_back(owning(f.params[i]) && !mutates(f.decl->params[i].name, *f.decl->body));
|
||||
};
|
||||
for (auto &[name, m] : modules)
|
||||
for (auto &[fname, f] : m.fns) borrow(f);
|
||||
for (auto &[s, ms] : methods)
|
||||
for (auto &[name, f] : ms) borrow(f);
|
||||
}
|
||||
|
||||
FType Codegen::structType(StructInfo *s) {
|
||||
FType t(FType::Struct);
|
||||
t.info = s;
|
||||
t.name = s->name;
|
||||
t.module = s->module == "C" ? "" : s->module;
|
||||
return t;
|
||||
}
|
||||
|
||||
llvm::Type *Codegen::retType(const Fn &f) {
|
||||
if (!f.fallible) return ty(f.ret);
|
||||
llvm::Type *v = f.ret.kind == FType::Void ? (llvm::Type *)b.getInt8Ty() : ty(f.ret);
|
||||
return StructType::get(ctx, {b.getInt1Ty(), v, ty(FType::Str)});
|
||||
}
|
||||
|
||||
void Codegen::declare(const Program &p, const FnDecl &fn) {
|
||||
ModuleScope &m = modules[p.module];
|
||||
Pos pos = fn.pos;
|
||||
if (m.fns.count(fn.name)) failAt(pos.file, pos.line, pos.col, "function '" + fn.name + "' is defined twice");
|
||||
if (isBuiltin(fn.name)) failAt(pos.file, pos.line, pos.col, "'" + fn.name + "' is a built-in function, pick another name");
|
||||
if (m.structs.count(fn.name)) failAt(pos.file, pos.line, pos.col, "'" + fn.name + "' is already the name of a struct");
|
||||
|
||||
curModule = p.module;
|
||||
StructInfo *recv = nullptr;
|
||||
if (!fn.recv.empty()) { // fn Point.move(...)
|
||||
Pos rp = fn.recvPos;
|
||||
recv = findStruct("", fn.recv, false);
|
||||
if (!recv)
|
||||
failAt(rp.file, rp.line, rp.col, "there is no struct named '" + fn.recv + "' here (a method goes in the same file as its struct, or on a struct from C)");
|
||||
resolveStruct(recv);
|
||||
for (auto &f : recv->fields)
|
||||
if (f.name == fn.name && !f.hidden)
|
||||
failAt(fn.namePos.file, fn.namePos.line, fn.namePos.col, "the struct " + recv->name + " already has a field named '" + fn.name + "'");
|
||||
if (methods[recv].count(fn.name)) failAt(pos.file, pos.line, pos.col, "the method " + fn.recv + "." + fn.name + " is defined twice");
|
||||
for (const Param &prm : fn.params)
|
||||
if (prm.name == "self")
|
||||
failAt(prm.namePos.file, prm.namePos.line, prm.namePos.col, "'self' is already the " + recv->name + " the method is called on");
|
||||
} else {
|
||||
if (m.fns.count(fn.name)) failAt(pos.file, pos.line, pos.col, "function '" + fn.name + "' is defined twice");
|
||||
if (isBuiltin(fn.name)) failAt(pos.file, pos.line, pos.col, "'" + fn.name + "' is a built-in function, pick another name");
|
||||
if (m.structs.count(fn.name)) failAt(pos.file, pos.line, pos.col, "'" + fn.name + "' is already the name of a struct");
|
||||
}
|
||||
|
||||
Fn f;
|
||||
f.decl = &fn;
|
||||
f.module = p.module;
|
||||
f.recv = recv;
|
||||
f.fallible = fn.fallible;
|
||||
f.ret = resolve(fn.ret, pos);
|
||||
for (const Param &prm : fn.params) f.params.push_back(resolve(prm.type, prm.pos));
|
||||
|
||||
|
|
@ -128,14 +183,13 @@ void Codegen::declare(const Program &p, const FnDecl &fn) {
|
|||
}
|
||||
|
||||
std::vector<llvm::Type *> params;
|
||||
for (size_t i = 0; i < f.params.size(); i++) {
|
||||
params.push_back(ty(f.params[i]));
|
||||
f.borrowParam.push_back(owning(f.params[i]) && !mutates(fn.params[i].name, *fn.body));
|
||||
}
|
||||
if (recv) params.push_back(b.getPtrTy()); // self
|
||||
for (size_t i = 0; i < f.params.size(); i++) params.push_back(ty(f.params[i]));
|
||||
// User functions get a prefix so they never clash with C library names.
|
||||
std::string name = "finch." + (p.module.empty() ? "" : p.module + ".") + fn.name;
|
||||
f.llvm = Function::Create(FunctionType::get(ty(f.ret), params, false), Function::InternalLinkage, name, mod.get());
|
||||
m.fns[fn.name] = f;
|
||||
std::string name = "finch." + (p.module.empty() ? "" : p.module + ".") + (recv ? recv->name + "." : "") + fn.name;
|
||||
f.llvm = Function::Create(FunctionType::get(retType(f), params, false), Function::InternalLinkage, name, mod.get());
|
||||
if (recv) methods[recv][fn.name] = f;
|
||||
else m.fns[fn.name] = f;
|
||||
}
|
||||
|
||||
// The real C `main` calls the Finch one and turns its result into an exit code.
|
||||
|
|
@ -155,6 +209,17 @@ void Codegen::defineMainWrapper() {
|
|||
}
|
||||
Value *r = b.CreateCall(fm.llvm, args);
|
||||
if (argsArr) dropValue(argsArr, fm.params[0]);
|
||||
if (fm.fallible) { // fn main() -> !: a failure is printed, and the exit code is 1
|
||||
BasicBlock *bad = BasicBlock::Create(ctx, "main.failed", w), *good = BasicBlock::Create(ctx, "main.ok", w);
|
||||
b.CreateCondBr(b.CreateExtractValue(r, 0), good, bad);
|
||||
b.SetInsertPoint(bad);
|
||||
Value *msg = tmp(b.CreateExtractValue(r, 2));
|
||||
b.CreateCall(rt("finch_report_error"), {msg});
|
||||
b.CreateCall(rt("finch_str_drop"), {msg});
|
||||
b.CreateRet(b.getInt32(1));
|
||||
b.SetInsertPoint(good);
|
||||
if (fm.ret.kind != FType::Void) r = b.CreateExtractValue(r, 1);
|
||||
}
|
||||
if (fm.ret.kind == FType::Void) b.CreateRet(b.getInt32(0));
|
||||
else b.CreateRet(b.CreateIntCast(r, b.getInt32Ty(), true));
|
||||
}
|
||||
|
|
@ -164,7 +229,8 @@ void Codegen::define(Fn &fn) {
|
|||
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};
|
||||
std::string shown = fn.recv ? fn.recv->name + "." + d.name : d.name;
|
||||
FnInfo fi{fn.module, shown, 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);
|
||||
|
|
@ -186,12 +252,33 @@ void Codegen::define(Fn &fn) {
|
|||
varOrder = 0;
|
||||
pushScope();
|
||||
unsigned i = 0;
|
||||
for (Argument &arg : f->args()) {
|
||||
auto argIt = f->arg_begin();
|
||||
if (fn.recv) { // `self` is the caller's struct itself, reached through its address
|
||||
Argument &selfArg = *argIt++;
|
||||
selfArg.setName("self");
|
||||
Var v;
|
||||
v.slot = &selfArg;
|
||||
v.type = structType(fn.recv);
|
||||
v.order = ++varOrder;
|
||||
v.pos = d.recvPos;
|
||||
scopes.back().vars["self"] = v;
|
||||
if (g_index) g_index->vars.push_back({"self", v.type.show(), d.pos.file, d.pos.line, d.pos.col, d.pos.file, d.pos.line, d.body->end.line});
|
||||
if (di) {
|
||||
FType pt = FType::ptrTo(v.type);
|
||||
AllocaInst *a = slot(pt, "self.addr");
|
||||
b.CreateStore(&selfArg, a);
|
||||
DILocalVariable *dv = di->createParameterVariable(diFn, "self", 1, diFiles[d.pos.file], d.pos.line, diType(pt));
|
||||
di->insertDeclare(a, dv, di->createExpression(), DILocation::get(ctx, d.pos.line, d.pos.col, diFn), b.GetInsertBlock());
|
||||
}
|
||||
}
|
||||
for (; argIt != f->arg_end(); ++argIt) {
|
||||
Argument &arg = *argIt;
|
||||
const Param &p = d.params[i];
|
||||
const FType &t = fn.params[i];
|
||||
arg.setName(p.name);
|
||||
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);
|
||||
unsigned argNo = i + 1 + (fn.recv ? 1 : 0);
|
||||
if (owning(t) && !fn.borrowParam[i]) addVar(p.namePos, p.name, t, copyValue(&arg, t), true, false, argNo); // changed inside: own a copy
|
||||
else addVar(p.namePos, p.name, t, &arg, false, false, argNo);
|
||||
i++;
|
||||
}
|
||||
blockBody(*d.body);
|
||||
|
|
@ -199,7 +286,7 @@ void Codegen::define(Fn &fn) {
|
|||
if (fn.ret.kind != FType::Void)
|
||||
failAt(d.pos.file, d.pos.line, d.pos.col, "function '" + d.name + "' can reach its end without returning " + fn.ret.show());
|
||||
emitCleanups(0);
|
||||
b.CreateRetVoid();
|
||||
retValue(nullptr);
|
||||
}
|
||||
scopes.clear();
|
||||
diFn = nullptr;
|
||||
|
|
@ -315,7 +402,11 @@ void Codegen::stmt(const Stmt &s) {
|
|||
case StmtKind::Block: block(static_cast<const BlockStmt &>(s)); break;
|
||||
case StmtKind::VarDecl: varDecl(static_cast<const VarDeclStmt &>(s)); break;
|
||||
case StmtKind::Assign: assign(static_cast<const AssignStmt &>(s)); break;
|
||||
case StmtKind::Expr: release(expr(*static_cast<const ExprStmt &>(s).expr)); break;
|
||||
case StmtKind::Expr: {
|
||||
const Expr &x = *static_cast<const ExprStmt &>(s).expr;
|
||||
release(x.kind == ExprKind::OrElse ? orElse(static_cast<const OrElseExpr &>(x), true) : expr(x));
|
||||
break;
|
||||
}
|
||||
case StmtKind::If: ifStmt(static_cast<const IfStmt &>(s)); break;
|
||||
case StmtKind::While: whileStmt(static_cast<const WhileStmt &>(s)); break;
|
||||
case StmtKind::For: forStmt(static_cast<const ForStmt &>(s)); break;
|
||||
|
|
@ -486,7 +577,8 @@ void Codegen::forStmt(const ForStmt &s) {
|
|||
popScope();
|
||||
}
|
||||
|
||||
// for x in list: x is each element (or each char of a str)
|
||||
// for x in list: x is each element (or each char of a str); for i, x in list: also its index;
|
||||
// for key in map / for key, value in map: in the order the keys were added
|
||||
void Codegen::forEachStmt(const ForEachStmt &s) {
|
||||
pushScope();
|
||||
LRef r = ref(*s.list);
|
||||
|
|
@ -500,9 +592,50 @@ void Codegen::forEachStmt(const ForEachStmt &s) {
|
|||
lt = r.val.type;
|
||||
addr = addVar(s.pos, "$list" + std::to_string(varOrder), lt, own(r.val), true).slot;
|
||||
}
|
||||
if (lt.kind == FType::Map) {
|
||||
FType kt = *lt.key, vt = *lt.elem;
|
||||
StructType *ET = mapEntryTy(lt);
|
||||
llvm::Type *MT = ty(lt);
|
||||
// if the loop changes the map, keys and values are copied, so they can't disappear under us
|
||||
bool copyKV = live && mutates(rootVar(*s.list), *s.body);
|
||||
Value *i = slot(FType::I64, "$i");
|
||||
b.CreateStore(b.getInt64(0), i);
|
||||
BasicBlock *condBB = newBlock("map.cond"), *check = newBlock("map.check"), *body = newBlock("map.body"),
|
||||
*step = newBlock("map.step"), *end = newBlock("map.end");
|
||||
b.CreateBr(condBB);
|
||||
b.SetInsertPoint(condBB);
|
||||
Value *iv = b.CreateLoad(b.getInt64Ty(), i);
|
||||
b.CreateCondBr(b.CreateICmpSLT(iv, b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(MT, addr, 2))), check, end);
|
||||
b.SetInsertPoint(check);
|
||||
Value *entry = b.CreateInBoundsGEP(ET, b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(MT, addr, 0)), iv);
|
||||
Value *hash = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ET, entry, 0));
|
||||
b.CreateCondBr(b.CreateICmpEQ(hash, b.getInt64(0)), step, body); // a removed entry
|
||||
b.SetInsertPoint(body);
|
||||
loops.push_back({step, end, scopes.size()});
|
||||
pushScope();
|
||||
std::string why = " can't be changed (to change the map, use m[key] = ...)";
|
||||
Value *kv = b.CreateLoad(ty(kt), b.CreateStructGEP(ET, entry, 1));
|
||||
Var &k = addVar(s.varPos, s.var, kt, copyKV ? copyValue(kv, kt) : kv, copyKV, true);
|
||||
k.readonlyWhy = "the loop variable '" + s.var + "'" + why;
|
||||
if (!s.var2.empty()) {
|
||||
Value *vv = b.CreateLoad(ty(vt), b.CreateStructGEP(ET, entry, 2));
|
||||
Var &v = addVar(s.var2Pos, s.var2, vt, copyKV ? copyValue(vv, vt) : vv, copyKV, true);
|
||||
v.readonlyWhy = "the loop variable '" + s.var2 + "'" + why;
|
||||
}
|
||||
block(*s.body);
|
||||
popScope();
|
||||
loops.pop_back();
|
||||
if (!terminated()) b.CreateBr(step);
|
||||
b.SetInsertPoint(step);
|
||||
b.CreateStore(b.CreateAdd(b.CreateLoad(b.getInt64Ty(), i), b.getInt64(1)), i);
|
||||
b.CreateBr(condBB);
|
||||
b.SetInsertPoint(end);
|
||||
popScope();
|
||||
return;
|
||||
}
|
||||
if (lt.kind != FType::Array && lt.kind != FType::Str && lt.kind != FType::Fixed)
|
||||
failAt(s.list->pos.file, s.list->pos.line, s.list->pos.col,
|
||||
"for ... in needs an array or a str, but this is " + lt.show() + " (for a range of numbers write 0..n)");
|
||||
"for ... in needs an array, a str or a map, but this is " + lt.show() + " (for a range of numbers write 0..n)");
|
||||
FType et = lt.kind == FType::Str ? FType(FType::Char) : *lt.elem;
|
||||
// If the loop changes the list, each element is copied, so it can't disappear under us.
|
||||
bool copyElems = live && owning(et) && mutates(rootVar(*s.list), *s.body);
|
||||
|
|
@ -531,8 +664,16 @@ 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.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])";
|
||||
std::string elemVar = s.var;
|
||||
Pos elemPos = s.varPos;
|
||||
if (!s.var2.empty()) { // for i, x in list
|
||||
Var &index = addVar(s.varPos, s.var, FType::I64, iv, false, true);
|
||||
index.readonlyWhy = "the loop variable '" + s.var + "' can't be changed";
|
||||
elemVar = s.var2;
|
||||
elemPos = s.var2Pos;
|
||||
}
|
||||
Var &xv = addVar(elemPos, elemVar, et, copyElems ? copyValue(elem, et) : elem, copyElems, true);
|
||||
xv.readonlyWhy = "the loop variable '" + elemVar + "' can't be changed (to change the list, use list[i] = ...)";
|
||||
block(*s.body);
|
||||
popScope();
|
||||
loops.pop_back();
|
||||
|
|
@ -546,14 +687,46 @@ void Codegen::forEachStmt(const ForEachStmt &s) {
|
|||
popScope();
|
||||
}
|
||||
|
||||
void Codegen::retValue(Value *v) {
|
||||
if (!curFn->fallible) {
|
||||
if (v) b.CreateRet(v);
|
||||
else b.CreateRetVoid();
|
||||
return;
|
||||
}
|
||||
Value *r = Constant::getNullValue(curFn->llvm->getReturnType());
|
||||
r = b.CreateInsertValue(r, b.getInt1(true), 0);
|
||||
if (v) r = b.CreateInsertValue(r, v, 1);
|
||||
b.CreateRet(r);
|
||||
}
|
||||
|
||||
void Codegen::retError(Value *msg) {
|
||||
Value *r = Constant::getNullValue(curFn->llvm->getReturnType());
|
||||
b.CreateRet(b.CreateInsertValue(r, msg, 2));
|
||||
}
|
||||
|
||||
void Codegen::returnStmt(const ReturnStmt &s) {
|
||||
const FnDecl &d = *curFn->decl;
|
||||
if (inDefer) failAt(s.pos.file, s.pos.line, s.pos.col, "return can't be used inside defer");
|
||||
// return error("...")
|
||||
if (s.value && s.value->kind == ExprKind::Call && static_cast<const CallExpr &>(*s.value).callee == "error") {
|
||||
auto &c = static_cast<const CallExpr &>(*s.value);
|
||||
if (!curFn->fallible)
|
||||
failAt(c.pos.file, c.pos.line, c.pos.col, "'" + d.name + "' can't fail, so it can't return an error (make it fallible: -> " +
|
||||
(curFn->ret.kind == FType::Void ? std::string("!") : curFn->ret.show() + "!") + ")");
|
||||
if (g_index)
|
||||
for (const BuiltinDoc &bd : kBuiltins)
|
||||
if (std::string(bd.name) == "error") note(c.pos, 5, "```finch\n" + std::string(bd.signature) + "\n```\n" + bd.doc);
|
||||
checkArgs(c.args, c.argNames, 1, c.pos, "error");
|
||||
Value *msg = own(coerce(expr(*c.args[0]), FType::Str, c.args[0]->pos, "the error message"));
|
||||
emitCleanups(0);
|
||||
retError(msg);
|
||||
return;
|
||||
}
|
||||
if (curFn->ret.kind == FType::Void) {
|
||||
if (s.value)
|
||||
failAt(s.value->pos.file, s.value->pos.line, s.value->pos.col, "'" + d.name + "' doesn't return a value (it has no '-> type')");
|
||||
emitCleanups(0);
|
||||
b.CreateRetVoid();
|
||||
retValue(nullptr);
|
||||
return;
|
||||
}
|
||||
if (!s.value) failAt(s.pos.file, s.pos.line, s.pos.col, "'" + d.name + "' must return " + curFn->ret.show());
|
||||
|
|
@ -569,7 +742,7 @@ void Codegen::returnStmt(const ReturnStmt &s) {
|
|||
it->second.moved = true;
|
||||
emitCleanups(0);
|
||||
scopes[idx].vars[name].moved = false;
|
||||
b.CreateRet(v);
|
||||
retValue(v);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
|
@ -578,7 +751,7 @@ void Codegen::returnStmt(const ReturnStmt &s) {
|
|||
Value_ v = coerce(exprWant(*s.value, curFn->ret), curFn->ret, s.value->pos, "the returned value");
|
||||
Value *out = own(v);
|
||||
emitCleanups(0);
|
||||
b.CreateRet(out);
|
||||
retValue(out);
|
||||
}
|
||||
|
||||
// ---------- mutation analysis (does a function or loop change a variable?) ----------
|
||||
|
|
@ -595,6 +768,17 @@ std::string Codegen::rootVar(const Expr &e) {
|
|||
bool Codegen::mutatesExpr(const std::string &name, const Expr &e) {
|
||||
static const std::set<std::string> changing = {"push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"};
|
||||
switch (e.kind) {
|
||||
case ExprKind::Try: return mutatesExpr(name, *static_cast<const TryExpr &>(e).call);
|
||||
case ExprKind::OrElse: {
|
||||
auto &x = static_cast<const OrElseExpr &>(e);
|
||||
return mutatesExpr(name, *x.call) || (x.fallback && mutatesExpr(name, *x.fallback)) || (x.block && mutates(name, *x.block));
|
||||
}
|
||||
case ExprKind::MapLit: {
|
||||
auto &x = static_cast<const MapLitExpr &>(e);
|
||||
for (size_t k = 0; k < x.keys.size(); k++)
|
||||
if (mutatesExpr(name, *x.keys[k]) || mutatesExpr(name, *x.values[k])) return true;
|
||||
return false;
|
||||
}
|
||||
case ExprKind::Unary: return mutatesExpr(name, *static_cast<const UnaryExpr &>(e).operand);
|
||||
case ExprKind::Binary: {
|
||||
auto &x = static_cast<const BinaryExpr &>(e);
|
||||
|
|
@ -618,7 +802,7 @@ bool Codegen::mutatesExpr(const std::string &name, const Expr &e) {
|
|||
return false;
|
||||
case ExprKind::Method: {
|
||||
auto &m = static_cast<const MethodExpr &>(e);
|
||||
if (changing.count(m.name) && rootVar(*m.obj) == name) return true;
|
||||
if ((changing.count(m.name) || mutatingMethods.count(m.name)) && rootVar(*m.obj) == name) return true;
|
||||
if (mutatesExpr(name, *m.obj)) return true;
|
||||
for (auto &a : m.args)
|
||||
if (mutatesExpr(name, *a)) return true;
|
||||
|
|
@ -667,9 +851,96 @@ bool Codegen::mutates(const std::string &name, const Stmt &s) {
|
|||
|
||||
Value_ Codegen::exprWant(const Expr &e, const FType &want) {
|
||||
if (e.kind == ExprKind::ArrayLit) return arrayLit(static_cast<const ArrayLitExpr &>(e), &want);
|
||||
if (e.kind == ExprKind::MapLit) return mapLit(static_cast<const MapLitExpr &>(e), &want);
|
||||
return expr(e);
|
||||
}
|
||||
|
||||
// ---------- errors as values ----------
|
||||
|
||||
// Emit a call that may fail; `failed` (i1) and `msg` (str) tell how it went.
|
||||
Value_ Codegen::fallible(const Expr &call, Value *&failed, Value *&msg) {
|
||||
Pos p = call.pos;
|
||||
if (call.kind != ExprKind::Call && call.kind != ExprKind::Method)
|
||||
failAt(p.file, p.line, p.col, "only a function call can fail; 'or' and 'try' go after one, like int(text) or 0");
|
||||
const Expr *savedTarget = fallibleTarget;
|
||||
Value *savedFailed = fallibleFailed, *savedMsg = fallibleMsg;
|
||||
fallibleTarget = &call;
|
||||
fallibleFailed = fallibleMsg = nullptr;
|
||||
Value_ v = expr(call);
|
||||
failed = fallibleFailed;
|
||||
msg = fallibleMsg;
|
||||
fallibleTarget = savedTarget;
|
||||
fallibleFailed = savedFailed;
|
||||
fallibleMsg = savedMsg;
|
||||
if (!failed) failAt(p.file, p.line, p.col, "this call can't fail, so it needs no 'or' or 'try'");
|
||||
return v;
|
||||
}
|
||||
|
||||
// try f(x): on failure, the current function fails with the same error
|
||||
Value_ Codegen::tryExpr(const TryExpr &t) {
|
||||
Pos p = t.pos;
|
||||
if (inDefer) failAt(p.file, p.line, p.col, "try can't be used inside defer");
|
||||
if (!curFn->fallible)
|
||||
failAt(p.file, p.line, p.col, "try passes the error on, so '" + curFn->decl->name + "' must be able to fail too (-> " +
|
||||
(curFn->ret.kind == FType::Void ? std::string("!") : curFn->ret.show() + "!") +
|
||||
"); or handle it here: ... or <value> / ... or { ... }");
|
||||
Value *failed, *msg;
|
||||
Value_ v = fallible(*t.call, failed, msg);
|
||||
BasicBlock *bad = newBlock("try.failed"), *ok = newBlock("try.ok");
|
||||
b.CreateCondBr(failed, bad, ok);
|
||||
b.SetInsertPoint(bad);
|
||||
emitCleanups(0);
|
||||
retError(msg);
|
||||
b.SetInsertPoint(ok);
|
||||
return v;
|
||||
}
|
||||
|
||||
// f(x) or fallback / f(x) or { ... err ... }
|
||||
Value_ Codegen::orElse(const OrElseExpr &e, bool discard) {
|
||||
Value *failed, *msg;
|
||||
Value_ v = fallible(*e.call, failed, msg);
|
||||
BasicBlock *bad = newBlock("or.failed"), *ok = newBlock("or.ok");
|
||||
b.CreateCondBr(failed, bad, ok);
|
||||
b.SetInsertPoint(bad);
|
||||
if (e.block) {
|
||||
pushScope();
|
||||
addVar(e.block->pos, "err", FType::Str, msg, true);
|
||||
blockBody(*e.block);
|
||||
bool left = terminated();
|
||||
if (!left && v.type.kind != FType::Void && !discard)
|
||||
failAt(e.block->end.file, e.block->end.line, e.block->end.col,
|
||||
"this block must leave with return, break, continue or exit(...): after a failure there is no value to go on with (or give one: ... or <value>)");
|
||||
popScope();
|
||||
if (discard && v.type.kind != FType::Void) { // read_file(p) or { ... } as a statement: drop the unused value
|
||||
BasicBlock *join = newBlock("or.end");
|
||||
if (!left) b.CreateBr(join);
|
||||
b.SetInsertPoint(ok);
|
||||
release(v);
|
||||
b.CreateBr(join);
|
||||
b.SetInsertPoint(join);
|
||||
return {nullptr, FType::Void};
|
||||
}
|
||||
if (!left) b.CreateBr(ok);
|
||||
b.SetInsertPoint(ok);
|
||||
return v;
|
||||
}
|
||||
Pos fp = e.fallback->pos;
|
||||
if (v.type.kind == FType::Void)
|
||||
failAt(fp.file, fp.line, fp.col, "this call gives back no value, so there is nothing to replace; handle the failure with or { ... }");
|
||||
dropValue(msg, FType::Str);
|
||||
Value *fv = own(coerce(exprWant(*e.fallback, v.type), v.type, fp, "the value after 'or'"));
|
||||
BasicBlock *badEnd = b.GetInsertBlock();
|
||||
BasicBlock *join = newBlock("or.end");
|
||||
b.CreateBr(join);
|
||||
b.SetInsertPoint(ok);
|
||||
b.CreateBr(join);
|
||||
b.SetInsertPoint(join);
|
||||
PHINode *phi = b.CreatePHI(ty(v.type), 2);
|
||||
phi->addIncoming(fv, badEnd);
|
||||
phi->addIncoming(v.v, ok);
|
||||
return {phi, v.type, false, owning(v.type)};
|
||||
}
|
||||
|
||||
Value_ Codegen::expr(const Expr &e) {
|
||||
switch (e.kind) {
|
||||
case ExprKind::Int: return {b.getInt64(static_cast<const IntExpr &>(e).value), FType::I64, true};
|
||||
|
|
@ -711,6 +982,9 @@ Value_ Codegen::expr(const Expr &e) {
|
|||
case ExprKind::Call: return call(static_cast<const CallExpr &>(e));
|
||||
case ExprKind::Method: return method(static_cast<const MethodExpr &>(e));
|
||||
case ExprKind::ArrayLit: return arrayLit(static_cast<const ArrayLitExpr &>(e), nullptr);
|
||||
case ExprKind::MapLit: return mapLit(static_cast<const MapLitExpr &>(e), nullptr);
|
||||
case ExprKind::Try: return tryExpr(static_cast<const TryExpr &>(e));
|
||||
case ExprKind::OrElse: return orElse(static_cast<const OrElseExpr &>(e));
|
||||
}
|
||||
return {nullptr, FType::Void};
|
||||
}
|
||||
|
|
@ -785,6 +1059,19 @@ LRef Codegen::member(const MemberExpr &m, bool forWrite) {
|
|||
t = *t.elem;
|
||||
}
|
||||
|
||||
if (t.kind == FType::Map) {
|
||||
if (m.field != "len") failAt(p.file, p.line, p.col, "a map has .len (the number of keys), not '." + m.field + "'");
|
||||
if (forWrite) failAt(p.file, p.line, p.col, ".len can't be changed directly (use m[key] = ..., remove or clear)");
|
||||
note(Pos{p.line, p.col + 1, p.file}, 3, "`.len -> int`: the number of keys");
|
||||
Value *len;
|
||||
if (obj.isPlace) {
|
||||
len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), obj.pl.addr, 1));
|
||||
} else {
|
||||
len = b.CreateExtractValue(obj.val.v, 1);
|
||||
release(obj.val);
|
||||
}
|
||||
return {false, {}, {len, FType::I64}};
|
||||
}
|
||||
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(),
|
||||
|
|
@ -839,6 +1126,7 @@ LRef Codegen::index(const IndexExpr &e, bool forWrite) {
|
|||
Pos p = e.pos;
|
||||
LRef obj = ref(*e.obj, forWrite);
|
||||
FType t = obj.isPlace ? obj.pl.type : obj.val.type;
|
||||
if (t.kind == FType::Map) return mapIndex(e, obj, t, forWrite);
|
||||
Value *i = coerce(expr(*e.index), FType::I64, e.index->pos, "an index").v;
|
||||
|
||||
if (t.isPtr()) { // C-style: p[i], no bounds known
|
||||
|
|
@ -854,7 +1142,7 @@ LRef Codegen::index(const IndexExpr &e, bool forWrite) {
|
|||
return {false, {}, {b.CreateLoad(ty(*t.elem), b.CreateInBoundsGEP(ty(t), a, {b.getInt64(0), i})), *t.elem}};
|
||||
}
|
||||
if (t.kind != FType::Array && t.kind != FType::Str)
|
||||
failAt(p.file, p.line, p.col, "[ ] works on arrays, str and pointers, not on " + t.show());
|
||||
failAt(p.file, p.line, p.col, "[ ] works on arrays, maps, str and pointers, not on " + t.show());
|
||||
FType et = t.kind == FType::Str ? FType(FType::Char) : *t.elem;
|
||||
if (t.kind == FType::Str && forWrite) {
|
||||
if (!obj.isPlace) failAt(p.file, p.line, p.col, "this is a temporary str, it can't be changed");
|
||||
|
|
@ -1139,10 +1427,12 @@ void Codegen::note(Pos at, size_t len, const std::string &hover, Pos def) {
|
|||
}
|
||||
|
||||
std::string Codegen::signature(const Fn &fn) {
|
||||
std::string s = "fn " + (fn.module.empty() ? "" : fn.module + ".") + fn.decl->name + "(";
|
||||
std::string owner = fn.recv ? fn.recv->name + "." : fn.module.empty() ? "" : fn.module + ".";
|
||||
std::string s = "fn " + owner + 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();
|
||||
if (fn.ret.kind != FType::Void) s += " -> " + fn.ret.show() + (fn.fallible ? "!" : "");
|
||||
else if (fn.fallible) s += " -> !";
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace finch {
|
|||
|
||||
bool Codegen::isBuiltin(const std::string &n) {
|
||||
static const std::set<std::string> names = {"print", "addr", "input", "new", "free", "read_file", "write_file",
|
||||
"file_exists", "delete_file", "exit", "shell"};
|
||||
"file_exists", "delete_file", "exit", "shell", "error"};
|
||||
FType t;
|
||||
return names.count(n) || typeFromName(n, t);
|
||||
}
|
||||
|
|
@ -95,6 +95,28 @@ Function *Codegen::rt(const std::string &name) {
|
|||
{"finch_shell", {I, {P}}},
|
||||
{"finch_delete_file", {I32, {P}}},
|
||||
{"finch_std_stream", {P, {I32}}},
|
||||
{"finch_str_try_int", {I32, {P, P, P}}},
|
||||
{"finch_str_try_float", {I32, {P, P, P}}},
|
||||
{"finch_try_read_file", {I32, {P, P, P}}},
|
||||
{"finch_try_write_file", {I32, {P, P, P}}},
|
||||
{"finch_try_delete_file", {I32, {P, P}}},
|
||||
{"finch_report_error", {V, {P}}},
|
||||
{"finch_map_find", {I, {P, P, I, I, I32}}},
|
||||
{"finch_map_slot", {I, {P, P, I, I, I32, P}}},
|
||||
{"finch_map_remove_at", {V, {P, I, I}}},
|
||||
{"finch_map_clear", {V, {P}}},
|
||||
{"finch_map_free", {V, {P}}},
|
||||
{"finch_map_clone_raw", {V, {P, P, I}}},
|
||||
{"finch_map_missing", {V, {P, I, P}}},
|
||||
{"finch_buf_add", {V, {P, P, I}}},
|
||||
{"finch_buf_int", {V, {P, I}}},
|
||||
{"finch_buf_uint", {V, {P, I}}},
|
||||
{"finch_buf_float", {V, {P, D}}},
|
||||
{"finch_buf_bool", {V, {P, I32}}},
|
||||
{"finch_buf_ptr", {V, {P, P}}},
|
||||
{"finch_buf_char", {V, {P, b.getInt8Ty(), I32}}},
|
||||
{"finch_buf_str", {V, {P, P, I32}}},
|
||||
{"finch_buf_print", {V, {P}}},
|
||||
};
|
||||
auto it = sigs.find(name);
|
||||
if (it == sigs.end()) {
|
||||
|
|
@ -103,11 +125,11 @@ Function *Codegen::rt(const std::string &name) {
|
|||
}
|
||||
Function *f = Function::Create(FunctionType::get(it->second.ret, it->second.params, false), Function::ExternalLinkage,
|
||||
name, mod.get());
|
||||
if (name == "finch_panic" || name == "finch_panic_index") {
|
||||
if (name == "finch_panic" || name == "finch_panic_index" || name == "finch_map_missing") {
|
||||
f->addFnAttr(Attribute::NoReturn);
|
||||
f->addFnAttr(Attribute::Cold);
|
||||
}
|
||||
if (name == "finch_str_from_char") f->addParamAttr(1, Attribute::SExt);
|
||||
if (name == "finch_str_from_char" || name == "finch_buf_char") f->addParamAttr(1, Attribute::SExt);
|
||||
return f;
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +207,14 @@ Value_ Codegen::call(const CallExpr &c) {
|
|||
return *c.args[0];
|
||||
};
|
||||
|
||||
bool canFail = &c == fallibleTarget; // inside `... or ...` / `try ...`: report failures instead of stopping
|
||||
// a failure: set the flag and message that `or` / `try` look at
|
||||
auto failedIf = [&](Value *ok, Value *errSlot) {
|
||||
fallibleFailed = b.CreateICmpEQ(ok, b.getInt32(0));
|
||||
fallibleMsg = b.CreateLoad(ty(FType::Str), errSlot);
|
||||
};
|
||||
if (n == "error")
|
||||
failAt(p.file, p.line, p.col, "error(...) makes an error to return from a function that can fail: return error(\"...\")");
|
||||
if (n == "print") {
|
||||
checkArgs(c.args, c.argNames, 0, p, n, true);
|
||||
return print(c.args, p);
|
||||
|
|
@ -237,6 +267,13 @@ Value_ Codegen::call(const CallExpr &c) {
|
|||
Value_ path = coerce(expr(one("path")), FType::Str, c.args[0]->pos, "the file name");
|
||||
Value *ss = tmpOf(ty(FType::Str));
|
||||
b.CreateCall(rt("finch_str_copy"), {ss, tmp(path.v)}); // NUL-terminated copy
|
||||
if (canFail) {
|
||||
Value *err = tmp(zero(FType::Str));
|
||||
failedIf(b.CreateCall(rt("finch_try_delete_file"), {ss, err}), err);
|
||||
dropAt(ss, FType::Str);
|
||||
release(path);
|
||||
return {nullptr, FType::Void};
|
||||
}
|
||||
Value *r = b.CreateCall(rt("finch_delete_file"), {ss});
|
||||
dropAt(ss, FType::Str);
|
||||
release(path);
|
||||
|
|
@ -248,6 +285,10 @@ Value_ Codegen::call(const CallExpr &c) {
|
|||
Value_ res;
|
||||
if (n == "file_exists") {
|
||||
res = {b.CreateICmpNE(b.CreateCall(rt("finch_file_exists"), {pp}), b.getInt32(0)), FType::Bool};
|
||||
} else if (canFail) {
|
||||
Value *out = tmp(zero(FType::Str)), *err = tmp(zero(FType::Str));
|
||||
failedIf(b.CreateCall(rt("finch_try_read_file"), {out, pp, err}), err);
|
||||
res = {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
|
||||
} else {
|
||||
Value *out = tmpOf(ty(FType::Str));
|
||||
b.CreateCall(rt("finch_read_file"), {out, pp, fileName(p), b.getInt64(p.line)});
|
||||
|
|
@ -269,6 +310,13 @@ Value_ Codegen::call(const CallExpr &c) {
|
|||
checkArgs(c.args, c.argNames, 2, p, n);
|
||||
Value_ path = coerce(expr(*c.args[0]), FType::Str, c.args[0]->pos, "the file name");
|
||||
Value_ text = coerce(expr(*c.args[1]), FType::Str, c.args[1]->pos, "the text");
|
||||
if (canFail) {
|
||||
Value *err = tmp(zero(FType::Str));
|
||||
failedIf(b.CreateCall(rt("finch_try_write_file"), {tmp(path.v), tmp(text.v), err}), err);
|
||||
release(path);
|
||||
release(text);
|
||||
return {nullptr, FType::Void};
|
||||
}
|
||||
Value *ok = b.CreateCall(rt("finch_write_file"), {tmp(path.v), tmp(text.v)});
|
||||
release(path);
|
||||
release(text);
|
||||
|
|
@ -277,13 +325,13 @@ Value_ Codegen::call(const CallExpr &c) {
|
|||
FType target;
|
||||
if (typeFromName(n, target)) {
|
||||
checkArgs(c.args, c.argNames, 1, p, n);
|
||||
return convert(target, c.args, p, n);
|
||||
return convert(target, c.args, p, n, canFail);
|
||||
}
|
||||
|
||||
ModuleScope &m = modules[curModule];
|
||||
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);
|
||||
return callFinch(it->second, c.args, c.argNames, p, n, &c);
|
||||
}
|
||||
if (StructInfo *s = findStruct("", n, false)) {
|
||||
note(p, n.size(), structHover(s), s->decl ? s->decl->namePos : Pos{});
|
||||
|
|
@ -303,20 +351,33 @@ Value_ Codegen::call(const CallExpr &c) {
|
|||
}
|
||||
|
||||
Value_ Codegen::callFinch(const Fn &fn, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p,
|
||||
const std::string &shown) {
|
||||
const std::string &shown, const Expr *site, Value *self, const std::string &selfRoot) {
|
||||
const FnDecl &d = *fn.decl;
|
||||
if (fn.module.empty() && d.name == "main") failAt(p.file, p.line, p.col, "main can't be called, it is where the program starts");
|
||||
if (fn.module.empty() && d.name == "main" && !fn.recv) failAt(p.file, p.line, p.col, "main can't be called, it is where the program starts");
|
||||
if (fn.fallible && site != fallibleTarget)
|
||||
failAt(p.file, p.line, p.col, "'" + shown + "' can fail, so say what happens then: " + shown + "(...) or <value>, " + shown +
|
||||
"(...) or { ... }, or try " + shown + "(...)");
|
||||
checkArgs(args, names, fn.params.size(), p, shown);
|
||||
std::vector<Value *> vals;
|
||||
std::vector<Value_> fresh;
|
||||
if (self) vals.push_back(self);
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
Value_ v = coerce(exprWant(*args[i], fn.params[i]), fn.params[i], args[i]->pos, "argument '" + d.params[i].name + "'");
|
||||
// a method that changes self gets its own copy of anything that lives inside self (p.add(p.items))
|
||||
if (fn.mutatesSelf && !selfRoot.empty() && owning(v.type) && !v.fresh && rootVar(*args[i]) == selfRoot)
|
||||
v = {copyValue(v.v, v.type), v.type, false, true};
|
||||
vals.push_back(v.v); // owning values are lent: the function never drops them
|
||||
if (v.fresh) fresh.push_back(v);
|
||||
}
|
||||
setLoc(p);
|
||||
Value *r = b.CreateCall(fn.llvm, vals);
|
||||
for (auto &v : fresh) release(v);
|
||||
if (fn.fallible) {
|
||||
fallibleFailed = b.CreateNot(b.CreateExtractValue(r, 0));
|
||||
fallibleMsg = b.CreateExtractValue(r, 2);
|
||||
if (fn.ret.kind == FType::Void) return {nullptr, FType::Void};
|
||||
return {b.CreateExtractValue(r, 1), fn.ret, false, owning(fn.ret)};
|
||||
}
|
||||
if (fn.ret.kind == FType::Void) return {nullptr, FType::Void};
|
||||
return {r, fn.ret, false, owning(fn.ret)};
|
||||
}
|
||||
|
|
@ -433,7 +494,7 @@ Value_ Codegen::addrOf(const std::vector<ExprPtr> &args, Pos p) {
|
|||
}
|
||||
|
||||
// Explicit conversions: int(x), u8(x), float(x), char(x), bool(x), str(x), ptr(x) ...
|
||||
Value_ Codegen::convert(const FType &to, const std::vector<ExprPtr> &args, Pos, const std::string &name) {
|
||||
Value_ Codegen::convert(const FType &to, const std::vector<ExprPtr> &args, Pos, const std::string &name, bool canFail) {
|
||||
Value_ v = expr(*args[0]);
|
||||
const FType &from = v.type;
|
||||
Pos p = args[0]->pos;
|
||||
|
|
@ -449,12 +510,28 @@ Value_ Codegen::convert(const FType &to, const std::vector<ExprPtr> &args, Pos,
|
|||
else if (from.kind == FType::Array && (from.elem->kind == FType::U8 || from.elem->kind == FType::Char)) {
|
||||
b.CreateCall(rt("finch_str_from_bytes"), {out, tmp(v.v)});
|
||||
release(v);
|
||||
} else if (from.kind == FType::Array || from.kind == FType::Fixed || from.kind == FType::Map || from.kind == FType::Struct) {
|
||||
b.CreateStore(zero(FType::Str), out); // the same text print(...) shows
|
||||
emitFormat(out, v.v, from, false);
|
||||
release(v);
|
||||
} else {
|
||||
failAt(p.file, p.line, p.col, "can't turn " + from.show() + " into str");
|
||||
}
|
||||
return {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
|
||||
}
|
||||
if (from == to) return v;
|
||||
if (from.kind == FType::Str && (to.isInt() || to.isFloat()) && canFail) { // int(text) or 0
|
||||
Value *sp = tmp(v.v), *err = tmp(zero(FType::Str));
|
||||
Value *out = tmp(to.isInt() ? (Value *)b.getInt64(0) : ConstantFP::get(b.getDoubleTy(), 0));
|
||||
Value *ok = b.CreateCall(rt(to.isInt() ? "finch_str_try_int" : "finch_str_try_float"), {sp, out, err});
|
||||
fallibleFailed = b.CreateICmpEQ(ok, b.getInt32(0));
|
||||
fallibleMsg = b.CreateLoad(ty(FType::Str), err);
|
||||
Value_ r;
|
||||
if (to.isInt()) r = {intCast(b.CreateLoad(b.getInt64Ty(), out), FType::I64, to), to};
|
||||
else r = {b.CreateFPCast(b.CreateLoad(b.getDoubleTy(), out), ty(to)), to};
|
||||
release(v);
|
||||
return r;
|
||||
}
|
||||
if (from.kind == FType::Str && (to.isInt() || to.isFloat())) {
|
||||
Value *sp = tmp(v.v);
|
||||
Value_ r;
|
||||
|
|
@ -494,7 +571,7 @@ Value_ Codegen::method(const MethodExpr &m) {
|
|||
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);
|
||||
return callFinch(it->second, m.args, m.argNames, p, mn + "." + m.name, &m);
|
||||
}
|
||||
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);
|
||||
|
|
@ -507,8 +584,20 @@ Value_ Codegen::method(const MethodExpr &m) {
|
|||
"there is no variable or module named '" + mn + "' (to use a module, add import " + mn + " at the top)");
|
||||
}
|
||||
static const std::set<std::string> changing = {"push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"};
|
||||
LRef r = ref(*m.obj, changing.count(m.name) > 0);
|
||||
// writing through a map's m[key] adds the key if it's missing: groups[k].push(x)
|
||||
LRef r = ref(*m.obj, changing.count(m.name) > 0 || mutatingMethods.count(m.name) > 0);
|
||||
FType t = r.isPlace ? r.pl.type : r.val.type;
|
||||
if (t.kind == FType::Struct || (t.isPtr() && t.elem && t.elem->kind == FType::Struct)) return structMethod(m, r, t);
|
||||
if (changing.count(m.name) && (t.kind == FType::Array || t.kind == FType::Map)) {
|
||||
std::string root = rootVar(*m.obj);
|
||||
if (Var *v = root.empty() ? nullptr : lookup(root); v && v->readonly) failAt(p.file, p.line, p.col, v->readonlyWhy);
|
||||
}
|
||||
if (t.kind == FType::Map) {
|
||||
Value_ recv = r.isPlace ? Value_{r.pl.addr, t} : Value_{tmp(r.val.v), t, false, r.val.fresh};
|
||||
Value_ out = mapMethod(m, &recv);
|
||||
if (!r.isPlace && r.val.fresh) dropAt(recv.v, t);
|
||||
return out;
|
||||
}
|
||||
if (g_index && (t.kind == FType::Array || t.kind == FType::Str)) {
|
||||
auto look = [&](const BuiltinDoc *first, const BuiltinDoc *last) {
|
||||
for (; first != last; ++first)
|
||||
|
|
@ -589,11 +678,47 @@ Value_ Codegen::method(const MethodExpr &m) {
|
|||
if (!r.isPlace) release(sv);
|
||||
return res;
|
||||
}
|
||||
if (t.kind == FType::Struct)
|
||||
failAt(p.file, p.line, p.col, "structs don't have methods; write a function and call it: " + m.name + "(value, ...)");
|
||||
failAt(p.file, p.line, p.col, t.show() + " has no methods");
|
||||
}
|
||||
|
||||
// p.move(1, 2): a method of a struct (also through a ptr[Struct]); self is passed by address
|
||||
Value_ Codegen::structMethod(const MethodExpr &m, LRef &r, const FType &t) {
|
||||
Pos p = m.pos;
|
||||
FType st = t.isPtr() ? *t.elem : t;
|
||||
auto ms = methods.find(st.info);
|
||||
const Fn *fn = nullptr;
|
||||
if (ms != methods.end())
|
||||
if (auto it = ms->second.find(m.name); it != ms->second.end()) fn = &it->second;
|
||||
if (!fn) {
|
||||
std::string names;
|
||||
if (ms != methods.end())
|
||||
for (auto &[n, f] : ms->second) names += (names.empty() ? "" : ", ") + n;
|
||||
if (names.empty())
|
||||
failAt(p.file, p.line, p.col, "the struct " + st.show() + " has no methods; add one with fn " + st.name + "." + m.name + "(...) { ... }");
|
||||
failAt(p.file, p.line, p.col, "the struct " + st.show() + " has no method '" + m.name + "' (it has: " + names + ")");
|
||||
}
|
||||
note(Pos{p.line, p.col + 1, p.file}, m.name.size(), "```finch\n" + signature(*fn) + "\n```", fn->decl->namePos);
|
||||
std::string root = rootVar(*m.obj);
|
||||
if (fn->mutatesSelf && !root.empty() && !t.isPtr())
|
||||
if (Var *v = lookup(root); v && v->readonly) failAt(p.file, p.line, p.col, v->readonlyWhy);
|
||||
|
||||
Value *self;
|
||||
bool temp = false;
|
||||
if (t.isPtr()) {
|
||||
self = r.isPlace ? b.CreateLoad(b.getPtrTy(), r.pl.addr) : r.val.v;
|
||||
panicIf(b.CreateIsNull(self), "called ." + m.name + "() on a null pointer", p);
|
||||
root.clear(); // self lives somewhere else
|
||||
} else if (r.isPlace) {
|
||||
self = r.pl.addr;
|
||||
} else {
|
||||
self = tmp(r.val.v); // a temporary struct: Point(1, 2).length()
|
||||
temp = true;
|
||||
}
|
||||
Value_ out = callFinch(*fn, m.args, m.argNames, p, st.name + "." + m.name, &m, self, root);
|
||||
if (temp && r.val.fresh && owning(st)) dropAt(self, st);
|
||||
return out;
|
||||
}
|
||||
|
||||
// An element compare for find/contains/sort: -1, 0, 1 (only numbers, chars, bools and str)
|
||||
static bool comparable(const FType &t) {
|
||||
return t.isNumber() || t.kind == FType::Char || t.kind == FType::Bool || t.kind == FType::Str;
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ struct Fn {
|
|||
llvm::Function *cThunk = nullptr;
|
||||
FType ret; // resolved types
|
||||
std::vector<FType> params;
|
||||
StructInfo *recv = nullptr; // a method of this struct: the first LLVM parameter is `self`'s address
|
||||
bool mutatesSelf = false; // the method changes `self`
|
||||
bool fallible = false; // -> T!: returns { i1 ok, T value, str error }
|
||||
};
|
||||
|
||||
struct Loop {
|
||||
|
|
@ -139,6 +142,13 @@ public:
|
|||
int deferLimit = -1; // while emitting a defer: hide variables declared after it
|
||||
int inDefer = 0;
|
||||
std::map<std::string, llvm::Function *> helpers; // drop/copy/print per type
|
||||
std::map<StructInfo *, std::map<std::string, Fn>> methods;
|
||||
std::set<std::string> mutatingMethods; // names of methods that change self (for the mutation analysis)
|
||||
|
||||
// errors as values: the call that `or` / `try` is handling reports here whether it failed
|
||||
const Expr *fallibleTarget = nullptr;
|
||||
llvm::Value *fallibleFailed = nullptr; // i1
|
||||
llvm::Value *fallibleMsg = nullptr; // str
|
||||
|
||||
// debug info (-g)
|
||||
bool debug;
|
||||
|
|
@ -164,6 +174,9 @@ public:
|
|||
void forStmt(const ForStmt &s);
|
||||
void forEachStmt(const ForEachStmt &s);
|
||||
void returnStmt(const ReturnStmt &s);
|
||||
llvm::Type *retType(const Fn &f);
|
||||
void retValue(llvm::Value *v); // return from the current function (wrapped as success if it can fail)
|
||||
void retError(llvm::Value *msg); // fail the current function with the str `msg`
|
||||
void jump(bool isBreak, Pos p);
|
||||
llvm::Value *condition(const Expr &e);
|
||||
void continueAt(llvm::BasicBlock *bb, llvm::BasicBlock *deadEnd);
|
||||
|
|
@ -194,6 +207,10 @@ public:
|
|||
LRef member(const MemberExpr &m, bool forWrite);
|
||||
LRef index(const IndexExpr &e, bool forWrite);
|
||||
Value_ arrayLit(const ArrayLitExpr &a, const FType *want);
|
||||
Value_ fallible(const Expr &call, llvm::Value *&failed, llvm::Value *&msg);
|
||||
Value_ tryExpr(const TryExpr &t);
|
||||
Value_ orElse(const OrElseExpr &e, bool discard = false); // discard: a statement, the value isn't used
|
||||
FType structType(StructInfo *s);
|
||||
std::string rootVar(const Expr &e);
|
||||
bool mutates(const std::string &name, const Stmt &s);
|
||||
bool mutatesExpr(const std::string &name, const Expr &e);
|
||||
|
|
@ -225,7 +242,9 @@ public:
|
|||
std::string typeKey(const FType &t);
|
||||
llvm::Value *tmp(llvm::Value *v); // a stack copy of v, to pass by address
|
||||
void emitPrint(llvm::Value *v, const FType &t, bool quoted);
|
||||
llvm::Function *printFn(const FType &t);
|
||||
void emitFormat(llvm::Value *buf, llvm::Value *v, const FType &t, bool quoted); // append v as text to the str at buf
|
||||
void bufText(llvm::Value *buf, const std::string &text);
|
||||
llvm::Function *formatFn(const FType &t);
|
||||
void printf_(const std::string &fmt, std::vector<llvm::Value *> args);
|
||||
llvm::Value *strConst(const std::string &s);
|
||||
llvm::Value *cstr(llvm::Value *strValue); // str -> char* for C
|
||||
|
|
@ -236,13 +255,26 @@ public:
|
|||
Value_ call(const CallExpr &c);
|
||||
Value_ method(const MethodExpr &m);
|
||||
Value_ callFinch(const Fn &fn, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p,
|
||||
const std::string &shownName);
|
||||
const std::string &shownName, const Expr *site, llvm::Value *self = nullptr,
|
||||
const std::string &selfRoot = "");
|
||||
Value_ construct(StructInfo *s, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p);
|
||||
Value_ callC(const CFunc &f, const std::vector<ExprPtr> &args, Pos p);
|
||||
Value_ convert(const FType &to, const std::vector<ExprPtr> &args, Pos p, const std::string &name);
|
||||
Value_ convert(const FType &to, const std::vector<ExprPtr> &args, Pos p, const std::string &name, bool canFail = false);
|
||||
Value_ print(const std::vector<ExprPtr> &args, Pos p);
|
||||
Value_ addrOf(const std::vector<ExprPtr> &args, Pos p);
|
||||
Value_ arrayMethod(const MethodExpr &m, Value_ *recv);
|
||||
Value_ structMethod(const MethodExpr &m, LRef &r, const FType &t);
|
||||
|
||||
// ---------- codegen_map.cpp: maps ----------
|
||||
llvm::StructType *mapEntryTy(const FType &t);
|
||||
Value_ mapLit(const MapLitExpr &e, const FType *want);
|
||||
LRef mapIndex(const IndexExpr &e, LRef &obj, const FType &t, bool forWrite);
|
||||
Value_ mapMethod(const MethodExpr &m, Value_ *recv);
|
||||
std::vector<llvm::Value *> mapArgs(llvm::Value *map, llvm::Value *keyAddr, const FType &t);
|
||||
llvm::Value *mapSlot(llvm::Value *map, const FType &t, const Value_ &key, Pos p, bool fill);
|
||||
llvm::Value *mapEntry(llvm::Value *map, const FType &t, llvm::Value *i);
|
||||
void forEachEntry(llvm::Value *map, const FType &t, const std::function<void(llvm::Value *i, llvm::Value *entry)> &body);
|
||||
llvm::Value *keyText(const Value_ &key);
|
||||
bool isBuiltin(const std::string &n);
|
||||
std::string headerHint(const std::string &name);
|
||||
void checkArgs(const std::vector<ExprPtr> &args, const std::vector<std::string> &names, size_t want, Pos p,
|
||||
|
|
|
|||
242
src/codegen_map.cpp
Normal file
242
src/codegen_map.cpp
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
// Code generation, part 4: maps.
|
||||
//
|
||||
// map[K]V is { entries, len, used, cap, index, icap } (see runtime/finch_rt.c). The runtime finds and
|
||||
// places entries; the compiler stores, copies and drops the keys and values in them, like it does
|
||||
// for array elements. An entry is { i64 hash, K key, V value }; hash 0 marks a removed entry.
|
||||
|
||||
#include "codegen_impl.h"
|
||||
#include "builtins_doc.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace finch {
|
||||
|
||||
StructType *Codegen::mapEntryTy(const FType &t) {
|
||||
return StructType::get(ctx, {b.getInt64Ty(), ty(*t.key), ty(*t.elem)});
|
||||
}
|
||||
|
||||
// The runtime's (map, key, entry size, key size, key is str) arguments.
|
||||
std::vector<Value *> Codegen::mapArgs(Value *map, Value *keyAddr, const FType &t) {
|
||||
return {map, keyAddr, b.getInt64(dl->getTypeAllocSize(mapEntryTy(t))), elemSize(*t.key),
|
||||
b.getInt32(t.key->kind == FType::Str ? 1 : 0)};
|
||||
}
|
||||
|
||||
Value *Codegen::mapEntry(Value *map, const FType &t, Value *i) {
|
||||
Value *entries = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), map, 0));
|
||||
return b.CreateInBoundsGEP(mapEntryTy(t), entries, i);
|
||||
}
|
||||
|
||||
// The entry for `key`, added if it's missing: the key is copied in, and the value is its type's
|
||||
// default when `fill` (m[k] += 1 starts from 0).
|
||||
Value *Codegen::mapSlot(Value *map, const FType &t, const Value_ &key, Pos p, bool fill) {
|
||||
StructType *ET = mapEntryTy(t);
|
||||
Value *keyAddr = tmp(key.v);
|
||||
Value *added = tmpOf(b.getInt32Ty());
|
||||
std::vector<Value *> args = mapArgs(map, keyAddr, t);
|
||||
args.push_back(added);
|
||||
Value *entry = mapEntry(map, t, b.CreateCall(rt("finch_map_slot"), args));
|
||||
BasicBlock *fresh = newBlock("map.new"), *have = newBlock("map.have");
|
||||
b.CreateCondBr(b.CreateICmpNE(b.CreateLoad(b.getInt32Ty(), added), b.getInt32(0)), fresh, have);
|
||||
b.SetInsertPoint(fresh);
|
||||
copyAt(b.CreateStructGEP(ET, entry, 1), keyAddr, *t.key);
|
||||
if (fill) b.CreateStore(own(defaultValue(*t.elem, p)), b.CreateStructGEP(ET, entry, 2));
|
||||
b.CreateBr(have);
|
||||
b.SetInsertPoint(have);
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Every live entry, in insertion order: body(entry number, entry address).
|
||||
void Codegen::forEachEntry(Value *map, const FType &t, const std::function<void(Value *, Value *)> &body) {
|
||||
Value *used = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), map, 2));
|
||||
forN(used, [&](Value *i) {
|
||||
Value *entry = mapEntry(map, t, i);
|
||||
Value *hash = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(mapEntryTy(t), entry, 0));
|
||||
BasicBlock *live = newBlock("entry.live"), *next = newBlock("entry.next");
|
||||
b.CreateCondBr(b.CreateICmpNE(hash, b.getInt64(0)), live, next);
|
||||
b.SetInsertPoint(live);
|
||||
body(i, entry);
|
||||
b.CreateBr(next);
|
||||
b.SetInsertPoint(next);
|
||||
});
|
||||
}
|
||||
|
||||
// A key as text for error messages: "bob" or 42
|
||||
Value *Codegen::keyText(const Value_ &key) {
|
||||
Value *out = tmpOf(ty(FType::Str));
|
||||
const FType &t = key.type;
|
||||
if (t.kind == FType::Str) {
|
||||
Value *q = tmp(strConst("\"")), *half = tmpOf(ty(FType::Str));
|
||||
b.CreateCall(rt("finch_str_concat"), {half, q, tmp(key.v)});
|
||||
b.CreateCall(rt("finch_str_concat"), {out, half, q});
|
||||
} else if (t.isSigned()) {
|
||||
b.CreateCall(rt("finch_str_from_int"), {out, b.CreateSExt(key.v, b.getInt64Ty())});
|
||||
} else if (t.isUnsigned()) {
|
||||
b.CreateCall(rt("finch_str_from_uint"), {out, b.CreateZExt(key.v, b.getInt64Ty())});
|
||||
} else if (t.kind == FType::Char) {
|
||||
b.CreateCall(rt("finch_str_from_char"), {out, key.v});
|
||||
} else {
|
||||
b.CreateCall(rt("finch_str_from_bool"), {out, b.CreateZExt(key.v, b.getInt32Ty())});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// m[key]: reading a missing key stops the program; writing (m[k] = v, m[k] += 1, m[k].push(x)) adds it
|
||||
LRef Codegen::mapIndex(const IndexExpr &e, LRef &obj, const FType &t, bool forWrite) {
|
||||
Pos p = e.pos;
|
||||
FType kt = *t.key, vt = *t.elem;
|
||||
Value_ key = coerce(exprWant(*e.index, kt), kt, e.index->pos, "the key");
|
||||
if (forWrite) {
|
||||
if (!obj.isPlace) failAt(p.file, p.line, p.col, "this is a temporary map, it can't be changed");
|
||||
Value *entry = mapSlot(obj.pl.addr, t, key, p, true);
|
||||
release(key);
|
||||
return {true, {b.CreateStructGEP(mapEntryTy(t), entry, 2), vt}, {}};
|
||||
}
|
||||
Value *map = obj.isPlace ? obj.pl.addr : tmp(obj.val.v);
|
||||
Value *i = b.CreateCall(rt("finch_map_find"), mapArgs(map, tmp(key.v), t));
|
||||
BasicBlock *missing = newBlock("map.missing"), *found = newBlock("map.found");
|
||||
b.CreateCondBr(b.CreateICmpSLT(i, b.getInt64(0)), missing, found);
|
||||
b.SetInsertPoint(missing);
|
||||
b.CreateCall(rt("finch_map_missing"), {fileName(p), b.getInt64(p.line), keyText(key)});
|
||||
b.CreateUnreachable();
|
||||
b.SetInsertPoint(found);
|
||||
release(key);
|
||||
Value *valueAddr = b.CreateStructGEP(mapEntryTy(t), mapEntry(map, t, i), 2);
|
||||
if (obj.isPlace) return {true, {valueAddr, vt}, {}};
|
||||
Value *v = b.CreateLoad(ty(vt), valueAddr); // from a temporary map: take the value, drop the rest
|
||||
Value_ out{v, vt};
|
||||
if (owning(vt) && obj.val.fresh) out = {copyValue(v, vt), vt, false, true};
|
||||
release(obj.val);
|
||||
return {false, {}, out};
|
||||
}
|
||||
|
||||
// ["a": 1, "b": 2], or [:] where the type is known
|
||||
Value_ Codegen::mapLit(const MapLitExpr &e, const FType *want) {
|
||||
Pos p = e.pos;
|
||||
FType t;
|
||||
Value_ k0, v0; // the first entry, when it decides the types
|
||||
bool first = false;
|
||||
if (want && want->kind == FType::Map) {
|
||||
t = *want;
|
||||
} else if (e.keys.empty()) {
|
||||
failAt(p.file, p.line, p.col, "an empty [:] needs a type, like: map[str]int m = [:]");
|
||||
} else {
|
||||
// number values written in the code are all floats if any of them has a dot
|
||||
bool allNum = true, anyFloat = false;
|
||||
for (auto &x : e.values) {
|
||||
const Expr *y = x.get();
|
||||
if (y->kind == ExprKind::Unary && static_cast<const UnaryExpr *>(y)->op == '-') y = static_cast<const UnaryExpr *>(y)->operand.get();
|
||||
if (y->kind == ExprKind::Float) anyFloat = true;
|
||||
else if (y->kind != ExprKind::Int) allNum = false;
|
||||
}
|
||||
k0 = expr(*e.keys[0]);
|
||||
v0 = expr(*e.values[0]);
|
||||
Pos kp = e.keys[0]->pos, vp = e.values[0]->pos;
|
||||
if (k0.type.kind == FType::Void || k0.type.kind == FType::Null) failAt(kp.file, kp.line, kp.col, "a map key needs a value with a type");
|
||||
if (v0.type.kind == FType::Void || v0.type.kind == FType::Null) failAt(vp.file, vp.line, vp.col, "a map value needs a value with a type");
|
||||
t = resolve(FType::mapOf(k0.type, allNum && anyFloat ? FType(FType::F64) : v0.type), kp);
|
||||
first = true;
|
||||
}
|
||||
Value *map = tmp(zero(t));
|
||||
StructType *ET = mapEntryTy(t);
|
||||
for (size_t i = 0; i < e.keys.size(); i++) {
|
||||
Value_ k = i == 0 && first ? k0 : exprWant(*e.keys[i], *t.key);
|
||||
k = coerce(k, *t.key, e.keys[i]->pos, "every key of this map");
|
||||
Value_ v = i == 0 && first ? v0 : exprWant(*e.values[i], *t.elem);
|
||||
Value *vv = own(coerce(v, *t.elem, e.values[i]->pos, "every value of this map"));
|
||||
Value *valueAddr = b.CreateStructGEP(ET, mapSlot(map, t, k, p, false), 2);
|
||||
dropAt(valueAddr, *t.elem); // a key given twice: the last value wins
|
||||
b.CreateStore(vv, valueAddr);
|
||||
release(k);
|
||||
}
|
||||
return {b.CreateLoad(ty(t), map), t, false, true};
|
||||
}
|
||||
|
||||
Value_ Codegen::mapMethod(const MethodExpr &m, Value_ *recv) {
|
||||
Pos p = m.pos;
|
||||
const std::string &n = m.name;
|
||||
FType t = recv->type, kt = *t.key, vt = *t.elem;
|
||||
StructType *ET = mapEntryTy(t);
|
||||
Value *map = recv->v;
|
||||
auto need = [&](size_t k) { checkArgs(m.args, m.argNames, k, p, n); };
|
||||
auto keyArg = [&]() { return coerce(exprWant(*m.args[0], kt), kt, m.args[0]->pos, "the key"); };
|
||||
if (g_index)
|
||||
for (const BuiltinDoc &d : kMapMethods)
|
||||
if (n == d.name) note(Pos{p.line, p.col + 1, p.file}, n.size(), "```finch\n" + std::string(d.signature) + "\n```\n" + d.doc);
|
||||
|
||||
if (n == "has") {
|
||||
need(1);
|
||||
Value_ k = keyArg();
|
||||
Value *i = b.CreateCall(rt("finch_map_find"), mapArgs(map, tmp(k.v), t));
|
||||
release(k);
|
||||
return {b.CreateICmpSGE(i, b.getInt64(0)), FType::Bool};
|
||||
}
|
||||
if (n == "get") { // m.get(key, default): the default is only computed when the key is missing
|
||||
need(2);
|
||||
Value_ k = keyArg();
|
||||
Value *i = b.CreateCall(rt("finch_map_find"), mapArgs(map, tmp(k.v), t));
|
||||
release(k);
|
||||
BasicBlock *found = newBlock("get.found"), *missing = newBlock("get.missing"), *end = newBlock("get.end");
|
||||
b.CreateCondBr(b.CreateICmpSGE(i, b.getInt64(0)), found, missing);
|
||||
b.SetInsertPoint(found);
|
||||
Value *v = b.CreateLoad(ty(vt), b.CreateStructGEP(ET, mapEntry(map, t, i), 2));
|
||||
v = copyValue(v, vt);
|
||||
BasicBlock *foundEnd = b.GetInsertBlock();
|
||||
b.CreateBr(end);
|
||||
b.SetInsertPoint(missing);
|
||||
Value *d = own(coerce(exprWant(*m.args[1], vt), vt, m.args[1]->pos, "the default"));
|
||||
BasicBlock *missingEnd = b.GetInsertBlock();
|
||||
b.CreateBr(end);
|
||||
b.SetInsertPoint(end);
|
||||
PHINode *phi = b.CreatePHI(ty(vt), 2);
|
||||
phi->addIncoming(v, foundEnd);
|
||||
phi->addIncoming(d, missingEnd);
|
||||
return {phi, vt, false, owning(vt)};
|
||||
}
|
||||
if (n == "remove") { // true if the key was there
|
||||
need(1);
|
||||
Value_ k = keyArg();
|
||||
Value *i = b.CreateCall(rt("finch_map_find"), mapArgs(map, tmp(k.v), t));
|
||||
release(k);
|
||||
BasicBlock *hit = newBlock("remove.hit"), *end = newBlock("remove.end");
|
||||
Value *was = b.CreateICmpSGE(i, b.getInt64(0));
|
||||
b.CreateCondBr(was, hit, end);
|
||||
b.SetInsertPoint(hit);
|
||||
Value *entry = mapEntry(map, t, i);
|
||||
dropAt(b.CreateStructGEP(ET, entry, 1), kt);
|
||||
dropAt(b.CreateStructGEP(ET, entry, 2), vt);
|
||||
b.CreateCall(rt("finch_map_remove_at"), {map, i, b.getInt64(dl->getTypeAllocSize(ET))});
|
||||
b.CreateBr(end);
|
||||
b.SetInsertPoint(end);
|
||||
return {was, FType::Bool};
|
||||
}
|
||||
if (n == "clear") {
|
||||
need(0);
|
||||
if (owning(kt) || owning(vt))
|
||||
forEachEntry(map, t, [&](Value *, Value *entry) {
|
||||
dropAt(b.CreateStructGEP(ET, entry, 1), kt);
|
||||
dropAt(b.CreateStructGEP(ET, entry, 2), vt);
|
||||
});
|
||||
b.CreateCall(rt("finch_map_clear"), {map});
|
||||
return {nullptr, FType::Void};
|
||||
}
|
||||
if (n == "keys" || n == "values") { // a new array, in insertion order
|
||||
need(0);
|
||||
FType et = n == "keys" ? kt : vt;
|
||||
unsigned field = n == "keys" ? 1 : 2;
|
||||
FType at = FType::arrayOf(et);
|
||||
Value *out = tmpOf(ty(at));
|
||||
Value *len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), map, 1));
|
||||
b.CreateCall(rt("finch_arr_make"), {out, len, elemSize(et)});
|
||||
Value *data = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(at), out, 0));
|
||||
Value *j = tmp(b.getInt64(0));
|
||||
forEachEntry(map, t, [&](Value *, Value *entry) {
|
||||
Value *jv = b.CreateLoad(b.getInt64Ty(), j);
|
||||
copyAt(b.CreateInBoundsGEP(ty(et), data, jv), b.CreateStructGEP(ET, entry, field), et);
|
||||
b.CreateStore(b.CreateAdd(jv, b.getInt64(1)), j);
|
||||
});
|
||||
return {b.CreateLoad(ty(at), out), at, false, true};
|
||||
}
|
||||
failAt(p.file, p.line, p.col, "a map has no method '" + n + "' (it has: has, get, remove, clear, keys, values; and m[key], .len)");
|
||||
}
|
||||
|
||||
} // namespace finch
|
||||
|
|
@ -25,6 +25,10 @@ llvm::Type *Codegen::ty(const FType &t) {
|
|||
case FType::F64: return b.getDoubleTy();
|
||||
case FType::Str:
|
||||
case FType::Array: return StructType::get(ctx, {b.getPtrTy(), b.getInt64Ty(), b.getInt64Ty()});
|
||||
case FType::Map: {
|
||||
llvm::Type *P = b.getPtrTy(), *I = b.getInt64Ty();
|
||||
return StructType::get(ctx, {P, I, I, I, P, I}); // entries, len, used, cap, index, icap
|
||||
}
|
||||
case FType::Ptr:
|
||||
case FType::Null: return b.getPtrTy();
|
||||
case FType::Fixed: return ArrayType::get(ty(*t.elem), t.count);
|
||||
|
|
@ -60,6 +64,14 @@ FType Codegen::resolveT(const FType &t, Pos p, bool deep) {
|
|||
if (!t.elem) return t;
|
||||
return FType::ptrTo(resolveT(*t.elem, p, false));
|
||||
case FType::Array: return FType::arrayOf(resolveT(*t.elem, p, false));
|
||||
case FType::Map: {
|
||||
FType k = resolveT(*t.key, p, true);
|
||||
if (!k.isInt() && k.kind != FType::Char && k.kind != FType::Bool && k.kind != FType::Str)
|
||||
failAt(p.file, p.line, p.col, "a map key must be a whole number, a char, a bool or a str, not " + k.show());
|
||||
FType v = resolveT(*t.elem, p, false);
|
||||
if (v.kind == FType::Void) failAt(p.file, p.line, p.col, "a map value can't have the type 'nothing'");
|
||||
return FType::mapOf(k, v);
|
||||
}
|
||||
case FType::Fixed: {
|
||||
FType f = t;
|
||||
f.elem = std::make_shared<FType>(resolveT(*t.elem, p, deep));
|
||||
|
|
@ -136,7 +148,8 @@ void Codegen::resolveStruct(StructInfo *s) {
|
|||
bool Codegen::owning(const FType &t) {
|
||||
switch (t.kind) {
|
||||
case FType::Str:
|
||||
case FType::Array: return true;
|
||||
case FType::Array:
|
||||
case FType::Map: return true;
|
||||
case FType::Struct:
|
||||
resolveStruct(t.info);
|
||||
return t.info->owning;
|
||||
|
|
@ -279,6 +292,7 @@ void Codegen::forN(Value *n, const std::function<void(Value *)> &body) {
|
|||
std::string Codegen::typeKey(const FType &t) {
|
||||
switch (t.kind) {
|
||||
case FType::Array: return "arr_" + typeKey(*t.elem);
|
||||
case FType::Map: return "map_" + typeKey(*t.key) + "_" + typeKey(*t.elem);
|
||||
case FType::Fixed: return "fix" + std::to_string(t.count) + "_" + typeKey(*t.elem);
|
||||
case FType::Ptr: return "ptr";
|
||||
case FType::Struct: return "S_" + (t.info->module.empty() ? std::string("main") : t.info->module) + "_" + t.info->name;
|
||||
|
|
@ -315,6 +329,14 @@ Function *Codegen::dropFn(const FType &t) {
|
|||
forN(len, [&](Value *i) { dropAt(b.CreateInBoundsGEP(ty(et), data, i), et); });
|
||||
}
|
||||
b.CreateCall(rt("finch_arr_free"), {a});
|
||||
} else if (t.kind == FType::Map) {
|
||||
FType kt = *t.key, vt = *t.elem;
|
||||
if (owning(kt) || owning(vt))
|
||||
forEachEntry(a, t, [&](Value *, Value *entry) {
|
||||
dropAt(b.CreateStructGEP(mapEntryTy(t), entry, 1), kt);
|
||||
dropAt(b.CreateStructGEP(mapEntryTy(t), entry, 2), vt);
|
||||
});
|
||||
b.CreateCall(rt("finch_map_free"), {a});
|
||||
} else if (t.kind == FType::Struct) {
|
||||
for (auto &fl : t.info->fields)
|
||||
if (owning(fl.type)) dropAt(b.CreateStructGEP(t.info->llvm, a, fl.llvmIndex), fl.type);
|
||||
|
|
@ -342,6 +364,16 @@ Function *Codegen::copyFn(const FType &t) {
|
|||
copyAt(b.CreateInBoundsGEP(ty(et), d, i), b.CreateInBoundsGEP(ty(et), s, i), et);
|
||||
});
|
||||
}
|
||||
} else if (t.kind == FType::Map) {
|
||||
StructType *ET = mapEntryTy(t);
|
||||
FType kt = *t.key, vt = *t.elem;
|
||||
b.CreateCall(rt("finch_map_clone_raw"), {dst, src, b.getInt64(dl->getTypeAllocSize(ET))});
|
||||
if (owning(kt) || owning(vt)) // the slots were copied byte for byte: now give the copy its own keys and values
|
||||
forEachEntry(src, t, [&](Value *i, Value *from) {
|
||||
Value *to = mapEntry(dst, t, i);
|
||||
copyAt(b.CreateStructGEP(ET, to, 1), b.CreateStructGEP(ET, from, 1), kt);
|
||||
copyAt(b.CreateStructGEP(ET, to, 2), b.CreateStructGEP(ET, from, 2), vt);
|
||||
});
|
||||
} else if (t.kind == FType::Struct) {
|
||||
b.CreateStore(b.CreateLoad(ty(t), src), dst);
|
||||
for (auto &fl : t.info->fields)
|
||||
|
|
@ -388,30 +420,37 @@ void Codegen::emitPrint(Value *v, const FType &t, bool quoted) {
|
|||
else if (t.kind == FType::Bool) printf_("%s", {b.CreateSelect(v, b.CreateGlobalString("true", "true"), b.CreateGlobalString("false", "false"))});
|
||||
else if (t.kind == FType::Str)
|
||||
printf_(quoted ? "\"%.*s\"" : "%.*s", {b.CreateTrunc(b.CreateExtractValue(v, 1), b.getInt32Ty()), cstr(v)});
|
||||
else if (t.isPtr()) { // the same on every system: null or 0x...
|
||||
Value *isNull = b.CreateIsNull(v);
|
||||
BasicBlock *nul = newBlock("ptr.null"), *addr = newBlock("ptr.addr"), *done = newBlock("ptr.done");
|
||||
b.CreateCondBr(isNull, nul, addr);
|
||||
b.SetInsertPoint(nul);
|
||||
printf_("null", {});
|
||||
b.CreateBr(done);
|
||||
b.SetInsertPoint(addr);
|
||||
printf_("0x%llx", {b.CreatePtrToInt(v, b.getInt64Ty())});
|
||||
b.CreateBr(done);
|
||||
b.SetInsertPoint(done);
|
||||
else { // pointers, arrays, maps, structs: built as text first (the same text str(x) gives)
|
||||
Value *buf = tmp(zero(FType::Str));
|
||||
emitFormat(buf, v, t, quoted);
|
||||
b.CreateCall(rt("finch_buf_print"), {buf});
|
||||
}
|
||||
else if (t.kind == FType::Null) printf_("null", {});
|
||||
else b.CreateCall(printFn(t), {tmp(v)});
|
||||
}
|
||||
|
||||
Function *Codegen::printFn(const FType &t) {
|
||||
std::string key = "print." + typeKey(t);
|
||||
void Codegen::bufText(Value *buf, const std::string &text) {
|
||||
b.CreateCall(rt("finch_buf_add"), {buf, b.CreateGlobalString(text, "txt"), b.getInt64(text.size())});
|
||||
}
|
||||
|
||||
void Codegen::emitFormat(Value *buf, Value *v, const FType &t, bool quoted) {
|
||||
if (t.isSigned()) b.CreateCall(rt("finch_buf_int"), {buf, b.CreateSExt(v, b.getInt64Ty())});
|
||||
else if (t.isUnsigned()) b.CreateCall(rt("finch_buf_uint"), {buf, b.CreateZExt(v, b.getInt64Ty())});
|
||||
else if (t.isFloat()) b.CreateCall(rt("finch_buf_float"), {buf, b.CreateFPExt(v, b.getDoubleTy())});
|
||||
else if (t.kind == FType::Char) b.CreateCall(rt("finch_buf_char"), {buf, v, b.getInt32(quoted)});
|
||||
else if (t.kind == FType::Bool) b.CreateCall(rt("finch_buf_bool"), {buf, b.CreateZExt(v, b.getInt32Ty())});
|
||||
else if (t.kind == FType::Str) b.CreateCall(rt("finch_buf_str"), {buf, tmp(v), b.getInt32(quoted)});
|
||||
else if (t.isPtr() || t.kind == FType::Null) b.CreateCall(rt("finch_buf_ptr"), {buf, v}); // the same everywhere: null or 0x...
|
||||
else b.CreateCall(formatFn(t), {buf, tmp(v)});
|
||||
}
|
||||
|
||||
// One function per array / map / struct type: (str *buf, T *value)
|
||||
Function *Codegen::formatFn(const FType &t) {
|
||||
std::string key = "format." + typeKey(t);
|
||||
if (auto it = helpers.find(key); it != helpers.end()) return it->second;
|
||||
Function *f = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy()}, false), Function::InternalLinkage,
|
||||
"finch." + key, mod.get());
|
||||
Function *f = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy(), b.getPtrTy()}, false),
|
||||
Function::InternalLinkage, "finch." + key, mod.get());
|
||||
helpers[key] = f;
|
||||
HelperScope hs(*this, f);
|
||||
Value *a = f->getArg(0);
|
||||
Value *buf = f->getArg(0), *a = f->getArg(1);
|
||||
if (t.kind == FType::Array || t.kind == FType::Fixed) {
|
||||
FType et = *t.elem;
|
||||
Value *data, *len;
|
||||
|
|
@ -422,26 +461,44 @@ Function *Codegen::printFn(const FType &t) {
|
|||
data = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), a, 0));
|
||||
len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), a, 1));
|
||||
}
|
||||
printf_("[", {});
|
||||
bufText(buf, "[");
|
||||
forN(len, [&](Value *i) {
|
||||
BasicBlock *sep = newBlock("sep"), *item = newBlock("item");
|
||||
b.CreateCondBr(b.CreateICmpSGT(i, b.getInt64(0)), sep, item);
|
||||
b.SetInsertPoint(sep);
|
||||
printf_(", ", {});
|
||||
bufText(buf, ", ");
|
||||
b.CreateBr(item);
|
||||
b.SetInsertPoint(item);
|
||||
emitPrint(b.CreateLoad(ty(et), b.CreateInBoundsGEP(ty(et), data, i)), et, true);
|
||||
emitFormat(buf, b.CreateLoad(ty(et), b.CreateInBoundsGEP(ty(et), data, i)), et, true);
|
||||
});
|
||||
printf_("]", {});
|
||||
bufText(buf, "]");
|
||||
} else if (t.kind == FType::Map) { // {"a": 1, "b": 2}
|
||||
StructType *ET = mapEntryTy(t);
|
||||
Value *firstItem = tmp(b.getInt1(true));
|
||||
bufText(buf, "{");
|
||||
forEachEntry(a, t, [&](Value *, Value *entry) {
|
||||
BasicBlock *sep = newBlock("sep"), *item = newBlock("item");
|
||||
b.CreateCondBr(b.CreateLoad(b.getInt1Ty(), firstItem), item, sep);
|
||||
b.SetInsertPoint(sep);
|
||||
bufText(buf, ", ");
|
||||
b.CreateBr(item);
|
||||
b.SetInsertPoint(item);
|
||||
b.CreateStore(b.getInt1(false), firstItem);
|
||||
emitFormat(buf, b.CreateLoad(ty(*t.key), b.CreateStructGEP(ET, entry, 1)), *t.key, true);
|
||||
bufText(buf, ": ");
|
||||
emitFormat(buf, b.CreateLoad(ty(*t.elem), b.CreateStructGEP(ET, entry, 2)), *t.elem, true);
|
||||
});
|
||||
bufText(buf, "}");
|
||||
} else if (t.kind == FType::Struct) {
|
||||
printf_(t.info->name + "(", {});
|
||||
bufText(buf, t.info->name + "(");
|
||||
bool first = true;
|
||||
for (auto &fl : t.info->fields) {
|
||||
printf_((first ? "" : ", ") + fl.name + ": ", {});
|
||||
if (fl.hidden) continue;
|
||||
bufText(buf, (first ? "" : ", ") + fl.name + ": ");
|
||||
first = false;
|
||||
emitPrint(b.CreateLoad(ty(fl.type), b.CreateStructGEP(t.info->llvm, a, fl.llvmIndex)), fl.type, true);
|
||||
emitFormat(buf, b.CreateLoad(ty(fl.type), b.CreateStructGEP(t.info->llvm, a, fl.llvmIndex)), fl.type, true);
|
||||
}
|
||||
printf_(")", {});
|
||||
bufText(buf, ")");
|
||||
}
|
||||
b.CreateRetVoid();
|
||||
return f;
|
||||
|
|
@ -486,6 +543,16 @@ DIType *Codegen::diType(const FType &t) {
|
|||
d = di->createStructType(diUnit, t.show(), f, 0, 192, 64, DINode::FlagZero, nullptr, di->getOrCreateArray(els));
|
||||
break;
|
||||
}
|
||||
case FType::Map: { // shown by its parts; the entries are behind `entries`
|
||||
DIType *i64 = diType(FType::I64), *pt = di->createPointerType(nullptr, 64);
|
||||
DIFile *f = diFiles[0];
|
||||
const char *names[] = {"entries", "len", "used", "cap", "index", "icap"};
|
||||
std::vector<Metadata *> els;
|
||||
for (unsigned k = 0; k < 6; k++)
|
||||
els.push_back(di->createMemberType(diUnit, names[k], f, 0, 64, 64, 64 * k, DINode::FlagZero, k == 0 || k == 4 ? pt : i64));
|
||||
d = di->createStructType(diUnit, t.show(), f, 0, 384, 64, DINode::FlagZero, nullptr, di->getOrCreateArray(els));
|
||||
break;
|
||||
}
|
||||
case FType::Struct: {
|
||||
StructInfo *s = t.info;
|
||||
DIFile *f = s->isC ? diFiles[0] : diFiles[s->pos.file];
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ static const std::unordered_map<std::string, Tok> keywords = {
|
|||
{"in", Tok::In}, {"break", Tok::Break}, {"continue", Tok::Continue},
|
||||
{"true", Tok::True}, {"false", Tok::False}, {"null", Tok::Null},
|
||||
{"import", Tok::Import}, {"link", Tok::Link}, {"struct", Tok::Struct},
|
||||
{"defer", Tok::Defer},
|
||||
{"defer", Tok::Defer}, {"or", Tok::OrElse}, {"try", Tok::Try},
|
||||
};
|
||||
|
||||
std::vector<Token> lex(int file) {
|
||||
|
|
@ -169,6 +169,8 @@ const char *tokName(Tok t) {
|
|||
case Tok::Link: return "'link'";
|
||||
case Tok::Struct: return "'struct'";
|
||||
case Tok::Defer: return "'defer'";
|
||||
case Tok::OrElse: return "'or'";
|
||||
case Tok::Try: return "'try'";
|
||||
case Tok::Colon: return "':'";
|
||||
case Tok::LParen: return "'('";
|
||||
case Tok::RParen: return "')'";
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ enum class Tok {
|
|||
|
||||
// keywords
|
||||
Fn, Return, If, Else, While, For, In, Break, Continue, True, False, Null, Import, Link, Struct, Defer,
|
||||
OrElse, Try, // or, try
|
||||
|
||||
// punctuation
|
||||
LParen, RParen, LBrace, RBrace, LBracket, RBracket, Comma, Colon, Dot, DotDot, Arrow,
|
||||
|
|
|
|||
25
src/lsp.cpp
25
src/lsp.cpp
|
|
@ -412,17 +412,34 @@ json::Value Server::completion(const std::string &path, int line, int col) {
|
|||
if (v.name == name && v.line <= line && line <= v.fnEnd + 1) type = v.type;
|
||||
if (element && type.rfind("[]", 0) == 0) type = type.substr(2);
|
||||
if (element && type == "str") type = "char";
|
||||
if (element && type.rfind("map[", 0) == 0) { // m[key]. -> the value type
|
||||
int depth = 0;
|
||||
for (size_t k = 3; k < type.size(); k++) {
|
||||
if (type[k] == '[') depth++;
|
||||
else if (type[k] == ']' && --depth == 0) {
|
||||
type = type.substr(k + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type.rfind("ptr[", 0) == 0) type = type.substr(4, type.size() - 5); // fields through a pointer
|
||||
if (type == "str") {
|
||||
for (const BuiltinDoc &d : kStrMethods) items.push_back(item(d.name, d.name[0] == 'l' || d.name[0] == 'p' ? KField : KMethod, d.signature, d.doc));
|
||||
} else if (type.rfind("[]", 0) == 0) {
|
||||
for (const BuiltinDoc &d : kArrayMethods) items.push_back(item(d.name, std::string(d.name) == "len" || std::string(d.name) == "ptr" ? KField : KMethod, d.signature, d.doc));
|
||||
} else if (type.rfind("map[", 0) == 0) {
|
||||
for (const BuiltinDoc &d : kMapMethods) items.push_back(item(d.name, std::string(d.name) == "len" ? KField : KMethod, d.signature, d.doc));
|
||||
} else if (!type.empty()) {
|
||||
std::string mod, sname = type;
|
||||
if (size_t dot = type.find('.'); dot != std::string::npos) mod = type.substr(0, dot), sname = type.substr(dot + 1);
|
||||
for (const StructIndex &s2 : a->index.structs)
|
||||
if (s2.name == sname && (mod.empty() || s2.module == mod))
|
||||
for (auto &[fname, ftype] : s2.fields) items.push_back(item(fname, KField, ftype + " " + fname));
|
||||
std::set<std::string> seen;
|
||||
for (const FnInfo &f : a->index.fns) // methods: indexed as Struct.name
|
||||
if (f.name.size() > sname.size() + 1 && f.name.compare(0, sname.size() + 1, sname + ".") == 0 &&
|
||||
seen.insert(f.name).second)
|
||||
items.push_back(item(f.name.substr(sname.size() + 1), KMethod, f.signature));
|
||||
if (type.rfind("ptr", 0) == 0 || items.empty()) items.push_back(item("value", KField, "the value the pointer points to"));
|
||||
}
|
||||
return json::Object{{"isIncomplete", false}, {"items", std::move(items)}};
|
||||
|
|
@ -511,9 +528,12 @@ json::Value Server::signatureHelp(const std::string &path, int line, int col) {
|
|||
std::string label, doc;
|
||||
std::vector<std::string> params;
|
||||
if (a) {
|
||||
for (const FnInfo &f : a->index.fns)
|
||||
if (f.name == name && (f.module == mod || (mod.empty() && f.module.empty()) || !a->modules.count(mod)))
|
||||
for (const FnInfo &f : a->index.fns) {
|
||||
bool method = f.name.size() > name.size() && f.name.compare(f.name.size() - name.size() - 1, name.size() + 1, "." + name) == 0;
|
||||
if ((f.name == name && (f.module == mod || (mod.empty() && f.module.empty()) || !a->modules.count(mod))) ||
|
||||
(method && !mod.empty() && !a->modules.count(mod))) // p.move( -> Point.move
|
||||
label = f.signature, params = f.params;
|
||||
}
|
||||
if (label.empty())
|
||||
for (const StructIndex &s : a->index.structs)
|
||||
if (s.name == name) {
|
||||
|
|
@ -544,6 +564,7 @@ json::Value Server::signatureHelp(const std::string &path, int line, int col) {
|
|||
if (!mod.empty()) {
|
||||
look(std::begin(kArrayMethods), std::end(kArrayMethods));
|
||||
look(std::begin(kStrMethods), std::end(kStrMethods));
|
||||
look(std::begin(kMapMethods), std::end(kMapMethods));
|
||||
}
|
||||
}
|
||||
if (label.empty()) return nullptr;
|
||||
|
|
|
|||
|
|
@ -39,10 +39,11 @@ static void usage() {
|
|||
" finch build <file.fch> [-o name] compile to a program\n"
|
||||
" finch ir <file.fch> show the generated LLVM IR\n"
|
||||
" finch version show the version\n"
|
||||
" finch lsp the language server, for editors (VS Code)\n"
|
||||
" finch lsp the language server, for editors (VS Code, Kate, ...)\n"
|
||||
"\n"
|
||||
" -l <lib> link a C library, same as link \"lib\" in the file\n"
|
||||
" --target <name> build for another system: windows, linux, or an LLVM triple\n"
|
||||
" --target <name> build for another system: windows, linux, arm64 (Linux), macos,\n"
|
||||
" or an LLVM triple\n"
|
||||
" -g add debug info (gdb / lldb / Visual Studio)\n"
|
||||
" -O0 skip optimizations (to read the raw IR)\n");
|
||||
std::exit(1);
|
||||
|
|
|
|||
|
|
@ -79,13 +79,20 @@ private:
|
|||
// 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 ...
|
||||
// int, u8, ptr, ptr[int], []str, map[str]int, 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)");
|
||||
if (cur().text == "map" && peekTok().kind == Tok::LBracket) {
|
||||
next();
|
||||
next();
|
||||
Type k = type();
|
||||
expect(Tok::RBracket, "']' (a map type looks like map[str]int)");
|
||||
return Type::mapOf(k, type());
|
||||
}
|
||||
std::string name = next().text;
|
||||
Type t;
|
||||
if (typeFromName(name, t)) {
|
||||
|
|
@ -111,7 +118,7 @@ private:
|
|||
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;
|
||||
if ((cur().text == "ptr" || cur().text == "map") && n == Tok::LBracket) return true;
|
||||
return n == Tok::Dot && peekTok(2).kind == Tok::Ident && peekTok(3).kind == Tok::Ident &&
|
||||
!peekTok(3).newlineBefore;
|
||||
}
|
||||
|
|
@ -179,6 +186,12 @@ private:
|
|||
|
||||
fn.namePos = pos();
|
||||
fn.name = expect(Tok::Ident, "a function name").text;
|
||||
if (accept(Tok::Dot)) { // fn Point.move(...): a method
|
||||
fn.recv = fn.name;
|
||||
fn.recvPos = fn.namePos;
|
||||
fn.namePos = pos();
|
||||
fn.name = expect(Tok::Ident, "a method name (fn Point.name(...))").text;
|
||||
}
|
||||
expect(Tok::LParen);
|
||||
parenDepth++;
|
||||
if (!at(Tok::RParen)) {
|
||||
|
|
@ -193,7 +206,12 @@ private:
|
|||
}
|
||||
parenDepth--;
|
||||
expect(Tok::RParen);
|
||||
fn.ret = accept(Tok::Arrow) ? type() : Type(Type::Void);
|
||||
fn.ret = Type(Type::Void);
|
||||
if (accept(Tok::Arrow)) {
|
||||
if (!at(Tok::Not)) fn.ret = type();
|
||||
fn.fallible = accept(Tok::Not); // -> int! or -> !
|
||||
if (!fn.fallible && fn.ret.kind == Type::Void) unexpected("(after '->' comes a type, or ! for a function that can fail)");
|
||||
}
|
||||
fn.body = block();
|
||||
return fn;
|
||||
}
|
||||
|
|
@ -236,8 +254,15 @@ private:
|
|||
next();
|
||||
Pos varPos = pos();
|
||||
std::string var = expect(Tok::Ident, "a loop variable name").text;
|
||||
std::string var2;
|
||||
Pos var2Pos;
|
||||
if (accept(Tok::Comma)) { // for i, x in list / for key, value in map
|
||||
var2Pos = pos();
|
||||
var2 = expect(Tok::Ident, "a second loop variable name").text;
|
||||
}
|
||||
expect(Tok::In);
|
||||
ExprPtr first = expr();
|
||||
if (!var2.empty() && at(Tok::DotDot)) fail(var2Pos.line, var2Pos.col, "a range (for i in 0..n) has one loop variable");
|
||||
if (accept(Tok::DotDot)) { // for i in 0..10
|
||||
auto s = std::make_unique<ForStmt>(p);
|
||||
s->var = var;
|
||||
|
|
@ -250,6 +275,8 @@ private:
|
|||
auto s = std::make_unique<ForEachStmt>(p); // for x in list
|
||||
s->var = var;
|
||||
s->varPos = varPos;
|
||||
s->var2 = var2;
|
||||
s->var2Pos = var2Pos;
|
||||
s->list = std::move(first);
|
||||
s->body = block();
|
||||
return s;
|
||||
|
|
@ -323,7 +350,7 @@ private:
|
|||
return s;
|
||||
}
|
||||
|
||||
if (e->kind != ExprKind::Call && e->kind != ExprKind::Method)
|
||||
if (e->kind != ExprKind::Call && e->kind != ExprKind::Method && e->kind != ExprKind::Try && e->kind != ExprKind::OrElse)
|
||||
fail(p.line, p.col, "this value is computed but never used");
|
||||
return std::make_unique<ExprStmt>(p, std::move(e));
|
||||
}
|
||||
|
|
@ -342,7 +369,17 @@ private:
|
|||
|
||||
// ---------- expressions (lowest to highest precedence) ----------
|
||||
|
||||
ExprPtr expr() { return orExpr(); }
|
||||
// f(x) or fallback / f(x) or { ... }: the lowest precedence, right to left
|
||||
ExprPtr expr() {
|
||||
ExprPtr l = orExpr();
|
||||
if (!sameLine() || !at(Tok::OrElse)) return l;
|
||||
Pos p = pos();
|
||||
next();
|
||||
auto e = std::make_unique<OrElseExpr>(p, std::move(l));
|
||||
if (at(Tok::LBrace)) e->block = block();
|
||||
else e->fallback = expr();
|
||||
return e;
|
||||
}
|
||||
|
||||
ExprPtr orExpr() {
|
||||
ExprPtr l = andExpr();
|
||||
|
|
@ -429,6 +466,7 @@ private:
|
|||
if (accept(Tok::Minus)) return std::make_unique<UnaryExpr>(p, '-', unary());
|
||||
if (accept(Tok::Not)) return std::make_unique<UnaryExpr>(p, '!', unary());
|
||||
if (accept(Tok::Tilde)) return std::make_unique<UnaryExpr>(p, '~', unary());
|
||||
if (accept(Tok::Try)) return std::make_unique<TryExpr>(p, postfix());
|
||||
return postfix();
|
||||
}
|
||||
|
||||
|
|
@ -506,14 +544,37 @@ private:
|
|||
expect(Tok::RParen);
|
||||
return e;
|
||||
}
|
||||
case Tok::LBracket: { // [1, 2, 3]
|
||||
case Tok::LBracket: { // [1, 2, 3] or a map: ["a": 1, "b": 2], [:]
|
||||
next();
|
||||
auto a = std::make_unique<ArrayLitExpr>(p);
|
||||
parenDepth++;
|
||||
while (!at(Tok::RBracket)) {
|
||||
a->elems.push_back(expr());
|
||||
if (!accept(Tok::Comma)) break; // a trailing comma is fine
|
||||
if (accept(Tok::Colon)) {
|
||||
parenDepth--;
|
||||
expect(Tok::RBracket, "']' (an empty map is [:])");
|
||||
return std::make_unique<MapLitExpr>(p);
|
||||
}
|
||||
if (!at(Tok::RBracket)) {
|
||||
ExprPtr first = expr();
|
||||
if (accept(Tok::Colon)) {
|
||||
auto m = std::make_unique<MapLitExpr>(p);
|
||||
m->keys.push_back(std::move(first));
|
||||
m->values.push_back(expr());
|
||||
while (accept(Tok::Comma) && !at(Tok::RBracket)) { // a trailing comma is fine
|
||||
m->keys.push_back(expr());
|
||||
expect(Tok::Colon, "':' (every map entry is key: value)");
|
||||
m->values.push_back(expr());
|
||||
}
|
||||
parenDepth--;
|
||||
expect(Tok::RBracket);
|
||||
return m;
|
||||
}
|
||||
auto a = std::make_unique<ArrayLitExpr>(p);
|
||||
a->elems.push_back(std::move(first));
|
||||
while (accept(Tok::Comma) && !at(Tok::RBracket)) a->elems.push_back(expr());
|
||||
parenDepth--;
|
||||
expect(Tok::RBracket);
|
||||
return a;
|
||||
}
|
||||
auto a = std::make_unique<ArrayLitExpr>(p);
|
||||
parenDepth--;
|
||||
expect(Tok::RBracket);
|
||||
return a;
|
||||
|
|
|
|||
Loading…
Reference in a new issue