fix: print test
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
import { CatchableError, HAS_CONTEXT, type NixValue } from "../types";
|
import { CatchableError, HAS_CONTEXT, type NixValue } from "../types";
|
||||||
import { force } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import { coerceToString, StringCoercionMode } from "./conversion";
|
import { coerceToString, StringCoercionMode } from "./conversion";
|
||||||
|
import { printValue } from "../print";
|
||||||
|
|
||||||
export const seq =
|
export const seq =
|
||||||
(e1: NixValue) =>
|
(e1: NixValue) =>
|
||||||
@@ -40,7 +41,7 @@ export const throwFunc = (s: NixValue): never => {
|
|||||||
export const trace =
|
export const trace =
|
||||||
(e1: NixValue) =>
|
(e1: NixValue) =>
|
||||||
(e2: NixValue): NixValue => {
|
(e2: NixValue): NixValue => {
|
||||||
console.log(`trace: ${coerceToString(e1, StringCoercionMode.Base)}`);
|
console.error(`trace: ${printValue(e1)}`);
|
||||||
return e2;
|
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.
|
* Deeply force a value, handling cycles by returning a special marker.
|
||||||
* Uses WeakSet to track seen objects and avoid infinite recursion.
|
* Uses WeakSet to track seen objects and avoid infinite recursion.
|
||||||
* Returns a fully forced value where thunks are replaced with their results.
|
* 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 => {
|
export const forceDeepSafe = (value: NixValue, seen: WeakSet<object> = new WeakSet()): NixStrictValue => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
@@ -159,6 +159,9 @@ export const forceDeepSafe = (value: NixValue, seen: WeakSet<object> = new WeakS
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (seen.has(forced)) {
|
if (seen.has(forced)) {
|
||||||
|
if (Array.isArray(forced)) {
|
||||||
|
return [CYCLE_MARKER];
|
||||||
|
}
|
||||||
return CYCLE_MARKER;
|
return CYCLE_MARKER;
|
||||||
}
|
}
|
||||||
seen.add(forced);
|
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.
|
/// Represents a Nix list, which is a vector of values.
|
||||||
#[derive(Constructor, Default, Clone, Debug, PartialEq)]
|
#[derive(Constructor, Default, Clone, Debug, PartialEq)]
|
||||||
pub struct List {
|
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.
|
/// Represents any possible Nix value that can be returned from an evaluation.
|
||||||
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
||||||
pub enum Value {
|
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 {
|
fn format_value(value: &Value) -> String {
|
||||||
value.to_string()
|
value.display_compat().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! eval_okay_test {
|
macro_rules! eval_okay_test {
|
||||||
|
|||||||
Reference in New Issue
Block a user