spec: read/write/display — S-expression reader/writer on ports
Adds read, write, display, newline, write-to-string, display-to-string
and current-*-port primitives to both JS and OCaml hosts.
JS: sxReadNormalize (#t/#f→true/false), sxReadConvert (()→nil),
sxEq array comparison, sxWriteVal symbol/keyword name fix,
readerMacroGet/readerMacroSet registry in parser platform.
OCaml: sx_write_val/sx_display_val helpers, read/write/display/newline
primitives on port types; parser extended for #t/#f and N/D rationals.
42 new tests (test-read-write.sx), all passing on JS and OCaml.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -849,6 +849,13 @@ PREAMBLE = '''\
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
if (a.length !== b.length) return false;
|
||||
for (var _j = 0; _j < a.length; _j++) {
|
||||
if (!sxEq(a[_j], b[_j])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (a && b && a._rational && b._rational) return a._n === b._n && a._d === b._d;
|
||||
if (a && a._rational && typeof b === "number") return b === a._n / a._d;
|
||||
if (b && b._rational && typeof a === "number") return a === b._n / b._d;
|
||||
@@ -1257,6 +1264,100 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
||||
if (!p._port || p._kind !== "input") return false;
|
||||
return !p._closed && p._pos < p._source.length;
|
||||
};
|
||||
// read/write/display
|
||||
var _sxBs92 = String.fromCharCode(92);
|
||||
function sxReadNormalize(src) {
|
||||
var out = "", i = 0, n = src.length;
|
||||
while (i < n) {
|
||||
if (src[i] === '"') {
|
||||
out += '"'; i++;
|
||||
while (i < n) {
|
||||
if (src[i] === _sxBs92 && i+1 < n) { out += src[i]; out += src[i+1]; i += 2; continue; }
|
||||
if (src[i] === '"') { out += src[i++]; break; }
|
||||
out += src[i++];
|
||||
}
|
||||
} else if (src[i] === '#' && i+1 < n && (src[i+1] === 't' || src[i+1] === 'f')) {
|
||||
var nc2 = i+2 < n ? src[i+2] : '';
|
||||
if (!nc2 || !/[a-zA-Z0-9_]/.test(nc2)) {
|
||||
out += (src[i+1] === 't') ? 'true' : 'false';
|
||||
i += 2;
|
||||
} else { out += src[i++]; }
|
||||
} else { out += src[i++]; }
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function sxReadConvert(v) {
|
||||
if (Array.isArray(v) && v.length === 0) return NIL;
|
||||
if (Array.isArray(v)) return v.map(sxReadConvert);
|
||||
return v;
|
||||
}
|
||||
PRIMITIVES["read"] = function() {
|
||||
var p = arguments.length > 0 && arguments[0] && arguments[0]._port ? arguments[0] : null;
|
||||
if (!p || p._kind !== "input" || p._closed) return _eof;
|
||||
if (!p._forms) {
|
||||
var sxP = PRIMITIVES["sx-parse"];
|
||||
var src = sxReadNormalize(p._source.slice(p._pos || 0));
|
||||
p._forms = sxP ? (sxP(src) || []) : [];
|
||||
p._form_idx = 0;
|
||||
}
|
||||
if (p._form_idx >= p._forms.length) return _eof;
|
||||
return sxReadConvert(p._forms[p._form_idx++]);
|
||||
};
|
||||
var _sxBs = String.fromCharCode(92);
|
||||
var _sxDq = String.fromCharCode(34);
|
||||
function sxWriteVal(v, mode) {
|
||||
if (v === null || v === undefined || v === NIL) return "()";
|
||||
if (v && v._eof) return "#!eof";
|
||||
if (typeof v === "boolean") return v ? "#t" : "#f";
|
||||
if (typeof v === "number") return String(v);
|
||||
if (v && v._rational) return v._n + "/" + v._d;
|
||||
if (typeof v === "string") {
|
||||
if (mode === "display") return v;
|
||||
return _sxDq + v.split("").map(function(c) {
|
||||
var n = c.charCodeAt(0);
|
||||
if (n === 34) return _sxBs + _sxDq;
|
||||
if (n === 92) return _sxBs + _sxBs;
|
||||
if (n === 10) return _sxBs + "n";
|
||||
if (n === 13) return _sxBs + "r";
|
||||
if (n === 9) return _sxBs + "t";
|
||||
return c;
|
||||
}).join("") + _sxDq;
|
||||
}
|
||||
if (v && v._char) {
|
||||
if (mode === "display") return String.fromCodePoint(v.codepoint);
|
||||
var cp = v.codepoint;
|
||||
if (cp === 32) return "#" + _sxBs + "space";
|
||||
if (cp === 10) return "#" + _sxBs + "newline";
|
||||
if (cp === 9) return "#" + _sxBs + "tab";
|
||||
return "#" + _sxBs + String.fromCodePoint(cp);
|
||||
}
|
||||
if (v && v._sym) return v.name;
|
||||
if (v && v._kw) return ":" + v.name;
|
||||
if (Array.isArray(v)) return "(" + v.map(function(x){ return sxWriteVal(x, mode); }).join(" ") + ")";
|
||||
return String(v);
|
||||
}
|
||||
PRIMITIVES["write"] = function() {
|
||||
var val = arguments[0], port = arguments[1];
|
||||
var s = sxWriteVal(val, "write");
|
||||
if (port && port._port && port._kind === "output" && !port._closed) port._buffer += s;
|
||||
return NIL;
|
||||
};
|
||||
PRIMITIVES["display"] = function() {
|
||||
var val = arguments[0], port = arguments[1];
|
||||
var s = sxWriteVal(val, "display");
|
||||
if (port && port._port && port._kind === "output" && !port._closed) port._buffer += s;
|
||||
return NIL;
|
||||
};
|
||||
PRIMITIVES["newline"] = function() {
|
||||
var port = arguments[0];
|
||||
if (port && port._port && port._kind === "output" && !port._closed) port._buffer += String.fromCharCode(10);
|
||||
return NIL;
|
||||
};
|
||||
PRIMITIVES["write-to-string"] = function(val) { return sxWriteVal(val, "write"); };
|
||||
PRIMITIVES["display-to-string"] = function(val) { return sxWriteVal(val, "display"); };
|
||||
PRIMITIVES["current-input-port"] = function() { return NIL; };
|
||||
PRIMITIVES["current-output-port"] = function() { return NIL; };
|
||||
PRIMITIVES["current-error-port"] = function() { return NIL; };
|
||||
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; };
|
||||
@@ -1571,6 +1672,7 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
||||
PRIMITIVES["rational?"] = function(v) { return v instanceof SxRational; };
|
||||
PRIMITIVES["numerator"] = function(r) { return r instanceof SxRational ? r._n : r; };
|
||||
PRIMITIVES["denominator"] = function(r) { return r instanceof SxRational ? r._d : 1; };
|
||||
var makeRational = PRIMITIVES["make-rational"];
|
||||
''',
|
||||
"stdlib.hash-table": '''
|
||||
// stdlib.hash-table
|
||||
@@ -2294,6 +2396,11 @@ PLATFORM_PARSER_JS = r"""
|
||||
var makeChar = PRIMITIVES["make-char"];
|
||||
var charToInteger = PRIMITIVES["char->integer"];
|
||||
var isChar = PRIMITIVES["char?"];
|
||||
var _readerMacros = {};
|
||||
function readerMacroGet(name) { return _readerMacros[name] || false; }
|
||||
function readerMacroSet(name, fn) { _readerMacros[name] = fn; }
|
||||
PRIMITIVES["reader-macro-get"] = readerMacroGet;
|
||||
PRIMITIVES["reader-macro-set!"] = readerMacroSet;
|
||||
"""
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user