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. Each4
// 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 it6
// under the cheatah runtime, and asserts the exact stdout. Complements the7
// in-process unit tests (stdlib/tests/builtins_test.cpp).8
//9
// Note: the keyword-named conversions are spelled with their Python names in10
// cheatah source (`int`/`float`/`bool`), which the compiler maps to11
// builtins::to_int/to_float/to_bool. The `hash` built-in is intentionally not12
// covered here: its value is implementation-defined and not stable across13
// compilers/libstdc++ versions, so it has no portable expected stdout.14
#include "e2e_harness.hpp"16
TEST(BuiltinsCompileRun, Len) {17
e2e::expect_e2e("builtins_len", R"PURR(import io18
io.print(len("meow"))19
)PURR", "4\n");20
}22
TEST(BuiltinsCompileRun, IndexBoolList) {23
// list<bool> lowers to std::vector<bool> (bit-packed, proxy references, no24
// .data()), which needs its own builtins::index overload — indexing and25
// negative indexing must behave like every other list.26
e2e::expect_e2e("builtins_index_bool_list", R"PURR(import io27
let flags = [true, false, true]28
io.print(flags[0])29
io.print(flags[1])30
io.print(flags[-1])31
)PURR", "True\nFalse\nTrue\n");32
}34
TEST(BuiltinsCompileRun, Ord) {35
e2e::expect_e2e("builtins_ord", R"PURR(import io36
io.print(ord("A"))37
)PURR", "65\n");38
}40
TEST(BuiltinsCompileRun, Str) {41
// Bare str() (no import, no module prefix): int, float, bool, and inside a concat where42
// it is redundant ("n=" + 7 auto-stringifies to the same thing).43
e2e::expect_e2e("builtins_str", R"PURR(import io44
io.print(str(42))45
io.print(str(3.14))46
io.print(str(true))47
io.print("n=" + str(7))48
)PURR", "42\n3.14\nTrue\nn=7\n");49
}51
TEST(BuiltinsCompileRun, Chr) {52
e2e::expect_e2e("builtins_chr", R"PURR(import io53
io.print(chr(65))54
)PURR", "A\n");55
}57
TEST(BuiltinsCompileRun, Hex) {58
e2e::expect_e2e("builtins_hex", R"PURR(import io59
io.print(hex(255))60
)PURR", "0xff\n");61
}63
TEST(BuiltinsCompileRun, Oct) {64
e2e::expect_e2e("builtins_oct", R"PURR(import io65
io.print(oct(8))66
)PURR", "0o10\n");67
}69
TEST(BuiltinsCompileRun, Bin) {70
e2e::expect_e2e("builtins_bin", R"PURR(import io71
io.print(bin(5))72
)PURR", "0b101\n");73
}75
TEST(BuiltinsCompileRun, Ascii) {76
e2e::expect_e2e("builtins_ascii", R"PURR(import io77
io.print(ascii("hi"))78
)PURR", "'hi'\n");79
}81
TEST(BuiltinsCompileRun, IntFromString) {82
e2e::expect_e2e("builtins_int_str", R"PURR(import io83
io.print(int("42"))84
)PURR", "42\n");85
}87
TEST(BuiltinsCompileRun, IntFromFloat) {88
e2e::expect_e2e("builtins_int_float", R"PURR(import io89
io.print(int(3.9))90
)PURR", "3\n");91
}93
TEST(BuiltinsCompileRun, FloatFromString) {94
e2e::expect_e2e("builtins_float_str", R"PURR(import io95
io.print(float("2.5"))96
)PURR", "2.5\n");97
}99
TEST(BuiltinsCompileRun, FloatFromInt) {100
e2e::expect_e2e("builtins_float_int", R"PURR(import io101
io.print(float(7))102
)PURR", "7\n");103
}105
TEST(BuiltinsCompileRun, FloatFromFloat) {106
e2e::expect_e2e("builtins_float_float", R"PURR(import io107
io.print(float(0.95))108
)PURR", "0.95\n");109
}111
TEST(BuiltinsCompileRun, BoolFromString) {112
e2e::expect_e2e("builtins_bool_str", R"PURR(import io113
io.print(bool("x"))114
)PURR", "True\n");115
}117
TEST(BuiltinsCompileRun, BoolFromZero) {118
e2e::expect_e2e("builtins_bool_zero", R"PURR(import io119
io.print(bool(0))120
)PURR", "False\n");121
}123
TEST(BuiltinsCompileRun, BoolFromNonzero) {124
e2e::expect_e2e("builtins_bool_nonzero", R"PURR(import io125
io.print(bool(7))126
)PURR", "True\n");127
}129
TEST(BuiltinsCompileRun, TrueDivision) {130
e2e::expect_e2e("builtins_truediv", R"PURR(import io131
io.print(6 / 4)132
io.print(6 / 2)133
)PURR", "1.5\n3\n");134
}136
TEST(BuiltinsCompileRun, FloorDivision) {137
e2e::expect_e2e("builtins_floordiv", R"PURR(import io138
io.print(7 // 2)139
io.print(-7 // 2)140
)PURR", "3\n-4\n");141
}143
TEST(BuiltinsCompileRun, Sizeof) {144
// sizeof(<type>) is a compile-time builtin lowered to C++ sizeof — never a hardcoded byte145
// count. Two argument families: the EXPLICIT width names (f32, i64, u8, …) that ABI-facing146
// 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 exactly148
// why a foreign layout must be sized with the width names, not with them.149
e2e::expect_e2e("builtins_sizeof", R"PURR(import io150
io.print(sizeof(u8))151
io.print(sizeof(i16))152
io.print(sizeof(f32))153
io.print(sizeof(f64))154
io.print(sizeof(i64))155
io.print(sizeof(int))156
io.print(sizeof(float))157
io.print(sizeof(bool))158
io.print(sizeof(f32) * 256)159
io.print(sizeof(uint8))160
io.print(sizeof(int16))161
io.print(sizeof(float32))162
io.print(sizeof(float64))163
io.print(sizeof(int64))164
io.print(sizeof(uint32))165
)PURR", "1\n2\n4\n8\n8\n8\n8\n1\n1024\n1\n2\n4\n8\n8\n4\n");166
}168
TEST(BuiltinsCompileRun, SizeofExpr) {169
// sizeof(<expression>) keeps C++ semantics: the STATIC size of the value's type (an int170
// 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 io172
let n = 5173
io.print(sizeof(n))174
io.print(sizeof(1.5))175
)PURR", "8\n8\n");176
}