mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
const, if as a value, """ text over several lines, min/max/abs/clamp, import a/b and import x as y, the fs module
- const NAME = value (numbers, text, other constants; typed: const f32 G = 9.8), also module.NAME
- x := if c { a } else { b }
- """ ... """: the closing quotes' indentation goes from every line
- min, max, abs, clamp for any numbers, unless your own function has the name
- import ui/button (a module in a folder), import path as p
- fs: read_bytes, write_bytes, exists, is_dir, mkdir (with parents), list, remove_dir, data_dir(app)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
This commit is contained in:
parent
d0666cf788
commit
f2d6480749
21 changed files with 688 additions and 17 deletions
|
|
@ -114,11 +114,13 @@ struct Node {
|
|||
struct Program {
|
||||
str module
|
||||
str path
|
||||
[]str imports // Finch modules
|
||||
[]str imports // Finch modules, by the name this file uses: import ui/button -> "button"
|
||||
[]str modules // which module each is: "ui/button" (import path as p: "path")
|
||||
[]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
|
||||
[]Node consts // const NAME = value
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
220
src/gen.fch
220
src/gen.fch
|
|
@ -163,6 +163,7 @@ struct Ref {
|
|||
|
||||
struct Gen {
|
||||
[]ast.Program progs
|
||||
[]str constBusy // the constants being worked out: a loop between them is an error
|
||||
[]str files
|
||||
[]str head
|
||||
[]str out
|
||||
|
|
@ -520,7 +521,17 @@ fn progOf(ptr[Gen] g, str module) -> int {
|
|||
}
|
||||
|
||||
fn imports(ptr[Gen] g, str module) -> bool {
|
||||
return g.progs[progOf(g, g.module)].imports.contains(module)
|
||||
return modFor(g, module).len > 0
|
||||
}
|
||||
|
||||
// the module a name stands for in this file: button -> "ui/button", p -> "path"; "" if none
|
||||
fn modFor(ptr[Gen] g, str name) -> str {
|
||||
pr := g.progs[progOf(g, g.module)]
|
||||
k := pr.imports.find(name)
|
||||
if k < 0 {
|
||||
return ""
|
||||
}
|
||||
return pr.modules[k]
|
||||
}
|
||||
|
||||
fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
|
||||
|
|
@ -567,7 +578,7 @@ fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
|
|||
if !imports(g, t.module) {
|
||||
fail(g, at, "'" + t.module + "' is not imported here (add: import " + t.module + ")")
|
||||
}
|
||||
gm = t.module
|
||||
gm = modFor(g, t.module)
|
||||
}
|
||||
return instStruct(g, gm, t.name, args, at)
|
||||
}
|
||||
|
|
@ -582,7 +593,7 @@ fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
|
|||
if !imports(g, t.module) {
|
||||
fail(g, at, "'" + t.module + "' is not imported here (add: import " + t.module + ")")
|
||||
}
|
||||
module = t.module
|
||||
module = modFor(g, t.module)
|
||||
}
|
||||
if findEnum(g, module, t.name) >= 0 {
|
||||
return ast.Type(kind: "enum", name: t.name, module: module)
|
||||
|
|
@ -606,7 +617,7 @@ fn modName(str m) -> str {
|
|||
if m.len == 0 {
|
||||
return "main"
|
||||
}
|
||||
return m
|
||||
return m.replace("/", "$") // ui/button -> ui$button in symbol names
|
||||
}
|
||||
|
||||
fn llty(ptr[Gen] g, ast.Type t) -> str {
|
||||
|
|
@ -2423,6 +2434,9 @@ fn assignStmt(ptr[Gen] g, ast.Node s) {
|
|||
if root.len > 0 {
|
||||
id := lookup(g, root)
|
||||
if id < 0 && target.kind == "var" {
|
||||
if findConst(g, g.module, root) >= 0 {
|
||||
fail(g, s, "'" + root + "' is a constant; it can't be changed")
|
||||
}
|
||||
fail(g, s, "there is no variable named '" + root + "' (create it with " + root + " := ...)")
|
||||
}
|
||||
if id >= 0 && varAt(g, id).readonly {
|
||||
|
|
@ -3266,6 +3280,38 @@ fn matchGen(ptr[Gen] g, ast.Node e, bool asValue) -> Val {
|
|||
return Val(ty: T("void"))
|
||||
}
|
||||
|
||||
// x := if c { a } else { b }: both branches give the same type
|
||||
fn ifValue(ptr[Gen] g, ast.Node e, ast.Type want) -> Val {
|
||||
c := coerce(g, expr(g, e.kids[0]), T("bool"), e.kids[0], "the condition")
|
||||
yes := newLabel(g, "ifv.yes")
|
||||
no := newLabel(g, "ifv.no")
|
||||
end := newLabel(g, "ifv.end")
|
||||
condBr(g, c.v, yes, no)
|
||||
label(g, yes)
|
||||
a := exprWant(g, e.kids[1], want)
|
||||
if a.ty.kind == "void" || a.ty.kind == "null" {
|
||||
fail(g, e.kids[1], "this branch has to give a value")
|
||||
}
|
||||
if want.kind != "void" {
|
||||
a = coerce(g, a, want, e.kids[1], "this branch")
|
||||
}
|
||||
t := a.ty
|
||||
av := own(g, a)
|
||||
fromYes := newLabel(g, "ifv.from")
|
||||
br(g, fromYes)
|
||||
label(g, fromYes)
|
||||
br(g, end)
|
||||
label(g, no)
|
||||
b := coerce(g, exprWant(g, e.kids[2], t), t, e.kids[2], "this branch (both branches of the if have to give the same type)")
|
||||
bv := own(g, b)
|
||||
fromNo := newLabel(g, "ifv.from")
|
||||
br(g, fromNo)
|
||||
label(g, fromNo)
|
||||
br(g, end)
|
||||
label(g, end)
|
||||
return Val(v: op(g, "phi " + llty(g, t) + " " + phiList([av, bv], [fromYes, fromNo])), ty: t, fresh: owning(g, t))
|
||||
}
|
||||
|
||||
fn phiList([]str vals, []str labels) -> str {
|
||||
[]str parts
|
||||
for i in 0..vals.len {
|
||||
|
|
@ -3385,6 +3431,9 @@ fn exprWant(ptr[Gen] g, ast.Node e, ast.Type want) -> Val {
|
|||
if e.kind == "map" {
|
||||
return mapLit(g, e, want)
|
||||
}
|
||||
if e.kind == "ifvalue" {
|
||||
return ifValue(g, e, want) // f32 x = if c { 1.0 } else { 0.5 }
|
||||
}
|
||||
return expr(g, e)
|
||||
}
|
||||
|
||||
|
|
@ -3433,6 +3482,9 @@ fn expr(ptr[Gen] g, ast.Node e) -> Val {
|
|||
if k == "match" {
|
||||
return matchGen(g, e, true)
|
||||
}
|
||||
if k == "ifvalue" {
|
||||
return ifValue(g, e, T("void"))
|
||||
}
|
||||
if k == "try" {
|
||||
return tryExpr(g, e)
|
||||
}
|
||||
|
|
@ -3840,6 +3892,12 @@ fn ref(ptr[Gen] g, ast.Node e, bool write) -> Ref {
|
|||
}
|
||||
return Ref(isPlace: true, pl: Place(addr: "@" + e.name, ty: gt))
|
||||
}
|
||||
if id < 0 && findConst(g, g.module, e.name) >= 0 {
|
||||
if write {
|
||||
fail(g, e, "'" + e.name + "' is a constant; it can't be changed")
|
||||
}
|
||||
return Ref(isPlace: false, val: constValue(g, g.module, e.name, e))
|
||||
}
|
||||
if id < 0 {
|
||||
if imports(g, e.name) {
|
||||
fail(g, e, "'" + e.name + "' is a module; use something from it, like " + e.name + ".name(...)")
|
||||
|
|
@ -3878,13 +3936,76 @@ fn llFloat(str text) -> str {
|
|||
return s
|
||||
}
|
||||
|
||||
// ---------- constants ----------
|
||||
|
||||
fn findConst(ptr[Gen] g, str module, str name) -> int {
|
||||
pi := progOf(g, module)
|
||||
if pi < 0 {
|
||||
return -1
|
||||
}
|
||||
for i in 0..g.progs[pi].consts.len {
|
||||
if g.progs[pi].consts[i].name == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// A constant is worked out where it is used, from the code it was written with: numbers,
|
||||
// text, true/false, chars, other constants, enum variants and operators between them.
|
||||
fn constValue(ptr[Gen] g, str module, str name, ast.Node at) -> Val {
|
||||
pi := progOf(g, module)
|
||||
c := g.progs[pi].consts[findConst(g, module, name)]
|
||||
key := module + "." + name
|
||||
if g.constBusy.contains(key) {
|
||||
fail(g, at, "the constant '" + name + "' is worked out from itself")
|
||||
}
|
||||
checkConst(g, c.kids[0])
|
||||
g.constBusy.push(key)
|
||||
savedScopes := g.scopes
|
||||
savedModule := g.module
|
||||
[]Scope none
|
||||
g.scopes = none
|
||||
pushScope(g)
|
||||
g.module = module
|
||||
v := Val()
|
||||
if c.flag {
|
||||
t := resolve(g, c.ty, c)
|
||||
v = coerce(g, exprWant(g, c.kids[0], t), t, c.kids[0], "the constant '" + name + "'")
|
||||
} else {
|
||||
v = expr(g, c.kids[0])
|
||||
}
|
||||
g.module = savedModule
|
||||
g.scopes = savedScopes
|
||||
g.constBusy.pop()
|
||||
noteRef(g, at, at.col, name.len, "```finch\nconst " + ast.show(v.ty) + " " + name + "\n```", g.files[c.file], c.line, c.col)
|
||||
return v
|
||||
}
|
||||
|
||||
fn checkConst(ptr[Gen] g, ast.Node e) {
|
||||
k := e.kind
|
||||
if ["int", "float", "char", "str", "bool", "var", "unary", "binary", "member"].contains(k) {
|
||||
for kid in e.kids {
|
||||
checkConst(g, kid)
|
||||
}
|
||||
return
|
||||
}
|
||||
if k == "call" && numType(e.name).len > 0 {
|
||||
for kid in e.kids {
|
||||
checkConst(g, kid)
|
||||
}
|
||||
return // f32(0.5), u8(255)
|
||||
}
|
||||
fail(g, e, "a constant is made of numbers, text, true/false, chars and other constants (not calls or variables)")
|
||||
}
|
||||
|
||||
// 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 findEnum(g, modFor(g, n.kids[0].name), n.name)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
|
@ -3895,7 +4016,7 @@ fn sumNamed(ptr[Gen] g, ast.Node n) -> int {
|
|||
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 findSum(g, modFor(g, n.kids[0].name), n.name)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
|
@ -3997,6 +4118,13 @@ fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref {
|
|||
}
|
||||
return Ref(isPlace: false, val: enumValue(g, m, ei))
|
||||
}
|
||||
mod := m.kids[0]
|
||||
if mod.kind == "var" && lookup(g, mod.name) < 0 && imports(g, mod.name) && findConst(g, modFor(g, mod.name), m.name) >= 0 {
|
||||
if write {
|
||||
fail(g, m, "'" + mod.name + "." + m.name + "' is a constant; it can't be changed")
|
||||
}
|
||||
return Ref(isPlace: false, val: constValue(g, modFor(g, mod.name), m.name, m)) // shapes.SIDES
|
||||
}
|
||||
obj := ref(g, m.kids[0], write)
|
||||
t := obj.val.ty
|
||||
if obj.isPlace {
|
||||
|
|
@ -5198,6 +5326,9 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
|||
if si >= 0 {
|
||||
return construct(g, si, c.kids, c.names, c)
|
||||
}
|
||||
if ["min", "max", "abs", "clamp"].contains(n) {
|
||||
return mathCall(g, c) // only when nothing of yours (or C's abs) has the name
|
||||
}
|
||||
if lookup(g, n) >= 0 {
|
||||
fail(g, c, "'" + n + "' is a variable, not a function")
|
||||
}
|
||||
|
|
@ -5205,6 +5336,75 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
|||
return 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
|
||||
for nm in c.names {
|
||||
if nm.len > 0 {
|
||||
fail(g, c, "named arguments (" + nm + ": ...) are only for creating structs")
|
||||
}
|
||||
}
|
||||
[]Val vals
|
||||
for a in c.kids {
|
||||
v := expr(g, a)
|
||||
if !isNumber(v.ty) {
|
||||
fail(g, a, n + "(...) is for numbers, but this is " + ast.show(v.ty))
|
||||
}
|
||||
vals.push(v)
|
||||
}
|
||||
if n == "abs" {
|
||||
checkArgs(g, c, 0, 1, n)
|
||||
x := vals[0]
|
||||
if isUnsigned(x.ty) {
|
||||
return x
|
||||
}
|
||||
zero := "0"
|
||||
if isFloat(x.ty) {
|
||||
zero = "0.0"
|
||||
}
|
||||
neg := arith(g, "-", Val(v: zero, ty: x.ty, lit: true), x, c)
|
||||
below := compare(g, "<", x, Val(v: zero, ty: x.ty, lit: true), c)
|
||||
return Val(v: op(g, "select i1 " + below.v + ", " + llty(g, x.ty) + " " + neg.v + ", " + llty(g, x.ty) + " " + x.v), ty: x.ty)
|
||||
}
|
||||
if n == "clamp" {
|
||||
checkArgs(g, c, 0, 3, n)
|
||||
low := pick(g, "<", vals[0], vals[2], c) // min(x, hi)
|
||||
return pick(g, ">", low, vals[1], c) // max(that, lo)
|
||||
}
|
||||
if vals.len < 2 {
|
||||
fail(g, c, "'" + n + "' takes two or more numbers")
|
||||
}
|
||||
o := "<"
|
||||
if n == "max" {
|
||||
o = ">"
|
||||
}
|
||||
out := vals[0]
|
||||
for i in 1..vals.len {
|
||||
out = pick(g, o, out, vals[i], c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// the smaller (o "<") or bigger (o ">") of two numbers, in the type both fit
|
||||
fn pick(ptr[Gen] g, str o, Val a, Val b, ast.Node at) -> Val {
|
||||
l := a
|
||||
r := b
|
||||
if !unify(g, addr(l), addr(r)) {
|
||||
badOperands(g, o, a, b, at)
|
||||
}
|
||||
if l.lit && r.lit && l.ty.kind == "int" {
|
||||
x := int(l.v) or 0
|
||||
y := int(r.v) or 0
|
||||
if (o == "<") == (x < y) {
|
||||
return l
|
||||
}
|
||||
return r
|
||||
}
|
||||
first := compare(g, o, l, r, at)
|
||||
t := llty(g, l.ty)
|
||||
return Val(v: op(g, "select i1 " + first.v + ", " + t + " " + l.v + ", " + t + " " + r.v), ty: l.ty)
|
||||
}
|
||||
|
||||
fn callFn(ptr[Gen] g, int fi, ast.Node c, int first, str shown, str selfAddr, str selfRoot) -> Val {
|
||||
sig := g.sigs[fi]
|
||||
if sig.module.len == 0 && sig.name == "main" {
|
||||
|
|
@ -5375,13 +5575,15 @@ fn method(ptr[Gen] g, ast.Node m) -> Val {
|
|||
if !imports(g, mn) {
|
||||
fail(g, objNode, "there is no variable or module named '" + mn + "' (to use a module, add import " + mn + " at the top)")
|
||||
}
|
||||
shown := mn
|
||||
mn = modFor(g, mn)
|
||||
fi := findFn(g, mn, m.name)
|
||||
if fi >= 0 {
|
||||
return callFn(g, fi, m, 1, mn + "." + m.name, "", "")
|
||||
return callFn(g, fi, m, 1, shown + "." + m.name, "", "")
|
||||
}
|
||||
mti := findTemplate(g, mn, m.name, "", false)
|
||||
if mti >= 0 {
|
||||
return genericCall(g, m, mti, 1, mn + "." + m.name)
|
||||
return genericCall(g, m, mti, 1, shown + "." + m.name)
|
||||
}
|
||||
mei := findEnum(g, mn, m.name)
|
||||
if mei >= 0 {
|
||||
|
|
@ -5395,7 +5597,7 @@ fn method(ptr[Gen] g, ast.Node m) -> Val {
|
|||
}
|
||||
return construct(g, si, args, m.names, m)
|
||||
}
|
||||
fail(g, m, "the module '" + mn + "' has no function or struct named '" + m.name + "'")
|
||||
fail(g, m, "the module '" + shown + "' has no function or struct named '" + m.name + "'")
|
||||
}
|
||||
changing := ["push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"]
|
||||
r := ref(g, objNode, changing.contains(m.name))
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ fn isHex(char c) -> bool {
|
|||
|
||||
fn isKeyword(str w) -> bool {
|
||||
keywords := ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false",
|
||||
"null", "import", "link", "struct", "defer", "or", "try", "enum", "match", "export"]
|
||||
"null", "import", "link", "struct", "defer", "or", "try", "enum", "match", "export", "const"]
|
||||
return keywords.contains(w)
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +78,57 @@ fn operator(ptr[Lexer] lx) -> str {
|
|||
return ""
|
||||
}
|
||||
|
||||
// """ text over several lines """: the line break after the opening quotes is dropped, and so is
|
||||
// the indentation of the closing quotes, from every line (the text can be indented with the code).
|
||||
fn longString(ptr[Lexer] lx, Token t) -> str {
|
||||
for k in 0..3 {
|
||||
advance(lx)
|
||||
}
|
||||
[]str parts
|
||||
while !(peek(lx, 0) == '"' && peek(lx, 1) == '"' && peek(lx, 2) == '"') {
|
||||
if lx.i >= lx.src.len {
|
||||
fail(lx, t.line, t.col, "text is never closed (missing \"\"\")")
|
||||
}
|
||||
ch := peek(lx, 0)
|
||||
advance(lx)
|
||||
if ch == '\\' {
|
||||
e := peek(lx, 0)
|
||||
advance(lx)
|
||||
ch = escape(lx, e)
|
||||
} else if ch == '\r' {
|
||||
continue
|
||||
}
|
||||
parts.push(str(ch))
|
||||
}
|
||||
for k in 0..3 {
|
||||
advance(lx)
|
||||
}
|
||||
lx.nl = false // the line breaks inside were text, not the end of a statement
|
||||
body := parts.join("")
|
||||
if body.starts_with("\n") {
|
||||
body = body.sub(1, body.len)
|
||||
}
|
||||
last := body.split("\n")
|
||||
indent := last[last.len - 1]
|
||||
if last.len > 1 && indent.trim().len == 0 {
|
||||
// the closing quotes are on their own line: their indentation goes from every line
|
||||
[]str lines
|
||||
for i in 0..last.len - 1 {
|
||||
line := last[i]
|
||||
if line.starts_with(indent) {
|
||||
line = line.sub(indent.len, line.len)
|
||||
} else if line.trim().len > 0 {
|
||||
fail(lx, t.line, t.col, "a line of this text is indented less than the closing \"\"\"")
|
||||
} else {
|
||||
line = ""
|
||||
}
|
||||
lines.push(line)
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
fn escape(ptr[Lexer] lx, char e) -> char {
|
||||
if e == 'n' {
|
||||
return '\n'
|
||||
|
|
@ -185,6 +236,9 @@ fn lex(str src, str path) -> []Token {
|
|||
if mant.len > 0 {
|
||||
t.text = (mant + lx.src.sub(expStart, lx.i)).replace("_", "") // LLVM wants the '.'
|
||||
}
|
||||
} else if c == '"' && peek(lx, 1) == '"' && peek(lx, 2) == '"' {
|
||||
t.kind = "string"
|
||||
t.text = longString(lx, t)
|
||||
} else if c == '"' || c == '\'' {
|
||||
quote := c
|
||||
t.kind = "string"
|
||||
|
|
|
|||
|
|
@ -104,9 +104,9 @@ fn load(ptr[Loader] l, str path, str module) {
|
|||
prog := parser.parse(lexer.lex(src, path), file, path)
|
||||
prog.module = module
|
||||
prog.path = path
|
||||
imports := prog.imports
|
||||
modules := prog.modules
|
||||
l.progs.push(prog)
|
||||
for name in imports {
|
||||
for name in modules {
|
||||
if !l.loaded.contains(name) {
|
||||
l.loaded.push(name)
|
||||
load(l, findModule(l, path, name), name)
|
||||
|
|
|
|||
|
|
@ -236,7 +236,23 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
|||
if !at(p, "ident") {
|
||||
fail(p, t, "expected a module name or a C header after 'import'")
|
||||
}
|
||||
prog.imports.push(next(p).text)
|
||||
// import ui/button (a folder), import path as p (another name here)
|
||||
key := next(p).text
|
||||
name := key
|
||||
while at(p, "/") && !cur(p).nl {
|
||||
next(p)
|
||||
name = expect(p, "ident", "a module name after '/'").text
|
||||
key = key + "/" + name
|
||||
}
|
||||
if at(p, "ident") && cur(p).text == "as" && !cur(p).nl {
|
||||
next(p)
|
||||
name = expect(p, "ident", "the name to use for the module, like import path as p").text
|
||||
}
|
||||
if prog.imports.contains(name) {
|
||||
fail(p, t, "the name '" + name + "' is already used by another import (pick one with: import " + key + " as other)")
|
||||
}
|
||||
prog.imports.push(name)
|
||||
prog.modules.push(key)
|
||||
}
|
||||
} else if at(p, "link") {
|
||||
lt := next(p)
|
||||
|
|
@ -248,6 +264,8 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
|||
prog.links.push(lib)
|
||||
} else if at(p, "struct") {
|
||||
prog.structs.push(parseStruct(p))
|
||||
} else if at(p, "const") {
|
||||
prog.consts.push(parseConst(p))
|
||||
} else if at(p, "enum") {
|
||||
e := parseEnum(p)
|
||||
prog.enums.push(e)
|
||||
|
|
@ -261,6 +279,24 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
|
|||
return prog
|
||||
}
|
||||
|
||||
// const WIDTH = 640 const f32 GRAVITY = 9.8 const TITLE = "Game"
|
||||
fn parseConst(ptr[Parser] p) -> ast.Node {
|
||||
c := node(p, "const")
|
||||
expect(p, "const", "'const'")
|
||||
if !(at(p, "ident") && peekKind(p, 1) == "=") {
|
||||
c.ty = parseType(p)
|
||||
c.flag = true
|
||||
}
|
||||
t := expect(p, "ident", "the constant's name, like const WIDTH = 640")
|
||||
c.name = t.text
|
||||
c.line = t.line
|
||||
c.col = t.col
|
||||
expect(p, "=", "'=' and its value, like const WIDTH = 640")
|
||||
c.kids.push(parseExpr(p))
|
||||
endOfStatement(p)
|
||||
return c
|
||||
}
|
||||
|
||||
// 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.
|
||||
// A variant can hold values: enum Shape { Circle(float), Rect(float, float), Point } (e.flag is then true).
|
||||
|
|
@ -433,7 +469,7 @@ fn parseFn(ptr[Parser] p) -> ast.Node {
|
|||
if at(p, "ident") && isTypeName(cur(p).text) {
|
||||
fail(p, cur(p), "functions start with 'fn', like: fn add(int a, int b) -> int { ... }")
|
||||
}
|
||||
expect(p, "fn", "'fn', 'struct' or 'import'")
|
||||
expect(p, "fn", "'fn', 'struct', 'enum', 'const' or 'import'")
|
||||
f.name = expect(p, "ident", "a function name").text
|
||||
if at(p, "[") {
|
||||
f.tparams = parseTParams(p) // fn max[T](...) or the struct's, in fn Stack[T].push(...)
|
||||
|
|
@ -612,6 +648,33 @@ fn parseIf(ptr[Parser] p) -> ast.Node {
|
|||
|
||||
// ---------- expressions ----------
|
||||
|
||||
// if as a value: x := if a > b { a } else { b } (each branch is one value; the else is needed)
|
||||
fn parseIfValue(ptr[Parser] p) -> ast.Node {
|
||||
e := node(p, "ifvalue")
|
||||
expect(p, "if", "'if'")
|
||||
e.kids.push(parseExpr(p))
|
||||
e.kids.push(branchValue(p))
|
||||
if !accept(p, "else") {
|
||||
fail(p, cur(p), "an if that gives a value needs an else: if c { a } else { b }")
|
||||
}
|
||||
if at(p, "if") {
|
||||
e.kids.push(parseIfValue(p))
|
||||
} else {
|
||||
e.kids.push(branchValue(p))
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
fn branchValue(ptr[Parser] p) -> ast.Node {
|
||||
expect(p, "{", "'{'")
|
||||
saved := p.depth
|
||||
p.depth = 1
|
||||
v := parseExpr(p)
|
||||
p.depth = saved
|
||||
expect(p, "}", "'}' (a branch of an if that gives a value holds just that value)")
|
||||
return v
|
||||
}
|
||||
|
||||
fn binaryLevel(str op) -> int {
|
||||
if op == "||" {
|
||||
return 1
|
||||
|
|
@ -769,6 +832,9 @@ fn parsePrimary(ptr[Parser] p) -> ast.Node {
|
|||
if k == "match" {
|
||||
return parseMatch(p, true)
|
||||
}
|
||||
if k == "if" {
|
||||
return parseIfValue(p)
|
||||
}
|
||||
if k == "[" {
|
||||
next(p)
|
||||
e.kind = "array"
|
||||
|
|
|
|||
Loading…
Reference in a new issue