"""Build Honey first with `bro build`, then run `python3 tests/native.py`.""" import os from pathlib import Path import subprocess import tempfile import textwrap ROOT = Path(__file__).resolve().parents[1] HONEY = ROOT / "build/honey" # Explicit expectations guard against frontend bugs shared by both execution paths. CASES = { "void": ("main proc() void {}", b"", None), "signed_limits": ( "main proc() [2]i64 { return [-9223372036854775808, 9223372036854775807] }", b"[-9223372036854775808, 9223372036854775807]\n", None, ), "float_context": ("main proc() float { return 3 / 2 }", b"1.5\n", None), "float_ieee": ( """ main proc() bool { nan :: 0.0 / 0.0 inf :: 1.0 / 0.0 return nan != nan and !(nan == nan) and !(nan < 0.0) and !(nan >= 0.0) and inf > 1.0 } """, b"true\n", None, ), "float_rounding": ( "main proc() f64 { return 9007199254740993.0 - 9007199254740992 }", b"0\n", None, ), "strings": (r'main proc() string { return "a\0b\n\"\\" }', b'a\0b\n"\\\n', None), "scalar_call_types": ( """ nothing proc() void {} twice proc(x f64) f64 { return x * 2 } select proc(flag bool, left string, right string) string { nothing() if flag { return left } return right } main proc() string { return select(twice(1.5) == 3.0, "yes", "no") } """, b"yes\n", None, ), "mutual_recursion": ( """ even proc(n int) bool { if n == 0 { return true } return odd(n - 1) } odd proc(n int) bool { if n == 0 { return false } return even(n - 1) } main proc() bool { return even(20) and odd(19) } """, b"true\n", None, ), "function_values": ( """ plus proc(n int) int { return n + 1 } minus proc(n int) int { return n - 1 } main proc() bool { f := plus f = minus a :: [plus, f] return a[0](41) == 42 and a[1](43) == 42 } """, b"true\n", None, ), "short_circuit": ( """ cycle bool :: cycle bad proc() bool { return 9223372036854775807 + 1 == 0 } main proc() bool { return !(false and bad()) and (true or cycle) } """, b"true\n", None, ), "lazy_globals": ( """ first :: identity(later) later [2][2]string :: [["one", "two"], ["three", "four"]] identity proc(a [2][2]string) [2][2]string { return a } main proc() [2][2]string { return identity(identity(first)) } """, b"[[one, two], [three, four]]\n", None, ), "array_copies": ( """ seed [2]mut [2]mut i64 :: [[1, 2], [3, 4]] identity proc(a [2]mut [2]mut i64) [2]mut [2]mut i64 { return a } main proc() [2]mut [2]mut i64 { a := identity(seed) b := a (a[0])[1] = 99 a[1] = a[0] return b } """, b"[[1, 2], [3, 4]]\n", None, ), "zero_leaf_arrays": ( """ identity proc(a [2][0]bool) [2][0]bool { return a } main proc() [2][0]bool { return identity([[], []]) } """, b"[[], []]\n", None, ), "array_branchful_arguments": ( """ identity proc(a [2]bool) [2]bool { return a } pair proc(a [2]bool, b [2]bool) [2][2]bool { return [a, b] } main proc() [2][2]bool { return pair(identity([true or false, false and true]), identity([false or true, !false])) } """, b"[[true, false], [true, true]]\n", None, ), "unsigned_max_range": ( """ main proc() int { end usize :: 18446744073709551615 start usize :: 18446744073709551614 count := 0 for start..=end |i| { if (i < start) return 0 count = count + 1 continue } return count } """, b"2\n", None, ), "unsigned_comparisons": ( """ main proc() bool { a usize :: 9223372036854775808 return a > 9223372036854775807 and a * 1 + 1 - 1 == a } """, b"true\n", None, ), "signed_range_limits": ( """ main proc() int { maximum :: 9223372036854775807 count := 0 for maximum..=maximum |_| { count = count + 1 } for 2..1 |_| { count = 0 } for (-2)..0 |_| { count = count + 1 } return count } """, b"3\n", None, ), "loop_array_storage": ( """ main proc() int { n := 0 a [1]mut i64 := [0] while n < 10000 { row [1]mut i64 := [a[0]] row[0] = row[0] + 1 a = row n = n + 1 } return a[0] } """, b"10000\n", None, ), "forward_local": ( "main proc() int {\n before :: after\n after :: 42\n return before\n}", b"", b"local value was read before initialization", ), "forward_assignment": ( "main proc() int {\n after = 1\n after := 42\n return after\n}", b"", b"local value was assigned before initialization", ), "loop_local_reset": ( """ main proc() int { n := 0 while n < 2 { n = n + 1 if (n == 2) return later[0] later [1]i64 :: [42] } return 0 } """, b"", b"local value was read before initialization", ), "global_cycle": ( "a [1]i64 :: b\nb [1]i64 :: a\nmain proc() int { return a[0] }", b"", b"global initialization cycle", ), "bounds": ( "main proc() int {\n a :: [42]\n return a[1]\n}", b"", b"index out of bounds", ), "empty_bounds": ( "main proc() int {\n a [0]i64 :: []\n return a[0]\n}", b"", b"index out of bounds", ), "unsigned_bounds": ( "main proc() int {\n a [1]mut i64 := [1]\n a[18446744073709551615] = 2\n return 0\n}", b"", b"index out of bounds", ), "assignment_error_order": ( "main proc() int {\n a [0]mut i64 := []\n a[0] = 9223372036854775807 + 1\n return 0\n}", b"", b"integer arithmetic overflow", ), "signed_add_overflow": ( "main proc() int { return 9223372036854775807 + 1 }", b"", b"integer arithmetic overflow", ), "signed_subtract_overflow": ( "main proc() int { return -9223372036854775808 - 1 }", b"", b"integer arithmetic overflow", ), "signed_multiply_overflow": ( "main proc() int { return 9223372036854775807 * 2 }", b"", b"integer arithmetic overflow", ), "signed_negate_overflow": ( "main proc() int { return -(-9223372036854775808) }", b"", b"integer arithmetic overflow", ), "unsigned_subtract_overflow": ( "main proc() usize {\n zero usize :: 0\n return zero - 1\n}", b"", b"integer arithmetic overflow", ), "unsigned_add_overflow": ( "main proc() usize { return 18446744073709551615 + 1 }", b"", b"integer arithmetic overflow", ), "unsigned_multiply_overflow": ( "main proc() usize {\n max usize :: 18446744073709551615\n return max * 2\n}", b"", b"integer arithmetic overflow", ), "unused_errors": ( "bad proc() int { return absent }\nmain proc() int { return 42 }", b"42\n", b"unknown name", ), "reached_source_error": ( "main proc() int { return absent }", b"", b"unknown name", ), "missing_return": ( "main proc() int { if false { return 42 } }", b"", b"nonvoid function can finish", ), "missing_entry": ("other proc() void {}", b"", b"program requires a main function"), "invalid_unused_signature": ( "bad proc(a void) void {}\nmain proc() int { return 42 }", b"42\n", b"void cannot be stored", ), } def run(*args, env=None): return subprocess.run(args, cwd=ROOT, capture_output=True, timeout=30, env=env) def outcome(result): return result.returncode, result.stdout, result.stderr def check(source, executable, expected=None): interpreted = run(str(HONEY), "run", str(source)) assert interpreted.returncode in (0, 1), outcome(interpreted) if expected is not None: stdout, error = expected assert interpreted.stdout == stdout, (source.name, outcome(interpreted), stdout) assert interpreted.returncode == (1 if error else 0), outcome(interpreted) if error: assert error in interpreted.stderr, outcome(interpreted) for optimization in ("-O0", "-O2"): built = run(str(HONEY), "build", str(source), "-o", str(executable), optimization) assert built.returncode in (0, 1), (source.name, optimization, outcome(built)) assert built.stdout == b"", outcome(built) assert executable.is_file(), (source.name, outcome(built)) compiled = run(str(executable)) assert outcome(compiled) == outcome(interpreted), ( source.name, optimization, outcome(interpreted), outcome(compiled) ) executable.unlink() def main(): with tempfile.TemporaryDirectory(prefix="honey native ' ; $") as directory: temporary = Path(directory) executable = temporary / "program ' ; $" count = 0 for source in sorted((ROOT / "programs").glob("*.hon")): check(source, executable) count += 1 for name, (text, stdout, error) in CASES.items(): source = temporary / f"{name}.hon" source.write_text(textwrap.dedent(text).strip() + "\n") check(source, executable, (stdout, error)) count += 1 source = ROOT / "programs/factorial.hon" emitted = run(str(HONEY), "emit-llvm", str(source)) assert emitted.returncode == 0 and emitted.stderr == b"", outcome(emitted) ir = temporary / "factorial.ll" ir.write_bytes(emitted.stdout) assembled = run("clang", str(ir), "-o", str(executable)) assert assembled.returncode == 0, outcome(assembled) assert outcome(run(str(executable))) == (0, b"120\n", b"") executable.unlink() missing_compiler = run( str(HONEY), "build", str(source), "-o", str(executable), env={**os.environ, "PATH": str(temporary)}, ) assert missing_compiler.returncode == 2, outcome(missing_compiler) assert b"cannot start native compiler" in missing_compiler.stderr, outcome(missing_compiler) assert not executable.exists() invalid = run(str(HONEY), "build", str(source), "-o", str(executable), "-O3") assert invalid.returncode == 2 and b"usage:" in invalid.stderr, outcome(invalid) print(f"{count} programs matched interpreter at -O0 and -O2; LLVM output and CLI errors passed") if __name__ == "__main__": main()