diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 13d0652..0000000 --- a/.clang-format +++ /dev/null @@ -1,18 +0,0 @@ ---- -Language: Cpp -BasedOnStyle: LLVM -IndentWidth: 2 -TabWidth: 2 -UseTab: Never -ColumnLimit: 100 -BreakBeforeBraces: Attach -AllowShortFunctionsOnASingleLine: Inline -AllowShortIfStatementsOnASingleLine: Never -AllowShortLoopsOnASingleLine: false -SpaceAfterCStyleCast: true -SpaceBeforeParens: ControlStatements -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -NamespaceIndentation: None -PointerAlignment: Left -ReferenceAlignment: Left diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ddb05bd..0000000 --- a/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -build/ -nix/ -result* -/.direnv - -# Build artifacts -nix-irc -*.so -**/*.nixir -regression_test - -# Generated files -cmake_install.cmake -Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt index edb503c..c310f42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,22 +73,3 @@ set_target_properties(nix-ir-plugin PROPERTIES # Install to plugin directory install(TARGETS nix-ir-plugin LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/nix/plugins") - -# Regression tests -add_executable(regression_test - tests/regression_test.cpp - src/irc/serializer.cpp -) - -target_include_directories(regression_test PRIVATE - ${CMAKE_SOURCE_DIR}/src - ${NIX_STORE_INCLUDE_DIRS} - ${NIX_EXPR_INCLUDE_DIRS} - ${NIX_UTIL_INCLUDE_DIRS} -) - -target_link_libraries(regression_test PRIVATE - ${NIX_STORE_LINK_LIBRARIES} - ${NIX_EXPR_LINK_LIBRARIES} - ${NIX_UTIL_LINK_LIBRARIES} -) diff --git a/README.md b/README.md index 1048d9c..25cf524 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ where the plugin parses and compiles Nix code at runtime, or; **ahead-of-time** compilation where the `nix-irc` tool pre-compiles `.nix` files into `.nixir` files. +The plugin automatically chooses the fastest path based on file availability. + ## Supported Nix Constructs - Literals: integers, strings, booleans, null, paths @@ -25,160 +27,8 @@ files. `||`, `->` - Unary: `-`, `!` -## Overview - -Nixir is a Nix evaluator plugin that compiles Nix expressions to a custom binary -intermediate representation (IR). Think of it like a compiler for Nix: it -translates human-readable Nix code into a compact, fast-to-execute format that -runs in a custom virtual machine. - -The plugin works in two ways: - -1. **Ahead-of-time**: Use the `nix-irc` tool to compile `.nix` files to `.nixir` - once, then load them instantly -2. **On-the-fly**: Let the plugin parse and compile Nix code at runtime when you - need it - -While Nixir _is_ designed as a toy research project, I _envision_[^1] a few -_potential_ use cases built around working with Nix. Sure, you _probably_ would -not go work with Nix willingly, science is not about why, it is about _why not_. - -Some potential use cases for Nixir _might_ include: - -- **CI/CD Acceleration**: Pre-compile stable Nix expressions to `.nixir` for - faster repeated evaluation in CI pipelines -- **Embedded Nix**: Use Nix as a configuration language in C++ applications - without bundling the full evaluator -- **Plugin Ecosystem**: Extend Nix with custom evaluation strategies via the - plugin API -- **Build Caching**: Cache compiled IR alongside source for instant startup of - Nix-based tools - -[^1]: I'm not entirely convinced either, do not ask. - -### Architecture - -```mermaid -flowchart TD - - subgraph Source["User Source"] - A[".nix Source Files"] - end - - subgraph Compiler["External Tool: nix-irc"] - B1["Parse Nix"] - B2["Static Import Resolution"] - B3["Flatten Import Graph"] - B4["Desugar + De Bruijn Conversion"] - B5["Emit Versioned IR Bundle (.nixir)"] - end - - subgraph IR["IR Bundle"] - C1["Binary IR Format"] - C2["Versioned Header"] - C3["No Names, Indexed Vars"] - end - - subgraph Plugin["nix-ir-plugin.so"] - D1["Primop Registration"] - D2["prim_loadIR"] - D3["prim_compileNix"] - D4["prim_info"] - end - - subgraph CompilePath["On-the-fly Path"] - E1["Parse Source String"] - E2["IR Generation"] - end - - subgraph LoadPath["Pre-compiled Path"] - F1["Deserialize .nixir"] - end - - subgraph VM["Custom Lazy VM"] - G1["Heap-Allocated Thunks"] - G2["Memoization"] - G3["Cycle Detection"] - G4["Closure Environments (Array-Based)"] - G5["FORCE / THUNK Execution"] - end - - A --> B1 - B1 --> B2 - B2 --> B3 - B3 --> B4 - B4 --> B5 - B5 --> C1 - - C1 --> D1 - - D2 -->|explicit| F1 - F1 --> G1 - - D3 -->|explicit| E1 - E1 --> E2 - E2 --> G1 - - G1 -.-> G2 -.-> G3 -.-> G4 -.-> G5 -``` - -The same compiler code runs both in the standalone `nix-irc` CLI tool and inside -the plugin for on-the-fly compilation. This ensures consistent behavior between -pre-compiled and runtime-compiled paths. The intermediate representation (IR) -design uses De Brujin indices instead of names for variable binding, which -eliminates string lookup and the binary format uses a versioned header -(`0x4E495258`). In addition, we make use of string interning for repeated -identifiers and type-tagged nodes for efficient dispatching. - -The runtime implements lazy evaluation using heap-allocated thunks. Each thunk -holds a delayed computation and is evaluated at most once through memoization. -Recursive definitions are handled through a blackhole mechanism that detects -cycles at runtime. Variable lookup uses array-based closure environments, -providing O(1) access by index rather than name-based lookup. - -The plugin integrates with Nix through the `RegisterPrimOp` API, exposing three -operations: `nixIR_loadIR` for loading pre-compiled `.nixir` bundles, -`nixIR_compile` for on-the-fly compilation, and `nixIR_info` for metadata. This -integration path is compatible with Nix 2.32+. - -### IR Format - -The `.nixir` files use a versioned binary format: - -```plaintext -Header: - - Magic: 0x4E495258 ("NIRX") - - Version: 1 (uint32) - - Source count: uint32 - - Import count: uint32 - - String table size: uint32 - -String Table: - - Interned strings for efficient storage - -Nodes: - - Binary encoding of IR nodes - - Each node has type tag + inline data - -Entry: - - Main expression node index -``` - ## Usage -### Building - -```bash -# Configure -$ cmake -B build - -# Build -$ make - -# The nix-irc executable will be in the project root -$ ./nix-irc --help -``` - ### Compiling Nix to IR ```bash @@ -192,48 +42,13 @@ $ nix-irc -I ./lib -I /nix/store/... input.nix output.nixir $ nix-irc --no-imports input.nix output.nixir ``` -### Runtime Evaluation (Plugin) - - - -```bash -# Load the plugin and evaluate IR -$ nix --plugin-files ./nix-ir-plugin.so eval --expr 'builtins.nixIR_loadIR "output.nixir"' - -# On-the-fly compilation and evaluation -$ nix --plugin-files ./nix-ir-plugin.so eval --expr 'builtins.nixIR_compile "1 + 2 * 3"' - -# Get plugin info -$ nix --plugin-files ./nix-ir-plugin.so eval --expr 'builtins.nixIR_info' -``` - - - -### Running Tests - -```bash -# Test all sample files -for f in tests/*.nix; do - ./nix-irc "$f" "${f%.nix}.nixir" -done - -# Verify IR format -$ hexdump -C tests/simple.nixir | head -3 -``` - ## Contributing -This is a research project (with no formal association, i.e., no thesis or -anything) that I'm working on entirely for fun and out of curiousity. Extremely -experimental, could change any time. While I do not suggest running this project -in a serious context, I am happy to receive any kind of feedback you might have. -You will notice _very_ quickly that I'm a little out of my depth, and the code -is in a rough shape. Areas where help is needed: +This is a research/experimental project. Contributions welcome! -- Compiler semantics +Areas where help is needed: + +- Expanding parser to handle more Nix syntax - Performance optimization - Test coverage - Documentation improvements -- Expanding parser to handle more Nix syntax (module system in particular) - -Contributions _are_ welcome! diff --git a/src/irc/evaluator.cpp b/src/irc/evaluator.cpp index 977ea85..8aa64c7 100644 --- a/src/irc/evaluator.cpp +++ b/src/irc/evaluator.cpp @@ -21,6 +21,10 @@ struct IREnvironment { explicit IREnvironment(IREnvironment* p = nullptr) : parent(p), with_attrs(nullptr) {} + IREnvironment* push() { + return new IREnvironment(this); + } + void bind(Value* val) { bindings.push_back(val); } @@ -100,7 +104,7 @@ struct Evaluator::Impl { thunk->blackholed = true; eval_node(thunk->expr, *v, thunk->env); - thunks.erase(v); + thunks.erase(it); } void eval_node(const std::shared_ptr& node, Value& v, IREnvironment* env) { @@ -227,8 +231,6 @@ struct Evaluator::Impl { case BinaryOp::ADD: if (left->type() == nInt && right->type() == nInt) { v.mkInt((left->integer() + right->integer()).valueWrapping()); - } else if (left->type() == nString && right->type() == nString) { - v.mkString(std::string(left->c_str()) + std::string(right->c_str())); } else { state.error("type error in addition").debugThrow(); } @@ -266,8 +268,6 @@ struct Evaluator::Impl { case BinaryOp::LT: if (left->type() == nInt && right->type() == nInt) { v.mkBool(left->integer() < right->integer()); - } else if (left->type() == nString && right->type() == nString) { - v.mkBool(std::string(left->c_str()) < std::string(right->c_str())); } else { state.error("type error in comparison").debugThrow(); } @@ -275,8 +275,6 @@ struct Evaluator::Impl { case BinaryOp::GT: if (left->type() == nInt && right->type() == nInt) { v.mkBool(left->integer() > right->integer()); - } else if (left->type() == nString && right->type() == nString) { - v.mkBool(std::string(left->c_str()) > std::string(right->c_str())); } else { state.error("type error in comparison").debugThrow(); } @@ -284,8 +282,6 @@ struct Evaluator::Impl { case BinaryOp::LE: if (left->type() == nInt && right->type() == nInt) { v.mkBool(left->integer() <= right->integer()); - } else if (left->type() == nString && right->type() == nString) { - v.mkBool(std::string(left->c_str()) <= std::string(right->c_str())); } else { state.error("type error in comparison").debugThrow(); } @@ -293,15 +289,16 @@ struct Evaluator::Impl { case BinaryOp::GE: if (left->type() == nInt && right->type() == nInt) { v.mkBool(left->integer() >= right->integer()); - } else if (left->type() == nString && right->type() == nString) { - v.mkBool(std::string(left->c_str()) >= std::string(right->c_str())); } else { state.error("type error in comparison").debugThrow(); } break; case BinaryOp::CONCAT: - // ++ is list concatenation in Nix; string concat uses ADD (+) - state.error("list concatenation not yet implemented").debugThrow(); + if (left->type() == nString && right->type() == nString) { + v.mkString(std::string(left->c_str()) + std::string(right->c_str())); + } else { + state.error("type error in concatenation").debugThrow(); + } break; default: state.error("unknown binary operator").debugThrow(); @@ -413,9 +410,7 @@ struct Evaluator::Impl { auto attr = obj->attrs()->get(sym); if (attr) { - Value* val = attr->value; - force(val); - v = *val; + v = *attr->value; } else if (n->default_expr) { eval_node(*n->default_expr, v, env); } else { diff --git a/src/irc/parser.cpp b/src/irc/parser.cpp index 8a47e2a..3ec6f86 100644 --- a/src/irc/parser.cpp +++ b/src/irc/parser.cpp @@ -27,10 +27,7 @@ static std::string read_file(const std::string& path) { long size = ftell(f); fseek(f, 0, SEEK_SET); std::string content(size, '\0'); - if (fread(content.data(), 1, size, f) != static_cast(size)) { - fclose(f); - throw std::runtime_error("Failed to read file: " + path); - } + fread(content.data(), 1, size, f); fclose(f); return content; } @@ -764,11 +761,7 @@ public: } } - if (!consume(Token::RBRACE)) { - // Not a lambda pattern, restore - pos = saved_pos; - return nullptr; - } + expect(Token::RBRACE); if (!consume(Token::COLON)) { // Not a lambda, restore @@ -824,7 +817,6 @@ public: // Create lambda auto lambda = LambdaNode(1, let_node); lambda.param_name = arg_name; - lambda.strict_pattern = !has_ellipsis; return std::make_shared(std::move(lambda)); } @@ -850,33 +842,13 @@ public: i += 2; // Skip ${ int depth = 1; size_t expr_start = i; - bool in_string = false; - char string_quote = 0; while (i < raw.size() && depth > 0) { - if (!in_string) { - if (raw[i] == '"' || raw[i] == '\'') { - in_string = true; - string_quote = raw[i]; - } else if (raw[i] == '{') { - depth++; - } else if (raw[i] == '}') { - depth--; - } - } else { - if (raw[i] == string_quote && (i == 0 || raw[i-1] != '\\')) { - in_string = false; - } else if (raw[i] == '\\') { - i++; - } - } + if (raw[i] == '{') depth++; + else if (raw[i] == '}') depth--; if (depth > 0) i++; } - if (depth > 0) { - throw std::runtime_error("unterminated ${ in string interpolation"); - } - // Parse the expression std::string expr_str = raw.substr(expr_start, i - expr_start); @@ -921,8 +893,7 @@ public: auto result = parts[0]; for (size_t j = 1; j < parts.size(); j++) { - // Use ADD (+) for string concatenation; CONCAT (++) is Nix list concatenation - result = std::make_shared(BinaryOpNode(BinaryOp::ADD, result, parts[j])); + result = std::make_shared(BinaryOpNode(BinaryOp::CONCAT, result, parts[j])); } return result; diff --git a/src/irc/serializer.cpp b/src/irc/serializer.cpp index 8819789..7a83e55 100644 --- a/src/irc/serializer.cpp +++ b/src/irc/serializer.cpp @@ -271,14 +271,7 @@ struct Deserializer::Impl { case NodeType::SELECT: { auto expr = read_node(); auto attr = read_node(); - uint8_t has_default = read_u8(); - std::optional> default_expr; - if (has_default) { - default_expr = read_node(); - } - SelectNode select_node(expr, attr, line); - select_node.default_expr = default_expr; - return std::make_shared(std::move(select_node)); + return std::make_shared(SelectNode(expr, attr, line)); } case NodeType::HAS_ATTR: { auto expr = read_node(); diff --git a/src/irc/types.h b/src/irc/types.h index d10acf1..50f1cb9 100644 --- a/src/irc/types.h +++ b/src/irc/types.h @@ -14,7 +14,7 @@ namespace nix_irc { constexpr uint32_t IR_MAGIC = 0x4E495258; -constexpr uint32_t IR_VERSION = 2; +constexpr uint32_t IR_VERSION = 1; enum class NodeType : uint8_t { CONST_INT = 0x01, @@ -29,8 +29,8 @@ enum class NodeType : uint8_t { UNARY_OP = 0x23, ATTRSET = 0x30, SELECT = 0x31, - HAS_ATTR = 0x34, - WITH = 0x32, + HAS_ATTR = 0x32, + WITH = 0x33, IF = 0x40, LET = 0x50, LETREC = 0x51, @@ -94,7 +94,6 @@ struct LambdaNode { uint32_t arity = 1; std::shared_ptr body; std::optional param_name; - bool strict_pattern = true; uint32_t line = 0; LambdaNode(uint32_t a, std::shared_ptr b, uint32_t l = 0); }; diff --git a/tests/attrset.nix b/tests/attrset.nix deleted file mode 100644 index 18425c5..0000000 --- a/tests/attrset.nix +++ /dev/null @@ -1,8 +0,0 @@ -# Attrset test -{ - name = "test"; - value = 123; - nested = { - inner = true; - }; -} diff --git a/tests/attrset.nixir b/tests/attrset.nixir deleted file mode 100644 index 708f5dd..0000000 Binary files a/tests/attrset.nixir and /dev/null differ diff --git a/tests/attrset_var.nix b/tests/attrset_var.nix deleted file mode 100644 index 11e1da6..0000000 --- a/tests/attrset_var.nix +++ /dev/null @@ -1,4 +0,0 @@ -let - x = 10; -in - { a = x; } diff --git a/tests/comparison.nix b/tests/comparison.nix deleted file mode 100644 index be42016..0000000 --- a/tests/comparison.nix +++ /dev/null @@ -1,6 +0,0 @@ -# Test comparison operators -let - a = 10; - b = 20; -in - if a < b then true else false diff --git a/tests/comparison.nixir b/tests/comparison.nixir deleted file mode 100644 index fb7b4fd..0000000 Binary files a/tests/comparison.nixir and /dev/null differ diff --git a/tests/if.nix b/tests/if.nix deleted file mode 100644 index 0ef94a5..0000000 --- a/tests/if.nix +++ /dev/null @@ -1,2 +0,0 @@ -# Conditional test -if true then 1 else 2 diff --git a/tests/if.nixir b/tests/if.nixir deleted file mode 100644 index 4ee0f59..0000000 Binary files a/tests/if.nixir and /dev/null differ diff --git a/tests/inherit.nix b/tests/inherit.nix deleted file mode 100644 index 470cccc..0000000 --- a/tests/inherit.nix +++ /dev/null @@ -1,17 +0,0 @@ -# Test inherit keyword -let - x = 10; - y = 20; - attrs = { a = 1; b = 2; c = 3; }; -in - { - # Basic inherit from outer scope - inherit x y; - - # Inherit from expression - inherit (attrs) a b; - - # Mixed - z = 30; - inherit (attrs) c; - } diff --git a/tests/inherit_from.nix b/tests/inherit_from.nix deleted file mode 100644 index e2d3797..0000000 --- a/tests/inherit_from.nix +++ /dev/null @@ -1,4 +0,0 @@ -let - attrs = { a = 1; }; -in - { inherit (attrs) a; } diff --git a/tests/inherit_simple.nix b/tests/inherit_simple.nix deleted file mode 100644 index 6c004ca..0000000 --- a/tests/inherit_simple.nix +++ /dev/null @@ -1,4 +0,0 @@ -let - x = 10; -in - { inherit x; } diff --git a/tests/lambda_pattern.nix b/tests/lambda_pattern.nix deleted file mode 100644 index dfbef9b..0000000 --- a/tests/lambda_pattern.nix +++ /dev/null @@ -1,36 +0,0 @@ -# Test lambda patterns -let - # Basic destructuring - f1 = { a, b }: a + b; - - # With default values - f2 = { a, b ? 10 }: a + b; - - # With ellipsis (extra fields allowed) - f3 = { a, ... }: a * 2; - - # Named pattern with ellipsis to allow extra fields - f4 = arg@{ a, b, ... }: a + b + arg.c; - - # Simple lambda (not a pattern) - f5 = x: x + 1; -in - { - # Test basic destructuring - test1 = f1 { a = 3; b = 4; }; - - # Test with defaults (provide both) - test2a = f2 { a = 5; b = 6; }; - - # Test with defaults (use default for b) - test2b = f2 { a = 5; }; - - # Test ellipsis (extra field ignored) - test3 = f3 { a = 7; extra = 999; }; - - # Test named pattern - test4 = f4 { a = 1; b = 2; c = 3; }; - - # Test simple lambda - test5 = f5 10; - } diff --git a/tests/let.nix b/tests/let.nix deleted file mode 100644 index d9c706b..0000000 --- a/tests/let.nix +++ /dev/null @@ -1,5 +0,0 @@ -# Let binding test -let - x = 10; - y = 20; -in x diff --git a/tests/let.nixir b/tests/let.nixir deleted file mode 100644 index cb9dd41..0000000 Binary files a/tests/let.nixir and /dev/null differ diff --git a/tests/logical.nix b/tests/logical.nix deleted file mode 100644 index e2e2dac..0000000 --- a/tests/logical.nix +++ /dev/null @@ -1,6 +0,0 @@ -# Test logical operators -let - x = true; - y = false; -in - if x && y then 1 else if x || y then 2 else 3 diff --git a/tests/logical.nixir b/tests/logical.nixir deleted file mode 100644 index 010a5f5..0000000 Binary files a/tests/logical.nixir and /dev/null differ diff --git a/tests/operators.nix b/tests/operators.nix deleted file mode 100644 index c9e383b..0000000 --- a/tests/operators.nix +++ /dev/null @@ -1,6 +0,0 @@ -# Test arithmetic operators -let - x = 10; - y = 5; -in - (x + y) * 2 diff --git a/tests/operators.nixir b/tests/operators.nixir deleted file mode 100644 index f71f899..0000000 Binary files a/tests/operators.nixir and /dev/null differ diff --git a/tests/precedence.nix b/tests/precedence.nix deleted file mode 100644 index 2949308..0000000 --- a/tests/precedence.nix +++ /dev/null @@ -1,8 +0,0 @@ -# Test operator precedence -let - a = 1 + 2 * 3; # Should be 1 + (2 * 3) = 7 - b = 10 - 5 - 2; # Should be (10 - 5) - 2 = 3 - c = true && false || true; # Should be (true && false) || true = true - d = 1 < 2 && 3 > 2; # Should be (1 < 2) && (3 > 2) = true -in - { a = a; b = b; c = c; d = d; } diff --git a/tests/precedence.nixir b/tests/precedence.nixir deleted file mode 100644 index de1b0d4..0000000 Binary files a/tests/precedence.nixir and /dev/null differ diff --git a/tests/regression_test.cpp b/tests/regression_test.cpp deleted file mode 100644 index 10123a0..0000000 --- a/tests/regression_test.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include "irc/serializer.h" -#include "irc/types.h" -#include -#include - -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(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; - } - - if (static_cast(NodeType::HAS_ATTR) == 0x34) { - 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) - << " (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; - } else if (static_cast(NodeType::WITH) == 0x32) { - 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; - } -} - -void test_serializer_select_with_default() { - std::cout << "> SELECT serialization with default_expr..." << std::endl; - - auto expr = std::make_shared(ConstIntNode(42)); - auto attr = std::make_shared(ConstStringNode("key")); - auto default_val = std::make_shared(ConstIntNode(100)); - - SelectNode select_node(expr, attr); - select_node.default_expr = default_val; - auto select = std::make_shared(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(); - 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; - } 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(ConstIntNode(42)); - auto attr = std::make_shared(ConstStringNode("key")); - - SelectNode select_node(expr, attr); - auto select = std::make_shared(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(); - 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} - )"; - - 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::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::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; -} - -int main() { - std::cout << "=== Regression Tests for Nixir ===" << 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; - - std::cout << "=== Tests Complete ===" << std::endl; - std::cout << "Failures: " << failures << std::endl; - return failures > 0 ? 1 : 0; -} diff --git a/tests/shortcircuit.nix b/tests/shortcircuit.nix deleted file mode 100644 index 063bf70..0000000 --- a/tests/shortcircuit.nix +++ /dev/null @@ -1,11 +0,0 @@ -# Test short-circuit evaluation -let - alwaysFalse = false; - alwaysTrue = true; - x = 10; -in - { - and_false = alwaysFalse && alwaysTrue; - or_true = alwaysTrue || alwaysFalse; - impl_false = alwaysFalse -> alwaysFalse; - } diff --git a/tests/shortcircuit2.nix b/tests/shortcircuit2.nix deleted file mode 100644 index b75cf38..0000000 --- a/tests/shortcircuit2.nix +++ /dev/null @@ -1,6 +0,0 @@ -# Test short-circuit evaluation -{ - and_false = false && true; - or_true = true || false; - impl_false = false -> false; -} diff --git a/tests/simple.nix b/tests/simple.nix deleted file mode 100644 index 68e636c..0000000 --- a/tests/simple.nix +++ /dev/null @@ -1,2 +0,0 @@ -# Simple constant test -42 diff --git a/tests/simple.nixir b/tests/simple.nixir deleted file mode 100644 index 3e26f83..0000000 Binary files a/tests/simple.nixir and /dev/null differ diff --git a/tests/simple_op.nix b/tests/simple_op.nix deleted file mode 100644 index 193df0b..0000000 --- a/tests/simple_op.nix +++ /dev/null @@ -1 +0,0 @@ -1 + 2 \ No newline at end of file diff --git a/tests/simple_op.nixir b/tests/simple_op.nixir deleted file mode 100644 index 18ffbd3..0000000 Binary files a/tests/simple_op.nixir and /dev/null differ diff --git a/tests/string_interp.nix b/tests/string_interp.nix deleted file mode 100644 index b9ae519..0000000 --- a/tests/string_interp.nix +++ /dev/null @@ -1,19 +0,0 @@ -# Test string interpolation -let - name = "world"; - x = 42; - bool_val = true; -in - { - # Simple interpolation - greeting = "Hello ${name}!"; - - # Multiple interpolations - multi = "x is ${x} and name is ${name}"; - - # Nested expression - nested = "Result: ${if bool_val then "yes" else "no"}"; - - # Just a string (no interpolation) - plain = "plain text"; - } diff --git a/tests/unary.nix b/tests/unary.nix deleted file mode 100644 index cc7ddab..0000000 --- a/tests/unary.nix +++ /dev/null @@ -1,6 +0,0 @@ -# Test unary operators -let - x = 10; - y = true; -in - { neg = -x; not = !y; } diff --git a/tests/unary.nixir b/tests/unary.nixir deleted file mode 100644 index 652fabc..0000000 Binary files a/tests/unary.nixir and /dev/null differ