feat: IS_PRIMOP
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use std::process::Command;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let runtime_ts_dir = Path::new("runtime-ts");
|
let runtime_ts_dir = Path::new("runtime-ts");
|
||||||
@@ -17,7 +17,11 @@ fn main() {
|
|||||||
|
|
||||||
if !runtime_ts_dir.join("node_modules").exists() {
|
if !runtime_ts_dir.join("node_modules").exists() {
|
||||||
println!("Installing npm dependencies...");
|
println!("Installing npm dependencies...");
|
||||||
let npm_cmd = if cfg!(target_os = "windows") { "npm.cmd" } else { "npm" };
|
let npm_cmd = if cfg!(target_os = "windows") {
|
||||||
|
"npm.cmd"
|
||||||
|
} else {
|
||||||
|
"npm"
|
||||||
|
};
|
||||||
let status = Command::new(npm_cmd)
|
let status = Command::new(npm_cmd)
|
||||||
.arg("install")
|
.arg("install")
|
||||||
.current_dir(runtime_ts_dir)
|
.current_dir(runtime_ts_dir)
|
||||||
@@ -30,7 +34,11 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println!("Running TypeScript type checking...");
|
println!("Running TypeScript type checking...");
|
||||||
let npm_cmd = if cfg!(target_os = "windows") { "npm.cmd" } else { "npm" };
|
let npm_cmd = if cfg!(target_os = "windows") {
|
||||||
|
"npm.cmd"
|
||||||
|
} else {
|
||||||
|
"npm"
|
||||||
|
};
|
||||||
let status = Command::new(npm_cmd)
|
let status = Command::new(npm_cmd)
|
||||||
.arg("run")
|
.arg("run")
|
||||||
.arg("typecheck")
|
.arg("typecheck")
|
||||||
|
|||||||
@@ -5,6 +5,98 @@
|
|||||||
|
|
||||||
import { create_thunk } from '../thunk';
|
import { create_thunk } from '../thunk';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symbol used to mark functions as primops (primitive operations)
|
||||||
|
* This is similar to IS_THUNK but for builtin functions
|
||||||
|
*/
|
||||||
|
export const IS_PRIMOP = Symbol('is_primop');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata interface for primop functions
|
||||||
|
*/
|
||||||
|
export interface PrimopMetadata {
|
||||||
|
/** The name of the primop (e.g., "add", "map") */
|
||||||
|
name: string;
|
||||||
|
/** Total arity of the function (number of arguments it expects) */
|
||||||
|
arity: number;
|
||||||
|
/** Number of arguments already applied (for partial applications) */
|
||||||
|
applied: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a function as a primop with metadata
|
||||||
|
* For curried functions, this recursively marks each layer
|
||||||
|
*
|
||||||
|
* @param func - The function to mark
|
||||||
|
* @param name - Name of the primop
|
||||||
|
* @param arity - Total number of arguments expected
|
||||||
|
* @param applied - Number of arguments already applied (default: 0)
|
||||||
|
* @returns The marked function
|
||||||
|
*/
|
||||||
|
export const markPrimop = <T extends Function>(
|
||||||
|
func: T,
|
||||||
|
name: string,
|
||||||
|
arity: number,
|
||||||
|
applied: number = 0
|
||||||
|
): T => {
|
||||||
|
// Mark this function as a primop
|
||||||
|
(func as any)[IS_PRIMOP] = {
|
||||||
|
name,
|
||||||
|
arity,
|
||||||
|
applied,
|
||||||
|
} as PrimopMetadata;
|
||||||
|
|
||||||
|
// If this is a curried function and not fully applied,
|
||||||
|
// wrap it to mark the next layer too
|
||||||
|
if (applied < arity - 1) {
|
||||||
|
const wrappedFunc = ((...args: any[]) => {
|
||||||
|
const result = func(...args);
|
||||||
|
// If result is a function, mark it as the next layer
|
||||||
|
if (typeof result === 'function') {
|
||||||
|
return markPrimop(result, name, arity, applied + args.length);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
// Copy the primop metadata to the wrapper
|
||||||
|
wrappedFunc[IS_PRIMOP] = {
|
||||||
|
name,
|
||||||
|
arity,
|
||||||
|
applied,
|
||||||
|
} as PrimopMetadata;
|
||||||
|
|
||||||
|
return wrappedFunc as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return func;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard to check if a value is a primop
|
||||||
|
* @param value - Value to check
|
||||||
|
* @returns true if value is marked as a primop
|
||||||
|
*/
|
||||||
|
export const is_primop = (value: unknown): value is Function & { [IS_PRIMOP]: PrimopMetadata } => {
|
||||||
|
return (
|
||||||
|
typeof value === 'function' &&
|
||||||
|
IS_PRIMOP in value &&
|
||||||
|
typeof value[IS_PRIMOP] === 'object' &&
|
||||||
|
value[IS_PRIMOP] !== null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get primop metadata from a function
|
||||||
|
* @param func - Function to get metadata from
|
||||||
|
* @returns Metadata if function is a primop, undefined otherwise
|
||||||
|
*/
|
||||||
|
export const get_primop_metadata = (func: unknown): PrimopMetadata | undefined => {
|
||||||
|
if (is_primop(func)) {
|
||||||
|
return func[IS_PRIMOP];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
// Import all builtin categories
|
// Import all builtin categories
|
||||||
import * as arithmetic from './arithmetic';
|
import * as arithmetic from './arithmetic';
|
||||||
import * as math from './math';
|
import * as math from './math';
|
||||||
@@ -24,144 +116,133 @@ import * as misc from './misc';
|
|||||||
* All functions are curried for Nix semantics:
|
* All functions are curried for Nix semantics:
|
||||||
* - Single argument functions: (a) => result
|
* - Single argument functions: (a) => result
|
||||||
* - Multi-argument functions: (a) => (b) => result
|
* - Multi-argument functions: (a) => (b) => result
|
||||||
|
*
|
||||||
|
* All primop functions are marked with IS_PRIMOP symbol for runtime introspection
|
||||||
*/
|
*/
|
||||||
export const builtins: any = {
|
export const builtins: any = {
|
||||||
// Arithmetic (curried binary functions)
|
add: markPrimop(arithmetic.add, 'add', 2),
|
||||||
add: arithmetic.add,
|
sub: markPrimop(arithmetic.sub, 'sub', 2),
|
||||||
sub: arithmetic.sub,
|
mul: markPrimop(arithmetic.mul, 'mul', 2),
|
||||||
mul: arithmetic.mul,
|
div: markPrimop(arithmetic.div, 'div', 2),
|
||||||
div: arithmetic.div,
|
bitAnd: markPrimop(arithmetic.bitAnd, 'bitAnd', 2),
|
||||||
bitAnd: arithmetic.bitAnd,
|
bitOr: markPrimop(arithmetic.bitOr, 'bitOr', 2),
|
||||||
bitOr: arithmetic.bitOr,
|
bitXor: markPrimop(arithmetic.bitXor, 'bitXor', 2),
|
||||||
bitXor: arithmetic.bitXor,
|
lessThan: markPrimop(arithmetic.lessThan, 'lessThan', 2),
|
||||||
lessThan: arithmetic.lessThan,
|
|
||||||
|
|
||||||
// Math
|
ceil: markPrimop(math.ceil, 'ceil', 1),
|
||||||
ceil: math.ceil,
|
floor: markPrimop(math.floor, 'floor', 1),
|
||||||
floor: math.floor,
|
|
||||||
|
|
||||||
// Type checking
|
isAttrs: markPrimop(typeCheck.isAttrs, 'isAttrs', 1),
|
||||||
isAttrs: typeCheck.isAttrs,
|
isBool: markPrimop(typeCheck.isBool, 'isBool', 1),
|
||||||
isBool: typeCheck.isBool,
|
isFloat: markPrimop(typeCheck.isFloat, 'isFloat', 1),
|
||||||
isFloat: typeCheck.isFloat,
|
isFunction: markPrimop(typeCheck.isFunction, 'isFunction', 1),
|
||||||
isFunction: typeCheck.isFunction,
|
isInt: markPrimop(typeCheck.isInt, 'isInt', 1),
|
||||||
isInt: typeCheck.isInt,
|
isList: markPrimop(typeCheck.isList, 'isList', 1),
|
||||||
isList: typeCheck.isList,
|
isNull: markPrimop(typeCheck.isNull, 'isNull', 1),
|
||||||
isNull: typeCheck.isNull,
|
isPath: markPrimop(typeCheck.isPath, 'isPath', 1),
|
||||||
isPath: typeCheck.isPath,
|
isString: markPrimop(typeCheck.isString, 'isString', 1),
|
||||||
isString: typeCheck.isString,
|
typeOf: markPrimop(typeCheck.typeOf, 'typeOf', 1),
|
||||||
typeOf: typeCheck.typeOf,
|
|
||||||
|
|
||||||
// List operations
|
map: markPrimop(list.map, 'map', 2),
|
||||||
map: list.map,
|
filter: markPrimop(list.filter, 'filter', 2),
|
||||||
filter: list.filter,
|
length: markPrimop(list.length, 'length', 1),
|
||||||
length: list.length,
|
head: markPrimop(list.head, 'head', 1),
|
||||||
head: list.head,
|
tail: markPrimop(list.tail, 'tail', 1),
|
||||||
tail: list.tail,
|
elem: markPrimop(list.elem, 'elem', 2),
|
||||||
elem: list.elem,
|
elemAt: markPrimop(list.elemAt, 'elemAt', 2),
|
||||||
elemAt: list.elemAt,
|
concatLists: markPrimop(list.concatLists, 'concatLists', 1),
|
||||||
concatLists: list.concatLists,
|
concatMap: markPrimop(list.concatMap, 'concatMap', 2),
|
||||||
concatMap: list.concatMap,
|
'foldl\'': markPrimop(list.foldlPrime, 'foldl\'', 3),
|
||||||
'foldl\'': list.foldlPrime,
|
sort: markPrimop(list.sort, 'sort', 2),
|
||||||
sort: list.sort,
|
partition: markPrimop(list.partition, 'partition', 2),
|
||||||
partition: list.partition,
|
genList: markPrimop(list.genList, 'genList', 2),
|
||||||
genList: list.genList,
|
all: markPrimop(list.all, 'all', 2),
|
||||||
all: list.all,
|
any: markPrimop(list.any, 'any', 2),
|
||||||
any: list.any,
|
|
||||||
|
|
||||||
// Attribute set operations
|
attrNames: markPrimop(attrs.attrNames, 'attrNames', 1),
|
||||||
attrNames: attrs.attrNames,
|
attrValues: markPrimop(attrs.attrValues, 'attrValues', 1),
|
||||||
attrValues: attrs.attrValues,
|
getAttr: markPrimop(attrs.getAttr, 'getAttr', 2),
|
||||||
getAttr: attrs.getAttr,
|
hasAttr: markPrimop(attrs.hasAttr, 'hasAttr', 2),
|
||||||
hasAttr: attrs.hasAttr,
|
mapAttrs: markPrimop(attrs.mapAttrs, 'mapAttrs', 2),
|
||||||
mapAttrs: attrs.mapAttrs,
|
listToAttrs: markPrimop(attrs.listToAttrs, 'listToAttrs', 1),
|
||||||
listToAttrs: attrs.listToAttrs,
|
intersectAttrs: markPrimop(attrs.intersectAttrs, 'intersectAttrs', 2),
|
||||||
intersectAttrs: attrs.intersectAttrs,
|
catAttrs: markPrimop(attrs.catAttrs, 'catAttrs', 2),
|
||||||
catAttrs: attrs.catAttrs,
|
groupBy: markPrimop(attrs.groupBy, 'groupBy', 2),
|
||||||
groupBy: attrs.groupBy,
|
|
||||||
|
|
||||||
// String operations
|
stringLength: markPrimop(string.stringLength, 'stringLength', 1),
|
||||||
stringLength: string.stringLength,
|
substring: markPrimop(string.substring, 'substring', 3),
|
||||||
substring: string.substring,
|
concatStringsSep: markPrimop(string.concatStringsSep, 'concatStringsSep', 2),
|
||||||
concatStringsSep: string.concatStringsSep,
|
baseNameOf: markPrimop(string.baseNameOf, 'baseNameOf', 1),
|
||||||
baseNameOf: string.baseNameOf,
|
|
||||||
|
|
||||||
// Functional
|
seq: markPrimop(functional.seq, 'seq', 2),
|
||||||
seq: functional.seq,
|
deepSeq: markPrimop(functional.deepSeq, 'deepSeq', 2),
|
||||||
deepSeq: functional.deepSeq,
|
abort: markPrimop(functional.abort, 'abort', 1),
|
||||||
abort: functional.abort,
|
throw: markPrimop(functional.throwFunc, 'throw', 1),
|
||||||
throw: functional.throwFunc,
|
trace: markPrimop(functional.trace, 'trace', 2),
|
||||||
trace: functional.trace,
|
warn: markPrimop(functional.warn, 'warn', 2),
|
||||||
warn: functional.warn,
|
break: markPrimop(functional.breakFunc, 'break', 1),
|
||||||
break: functional.breakFunc,
|
|
||||||
|
|
||||||
// I/O (unimplemented)
|
import: markPrimop(io.importFunc, 'import', 1),
|
||||||
import: io.importFunc,
|
scopedImport: markPrimop(io.scopedImport, 'scopedImport', 2),
|
||||||
scopedImport: io.scopedImport,
|
fetchClosure: markPrimop(io.fetchClosure, 'fetchClosure', 1),
|
||||||
fetchClosure: io.fetchClosure,
|
fetchGit: markPrimop(io.fetchGit, 'fetchGit', 1),
|
||||||
fetchGit: io.fetchGit,
|
fetchTarball: markPrimop(io.fetchTarball, 'fetchTarball', 1),
|
||||||
fetchTarball: io.fetchTarball,
|
fetchTree: markPrimop(io.fetchTree, 'fetchTree', 1),
|
||||||
fetchTree: io.fetchTree,
|
fetchurl: markPrimop(io.fetchurl, 'fetchurl', 1),
|
||||||
fetchurl: io.fetchurl,
|
readDir: markPrimop(io.readDir, 'readDir', 1),
|
||||||
readDir: io.readDir,
|
readFile: markPrimop(io.readFile, 'readFile', 1),
|
||||||
readFile: io.readFile,
|
readFileType: markPrimop(io.readFileType, 'readFileType', 1),
|
||||||
readFileType: io.readFileType,
|
pathExists: markPrimop(io.pathExists, 'pathExists', 1),
|
||||||
pathExists: io.pathExists,
|
path: markPrimop(io.path, 'path', 1),
|
||||||
path: io.path,
|
toFile: markPrimop(io.toFile, 'toFile', 2),
|
||||||
toFile: io.toFile,
|
toPath: markPrimop(io.toPath, 'toPath', 1),
|
||||||
toPath: io.toPath,
|
filterSource: markPrimop(io.filterSource, 'filterSource', 2),
|
||||||
filterSource: io.filterSource,
|
findFile: markPrimop(io.findFile, 'findFile', 2),
|
||||||
findFile: io.findFile,
|
getEnv: markPrimop(io.getEnv, 'getEnv', 1),
|
||||||
getEnv: io.getEnv,
|
|
||||||
|
|
||||||
// Conversion (unimplemented)
|
fromJSON: markPrimop(conversion.fromJSON, 'fromJSON', 1),
|
||||||
fromJSON: conversion.fromJSON,
|
fromTOML: markPrimop(conversion.fromTOML, 'fromTOML', 1),
|
||||||
fromTOML: conversion.fromTOML,
|
toJSON: markPrimop(conversion.toJSON, 'toJSON', 1),
|
||||||
toJSON: conversion.toJSON,
|
toXML: markPrimop(conversion.toXML, 'toXML', 1),
|
||||||
toXML: conversion.toXML,
|
toString: markPrimop(conversion.toString, 'toString', 1),
|
||||||
toString: conversion.toString,
|
|
||||||
|
|
||||||
// Miscellaneous (unimplemented)
|
getContext: markPrimop(misc.getContext, 'getContext', 1),
|
||||||
getContext: misc.getContext,
|
hasContext: markPrimop(misc.hasContext, 'hasContext', 1),
|
||||||
hasContext: misc.hasContext,
|
hashFile: markPrimop(misc.hashFile, 'hashFile', 2),
|
||||||
hashFile: misc.hashFile,
|
hashString: markPrimop(misc.hashString, 'hashString', 2),
|
||||||
hashString: misc.hashString,
|
convertHash: markPrimop(misc.convertHash, 'convertHash', 2),
|
||||||
convertHash: misc.convertHash,
|
unsafeDiscardOutputDependency: markPrimop(misc.unsafeDiscardOutputDependency, 'unsafeDiscardOutputDependency', 1),
|
||||||
unsafeDiscardOutputDependency: misc.unsafeDiscardOutputDependency,
|
unsafeDiscardStringContext: markPrimop(misc.unsafeDiscardStringContext, 'unsafeDiscardStringContext', 1),
|
||||||
unsafeDiscardStringContext: misc.unsafeDiscardStringContext,
|
unsafeGetAttrPos: markPrimop(misc.unsafeGetAttrPos, 'unsafeGetAttrPos', 2),
|
||||||
unsafeGetAttrPos: misc.unsafeGetAttrPos,
|
addDrvOutputDependencies: markPrimop(misc.addDrvOutputDependencies, 'addDrvOutputDependencies', 2),
|
||||||
addDrvOutputDependencies: misc.addDrvOutputDependencies,
|
compareVersions: markPrimop(misc.compareVersions, 'compareVersions', 2),
|
||||||
compareVersions: misc.compareVersions,
|
dirOf: markPrimop(misc.dirOf, 'dirOf', 1),
|
||||||
dirOf: misc.dirOf,
|
flakeRefToString: markPrimop(misc.flakeRefToString, 'flakeRefToString', 1),
|
||||||
flakeRefToString: misc.flakeRefToString,
|
functionArgs: markPrimop(misc.functionArgs, 'functionArgs', 1),
|
||||||
functionArgs: misc.functionArgs,
|
genericClosure: markPrimop(misc.genericClosure, 'genericClosure', 1),
|
||||||
genericClosure: misc.genericClosure,
|
getFlake: markPrimop(misc.getFlake, 'getFlake', 1),
|
||||||
getFlake: misc.getFlake,
|
match: markPrimop(misc.match, 'match', 2),
|
||||||
match: misc.match,
|
outputOf: markPrimop(misc.outputOf, 'outputOf', 2),
|
||||||
outputOf: misc.outputOf,
|
parseDrvName: markPrimop(misc.parseDrvName, 'parseDrvName', 1),
|
||||||
parseDrvName: misc.parseDrvName,
|
parseFlakeName: markPrimop(misc.parseFlakeName, 'parseFlakeName', 1),
|
||||||
parseFlakeName: misc.parseFlakeName,
|
placeholder: markPrimop(misc.placeholder, 'placeholder', 1),
|
||||||
placeholder: misc.placeholder,
|
replaceStrings: markPrimop(misc.replaceStrings, 'replaceStrings', 3),
|
||||||
replaceStrings: misc.replaceStrings,
|
split: markPrimop(misc.split, 'split', 2),
|
||||||
split: misc.split,
|
splitVersion: markPrimop(misc.splitVersion, 'splitVersion', 1),
|
||||||
splitVersion: misc.splitVersion,
|
traceVerbose: markPrimop(misc.traceVerbose, 'traceVerbose', 2),
|
||||||
traceVerbose: misc.traceVerbose,
|
tryEval: markPrimop(misc.tryEval, 'tryEval', 1),
|
||||||
tryEval: misc.tryEval,
|
zipAttrsWith: markPrimop(misc.zipAttrsWith, 'zipAttrsWith', 2),
|
||||||
zipAttrsWith: misc.zipAttrsWith,
|
|
||||||
|
|
||||||
// Meta - self-reference and constants
|
builtins: create_thunk(() => builtins),
|
||||||
builtins: create_thunk(() => builtins), // Recursive reference
|
|
||||||
currentSystem: create_thunk(() => {
|
currentSystem: create_thunk(() => {
|
||||||
throw 'Not implemented: currentSystem';
|
throw 'Not implemented: currentSystem';
|
||||||
}),
|
}),
|
||||||
currentTime: create_thunk(() => Date.now()),
|
currentTime: create_thunk(() => Date.now()),
|
||||||
|
|
||||||
// Constants (special keys)
|
|
||||||
false: false,
|
false: false,
|
||||||
true: true,
|
true: true,
|
||||||
null: null,
|
null: null,
|
||||||
|
|
||||||
// Version information
|
|
||||||
langVersion: 6,
|
langVersion: 6,
|
||||||
nixPath: [],
|
nixPath: [],
|
||||||
nixVersion: 'NIX_JS_VERSION',
|
nixVersion: 'NIX_JS_VERSION',
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { create_thunk, force, is_thunk, IS_THUNK } from './thunk';
|
import { create_thunk, force, is_thunk, IS_THUNK } from './thunk';
|
||||||
import { select, select_with_default, validate_params } from './helpers';
|
import { select, select_with_default, validate_params } from './helpers';
|
||||||
import { op } from './operators';
|
import { op } from './operators';
|
||||||
import { builtins } from './builtins';
|
import { builtins, IS_PRIMOP } from './builtins';
|
||||||
|
|
||||||
export type NixRuntime = typeof Nix;
|
export type NixRuntime = typeof Nix;
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ export const Nix = {
|
|||||||
|
|
||||||
op,
|
op,
|
||||||
builtins,
|
builtins,
|
||||||
|
IS_PRIMOP,
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.Nix = Nix;
|
globalThis.Nix = Nix;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
match self {
|
match self {
|
||||||
Ir::Const(Const { val }) => match val {
|
Ir::Const(Const { val }) => match val {
|
||||||
crate::value::Const::Null => "null".to_string(),
|
crate::value::Const::Null => "null".to_string(),
|
||||||
crate::value::Const::Int(val) => format!("{}n", val), // Generate BigInt literal
|
crate::value::Const::Int(val) => format!("{}n", val), // Generate BigInt literal
|
||||||
crate::value::Const::Float(val) => val.to_string(),
|
crate::value::Const::Float(val) => val.to_string(),
|
||||||
crate::value::Const::Bool(val) => val.to_string(),
|
crate::value::Const::Bool(val) => val.to_string(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ impl Default for Context {
|
|||||||
"true",
|
"true",
|
||||||
"false",
|
"false",
|
||||||
"null",
|
"null",
|
||||||
|
|
||||||
"abort",
|
"abort",
|
||||||
"baseNameOf",
|
"baseNameOf",
|
||||||
"break",
|
"break",
|
||||||
@@ -681,10 +680,7 @@ mod test {
|
|||||||
Value::Const(Const::Float(3.5))
|
Value::Const(Const::Float(3.5))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(ctx.eval("(-7) / 3").unwrap(), Value::Const(Const::Int(-2)));
|
||||||
ctx.eval("(-7) / 3").unwrap(),
|
|
||||||
Value::Const(Const::Int(-2))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -700,8 +696,7 @@ mod test {
|
|||||||
|
|
||||||
// Test builtin mul with large numbers
|
// Test builtin mul with large numbers
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ctx.eval("builtins.mul 1000000000 1000000000")
|
ctx.eval("builtins.mul 1000000000 1000000000").unwrap(),
|
||||||
.unwrap(),
|
|
||||||
Value::Const(Const::Int(1000000000000000000i64))
|
Value::Const(Const::Int(1000000000000000000i64))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,14 +23,17 @@ pub fn run(script: &str) -> Result<Value> {
|
|||||||
struct RuntimeContext<'a, 'b> {
|
struct RuntimeContext<'a, 'b> {
|
||||||
scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>,
|
scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>,
|
||||||
is_thunk_symbol: Option<v8::Local<'a, v8::Symbol>>,
|
is_thunk_symbol: Option<v8::Local<'a, v8::Symbol>>,
|
||||||
|
is_primop_symbol: Option<v8::Local<'a, v8::Symbol>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> RuntimeContext<'a, 'b> {
|
impl<'a, 'b> RuntimeContext<'a, 'b> {
|
||||||
fn new(scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>) -> Self {
|
fn new(scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>) -> Self {
|
||||||
let is_thunk_symbol = Self::get_is_thunk_symbol(scope);
|
let is_thunk_symbol = Self::get_is_thunk_symbol(scope);
|
||||||
|
let is_primop_symbol = Self::get_is_primop_symbol(scope);
|
||||||
Self {
|
Self {
|
||||||
scope,
|
scope,
|
||||||
is_thunk_symbol,
|
is_thunk_symbol,
|
||||||
|
is_primop_symbol,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +53,23 @@ impl<'a, 'b> RuntimeContext<'a, 'b> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_is_primop_symbol(
|
||||||
|
scope: &v8::PinnedRef<'a, v8::HandleScope<'b>>,
|
||||||
|
) -> Option<v8::Local<'a, v8::Symbol>> {
|
||||||
|
let global = scope.get_current_context().global(scope);
|
||||||
|
let nix_key = v8::String::new(scope, "Nix")?;
|
||||||
|
let nix_obj = global.get(scope, nix_key.into())?.to_object(scope)?;
|
||||||
|
|
||||||
|
let is_primop_sym_key = v8::String::new(scope, "IS_PRIMOP")?;
|
||||||
|
let is_primop_sym = nix_obj.get(scope, is_primop_sym_key.into())?;
|
||||||
|
|
||||||
|
if is_primop_sym.is_symbol() {
|
||||||
|
is_primop_sym.try_cast().ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_impl(script: &str, isolate: &mut v8::Isolate) -> Result<Value> {
|
fn run_impl(script: &str, isolate: &mut v8::Isolate) -> Result<Value> {
|
||||||
@@ -127,8 +147,11 @@ fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>)
|
|||||||
_ if val.is_number() => {
|
_ if val.is_number() => {
|
||||||
let val = val.to_number(scope).unwrap().value();
|
let val = val.to_number(scope).unwrap().value();
|
||||||
// Heuristic: convert whole numbers to Int (for backward compatibility and JS interop)
|
// Heuristic: convert whole numbers to Int (for backward compatibility and JS interop)
|
||||||
if val.is_finite() && val.fract() == 0.0
|
if val.is_finite()
|
||||||
&& val >= i64::MIN as f64 && val <= i64::MAX as f64 {
|
&& val.fract() == 0.0
|
||||||
|
&& val >= i64::MIN as f64
|
||||||
|
&& val <= i64::MAX as f64
|
||||||
|
{
|
||||||
Value::Const(Const::Int(val as i64))
|
Value::Const(Const::Int(val as i64))
|
||||||
} else {
|
} else {
|
||||||
Value::Const(Const::Float(val))
|
Value::Const(Const::Float(val))
|
||||||
@@ -152,7 +175,15 @@ fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>)
|
|||||||
.collect();
|
.collect();
|
||||||
Value::List(List::new(list))
|
Value::List(List::new(list))
|
||||||
}
|
}
|
||||||
_ if val.is_function() => Value::Func,
|
_ if val.is_function() => {
|
||||||
|
if let Some(name) = primop_app_name(val, ctx) {
|
||||||
|
Value::PrimOpApp(name)
|
||||||
|
} else if let Some(name) = primop_name(val, ctx) {
|
||||||
|
Value::PrimOp(name)
|
||||||
|
} else {
|
||||||
|
Value::Func
|
||||||
|
}
|
||||||
|
}
|
||||||
_ if val.is_object() => {
|
_ if val.is_object() => {
|
||||||
if is_thunk(val, ctx) {
|
if is_thunk(val, ctx) {
|
||||||
return Value::Thunk;
|
return Value::Thunk;
|
||||||
@@ -193,6 +224,58 @@ fn is_thunk<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>)
|
|||||||
matches!(obj.get(scope, is_thunk_sym.into()), Some(v) if v.is_true())
|
matches!(obj.get(scope, is_thunk_sym.into()), Some(v) if v.is_true())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a function is a primop
|
||||||
|
fn primop_name<'a, 'b>(
|
||||||
|
val: v8::Local<'a, v8::Value>,
|
||||||
|
ctx: &RuntimeContext<'a, 'b>,
|
||||||
|
) -> Option<String> {
|
||||||
|
if !val.is_function() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use cached IS_PRIMOP symbol from context
|
||||||
|
let is_primop_sym = ctx.is_primop_symbol?;
|
||||||
|
|
||||||
|
let scope = ctx.scope;
|
||||||
|
let obj = val.to_object(scope).unwrap();
|
||||||
|
|
||||||
|
if let Some(metadata) = obj.get(scope, is_primop_sym.into())
|
||||||
|
&& let Some(metadata_obj) = metadata.to_object(scope)
|
||||||
|
&& let Some(name_key) = v8::String::new(scope, "name")
|
||||||
|
&& let Some(name_val) = metadata_obj.get(scope, name_key.into())
|
||||||
|
{
|
||||||
|
Some(name_val.to_rust_string_lossy(scope))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a primop is partially applied (has applied > 0)
|
||||||
|
fn primop_app_name<'a, 'b>(
|
||||||
|
val: v8::Local<'a, v8::Value>,
|
||||||
|
ctx: &RuntimeContext<'a, 'b>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let name = primop_name(val, ctx)?;
|
||||||
|
|
||||||
|
// Get cached IS_PRIMOP symbol
|
||||||
|
let is_primop_sym = ctx.is_primop_symbol?;
|
||||||
|
|
||||||
|
let scope = ctx.scope;
|
||||||
|
let obj = val.to_object(scope).unwrap();
|
||||||
|
|
||||||
|
if let Some(metadata) = obj.get(scope, is_primop_sym.into())
|
||||||
|
&& let Some(metadata_obj) = metadata.to_object(scope)
|
||||||
|
&& let Some(applied_key) = v8::String::new(scope, "applied")
|
||||||
|
&& let Some(applied_val) = metadata_obj.get(scope, applied_key.into())
|
||||||
|
&& let Some(applied_num) = applied_val.to_number(scope)
|
||||||
|
&& applied_num.value() > 0.0
|
||||||
|
{
|
||||||
|
Some(name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_value_working() {
|
fn to_value_working() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -185,9 +185,9 @@ pub enum Value {
|
|||||||
/// A function (lambda).
|
/// A function (lambda).
|
||||||
Func,
|
Func,
|
||||||
/// A primitive (built-in) operation.
|
/// A primitive (built-in) operation.
|
||||||
PrimOp,
|
PrimOp(String),
|
||||||
/// A partially applied primitive operation.
|
/// A partially applied primitive operation.
|
||||||
PrimOpApp,
|
PrimOpApp(String),
|
||||||
/// A marker for a value that has been seen before during serialization, to break cycles.
|
/// A marker for a value that has been seen before during serialization, to break cycles.
|
||||||
/// This is used to prevent infinite recursion when printing or serializing cyclic data structures.
|
/// This is used to prevent infinite recursion when printing or serializing cyclic data structures.
|
||||||
Repeated,
|
Repeated,
|
||||||
@@ -201,10 +201,10 @@ impl Display for Value {
|
|||||||
String(x) => write!(f, r#""{x}""#),
|
String(x) => write!(f, r#""{x}""#),
|
||||||
AttrSet(x) => write!(f, "{x}"),
|
AttrSet(x) => write!(f, "{x}"),
|
||||||
List(x) => write!(f, "{x}"),
|
List(x) => write!(f, "{x}"),
|
||||||
Thunk => write!(f, "<CODE>"),
|
Thunk => write!(f, "«code»"),
|
||||||
Func => write!(f, "<LAMBDA>"),
|
Func => write!(f, "«lambda»"),
|
||||||
PrimOp => write!(f, "<PRIMOP>"),
|
PrimOp(name) => write!(f, "«primop {name}»"),
|
||||||
PrimOpApp => write!(f, "<PRIMOP-APP>"),
|
PrimOpApp(name) => write!(f, "«partially applied primop {name}»"),
|
||||||
Repeated => write!(f, "<REPEATED>"),
|
Repeated => write!(f, "<REPEATED>"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user