fix: print test

This commit is contained in:
2026-01-31 15:35:33 +08:00
parent 1cfa8223c6
commit b8f8b5764d
5 changed files with 192 additions and 3 deletions

View File

@@ -5,6 +5,7 @@
import { CatchableError, HAS_CONTEXT, type NixValue } from "../types";
import { force } from "../thunk";
import { coerceToString, StringCoercionMode } from "./conversion";
import { printValue } from "../print";
export const seq =
(e1: NixValue) =>
@@ -40,7 +41,7 @@ export const throwFunc = (s: NixValue): never => {
export const trace =
(e1: NixValue) =>
(e2: NixValue): NixValue => {
console.log(`trace: ${coerceToString(e1, StringCoercionMode.Base)}`);
console.error(`trace: ${printValue(e1)}`);
return e2;
};

View File

@@ -0,0 +1,107 @@
import { isThunk, IS_CYCLE } from "./thunk";
import { isStringWithContext } from "./string-context";
import { isNixPath, type NixValue } from "./types";
import { is_primop, get_primop_metadata } from "./builtins/index";
export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet()): string => {
if (isThunk(value)) {
return "«thunk»";
}
if (value === null) {
return "null";
}
if (typeof value === "boolean") {
return value ? "true" : "false";
}
if (typeof value === "bigint") {
return value.toString();
}
if (typeof value === "number") {
return value.toString();
}
if (typeof value === "string") {
return printString(value);
}
if (typeof value === "function") {
if (is_primop(value)) {
const meta = get_primop_metadata(value);
if (meta && meta.applied > 0) {
return "<PRIMOP-APP>";
}
return "<PRIMOP>";
}
return "<LAMBDA>";
}
if (typeof value === "object") {
if (IS_CYCLE in value && (value as any)[IS_CYCLE] === true) {
return "«repeated»";
}
if (seen.has(value)) {
return "«repeated»";
}
seen.add(value);
if (isNixPath(value)) {
return value.value;
}
if (isStringWithContext(value)) {
return printString(value.value);
}
if (Array.isArray(value)) {
const items = value.map((v) => printValue(v, seen)).join(" ");
return `[ ${items} ]`;
}
const entries = Object.entries(value)
.map(([k, v]) => `${printSymbol(k)} = ${printValue(v, seen)};`)
.join(" ");
return `{${entries ? ` ${entries} ` : " "}}`;
}
return "<unknown>";
};
const printString = (s: string): string => {
let result = '"';
for (const c of s) {
switch (c) {
case "\\":
result += "\\\\";
break;
case '"':
result += '\\"';
break;
case "\n":
result += "\\n";
break;
case "\r":
result += "\\r";
break;
case "\t":
result += "\\t";
break;
default:
result += c;
}
}
return result + '"';
};
const SYMBOL_REGEX = /^[a-zA-Z_][a-zA-Z0-9_'-]*$/;
const printSymbol = (s: string): string => {
if (SYMBOL_REGEX.test(s)) {
return s;
}
return printString(s);
};

View File

@@ -149,7 +149,7 @@ export const CYCLE_MARKER = { [IS_CYCLE]: true };
* Deeply force a value, handling cycles by returning a special marker.
* Uses WeakSet to track seen objects and avoid infinite recursion.
* Returns a fully forced value where thunks are replaced with their results.
* Cyclic references are replaced with CYCLE_MARKER.
* Cyclic references are replaced with CYCLE_MARKER, preserving the container type.
*/
export const forceDeepSafe = (value: NixValue, seen: WeakSet<object> = new WeakSet()): NixStrictValue => {
const forced = force(value);
@@ -159,6 +159,9 @@ export const forceDeepSafe = (value: NixValue, seen: WeakSet<object> = new WeakS
}
if (seen.has(forced)) {
if (Array.isArray(forced)) {
return [CYCLE_MARKER];
}
return CYCLE_MARKER;
}
seen.add(forced);

View File

@@ -125,6 +125,24 @@ impl Display for AttrSet {
}
}
impl AttrSet {
pub fn display_compat(&self) -> AttrSetCompatDisplay<'_> {
AttrSetCompatDisplay(self)
}
}
pub struct AttrSetCompatDisplay<'a>(&'a AttrSet);
impl Display for AttrSetCompatDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{{")?;
for (k, v) in self.0.data.iter() {
write!(f, " {k} = {};", v.display_compat())?;
}
write!(f, " }}")
}
}
/// Represents a Nix list, which is a vector of values.
#[derive(Constructor, Default, Clone, Debug, PartialEq)]
pub struct List {
@@ -153,6 +171,24 @@ impl Display for List {
}
}
impl List {
pub fn display_compat(&self) -> ListCompatDisplay<'_> {
ListCompatDisplay(self)
}
}
pub struct ListCompatDisplay<'a>(&'a List);
impl Display for ListCompatDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "[ ")?;
for v in self.0.data.iter() {
write!(f, "{} ", v.display_compat())?;
}
write!(f, "]")
}
}
/// Represents any possible Nix value that can be returned from an evaluation.
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
pub enum Value {
@@ -218,3 +254,45 @@ impl Display for Value {
}
}
}
impl Value {
pub fn display_compat(&self) -> ValueCompatDisplay<'_> {
ValueCompatDisplay(self)
}
}
pub struct ValueCompatDisplay<'a>(&'a Value);
impl Display for ValueCompatDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
use Value::*;
match self.0 {
&Int(x) => write!(f, "{x}"),
&Float(x) => write!(f, "{x}"),
&Bool(x) => write!(f, "{x}"),
Null => write!(f, "null"),
String(x) => {
write!(f, "\"")?;
for c in x.chars() {
match c {
'\\' => write!(f, "\\\\")?,
'"' => write!(f, "\\\"")?,
'\n' => write!(f, "\\n")?,
'\r' => write!(f, "\\r")?,
'\t' => write!(f, "\\t")?,
c => write!(f, "{c}")?,
}
}
write!(f, "\"")
}
Path(x) => write!(f, "{x}"),
AttrSet(x) => write!(f, "{}", x.display_compat()),
List(x) => write!(f, "{}", x.display_compat()),
Thunk => write!(f, "«thunk»"),
Func => write!(f, "<LAMBDA>"),
PrimOp(_) => write!(f, "<PRIMOP>"),
PrimOpApp(_) => write!(f, "<PRIMOP-APP>"),
Repeated => write!(f, "«repeated»"),
}
}
}

View File

@@ -38,7 +38,7 @@ fn read_expected(name: &str) -> String {
}
fn format_value(value: &Value) -> String {
value.to_string()
value.display_compat().to_string()
}
macro_rules! eval_okay_test {