spec: multiple values — values/call-with-values/let-values/define-values

25 tests pass on both JS and OCaml hosts. Uses dict marker
{:_values true :_list [...]} for 0/2+ values; 1 value passes
through directly. step-sf-define extended to desugar shorthand
(define (name params) body) forms on both hosts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 08:03:17 +00:00
parent 8328e96ff6
commit 43cc1d9003
5 changed files with 498 additions and 88 deletions

View File

@@ -31,7 +31,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-04-26T19:02:22Z";
var SX_VERSION = "2026-05-01T07:58:35Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -780,6 +780,7 @@
if (isLambda(f)) return trampoline(callLambda(f, args, lambdaClosure(f)));
return f.apply(null, args);
};
PRIMITIVES["apply"] = apply;
// Additional primitive aliases used by adapter/engine transpiled code
var split = PRIMITIVES["split"];
@@ -2007,6 +2008,58 @@ PRIMITIVES["qq-expand"] = qqExpand;
})(); };
PRIMITIVES["sf-letrec"] = sfLetrec;
// call-with-values
var callWithValues = function(producer, consumer) { return (function() {
var result = apply(producer, []);
return (isSxTruthy((isSxTruthy(isDict(result)) && get(result, "_values", false))) ? apply(consumer, get(result, "_list")) : apply(consumer, [result]));
})(); };
PRIMITIVES["call-with-values"] = callWithValues;
// sf-let-values
var sfLetValues = function(args, env) { return (function() {
var clauses = first(args);
var body = rest(args);
var local = envExtend(env);
{ var _c = clauses; for (var _i = 0; _i < _c.length; _i++) { var clause = _c[_i]; (function() {
var names = first(clause);
var valExpr = nth(clause, 1);
return (function() {
var result = trampoline(evalExpr(valExpr, local));
return (function() {
var vs = (isSxTruthy((isSxTruthy(isDict(result)) && get(result, "_values", false))) ? get(result, "_list") : [result]);
return forEachIndexed(function(idx, name) { return envBind(local, symbolName(name), nth(vs, idx)); }, names);
})();
})();
})(); } }
return (function() {
var lastVal = NIL;
{ var _c = body; for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; lastVal = trampoline(evalExpr(e, local)); } }
return lastVal;
})();
})(); };
PRIMITIVES["sf-let-values"] = sfLetValues;
// sf-define-values
var sfDefineValues = function(args, env) { return (function() {
var names = first(args);
var valExpr = nth(args, 1);
return (function() {
var result = trampoline(evalExpr(valExpr, env));
return (function() {
var vs = (isSxTruthy((isSxTruthy(isDict(result)) && get(result, "_values", false))) ? get(result, "_list") : [result]);
forEachIndexed(function(idx, name) { return envBind(env, symbolName(name), nth(vs, idx)); }, names);
return NIL;
})();
})();
})(); };
PRIMITIVES["sf-define-values"] = sfDefineValues;
// (register-special-form! ...)
registerSpecialForm("define-values", sfDefineValues);
// (register-special-form! ...)
registerSpecialForm("let-values", sfLetValues);
// step-sf-letrec
var stepSfLetrec = function(args, env, kont) { return (function() {
var thk = sfLetrec(args, env);
@@ -2200,6 +2253,10 @@ PRIMITIVES["step-eval-list"] = stepEvalList;
})(); };
PRIMITIVES["sf-define-type"] = sfDefineType;
// values
var values = function() { var vs = Array.prototype.slice.call(arguments, 0); return (isSxTruthy(sxEq(len(vs), 1)) ? first(vs) : {"_values": true, "_list": vs}); };
PRIMITIVES["values"] = values;
// (register-special-form! ...)
registerSpecialForm("define-type", sfDefineType);
@@ -2692,11 +2749,19 @@ PRIMITIVES["step-sf-let"] = stepSfLet;
// step-sf-define
var stepSfDefine = function(args, env, kont) { return (function() {
var nameSym = first(args);
var hasEffects = (isSxTruthy((len(args) >= 4)) && isSxTruthy(sxEq(typeOf(nth(args, 1)), "keyword")) && sxEq(keywordName(nth(args, 1)), "effects"));
var valIdx = (isSxTruthy((isSxTruthy((len(args) >= 4)) && isSxTruthy(sxEq(typeOf(nth(args, 1)), "keyword")) && sxEq(keywordName(nth(args, 1)), "effects"))) ? 3 : 1);
var effectList = (isSxTruthy((isSxTruthy((len(args) >= 4)) && isSxTruthy(sxEq(typeOf(nth(args, 1)), "keyword")) && sxEq(keywordName(nth(args, 1)), "effects"))) ? nth(args, 2) : NIL);
return makeCekState(nth(args, valIdx), env, kontPush(makeDefineFrame(symbolName(nameSym), env, hasEffects, effectList), kont));
var resolvedArgs = (isSxTruthy(sxEq(typeOf(first(args)), "list")) ? (function() {
var fnName = first(first(args));
var params = rest(first(args));
var bodyParts = rest(args);
return [fnName, concat([makeSymbol("fn")], [params], bodyParts)];
})() : args);
return (function() {
var nameSym = first(resolvedArgs);
var hasEffects = (isSxTruthy((len(resolvedArgs) >= 4)) && isSxTruthy(sxEq(typeOf(nth(resolvedArgs, 1)), "keyword")) && sxEq(keywordName(nth(resolvedArgs, 1)), "effects"));
var valIdx = (isSxTruthy((isSxTruthy((len(resolvedArgs) >= 4)) && isSxTruthy(sxEq(typeOf(nth(resolvedArgs, 1)), "keyword")) && sxEq(keywordName(nth(resolvedArgs, 1)), "effects"))) ? 3 : 1);
var effectList = (isSxTruthy((isSxTruthy((len(resolvedArgs) >= 4)) && isSxTruthy(sxEq(typeOf(nth(resolvedArgs, 1)), "keyword")) && sxEq(keywordName(nth(resolvedArgs, 1)), "effects"))) ? nth(resolvedArgs, 2) : NIL);
return makeCekState(nth(resolvedArgs, valIdx), env, kontPush(makeDefineFrame(symbolName(nameSym), env, hasEffects, effectList), kont));
})();
})(); };
PRIMITIVES["step-sf-define"] = stepSfDefine;