clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.gitCommit 618a58b2b8c7
tarballunverified14 files changed+2119-0
@@ -0,0 +1,74 @@+cmake_minimum_required(VERSION 3.19)+project(nix-ir-plugin)++set(CMAKE_CXX_STANDARD 23)+set(CMAKE_CXX_STANDARD_REQUIRED ON)++find_package(PkgConfig REQUIRED)++pkg_check_modules(NIX_STORE REQUIRED IMPORTED_TARGET nix-store)+pkg_check_modules(NIX_EXPR REQUIRED IMPORTED_TARGET nix-expr)+pkg_check_modules(NIX_CMD REQUIRED IMPORTED_TARGET nix-cmd)+pkg_check_modules(NIX_UTIL REQUIRED IMPORTED_TARGET nix-util)+pkg_check_modules(NIX_FETCHERS REQUIRED IMPORTED_TARGET nix-fetchers)+pkg_check_modules(NIX_MAIN REQUIRED IMPORTED_TARGET nix-main)++# nix-irc (External IR Compiler)+add_executable(nix-irc+ src/irc/main.cpp+ src/irc/parser.cpp+ src/irc/resolver.cpp+ src/irc/ir_gen.cpp+ src/irc/serializer.cpp+)++target_include_directories(nix-irc PRIVATE+ ${NIX_STORE_INCLUDE_DIRS}+ ${NIX_EXPR_INCLUDE_DIRS}+ ${NIX_UTIL_INCLUDE_DIRS}+)++target_link_libraries(nix-irc PRIVATE+ ${NIX_STORE_LINK_LIBRARIES}+ ${NIX_EXPR_LINK_LIBRARIES}+ ${NIX_UTIL_LINK_LIBRARIES}+)++# Nix IR Plugin+add_library(nix-ir-plugin MODULE+ src/plugin.cpp+ src/irc/parser.cpp+ src/irc/resolver.cpp+ src/irc/ir_gen.cpp+ src/irc/serializer.cpp+)++# Include directories from pkg-config+target_include_directories(nix-ir-plugin PRIVATE+ ${CMAKE_SOURCE_DIR}/src+ ${NIX_STORE_INCLUDE_DIRS}+ ${NIX_EXPR_INCLUDE_DIRS}+ ${NIX_CMD_INCLUDE_DIRS}+ ${NIX_UTIL_INCLUDE_DIRS}+ ${NIX_FETCHERS_INCLUDE_DIRS}+ ${NIX_MAIN_INCLUDE_DIRS}+)++# Link libraries+target_link_libraries(nix-ir-plugin PRIVATE+ ${NIX_STORE_LINK_LIBRARIES}+ ${NIX_EXPR_LINK_LIBRARIES}+ ${NIX_CMD_LINK_LIBRARIES}+ ${NIX_UTIL_LINK_LIBRARIES}+ ${NIX_FETCHERS_LINK_LIBRARIES}+ ${NIX_MAIN_LINK_LIBRARIES}+)++# Set output name+set_target_properties(nix-ir-plugin PROPERTIES+ PREFIX ""+ OUTPUT_NAME "nix-ir-plugin"+)++# Install to plugin directory+install(TARGETS nix-ir-plugin LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/nix/plugins")@@ -0,0 +1,27 @@+{+ "nodes": {+ "nixpkgs": {+ "locked": {+ "lastModified": 1771369470,+ "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=",+ "owner": "NixOS",+ "repo": "nixpkgs",+ "rev": "0182a361324364ae3f436a63005877674cf45efb",+ "type": "github"+ },+ "original": {+ "owner": "NixOS",+ "ref": "nixos-unstable",+ "repo": "nixpkgs",+ "type": "github"+ }+ },+ "root": {+ "inputs": {+ "nixpkgs": "nixpkgs"+ }+ }+ },+ "root": "root",+ "version": 7+}@@ -0,0 +1,32 @@+{+ inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";++ outputs = {nixpkgs, ...}: let+ systems = ["x86_64-linux" "aarch64-linux"];+ forAllSystems = nixpkgs.lib.genAttrs systems;+ in {+ devShells = forAllSystems (system: {+ default = nixpkgs.legacyPackages.${system}.mkShell {+ buildInputs = with nixpkgs.legacyPackages.${system}; [+ cmake+ pkg-config+ ninja+ boost+ bear++ nixVersions.nixComponents_2_32.nix-store+ nixVersions.nixComponents_2_32.nix-expr+ nixVersions.nixComponents_2_32.nix-cmd+ nixVersions.nixComponents_2_32.nix-fetchers+ nixVersions.nixComponents_2_32.nix-main+ nixVersions.nixComponents_2_32.nix-util+ nixVersions.nix_2_32+ ];++ env = {+ NIX_PLUGINABI = "0.2";+ };+ };+ });+ };+}@@ -0,0 +1,214 @@+#include "ir_gen.h"+#include <stack>+#include <unordered_map>+#include <algorithm>++namespace nix_irc {++struct NameResolver::Impl {+ std::vector<std::unordered_map<std::string, uint32_t>> scopes;+ std::vector<std::vector<std::string>> scope_names;++ Impl() {+ scopes.push_back({});+ scope_names.push_back({});+ }+};++NameResolver::NameResolver() : pImpl(std::make_unique<Impl>()) {}+NameResolver::~NameResolver() = default;++void NameResolver::enter_scope() {+ pImpl->scopes.push_back({});+ pImpl->scope_names.push_back({});+}++void NameResolver::exit_scope() {+ if (!pImpl->scopes.empty()) {+ pImpl->scopes.pop_back();+ pImpl->scope_names.pop_back();+ }+}++void NameResolver::bind(const std::string& name) {+ if (pImpl->scopes.empty()) return;+ uint32_t idx = pImpl->scope_names.back().size();+ pImpl->scopes.back()[name] = idx;+ pImpl->scope_names.back().push_back(name);+}++uint32_t NameResolver::resolve(const std::string& name) {+ for (int i = (int)pImpl->scopes.size() - 1; i >= 0; --i) {+ auto it = pImpl->scopes[i].find(name);+ if (it != pImpl->scopes[i].end()) {+ uint32_t depth = pImpl->scopes.size() - 1 - i;+ uint32_t offset = it->second;+ return depth << 16 | offset;+ }+ }+ return 0xFFFFFFFF;+}++bool NameResolver::is_bound(const std::string& name) const {+ for (auto it = pImpl->scopes.rbegin(); it != pImpl->scopes.rend(); ++it) {+ if (it->count(name)) return true;+ }+ return false;+}++struct IRGenerator::Impl {+ std::unordered_map<std::string, uint32_t> string_table;+ uint32_t next_string_id = 0;+ NameResolver name_resolver;++ Impl() {}++ uint32_t add_string(const std::string& str) {+ auto it = string_table.find(str);+ if (it != string_table.end()) {+ return it->second;+ }+ uint32_t id = next_string_id++;+ string_table[str] = id;+ return id;+ }++ std::shared_ptr<Node> convert(const std::shared_ptr<Node>& node_ptr) {+ if (!node_ptr) return std::make_shared<Node>(ConstNullNode{});++ const Node& node = *node_ptr;++ if (auto* n = node.get_if<ConstIntNode>()) {+ return std::make_shared<Node>(*n);+ }+ if (auto* n = node.get_if<ConstStringNode>()) {+ return std::make_shared<Node>(*n);+ }+ if (auto* n = node.get_if<ConstPathNode>()) {+ return std::make_shared<Node>(*n);+ }+ if (auto* n = node.get_if<ConstBoolNode>()) {+ return std::make_shared<Node>(*n);+ }+ if (auto* n = node.get_if<ConstNullNode>()) {+ return std::make_shared<Node>(*n);+ }+ if (auto* n = node.get_if<VarNode>()) {+ uint32_t idx = name_resolver.resolve(n->name.value_or(""));+ VarNode converted(idx);+ converted.name = n->name;+ converted.line = n->line;+ return std::make_shared<Node>(converted);+ }+ if (auto* n = node.get_if<LambdaNode>()) {+ name_resolver.enter_scope();+ if (n->param_name) {+ name_resolver.bind(*n->param_name);+ }+ auto body = convert(n->body);+ name_resolver.exit_scope();+ LambdaNode lambda(n->arity, body, n->line);+ lambda.param_name = n->param_name;+ return std::make_shared<Node>(lambda);+ }+ if (auto* n = node.get_if<AppNode>()) {+ auto func = convert(n->func);+ auto arg = convert(n->arg);+ return std::make_shared<Node>(AppNode(func, arg, n->line));+ }+ if (auto* n = node.get_if<AttrsetNode>()) {+ AttrsetNode attrs(n->recursive, n->line);+ name_resolver.enter_scope();+ for (const auto& [key, val] : n->attrs) {+ name_resolver.bind(key);+ }+ for (const auto& [key, val] : n->attrs) {+ attrs.attrs.push_back({key, convert(val)});+ }+ name_resolver.exit_scope();+ return std::make_shared<Node>(attrs);+ }+ if (auto* n = node.get_if<SelectNode>()) {+ auto expr = convert(n->expr);+ auto attr = convert(n->attr);+ SelectNode select(expr, attr, n->line);+ if (n->default_expr) {+ select.default_expr = convert(*n->default_expr);+ }+ return std::make_shared<Node>(select);+ }+ if (auto* n = node.get_if<WithNode>()) {+ auto attrs = convert(n->attrs);+ auto body = convert(n->body);+ return std::make_shared<Node>(WithNode(attrs, body, n->line));+ }+ if (auto* n = node.get_if<IfNode>()) {+ auto cond = convert(n->cond);+ auto then_b = convert(n->then_branch);+ auto else_b = convert(n->else_branch);+ return std::make_shared<Node>(IfNode(cond, then_b, else_b, n->line));+ }+ if (auto* n = node.get_if<LetNode>()) {+ name_resolver.enter_scope();+ for (const auto& [key, val] : n->bindings) {+ name_resolver.bind(key);+ }+ std::vector<std::pair<std::string, std::shared_ptr<Node>>> new_bindings;+ for (const auto& [key, val] : n->bindings) {+ new_bindings.push_back({key, convert(val)});+ }+ auto body = convert(n->body);+ name_resolver.exit_scope();+ LetNode let(body, n->line);+ let.bindings = std::move(new_bindings);+ return std::make_shared<Node>(let);+ }+ if (auto* n = node.get_if<LetRecNode>()) {+ name_resolver.enter_scope();+ for (const auto& [key, val] : n->bindings) {+ name_resolver.bind(key);+ }+ std::vector<std::pair<std::string, std::shared_ptr<Node>>> new_bindings;+ for (const auto& [key, val] : n->bindings) {+ new_bindings.push_back({key, convert(val)});+ }+ auto body = convert(n->body);+ name_resolver.exit_scope();+ LetRecNode letrec(body, n->line);+ letrec.bindings = std::move(new_bindings);+ return std::make_shared<Node>(letrec);+ }+ if (auto* n = node.get_if<AssertNode>()) {+ auto cond = convert(n->cond);+ auto body = convert(n->body);+ return std::make_shared<Node>(AssertNode(cond, body, n->line));+ }+ if (auto* n = node.get_if<BinaryOpNode>()) {+ auto left = convert(n->left);+ auto right = convert(n->right);+ return std::make_shared<Node>(BinaryOpNode(n->op, left, right, n->line));+ }+ if (auto* n = node.get_if<UnaryOpNode>()) {+ auto operand = convert(n->operand);+ return std::make_shared<Node>(UnaryOpNode(n->op, operand, n->line));+ }+ return std::make_shared<Node>(ConstNullNode{});+ }+};++IRGenerator::IRGenerator() : pImpl(std::make_unique<Impl>()) {}+IRGenerator::~IRGenerator() = default;++void IRGenerator::set_string_table(const std::unordered_map<std::string, uint32_t>& table) {+ pImpl->string_table = table;+}++uint32_t IRGenerator::add_string(const std::string& str) {+ return pImpl->add_string(str);+}++std::shared_ptr<Node> IRGenerator::generate(const std::shared_ptr<Node>& ast) {+ return pImpl->convert(ast);+}++}@@ -0,0 +1,45 @@+#ifndef NIX_IRC_IR_GEN_H+#define NIX_IRC_IR_GEN_H++#include "types.h"+#include <string>+#include <unordered_map>+#include <vector>+#include <memory>++namespace nix_irc {++class IRGenerator {+public:+ IRGenerator();+ ~IRGenerator();+ + void set_string_table(const std::unordered_map<std::string, uint32_t>& table);+ uint32_t add_string(const std::string& str);+ + std::shared_ptr<Node> generate(const std::shared_ptr<Node>& ast);+ +private:+ struct Impl;+ std::unique_ptr<Impl> pImpl;+};++class NameResolver {+public:+ NameResolver();+ ~NameResolver();+ + void enter_scope();+ void exit_scope();+ void bind(const std::string& name);+ uint32_t resolve(const std::string& name);+ bool is_bound(const std::string& name) const;+ +private:+ struct Impl;+ std::unique_ptr<Impl> pImpl;+};++}++#endif\ No newline at end of file@@ -0,0 +1,150 @@+#include <iostream>+#include "parser.h"+#include "resolver.h"+#include "ir_gen.h"+#include "serializer.h"+#include <string>+#include <vector>+#include <cstring>++namespace nix_irc {++void print_usage(const char* prog) {+ std::cout << "Usage: " << prog << " [options] <input.nix> [output.nixir]\n"+ << "\nOptions:\n"+ << " -I <path> Add search path for imports\n"+ << " --no-imports Disable import resolution\n"+ << " --help Show this help\n";+}++int run_compile(int argc, char** argv) {+ std::string input_file;+ std::string output_file;+ std::vector<std::string> search_paths;+ bool resolve_imports = true;+ + int i = 1;+ while (i < argc) {+ std::string arg = argv[i];+ if (arg == "-I") {+ if (i + 1 >= argc) {+ std::cerr << "Error: -I requires a path argument\n";+ return 1;+ }+ search_paths.push_back(argv[++i]);+ } else if (arg == "--no-imports") {+ resolve_imports = false;+ } else if (arg == "--help" || arg == "-h") {+ print_usage(argv[0]);+ return 0;+ } else if (arg[0] != '-') {+ input_file = arg;+ if (i + 1 < argc && argv[i + 1][0] != '-') {+ output_file = argv[++i];+ }+ } else {+ std::cerr << "Unknown option: " << arg << "\n";+ print_usage(argv[0]);+ return 1;+ }+ i++;+ }+ + if (input_file.empty()) {+ std::cerr << "Error: No input file specified\n";+ print_usage(argv[0]);+ return 1;+ }+ + if (output_file.empty()) {+ output_file = input_file + "r";+ }+ + try {+ Parser parser;+ Resolver resolver;+ + for (const auto& path : search_paths) {+ resolver.add_search_path(path);+ }+ + std::cout << "Parsing: " << input_file << "\n";+ auto ast = parser.parse_file(input_file);+ + if (!ast) {+ std::cerr << "Error: Failed to parse input\n";+ return 1;+ }+ + std::cout << "Resolving imports...\n";+ + IRGenerator ir_gen;+ + std::cout << "Generating IR...\n";+ auto ir = ir_gen.generate(ast);++ IRModule module;+ module.version = IR_VERSION;+ module.entry = ir;+ + std::cout << "Serializing to: " << output_file << "\n";+ Serializer serializer;+ serializer.serialize(module, output_file);+ + std::cout << "Done!\n";+ return 0;+ + } catch (const std::exception& e) {+ std::cerr << "Error: " << e.what() << "\n";+ return 1;+ }+}++void print_decompile_usage(const char* prog) {+ std::cout << "Usage: " << prog << " decompile <input.nixir>\n";+}++int run_decompile(int argc, char** argv) {+ if (argc < 3) {+ print_decompile_usage(argv[0]);+ return 1;+ }+ + std::string input_file = argv[2];+ + try {+ Deserializer deserializer;+ auto module = deserializer.deserialize(input_file);+ + std::cout << "IR Version: " << module.version << "\n";+ std::cout << "Sources: " << module.sources.size() << "\n";+ std::cout << "Imports: " << module.imports.size() << "\n";+ + return 0;+ } catch (const std::exception& e) {+ std::cerr << "Error: " << e.what() << "\n";+ return 1;+ }+}++}++int main(int argc, char** argv) {+ if (argc < 2) {+ nix_irc::print_usage(argv[0]);+ return 1;+ }+ + std::string cmd = argv[1];+ + if (cmd == "compile" || cmd == "c") {+ return nix_irc::run_compile(argc - 1, argv + 1);+ } else if (cmd == "decompile" || cmd == "d") {+ return nix_irc::run_decompile(argc, argv);+ } else if (cmd == "help" || cmd == "--help" || cmd == "-h") {+ nix_irc::print_usage(argv[0]);+ return 0;+ } else {+ return nix_irc::run_compile(argc, argv);+ }+}@@ -0,0 +1,621 @@+#include "parser.h"+#include <iostream>+#include <cstdio>+#include <cstdlib>+#include <memory>+#include <stdexcept>+#include <sstream>+#include <vector>+#include <regex>+#include <array>++namespace nix_irc {++static std::string trim(const std::string& s) {+ size_t start = s.find_first_not_of(" \t\n\r");+ if (start == std::string::npos) return "";+ size_t end = s.find_last_not_of(" \t\n\r");+ return s.substr(start, end - start + 1);+}++static std::string read_file(const std::string& path) {+ FILE* f = fopen(path.c_str(), "r");+ if (!f) {+ throw std::runtime_error("Cannot open file: " + path);+ }+ fseek(f, 0, SEEK_END);+ long size = ftell(f);+ fseek(f, 0, SEEK_SET);+ std::string content(size, '\0');+ fread(content.data(), 1, size, f);+ fclose(f);+ return content;+}++static std::pair<std::string, std::string> run_command(const std::string& cmd) {+ std::array<char, 256> buffer;+ std::string result;+ std::string error;++ FILE* pipe = popen(cmd.c_str(), "r");+ if (!pipe) throw std::runtime_error("popen failed");++ while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {+ result += buffer.data();+ }++ int status = pclose(pipe);+ if (status != 0) {+ throw std::runtime_error("Command failed: " + cmd);+ }+ return {result, error};+}++struct Token {+ enum Type {+ LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET,+ IDENT, STRING, PATH, INT, BOOL,+ LET, IN, REC, IF, THEN, ELSE, ASSERT, WITH,+ DOT, SEMICOLON, COLON, AT, COMMA,+ // Operators+ PLUS, MINUS, STAR, SLASH, CONCAT,+ EQEQ, NE, LT, GT, LE, GE,+ AND, OR, IMPL, NOT,+ EOF_+ } type;+ std::string value;+ size_t line;+ size_t col;+};++class Lexer {+public:+ Lexer(const std::string& input) : input(input), pos(0), line(1), col(1) {}++ std::vector<Token> tokenize() {+ #define TOKEN(t) Token{Token::t, "", line, col}++ while (pos < input.size()) {+ skip_whitespace();+ if (pos >= input.size()) break;++ char c = input[pos];++ if (c == '(') { emit(TOKEN(LPAREN)); }+ else if (c == ')') { emit(TOKEN(RPAREN)); }+ else if (c == '{') { emit(TOKEN(LBRACE)); }+ else if (c == '}') { emit(TOKEN(RBRACE)); }+ else if (c == '[') { emit(TOKEN(LBRACKET)); }+ else if (c == ']') { emit(TOKEN(RBRACKET)); }+ else if (c == ';') { emit(TOKEN(SEMICOLON)); }+ else if (c == ':') { emit(TOKEN(COLON)); }+ else if (c == '@') { emit(TOKEN(AT)); }+ else if (c == ',') { emit(TOKEN(COMMA)); }+ else if (c == '"') { tokenize_string(); }+ // Two-char operators+ else if (c == '=' && pos + 1 < input.size() && input[pos + 1] == '=') {+ tokens.push_back(TOKEN(EQEQ));+ pos += 2; col += 2;+ }+ else if (c == '!' && pos + 1 < input.size() && input[pos + 1] == '=') {+ tokens.push_back(TOKEN(NE));+ pos += 2; col += 2;+ }+ else if (c == '<' && pos + 1 < input.size() && input[pos + 1] == '=') {+ tokens.push_back(TOKEN(LE));+ pos += 2; col += 2;+ }+ else if (c == '>' && pos + 1 < input.size() && input[pos + 1] == '=') {+ tokens.push_back(TOKEN(GE));+ pos += 2; col += 2;+ }+ else if (c == '+' && pos + 1 < input.size() && input[pos + 1] == '+') {+ tokens.push_back(TOKEN(CONCAT));+ pos += 2; col += 2;+ }+ else if (c == '&' && pos + 1 < input.size() && input[pos + 1] == '&') {+ tokens.push_back(TOKEN(AND));+ pos += 2; col += 2;+ }+ else if (c == '|' && pos + 1 < input.size() && input[pos + 1] == '|') {+ tokens.push_back(TOKEN(OR));+ pos += 2; col += 2;+ }+ else if (c == '-' && pos + 1 < input.size() && input[pos + 1] == '>') {+ tokens.push_back(TOKEN(IMPL));+ pos += 2; col += 2;+ }+ // Single-char operators+ else if (c == '+') { emit(TOKEN(PLUS)); }+ else if (c == '*') { emit(TOKEN(STAR)); }+ else if (c == '/') {+ // Check if it's a path or division+ if (pos + 1 < input.size() && (isalnum(input[pos + 1]) || input[pos + 1] == '.')) {+ tokenize_path();+ } else {+ emit(TOKEN(SLASH));+ }+ }+ else if (c == '<') { emit(TOKEN(LT)); }+ else if (c == '>') { emit(TOKEN(GT)); }+ else if (c == '!') { emit(TOKEN(NOT)); }+ else if (c == '.') { emit(TOKEN(DOT)); }+ else if (c == '-') {+ // Check if it's a negative number or minus operator+ if (pos + 1 < input.size() && isdigit(input[pos + 1])) {+ tokenize_int();+ } else {+ emit(TOKEN(MINUS));+ }+ }+ else if (isdigit(c)) { tokenize_int(); }+ else if (isalpha(c) || c == '_') { tokenize_ident(); }+ else { pos++; col++; }+ }+ tokens.push_back({Token::EOF_, "", line, col});++ #undef TOKEN+ return tokens;+ }++private:+ std::vector<Token> tokens;+ const std::string& input;+ size_t pos;+ size_t line;+ size_t col;++ void emit(Token t) {+ tokens.push_back(t);+ pos++;+ col++;+ }++ void skip_whitespace() {+ while (pos < input.size()) {+ char c = input[pos];+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {+ if (c == '\n') { line++; col = 1; }+ else { col++; }+ pos++;+ } else if (c == '#') {+ while (pos < input.size() && input[pos] != '\n') pos++;+ } else {+ break;+ }+ }+ }++ void tokenize_string() {+ pos++;+ std::string s;+ while (pos < input.size() && input[pos] != '"') {+ if (input[pos] == '\\' && pos + 1 < input.size()) {+ pos++;+ switch (input[pos]) {+ case 'n': s += '\n'; break;+ case 't': s += '\t'; break;+ case 'r': s += '\r'; break;+ case '"': s += '"'; break;+ case '\\': s += '\\'; break;+ default: s += input[pos]; break;+ }+ } else {+ s += input[pos];+ }+ pos++;+ }+ pos++;+ tokens.push_back({Token::STRING, s, line, col});+ col += s.size() + 2;+ }++ void tokenize_path() {+ size_t start = pos;+ while (pos < input.size() && !isspace(input[pos]) &&+ input[pos] != '(' && input[pos] != ')' &&+ input[pos] != '{' && input[pos] != '}' &&+ input[pos] != '[' && input[pos] != ']') {+ pos++;+ }+ std::string path = input.substr(start, pos - start);+ tokens.push_back({Token::PATH, path, line, col});+ col += path.size();+ }++ void tokenize_int() {+ size_t start = pos;+ if (input[pos] == '-') pos++;+ while (pos < input.size() && isdigit(input[pos])) pos++;+ std::string num = input.substr(start, pos - start);+ tokens.push_back({Token::INT, num, line, col});+ col += num.size();+ }++ void tokenize_ident() {+ size_t start = pos;+ while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-')) pos++;+ std::string ident = input.substr(start, pos - start);++ Token::Type type = Token::IDENT;+ if (ident == "let") type = Token::LET;+ else if (ident == "in") type = Token::IN;+ else if (ident == "rec") type = Token::REC;+ else if (ident == "if") type = Token::IF;+ else if (ident == "then") type = Token::THEN;+ else if (ident == "else") type = Token::ELSE;+ else if (ident == "assert") type = Token::ASSERT;+ else if (ident == "with") type = Token::WITH;+ else if (ident == "true") type = Token::BOOL;+ else if (ident == "false") type = Token::BOOL;++ tokens.push_back({type, ident, line, col});+ col += ident.size();+ }+};++class Parser::Impl {+public:+ std::vector<Token> tokens;+ size_t pos = 0;+ std::string current_file;++ const Token& current() {+ if (pos < tokens.size()) return tokens[pos];+ static Token eof{Token::EOF_, "", 0, 0};+ return eof;+ }++ void advance() { pos++; }++ bool consume(Token::Type type) {+ if (current().type == type) {+ advance();+ return true;+ }+ return false;+ }++ bool expect(Token::Type type) {+ if (current().type != type) {+ std::cerr << "Expected token " << type << " but got " << current().type+ << " at " << current().line << ":" << current().col << "\n";+ return false;+ }+ advance();+ return true;+ }++ // Get operator precedence (higher = tighter binding)+ int get_precedence(Token::Type type) {+ switch (type) {+ case Token::OR: return 1;+ case Token::AND: return 2;+ case Token::IMPL: return 3;+ case Token::EQEQ: case Token::NE: return 4;+ case Token::LT: case Token::GT: case Token::LE: case Token::GE: return 5;+ case Token::CONCAT: return 6;+ case Token::PLUS: case Token::MINUS: return 7;+ case Token::STAR: case Token::SLASH: return 8;+ default: return 0;+ }+ }++ // Convert token type to binary operator+ BinaryOp token_to_binop(Token::Type type) {+ switch (type) {+ case Token::PLUS: return BinaryOp::ADD;+ case Token::MINUS: return BinaryOp::SUB;+ case Token::STAR: return BinaryOp::MUL;+ case Token::SLASH: return BinaryOp::DIV;+ case Token::CONCAT: return BinaryOp::CONCAT;+ case Token::EQEQ: return BinaryOp::EQ;+ case Token::NE: return BinaryOp::NE;+ case Token::LT: return BinaryOp::LT;+ case Token::GT: return BinaryOp::GT;+ case Token::LE: return BinaryOp::LE;+ case Token::GE: return BinaryOp::GE;+ case Token::AND: return BinaryOp::AND;+ case Token::OR: return BinaryOp::OR;+ case Token::IMPL: return BinaryOp::IMPL;+ default: throw std::runtime_error("Invalid binary operator");+ }+ }++ std::shared_ptr<Node> parse_expr() {+ if (consume(Token::IF)) {+ auto cond = parse_expr();+ expect(Token::THEN);+ auto then = parse_expr();+ expect(Token::ELSE);+ auto else_ = parse_expr();+ return std::make_shared<Node>(IfNode(cond, then, else_));+ }+ if (consume(Token::LET)) {+ bool is_rec = consume(Token::REC);+ std::vector<std::pair<std::string, std::shared_ptr<Node>>> bindings;+ parse_bindings(bindings);+ expect(Token::IN);+ auto body = parse_expr();++ if (is_rec) {+ auto letrec = LetRecNode(body);+ letrec.bindings = std::move(bindings);+ return std::make_shared<Node>(std::move(letrec));+ } else {+ auto let = LetNode(body);+ let.bindings = std::move(bindings);+ return std::make_shared<Node>(std::move(let));+ }+ }+ if (consume(Token::ASSERT)) {+ auto cond = parse_expr();+ expect(Token::SEMICOLON);+ auto body = parse_expr();+ return std::make_shared<Node>(AssertNode(cond, body));+ }+ if (consume(Token::WITH)) {+ auto attrs = parse_expr();+ expect(Token::SEMICOLON);+ auto body = parse_expr();+ return std::make_shared<Node>(WithNode(attrs, body));+ }++ return parse_expr1();+ }++ std::shared_ptr<Node> parse_expr1() {+ return parse_binary_op(0);+ }++ // Precedence climbing for binary operators+ std::shared_ptr<Node> parse_binary_op(int min_prec) {+ auto left = parse_selection();++ while (true) {+ int prec = get_precedence(current().type);+ if (prec == 0 || prec < min_prec) break;++ Token op_token = current();+ advance();++ auto right = parse_binary_op(prec + 1);+ left = std::make_shared<Node>(BinaryOpNode(+ token_to_binop(op_token.type),+ left,+ right+ ));+ }++ return left;+ }++ std::shared_ptr<Node> parse_selection() {+ auto left = parse_expr2();++ while (current().type == Token::DOT) {+ advance();+ Token name = current();+ if (name.type == Token::IDENT) {+ advance();+ auto attr = std::make_shared<Node>(ConstStringNode(name.value));+ auto result = std::make_shared<Node>(SelectNode(left, attr));++ if (consume(Token::DOT)) {+ Token name2 = current();+ if (name2.type == Token::IDENT) {+ advance();+ auto attr2 = std::make_shared<Node>(ConstStringNode(name2.value));+ auto* curr = result->get_if<SelectNode>();+ while (curr && consume(Token::DOT)) {+ Token n = current();+ expect(Token::IDENT);+ auto a = std::make_shared<Node>(ConstStringNode(n.value));+ curr->attr = std::make_shared<Node>(AppNode(+ std::make_shared<Node>(AppNode(curr->attr, a)),+ std::make_shared<Node>(ConstNullNode())+ ));+ }+ }+ }+ return result;+ } else if (consume(Token::LBRACE)) {+ auto result = std::make_shared<Node>(SelectNode(left, std::make_shared<Node>(ConstStringNode(name.value))));+ parse_expr_attrs(result);+ expect(Token::RBRACE);+ return result;+ }+ return left;+ }++ return left;+ }++ void parse_expr_attrs(std::shared_ptr<Node>&) {+ // Extended selection syntax+ }++ std::shared_ptr<Node> parse_expr2() {+ std::shared_ptr<Node> left = parse_expr3();++ while (true) {+ if (current().type == Token::LBRACKET) {+ advance();+ auto arg = parse_expr();+ expect(Token::RBRACKET);+ left = std::make_shared<Node>(AppNode(left, arg));+ } else if (current().type == Token::STRING) {+ Token s = current();+ advance();+ auto arg = std::make_shared<Node>(ConstStringNode(s.value));+ left = std::make_shared<Node>(AppNode(left, arg));+ } else {+ break;+ }+ }++ return left;+ }++ std::shared_ptr<Node> parse_expr3() {+ // Handle unary operators+ if (consume(Token::MINUS)) {+ auto operand = parse_expr3();+ return std::make_shared<Node>(UnaryOpNode(UnaryOp::NEG, operand));+ }++ if (consume(Token::NOT)) {+ auto operand = parse_expr3();+ return std::make_shared<Node>(UnaryOpNode(UnaryOp::NOT, operand));+ }++ if (consume(Token::LPAREN)) {+ auto expr = parse_expr();+ expect(Token::RPAREN);+ return expr;+ }++ if (consume(Token::LBRACE)) {+ return parse_attrs();+ }++ if (consume(Token::LBRACKET)) {+ return parse_list();+ }++ Token t = current();++ if (t.type == Token::IDENT) {+ advance();+ return std::make_shared<Node>(VarNode(0, t.value));+ }++ if (t.type == Token::INT) {+ advance();+ return std::make_shared<Node>(ConstIntNode(std::stoll(t.value)));+ }++ if (t.type == Token::STRING) {+ advance();+ return std::make_shared<Node>(ConstStringNode(t.value));+ }++ if (t.type == Token::PATH) {+ advance();+ return std::make_shared<Node>(ConstPathNode(t.value));+ }++ if (t.type == Token::BOOL) {+ advance();+ return std::make_shared<Node>(ConstBoolNode(t.value == "true"));+ }++ std::cerr << "Unknown token: " << t.value << " (type " << t.type << ")\n";+ advance();+ return std::make_shared<Node>(ConstNullNode());+ }++ std::shared_ptr<Node> parse_attrs() {+ auto attrs = AttrsetNode(false);++ while (current().type != Token::RBRACE && current().type != Token::EOF_) {+ if (consume(Token::REC)) {+ attrs.recursive = true;+ continue;+ }++ if (current().type == Token::IDENT || current().type == Token::STRING) {+ Token key = current();+ advance();+ std::string key_str = key.value;++ if (consume(Token::COLON)) {+ auto value = parse_expr();+ attrs.attrs.push_back({key_str, value});+ } else if (consume(Token::AT)) {+ auto pattern = parse_expr();+ auto value = parse_expr();+ attrs.attrs.push_back({key_str, value});+ }+ }++ if (consume(Token::COMMA)) continue;+ if (consume(Token::SEMICOLON)) continue;+ }++ expect(Token::RBRACE);+ return std::make_shared<Node>(std::move(attrs));+ }++ std::shared_ptr<Node> parse_list() {+ std::shared_ptr<Node> list = std::make_shared<Node>(ConstNullNode());++ if (consume(Token::RBRACKET)) {+ return list;+ }++ std::vector<std::shared_ptr<Node>> elements;+ while (current().type != Token::RBRACKET) {+ elements.push_back(parse_expr());+ if (!consume(Token::COMMA)) break;+ }+ expect(Token::RBRACKET);++ for (auto it = elements.rbegin(); it != elements.rend(); ++it) {+ list = std::make_shared<Node>(AppNode(+ std::make_shared<Node>(AppNode(+ std::make_shared<Node>(VarNode(0, "__list")),+ *it+ )),+ list+ ));+ }++ return list;+ }++ void parse_bindings(std::vector<std::pair<std::string, std::shared_ptr<Node>>>& bindings) {+ while (current().type == Token::IDENT || current().type == Token::LBRACE) {+ if (current().type == Token::LBRACE) {+ auto inherit = parse_expr();+ (void)inherit;+ continue;+ }++ Token key = current();+ expect(Token::IDENT);++ if (consume(Token::AT)) {+ auto pattern = parse_expr();+ auto value = parse_expr();+ bindings.push_back({key.value, value});+ } else {+ expect(Token::COLON);+ auto value = parse_expr();+ bindings.push_back({key.value, value});+ }++ if (!consume(Token::SEMICOLON)) break;+ }+ }+};++Parser::Parser() : pImpl(std::make_unique<Impl>()) {}+Parser::~Parser() = default;++std::shared_ptr<Node> Parser::parse(const std::string& source, const std::string& path) {+ pImpl->current_file = path;++ Lexer lexer(source);+ pImpl->tokens = lexer.tokenize();+ pImpl->pos = 0;++ return pImpl->parse_expr();+}++std::shared_ptr<Node> Parser::parse_file(const std::string& path) {+ std::string content = read_file(path);+ return parse(content, path);+}++}@@ -0,0 +1,25 @@+#ifndef NIX_IRC_PARSER_H+#define NIX_IRC_PARSER_H++#include "types.h"+#include <string>+#include <memory>++namespace nix_irc {++class Parser {+public:+ Parser();+ ~Parser();+ + std::shared_ptr<Node> parse(const std::string& source, const std::string& path = "<stdin>");+ std::shared_ptr<Node> parse_file(const std::string& path);+ +private:+ struct Impl;+ std::unique_ptr<Impl> pImpl;+};++}++#endif\ No newline at end of file@@ -0,0 +1,111 @@+#include "resolver.h"+#include "parser.h"+#include <iostream>+#include <fstream>+#include <sstream>+#include <filesystem>+#include <regex>++namespace nix_irc {++namespace fs = std::filesystem;++struct Resolver::Impl {+ ResolverConfig config;+ std::vector<std::pair<std::string, std::string>> resolved_imports;+ std::unordered_set<std::string> visited;+ Parser parser;+ + Impl(const ResolverConfig& cfg) : config(cfg) {}+ + std::string resolve_path(const std::string& path, const std::string& from_file) {+ fs::path p(path);+ + if (p.is_absolute()) {+ if (fs::exists(p)) return path;+ return "";+ }+ + fs::path from_dir = fs::path(from_file).parent_path();+ fs::path candidate = from_dir / p;+ if (fs::exists(candidate)) return candidate.string();+ + for (const auto& search : config.search_paths) {+ candidate = fs::path(search) / p;+ if (fs::exists(candidate)) return candidate.string();+ }+ + return "";+ }+ + ImportResult do_resolve(const std::string& path, const std::string& from_file) {+ std::string resolved = resolve_path(path, from_file);+ + if (resolved.empty()) {+ return {false, "", "Cannot find file: " + path, nullptr};+ }+ + if (visited.count(resolved)) {+ return {true, resolved, "", nullptr};+ }+ visited.insert(resolved);+ + try {+ auto ast = parser.parse_file(resolved);+ return {true, resolved, "", ast};+ } catch (const std::exception& e) {+ return {false, "", e.what(), nullptr};+ }+ }+};++Resolver::Resolver(const ResolverConfig& config) : pImpl(std::make_unique<Impl>(config)) {}+Resolver::~Resolver() = default;++void Resolver::add_search_path(const std::string& path) {+ pImpl->config.search_paths.push_back(path);+}++void Resolver::set_search_paths(const std::vector<std::string>& paths) {+ pImpl->config.search_paths = paths;+}++ImportResult Resolver::resolve_import(const std::string& path, const std::string& from_file) {+ auto result = pImpl->do_resolve(path, from_file);+ if (result.success && result.ast) {+ pImpl->resolved_imports.push_back({path, result.path});+ }+ return result;+}++ImportResult Resolver::resolve_import(const Node& import_node, const std::string& from_file) {+ const ConstPathNode* path_node = import_node.get_if<ConstPathNode>();+ if (!path_node) {+ return {false, "", "Dynamic import not supported", nullptr};+ }+ return resolve_import(path_node->value, from_file);+}++std::vector<std::string> Resolver::get_resolved_files() const {+ std::vector<std::string> files;+ for (const auto& [orig, resolved] : pImpl->resolved_imports) {+ (void)orig;+ files.push_back(resolved);+ }+ return files;+}++std::vector<std::pair<std::string, std::string>> Resolver::get_imports() const {+ return pImpl->resolved_imports;+}++bool is_static_import(const Node& node) {+ return node.holds<ConstPathNode>();+}++std::string normalize_path(const std::string& path) {+ fs::path p(path);+ return fs::absolute(p).string();+}++}@@ -0,0 +1,48 @@+#ifndef NIX_IRC_RESOLVER_H+#define NIX_IRC_RESOLVER_H++#include "types.h"+#include <string>+#include <vector>+#include <unordered_set>+#include <filesystem>++namespace nix_irc {++struct ImportResult {+ bool success;+ std::string path;+ std::string error;+ std::shared_ptr<Node> ast;+};++struct ResolverConfig {+ std::vector<std::string> search_paths;+ bool resolve_imports = true;+};++class Resolver {+public:+ Resolver(const ResolverConfig& config = {});+ ~Resolver();+ + void add_search_path(const std::string& path);+ void set_search_paths(const std::vector<std::string>& paths);+ + ImportResult resolve_import(const std::string& path, const std::string& from_file);+ ImportResult resolve_import(const Node& import_node, const std::string& from_file);+ + std::vector<std::string> get_resolved_files() const;+ std::vector<std::pair<std::string, std::string>> get_imports() const;+ +private:+ struct Impl;+ std::unique_ptr<Impl> pImpl;+};++bool is_static_import(const Node& node);+std::string normalize_path(const std::string& path);++}++#endif\ No newline at end of file@@ -0,0 +1,265 @@+#include "serializer.h"+#include <cstring>+#include <sstream>+#include <iostream>++namespace nix_irc {++struct Serializer::Impl {+ std::vector<uint8_t> buffer;++ void write_u32(uint32_t val) {+ buffer.push_back((val >> 0) & 0xFF);+ buffer.push_back((val >> 8) & 0xFF);+ buffer.push_back((val >> 16) & 0xFF);+ buffer.push_back((val >> 24) & 0xFF);+ }++ void write_u64(uint64_t val) {+ for (int i = 0; i < 8; i++) {+ buffer.push_back((val >> (i * 8)) & 0xFF);+ }+ }++ void write_u8(uint8_t val) {+ buffer.push_back(val);+ }++ void write_string(const std::string& str) {+ write_u32(str.size());+ buffer.insert(buffer.end(), str.begin(), str.end());+ }++ NodeType get_node_type(const Node& node) {+ if (node.holds<ConstIntNode>()) return NodeType::CONST_INT;+ if (node.holds<ConstStringNode>()) return NodeType::CONST_STRING;+ if (node.holds<ConstPathNode>()) return NodeType::CONST_PATH;+ if (node.holds<ConstBoolNode>()) return NodeType::CONST_BOOL;+ if (node.holds<ConstNullNode>()) return NodeType::CONST_NULL;+ if (node.holds<VarNode>()) return NodeType::VAR;+ if (node.holds<LambdaNode>()) return NodeType::LAMBDA;+ if (node.holds<AppNode>()) return NodeType::APP;+ if (node.holds<BinaryOpNode>()) return NodeType::BINARY_OP;+ if (node.holds<UnaryOpNode>()) return NodeType::UNARY_OP;+ if (node.holds<AttrsetNode>()) return NodeType::ATTRSET;+ if (node.holds<SelectNode>()) return NodeType::SELECT;+ if (node.holds<WithNode>()) return NodeType::WITH;+ if (node.holds<IfNode>()) return NodeType::IF;+ if (node.holds<LetNode>()) return NodeType::LET;+ if (node.holds<LetRecNode>()) return NodeType::LETREC;+ if (node.holds<AssertNode>()) return NodeType::ASSERT;+ return NodeType::ERROR;+ }++ uint32_t get_node_line(const Node& node) {+ return std::visit([](const auto& n) { return n.line; }, node.data);+ }++ void write_node(const Node& node) {+ write_u8(static_cast<uint8_t>(get_node_type(node)));+ write_u32(get_node_line(node));++ if (auto* n = node.get_if<ConstIntNode>()) {+ write_u64(static_cast<uint64_t>(n->value));+ } else if (auto* n = node.get_if<ConstStringNode>()) {+ write_string(n->value);+ } else if (auto* n = node.get_if<ConstPathNode>()) {+ write_string(n->value);+ } else if (auto* n = node.get_if<ConstBoolNode>()) {+ write_u8(n->value ? 1 : 0);+ } else if (auto* n = node.get_if<ConstNullNode>()) {+ // No data for null+ } else if (auto* n = node.get_if<VarNode>()) {+ write_u32(n->index);+ } else if (auto* n = node.get_if<LambdaNode>()) {+ write_u32(n->arity);+ if (n->body) write_node(*n->body);+ } else if (auto* n = node.get_if<AppNode>()) {+ if (n->func) write_node(*n->func);+ if (n->arg) write_node(*n->arg);+ } else if (auto* n = node.get_if<BinaryOpNode>()) {+ write_u8(static_cast<uint8_t>(n->op));+ if (n->left) write_node(*n->left);+ if (n->right) write_node(*n->right);+ } else if (auto* n = node.get_if<UnaryOpNode>()) {+ write_u8(static_cast<uint8_t>(n->op));+ if (n->operand) write_node(*n->operand);+ } else if (auto* n = node.get_if<AttrsetNode>()) {+ write_u8(n->recursive ? 1 : 0);+ write_u32(n->attrs.size());+ for (const auto& [key, val] : n->attrs) {+ write_string(key);+ if (val) write_node(*val);+ }+ } else if (auto* n = node.get_if<SelectNode>()) {+ if (n->expr) write_node(*n->expr);+ if (n->attr) write_node(*n->attr);+ if (n->default_expr && *n->default_expr) {+ write_u8(1);+ write_node(**n->default_expr);+ } else {+ write_u8(0);+ }+ } else if (auto* n = node.get_if<WithNode>()) {+ if (n->attrs) write_node(*n->attrs);+ if (n->body) write_node(*n->body);+ } else if (auto* n = node.get_if<IfNode>()) {+ if (n->cond) write_node(*n->cond);+ if (n->then_branch) write_node(*n->then_branch);+ if (n->else_branch) write_node(*n->else_branch);+ } else if (auto* n = node.get_if<LetNode>()) {+ write_u32(n->bindings.size());+ for (const auto& [key, val] : n->bindings) {+ write_string(key);+ if (val) write_node(*val);+ }+ if (n->body) write_node(*n->body);+ } else if (auto* n = node.get_if<LetRecNode>()) {+ write_u32(n->bindings.size());+ for (const auto& [key, val] : n->bindings) {+ write_string(key);+ if (val) write_node(*val);+ }+ if (n->body) write_node(*n->body);+ } else if (auto* n = node.get_if<AssertNode>()) {+ if (n->cond) write_node(*n->cond);+ if (n->body) write_node(*n->body);+ }+ }+};++Serializer::Serializer() : pImpl(std::make_unique<Impl>()) {}+Serializer::~Serializer() = default;++void Serializer::serialize(const IRModule& module, const std::string& path) {+ auto bytes = serialize_to_bytes(module);+ std::ofstream out(path, std::ios::binary);+ out.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());+}++std::vector<uint8_t> Serializer::serialize_to_bytes(const IRModule& module) {+ pImpl->buffer.clear();++ pImpl->write_u32(IR_MAGIC);+ pImpl->write_u32(IR_VERSION);++ pImpl->write_u32(module.sources.size());+ for (const auto& src : module.sources) {+ pImpl->write_string(src.path);+ pImpl->write_string(src.content);+ }++ pImpl->write_u32(module.imports.size());+ for (const auto& [from, to] : module.imports) {+ pImpl->write_string(from);+ pImpl->write_string(to);+ }++ pImpl->write_u32(module.string_table.size());+ for (const auto& [str, id] : module.string_table) {+ pImpl->write_string(str);+ pImpl->write_u32(id);+ }++ if (module.entry && module.entry != nullptr) {+ pImpl->write_u8(1);+ pImpl->write_node(*module.entry);+ } else {+ pImpl->write_u8(0);+ }++ return pImpl->buffer;+}++struct Deserializer::Impl {+ std::vector<uint8_t> buffer;+ size_t pos = 0;++ uint32_t read_u32() {+ uint32_t val = 0;+ val |= buffer[pos + 0];+ val |= (uint32_t)buffer[pos + 1] << 8;+ val |= (uint32_t)buffer[pos + 2] << 16;+ val |= (uint32_t)buffer[pos + 3] << 24;+ pos += 4;+ return val;+ }++ uint64_t read_u64() {+ uint64_t val = 0;+ for (int i = 0; i < 8; i++) {+ val |= (uint64_t)buffer[pos + i] << (i * 8);+ }+ pos += 8;+ return val;+ }++ uint8_t read_u8() {+ return buffer[pos++];+ }++ std::string read_string() {+ uint32_t len = read_u32();+ std::string str(reinterpret_cast<const char*>(&buffer[pos]), len);+ pos += len;+ return str;+ }+};++Deserializer::Deserializer() : pImpl(std::make_unique<Impl>()) {}+Deserializer::~Deserializer() = default;++IRModule Deserializer::deserialize(const std::string& path) {+ std::ifstream in(path, std::ios::binary | std::ios::ate);+ size_t size = in.tellg();+ in.seekg(0);+ pImpl->buffer.resize(size);+ in.read(reinterpret_cast<char*>(pImpl->buffer.data()), size);+ pImpl->pos = 0;+ return deserialize(pImpl->buffer);+}++IRModule Deserializer::deserialize(const std::vector<uint8_t>& data) {+ pImpl->buffer = data;+ pImpl->pos = 0;++ IRModule module;++ uint32_t magic = pImpl->read_u32();+ if (magic != IR_MAGIC) {+ throw std::runtime_error("Invalid IR file");+ }++ uint32_t version = pImpl->read_u32();+ if (version != IR_VERSION) {+ throw std::runtime_error("Unsupported IR version");+ }++ uint32_t num_sources = pImpl->read_u32();+ for (uint32_t i = 0; i < num_sources; i++) {+ SourceFile src;+ src.path = pImpl->read_string();+ src.content = pImpl->read_string();+ module.sources.push_back(src);+ }++ uint32_t num_imports = pImpl->read_u32();+ for (uint32_t i = 0; i < num_imports; i++) {+ module.imports.push_back({pImpl->read_string(), pImpl->read_string()});+ }++ uint32_t num_strings = pImpl->read_u32();+ for (uint32_t i = 0; i < num_strings; i++) {+ std::string str = pImpl->read_string();+ uint32_t id = pImpl->read_u32();+ module.string_table[str] = id;+ }++ if (pImpl->read_u8()) {+ // TODO: deserialize AST+ }++ return module;+}++}@@ -0,0 +1,39 @@+#ifndef NIX_IRC_SERIALIZER_H+#define NIX_IRC_SERIALIZER_H++#include "types.h"+#include <string>+#include <vector>+#include <fstream>++namespace nix_irc {++class Serializer {+public:+ Serializer();+ ~Serializer();+ + void serialize(const IRModule& module, const std::string& path);+ std::vector<uint8_t> serialize_to_bytes(const IRModule& module);+ +private:+ struct Impl;+ std::unique_ptr<Impl> pImpl;+};++class Deserializer {+public:+ Deserializer();+ ~Deserializer();+ + IRModule deserialize(const std::string& path);+ IRModule deserialize(const std::vector<uint8_t>& data);+ +private:+ struct Impl;+ std::unique_ptr<Impl> pImpl;+};++}++#endif\ No newline at end of file@@ -0,0 +1,277 @@+#ifndef NIX_IRC_TYPES_H+#define NIX_IRC_TYPES_H++#include <cstdint>+#include <string>+#include <vector>+#include <unordered_map>+#include <optional>+#include <memory>+#include <variant>+#include <fstream>+#include <sstream>++namespace nix_irc {++constexpr uint32_t IR_MAGIC = 0x4E495258;+constexpr uint32_t IR_VERSION = 1;++enum class NodeType : uint8_t {+ CONST_INT = 0x01,+ CONST_STRING = 0x02,+ CONST_PATH = 0x03,+ CONST_BOOL = 0x04,+ CONST_NULL = 0x05,+ VAR = 0x10,+ LAMBDA = 0x20,+ APP = 0x21,+ BINARY_OP = 0x22,+ UNARY_OP = 0x23,+ ATTRSET = 0x30,+ SELECT = 0x31,+ WITH = 0x32,+ IF = 0x40,+ LET = 0x50,+ LETREC = 0x51,+ ASSERT = 0x52,+ THUNK = 0x60,+ FORCE = 0x61,+ ERROR = 0xFF+};++enum class BinaryOp : uint8_t {+ ADD, SUB, MUL, DIV, CONCAT,+ EQ, NE, LT, GT, LE, GE,+ AND, OR, IMPL+};++enum class UnaryOp : uint8_t {+ NEG, NOT+};++// Forward declare Node for use in shared_ptr+class Node;++struct ConstIntNode {+ int64_t value;+ uint32_t line = 0;+ ConstIntNode(int64_t v = 0, uint32_t l = 0) : value(v), line(l) {}+};++struct ConstStringNode {+ std::string value;+ uint32_t line = 0;+ ConstStringNode(std::string v = "", uint32_t l = 0) : value(std::move(v)), line(l) {}+};++struct ConstPathNode {+ std::string value;+ uint32_t line = 0;+ ConstPathNode(std::string v = "", uint32_t l = 0) : value(std::move(v)), line(l) {}+};++struct ConstBoolNode {+ bool value;+ uint32_t line = 0;+ ConstBoolNode(bool v = false, uint32_t l = 0) : value(v), line(l) {}+};++struct ConstNullNode {+ uint32_t line = 0;+ ConstNullNode(uint32_t l = 0) : line(l) {}+};++struct VarNode {+ uint32_t index = 0;+ std::optional<std::string> name;+ uint32_t line = 0;+ VarNode(uint32_t idx = 0, std::string n = "", uint32_t l = 0)+ : index(idx), name(n.empty() ? std::nullopt : std::optional<std::string>(n)), line(l) {}+};++struct LambdaNode {+ uint32_t arity = 1;+ std::shared_ptr<Node> body;+ std::optional<std::string> param_name;+ uint32_t line = 0;+ LambdaNode(uint32_t a, std::shared_ptr<Node> b, uint32_t l = 0);+};++struct AppNode {+ std::shared_ptr<Node> func;+ std::shared_ptr<Node> arg;+ uint32_t line = 0;+ AppNode(std::shared_ptr<Node> f, std::shared_ptr<Node> a, uint32_t l = 0);+};++struct BinaryOpNode {+ BinaryOp op;+ std::shared_ptr<Node> left;+ std::shared_ptr<Node> right;+ uint32_t line = 0;+ BinaryOpNode(BinaryOp o, std::shared_ptr<Node> l, std::shared_ptr<Node> r, uint32_t ln = 0);+};++struct UnaryOpNode {+ UnaryOp op;+ std::shared_ptr<Node> operand;+ uint32_t line = 0;+ UnaryOpNode(UnaryOp o, std::shared_ptr<Node> operand, uint32_t l = 0);+};++struct AttrsetNode {+ std::vector<std::pair<std::string, std::shared_ptr<Node>>> attrs;+ bool recursive = false;+ uint32_t line = 0;+ AttrsetNode(bool rec = false, uint32_t l = 0) : recursive(rec), line(l) {}+};++struct SelectNode {+ std::shared_ptr<Node> expr;+ std::shared_ptr<Node> attr;+ std::optional<std::shared_ptr<Node>> default_expr;+ uint32_t line = 0;+ SelectNode(std::shared_ptr<Node> e, std::shared_ptr<Node> a, uint32_t l = 0);+};++struct WithNode {+ std::shared_ptr<Node> attrs;+ std::shared_ptr<Node> body;+ uint32_t line = 0;+ WithNode(std::shared_ptr<Node> a, std::shared_ptr<Node> b, uint32_t l = 0);+};++struct IfNode {+ std::shared_ptr<Node> cond;+ std::shared_ptr<Node> then_branch;+ std::shared_ptr<Node> else_branch;+ uint32_t line = 0;+ IfNode(std::shared_ptr<Node> c, std::shared_ptr<Node> t, std::shared_ptr<Node> e, uint32_t l = 0);+};++struct LetNode {+ std::vector<std::pair<std::string, std::shared_ptr<Node>>> bindings;+ std::shared_ptr<Node> body;+ uint32_t line = 0;+ LetNode(std::shared_ptr<Node> b, uint32_t l = 0);+};++struct LetRecNode {+ std::vector<std::pair<std::string, std::shared_ptr<Node>>> bindings;+ std::shared_ptr<Node> body;+ uint32_t line = 0;+ LetRecNode(std::shared_ptr<Node> b, uint32_t l = 0);+};++struct AssertNode {+ std::shared_ptr<Node> cond;+ std::shared_ptr<Node> body;+ uint32_t line = 0;+ AssertNode(std::shared_ptr<Node> c, std::shared_ptr<Node> b, uint32_t l = 0);+};++struct ThunkNode {+ std::shared_ptr<Node> expr;+ uint32_t line = 0;+ ThunkNode(std::shared_ptr<Node> e, uint32_t l = 0);+};++struct ForceNode {+ std::shared_ptr<Node> expr;+ uint32_t line = 0;+ ForceNode(std::shared_ptr<Node> e, uint32_t l = 0);+};++// Node wraps a variant for type-safe AST+class Node {+public:+ using Variant = std::variant<+ ConstIntNode,+ ConstStringNode,+ ConstPathNode,+ ConstBoolNode,+ ConstNullNode,+ VarNode,+ LambdaNode,+ AppNode,+ BinaryOpNode,+ UnaryOpNode,+ AttrsetNode,+ SelectNode,+ WithNode,+ IfNode,+ LetNode,+ LetRecNode,+ AssertNode,+ ThunkNode,+ ForceNode+ >;++ Variant data;++ template<typename T>+ Node(T&& value) : data(std::forward<T>(value)) {}++ template<typename T>+ T* get_if() { return std::get_if<T>(&data); }++ template<typename T>+ const T* get_if() const { return std::get_if<T>(&data); }++ template<typename T>+ bool holds() const { return std::holds_alternative<T>(data); }+};++// Constructor implementations+inline LambdaNode::LambdaNode(uint32_t a, std::shared_ptr<Node> b, uint32_t l)+ : arity(a), body(b), line(l) {}++inline AppNode::AppNode(std::shared_ptr<Node> f, std::shared_ptr<Node> a, uint32_t l)+ : func(f), arg(a), line(l) {}++inline BinaryOpNode::BinaryOpNode(BinaryOp o, std::shared_ptr<Node> l, std::shared_ptr<Node> r, uint32_t ln)+ : op(o), left(l), right(r), line(ln) {}++inline UnaryOpNode::UnaryOpNode(UnaryOp o, std::shared_ptr<Node> operand, uint32_t l)+ : op(o), operand(operand), line(l) {}++inline SelectNode::SelectNode(std::shared_ptr<Node> e, std::shared_ptr<Node> a, uint32_t l)+ : expr(e), attr(a), line(l) {}++inline WithNode::WithNode(std::shared_ptr<Node> a, std::shared_ptr<Node> b, uint32_t l)+ : attrs(a), body(b), line(l) {}++inline IfNode::IfNode(std::shared_ptr<Node> c, std::shared_ptr<Node> t, std::shared_ptr<Node> e, uint32_t l)+ : cond(c), then_branch(t), else_branch(e), line(l) {}++inline LetNode::LetNode(std::shared_ptr<Node> b, uint32_t l)+ : body(b), line(l) {}++inline LetRecNode::LetRecNode(std::shared_ptr<Node> b, uint32_t l)+ : body(b), line(l) {}++inline AssertNode::AssertNode(std::shared_ptr<Node> c, std::shared_ptr<Node> b, uint32_t l)+ : cond(c), body(b), line(l) {}++inline ThunkNode::ThunkNode(std::shared_ptr<Node> e, uint32_t l)+ : expr(e), line(l) {}++inline ForceNode::ForceNode(std::shared_ptr<Node> e, uint32_t l)+ : expr(e), line(l) {}++struct SourceFile {+ std::string path;+ std::string content;+ std::shared_ptr<Node> ast;+};++struct IRModule {+ uint32_t version = IR_VERSION;+ std::vector<SourceFile> sources;+ std::vector<std::pair<std::string, std::string>> imports;+ std::shared_ptr<Node> entry;+ std::unordered_map<std::string, uint32_t> string_table;+};++}+#endif@@ -0,0 +1,191 @@+#ifdef unix+#undef unix+#endif++#include "nix/expr/eval.hh"+#include "nix/expr/primops.hh"+#include "nix/expr/value.hh"+#include "nix/store/store-api.hh"+#include "nix/util/source-path.hh"++#include "irc/ir_gen.h"+#include "irc/parser.h"+#include "irc/resolver.h"+#include "irc/serializer.h"+#include "irc/types.h"++#include <fstream>+#include <iostream>+#include <memory>+#include <optional>++namespace nix_ir_plugin {++using namespace nix;+using namespace nix_irc;++/**+ * Load and evaluate a pre-compiled IR bundle+ * Usage: builtins.nixIR.loadIR "/path/to/file.nixir"+ */+static void prim_loadIR(EvalState &state, const PosIdx pos, Value **args,+ Value &v) {+ auto path = state.forceStringNoCtx(+ *args[0], pos,+ "while evaluating the first argument to builtins.nixIR.loadIR");++ std::string pathStr(path);++ // Try to load the IR bundle+ std::ifstream file(pathStr, std::ios::binary | std::ios::ate);+ if (!file.is_open()) {+ state.error<EvalError>("cannot open IR bundle: %s", pathStr)+ .atPos(pos)+ .debugThrow();+ }++ // Read magic number to verify it's an IR file+ file.seekg(0);+ uint32_t magic = 0;+ file.read(reinterpret_cast<char *>(&magic), sizeof(magic));++ if (magic != IR_MAGIC) {+ state+ .error<EvalError>("not a valid IR bundle: %s (bad magic number)",+ pathStr)+ .atPos(pos)+ .debugThrow();+ }++ // For now, just return a marker that this is an IR file+ // FIXME: complete full VM implementation+ auto bindings = state.buildBindings(2);++ Value *vType = state.allocValue();+ vType->mkString("ir-bundle");+ bindings.insert(state.symbols.create("type"), vType);++ Value *vPath = state.allocValue();+ vPath->mkString(pathStr);+ bindings.insert(state.symbols.create("path"), vPath);++ v.mkAttrs(bindings.finish());+}++/**+ * Compile Nix source to IR on-the-fly+ * Usage: builtins.nixIR.compile "{ x = 1; }"+ */+static void prim_compileNix(EvalState &state, const PosIdx pos, Value **args,+ Value &v) {+ auto source = state.forceStringNoCtx(+ *args[0], pos,+ "while evaluating the first argument to builtins.nixIR.compile");++ std::string sourceStr(source);++ try {+ // Parse the Nix source+ Parser parser;+ auto ast = parser.parse(sourceStr, "<inline>");++ if (!ast) {+ state.error<EvalError>("failed to parse Nix expression")+ .atPos(pos)+ .debugThrow();+ }++ // Generate IR+ IRGenerator ir_gen;+ auto ir = ir_gen.generate(ast);++ // For now, return a marker that compilation succeeded+ // FIXME: full evaluation+ auto bindings = state.buildBindings(2);++ Value *vType = state.allocValue();+ vType->mkString("ir-compiled");+ bindings.insert(state.symbols.create("type"), vType);++ Value *vSource = state.allocValue();+ vSource->mkString(sourceStr.substr(0, 50));+ bindings.insert(state.symbols.create("source"), vSource);++ v.mkAttrs(bindings.finish());++ } catch (const std::exception &e) {+ state.error<EvalError>("IR compilation failed: %s", e.what())+ .atPos(pos)+ .debugThrow();+ }+}++/**+ * Get information about the IR plugin+ * Usage: builtins.nixIR.info+ */+static void prim_info(EvalState &state, const PosIdx pos, Value **args,+ Value &v) {+ auto bindings = state.buildBindings(4);++ Value *vName = state.allocValue();+ vName->mkString("nix-ir-plugin");+ bindings.insert(state.symbols.create("name"), vName);++ Value *vVersion = state.allocValue();+ vVersion->mkString("0.1.0-alpha");+ bindings.insert(state.symbols.create("version"), vVersion);++ Value *vPhase = state.allocValue();+ vPhase->mkString("phase-4");+ bindings.insert(state.symbols.create("phase"), vPhase);++ Value *vStatus = state.allocValue();+ vStatus->mkString("compiler-complete");+ bindings.insert(state.symbols.create("status"), vStatus);++ v.mkAttrs(bindings.finish());+}++// Register primops on plugin load+static RegisterPrimOp rp_loadIR({+ .name = "__nixIR_loadIR",+ .args = {"path"},+ .arity = 1,+ .doc = R"(+ Load a pre-compiled IR bundle from the given path.+ Returns an attrset with information about the loaded bundle.++ )",+ .fun = prim_loadIR,+});++static RegisterPrimOp rp_compileNix({+ .name = "__nixIR_compile",+ .args = {"source"},+ .arity = 1,+ .doc = R"(+ Compile Nix source code to IR on-the-fly.+ Returns an attrset with information about the compiled IR.++ )",+ .fun = prim_compileNix,+});++static RegisterPrimOp rp_info({+ .name = "__nixIR_info",+ .args = {},+ .arity = 0,+ .doc = R"(+ Get information about the IR plugin.+ Returns an attrset with plugin name, version, and status.+ )",+ .fun = prim_info,+});++} // namespace nix_ir_plugin++// Plugin initialization message+__attribute__((constructor)) static void init_plugin() {+ std::cerr << "nix-ir-plugin loaded" << std::endl;+}