Add strict typing mode + 139 new tests: 749/749 passing

Strict mode (spec/eval.sx):
- *strict* flag, set-strict!, set-prim-param-types!
- value-matches-type? checks values against declared types
- strict-check-args validates primitive call args at runtime
- Injected into eval-call before apply — zero cost when off
- Supports positional params, rest-type, nullable ("string?")

New test files:
- test-strict.sx (25): value-matches-type?, toggle, 12 type error cases
- test-errors.sx (74): undefined symbols, arity, permissive coercion,
  strict type mismatches, nil/empty edge cases, number edge cases,
  string edge cases, recursion patterns
- test-advanced.sx (39): nested special forms, higher-order patterns,
  define patterns, quasiquote advanced, thread-first, letrec, case/cond

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 12:12:48 +00:00
parent c67adaceaf
commit 8f146cc810
5 changed files with 1027 additions and 3 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-15T12:00:19Z";
var SX_VERSION = "2026-03-15T12:12:33Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -815,6 +815,53 @@
})(); };
PRIMITIVES["trampoline"] = trampoline;
// *strict*
var _strict_ = false;
PRIMITIVES["*strict*"] = _strict_;
// set-strict!
var setStrict_b = function(val) { return (_strict_ = val); };
PRIMITIVES["set-strict!"] = setStrict_b;
// *prim-param-types*
var _primParamTypes_ = NIL;
PRIMITIVES["*prim-param-types*"] = _primParamTypes_;
// set-prim-param-types!
var setPrimParamTypes_b = function(types) { return (_primParamTypes_ = types); };
PRIMITIVES["set-prim-param-types!"] = setPrimParamTypes_b;
// value-matches-type?
var valueMatchesType_p = function(val, expectedType) { return (isSxTruthy((expectedType == "any")) ? true : (isSxTruthy((expectedType == "number")) ? isNumber(val) : (isSxTruthy((expectedType == "string")) ? isString(val) : (isSxTruthy((expectedType == "boolean")) ? boolean_p(val) : (isSxTruthy((expectedType == "nil")) ? isNil(val) : (isSxTruthy((expectedType == "list")) ? isList(val) : (isSxTruthy((expectedType == "dict")) ? isDict(val) : (isSxTruthy((expectedType == "lambda")) ? isLambda(val) : (isSxTruthy((expectedType == "symbol")) ? (typeOf(val) == "symbol") : (isSxTruthy((expectedType == "keyword")) ? (typeOf(val) == "keyword") : (isSxTruthy((isSxTruthy(isString(expectedType)) && endsWith(expectedType, "?"))) ? sxOr(isNil(val), valueMatchesType_p(val, slice(expectedType, 0, (stringLength(expectedType) - 1)))) : true))))))))))); };
PRIMITIVES["value-matches-type?"] = valueMatchesType_p;
// strict-check-args
var strictCheckArgs = function(name, args) { return (isSxTruthy((isSxTruthy(_strict_) && _primParamTypes_)) ? (function() {
var spec = get(_primParamTypes_, name);
return (isSxTruthy(spec) ? (function() {
var positional = get(spec, "positional");
var restType = get(spec, "rest-type");
if (isSxTruthy(positional)) {
{ var _c = mapIndexed(function(i, p) { return [i, p]; }, positional); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; (function() {
var idx = first(pair);
var param = nth(pair, 1);
var pName = first(param);
var pType = nth(param, 1);
return (isSxTruthy((idx < len(args))) ? (function() {
var val = nth(args, idx);
return (isSxTruthy(!isSxTruthy(valueMatchesType_p(val, pType))) ? error((String("Type error: ") + String(name) + String(" expected ") + String(pType) + String(" for param ") + String(pName) + String(", got ") + String(typeOf(val)) + String(" (") + String((String(val))) + String(")"))) : NIL);
})() : NIL);
})(); } }
}
return (isSxTruthy((isSxTruthy(restType) && (len(args) > len(sxOr(positional, []))))) ? forEach(function(pair) { return (function() {
var idx = first(pair);
var val = nth(pair, 1);
return (isSxTruthy(!isSxTruthy(valueMatchesType_p(val, restType))) ? error((String("Type error: ") + String(name) + String(" expected ") + String(restType) + String(" for rest arg ") + String(idx) + String(", got ") + String(typeOf(val)) + String(" (") + String((String(val))) + String(")"))) : NIL);
})(); }, mapIndexed(function(i, v) { return [i, v]; }, slice(args, len(sxOr(positional, []))))) : NIL);
})() : NIL);
})() : NIL); };
PRIMITIVES["strict-check-args"] = strictCheckArgs;
// eval-expr
var evalExpr = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() {
var name = symbolName(expr);
@@ -840,7 +887,7 @@ PRIMITIVES["eval-list"] = evalList;
var evalCall = function(head, args, env) { return (function() {
var f = trampoline(evalExpr(head, env));
var evaluatedArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, args);
return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? apply(f, evaluatedArgs) : (isSxTruthy(isLambda(f)) ? callLambda(f, evaluatedArgs, env) : (isSxTruthy(isComponent(f)) ? callComponent(f, args, env) : (isSxTruthy(isIsland(f)) ? callComponent(f, args, env) : error((String("Not callable: ") + String(inspect(f))))))));
return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? ((isSxTruthy((isSxTruthy(_strict_) && (typeOf(head) == "symbol"))) ? strictCheckArgs(symbolName(head), evaluatedArgs) : NIL), apply(f, evaluatedArgs)) : (isSxTruthy(isLambda(f)) ? callLambda(f, evaluatedArgs, env) : (isSxTruthy(isComponent(f)) ? callComponent(f, args, env) : (isSxTruthy(isIsland(f)) ? callComponent(f, args, env) : error((String("Not callable: ") + String(inspect(f))))))));
})(); };
PRIMITIVES["eval-call"] = evalCall;