mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 16:54:25 +00:00
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:
parent
a5ba4bc8f0
commit
609ceffe58
15 changed files with 576 additions and 18 deletions
|
|
@ -40,7 +40,7 @@ fn same(Type a, Type b) -> bool {
|
||||||
if a.kind != b.kind {
|
if a.kind != b.kind {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if a.kind == "struct" || a.kind == "named" {
|
if a.kind == "struct" || a.kind == "named" || a.kind == "enum" {
|
||||||
return a.name == b.name && a.module == b.module
|
return a.name == b.name && a.module == b.module
|
||||||
}
|
}
|
||||||
if a.kind == "fixed" && a.count != b.count {
|
if a.kind == "fixed" && a.count != b.count {
|
||||||
|
|
@ -76,7 +76,7 @@ fn show(Type t) -> str {
|
||||||
}
|
}
|
||||||
return "ptr[" + show(t.elem[0]) + "]"
|
return "ptr[" + show(t.elem[0]) + "]"
|
||||||
}
|
}
|
||||||
if t.kind == "struct" || t.kind == "named" {
|
if t.kind == "struct" || t.kind == "named" || t.kind == "enum" {
|
||||||
if t.module.len > 0 {
|
if t.module.len > 0 {
|
||||||
return t.module + "." + t.name
|
return t.module + "." + t.name
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +91,8 @@ fn show(Type t) -> str {
|
||||||
// One node of the tree, for expressions, statements, functions and structs alike.
|
// 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
|
// 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
|
// statements: decl assign expr if while for each return break continue block
|
||||||
// top level: fn param struct field
|
// 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 {
|
struct Node {
|
||||||
str kind
|
str kind
|
||||||
str name // identifier, operator, callee, field or method name
|
str name // identifier, operator, callee, field or method name
|
||||||
|
|
@ -115,5 +116,6 @@ struct Program {
|
||||||
[]str cwhere // where each was written, for error messages
|
[]str cwhere // where each was written, for error messages
|
||||||
[]str links // link "glfw", link "mine.c"
|
[]str links // link "glfw", link "mine.c"
|
||||||
[]Node structs
|
[]Node structs
|
||||||
|
[]Node enums
|
||||||
[]Node fns
|
[]Node fns
|
||||||
}
|
}
|
||||||
|
|
|
||||||
322
boot/gen.fch
322
boot/gen.fch
|
|
@ -16,6 +16,13 @@ struct Var {
|
||||||
|
|
||||||
// A scope's variables, and the defers that run when it ends, newest first.
|
// A scope's variables, and the defers that run when it ends, newest first.
|
||||||
// A defer sees only the variables declared before it: `orders` says how many.
|
// A defer sees only the variables declared before it: `orders` says how many.
|
||||||
|
struct EnumDef {
|
||||||
|
str module
|
||||||
|
str name
|
||||||
|
[]str variants
|
||||||
|
[]int values
|
||||||
|
}
|
||||||
|
|
||||||
struct Scope {
|
struct Scope {
|
||||||
[]Var vars
|
[]Var vars
|
||||||
[]ast.Node defers
|
[]ast.Node defers
|
||||||
|
|
@ -33,6 +40,7 @@ struct FnSig {
|
||||||
str name
|
str name
|
||||||
str recv // a method of this struct ("" for a function)
|
str recv // a method of this struct ("" for a function)
|
||||||
str recvmod // and the module of that struct: "C" for a struct from a header
|
str recvmod // and the module of that struct: "C" for a struct from a header
|
||||||
|
bool recvEnum // the receiver is an enum
|
||||||
bool fallible // -> T!: returns { i1 ok, T value, str error }
|
bool fallible // -> T!: returns { i1 ok, T value, str error }
|
||||||
bool mutself // the method changes self
|
bool mutself // the method changes self
|
||||||
str ll
|
str ll
|
||||||
|
|
@ -96,6 +104,7 @@ struct Gen {
|
||||||
str module
|
str module
|
||||||
[]FnSig sigs
|
[]FnSig sigs
|
||||||
[]StructDef structs
|
[]StructDef structs
|
||||||
|
[]EnumDef enums
|
||||||
[]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
|
||||||
|
|
@ -273,6 +282,15 @@ fn findStruct(ptr[Gen] g, str module, str name) -> int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn findEnum(ptr[Gen] g, str module, str name) -> int {
|
||||||
|
for i in 0..g.enums.len {
|
||||||
|
if g.enums[i].module == module && g.enums[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 {
|
||||||
|
|
@ -320,6 +338,9 @@ fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
|
||||||
}
|
}
|
||||||
module = t.module
|
module = t.module
|
||||||
}
|
}
|
||||||
|
if findEnum(g, module, t.name) >= 0 {
|
||||||
|
return ast.Type(kind: "enum", name: t.name, module: module)
|
||||||
|
}
|
||||||
if findStruct(g, module, t.name) < 0 {
|
if findStruct(g, module, t.name) < 0 {
|
||||||
// a bare name may be a struct from a C header
|
// a bare name may be a struct from a C header
|
||||||
if t.module.len == 0 && findStruct(g, "C", t.name) >= 0 {
|
if t.module.len == 0 && findStruct(g, "C", t.name) >= 0 {
|
||||||
|
|
@ -360,6 +381,9 @@ fn llty(ptr[Gen] g, ast.Type t) -> str {
|
||||||
if k == "fixed" {
|
if k == "fixed" {
|
||||||
return "[" + str(t.count) + " x " + llty(g, t.elem[0]) + "]"
|
return "[" + str(t.count) + " x " + llty(g, t.elem[0]) + "]"
|
||||||
}
|
}
|
||||||
|
if k == "enum" {
|
||||||
|
return "i32"
|
||||||
|
}
|
||||||
if k == "ptr" || k == "null" {
|
if k == "ptr" || k == "null" {
|
||||||
return "ptr"
|
return "ptr"
|
||||||
}
|
}
|
||||||
|
|
@ -389,7 +413,7 @@ fn nodeKey(ast.Node n) -> str {
|
||||||
|
|
||||||
fn zeroOf(ast.Type t) -> str {
|
fn zeroOf(ast.Type t) -> str {
|
||||||
k := t.kind
|
k := t.kind
|
||||||
if isInt(t) || k == "char" {
|
if isInt(t) || k == "char" || k == "enum" {
|
||||||
return "0"
|
return "0"
|
||||||
}
|
}
|
||||||
if k == "bool" {
|
if k == "bool" {
|
||||||
|
|
@ -425,6 +449,9 @@ fn typeKey(ast.Type t) -> str {
|
||||||
if t.kind == "map" {
|
if t.kind == "map" {
|
||||||
return "map_" + typeKey(t.elem[0]) + "_" + typeKey(t.elem[1])
|
return "map_" + typeKey(t.elem[0]) + "_" + typeKey(t.elem[1])
|
||||||
}
|
}
|
||||||
|
if t.kind == "enum" {
|
||||||
|
return "E_" + modName(t.module) + "_" + t.name
|
||||||
|
}
|
||||||
if t.kind == "struct" {
|
if t.kind == "struct" {
|
||||||
return "S_" + modName(t.module) + "_" + t.name
|
return "S_" + modName(t.module) + "_" + t.name
|
||||||
}
|
}
|
||||||
|
|
@ -751,6 +778,31 @@ fn formatFn(ptr[Gen] g, ast.Type t) -> str {
|
||||||
emitFormat(g, "%buf", load(g, llty(g, et), ep), et, true)
|
emitFormat(g, "%buf", load(g, llty(g, et), ep), et, true)
|
||||||
loopEnd(g, l)
|
loopEnd(g, l)
|
||||||
bufText(g, "%buf", "]")
|
bufText(g, "%buf", "]")
|
||||||
|
} else if t.kind == "enum" {
|
||||||
|
// the variant's name; a number that is no variant shows as Color(7)
|
||||||
|
ed := g.enums[findEnum(g, t.module, t.name)]
|
||||||
|
v := load(g, "i32", "%a")
|
||||||
|
def := newLabel(g, "enum.other")
|
||||||
|
done := newLabel(g, "enum.done")
|
||||||
|
[]str cases
|
||||||
|
[]str labels
|
||||||
|
for k in 0..ed.variants.len {
|
||||||
|
labels.push(newLabel(g, "enum.v"))
|
||||||
|
cases.push("i32 " + str(ed.values[k]) + ", label %" + labels[k])
|
||||||
|
}
|
||||||
|
emit(g, "switch i32 " + v + ", label %" + def + " [ " + cases.join(" ") + " ]")
|
||||||
|
g.term = true
|
||||||
|
for k in 0..ed.variants.len {
|
||||||
|
label(g, labels[k])
|
||||||
|
bufText(g, "%buf", ed.variants[k])
|
||||||
|
br(g, done)
|
||||||
|
}
|
||||||
|
label(g, def)
|
||||||
|
bufText(g, "%buf", ed.name + "(")
|
||||||
|
emit(g, "call void @finch_buf_int(ptr %buf, i64 " + op(g, "sext i32 " + v + " to i64") + ")")
|
||||||
|
bufText(g, "%buf", ")")
|
||||||
|
br(g, done)
|
||||||
|
label(g, done)
|
||||||
} else if t.kind == "map" {
|
} else if t.kind == "map" {
|
||||||
// {"a": 1, "b": 2}
|
// {"a": 1, "b": 2}
|
||||||
first := alloca(g, "i1")
|
first := alloca(g, "i1")
|
||||||
|
|
@ -1038,6 +1090,32 @@ fn declare(ptr[Gen] g) {
|
||||||
g.structs.push(StructDef(module: g.module, name: s.name, prog: pi, index: si))
|
g.structs.push(StructDef(module: g.module, name: s.name, prog: pi, index: si))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for pi in 0..g.progs.len {
|
||||||
|
g.module = g.progs[pi].module
|
||||||
|
for ei in 0..g.progs[pi].enums.len {
|
||||||
|
e := g.progs[pi].enums[ei]
|
||||||
|
if findEnum(g, g.module, e.name) >= 0 || findStruct(g, g.module, e.name) >= 0 {
|
||||||
|
fail(g, e, "'" + e.name + "' is defined twice")
|
||||||
|
}
|
||||||
|
ed := EnumDef(module: g.module, name: e.name)
|
||||||
|
next := 0
|
||||||
|
for v in e.kids {
|
||||||
|
if v.text.len > 0 {
|
||||||
|
next = int(v.text)
|
||||||
|
}
|
||||||
|
if ed.variants.contains(v.name) {
|
||||||
|
fail(g, v, "the enum " + e.name + " has the variant " + v.name + " twice")
|
||||||
|
}
|
||||||
|
if ed.values.contains(next) {
|
||||||
|
fail(g, v, "two variants of " + e.name + " have the number " + str(next) + " (give each its own)")
|
||||||
|
}
|
||||||
|
ed.variants.push(v.name)
|
||||||
|
ed.values.push(next)
|
||||||
|
next += 1
|
||||||
|
}
|
||||||
|
g.enums.push(ed)
|
||||||
|
}
|
||||||
|
}
|
||||||
for ni in 0..g.ci.structOrder.len {
|
for ni in 0..g.ci.structOrder.len {
|
||||||
g.structs.push(StructDef(module: "C", name: g.ci.structOrder[ni], isC: true, prog: -1, index: ni))
|
g.structs.push(StructDef(module: "C", name: g.ci.structOrder[ni], isC: true, prog: -1, index: ni))
|
||||||
}
|
}
|
||||||
|
|
@ -1049,7 +1127,13 @@ fn declare(ptr[Gen] g) {
|
||||||
for fi in 0..g.progs[pi].fns.len {
|
for fi in 0..g.progs[pi].fns.len {
|
||||||
f := g.progs[pi].fns[fi]
|
f := g.progs[pi].fns[fi]
|
||||||
recvmod := g.module
|
recvmod := g.module
|
||||||
if f.recv.len > 0 {
|
recvEnum := false
|
||||||
|
if f.recv.len > 0 && findEnum(g, g.module, f.recv) >= 0 {
|
||||||
|
recvEnum = true
|
||||||
|
if findMethod(g, recvmod, f.recv, f.name) >= 0 {
|
||||||
|
fail(g, f, "the method " + f.recv + "." + f.name + " is defined twice")
|
||||||
|
}
|
||||||
|
} else if f.recv.len > 0 {
|
||||||
si := findStruct(g, g.module, f.recv)
|
si := findStruct(g, g.module, f.recv)
|
||||||
if si < 0 && findStruct(g, "C", f.recv) >= 0 {
|
if si < 0 && findStruct(g, "C", f.recv) >= 0 {
|
||||||
si = findStruct(g, "C", f.recv) // a method on a struct from C
|
si = findStruct(g, "C", f.recv) // a method on a struct from C
|
||||||
|
|
@ -1072,7 +1156,7 @@ fn declare(ptr[Gen] g) {
|
||||||
fail(g, f, "'" + f.name + "' is a built-in function, pick another name")
|
fail(g, f, "'" + f.name + "' is a built-in function, pick another name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sig := FnSig(module: g.module, name: f.name, recv: f.recv, recvmod: recvmod, fallible: f.fallible, prog: pi, index: fi)
|
sig := FnSig(module: g.module, name: f.name, recv: f.recv, recvmod: recvmod, recvEnum: recvEnum, fallible: f.fallible, prog: pi, index: fi)
|
||||||
sig.ll = "@finch." + modName(g.module) + "." + f.name
|
sig.ll = "@finch." + modName(g.module) + "." + f.name
|
||||||
if f.recv.len > 0 {
|
if f.recv.len > 0 {
|
||||||
sig.ll = "@finch." + modName(g.module) + "." + f.recv + "." + f.name
|
sig.ll = "@finch." + modName(g.module) + "." + f.recv + "." + f.name
|
||||||
|
|
@ -1208,7 +1292,11 @@ fn define(ptr[Gen] g, int si) {
|
||||||
if g.cur.recv.len > 0 {
|
if g.cur.recv.len > 0 {
|
||||||
// self is the caller's struct itself, reached through its address
|
// self is the caller's struct itself, reached through its address
|
||||||
params.push("ptr %self")
|
params.push("ptr %self")
|
||||||
g.scopes[0].vars.push(Var(name: "self", ty: ast.Type(kind: "struct", name: g.cur.recv, module: g.cur.recvmod), slot: "%self", readonly: false))
|
selfKind := "struct"
|
||||||
|
if g.cur.recvEnum {
|
||||||
|
selfKind = "enum"
|
||||||
|
}
|
||||||
|
g.scopes[0].vars.push(Var(name: "self", ty: ast.Type(kind: selfKind, name: g.cur.recv, module: g.cur.recvmod), slot: "%self", readonly: false))
|
||||||
}
|
}
|
||||||
for k in 0..g.cur.params.len {
|
for k in 0..g.cur.params.len {
|
||||||
pt := g.cur.params[k]
|
pt := g.cur.params[k]
|
||||||
|
|
@ -1266,6 +1354,8 @@ fn stmt(ptr[Gen] g, ast.Node s) {
|
||||||
} else {
|
} else {
|
||||||
expr(g, s.kids[0])
|
expr(g, s.kids[0])
|
||||||
}
|
}
|
||||||
|
} else if k == "match" {
|
||||||
|
matchGen(g, s, false)
|
||||||
} else if k == "if" {
|
} else if k == "if" {
|
||||||
ifStmt(g, s)
|
ifStmt(g, s)
|
||||||
} else if k == "while" {
|
} else if k == "while" {
|
||||||
|
|
@ -1914,6 +2004,139 @@ fn mapMethod(ptr[Gen] g, ast.Node m, str mp, ast.Type t) -> Val {
|
||||||
return Val()
|
return Val()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- match ----------
|
||||||
|
|
||||||
|
// A pattern as a value of the subject's type. For an enum, a bare variant name (Red) works too.
|
||||||
|
fn patternValue(ptr[Gen] g, ast.Node pat, ast.Type st) -> Val {
|
||||||
|
if st.kind == "enum" && pat.kind == "var" && lookup(g, pat.name) < 0 {
|
||||||
|
ed := g.enums[findEnum(g, st.module, st.name)]
|
||||||
|
k := ed.variants.find(pat.name)
|
||||||
|
if k < 0 {
|
||||||
|
fail(g, pat, "'" + pat.name + "' is not a variant of " + ed.name + " (it has: " + ed.variants.join(", ") + ")")
|
||||||
|
}
|
||||||
|
return Val(v: str(ed.values[k]), ty: st)
|
||||||
|
}
|
||||||
|
return coerce(g, exprWant(g, pat, st), st, pat, "this pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
// match value { A, B -> ... C -> ... else -> ... }, as a statement or (asValue) as an expression.
|
||||||
|
// The patterns are tried in order; for an enum the arms have to cover every variant, or end with else.
|
||||||
|
fn matchGen(ptr[Gen] g, ast.Node e, bool asValue) -> Val {
|
||||||
|
subj := expr(g, e.kids[0])
|
||||||
|
st := subj.ty
|
||||||
|
if st.kind == "void" || st.kind == "null" {
|
||||||
|
fail(g, e.kids[0], "there is nothing to match here")
|
||||||
|
}
|
||||||
|
end := newLabel(g, "match.end")
|
||||||
|
hasElse := false
|
||||||
|
reached := false // does any arm go on to the end?
|
||||||
|
[]str covered
|
||||||
|
[]str inVals
|
||||||
|
[]str inLabels
|
||||||
|
resultTy := T("void")
|
||||||
|
arms := e.kids.len - 1
|
||||||
|
for ai in 0..arms {
|
||||||
|
arm := e.kids[1 + ai]
|
||||||
|
if hasElse {
|
||||||
|
fail(g, arm, "this arm can never be used: 'else' already takes everything that is left")
|
||||||
|
}
|
||||||
|
body := newLabel(g, "match.arm")
|
||||||
|
next := newLabel(g, "match.next")
|
||||||
|
if arm.flag {
|
||||||
|
hasElse = true
|
||||||
|
br(g, body)
|
||||||
|
} else {
|
||||||
|
pats := arm.kids[0].kids
|
||||||
|
for pi in 0..pats.len {
|
||||||
|
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)
|
||||||
|
hit := compare(g, "==", subj, pv, pats[pi])
|
||||||
|
if pi == pats.len - 1 {
|
||||||
|
condBr(g, hit.v, body, next)
|
||||||
|
} else {
|
||||||
|
more := newLabel(g, "match.or")
|
||||||
|
condBr(g, hit.v, body, more)
|
||||||
|
label(g, more)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
label(g, body)
|
||||||
|
node := arm.kids[1]
|
||||||
|
if asValue {
|
||||||
|
v := expr(g, node)
|
||||||
|
if ai == 0 || resultTy.kind == "void" {
|
||||||
|
if v.ty.kind == "void" || v.ty.kind == "null" {
|
||||||
|
fail(g, node, "an arm of a match that gives a value has to give one")
|
||||||
|
}
|
||||||
|
resultTy = v.ty
|
||||||
|
}
|
||||||
|
v = coerce(g, v, resultTy, node, "this arm (the arms of a match have to give the same type)")
|
||||||
|
out := own(g, v)
|
||||||
|
from := newLabel(g, "match.from")
|
||||||
|
br(g, from)
|
||||||
|
label(g, from)
|
||||||
|
inVals.push(out)
|
||||||
|
inLabels.push(from)
|
||||||
|
br(g, end)
|
||||||
|
reached = true
|
||||||
|
} else {
|
||||||
|
if node.kind == "block" {
|
||||||
|
block(g, node, true)
|
||||||
|
} else {
|
||||||
|
stmt(g, node)
|
||||||
|
}
|
||||||
|
if !g.term {
|
||||||
|
reached = true
|
||||||
|
}
|
||||||
|
br(g, end)
|
||||||
|
}
|
||||||
|
if !arm.flag {
|
||||||
|
label(g, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasElse {
|
||||||
|
if st.kind == "enum" {
|
||||||
|
ed := g.enums[findEnum(g, st.module, st.name)]
|
||||||
|
[]str missing
|
||||||
|
for k in 0..ed.variants.len {
|
||||||
|
if !covered.contains(str(ed.values[k])) {
|
||||||
|
missing.push(ed.variants[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if missing.len > 0 {
|
||||||
|
fail(g, e, "this match doesn't handle " + missing.join(", ") + " (add them, or an 'else' arm)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail(g, e, "a match on " + ast.show(st) + " needs an 'else' arm for everything that is not listed")
|
||||||
|
}
|
||||||
|
// every variant is handled; only a number that was never a variant gets here
|
||||||
|
panicIf(g, "true", "no arm of this match fits the value", e)
|
||||||
|
emit(g, "unreachable")
|
||||||
|
g.term = true
|
||||||
|
}
|
||||||
|
label(g, end)
|
||||||
|
if !reached {
|
||||||
|
emit(g, "unreachable")
|
||||||
|
g.term = true
|
||||||
|
return Val(ty: T("void"))
|
||||||
|
}
|
||||||
|
if asValue {
|
||||||
|
return Val(v: op(g, "phi " + llty(g, resultTy) + " " + phiList(inVals, inLabels)), ty: resultTy, fresh: owning(g, resultTy))
|
||||||
|
}
|
||||||
|
return Val(ty: T("void"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn phiList([]str vals, []str labels) -> str {
|
||||||
|
[]str parts
|
||||||
|
for i in 0..vals.len {
|
||||||
|
parts.push("[ " + vals[i] + ", %" + labels[i] + " ]")
|
||||||
|
}
|
||||||
|
return parts.join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- errors as values ----------
|
// ---------- errors as values ----------
|
||||||
|
|
||||||
struct Fallible {
|
struct Fallible {
|
||||||
|
|
@ -2068,6 +2291,9 @@ fn expr(ptr[Gen] g, ast.Node e) -> Val {
|
||||||
}
|
}
|
||||||
return Val(v: load(g, llty(g, r.pl.ty), r.pl.addr), ty: r.pl.ty)
|
return Val(v: load(g, llty(g, r.pl.ty), r.pl.addr), ty: r.pl.ty)
|
||||||
}
|
}
|
||||||
|
if k == "match" {
|
||||||
|
return matchGen(g, e, true)
|
||||||
|
}
|
||||||
if k == "try" {
|
if k == "try" {
|
||||||
return tryExpr(g, e)
|
return tryExpr(g, e)
|
||||||
}
|
}
|
||||||
|
|
@ -2263,6 +2489,16 @@ fn compare(ptr[Gen] g, str o, Val l, Val r, ast.Node at) -> Val {
|
||||||
preds := ["oeq", "une", "olt", "ole", "ogt", "oge"]
|
preds := ["oeq", "une", "olt", "ole", "ogt", "oge"]
|
||||||
return Val(v: op(g, "fcmp " + preds[ops.find(o)] + " " + llty(g, l.ty) + " " + l.v + ", " + r.v), ty: T("bool"))
|
return Val(v: op(g, "fcmp " + preds[ops.find(o)] + " " + llty(g, l.ty) + " " + l.v + ", " + r.v), ty: T("bool"))
|
||||||
}
|
}
|
||||||
|
if k == "enum" {
|
||||||
|
if !eq {
|
||||||
|
fail(g, at, "enums can only be compared with == and != (to go through them in order, give the variants numbers and use int(...))")
|
||||||
|
}
|
||||||
|
pred := "eq"
|
||||||
|
if o == "!=" {
|
||||||
|
pred = "ne"
|
||||||
|
}
|
||||||
|
return Val(v: op(g, "icmp " + pred + " i32 " + l.v + ", " + r.v), ty: T("bool"))
|
||||||
|
}
|
||||||
if k == "bool" && !eq {
|
if k == "bool" && !eq {
|
||||||
badOperands(g, o, l, r, at)
|
badOperands(g, o, l, r, at)
|
||||||
}
|
}
|
||||||
|
|
@ -2420,7 +2656,35 @@ fn llFloat(str text) -> str {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Which enum does `m.kids[0]` name, if it names one? Color, or shapes.Color. -1 if neither.
|
||||||
|
fn enumNamed(ptr[Gen] g, ast.Node n) -> int {
|
||||||
|
if n.kind == "var" && lookup(g, n.name) < 0 {
|
||||||
|
return findEnum(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 findEnum(g, n.kids[0].name, n.name)
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color.Red
|
||||||
|
fn enumValue(ptr[Gen] g, ast.Node m, int ei) -> Val {
|
||||||
|
ed := g.enums[ei]
|
||||||
|
k := ed.variants.find(m.name)
|
||||||
|
if k < 0 {
|
||||||
|
fail(g, m, "the enum " + ed.name + " has no variant '" + m.name + "' (it has: " + ed.variants.join(", ") + ")")
|
||||||
|
}
|
||||||
|
return Val(v: str(ed.values[k]), ty: ast.Type(kind: "enum", name: ed.name, module: ed.module))
|
||||||
|
}
|
||||||
|
|
||||||
fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref {
|
fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref {
|
||||||
|
ei := enumNamed(g, m.kids[0])
|
||||||
|
if ei >= 0 {
|
||||||
|
if write {
|
||||||
|
fail(g, m, "a variant of an enum can't be changed")
|
||||||
|
}
|
||||||
|
return Ref(isPlace: false, val: enumValue(g, m, ei))
|
||||||
|
}
|
||||||
obj := ref(g, m.kids[0], write)
|
obj := ref(g, m.kids[0], write)
|
||||||
t := obj.val.ty
|
t := obj.val.ty
|
||||||
if obj.isPlace {
|
if obj.isPlace {
|
||||||
|
|
@ -2655,6 +2919,9 @@ fn convert(ptr[Gen] g, ast.Node c, ast.Type to) -> Val {
|
||||||
r := op(g, "call double @finch_str_to_float(ptr " + spill(g, v) + ", ptr " + file + ", i64 " + str(c.line) + ")")
|
r := op(g, "call double @finch_str_to_float(ptr " + spill(g, v) + ", ptr " + file + ", i64 " + str(c.line) + ")")
|
||||||
return Val(v: floatCast(g, r, T("float"), to), ty: to)
|
return Val(v: floatCast(g, r, T("float"), to), ty: to)
|
||||||
}
|
}
|
||||||
|
if isInt(to) && from.kind == "enum" {
|
||||||
|
return Val(v: intCast(g, v.v, T("i32"), to), ty: to)
|
||||||
|
}
|
||||||
if isInt(to) || to.kind == "char" {
|
if isInt(to) || to.kind == "char" {
|
||||||
if isInt(from) || from.kind == "char" {
|
if isInt(from) || from.kind == "char" {
|
||||||
return Val(v: intCast(g, v.v, from, to), ty: to)
|
return Val(v: intCast(g, v.v, from, to), ty: to)
|
||||||
|
|
@ -2964,6 +3231,35 @@ fn cThunk(ptr[Gen] g, int fi, ast.Node at) -> str {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Color(n): a number as an enum. A literal is checked while compiling, anything else when it runs.
|
||||||
|
fn enumFrom(ptr[Gen] g, ast.Node c, int ei) -> Val {
|
||||||
|
ed := g.enums[ei]
|
||||||
|
checkArgs(g, c, 0, 1, ed.name)
|
||||||
|
v := expr(g, c.kids[0])
|
||||||
|
if !isInt(v.ty) {
|
||||||
|
fail(g, c.kids[0], ed.name + "(...) turns a whole number into the enum, but this is " + ast.show(v.ty))
|
||||||
|
}
|
||||||
|
t := ast.Type(kind: "enum", name: ed.name, module: ed.module)
|
||||||
|
if v.lit {
|
||||||
|
if !ed.values.contains(int(v.v)) {
|
||||||
|
fail(g, c.kids[0], v.v + " is not the number of any variant of " + ed.name)
|
||||||
|
}
|
||||||
|
return Val(v: v.v, ty: t)
|
||||||
|
}
|
||||||
|
wide := intCast(g, v.v, v.ty, T("int"))
|
||||||
|
known := ""
|
||||||
|
for k in 0..ed.values.len {
|
||||||
|
hit := op(g, "icmp eq i64 " + wide + ", " + str(ed.values[k]))
|
||||||
|
if k == 0 {
|
||||||
|
known = hit
|
||||||
|
} else {
|
||||||
|
known = op(g, "or i1 " + known + ", " + hit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panicIf(g, op(g, "xor i1 " + known + ", true"), "this number is not a variant of " + ed.name, c)
|
||||||
|
return Val(v: op(g, "trunc i64 " + wide + " to i32"), ty: t)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- calling C ----------
|
// ---------- calling C ----------
|
||||||
|
|
||||||
// How C extends a number narrower than 32 bits: plain `char` is signed on x86.
|
// How C extends a number narrower than 32 bits: plain `char` is signed on x86.
|
||||||
|
|
@ -3173,7 +3469,7 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
||||||
} else if k == "bool" {
|
} else if k == "bool" {
|
||||||
z := op(g, "zext i1 " + v.v + " to i32")
|
z := op(g, "zext i1 " + v.v + " to i32")
|
||||||
emit(g, "call void @finch_str_from_bool(ptr " + out + ", i32 " + z + ")")
|
emit(g, "call void @finch_str_from_bool(ptr " + out + ", i32 " + z + ")")
|
||||||
} else if k == "bool" || k == "char" || k == "array" || k == "map" || k == "struct" {
|
} else if k == "bool" || k == "char" || k == "array" || k == "map" || k == "struct" || k == "enum" {
|
||||||
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
||||||
emitFormat(g, out, v.v, v.ty, false)
|
emitFormat(g, out, v.v, v.ty, false)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3286,6 +3582,10 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
||||||
if si >= 0 {
|
if si >= 0 {
|
||||||
return construct(g, si, c.kids, c.names, c)
|
return construct(g, si, c.kids, c.names, c)
|
||||||
}
|
}
|
||||||
|
ei := findEnum(g, g.module, n)
|
||||||
|
if ei >= 0 {
|
||||||
|
return enumFrom(g, c, ei)
|
||||||
|
}
|
||||||
if g.ci.fns.has(n) {
|
if g.ci.fns.has(n) {
|
||||||
return callC(g, c, g.ci.fns[n])
|
return callC(g, c, g.ci.fns[n])
|
||||||
}
|
}
|
||||||
|
|
@ -3411,6 +3711,10 @@ fn method(ptr[Gen] g, ast.Node m) -> Val {
|
||||||
if fi >= 0 {
|
if fi >= 0 {
|
||||||
return callFn(g, fi, m, 1, mn + "." + m.name, "", "")
|
return callFn(g, fi, m, 1, mn + "." + m.name, "", "")
|
||||||
}
|
}
|
||||||
|
mei := findEnum(g, mn, m.name)
|
||||||
|
if mei >= 0 {
|
||||||
|
return enumFrom(g, m, mei)
|
||||||
|
}
|
||||||
si := findStruct(g, mn, m.name)
|
si := findStruct(g, mn, m.name)
|
||||||
if si >= 0 {
|
if si >= 0 {
|
||||||
[]ast.Node args
|
[]ast.Node args
|
||||||
|
|
@ -3468,20 +3772,20 @@ fn method(ptr[Gen] g, ast.Node m) -> Val {
|
||||||
panicIf(g, op(g, "icmp eq ptr " + pv + ", null"), "called ." + m.name + "() on a null pointer", m)
|
panicIf(g, op(g, "icmp eq ptr " + pv + ", null"), "called ." + m.name + "() on a null pointer", m)
|
||||||
st = t.elem[0]
|
st = t.elem[0]
|
||||||
selfAddr = pv
|
selfAddr = pv
|
||||||
} else if t.kind == "struct" {
|
} else if t.kind == "struct" || t.kind == "enum" {
|
||||||
if r.isPlace {
|
if r.isPlace {
|
||||||
selfAddr = r.pl.addr
|
selfAddr = r.pl.addr
|
||||||
} else {
|
} else {
|
||||||
selfAddr = spill(g, r.val)
|
selfAddr = spill(g, r.val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if st.kind == "struct" {
|
if st.kind == "struct" || st.kind == "enum" {
|
||||||
fi := findMethod(g, st.module, st.name, m.name)
|
fi := findMethod(g, st.module, st.name, m.name)
|
||||||
if fi < 0 {
|
if fi < 0 {
|
||||||
fail(g, m, "the struct " + ast.show(st) + " has no method '" + m.name + "'")
|
fail(g, m, "the " + st.kind + " " + ast.show(st) + " has no method '" + m.name + "'")
|
||||||
}
|
}
|
||||||
// a method that changes self can't be called on a temporary or a read-only value
|
// a method that changes self can't be called on a temporary or a read-only value
|
||||||
if g.sigs[fi].mutself && t.kind == "struct" {
|
if g.sigs[fi].mutself && (t.kind == "struct" || t.kind == "enum") {
|
||||||
if !r.isPlace {
|
if !r.isPlace {
|
||||||
fail(g, m, m.name + "() would change a temporary " + ast.show(st) + "; store it in a variable first")
|
fail(g, m, m.name + "() would change a temporary " + ast.show(st) + "; store it in a variable first")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ fn isHex(char c) -> bool {
|
||||||
|
|
||||||
fn isKeyword(str w) -> bool {
|
fn isKeyword(str w) -> bool {
|
||||||
keywords := ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false",
|
keywords := ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false",
|
||||||
"null", "import", "link", "struct", "defer", "or", "try"]
|
"null", "import", "link", "struct", "defer", "or", "try", "enum", "match"]
|
||||||
return keywords.contains(w)
|
return keywords.contains(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
prog.links.push(expect(p, "string", "a library or C file name, like link \"glfw\"").text)
|
||||||
} 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") {
|
||||||
|
prog.enums.push(parseEnum(p))
|
||||||
} else {
|
} else {
|
||||||
prog.fns.push(parseFn(p))
|
prog.fns.push(parseFn(p))
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +182,86 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
||||||
return prog
|
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 {
|
fn parseStruct(ptr[Parser] p) -> ast.Node {
|
||||||
s := node(p, "struct")
|
s := node(p, "struct")
|
||||||
expect(p, "struct", "'struct'")
|
expect(p, "struct", "'struct'")
|
||||||
|
|
@ -293,6 +375,9 @@ fn parseStatement(ptr[Parser] p) -> ast.Node {
|
||||||
s.kids.push(parseBlock(p))
|
s.kids.push(parseBlock(p))
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
if k == "match" {
|
||||||
|
return parseMatch(p, false)
|
||||||
|
}
|
||||||
if k == "return" {
|
if k == "return" {
|
||||||
s := node(p, "return")
|
s := node(p, "return")
|
||||||
next(p)
|
next(p)
|
||||||
|
|
@ -532,6 +617,9 @@ fn parsePrimary(ptr[Parser] p) -> ast.Node {
|
||||||
expect(p, ")", "')'")
|
expect(p, ")", "')'")
|
||||||
return inner
|
return inner
|
||||||
}
|
}
|
||||||
|
if k == "match" {
|
||||||
|
return parseMatch(p, true)
|
||||||
|
}
|
||||||
if k == "[" {
|
if k == "[" {
|
||||||
next(p)
|
next(p)
|
||||||
e.kind = "array"
|
e.kind = "array"
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,10 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pass=0; failed=0; skipped=0
|
pass=0; failed=0; skipped=0
|
||||||
for f in tests/run/*.fch; do
|
# tests/run: what the C++ compiler also runs. tests/new: the language since then (enums, match, ...),
|
||||||
|
# which only this compiler has.
|
||||||
|
for f in tests/run/*.fch tests/new/*.fch; do
|
||||||
grep -q "^fn main" "$f" || continue
|
grep -q "^fn main" "$f" || continue
|
||||||
# the bootstrap compiler doesn't do C structs by value
|
|
||||||
# (checked in the test and in the modules it imports)
|
|
||||||
files="$f"
|
|
||||||
for m in $(sed -n 's/^import \([a-z_]*\)$/\1/p' "$f"); do files="$files tests/run/$m.fch"; done
|
|
||||||
input="${f%.fch}.in"
|
input="${f%.fch}.in"
|
||||||
[ -f "$input" ] || input=/dev/null
|
[ -f "$input" ] || input=/dev/null
|
||||||
if ! "$boot" build "$f" -o "$tmp/prog" > "$tmp/err" 2>&1; then
|
if ! "$boot" build "$f" -o "$tmp/prog" > "$tmp/err" 2>&1; then
|
||||||
|
|
@ -49,5 +47,18 @@ for f in tests/run/*.fch; do
|
||||||
diff <(echo "$got") "${f%.fch}.out" | head -10
|
diff <(echo "$got") "${f%.fch}.out" | head -10
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
# programs that must be refused, and say why: the first line is "// expect: <text of the error>"
|
||||||
|
for f in tests/new/fail/*.fch; do
|
||||||
|
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
|
||||||
|
got=$("$boot" build "$f" -o "$tmp/refused" 2>&1)
|
||||||
|
status=$?
|
||||||
|
if [ $status -ne 0 ] && [[ "$got" == *"$expect"* ]]; then
|
||||||
|
pass=$((pass + 1))
|
||||||
|
else
|
||||||
|
failed=$((failed + 1))
|
||||||
|
echo "FAIL $f (wanted: $expect)"
|
||||||
|
echo "$got" | head -3
|
||||||
|
fi
|
||||||
|
done
|
||||||
echo "self-hosted compiler: $pass passed, $failed failed, $skipped skipped (features it doesn't have)"
|
echo "self-hosted compiler: $pass passed, $failed failed, $skipped skipped (features it doesn't have)"
|
||||||
[ $failed -eq 0 ]
|
[ $failed -eq 0 ]
|
||||||
|
|
|
||||||
75
tests/new/enums.fch
Normal file
75
tests/new/enums.fch
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
enum Color { Red, Green, Blue }
|
||||||
|
enum Status { Ok = 200, NotFound = 404, Teapot = 418 }
|
||||||
|
|
||||||
|
struct Pixel {
|
||||||
|
Color color
|
||||||
|
int x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Color.warm() -> bool {
|
||||||
|
return self == Color.Red
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Color.next() -> Color {
|
||||||
|
if self == Color.Red {
|
||||||
|
return Color.Green
|
||||||
|
}
|
||||||
|
if self == Color.Green {
|
||||||
|
return Color.Blue
|
||||||
|
}
|
||||||
|
return Color.Red
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe(Color c) -> str {
|
||||||
|
return match c {
|
||||||
|
Red -> "red"
|
||||||
|
Green -> "green"
|
||||||
|
Blue -> "blue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code(Status s) -> str {
|
||||||
|
match s {
|
||||||
|
Status.Ok -> return "fine"
|
||||||
|
Status.NotFound, Status.Teapot -> return "odd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
c := Color.Green
|
||||||
|
print(c, Color.Blue, int(Color.Blue), int(Status.Teapot))
|
||||||
|
print(c == Color.Green, c != Color.Green)
|
||||||
|
print(describe(Color.Red), describe(c.next()), c.warm(), Color.Red.warm())
|
||||||
|
print(code(Status.Ok), code(Status.NotFound))
|
||||||
|
p := Pixel(color: Color.Blue, x: 3)
|
||||||
|
print(p)
|
||||||
|
[]Color all = [Color.Red, Color.Blue]
|
||||||
|
print(all, all.len)
|
||||||
|
for col in all {
|
||||||
|
match col {
|
||||||
|
Color.Red -> print("a red one")
|
||||||
|
else -> print("something else")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s := Status(404)
|
||||||
|
print(s, Status(200))
|
||||||
|
n := 5
|
||||||
|
kind := match n {
|
||||||
|
1, 2, 3 -> "small"
|
||||||
|
4, 5 -> "medium"
|
||||||
|
else -> "large"
|
||||||
|
}
|
||||||
|
print(kind)
|
||||||
|
word := "two"
|
||||||
|
match word {
|
||||||
|
"one" -> print(1)
|
||||||
|
"two" -> {
|
||||||
|
print(2)
|
||||||
|
print("two!")
|
||||||
|
}
|
||||||
|
else -> print("many")
|
||||||
|
}
|
||||||
|
counts := [Color.Red: 2, Color.Blue: 5]
|
||||||
|
print(counts[Color.Blue], counts)
|
||||||
|
print(str(Color.Green) + "!")
|
||||||
|
}
|
||||||
14
tests/new/enums.out
Normal file
14
tests/new/enums.out
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
Green Blue 2 418
|
||||||
|
true false
|
||||||
|
red blue false true
|
||||||
|
fine odd
|
||||||
|
Pixel(color: Blue, x: 3)
|
||||||
|
[Red, Blue] 2
|
||||||
|
a red one
|
||||||
|
something else
|
||||||
|
NotFound Ok
|
||||||
|
medium
|
||||||
|
2
|
||||||
|
two!
|
||||||
|
5 {Red: 2, Blue: 5}
|
||||||
|
Green!
|
||||||
6
tests/new/fail/enum_bad_number.fch
Normal file
6
tests/new/fail/enum_bad_number.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: 7 is not the number of any variant of Color
|
||||||
|
enum Color { Red, Green }
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
print(Color(7))
|
||||||
|
}
|
||||||
6
tests/new/fail/enum_compare_order.fch
Normal file
6
tests/new/fail/enum_compare_order.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: enums can only be compared with == and !=
|
||||||
|
enum Color { Red, Green }
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
print(Color.Red < Color.Green)
|
||||||
|
}
|
||||||
6
tests/new/fail/enum_no_variant.fch
Normal file
6
tests/new/fail/enum_no_variant.fch
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// expect: the enum Color has no variant 'Blue'
|
||||||
|
enum Color { Red, Green }
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
print(Color.Blue)
|
||||||
|
}
|
||||||
8
tests/new/fail/match_arm_after_else.fch
Normal file
8
tests/new/fail/match_arm_after_else.fch
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
// expect: this arm can never be used
|
||||||
|
fn main() {
|
||||||
|
n := 3
|
||||||
|
match n {
|
||||||
|
else -> print("any")
|
||||||
|
1 -> print("one")
|
||||||
|
}
|
||||||
|
}
|
||||||
9
tests/new/fail/match_arms_differ.fch
Normal file
9
tests/new/fail/match_arms_differ.fch
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
// expect: the arms of a match have to give the same type
|
||||||
|
fn main() {
|
||||||
|
n := 3
|
||||||
|
x := match n {
|
||||||
|
1 -> "one"
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
print(x)
|
||||||
|
}
|
||||||
10
tests/new/fail/match_missing_variant.fch
Normal file
10
tests/new/fail/match_missing_variant.fch
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
// expect: doesn't handle Blue
|
||||||
|
enum Color { Red, Green, Blue }
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
c := Color.Red
|
||||||
|
match c {
|
||||||
|
Red -> print(1)
|
||||||
|
Green -> print(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
tests/new/fail/match_needs_else.fch
Normal file
8
tests/new/fail/match_needs_else.fch
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
// expect: needs an 'else' arm
|
||||||
|
fn main() {
|
||||||
|
n := 3
|
||||||
|
match n {
|
||||||
|
1 -> print("one")
|
||||||
|
2 -> print("two")
|
||||||
|
}
|
||||||
|
}
|
||||||
11
tests/new/fail/match_not_a_variant.fch
Normal file
11
tests/new/fail/match_not_a_variant.fch
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
// expect: 'Purple' is not a variant of Color
|
||||||
|
enum Color { Red, Green }
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
c := Color.Red
|
||||||
|
match c {
|
||||||
|
Red -> print(1)
|
||||||
|
Purple -> print(2)
|
||||||
|
else -> print(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue