irc: add Float and URI literal support

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I40c59d94f650e7b9e68f77598492d7ab6a6a6964
This commit is contained in:
raf 2026-02-22 00:41:19 +03:00
commit 38c13de01d
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
6 changed files with 126 additions and 9 deletions

View file

@ -6,6 +6,7 @@
#include "nix/expr/eval.hh"
#include "nix/expr/value.hh"
#include "nix/util/error.hh"
#include "nix/util/url.hh"
#include <stdexcept>
#include <unordered_map>
@ -108,6 +109,8 @@ struct Evaluator::Impl {
if (auto* n = node->get_if<ConstIntNode>()) {
v.mkInt(n->value);
} else if (auto* n = node->get_if<ConstFloatNode>()) {
v.mkFloat(n->value);
} else if (auto* n = node->get_if<ConstStringNode>()) {
v.mkString(n->value);
} else if (auto* n = node->get_if<ConstPathNode>()) {
@ -116,6 +119,11 @@ struct Evaluator::Impl {
v.mkBool(n->value);
} else if (auto* n = node->get_if<ConstNullNode>()) { // NOLINT(bugprone-branch-clone)
v.mkNull();
} else if (auto* n = node->get_if<ConstURINode>()) {
// Parse and validate URI, then create string with URI context
auto parsed = parseURL(n->value, true);
// Store URI with context - the parsed URL string
v.mkString(parsed.to_string(), nix::NixStringContext{}, state.mem);
} else if (auto* n = node->get_if<VarNode>()) {
Value* bound = env ? env->lookup(n->index) : nullptr;
if (!bound && env && n->name.has_value()) {

View file

@ -69,6 +69,8 @@ struct Token {
STRING_INTERP,
PATH,
INT,
FLOAT,
URI,
BOOL,
LET,
IN,
@ -216,14 +218,37 @@ public:
} else if (c == '-') {
// Check if it's a negative number or minus operator
if (pos + 1 < input.size() && isdigit(input[pos + 1])) {
tokenize_int();
// Check for negative float
if (pos + 2 < input.size() && input[pos + 2] == '.') {
tokenize_float();
} else {
tokenize_int();
}
} else {
emit(TOKEN(MINUS));
}
} else if (isdigit(c)) {
tokenize_int();
} else if (isalpha(c) || c == '_') {
tokenize_ident();
// 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();
}
} else {
pos++;
col++;
@ -339,12 +364,48 @@ 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] == '-'))
while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_' || 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;
@ -646,6 +707,16 @@ public:
return std::make_shared<Node>(ConstIntNode(std::stoll(t.value)));
}
if (t.type == Token::FLOAT) {
advance();
return std::make_shared<Node>(ConstFloatNode(std::stod(t.value)));
}
if (t.type == Token::URI) {
advance();
return std::make_shared<Node>(ConstURINode(t.value));
}
if (t.type == Token::STRING) {
advance();
return std::make_shared<Node>(ConstStringNode(t.value));

View file

@ -31,6 +31,8 @@ struct Serializer::Impl {
NodeType get_node_type(const Node& node) {
if (node.holds<ConstIntNode>())
return NodeType::CONST_INT;
if (node.holds<ConstFloatNode>())
return NodeType::CONST_FLOAT;
if (node.holds<ConstStringNode>())
return NodeType::CONST_STRING;
if (node.holds<ConstPathNode>())
@ -39,6 +41,8 @@ struct Serializer::Impl {
return NodeType::CONST_BOOL;
if (node.holds<ConstNullNode>())
return NodeType::CONST_NULL;
if (node.holds<ConstURINode>())
return NodeType::CONST_URI;
if (node.holds<VarNode>())
return NodeType::VAR;
if (node.holds<LambdaNode>())
@ -78,6 +82,11 @@ struct Serializer::Impl {
if (auto* n = node.get_if<ConstIntNode>()) {
write_u64(static_cast<uint64_t>(n->value));
} else if (auto* n = node.get_if<ConstFloatNode>()) {
double val = n->value;
uint64_t bits = 0;
std::memcpy(&bits, &val, sizeof(bits));
write_u64(bits);
} else if (auto* n = node.get_if<ConstStringNode>()) {
write_string(n->value);
} else if (auto* n = node.get_if<ConstPathNode>()) {
@ -86,6 +95,8 @@ struct Serializer::Impl {
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<ConstURINode>()) {
write_string(n->value);
} else if (auto* n = node.get_if<VarNode>()) {
write_u32(n->index);
} else if (auto* n = node.get_if<LambdaNode>()) {
@ -254,6 +265,12 @@ struct Deserializer::Impl {
int64_t val = static_cast<int64_t>(read_u64());
return std::make_shared<Node>(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<Node>(ConstFloatNode(val, line));
}
case NodeType::CONST_STRING: {
std::string val = read_string();
return std::make_shared<Node>(ConstStringNode(val, line));
@ -268,6 +285,10 @@ struct Deserializer::Impl {
}
case NodeType::CONST_NULL:
return std::make_shared<Node>(ConstNullNode(line));
case NodeType::CONST_URI: {
std::string val = read_string();
return std::make_shared<Node>(ConstURINode(val, line));
}
case NodeType::VAR: {
uint32_t index = read_u32();
return std::make_shared<Node>(VarNode(index, "", line));

View file

@ -19,10 +19,12 @@ 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,
VAR = 0x10,
LAMBDA = 0x20,
APP = 0x21,
@ -77,6 +79,18 @@ 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 VarNode {
uint32_t index = 0;
std::optional<std::string> name;
@ -189,10 +203,11 @@ struct ForceNode {
// 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, HasAttrNode, WithNode, IfNode,
LetNode, LetRecNode, AssertNode, ThunkNode, ForceNode>;
using Variant = std::variant<ConstIntNode, ConstFloatNode, ConstStringNode, ConstPathNode,
ConstBoolNode, ConstNullNode, ConstURINode, VarNode, LambdaNode,
AppNode, BinaryOpNode, UnaryOpNode, AttrsetNode, SelectNode,
HasAttrNode, WithNode, IfNode, LetNode, LetRecNode, AssertNode,
ThunkNode, ForceNode>;
Variant data;

1
tests/float_test.nix Normal file
View file

@ -0,0 +1 @@
1.5

1
tests/uri_test.nix Normal file
View file

@ -0,0 +1 @@
https://example.com/path?query=1#frag