fix: print test
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
107
nix-js/runtime-ts/src/print.ts
Normal file
107
nix-js/runtime-ts/src/print.ts
Normal 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);
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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»"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user