cheatah
Source

tests/purrc/builtins_cr_test.cpp

1// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).
2// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.
3// Compile-run unit tests for the `builtins` module: one test per built-in. Each
4// writes a tiny .purr that calls a single built-in (built-ins need no `import`
5// and take no module prefix, e.g. `len("hi")`), compiles it with purrc, runs it
6// under the cheatah runtime, and asserts the exact stdout. Complements the
7// in-process unit tests (stdlib/tests/builtins_test.cpp).
8//
9// Note: the keyword-named conversions are spelled with their Python names in
10// cheatah source (`int`/`float`/`bool`), which the compiler maps to
11// builtins::to_int/to_float/to_bool. The `hash` built-in is intentionally not
12// covered here: its value is implementation-defined and not stable across
13// compilers/libstdc++ versions, so it has no portable expected stdout.
14#include "e2e_harness.hpp"
16TEST(BuiltinsCompileRun, Len) {
17 e2e::expect_e2e("builtins_len", R"PURR(import io
18io.print(len("meow"))
19)PURR", "4\n");
22TEST(BuiltinsCompileRun, IndexBoolList) {
23 // list<bool> lowers to std::vector<bool> (bit-packed, proxy references, no
24 // .data()), which needs its own builtins::index overload — indexing and
25 // negative indexing must behave like every other list.
26 e2e::expect_e2e("builtins_index_bool_list", R"PURR(import io
27let flags = [true, false, true]
28io.print(flags[0])
29io.print(flags[1])
30io.print(flags[-1])
31)PURR", "True\nFalse\nTrue\n");
34TEST(BuiltinsCompileRun, Ord) {
35 e2e::expect_e2e("builtins_ord", R"PURR(import io
36io.print(ord("A"))
37)PURR", "65\n");
40TEST(BuiltinsCompileRun, Str) {
41 // Bare str() (no import, no module prefix): int, float, bool, and inside a concat where
42 // it is redundant ("n=" + 7 auto-stringifies to the same thing).
43 e2e::expect_e2e("builtins_str", R"PURR(import io
44io.print(str(42))
45io.print(str(3.14))
46io.print(str(true))
47io.print("n=" + str(7))
48)PURR", "42\n3.14\nTrue\nn=7\n");
51TEST(BuiltinsCompileRun, Chr) {
52 e2e::expect_e2e("builtins_chr", R"PURR(import io
53io.print(chr(65))
54)PURR", "A\n");
57TEST(BuiltinsCompileRun, Hex) {
58 e2e::expect_e2e("builtins_hex", R"PURR(import io
59io.print(hex(255))
60)PURR", "0xff\n");
63TEST(BuiltinsCompileRun, Oct) {
64 e2e::expect_e2e("builtins_oct", R"PURR(import io
65io.print(oct(8))
66)PURR", "0o10\n");
69TEST(BuiltinsCompileRun, Bin) {
70 e2e::expect_e2e("builtins_bin", R"PURR(import io
71io.print(bin(5))
72)PURR", "0b101\n");
75TEST(BuiltinsCompileRun, Ascii) {
76 e2e::expect_e2e("builtins_ascii", R"PURR(import io
77io.print(ascii("hi"))
78)PURR", "'hi'\n");
81TEST(BuiltinsCompileRun, IntFromString) {
82 e2e::expect_e2e("builtins_int_str", R"PURR(import io
83io.print(int("42"))
84)PURR", "42\n");
87TEST(BuiltinsCompileRun, IntFromFloat) {
88 e2e::expect_e2e("builtins_int_float", R"PURR(import io
89io.print(int(3.9))
90)PURR", "3\n");
93TEST(BuiltinsCompileRun, FloatFromString) {
94 e2e::expect_e2e("builtins_float_str", R"PURR(import io
95io.print(float("2.5"))
96)PURR", "2.5\n");
99TEST(BuiltinsCompileRun, FloatFromInt) {
100 e2e::expect_e2e("builtins_float_int", R"PURR(import io
101io.print(float(7))
102)PURR", "7\n");
105TEST(BuiltinsCompileRun, FloatFromFloat) {
106 e2e::expect_e2e("builtins_float_float", R"PURR(import io
107io.print(float(0.95))
108)PURR", "0.95\n");
111TEST(BuiltinsCompileRun, BoolFromString) {
112 e2e::expect_e2e("builtins_bool_str", R"PURR(import io
113io.print(bool("x"))
114)PURR", "True\n");
117TEST(BuiltinsCompileRun, BoolFromZero) {
118 e2e::expect_e2e("builtins_bool_zero", R"PURR(import io
119io.print(bool(0))
120)PURR", "False\n");
123TEST(BuiltinsCompileRun, BoolFromNonzero) {
124 e2e::expect_e2e("builtins_bool_nonzero", R"PURR(import io
125io.print(bool(7))
126)PURR", "True\n");
129TEST(BuiltinsCompileRun, TrueDivision) {
130 e2e::expect_e2e("builtins_truediv", R"PURR(import io
131io.print(6 / 4)
132io.print(6 / 2)
133)PURR", "1.5\n3\n");
136TEST(BuiltinsCompileRun, FloorDivision) {
137 e2e::expect_e2e("builtins_floordiv", R"PURR(import io
138io.print(7 // 2)
139io.print(-7 // 2)
140)PURR", "3\n-4\n");
143TEST(BuiltinsCompileRun, Sizeof) {
144 // sizeof(<type>) is a compile-time builtin lowered to C++ sizeof — never a hardcoded byte
145 // count. Two argument families: the EXPLICIT width names (f32, i64, u8, …) that ABI-facing
146 // code (GPU buffers, wire formats) sizes elements with, and cheatah's own value types —
147 // whose widths are implementation policy (int is i64, float is f64 today), which is exactly
148 // why a foreign layout must be sized with the width names, not with them.
149 e2e::expect_e2e("builtins_sizeof", R"PURR(import io
150io.print(sizeof(u8))
151io.print(sizeof(i16))
152io.print(sizeof(f32))
153io.print(sizeof(f64))
154io.print(sizeof(i64))
155io.print(sizeof(int))
156io.print(sizeof(float))
157io.print(sizeof(bool))
158io.print(sizeof(f32) * 256)
159io.print(sizeof(uint8))
160io.print(sizeof(int16))
161io.print(sizeof(float32))
162io.print(sizeof(float64))
163io.print(sizeof(int64))
164io.print(sizeof(uint32))
165)PURR", "1\n2\n4\n8\n8\n8\n8\n1\n1024\n1\n2\n4\n8\n8\n4\n");
168TEST(BuiltinsCompileRun, SizeofExpr) {
169 // sizeof(<expression>) keeps C++ semantics: the STATIC size of the value's type (an int
170 // variable is 8 bytes regardless of its value) — distinct from len(), which is a length.
171 e2e::expect_e2e("builtins_sizeof_expr", R"PURR(import io
172let n = 5
173io.print(sizeof(n))
174io.print(sizeof(1.5))
175)PURR", "8\n8\n");