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::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let runtime_ts_dir = Path::new("runtime-ts");
|
||||
@@ -17,7 +17,11 @@ fn main() {
|
||||
|
||||
if !runtime_ts_dir.join("node_modules").exists() {
|
||||
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)
|
||||
.arg("install")
|
||||
.current_dir(runtime_ts_dir)
|
||||
@@ -30,7 +34,11 @@ fn main() {
|
||||
}
|
||||
|
||||
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)
|
||||
.arg("run")
|
||||
.arg("typecheck")
|
||||
|
||||
@@ -5,6 +5,98 @@
|
||||
|
||||
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 * as arithmetic from './arithmetic';
|
||||
import * as math from './math';
|
||||
@@ -24,144 +116,133 @@ import * as misc from './misc';
|
||||
* All functions are curried for Nix semantics:
|
||||
* - Single argument functions: (a) => result
|
||||
* - Multi-argument functions: (a) => (b) => result
|
||||
*
|
||||
* All primop functions are marked with IS_PRIMOP symbol for runtime introspection
|
||||
*/
|
||||
export const builtins: any = {
|
||||
// Arithmetic (curried binary functions)
|
||||
add: arithmetic.add,
|
||||
sub: arithmetic.sub,
|
||||
mul: arithmetic.mul,
|
||||
div: arithmetic.div,
|
||||
bitAnd: arithmetic.bitAnd,
|
||||
bitOr: arithmetic.bitOr,
|
||||
bitXor: arithmetic.bitXor,
|
||||
lessThan: arithmetic.lessThan,
|
||||
add: markPrimop(arithmetic.add, 'add', 2),
|
||||
sub: markPrimop(arithmetic.sub, 'sub', 2),
|
||||
mul: markPrimop(arithmetic.mul, 'mul', 2),
|
||||
div: markPrimop(arithmetic.div, 'div', 2),
|
||||
bitAnd: markPrimop(arithmetic.bitAnd, 'bitAnd', 2),
|
||||
bitOr: markPrimop(arithmetic.bitOr, 'bitOr', 2),
|
||||
bitXor: markPrimop(arithmetic.bitXor, 'bitXor', 2),
|
||||
lessThan: markPrimop(arithmetic.lessThan, 'lessThan', 2),
|
||||
|
||||
// Math
|
||||
ceil: math.ceil,
|
||||
floor: math.floor,
|
||||
ceil: markPrimop(math.ceil, 'ceil', 1),
|
||||
floor: markPrimop(math.floor, 'floor', 1),
|
||||
|
||||
// Type checking
|
||||
isAttrs: typeCheck.isAttrs,
|
||||
isBool: typeCheck.isBool,
|
||||
isFloat: typeCheck.isFloat,
|
||||
isFunction: typeCheck.isFunction,
|
||||
isInt: typeCheck.isInt,
|
||||
isList: typeCheck.isList,
|
||||
isNull: typeCheck.isNull,
|
||||
isPath: typeCheck.isPath,
|
||||
isString: typeCheck.isString,
|
||||
typeOf: typeCheck.typeOf,
|
||||
isAttrs: markPrimop(typeCheck.isAttrs, 'isAttrs', 1),
|
||||
isBool: markPrimop(typeCheck.isBool, 'isBool', 1),
|
||||
isFloat: markPrimop(typeCheck.isFloat, 'isFloat', 1),
|
||||
isFunction: markPrimop(typeCheck.isFunction, 'isFunction', 1),
|
||||
isInt: markPrimop(typeCheck.isInt, 'isInt', 1),
|
||||
isList: markPrimop(typeCheck.isList, 'isList', 1),
|
||||
isNull: markPrimop(typeCheck.isNull, 'isNull', 1),
|
||||
isPath: markPrimop(typeCheck.isPath, 'isPath', 1),
|
||||
isString: markPrimop(typeCheck.isString, 'isString', 1),
|
||||
typeOf: markPrimop(typeCheck.typeOf, 'typeOf', 1),
|
||||
|
||||
// List operations
|
||||
map: list.map,
|
||||
filter: list.filter,
|
||||
length: list.length,
|
||||
head: list.head,
|
||||
tail: list.tail,
|
||||
elem: list.elem,
|
||||
elemAt: list.elemAt,
|
||||
concatLists: list.concatLists,
|
||||
concatMap: list.concatMap,
|
||||
'foldl\'': list.foldlPrime,
|
||||
sort: list.sort,
|
||||
partition: list.partition,
|
||||
genList: list.genList,
|
||||
all: list.all,
|
||||
any: list.any,
|
||||
map: markPrimop(list.map, 'map', 2),
|
||||
filter: markPrimop(list.filter, 'filter', 2),
|
||||
length: markPrimop(list.length, 'length', 1),
|
||||
head: markPrimop(list.head, 'head', 1),
|
||||
tail: markPrimop(list.tail, 'tail', 1),
|
||||
elem: markPrimop(list.elem, 'elem', 2),
|
||||
elemAt: markPrimop(list.elemAt, 'elemAt', 2),
|
||||
concatLists: markPrimop(list.concatLists, 'concatLists', 1),
|
||||
concatMap: markPrimop(list.concatMap, 'concatMap', 2),
|
||||
'foldl\'': markPrimop(list.foldlPrime, 'foldl\'', 3),
|
||||
sort: markPrimop(list.sort, 'sort', 2),
|
||||
partition: markPrimop(list.partition, 'partition', 2),
|
||||
genList: markPrimop(list.genList, 'genList', 2),
|
||||
all: markPrimop(list.all, 'all', 2),
|
||||
any: markPrimop(list.any, 'any', 2),
|
||||
|
||||
// Attribute set operations
|
||||
attrNames: attrs.attrNames,
|
||||
attrValues: attrs.attrValues,
|
||||
getAttr: attrs.getAttr,
|
||||
hasAttr: attrs.hasAttr,
|
||||
mapAttrs: attrs.mapAttrs,
|
||||
listToAttrs: attrs.listToAttrs,
|
||||
intersectAttrs: attrs.intersectAttrs,
|
||||
catAttrs: attrs.catAttrs,
|
||||
groupBy: attrs.groupBy,
|
||||
attrNames: markPrimop(attrs.attrNames, 'attrNames', 1),
|
||||
attrValues: markPrimop(attrs.attrValues, 'attrValues', 1),
|
||||
getAttr: markPrimop(attrs.getAttr, 'getAttr', 2),
|
||||
hasAttr: markPrimop(attrs.hasAttr, 'hasAttr', 2),
|
||||
mapAttrs: markPrimop(attrs.mapAttrs, 'mapAttrs', 2),
|
||||
listToAttrs: markPrimop(attrs.listToAttrs, 'listToAttrs', 1),
|
||||
intersectAttrs: markPrimop(attrs.intersectAttrs, 'intersectAttrs', 2),
|
||||
catAttrs: markPrimop(attrs.catAttrs, 'catAttrs', 2),
|
||||
groupBy: markPrimop(attrs.groupBy, 'groupBy', 2),
|
||||
|
||||
// String operations
|
||||
stringLength: string.stringLength,
|
||||
substring: string.substring,
|
||||
concatStringsSep: string.concatStringsSep,
|
||||
baseNameOf: string.baseNameOf,
|
||||
stringLength: markPrimop(string.stringLength, 'stringLength', 1),
|
||||
substring: markPrimop(string.substring, 'substring', 3),
|
||||
concatStringsSep: markPrimop(string.concatStringsSep, 'concatStringsSep', 2),
|
||||
baseNameOf: markPrimop(string.baseNameOf, 'baseNameOf', 1),
|
||||
|
||||
// Functional
|
||||
seq: functional.seq,
|
||||
deepSeq: functional.deepSeq,
|
||||
abort: functional.abort,
|
||||
throw: functional.throwFunc,
|
||||
trace: functional.trace,
|
||||
warn: functional.warn,
|
||||
break: functional.breakFunc,
|
||||
seq: markPrimop(functional.seq, 'seq', 2),
|
||||
deepSeq: markPrimop(functional.deepSeq, 'deepSeq', 2),
|
||||
abort: markPrimop(functional.abort, 'abort', 1),
|
||||
throw: markPrimop(functional.throwFunc, 'throw', 1),
|
||||
trace: markPrimop(functional.trace, 'trace', 2),
|
||||
warn: markPrimop(functional.warn, 'warn', 2),
|
||||
break: markPrimop(functional.breakFunc, 'break', 1),
|
||||
|
||||
// I/O (unimplemented)
|
||||
import: io.importFunc,
|
||||
scopedImport: io.scopedImport,
|
||||
fetchClosure: io.fetchClosure,
|
||||
fetchGit: io.fetchGit,
|
||||
fetchTarball: io.fetchTarball,
|
||||
fetchTree: io.fetchTree,
|
||||
fetchurl: io.fetchurl,
|
||||
readDir: io.readDir,
|
||||
readFile: io.readFile,
|
||||
readFileType: io.readFileType,
|
||||
pathExists: io.pathExists,
|
||||
path: io.path,
|
||||
toFile: io.toFile,
|
||||
toPath: io.toPath,
|
||||
filterSource: io.filterSource,
|
||||
findFile: io.findFile,
|
||||
getEnv: io.getEnv,
|
||||
import: markPrimop(io.importFunc, 'import', 1),
|
||||
scopedImport: markPrimop(io.scopedImport, 'scopedImport', 2),
|
||||
fetchClosure: markPrimop(io.fetchClosure, 'fetchClosure', 1),
|
||||
fetchGit: markPrimop(io.fetchGit, 'fetchGit', 1),
|
||||
fetchTarball: markPrimop(io.fetchTarball, 'fetchTarball', 1),
|
||||
fetchTree: markPrimop(io.fetchTree, 'fetchTree', 1),
|
||||
fetchurl: markPrimop(io.fetchurl, 'fetchurl', 1),
|
||||
readDir: markPrimop(io.readDir, 'readDir', 1),
|
||||
readFile: markPrimop(io.readFile, 'readFile', 1),
|
||||
readFileType: markPrimop(io.readFileType, 'readFileType', 1),
|
||||
pathExists: markPrimop(io.pathExists, 'pathExists', 1),
|
||||
path: markPrimop(io.path, 'path', 1),
|
||||
toFile: markPrimop(io.toFile, 'toFile', 2),
|
||||
toPath: markPrimop(io.toPath, 'toPath', 1),
|
||||
filterSource: markPrimop(io.filterSource, 'filterSource', 2),
|
||||
findFile: markPrimop(io.findFile, 'findFile', 2),
|
||||
getEnv: markPrimop(io.getEnv, 'getEnv', 1),
|
||||
|
||||
// Conversion (unimplemented)
|
||||
fromJSON: conversion.fromJSON,
|
||||
fromTOML: conversion.fromTOML,
|
||||
toJSON: conversion.toJSON,
|
||||
toXML: conversion.toXML,
|
||||
toString: conversion.toString,
|
||||
fromJSON: markPrimop(conversion.fromJSON, 'fromJSON', 1),
|
||||
fromTOML: markPrimop(conversion.fromTOML, 'fromTOML', 1),
|
||||
toJSON: markPrimop(conversion.toJSON, 'toJSON', 1),
|
||||
toXML: markPrimop(conversion.toXML, 'toXML', 1),
|
||||
toString: markPrimop(conversion.toString, 'toString', 1),
|
||||
|
||||
// Miscellaneous (unimplemented)
|
||||
getContext: misc.getContext,
|
||||
hasContext: misc.hasContext,
|
||||
hashFile: misc.hashFile,
|
||||
hashString: misc.hashString,
|
||||
convertHash: misc.convertHash,
|
||||
unsafeDiscardOutputDependency: misc.unsafeDiscardOutputDependency,
|
||||
unsafeDiscardStringContext: misc.unsafeDiscardStringContext,
|
||||
unsafeGetAttrPos: misc.unsafeGetAttrPos,
|
||||
addDrvOutputDependencies: misc.addDrvOutputDependencies,
|
||||
compareVersions: misc.compareVersions,
|
||||
dirOf: misc.dirOf,
|
||||
flakeRefToString: misc.flakeRefToString,
|
||||
functionArgs: misc.functionArgs,
|
||||
genericClosure: misc.genericClosure,
|
||||
getFlake: misc.getFlake,
|
||||
match: misc.match,
|
||||
outputOf: misc.outputOf,
|
||||
parseDrvName: misc.parseDrvName,
|
||||
parseFlakeName: misc.parseFlakeName,
|
||||
placeholder: misc.placeholder,
|
||||
replaceStrings: misc.replaceStrings,
|
||||
split: misc.split,
|
||||
splitVersion: misc.splitVersion,
|
||||
traceVerbose: misc.traceVerbose,
|
||||
tryEval: misc.tryEval,
|
||||
zipAttrsWith: misc.zipAttrsWith,
|
||||
getContext: markPrimop(misc.getContext, 'getContext', 1),
|
||||
hasContext: markPrimop(misc.hasContext, 'hasContext', 1),
|
||||
hashFile: markPrimop(misc.hashFile, 'hashFile', 2),
|
||||
hashString: markPrimop(misc.hashString, 'hashString', 2),
|
||||
convertHash: markPrimop(misc.convertHash, 'convertHash', 2),
|
||||
unsafeDiscardOutputDependency: markPrimop(misc.unsafeDiscardOutputDependency, 'unsafeDiscardOutputDependency', 1),
|
||||
unsafeDiscardStringContext: markPrimop(misc.unsafeDiscardStringContext, 'unsafeDiscardStringContext', 1),
|
||||
unsafeGetAttrPos: markPrimop(misc.unsafeGetAttrPos, 'unsafeGetAttrPos', 2),
|
||||
addDrvOutputDependencies: markPrimop(misc.addDrvOutputDependencies, 'addDrvOutputDependencies', 2),
|
||||
compareVersions: markPrimop(misc.compareVersions, 'compareVersions', 2),
|
||||
dirOf: markPrimop(misc.dirOf, 'dirOf', 1),
|
||||
flakeRefToString: markPrimop(misc.flakeRefToString, 'flakeRefToString', 1),
|
||||
functionArgs: markPrimop(misc.functionArgs, 'functionArgs', 1),
|
||||
genericClosure: markPrimop(misc.genericClosure, 'genericClosure', 1),
|
||||
getFlake: markPrimop(misc.getFlake, 'getFlake', 1),
|
||||
match: markPrimop(misc.match, 'match', 2),
|
||||
outputOf: markPrimop(misc.outputOf, 'outputOf', 2),
|
||||
parseDrvName: markPrimop(misc.parseDrvName, 'parseDrvName', 1),
|
||||
parseFlakeName: markPrimop(misc.parseFlakeName, 'parseFlakeName', 1),
|
||||
placeholder: markPrimop(misc.placeholder, 'placeholder', 1),
|
||||
replaceStrings: markPrimop(misc.replaceStrings, 'replaceStrings', 3),
|
||||
split: markPrimop(misc.split, 'split', 2),
|
||||
splitVersion: markPrimop(misc.splitVersion, 'splitVersion', 1),
|
||||
traceVerbose: markPrimop(misc.traceVerbose, 'traceVerbose', 2),
|
||||
tryEval: markPrimop(misc.tryEval, 'tryEval', 1),
|
||||
zipAttrsWith: markPrimop(misc.zipAttrsWith, 'zipAttrsWith', 2),
|
||||
|
||||
// Meta - self-reference and constants
|
||||
builtins: create_thunk(() => builtins), // Recursive reference
|
||||
builtins: create_thunk(() => builtins),
|
||||
currentSystem: create_thunk(() => {
|
||||
throw 'Not implemented: currentSystem';
|
||||
}),
|
||||
currentTime: create_thunk(() => Date.now()),
|
||||
|
||||
// Constants (special keys)
|
||||
false: false,
|
||||
true: true,
|
||||
null: null,
|
||||
|
||||
// Version information
|
||||
langVersion: 6,
|
||||
nixPath: [],
|
||||
nixVersion: 'NIX_JS_VERSION',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { create_thunk, force, is_thunk, IS_THUNK } from './thunk';
|
||||
import { select, select_with_default, validate_params } from './helpers';
|
||||
import { op } from './operators';
|
||||
import { builtins } from './builtins';
|
||||
import { builtins, IS_PRIMOP } from './builtins';
|
||||
|
||||
export type NixRuntime = typeof Nix;
|
||||
|
||||
@@ -26,6 +26,7 @@ export const Nix = {
|
||||
|
||||
op,
|
||||
builtins,
|
||||
IS_PRIMOP,
|
||||
};
|
||||
|
||||
globalThis.Nix = Nix;
|
||||
|
||||
@@ -44,7 +44,6 @@ impl Default for Context {
|
||||
"true",
|
||||
"false",
|
||||
"null",
|
||||
|
||||
"abort",
|
||||
"baseNameOf",
|
||||
"break",
|
||||
@@ -681,10 +680,7 @@ mod test {
|
||||
Value::Const(Const::Float(3.5))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ctx.eval("(-7) / 3").unwrap(),
|
||||
Value::Const(Const::Int(-2))
|
||||
);
|
||||
assert_eq!(ctx.eval("(-7) / 3").unwrap(), Value::Const(Const::Int(-2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -700,8 +696,7 @@ mod test {
|
||||
|
||||
// Test builtin mul with large numbers
|
||||
assert_eq!(
|
||||
ctx.eval("builtins.mul 1000000000 1000000000")
|
||||
.unwrap(),
|
||||
ctx.eval("builtins.mul 1000000000 1000000000").unwrap(),
|
||||
Value::Const(Const::Int(1000000000000000000i64))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,14 +23,17 @@ pub fn run(script: &str) -> Result<Value> {
|
||||
struct RuntimeContext<'a, 'b> {
|
||||
scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>,
|
||||
is_thunk_symbol: Option<v8::Local<'a, v8::Symbol>>,
|
||||
is_primop_symbol: Option<v8::Local<'a, v8::Symbol>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> RuntimeContext<'a, 'b> {
|
||||
fn new(scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>) -> Self {
|
||||
let is_thunk_symbol = Self::get_is_thunk_symbol(scope);
|
||||
let is_primop_symbol = Self::get_is_primop_symbol(scope);
|
||||
Self {
|
||||
scope,
|
||||
is_thunk_symbol,
|
||||
is_primop_symbol,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +53,23 @@ impl<'a, 'b> RuntimeContext<'a, 'b> {
|
||||
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> {
|
||||
@@ -127,8 +147,11 @@ fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>)
|
||||
_ if val.is_number() => {
|
||||
let val = val.to_number(scope).unwrap().value();
|
||||
// Heuristic: convert whole numbers to Int (for backward compatibility and JS interop)
|
||||
if val.is_finite() && val.fract() == 0.0
|
||||
&& val >= i64::MIN as f64 && val <= i64::MAX as f64 {
|
||||
if val.is_finite()
|
||||
&& val.fract() == 0.0
|
||||
&& val >= i64::MIN as f64
|
||||
&& val <= i64::MAX as f64
|
||||
{
|
||||
Value::Const(Const::Int(val as i64))
|
||||
} else {
|
||||
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();
|
||||
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 is_thunk(val, ctx) {
|
||||
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())
|
||||
}
|
||||
|
||||
/// 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]
|
||||
fn to_value_working() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -185,9 +185,9 @@ pub enum Value {
|
||||
/// A function (lambda).
|
||||
Func,
|
||||
/// A primitive (built-in) operation.
|
||||
PrimOp,
|
||||
PrimOp(String),
|
||||
/// A partially applied primitive operation.
|
||||
PrimOpApp,
|
||||
PrimOpApp(String),
|
||||
/// 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.
|
||||
Repeated,
|
||||
@@ -201,10 +201,10 @@ impl Display for Value {
|
||||
String(x) => write!(f, r#""{x}""#),
|
||||
AttrSet(x) => write!(f, "{x}"),
|
||||
List(x) => write!(f, "{x}"),
|
||||
Thunk => write!(f, "<CODE>"),
|
||||
Func => write!(f, "<LAMBDA>"),
|
||||
PrimOp => write!(f, "<PRIMOP>"),
|
||||
PrimOpApp => write!(f, "<PRIMOP-APP>"),
|
||||
Thunk => write!(f, "«code»"),
|
||||
Func => write!(f, "«lambda»"),
|
||||
PrimOp(name) => write!(f, "«primop {name}»"),
|
||||
PrimOpApp(name) => write!(f, "«partially applied primop {name}»"),
|
||||
Repeated => write!(f, "<REPEATED>"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user