Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Iad057cd5f51ef26e7de93ccca7b3d3156a6a6964
693 lines
24 KiB
C++
693 lines
24 KiB
C++
#include "irc/parser.h"
|
|
#include "irc/serializer.h"
|
|
#include "irc/types.h"
|
|
#include <cassert>
|
|
#include <iostream>
|
|
|
|
using namespace nix_irc;
|
|
|
|
int failures = 0;
|
|
|
|
#define TEST_CHECK(cond, msg) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::cerr << " FAIL: " << msg << std::endl; \
|
|
failures++; \
|
|
} else { \
|
|
std::cout << " PASS: " << msg << std::endl; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define TEST_PASS(msg) std::cout << " PASS: " << msg << std::endl
|
|
#define TEST_FAIL(msg) \
|
|
do { \
|
|
std::cerr << " FAIL: " << msg << std::endl; \
|
|
failures++; \
|
|
} while (0)
|
|
|
|
void test_enum_compatibility() {
|
|
std::cout << "> Enum compatibility..." << std::endl;
|
|
|
|
if (static_cast<uint8_t>(NodeType::WITH) == 0x32) {
|
|
std::cout << " PASS: WITH has correct value 0x32" << std::endl;
|
|
} else {
|
|
std::cerr << " FAIL: WITH should be 0x32, got " << static_cast<uint8_t>(NodeType::WITH)
|
|
<< std::endl;
|
|
}
|
|
|
|
if (static_cast<uint8_t>(NodeType::HAS_ATTR) == 0x34) {
|
|
std::cout << " PASS: HAS_ATTR has value 0x34 (new slot after WITH bump)" << std::endl;
|
|
} else if (static_cast<uint8_t>(NodeType::HAS_ATTR) == 0x33 &&
|
|
static_cast<uint8_t>(NodeType::WITH) == 0x32) {
|
|
std::cout << " PASS: HAS_ATTR has value 0x33 (restored original with WITH "
|
|
"at 0x32)"
|
|
<< std::endl;
|
|
} else {
|
|
std::cerr << " FAIL: HAS_ATTR value is " << static_cast<uint8_t>(NodeType::HAS_ATTR)
|
|
<< " (expected 0x34 or 0x33 with WITH=0x32)" << std::endl;
|
|
}
|
|
|
|
if (IR_VERSION == 3) {
|
|
std::cout << " PASS: IR_VERSION is 3" << std::endl;
|
|
} else {
|
|
std::cerr << " FAIL: IR_VERSION should be 3, got " << IR_VERSION << std::endl;
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
void test_serializer_select_with_default() {
|
|
std::cout << "> SELECT serialization with default_expr..." << std::endl;
|
|
|
|
auto expr = std::make_shared<Node>(ConstIntNode(42));
|
|
auto attr = std::make_shared<Node>(ConstStringNode("key"));
|
|
auto default_val = std::make_shared<Node>(ConstIntNode(100));
|
|
|
|
SelectNode select_node(expr, attr);
|
|
select_node.default_expr = default_val;
|
|
auto select = std::make_shared<Node>(select_node);
|
|
|
|
IRModule module;
|
|
module.entry = select;
|
|
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
auto* loaded_select = loaded.entry->get_if<SelectNode>();
|
|
if (loaded_select && loaded_select->default_expr && *loaded_select->default_expr) {
|
|
auto* def_val = (*loaded_select->default_expr)->get_if<ConstIntNode>();
|
|
if (def_val && def_val->value == 100) {
|
|
std::cout << " PASS: SELECT with default_expr round-trips correctly" << std::endl;
|
|
} else {
|
|
std::cerr << " FAIL: default_expr value incorrect" << std::endl;
|
|
}
|
|
} else {
|
|
std::cerr << " FAIL: default_expr not deserialized (missing u8 flag read)" << std::endl;
|
|
}
|
|
}
|
|
|
|
void test_serializer_select_without_default() {
|
|
std::cout << "> SELECT serialization without default_expr..." << std::endl;
|
|
|
|
auto expr = std::make_shared<Node>(ConstIntNode(42));
|
|
auto attr = std::make_shared<Node>(ConstStringNode("key"));
|
|
|
|
SelectNode select_node(expr, attr);
|
|
auto select = std::make_shared<Node>(select_node);
|
|
|
|
IRModule module;
|
|
module.entry = select;
|
|
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
auto* loaded_select = loaded.entry->get_if<SelectNode>();
|
|
if (loaded_select && (!loaded_select->default_expr || !*loaded_select->default_expr)) {
|
|
std::cout << " PASS: SELECT without default_expr round-trips correctly" << std::endl;
|
|
} else {
|
|
std::cerr << " FAIL: default_expr should be null/absent" << std::endl;
|
|
}
|
|
}
|
|
|
|
void test_parser_brace_depth_in_strings() {
|
|
std::cout << "> Parser brace depth handling in strings..." << std::endl;
|
|
|
|
std::string test_input = R"(let s = "test}"; in s)";
|
|
|
|
try {
|
|
Parser parser;
|
|
auto ast = parser.parse(test_input);
|
|
TEST_PASS("Brace inside string does not confuse parser");
|
|
} catch (const std::exception& e) {
|
|
TEST_FAIL("Parser should handle '}' inside strings");
|
|
}
|
|
}
|
|
|
|
void test_parser_has_ellipsis_usage() {
|
|
std::cout << "> Parser has_ellipsis usage..." << std::endl;
|
|
|
|
std::string with_ellipsis = "{ a, ... }: a";
|
|
std::string without_ellipsis = "{ a, b }: a + b";
|
|
|
|
try {
|
|
Parser parser1;
|
|
auto ast1 = parser1.parse(with_ellipsis);
|
|
TEST_PASS("Pattern with ellipsis parses correctly");
|
|
|
|
Parser parser2;
|
|
auto ast2 = parser2.parse(without_ellipsis);
|
|
TEST_PASS("Pattern without ellipsis parses correctly");
|
|
} catch (const std::exception& e) {
|
|
TEST_FAIL("Pattern parsing failed");
|
|
}
|
|
}
|
|
|
|
void test_parser_expect_in_speculative_parsing() {
|
|
std::cout << "> Parser expect() in speculative parsing..." << std::endl;
|
|
|
|
std::string not_a_lambda = "1 + 2";
|
|
std::string actual_lambda = "x: x + 1";
|
|
|
|
try {
|
|
Parser parser1;
|
|
auto ast1 = parser1.parse(not_a_lambda);
|
|
TEST_PASS("Non-lambda input does not cause parser to throw");
|
|
|
|
Parser parser2;
|
|
auto ast2 = parser2.parse(actual_lambda);
|
|
TEST_PASS("Actual lambda parses correctly");
|
|
} catch (const std::exception& e) {
|
|
TEST_FAIL("Parser should handle both lambda and non-lambda input");
|
|
}
|
|
}
|
|
|
|
void test_lookup_path_node() {
|
|
std::cout << "> Lookup path serialization..." << std::endl;
|
|
|
|
auto lookup = std::make_shared<Node>(ConstLookupPathNode("nixpkgs"));
|
|
IRModule module;
|
|
module.entry = lookup;
|
|
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
auto* loaded_lookup = loaded.entry->get_if<ConstLookupPathNode>();
|
|
TEST_CHECK(loaded_lookup != nullptr, "Deserialized node is ConstLookupPathNode");
|
|
TEST_CHECK(loaded_lookup && loaded_lookup->value == "nixpkgs", "Lookup path value is 'nixpkgs'");
|
|
}
|
|
|
|
void test_import_node() {
|
|
std::cout << "> Import node serialization..." << std::endl;
|
|
|
|
auto path = std::make_shared<Node>(ConstPathNode("./test.nix"));
|
|
auto import_node = std::make_shared<Node>(ImportNode(path));
|
|
IRModule module;
|
|
module.entry = import_node;
|
|
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
auto* loaded_import = loaded.entry->get_if<ImportNode>();
|
|
TEST_CHECK(loaded_import != nullptr, "Deserialized node is ImportNode");
|
|
TEST_CHECK(loaded_import && loaded_import->path != nullptr, "Import node has path");
|
|
|
|
if (loaded_import && loaded_import->path) {
|
|
auto* path_node = loaded_import->path->get_if<ConstPathNode>();
|
|
TEST_CHECK(path_node != nullptr, "Import path is ConstPathNode");
|
|
TEST_CHECK(path_node && path_node->value == "./test.nix", "Import path value is './test.nix'");
|
|
}
|
|
}
|
|
|
|
void test_import_with_lookup_path() {
|
|
std::cout << "> Import with lookup path..." << std::endl;
|
|
|
|
auto lookup = std::make_shared<Node>(ConstLookupPathNode("nixpkgs"));
|
|
auto import_node = std::make_shared<Node>(ImportNode(lookup));
|
|
IRModule module;
|
|
module.entry = import_node;
|
|
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
auto* loaded_import = loaded.entry->get_if<ImportNode>();
|
|
TEST_CHECK(loaded_import != nullptr, "Deserialized node is ImportNode");
|
|
|
|
if (loaded_import && loaded_import->path) {
|
|
auto* lookup_node = loaded_import->path->get_if<ConstLookupPathNode>();
|
|
TEST_CHECK(lookup_node != nullptr, "Import path is ConstLookupPathNode");
|
|
TEST_CHECK(lookup_node && lookup_node->value == "nixpkgs", "Lookup path value is 'nixpkgs'");
|
|
}
|
|
}
|
|
|
|
void test_uri_node() {
|
|
std::cout << "> URI node serialization..." << std::endl;
|
|
|
|
auto uri = std::make_shared<Node>(ConstURINode("https://example.com"));
|
|
IRModule module;
|
|
module.entry = uri;
|
|
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
auto* loaded_uri = loaded.entry->get_if<ConstURINode>();
|
|
TEST_CHECK(loaded_uri != nullptr, "Deserialized node is ConstURINode");
|
|
TEST_CHECK(loaded_uri && loaded_uri->value == "https://example.com",
|
|
"URI value is 'https://example.com'");
|
|
}
|
|
|
|
void test_float_node() {
|
|
std::cout << "> Float node serialization..." << std::endl;
|
|
|
|
auto float_val = std::make_shared<Node>(ConstFloatNode(3.14159));
|
|
IRModule module;
|
|
module.entry = float_val;
|
|
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
auto* loaded_float = loaded.entry->get_if<ConstFloatNode>();
|
|
TEST_CHECK(loaded_float != nullptr, "Deserialized node is ConstFloatNode");
|
|
TEST_CHECK(loaded_float && loaded_float->value > 3.14 && loaded_float->value < 3.15,
|
|
"Float value is approximately 3.14159");
|
|
}
|
|
|
|
// LambdaPatternNode Tests
|
|
void test_lambda_pattern_simple() {
|
|
std::cout << "> LambdaPatternNode simple ({ a, b }: a + b)..." << std::endl;
|
|
|
|
// Body: a + b (using VarNode for a and b)
|
|
auto var_a = std::make_shared<Node>(VarNode(0, "a"));
|
|
auto var_b = std::make_shared<Node>(VarNode(0, "b"));
|
|
auto body = std::make_shared<Node>(BinaryOpNode(BinaryOp::ADD, var_a, var_b));
|
|
|
|
// Create lambda pattern with two required fields
|
|
LambdaPatternNode lambda_pattern(body);
|
|
lambda_pattern.required_fields.emplace_back("a", std::nullopt);
|
|
lambda_pattern.required_fields.emplace_back("b", std::nullopt);
|
|
lambda_pattern.allow_extra = false;
|
|
|
|
auto node = std::make_shared<Node>(std::move(lambda_pattern));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify
|
|
auto* loaded_node = loaded.entry->get_if<LambdaPatternNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is LambdaPatternNode");
|
|
TEST_CHECK(loaded_node && loaded_node->required_fields.size() == 2, "Has 2 required fields");
|
|
TEST_CHECK(loaded_node && loaded_node->optional_fields.size() == 0, "Has 0 optional fields");
|
|
TEST_CHECK(loaded_node && loaded_node->required_fields[0].name == "a", "First field is 'a'");
|
|
TEST_CHECK(loaded_node && loaded_node->required_fields[1].name == "b", "Second field is 'b'");
|
|
TEST_CHECK(loaded_node && !loaded_node->at_binding.has_value(), "No at-binding");
|
|
TEST_CHECK(loaded_node && !loaded_node->allow_extra, "No ellipsis");
|
|
TEST_CHECK(loaded_node && loaded_node->body != nullptr, "Has body");
|
|
}
|
|
|
|
void test_lambda_pattern_with_defaults() {
|
|
std::cout << "> LambdaPatternNode with defaults ({ a, b ? 10 }: a + b)..." << std::endl;
|
|
|
|
// Default value for b
|
|
auto default_b = std::make_shared<Node>(ConstIntNode(10));
|
|
|
|
// Body: a + b
|
|
auto var_a = std::make_shared<Node>(VarNode(0, "a"));
|
|
auto var_b = std::make_shared<Node>(VarNode(0, "b"));
|
|
auto body = std::make_shared<Node>(BinaryOpNode(BinaryOp::ADD, var_a, var_b));
|
|
|
|
// Create lambda pattern
|
|
LambdaPatternNode lambda_pattern(body);
|
|
lambda_pattern.required_fields.emplace_back("a", std::nullopt);
|
|
lambda_pattern.optional_fields.emplace_back("b", default_b);
|
|
lambda_pattern.allow_extra = false;
|
|
|
|
auto node = std::make_shared<Node>(std::move(lambda_pattern));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify
|
|
auto* loaded_node = loaded.entry->get_if<LambdaPatternNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is LambdaPatternNode");
|
|
TEST_CHECK(loaded_node && loaded_node->required_fields.size() == 1, "Has 1 required field");
|
|
TEST_CHECK(loaded_node && loaded_node->optional_fields.size() == 1, "Has 1 optional field");
|
|
TEST_CHECK(loaded_node && loaded_node->required_fields[0].name == "a", "Required field is 'a'");
|
|
TEST_CHECK(loaded_node && loaded_node->optional_fields[0].name == "b", "Optional field is 'b'");
|
|
TEST_CHECK(loaded_node && loaded_node->optional_fields[0].default_value.has_value(),
|
|
"Optional field has default");
|
|
|
|
if (loaded_node && loaded_node->optional_fields[0].default_value) {
|
|
auto* def_val = (*loaded_node->optional_fields[0].default_value)->get_if<ConstIntNode>();
|
|
TEST_CHECK(def_val && def_val->value == 10, "Default value is 10");
|
|
}
|
|
}
|
|
|
|
void test_lambda_pattern_at_binding() {
|
|
std::cout << "> LambdaPatternNode with at-binding (args@{ a, b }: args.a)..." << std::endl;
|
|
|
|
// Body: args.a (select expression)
|
|
auto var_args = std::make_shared<Node>(VarNode(0, "args"));
|
|
auto attr = std::make_shared<Node>(ConstStringNode("a"));
|
|
auto body = std::make_shared<Node>(SelectNode(var_args, attr));
|
|
|
|
// Create lambda pattern with at-binding
|
|
LambdaPatternNode lambda_pattern(body);
|
|
lambda_pattern.required_fields.emplace_back("a", std::nullopt);
|
|
lambda_pattern.required_fields.emplace_back("b", std::nullopt);
|
|
lambda_pattern.at_binding = "args";
|
|
lambda_pattern.allow_extra = false;
|
|
|
|
auto node = std::make_shared<Node>(std::move(lambda_pattern));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify
|
|
auto* loaded_node = loaded.entry->get_if<LambdaPatternNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is LambdaPatternNode");
|
|
TEST_CHECK(loaded_node && loaded_node->at_binding.has_value(), "Has at-binding");
|
|
TEST_CHECK(loaded_node && loaded_node->at_binding.value() == "args", "At-binding is 'args'");
|
|
}
|
|
|
|
void test_lambda_pattern_ellipsis() {
|
|
std::cout << "> LambdaPatternNode with ellipsis ({ a, ... }: a)..." << std::endl;
|
|
|
|
// Body: a
|
|
auto body = std::make_shared<Node>(VarNode(0, "a"));
|
|
|
|
// Create lambda pattern with ellipsis
|
|
LambdaPatternNode lambda_pattern(body);
|
|
lambda_pattern.required_fields.emplace_back("a", std::nullopt);
|
|
lambda_pattern.allow_extra = true;
|
|
|
|
auto node = std::make_shared<Node>(std::move(lambda_pattern));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify
|
|
auto* loaded_node = loaded.entry->get_if<LambdaPatternNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is LambdaPatternNode");
|
|
TEST_CHECK(loaded_node && loaded_node->allow_extra, "Has ellipsis (allow_extra=true)");
|
|
}
|
|
|
|
void test_lambda_pattern_complete() {
|
|
std::cout << "> LambdaPatternNode complete (args@{ a, b ? 5, ... }: body)..." << std::endl;
|
|
|
|
// Default value for b
|
|
auto default_b = std::make_shared<Node>(ConstIntNode(5));
|
|
|
|
// Body: simple var
|
|
auto body = std::make_shared<Node>(VarNode(0, "x"));
|
|
|
|
// Create lambda pattern with all features
|
|
LambdaPatternNode lambda_pattern(body);
|
|
lambda_pattern.required_fields.emplace_back("a", std::nullopt);
|
|
lambda_pattern.optional_fields.emplace_back("b", default_b);
|
|
lambda_pattern.at_binding = "args";
|
|
lambda_pattern.allow_extra = true;
|
|
|
|
auto node = std::make_shared<Node>(std::move(lambda_pattern));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify all fields
|
|
auto* loaded_node = loaded.entry->get_if<LambdaPatternNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is LambdaPatternNode");
|
|
TEST_CHECK(loaded_node && loaded_node->required_fields.size() == 1, "Has 1 required field");
|
|
TEST_CHECK(loaded_node && loaded_node->optional_fields.size() == 1, "Has 1 optional field");
|
|
TEST_CHECK(loaded_node && loaded_node->at_binding.has_value(), "Has at-binding");
|
|
TEST_CHECK(loaded_node && loaded_node->at_binding.value() == "args", "At-binding is 'args'");
|
|
TEST_CHECK(loaded_node && loaded_node->allow_extra, "Has ellipsis");
|
|
}
|
|
|
|
void test_lambda_pattern_empty() {
|
|
std::cout << "> LambdaPatternNode empty ({ }: body)..." << std::endl;
|
|
|
|
// Body: simple constant
|
|
auto body = std::make_shared<Node>(ConstIntNode(42));
|
|
|
|
// Create empty lambda pattern
|
|
LambdaPatternNode lambda_pattern(body);
|
|
lambda_pattern.allow_extra = false;
|
|
|
|
auto node = std::make_shared<Node>(std::move(lambda_pattern));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify
|
|
auto* loaded_node = loaded.entry->get_if<LambdaPatternNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is LambdaPatternNode");
|
|
TEST_CHECK(loaded_node && loaded_node->required_fields.size() == 0, "Has 0 required fields");
|
|
TEST_CHECK(loaded_node && loaded_node->optional_fields.size() == 0, "Has 0 optional fields");
|
|
TEST_CHECK(loaded_node && !loaded_node->at_binding.has_value(), "No at-binding");
|
|
TEST_CHECK(loaded_node && !loaded_node->allow_extra, "No ellipsis");
|
|
}
|
|
|
|
// StringInterpolationNode Tests
|
|
|
|
void test_string_interpolation_simple() {
|
|
std::cout << "> StringInterpolationNode simple (\"hello ${name}\")..." << std::endl;
|
|
|
|
// "hello ${name}" = literal "hello " + expr(name)
|
|
std::vector<StringPart> parts;
|
|
parts.push_back(StringPart::make_literal("hello "));
|
|
parts.push_back(StringPart::make_expr(std::make_shared<Node>(VarNode(0, "name"))));
|
|
|
|
auto node = std::make_shared<Node>(StringInterpolationNode(std::move(parts)));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify
|
|
auto* loaded_node = loaded.entry->get_if<StringInterpolationNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is StringInterpolationNode");
|
|
TEST_CHECK(loaded_node && loaded_node->parts.size() == 2, "Has 2 parts");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[0].type == StringPart::Type::LITERAL,
|
|
"First part is LITERAL");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[0].literal == "hello ", "First part is 'hello '");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[1].type == StringPart::Type::EXPR,
|
|
"Second part is EXPR");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[1].expr != nullptr, "Second part has expression");
|
|
}
|
|
|
|
void test_string_interpolation_multiple() {
|
|
std::cout << "> StringInterpolationNode multiple (\"${a} and ${b}\")..." << std::endl;
|
|
|
|
// "${a} and ${b}" = expr(a) + literal " and " + expr(b)
|
|
std::vector<StringPart> parts;
|
|
parts.push_back(StringPart::make_expr(std::make_shared<Node>(VarNode(0, "a"))));
|
|
parts.push_back(StringPart::make_literal(" and "));
|
|
parts.push_back(StringPart::make_expr(std::make_shared<Node>(VarNode(0, "b"))));
|
|
|
|
auto node = std::make_shared<Node>(StringInterpolationNode(std::move(parts)));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify
|
|
auto* loaded_node = loaded.entry->get_if<StringInterpolationNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is StringInterpolationNode");
|
|
TEST_CHECK(loaded_node && loaded_node->parts.size() == 3, "Has 3 parts");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[0].type == StringPart::Type::EXPR, "Part 0 is EXPR");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[1].type == StringPart::Type::LITERAL,
|
|
"Part 1 is LITERAL");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[1].literal == " and ", "Part 1 is ' and '");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[2].type == StringPart::Type::EXPR, "Part 2 is EXPR");
|
|
}
|
|
|
|
void test_string_interpolation_complex() {
|
|
std::cout << "> StringInterpolationNode complex (\"result: ${a + b}\")..." << std::endl;
|
|
|
|
// "result: ${a + b}" = literal "result: " + expr(a + b)
|
|
auto expr_a = std::make_shared<Node>(VarNode(0, "a"));
|
|
auto expr_b = std::make_shared<Node>(VarNode(0, "b"));
|
|
auto add_expr = std::make_shared<Node>(BinaryOpNode(BinaryOp::ADD, expr_a, expr_b));
|
|
|
|
std::vector<StringPart> parts;
|
|
parts.push_back(StringPart::make_literal("result: "));
|
|
parts.push_back(StringPart::make_expr(add_expr));
|
|
|
|
auto node = std::make_shared<Node>(StringInterpolationNode(std::move(parts)));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify
|
|
auto* loaded_node = loaded.entry->get_if<StringInterpolationNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is StringInterpolationNode");
|
|
TEST_CHECK(loaded_node && loaded_node->parts.size() == 2, "Has 2 parts");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[1].type == StringPart::Type::EXPR, "Part 1 is EXPR");
|
|
|
|
// Verify the expression is a BinaryOpNode
|
|
if (loaded_node && loaded_node->parts[1].expr) {
|
|
auto* bin_op = loaded_node->parts[1].expr->get_if<BinaryOpNode>();
|
|
TEST_CHECK(bin_op != nullptr, "Expression is BinaryOpNode");
|
|
TEST_CHECK(bin_op && bin_op->op == BinaryOp::ADD, "Operation is ADD");
|
|
}
|
|
}
|
|
|
|
void test_string_interpolation_nested() {
|
|
std::cout << "> StringInterpolationNode nested (\"${prefix}/${path}\")..." << std::endl;
|
|
|
|
// "${prefix}/${path}" = expr(prefix) + literal "/" + expr(path)
|
|
std::vector<StringPart> parts;
|
|
parts.push_back(StringPart::make_expr(std::make_shared<Node>(VarNode(0, "prefix"))));
|
|
parts.push_back(StringPart::make_literal("/"));
|
|
parts.push_back(StringPart::make_expr(std::make_shared<Node>(VarNode(0, "path"))));
|
|
|
|
auto node = std::make_shared<Node>(StringInterpolationNode(std::move(parts)));
|
|
|
|
// Serialize
|
|
IRModule module;
|
|
module.entry = node;
|
|
Serializer ser;
|
|
auto bytes = ser.serialize_to_bytes(module);
|
|
|
|
// Deserialize
|
|
Deserializer deser;
|
|
auto loaded = deser.deserialize(bytes);
|
|
|
|
// Verify
|
|
auto* loaded_node = loaded.entry->get_if<StringInterpolationNode>();
|
|
TEST_CHECK(loaded_node != nullptr, "Type is StringInterpolationNode");
|
|
TEST_CHECK(loaded_node && loaded_node->parts.size() == 3, "Has 3 parts");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[1].type == StringPart::Type::LITERAL,
|
|
"Middle part is LITERAL");
|
|
TEST_CHECK(loaded_node && loaded_node->parts[1].literal == "/", "Middle part is '/'");
|
|
}
|
|
|
|
int main() {
|
|
std::cout << "=== Regression Tests ===" << std::endl << std::endl;
|
|
|
|
test_enum_compatibility();
|
|
std::cout << std::endl;
|
|
|
|
test_serializer_select_with_default();
|
|
std::cout << std::endl;
|
|
|
|
test_serializer_select_without_default();
|
|
std::cout << std::endl;
|
|
|
|
test_parser_brace_depth_in_strings();
|
|
std::cout << std::endl;
|
|
|
|
test_parser_has_ellipsis_usage();
|
|
std::cout << std::endl;
|
|
|
|
test_parser_expect_in_speculative_parsing();
|
|
std::cout << std::endl;
|
|
|
|
test_lookup_path_node();
|
|
std::cout << std::endl;
|
|
|
|
test_import_node();
|
|
std::cout << std::endl;
|
|
|
|
test_import_with_lookup_path();
|
|
std::cout << std::endl;
|
|
|
|
test_uri_node();
|
|
std::cout << std::endl;
|
|
|
|
test_float_node();
|
|
std::cout << std::endl;
|
|
|
|
test_lambda_pattern_simple();
|
|
std::cout << std::endl;
|
|
|
|
test_lambda_pattern_with_defaults();
|
|
std::cout << std::endl;
|
|
|
|
test_lambda_pattern_at_binding();
|
|
std::cout << std::endl;
|
|
|
|
test_lambda_pattern_ellipsis();
|
|
std::cout << std::endl;
|
|
|
|
test_lambda_pattern_complete();
|
|
std::cout << std::endl;
|
|
|
|
test_lambda_pattern_empty();
|
|
std::cout << std::endl;
|
|
|
|
test_string_interpolation_simple();
|
|
std::cout << std::endl;
|
|
|
|
test_string_interpolation_multiple();
|
|
std::cout << std::endl;
|
|
|
|
test_string_interpolation_complex();
|
|
std::cout << std::endl;
|
|
|
|
test_string_interpolation_nested();
|
|
std::cout << std::endl;
|
|
|
|
std::cout << "=== Tests Complete ===" << std::endl;
|
|
std::cout << "Failures: " << failures << std::endl;
|
|
return failures > 0 ? 1 : 0;
|
|
}
|