Finch/boot/ast.fch
Nexoniarz 04eb0cce55 generics (in the compiler written in Finch)
fn max[T](T a, T b) -> T      struct Stack[T] { []T items }      fn Stack[T].push(T x)

The type is worked out from the arguments (max(3, 4), first([1, 2])) and written out
where it cannot be: max[float](1, 2.5), Stack[int](), Pair[str, int](...). Each use makes
its own copy, so a generic costs nothing at run time. Several parameters
(Pair[A, B]), nesting (Stack[Pair[int, str]]), methods, and printing (Stack[int](items: ...))
work. An error inside a generic says which version it was building.

boot.sh: 37 passed, 0 failed (stage 2 and 3 identical).
2026-10-25 20:53:13 +01:00

122 lines
3.4 KiB
Text

// The syntax tree of the self-hosted compiler.
// A type as written or resolved.
// kind: "void" "int" "bool" "char" "float" "str" "null" "ptr" "array" "named" "struct"
// elem: the element of an array or the target of a ptr (zero or one entry)
// name, module: structs ("" module = the main file)
struct Type {
str kind
[]Type elem
str name
str module
int count // "fixed": [N]T, only inside C structs
}
fn simple(str kind) -> Type {
return Type(kind: kind)
}
fn arrayOf(Type t) -> Type {
out := Type(kind: "array")
out.elem.push(t)
return out
}
// map[K]V: elem holds the key, then the value
fn mapOf(Type k, Type v) -> Type {
out := Type(kind: "map")
out.elem.push(k)
out.elem.push(v)
return out
}
fn ptrTo(Type t) -> Type {
out := Type(kind: "ptr")
out.elem.push(t)
return out
}
fn same(Type a, Type b) -> bool {
if a.kind != b.kind {
return false
}
if a.kind == "struct" || a.kind == "named" || a.kind == "enum" {
return a.name == b.name && a.module == b.module
}
if a.kind == "fixed" && a.count != b.count {
return false
}
if a.kind == "array" || a.kind == "ptr" || a.kind == "map" || a.kind == "fixed" {
if a.elem.len != b.elem.len {
return false
}
for i in 0..a.elem.len {
if !same(a.elem[i], b.elem[i]) {
return false
}
}
return true
}
return true
}
fn show(Type t) -> str {
if t.kind == "array" {
return "[]" + show(t.elem[0])
}
if t.kind == "fixed" {
return "[" + str(t.count) + "]" + show(t.elem[0])
}
if t.kind == "map" {
return "map[" + show(t.elem[0]) + "]" + show(t.elem[1])
}
if t.kind == "ptr" {
if t.elem.len == 0 {
return "ptr"
}
return "ptr[" + show(t.elem[0]) + "]"
}
if t.kind == "struct" || t.kind == "named" || t.kind == "enum" {
if t.module.len > 0 {
return t.module + "." + t.name
}
return t.name
}
if t.kind == "void" {
return "nothing"
}
return t.kind
}
// One node of the tree, for expressions, statements, functions and structs alike.
// expressions: int float str char bool null var unary binary call member index array method
// statements: decl assign expr if while for each return break continue block
// top level: fn param struct field enum variant
// match: match (kids: subject, arms...), arm (kids: patterns node, body; flag: the else arm), patterns
struct Node {
str kind
str name // identifier, operator, callee, field or method name
str recv // fn Point.move(...): "Point"; empty for a plain function
[]str tparams // fn max[T](...), struct Stack[T], fn Stack[T].push: the type parameters
str text // literal text
Type ty // declared type (decl, param, field, fn result)
bool flag // decl: has a type; or: the right side is a { block }
bool fallible // fn: -> T! or -> !
[]Node kids
[]str names // named arguments: Point(x: 1)
int line
int col
int file
}
struct Program {
str module
str path
[]str imports // Finch modules
[]str cimports // C headers: import "stdio.h"
[]str cwhere // where each was written, for error messages
[]str links // link "glfw", link "mine.c"
[]Node structs
[]Node enums
[]Node fns
}