Track types in the compiler. Maintain a parallel array of type flags for globals and locals. When c_factor() encounters a float variable or float literal, set a flag. When c_term() / c_add_sub() emit arithmetic, choose based on the flag:
var c_expr_is_float: bool = false;
fn c_factor() void {
// Float literal
if (cur() >= '0' and cur() <= '9') {
const val: i64 = number();
if (cur() == '.' and pos + 1 < source.len and source[pos + 1] >= '0') {
// Parse float, emit f64.const
c_expr_is_float = true;
emit_str(" f64.const ");
emit_num(val);
emit_byte('.');
pos += 1;
while (cur() >= '0' and cur() <= '9') {
emit_byte(cur());
pos += 1;
}
emit_byte('\n');
skip();
return;
}
emit_const(val);
return;
}
// Variable reference
if (is_letter(cur())) {
const name: []const u8 = read_name();
if (c_is_float(name)) { c_expr_is_float = true; }
// ... emit local.get/global.get as before
}
}
fn c_term() void {
c_factor();
while (cur() == '*' or cur() == '/' or cur() == '%') {
const op: u8 = cur();
pos += 1; skip();
c_factor();
if (c_expr_is_float) {
if (op == '*') { emit_op("f64.mul"); }
else if (op == '/') { emit_op("f64.div"); }
} else {
if (op == '*') { emit_op("i64.mul"); }
else if (op == '/') { emit_op("i64.div_s"); }
else { emit_op("i64.rem_s"); }
}
}
}
Same pattern for c_add_sub() and c_comparison().
For local declarations, emit the right type:
if (c_is_float(local_names[i])) {
emit_str(" (local $"); emit_str(local_names[i]); emit_str(" f64)\n");
} else {
emit_str(" (local $"); emit_str(local_names[i]); emit_str(" i64)\n");
}
For globals:
emit_str("(global $"); emit_str(name);
if (is_float) {
emit_str(" (mut f64) (f64.const 0))\n");
} else {
emit_str(" (mut i64) (i64.const 0))\n");
}