Modules are known by their file: import button from ui/menu.fch and import ui/button are one module, two util.fch are two; min/max/abs/clamp win over C's int-only abs; completion after an import's other name; hover docs; engineers' guides

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
This commit is contained in:
Nexoniarz 2026-10-29 09:52:04 +01:00
commit 3ba2dcd5d6
16 changed files with 152 additions and 18 deletions

View file

@ -21,11 +21,15 @@ fn builtins() -> []Doc {
doc("delete_file", "delete_file(str path) -> bool", "Deletes the file. true if it worked; with or / try, the reason it didn't."),
doc("error", "error(str message)", "The failure a fallible function returns: return error(\"...\"). The caller handles it with or / try."),
doc("shell", "shell(str command) -> int", "Runs a command in the system's shell and gives back its exit code."),
doc("exit", "exit(int code)", "Ends the program right away with this exit code."),
doc("exit", "exit(int code)", "Ends the program right away with this exit code (it also ends an or { } block)."),
doc("min", "min(a, b, ...) -> number", "The smallest of the numbers, in the type they all fit."),
doc("max", "max(a, b, ...) -> number", "The biggest of the numbers, in the type they all fit."),
doc("abs", "abs(x) -> number", "x without its minus sign (any number type)."),
doc("clamp", "clamp(x, lo, hi) -> number", "x, but at least lo and at most hi."),
doc("addr", "addr(x) -> ptr[T]", "A pointer to a variable, field or element. addr(function) gives C a callback."),
doc("new", "new(value) -> ptr[T]", "Puts a copy of the value on the heap. Give it back with free(...)."),
doc("free", "free(ptr p)", "Gives back memory from new(...) (or from C), freeing what the value owns."),
doc("str", "str(x) -> str", "Turns a number, bool, char, []u8 or C char pointer into text."),
doc("str", "str(x) -> str", "Turns a number, bool, char, []u8 or C char pointer into text. str(x, 2): a number with 2 digits after the point."),
doc("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)."),
doc("float", "float(x) -> float", "Converts to a decimal number; parses text (int(s) or 0.0 handles bad text)."),
doc("char", "char(int n) -> char", "The character with this code."),
@ -86,7 +90,7 @@ fn mapMethods() -> []Doc {
fn keywords() -> []str {
return ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false", "null", "import",
"link", "struct", "enum", "match", "export", "defer", "or", "try", "self"]
"link", "struct", "enum", "match", "export", "defer", "or", "try", "self", "const"]
}
fn typeNames() -> []str {

View file

@ -279,8 +279,8 @@ fn noteCall(ptr[Gen] g, ast.Node c, FnSig sig) {
// what is known about every function, struct and enum, for completion and the outline
fn indexAll(ptr[Gen] g, []str mainImports) {
for im in mainImports {
g.idx.push("@I\t" + im)
for i in 0..mainImports.len {
g.idx.push("@I\t" + mainImports[i] + "\t" + g.progs[0].modules[i]) // the name used here, the module
}
for sg in g.sigs {
f := g.progs[sg.prog].fns[sg.index]
@ -5319,8 +5319,8 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
if ei >= 0 {
return enumFrom(g, c, ei)
}
if g.ci.fns.has(n) {
return callC(g, c, g.ci.fns[n])
if g.ci.fns.has(n) && !["min", "max", "abs", "clamp"].contains(n) {
return callC(g, c, g.ci.fns[n]) // (C's abs only takes an int; Finch's takes any number)
}
si = findStruct(g, "C", n)
if si >= 0 {
@ -5339,6 +5339,9 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
// min(a, b, ...), max(a, b, ...), abs(x), clamp(x, lo, hi) for any numbers
fn mathCall(ptr[Gen] g, ast.Node c) -> Val {
n := c.name
if g.index {
noteRef(g, c, c.col, n.len, docs.hover(docs.find(docs.builtins(), n)), "", 0, 0)
}
for nm in c.names {
if nm.len > 0 {
fail(g, c, "named arguments (" + nm + ": ...) are only for creating structs")

View file

@ -80,6 +80,7 @@ struct Analysis {
[]StructRec structs
[]EnumRec enums
[]str imports
[]str importKeys // the module each import is: btn -> ui/button
Diag diag
}
@ -315,6 +316,11 @@ fn parseOutput(str output) -> Analysis {
a.enums.push(EnumRec(module: f[1], name: f[2], file: f[3], line: intOf(f[4]), col: intOf(f[5]), variants: f[6].split(",")))
} else if tag == "@I" && f.len >= 2 {
a.imports.push(f[1])
if f.len >= 3 {
a.importKeys.push(f[2])
} else {
a.importKeys.push(f[1])
}
}
} else if !a.diag.has {
a.diag = parseDiag(line)
@ -348,6 +354,7 @@ fn Server.analyse(str path) {
a.vars = g.vars
if a.imports.len == 0 {
a.imports = g.imports
a.importKeys = g.importKeys
}
}
self.results[path] = a
@ -537,6 +544,7 @@ fn completionAt(Analysis a, str text, str path, int line, int col) -> json.Json
}
// a module: its functions, structs and enums
if a.imports.contains(owner) {
owner = a.importKeys[a.imports.find(owner)]
for f in a.fns {
if f.module == owner && f.recv.len == 0 {
items.push(item(f.name, 3, f.signature, ""))

View file

@ -47,6 +47,7 @@ struct Loader {
str os // "windows", "macos" or "linux": a module may have a version for each (time_windows.fch)
str std // the standard library's folder, or ""
[]str paths // FINCH_PATH: more folders to look for modules in
str root // the main file's folder: modules are named by their path from here (ui/button)
[]str overlayPaths // --overlay path=file: read `path` from `file` (the text an editor has, not saved yet)
[]str overlayFiles
}
@ -89,6 +90,27 @@ fn findModule(ptr[Loader] l, str from, str name) -> str {
return local
}
// A module is named by its file, so `import button` in ui/menu.fch and `import ui/button` in main.fch
// are the same module, and two different util.fch files are two modules.
fn moduleKey(ptr[Loader] l, str file, str name) -> str {
[]str roots
roots.push(l.root)
for d in l.paths {
roots.push(d)
}
roots.push(l.std)
for r in roots {
if r.len > 0 && file.starts_with(r + "/") && file.ends_with(".fch") {
key := file.sub(r.len + 1, file.len - 4)
if key.ends_with("_" + l.os) {
key = key.sub(0, key.len - l.os.len - 1) // time_windows.fch is the module time
}
return key
}
}
return name
}
fn load(ptr[Loader] l, str path, str module) {
if !file_exists(path) && !l.overlayPaths.contains(path) {
if module.len > 0 {
@ -104,12 +126,21 @@ fn load(ptr[Loader] l, str path, str module) {
prog := parser.parse(lexer.lex(src, path), file, path)
prog.module = module
prog.path = path
if module.len == 0 {
l.root = dirOf(path)
}
[]str files
for i in 0..prog.modules.len {
f := findModule(l, path, prog.modules[i])
files.push(f)
prog.modules[i] = moduleKey(l, f, prog.modules[i])
}
modules := prog.modules
l.progs.push(prog)
for name in modules {
if !l.loaded.contains(name) {
l.loaded.push(name)
load(l, findModule(l, path, name), name)
for i in 0..modules.len {
if !l.loaded.contains(modules[i]) {
l.loaded.push(modules[i])
load(l, files[i], modules[i])
}
}
}