diff --git a/src/irc/evaluator.cpp b/src/irc/evaluator.cpp index 7a50dc7..dfd6eb1 100644 --- a/src/irc/evaluator.cpp +++ b/src/irc/evaluator.cpp @@ -5,7 +5,9 @@ #include "evaluator.h" #include "nix/expr/eval.hh" #include "nix/expr/value.hh" -#include "nix/util/url.hh" +#include "nix/util/error.hh" + +#include #include namespace nix_irc { @@ -64,7 +66,11 @@ struct Evaluator::Impl { explicit Impl(EvalState& s) : state(s) {} - // Destructor not needed - unique_ptr handles cleanup automatically + ~Impl() { + for (auto& env : environments) { + delete env.release(); + } + } IREnvironment* make_env(IREnvironment* parent = nullptr) { auto env = new IREnvironment(parent); @@ -102,42 +108,14 @@ struct Evaluator::Impl { if (auto* n = node->get_if()) { v.mkInt(n->value); - } else if (auto* n = node->get_if()) { - v.mkFloat(n->value); } else if (auto* n = node->get_if()) { v.mkString(n->value); } else if (auto* n = node->get_if()) { - std::string path = n->value; - // Expand ~/ to home directory - if (path.size() >= 2 && path[0] == '~' && path[1] == '/') { - const char* home = getenv("HOME"); - if (home) { - path = std::string(home) + path.substr(1); - } - } - v.mkPath(state.rootPath(CanonPath(path))); + v.mkPath(state.rootPath(CanonPath(n->value))); } else if (auto* n = node->get_if()) { v.mkBool(n->value); - } else if (auto* n = node->get_if()) { // NOLINT(bugprone-branch-clone) + } else if (auto* n = node->get_if()) { // NOLINT(bugprone-branch-clone) v.mkNull(); - } else if (auto* n = node->get_if()) { - // Parse and validate URI, then create string with URI context - auto parsed = parseURL(n->value, true); - // Store URI with context - use simple mkString with context - v.mkString(parsed.to_string(), nix::NixStringContext{}); - } else if (auto* n = node->get_if()) { - // Lookup path like ; resolve via Nix search path - // We can use EvalState's searchPath to resolve - auto path = state.findFile(n->value); - v.mkPath(path); - } else if (auto* n = node->get_if()) { - // Evaluate list - allocate and populate - auto builder = state.buildList(n->elements.size()); - for (size_t i = 0; i < n->elements.size(); i++) { - builder.elems[i] = state.allocValue(); - eval_node(n->elements[i], *builder.elems[i], env); - } - v.mkList(builder); } else if (auto* n = node->get_if()) { Value* bound = env ? env->lookup(n->index) : nullptr; if (!bound && env && n->name.has_value()) { @@ -238,22 +216,6 @@ struct Evaluator::Impl { 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 if (left->type() == nPath && right->type() == nString) { - // Path + string = path - std::string leftPath = std::string(left->path().path.abs()); - std::string result = leftPath + std::string(right->c_str()); - v.mkPath(state.rootPath(CanonPath(result))); - } else if (left->type() == nString && right->type() == nPath) { - // String + path = path - std::string rightPath = std::string(right->path().path.abs()); - std::string result = std::string(left->c_str()) + rightPath; - v.mkPath(state.rootPath(CanonPath(result))); - } else if (left->type() == nPath && right->type() == nPath) { - // Path + path = path - std::string leftPath = std::string(left->path().path.abs()); - std::string rightPath = std::string(right->path().path.abs()); - std::string result = leftPath + rightPath; - v.mkPath(state.rootPath(CanonPath(result))); } else { state.error("type error in addition").debugThrow(); } @@ -324,60 +286,10 @@ struct Evaluator::Impl { state.error("type error in comparison").debugThrow(); } break; - case BinaryOp::CONCAT: { - // List concatenation: left ++ right - if (left->type() != nList || right->type() != nList) { - state.error("list concatenation requires two lists").debugThrow(); - } - - size_t left_size = left->listSize(); - size_t right_size = right->listSize(); - size_t total_size = left_size + right_size; - - auto builder = state.buildList(total_size); - auto left_view = left->listView(); - auto right_view = right->listView(); - - // Copy elements from left list - size_t idx = 0; - for (auto elem : left_view) { - builder.elems[idx++] = elem; - } - - // Copy elements from right list - for (auto elem : right_view) { - builder.elems[idx++] = elem; - } - - v.mkList(builder); + case BinaryOp::CONCAT: + // ++ is list concatenation in Nix; string concat uses ADD (+) + state.error("list concatenation not yet implemented").debugThrow(); break; - } - case BinaryOp::MERGE: { - // // is attrset merge - right overrides left - if (left->type() != nAttrs || right->type() != nAttrs) { - state.error("attrset merge requires two attrsets").debugThrow(); - } - - // Build a map of right attrs first (these have priority) - std::unordered_map right_attrs; - for (auto& attr : *right->attrs()) { - right_attrs[attr.name] = attr.value; - } - - // Copy right attrs to result - auto builder = state.buildBindings(left->attrs()->size() + right->attrs()->size()); - for (auto& attr : *right->attrs()) { - builder.insert(attr.name, attr.value); - } - // Add left attrs that don't exist in right - for (auto& attr : *left->attrs()) { - if (right_attrs.find(attr.name) == right_attrs.end()) { - builder.insert(attr.name, attr.value); - } - } - v.mkAttrs(builder.finish()); - break; - } default: state.error("unknown binary operator").debugThrow(); } @@ -423,16 +335,17 @@ struct Evaluator::Impl { } else if (auto* n = node->get_if()) { auto let_env = make_env(env); for (const auto& [name, expr] : n->bindings) { - // Create thunks in let_env so bindings can reference each other - Value* val = make_thunk(expr, let_env); + Value* val = make_thunk(expr, env); let_env->bind(val); } eval_node(n->body, v, let_env); } else if (auto* n = node->get_if()) { auto letrec_env = make_env(env); + std::vector thunk_vals; for (const auto& [name, expr] : n->bindings) { Value* val = make_thunk(expr, letrec_env); + thunk_vals.push_back(val); letrec_env->bind(val); } @@ -442,36 +355,21 @@ struct Evaluator::Impl { IREnvironment* attr_env = env; if (n->recursive) { - // For recursive attrsets, create environment where all bindings can - // see each other attr_env = make_env(env); - for (const auto& binding : n->attrs) { - if (!binding.is_dynamic()) { - Value* thunk = make_thunk(binding.value, attr_env); - attr_env->bind(thunk); - } + for (const auto& [key, val] : n->attrs) { + Value* thunk = make_thunk(val, attr_env); + attr_env->bind(thunk); } } - // Attributes should be lazy, so store as thunks and not evaluated values - for (const auto& binding : n->attrs) { - Value* attr_val = make_thunk(binding.value, attr_env); - - if (binding.is_dynamic()) { - // Evaluate key expression to get attribute name - Value* key_val = state.allocValue(); - eval_node(binding.dynamic_name, *key_val, attr_env); - force(key_val); - - if (key_val->type() != nString) { - state.error("dynamic attribute name must evaluate to a string").debugThrow(); - } - - std::string key_str = std::string(key_val->c_str()); - bindings.insert(state.symbols.create(key_str), attr_val); + for (const auto& [key, val] : n->attrs) { + Value* attr_val = state.allocValue(); + if (n->recursive) { + eval_node(val, *attr_val, attr_env); } else { - bindings.insert(state.symbols.create(binding.static_name.value()), attr_val); + eval_node(val, *attr_val, env); } + bindings.insert(state.symbols.create(key), attr_val); } v.mkAttrs(bindings.finish()); @@ -548,21 +446,6 @@ struct Evaluator::Impl { } eval_node(n->body, v, env); - } else if (auto* n = node->get_if()) { - // Evaluate path expression to get the file path - Value* path_val = state.allocValue(); - eval_node(n->path, *path_val, env); - force(path_val); - - // Path should be a string or path type, convert to SourcePath - if (path_val->type() == nPath) { - state.evalFile(path_val->path(), v); - } else if (path_val->type() == nString) { - auto path = state.rootPath(CanonPath(path_val->c_str())); - state.evalFile(path, v); - } else { - state.error("import argument must be a path or string").debugThrow(); - } } else { v.mkNull(); } diff --git a/src/irc/ir_gen.cpp b/src/irc/ir_gen.cpp index ff9f18c..a2561be 100644 --- a/src/irc/ir_gen.cpp +++ b/src/irc/ir_gen.cpp @@ -122,17 +122,11 @@ struct IRGenerator::Impl { if (auto* n = node.get_if()) { AttrsetNode attrs(n->recursive, n->line); name_resolver.enter_scope(); - for (const auto& binding : n->attrs) { - if (!binding.is_dynamic()) { - name_resolver.bind(binding.static_name.value()); - } + for (const auto& [key, val] : n->attrs) { + name_resolver.bind(key); } - for (const auto& binding : n->attrs) { - if (binding.is_dynamic()) { - attrs.attrs.push_back(AttrBinding(convert(binding.dynamic_name), convert(binding.value))); - } else { - attrs.attrs.push_back(AttrBinding(binding.static_name.value(), convert(binding.value))); - } + for (const auto& [key, val] : n->attrs) { + attrs.attrs.push_back({key, convert(val)}); } name_resolver.exit_scope(); return std::make_shared(attrs); @@ -168,7 +162,6 @@ struct IRGenerator::Impl { name_resolver.bind(key); } std::vector>> new_bindings; - new_bindings.reserve(n->bindings.size()); for (const auto& [key, val] : n->bindings) { new_bindings.push_back({key, convert(val)}); } @@ -184,7 +177,6 @@ struct IRGenerator::Impl { name_resolver.bind(key); } std::vector>> new_bindings; - new_bindings.reserve(n->bindings.size()); for (const auto& [key, val] : n->bindings) { new_bindings.push_back({key, convert(val)}); } diff --git a/src/irc/parser.cpp b/src/irc/parser.cpp index 8d92617..e72d034 100644 --- a/src/irc/parser.cpp +++ b/src/irc/parser.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -22,27 +24,22 @@ static std::string read_file(const std::string& path) { if (!f) { throw std::runtime_error("Cannot open file: " + path); } - - // Ensure FILE* is always closed - auto file_closer = [](FILE* fp) { - if (fp) - fclose(fp); - }; - std::unique_ptr file_guard(f, file_closer); - fseek(f, 0, SEEK_END); 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); } + fclose(f); return content; } static std::pair run_command(const std::string& cmd) { std::array buffer; std::string result; + std::string error; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) @@ -56,7 +53,7 @@ static std::pair run_command(const std::string& cmd) { if (status != 0) { throw std::runtime_error("Command failed: " + cmd); } - return {result, ""}; + return {result, error}; } struct Token { @@ -70,13 +67,8 @@ struct Token { IDENT, STRING, STRING_INTERP, - INDENTED_STRING, - INDENTED_STRING_INTERP, PATH, - LOOKUP_PATH, INT, - FLOAT, - URI, BOOL, LET, IN, @@ -87,7 +79,6 @@ struct Token { ASSERT, WITH, INHERIT, - IMPORT, DOT, SEMICOLON, COLON, @@ -102,7 +93,6 @@ struct Token { STAR, SLASH, CONCAT, - MERGE, EQEQ, NE, LT, @@ -155,8 +145,6 @@ public: emit(TOKEN(AT)); } else if (c == ',') { emit(TOKEN(COMMA)); - } else if (c == '\'' && pos + 1 < input.size() && input[pos + 1] == '\'') { - tokenize_indented_string(); } else if (c == '"') { tokenize_string(); } @@ -183,10 +171,6 @@ public: tokens.push_back(TOKEN(CONCAT)); pos += 2; col += 2; - } else if (c == '/' && pos + 1 < input.size() && input[pos + 1] == '/') { - tokens.push_back(TOKEN(MERGE)); - pos += 2; - col += 2; } else if (c == '&' && pos + 1 < input.size() && input[pos + 1] == '&') { tokens.push_back(TOKEN(AND)); pos += 2; @@ -213,29 +197,7 @@ public: emit(TOKEN(SLASH)); } } else if (c == '<') { - // Check for lookup path vs comparison operator - size_t end = pos + 1; - bool is_lookup_path = false; - - // Scan for valid lookup path characters until > - while (end < input.size() && - (isalnum(input[end]) || input[end] == '-' || input[end] == '_' || - input[end] == '/' || input[end] == '.')) { - end++; - } - - // If we found > and there's content, it's a lookup path - if (end < input.size() && input[end] == '>' && end > pos + 1) { - std::string path = input.substr(pos + 1, end - pos - 1); - tokens.push_back({Token::LOOKUP_PATH, path, line, col}); - pos = end + 1; - col += (end - pos + 1); - is_lookup_path = true; - } - - if (!is_lookup_path) { - emit(TOKEN(LT)); - } + emit(TOKEN(LT)); } else if (c == '>') { emit(TOKEN(GT)); } else if (c == '!') { @@ -251,48 +213,17 @@ public: } } else if (c == '?') { emit(TOKEN(QUESTION)); - } else if (c == '~') { - // Home-relative path ~/... - if (pos + 1 < input.size() && input[pos + 1] == '/') { - tokenize_home_path(); - } else { - // Just ~ by itself is an identifier - tokenize_ident(); - } } else if (c == '-') { // Check if it's a negative number or minus operator if (pos + 1 < input.size() && isdigit(input[pos + 1])) { - // Check for negative float - if (pos + 2 < input.size() && input[pos + 2] == '.') { - tokenize_float(); - } else { - tokenize_int(); - } + tokenize_int(); } else { emit(TOKEN(MINUS)); } } else if (isdigit(c)) { - // Check if it's a float (digit followed by '.') - if (pos + 1 < input.size() && input[pos + 1] == '.') { - tokenize_float(); - } else { - tokenize_int(); - } - } else if (isalpha(c)) { - // Check if it's a URI (contains ://) - look ahead - size_t lookahead = pos; - while (lookahead < input.size() && - (isalnum(input[lookahead]) || input[lookahead] == '_' || input[lookahead] == '-' || - input[lookahead] == '+' || input[lookahead] == '.')) - lookahead++; - std::string potential_scheme = input.substr(pos, lookahead - pos); - if (lookahead + 2 < input.size() && input[lookahead] == ':' && - input[lookahead + 1] == '/' && input[lookahead + 2] == '/') { - // It's a URI, consume the whole thing - tokenize_uri(); - } else { - tokenize_ident(); - } + tokenize_int(); + } else if (isalpha(c) || c == '_') { + tokenize_ident(); } else { pos++; col++; @@ -311,7 +242,7 @@ private: size_t line; size_t col; - void emit(const Token& t) { + void emit(Token t) { tokens.push_back(t); pos++; col++; @@ -329,26 +260,8 @@ private: } pos++; } else if (c == '#') { - // Line comment - skip until newline while (pos < input.size() && input[pos] != '\n') pos++; - } else if (c == '/' && pos + 1 < input.size() && input[pos + 1] == '*') { - // Block comment /* ... */ - // Note: Nix block comments do NOT nest - pos += 2; // Skip /* - while (pos + 1 < input.size()) { - if (input[pos] == '*' && input[pos + 1] == '/') { - pos += 2; // Skip */ - break; - } - if (input[pos] == '\n') { - line++; - col = 1; - } else { - col++; - } - pos++; - } } else { break; } @@ -404,175 +317,10 @@ private: col += s.size() + 2; } - void tokenize_indented_string() { - pos += 2; // Skip opening '' - std::string raw_content; - bool has_interp = false; - size_t start_line = line; - - // Collect raw content until closing '' - while (pos < input.size()) { - // Check for escape sequences - if (pos + 1 < input.size() && input[pos] == '\'' && input[pos + 1] == '\'') { - // Check if it's an escape or the closing delimiter - if (pos + 2 < input.size() && input[pos + 2] == '\'') { - // ''' -> escape for '' - raw_content += "''"; - pos += 3; - continue; - } else if (pos + 2 < input.size() && input[pos + 2] == '$') { - // ''$ -> escape for $ - raw_content += '$'; - pos += 3; - continue; - } else if (pos + 2 < input.size() && input[pos + 2] == '\\') { - // ''\ -> check what follows - if (pos + 3 < input.size()) { - char next = input[pos + 3]; - if (next == 'n') { - raw_content += '\n'; - pos += 4; - continue; - } else if (next == 'r') { - raw_content += '\r'; - pos += 4; - continue; - } else if (next == 't') { - raw_content += '\t'; - pos += 4; - continue; - } else if (next == ' ' || next == '\t') { - // ''\ before whitespace - preserve the whitespace (mark it specially) - raw_content += "\x01"; // Use control char as marker for preserved whitespace - raw_content += next; - pos += 4; - continue; - } - } - // Default: literal backslash - raw_content += '\\'; - pos += 3; - continue; - } else { - // Just closing '' - pos += 2; - break; - } - } - - // Check for interpolation - if (input[pos] == '$' && pos + 1 < input.size() && input[pos + 1] == '{') { - has_interp = true; - raw_content += input[pos]; - pos++; - if (input[pos] == '\n') { - line++; - } - continue; - } - - // Track newlines - if (input[pos] == '\n') { - line++; - raw_content += input[pos]; - pos++; - } else { - raw_content += input[pos]; - pos++; - } - } - - // Strip common indentation - std::string stripped = strip_indentation(raw_content); - - Token::Type type = has_interp ? Token::INDENTED_STRING_INTERP : Token::INDENTED_STRING; - tokens.push_back({type, stripped, start_line, col}); - } - - std::string strip_indentation(const std::string& s) { - if (s.empty()) - return s; - - // Split into lines - std::vector lines; - std::string current_line; - for (char c : s) { - if (c == '\n') { - lines.push_back(current_line); - current_line.clear(); - } else { - current_line += c; - } - } - if (!current_line.empty() || (!s.empty() && s.back() == '\n')) { - lines.push_back(current_line); - } - - // Find minimum indentation (spaces/tabs at start of non-empty lines) - // \x01 marker indicates preserved whitespace (from ''\ escape) - size_t min_indent = std::string::npos; - for (const auto& line : lines) { - if (line.empty()) - continue; // Skip empty lines when calculating indentation - size_t indent = 0; - for (size_t i = 0; i < line.size(); i++) { - char c = line[i]; - // If we hit the preserved whitespace marker, stop counting indentation - if (c == '\x01') - break; - if (c == ' ' || c == '\t') - indent++; - else - break; - } - if (indent < min_indent) - min_indent = indent; - } - - if (min_indent == std::string::npos) - min_indent = 0; - - // Strip min_indent from all lines and remove \x01 markers - std::string result; - for (size_t i = 0; i < lines.size(); i++) { - const auto& line = lines[i]; - if (line.empty()) { - // Preserve empty lines - if (i + 1 < lines.size()) - result += '\n'; - } else { - // Strip indentation, being careful about \x01 markers - size_t skip = 0; - size_t pos = 0; - while (skip < min_indent && pos < line.size()) { - if (line[pos] == '\x01') { - // Hit preserved whitespace marker - don't strip any more - break; - } - skip++; - pos++; - } - - // Add the rest of the line, removing \x01 markers - for (size_t j = pos; j < line.size(); j++) { - if (line[j] != '\x01') { - result += line[j]; - } - } - - if (i + 1 < lines.size()) - result += '\n'; - } - } - - return result; - } - 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] != ']' && - input[pos] != ';') { + input[pos] != '{' && input[pos] != '}' && input[pos] != '[' && input[pos] != ']') { pos++; } std::string path = input.substr(start, pos - start); @@ -580,22 +328,6 @@ private: col += path.size(); } - void tokenize_home_path() { - size_t start = pos; - pos++; // Skip ~ - if (pos < input.size() && input[pos] == '/') { - // Home-relative path ~/something - while (pos < input.size() && !isspace(input[pos]) && 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] == '-') @@ -607,48 +339,12 @@ private: col += num.size(); } - void tokenize_float() { - size_t start = pos; - if (input[pos] == '-') - pos++; - while (pos < input.size() && isdigit(input[pos])) - pos++; - if (pos < input.size() && input[pos] == '.') { - pos++; - while (pos < input.size() && isdigit(input[pos])) - pos++; - } - std::string num = input.substr(start, pos - start); - tokens.push_back({Token::FLOAT, num, line, col}); - col += num.size(); - } - - void tokenize_uri() { - size_t start = pos; - while (pos < input.size() && !isspace(input[pos]) && input[pos] != ')' && input[pos] != ']' && - input[pos] != ';') { - pos++; - } - std::string uri = input.substr(start, pos - start); - tokens.push_back({Token::URI, uri, line, col}); - col += uri.size(); - } - void tokenize_ident() { size_t start = pos; - while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-' || - input[pos] == '+' || input[pos] == '.')) + while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || input[pos] == '-')) pos++; std::string ident = input.substr(start, pos - start); - // Check if it's a URI (contains ://) - size_t scheme_end = ident.find("://"); - if (scheme_end != std::string::npos && scheme_end > 0) { - tokens.push_back({Token::URI, ident, line, col}); - col += ident.size(); - return; - } - Token::Type type = Token::IDENT; if (ident == "let") type = Token::LET; @@ -668,8 +364,6 @@ private: type = Token::WITH; else if (ident == "inherit") type = Token::INHERIT; - else if (ident == "import") - type = Token::IMPORT; else if (ident == "true") type = Token::BOOL; else if (ident == "false") @@ -716,8 +410,6 @@ public: // Get operator precedence (higher = tighter binding) int get_precedence(Token::Type type) { switch (type) { - case Token::MERGE: - return 1; // Low precedence - binds loosely, but must be > 0 to be recognized as binary op case Token::OR: return 1; case Token::AND: @@ -758,8 +450,6 @@ public: return BinaryOp::DIV; case Token::CONCAT: return BinaryOp::CONCAT; - case Token::MERGE: - return BinaryOp::MERGE; case Token::EQEQ: return BinaryOp::EQ; case Token::NE: @@ -798,45 +488,6 @@ public: return std::make_shared(IfNode(cond, then, else_)); } if (consume(Token::LET)) { - // Check for ancient let syntax: let { x = 1; body = x; } - if (current().type == Token::LBRACE) { - advance(); // consume { - std::vector>> bindings; - std::shared_ptr body_expr; - - while (current().type != Token::RBRACE && current().type != Token::EOF_) { - if (current().type != Token::IDENT && current().type != Token::STRING && - current().type != Token::INDENTED_STRING) { - throw std::runtime_error("Expected identifier in ancient let"); - } - - std::string name = current().value; - advance(); - expect(Token::EQUALS); - auto value = parse_expr(); - expect(Token::SEMICOLON); - - // Check if this is the special 'body' binding - if (name == "body") { - body_expr = value; - } else { - bindings.push_back({name, value}); - } - } - - expect(Token::RBRACE); - - if (!body_expr) { - throw std::runtime_error("Ancient let syntax requires 'body' attribute"); - } - - // Ancient let is always recursive - auto letrec = LetRecNode(body_expr); - letrec.bindings = std::move(bindings); - return std::make_shared(std::move(letrec)); - } - - // Modern let syntax: let x = 1; in x bool is_rec = consume(Token::REC); std::vector>> bindings; parse_bindings(bindings); @@ -899,30 +550,42 @@ public: if (name.type == Token::IDENT) { advance(); auto attr = std::make_shared(ConstStringNode(name.value)); - left = std::make_shared(SelectNode(left, attr)); - // Continue loop to handle multi-dot selections (a.b.c) - continue; - } - // If we get here, the token after DOT was not IDENT - // This is a parse error, but we'll just return what we have - break; - } + auto result = std::make_shared(SelectNode(left, attr)); - // Check for 'or' default value: a.b or default - // This is checked after all selections, so works for any selection depth - // 'or' is contextual - only special after a selection expression - if (left->get_if() && current().type == Token::IDENT && current().value == "or") { - advance(); - // Parse default as a primary expression - auto default_expr = parse_expr3(); - // Update the SelectNode with the default expression - auto* select = left->get_if(); - select->default_expr = default_expr; + if (consume(Token::DOT)) { + Token name2 = current(); + if (name2.type == Token::IDENT) { + advance(); + auto attr2 = std::make_shared(ConstStringNode(name2.value)); + auto* curr = result->get_if(); + while (curr && consume(Token::DOT)) { + Token n = current(); + expect(Token::IDENT); + auto a = std::make_shared(ConstStringNode(n.value)); + curr->attr = + std::make_shared(AppNode(std::make_shared(AppNode(curr->attr, a)), + std::make_shared(ConstNullNode()))); + } + } + } + return result; + } else if (consume(Token::LBRACE)) { + auto result = std::make_shared( + SelectNode(left, std::make_shared(ConstStringNode(name.value)))); + parse_expr_attrs(result); + expect(Token::RBRACE); + return result; + } + return left; } return left; } + void parse_expr_attrs(std::shared_ptr&) { + // Extended selection syntax + } + std::shared_ptr parse_expr2() { std::shared_ptr left = parse_expr3(); @@ -946,12 +609,6 @@ public: } std::shared_ptr parse_expr3() { - // Handle import expression - if (consume(Token::IMPORT)) { - auto path_expr = parse_expr3(); - return std::make_shared(ImportNode(path_expr)); - } - // Handle unary operators if (consume(Token::MINUS)) { auto operand = parse_expr3(); @@ -989,16 +646,6 @@ public: return std::make_shared(ConstIntNode(std::stoll(t.value))); } - if (t.type == Token::FLOAT) { - advance(); - return std::make_shared(ConstFloatNode(std::stod(t.value))); - } - - if (t.type == Token::URI) { - advance(); - return std::make_shared(ConstURINode(t.value)); - } - if (t.type == Token::STRING) { advance(); return std::make_shared(ConstStringNode(t.value)); @@ -1010,27 +657,11 @@ public: return parse_string_interp(str_token.value); } - if (t.type == Token::INDENTED_STRING) { - advance(); - return std::make_shared(ConstStringNode(t.value)); - } - - if (t.type == Token::INDENTED_STRING_INTERP) { - Token str_token = current(); - advance(); - return parse_string_interp(str_token.value); - } - if (t.type == Token::PATH) { advance(); return std::make_shared(ConstPathNode(t.value)); } - if (t.type == Token::LOOKUP_PATH) { - advance(); - return std::make_shared(ConstLookupPathNode(t.value)); - } - if (t.type == Token::BOOL) { advance(); return std::make_shared(ConstBoolNode(t.value == "true")); @@ -1069,11 +700,11 @@ public: // inherit (expr) x → x = expr.x auto select = std::make_shared( SelectNode(source, std::make_shared(ConstStringNode(name.value)))); - attrs.attrs.push_back(AttrBinding(name.value, select)); + attrs.attrs.push_back({name.value, select}); } else { // inherit x → x = x auto var = std::make_shared(VarNode(0, name.value)); - attrs.attrs.push_back(AttrBinding(name.value, var)); + attrs.attrs.push_back({name.value, var}); } } @@ -1081,58 +712,18 @@ public: continue; } - // Check for dynamic attribute name: ${expr} = value - if (current().type == Token::STRING_INTERP || - current().type == Token::INDENTED_STRING_INTERP) { - Token str_token = current(); + if (current().type == Token::IDENT || current().type == Token::STRING) { + Token key = current(); advance(); - auto name_expr = parse_string_interp(str_token.value); + std::string key_str = key.value; if (consume(Token::EQUALS)) { auto value = parse_expr(); - // Dynamic attribute - name is evaluated at runtime - attrs.attrs.push_back(AttrBinding(name_expr, value)); - } - } else if (current().type == Token::IDENT || current().type == Token::STRING || - current().type == Token::INDENTED_STRING) { - // Parse attribute path: a.b.c = value - std::vector path; - path.push_back(current().value); - advance(); - - // Collect dot-separated path components - while (consume(Token::DOT)) { - if (current().type == Token::IDENT || current().type == Token::STRING || - current().type == Token::INDENTED_STRING) { - path.push_back(current().value); - advance(); - } else { - break; - } - } - - if (consume(Token::EQUALS)) { - auto value = parse_expr(); - - // Desugar nested paths: a.b.c = v becomes a = { b = { c = v; }; } - if (path.size() == 1) { - // Simple case: just one key - attrs.attrs.push_back(AttrBinding(path[0], value)); - } else { - // Nested case: build nested attrsets from right to left - auto nested = value; - for (int i = path.size() - 1; i > 0; i--) { - auto inner_attrs = AttrsetNode(false); - inner_attrs.attrs.push_back(AttrBinding(path[i], nested)); - nested = std::make_shared(std::move(inner_attrs)); - } - attrs.attrs.push_back(AttrBinding(path[0], nested)); - } + attrs.attrs.push_back({key_str, value}); } else if (consume(Token::AT)) { - // @ pattern - not affected by nested paths auto pattern = parse_expr(); auto value = parse_expr(); - attrs.attrs.push_back(AttrBinding(path[0], value)); + attrs.attrs.push_back({key_str, value}); } } @@ -1152,25 +743,27 @@ public: } std::shared_ptr parse_list() { - std::vector> elements; + std::shared_ptr list = std::make_shared(ConstNullNode()); if (consume(Token::RBRACKET)) { - return std::make_shared(ListNode(elements)); + return list; } + std::vector> elements; 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)); - } + if (!consume(Token::COMMA)) + break; + } + expect(Token::RBRACKET); + + for (auto it = elements.rbegin(); it != elements.rend(); ++it) { + list = std::make_shared(AppNode( + std::make_shared(AppNode(std::make_shared(VarNode(0, "__list")), *it)), + list)); } - // Unreachable, but for safety - return std::make_shared(ListNode(elements)); + return list; } void parse_bindings(std::vector>>& bindings) { diff --git a/src/irc/serializer.cpp b/src/irc/serializer.cpp index f5b1982..fff2208 100644 --- a/src/irc/serializer.cpp +++ b/src/irc/serializer.cpp @@ -1,6 +1,7 @@ #include "serializer.h" #include #include +#include namespace nix_irc { @@ -30,8 +31,6 @@ struct Serializer::Impl { NodeType get_node_type(const Node& node) { if (node.holds()) return NodeType::CONST_INT; - if (node.holds()) - return NodeType::CONST_FLOAT; if (node.holds()) return NodeType::CONST_STRING; if (node.holds()) @@ -40,10 +39,6 @@ struct Serializer::Impl { return NodeType::CONST_BOOL; if (node.holds()) return NodeType::CONST_NULL; - if (node.holds()) - return NodeType::CONST_URI; - if (node.holds()) - return NodeType::CONST_LOOKUP_PATH; if (node.holds()) return NodeType::VAR; if (node.holds()) @@ -54,8 +49,6 @@ struct Serializer::Impl { return NodeType::BINARY_OP; if (node.holds()) return NodeType::UNARY_OP; - if (node.holds()) - return NodeType::IMPORT; if (node.holds()) return NodeType::ATTRSET; if (node.holds()) @@ -64,8 +57,6 @@ struct Serializer::Impl { return NodeType::HAS_ATTR; if (node.holds()) return NodeType::WITH; - if (node.holds()) - return NodeType::LIST; if (node.holds()) return NodeType::IF; if (node.holds()) @@ -87,11 +78,6 @@ struct Serializer::Impl { if (auto* n = node.get_if()) { write_u64(static_cast(n->value)); - } else if (auto* n = node.get_if()) { - double val = n->value; - uint64_t bits = 0; - std::memcpy(&bits, &val, sizeof(bits)); - write_u64(bits); } else if (auto* n = node.get_if()) { write_string(n->value); } else if (auto* n = node.get_if()) { @@ -100,10 +86,6 @@ struct Serializer::Impl { write_u8(n->value ? 1 : 0); } else if (auto* n = node.get_if()) { // No data for null - } else if (auto* n = node.get_if()) { - write_string(n->value); - } else if (auto* n = node.get_if()) { - write_string(n->value); } else if (auto* n = node.get_if()) { write_u32(n->index); } else if (auto* n = node.get_if()) { @@ -125,22 +107,13 @@ struct Serializer::Impl { write_u8(static_cast(n->op)); if (n->operand) write_node(*n->operand); - } else if (auto* n = node.get_if()) { - if (n->path) - write_node(*n->path); } else if (auto* n = node.get_if()) { write_u8(n->recursive ? 1 : 0); write_u32(n->attrs.size()); - for (const auto& binding : n->attrs) { - if (binding.is_dynamic()) { - write_u8(1); // Dynamic flag - write_node(*binding.dynamic_name); - } else { - write_u8(0); // Static flag - write_string(binding.static_name.value()); - } - if (binding.value) - write_node(*binding.value); + for (const auto& [key, val] : n->attrs) { + write_string(key); + if (val) + write_node(*val); } } else if (auto* n = node.get_if()) { if (n->expr) @@ -163,12 +136,6 @@ struct Serializer::Impl { write_node(*n->attrs); if (n->body) write_node(*n->body); - } else if (auto* n = node.get_if()) { - write_u32(n->elements.size()); - for (const auto& elem : n->elements) { - if (elem) - write_node(*elem); - } } else if (auto* n = node.get_if()) { if (n->cond) write_node(*n->cond); @@ -287,12 +254,6 @@ struct Deserializer::Impl { int64_t val = static_cast(read_u64()); return std::make_shared(ConstIntNode(val, line)); } - case NodeType::CONST_FLOAT: { - uint64_t bits = read_u64(); - double val = 0.0; - std::memcpy(&val, &bits, sizeof(val)); - return std::make_shared(ConstFloatNode(val, line)); - } case NodeType::CONST_STRING: { std::string val = read_string(); return std::make_shared(ConstStringNode(val, line)); @@ -307,14 +268,6 @@ struct Deserializer::Impl { } case NodeType::CONST_NULL: return std::make_shared(ConstNullNode(line)); - case NodeType::CONST_URI: { - std::string val = read_string(); - return std::make_shared(ConstURINode(val, line)); - } - case NodeType::CONST_LOOKUP_PATH: { - std::string val = read_string(); - return std::make_shared(ConstLookupPathNode(val, line)); - } case NodeType::VAR: { uint32_t index = read_u32(); return std::make_shared(VarNode(index, "", line)); @@ -340,25 +293,14 @@ struct Deserializer::Impl { auto operand = read_node(); return std::make_shared(UnaryOpNode(op, operand, line)); } - case NodeType::IMPORT: { - auto path = read_node(); - return std::make_shared(ImportNode(path, line)); - } case NodeType::ATTRSET: { bool recursive = read_u8() != 0; uint32_t num_attrs = read_u32(); AttrsetNode attrs(recursive, line); for (uint32_t i = 0; i < num_attrs; i++) { - uint8_t is_dynamic = read_u8(); - if (is_dynamic) { - auto key_expr = read_node(); - auto val = read_node(); - attrs.attrs.push_back(AttrBinding(key_expr, val)); - } else { - std::string key = read_string(); - auto val = read_node(); - attrs.attrs.push_back(AttrBinding(key, val)); - } + std::string key = read_string(); + auto val = read_node(); + attrs.attrs.push_back({key, val}); } return std::make_shared(std::move(attrs)); } @@ -384,15 +326,6 @@ struct Deserializer::Impl { auto body = read_node(); return std::make_shared(WithNode(attrs, body, line)); } - case NodeType::LIST: { - uint32_t num_elements = read_u32(); - std::vector> elements; - elements.reserve(num_elements); -for (uint32_t i = 0; i < num_elements; i++) { - elements.push_back(read_node()); - } - return std::make_shared(ListNode(std::move(elements), line)); - } case NodeType::IF: { auto cond = read_node(); auto then_branch = read_node(); diff --git a/src/irc/types.h b/src/irc/types.h index 7b0765d..f52db8d 100644 --- a/src/irc/types.h +++ b/src/irc/types.h @@ -2,8 +2,10 @@ #define NIX_IRC_TYPES_H #include +#include #include #include +#include #include #include #include @@ -17,24 +19,19 @@ constexpr uint32_t IR_VERSION = 2; enum class NodeType : uint8_t { CONST_INT = 0x01, - CONST_FLOAT = 0x06, CONST_STRING = 0x02, CONST_PATH = 0x03, CONST_BOOL = 0x04, CONST_NULL = 0x05, - CONST_URI = 0x07, - CONST_LOOKUP_PATH = 0x08, VAR = 0x10, LAMBDA = 0x20, APP = 0x21, BINARY_OP = 0x22, UNARY_OP = 0x23, - IMPORT = 0x24, ATTRSET = 0x30, SELECT = 0x31, HAS_ATTR = 0x34, WITH = 0x32, - LIST = 0x33, IF = 0x40, LET = 0x50, LETREC = 0x51, @@ -44,23 +41,7 @@ enum class NodeType : uint8_t { ERROR = 0xFF }; -enum class BinaryOp : uint8_t { - ADD, - SUB, - MUL, - DIV, - CONCAT, - EQ, - NE, - LT, - GT, - LE, - GE, - AND, - OR, - IMPL, - MERGE -}; +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 }; @@ -96,24 +77,6 @@ struct ConstNullNode { ConstNullNode(uint32_t l = 0) : line(l) {} }; -struct ConstFloatNode { - double value; - uint32_t line = 0; - ConstFloatNode(double v = 0.0, uint32_t l = 0) : value(v), line(l) {} -}; - -struct ConstURINode { - std::string value; - uint32_t line = 0; - ConstURINode(std::string v = "", uint32_t l = 0) : value(std::move(v)), line(l) {} -}; - -struct ConstLookupPathNode { - std::string value; // e.g., "nixpkgs" or "nixpkgs/lib" - uint32_t line = 0; - ConstLookupPathNode(std::string v = "", uint32_t l = 0) : value(std::move(v)), line(l) {} -}; - struct VarNode { uint32_t index = 0; std::optional name; @@ -153,24 +116,8 @@ struct UnaryOpNode { UnaryOpNode(UnaryOp o, std::shared_ptr operand, uint32_t l = 0); }; -struct AttrBinding { - std::optional static_name; // Static key like "foo" - std::shared_ptr dynamic_name; // Dynamic key like ${expr} - std::shared_ptr value; - - // Static attribute - AttrBinding(std::string name, std::shared_ptr val) - : static_name(std::move(name)), value(std::move(val)) {} - - // Dynamic attribute - AttrBinding(std::shared_ptr name_expr, std::shared_ptr val) - : dynamic_name(std::move(name_expr)), value(std::move(val)) {} - - bool is_dynamic() const { return !static_name.has_value(); } -}; - struct AttrsetNode { - std::vector attrs; + std::vector>> attrs; bool recursive = false; uint32_t line = 0; AttrsetNode(bool rec = false, uint32_t l = 0) : recursive(rec), line(l) {} @@ -227,12 +174,6 @@ struct AssertNode { AssertNode(std::shared_ptr c, std::shared_ptr b, uint32_t l = 0); }; -struct ImportNode { - std::shared_ptr path; // Path expression to import - uint32_t line = 0; - ImportNode(std::shared_ptr p, uint32_t l = 0); -}; - struct ThunkNode { std::shared_ptr expr; uint32_t line = 0; @@ -245,21 +186,13 @@ struct ForceNode { ForceNode(std::shared_ptr e, uint32_t l = 0); }; -struct ListNode { - std::vector> elements; - uint32_t line = 0; - ListNode(std::vector> elems = {}, uint32_t l = 0) - : elements(std::move(elems)), line(l) {} -}; - // Node wraps a variant for type-safe AST class Node { public: - using Variant = std::variant; + using Variant = std::variant; Variant data; @@ -306,8 +239,6 @@ inline LetRecNode::LetRecNode(std::shared_ptr b, uint32_t l) : body(std::m inline AssertNode::AssertNode(std::shared_ptr c, std::shared_ptr b, uint32_t l) : cond(std::move(c)), body(std::move(b)), line(l) {} -inline ImportNode::ImportNode(std::shared_ptr p, uint32_t l) : path(std::move(p)), line(l) {} - inline ThunkNode::ThunkNode(std::shared_ptr e, uint32_t l) : expr(std::move(e)), line(l) {} inline ForceNode::ForceNode(std::shared_ptr e, uint32_t l) : expr(std::move(e)), line(l) {} diff --git a/tests/ancient_let.nix b/tests/ancient_let.nix deleted file mode 100644 index 3d4cfec..0000000 --- a/tests/ancient_let.nix +++ /dev/null @@ -1,8 +0,0 @@ -# Test ancient let syntax: let { bindings; body = expr; } -# This is equivalent to: let bindings in expr, but has been deprecated -# in newer Nix versions. -let { - x = 10; - y = 20; - body = x + y; -} diff --git a/tests/attrset.nixir b/tests/attrset.nixir new file mode 100644 index 0000000..708f5dd Binary files /dev/null and b/tests/attrset.nixir differ diff --git a/tests/block_comments.nix b/tests/block_comments.nix deleted file mode 100644 index b5de60f..0000000 --- a/tests/block_comments.nix +++ /dev/null @@ -1,12 +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/comparison.nixir b/tests/comparison.nixir new file mode 100644 index 0000000..fb7b4fd Binary files /dev/null and b/tests/comparison.nixir differ diff --git a/tests/dynamic_attrs.nix b/tests/dynamic_attrs.nix deleted file mode 100644 index 3c32fd5..0000000 --- a/tests/dynamic_attrs.nix +++ /dev/null @@ -1,15 +0,0 @@ -# Test dynamic attribute names -# Note: Full dynamic attrs require runtime evaluation -# For now, testing that syntax is recognized -let - key = "mykey"; -in { - # Static attribute for comparison - static = "value"; - - # Dynamic attribute name (basic string interpolation) - # "${key}" = "dynamic_value"; - - # For now, use workaround with static names - mykey = "works"; -} diff --git a/tests/float_test.nix b/tests/float_test.nix deleted file mode 100644 index c239c60..0000000 --- a/tests/float_test.nix +++ /dev/null @@ -1 +0,0 @@ -1.5 diff --git a/tests/home_path.nix b/tests/home_path.nix deleted file mode 100644 index ccfb107..0000000 --- a/tests/home_path.nix +++ /dev/null @@ -1,11 +0,0 @@ -# Test home-relative paths -# Note: This will resolve to the actual home directory at evaluation time -let - # Example home path (will be expanded by evaluator) - config = ~/..config; - file = ~/.bashrc; -in { - # These are just path values that will be expanded - configPath = config; - filePath = file; -} diff --git a/tests/if.nixir b/tests/if.nixir new file mode 100644 index 0000000..4ee0f59 Binary files /dev/null and b/tests/if.nixir differ diff --git a/tests/import_lookup.nix b/tests/import_lookup.nix deleted file mode 100644 index 448b1ea..0000000 --- a/tests/import_lookup.nix +++ /dev/null @@ -1,3 +0,0 @@ -# Test import with lookup path -# Common pattern: import { } -import diff --git a/tests/import_simple.nix b/tests/import_simple.nix deleted file mode 100644 index 023b49d..0000000 --- a/tests/import_simple.nix +++ /dev/null @@ -1,11 +0,0 @@ -# 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/indented_string.nix b/tests/indented_string.nix deleted file mode 100644 index 16c6026..0000000 --- a/tests/indented_string.nix +++ /dev/null @@ -1,31 +0,0 @@ -# Test indented strings (multi-line strings with '' delimiters) -let - # Simple indented string - simple = '' - Hello - World - ''; - - # Indented string with interpolation - name = "Nix"; - greeting = '' - Welcome to ${name}! - This is indented. - ''; - - # Escape sequences - escapes = '' - Literal dollar: ''$ - Literal quotes: ''' - Regular text - ''; - - # Shell script example (common use case) - script = '' - #!/bin/bash - echo "Running script" - ls -la - ''; -in { - inherit simple greeting escapes script; -} diff --git a/tests/let.nixir b/tests/let.nixir new file mode 100644 index 0000000..cb9dd41 Binary files /dev/null and b/tests/let.nixir differ diff --git a/tests/list_concat.nix b/tests/list_concat.nix deleted file mode 100644 index a1b09f1..0000000 --- a/tests/list_concat.nix +++ /dev/null @@ -1,15 +0,0 @@ -# Test list concatenation operator ++ -let - list1 = [1 2 3]; - list2 = [4 5 6]; - empty = []; -in { - # Basic concatenation - combined = list1 ++ list2; - - # Concatenate with empty list - with_empty = list1 ++ empty; - - # Nested concatenation - triple = [1] ++ [2] ++ [3]; -} diff --git a/tests/logical.nixir b/tests/logical.nixir new file mode 100644 index 0000000..010a5f5 Binary files /dev/null and b/tests/logical.nixir differ diff --git a/tests/lookup_path.nix b/tests/lookup_path.nix deleted file mode 100644 index e8bb4ca..0000000 --- a/tests/lookup_path.nix +++ /dev/null @@ -1,9 +0,0 @@ -# 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/lookup_path_nested.nix b/tests/lookup_path_nested.nix deleted file mode 100644 index 0478b00..0000000 --- a/tests/lookup_path_nested.nix +++ /dev/null @@ -1,3 +0,0 @@ -# Test nested lookup path -# Common pattern in Nix: or - diff --git a/tests/merge.nix b/tests/merge.nix deleted file mode 100644 index 221d0f6..0000000 --- a/tests/merge.nix +++ /dev/null @@ -1,2 +0,0 @@ -# Test attrset merge operator (//) -{a = {x = 1;} // {y = 2;};} diff --git a/tests/nested_attrs.nix b/tests/nested_attrs.nix deleted file mode 100644 index 874d08b..0000000 --- a/tests/nested_attrs.nix +++ /dev/null @@ -1,13 +0,0 @@ -# Test nested attribute paths -{ - # Simple nested path - a.b.c = 42; - - # Multiple nested paths - x.y = 1; - x.z = 2; - - # Mix of nested and non-nested - foo = "bar"; - nested.deep.value = 100; -} diff --git a/tests/operators.nixir b/tests/operators.nixir new file mode 100644 index 0000000..f71f899 Binary files /dev/null and b/tests/operators.nixir differ diff --git a/tests/or_in_attrset.nix b/tests/or_in_attrset.nix deleted file mode 100644 index 406149b..0000000 --- a/tests/or_in_attrset.nix +++ /dev/null @@ -1,6 +0,0 @@ -# Test 'or' in attrset context -let - attrs = { a = 1; }; -in { - test = attrs.a or 999; -} diff --git a/tests/or_simple.nix b/tests/or_simple.nix deleted file mode 100644 index 6025a4d..0000000 --- a/tests/or_simple.nix +++ /dev/null @@ -1,4 +0,0 @@ -# Simplest 'or' test -let - x = { a = 1; }; -in x.a or 2 diff --git a/tests/path_concat.nix b/tests/path_concat.nix deleted file mode 100644 index 682175c..0000000 --- a/tests/path_concat.nix +++ /dev/null @@ -1,13 +0,0 @@ -# Test path concatenation -let - # Path + string = path - p1 = ./foo + "/bar"; - - # String + path = path - p2 = "/prefix" + ./suffix; - - # Path + path = path - p3 = ./dir + ./file; -in { - inherit p1 p2 p3; -} diff --git a/tests/precedence.nixir b/tests/precedence.nixir new file mode 100644 index 0000000..de1b0d4 Binary files /dev/null and b/tests/precedence.nixir differ diff --git a/tests/regression_test.cpp b/tests/regression_test.cpp index 1b4c4ce..10123a0 100644 --- a/tests/regression_test.cpp +++ b/tests/regression_test.cpp @@ -157,116 +157,6 @@ void test_parser_expect_in_speculative_parsing() { << std::endl; } -void test_lookup_path_node() { - std::cout << "> Lookup path serialization..." << std::endl; - - auto lookup = std::make_shared(ConstLookupPathNode("nixpkgs")); - IRModule module; - module.entry = lookup; - - Serializer ser; - auto bytes = ser.serialize_to_bytes(module); - - Deserializer deser; - auto loaded = deser.deserialize(bytes); - - auto *loaded_lookup = loaded.entry->get_if(); - TEST_CHECK(loaded_lookup != nullptr, "Deserialized node is ConstLookupPathNode"); - TEST_CHECK(loaded_lookup && loaded_lookup->value == "nixpkgs", - "Lookup path value is 'nixpkgs'"); -} - -void test_import_node() { - std::cout << "> Import node serialization..." << std::endl; - - auto path = std::make_shared(ConstPathNode("./test.nix")); - auto import_node = std::make_shared(ImportNode(path)); - IRModule module; - module.entry = import_node; - - Serializer ser; - auto bytes = ser.serialize_to_bytes(module); - - Deserializer deser; - auto loaded = deser.deserialize(bytes); - - auto *loaded_import = loaded.entry->get_if(); - TEST_CHECK(loaded_import != nullptr, "Deserialized node is ImportNode"); - TEST_CHECK(loaded_import && loaded_import->path != nullptr, - "Import node has path"); - - if (loaded_import && loaded_import->path) { - auto *path_node = loaded_import->path->get_if(); - TEST_CHECK(path_node != nullptr, "Import path is ConstPathNode"); - TEST_CHECK(path_node && path_node->value == "./test.nix", - "Import path value is './test.nix'"); - } -} - -void test_import_with_lookup_path() { - std::cout << "> Import with lookup path..." << std::endl; - - auto lookup = std::make_shared(ConstLookupPathNode("nixpkgs")); - auto import_node = std::make_shared(ImportNode(lookup)); - IRModule module; - module.entry = import_node; - - Serializer ser; - auto bytes = ser.serialize_to_bytes(module); - - Deserializer deser; - auto loaded = deser.deserialize(bytes); - - auto *loaded_import = loaded.entry->get_if(); - TEST_CHECK(loaded_import != nullptr, "Deserialized node is ImportNode"); - - if (loaded_import && loaded_import->path) { - 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'"); - } -} - -void test_uri_node() { - std::cout << "> URI node serialization..." << std::endl; - - auto uri = std::make_shared(ConstURINode("https://example.com")); - IRModule module; - module.entry = uri; - - Serializer ser; - auto bytes = ser.serialize_to_bytes(module); - - Deserializer deser; - auto loaded = deser.deserialize(bytes); - - auto *loaded_uri = loaded.entry->get_if(); - TEST_CHECK(loaded_uri != nullptr, "Deserialized node is ConstURINode"); - TEST_CHECK(loaded_uri && loaded_uri->value == "https://example.com", - "URI value is 'https://example.com'"); -} - -void test_float_node() { - std::cout << "> Float node serialization..." << std::endl; - - auto float_val = std::make_shared(ConstFloatNode(3.14159)); - IRModule module; - module.entry = float_val; - - Serializer ser; - auto bytes = ser.serialize_to_bytes(module); - - Deserializer deser; - auto loaded = deser.deserialize(bytes); - - auto *loaded_float = loaded.entry->get_if(); - TEST_CHECK(loaded_float != nullptr, "Deserialized node is ConstFloatNode"); - TEST_CHECK(loaded_float && loaded_float->value > 3.14 && - loaded_float->value < 3.15, - "Float value is approximately 3.14159"); -} - int main() { std::cout << "=== Regression Tests for Nixir ===" << std::endl << std::endl; @@ -288,21 +178,6 @@ int main() { test_parser_expect_in_speculative_parsing(); std::cout << std::endl; - test_lookup_path_node(); - std::cout << std::endl; - - test_import_node(); - std::cout << std::endl; - - test_import_with_lookup_path(); - std::cout << std::endl; - - test_uri_node(); - std::cout << std::endl; - - test_float_node(); - std::cout << std::endl; - std::cout << "=== Tests Complete ===" << std::endl; std::cout << "Failures: " << failures << std::endl; return failures > 0 ? 1 : 0; diff --git a/tests/select_or_default.nix b/tests/select_or_default.nix deleted file mode 100644 index df91875..0000000 --- a/tests/select_or_default.nix +++ /dev/null @@ -1,16 +0,0 @@ -# Test selection with 'or' default -let - attrs = { a = 1; b = 2; }; -in { - # Attribute exists - should use value from attrs - has_attr = attrs.a or 999; - - # Attribute doesn't exist - should use default - missing_attr = attrs.c or 100; - - # Nested default expression - nested = attrs.d or (attrs.a + attrs.b); - - # Default with literal - with_string = attrs.name or "default_name"; -} diff --git a/tests/simple.nixir b/tests/simple.nixir new file mode 100644 index 0000000..3e26f83 Binary files /dev/null and b/tests/simple.nixir differ diff --git a/tests/simple_op.nixir b/tests/simple_op.nixir new file mode 100644 index 0000000..18ffbd3 Binary files /dev/null and b/tests/simple_op.nixir differ diff --git a/tests/string_interp.nix b/tests/string_interp.nix index 9edc11b..af7b42d 100644 --- a/tests/string_interp.nix +++ b/tests/string_interp.nix @@ -10,11 +10,13 @@ in { # Multiple interpolations multi = "x is ${x} and name is ${name}"; - # Expression evaluation in interpolation - computed = "x + 10 = ${x + 10}"; + # Nested expression + nested = "Result: ${ + if bool_val + then "yes" + else "no" + }"; - bool_check = "${bool_val} is true!"; - - # Just a string, no interpolation + # Just a string (no interpolation) plain = "plain text"; } diff --git a/tests/unary.nixir b/tests/unary.nixir new file mode 100644 index 0000000..652fabc Binary files /dev/null and b/tests/unary.nixir differ diff --git a/tests/uri_test.nix b/tests/uri_test.nix deleted file mode 100644 index ad93389..0000000 --- a/tests/uri_test.nix +++ /dev/null @@ -1,3 +0,0 @@ -https://example.com/path?query=1 -#frag -