mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
Struct defaults apply inside structs; exit() ends an or-block; 1e9 literals; x * 2.0 stays f32; floats print as the shortest exact text; str(x, digits)
Also frees main's args and the message of a failing main (valgrind was not clean on two tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b24a7845ef
commit
6142e47e71
10 changed files with 207 additions and 10 deletions
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -153,12 +154,50 @@ void finch_str_from_uint(FStr *out, uint64_t v) {
|
|||
str_heap(out, buf, n);
|
||||
}
|
||||
|
||||
// The shortest text that reads back as the same number (as an f32 when `single`):
|
||||
// 0.1 -> "0.1", 1.0/3 -> "0.3333333333333333", 1e8 -> "100000000", 1e20 -> "1e+20".
|
||||
static int fmt_float(char *buf, size_t size, double v, int single) {
|
||||
if (isnan(v)) return snprintf(buf, size, "nan");
|
||||
if (isinf(v)) return snprintf(buf, size, v < 0 ? "-inf" : "inf");
|
||||
int p = 1;
|
||||
for (; p < 17; p++) {
|
||||
snprintf(buf, size, "%.*e", p - 1, v);
|
||||
double back = strtod(buf, NULL);
|
||||
if (single ? (float)back == (float)v : back == v) break;
|
||||
}
|
||||
int exp = atoi(strchr(buf, 'e') + 1);
|
||||
if (exp < -5 || exp >= 17) return snprintf(buf, size, "%.*g", p, v);
|
||||
int decimals = p - 1 - exp;
|
||||
return snprintf(buf, size, "%.*f", decimals > 0 ? decimals : 0, v);
|
||||
}
|
||||
|
||||
void finch_str_from_float(FStr *out, double v) {
|
||||
char buf[64];
|
||||
int n = snprintf(buf, sizeof buf, "%g", v);
|
||||
int n = fmt_float(buf, sizeof buf, v, 0);
|
||||
str_heap(out, buf, n);
|
||||
}
|
||||
|
||||
void finch_str_from_f32(FStr *out, double v) {
|
||||
char buf[64];
|
||||
int n = fmt_float(buf, sizeof buf, v, 1);
|
||||
str_heap(out, buf, n);
|
||||
}
|
||||
|
||||
// str(x, 2): a fixed number of digits after the point
|
||||
void finch_str_fixed(FStr *out, double v, int64_t digits) {
|
||||
char buf[400];
|
||||
if (digits < 0) digits = 0;
|
||||
if (digits > 60) digits = 60;
|
||||
int n = snprintf(buf, sizeof buf, "%.*f", (int)digits, v);
|
||||
str_heap(out, buf, n);
|
||||
}
|
||||
|
||||
void finch_print_float(double v, int32_t single) {
|
||||
char buf[64];
|
||||
fmt_float(buf, sizeof buf, v, single);
|
||||
fputs(buf, stdout);
|
||||
}
|
||||
|
||||
void finch_str_from_char(FStr *out, char c) { str_heap(out, &c, 1); }
|
||||
|
||||
void finch_str_from_bool(FStr *out, int b) { str_static(out, b ? "true" : "false"); }
|
||||
|
|
@ -242,6 +281,7 @@ static void file_error(FStr *err, const char *what, const FStr *path) {
|
|||
void finch_report_error(const FStr *msg) {
|
||||
fflush(stdout);
|
||||
fprintf(stderr, "error: %.*s\n", (int)msg->len, msg->ptr ? msg->ptr : "");
|
||||
finch_str_drop((FStr *)msg);
|
||||
}
|
||||
|
||||
void finch_str_sub(FStr *out, const FStr *s, int64_t start, int64_t end, const char *file, int64_t line) {
|
||||
|
|
@ -399,7 +439,14 @@ static void buf_fmt(FStr *b, const char *fmt, ...) {
|
|||
|
||||
void finch_buf_int(FStr *b, int64_t v) { buf_fmt(b, "%lld", (long long)v); }
|
||||
void finch_buf_uint(FStr *b, uint64_t v) { buf_fmt(b, "%llu", (unsigned long long)v); }
|
||||
void finch_buf_float(FStr *b, double v) { buf_fmt(b, "%g", v); }
|
||||
void finch_buf_float(FStr *b, double v) {
|
||||
char t[64];
|
||||
finch_buf_add(b, t, fmt_float(t, sizeof t, v, 0));
|
||||
}
|
||||
void finch_buf_f32(FStr *b, double v) {
|
||||
char t[64];
|
||||
finch_buf_add(b, t, fmt_float(t, sizeof t, v, 1));
|
||||
}
|
||||
void finch_buf_bool(FStr *b, int v) { finch_buf_add(b, v ? "true" : "false", v ? 4 : 5); }
|
||||
void finch_buf_ptr(FStr *b, const void *p) {
|
||||
if (p) buf_fmt(b, "0x%llx", (unsigned long long)(uintptr_t)p);
|
||||
|
|
@ -498,6 +545,12 @@ void finch_args(FArr *out, int argc, char **argv) {
|
|||
for (int i = 0; i < argc; i++) str_heap((FStr *)out->ptr + i, argv[i], (int64_t)strlen(argv[i]));
|
||||
}
|
||||
|
||||
// after main: the arguments go
|
||||
void finch_args_free(FArr *a) {
|
||||
for (int64_t i = 0; i < a->len; i++) finch_str_drop((FStr *)a->ptr + i);
|
||||
finch_arr_free(a);
|
||||
}
|
||||
|
||||
// input("Name? ") prints the prompt and reads one line, without the newline.
|
||||
void finch_input(FStr *out, const FStr *prompt) {
|
||||
if (prompt->len) fwrite(prompt->ptr, 1, (size_t)prompt->len, stdout);
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
68
src/gen.fch
68
src/gen.fch
|
|
@ -1020,6 +1020,21 @@ fn own(ptr[Gen] g, Val v) -> str {
|
|||
|
||||
// ---------- printing ----------
|
||||
|
||||
// f32 prints the shortest text that is the same f32 (0.1, not 0.100000001)
|
||||
fn floatName(ast.Type t) -> str {
|
||||
if t.kind == "f32" {
|
||||
return "f32"
|
||||
}
|
||||
return "float"
|
||||
}
|
||||
|
||||
fn single(ast.Type t) -> str {
|
||||
if t.kind == "f32" {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
fn printf(ptr[Gen] g, str fmt, str args) {
|
||||
f := strGlobal(g, fmt)
|
||||
t := tmpName(g)
|
||||
|
|
@ -1044,7 +1059,7 @@ fn emitPrint(ptr[Gen] g, str v, ast.Type t, bool quoted) {
|
|||
} else if isUnsigned(t) {
|
||||
printf(g, "%llu", "i64 " + intCast(g, v, t, T("u64")))
|
||||
} else if isFloat(t) {
|
||||
printf(g, "%g", "double " + floatCast(g, v, t, T("float")))
|
||||
emit(g, "call void @finch_print_float(double " + floatCast(g, v, t, T("float")) + ", i32 " + single(t) + ")")
|
||||
} else if k == "char" {
|
||||
z := op(g, "sext i8 " + v + " to i32")
|
||||
if quoted {
|
||||
|
|
@ -1106,7 +1121,7 @@ fn emitFormat(ptr[Gen] g, str buf, str v, ast.Type t, bool quoted) {
|
|||
} else if isUnsigned(t) {
|
||||
emit(g, "call void @finch_buf_uint(ptr " + buf + ", i64 " + intCast(g, v, t, T("u64")) + ")")
|
||||
} else if isFloat(t) {
|
||||
emit(g, "call void @finch_buf_float(ptr " + buf + ", double " + floatCast(g, v, t, T("float")) + ")")
|
||||
emit(g, "call void @finch_buf_" + floatName(t) + "(ptr " + buf + ", double " + floatCast(g, v, t, T("float")) + ")")
|
||||
} else if k == "char" {
|
||||
emit(g, "call void @finch_buf_char(ptr " + buf + ", i8 " + v + ", i32 " + q + ")")
|
||||
} else if k == "bool" {
|
||||
|
|
@ -2254,7 +2269,7 @@ fn block(ptr[Gen] g, ast.Node b, bool scope) {
|
|||
}
|
||||
for s in b.kids {
|
||||
if g.term {
|
||||
fail(g, s, "this code can never run (it comes after return, break or continue)")
|
||||
fail(g, s, "this code can never run (it comes after return, break, continue or exit)")
|
||||
}
|
||||
stmt(g, s)
|
||||
}
|
||||
|
|
@ -2357,6 +2372,12 @@ fn hasDefaults(ptr[Gen] g, ast.Type t) -> bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
// a field that is itself a struct with defaults gets them too
|
||||
for ft in sd.ftypes {
|
||||
if ft.kind == "struct" && hasDefaults(g, ft) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -3492,6 +3513,15 @@ fn unify(ptr[Gen] g, ptr[Val] l, ptr[Val] r) -> bool {
|
|||
return true
|
||||
}
|
||||
if isFloat(l.ty) && isFloat(r.ty) {
|
||||
// a number written in the code adapts to an f32 on the other side: x * 2.0 stays f32
|
||||
if l.ty.kind == "f32" && r.lit {
|
||||
r.value = Val(v: floatCast(g, r.v, r.ty, l.ty), ty: l.ty)
|
||||
return true
|
||||
}
|
||||
if r.ty.kind == "f32" && l.lit {
|
||||
l.value = Val(v: floatCast(g, l.v, l.ty, r.ty), ty: r.ty)
|
||||
return true
|
||||
}
|
||||
if l.ty.kind == "f32" {
|
||||
l.value = Val(v: floatCast(g, l.v, l.ty, T("float")), ty: T("float"))
|
||||
}
|
||||
|
|
@ -4872,6 +4902,19 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
|||
return Val(v: pl.addr, ty: ast.ptrTo(pl.ty))
|
||||
}
|
||||
if n == "str" {
|
||||
if c.kids.len == 2 {
|
||||
// str(x, 2): a float with that many digits after the point
|
||||
checkArgs(g, c, 0, 2, n)
|
||||
x := expr(g, c.kids[0])
|
||||
if !isNumber(x.ty) {
|
||||
fail(g, c.kids[0], "str(x, digits) is for numbers, but this is " + ast.show(x.ty))
|
||||
}
|
||||
x = coerce(g, x, T("float"), c.kids[0], "the number")
|
||||
d := coerce(g, expr(g, c.kids[1]), T("int"), c.kids[1], "the number of digits")
|
||||
fixed := strOut(g)
|
||||
emit(g, "call void @finch_str_fixed(ptr " + fixed + ", double " + x.v + ", i64 " + d.v + ")")
|
||||
return Val(v: load(g, "{ ptr, i64, i64 }", fixed), ty: T("str"), fresh: true)
|
||||
}
|
||||
checkArgs(g, c, 0, 1, n)
|
||||
v := expr(g, c.kids[0])
|
||||
out := strOut(g)
|
||||
|
|
@ -4884,7 +4927,7 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
|||
} else if isUnsigned(v.ty) {
|
||||
emit(g, "call void @finch_str_from_uint(ptr " + out + ", i64 " + intCast(g, v.v, v.ty, T("u64")) + ")")
|
||||
} else if isFloat(v.ty) {
|
||||
emit(g, "call void @finch_str_from_float(ptr " + out + ", double " + floatCast(g, v.v, v.ty, T("float")) + ")")
|
||||
emit(g, "call void @finch_str_from_" + floatName(v.ty) + "(ptr " + out + ", double " + floatCast(g, v.v, v.ty, T("float")) + ")")
|
||||
} else if k == "char" {
|
||||
emit(g, "call void @finch_str_from_char(ptr " + out + ", i8 signext " + v.v + ")")
|
||||
} else if k == "bool" {
|
||||
|
|
@ -4998,6 +5041,8 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
|||
t := op(g, "trunc i64 " + code.v + " to i32")
|
||||
f := op(g, "call i32 @fflush(ptr null)")
|
||||
emit(g, "call void @exit(i32 " + t + ")")
|
||||
emit(g, "unreachable")
|
||||
g.term = true // like return: an or-block may end with it
|
||||
return Val(ty: T("void"))
|
||||
}
|
||||
if n == "input" {
|
||||
|
|
@ -5200,6 +5245,8 @@ fn construct(ptr[Gen] g, int si, []ast.Node args, []str names, ast.Node at) -> V
|
|||
g.tnames = savedTN
|
||||
g.targs = savedTA
|
||||
g.scopes = savedScopes
|
||||
} else if ft.kind == "struct" && hasDefaults(g, ft) {
|
||||
fv = Val(v: defaultVal(g, ft, at), ty: ft, fresh: owning(g, ft))
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
|
@ -5716,6 +5763,10 @@ fn runtimeDecls() -> str {
|
|||
"declare void @finch_str_from_int(ptr, i64)",
|
||||
"declare void @finch_str_from_uint(ptr, i64)",
|
||||
"declare void @finch_str_from_float(ptr, double)",
|
||||
"declare void @finch_str_from_f32(ptr, double)",
|
||||
"declare void @finch_str_fixed(ptr, double, i64)",
|
||||
"declare void @finch_print_float(double, i32)",
|
||||
"declare void @finch_buf_f32(ptr, double)",
|
||||
"declare void @finch_str_from_char(ptr, i8 signext)",
|
||||
"declare void @finch_str_from_bool(ptr, i32)",
|
||||
"declare i64 @finch_str_to_int(ptr, ptr, i64)",
|
||||
|
|
@ -5747,6 +5798,7 @@ fn runtimeDecls() -> str {
|
|||
"declare void @finch_arr_remove_gap(ptr, i64, i64)",
|
||||
"declare void @qsort(ptr, i64, i64, ptr)",
|
||||
"declare void @finch_args(ptr, i32, ptr)",
|
||||
"declare void @finch_args_free(ptr)",
|
||||
"declare void @finch_input(ptr, ptr)",
|
||||
"declare i32 @finch_file_exists(ptr)",
|
||||
"declare i32 @finch_str_try_int(ptr, ptr, ptr)",
|
||||
|
|
@ -5846,6 +5898,10 @@ fn generate([]ast.Program progs, []str files, cimport.CImports ci, Options opt)
|
|||
}
|
||||
callText = callText + ")"
|
||||
ret := " ret i32 0\n"
|
||||
after := ""
|
||||
if m.params.len == 1 {
|
||||
after = " call void @finch_args_free(ptr %args)\n"
|
||||
}
|
||||
if m.fallible {
|
||||
// fn main() -> !: a failure is printed, and the exit code is 1
|
||||
callText = "%r = " + callText
|
||||
|
|
@ -5854,7 +5910,7 @@ fn generate([]ast.Program progs, []str files, cimport.CImports ci, Options opt)
|
|||
rt := retTy(g, m)
|
||||
pre = pre + " %ok = extractvalue " + rt + " %r, 0\n br i1 %ok, label %main.ok, label %main.bad\nmain.bad:\n"
|
||||
pre = pre + " %m = extractvalue " + rt + " %r, 2\n %mp = alloca { ptr, i64, i64 }\n store { ptr, i64, i64 } %m, ptr %mp\n"
|
||||
pre = pre + " call void @finch_report_error(ptr %mp)\n ret i32 1\nmain.ok:\n"
|
||||
pre = pre + " call void @finch_report_error(ptr %mp)\n" + after + " ret i32 1\nmain.ok:\n"
|
||||
if m.ret.kind == "int" {
|
||||
ret = " %v = extractvalue " + rt + " %r, 1\n %code = trunc i64 %v to i32\n ret i32 %code\n"
|
||||
}
|
||||
|
|
@ -5862,6 +5918,6 @@ fn generate([]ast.Program progs, []str files, cimport.CImports ci, Options opt)
|
|||
callText = "%r = " + callText
|
||||
ret = " %code = trunc i64 %r to i32\n ret i32 %code\n"
|
||||
}
|
||||
g.out.push("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n" + pre + " " + callText + "\n" + ret + "}\n")
|
||||
g.out.push("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n" + pre + " " + callText + "\n" + after + ret + "}\n")
|
||||
return assemble(g)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,8 @@ fn lex(str src, str path) -> []Token {
|
|||
}
|
||||
} else if isDigit(c) {
|
||||
t.kind = "int"
|
||||
mant := ""
|
||||
expStart := 0
|
||||
if c == '0' && (peek(lx, 1) == 'x' || peek(lx, 1) == 'X') {
|
||||
advance(lx)
|
||||
advance(lx)
|
||||
|
|
@ -161,8 +163,28 @@ fn lex(str src, str path) -> []Token {
|
|||
advance(lx)
|
||||
}
|
||||
}
|
||||
// an exponent: 1e9, 2.5e-3
|
||||
e := peek(lx, 0)
|
||||
sign := peek(lx, 1) == '+' || peek(lx, 1) == '-'
|
||||
if (e == 'e' || e == 'E') && (isDigit(peek(lx, 1)) || (sign && isDigit(peek(lx, 2)))) {
|
||||
if t.kind == "int" {
|
||||
t.kind = "float"
|
||||
mant = lx.src.sub(start, lx.i) + ".0"
|
||||
}
|
||||
expStart = lx.i
|
||||
advance(lx)
|
||||
if sign {
|
||||
advance(lx)
|
||||
}
|
||||
while lx.i < lx.src.len && isDigit(lx.src[lx.i]) {
|
||||
advance(lx)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.text = lx.src.sub(start, lx.i).replace("_", "")
|
||||
if mant.len > 0 {
|
||||
t.text = (mant + lx.src.sub(expStart, lx.i)).replace("_", "") // LLVM wants the '.'
|
||||
}
|
||||
} else if c == '"' || c == '\'' {
|
||||
quote := c
|
||||
t.kind = "string"
|
||||
|
|
|
|||
43
tests/new/defaults_numbers.fch
Normal file
43
tests/new/defaults_numbers.fch
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// struct defaults apply inside structs too; number literals and float text
|
||||
|
||||
struct Audio {
|
||||
int rate = 48000
|
||||
float volume = 0.8
|
||||
}
|
||||
|
||||
struct Mixer {
|
||||
Audio audio
|
||||
[]Audio extra
|
||||
int voices = 5
|
||||
}
|
||||
|
||||
struct Box[T] {
|
||||
T item
|
||||
Audio audio
|
||||
}
|
||||
|
||||
fn make() -> Mixer {
|
||||
Mixer m
|
||||
return m
|
||||
}
|
||||
|
||||
fn scale(f32 x) -> f32 {
|
||||
return x * 2.0 + 1
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := Mixer()
|
||||
Mixer b
|
||||
c := Mixer(voices: 2)
|
||||
list := [Mixer()]
|
||||
m := ["x": Mixer()]
|
||||
map[str]Mixer counts
|
||||
counts["new"].voices += 1
|
||||
bx := Box[int]()
|
||||
print(a.audio.rate, b.audio.volume, c.audio.rate, c.voices, list[0].audio.rate, m["x"].audio.rate)
|
||||
print(make().audio.rate, counts["new"].audio.rate, counts["new"].voices, bx.audio.rate)
|
||||
|
||||
print(1e3, 2.5e-3, 1E+6, 1_000.5e1, scale(1.5))
|
||||
print(1.0 / 3.0, 0.1 + 0.2, 100000000.0, 1e20, 1e-7, float(7), f32(0.1))
|
||||
print(str(3.14159, 2), str(2, 1), str(f32(0.1)), "x=" + str(0.25), [0.5, 1.25])
|
||||
}
|
||||
5
tests/new/defaults_numbers.out
Normal file
5
tests/new/defaults_numbers.out
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
48000 0.8 48000 2 48000 48000
|
||||
48000 48000 6 48000
|
||||
1000 0.0025 1000000 10005 4
|
||||
0.3333333333333333 0.30000000000000004 100000000 1e+20 1e-07 7 0.1
|
||||
3.14 2.0 0.1 x=0.25 [0.5, 1.25]
|
||||
12
tests/new/exit_or.fch
Normal file
12
tests/new/exit_or.fch
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// exit(...) ends an or-block, like return
|
||||
fn load(str p) -> int! {
|
||||
return error("no such file")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
n := load("a.txt") or {
|
||||
print("stopping:", err)
|
||||
exit(0)
|
||||
}
|
||||
print("never", n)
|
||||
}
|
||||
1
tests/new/exit_or.out
Normal file
1
tests/new/exit_or.out
Normal file
|
|
@ -0,0 +1 @@
|
|||
stopping: no such file
|
||||
5
tests/new/fail/after_exit.fch
Normal file
5
tests/new/fail/after_exit.fch
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// expect: this code can never run (it comes after return, break, continue or exit)
|
||||
fn main() {
|
||||
exit(1)
|
||||
print("x")
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
5
|
||||
(4, 5) of length 6.40312
|
||||
(4, 5) of length 6.4031242374328485
|
||||
Vec(x: 0, y: 0)
|
||||
10 Box(corner: Vec(x: 0, y: 0), size: Vec(x: 2, y: 5))
|
||||
total 3
|
||||
|
|
|
|||
Loading…
Reference in a new issue