mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
Structs, arrays, owned strings, memory model, modules, debug info
- struct with default values; constructors Point(1, 2) / Point(x: 1); field access, auto-deref through pointers - []T arrays with bounds checks, literals, for-each, push/pop/insert/remove/clear/resize/find/contains/join/slice/reverse/sort - str carries its length: .len, s[i], +, comparison, sub/find/split/trim/upper/lower/replace/repeat/bytes, str(x), int(s), float(s) - Memory: values own their memory and are freed at the end of their block; assignment copies, temporaries move; parameters are lent and only copied when the function changes them; defer; new()/free() for manual heap memory - input(), read_file/write_file/file_exists, exit(), fn main([]str args) - Finch modules: import name, module.fn(), module.Struct - C structs from headers (fields, fixed arrays, nested); passed and returned by value with the System V x86-64 ABI; addr(fn) gives C a callback - link "file.c" compiles and links your own C code - -g emits DWARF debug info (gdb/lldb) - Small C runtime (runtime/finch_rt.c) embedded in the compiler, cached in ~/.cache/finch - Tests: 58, all clean under valgrind (MEMCHECK=1 tests/run.sh) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0591cd20f3
commit
1dc976bd8f
59 changed files with 4577 additions and 1047 deletions
181
src/main.cpp
181
src/main.cpp
|
|
@ -1,14 +1,16 @@
|
|||
#include "cimport.h"
|
||||
#include "codegen.h"
|
||||
#include "error.h"
|
||||
#include "lexer.h"
|
||||
#include "parser.h"
|
||||
|
||||
#include <llvm/Config/llvm-config.h>
|
||||
#include <llvm/IR/LegacyPassManager.h>
|
||||
#include <llvm/MC/TargetRegistry.h>
|
||||
#include <llvm/Passes/PassBuilder.h>
|
||||
#include <llvm/Support/FileSystem.h>
|
||||
#include <llvm/Support/Path.h>
|
||||
#include <llvm/Support/TargetSelect.h>
|
||||
#include <llvm/Config/llvm-config.h>
|
||||
#include <llvm/Target/TargetMachine.h>
|
||||
#include <llvm/TargetParser/Host.h>
|
||||
|
||||
|
|
@ -18,38 +20,90 @@
|
|||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <sys/wait.h>
|
||||
#include <string>
|
||||
#include <sys/wait.h>
|
||||
#include <vector>
|
||||
|
||||
#include "rt_source.inc" // kRuntimeSource: runtime/finch_rt.c, embedded at build time
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
static void usage() {
|
||||
std::fprintf(stderr,
|
||||
"Finch " FINCH_VERSION " - small, quick, sharp.\n\n"
|
||||
"usage:\n"
|
||||
" finch run <file.fn> compile and run\n"
|
||||
" finch run <file.fn> [args...] compile and run\n"
|
||||
" finch build <file.fn> [-o name] compile to a program\n"
|
||||
" finch ir <file.fn> show the generated LLVM IR\n"
|
||||
" finch version show the version\n"
|
||||
"\n"
|
||||
" -l <lib> link a C library, same as link \"lib\" in the file\n"
|
||||
" -O0 skip optimizations (to read the raw IR)\n"
|
||||
" finch ir <file.fn> show the generated LLVM IR\n"
|
||||
" finch version show the version\n");
|
||||
" -g add debug info (for gdb / lldb)\n"
|
||||
" -O0 skip optimizations (to read the raw IR)\n");
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
static std::string readFile(const std::string &path) {
|
||||
static bool readFile(const std::string &path, std::string &out) {
|
||||
std::ifstream in(path);
|
||||
if (!in) {
|
||||
std::fprintf(stderr, "error: can't open '%s'\n", path.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
if (!in) return false;
|
||||
std::stringstream ss;
|
||||
ss << in.rdbuf();
|
||||
return ss.str();
|
||||
out = ss.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string dirOf(const std::string &path) {
|
||||
size_t slash = path.find_last_of('/');
|
||||
return slash == std::string::npos ? "." : path.substr(0, slash);
|
||||
}
|
||||
|
||||
// ---------- loading the program and its modules ----------
|
||||
|
||||
struct Loader {
|
||||
std::vector<Program> progs;
|
||||
std::map<std::string, bool> loaded; // module name -> seen
|
||||
|
||||
void load(const std::string &path, const std::string &module, Pos from) {
|
||||
std::string text;
|
||||
if (!readFile(path, text)) {
|
||||
if (from.line) failAt(from.file, from.line, from.col, "can't find the module '" + module + "' (looked for " + path + ")");
|
||||
std::fprintf(stderr, "error: can't open '%s'\n", path.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
int idx = (int)g_files.size();
|
||||
g_files.push_back({path, text});
|
||||
Program p = parse(lex(idx), idx);
|
||||
p.module = module;
|
||||
p.path = path;
|
||||
std::vector<Import> mods;
|
||||
for (const Import &im : p.imports)
|
||||
if (!im.isC) mods.push_back(im);
|
||||
progs.push_back(std::move(p));
|
||||
for (const Import &im : mods) {
|
||||
if (im.path == "main") failAt(im.pos.file, im.pos.line, im.pos.col, "'main' can't be imported");
|
||||
if (loaded[im.path]) continue;
|
||||
loaded[im.path] = true;
|
||||
load(findModule(dirOf(path), im.path), im.path, im.pos);
|
||||
}
|
||||
}
|
||||
|
||||
// next to the importing file, then in $FINCH_PATH folders
|
||||
static std::string findModule(const std::string &dir, const std::string &name) {
|
||||
std::string here = dir + "/" + name + ".fn";
|
||||
if (sys::fs::exists(here)) return here;
|
||||
if (const char *fp = std::getenv("FINCH_PATH")) {
|
||||
std::stringstream ss(fp);
|
||||
for (std::string d; std::getline(ss, d, ':');)
|
||||
if (!d.empty() && sys::fs::exists(d + "/" + name + ".fn")) return d + "/" + name + ".fn";
|
||||
}
|
||||
return here;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------- LLVM ----------
|
||||
|
||||
static void optimize(Module &mod, TargetMachine *tm) {
|
||||
LoopAnalysisManager lam;
|
||||
FunctionAnalysisManager fam;
|
||||
|
|
@ -93,6 +147,8 @@ static void emitObject(Module &mod, TargetMachine *tm, const std::string &path)
|
|||
pm.run(mod);
|
||||
}
|
||||
|
||||
// ---------- linking ----------
|
||||
|
||||
// Run a shell command; returns its exit status, everything it printed goes to `output`.
|
||||
static int capture(const std::string &cmd, std::string &output) {
|
||||
FILE *p = popen((cmd + " 2>&1").c_str(), "r");
|
||||
|
|
@ -104,6 +160,38 @@ static int capture(const std::string &cmd, std::string &output) {
|
|||
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
}
|
||||
|
||||
static std::string ccName() {
|
||||
const char *cc = std::getenv("CC");
|
||||
return cc ? cc : "cc";
|
||||
}
|
||||
|
||||
// The runtime is compiled once per version and kept in ~/.cache/finch.
|
||||
static std::string runtimeObject() {
|
||||
std::string dir;
|
||||
if (const char *x = std::getenv("XDG_CACHE_HOME")) dir = std::string(x) + "/finch";
|
||||
else if (const char *h = std::getenv("HOME")) dir = std::string(h) + "/.cache/finch";
|
||||
else dir = "/tmp/finch-cache";
|
||||
size_t hash = std::hash<std::string>()(std::string(kRuntimeSource) + FINCH_VERSION + ccName());
|
||||
std::string obj = dir + "/rt-" + std::to_string(hash) + ".o";
|
||||
if (sys::fs::exists(obj)) return obj;
|
||||
|
||||
sys::fs::create_directories(dir);
|
||||
std::string src = obj + ".c", part = obj + ".part";
|
||||
{
|
||||
std::ofstream out(src);
|
||||
out << kRuntimeSource;
|
||||
}
|
||||
std::string output;
|
||||
int status = capture(ccName() + " -O2 -fPIC -c '" + src + "' -o '" + part + "'", output);
|
||||
std::remove(src.c_str());
|
||||
if (status != 0) {
|
||||
std::fprintf(stderr, "%serror: couldn't build the Finch runtime (is a C compiler installed?)\n", output.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
sys::fs::rename(part, obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// pkg-config knows the right paths for a library if it has one; otherwise plain -l<name>.
|
||||
static std::string libFlags(const std::string &lib) {
|
||||
std::string out;
|
||||
|
|
@ -151,7 +239,6 @@ static std::string guessLib(const std::string &header) {
|
|||
for (const std::string &lib : missingLibs)
|
||||
std::fprintf(stderr, "error: the library '%s' wasn't found. Is it installed? (on Nix: add it to shell.nix)\n", lib.c_str());
|
||||
|
||||
// group the missing functions by the header they came from
|
||||
std::vector<std::pair<std::string, std::vector<std::string>>> byHeader;
|
||||
for (const std::string &name : missing) {
|
||||
auto f = c.fns.find(name);
|
||||
|
|
@ -178,14 +265,34 @@ static std::string guessLib(const std::string &header) {
|
|||
std::exit(1);
|
||||
}
|
||||
|
||||
static bool endsWith(const std::string &s, const char *x) {
|
||||
size_t n = std::strlen(x);
|
||||
return s.size() > n && s.compare(s.size() - n, n, x) == 0;
|
||||
}
|
||||
|
||||
static void link(const std::string &obj, const std::string &exe, const std::vector<std::string> &libs,
|
||||
const CImports &c) {
|
||||
const char *cc = std::getenv("CC");
|
||||
std::string cmd = std::string(cc ? cc : "cc") + " '" + obj + "' -o '" + exe + "' -lm";
|
||||
for (const std::string &l : libs) cmd += libFlags(l);
|
||||
std::string cmd = ccName() + " '" + obj + "' '" + runtimeObject() + "' -o '" + exe + "' -lm";
|
||||
std::vector<std::string> temps;
|
||||
for (const std::string &l : libs) {
|
||||
if (endsWith(l, ".c")) { // compile the user's C file
|
||||
std::string o = obj + "." + std::to_string(temps.size()) + ".o", output;
|
||||
if (capture(ccName() + " -O2 -fPIC -c '" + l + "' -o '" + o + "'", output) != 0) {
|
||||
std::fprintf(stderr, "%serror: couldn't compile %s\n", output.c_str(), l.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
temps.push_back(o);
|
||||
cmd += " '" + o + "'";
|
||||
} else if (endsWith(l, ".o") || endsWith(l, ".a")) {
|
||||
cmd += " '" + l + "'";
|
||||
} else {
|
||||
cmd += libFlags(l);
|
||||
}
|
||||
}
|
||||
std::string output;
|
||||
int status = capture(cmd, output);
|
||||
std::remove(obj.c_str());
|
||||
for (const std::string &t : temps) std::remove(t.c_str());
|
||||
if (status == 127) {
|
||||
std::fprintf(stderr, "error: no C compiler found to link with (install gcc or clang, or set CC)\n");
|
||||
std::exit(1);
|
||||
|
|
@ -201,6 +308,12 @@ static std::string stem(const std::string &path) {
|
|||
return dot == std::string::npos ? name : name.substr(0, dot);
|
||||
}
|
||||
|
||||
static std::string shellQuote(const std::string &s) {
|
||||
std::string out = "'";
|
||||
for (char ch : s) out += ch == '\'' ? std::string("'\\''") : std::string(1, ch);
|
||||
return out + "'";
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc == 2 && (std::string(argv[1]) == "version" || std::string(argv[1]) == "--version")) {
|
||||
std::printf("finch %s (LLVM %s)\n", FINCH_VERSION, LLVM_VERSION_STRING);
|
||||
|
|
@ -208,26 +321,40 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
if (argc < 3) usage();
|
||||
std::string cmd = argv[1], file = argv[2];
|
||||
if (cmd != "run" && cmd != "build" && cmd != "ir") usage();
|
||||
std::string out = stem(file);
|
||||
std::vector<std::string> libs;
|
||||
bool optimizeCode = true;
|
||||
std::vector<std::string> libs, programArgs;
|
||||
bool optimizeCode = true, debug = false;
|
||||
for (int i = 3; i < argc; i++) {
|
||||
std::string a = argv[i];
|
||||
if (a == "-o" && i + 1 < argc) out = argv[++i];
|
||||
if (cmd == "run" && !programArgs.empty()) programArgs.push_back(a);
|
||||
else if (a == "-o" && i + 1 < argc && cmd == "build") out = argv[++i];
|
||||
else if (a == "-O0") optimizeCode = false;
|
||||
else if (a == "-g") debug = true;
|
||||
else if (a == "-l" && i + 1 < argc) libs.push_back(argv[++i]);
|
||||
else if (a.rfind("-l", 0) == 0 && a.size() > 2) libs.push_back(a.substr(2));
|
||||
else if (cmd == "run") programArgs.push_back(a);
|
||||
else usage();
|
||||
}
|
||||
if (cmd != "run" && cmd != "build" && cmd != "ir") usage();
|
||||
|
||||
Program prog = parse(lex(readFile(file), file));
|
||||
for (const Link &l : prog.links) libs.push_back(l.lib);
|
||||
size_t slash = file.find_last_of('/');
|
||||
CImports c = importHeaders(prog.imports, slash == std::string::npos ? "." : file.substr(0, slash));
|
||||
Loader loader;
|
||||
loader.load(file, "", Pos{});
|
||||
std::vector<Import> allImports;
|
||||
std::vector<std::string> dirs;
|
||||
for (const Program &p : loader.progs) {
|
||||
allImports.insert(allImports.end(), p.imports.begin(), p.imports.end());
|
||||
for (const Link &l : p.links) {
|
||||
bool file = endsWith(l.lib, ".c") || endsWith(l.lib, ".o") || endsWith(l.lib, ".a");
|
||||
std::string path = file && l.lib[0] != '/' ? dirOf(p.path) + "/" + l.lib : l.lib;
|
||||
if (file && !sys::fs::exists(path)) failAt(l.pos.file, l.pos.line, l.pos.col, "can't find the file '" + path + "'");
|
||||
libs.push_back(path);
|
||||
}
|
||||
if (std::find(dirs.begin(), dirs.end(), dirOf(p.path)) == dirs.end()) dirs.push_back(dirOf(p.path));
|
||||
}
|
||||
CImports c = importHeaders(allImports, dirs);
|
||||
LLVMContext ctx;
|
||||
TargetMachine *tm = hostMachine();
|
||||
std::unique_ptr<Module> mod = generate(prog, c, ctx, *tm);
|
||||
std::unique_ptr<Module> mod = generate(loader.progs, c, ctx, *tm, debug);
|
||||
if (optimizeCode) optimize(*mod, tm);
|
||||
|
||||
if (cmd == "ir") {
|
||||
|
|
@ -246,7 +373,9 @@ int main(int argc, char **argv) {
|
|||
link(obj, out, libs, c);
|
||||
|
||||
if (cmd == "run") {
|
||||
int status = std::system(("'" + out + "'").c_str());
|
||||
std::string line = shellQuote(out);
|
||||
for (const std::string &a : programArgs) line += " " + shellQuote(a);
|
||||
int status = std::system(line.c_str());
|
||||
std::remove(out.c_str());
|
||||
if (WIFSIGNALED(status)) {
|
||||
std::fprintf(stderr, "\nthe program crashed: %s\n", strsignal(WTERMSIG(status)));
|
||||
|
|
|
|||
Loading…
Reference in a new issue