feat(lang): numbers float - a module whose decimal literals are float
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e6f565a6c4
commit
3ac0d8d5cb
5 changed files with 36 additions and 0 deletions
|
|
@ -240,6 +240,9 @@ program Shade {
|
||||||
exactly that decimal, not its Q16.16 approximation. With no float in sight it
|
exactly that decimal, not its Q16.16 approximation. With no float in sight it
|
||||||
stays `fixed`, so existing code keeps its meaning. A whole literal expression
|
stays `fixed`, so existing code keeps its meaning. A whole literal expression
|
||||||
(`1.0 / 3.0`) is evaluated in the context's type.
|
(`1.0 / 3.0`) is evaluated in the context's type.
|
||||||
|
- **`numbers float`.** A file that begins with `numbers float` (or has it inside its `program`
|
||||||
|
block) takes bare decimal literals as `float`, not `fixed`; the files it imports inherit the
|
||||||
|
mode (runtime files never do). One line in a package's barrel makes the package float.
|
||||||
- **Promotion.** `int` and `long` promote to the float type of the other operand;
|
- **Promotion.** `int` and `long` promote to the float type of the other operand;
|
||||||
`float` with `double` promotes to `double`.
|
`float` with `double` promotes to `double`.
|
||||||
- **Explicit conversions.** `float(x)`, `double(x)`, `int(x)` (truncates toward
|
- **Explicit conversions.** `float(x)`, `double(x)`, `int(x)` (truncates toward
|
||||||
|
|
|
||||||
9
changes/numbers-float.md
Normal file
9
changes/numbers-float.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
bump: minor
|
||||||
|
type: feat
|
||||||
|
**`numbers float` — a module whose decimal literals are `float`.** A bare `1.5` is `fixed`
|
||||||
|
unless a float is in sight, which is the right default for deterministic code and the wrong
|
||||||
|
one for a renderer or a game whose numbers are all IEEE: `Math.sqrt(2.0)` came out in `fixed`,
|
||||||
|
and every literal needed a `float(...)` round it. A file that says `numbers float` (at the top,
|
||||||
|
or inside a `program` block) gets float literals, and passes the mode on to every non-runtime
|
||||||
|
file it imports, so one line in a barrel or an entry file covers a whole package. Runtime files
|
||||||
|
under `runtime/` are never switched, so the engine's own fixed-point code keeps its meaning.
|
||||||
|
|
@ -1437,6 +1437,11 @@ function emit_call(e: Node) -> Val {
|
||||||
function emit_expr(e: Node) -> Val {
|
function emit_expr(e: Node) -> Val {
|
||||||
if (e == null) { return val("0", "int") }
|
if (e == null) { return val("0", "int") }
|
||||||
if e.kind == E_INT { return val(itoa(e.ival), "int") }
|
if e.kind == E_INT { return val(itoa(e.ival), "int") }
|
||||||
|
if e.kind == E_FLOAT and is_float_file(e.file) {
|
||||||
|
let ff = val(fp_lit_code(e.s, "float"), "float")
|
||||||
|
ff.lit = e
|
||||||
|
return ff
|
||||||
|
}
|
||||||
if e.kind == E_FLOAT { let fv = val(itoa(e.ival), "fixed"); fv.lit = e; return fv }
|
if e.kind == E_FLOAT { let fv = val(itoa(e.ival), "fixed"); fv.lit = e; return fv }
|
||||||
if e.kind == E_PREVAL { return g_prevals[e.ival] }
|
if e.kind == E_PREVAL { return g_prevals[e.ival] }
|
||||||
if e.kind == E_BOOL { return val(itoa(e.ival), "bool") }
|
if e.kind == E_BOOL { return val(itoa(e.ival), "bool") }
|
||||||
|
|
|
||||||
|
|
@ -358,6 +358,7 @@ function static_type(e: Node) -> pointer {
|
||||||
}
|
}
|
||||||
if e.kind == E_STR or e.kind == E_SLICE { return "string" }
|
if e.kind == E_STR or e.kind == E_SLICE { return "string" }
|
||||||
if e.kind == E_INT { return "int" }
|
if e.kind == E_INT { return "int" }
|
||||||
|
if e.kind == E_FLOAT and is_float_file(e.file) { return "float" }
|
||||||
if e.kind == E_FLOAT { return "fixed" }
|
if e.kind == E_FLOAT { return "fixed" }
|
||||||
if e.kind == E_BOOL { return "bool" }
|
if e.kind == E_BOOL { return "bool" }
|
||||||
return null
|
return null
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,18 @@ var g_game_name: pointer # the `program` name
|
||||||
# build tools parse. While parsing, the location is the current token's; while
|
# build tools parse. While parsing, the location is the current token's; while
|
||||||
# lowering, it is the statement being emitted (emit_stmt records it).
|
# lowering, it is the statement being emitted (emit_stmt records it).
|
||||||
var g_parse_file: pointer = "" # the file whose tokens are being parsed
|
var g_parse_file: pointer = "" # the file whose tokens are being parsed
|
||||||
|
# `numbers float`: files whose bare decimal literals are float, not fixed. A file that says
|
||||||
|
# so is listed, and so is every non-runtime file it imports (a barrel passes it on).
|
||||||
|
var g_float_files: []pointer = new []pointer
|
||||||
|
function is_float_file(f: pointer) -> bool {
|
||||||
|
if (f == null) { return false }
|
||||||
|
var i = 0
|
||||||
|
while i < len(g_float_files) {
|
||||||
|
if (g_float_files[i] == f) { return true }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
var g_parsing: bool = true # false once lowering starts
|
var g_parsing: bool = true # false once lowering starts
|
||||||
var g_err_file: pointer = "" # the statement being lowered
|
var g_err_file: pointer = "" # the statement being lowered
|
||||||
var g_err_line: int = 0
|
var g_err_line: int = 0
|
||||||
|
|
@ -816,6 +828,11 @@ function parse_one_decl() -> void {
|
||||||
}
|
}
|
||||||
skipnl()
|
skipnl()
|
||||||
}
|
}
|
||||||
|
if is_id("numbers") and (toks[pi + 1].text == "float") {
|
||||||
|
pi += 2
|
||||||
|
if not is_float_file(g_parse_file) { push(g_float_files, g_parse_file) }
|
||||||
|
return
|
||||||
|
}
|
||||||
if is_id("import") { pi += 1
|
if is_id("import") { pi += 1
|
||||||
let t = toks[pi]
|
let t = toks[pi]
|
||||||
if t.kind != TK_STR { perr("expected \"path\" after import") }
|
if t.kind != TK_STR { perr("expected \"path\" after import") }
|
||||||
|
|
@ -1004,6 +1021,7 @@ function do_import(rel: pointer) -> void {
|
||||||
}
|
}
|
||||||
if already_loaded(full) { return }
|
if already_loaded(full) { return }
|
||||||
push(loaded_paths, full)
|
push(loaded_paths, full)
|
||||||
|
if is_float_file(g_parse_file) and not is_runtime_path(rel) and not is_float_file(full) { push(g_float_files, full) }
|
||||||
if (src == null) { perr(`cannot open import {full}`) }
|
if (src == null) { perr(`cannot open import {full}`) }
|
||||||
# the audio runtime can arrive through atlas.ludic's own import or the Assets
|
# the audio runtime can arrive through atlas.ludic's own import or the Assets
|
||||||
# splice, not only through an Audio.* call in the game; a windowed build must
|
# splice, not only through an Audio.* call in the game; a windowed build must
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue