mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
Enums whose variants hold values
enum Shape { Circle(float), Rect(float, float), Point }; Shape.Circle(2.0) makes one, match names the values
(Circle(r), _ to ignore), exhaustive like plain enums, printing shows Rect(2, 3.5). Underneath it is a struct (tag +
every variant's values), so copying, freeing, methods and valgrind-cleanliness come with it. Also: stray note files removed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015f73FesGZGYPkmDvUcj6QX
This commit is contained in:
parent
dc0334fa0e
commit
3e45b62812
13 changed files with 75170 additions and 66737 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -428,4 +428,4 @@ test (22 checks).
|
||||||
- The driver uses `sh` utilities (`rm`, `mkdir`), so it needs a POSIX shell.
|
- The driver uses `sh` utilities (`rm`, `mkdir`), so it needs a POSIX shell.
|
||||||
- Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error
|
- Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error
|
||||||
inside the instantiated body.
|
inside the instantiated body.
|
||||||
- No payload enums yet (variants carry no data).
|
|
||||||
|
|
|
||||||
|
|
@ -924,6 +924,29 @@ tests/cross.sh windows # every test built for Windows and run with Wine
|
||||||
tests/cross.sh arm64 # every test built for ARM64 Linux and run with QEMU
|
tests/cross.sh arm64 # every test built for ARM64 Linux and run with QEMU
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Variants that hold values
|
||||||
|
|
||||||
|
A variant can carry values. Make one with `Shape.Circle(2.0)`, take them out with `match`, which names them:
|
||||||
|
|
||||||
|
```c
|
||||||
|
enum Shape {
|
||||||
|
Circle(float)
|
||||||
|
Rect(float, float)
|
||||||
|
Point
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Shape.area() -> float {
|
||||||
|
return match self {
|
||||||
|
Circle(r) -> 3.14 * r * r
|
||||||
|
Rect(w, h) -> w * h
|
||||||
|
Point -> 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`_` ignores a value. `match` must handle every variant (or end with `else`). Printing shows `Rect(2, 3.5)`. Such an enum is
|
||||||
|
copied and freed like a struct; it can't be compared with `==` (match on it). Variants of these enums can't have numbers.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 23. Enums and match
|
## 23. Enums and match
|
||||||
|
|
@ -1027,7 +1050,7 @@ write_file(path.join("out", "config.json"), json.pretty(doc))
|
||||||
|
|
||||||
Not there yet:
|
Not there yet:
|
||||||
|
|
||||||
- Interfaces, and enums whose variants carry values (`Circle(radius)`).
|
- Interfaces.
|
||||||
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
|
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
|
||||||
- Threads.
|
- Threads.
|
||||||
- Windows on ARM64, 32-bit systems, WebAssembly.
|
- Windows on ARM64, 32-bit systems, WebAssembly.
|
||||||
|
|
|
||||||
|
|
@ -421,4 +421,4 @@ serwer podpowiada dalej z ostatniej wersji, która się skompilowała. Test akce
|
||||||
- Sterownik używa narzędzi `sh` (`rm`, `mkdir`), więc wymaga powłoki POSIX.
|
- Sterownik używa narzędzi `sh` (`rm`, `mkdir`), więc wymaga powłoki POSIX.
|
||||||
- Generyki są monomorfizowane (kod rośnie z liczbą instancji); nie ma ograniczeń na typy, błędne użycie to błąd wewnątrz
|
- Generyki są monomorfizowane (kod rośnie z liczbą instancji); nie ma ograniczeń na typy, błędne użycie to błąd wewnątrz
|
||||||
wygenerowanego ciała.
|
wygenerowanego ciała.
|
||||||
- Jeszcze nie ma enumów z danymi (warianty nie niosą wartości).
|
|
||||||
|
|
|
||||||
|
|
@ -928,6 +928,29 @@ tests/cross.sh windows # każdy test zbudowany na Windowsa i uruchomiony w
|
||||||
tests/cross.sh arm64 # każdy test zbudowany na Linuksa ARM64 i uruchomiony w QEMU
|
tests/cross.sh arm64 # każdy test zbudowany na Linuksa ARM64 i uruchomiony w QEMU
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Warianty z wartościami
|
||||||
|
|
||||||
|
Wariant może nieść wartości. Tworzysz go przez `Shape.Circle(2.0)`, a wyciągasz `match`-em, który je nazywa:
|
||||||
|
|
||||||
|
```c
|
||||||
|
enum Shape {
|
||||||
|
Circle(float)
|
||||||
|
Rect(float, float)
|
||||||
|
Point
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Shape.area() -> float {
|
||||||
|
return match self {
|
||||||
|
Circle(r) -> 3.14 * r * r
|
||||||
|
Rect(w, h) -> w * h
|
||||||
|
Point -> 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`_` pomija wartość. `match` musi obsłużyć każdy wariant (albo kończyć się `else`). Wypisanie pokazuje `Rect(2, 3.5)`. Taki enum
|
||||||
|
jest kopiowany i zwalniany jak struktura; nie da się go porównać przez `==` (użyj `match`). Warianty takich enumów nie mają numerów.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 23. Enumy i match
|
## 23. Enumy i match
|
||||||
|
|
@ -1031,7 +1054,7 @@ write_file(path.join("out", "config.json"), json.pretty(dok))
|
||||||
|
|
||||||
Jeszcze nie ma:
|
Jeszcze nie ma:
|
||||||
|
|
||||||
- Interfejsów i enumów, których warianty niosą wartości (`Kolo(promien)`).
|
- Interfejsów.
|
||||||
- Tekstu świadomego Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII.
|
- Tekstu świadomego Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII.
|
||||||
- Wątków.
|
- Wątków.
|
||||||
- Windowsa na ARM64, systemów 32-bitowych, WebAssembly.
|
- Windowsa na ARM64, systemów 32-bitowych, WebAssembly.
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Kupić mleko
|
|
||||||
1
note.txt
1
note.txt
|
|
@ -1 +0,0 @@
|
||||||
Buy milk
|
|
||||||
251
src/gen.fch
251
src/gen.fch
|
|
@ -63,6 +63,22 @@ struct Cleanup {
|
||||||
int idx
|
int idx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An enum whose variants hold values (see sumStruct in parser.fch): it is a struct `tag, V_0, ...`.
|
||||||
|
// firsts[k] is the number of the struct field holding variant k's first value.
|
||||||
|
struct SumDef {
|
||||||
|
str module
|
||||||
|
str name
|
||||||
|
[]str variants
|
||||||
|
[]int counts
|
||||||
|
[]int firsts
|
||||||
|
[]ast.Node nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SumPat {
|
||||||
|
int k
|
||||||
|
[]ast.Node binds
|
||||||
|
}
|
||||||
|
|
||||||
struct Scope {
|
struct Scope {
|
||||||
[]Var vars
|
[]Var vars
|
||||||
[]ast.Node defers
|
[]ast.Node defers
|
||||||
|
|
@ -167,6 +183,7 @@ struct Gen {
|
||||||
[]ast.Type targs
|
[]ast.Type targs
|
||||||
str instNote // " (while building max[Pixel])", added to errors found inside a generic
|
str instNote // " (while building max[Pixel])", added to errors found inside a generic
|
||||||
[]EnumDef enums
|
[]EnumDef enums
|
||||||
|
[]SumDef sums
|
||||||
[]str done
|
[]str done
|
||||||
[]str mm // names of methods that change self, for the mutation analysis
|
[]str mm // names of methods that change self, for the mutation analysis
|
||||||
int nvars // variables declared so far in this function
|
int nvars // variables declared so far in this function
|
||||||
|
|
@ -483,6 +500,15 @@ fn findEnum(ptr[Gen] g, str module, str name) -> int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn findSum(ptr[Gen] g, str module, str name) -> int {
|
||||||
|
for i in 0..g.sums.len {
|
||||||
|
if g.sums[i].module == module && g.sums[i].name == name {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
fn progOf(ptr[Gen] g, str module) -> int {
|
fn progOf(ptr[Gen] g, str module) -> int {
|
||||||
for i in 0..g.progs.len {
|
for i in 0..g.progs.len {
|
||||||
if g.progs[i].module == module {
|
if g.progs[i].module == module {
|
||||||
|
|
@ -1172,6 +1198,43 @@ fn formatFn(ptr[Gen] g, ast.Type t) -> str {
|
||||||
label(g, next)
|
label(g, next)
|
||||||
loopEnd(g, l)
|
loopEnd(g, l)
|
||||||
bufText(g, "%buf", "}")
|
bufText(g, "%buf", "}")
|
||||||
|
} else if t.kind == "struct" && findSum(g, t.module, t.name) >= 0 {
|
||||||
|
// Circle(2.5), Point
|
||||||
|
sm := g.sums[findSum(g, t.module, t.name)]
|
||||||
|
sd := g.structs[findStruct(g, t.module, t.name)]
|
||||||
|
st := llty(g, t)
|
||||||
|
tag := load(g, "i64", op(g, "getelementptr inbounds " + st + ", ptr %a, i32 0, i32 " + str(sd.lidx[0])))
|
||||||
|
def := newLabel(g, "sum.other")
|
||||||
|
done := newLabel(g, "sum.done")
|
||||||
|
[]str cases
|
||||||
|
[]str labels
|
||||||
|
for k in 0..sm.variants.len {
|
||||||
|
labels.push(newLabel(g, "sum.v"))
|
||||||
|
cases.push("i64 " + str(k) + ", label %" + labels[k])
|
||||||
|
}
|
||||||
|
emit(g, "switch i64 " + tag + ", label %" + def + " [ " + cases.join(" ") + " ]")
|
||||||
|
g.term = true
|
||||||
|
for k in 0..sm.variants.len {
|
||||||
|
label(g, labels[k])
|
||||||
|
bufText(g, "%buf", sm.variants[k])
|
||||||
|
if sm.counts[k] > 0 {
|
||||||
|
bufText(g, "%buf", "(")
|
||||||
|
for j in 0..sm.counts[k] {
|
||||||
|
if j > 0 {
|
||||||
|
bufText(g, "%buf", ", ")
|
||||||
|
}
|
||||||
|
fi := sm.firsts[k] + j
|
||||||
|
fp := op(g, "getelementptr inbounds " + st + ", ptr %a, i32 0, i32 " + str(sd.lidx[fi]))
|
||||||
|
emitFormat(g, "%buf", load(g, llty(g, sd.ftypes[fi]), fp), sd.ftypes[fi], true)
|
||||||
|
}
|
||||||
|
bufText(g, "%buf", ")")
|
||||||
|
}
|
||||||
|
br(g, done)
|
||||||
|
}
|
||||||
|
label(g, def)
|
||||||
|
bufText(g, "%buf", sm.name + "(?)")
|
||||||
|
br(g, done)
|
||||||
|
label(g, done)
|
||||||
} else {
|
} else {
|
||||||
sd := g.structs[findStruct(g, t.module, t.name)]
|
sd := g.structs[findStruct(g, t.module, t.name)]
|
||||||
st := llty(g, t)
|
st := llty(g, t)
|
||||||
|
|
@ -1460,6 +1523,24 @@ fn declare(ptr[Gen] g) {
|
||||||
g.module = g.progs[pi].module
|
g.module = g.progs[pi].module
|
||||||
for ei in 0..g.progs[pi].enums.len {
|
for ei in 0..g.progs[pi].enums.len {
|
||||||
e := g.progs[pi].enums[ei]
|
e := g.progs[pi].enums[ei]
|
||||||
|
if e.flag {
|
||||||
|
if findEnum(g, g.module, e.name) >= 0 || findSum(g, g.module, e.name) >= 0 {
|
||||||
|
fail(g, e, "'" + e.name + "' is defined twice")
|
||||||
|
}
|
||||||
|
sd := SumDef(module: g.module, name: e.name)
|
||||||
|
first := 1
|
||||||
|
for v in e.kids {
|
||||||
|
if sd.variants.contains(v.name) {
|
||||||
|
fail(g, v, "the enum " + e.name + " has the variant " + v.name + " twice")
|
||||||
|
}
|
||||||
|
sd.variants.push(v.name)
|
||||||
|
sd.counts.push(v.kids.len)
|
||||||
|
sd.firsts.push(first)
|
||||||
|
first += v.kids.len
|
||||||
|
}
|
||||||
|
g.sums.push(sd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if findEnum(g, g.module, e.name) >= 0 || findStruct(g, g.module, e.name) >= 0 {
|
if findEnum(g, g.module, e.name) >= 0 || findStruct(g, g.module, e.name) >= 0 {
|
||||||
fail(g, e, "'" + e.name + "' is defined twice")
|
fail(g, e, "'" + e.name + "' is defined twice")
|
||||||
}
|
}
|
||||||
|
|
@ -2965,6 +3046,14 @@ fn matchGen(ptr[Gen] g, ast.Node e, bool asValue) -> Val {
|
||||||
}
|
}
|
||||||
mark := g.pending.len
|
mark := g.pending.len
|
||||||
hold(g, subj) // a temporary subject lives through the arms
|
hold(g, subj) // a temporary subject lives through the arms
|
||||||
|
si := -1
|
||||||
|
tagv := ""
|
||||||
|
if st.kind == "struct" {
|
||||||
|
si = findSum(g, st.module, st.name)
|
||||||
|
if si >= 0 {
|
||||||
|
tagv = op(g, "extractvalue " + llty(g, st) + " " + subj.v + ", " + str(g.structs[findStruct(g, st.module, st.name)].lidx[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
end := newLabel(g, "match.end")
|
end := newLabel(g, "match.end")
|
||||||
hasElse := false
|
hasElse := false
|
||||||
reached := false // does any arm go on to the end?
|
reached := false // does any arm go on to the end?
|
||||||
|
|
@ -2980,28 +3069,61 @@ fn matchGen(ptr[Gen] g, ast.Node e, bool asValue) -> Val {
|
||||||
}
|
}
|
||||||
body := newLabel(g, "match.arm")
|
body := newLabel(g, "match.arm")
|
||||||
next := newLabel(g, "match.next")
|
next := newLabel(g, "match.next")
|
||||||
|
[]ast.Node binds
|
||||||
|
bindK := -1
|
||||||
if arm.flag {
|
if arm.flag {
|
||||||
hasElse = true
|
hasElse = true
|
||||||
br(g, body)
|
br(g, body)
|
||||||
} else {
|
} else {
|
||||||
pats := arm.kids[0].kids
|
pats := arm.kids[0].kids
|
||||||
for pi in 0..pats.len {
|
for pi in 0..pats.len {
|
||||||
pv := patternValue(g, pats[pi], st)
|
hitv := ""
|
||||||
if covered.contains(pv.v) && st.kind == "enum" {
|
if si >= 0 {
|
||||||
fail(g, pats[pi], "this variant is already handled by an earlier arm")
|
sp := sumPattern(g, pats[pi], si)
|
||||||
|
if covered.contains(str(sp.k)) {
|
||||||
|
fail(g, pats[pi], "this variant is already handled by an earlier arm")
|
||||||
|
}
|
||||||
|
covered.push(str(sp.k))
|
||||||
|
if sp.binds.len > 0 {
|
||||||
|
if pats.len > 1 {
|
||||||
|
fail(g, pats[pi], "an arm with several variants can't name their values")
|
||||||
|
}
|
||||||
|
binds = sp.binds
|
||||||
|
bindK = sp.k
|
||||||
|
}
|
||||||
|
hitv = op(g, "icmp eq i64 " + tagv + ", " + str(sp.k))
|
||||||
|
} else {
|
||||||
|
pv := patternValue(g, pats[pi], st)
|
||||||
|
if covered.contains(pv.v) && st.kind == "enum" {
|
||||||
|
fail(g, pats[pi], "this variant is already handled by an earlier arm")
|
||||||
|
}
|
||||||
|
covered.push(pv.v)
|
||||||
|
hitv = compare(g, "==", subj, pv, pats[pi]).v
|
||||||
}
|
}
|
||||||
covered.push(pv.v)
|
|
||||||
hit := compare(g, "==", subj, pv, pats[pi])
|
|
||||||
if pi == pats.len - 1 {
|
if pi == pats.len - 1 {
|
||||||
condBr(g, hit.v, body, next)
|
condBr(g, hitv, body, next)
|
||||||
} else {
|
} else {
|
||||||
more := newLabel(g, "match.or")
|
more := newLabel(g, "match.or")
|
||||||
condBr(g, hit.v, body, more)
|
condBr(g, hitv, body, more)
|
||||||
label(g, more)
|
label(g, more)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
label(g, body)
|
label(g, body)
|
||||||
|
scoped := binds.len > 0
|
||||||
|
if scoped {
|
||||||
|
pushScope(g)
|
||||||
|
sx := g.structs[findStruct(g, st.module, st.name)]
|
||||||
|
for j in 0..binds.len {
|
||||||
|
if binds[j].name == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fi := g.sums[si].firsts[bindK] + j
|
||||||
|
ft := sx.ftypes[fi]
|
||||||
|
fv := op(g, "extractvalue " + llty(g, st) + " " + subj.v + ", " + str(sx.lidx[fi]))
|
||||||
|
addVar(g, binds[j], binds[j].name, ft, copyValue(g, fv, ft), true, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
node := arm.kids[1]
|
node := arm.kids[1]
|
||||||
if asValue {
|
if asValue {
|
||||||
v := expr(g, node)
|
v := expr(g, node)
|
||||||
|
|
@ -3013,6 +3135,9 @@ fn matchGen(ptr[Gen] g, ast.Node e, bool asValue) -> Val {
|
||||||
}
|
}
|
||||||
v = coerce(g, v, resultTy, node, "this arm (the arms of a match have to give the same type)")
|
v = coerce(g, v, resultTy, node, "this arm (the arms of a match have to give the same type)")
|
||||||
out := own(g, v)
|
out := own(g, v)
|
||||||
|
if scoped {
|
||||||
|
popScope(g)
|
||||||
|
}
|
||||||
from := newLabel(g, "match.from")
|
from := newLabel(g, "match.from")
|
||||||
br(g, from)
|
br(g, from)
|
||||||
label(g, from)
|
label(g, from)
|
||||||
|
|
@ -3029,6 +3154,9 @@ fn matchGen(ptr[Gen] g, ast.Node e, bool asValue) -> Val {
|
||||||
if !g.term {
|
if !g.term {
|
||||||
reached = true
|
reached = true
|
||||||
}
|
}
|
||||||
|
if scoped {
|
||||||
|
popScope(g)
|
||||||
|
}
|
||||||
br(g, end)
|
br(g, end)
|
||||||
}
|
}
|
||||||
if !arm.flag {
|
if !arm.flag {
|
||||||
|
|
@ -3036,7 +3164,17 @@ fn matchGen(ptr[Gen] g, ast.Node e, bool asValue) -> Val {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasElse {
|
if !hasElse {
|
||||||
if st.kind == "enum" {
|
if si >= 0 {
|
||||||
|
[]str missing
|
||||||
|
for k in 0..g.sums[si].variants.len {
|
||||||
|
if !covered.contains(str(k)) {
|
||||||
|
missing.push(g.sums[si].variants[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if missing.len > 0 {
|
||||||
|
fail(g, e, "this match doesn't handle " + missing.join(", ") + " (add them, or an 'else' arm)")
|
||||||
|
}
|
||||||
|
} else if st.kind == "enum" {
|
||||||
ed := g.enums[findEnum(g, st.module, st.name)]
|
ed := g.enums[findEnum(g, st.module, st.name)]
|
||||||
[]str missing
|
[]str missing
|
||||||
for k in 0..ed.variants.len {
|
for k in 0..ed.variants.len {
|
||||||
|
|
@ -3651,6 +3789,83 @@ fn enumNamed(ptr[Gen] g, ast.Node n) -> int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Which payload enum does `n` name? Shape, or shapes.Shape. -1 if neither.
|
||||||
|
fn sumNamed(ptr[Gen] g, ast.Node n) -> int {
|
||||||
|
if n.kind == "var" && lookup(g, n.name) < 0 {
|
||||||
|
return findSum(g, g.module, n.name)
|
||||||
|
}
|
||||||
|
if n.kind == "member" && n.kids[0].kind == "var" && lookup(g, n.kids[0].name) < 0 && imports(g, n.kids[0].name) {
|
||||||
|
return findSum(g, n.kids[0].name, n.name)
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sumVariant(ptr[Gen] g, int si, str name, ast.Node at) -> int {
|
||||||
|
sd := g.sums[si]
|
||||||
|
k := sd.variants.find(name)
|
||||||
|
if k < 0 {
|
||||||
|
fail(g, at, "the enum " + sd.name + " has no variant '" + name + "' (it has: " + sd.variants.join(", ") + ")")
|
||||||
|
}
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shape.Circle(2.0), or Shape.Point
|
||||||
|
fn sumMake(ptr[Gen] g, int si, str vname, []ast.Node args, ast.Node at) -> Val {
|
||||||
|
sd := g.sums[si]
|
||||||
|
k := sumVariant(g, si, vname, at)
|
||||||
|
if args.len != sd.counts[k] {
|
||||||
|
if sd.counts[k] == 0 {
|
||||||
|
fail(g, at, vname + " holds nothing: write " + sd.name + "." + vname)
|
||||||
|
}
|
||||||
|
fail(g, at, vname + " holds " + str(sd.counts[k]) + " value(s), but got " + str(args.len) + ": " + sd.name + "." + vname + "(...)")
|
||||||
|
}
|
||||||
|
[]ast.Node all
|
||||||
|
[]str names
|
||||||
|
all.push(ast.Node(kind: "int", text: str(k), line: at.line, col: at.col, file: at.file))
|
||||||
|
names.push("tag")
|
||||||
|
for j in 0..args.len {
|
||||||
|
all.push(args[j])
|
||||||
|
names.push(vname + "_" + str(j))
|
||||||
|
}
|
||||||
|
noteRef(g, at, at.col, vname.len, "```finch\n" + sd.name + "." + vname + "\n```", "", 0, 0)
|
||||||
|
return construct(g, findStruct(g, sd.module, sd.name), all, names, at)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the variant a match pattern names, and the names it binds: Circle(r) Shape.Rect(w, h) Point
|
||||||
|
fn sumPattern(ptr[Gen] g, ast.Node pat, int si) -> SumPat {
|
||||||
|
sd := g.sums[si]
|
||||||
|
name := ""
|
||||||
|
[]ast.Node binds
|
||||||
|
if pat.kind == "var" && lookup(g, pat.name) < 0 {
|
||||||
|
name = pat.name
|
||||||
|
} else if pat.kind == "call" {
|
||||||
|
name = pat.name
|
||||||
|
binds = pat.kids
|
||||||
|
} else if pat.kind == "member" && sumNamed(g, pat.kids[0]) == si {
|
||||||
|
name = pat.name
|
||||||
|
} else if pat.kind == "method" && sumNamed(g, pat.kids[0]) == si {
|
||||||
|
name = pat.name
|
||||||
|
for q in 1..pat.kids.len {
|
||||||
|
binds.push(pat.kids[q])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail(g, pat, "this pattern is not a variant of " + sd.name + " (it has: " + sd.variants.join(", ") + ")")
|
||||||
|
}
|
||||||
|
k := sumVariant(g, si, name, pat)
|
||||||
|
if binds.len != sd.counts[k] {
|
||||||
|
if sd.counts[k] == 0 {
|
||||||
|
fail(g, pat, name + " holds nothing: write just " + name)
|
||||||
|
}
|
||||||
|
fail(g, pat, name + " holds " + str(sd.counts[k]) + " value(s): name them, like " + name + "(x), or use _ to ignore one")
|
||||||
|
}
|
||||||
|
for b in binds {
|
||||||
|
if b.kind != "var" {
|
||||||
|
fail(g, b, "write a name to hold the value (or _ to ignore it)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SumPat(k: k, binds: binds)
|
||||||
|
}
|
||||||
|
|
||||||
// Color.Red
|
// Color.Red
|
||||||
fn enumValue(ptr[Gen] g, ast.Node m, int ei) -> Val {
|
fn enumValue(ptr[Gen] g, ast.Node m, int ei) -> Val {
|
||||||
ed := g.enums[ei]
|
ed := g.enums[ei]
|
||||||
|
|
@ -3663,6 +3878,18 @@ fn enumValue(ptr[Gen] g, ast.Node m, int ei) -> Val {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref {
|
fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref {
|
||||||
|
sm := sumNamed(g, m.kids[0])
|
||||||
|
if sm >= 0 {
|
||||||
|
if write {
|
||||||
|
fail(g, m, "a variant of an enum can't be changed")
|
||||||
|
}
|
||||||
|
k := sumVariant(g, sm, m.name, m)
|
||||||
|
if g.sums[sm].counts[k] > 0 {
|
||||||
|
fail(g, m, m.name + " holds " + str(g.sums[sm].counts[k]) + " value(s): " + g.sums[sm].name + "." + m.name + "(...)")
|
||||||
|
}
|
||||||
|
[]ast.Node none
|
||||||
|
return Ref(isPlace: false, val: sumMake(g, sm, m.name, none, m))
|
||||||
|
}
|
||||||
ei := enumNamed(g, m.kids[0])
|
ei := enumNamed(g, m.kids[0])
|
||||||
if ei >= 0 {
|
if ei >= 0 {
|
||||||
if write {
|
if write {
|
||||||
|
|
@ -4999,6 +5226,14 @@ fn noteMethod(ptr[Gen] g, ast.Node m, []docs.Doc list) {
|
||||||
|
|
||||||
fn method(ptr[Gen] g, ast.Node m) -> Val {
|
fn method(ptr[Gen] g, ast.Node m) -> Val {
|
||||||
objNode := m.kids[0]
|
objNode := m.kids[0]
|
||||||
|
sm := sumNamed(g, objNode)
|
||||||
|
if sm >= 0 {
|
||||||
|
[]ast.Node vals
|
||||||
|
for q in 1..m.kids.len {
|
||||||
|
vals.push(m.kids[q])
|
||||||
|
}
|
||||||
|
return sumMake(g, sm, m.name, vals, m)
|
||||||
|
}
|
||||||
if objNode.kind == "var" && lookup(g, objNode.name) < 0 {
|
if objNode.kind == "var" && lookup(g, objNode.name) < 0 {
|
||||||
mn := objNode.name
|
mn := objNode.name
|
||||||
if !imports(g, mn) {
|
if !imports(g, mn) {
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,11 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
||||||
} else if at(p, "struct") {
|
} else if at(p, "struct") {
|
||||||
prog.structs.push(parseStruct(p))
|
prog.structs.push(parseStruct(p))
|
||||||
} else if at(p, "enum") {
|
} else if at(p, "enum") {
|
||||||
prog.enums.push(parseEnum(p))
|
e := parseEnum(p)
|
||||||
|
prog.enums.push(e)
|
||||||
|
if e.flag {
|
||||||
|
prog.structs.push(sumStruct(e)) // an enum with values inside is stored as a struct (see sumStruct)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
prog.fns.push(parseFn(p))
|
prog.fns.push(parseFn(p))
|
||||||
}
|
}
|
||||||
|
|
@ -259,6 +263,7 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
||||||
|
|
||||||
// enum Color { Red, Green, Blue } enum Status { Ok = 200, NotFound = 404 }
|
// 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.
|
// The variants are numbered from 0 (or from the last number given) and are written Color.Red.
|
||||||
|
// A variant can hold values: enum Shape { Circle(float), Rect(float, float), Point } (e.flag is then true).
|
||||||
fn parseEnum(ptr[Parser] p) -> ast.Node {
|
fn parseEnum(ptr[Parser] p) -> ast.Node {
|
||||||
e := node(p, "enum")
|
e := node(p, "enum")
|
||||||
expect(p, "enum", "'enum'")
|
expect(p, "enum", "'enum'")
|
||||||
|
|
@ -270,7 +275,25 @@ fn parseEnum(ptr[Parser] p) -> ast.Node {
|
||||||
}
|
}
|
||||||
v := node(p, "variant")
|
v := node(p, "variant")
|
||||||
v.name = expect(p, "ident", "a variant name, like Red").text
|
v.name = expect(p, "ident", "a variant name, like Red").text
|
||||||
|
if accept(p, "(") {
|
||||||
|
e.flag = true
|
||||||
|
while !at(p, ")") {
|
||||||
|
f := node(p, "field")
|
||||||
|
f.ty = parseType(p)
|
||||||
|
v.kids.push(f)
|
||||||
|
if !accept(p, ",") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(p, ")", "')' after the values of the variant")
|
||||||
|
if v.kids.len == 0 {
|
||||||
|
fail(p, cur(p), "the variant " + v.name + " has empty () : write just " + v.name + " if it holds nothing")
|
||||||
|
}
|
||||||
|
}
|
||||||
if accept(p, "=") {
|
if accept(p, "=") {
|
||||||
|
if e.flag {
|
||||||
|
fail(p, cur(p), "a variant can't have both values inside and a number")
|
||||||
|
}
|
||||||
sign := ""
|
sign := ""
|
||||||
if accept(p, "-") {
|
if accept(p, "-") {
|
||||||
sign = "-"
|
sign = "-"
|
||||||
|
|
@ -286,9 +309,34 @@ fn parseEnum(ptr[Parser] p) -> ast.Node {
|
||||||
if e.kids.len == 0 {
|
if e.kids.len == 0 {
|
||||||
fail(p, cur(p), "the enum " + e.name + " has no variants")
|
fail(p, cur(p), "the enum " + e.name + " has no variants")
|
||||||
}
|
}
|
||||||
|
if e.flag {
|
||||||
|
for v in e.kids {
|
||||||
|
if v.text.len > 0 {
|
||||||
|
fail(p, cur(p), "an enum whose variants hold values can't number them (" + v.name + " = " + v.text + ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An enum whose variants hold values is a struct underneath: a `tag` (which variant it is) and, one after the other,
|
||||||
|
// the values of every variant (Rect's are Rect_0, Rect_1). Only the tag's variant has values; the rest stay
|
||||||
|
// empty. It's simple and it makes copying, dropping and methods the struct's own.
|
||||||
|
fn sumStruct(ast.Node e) -> ast.Node {
|
||||||
|
s := ast.Node(kind: "struct", name: e.name, line: e.line, col: e.col, file: e.file)
|
||||||
|
tag := ast.Node(kind: "field", name: "tag", line: e.line, col: e.col, file: e.file)
|
||||||
|
tag.ty = ast.simple("int")
|
||||||
|
s.kids.push(tag)
|
||||||
|
for v in e.kids {
|
||||||
|
for j in 0..v.kids.len {
|
||||||
|
f := ast.Node(kind: "field", name: v.name + "_" + str(j), line: v.line, col: v.col, file: v.file)
|
||||||
|
f.ty = v.kids[j].ty
|
||||||
|
s.kids.push(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// match value { A, B -> ... C -> ... else -> ... }
|
// match value { A, B -> ... C -> ... else -> ... }
|
||||||
// As a statement the arms are statements or { blocks }; as an expression they are expressions.
|
// As a statement the arms are statements or { blocks }; as an expression they are expressions.
|
||||||
fn parseMatch(ptr[Parser] p, bool asExpr) -> ast.Node {
|
fn parseMatch(ptr[Parser] p, bool asExpr) -> ast.Node {
|
||||||
|
|
|
||||||
12
tests/new/fail/payload_missing.fch
Normal file
12
tests/new/fail/payload_missing.fch
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
// expect: this match doesn't handle Point
|
||||||
|
enum Shape {
|
||||||
|
Circle(float)
|
||||||
|
Point
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
s := Shape.Circle(1.0)
|
||||||
|
match s {
|
||||||
|
Circle(r) -> print(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
tests/new/payload_enums.fch
Normal file
38
tests/new/payload_enums.fch
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
enum Shape {
|
||||||
|
Circle(float)
|
||||||
|
Rect(float, float)
|
||||||
|
Named(str, []int)
|
||||||
|
Point
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Shape.area() -> float {
|
||||||
|
return match self {
|
||||||
|
Circle(r) -> 3.0 * r * r
|
||||||
|
Rect(w, h) -> w * h
|
||||||
|
Named(_, sizes) -> float(sizes.len)
|
||||||
|
Point -> 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe(Shape s) -> str {
|
||||||
|
match s {
|
||||||
|
Shape.Circle(r) -> return "circle " + str(r)
|
||||||
|
Shape.Named(name, _) -> return "named " + name
|
||||||
|
else -> return "other"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
shapes := [Shape.Circle(2.0), Shape.Rect(2.0, 3.5), Shape.Named("tri", [1, 2, 3]), Shape.Point]
|
||||||
|
for s in shapes {
|
||||||
|
print(s, s.area(), describe(s))
|
||||||
|
}
|
||||||
|
copy := shapes[2]
|
||||||
|
shapes.clear()
|
||||||
|
print(copy)
|
||||||
|
p := Shape.Point
|
||||||
|
match p {
|
||||||
|
Point -> print("a point")
|
||||||
|
else -> print("no")
|
||||||
|
}
|
||||||
|
}
|
||||||
6
tests/new/payload_enums.out
Normal file
6
tests/new/payload_enums.out
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
Circle(2) 12 circle 2
|
||||||
|
Rect(2, 3.5) 7 other
|
||||||
|
Named("tri", [1, 2, 3]) 3 named tri
|
||||||
|
Point 0 other
|
||||||
|
Named("tri", [1, 2, 3])
|
||||||
|
a point
|
||||||
Loading…
Reference in a new issue