enums and match (in the compiler written in Finch)

enum Color { Red, Green, Blue }   enum Status { Ok = 200, NotFound = 404 }

Color.Red, Color(n) (checked), int(c), == and != only, printed by name, usable as
map keys and struct fields, with methods (fn Color.next()).

match value { Red, Green -> ... Blue -> ... else -> ... } as a statement, or as an
expression that gives a value. Works on enums, whole numbers, chars, text and bools;
a match on an enum has to handle every variant or end with else, and says which are
missing. A bare variant name (Red) is enough inside a match on an enum.

Only the self-hosted compiler has them; tests/new holds their tests, tests/run stays what
the C++ compiler also runs. boot.sh: 31 passed.
This commit is contained in:
Nexoniarz 2026-10-25 20:49:04 +01:00
commit 609ceffe58
15 changed files with 576 additions and 18 deletions

View file

@ -173,6 +173,8 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
prog.links.push(expect(p, "string", "a library or C file name, like link \"glfw\"").text)
} else if at(p, "struct") {
prog.structs.push(parseStruct(p))
} else if at(p, "enum") {
prog.enums.push(parseEnum(p))
} else {
prog.fns.push(parseFn(p))
}
@ -180,6 +182,86 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
return prog
}
// enum Color { Red, Green, Blue } enum Status { Ok = 200, NotFound = 404 }
// The variants are numbered from 0 (or from the last number given) and are written Color.Red.
fn parseEnum(ptr[Parser] p) -> ast.Node {
e := node(p, "enum")
expect(p, "enum", "'enum'")
e.name = expect(p, "ident", "an enum name").text
expect(p, "{", "'{'")
while !at(p, "}") {
if at(p, "eof") {
fail(p, cur(p), "an enum is never closed (missing '}')")
}
v := node(p, "variant")
v.name = expect(p, "ident", "a variant name, like Red").text
if accept(p, "=") {
sign := ""
if accept(p, "-") {
sign = "-"
}
v.text = sign + expect(p, "int", "a whole number, like Ok = 200").text
}
e.kids.push(v)
if !accept(p, ",") {
endOfStatement(p)
}
}
next(p)
if e.kids.len == 0 {
fail(p, cur(p), "the enum " + e.name + " has no variants")
}
return e
}
// match value { A, B -> ... C -> ... else -> ... }
// As a statement the arms are statements or { blocks }; as an expression they are expressions.
fn parseMatch(ptr[Parser] p, bool asExpr) -> ast.Node {
m := node(p, "match")
m.flag = asExpr
expect(p, "match", "'match'")
m.kids.push(parseExpr(p))
expect(p, "{", "'{'")
saved := p.depth
p.depth = 0
while !at(p, "}") {
if at(p, "eof") {
fail(p, cur(p), "a match is never closed (missing '}')")
}
arm := node(p, "arm")
pats := node(p, "patterns")
if accept(p, "else") {
arm.flag = true
} else {
while true {
pats.kids.push(parseExpr(p))
if !accept(p, ",") {
break
}
}
}
arm.kids.push(pats)
expect(p, "->", "'->' after the value to match, like Red -> print(\"red\")")
if at(p, "{") && !asExpr {
arm.kids.push(parseBlock(p))
} else if asExpr {
arm.kids.push(parseExpr(p))
} else {
arm.kids.push(parseStatement(p))
}
m.kids.push(arm)
if !accept(p, ",") {
endOfStatement(p)
}
}
p.depth = saved
next(p)
if m.kids.len == 1 {
fail(p, cur(p), "a match needs at least one arm")
}
return m
}
fn parseStruct(ptr[Parser] p) -> ast.Node {
s := node(p, "struct")
expect(p, "struct", "'struct'")
@ -293,6 +375,9 @@ fn parseStatement(ptr[Parser] p) -> ast.Node {
s.kids.push(parseBlock(p))
return s
}
if k == "match" {
return parseMatch(p, false)
}
if k == "return" {
s := node(p, "return")
next(p)
@ -532,6 +617,9 @@ fn parsePrimary(ptr[Parser] p) -> ast.Node {
expect(p, ")", "')'")
return inner
}
if k == "match" {
return parseMatch(p, true)
}
if k == "[" {
next(p)
e.kind = "array"