Add index-of string primitive: spec, Python, JS, rebootstrap

(index-of s needle from?) returns first index of needle in s, or -1.
Optional start offset. Specced in primitives.sx, implemented in both
hand-written primitives.py and bootstrapper templates, rebootstrapped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 09:48:44 +00:00
parent e6cada972e
commit e112bffe5c
7 changed files with 16 additions and 141 deletions

View File

@@ -283,6 +283,7 @@
PRIMITIVES["split"] = function(s, sep) { return String(s).split(sep || " "); };
PRIMITIVES["join"] = function(sep, coll) { return coll.join(sep); };
PRIMITIVES["replace"] = function(s, old, nw) { return s.split(old).join(nw); };
PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); };
PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; };
PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; };
PRIMITIVES["slice"] = function(c, a, b) { return b !== undefined ? c.slice(a, b) : c.slice(a); };
@@ -3316,87 +3317,6 @@ callExpr.push(dictGet(kwargs, k)); } }
if (typeof aser === "function") PRIMITIVES["aser"] = aser;
if (typeof renderToDom === "function") PRIMITIVES["render-to-dom"] = renderToDom;
// =========================================================================
// Extension: Delimited continuations (shift/reset)
// =========================================================================
function Continuation(fn) { this.fn = fn; }
Continuation.prototype._continuation = true;
Continuation.prototype.call = function(value) { return this.fn(value !== undefined ? value : NIL); };
function ShiftSignal(kName, body, env) {
this.kName = kName;
this.body = body;
this.env = env;
}
PRIMITIVES["continuation?"] = function(x) { return x != null && x._continuation === true; };
var _resetResume = [];
function sfReset(args, env) {
var body = args[0];
try {
return trampoline(evalExpr(body, env));
} catch (e) {
if (e instanceof ShiftSignal) {
var sig = e;
var cont = new Continuation(function(value) {
if (value === undefined) value = NIL;
_resetResume.push(value);
try {
return trampoline(evalExpr(body, env));
} finally {
_resetResume.pop();
}
});
var sigEnv = merge(sig.env);
sigEnv[sig.kName] = cont;
return trampoline(evalExpr(sig.body, sigEnv));
}
throw e;
}
}
function sfShift(args, env) {
if (_resetResume.length > 0) {
return _resetResume[_resetResume.length - 1];
}
var kName = symbolName(args[0]);
var body = args[1];
throw new ShiftSignal(kName, body, env);
}
// Wrap evalList to intercept reset/shift
var _baseEvalList = evalList;
evalList = function(expr, env) {
var head = expr[0];
if (isSym(head)) {
var name = head.name;
if (name === "reset") return sfReset(expr.slice(1), env);
if (name === "shift") return sfShift(expr.slice(1), env);
}
return _baseEvalList(expr, env);
};
// Wrap aserSpecial to handle reset/shift in SX wire mode
if (typeof aserSpecial === "function") {
var _baseAserSpecial = aserSpecial;
aserSpecial = function(name, expr, env) {
if (name === "reset") return sfReset(expr.slice(1), env);
if (name === "shift") return sfShift(expr.slice(1), env);
return _baseAserSpecial(name, expr, env);
};
}
// Wrap typeOf to recognize continuations
var _baseTypeOf = typeOf;
typeOf = function(x) {
if (x != null && x._continuation) return "continuation";
return _baseTypeOf(x);
};
// Parser — compiled from parser.sx (see PLATFORM_PARSER_JS for ident char classes)
var parse = sxParse;
@@ -3488,4 +3408,4 @@ callExpr.push(dictGet(kwargs, k)); } }
if (typeof module !== "undefined" && module.exports) module.exports = Sx;
else global.Sx = Sx;
})(typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : this);
})(typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : this);