diff --git a/TinyBasic/assembler.py b/TinyBasic/assembler.py index 2fecf20..a8a87a6 100644 --- a/TinyBasic/assembler.py +++ b/TinyBasic/assembler.py @@ -368,7 +368,7 @@ class OpCode(IntEnum): out.append(buf[-1] | 0x80) -test = "".join(map("{:02X}".format, out)) # ty: ignore[no-matching-overload] +test = "".join(map("{:02X}".format, out)) assert test == "".join( line[5:line.index(";", 5)] for line in interpreter.splitlines() if len(line) > 5 ) diff --git a/aoc2024/day11.py b/aoc2024/day11.py index 07b03e7..f81f0d1 100644 --- a/aoc2024/day11.py +++ b/aoc2024/day11.py @@ -47,7 +47,7 @@ cache[stone] = sum(counts) # pyright: ignore[reportCallIssue, reportArgumentType] else: q.append(t) - q.extend(zip(repeat(n), compress(stones, map(not_, counts)))) # pyright: ignore[reportArgumentType] + q.extend(zip(repeat(n), compress(stones, map(not_, counts)))) # ty: ignore[invalid-argument-type] print(sum(caches[25].values())) print(sum(caches[75].values())) diff --git a/aoc2024/day19.py b/aoc2024/day19.py index 4cf7ef6..92d2a92 100644 --- a/aoc2024/day19.py +++ b/aoc2024/day19.py @@ -21,7 +21,7 @@ def count(haystack: str) -> int: with open("aoc2024/day19input.txt", "rt") as lines: patterns = next(lines).rstrip().replace(" ", "").split(",") - count = counter(sorted(patterns, key=len, reverse=True)) # ty: ignore[invalid-argument-type] + count = counter(sorted(patterns, key=len, reverse=True)) next(lines) counts = [count(line.rstrip()) for line in lines] diff --git a/aoc2025/day10.py b/aoc2025/day10.py index 5b731a4..0ab41ee 100644 --- a/aoc2025/day10.py +++ b/aoc2025/day10.py @@ -21,7 +21,7 @@ def first_half(desired: list[int], buttons: list[tuple[int]]): return total -def second_half(buttons: list[tuple[int]], joltages: list[tuple[int]]) -> int: +def second_half(buttons: list[tuple[int, ...]], joltages: list[tuple[int, ...]]) -> int: total = 0 for button, joltage in zip(buttons, joltages): A_eq = sparse.dok_array((len(joltage), len(button))) diff --git a/conftest.py b/conftest.py index 3088096..e268ea4 100644 --- a/conftest.py +++ b/conftest.py @@ -1,4 +1,5 @@ import os +import platform import re import subprocess @@ -43,10 +44,14 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: target = { "test_4th": "4th", "test_5th": "5th.ll", + "test_6th": "zig-out/bin/6th", }[metafunc.function.__name__] scenarios = scenarios.format( argv0=f"./{target}", uid=os.getuid(), + getuid=24 if platform.system() == "Darwin" else 102, + setuid=23 if platform.system() == "Darwin" else 105, + access=33 if platform.system() == "Darwin" else 21, ) subprocess.run(["make", target], cwd="forth", check=True) cp = subprocess.run( @@ -63,6 +68,7 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: ["actual", "expected"], zip( cp.stdout.splitlines(), - re.findall(r"(?<=\\ )(.+)$", scenarios, re.M), + re.findall(r"(?<=\\ )(.+)$", scenarios, flags=re.M), ), + ids=re.findall(r"^(.+?)(?= \\ )", scenarios, flags=re.M), ) diff --git a/forth/4th.c b/forth/4th.c index d549070..997d137 100644 --- a/forth/4th.c +++ b/forth/4th.c @@ -2,7 +2,6 @@ #if __has_include ("cosmopolitan.h") /* https://justine.lol/ape.html */ #include "cosmopolitan.h" -#define __NR_brk __NR_linux_brk #else #include #include /* O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC, O_APPEND, O_NONBLOCK */ @@ -10,7 +9,7 @@ #include /* EOF, getchar_unlocked, putchar_unlocked */ #include /* exit, strtol, syscall */ #include /* memcmp, memmove, memcpy */ -#include /* __NR_exit, __NR_open, __NR_close, __NR_read, __NR_write, __NR_creat, __NR_brk */ +#include /* SYS_exit, SYS_open, SYS_close, SYS_read, SYS_write, SYS_creat, SYS_brk */ #include /* read, write, intptr_t */ #endif @@ -40,7 +39,7 @@ code_##_label #include /* https://github.com/emscripten-core/emscripten/issues/6708 */ -enum SYS {__NR_read, __NR_write, __NR_open, __NR_close, __NR_brk=0x0c, __NR_exit=0x3c, __NR_creat=0x55}; +enum SYS {SYS_read, SYS_write, SYS_open, SYS_close, SYS_brk=0x0c, SYS_exit=0x3c, SYS_creat=0x55}; int syscall(int sysno, ...) { va_list ap; @@ -49,19 +48,25 @@ int syscall(int sysno, ...) switch(sysno) { - case __NR_read : status = read(va_arg(ap, int), va_arg(ap, void *), va_arg(ap, size_t)); break; - case __NR_write: status = write(va_arg(ap, int), va_arg(ap, const void *), va_arg(ap, size_t)); break; - case __NR_open : status = open(va_arg(ap, const char *), va_arg(ap, int), va_arg(ap, mode_t)); break; - case __NR_close: status = close(va_arg(ap, int)); break; - case __NR_brk : status = (intptr_t)va_arg(ap, void *); status = status ? brk((void *)status) : (intptr_t)sbrk(0); break; - case __NR_exit : status = va_arg(ap, int); va_end(ap); exit(status); - case __NR_creat: status = creat(va_arg(ap, const char *), va_arg(ap, mode_t)); break; + case SYS_read : status = read(va_arg(ap, int), va_arg(ap, void *), va_arg(ap, size_t)); break; + case SYS_write: status = write(va_arg(ap, int), va_arg(ap, const void *), va_arg(ap, size_t)); break; + case SYS_open : status = open(va_arg(ap, const char *), va_arg(ap, int), va_arg(ap, mode_t)); break; + case SYS_close: status = close(va_arg(ap, int)); break; + case SYS_brk : status = (intptr_t)va_arg(ap, void *); status = status ? brk((void *)status) : (intptr_t)sbrk(0); break; + case SYS_exit : status = va_arg(ap, int); va_end(ap); exit(status); + case SYS_creat: status = creat(va_arg(ap, const char *), va_arg(ap, mode_t)); break; } va_end(ap); return status; } #endif +#ifdef __APPLE__ +#define SYS_creat 0x55 +#define SYS_brk 0x0c +#define fflush_unlocked fflush +#endif + enum Flags {F_IMMED=0x80, F_HIDDEN=0x20, F_LENMASK=0x1f}; struct word_t { struct word_t *link; @@ -398,13 +403,13 @@ DEFCONST(__DOCOL, 0, "DODOES", __DODOES, &&DODOES); DEFCONST(__DODOES, 0, "F_IMMED", __F_IMMED, F_IMMED); DEFCONST(__F_IMMED, 0, "F_HIDDEN", __F_HIDDEN, F_HIDDEN); DEFCONST(__F_HIDDEN, 0, "F_LENMASK", __F_LENMASK, F_LENMASK); -DEFCONST(__F_LENMASK, 0, "SYS_EXIT", SYS_EXIT, __NR_exit); -DEFCONST(SYS_EXIT, 0, "SYS_OPEN", SYS_OPEN, __NR_open); -DEFCONST(SYS_OPEN, 0, "SYS_CLOSE", SYS_CLOSE, __NR_close); -DEFCONST(SYS_CLOSE, 0, "SYS_READ", SYS_READ, __NR_read); -DEFCONST(SYS_READ, 0, "SYS_WRITE", SYS_WRITE, __NR_write); -DEFCONST(SYS_WRITE, 0, "SYS_CREAT", SYS_CREAT, __NR_creat); -DEFCONST(SYS_CREAT, 0, "SYS_BRK", SYS_BRK, __NR_brk); +DEFCONST(__F_LENMASK, 0, "SYS_EXIT", SYS_EXIT, SYS_exit); +DEFCONST(SYS_EXIT, 0, "SYS_OPEN", SYS_OPEN, SYS_open); +DEFCONST(SYS_OPEN, 0, "SYS_CLOSE", SYS_CLOSE, SYS_close); +DEFCONST(SYS_CLOSE, 0, "SYS_READ", SYS_READ, SYS_read); +DEFCONST(SYS_READ, 0, "SYS_WRITE", SYS_WRITE, SYS_write); +DEFCONST(SYS_WRITE, 0, "SYS_CREAT", SYS_CREAT, SYS_creat); +DEFCONST(SYS_CREAT, 0, "SYS_BRK", SYS_BRK, SYS_brk); DEFCONST(SYS_BRK, 0, "O_RDONLY", __O_RDONLY, O_RDONLY); DEFCONST(__O_RDONLY, 0, "O_WRONLY", __O_WRONLY, O_WRONLY); DEFCONST(__O_WRONLY, 0, "O_RDWR", __O_RDWR, O_RDWR); diff --git a/forth/5th.c b/forth/5th.c index 286c676..2ab0392 100644 --- a/forth/5th.c +++ b/forth/5th.c @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #ifdef EMSCRIPTEN @@ -14,6 +14,11 @@ enum SYS {SYS_exit, SYS_open, SYS_close, SYS_read, SYS_write, SYS_creat, SYS_brk}; #endif +#ifdef __APPLE__ +#define SYS_creat 24 +#define SYS_brk 214 +#endif + #define NEXT __attribute__((musttail)) return ip->word->code(env, sp, rsp, ip + 1, ip->word) #define DEFCODE_(_link, _flag, _name, _label) \ intptr_t *_label(struct interp_t *, intptr_t *, union instr_t **, union instr_t *, union instr_t *); \ diff --git a/forth/6th.zig b/forth/6th.zig index 3bafbcb..7294a96 100644 --- a/forth/6th.zig +++ b/forth/6th.zig @@ -1,212 +1,345 @@ +//! Zorth is a [threaded](https://en.wikipedia.org/wiki/Threaded_code) +//! [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)) in +//! Zig. It is based on [jonesforth](https://rwmj.wordpress.com/tag/jonesforth/) +//! which you should definitely check out. +//! +//! Jonesforth leans heavily on [indirect jumps](https://en.wikipedia.org/wiki/Indirect_branch) +//! as is natural in assembly. The closest Zig analog would be +//! [labeled continue](https://github.com/ziglang/zig/issues/8220). Unfortunately, +//! that requires sticking all the code in one giant `switch` statement, à la +//! `ceval.c` in cpython. Fortunately, Zig provides an alternative that lets us +//! write cleaner code: `.always_tail`. Instead of switch prongs, built-in Forth +//! words map to Zig functions which are only ever tail called. const std = @import("std"); const fmt = std.fmt; -const fs = std.fs; const mem = std.mem; const os = std.os; const syscalls = os.linux.syscalls; -const arch = @import("builtin").cpu.arch; +const testing = std.testing; +const builtin = @import("builtin"); +const arch = builtin.cpu.arch; const conv: std.builtin.CallingConvention = switch (arch) { - .x86_64 => .c, + .x86_64 => .winapi, else => .auto, }; -var stdin_buffer: [2048]u8 = undefined; -var stdin_reader = fs.File.stdin().reader(&stdin_buffer); -const stdin = &stdin_reader.interface; - -var stdout_buffer: [2048]u8 = undefined; -var stdout_writer = fs.File.stdout().writer(&stdout_buffer); -const stdout = &stdout_writer.interface; const O_RDONLY = 0o0; const O_WRONLY = 0o1; const O_RDWR = 0o2; const O_CREAT = 0o100; const O_EXCL = 0o200; -const O_TRUNC = 0o1000; -const O_APPEND = 0o2000; -const O_NONBLOCK = 0o4000; +const O_TRUNC = 0o1_000; +const O_APPEND = 0o2_000; +const O_NONBLOCK = 0o4_000; const F_LENMASK = std.ascii.control_code.us; const Flag = enum(u8) { IMMED = 0x80, HIDDEN = ' ', ZERO = 0x0 }; -const Word = extern struct { - link: ?*const Word, - flag: u8, - name: [F_LENMASK]u8 align(1), +// https://matklad.github.io/2025/12/23/zig-newtypes-index-pattern.html +const Word = enum(u32) { + DROP = 1, + SWAP, + DUP, + OVER, + ROT, + @"-ROT", + @"2DROP", + @"2DUP", + @"2SWAP", + @"?DUP", + @"1+", + @"1-", + @"4+", + @"4-", + @"+", + @"-", + @"*", + @"/MOD", + @"=", + @"<>", + @"<", + @">", + @"<=", + @">=", + @"0=", + @"0<>", + @"0<", + @"0>", + @"0<=", + @"0>=", + AND, + OR, + XOR, + INVERT, + EXIT, + LIT, + @"!", + @"@", + @"+!", + @"-!", + @"C!", + @"C@", + @"C@C!", + CMOVE, + STATE, + HERE, + LATEST, + S0, + BASE, + @"(ARGC)", + VERSION, + R0, + DOCOL, + F_IMMED, + F_HIDDEN, + F_LENMASK, + SYS_EXIT, + SYS_OPEN, + SYS_CLOSE, + SYS_READ, + SYS_WRITE, + SYS_CREAT, + SYS_BRK, + O_RDONLY, + O_WRONLY, + O_RDWR, + O_CREAT, + O_EXCL, + O_TRUNC, + O_APPEND, + O_NONBLOCK, + @">R", + @"R>", + @"RSP@", + @"RSP!", + RDROP, + @"DSP@", + @"DSP!", + KEY, + EMIT, + WORD, + NUMBER, + FIND, + @">CFA", + CREATE, + @",", + @"[", + @"]", + IMMEDIATE, + HIDDEN, + @"'", + BRANCH, + @"0BRANCH", + LITSTRING, + TELL, + INTERPRET, + CHAR, + EXECUTE, + SYSCALL3, + SYSCALL2, + SYSCALL1, + SYSCALL0, + _, + + /// The layout of this `struct` is very important for introspection to work. + /// Ideally, we would use [flexible array members](https://en.wikipedia.org/wiki/Flexible_array_member) + /// but I'm not sure how they work in Zig. So I lie a little and `Word` only + /// represents metadata. "Real" words are arrays of `Address` whose first + /// `offset` elements are disgustingly `@ptrCast`ed to a `Word` when + /// necessary. Ugh. + pub const Data = extern struct { + link: Address, + flag: u8, + name: [F_LENMASK]u8 align(1), + code: Word, + }; }; -const offset = @divExact(@sizeOf(Word), @sizeOf(Instr)); +inline fn codeFieldAddress(w: Address) usize { + return @intFromEnum(w) + @offsetOf(Word.Data, "code"); +} -inline fn codeFieldAddress(w: [*]const Instr) [*]const Instr { - return w + offset; +inline fn openFlags(flags: usize) std.c.O { + return switch (builtin.os.tag) { + .emscripten => .{ + .ACCMODE = @enumFromInt(flags & O_RDWR), + .CREAT = (flags & O_CREAT) != 0, + .EXCL = (flags & O_EXCL) != 0, + .TRUNC = (flags & O_TRUNC) != 0, + .APPEND = (flags & O_APPEND) != 0, + .NONBLOCK = (flags & O_NONBLOCK) != 0, + }, + .wasi => .{ + .read = (flags & O_WRONLY) == 0, + .write = (flags & O_RDONLY) == 0, + .CREAT = (flags & O_CREAT) != 0, + .EXCL = (flags & O_EXCL) != 0, + .TRUNC = (flags & O_TRUNC) != 0, + .APPEND = (flags & O_APPEND) != 0, + .NONBLOCK = (flags & O_NONBLOCK) != 0, + }, + else => unreachable, + }; } -fn InterpAligned(comptime alignment: mem.Alignment) type { - return struct { - const Self = @This(); - - state: isize, - latest: *Word, - s0: [*]const isize, - base: isize, - r0: [*]const [*]const Instr, - buffer: [32]u8, - memory: *std.array_list.AlignedManaged(u8, alignment), - here: [*]u8, - - pub fn init(sp: []const isize, rsp: []const [*]const Instr, m: *std.array_list.AlignedManaged(u8, alignment)) Self { - m.ensureUnusedCapacity(@sizeOf(Instr)) catch @panic("init cannot ensureUnusedCapacity"); - return .{ - .state = 0, - .latest = @ptrCast(&syscall0), - .s0 = sp.ptr, - .base = 10, - .r0 = rsp.ptr, - .buffer = undefined, - .memory = m, - .here = m.items.ptr + m.items.len, - }; - } +const Interp = struct { + const Self = @This(); - pub inline fn next(self: *Self, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) void { - _ = target; - const tgt = ip[0].word; - return @call(.always_tail, tgt[0].code, .{ self, sp, rsp, ip[1..], tgt }); - } + memory: std.array_list.AlignedManaged(u8, .of(u32)), + reader: *std.Io.Reader, + writer: *std.Io.Writer, - pub fn word(self: *Self) usize { - var ch: u8 = std.ascii.control_code.nul; - var i: usize = 0; + pub fn init( + memory: std.array_list.AlignedManaged(u8, .of(u32)), + reader: *std.Io.Reader, + writer: *std.Io.Writer, + ) Self { + return .{ + .memory = memory, + .reader = reader, + .writer = writer, + }; + } - while (ch <= ' ') { - ch = key(); - if (ch == '\\') { // comment ⇒ skip line - while (ch != '\n') ch = key(); - } - } - while (ch > ' ') { - self.buffer[i] = ch; - i += 1; - ch = key(); + pub inline fn next(self: *Self, sp: usize, rsp: usize, ip: usize, target: usize) void { + _ = target; + const tgt = @abs(self.readInt(ip)); + const code = @abs(self.readInt(tgt)); + return @call(.always_tail, primitives[code], .{ self, sp, rsp, ip + 4, tgt }); + } + + fn key(self: *Self) !u8 { + return self.reader.takeByte(); + } + + pub fn word(self: *Self) ![]u8 { + var ch: u8 = std.ascii.control_code.nul; + var i: usize = 0; + var buffer = self.slice(@offsetOf(Header, "buffer"), 0x20); + + while (ch <= ' ') { + ch = try self.key(); + if (ch == '\\') { // comment ⇒ skip line + while (ch != '\n') ch = try self.key(); } - return i; } + while (ch > ' ') { + buffer[i] = ch; + i += 1; + ch = try self.key(); + } + return buffer[0..i]; + } - pub fn find(self: Self, name: []u8) ?*const Word { - const mask = @intFromEnum(Flag.HIDDEN) | F_LENMASK; - var node: *const Word = self.latest; - while (node.flag & mask != name.len or !mem.eql(u8, node.name[0..name.len], name)) - node = node.link orelse return null; + pub inline fn slice(self: Self, address: usize, len: usize) []u8 { + // Bounds checking (removing .ptr) breaks HERE @ + return self.memory.items.ptr[address .. address + len]; + } - return node; - } + pub inline fn writeInt(self: Self, address: usize, val: i32) void { + mem.writeInt(i32, self.memory.items[address..][0..4], val, .native); + } - pub fn append(self: *Self, instr: Instr) void { - self.memory.items.len = @intFromPtr(self.here) - @intFromPtr(self.memory.items.ptr); - self.memory.appendSlice(mem.asBytes(&instr)) catch @panic("append cannot appendSlice"); - self.here = self.memory.items.ptr + self.memory.items.len; - } - }; -} + pub inline fn readInt(self: Self, address: usize) i32 { + return mem.readInt(i32, self.memory.items[address..][0..4], .native); + } -const Interp = InterpAligned(mem.Alignment.of(Instr)); + pub fn find(self: Self, name: []const u8) ?*const Word.Data { + const mask = @intFromEnum(Flag.HIDDEN) | F_LENMASK; + const buf = self.memory.items; + var node: *const Word.Data = @ptrCast(@alignCast(&buf[@abs(self.readInt(@offsetOf(Header, "latest")))])); + while (node.flag & mask != name.len or !mem.eql(u8, node.name[0..name.len], name)) { + const link = node.link; + if (link == .sentinel) + return null; + node = @ptrCast(@alignCast(&self.memory.items[@intFromEnum(link)])); + } + return node; + } -const Code = fn (*Interp, [*]isize, [*][*]const Instr, [*]const Instr, [*]const Instr) callconv(conv) void; + pub fn append(self: *Self, instr: Address.Data) void { + self.memory.items.len = @abs(self.readInt(@offsetOf(Header, "here"))); + self.memory.appendSlice(mem.asBytes(&instr)) catch @panic("append cannot appendSlice"); + self.writeInt(@offsetOf(Header, "here"), @intCast(self.memory.items.len)); + } +}; -const Instr = packed union { - code: *const Code, - literal: isize, - word: [*]const Instr, +const Header = extern struct { + stack: [2048]i32, + return_stack: [2048]u32, + input_buffer: [2048]u8, + output_buffer: [2048]u8, + state: u32, + here: u32, + latest: u32, + s0: u32, + base: u32, + cold_start: [1]u32, + buffer: [32]u8, }; -fn key() u8 { - const b = stdin.takeByte() catch std.process.exit(0); - return b; -} - -fn defword( - comptime last: ?[]const Instr, - comptime flag: Flag, - comptime name: []const u8, - comptime code: []const Instr, -) [offset + code.len]Instr { - var instrs: [offset + code.len]Instr = undefined; - const p: *Word = @ptrCast(&instrs[0]); - p.link = if (last) |link| @ptrCast(link.ptr) else null; - p.flag = name.len | @intFromEnum(flag); - @memcpy(p.name[0..name.len], name); - @memset(p.name[name.len..F_LENMASK], 0); - @memcpy(instrs[offset..], code); - return instrs; -} - -fn wrap(comptime stack: fn ([*]isize) callconv(.@"inline") [*]isize) []const Instr { - const t = struct { - fn code(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - self.next(stack(sp), rsp, ip, target); - } +/// In jonesforth, instructions are simply machine words with context-dependent +/// semantics. Zig's type system lets us be more explicit. +const Address = enum(u32) { + sentinel, + LIT = @offsetOf(Header, "buffer") + 28 + 36 * 0x28, // should be @sizeOf(Word.Data) + _, + + pub const Data = packed union { + /// built-in + code: Word, + /// LIT, LITSTRING, BRANCH, 0BRANCH, and ' are followed by one argument in the instruction stream + literal: i32, + /// written in Forth + word: Address, }; - return &.{.{ .code = t.code }}; -} +}; -fn attr(comptime name: []const u8) []const Instr { - const t = struct { - fn code(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 1; - s[0] = @intCast(@intFromPtr(&@field(self, name))); - self.next(s, rsp, ip, target); - } - }; - return &.{.{ .code = t.code }}; -} +const Code = fn (*Interp, usize, usize, usize, usize) callconv(conv) void; -fn value(comptime literal: isize) []const Instr { - const t = struct { - fn code(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 1; - s[0] = literal; - self.next(s, rsp, ip, target); +fn wrap(comptime stack: fn ([*]i32) callconv(.@"inline") [*]i32) Code { + return struct { + fn code(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const s: [*]i32 = @ptrCast(@alignCast(self.memory.items[sp..])); + const t = stack(s); + self.next(@intFromPtr(t) - @intFromPtr(self.memory.items.ptr), rsp, ip, target); } - }; - return &.{.{ .code = t.code }}; + }.code; } -fn words(comptime data: []const []const Instr) []const Instr { - comptime var code: [data.len + 1]Instr = undefined; - code[0].code = docol_; - inline for (code[1..], data) |*d, s| - d.word = codeFieldAddress(s.ptr); - return &code; +fn value(comptime literal: i32) Code { + return struct { + fn code(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(sp - 4, literal); + self.next(sp - 4, rsp, ip, target); + } + }.code; } -inline fn _drop(sp: [*]isize) [*]isize { - return sp[1..]; +inline fn _drop(sp: [*]i32) [*]i32 { + return sp + 1; } -const drop = defword(null, Flag.ZERO, "DROP", wrap(_drop)); -inline fn _swap(sp: [*]isize) [*]isize { +inline fn _swap(sp: [*]i32) [*]i32 { const temp = sp[1]; sp[1] = sp[0]; sp[0] = temp; return sp; } -const swap = defword(&drop, Flag.ZERO, "SWAP", wrap(_swap)); -inline fn _dup(sp: [*]isize) [*]isize { +inline fn _dup(sp: [*]i32) [*]i32 { const s = sp - 1; s[0] = sp[0]; return s; } -const dup = defword(&swap, Flag.ZERO, "DUP", wrap(_dup)); -inline fn _over(sp: [*]isize) [*]isize { +inline fn _over(sp: [*]i32) [*]i32 { const s = sp - 1; s[0] = sp[1]; return s; } -const over = defword(&dup, Flag.ZERO, "OVER", wrap(_over)); -inline fn _rot(sp: [*]isize) [*]isize { +inline fn _rot(sp: [*]i32) [*]i32 { const a = sp[0]; const b = sp[1]; const c = sp[2]; @@ -215,9 +348,8 @@ inline fn _rot(sp: [*]isize) [*]isize { sp[0] = c; return sp; } -const rot = defword(&over, Flag.ZERO, "ROT", wrap(_rot)); -inline fn _nrot(sp: [*]isize) [*]isize { +inline fn _nrot(sp: [*]i32) [*]i32 { const a = sp[0]; const b = sp[1]; const c = sp[2]; @@ -226,22 +358,19 @@ inline fn _nrot(sp: [*]isize) [*]isize { sp[0] = b; return sp; } -const nrot = defword(&rot, Flag.ZERO, "-ROT", wrap(_nrot)); -inline fn _twodrop(sp: [*]isize) [*]isize { - return sp[2..]; +inline fn _twodrop(sp: [*]i32) [*]i32 { + return sp + 2; } -const twodrop = defword(&nrot, Flag.ZERO, "2DROP", wrap(_twodrop)); -inline fn _twodup(sp: [*]isize) [*]isize { +inline fn _twodup(sp: [*]i32) [*]i32 { const s = sp - 2; s[1] = sp[1]; s[0] = sp[0]; return s; } -const twodup = defword(&twodrop, Flag.ZERO, "2DUP", wrap(_twodup)); -inline fn _twoswap(sp: [*]isize) [*]isize { +inline fn _twoswap(sp: [*]i32) [*]i32 { const a = sp[0]; const b = sp[1]; const c = sp[2]; @@ -252,691 +381,471 @@ inline fn _twoswap(sp: [*]isize) [*]isize { sp[0] = c; return sp; } -const twoswap = defword(&twodup, Flag.ZERO, "2SWAP", wrap(_twoswap)); -inline fn _qdup(sp: [*]isize) [*]isize { - if (sp[0] != 0) { - const s = sp - 1; - s[0] = sp[0]; - return s; - } - return sp; +inline fn _qdup(sp: [*]i32) [*]i32 { + const s = if (sp[0] == 0) sp else sp - 1; + s[0] = sp[0]; // no-op when TOS is 0 + return s; } -const qdup = defword(&twoswap, Flag.ZERO, "?DUP", wrap(_qdup)); -inline fn _incr(sp: [*]isize) [*]isize { +inline fn _incr(sp: [*]i32) [*]i32 { sp[0] += 1; return sp; } -const incr = defword(&qdup, Flag.ZERO, "1+", wrap(_incr)); -inline fn _decr(sp: [*]isize) [*]isize { +inline fn _decr(sp: [*]i32) [*]i32 { sp[0] -= 1; return sp; } -const decr = defword(&incr, Flag.ZERO, "1-", wrap(_decr)); -inline fn _incrp(sp: [*]isize) [*]isize { - sp[0] += @sizeOf(usize); +inline fn _incrp(sp: [*]i32) [*]i32 { + sp[0] += 4; return sp; } -const incrp = defword(&decr, Flag.ZERO, fmt.comptimePrint("{d}+", .{@sizeOf(usize)}), wrap(_incrp)); -inline fn _decrp(sp: [*]isize) [*]isize { - sp[0] -= @sizeOf(usize); +inline fn _decrp(sp: [*]i32) [*]i32 { + sp[0] -= 4; return sp; } -const decrp = defword(&incrp, Flag.ZERO, fmt.comptimePrint("{d}-", .{@sizeOf(usize)}), wrap(_decrp)); -inline fn _add(sp: [*]isize) [*]isize { +inline fn _add(sp: [*]i32) [*]i32 { sp[1] += sp[0]; - return sp[1..]; + return sp + 1; } -const add = defword(&decrp, Flag.ZERO, "+", wrap(_add)); -inline fn _sub(sp: [*]isize) [*]isize { +inline fn _sub(sp: [*]i32) [*]i32 { sp[1] -= sp[0]; - return sp[1..]; + return sp + 1; } -const sub = defword(&add, Flag.ZERO, "-", wrap(_sub)); -inline fn _mul(sp: [*]isize) [*]isize { +inline fn _mul(sp: [*]i32) [*]i32 { sp[1] *= sp[0]; - return sp[1..]; + return sp + 1; } -const mul = defword(&sub, Flag.ZERO, "*", wrap(_mul)); -inline fn _divmod(sp: [*]isize) [*]isize { +inline fn _divmod(sp: [*]i32) [*]i32 { const a = sp[1]; const b = sp[0]; sp[1] = @rem(a, b); sp[0] = @divTrunc(a, b); return sp; } -const divmod = defword(&mul, Flag.ZERO, "/MOD", wrap(_divmod)); -inline fn _equ(sp: [*]isize) [*]isize { +inline fn _equ(sp: [*]i32) [*]i32 { sp[1] = if (sp[1] == sp[0]) -1 else 0; - return sp[1..]; + return sp + 1; } -const equ = defword(&divmod, Flag.ZERO, "=", wrap(_equ)); -inline fn _nequ(sp: [*]isize) [*]isize { +inline fn _nequ(sp: [*]i32) [*]i32 { sp[1] = if (sp[1] == sp[0]) 0 else -1; - return sp[1..]; + return sp + 1; } -const nequ = defword(&equ, Flag.ZERO, "<>", wrap(_nequ)); -inline fn _lt(sp: [*]isize) [*]isize { +inline fn _lt(sp: [*]i32) [*]i32 { sp[1] = if (sp[1] < sp[0]) -1 else 0; - return sp[1..]; + return sp + 1; } -const lt = defword(&nequ, Flag.ZERO, "<", wrap(_lt)); -inline fn _gt(sp: [*]isize) [*]isize { +inline fn _gt(sp: [*]i32) [*]i32 { sp[1] = if (sp[1] > sp[0]) -1 else 0; - return sp[1..]; + return sp + 1; } -const gt = defword(<, Flag.ZERO, ">", wrap(_gt)); -inline fn _le(sp: [*]isize) [*]isize { +inline fn _le(sp: [*]i32) [*]i32 { sp[1] = if (sp[1] <= sp[0]) -1 else 0; - return sp[1..]; + return sp + 1; } -const le = defword(>, Flag.ZERO, "<=", wrap(_le)); -inline fn _ge(sp: [*]isize) [*]isize { +inline fn _ge(sp: [*]i32) [*]i32 { sp[1] = if (sp[1] >= sp[0]) -1 else 0; - return sp[1..]; + return sp + 1; } -const ge = defword(&le, Flag.ZERO, ">=", wrap(_ge)); -inline fn _zequ(sp: [*]isize) [*]isize { +inline fn _zequ(sp: [*]i32) [*]i32 { sp[0] = if (sp[0] == 0) -1 else 0; return sp; } -const zequ = defword(&ge, Flag.ZERO, "0=", wrap(_zequ)); -inline fn _znequ(sp: [*]isize) [*]isize { +inline fn _znequ(sp: [*]i32) [*]i32 { sp[0] = if (sp[0] != 0) -1 else 0; return sp; } -const znequ = defword(&zequ, Flag.ZERO, "0<>", wrap(_znequ)); -inline fn _zlt(sp: [*]isize) [*]isize { +inline fn _zlt(sp: [*]i32) [*]i32 { sp[0] = if (sp[0] < 0) -1 else 0; return sp; } -const zlt = defword(&znequ, Flag.ZERO, "0<", wrap(_zlt)); -inline fn _zgt(sp: [*]isize) [*]isize { +inline fn _zgt(sp: [*]i32) [*]i32 { sp[0] = if (sp[0] > 0) -1 else 0; return sp; } -const zgt = defword(&zlt, Flag.ZERO, "0>", wrap(_zgt)); -inline fn _zle(sp: [*]isize) [*]isize { +inline fn _zle(sp: [*]i32) [*]i32 { sp[0] = if (sp[0] <= 0) -1 else 0; return sp; } -const zle = defword(&zgt, Flag.ZERO, "0<=", wrap(_zle)); -inline fn _zge(sp: [*]isize) [*]isize { +inline fn _zge(sp: [*]i32) [*]i32 { sp[0] = if (sp[0] >= 0) -1 else 0; return sp; } -const zge = defword(&zle, Flag.ZERO, "0>=", wrap(_zge)); -inline fn _and(sp: [*]isize) [*]isize { +inline fn _and(sp: [*]i32) [*]i32 { sp[1] &= sp[0]; - return sp[1..]; + return sp + 1; } -const and_ = defword(&zge, Flag.ZERO, "AND", wrap(_and)); -inline fn _or(sp: [*]isize) [*]isize { +inline fn _or(sp: [*]i32) [*]i32 { sp[1] |= sp[0]; - return sp[1..]; + return sp + 1; } -const or_ = defword(&and_, Flag.ZERO, "OR", wrap(_or)); -inline fn _xor(sp: [*]isize) [*]isize { +inline fn _xor(sp: [*]i32) [*]i32 { sp[1] ^= sp[0]; - return sp[1..]; + return sp + 1; } -const xor = defword(&or_, Flag.ZERO, "XOR", wrap(_xor)); -inline fn _invert(sp: [*]isize) [*]isize { +inline fn _invert(sp: [*]i32) [*]i32 { sp[0] = ~sp[0]; return sp; } -const invert = defword(&xor, Flag.ZERO, "INVERT", wrap(_invert)); -fn _exit(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { +fn _exit(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { _ = ip; - self.next(sp, rsp[1..], rsp[0], target); + self.next(sp, rsp + 4, @abs(self.readInt(rsp)), target); } -const exit = defword(&invert, Flag.ZERO, "EXIT", &.{.{ .code = _exit }}); -fn _lit(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 1; - s[0] = ip[0].literal; - self.next(s, rsp, ip[1..], target); +fn _lit(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(sp - 4, self.readInt(ip)); + self.next(sp - 4, rsp, ip + 4, target); } -const lit = defword(&exit, Flag.ZERO, "LIT", &.{.{ .code = _lit }}); -inline fn _store(sp: [*]isize) [*]isize { - const p: *isize = @ptrFromInt(@abs(sp[0])); - p.* = sp[1]; - return sp[2..]; -} -const store = defword(&lit, Flag.ZERO, "!", wrap(_store)); - -inline fn _fetch(sp: [*]isize) [*]isize { - const p: *isize = @ptrFromInt(@abs(sp[0])); - sp[0] = p.*; - return sp; +fn _store(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(@abs(self.readInt(sp)), self.readInt(sp + 4)); + self.next(sp + 8, rsp, ip, target); } -const fetch = defword(&store, Flag.ZERO, "@", wrap(_fetch)); -inline fn _addstore(sp: [*]isize) [*]isize { - const p: *[*]u8 = @ptrFromInt(@abs(sp[0])); - p.* += @abs(sp[1]); - return sp[2..]; +fn _fetch(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(sp, self.readInt(@abs(self.readInt(sp)))); + self.next(sp, rsp, ip, target); } -const addstore = defword(&fetch, Flag.ZERO, "+!", wrap(_addstore)); -inline fn _substore(sp: [*]isize) [*]isize { - const p: *[*]u8 = @ptrFromInt(@abs(sp[0])); - p.* -= @abs(sp[1]); - return sp[2..]; +fn _addstore(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const p = @abs(self.readInt(sp)); + self.writeInt(p, self.readInt(p) + self.readInt(sp + 4)); + self.next(sp + 8, rsp, ip, target); } -const substore = defword(&addstore, Flag.ZERO, "-!", wrap(_substore)); -inline fn _storebyte(sp: [*]isize) [*]isize { - const p: [*]u8 = @ptrFromInt(@abs(sp[0])); - const v: u8 = @truncate(@abs(sp[1])); - p[0] = v; - return sp[2..]; +fn _substore(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const p = @abs(self.readInt(sp)); + self.writeInt(p, self.readInt(p) - self.readInt(sp + 4)); + self.next(sp + 8, rsp, ip, target); } -const storebyte = defword(&substore, Flag.ZERO, "C!", wrap(_storebyte)); -inline fn _fetchbyte(sp: [*]isize) [*]isize { - const p: [*]u8 = @ptrFromInt(@abs(sp[0])); - sp[0] = p[0]; - return sp; +fn _storebyte(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const p = @abs(self.readInt(sp)); + const q = @abs(self.readInt(sp + 4)); + self.memory.items.ptr[p] = @truncate(q); + self.next(sp + 8, rsp, ip, target); } -const fetchbyte = defword(&storebyte, Flag.ZERO, "C@", wrap(_fetchbyte)); -inline fn _ccopy(sp: [*]isize) [*]isize { - const p: [*]u8 = @ptrFromInt(@abs(sp[0])); - const q: [*]u8 = @ptrFromInt(@abs(sp[1])); - q[0] = p[0]; - return sp[2..]; +fn _fetchbyte(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const p = @abs(self.readInt(sp)); + self.writeInt(sp, self.memory.items[p]); + self.next(sp, rsp, ip, target); } -const ccopy = defword(&fetchbyte, Flag.ZERO, "C@C!", wrap(_ccopy)); -inline fn _cmove(sp: [*]isize) [*]isize { - const n = @abs(sp[0]); - @memcpy(dest: { - const p: [*]u8 = @ptrFromInt(@abs(sp[1])); - break :dest p[0..n]; - }, source: { - const q: [*]u8 = @ptrFromInt(@abs(sp[2])); - break :source q[0..n]; - }); - sp[2] = sp[1]; - return sp[2..]; -} -const cmove = defword(&ccopy, Flag.ZERO, "CMOVE", wrap(_cmove)); -const state = defword(&cmove, Flag.ZERO, "STATE", attr("state")); +fn _ccopy(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const p = @abs(self.readInt(sp)); + const q = @abs(self.readInt(sp + 4)); -fn _here(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 1; - self.memory.ensureUnusedCapacity(@sizeOf(Instr)) catch @panic("_here cannot ensureUnusedCapacity"); - s[0] = @intCast(@intFromPtr(&self.here)); - self.next(s, rsp, ip, target); + const memory = self.memory.items.ptr; + memory[q] = memory[p]; + self.next(sp + 4, rsp, ip, target); } -const here = defword(&state, Flag.ZERO, "HERE", &.{.{ .code = _here }}); -const latest = defword(&here, Flag.ZERO, "LATEST", attr("latest")); -const sz = defword(&latest, Flag.ZERO, "S0", attr("s0")); -const base = defword(&sz, Flag.ZERO, "BASE", attr("base")); -inline fn _argc(sp: [*]isize) [*]isize { - const s = sp - 1; - const u = @intFromPtr(os.argv.ptr - 1); - s[0] = @intCast(u); - return s; +fn _cmove(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const n = @abs(self.readInt(sp)); + const p = @abs(self.readInt(sp + 4)); + const q = @abs(self.readInt(sp + 8)); + @memcpy(self.slice(p, n), self.slice(q, n)); + self.writeInt(sp + 8, @intCast(p)); + self.next(sp + 8, rsp, ip, target); } -const argc = defword(&base, Flag.ZERO, "(ARGC)", wrap(_argc)); -const version = defword(&if (arch.isWasm()) base else argc, Flag.ZERO, "VERSION", value(47)); -fn _rz(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 1; - const u = @intFromPtr(self.r0); - s[0] = @intCast(u); - self.next(s, rsp, ip, target); +fn _here(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.memory.ensureUnusedCapacity(@sizeOf(Address)) catch @panic("_here cannot ensureUnusedCapacity"); + self.writeInt(sp - 4, @offsetOf(Header, "here")); + self.next(sp - 4, rsp, ip, target); } -const rz = defword(&version, Flag.ZERO, "R0", &.{.{ .code = _rz }}); -fn docol_(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const r = rsp - 1; - r[0] = ip; - self.next(sp, r, target[1..], target); +fn _argc(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.next(sp, rsp, ip, target); } -inline fn _docol(sp: [*]isize) [*]isize { - const s = sp - 1; - s[0] = @intCast(@intFromPtr(&docol_)); - return s; +fn docol(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(rsp - 4, @intCast(ip)); + self.next(sp, rsp - 4, target + 4, target); } -const docol = defword(&rz, Flag.ZERO, "DOCOL", wrap(_docol)); -fn dodoes_(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const r = rsp - 1; - const s = sp - 1; - r[0] = ip; - s[0] = @intCast(@intFromPtr(&target[2])); - self.next(s, r, target[1].word, target); +fn _tor(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(rsp - 4, self.readInt(sp)); + self.next(sp + 4, rsp - 4, ip, target); } -inline fn _dodoes(sp: [*]isize) [*]isize { - const s = sp - 1; - s[0] = @intCast(@intFromPtr(&dodoes_)); - return s; -} -const dodoes = defword(&docol, Flag.ZERO, "DODOES", wrap(_dodoes)); -const f_immed = defword(&dodoes, Flag.ZERO, "F_IMMED", value(@intFromEnum(Flag.IMMED))); -const f_hidden = defword(&f_immed, Flag.ZERO, "F_HIDDEN", value(@intFromEnum(Flag.HIDDEN))); -const f_lenmask = defword(&f_hidden, Flag.ZERO, "F_LENMASK", value(F_LENMASK)); -const sys_exit = defword(&f_lenmask, Flag.ZERO, "SYS_EXIT", value(@intFromEnum(syscalls.X64.exit))); -const sys_open = defword(&sys_exit, Flag.ZERO, "SYS_OPEN", value(@intFromEnum(syscalls.X64.open))); -const sys_close = defword(&sys_open, Flag.ZERO, "SYS_CLOSE", value(@intFromEnum(syscalls.X64.close))); -const sys_read = defword(&sys_close, Flag.ZERO, "SYS_READ", value(@intFromEnum(syscalls.X64.read))); -const sys_write = defword(&sys_read, Flag.ZERO, "SYS_WRITE", value(@intFromEnum(syscalls.X64.write))); -const sys_creat = defword(&sys_write, Flag.ZERO, "SYS_CREAT", value(@intFromEnum(syscalls.X64.creat))); -const sys_brk = defword(&sys_creat, Flag.ZERO, "SYS_BRK", value(@intFromEnum(syscalls.X64.brk))); -const o_rdonly = defword(&sys_brk, Flag.ZERO, "O_RDONLY", value(O_RDONLY)); -const o_wronly = defword(&o_rdonly, Flag.ZERO, "O_WRONLY", value(O_WRONLY)); -const o_rdwr = defword(&o_wronly, Flag.ZERO, "O_RDWR", value(O_RDWR)); -const o_creat = defword(&o_rdwr, Flag.ZERO, "O_CREAT", value(O_CREAT)); -const o_excl = defword(&o_creat, Flag.ZERO, "O_EXCL", value(O_EXCL)); -const o_trunc = defword(&o_excl, Flag.ZERO, "O_TRUNC", value(O_TRUNC)); -const o_append = defword(&o_trunc, Flag.ZERO, "O_APPEND", value(O_APPEND)); -const o_nonblock = defword(&o_append, Flag.ZERO, "O_NONBLOCK", value(O_NONBLOCK)); - -fn _tor(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const r = rsp - 1; - const t: [*]Instr = @ptrFromInt(@abs(sp[0])); - r[0] = t; - self.next(sp[1..], r, ip, target); -} -const tor = defword(&o_nonblock, Flag.ZERO, ">R", &.{.{ .code = _tor }}); - -fn _fromr(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 1; - s[0] = @intCast(@intFromPtr(rsp[0])); - self.next(s, rsp[1..], ip, target); +fn _fromr(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(sp - 4, self.readInt(rsp)); + self.next(sp - 4, rsp + 4, ip, target); } -const fromr = defword(&tor, Flag.ZERO, "R>", &.{.{ .code = _fromr }}); -fn _rspfetch(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 1; - s[0] = @intCast(@intFromPtr(rsp)); - self.next(s, rsp, ip, target); +fn _rspfetch(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(sp - 4, @intCast(rsp)); + self.next(sp - 4, rsp, ip, target); } -const rspfetch = defword(&fromr, Flag.ZERO, "RSP@", &.{.{ .code = _rspfetch }}); -fn _rspstore(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { +fn _rspstore(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { _ = rsp; - const s = @abs(sp[0]); - const t: [*][*]const Instr = @ptrFromInt(s); - self.next(sp[1..], t, ip, target); + self.next(sp + 4, @abs(self.readInt(sp)), ip, target); } -const rspstore = defword(&rspfetch, Flag.ZERO, "RSP!", &.{.{ .code = _rspstore }}); -fn _rdrop(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - self.next(sp, rsp[1..], ip, target); +fn _rdrop(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.next(sp, rsp + 4, ip, target); } -const rdrop = defword(&rspstore, Flag.ZERO, "RDROP", &.{.{ .code = _rdrop }}); -inline fn _dspfetch(sp: [*]isize) [*]isize { - const s = sp - 1; - s[0] = @intCast(@intFromPtr(sp)); - return s; +fn _dspfetch(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(sp - 4, @intCast(sp)); + self.next(sp - 4, rsp, ip, target); } -const dspfetch = defword(&rdrop, Flag.ZERO, "DSP@", wrap(_dspfetch)); -inline fn _dspstore(sp: [*]isize) [*]isize { - const u = @abs(sp[0]); - const p: [*]isize = @ptrFromInt(u); - return p; +fn _dspstore(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.next(@abs(self.readInt(sp)), rsp, ip, target); } -const dspstore = defword(&dspfetch, Flag.ZERO, "DSP!", wrap(_dspstore)); -inline fn _key(sp: [*]isize) [*]isize { - const s = sp - 1; - s[0] = @intCast(key()); - return s; +fn _key(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(sp - 4, self.key() catch std.process.exit(0)); + self.next(sp - 4, rsp, ip, target); } -const key_ = defword(&dspstore, Flag.ZERO, "KEY", wrap(_key)); -inline fn _emit(sp: [*]isize) [*]isize { - const c: u8 = @truncate(@abs(sp[0])); - stdout.print("{c}", .{c}) catch {}; - stdout.flush() catch {}; - return sp[1..]; +fn _emit(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const c: u8 = @truncate(@abs(self.readInt(sp))); + self.writer.print("{c}", .{c}) catch {}; + self.writer.flush() catch {}; + self.next(sp + 4, rsp, ip, target); } -const emit = defword(&key_, Flag.ZERO, "EMIT", wrap(_emit)); -fn _word(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 2; - const u = @intFromPtr(&self.buffer); - s[1] = @intCast(u); - s[0] = @intCast(self.word()); - self.next(s, rsp, ip, target); +fn _word(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const word = self.word() catch std.process.exit(0); + self.writeInt(sp - 4, @offsetOf(Header, "buffer")); + self.writeInt(sp - 8, @intCast(word.len)); + self.next(sp - 8, rsp, ip, target); } -const word_ = defword(&emit, Flag.ZERO, "WORD", &.{.{ .code = _word }}); -fn _number(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - if (fmt.parseInt(isize, buf: { - const s: [*]u8 = @ptrFromInt(@abs(sp[1])); - break :buf s[0..@abs(sp[0])]; - }, @truncate(@abs(self.base)))) |num| { - sp[1] = num; - sp[0] = 0; +fn _number(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + if (fmt.parseInt(i32, self.slice(@abs(self.readInt(sp + 4)), @abs(self.readInt(sp))), @truncate(@abs(self.readInt(@offsetOf(Header, "base")))))) |num| { + self.writeInt(sp, 0); + self.writeInt(sp + 4, num); } else |_| {} self.next(sp, rsp, ip, target); } -const number = defword(&word_, Flag.ZERO, "NUMBER", &.{.{ .code = _number }}); -fn _find(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s: [*]u8 = @ptrFromInt(@abs(sp[1])); - const v = self.find(s[0..@abs(sp[0])]); +fn _find(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const v = self.find(self.slice(@abs(self.readInt(sp + 4)), @abs(self.readInt(sp)))); - sp[1] = @intCast(@intFromPtr(v)); - self.next(sp[1..], rsp, ip, target); + self.writeInt(sp + 4, @intCast(if (v) |ptr| @intFromPtr(ptr) - @intFromPtr(self.memory.items.ptr) else 0)); + self.next(sp + 4, rsp, ip, target); } -const find_ = defword(&number, Flag.ZERO, "FIND", &.{.{ .code = _find }}); -inline fn _tcfa(sp: [*]isize) [*]isize { - const w: [*]const Instr = @ptrFromInt(@abs(sp[0])); - sp[0] = @intCast(@intFromPtr(codeFieldAddress(w))); +inline fn _tcfa(sp: [*]i32) [*]i32 { + const w: Address = @enumFromInt(@abs(sp[0])); + sp[0] = @intCast(codeFieldAddress(w)); return sp; } -const tcfa = defword(&find_, Flag.ZERO, ">CFA", wrap(_tcfa)); -const tdfa = defword( - &tcfa, - Flag.ZERO, - ">DFA", - words(&.{ &tcfa, &incrp, &exit, &exit }), -); - -fn _create(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const c = @abs(sp[0]); - const s: [*]u8 = @ptrFromInt(@abs(sp[1])); - const code = self.memory.addManyAsSlice(@sizeOf(Word)) catch @panic("_create cannot addManyAsSlice"); - var new: *Word = @ptrCast(@alignCast(code.ptr)); - new.link = self.latest; - new.flag = @truncate(c); - @memcpy(new.name[0..c], s[0..c]); - @memset(new.name[c..F_LENMASK], 0); - self.latest = new; - self.here = self.memory.items.ptr + self.memory.items.len; - self.next(sp[2..], rsp, ip, target); -} -const create = defword(&tdfa, Flag.ZERO, "CREATE", &.{.{ .code = _create }}); - -fn _comma(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s: isize = sp[0]; - const instr: Instr = if (s < 0x1000) - .{ .literal = s } - else if (s == @intFromPtr(&docol_)) - .{ .code = docol_ } - else - .{ .word = blk: { - const p: [*]const Instr = @ptrFromInt(@abs(s)); - break :blk p; - } }; - self.append(instr); - self.next(sp[1..], rsp, ip, target); -} -const comma = defword(&create, Flag.ZERO, ",", &.{.{ .code = _comma }}); - -fn _lbrac(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - self.state = 0; + +fn _create(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const name = self.slice(@abs(self.readInt(sp + 4)), @abs(self.readInt(sp))); + var word: Word.Data = .{ + .link = @enumFromInt(self.readInt(@offsetOf(Header, "latest"))), + .flag = @truncate(name.len), + .name = @splat(0), + .code = undefined, + }; + @memcpy(word.name[0..name.len], name); + self.writeInt(@offsetOf(Header, "latest"), @intCast(self.memory.items.len)); + self.memory.appendSlice(mem.asBytes(&word)) catch @panic("_create cannot appendSlice"); + self.memory.items.len -= 4; // .code is undefined + self.writeInt(@offsetOf(Header, "here"), @intCast(self.memory.items.len)); + self.next(sp + 8, rsp, ip, target); +} + +fn _comma(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.append(.{ .literal = self.readInt(sp) }); + self.next(sp + 4, rsp, ip, target); +} + +fn _lbrac(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(@offsetOf(Header, "state"), 0); self.next(sp, rsp, ip, target); } -const lbrac = defword( - &comma, - Flag.IMMED, - "[", - &.{.{ .code = _lbrac }}, -); -fn _rbrac(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - self.state = 1; +fn _rbrac(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(@offsetOf(Header, "state"), 1); self.next(sp, rsp, ip, target); } -const rbrac = defword(&lbrac, Flag.ZERO, "]", &.{.{ .code = _rbrac }}); -fn _immediate(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - self.latest.flag ^= @intFromEnum(Flag.IMMED); +fn _immediate(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const w: *Word.Data = @ptrCast(@alignCast(self.slice(@abs(self.readInt(@offsetOf(Header, "latest"))), @sizeOf(Word.Data)))); + w.flag ^= @intFromEnum(Flag.IMMED); self.next(sp, rsp, ip, target); } -const immediate = defword( - &rbrac, - Flag.IMMED, - "IMMEDIATE", - &.{.{ .code = _immediate }}, -); -inline fn _hidden(sp: [*]isize) [*]isize { - const w: *Word = @ptrFromInt(@abs(sp[0])); +fn _hidden(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const w: *Word.Data = @ptrCast(@alignCast(self.slice(@abs(self.readInt(sp)), @sizeOf(Word.Data)))); w.flag ^= @intFromEnum(Flag.HIDDEN); - return sp[1..]; -} -const hidden = defword(&immediate, Flag.ZERO, "HIDDEN", wrap(_hidden)); -const hide = defword( - &hidden, - Flag.ZERO, - "HIDE", - words(&.{ &word_, &find_, &hidden, &exit }), -); -const colon = defword( - &hide, - Flag.ZERO, - ":", - words(&.{ &word_, &create, &docol, &comma, &latest, &fetch, &hidden, &rbrac, &exit, &exit }), -); - -fn _tick(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 1; - const u = @intFromPtr(ip[0].word); - s[0] = @intCast(u); - self.next(s, rsp, ip[1..], target); -} -const tick = defword(&colon, Flag.ZERO, "'", &.{.{ .code = _tick }}); -const semicolon = defword( - &tick, - Flag.IMMED, - ";", - words(&.{ &tick, &exit, &comma, &latest, &fetch, &hidden, &lbrac, &exit }), -); - -fn _branch(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const n = @divTrunc(ip[0].literal, @sizeOf(Instr)); + self.next(sp + 4, rsp, ip, target); +} + +fn _tick(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.writeInt(sp - 4, self.readInt(ip)); + self.next(sp - 4, rsp, ip + 4, target); +} + +fn _branch(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const n = self.readInt(ip); const a = @abs(n); const p = if (n < 0) ip - a else ip + a; self.next(sp, rsp, p, target); } -const branch = defword(&semicolon, Flag.ZERO, "BRANCH", &.{.{ .code = _branch }}); -fn _zbranch(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - if (sp[0] == 0) - return @call(.always_tail, _branch, .{ self, sp[1..], rsp, ip, target }); - self.next(sp[1..], rsp, ip[1..], target); +fn _zbranch(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + if (self.readInt(sp) == 0) + return @call(.always_tail, _branch, .{ self, sp + 4, rsp, ip, target }); + self.next(sp + 4, rsp, ip + 4, target); } -const zbranch = defword(&branch, Flag.ZERO, "0BRANCH", &.{.{ .code = _zbranch }}); -fn _litstring(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const c = @abs(ip[0].literal); - const s = sp - 2; - s[1] = @intCast(@intFromPtr(&ip[1])); - s[0] = @intCast(c); - const n = @abs(1 + @divTrunc(c + @sizeOf(Instr), @sizeOf(Instr))); - self.next(s, rsp, ip[n..], target); +fn _litstring(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const c = self.readInt(ip); + self.writeInt(sp - 4, @intCast(ip + 4)); + self.writeInt(sp - 8, c); + self.next(sp - 8, rsp, (ip + @abs(c) + 7) & ~@as(usize, 3), target); } -const litstring = defword(&zbranch, Flag.ZERO, "LITSTRING", &.{.{ .code = _litstring }}); -inline fn _tell(sp: [*]isize) [*]isize { - const p: [*]u8 = @ptrFromInt(@abs(sp[1])); - _ = stdout.write(p[0..@abs(sp[0])]) catch -1; - stdout.flush() catch {}; - return sp[2..]; +fn _tell(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const n = @abs(self.readInt(sp)); + const p = @abs(self.readInt(sp + 4)); + _ = self.writer.write(self.slice(p, n)) catch -1; + self.writer.flush() catch {}; + self.next(sp + 8, rsp, ip, target); } -const tell = defword(&litstring, Flag.ZERO, "TELL", wrap(_tell)); -fn _interpret(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const c = self.word(); +fn _interpret(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const word = self.word() catch return; var s = sp; - if (self.find(self.buffer[0..c])) |new| { - const tgt = codeFieldAddress(@ptrCast(new)); - if ((new.flag & @intFromEnum(Flag.IMMED)) != 0 or self.state == 0) { - return @call(.always_tail, tgt[0].code, .{ self, sp, rsp, ip, tgt }); + if (self.find(word)) |new| { + const tgt = @intFromPtr(&new.code) - @intFromPtr(self.memory.items.ptr); + if ((new.flag & @intFromEnum(Flag.IMMED)) != 0 or self.readInt(@offsetOf(Header, "state")) == 0) { + return @call(.always_tail, primitives[@intFromEnum(new.code)], .{ self, sp, rsp, ip, tgt }); } else { - self.append(.{ .word = tgt }); + self.append(.{ .word = @enumFromInt(tgt) }); } - } else if (fmt.parseInt(isize, self.buffer[0..c], @truncate(@abs(self.base)))) |a| { - if (self.state == 1) { - self.append(.{ .word = &.{.{ .code = _lit }} }); + } else if (fmt.parseInt(i32, word, @intCast(self.readInt(@offsetOf(Header, "base"))))) |a| { + if (self.readInt(@offsetOf(Header, "state")) == 1) { + self.append(.{ .word = .LIT }); self.append(.{ .literal = a }); } else { - s = sp - 1; - s[0] = a; + s -= 4; + self.writeInt(s, a); } } else |_| { - std.debug.print("PARSE ERROR: {s}\n", .{self.buffer[0..c]}); + if (word.len == 1 and word[0] == std.ascii.control_code.del) + return; + std.debug.print("PARSE ERROR: {s}\n", .{word}); std.process.exit(0); } self.next(s, rsp, ip, target); } -const interpret = defword(&tell, Flag.ZERO, "INTERPRET", &.{.{ .code = _interpret }}); -const _quit: [7]Instr = .{ - .{ .code = docol_ }, - .{ .word = codeFieldAddress(&rz) }, - .{ .word = codeFieldAddress(&rspstore) }, - .{ .word = codeFieldAddress(&interpret) }, - .{ .word = codeFieldAddress(&branch) }, - .{ .literal = -2 * @sizeOf(Instr) }, - .{ .word = codeFieldAddress(&exit) }, -}; -const quit = defword(&interpret, Flag.ZERO, "QUIT", &_quit); -fn _char(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const s = sp - 1; - _ = self.word(); - s[0] = self.buffer[0]; - self.next(s, rsp, ip, target); +fn _char(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const buf = self.word() catch std.process.exit(0); + self.writeInt(sp - 4, buf[0]); + self.next(sp - 4, rsp, ip, target); } -const char = defword(&quit, Flag.ZERO, "CHAR", &.{.{ .code = _char }}); -fn _execute(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { +fn _execute(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { _ = target; - const target_: *Instr = @ptrFromInt(@abs(sp[0])); - return @call(.always_tail, target_.code, .{ self, sp[1..], rsp, ip, target_[0..0] }); + const target_ = @abs(self.readInt(sp)); + const code = @abs(self.readInt(target_)); + return @call(.always_tail, primitives[code], .{ self, sp + 4, rsp, ip, target_ }); } -const execute = defword(&char, Flag.ZERO, "EXECUTE", &.{.{ .code = _execute }}); -inline fn _syscall3(sp: [*]isize) [*]isize { +inline fn _syscall3(sp: [*]i32) [*]i32 { const number_: syscalls.X64 = @enumFromInt(sp[0]); switch (number_) { .open => { - const p: usize = @intCast(sp[1]); + const p: usize = @abs(sp[1]); const file_path: [*:0]u8 = @ptrFromInt(p); - const flags: u32 = @intCast(sp[2]); - const perm: fs.File.Mode = @intCast(sp[3]); - sp[3] = if (fs.cwd().createFileZ(file_path, .{ - .read = (flags & (O_RDONLY | O_RDWR)) != 0, - .truncate = (flags & O_TRUNC) != 0, - .exclusive = (flags & O_EXCL) != 0, - .lock_nonblocking = (flags & O_NONBLOCK) != 0, - .mode = perm, - })) |file| @intCast(file.handle) else |_| -1; + const mode: std.c.mode_t = @truncate(@abs(sp[3])); + sp[3] = std.c.openat(std.c.AT.FDCWD, file_path, openFlags(@abs(sp[2])), mode); }, .read => { - const file: fs.File = .{ .handle = @intCast(sp[1]) }; - const p: usize = @intCast(sp[2]); + const fd: std.c.fd_t = @intCast(sp[1]); + const p: usize = @abs(sp[2]); const buf: [*]u8 = @ptrFromInt(p); const n: usize = @intCast(sp[3]); - sp[3] = if (file.read(buf[0..n])) |m| @intCast(m) else |_| -1; + sp[3] = @intCast(std.c.read(fd, buf, n)); }, .write => { - const file: fs.File = .{ .handle = @intCast(sp[1]) }; - const p: usize = @intCast(sp[2]); + const fd: std.c.fd_t = @intCast(sp[1]); + const p: usize = @abs(sp[2]); const buf: [*]u8 = @ptrFromInt(p); const n: usize = @intCast(sp[3]); - sp[3] = if (file.write(buf[0..n])) |m| @intCast(m) else |_| -1; + sp[3] = @intCast(std.c.write(fd, buf, n)); }, else => {}, } return sp[3..]; } -const syscall3 = defword(&execute, Flag.ZERO, "SYSCALL3", wrap(_syscall3)); -inline fn _syscall2(sp: [*]isize) [*]isize { +inline fn _syscall2(sp: [*]i32) [*]i32 { const number_: syscalls.X64 = @enumFromInt(sp[0]); switch (number_) { .open => { - const p: usize = @intCast(sp[1]); + const p: usize = @abs(sp[1]); const file_path: [*:0]u8 = @ptrFromInt(p); - const flags: u32 = @intCast(sp[2]); - sp[2] = if (fs.cwd().openFileZ(file_path, .{ - .mode = @enumFromInt(flags & (O_RDONLY | O_WRONLY | O_RDWR)), - .lock_nonblocking = (flags & O_NONBLOCK) != 0, - })) |file| @intCast(file.handle) else |_| -1; + sp[2] = std.c.openat(std.c.AT.FDCWD, file_path, openFlags(@abs(sp[2]))); }, else => {}, } return sp[2..]; } -const syscall2 = defword(&syscall3, Flag.ZERO, "SYSCALL2", wrap(_syscall2)); -fn _syscall1(self: *Interp, sp: [*]isize, rsp: [*][*]const Instr, ip: [*]const Instr, target: [*]const Instr) callconv(conv) void { - const number_: syscalls.X64 = @enumFromInt(sp[0]); +fn _syscall1(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + const number_: syscalls.X64 = @enumFromInt(self.readInt(sp)); switch (number_) { .exit => { - const status: u8 = @truncate(@abs(sp[1])); + const status: u8 = @truncate(@abs(self.readInt(sp + 4))); std.process.exit(status); }, .close => { - const file: fs.File = .{ .handle = @intCast(sp[1]) }; - file.close(); + const file: std.c.fd_t = @intCast(self.readInt(sp + 4)); + self.writeInt(sp + 4, std.c.close(file)); }, .brk => { - const m = @abs(sp[1]); - const p: *std.heap.FixedBufferAllocator = @ptrCast(@alignCast(self.memory.allocator.ptr)); - if (m > 0) { - const n = if (arch.isWasm()) - @wasmMemoryGrow(0, @divTrunc(m, 0x10000)) - else - os.linux.syscall1(.brk, m); - if (n < m) - @panic("brk syscall failed"); - p.buffer.len = m - @intFromPtr(p.buffer.ptr); - } - sp[1] = @intCast(@intFromPtr(p.buffer.ptr + p.buffer.len)); + const m = self.memory.capacity; + const n = @abs(self.readInt(sp + 4)); + self.memory.ensureTotalCapacityPrecise(m + n) catch @panic("_syscall1 cannot ensureTotalCapacityPrecise"); + self.writeInt(sp + 4, @intCast(m)); }, else => {}, } - self.next(sp[1..], rsp, ip, target); + self.next(sp + 4, rsp, ip, target); } -const syscall1 = defword(&syscall2, Flag.ZERO, "SYSCALL1", &.{.{ .code = _syscall1 }}); -inline fn _syscall0(sp: [*]isize) [*]isize { +inline fn _syscall0(sp: [*]i32) [*]i32 { const number_: syscalls.X64 = @enumFromInt(sp[0]); switch (number_) { .getppid => { @@ -949,33 +858,374 @@ inline fn _syscall0(sp: [*]isize) [*]isize { } return sp; } -var syscall0 = defword(&syscall1, Flag.ZERO, "SYSCALL0", wrap(_syscall0)); - -var memory: [0x800000]u8 linksection(".bss") = undefined; -pub fn main() callconv(conv) void { - const N = 0x20; - var stack: [N]isize = undefined; - const sp = stack[N..]; - var return_stack: [N][*]const Instr = undefined; - const rsp = return_stack[N..]; - var fba: std.heap.FixedBufferAllocator = .init(&memory); - var m: std.array_list.AlignedManaged(u8, mem.Alignment.of(Instr)) = .init(fba.allocator()); - defer m.deinit(); - var env: Interp = .init(sp, rsp, &m); - const target = &_quit; - const cold_start: [1]Instr = .{.{ .word = target }}; - const ip: [*]const Instr = &cold_start; +const primitives = [_]*const Code{ + docol, + wrap(_drop), + wrap(_swap), + wrap(_dup), + wrap(_over), + wrap(_rot), + wrap(_nrot), + wrap(_twodrop), + wrap(_twodup), + wrap(_twoswap), + wrap(_qdup), + wrap(_incr), + wrap(_decr), + wrap(_incrp), + wrap(_decrp), + wrap(_add), + wrap(_sub), + wrap(_mul), + wrap(_divmod), + wrap(_equ), + wrap(_nequ), + wrap(_lt), + wrap(_gt), + wrap(_le), + wrap(_ge), + wrap(_zequ), + wrap(_znequ), + wrap(_zlt), + wrap(_zgt), + wrap(_zle), + wrap(_zge), + wrap(_and), + wrap(_or), + wrap(_xor), + wrap(_invert), + _exit, + _lit, + _store, + _fetch, + _addstore, + _substore, + _storebyte, + _fetchbyte, + _ccopy, + _cmove, + value(@offsetOf(Header, "state")), + value(@offsetOf(Header, "here")), + value(@offsetOf(Header, "latest")), + value(@offsetOf(Header, "s0")), + value(@offsetOf(Header, "base")), + _argc, + value(47), + value(@offsetOf(Header, "input_buffer")), + value(0), + value(@intFromEnum(Flag.IMMED)), + value(@intFromEnum(Flag.HIDDEN)), + value(F_LENMASK), + value(@intFromEnum(syscalls.X64.exit)), + value(@intFromEnum(syscalls.X64.open)), + value(@intFromEnum(syscalls.X64.close)), + value(@intFromEnum(syscalls.X64.read)), + value(@intFromEnum(syscalls.X64.write)), + value(@intFromEnum(syscalls.X64.creat)), + value(@intFromEnum(syscalls.X64.brk)), + value(O_RDONLY), + value(O_WRONLY), + value(O_RDWR), + value(O_CREAT), + value(O_EXCL), + value(O_TRUNC), + value(O_APPEND), + value(O_NONBLOCK), + _tor, + _fromr, + _rspfetch, + _rspstore, + _rdrop, + _dspfetch, + _dspstore, + _key, + _emit, + _word, + _number, + _find, + wrap(_tcfa), + _create, + _comma, + _lbrac, + _rbrac, + _immediate, + _hidden, + _tick, + _branch, + _zbranch, + _litstring, + _tell, + _interpret, + _char, + _execute, + wrap(_syscall3), + wrap(_syscall2), + _syscall1, + wrap(_syscall0), +}; - target[0].code(&env, sp, rsp, ip, target); +const numBytes = @sizeOf(Header) + @sizeOf(Word.Data) * 107 + 30 * @sizeOf(Address.Data); +fn defwords() align(4) [numBytes]u8 { + @setEvalBranchQuota(8_650); + const names = + \\DROP SWAP DUP OVER ROT -ROT 2DROP 2DUP 2SWAP ?DUP 1+ 1- 4+ 4- + - * /MOD = <> < > <= >= 0= 0<> 0< 0> 0<= 0>= + \\AND OR XOR INVERT EXIT LIT ! @ +! -! C! C@ C@C! CMOVE STATE HERE LATEST S0 BASE (ARGC) VERSION R0 DOCOL + \\F_IMMED F_HIDDEN F_LENMASK SYS_EXIT SYS_OPEN SYS_CLOSE SYS_READ SYS_WRITE SYS_CREAT SYS_BRK + \\O_RDONLY O_WRONLY O_RDWR O_CREAT O_EXCL O_TRUNC O_APPEND O_NONBLOCK >R R> RSP@ RSP! RDROP DSP@ DSP! + \\KEY EMIT WORD NUMBER FIND >CFA >DFA CREATE , [ ] IMMEDIATE HIDDEN HIDE : ; ' BRANCH 0BRANCH LITSTRING TELL + \\INTERPRET QUIT CHAR EXECUTE SYSCALL3 SYSCALL2 SYSCALL1 SYSCALL0 + ; + const immediate = "[ IMMEDIATE ;"; + const composite: std.StaticStringMap([]const u8) = .initComptime(.{ + .{ ">DFA", ">CFA 4+ EXIT" }, + .{ ":", "WORD CREATE LIT 0 , LATEST @ HIDDEN ] EXIT" }, + .{ ";", "LIT EXIT , LATEST @ HIDDEN [ EXIT" }, + .{ "HIDE", "WORD FIND HIDDEN EXIT" }, + .{ "QUIT", "R0 RSP! INTERPRET BRANCH -8" }, + }); + var latest: u32 = 0; + var code: u32 = 1; + var buffer: [numBytes]u8 align(4) = @splat(0); + var writer: std.Io.Writer = .fixed(&buffer); + writer.advance(@sizeOf(Header)); + + var iter_name = mem.tokenizeAny(u8, names, " \n"); + while (iter_name.next()) |name| { + const definition = composite.get(name) orelse ""; + var word: Word.Data = .{ + .link = @enumFromInt(latest), + .flag = @truncate(name.len | if (mem.find(u8, immediate, name)) |_| @intFromEnum(Flag.IMMED) else 0), + .name = @splat(0), + .code = @enumFromInt(if (definition.len > 0) 0 else code), + }; + @memcpy(word.name[0..name.len], name); + latest = @truncate(writer.end); + code += if (definition.len > 0) 0 else 1; + writer.writeStruct(word, .native) catch unreachable; + + var it = mem.tokenizeScalar(u8, definition, ' '); + while (it.next()) |item| { + if (fmt.parseInt(i32, item, 10)) |num| { + writer.writeInt(i32, num, .native) catch unreachable; + } else |_| { + var node = latest; + while (writer.buffer[node + 4] & F_LENMASK != item.len or !mem.eql(u8, writer.buffer[node + 5 ..][0..item.len], item)) + node = @bitCast(writer.buffer[node..][0..4].*); + writer.writeInt(u32, node + @offsetOf(Word.Data, "code"), .native) catch unreachable; + } + } + } + const here = writer.end; + + var quit = latest; + while (writer.buffer[quit + 4] != 4 or !mem.eql(u8, writer.buffer[quit + 5 ..][0..4], "QUIT")) + quit = @bitCast(writer.buffer[quit..][0..4].*); + + const header: Header = .{ + .stack = @splat(0), + .return_stack = @splat(0), + .input_buffer = @splat(0), + .output_buffer = @splat(0), + .state = 0, + .here = @truncate(here), + .latest = latest, + .s0 = @offsetOf(Header, "return_stack"), + .base = 10, + .buffer = @splat(0), + .cold_start = .{quit + @offsetOf(Word.Data, "code")}, + }; + writer.undo(here); // rewind to start + writer.writeStruct(header, .native) catch unreachable; + return buffer; +} + +test defwords { + const buffer: [numBytes]u8 align(4) = comptime defwords(); + const start = @sizeOf(Header); + const header: *const Header = @ptrCast(buffer[0..start]); + const words: [*]const Word.Data = @ptrCast(buffer[start..]); + try testing.expectEqual(numBytes, header.here); + try testing.expectEqual(.sentinel, words[0].link); + try testing.expectEqualSlices(u8, "DROP", words[0].name[0..words[0].flag]); + try testing.expectEqual(.DROP, words[0].code); + try testing.expectEqual(@as(Address, @enumFromInt(start)), words[1].link); + try testing.expectEqualSlices(u8, "SWAP", words[1].name[0..words[1].flag]); + try testing.expectEqual(.LIT, words[35].code); + try testing.expectEqual(.R0, words[51].code); + try testing.expectEqualSlices(u8, "DOCOL", words[52].name[0..words[52].flag]); + try testing.expectEqual(.@">CFA", words[83].code); + try testing.expectEqualSlices(u8, ">DFA", words[84].name[0..words[84].flag]); + try testing.expectEqual(0, @intFromEnum(words[84].code)); + // This is a kludge. >DFA is composite so its code field is followed by 3 data field. + // >DFA's first data field occupies words[85].link + try testing.expectEqual(@as(Address, @enumFromInt(codeFieldAddress(words[84].link))), words[85].link); + + var node: u32 = @offsetOf(Header, "cold_start"); // points to CFA of "QUIT" + node = mem.readInt(u32, buffer[node..][0..4], .native); + try testing.expectEqual(0, mem.readInt(u32, buffer[node..][0..4], .native)); // CFA of "QUIT" is DOCOL ✓ + node = mem.readInt(u32, buffer[node + 4 ..][0..4], .native); // follow link to CFA of "R0" + try testing.expectEqual(@intFromEnum(Word.R0), mem.readInt(u32, buffer[node..][0..4], .native)); // CFA of "R0" is R0 ✓ +} + +test Interp { + const input = + \\: / /MOD SWAP DROP ; + \\: '\n' 10 ; + \\: BL 32 ; + \\: CR '\n' EMIT ; + \\: SPACE BL EMIT ; + \\: NEGATE 0 SWAP - ; + \\: TRUE 1 ; + \\: FALSE 0 ; + \\: LITERAL IMMEDIATE ' LIT , , ; + \\: ':' [ CHAR : ] LITERAL ; + \\: ';' [ CHAR ; ] LITERAL ; + \\: '"' [ CHAR " ] LITERAL ; + \\: 'A' [ CHAR A ] LITERAL ; + \\: '0' [ CHAR 0 ] LITERAL ; + \\: '-' [ CHAR - ] LITERAL ; + \\: [COMPILE] IMMEDIATE WORD FIND >CFA , ; + \\: RECURSE IMMEDIATE LATEST @ >CFA , ; + \\: IF IMMEDIATE ' 0BRANCH , HERE @ 0 , ; + \\: THEN IMMEDIATE DUP HERE @ SWAP - SWAP ! ; + \\: ELSE IMMEDIATE ' BRANCH , HERE @ 0 , SWAP DUP HERE @ SWAP - SWAP ! ; + \\: BEGIN IMMEDIATE HERE @ ; + \\: AGAIN IMMEDIATE ' BRANCH , HERE @ - , ; + \\: WHILE IMMEDIATE ' 0BRANCH , HERE @ 0 , ; + \\: REPEAT IMMEDIATE ' BRANCH , SWAP HERE @ - , DUP HERE @ SWAP - SWAP ! ; + \\: NIP SWAP DROP ; + \\: PICK 1+ 4 * DSP@ + @ ; + \\: SPACES BEGIN DUP 0> WHILE SPACE 1- REPEAT DROP ; + \\: U. BASE @ /MOD ?DUP IF RECURSE THEN DUP 10 < IF '0' ELSE 10 - 'A' THEN + EMIT ; + \\: .S DSP@ BEGIN DUP S0 @ < WHILE DUP @ U. 4+ SPACE REPEAT DROP ; + \\: UWIDTH BASE @ / ?DUP IF RECURSE 1+ ELSE 1 THEN ; + \\: U.R SWAP DUP UWIDTH ROT SWAP - SPACES U. ; + \\: .R SWAP DUP 0< IF NEGATE 1 SWAP ROT 1- ELSE 0 SWAP ROT THEN SWAP DUP UWIDTH ROT SWAP - SPACES SWAP IF '-' EMIT THEN U. ; + \\: . 0 .R SPACE ; + \\: U. U. SPACE ; + \\: WITHIN -ROT OVER <= IF > IF TRUE ELSE FALSE THEN ELSE 2DROP FALSE THEN ; + \\: ALIGNED 3 + -4 AND ; + \\: ALIGN HERE @ ALIGNED HERE ! ; + \\: C, HERE @ C! 1 HERE +! ; + \\: S" IMMEDIATE STATE @ IF + \\ ' LITSTRING , HERE @ 0 , BEGIN KEY DUP '"' <> WHILE C, REPEAT DROP DUP HERE @ SWAP - 4- SWAP ! ALIGN ELSE + \\ HERE @ BEGIN KEY DUP '"' <> WHILE OVER C! 1+ REPEAT DROP HERE @ - HERE @ SWAP THEN ; + \\: ." IMMEDIATE STATE @ IF [COMPILE] S" ' TELL , ELSE BEGIN KEY DUP '"' = IF DROP EXIT THEN EMIT AGAIN THEN ; + \\: CELLS 4 * ; + \\: ID. 4+ DUP C@ F_LENMASK AND BEGIN DUP 0> WHILE SWAP 1+ DUP C@ EMIT SWAP 1- REPEAT 2DROP ; + \\: ?IMMEDIATE 4+ C@ F_IMMED AND ; + \\: CASE IMMEDIATE 0 ; + \\: OF IMMEDIATE ' OVER , ' = , [COMPILE] IF ' DROP , ; + \\: ENDOF IMMEDIATE [COMPILE] ELSE ; + \\: ENDCASE IMMEDIATE ' DROP , BEGIN ?DUP WHILE [COMPILE] THEN REPEAT ; + \\: CFA> LATEST @ BEGIN ?DUP WHILE 2DUP SWAP < IF NIP EXIT THEN @ REPEAT DROP 0 ; + \\: SEE WORD FIND HERE @ LATEST @ BEGIN 2 PICK OVER <> WHILE NIP DUP @ REPEAT DROP SWAP + \\ ':' EMIT SPACE DUP ID. SPACE DUP ?IMMEDIATE IF ." IMMEDIATE " THEN >DFA + \\ BEGIN 2DUP > WHILE DUP @ + \\ CASE + \\ ' LIT OF 4+ DUP @ . ENDOF + \\ ' LITSTRING OF [ CHAR S ] LITERAL EMIT '"' EMIT SPACE 4+ DUP @ SWAP 4+ SWAP 2DUP TELL '"' EMIT SPACE + ALIGNED 4- ENDOF + \\ ' 0BRANCH OF ." 0BRANCH ( " 4+ DUP @ . ." ) " ENDOF + \\ ' BRANCH OF ." BRANCH ( " 4+ DUP @ . ." ) " ENDOF + \\ ' ' OF [ CHAR ' ] LITERAL EMIT SPACE 4+ DUP CFA> ID. SPACE ENDOF + \\ ' EXIT OF 2DUP 4+ <> IF ." EXIT " THEN ENDOF + \\ DUP CFA> ID. SPACE + \\ ENDCASE + \\ 4+ + \\ REPEAT + \\ ';' EMIT CR 2DROP ; + \\: ['] IMMEDIATE ' LIT , ; + \\: EXCEPTION-MARKER RDROP 0 ; + \\: CATCH DSP@ 4+ >R ' EXCEPTION-MARKER 4+ >R EXECUTE ; + \\: THROW ?DUP IF RSP@ BEGIN DUP R0 4- < WHILE DUP @ ' EXCEPTION-MARKER 4+ = IF 4+ RSP! DUP DUP DUP R> 4- SWAP OVER ! DSP! EXIT THEN 4+ REPEAT + \\ DROP CASE 0 1- OF ." ABORTED" CR ENDOF ." UNCAUGHT THROW " DUP . CR ENDCASE QUIT THEN ; + \\: STRLEN DUP BEGIN DUP C@ 0<> WHILE 1+ REPEAT SWAP - ; + \\ 65 EMIT CR \ A + \\ 777 65 EMIT DROP CR \ A + \\ 32 DUP + 1+ EMIT CR \ A + \\ 16 DUP 2DUP + + + 1+ EMIT CR \ A + \\ 8 DUP * 1+ EMIT CR \ A + \\ CHAR A EMIT CR \ A + \\ : SLOW WORD FIND >CFA EXECUTE ; 65 SLOW EMIT CR \ A + \\ 1179010630 DSP@ 4 TELL 2DROP CR \ FFFF + \\ 1179010630 DSP@ HERE @ 4 CMOVE HERE @ 4 TELL 2DROP CR \ FFFF + \\ 13622 DSP@ 2 NUMBER DROP EMIT CR \ A + \\ 64 >R RSP@ 1 TELL RDROP CR \ @ + \\ 64 DSP@ RSP@ SWAP C@C! RSP@ 1 TELL 2DROP CR \ @ + \\ 64 >R 1 RSP@ +! RSP@ 1 TELL RDROP CR \ A + \\ VERSION . CR \ 47 + \\ LATEST @ ID. CR \ SLOW + \\ 0 1 > . CR \ 0 + \\ 1 0 > . CR \ -1 + \\ 0 1 >= . CR \ 0 + \\ 0 0 >= . CR \ -1 + \\ 0 0<> . CR \ 0 + \\ 1 0<> . CR \ -1 + \\ 1 0<= . CR \ 0 + \\ 0 0 <= . CR \ -1 + \\ -1 0>= . CR \ 0 + \\ 0 0>= . CR \ -1 + \\ 0 0 OR . CR \ 0 + \\ 0 -1 OR . CR \ -1 + \\ -1 -1 XOR . CR \ 0 + \\ 0 -1 XOR . CR \ -1 + \\ -1 INVERT . CR \ 0 + \\ 0 INVERT . CR \ -1 + \\ F_IMMED F_HIDDEN .S 2DROP CR \ 32 128 + \\ : CFA@ WORD FIND >CFA @ ; CFA@ >DFA DOCOL = . CR \ -1 + \\ 3 4 5 .S 2DROP DROP CR \ 5 4 3 + \\ 3 4 5 WITHIN . CR \ 0 + \\ SEE >DFA \ : >DFA >CFA 4+ ; + \\ SEE HIDE \ : HIDE WORD FIND HIDDEN ; + \\ SEE QUIT \ : QUIT R0 RSP! INTERPRET BRANCH ( -8 ) ; + \\ : FOO THROW ; + \\ : TEST-EXCEPTIONS 25 ['] FOO CATCH ?DUP IF ." FOO threw exception: " . CR DROP THEN ; + \\ TEST-EXCEPTIONS \ FOO threw exception: 25 + ; + var output: [1024]u8 = undefined; + var reader: std.Io.Reader = .fixed(input); + var writer: std.Io.Writer = .fixed(output[0..]); + var memory: std.array_list.AlignedManaged(u8, .@"4") = try .initCapacity(testing.allocator, 0x20_000); + defer memory.deinit(); + try memory.appendSlice(comptime defwords()[0..]); + var env: Interp = .init(memory, &reader, &writer); + + cold_start( + &env, + @offsetOf(Header, "return_stack"), + @offsetOf(Header, "input_buffer"), + @offsetOf(Header, "cold_start"), + 0, + ); + + var expected = mem.tokenizeScalar(u8, input, '\n'); + var actual = mem.tokenizeScalar(u8, output[0..writer.end], '\n'); + while (expected.next()) |line| { + if (mem.cut(u8, line, " \\ ")) |cut| { + try testing.expectEqualStrings(cut.@"1", actual.next() orelse ""); + } + } } -fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { - _ = @as([*][*:0]u8, @ptrCast(c_argv))[0..@as(usize, @intCast(c_argc))]; - @call(.always_inline, main, .{}); - return 0; +fn cold_start(self: *Interp, sp: usize, rsp: usize, ip: usize, target: usize) callconv(conv) void { + self.next(sp, rsp, ip, target); } -comptime { - @export(&mainWithoutEnv, .{ .name = "__main_argc_argv" }); +pub fn main(init: std.process.Init) !void { + var memory: std.array_list.AlignedManaged(u8, .@"4") = try .initCapacity(init.gpa, 0x20_000); + defer memory.deinit(); + try memory.appendSlice(comptime defwords()[0..]); + var header: *Header = @ptrCast(memory.items.ptr); + var stdin_reader = std.Io.File.stdin().reader(init.io, header.input_buffer[0..]); + var stdout_writer = std.Io.File.stdout().writer(init.io, header.output_buffer[0..]); + var env: Interp = .init(memory, &stdin_reader.interface, &stdout_writer.interface); + + cold_start( + &env, + @offsetOf(Header, "return_stack"), + @offsetOf(Header, "input_buffer"), + @offsetOf(Header, "cold_start"), + 0, + ); } diff --git a/forth/Makefile b/forth/Makefile index 398d17f..06d9af9 100644 --- a/forth/Makefile +++ b/forth/Makefile @@ -1,6 +1,6 @@ CC = gcc EC ?= emcc -CFLAGS ?= -Wall -Wextra -Os -DNDEBUG -fomit-frame-pointer +CFLAGS ?= -Wall -Wextra -Os -DNDEBUG -fomit-frame-pointer -Wno-deprecated-declarations ARFLAGS = -m32 -S -fverbose-asm EFLAGS = -O -Wall -Wextra -sEXIT_RUNTIME=1 -sFORCE_FILESYSTEM=1 -sASSERTIONS=1 CPPFLAGS = -include cosmopolitan.h -I. @@ -56,7 +56,7 @@ cosmopolitan-amalgamation-2.2.zip : $(CC) -Wall -Wextra -Og --coverage -mstackrealign -o $@ $^ zig-out/bin/6th : 6th.zig - uv run --with ziglang python -m ziglang build + uvx --from ziglang python-zig build jansforth : jansforth.c $(CC) -m32 -std=c2x -Wall -Wextra -Wpedantic -Wconversion -D_DEFAULT_SOURCE -o $@ $^ diff --git a/forth/build.zig b/forth/build.zig index ddf2a32..8a0419c 100644 --- a/forth/build.zig +++ b/forth/build.zig @@ -22,11 +22,11 @@ fn buildWasm(b: *Build, target: Build.ResolvedTarget, optimize: OptimizeMode) !v .root_source_file = b.path("6th.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), .linkage = .static, }); lib.rdynamic = true; - lib.linkLibC(); const emcc = b.addSystemCommand(&.{"emcc"}); emcc.addArg("-mtail-call"); @@ -57,6 +57,7 @@ fn buildNative(b: *Build, target: Build.ResolvedTarget, optimize: OptimizeMode) .root_source_file = b.path("6th.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), .use_llvm = true, }); diff --git a/forth/test_4th.py b/forth/test_4th.py index 04216fb..de66ee4 100644 --- a/forth/test_4th.py +++ b/forth/test_4th.py @@ -127,9 +127,9 @@ class TestForth: F_IMMED F_HIDDEN .S 2DROP CR \ 32 128 : CFA@ WORD FIND >CFA @ ; CFA@ >DFA DOCOL = . CR \ -1 3 4 5 WITHIN . CR \ 0 -102 SYSCALL0 . CR \ {uid} -{uid} 105 SYSCALL1 . CR \ 0 -O_RDONLY Z" {argv0}" 21 SYSCALL2 . CR \ 0 +{getuid} SYSCALL0 . CR \ {uid} +{uid} {setuid} SYSCALL1 . CR \ 0 +O_RDONLY Z" {argv0}" {access} SYSCALL2 . CR \ 0 S" test" SWAP 1 SYS_WRITE SYSCALL3 . CR \ test4 ARGC . CR \ 1 0 ARGV TELL CR \ {argv0} diff --git a/progress/build.zig b/progress/build.zig index 394d4ad..2d8a21b 100644 --- a/progress/build.zig +++ b/progress/build.zig @@ -4,45 +4,41 @@ pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - if (b.option(std.Build.LazyPath, "include", "sysconfig.get_path('include')")) |include| { - const lib_mod = b.createModule(.{ - .root_source_file = b.path("binding.zig"), - .target = target, - .optimize = optimize, - }); - lib_mod.addIncludePath(include); + const version = b.option([]const u8, "version", "sysconfig.get_python_version()") orelse b.run(&.{ "python", "-c", "import sysconfig; print(sysconfig.get_python_version(), end='')" }); + const include: std.Build.LazyPath = b.option(std.Build.LazyPath, "include", "sysconfig.get_path('include')") orelse .{ .cwd_relative = b.run(&.{ "python", "-c", "import sysconfig; print(sysconfig.get_path('include'), end='')" }) }; + const stdlib: std.Build.LazyPath = b.option(std.Build.LazyPath, "stdlib", "sysconfig.get_path('stdlib')") orelse .{ .cwd_relative = b.run(&.{ "python", "-c", "import sysconfig; print(sysconfig.get_path('stdlib'), end='')" }) }; - const py = b.addTranslateC(.{ - .root_source_file = try include.join(b.allocator, "Python.h"), - .target = target, - .optimize = optimize, - }); - py.addIncludePath(include); - const py_mod = py.createModule(); + const py = b.addTranslateC(.{ + .root_source_file = try include.join(b.allocator, "Python.h"), + .target = target, + .optimize = optimize, + }); + py.addIncludePath(include); - if (b.option(std.Build.LazyPath, "stdlib", "sysconfig.get_path('stdlib')")) |stdlib| { - py_mod.addLibraryPath(stdlib); - lib_mod.addImport("python", py_mod); + const py_mod = py.createModule(); + py_mod.addLibraryPath(stdlib.dirname()); + py_mod.linkSystemLibrary(b.fmt("python{s}", .{version}), .{}); - const lib = b.addLibrary(.{ - .linkage = .dynamic, - .name = "binding", - .root_module = lib_mod, - }); - b.getInstallStep().dependOn(&b.addInstallFileWithDir( - lib.getEmittedBin(), - .prefix, - "binding.abi3.so", - ).step); - } else { - b.getInstallStep().dependOn(&b.addFail("The -Dstdlib=... option is required for this step").step); - } + const lib_mod = b.createModule(.{ + .root_source_file = b.path("binding.zig"), + .target = target, + .optimize = optimize, + }); + lib_mod.addImport("python", py_mod); - const unit_tests = b.addTest(.{ .root_module = lib_mod }); - const run_unit_tests = b.addRunArtifact(unit_tests); - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_unit_tests.step); - } else { - b.getInstallStep().dependOn(&b.addFail("The -Dinclude=... option is required for this step").step); - } + const lib = b.addLibrary(.{ + .linkage = .dynamic, + .name = "binding", + .root_module = lib_mod, + }); + b.getInstallStep().dependOn(&b.addInstallFileWithDir( + lib.getEmittedBin(), + .prefix, + "binding.abi3.so", + ).step); + + const unit_tests = b.addTest(.{ .root_module = lib_mod }); + const run_unit_tests = b.addRunArtifact(unit_tests); + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); } diff --git a/progress/meson.build b/progress/meson.build index 7bd8dd5..310e241 100644 --- a/progress/meson.build +++ b/progress/meson.build @@ -6,6 +6,7 @@ custom_target( '-m', 'ziglang', 'build', + '-Dversion=' + py.get_variable('VERSION'), '-Dinclude=' + py.get_path('include'), '-Dstdlib=' + py.get_path('stdlib'), '--prefix', diff --git a/regexp/regexp.py b/regexp/regexp.py index da8d7a3..65aad84 100644 --- a/regexp/regexp.py +++ b/regexp/regexp.py @@ -177,6 +177,7 @@ def __call__(self, string: str) -> bool: for char in string: n = [] for p in c: + assert isinstance(p, int) try: op = self[p] # str to match or jump targets except IndexError: