From 2cb35c65bde63f90d7afc3d7a8a985b0c6ea15d7 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 10 Aug 2026 11:11:18 -0700 Subject: Chapter 30: Text Formatting and Diagnostics Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki Co-authored-by: Claude Opus 5 --- src/main/c/simple_crt.c | 124 +++++++++---- src/main/java/com/seaofnodes/simple/README.md | 2 +- .../com/seaofnodes/simple/codegen/BuildLRG.java | 2 +- .../com/seaofnodes/simple/codegen/Coalesce.java | 2 +- .../java/com/seaofnodes/simple/codegen/IFG.java | 8 +- .../com/seaofnodes/simple/codegen/InstSelect.java | 8 +- .../com/seaofnodes/simple/codegen/Machine.java | 12 ++ .../com/seaofnodes/simple/codegen/ParseAll.java | 21 ++- .../com/seaofnodes/simple/codegen/RegAlloc.java | 51 ++++-- .../com/seaofnodes/simple/codegen/SourceError.java | 16 +- .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../com/seaofnodes/simple/node/FldOffNode.java | 2 +- .../com/seaofnodes/simple/node/FormatNode.java | 33 ++++ .../java/com/seaofnodes/simple/node/LoadNode.java | 3 + .../java/com/seaofnodes/simple/node/ScopeNode.java | 1 + .../seaofnodes/simple/node/cpus/arm/ExtendARM.java | 15 +- .../com/seaofnodes/simple/node/cpus/arm/arm.java | 3 +- .../simple/node/cpus/riscv/ExtendRISC.java | 15 +- .../seaofnodes/simple/node/cpus/riscv/riscv.java | 3 +- .../simple/node/cpus/x86_64_v2/ExtendX86.java | 15 +- .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 4 +- .../com/seaofnodes/simple/parser/Allocations.java | 20 ++- .../com/seaofnodes/simple/parser/ControlFlow.java | 4 +- .../com/seaofnodes/simple/parser/Declarations.java | 21 ++- .../com/seaofnodes/simple/parser/Expressions.java | 41 +++-- .../com/seaofnodes/simple/parser/Functions.java | 13 +- .../java/com/seaofnodes/simple/parser/Lexer.java | 139 +++++++++++---- .../java/com/seaofnodes/simple/parser/Parser.java | 23 ++- .../java/com/seaofnodes/simple/parser/README.md | 2 +- .../com/seaofnodes/simple/parser/References.java | 46 +++-- .../com/seaofnodes/simple/parser/Statements.java | 27 ++- .../java/com/seaofnodes/simple/parser/Strings.java | 59 +++++++ .../java/com/seaofnodes/simple/parser/Structs.java | 6 +- .../java/com/seaofnodes/simple/parser/Types.java | 6 +- .../com/seaofnodes/simple/parser/package-info.java | 2 +- .../com/seaofnodes/simple/type/TypeConAry.java | 15 ++ .../com/seaofnodes/simple/type/TypeConAryB.java | 2 +- .../com/seaofnodes/simple/type/TypeInteger.java | 10 +- src/main/smp/sys.smp | 2 + src/main/smp/sys/ary.smp | 26 ++- src/main/smp/sys/aryi64.smp | 25 +-- src/main/smp/sys/aryu8.smp | 45 ++--- src/main/smp/sys/io.smp | 75 +++++--- src/main/smp/sys/libc.smp | 25 ++- src/main/smp/sys/rt.smp | 3 + src/main/smp/sys/rt/simple_args.smp | 22 +++ src/main/smp/sys/text.smp | 98 +++++++++++ .../java/com/seaofnodes/simple/BoundaryTest.java | 12 +- .../java/com/seaofnodes/simple/BrainFuckTest.java | 2 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter05Test.java | 6 +- .../java/com/seaofnodes/simple/Chapter07Test.java | 18 +- .../java/com/seaofnodes/simple/Chapter08Test.java | 6 +- .../java/com/seaofnodes/simple/Chapter09Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter10Test.java | 9 +- .../java/com/seaofnodes/simple/Chapter12Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter13Test.java | 8 +- .../com/seaofnodes/simple/Chapter14FloatTest.java | 6 +- .../java/com/seaofnodes/simple/Chapter15Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter16Test.java | 4 +- .../java/com/seaofnodes/simple/Chapter17bTest.java | 23 +-- .../java/com/seaofnodes/simple/Chapter18Test.java | 46 ++--- .../java/com/seaofnodes/simple/Chapter19Test.java | 36 ++-- .../java/com/seaofnodes/simple/Chapter20Test.java | 14 +- .../java/com/seaofnodes/simple/Chapter21Test.java | 27 +-- .../java/com/seaofnodes/simple/Chapter22Test.java | 11 +- .../java/com/seaofnodes/simple/Chapter23Test.java | 2 +- .../java/com/seaofnodes/simple/Chapter24Test.java | 7 +- .../java/com/seaofnodes/simple/Chapter25Test.java | 26 +-- .../simple/ClassInitDependenciesTest.java | 14 +- .../seaofnodes/simple/CompoundAssignmentTest.java | 10 +- .../seaofnodes/simple/ConstructionGroupTest.java | 8 +- .../java/com/seaofnodes/simple/EscapeFlowTest.java | 10 +- .../seaofnodes/simple/FinalConstructionTest.java | 10 +- .../seaofnodes/simple/IncrementalBuildTest.java | 6 +- src/test/java/com/seaofnodes/simple/Int32Test.java | 94 ++++++++++ .../java/com/seaofnodes/simple/MergeSortTest.java | 13 +- .../java/com/seaofnodes/simple/MinusNodeTest.java | 2 +- .../seaofnodes/simple/ParserDiagnosticTest.java | 191 +++++++++++++++++++++ .../com/seaofnodes/simple/PartialReadTest.java | 6 +- .../com/seaofnodes/simple/RuntimeArgsTest.java | 29 ++++ .../seaofnodes/simple/SelectiveLoadingTest.java | 32 ++-- .../seaofnodes/simple/SeparateCompilationTest.java | 8 +- src/test/java/com/seaofnodes/simple/Simple.java | 30 +++- .../java/com/seaofnodes/simple/StreamIOTest.java | 138 +++++++++++++++ .../com/seaofnodes/simple/StringFormatTest.java | 158 +++++++++++++++++ .../com/seaofnodes/simple/TerminalInputTest.java | 184 ++++++++++++++++++++ src/test/java/com/seaofnodes/simple/TestC.java | 53 +++++- .../java/com/seaofnodes/simple/TicTacToeTest.java | 92 ++++++++++ .../simple/codegen/RegAllocTestSupport.java | 70 +++++++- .../java/com/seaofnodes/simple/progs/extern_data.c | 7 + src/test/java/com/seaofnodes/simple/progs/int32.c | 26 +++ .../com/seaofnodes/simple/progs/runtime_args.c | 35 ++++ src/test/java/com/seaofnodes/simple/progs/sort.c | 4 +- .../java/com/seaofnodes/simple/progs/stream_io.c | 49 ++++++ .../java/com/seaofnodes/simple/progs/stringHash.c | 2 +- src/test/smp/examples/A_helloWorld.smp | 2 - src/test/smp/examples/BubbleSort.smp | 36 ---- src/test/smp/examples/Capitalize.smp | 22 --- src/test/smp/examples/Dijkstra.smp | 72 -------- src/test/smp/examples/FileIO.smp | 50 ------ src/test/smp/separate/client/Main.smp | 2 +- 102 files changed, 2191 insertions(+), 650 deletions(-) create mode 100644 src/main/java/com/seaofnodes/simple/node/FormatNode.java create mode 100644 src/main/java/com/seaofnodes/simple/parser/Strings.java create mode 100644 src/main/smp/sys/rt.smp create mode 100644 src/main/smp/sys/rt/simple_args.smp create mode 100644 src/main/smp/sys/text.smp create mode 100644 src/test/java/com/seaofnodes/simple/Int32Test.java create mode 100644 src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java create mode 100644 src/test/java/com/seaofnodes/simple/RuntimeArgsTest.java create mode 100644 src/test/java/com/seaofnodes/simple/StreamIOTest.java create mode 100644 src/test/java/com/seaofnodes/simple/StringFormatTest.java create mode 100644 src/test/java/com/seaofnodes/simple/TerminalInputTest.java create mode 100644 src/test/java/com/seaofnodes/simple/TicTacToeTest.java create mode 100644 src/test/java/com/seaofnodes/simple/progs/int32.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/runtime_args.c create mode 100644 src/test/java/com/seaofnodes/simple/progs/stream_io.c delete mode 100644 src/test/smp/examples/A_helloWorld.smp delete mode 100644 src/test/smp/examples/BubbleSort.smp delete mode 100644 src/test/smp/examples/Capitalize.smp delete mode 100644 src/test/smp/examples/Dijkstra.smp delete mode 100644 src/test/smp/examples/FileIO.smp (limited to 'src') diff --git a/src/main/c/simple_crt.c b/src/main/c/simple_crt.c index 8656a3cc..438ae5f9 100644 --- a/src/main/c/simple_crt.c +++ b/src/main/c/simple_crt.c @@ -1,58 +1,106 @@ #include +#include #include #include #include +#include +#include // errno may be thread-local or a macro calling a platform accessor. int32_t simple_errno(void) { return errno; } +int32_t simple_set_errno(int32_t error) { errno = error; return -1; } +// Export platform errno constants; the buffering and retry loops are Simple. +// C identifiers avoid the macros; linker names match the Simple bindings. +int32_t simple_EINTR __asm__("EINTR") = EINTR; +int32_t simple_EIO __asm__("EIO") = EIO; +int32_t simple_EINVAL __asm__("EINVAL") = EINVAL; +int32_t simple_EOVERFLOW __asm__("EOVERFLOW") = EOVERFLOW; -// Native process arguments converted to ordinary Simple array layouts. -// The outer array is mutable; every contained byte array is published as -// immutable and non-null to Simple code. +// Read one stdin byte, without terminal line editing or echo. POSIX terminals +// include Cygwin consoles; redirected files/pipes need no mode change. +// Return 0..255, or -1 with errno set to EOF or an OS error. Control characters, +// including Ctrl-C, are returned as bytes so the mode is restored first. +int32_t simple_getch(void) { + struct termios saved, mode; + int terminal = tcgetattr(STDIN_FILENO, &saved) == 0; + if (!terminal && errno != ENOTTY) + return -1; + if (terminal) { + mode = saved; + mode.c_lflag &= ~(ICANON | ECHO | ECHONL | ISIG | IEXTEN); + mode.c_iflag &= ~IXON; + mode.c_cc[VMIN] = 1; + mode.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSANOW, &mode) != 0) + return -1; + } + unsigned char byte; + ssize_t count = read(STDIN_FILENO, &byte, 1); + int read_errno = errno; + if (terminal) { + int restored; + do { + restored = tcsetattr(STDIN_FILENO, TCSANOW, &saved); + } while (restored != 0 && errno == EINTR); + if (restored != 0) + return -1; + } + if (count < 0) { + errno = read_errno; + return -1; + } + if (count == 0) { + errno = EOF; + return -1; + } + return byte; +} + +// Simple byte-array layout, used only by the floating-point formatter. typedef struct SimpleBytes { uint32_t len; unsigned char bytes[]; } SimpleBytes; -typedef struct SimpleArgs { - uint32_t len; - uint32_t pad; - SimpleBytes *args[]; -} SimpleArgs; +// Binary floating-point conversion is the only native formatting primitive. +// Buffering, integer conversion, string precision and padding live in sys.text. +SimpleBytes *simple_text_float(double value, int64_t spec, + int64_t width, int64_t precision) { + char kind = spec & 255; + if (!kind) { kind = 'g'; precision = 17; } + if (!strchr("fFeEgGaA", kind) || (spec & ~8191LL) || + width < 0 || width > INT32_MAX || precision < -1 || precision > INT32_MAX) + abort(); + char fmt[64], *p = fmt; + *p++ = '%'; + const char *flags = "-+ #0"; + for (int i=0; i<5; i++) if (spec & (256LL<= 0) p += sprintf(p, ".%d", (int)precision); + *p++ = kind; + *p = 0; + int len = snprintf(NULL, 0, fmt, value); + if (len < 0) abort(); + SimpleBytes *str = malloc(sizeof(*str) + (size_t)len + 1); + if (!str) abort(); + str->len = (uint32_t)len; + snprintf((char *)str->bytes, (size_t)len + 1, fmt, value); + return str; +} +// Simple does not yet dereference raw native pointers. Keep just the argv +// element access here; allocation and array construction live in sys/rt/simple_args.smp. +const char *simple_argv(char **argv, int64_t index) { + return argv[index]; +} __attribute__((CALL_CONV)) -extern int64_t simple_main(SimpleArgs *args); - -static SimpleArgs *simple_args(int argc, char **argv) { - if (argc < 0 || (uint64_t)argc > UINT32_MAX) - return NULL; - - SimpleArgs *args = calloc(1, sizeof(*args) + (size_t)argc * sizeof(args->args[0])); - if (args == NULL) - return NULL; - args->len = (uint32_t)argc; - - for (int i = 0; i < argc; i++) { - size_t len = strlen(argv[i]); - if (len > UINT32_MAX) { - free(args); - return NULL; - } - SimpleBytes *str = malloc(sizeof(*str) + len); - if (str == NULL) { - for (int j = 0; j < i; j++) free(args->args[j]); - free(args); - return NULL; - } - str->len = (uint32_t)len; - memcpy(str->bytes, argv[i], len); - args->args[i] = str; - } - return args; -} +extern void *simple_args(int64_t argc, char **argv); +__attribute__((CALL_CONV)) +extern int64_t simple_main(void *args); int main(int argc, char **argv) { - SimpleArgs *args = simple_args(argc, argv); + // simple_args fills every slot before publishing this as u8[~][]. + void *args = simple_args(argc, argv); if (args == NULL) return 1; // The language-level hidden receiver is not part of the emitted ABI for diff --git a/src/main/java/com/seaofnodes/simple/README.md b/src/main/java/com/seaofnodes/simple/README.md index 613f66b4..af86f21e 100644 --- a/src/main/java/com/seaofnodes/simple/README.md +++ b/src/main/java/com/seaofnodes/simple/README.md @@ -2,7 +2,7 @@ | Directory | Contents | | --- | --- | -| `parser/` | Recursive-descent grammar routines, lexing, name resolution, and construction of the initial IR. `Var` describes a lexical binding. See the [grammar map](../../../../../../docs/chapter29/29-grammar.md). | +| `parser/` | Recursive-descent grammar routines, lexing, name resolution, and construction of the initial IR. `Var` describes a lexical binding. See the [grammar map](../../../../../../docs/chapter30/30-grammar.md). | | `node/` | Sea-of-Nodes IR nodes: graph edges, type computation, and local peephole rewrites. | | `node/cpus/` | Machine-specific nodes and instruction selection support for `arm`, `riscv`, and `x86_64_v2`. | | `codegen/` | Compilation driver and passes: module loading, optimization, scheduling, register allocation, serialization, and machine-code emission. `IterPeeps` runs the peephole worklist. | diff --git a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java index 0b314978..8d0237ea 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java +++ b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java @@ -16,7 +16,7 @@ abstract public class BuildLRG { // true if no hard-conflicts, although we still might not color. public static boolean run(int round, RegAlloc alloc) { FunNode fun = null; - for( Node bb : alloc._code._cfg ) { + for( Node bb : alloc._cfg ) { if( bb instanceof FunNode fun2 ) fun = fun2; for( Node n : bb.outs() ) { diff --git a/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java index 689ceceb..dbde2f8b 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java @@ -13,7 +13,7 @@ abstract public class Coalesce { // Walk all the splits, looking for coalesce chances boolean progress = false; - for( CFGNode bb : alloc._code._cfg ) { + for( CFGNode bb : alloc._cfg ) { outer: for( int j=0; j