spec: character type (char? char->integer #\a literals + predicates)
- Add SxChar tagged object {_char, codepoint} to JS platform
- char? char->integer integer->char char-upcase char-downcase
- char=? char<? char>? char<=? char>=? comparators
- char-ci=? char-ci<? char-ci>? char-ci<=? char-ci>=? case-insensitive
- char-alphabetic? char-numeric? char-whitespace? char-upper-case? char-lower-case?
- string->list (returns chars) and list->string (accepts chars)
- #\a #\space #\newline reader syntax in spec/parser.sx
- integer->char alias in spec/evaluator.sx
- js-char-renames dict in transpiler.sx for ->-containing names
- 43 tests in spec/tests/test-chars.sx, all passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-05-01T10:26:58Z";
|
||||
var SX_VERSION = "2026-05-01T11:46:28Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -168,6 +168,7 @@
|
||||
if (x._macro) return "macro";
|
||||
if (x._raw) return "raw-html";
|
||||
if (x._sx_expr) return "sx-expr";
|
||||
if (x._char) return "char";
|
||||
if (x._vector) return "vector";
|
||||
if (x._string_buffer) return "string-buffer";
|
||||
if (x._hash_table) return "hash-table";
|
||||
@@ -475,6 +476,41 @@
|
||||
PRIMITIVES["slice"] = function(c, a, b) { if (!c || typeof c.slice !== "function") { console.error("[sx-debug] slice called on non-sliceable:", typeof c, c, "a=", a, "b=", b, new Error().stack); return []; } return b !== undefined ? c.slice(a, b) : c.slice(a); };
|
||||
PRIMITIVES["substring"] = function(s, a, b) { return String(s).substring(a, b); };
|
||||
PRIMITIVES["char-from-code"] = function(n) { return String.fromCharCode(n); };
|
||||
PRIMITIVES["char-code"] = function(s) { return String(s).charCodeAt(0); };
|
||||
var charCode = PRIMITIVES["char-code"];
|
||||
function makeChar(n) { return {_char: true, codepoint: n}; }
|
||||
PRIMITIVES["make-char"] = makeChar;
|
||||
var isChar = function(v) { return v != null && typeof v === "object" && v._char === true; };
|
||||
PRIMITIVES["char?"] = isChar;
|
||||
var charToInteger = function(c) { return c.codepoint; };
|
||||
PRIMITIVES["char->integer"] = charToInteger;
|
||||
var charUpcase = function(c) { return makeChar(String.fromCharCode(c.codepoint).toUpperCase().charCodeAt(0)); };
|
||||
PRIMITIVES["char-upcase"] = charUpcase;
|
||||
var charDowncase = function(c) { return makeChar(String.fromCharCode(c.codepoint).toLowerCase().charCodeAt(0)); };
|
||||
PRIMITIVES["char-downcase"] = charDowncase;
|
||||
PRIMITIVES["char=?"] = function(a, b) { return a.codepoint === b.codepoint; };
|
||||
PRIMITIVES["char<?"] = function(a, b) { return a.codepoint < b.codepoint; };
|
||||
PRIMITIVES["char>?"] = function(a, b) { return a.codepoint > b.codepoint; };
|
||||
PRIMITIVES["char<=?"] = function(a, b) { return a.codepoint <= b.codepoint; };
|
||||
PRIMITIVES["char>=?"] = function(a, b) { return a.codepoint >= b.codepoint; };
|
||||
PRIMITIVES["char-ci=?"] = function(a, b) { return charDowncase(a).codepoint === charDowncase(b).codepoint; };
|
||||
PRIMITIVES["char-ci<?"] = function(a, b) { return charDowncase(a).codepoint < charDowncase(b).codepoint; };
|
||||
PRIMITIVES["char-ci>?"] = function(a, b) { return charDowncase(a).codepoint > charDowncase(b).codepoint; };
|
||||
PRIMITIVES["char-ci<=?"] = function(a, b) { return charDowncase(a).codepoint <= charDowncase(b).codepoint; };
|
||||
PRIMITIVES["char-ci>=?"] = function(a, b) { return charDowncase(a).codepoint >= charDowncase(b).codepoint; };
|
||||
PRIMITIVES["char-alphabetic?"] = function(c) { var n = c.codepoint; return (n >= 65 && n <= 90) || (n >= 97 && n <= 122); };
|
||||
PRIMITIVES["char-numeric?"] = function(c) { var n = c.codepoint; return n >= 48 && n <= 57; };
|
||||
PRIMITIVES["char-whitespace?"] = function(c) { var n = c.codepoint; return n === 32 || n === 9 || n === 10 || n === 13; };
|
||||
PRIMITIVES["char-upper-case?"] = function(c) { var n = c.codepoint; return n >= 65 && n <= 90; };
|
||||
PRIMITIVES["char-lower-case?"] = function(c) { var n = c.codepoint; return n >= 97 && n <= 122; };
|
||||
PRIMITIVES["string->list"] = function(s) {
|
||||
var chars = []; var str = String(s);
|
||||
for (var i = 0; i < str.length; i++) chars.push(makeChar(str.charCodeAt(i)));
|
||||
return chars;
|
||||
};
|
||||
PRIMITIVES["list->string"] = function(chars) {
|
||||
return chars.map(function(c) { return String.fromCharCode(c.codepoint); }).join('');
|
||||
};
|
||||
PRIMITIVES["string-length"] = function(s) { return String(s).length; };
|
||||
var stringLength = PRIMITIVES["string-length"];
|
||||
PRIMITIVES["string-contains?"] = function(s, sub) { return String(s).indexOf(String(sub)) !== -1; };
|
||||
@@ -1102,6 +1138,9 @@
|
||||
}
|
||||
function sxExprSource(e) { return typeof e === "string" ? e : (e && e.source ? e.source : String(e)); }
|
||||
var charFromCode = PRIMITIVES["char-from-code"];
|
||||
var makeChar = PRIMITIVES["make-char"];
|
||||
var charToInteger = PRIMITIVES["char->integer"];
|
||||
var isChar = PRIMITIVES["char?"];
|
||||
|
||||
|
||||
// String/number utilities needed by transpiled spec code (content-hash etc)
|
||||
@@ -3599,6 +3638,10 @@ PRIMITIVES["intern"] = intern;
|
||||
var symbolInterned_p = function(sym) { return true; };
|
||||
PRIMITIVES["symbol-interned?"] = symbolInterned_p;
|
||||
|
||||
// integer->char
|
||||
var integerToChar = makeChar;
|
||||
PRIMITIVES["integer->char"] = integerToChar;
|
||||
|
||||
|
||||
// === Transpiled from freeze (serializable state boundaries) ===
|
||||
|
||||
@@ -3901,6 +3944,21 @@ PRIMITIVES["raw-loop"] = rawLoop;
|
||||
return buf;
|
||||
})(); };
|
||||
PRIMITIVES["read-raw-string"] = readRawString;
|
||||
var readCharLiteral = function() { return (isSxTruthy((pos >= lenSrc)) ? error("Unexpected end of input after #\\") : (function() {
|
||||
var firstCh = nth(source, pos);
|
||||
return (isSxTruthy(isIdentStart(firstCh)) ? (function() {
|
||||
var charStart = pos;
|
||||
var readCharNameLoop = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && isIdentChar(nth(source, pos))))) { pos = (pos + 1);
|
||||
continue; } else { return NIL; } } };
|
||||
PRIMITIVES["read-char-name-loop"] = readCharNameLoop;
|
||||
readCharNameLoop();
|
||||
return (function() {
|
||||
var charName = slice(source, charStart, pos);
|
||||
return makeChar((isSxTruthy(sxEq(charName, "space")) ? 32 : (isSxTruthy(sxEq(charName, "newline")) ? 10 : (isSxTruthy(sxEq(charName, "tab")) ? 9 : (isSxTruthy(sxEq(charName, "nul")) ? 0 : (isSxTruthy(sxEq(charName, "null")) ? 0 : (isSxTruthy(sxEq(charName, "return")) ? 13 : (isSxTruthy(sxEq(charName, "escape")) ? 27 : (isSxTruthy(sxEq(charName, "delete")) ? 127 : (isSxTruthy(sxEq(charName, "backspace")) ? 8 : (isSxTruthy(sxEq(charName, "altmode")) ? 27 : (isSxTruthy(sxEq(charName, "rubout")) ? 127 : charCode(firstCh)))))))))))));
|
||||
})();
|
||||
})() : ((pos = (pos + 1)), makeChar(charCode(firstCh))));
|
||||
})()); };
|
||||
PRIMITIVES["read-char-literal"] = readCharLiteral;
|
||||
var readExpr = function() { while(true) { skipWs();
|
||||
if (isSxTruthy((pos >= lenSrc))) { return error("Unexpected end of input"); } else { { var ch = nth(source, pos);
|
||||
if (isSxTruthy(sxEq(ch, "("))) { pos = (pos + 1);
|
||||
@@ -3916,7 +3974,8 @@ if (isSxTruthy(sxEq(dispatchCh, ";"))) { pos = (pos + 1);
|
||||
readExpr();
|
||||
continue; } else if (isSxTruthy(sxEq(dispatchCh, "|"))) { pos = (pos + 1);
|
||||
return readRawString(); } else if (isSxTruthy(sxEq(dispatchCh, "'"))) { pos = (pos + 1);
|
||||
return [makeSymbol("quote"), readExpr()]; } else if (isSxTruthy(isIdentStart(dispatchCh))) { { var macroName = readIdent();
|
||||
return [makeSymbol("quote"), readExpr()]; } else if (isSxTruthy(sxEq(dispatchCh, "\\"))) { pos = (pos + 1);
|
||||
return readCharLiteral(); } else if (isSxTruthy(isIdentStart(dispatchCh))) { { var macroName = readIdent();
|
||||
{ var handler = readerMacroGet(macroName);
|
||||
if (isSxTruthy(handler)) { return handler(readExpr()); } else { return error((String("Unknown reader macro: #") + String(macroName))); } } } } else { return error((String("Unknown reader macro: #") + String(dispatchCh))); } } } } else if (isSxTruthy(sxOr((isSxTruthy((ch >= "0")) && (ch <= "9")), (isSxTruthy(sxEq(ch, "-")) && isSxTruthy(((pos + 1) < lenSrc)) && (function() {
|
||||
var nextCh = nth(source, (pos + 1));
|
||||
@@ -3937,7 +3996,10 @@ PRIMITIVES["parse-loop"] = parseLoop;
|
||||
PRIMITIVES["sx-parse"] = sxParse;
|
||||
|
||||
// sx-serialize
|
||||
var sxSerialize = function(val) { return (function() { var _m = typeOf(val); if (_m == "nil") return "nil"; if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "number") return (String(val)); if (_m == "string") return (String("\"") + String(escapeString(val)) + String("\"")); if (_m == "symbol") return symbolName(val); if (_m == "keyword") return (String(":") + String(keywordName(val))); if (_m == "list") return (String("(") + String(join(" ", map(sxSerialize, val))) + String(")")); if (_m == "dict") return sxSerializeDict(val); if (_m == "sx-expr") return sxExprSource(val); if (_m == "spread") return (String("(make-spread ") + String(sxSerializeDict(spreadAttrs(val))) + String(")")); return (String(val)); })(); };
|
||||
var sxSerialize = function(val) { return (function() { var _m = typeOf(val); if (_m == "nil") return "nil"; if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "number") return (String(val)); if (_m == "string") return (String("\"") + String(escapeString(val)) + String("\"")); if (_m == "symbol") return symbolName(val); if (_m == "keyword") return (String(":") + String(keywordName(val))); if (_m == "list") return (String("(") + String(join(" ", map(sxSerialize, val))) + String(")")); if (_m == "dict") return sxSerializeDict(val); if (_m == "sx-expr") return sxExprSource(val); if (_m == "spread") return (String("(make-spread ") + String(sxSerializeDict(spreadAttrs(val))) + String(")")); if (_m == "char") return (function() {
|
||||
var n = charToInteger(val);
|
||||
return (String("#\\") + String((isSxTruthy(sxEq(n, 32)) ? "space" : (isSxTruthy(sxEq(n, 10)) ? "newline" : (isSxTruthy(sxEq(n, 9)) ? "tab" : (isSxTruthy(sxEq(n, 13)) ? "return" : (isSxTruthy(sxEq(n, 0)) ? "nul" : (isSxTruthy(sxEq(n, 27)) ? "escape" : (isSxTruthy(sxEq(n, 127)) ? "delete" : (isSxTruthy(sxEq(n, 8)) ? "backspace" : charFromCode(n)))))))))));
|
||||
})(); return (String(val)); })(); };
|
||||
PRIMITIVES["sx-serialize"] = sxSerialize;
|
||||
|
||||
// sx-serialize-dict
|
||||
|
||||
Reference in New Issue
Block a user