clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.gitCommit 760094a2b70a
tarballunverified4 files changed+235-9
@@ -69,6 +69,36 @@ struct Evaluator::Impl { explicit Impl(EvalState& s) : state(s) {} + static std::string escape_nix_string(std::string_view value) {+ std::string escaped;+ escaped.reserve(value.size());++ for (char ch : value) {+ switch (ch) {+ case '\\':+ escaped += "\\\\";+ break;+ case '"':+ escaped += "\\\"";+ break;+ case '\n':+ escaped += "\\n";+ break;+ case '\r':+ escaped += "\\r";+ break;+ case '\t':+ escaped += "\\t";+ break;+ default:+ escaped.push_back(ch);+ break;+ }+ }++ return escaped;+ }+ IREnvironment* make_env(IREnvironment* parent = nullptr) { auto env = new IREnvironment(parent); environments.push_back(std::unique_ptr<IREnvironment>(env));@@ -612,6 +642,27 @@ struct Evaluator::Impl { } else { state.error<EvalError>("import argument must be a path or string").debugThrow(); }+ } else if (auto* n = node->get_if<BuiltinCallNode>()) {+ std::vector<Value*> args;+ args.reserve(n->args.size());++ for (const auto& arg_node : n->args) {+ Value* arg = state.allocValue();+ eval_node(arg_node, *arg, env);+ args.push_back(arg);+ }++ if (n->builtin_name == "getFlake") {+ if (args.size() != 1) {+ state.error<EvalError>("getFlake expects exactly one argument").debugThrow();+ }+ auto flake_ref = state.forceStringNoCtx(*args[0], noPos, "while evaluating getFlake");+ std::string expr = "builtins.getFlake \"" + escape_nix_string(flake_ref) + "\"";+ auto* parsed = state.parseExprFromString(expr, state.rootPath(CanonPath::root));+ state.eval(parsed, v);+ } else {+ state.error<EvalError>("unsupported builtin call: %s", n->builtin_name).debugThrow();+ } } else { v.mkNull(); }@@ -226,6 +226,14 @@ struct IRGenerator::Impl { } return std::make_shared<Node>(ListNode(std::move(elements), n->line)); }+ if (auto* n = node.get_if<BuiltinCallNode>()) {+ std::vector<std::shared_ptr<Node>> args;+ args.reserve(n->args.size());+ for (const auto& arg : n->args) {+ args.push_back(convert(arg));+ }+ return std::make_shared<Node>(BuiltinCallNode(n->builtin_name, std::move(args), n->line));+ } return std::make_shared<Node>(ConstNullNode{}); } };@@ -1,22 +1,165 @@ #include "ir_gen.h" #include "parser.h"-#include "resolver.h" #include "serializer.h"+#include <cctype> #include <cstring>+#include <filesystem> #include <iostream>+#include <stdexcept> #include <string> #include <vector> namespace nix_irc {+namespace fs = std::filesystem; void print_usage(const char* prog) {- std::cout << "Usage: " << prog << " [options] <input.nix> [output.nixir]\n"+ std::cout << "Usage: " << prog << " [options] <input.nix|flake#attr> [output.nixir]\n" << "\nOptions:\n" << " -I <path> Add search path for imports\n" << " --no-imports Disable import resolution\n" << " --help Show this help\n"; } +static bool is_flake_reference(const std::string& input) {+ return input.find('#') != std::string::npos;+}++static std::string sanitize_output_stem(const std::string& input) {+ std::string stem;+ stem.reserve(input.size());++ for (char ch : input) {+ if (std::isalnum(static_cast<unsigned char>(ch))) {+ stem.push_back(ch);+ } else if (stem.empty() || stem.back() != '-') {+ stem.push_back('-');+ }+ }++ while (!stem.empty() && stem.back() == '-') {+ stem.pop_back();+ }++ return stem.empty() ? "bundle" : stem;+}++static std::string default_output_path_for(const std::string& input) {+ if (!is_flake_reference(input)) {+ return input + "ir";+ }++ return sanitize_output_stem(input) + ".nixir";+}++static std::string normalize_local_flake_path(const std::string& raw_path) {+ fs::path path = raw_path.empty() ? fs::current_path() : fs::path(raw_path);+ fs::path absolute = path.is_absolute() ? path : fs::absolute(path);+ fs::path normalized = absolute.lexically_normal();++ if (!fs::exists(normalized)) {+ throw std::runtime_error("Flake path does not exist: " + normalized.string());+ }++ if (fs::is_directory(normalized) && !fs::exists(normalized / "flake.nix")) {+ throw std::runtime_error("Flake directory does not contain flake.nix: " + normalized.string());+ }++ return normalized.string();+}++static std::string normalize_flake_ref_source(const std::string& ref) {+ if (ref.empty()) {+ return normalize_local_flake_path(".");+ }++ if (ref.rfind("path:", 0) == 0) {+ return "path:" + normalize_local_flake_path(ref.substr(5));+ }++ if (ref[0] == '.' || ref[0] == '/') {+ return normalize_local_flake_path(ref);+ }++ if (fs::exists(ref)) {+ return normalize_local_flake_path(ref);+ }++ return ref;+}++static std::vector<std::string> parse_flake_attr_path(const std::string& raw_attr_path) {+ if (raw_attr_path.empty()) {+ throw std::runtime_error("Flake reference is missing an attribute path after '#'");+ }++ std::vector<std::string> segments;+ std::string current;+ bool in_quotes = false;+ bool escaping = false;++ for (char ch : raw_attr_path) {+ if (escaping) {+ current.push_back(ch);+ escaping = false;+ continue;+ }++ if (in_quotes) {+ if (ch == '\\') {+ escaping = true;+ } else if (ch == '"') {+ in_quotes = false;+ } else {+ current.push_back(ch);+ }+ continue;+ }++ if (ch == '"') {+ in_quotes = true;+ } else if (ch == '.') {+ if (current.empty()) {+ throw std::runtime_error("Flake attribute path contains an empty segment");+ }+ segments.push_back(current);+ current.clear();+ } else {+ current.push_back(ch);+ }+ }++ if (escaping || in_quotes) {+ throw std::runtime_error("Unterminated quoted segment in flake attribute path");+ }++ if (current.empty()) {+ throw std::runtime_error("Flake attribute path contains an empty segment");+ }++ segments.push_back(current);+ return segments;+}++static std::shared_ptr<Node> build_flake_ref_ast(const std::string& input) {+ size_t hash_pos = input.find('#');+ if (hash_pos == std::string::npos) {+ throw std::runtime_error("Not a flake reference: " + input);+ }++ std::string flake_source = normalize_flake_ref_source(input.substr(0, hash_pos));+ auto attr_path = parse_flake_attr_path(input.substr(hash_pos + 1));++ auto expr = std::make_shared<Node>(BuiltinCallNode(+ "getFlake",+ std::vector<std::shared_ptr<Node>>{std::make_shared<Node>(ConstStringNode(flake_source))}));++ for (const auto& attr : attr_path) {+ expr = std::make_shared<Node>(SelectNode(expr, std::make_shared<Node>(ConstStringNode(attr))));+ }++ return expr;+}+ int run_compile(int argc, char** argv) { std::string input_file; std::string output_file;@@ -57,19 +200,23 @@ int run_compile(int argc, char** argv) { } if (output_file.empty()) {- output_file = input_file + "ir";+ output_file = default_output_path_for(input_file); } try { Parser parser;- Resolver resolver;+ (void) search_paths;+ (void) resolve_imports; - for (const auto& path : search_paths) {- resolver.add_search_path(path);- }+ std::shared_ptr<Node> ast; - std::cout << "Parsing: " << input_file << "\n";- auto ast = parser.parse_file(input_file);+ if (is_flake_reference(input_file)) {+ std::cout << "Compiling flake reference: " << input_file << "\n";+ ast = build_flake_ref_ast(input_file);+ } else {+ std::cout << "Parsing: " << input_file << "\n";+ ast = parser.parse_file(input_file);+ } if (!ast) { std::cerr << "Error: Failed to parse input\n";@@ -78,6 +78,8 @@ struct Serializer::Impl { return NodeType::LAMBDA_PATTERN; if (node.holds<StringInterpolationNode>()) return NodeType::STRING_INTERPOLATION;+ if (node.holds<BuiltinCallNode>())+ return NodeType::BUILTIN_CALL; return NodeType::ERROR; } @@ -250,6 +252,13 @@ struct Serializer::Impl { write_node(*part.expr); } }+ } else if (auto* n = node.get_if<BuiltinCallNode>()) {+ write_string(n->builtin_name);+ write_u32(n->args.size());+ for (const auto& arg : n->args) {+ if (arg)+ write_node(*arg);+ } } } };@@ -366,6 +375,17 @@ struct Deserializer::Impl { std::string val = read_string(); return std::make_shared<Node>(ConstLookupPathNode(val, line)); }+ case NodeType::BUILTIN_CALL: {+ std::string builtin_name = read_string();+ uint32_t num_args = read_u32();+ std::vector<std::shared_ptr<Node>> args;+ args.reserve(num_args);+ for (uint32_t i = 0; i < num_args; i++) {+ args.push_back(read_node());+ }+ return std::make_shared<Node>(+ BuiltinCallNode(std::move(builtin_name), std::move(args), line));+ } case NodeType::VAR: { uint32_t index = read_u32(); return std::make_shared<Node>(VarNode(index, "", line));