diff --git a/CMakeLists.txt b/CMakeLists.txt index 10ed5a2..edb503c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,7 +78,6 @@ install(TARGETS nix-ir-plugin LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/n add_executable(regression_test tests/regression_test.cpp src/irc/serializer.cpp - src/irc/parser.cpp ) target_include_directories(regression_test PRIVATE diff --git a/README.md b/README.md index 9ff280b..1048d9c 100644 --- a/README.md +++ b/README.md @@ -169,44 +169,27 @@ Entry: ### Building ```bash -# Using just (recommended) -$ just build +# Configure +$ cmake -B build -# Or manually with CMake -$ cmake -B build -G Ninja -$ cmake --build build +# Build +$ make -# The nix-irc executable will be in build/ -$ ./build/nix-irc --help +# The nix-irc executable will be in the project root +$ ./nix-irc --help ``` -### Available Tasks - -Run `just` to see all available tasks: - -- `just build` - Build all targets -- `just test` - Run all tests (unit, compile, integration) -- `just bench` - Run performance benchmarks -- `just clean` - Clean build artifacts -- `just smoke` - Run quick smoke test -- `just stats` - Show project statistics - -See `just --list` for the complete list of available commands. - ### Compiling Nix to IR ```bash # Basic compilation -$ ./build/nix-irc input.nix output.nixir +$ nix-irc input.nix output.nixir # With import search paths -$ ./build/nix-irc -I ./lib -I /nix/store/... input.nix output.nixir +$ nix-irc -I ./lib -I /nix/store/... input.nix output.nixir # Disable import resolution -$ ./build/nix-irc --no-imports input.nix output.nixir - -# Using just -$ just compile input.nix output.nixir +$ nix-irc --no-imports input.nix output.nixir ``` ### Runtime Evaluation (Plugin) @@ -229,21 +212,13 @@ $ nix --plugin-files ./nix-ir-plugin.so eval --expr 'builtins.nixIR_info' ### Running Tests ```bash -# Run all tests -$ just test - -# Run specific test suites -$ just test-unit # Unit tests only -$ just test-compile # Compilation tests only -$ just test-integration # Integration tests only - -# Manually test all fixtures -$ for f in tests/fixtures/*.nix; do - ./build/nix-irc "$f" "${f%.nix}.nixir" +# Test all sample files +for f in tests/*.nix; do + ./nix-irc "$f" "${f%.nix}.nixir" done # Verify IR format -$ hexdump -C tests/fixtures/simple.nixir | head -3 +$ hexdump -C tests/simple.nixir | head -3 ``` ## Contributing diff --git a/flake.nix b/flake.nix index 4f98a7f..a127312 100644 --- a/flake.nix +++ b/flake.nix @@ -30,8 +30,6 @@ ninja bear clang-tools - just - entr ]; env.NIX_PLUGINABI = "0.2"; diff --git a/justfile b/justfile deleted file mode 100644 index 9ca92f8..0000000 --- a/justfile +++ /dev/null @@ -1,98 +0,0 @@ -# Default recipe, show available commands -default: - @just --list - -# Build all targets -build: - cmake --build build - -# Clean build artifacts -clean: - rm -rf build - find tests -name '*.nixir' -delete - -# Configure and build from scratch -rebuild: clean - cmake -B build -G Ninja - cmake --build build - -# Run unit tests -test-unit: - ./build/regression_test - -# Run compilation tests (do all fixtures compile?) -test-compile: - #!/usr/bin/env bash - total=0 - success=0 - for f in tests/fixtures/*.nix; do - total=$((total+1)) - if ./build/nix-irc "$f" "${f%.nix}.nixir" 2>&1 | grep -q "Done!"; then - success=$((success+1)) - fi - done - echo "Compiled: $success/$total test files" - [ $success -eq $total ] - -# Run integration tests -test-integration: - ./tests/integration/run.sh - -# Run all tests -test: test-unit test-compile test-integration - @echo "All tests passed" - -# Run benchmarks -bench: - ./tests/benchmark/run.sh - -# Compile a single Nix file to IR -compile FILE OUTPUT="": - #!/usr/bin/env bash - if [ -z "{{OUTPUT}}" ]; then - file="{{FILE}}" - output="${file%.nix}.nixir" - else - output="{{OUTPUT}}" - fi - ./build/nix-irc "{{FILE}}" "$output" - -# Load plugin and evaluate Nix expression -eval FILE: - nix-instantiate --plugin-files ./build/nix-ir-plugin.so --eval --strict "{{FILE}}" - -# Format C++ code with clang-format -format: - find src tests -name '*.cpp' -o -name '*.h' | xargs clang-format -i - -# Run clang-tidy on source files -lint: - find src -name '*.cpp' | xargs clang-tidy --fix - -# Show project statistics -stats: - @echo "Lines of code:" - @find src -name '*.cpp' -o -name '*.h' | xargs wc -l | tail -1 - @echo "" - @echo "Test files:" - @find tests/fixtures -name '*.nix' | wc -l - @echo "" - @echo "Build status:" - @ls -lh build/nix-irc build/nix-ir-plugin.so build/regression_test 2>/dev/null || echo "Not built" - -# Run a quick smoke test -smoke: - ./build/nix-irc tests/fixtures/simple.nix /tmp/smoke.nixir - nix-instantiate --plugin-files ./build/nix-ir-plugin.so --eval tests/integration/simple_eval.nix - -# Generate IR from a Nix file and inspect it -inspect FILE: - ./build/nix-irc "{{FILE}}" /tmp/inspect.nixir - @echo "IR bundle size:" - @ls -lh /tmp/inspect.nixir | awk '{print $5}' - @echo "Magic number:" - @xxd -l 4 /tmp/inspect.nixir - -# Watch mode, rebuild on file changes -watch: - find src tests -name '*.cpp' -o -name '*.h' | entr -c just build test-unit diff --git a/src/irc/evaluator.cpp b/src/irc/evaluator.cpp index 987e38a..7a50dc7 100644 --- a/src/irc/evaluator.cpp +++ b/src/irc/evaluator.cpp @@ -21,20 +21,15 @@ struct IREnvironment { void bind(Value* val) { bindings.push_back(val); } - Value* lookup(uint32_t encoded_index) { - // Decode the index: high 16 bits = depth, low 16 bits = offset - uint32_t depth = encoded_index >> 16; - uint32_t offset = encoded_index & 0xFFFF; - + Value* lookup(uint32_t index) { IREnvironment* env = this; - // Skip 'depth' levels to get to the right scope - for (uint32_t i = 0; i < depth && env; i++) { + while (env) { + if (index < env->bindings.size()) { + return env->bindings[index]; + } + index -= env->bindings.size(); env = env->parent; } - - if (env && offset < env->bindings.size()) { - return env->bindings[offset]; - } return nullptr; } @@ -152,35 +147,7 @@ struct Evaluator::Impl { state.error("variable not found").debugThrow(); } force(bound); - // Copy the forced value's data into v - // For simple types, use mk* methods to ensure proper initialization - // For complex types (attrs, lists, functions), direct assignment is safe - state.forceValue(*bound, noPos); - switch (bound->type()) { - case nInt: - v.mkInt(bound->integer()); - break; - case nBool: - v.mkBool(bound->boolean()); - break; - case nString: - v.mkString(bound->c_str()); - break; - case nPath: - v.mkPath(bound->path()); - break; - case nNull: - v.mkNull(); - break; - case nFloat: - v.mkFloat(bound->fpoint()); - break; - default: - // For attrs, lists, functions, etc., direct assignment is safe - // as they use reference counting internally - v = *bound; - break; - } + v = *bound; } else if (auto* n = node->get_if()) { auto lambda_env = env; auto body = n->body; @@ -455,33 +422,20 @@ struct Evaluator::Impl { } } else if (auto* n = node->get_if()) { auto let_env = make_env(env); - // Nix's let is recursive: bind all names first, then evaluate - // We allocate Values immediately and evaluate into them - std::vector values; for (const auto& [name, expr] : n->bindings) { - Value* val = state.allocValue(); - values.push_back(val); + // Create thunks in let_env so bindings can reference each other + Value* val = make_thunk(expr, let_env); let_env->bind(val); } - // Now evaluate each binding expression into its pre-allocated Value - size_t idx = 0; - for (const auto& [name, expr] : n->bindings) { - eval_node(expr, *values[idx++], let_env); - } eval_node(n->body, v, let_env); } else if (auto* n = node->get_if()) { auto letrec_env = make_env(env); - // Same as LetNode - both are recursive in Nix - std::vector values; + for (const auto& [name, expr] : n->bindings) { - Value* val = state.allocValue(); - values.push_back(val); + Value* val = make_thunk(expr, letrec_env); letrec_env->bind(val); } - size_t idx = 0; - for (const auto& [name, expr] : n->bindings) { - eval_node(expr, *values[idx++], letrec_env); - } + eval_node(n->body, v, letrec_env); } else if (auto* n = node->get_if()) { auto bindings = state.buildBindings(n->attrs.size()); @@ -499,12 +453,9 @@ struct Evaluator::Impl { } } - // Evaluate attribute values immediately to avoid dangling thunks - // Our thunk system is tied to the Evaluator lifetime, so we can't - // return lazy thunks that outlive the evaluator + // Attributes should be lazy, so store as thunks and not evaluated values for (const auto& binding : n->attrs) { - Value* attr_val = state.allocValue(); - eval_node(binding.value, *attr_val, attr_env); + Value* attr_val = make_thunk(binding.value, attr_env); if (binding.is_dynamic()) { // Evaluate key expression to get attribute name @@ -547,35 +498,7 @@ struct Evaluator::Impl { if (attr) { Value* val = attr->value; force(val); - // Copy the forced value's data into v - // For simple types, use mk* methods to ensure proper initialization - // For complex types (attrs, lists, functions), direct assignment is safe - state.forceValue(*val, noPos); - switch (val->type()) { - case nInt: - v.mkInt(val->integer()); - break; - case nBool: - v.mkBool(val->boolean()); - break; - case nString: - v.mkString(val->c_str()); - break; - case nPath: - v.mkPath(val->path()); - break; - case nNull: - v.mkNull(); - break; - case nFloat: - v.mkFloat(val->fpoint()); - break; - default: - // For attrs, lists, functions, etc., direct assignment is safe - // as they use reference counting internally - v = *val; - break; - } + v = *val; } else if (n->default_expr) { eval_node(*n->default_expr, v, env); } else { diff --git a/src/irc/ir_gen.cpp b/src/irc/ir_gen.cpp index 90175f9..ff9f18c 100644 --- a/src/irc/ir_gen.cpp +++ b/src/irc/ir_gen.cpp @@ -1,6 +1,5 @@ #include "ir_gen.h" #include -#include #include #include @@ -98,8 +97,7 @@ struct IRGenerator::Impl { return std::make_shared(*n); } if (auto* n = node.get_if()) { - std::string var_name = n->name.value_or(""); - uint32_t idx = name_resolver.resolve(var_name); + uint32_t idx = name_resolver.resolve(n->name.value_or("")); VarNode converted(idx); converted.name = n->name; converted.line = n->line; @@ -123,17 +121,12 @@ struct IRGenerator::Impl { } if (auto* n = node.get_if()) { AttrsetNode attrs(n->recursive, n->line); - - // Only enter a new scope for recursive attrsets - if (n->recursive) { - name_resolver.enter_scope(); - for (const auto& binding : n->attrs) { - if (!binding.is_dynamic()) { - name_resolver.bind(binding.static_name.value()); - } + name_resolver.enter_scope(); + for (const auto& binding : n->attrs) { + if (!binding.is_dynamic()) { + name_resolver.bind(binding.static_name.value()); } } - for (const auto& binding : n->attrs) { if (binding.is_dynamic()) { attrs.attrs.push_back(AttrBinding(convert(binding.dynamic_name), convert(binding.value))); @@ -141,10 +134,7 @@ struct IRGenerator::Impl { attrs.attrs.push_back(AttrBinding(binding.static_name.value(), convert(binding.value))); } } - - if (n->recursive) { - name_resolver.exit_scope(); - } + name_resolver.exit_scope(); return std::make_shared(attrs); } if (auto* n = node.get_if()) { @@ -218,14 +208,6 @@ struct IRGenerator::Impl { auto operand = convert(n->operand); return std::make_shared(UnaryOpNode(n->op, operand, n->line)); } - if (auto* n = node.get_if()) { - std::vector> elements; - elements.reserve(n->elements.size()); - for (const auto& elem : n->elements) { - elements.push_back(convert(elem)); - } - return std::make_shared(ListNode(std::move(elements), n->line)); - } return std::make_shared(ConstNullNode{}); } }; diff --git a/src/irc/parser.cpp b/src/irc/parser.cpp index 2bb31ed..8d92617 100644 --- a/src/irc/parser.cpp +++ b/src/irc/parser.cpp @@ -636,9 +636,8 @@ private: void tokenize_ident() { size_t start = pos; - // Note: Don't include '.' here - it's used for selection (a.b.c) - // URIs are handled separately by checking for '://' pattern - while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-')) + while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-' || + input[pos] == '+' || input[pos] == '.')) pos++; std::string ident = input.substr(start, pos - start); @@ -928,25 +927,16 @@ public: std::shared_ptr left = parse_expr3(); while (true) { - if (current().type == Token::STRING) { + if (current().type == Token::LBRACKET) { + advance(); + auto arg = parse_expr(); + expect(Token::RBRACKET); + left = std::make_shared(AppNode(left, arg)); + } else if (current().type == Token::STRING) { Token s = current(); advance(); auto arg = std::make_shared(ConstStringNode(s.value)); left = std::make_shared(AppNode(left, arg)); - } else if (current().type == Token::LPAREN) { - // Function application with parenthesized argument: func (expr) - advance(); - auto arg = parse_expr(); - expect(Token::RPAREN); - left = std::make_shared(AppNode(left, arg)); - } else if (current().type == Token::IDENT || current().type == Token::INT || - current().type == Token::FLOAT || current().type == Token::BOOL || - current().type == Token::PATH || current().type == Token::LOOKUP_PATH || - current().type == Token::URI || current().type == Token::LBRACKET) { - // Juxtaposition application: f x - // Parse the argument as a primary expression (which handles lists, etc.) - auto arg = parse_expr3(); - left = std::make_shared(AppNode(left, arg)); } else { break; } @@ -979,16 +969,6 @@ public: return expr; } - // Handle rec { ... } syntax - if (consume(Token::REC)) { - expect(Token::LBRACE); - auto attrs = parse_attrs(); - if (auto* attrset = attrs->get_if()) { - attrset->recursive = true; - } - return attrs; - } - if (consume(Token::LBRACE)) { return parse_attrs(); } @@ -1171,35 +1151,6 @@ public: return std::make_shared(std::move(attrs)); } - // Parse a list element: supports selections but NOT juxtaposition application - // This prevents [1 2 3] from being parsed as ((1 2) 3) - std::shared_ptr parse_list_element() { - auto left = parse_expr3(); - - // Handle selections (a.b.c) - while (current().type == Token::DOT) { - advance(); - Token name = current(); - if (name.type == Token::IDENT) { - advance(); - auto attr = std::make_shared(ConstStringNode(name.value)); - left = std::make_shared(SelectNode(left, attr)); - continue; - } - break; - } - - // Check for 'or' default value - if (left->get_if() && current().type == Token::IDENT && current().value == "or") { - advance(); - auto default_expr = parse_expr3(); - auto* select = left->get_if(); - select->default_expr = default_expr; - } - - return left; - } - std::shared_ptr parse_list() { std::vector> elements; @@ -1207,14 +1158,18 @@ public: return std::make_shared(ListNode(elements)); } - while (current().type != Token::RBRACKET && current().type != Token::EOF_) { - elements.push_back(parse_list_element()); - if (current().type == Token::RBRACKET) { - break; + while (current().type != Token::RBRACKET) { + elements.push_back(parse_expr()); + if (!consume(Token::RBRACKET)) { + // Elements are whitespace-separated in Nix, no comma required + // But we'll continue parsing until we hit ] + } else { + // Found closing bracket + return std::make_shared(ListNode(elements)); } } - expect(Token::RBRACKET); + // Unreachable, but for safety return std::make_shared(ListNode(elements)); } diff --git a/src/irc/serializer.cpp b/src/irc/serializer.cpp index 73051a7..f5b1982 100644 --- a/src/irc/serializer.cpp +++ b/src/irc/serializer.cpp @@ -388,7 +388,7 @@ struct Deserializer::Impl { uint32_t num_elements = read_u32(); std::vector> elements; elements.reserve(num_elements); - for (uint32_t i = 0; i < num_elements; i++) { +for (uint32_t i = 0; i < num_elements; i++) { elements.push_back(read_node()); } return std::make_shared(ListNode(std::move(elements), line)); diff --git a/src/plugin.cpp b/src/plugin.cpp index 4a821f5..18a832c 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -12,7 +12,6 @@ #include "irc/serializer.h" #include "irc/types.h" -#include #include namespace nix_ir_plugin { @@ -30,8 +29,6 @@ static void prim_loadIR(EvalState& state, const PosIdx pos, Value** args, Value& std::string pathStr(path); - auto t_start = std::chrono::high_resolution_clock::now(); - Deserializer deserializer; IRModule module; @@ -41,8 +38,6 @@ static void prim_loadIR(EvalState& state, const PosIdx pos, Value** args, Value& state.error("failed to deserialize IR bundle: %s", e.what()).atPos(pos).debugThrow(); } - auto t_deser = std::chrono::high_resolution_clock::now(); - if (!module.entry) { state.error("IR bundle has no entry point").atPos(pos).debugThrow(); } @@ -53,14 +48,6 @@ static void prim_loadIR(EvalState& state, const PosIdx pos, Value** args, Value& } catch (const std::exception& e) { state.error("failed to evaluate IR: %s", e.what()).atPos(pos).debugThrow(); } - - auto t_eval = std::chrono::high_resolution_clock::now(); - - auto deser_us = std::chrono::duration_cast(t_deser - t_start).count(); - auto eval_us = std::chrono::duration_cast(t_eval - t_deser).count(); - - std::cerr << "nixIR timing: deser=" << deser_us << "us eval=" << eval_us - << "us total=" << (deser_us + eval_us) << "us" << std::endl; } /** @@ -152,7 +139,7 @@ static RegisterPrimOp rp_info({ } // namespace nix_ir_plugin -// Plugin initialization +// Plugin initialization message __attribute__((constructor)) static void init_plugin() { - // Plugin loads silently... + std::cerr << "nix-ir-plugin loaded" << std::endl; } diff --git a/tests/fixtures/ancient_let.nix b/tests/ancient_let.nix similarity index 100% rename from tests/fixtures/ancient_let.nix rename to tests/ancient_let.nix diff --git a/tests/fixtures/attrset.nix b/tests/attrset.nix similarity index 100% rename from tests/fixtures/attrset.nix rename to tests/attrset.nix diff --git a/tests/fixtures/attrset_var.nix b/tests/attrset_var.nix similarity index 100% rename from tests/fixtures/attrset_var.nix rename to tests/attrset_var.nix diff --git a/tests/benchmark/large.nix b/tests/benchmark/large.nix deleted file mode 100644 index bf29a77..0000000 --- a/tests/benchmark/large.nix +++ /dev/null @@ -1,237 +0,0 @@ -# Large benchmark for comprehensive stress testing -let - range = start: end: - if start >= end - then [] - else [start] ++ range (start + 1) end; - - concat = a: b: a ++ b; - - factorial = n: - if n <= 1 - then 1 - else n * factorial (n - 1); - - # Ackermann function (highly recursive) - ackermann = m: n: - if m == 0 - then n + 1 - else if n == 0 - then ackermann (m - 1) 1 - else ackermann (m - 1) (ackermann m (n - 1)); - - # Greatest common divisor - gcd = a: b: - if b == 0 - then a - else gcd b (a - (a / b) * b); - - # Power function - pow = base: exp: - if exp == 0 - then 1 - else if exp == 1 - then base - else base * pow base (exp - 1); - - compose = f: g: x: f (g x); - double = x: x * 2; - addTen = x: x + 10; - square = x: x * x; - - pipeline = compose square (compose double addTen); - - list_100 = range 1 101; - list_50 = range 1 51; - list_25 = range 1 26; - - largeAttrs = { - a1 = 1; - a2 = 2; - a3 = 3; - a4 = 4; - a5 = 5; - a6 = 6; - a7 = 7; - a8 = 8; - a9 = 9; - a10 = 10; - b1 = 11; - b2 = 12; - b3 = 13; - b4 = 14; - b5 = 15; - b6 = 16; - b7 = 17; - b8 = 18; - b9 = 19; - b10 = 20; - c1 = 21; - c2 = 22; - c3 = 23; - c4 = 24; - c5 = 25; - c6 = 26; - c7 = 27; - c8 = 28; - c9 = 29; - c10 = 30; - d1 = 31; - d2 = 32; - d3 = 33; - d4 = 34; - d5 = 35; - d6 = 36; - d7 = 37; - d8 = 38; - d9 = 39; - d10 = 40; - e1 = 41; - e2 = 42; - e3 = 43; - e4 = 44; - e5 = 45; - e6 = 46; - e7 = 47; - e8 = 48; - e9 = 49; - e10 = 50; - }; - - # Very deep nesting (10 levels) - deepNest = { - level1 = { - level2 = { - level3 = { - level4 = { - level5 = { - level6 = { - level7 = { - level8 = { - level9 = { - level10 = { - treasure = "found"; - value = 12345; - }; - }; - }; - }; - }; - }; - }; - }; - }; - }; - }; - - recursiveComplex = rec { - base = 10; - doubled = base * 2; - tripled = base * 3; - - sum = doubled + tripled; - product = doubled * tripled; - - x = base * 4; - y = x + doubled; - z = y * tripled; - - total = sum + product + z; - final = total * base; - }; - - config1 = rec { - multiplier = 5; - base = 100; - result = base * multiplier; - }; - - config2 = rec { - offset = 50; - scaled = config1.result + offset; - doubled = scaled * 2; - }; - - config3 = rec { - factor = 3; - combined = config2.doubled * factor; - final = combined + config1.multiplier; - }; - - baseConfig = { - system = { - arch = "x86_64"; - os = "linux"; - }; - settings = { - enabled = true; - level = 5; - }; - }; - - overrides = { - system = { - kernel = "6.1"; - }; - settings = { - level = 10; - extra = "custom"; - }; - newSection = { - value = 42; - }; - }; - - merged = - baseConfig - // overrides - // { - system = baseConfig.system // overrides.system; - settings = - baseConfig.settings - // overrides.settings - // { - combined = baseConfig.settings.level + overrides.settings.level; - }; - }; - - fact10 = factorial 10; - fact7 = factorial 7; - ack_3_3 = ackermann 3 3; - gcd_48_18 = gcd 48 18; - gcd_100_35 = gcd 100 35; - pow_2_10 = pow 2 10; - pow_3_5 = pow 3 5; - - pipelineResult = pipeline 5; # ((5 + 10) * 2)^2 = 900 - - # List operations - concatenated = concat [1 2 3] [4 5 6]; - multilevel = concat (concat [1] [2 3]) [4 5]; -in { - # Lists - inherit list_100 list_50 list_25 concatenated multilevel; - - # Math results - inherit fact10 fact7 ack_3_3 gcd_48_18 gcd_100_35 pow_2_10 pow_3_5 pipelineResult; - - # Data structures - inherit largeAttrs merged; - deepValue = deepNest.level1.level2.level3.level4.level5.level6.level7.level8.level9.level10.value; - deepTreasure = deepNest.level1.level2.level3.level4.level5.level6.level7.level8.level9.level10.treasure; - - # Recursive attrsets - recursiveTotal = recursiveComplex.total; - recursiveFinal = recursiveComplex.final; - computedZ = recursiveComplex.z; - - # Config chain - config1Result = config1.result; - config2Doubled = config2.doubled; - config3Final = config3.final; - - # Merged config - mergedCombined = merged.settings.combined; - mergedArch = merged.system.arch; - mergedKernel = merged.system.kernel; -} diff --git a/tests/benchmark/medium.nix b/tests/benchmark/medium.nix deleted file mode 100644 index 6234dca..0000000 --- a/tests/benchmark/medium.nix +++ /dev/null @@ -1,75 +0,0 @@ -let - # Recursive factorial - factorial = n: - if n <= 1 - then 1 - else n * factorial (n - 1); - - # Fibonacci sequence generator - fib = n: - if n <= 1 - then n - else fib (n - 1) + fib (n - 2); - - # List concatenation test - range = start: end: - if start >= end - then [] - else [start] ++ range (start + 1) end; - - # Curried function application - add = x: y: x + y; - add5 = add 5; - - # Complex computation - compute = x: y: let - a = x * 2; - b = y + 10; - c = a * b; - in - c / 2; - - # Data structures - numbers = range 1 11; - - # Nested attribute operations - base = { - config = { - enable = true; - value = 42; - }; - data = { - items = [1 2 3]; - }; - }; - - extended = - base - // { - config = - base.config - // { - extra = "test"; - multiplied = base.config.value * 2; - }; - computed = base.config.value + 100; - }; - - # Recursive attrset with selections - recursive = rec { - x = 10; - y = x * 2; - z = y + x; - result = z * 3; - final = result + x; - }; -in { - fact5 = factorial 5; - fib7 = fib 7; - sum15 = add5 10; - computed = compute 10 20; - inherit numbers extended; - deepValue = extended.config.multiplied; - recursiveResult = recursive.result; - recursiveFinal = recursive.final; -} diff --git a/tests/benchmark/run.sh b/tests/benchmark/run.sh deleted file mode 100755 index c392139..0000000 --- a/tests/benchmark/run.sh +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "# Running benchmarks..." -echo "" - -BENCH_DIR="$(pwd)/tests/benchmark" -IRC_BIN="$(pwd)/build/nix-irc" - -GREEN='\033[0;32m' -BLUE='\033[0;34m' -YELLOW='\033[0;33m' -NC='\033[0m' - -get_ms() { - local time_str="$1" - if [[ $time_str =~ ([0-9]+)m([0-9.]+)s ]]; then - local mins="${BASH_REMATCH[1]}" - local secs="${BASH_REMATCH[2]}" - local ms - ms=$(awk "BEGIN {printf \"%.1f\", ($mins * 60000) + ($secs * 1000)}") - echo "$ms" - else - echo "0" - fi -} - -run_benchmark() { - local name="$1" - local file="$2" - - echo -e "${BLUE}=== $name ===${NC}" - echo "" - - # Measure compilation time only - echo -n " Compilation only: " - local compile_start - compile_start=$(date +%s%N) - "$IRC_BIN" "$file" /tmp/bench.nixir >/dev/null 2>&1 - local compile_end - compile_end=$(date +%s%N) - local compile_ms=$(((compile_end - compile_start) / 1000000)) - echo -e "${YELLOW}${compile_ms}ms${NC}" - - # Measure IR loading only (deserialization + evaluation) - echo -n " IR load only: " - PLUGIN_PATH="$(pwd)/build/nix-ir-plugin.so" - if [ ! -f "$PLUGIN_PATH" ]; then - echo -e "${YELLOW}skipped${NC} (plugin not built)" - else - # Pre-compile the IR - "$IRC_BIN" "$file" /tmp/bench.nixir >/dev/null 2>&1 - - # Measure just the loading (average of 10 runs to reduce noise) - local total_load_us=0 - for _ in {1..10}; do - local load_output - load_output=$(nix-instantiate --plugin-files "$PLUGIN_PATH" --eval --expr "builtins.nixIR_loadIR \"/tmp/bench.nixir\"" 2>&1 >/dev/null | grep "nixIR timing" | grep -oP 'total=\K[0-9]+') - total_load_us=$((total_load_us + load_output)) - done - local avg_load_us=$((total_load_us / 10)) - local avg_load_ms_frac=$(awk "BEGIN {printf \"%.3f\", $avg_load_us / 1000}") - echo -e "${GREEN}${avg_load_ms_frac}ms${NC} avg (10 runs)" - fi - - # Measure full pipeline (compile + nix-instantiate overhead + IR load) - echo -n " Full pipeline: " - if [ ! -f "$PLUGIN_PATH" ]; then - echo -e "${YELLOW}skipped${NC}" - else - local pipeline_start - pipeline_start=$(date +%s%N) - "$IRC_BIN" "$file" /tmp/bench.nixir >/dev/null 2>&1 - nix-instantiate --plugin-files "$PLUGIN_PATH" --eval --expr "builtins.nixIR_loadIR \"/tmp/bench.nixir\"" >/dev/null 2>&1 - local pipeline_end - pipeline_end=$(date +%s%N) - local pipeline_ms=$(((pipeline_end - pipeline_start) / 1000000)) - echo -e "${YELLOW}${pipeline_ms}ms${NC}" - fi - - # Source and IR sizes - local src_size - src_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null) - local ir_size - ir_size=$(stat -c%s /tmp/bench.nixir 2>/dev/null || stat -f%z /tmp/bench.nixir 2>/dev/null) - local ratio=0 - if [[ "$src_size" -gt 0 ]]; then - ratio=$((ir_size * 100 / src_size)) - fi - echo -e " Source size: ${src_size}B" - echo -e " IR bundle size: ${ir_size}B (${ratio}% of source)" - - echo "" - - # Native Nix evaluation (baseline) - echo -n " Native Nix eval: " - local native_total=0 - for _ in {1..5}; do - local t - t=$( (time nix-instantiate --eval --strict "$file" >/dev/null 2>&1) 2>&1 | grep "real" | awk '{print $2}') - local ms - ms=$(get_ms "$t") - native_total=$(awk "BEGIN {print $native_total + $ms}") - done - local native_avg - native_avg=$(awk "BEGIN {printf \"%.1f\", $native_total / 5}") - echo -e "${GREEN}${native_avg}ms${NC} avg (5 runs)" - - echo "" -} - -echo "Measuring IR compilation speed and bundle size characteristics." -echo "" - -run_benchmark "Simple Expression" "$BENCH_DIR/simple.nix" -run_benchmark "Medium Complexity" "$BENCH_DIR/medium.nix" -run_benchmark "Large Expression" "$BENCH_DIR/large.nix" - -# Overall statistics -echo -e "${BLUE}=== Overall Statistics ===${NC}" -echo "" - -testdir=$(mktemp -d) -total_nix=0 -total_ir=0 -total_compile_time=0 - -for f in "$BENCH_DIR"/*.nix; do - nixsize=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f" 2>/dev/null) - base=$(basename "$f" .nix) - irfile="${testdir}/${base}.nixir" - - start=$(date +%s%N) - "$IRC_BIN" "$f" "$irfile" >/dev/null 2>&1 - end=$(date +%s%N) - compile_time=$(((end - start) / 1000000)) - - if [ -f "$irfile" ]; then - irsize=$(stat -c%s "$irfile" 2>/dev/null || stat -f%z "$irfile" 2>/dev/null) - total_nix=$((total_nix + nixsize)) - total_ir=$((total_ir + irsize)) - total_compile_time=$((total_compile_time + compile_time)) - fi -done - -total_ratio=$((total_ir * 100 / total_nix)) -avg_compile_time=$((total_compile_time / 3)) - -# TBH those are entirely unnecessary. However, I'm a sucker for data -# and those are trivial to compile. Might as well. Who knows, maybe it'll -# come in handy in the future. -echo " Total source size: ${total_nix}B" -echo " Total IR size: ${total_ir}B" -echo " Compression ratio: ${total_ratio}% of source" -echo " Average compile time: ${avg_compile_time}ms" -echo "" - -rm -rf "$testdir" diff --git a/tests/benchmark/simple.nix b/tests/benchmark/simple.nix deleted file mode 100644 index aec2fc9..0000000 --- a/tests/benchmark/simple.nix +++ /dev/null @@ -1,13 +0,0 @@ -let - x = 10; - y = 20; - z = x + y; -in { - result = z * 2; - list = [1 2 3 4 5]; - attrs = { - a = 1; - b = 2; - c = 3; - }; -} diff --git a/tests/block_comments.nix b/tests/block_comments.nix new file mode 100644 index 0000000..b5de60f --- /dev/null +++ b/tests/block_comments.nix @@ -0,0 +1,12 @@ +# Test block comments /* */ +/* This is a block comment */ +let + x = 42; /* inline block comment */ + /* Multi-line + block + comment */ + y = 100; +in +/* Comment before expression */ +x + y +/* Trailing comment */ diff --git a/tests/fixtures/comparison.nix b/tests/comparison.nix similarity index 100% rename from tests/fixtures/comparison.nix rename to tests/comparison.nix diff --git a/tests/fixtures/dynamic_attrs.nix b/tests/dynamic_attrs.nix similarity index 100% rename from tests/fixtures/dynamic_attrs.nix rename to tests/dynamic_attrs.nix diff --git a/tests/fixtures/block_comments.nix b/tests/fixtures/block_comments.nix deleted file mode 100644 index 301297d..0000000 --- a/tests/fixtures/block_comments.nix +++ /dev/null @@ -1,24 +0,0 @@ -# Test block comments /* */ -/* -This is a block comment -*/ -let - x = 42; - /* - inline block comment - */ - /* - Multi-line - block - comment - */ - y = 100; -in - /* - Comment before expression - */ - x + y -/* -Trailing comment -*/ - diff --git a/tests/fixtures/dynamic_attr_full.nix b/tests/fixtures/dynamic_attr_full.nix deleted file mode 100644 index 3c7b39b..0000000 --- a/tests/fixtures/dynamic_attr_full.nix +++ /dev/null @@ -1,14 +0,0 @@ -# Test dynamic attribute names -let - key = "mykey"; - value = 42; -in { - # Dynamic attribute with string interpolation - "${key}" = value; - - # Another dynamic attribute - "${key}_suffix" = value + 1; - - # Static attribute for comparison - static = 100; -} diff --git a/tests/fixtures/list_simple.nix b/tests/fixtures/list_simple.nix deleted file mode 100644 index 7167967..0000000 --- a/tests/fixtures/list_simple.nix +++ /dev/null @@ -1,8 +0,0 @@ -# Test basic list support -let - x = [1 2 3]; - y = [4 5 6]; - z = x ++ y; # List concatenation -in { - inherit x y z; -} diff --git a/tests/fixtures/or_simple.nix b/tests/fixtures/or_simple.nix deleted file mode 100644 index d4de5be..0000000 --- a/tests/fixtures/or_simple.nix +++ /dev/null @@ -1,5 +0,0 @@ -# Simplest 'or' test -let - x = {a = 1;}; -in - x.a or 2 diff --git a/tests/fixtures/float_test.nix b/tests/float_test.nix similarity index 100% rename from tests/fixtures/float_test.nix rename to tests/float_test.nix diff --git a/tests/fixtures/home_path.nix b/tests/home_path.nix similarity index 100% rename from tests/fixtures/home_path.nix rename to tests/home_path.nix diff --git a/tests/fixtures/if.nix b/tests/if.nix similarity index 100% rename from tests/fixtures/if.nix rename to tests/if.nix diff --git a/tests/fixtures/import_lookup.nix b/tests/import_lookup.nix similarity index 100% rename from tests/fixtures/import_lookup.nix rename to tests/import_lookup.nix diff --git a/tests/fixtures/import_simple.nix b/tests/import_simple.nix similarity index 99% rename from tests/fixtures/import_simple.nix rename to tests/import_simple.nix index 1e024cc..023b49d 100644 --- a/tests/fixtures/import_simple.nix +++ b/tests/import_simple.nix @@ -1,9 +1,11 @@ # Test import expression # Import evaluates the file and returns its value + # Import a file that returns a simple value (42) import ./simple.nix + # Can also import lookup paths: # import { } + # Import with path expressions: # import (./dir + "/file.nix") - diff --git a/tests/fixtures/indented_string.nix b/tests/indented_string.nix similarity index 100% rename from tests/fixtures/indented_string.nix rename to tests/indented_string.nix diff --git a/tests/fixtures/inherit.nix b/tests/inherit.nix similarity index 100% rename from tests/fixtures/inherit.nix rename to tests/inherit.nix diff --git a/tests/fixtures/inherit_from.nix b/tests/inherit_from.nix similarity index 100% rename from tests/fixtures/inherit_from.nix rename to tests/inherit_from.nix diff --git a/tests/fixtures/inherit_simple.nix b/tests/inherit_simple.nix similarity index 100% rename from tests/fixtures/inherit_simple.nix rename to tests/inherit_simple.nix diff --git a/tests/integration/import_test.nix b/tests/integration/import_test.nix deleted file mode 100644 index 7914eea..0000000 --- a/tests/integration/import_test.nix +++ /dev/null @@ -1,7 +0,0 @@ -# Test that import builtin still works -let - imported = import ./imported_module.nix; -in { - value = imported.foo + 100; - nested = imported.bar.baz; -} diff --git a/tests/integration/imported_module.nix b/tests/integration/imported_module.nix deleted file mode 100644 index a9b969c..0000000 --- a/tests/integration/imported_module.nix +++ /dev/null @@ -1,7 +0,0 @@ -# Module to be imported -{ - foo = 42; - bar = { - baz = "hello"; - }; -} diff --git a/tests/integration/ir_builtins_test.nix b/tests/integration/ir_builtins_test.nix deleted file mode 100644 index 4aa28e3..0000000 --- a/tests/integration/ir_builtins_test.nix +++ /dev/null @@ -1,13 +0,0 @@ -# Test our custom IR builtins -let - # Test nixIR_info - info = builtins.nixIR_info; - - # Test nixIR_compile - compiled = builtins.nixIR_compile "let x = 10; in x + 5"; - - # Test that normal builtins still work - list = builtins.map (x: x * 2) [1 2 3]; -in { - inherit info compiled list; -} diff --git a/tests/integration/regression_normal_nix.nix b/tests/integration/regression_normal_nix.nix deleted file mode 100644 index 2eb73f6..0000000 --- a/tests/integration/regression_normal_nix.nix +++ /dev/null @@ -1,39 +0,0 @@ -# Test that normal Nix evaluation is not broken -# This file should work identically with or without the plugin -let - # Basic arithmetic - math = 1 + 2 * 3; - - # String operations - str = "hello" + " " + "world"; - - # List operations - list = [1 2 3] ++ [4 5 6]; - - # Attrset operations - attrs = - { - a = 1; - b = 2; - } - // {c = 3;}; - - # Functions - double = x: x * 2; - result = double 21; - - # Conditionals - cond = - if true - then "yes" - else "no"; - - # Let bindings - nested = let - x = 10; - y = 20; - in - x + y; -in { - inherit math str list attrs result cond nested; -} diff --git a/tests/integration/run.sh b/tests/integration/run.sh deleted file mode 100755 index 8645388..0000000 --- a/tests/integration/run.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "=== Phase 6: Integration Testing ===" -echo "" - -PLUGIN_PATH="$(pwd)/build/nix-ir-plugin.so" -TEST_DIR="$(pwd)/tests/integration" - -if [ ! -f "$PLUGIN_PATH" ]; then - echo "ERROR: Plugin not found at $PLUGIN_PATH" - exit 1 -fi - -echo "Plugin path: $PLUGIN_PATH" -echo "" - -echo "Test 1: Plugin Loading" -echo "----------------------" -if nix-instantiate --plugin-files "$PLUGIN_PATH" --eval "$TEST_DIR/simple_eval.nix" 2>&1 | grep -q "30"; then - echo "[PASS] Plugin loads and evaluates correctly" -else - echo "[FAIL] Plugin failed to load or evaluate" - exit 1 -fi -echo "" - -echo "Test 2: Normal Nix Evaluation (No Plugin)" -echo "------------------------------------------" -result=$(nix-instantiate --eval --strict --json "$TEST_DIR/regression_normal_nix.nix" 2>&1) -if echo "$result" | grep -q '"math":7'; then - echo "[PASS] Normal Nix evaluation works without plugin" -else - echo "[FAIL] Normal Nix evaluation broken" - echo "$result" - exit 1 -fi -echo "" - -echo "Test 3: Normal Nix Evaluation (With Plugin)" -echo "--------------------------------------------" -result=$(nix-instantiate --plugin-files "$PLUGIN_PATH" --eval --strict --json "$TEST_DIR/regression_normal_nix.nix" 2>&1) -if echo "$result" | grep -q '"math":7'; then - echo "[PASS] Normal Nix evaluation works with plugin loaded" -else - echo "[FAIL] Plugin breaks normal Nix evaluation" - echo "$result" - exit 1 -fi -echo "" - -echo "Test 4: Import Builtin" -echo "----------------------" -cd "$TEST_DIR" -result=$(nix-instantiate --plugin-files "$PLUGIN_PATH" --eval --strict --json import_test.nix 2>&1) -if echo "$result" | grep -q '"value":142'; then - echo "[PASS] Import builtin works correctly" -else - echo "[FAIL] Import builtin broken" - echo "$result" - exit 1 -fi -cd - >/dev/null -echo "" - -echo "Test 5: IR Builtins Available" -echo "------------------------------" -result=$(nix-instantiate --plugin-files "$PLUGIN_PATH" --eval "$TEST_DIR/ir_builtins_test.nix" 2>&1) -if echo "$result" | grep -q "info.*="; then - echo "[PASS] IR builtins (nixIR_info, nixIR_compile, nixIR_loadIR) available" -else - echo "[WARN] IR builtins may not be available (check plugin initialization)" -fi -echo "" - -echo "Integration Tests Complete" diff --git a/tests/integration/simple_eval.nix b/tests/integration/simple_eval.nix deleted file mode 100644 index 9dc2744..0000000 --- a/tests/integration/simple_eval.nix +++ /dev/null @@ -1,6 +0,0 @@ -# Simple expression to test plugin loading -let - x = 10; - y = 20; -in - x + y diff --git a/tests/fixtures/lambda_pattern.nix b/tests/lambda_pattern.nix similarity index 100% rename from tests/fixtures/lambda_pattern.nix rename to tests/lambda_pattern.nix diff --git a/tests/fixtures/let.nix b/tests/let.nix similarity index 100% rename from tests/fixtures/let.nix rename to tests/let.nix diff --git a/tests/fixtures/list_concat.nix b/tests/list_concat.nix similarity index 100% rename from tests/fixtures/list_concat.nix rename to tests/list_concat.nix diff --git a/tests/fixtures/logical.nix b/tests/logical.nix similarity index 100% rename from tests/fixtures/logical.nix rename to tests/logical.nix diff --git a/tests/fixtures/lookup_path.nix b/tests/lookup_path.nix similarity index 99% rename from tests/fixtures/lookup_path.nix rename to tests/lookup_path.nix index a770360..e8bb4ca 100644 --- a/tests/fixtures/lookup_path.nix +++ b/tests/lookup_path.nix @@ -1,8 +1,9 @@ # Test lookup path syntax # Lookup paths resolve via NIX_PATH environment variable # Example: -> /nix/var/nix/profiles/per-user/root/channels/nixpkgs + # Simple lookup path + # Nested lookup path (common pattern) # - diff --git a/tests/fixtures/lookup_path_nested.nix b/tests/lookup_path_nested.nix similarity index 100% rename from tests/fixtures/lookup_path_nested.nix rename to tests/lookup_path_nested.nix diff --git a/tests/fixtures/merge.nix b/tests/merge.nix similarity index 100% rename from tests/fixtures/merge.nix rename to tests/merge.nix diff --git a/tests/fixtures/nested_attrs.nix b/tests/nested_attrs.nix similarity index 100% rename from tests/fixtures/nested_attrs.nix rename to tests/nested_attrs.nix diff --git a/tests/fixtures/operators.nix b/tests/operators.nix similarity index 100% rename from tests/fixtures/operators.nix rename to tests/operators.nix diff --git a/tests/fixtures/or_in_attrset.nix b/tests/or_in_attrset.nix similarity index 75% rename from tests/fixtures/or_in_attrset.nix rename to tests/or_in_attrset.nix index d1ab7f9..406149b 100644 --- a/tests/fixtures/or_in_attrset.nix +++ b/tests/or_in_attrset.nix @@ -1,6 +1,6 @@ # Test 'or' in attrset context let - attrs = {a = 1;}; + attrs = { a = 1; }; in { test = attrs.a or 999; } diff --git a/tests/or_simple.nix b/tests/or_simple.nix new file mode 100644 index 0000000..6025a4d --- /dev/null +++ b/tests/or_simple.nix @@ -0,0 +1,4 @@ +# Simplest 'or' test +let + x = { a = 1; }; +in x.a or 2 diff --git a/tests/fixtures/path_concat.nix b/tests/path_concat.nix similarity index 100% rename from tests/fixtures/path_concat.nix rename to tests/path_concat.nix diff --git a/tests/fixtures/precedence.nix b/tests/precedence.nix similarity index 100% rename from tests/fixtures/precedence.nix rename to tests/precedence.nix diff --git a/tests/regression_test.cpp b/tests/regression_test.cpp index f9f9914..1b4c4ce 100644 --- a/tests/regression_test.cpp +++ b/tests/regression_test.cpp @@ -1,4 +1,3 @@ -#include "irc/parser.h" #include "irc/serializer.h" #include "irc/types.h" #include @@ -8,21 +7,21 @@ 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; \ - } \ +#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++; \ +#define TEST_FAIL(msg) \ + do { \ + std::cerr << " FAIL: " << msg << std::endl; \ + failures++; \ } while (0) void test_enum_compatibility() { @@ -31,28 +30,33 @@ void test_enum_compatibility() { if (static_cast(NodeType::WITH) == 0x32) { std::cout << " PASS: WITH has correct value 0x32" << std::endl; } else { - std::cerr << " FAIL: WITH should be 0x32, got " << static_cast(NodeType::WITH) - << std::endl; + std::cerr << " FAIL: WITH should be 0x32, got " + << static_cast(NodeType::WITH) << std::endl; } if (static_cast(NodeType::HAS_ATTR) == 0x34) { - std::cout << " PASS: HAS_ATTR has value 0x34 (new slot after WITH bump)" << std::endl; + std::cout << " PASS: HAS_ATTR has value 0x34 (new slot after WITH bump)" + << std::endl; } else if (static_cast(NodeType::HAS_ATTR) == 0x33 && static_cast(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(NodeType::HAS_ATTR) + std::cerr << " FAIL: HAS_ATTR value is " + << static_cast(NodeType::HAS_ATTR) << " (expected 0x34 or 0x33 with WITH=0x32)" << std::endl; } if (IR_VERSION == 2) { - std::cout << " PASS: IR_VERSION bumped to 2 for breaking change" << std::endl; + std::cout << " PASS: IR_VERSION bumped to 2 for breaking change" + << std::endl; } else if (static_cast(NodeType::WITH) == 0x32) { - std::cout << " PASS: IR_VERSION unchanged but WITH restored to 0x32" << std::endl; + std::cout << " PASS: IR_VERSION unchanged but WITH restored to 0x32" + << std::endl; } else { - std::cerr << " FAIL: Either bump IR_VERSION or fix enum values" << std::endl; + std::cerr << " FAIL: Either bump IR_VERSION or fix enum values" + << std::endl; } } @@ -76,16 +80,19 @@ void test_serializer_select_with_default() { Deserializer deser; auto loaded = deser.deserialize(bytes); - auto* loaded_select = loaded.entry->get_if(); - if (loaded_select && loaded_select->default_expr && *loaded_select->default_expr) { - auto* def_val = (*loaded_select->default_expr)->get_if(); + auto *loaded_select = loaded.entry->get_if(); + if (loaded_select && loaded_select->default_expr && + *loaded_select->default_expr) { + auto *def_val = (*loaded_select->default_expr)->get_if(); if (def_val && def_val->value == 100) { - std::cout << " PASS: SELECT with default_expr round-trips correctly" << std::endl; + 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; + std::cerr << " FAIL: default_expr not deserialized (missing u8 flag read)" + << std::endl; } } @@ -107,9 +114,11 @@ void test_serializer_select_without_default() { Deserializer deser; auto loaded = deser.deserialize(bytes); - auto* loaded_select = loaded.entry->get_if(); - if (loaded_select && (!loaded_select->default_expr || !*loaded_select->default_expr)) { - std::cout << " PASS: SELECT without default_expr round-trips correctly" << std::endl; + auto *loaded_select = loaded.entry->get_if(); + 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; } @@ -118,53 +127,34 @@ void test_serializer_select_without_default() { 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)"; + 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"); - } + std::cout << " Test input contains '}' inside string - should not end " + "interpolation" + << std::endl; + std::cout << " NOTE: This test requires running through actual parser" + << std::endl; } 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"); - } + std::cout << " NOTE: LambdaNode should have strict_pattern field when " + "has_ellipsis is false" + << std::endl; + std::cout << " This requires checking the parser output for strict patterns" + << std::endl; } 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"); - } + std::cout << " NOTE: try_parse_lambda should not throw on non-lambda input" + << std::endl; + std::cout << " This requires testing parser with invalid lambda patterns" + << std::endl; } void test_lookup_path_node() { @@ -180,9 +170,10 @@ void test_lookup_path_node() { Deserializer deser; auto loaded = deser.deserialize(bytes); - auto* loaded_lookup = loaded.entry->get_if(); + auto *loaded_lookup = loaded.entry->get_if(); TEST_CHECK(loaded_lookup != nullptr, "Deserialized node is ConstLookupPathNode"); - TEST_CHECK(loaded_lookup && loaded_lookup->value == "nixpkgs", "Lookup path value is 'nixpkgs'"); + TEST_CHECK(loaded_lookup && loaded_lookup->value == "nixpkgs", + "Lookup path value is 'nixpkgs'"); } void test_import_node() { @@ -199,14 +190,16 @@ void test_import_node() { Deserializer deser; auto loaded = deser.deserialize(bytes); - auto* loaded_import = loaded.entry->get_if(); + auto *loaded_import = loaded.entry->get_if(); TEST_CHECK(loaded_import != nullptr, "Deserialized node is ImportNode"); - TEST_CHECK(loaded_import && loaded_import->path != nullptr, "Import node has path"); + 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(); + auto *path_node = loaded_import->path->get_if(); TEST_CHECK(path_node != nullptr, "Import path is ConstPathNode"); - TEST_CHECK(path_node && path_node->value == "./test.nix", "Import path value is './test.nix'"); + TEST_CHECK(path_node && path_node->value == "./test.nix", + "Import path value is './test.nix'"); } } @@ -224,13 +217,14 @@ void test_import_with_lookup_path() { Deserializer deser; auto loaded = deser.deserialize(bytes); - auto* loaded_import = loaded.entry->get_if(); + auto *loaded_import = loaded.entry->get_if(); TEST_CHECK(loaded_import != nullptr, "Deserialized node is ImportNode"); if (loaded_import && loaded_import->path) { - auto* lookup_node = loaded_import->path->get_if(); + auto *lookup_node = loaded_import->path->get_if(); TEST_CHECK(lookup_node != nullptr, "Import path is ConstLookupPathNode"); - TEST_CHECK(lookup_node && lookup_node->value == "nixpkgs", "Lookup path value is 'nixpkgs'"); + TEST_CHECK(lookup_node && lookup_node->value == "nixpkgs", + "Lookup path value is 'nixpkgs'"); } } @@ -247,7 +241,7 @@ void test_uri_node() { Deserializer deser; auto loaded = deser.deserialize(bytes); - auto* loaded_uri = loaded.entry->get_if(); + auto *loaded_uri = loaded.entry->get_if(); 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'"); @@ -266,9 +260,10 @@ void test_float_node() { Deserializer deser; auto loaded = deser.deserialize(bytes); - auto* loaded_float = loaded.entry->get_if(); + auto *loaded_float = loaded.entry->get_if(); TEST_CHECK(loaded_float != nullptr, "Deserialized node is ConstFloatNode"); - TEST_CHECK(loaded_float && loaded_float->value > 3.14 && loaded_float->value < 3.15, + TEST_CHECK(loaded_float && loaded_float->value > 3.14 && + loaded_float->value < 3.15, "Float value is approximately 3.14159"); } diff --git a/tests/fixtures/select_or_default.nix b/tests/select_or_default.nix similarity index 90% rename from tests/fixtures/select_or_default.nix rename to tests/select_or_default.nix index 6144ff5..df91875 100644 --- a/tests/fixtures/select_or_default.nix +++ b/tests/select_or_default.nix @@ -1,9 +1,6 @@ # Test selection with 'or' default let - attrs = { - a = 1; - b = 2; - }; + attrs = { a = 1; b = 2; }; in { # Attribute exists - should use value from attrs has_attr = attrs.a or 999; diff --git a/tests/fixtures/shortcircuit.nix b/tests/shortcircuit.nix similarity index 100% rename from tests/fixtures/shortcircuit.nix rename to tests/shortcircuit.nix diff --git a/tests/fixtures/shortcircuit2.nix b/tests/shortcircuit2.nix similarity index 100% rename from tests/fixtures/shortcircuit2.nix rename to tests/shortcircuit2.nix diff --git a/tests/fixtures/simple.nix b/tests/simple.nix similarity index 100% rename from tests/fixtures/simple.nix rename to tests/simple.nix diff --git a/tests/fixtures/simple_op.nix b/tests/simple_op.nix similarity index 100% rename from tests/fixtures/simple_op.nix rename to tests/simple_op.nix diff --git a/tests/fixtures/string_interp.nix b/tests/string_interp.nix similarity index 100% rename from tests/fixtures/string_interp.nix rename to tests/string_interp.nix diff --git a/tests/fixtures/unary.nix b/tests/unary.nix similarity index 100% rename from tests/fixtures/unary.nix rename to tests/unary.nix diff --git a/tests/fixtures/uri_test.nix b/tests/uri_test.nix similarity index 100% rename from tests/fixtures/uri_test.nix rename to tests/uri_test.nix