spec: string ports (open-input-string/open-output-string/read-char/etc)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled

Phase 14: port type + eof-object. Input ports track _pos cursor; output ports
accumulate _buffer. All 15 port primitives in spec/primitives.sx (stdlib.ports
module), platform.py (JS), and 39/39 tests in spec/tests/test-ports.sx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 12:38:08 +00:00
parent dfbcece644
commit 3d8937d759
4 changed files with 456 additions and 1 deletions

View File

@@ -1115,6 +1115,69 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
PRIMITIVES["list->string"] = function(chars) {
return chars.map(function(c) { return String.fromCharCode(c.codepoint); }).join('');
};
// Phase 14: string ports + eof-object
var _eof = {_eof: true};
PRIMITIVES["eof-object"] = function() { return _eof; };
PRIMITIVES["eof-object?"] = function(v) { return v != null && v._eof === true; };
var isEofObject = PRIMITIVES["eof-object?"];
PRIMITIVES["open-input-string"] = function(s) {
return {_port: true, _kind: "input", _source: String(s), _pos: 0, _closed: false};
};
PRIMITIVES["open-output-string"] = function() {
return {_port: true, _kind: "output", _buffer: "", _closed: false};
};
PRIMITIVES["get-output-string"] = function(p) {
if (!p || p._kind !== "output") throw new Error("get-output-string: expected output port");
return p._buffer;
};
PRIMITIVES["port?"] = function(v) { return v != null && v._port === true; };
PRIMITIVES["input-port?"] = function(v) { return v != null && v._port === true && v._kind === "input"; };
PRIMITIVES["output-port?"] = function(v) { return v != null && v._port === true && v._kind === "output"; };
PRIMITIVES["close-port"] = function(p) {
if (p && p._port) p._closed = true;
return NIL;
};
PRIMITIVES["read-char"] = function(p) {
if (p === undefined || p === NIL || p == null) {
return _eof; // no stdin in this env
}
if (!p._port || p._kind !== "input") throw new Error("read-char: expected input port");
if (p._closed || p._pos >= p._source.length) return _eof;
var cp = p._source.charCodeAt(p._pos);
p._pos++;
return makeChar(cp);
};
PRIMITIVES["peek-char"] = function(p) {
if (p === undefined || p === NIL || p == null) return _eof;
if (!p._port || p._kind !== "input") throw new Error("peek-char: expected input port");
if (p._closed || p._pos >= p._source.length) return _eof;
return makeChar(p._source.charCodeAt(p._pos));
};
PRIMITIVES["read-line"] = function(p) {
if (p === undefined || p === NIL || p == null) return _eof;
if (!p._port || p._kind !== "input") throw new Error("read-line: expected input port");
if (p._closed || p._pos >= p._source.length) return _eof;
var start = p._pos;
while (p._pos < p._source.length && p._source[p._pos] !== '\\n') p._pos++;
var line = p._source.slice(start, p._pos);
if (p._pos < p._source.length) p._pos++; // skip \n
return line;
};
PRIMITIVES["write-char"] = function(c, p) {
if (!p || !p._port || p._kind !== "output") throw new Error("write-char: expected char and output port");
if (!p._closed) p._buffer += String.fromCharCode(c.codepoint);
return NIL;
};
PRIMITIVES["write-string"] = function(s, p) {
if (!p || !p._port || p._kind !== "output") throw new Error("write-string: expected string and output port");
if (!p._closed) p._buffer += String(s);
return NIL;
};
PRIMITIVES["char-ready?"] = function(p) {
if (p === undefined || p === NIL || p == null) return false;
if (!p._port || p._kind !== "input") return false;
return !p._closed && p._pos < p._source.length;
};
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; };
@@ -1433,6 +1496,8 @@ PLATFORM_JS_PRE = '''
if (x._raw) return "raw-html";
if (x._sx_expr) return "sx-expr";
if (x._char) return "char";
if (x._eof) return "eof-object";
if (x._port) return x._kind === "input" ? "input-port" : "output-port";
if (x._vector) return "vector";
if (x._string_buffer) return "string-buffer";
if (x._hash_table) return "hash-table";