|
|
|
|
@@ -1,8 +1,9 @@
|
|
|
|
|
/**
|
|
|
|
|
* Conversion and serialization builtin functions (unimplemented)
|
|
|
|
|
* Conversion and serialization builtin functions
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import type { NixValue } from "../types";
|
|
|
|
|
import { force } from "../thunk";
|
|
|
|
|
|
|
|
|
|
export const fromJSON = (e: NixValue): never => {
|
|
|
|
|
throw new Error("Not implemented: fromJSON");
|
|
|
|
|
@@ -20,6 +21,176 @@ export const toXML = (e: NixValue): never => {
|
|
|
|
|
throw new Error("Not implemented: toXML");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const toString = (name: NixValue, s: NixValue): never => {
|
|
|
|
|
throw new Error("Not implemented: toString");
|
|
|
|
|
/**
|
|
|
|
|
* String coercion modes control which types can be coerced to strings
|
|
|
|
|
*
|
|
|
|
|
* - Base: Only strings are allowed (no coercion)
|
|
|
|
|
* - Interpolation: Used in string interpolation "${expr}" - allows strings and integers
|
|
|
|
|
* - ToString: Used in builtins.toString - allows all types (bools, floats, null, lists, etc.)
|
|
|
|
|
*/
|
|
|
|
|
export enum StringCoercionMode {
|
|
|
|
|
Base = 0,
|
|
|
|
|
Interpolation = 1,
|
|
|
|
|
ToString = 2,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper function to get human-readable type names for error messages
|
|
|
|
|
*/
|
|
|
|
|
const typeName = (value: NixValue): string => {
|
|
|
|
|
const val = force(value);
|
|
|
|
|
|
|
|
|
|
if (typeof val === "bigint") return "int";
|
|
|
|
|
if (typeof val === "number") return "float";
|
|
|
|
|
if (typeof val === "boolean") return "boolean";
|
|
|
|
|
if (typeof val === "string") return "string";
|
|
|
|
|
if (val === null) return "null";
|
|
|
|
|
if (Array.isArray(val)) return "list";
|
|
|
|
|
if (typeof val === "function") return "lambda";
|
|
|
|
|
if (typeof val === "object") return "attribute set";
|
|
|
|
|
|
|
|
|
|
return `unknown type`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Coerce a Nix value to a string according to the specified mode.
|
|
|
|
|
* This implements the same behavior as Lix's EvalState::coerceToString.
|
|
|
|
|
*
|
|
|
|
|
* @param value - The value to coerce
|
|
|
|
|
* @param mode - The coercion mode (controls which types are allowed)
|
|
|
|
|
* @param copyToStore - If true, paths should be copied to the Nix store (not implemented yet)
|
|
|
|
|
* @returns The string representation of the value
|
|
|
|
|
* @throws TypeError if the value cannot be coerced in the given mode
|
|
|
|
|
*
|
|
|
|
|
* Coercion rules by type:
|
|
|
|
|
* - String: Always returns as-is
|
|
|
|
|
* - Path: Returns the path string (copyToStore not implemented yet)
|
|
|
|
|
* - Integer: Only in Interpolation or ToString mode
|
|
|
|
|
* - Float: Only in ToString mode
|
|
|
|
|
* - Boolean: Only in ToString mode (true → "1", false → "")
|
|
|
|
|
* - Null: Only in ToString mode (→ "")
|
|
|
|
|
* - List: Only in ToString mode (recursively coerce elements, join with spaces)
|
|
|
|
|
* - Attrs: Check for __toString method or outPath attribute
|
|
|
|
|
* - Function: Never coercible (throws error)
|
|
|
|
|
*/
|
|
|
|
|
export const coerceToString = (
|
|
|
|
|
value: NixValue,
|
|
|
|
|
mode: StringCoercionMode = StringCoercionMode.ToString,
|
|
|
|
|
copyToStore: boolean = false,
|
|
|
|
|
): string => {
|
|
|
|
|
const v = force(value);
|
|
|
|
|
|
|
|
|
|
// Strings are always returned as-is, regardless of mode
|
|
|
|
|
if (typeof v === "string") {
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attribute sets can define custom string conversion via __toString method
|
|
|
|
|
// or may have an outPath attribute (for derivations and paths)
|
|
|
|
|
if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
|
|
|
// First, try the __toString method if present
|
|
|
|
|
// This allows custom types to define their own string representation
|
|
|
|
|
if ("__toString" in v) {
|
|
|
|
|
// Force the method in case it's a thunk
|
|
|
|
|
const toStringMethod = force(v["__toString"]);
|
|
|
|
|
if (typeof toStringMethod === "function") {
|
|
|
|
|
// Call the method with self as argument
|
|
|
|
|
const result = force(toStringMethod(v));
|
|
|
|
|
if (typeof result !== "string") {
|
|
|
|
|
throw new TypeError(`__toString returned ${typeName(result)} instead of string`);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If no __toString, try outPath (used for derivations and store paths)
|
|
|
|
|
// This allows derivation objects like { outPath = "/nix/store/..."; } to be coerced
|
|
|
|
|
if ("outPath" in v) {
|
|
|
|
|
// Recursively coerce the outPath value (it might itself be an attrs with __toString)
|
|
|
|
|
return coerceToString(v["outPath"], mode, copyToStore);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attribute sets without __toString or outPath cannot be coerced
|
|
|
|
|
throw new TypeError(`cannot coerce ${typeName(v)} to a string`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Integer coercion is allowed in Interpolation and ToString modes
|
|
|
|
|
// This enables string interpolation like "value: ${42}"
|
|
|
|
|
if (mode >= StringCoercionMode.Interpolation) {
|
|
|
|
|
if (typeof v === "bigint") {
|
|
|
|
|
return v.toString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The following types are only coercible in ToString mode (builtins.toString)
|
|
|
|
|
if (mode >= StringCoercionMode.ToString) {
|
|
|
|
|
// Booleans: true → "1", false → ""
|
|
|
|
|
// This is for shell scripting convenience (same as null)
|
|
|
|
|
if (typeof v === "boolean") {
|
|
|
|
|
return v ? "1" : "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Floats are converted using JavaScript's default toString
|
|
|
|
|
if (typeof v === "number") {
|
|
|
|
|
return v.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Null becomes empty string (for shell scripting convenience)
|
|
|
|
|
if (v === null) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lists are recursively converted and joined with spaces
|
|
|
|
|
// We cannot use Array.join() directly because of special spacing rules:
|
|
|
|
|
// - Elements are recursively coerced to strings
|
|
|
|
|
// - Spaces are added between elements, BUT:
|
|
|
|
|
// * No space is added after an element if it's an empty list
|
|
|
|
|
// * The last element never gets a trailing space
|
|
|
|
|
//
|
|
|
|
|
// Examples:
|
|
|
|
|
// - [ 1 2 3 ] → "1 2 3"
|
|
|
|
|
// - [ 1 [ ] 2 ] → "1 2" (empty list doesn't add space)
|
|
|
|
|
// - [ 1 [ [ ] ] 2 ] → "1 2" (nested empty list is not itself empty, so adds space)
|
|
|
|
|
// - [ [ 1 2 ] [ 3 4 ] ] → "1 2 3 4" (nested lists flatten)
|
|
|
|
|
if (Array.isArray(v)) {
|
|
|
|
|
let result = "";
|
|
|
|
|
for (let i = 0; i < v.length; i++) {
|
|
|
|
|
const item = v[i];
|
|
|
|
|
// Recursively convert element to string
|
|
|
|
|
const str = coerceToString(item, mode, copyToStore);
|
|
|
|
|
result += str;
|
|
|
|
|
|
|
|
|
|
// Add space after this element if:
|
|
|
|
|
// 1. It's not the last element, AND
|
|
|
|
|
// 2. The element is not an empty list
|
|
|
|
|
//
|
|
|
|
|
// Note: We check if the ELEMENT is an empty list, not if its
|
|
|
|
|
// string representation is empty.
|
|
|
|
|
// For example, [[]] is not an empty list (length 1), so it gets
|
|
|
|
|
// a trailing space even though its toString is "".
|
|
|
|
|
if (i < v.length - 1) {
|
|
|
|
|
const forcedItem = force(item);
|
|
|
|
|
if (!Array.isArray(forcedItem) || forcedItem.length !== 0) {
|
|
|
|
|
result += " ";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new TypeError(`cannot coerce ${typeName(v)} to a string`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* builtins.toString - Convert a value to a string
|
|
|
|
|
*
|
|
|
|
|
* This is the public builtin function exposed to Nix code.
|
|
|
|
|
* It uses ToString mode, which allows coercing all types except functions.
|
|
|
|
|
*
|
|
|
|
|
* @param value - The value to convert to a string
|
|
|
|
|
* @returns The string representation
|
|
|
|
|
*/
|
|
|
|
|
export const toString = (value: NixValue): string => {
|
|
|
|
|
return coerceToString(value, StringCoercionMode.ToString, false);
|
|
|
|
|
};
|
|
|
|
|
|