From 06fb5aceeb65a75b1cd643debd667c52fdcafcb9 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sun, 8 Nov 2026 14:10:02 -0800 Subject: Better error messages Better error messages. Buffs to the std lib; getch() to fetch a single character; streaming I/O handles retries, and large sizes, with better error reporting. Formatted strings. Default int moved to i32. TicTacToe, the game. --- README.md | 4 +- chapter28b/ainotes.txt | 34 ---- chapter29/ainotes.txt | 34 ---- chapter30/Makefile | 18 +- chapter30/README.ja.md | 33 ++- chapter30/README.md | 221 ++++++++++++++++++++- chapter30/ainotes.txt | 34 ---- chapter30/docs/30-grammar.md | 27 ++- chapter30/docs/examples/A_helloWorld.smp | 2 + chapter30/docs/examples/BubbleSort.smp | 38 ++++ chapter30/docs/examples/Capitalize.smp | 23 +++ chapter30/docs/examples/Dijkstra.smp | 74 +++++++ chapter30/docs/examples/FileIO.smp | 52 +++++ chapter30/docs/examples/README.md | 71 +++++++ chapter30/docs/examples/TicTacToe.smp | 133 +++++++++++++ chapter30/src/main/c/simple_crt.c | 124 ++++++++---- .../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 ++- .../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/type/TypeConAry.java | 15 ++ .../com/seaofnodes/simple/type/TypeConAryB.java | 2 +- .../com/seaofnodes/simple/type/TypeInteger.java | 10 +- chapter30/src/main/smp/sys.smp | 2 + chapter30/src/main/smp/sys/ary.smp | 26 ++- chapter30/src/main/smp/sys/aryi64.smp | 25 ++- chapter30/src/main/smp/sys/aryu8.smp | 45 ++--- chapter30/src/main/smp/sys/io.smp | 75 +++++-- chapter30/src/main/smp/sys/libc.smp | 25 ++- chapter30/src/main/smp/sys/rt.smp | 3 + chapter30/src/main/smp/sys/rt/simple_args.smp | 22 ++ chapter30/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 +- .../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 +- .../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 + .../test/java/com/seaofnodes/simple/progs/int32.c | 26 +++ .../com/seaofnodes/simple/progs/runtime_args.c | 35 ++++ .../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 +- chapter30/src/test/smp/examples/A_helloWorld.smp | 2 - chapter30/src/test/smp/examples/BubbleSort.smp | 36 ---- chapter30/src/test/smp/examples/Capitalize.smp | 22 -- chapter30/src/test/smp/examples/Dijkstra.smp | 72 ------- chapter30/src/test/smp/examples/FileIO.smp | 50 ----- chapter30/src/test/smp/separate/client/Main.smp | 2 +- docs/roadmap.ja.md | 26 ++- docs/roadmap.md | 211 +++++++++++++++----- skills/chapter25-codex-notes.md | 39 +++- 117 files changed, 3082 insertions(+), 827 deletions(-) delete mode 100644 chapter28b/ainotes.txt delete mode 100644 chapter29/ainotes.txt delete mode 100644 chapter30/ainotes.txt create mode 100644 chapter30/docs/examples/A_helloWorld.smp create mode 100644 chapter30/docs/examples/BubbleSort.smp create mode 100644 chapter30/docs/examples/Capitalize.smp create mode 100644 chapter30/docs/examples/Dijkstra.smp create mode 100644 chapter30/docs/examples/FileIO.smp create mode 100644 chapter30/docs/examples/README.md create mode 100644 chapter30/docs/examples/TicTacToe.smp create mode 100644 chapter30/src/main/java/com/seaofnodes/simple/node/FormatNode.java create mode 100644 chapter30/src/main/java/com/seaofnodes/simple/parser/Strings.java create mode 100644 chapter30/src/main/smp/sys/rt.smp create mode 100644 chapter30/src/main/smp/sys/rt/simple_args.smp create mode 100644 chapter30/src/main/smp/sys/text.smp create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/Int32Test.java create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/ParserDiagnosticTest.java create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/RuntimeArgsTest.java create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/StreamIOTest.java create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/StringFormatTest.java create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/TerminalInputTest.java create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/TicTacToeTest.java create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/progs/int32.c create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/progs/runtime_args.c create mode 100644 chapter30/src/test/java/com/seaofnodes/simple/progs/stream_io.c delete mode 100644 chapter30/src/test/smp/examples/A_helloWorld.smp delete mode 100644 chapter30/src/test/smp/examples/BubbleSort.smp delete mode 100644 chapter30/src/test/smp/examples/Capitalize.smp delete mode 100644 chapter30/src/test/smp/examples/Dijkstra.smp delete mode 100644 chapter30/src/test/smp/examples/FileIO.smp diff --git a/README.md b/README.md index d2f516fa..b66fba22 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,8 @@ The Simple language is styled after a subset of C or Java. * [Chapter 27](chapter27/README.md): Escape Analysis and Unknown Callers * [Chapter 28a](chapter28a/README.md): Modules and Lazy Class Initialization * [Chapter 28b](chapter28b/README.md): Separate Compilation -* [Chapter 29](chapter29/README.md): Tech Debt Cleanup -* [Chapter 30](chapter30/README.md): Work in Progress +* [Chapter 29](chapter29/README.md): Tech Debt Cleanup, Partial Objects +* [Chapter 30](chapter30/README.md): Text Formatting and Diagnostics Appendices: [Native libraries](docs/appendices/native-libraries.md), [optimizer and backend tuning](docs/appendices/optimizer-tuning.md). diff --git a/chapter28b/ainotes.txt b/chapter28b/ainotes.txt deleted file mode 100644 index 5678e686..00000000 --- a/chapter28b/ainotes.txt +++ /dev/null @@ -1,34 +0,0 @@ - -Historical design notes from the former combined Chapter 25, now Chapter 28b. -The cleanup record is in ../skills/stage-audit.md; remaining work is in -../docs/roadmap.md. The original intent below is preserved. - -Meta-info: I intend to keep extending and re-editing this "ainotes.txt" file so -I can reprompt the you (the AI) between AI restarts, As such this file will -contain a mix of high-level design decisions, goals, intents and wisdom, and -then taper down to more detailed problems and solutions - which probably keep -changing. - -AI is allowed access to all files recursively in "C:/Users/cliffc/Desktop/Simple" -directory. - -Meta-info: all the tests run with `make tests` currently run, and should again -run before we accept any changes. - - -The overall project is rooted in the `Simple/` directory, -and is a compiler tutorial broken up into chapters. -The chapters build on each other. - -Read the code in this chapter25; the goal in this chapter is to allow separate -compilation. I want to compile a "standard library", the start of which is in -`src/main/smp/sys.smp` and subdirectories and end up with either an archive or -a "fat ELF .o" file. - -Then I want to compile a 1-liner "hello world" program: - `sys.io.p("Hello World!");` -and have the symbols `sys`, `io` and `p` be found in the standard library. - -I am using a test case to represent this seperate compilation strategy in -`Chapter25Test.testModules`. There are comments there to help drive my -thinking. diff --git a/chapter29/ainotes.txt b/chapter29/ainotes.txt deleted file mode 100644 index 6dd4b14f..00000000 --- a/chapter29/ainotes.txt +++ /dev/null @@ -1,34 +0,0 @@ - -Historical design notes from the former combined Chapter 25, carried forward from Chapter 28b. -The cleanup record is in ../skills/stage-audit.md; remaining work is in -../docs/roadmap.md. The original intent below is preserved. - -Meta-info: I intend to keep extending and re-editing this "ainotes.txt" file so -I can reprompt the you (the AI) between AI restarts, As such this file will -contain a mix of high-level design decisions, goals, intents and wisdom, and -then taper down to more detailed problems and solutions - which probably keep -changing. - -AI is allowed access to all files recursively in "C:/Users/cliffc/Desktop/Simple" -directory. - -Meta-info: all the tests run with `make tests` currently run, and should again -run before we accept any changes. - - -The overall project is rooted in the `Simple/` directory, -and is a compiler tutorial broken up into chapters. -The chapters build on each other. - -Read the code in this chapter25; the goal in this chapter is to allow separate -compilation. I want to compile a "standard library", the start of which is in -`src/main/smp/sys.smp` and subdirectories and end up with either an archive or -a "fat ELF .o" file. - -Then I want to compile a 1-liner "hello world" program: - `sys.io.p("Hello World!");` -and have the symbols `sys`, `io` and `p` be found in the standard library. - -I am using a test case to represent this seperate compilation strategy in -`Chapter25Test.testModules`. There are comments there to help drive my -thinking. diff --git a/chapter30/Makefile b/chapter30/Makefile index ec59a033..e4537ca4 100644 --- a/chapter30/Makefile +++ b/chapter30/Makefile @@ -155,7 +155,19 @@ RELEASE:= build/release TEST_SYS:= $(OBJS)/lib_$(CPU_ABI)/sys.o RELEASE_SYS:= $(RELEASE)/$(CPU_ABI)/sys.o RELEASE_CRT:= $(RELEASE)/$(CPU_ABI)/simple_crt.o +RELEASE_ARGS:= $(RELEASE)/$(CPU_ABI)/simple_args.o C_CALL_CONV:= $(if $(filter win64,$(CALL_CONV)),ms_abi,sysv_abi) +GAME_DIR:= build/examples/$(CPU_ABI) +TICTACTOE:= $(GAME_DIR)/TicTacToe$(if $(filter Windows_NT,$(OS)),.exe,) + +# Build a runnable game without starting it. Native outputs are ABI-specific; +# WSL can use CLZDIR=build/classes-linux to keep its Java build separate too. +.PHONY: tictactoe +tictactoe: $(TICTACTOE) + +$(TICTACTOE): docs/examples/TicTacToe.smp $(CLZDIR)/test/.ttag $(RELEASE_SYS) $(RELEASE_CRT) Makefile + @echo "building " $@ + @$(JVM) com.seaofnodes.simple.Simple --link --cpu $(CPU_PORT) --abi $(CALL_CONV) -L $(dir $(RELEASE_SYS)) -o $(GAME_DIR)/TicTacToe.o $< $(TEST_SYS): $(CLZDIR)/main/.mtag $(simple_driver) $(SYS_SMPS) | $(CLZDIR)/test/.ttag @echo "simping " $@ @@ -167,7 +179,11 @@ $(RELEASE_SYS): $(OBJS)/lib_$(CPU_ABI)/sys.o @cp $< $@ @cp -r $(dir $<)sys $(dir $@) -$(RELEASE_CRT): src/main/c/simple_crt.c +$(RELEASE_ARGS): src/main/smp/sys/rt/simple_args.smp $(CLZDIR)/main/.mtag $(simple_driver) Makefile | $(CLZDIR)/test/.ttag + @echo "simping " $@ + @$(JVM) com.seaofnodes.simple.Simple --cpu $(CPU_PORT) --abi $(CALL_CONV) --norun -o $@ $< + +$(RELEASE_CRT): src/main/c/simple_crt.c $(RELEASE_ARGS) Makefile @echo "compiling " $@ @[ -d $(dir $@) ] || mkdir -p $(dir $@) @gcc -c -O2 -DCALL_CONV=$(C_CALL_CONV) -o $@ $< diff --git a/chapter30/README.ja.md b/chapter30/README.ja.md index 167b4ade..9e0771dc 100644 --- a/chapter30/README.ja.md +++ b/chapter30/README.ja.md @@ -1,8 +1,35 @@ -# 第30章:作業中 +# 第30章:テキスト整形と診断 [English](README.md) | 日本語 [前の章:第29章 — 技術的負債の整理](../chapter29/README.ja.md) -この章は第29章の完成したコンパイラを出発点としています。次のテーマは未定です。 -詳細とビルド手順は[英語版](README.md)を参照してください。 +ASCII の[三目並べ](docs/examples/TicTacToe.smp)を題材に、コンパイラと +標準ライブラリを改善します。ゲーム自体ではなく、実装中に必要になった +言語機能、診断、ランタイムが章のテーマです。実行可能な例は +[`docs/examples`](docs/examples/)にあります。 + +- `sys.io.getch()` による Enter 不要の1バイト入力と、端末モードの復元。 +- EOFまでの可変長読み込みと、部分書き込み・割り込みの再試行。 + 読み込みエラーは `null`、書き込みエラーは `-1` と errno で通知します。 + `aryu8` と `aryi64` の長さは `u32` とし、拡張時の桁あふれを防ぎます。 +- `file:line:column`、ソース行、位置マーカーを含む構文・型エラーの診断。 +- `int` を `i32` の別名に変更。型推論と中間演算の規則は維持し、 + 代入時に32ビットへ縮小します。ポインタの幅は変わりません。 +- `"CPU plays %(reply+1).\n"` のような文字列補間と、`sys.text` の型別変換。 + 整数などの整形は Simple で実装し、浮動小数点変換だけ C を呼び出します。 +- `sys.rt.simple_args` がネイティブの起動引数を Simple の配列へコピー。 +- 関数ごとのレジスタ割り当てと再試行。レジスタを使わない依存を生存解析から + 除外し、レジスタ破壊をまたぐ必要な転送を分割時に保持します。 + +リポジトリのルートからゲームをビルドします。 + +```sh +make -C chapter30 tictactoe CTAGS= +``` + +Windows では `chapter30/build/examples/x86_64_v2_win64/TicTacToe.exe`、 +Linux では `chapter30/build/examples/x86_64_v2_SystemV/TicTacToe` を実行します。 +詳しい書式指定、テスト、WSL でのビルドは[英語版](README.md)を、 +バックエンドなどの未完了の作業は[ロードマップ](../docs/roadmap.ja.md) +を参照してください。 diff --git a/chapter30/README.md b/chapter30/README.md index 843ccc69..3e27c874 100644 --- a/chapter30/README.md +++ b/chapter30/README.md @@ -1,12 +1,209 @@ -# Chapter 30: Work in Progress +# Chapter 30: Text Formatting and Diagnostics English | [日本語](README.ja.md) [Previous: Chapter 29 — Tech Debt Cleanup](../chapter29/README.md) -This chapter starts from the completed Chapter 29 compiler. Its next topic is -not yet selected. The parser organization, constructor checks, partial field -views, and regression tests are inherited unchanged. +This chapter uses a small game to drive improvements in the compiler and the +`sys` standard library. The game provides a concrete workload; the chapter's +subject is the language and library work uncovered. + +The first program is ASCII [tic-tac-toe](docs/examples/TicTacToe.smp). +The player moves first with single keystrokes; the CPU responds, occupied +squares are rejected, and play ends with a win or draw. +Runnable programs, including sorting, file I/O, and shortest-path examples, +live in [`docs/examples`](docs/examples/). + +## Terminal input and diagnostics + +[`sys.io.getch()`](src/main/smp/sys/io.smp) reads one byte without waiting for +Enter or echoing it. A C adapter changes and restores the terminal mode; +redirected files and pipes also work. It returns `0..255`, or `-1` with +`sys.libc.errno()` set to `sys.libc.EOF` or an OS error. Escape and UTF-8 +sequences arrive as multiple bytes. + +The game echoes accepted moves, ignores Enter, and quits on `q`, Escape, +Ctrl-C, Ctrl-D, or end of input. + +We also exposed a parser bug: branch guards from one function could affect the +next function. Function boundaries now isolate those guards. Loop-path merges +also preserve forward references discovered after a break or continue. + +The CLI reports parser and type errors as `file:line:column: error: message`, +followed by the source line and an underscore-and-caret pointer - suitable for +older text editors (VI, Emacs). Language errors now exit with status 1 instead +of throwing an exception; unexpected compiler errors keep their Java stack +traces. Quite a few error reporting points are cleaned up: malformed numbers, +invalid escapes, unterminated comments or quoted text are a few. + +## Stream I/O and resizable arrays + +`sys.io.read(fd)` grows its buffer and continues through short reads until +EOF. It returns an exact-sized byte array, empty for immediate EOF, or `null` +on error with `sys.libc.errno()` preserved. Partial input is discarded on an +error. `stdin()` uses the same behavior. + +`write`, `writeOff`, and `aryu8.write` write the complete requested data, +retrying partial writes. Reads and writes retry `EINTR`; other errors return +to the caller. Writes return a 64-bit byte count, or `-1` with errno set; a +failure can follow a successfully written prefix. A zero-progress write +reports `EIO`. `writeOff` rejects offsets past the array end with `EINVAL`, +caps the length at the remaining bytes, and accepts empty slices at the end. + +The buffering and retry loops are Simple code. C supplies access to errno and +its platform constants; the native read/write bindings use 64-bit counts and +results on the supported targets. Tests cover files, pipes, empty and large +inputs, and injected short transfers, interruptions, and failures. + +`aryu8` and `aryi64` store their lengths as `u32`, matching built-in array +lengths. Growth handles empty backing arrays, uses existing capacity when it +fits exactly, and calculates the next capacity in 64-bit arithmetic before +narrowing. Requests beyond the `u32` length limit are rejected: stream reads +return `null` with `EOVERFLOW`, while direct resizable-array growth exits with +status 1. The tests check arithmetic at the unsigned boundary without allocating +multi-gigabyte buffers. + +## Default integer type + +`int` now aliases `i32`. Its fields and array elements occupy four bytes; +assignments and updates use the existing signed 32-bit narrowing rules. +Explicit `i64` remains available for wide arithmetic and native addresses. +Literal inference, `var`/`val` inference, and intermediate arithmetic retain +their existing rules: `int x=2147483647; return x+1;` produces `2147483648`, +whereas assigning that sum back to `x` produces `-2147483648`. + +The standard library spells out `i64` where it needs full-width formatting or +native addresses/sizes. Resizable-array lengths, growth counts, and indices +use explicit `u32`. + +Function entries normalize narrow signed/unsigned parameters, including +stack arguments, using the backend extension operations. This also applies +to internal Simple calls. Loop type widening preserves signed and unsigned +32-bit bounds of values narrowed on every assignment. + +## Strings and formatting + +Strings can embed expressions from the surrounding scope: + +```java +sys.io.p("CPU plays %(reply+1).\n"); +sys.io.p("score=%04d(score), average=%.2f(average)\n"); +sys.io.p("name=%-12.8s(name), key=%c(key)\n"); +``` + +`%(expression)` selects decimal integer, floating-point, or byte-string +formatting from the expression's type. Booleans are integer aliases, so the +default prints `0` or `1`; `%b(expression)` prints `false` or `true`. +Expressions execute once, from left to right. Each value is formatted before +evaluating the next expression. The result is an ordinary immutable `u8[~]` +with its exact byte length. Strings without embedded expressions remain +constants. + +An explicit format has the form `%[flags][width][.precision]kind(expression)`: + +| Kind | Argument and output | +| --- | --- | +| `d`, `i` | Integer, signed decimal | +| `u`, `o`, `x`, `X` | Integer, unsigned decimal, octal, or hexadecimal; negative values use their 64-bit representation | +| `f`, `F`, `e`, `E`, `g`, `G`, `a`, `A` | Float, using the corresponding C format | +| `c` | Integer, one byte from its low eight bits | +| `s` | Byte string, including embedded zero bytes | +| `b` | Integer, `false` for zero and `true` otherwise | + +Flags are `-` (left alignment), `+` (explicit sign), space (positive sign +padding), `#` (alternate form), and `0` (zero padding), where applicable. +Width and precision are literal decimal integers. String precision limits +bytes; integer precision supplies a minimum digit count. `c` and `b` allow +width and left alignment, but no precision. Default floating-point formatting +uses `g` with 17 significant digits; explicit floating formats use C defaults +unless a precision is given. Positional arguments, `*` widths, C length +modifiers, and `%n` are not supported. + +Write `%%` or `\%` for a literal percent. Every other `%` must introduce a +complete format with parentheses. Inside those parentheses the normal +expression parser resumes, so nested strings work directly: + +```java +val text = "outer %("inner %(value)")"; +``` + +[`sys.text`](src/main/smp/sys/text.smp) provides explicitly named converters: +`int`, `flt`, `f32`, `f64`, `char`, `bool`, and `hex`. For example, +`sys.text.int(17)` returns `"17"`. Primitive type names are allowed as member +names and in member declarations; they remain reserved for local variables and +parameters. `sys.io.p` continues to require a byte string. + +`Strings` builds ordinary calls to `sys.text`, with normal module lookup, +class initialization, and control/memory edges. A small `FormatNode` +selects the default typed formatter as normal type flow resolves an expression, +including forward references; it folds away in valid programs. + +The formatting library is written in Simple: `begin` creates an `aryu8`, +`integer` and `string` append formatted values, and `end` copies the populated +bytes into the immutable result. The integer formatter covers signed and +unsigned 64-bit values, including minimum `i64`, and handles signs, prefixes, +width, and precision. Character and boolean formatting use the same builder. +`aryu8.num` shares the integer formatter, including its full-range handling. +Only binary floating-point conversion calls C, through one fixed-signature +adapter returning a byte string. There is no boxing or variadic Simple call. + +`sys.text` uses no interpolated strings, including in its named converters, so +the library builds from source without a staged bootstrap. Non-float +formatting can also run in the IR evaluator. The lexer shares its string and +format scanner with deferred constructor-body and loop-update scanning. + +## Runtime support in Simple + +Native startup also uses Simple: [`simple_args.smp`](src/main/smp/sys/rt/simple_args.smp) +allocates the argument array and copies each argument into a byte array. The +C entry adapter calls this compiled routine before `simple_main`; C supplies +only access to `argv[i]`, `strlen`, and `memcpy`. The converter fills every +nullable array slot before publishing the result as `u8[~][]`, including the +program name at index zero. It builds independently of `sys`, so even programs +without standard-library imports use it. + +The `sys.rt` module holds Run Time support, like Java's `rt.jar` or C's `crt.o`. +Make emits `simple_args.o` alongside `simple_crt.o`; the native linker driver +includes both runtime objects automatically. + +## Register allocation + +The allocator handles each function independently, retaining its completed +register assignments while reusing scratch space for the next function. +Building live ranges, constructing interferences, coalescing, coloring, and +splitting all run on that function's scheduled blocks. The eight-round limit +applies per function. A hard register-mask conflict in one function therefore +cannot prevent another function from reaching coloring or consume its retry +budget. + +Two changes avoid unnecessary retries. First, liveness ignores inputs whose +operand register mask is null: these are metadata or scheduling dependencies, +not runtime register uses. In particular, a static call's unused hidden class +argument must not keep its class-pointer constant live across other calls and +trigger rematerialization. + +Second, splitting retains both transfers when a value must move out of a fixed +register before a kill and back into it afterward. A buggy attempt at early +coalescing would undo a split needed to dodge the kill. Interference-aware +coalescing decides when they can be joined. + +Loop self-conflicts initially try cold transfers. If that does not separate +the ranges, the fallback also splits backedge inputs. Distinct backedge nodes +can still share a range through two-address instructions; copying only the Phi +outputs leaves that conflict intact. + +The spill-ranking heuristic and calling conventions are unchanged. Saving a +callee-save register can still require several coloring attempts as pressure +changes. The [allocator regressions](src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java) +check function independence in both scheduling orders, preservation across +register kills, and dependencies that do not consume registers. + +## Related follow-up work + +[Narrow integer instruction selection](../docs/roadmap.md#integer-instruction-widths) +and [per-function calling conventions](../docs/roadmap.md#per-function-calling-conventions) +remain separate backend projects. C pointer types and compact references are +also deferred; the `int` change does not shrink pointers. The [grammar and code map](docs/30-grammar.md), [source layout](src/main/java/com/seaofnodes/simple/README.md), and @@ -21,6 +218,22 @@ From the repository root: make lib tests release CHAPTERS=chapter30 CTAGS= ``` +Build the game without starting it: + +```sh +make -C chapter30 tictactoe CTAGS= +``` + +The Windows/Cygwin build produces +`chapter30/build/examples/x86_64_v2_win64/TicTacToe.exe`, which can be launched +from Cygwin or PowerShell. Linux produces +`chapter30/build/examples/x86_64_v2_SystemV/TicTacToe`. +When building in WSL alongside Windows, keep Java build outputs separate: + +```sh +make -C chapter30 tictactoe CLZDIR=build/classes-linux CTAGS= +``` + IntelliJ has a `chapter30` module and a **chapter30 tests** run configuration. Build the native runtime and `sys` library with Make before debugging tests that need them; see [the IntelliJ setup](../docs/intellij.md). diff --git a/chapter30/ainotes.txt b/chapter30/ainotes.txt deleted file mode 100644 index 6dd4b14f..00000000 --- a/chapter30/ainotes.txt +++ /dev/null @@ -1,34 +0,0 @@ - -Historical design notes from the former combined Chapter 25, carried forward from Chapter 28b. -The cleanup record is in ../skills/stage-audit.md; remaining work is in -../docs/roadmap.md. The original intent below is preserved. - -Meta-info: I intend to keep extending and re-editing this "ainotes.txt" file so -I can reprompt the you (the AI) between AI restarts, As such this file will -contain a mix of high-level design decisions, goals, intents and wisdom, and -then taper down to more detailed problems and solutions - which probably keep -changing. - -AI is allowed access to all files recursively in "C:/Users/cliffc/Desktop/Simple" -directory. - -Meta-info: all the tests run with `make tests` currently run, and should again -run before we accept any changes. - - -The overall project is rooted in the `Simple/` directory, -and is a compiler tutorial broken up into chapters. -The chapters build on each other. - -Read the code in this chapter25; the goal in this chapter is to allow separate -compilation. I want to compile a "standard library", the start of which is in -`src/main/smp/sys.smp` and subdirectories and end up with either an archive or -a "fat ELF .o" file. - -Then I want to compile a 1-liner "hello world" program: - `sys.io.p("Hello World!");` -and have the symbols `sys`, `io` and `p` be found in the standard library. - -I am using a test case to represent this seperate compilation strategy in -`Chapter25Test.testModules`. There are comments there to help drive my -thinking. diff --git a/chapter30/docs/30-grammar.md b/chapter30/docs/30-grammar.md index d65abe60..3bddfb87 100644 --- a/chapter30/docs/30-grammar.md +++ b/chapter30/docs/30-grammar.md @@ -8,6 +8,9 @@ The production-to-code map below the grammar describes where syntax ends and name resolution or graph construction begins. This grammar documents the language; it is not input to a parser generator. +`int` is an alias for `i32`; `i64` explicitly selects 64-bit integer storage. +This alias does not change literal inference or intermediate arithmetic. + ```antlrv4 grammar SimpleLanguage; @@ -58,11 +61,14 @@ constructorParameter : type '^'? bindingModifier? IDENTIFIER ; // A method is an ordinary fixed function binding in a struct definition. // The compiler supplies self; it is not written in parameterList. // Function types and expressions both receive their implicit receiver here. -methodDeclaration : 'val' bindingModifier? IDENTIFIER '=' functionExpression ';' ; +methodDeclaration : 'val' bindingModifier? memberName '=' functionExpression ';' ; +memberName : IDENTIFIER | primitiveType ; declaration : declarationType binding (',' binding)* ; declarationType : type | 'var' | 'val' ; binding : bindingModifier? IDENTIFIER ('=' initializer)? ; +// At class/struct member scope, memberName also permits primitive type names. +// Ordinary locals and parameters still require a non-keyword IDENTIFIER. bindingModifier : '!' | '~' ; // A declaration initializer consisting of the string "C" declares an extern // symbol: a function, or explicit numeric native storage. Ordinary strings @@ -132,17 +138,17 @@ unaryExpression // and array elements. All updates require an assignable operand. updateOperator : '++' | '--' ; postfixExpression : primaryExpression postfixSuffix* updateOperator? ; -postfixSuffix : '.' IDENTIFIER | '[' assignment ']' | '#' | callSuffix | methodCallSuffix ; +postfixSuffix : '.' memberName | '[' assignment ']' | '#' | callSuffix | methodCallSuffix ; // For a fixed function-valued member, object.name(args) passes object as self. // This overlaps ordinary field selection followed by a call syntactically; // name/type resolution determines which call form applies. -methodCallSuffix : '.' IDENTIFIER callSuffix ; +methodCallSuffix : '.' memberName callSuffix ; primaryExpression : INTEGER_LITERAL | FLOAT_LITERAL - | STRING_LITERAL + | stringExpression | CHARACTER_LITERAL | 'true' | 'false' @@ -177,9 +183,17 @@ INTEGER_LITERAL : '0' | [1-9] DIGIT* ; // The f suffix also accepts an integer-shaped token, e.g. 12f. FLOAT_LITERAL : DIGIT+ ('.' DIGIT* ('e' DIGIT+)? | 'e' '-'? DIGIT+) 'f'? | DIGIT+ 'f' ; -STRING_LITERAL : '"' (ESCAPE | ~["\\])* '"' ; +// String text uses a lexical mode: whitespace/comments are literal here. +// Interpolation re-enters the ordinary expression grammar, recursively. +stringExpression : '"' (STRING_TEXT | ESCAPE | '%%' | interpolation)* '"' ; +interpolation : '%' format? '(' assignment ')' ; +format : FORMAT_FLAG* FORMAT_WIDTH? ('.' FORMAT_WIDTH?)? FORMAT_KIND ; +// Flags: - + space # 0. Width/precision: decimal digits, no expressions. +// Kinds: d i u o x X f F e E g G a A c s b. +// Duplicate/inapplicable flags and precision on c/b are rejected. +// STRING_TEXT excludes double quote, backslash, and percent. CHARACTER_LITERAL : '\'' (ESCAPE | ~['\\]) '\'' ; -fragment ESCAPE : '\\' [0ntr\\'"] ; +fragment ESCAPE : '\\' [0ntr\\'"%] ; IDENTIFIER : NON_DIGIT (NON_DIGIT | DIGIT)* ; fragment NON_DIGIT : [a-zA-Z_] ; fragment DIGIT : [0-9] ; @@ -206,6 +220,7 @@ there is no inheritance chain, AST, or additional compiler pass. | `postfixExpression`, member selection and updates | `Expressions.parsePostfix` / `parsePostfixMethod` dispatch to [`References.parseField`](../src/main/java/com/seaofnodes/simple/parser/References.java); `References.parseName` handles bare names, implicit receivers, and forward references | | `functionExpression`, `parameterList`, calls, `argumentList` | [`Functions`](../src/main/java/com/seaofnodes/simple/parser/Functions.java): `parseFunction`, `parseConstructor`, shared `parameters` and `arguments`, then `parseBody` | | `newExpression` | [`Allocations.parseNew`](../src/main/java/com/seaofnodes/simple/parser/Allocations.java); the allocation-helper graph builder follows the expression routines | +| `stringExpression`, `interpolation` | [`Strings.parse`](../src/main/java/com/seaofnodes/simple/parser/Strings.java), using `Lexer.stringPart` / `stringFormat`; normal expression parsing resumes inside each interpolation | | Literals, identifiers, comments, token matching | [`Lexer`](../src/main/java/com/seaofnodes/simple/parser/Lexer.java); it also supplies source-location snapshots used by diagnostics and serialized IR | `ControlFlow` shares guarded scope merging between `if`, `?:`, `&&`, and `||`. diff --git a/chapter30/docs/examples/A_helloWorld.smp b/chapter30/docs/examples/A_helloWorld.smp new file mode 100644 index 00000000..4ed6abdf --- /dev/null +++ b/chapter30/docs/examples/A_helloWorld.smp @@ -0,0 +1,2 @@ +sys.io.p("Hello, World!\n"); +return 0; diff --git a/chapter30/docs/examples/BubbleSort.smp b/chapter30/docs/examples/BubbleSort.smp new file mode 100644 index 00000000..6bdb13a9 --- /dev/null +++ b/chapter30/docs/examples/BubbleSort.smp @@ -0,0 +1,38 @@ + +// An implementation of +// https://sampleprograms.io/projects/bubble-sort/ +// +// Bubble sort, with reading (parsing) and writing (formatting) the data. + +val sort = { !sys.aryi64 nums -> + sys.aryi64?! more = nums; + while( more ) { + more = null; + for( u32 i = 0; i < nums.len-1; i++ ) + if( nums.buf[i] > nums.buf[i+1] ) { + u32 next = i+1; + more = nums.swap(i,next); + } + } +}; + +// Read one process argument of the form "[ 4, 3, 2, 1]" and print it sorted. +// args[0] is the executable name, as with native C argc/argv. +val main = { u8[~][] args -> + val usage = { -> + sys.io.p("Usage: please provide a list of at least two integers to sort in the format \"[1, 2, 3, 4, 5]\"\n"); + return 0; + }; + if( args# != 2 ) return usage(); + + var nums = sys.aryi64.parseAryI64(args[1]); + if( !nums ) return usage(); + if( nums.len < 2 ) return usage(); + + // Bubble sort array of integers + sort(nums); + + // Print the same bracketed array-list format accepted as input. + nums.str().add('\n').write(1); + return 0; +}; diff --git a/chapter30/docs/examples/Capitalize.smp b/chapter30/docs/examples/Capitalize.smp new file mode 100644 index 00000000..00fdd481 --- /dev/null +++ b/chapter30/docs/examples/Capitalize.smp @@ -0,0 +1,23 @@ + +// An implementation of +// https://sampleprograms.io/projects/capitalize/ + +val main = { u8[~][] args -> + if( args# > 2 ) { + sys.io.p("Use quotes around multiple strings.\n"); + return 0; + } + if( args# != 2 || args[1]# == 0 ) { + sys.io.p("Usage: please provide a string\n"); + return 0; + } + + u8[~] input = args[1]; + var output = new u8[input# + 1]; + u32 len = input#; + sys.ary.copy(output,0,input,0,len); + output[0] = sys.char.toupper(input[0]); + output[input#] = '\n'; + sys.io.p(output); + return 0; +}; diff --git a/chapter30/docs/examples/Dijkstra.smp b/chapter30/docs/examples/Dijkstra.smp new file mode 100644 index 00000000..768bdd08 --- /dev/null +++ b/chapter30/docs/examples/Dijkstra.smp @@ -0,0 +1,74 @@ + +// An implementation of +// https://sampleprograms.io/projects/dijkstra/ + +val shortestPath = { !sys.aryi64 matrix, int nodes, int src, int dst -> + i64 infinity = (1 << 63)-1; + var dist = new i64[nodes]; + var visited = new sys.adt.bitset(); + // All nodes are infinitely far from each other + for( int i=0; i 0 && !visited.get(node) && best+weight < dist[node] ) + dist[node] = best+weight; + } + } + count++; + } + return result; +}; + +val usage = { -> + sys.io.p("Usage: please provide three inputs: a serialized matrix, a source node and a destination node\n"); + return 0; +}; + +val main = { u8[~][] args -> + // Exactly 3 args + if( args# != 4 ) return usage(); + // First is a serialized square matrix of positive numbers + var matrix = sys.aryi64.parseAryI64(args[1]); + if( !matrix ) return usage(); + if( !matrix.len ) return usage(); + // All numbers are positive + for( u32 i=0; i= nodes || dst < 0 || dst >= nodes ) + return usage(); // Source and destination land within the graph + + int source=src, destination=dst; + i64 cost = shortestPath(matrix,nodes,source,destination); + if( cost >= 0 ) { + new sys.aryu8().num(cost).add('\n').write(1); + return 0; + } + return usage(); +}; diff --git a/chapter30/docs/examples/FileIO.smp b/chapter30/docs/examples/FileIO.smp new file mode 100644 index 00000000..59b3159e --- /dev/null +++ b/chapter30/docs/examples/FileIO.smp @@ -0,0 +1,52 @@ + +// An implementation of +// https://sampleprograms.io/projects/file-input-output/ + +val FILE = "output.txt"; + +val main = { u8[~][] args -> + var str = new sys.aryu8(); + str.addAry("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n"); + str.addAry("Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n"); + str.addAry("Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n"); + str.addAry("Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"); + + // Create + i32 fd = sys.io.create(FILE); + if( fd == -1 ) return _error(); + // Write + u32 len = str.len; + int wrote = sys.io.writeOff(fd,str.buf,0,len); + if( wrote == -1 ) return _error(); + if( wrote != str.len ) return _error2(-2); + // Close + if( sys.io.close(fd) != 0 ) return _error(); + + // Open + i32 fd2 = sys.io.open(FILE); + if( fd2 == -1 ) return _error(); + // Read + var got = sys.io.read(fd2); + if( !got ) return _error(); + if( got# != str.len ) return _error2(-3); + // Close + if( sys.io.close(fd2) != 0 ) return _error(); + + // Check contents are equal + if( sys.ary.cmp(got,0,str.buf,0,len) ) + return _error2(-4); + + // Remove + if( sys.io.remove(FILE) != 0 ) return _error(); + + sys.io.p("File I/O succeeded\n"); + return 0; +}; + +val _error2 = { i32 errno -> + sys.io.remove(FILE); + new sys.aryu8().addAry("Failed with error code ").num(errno).add('\n').write(sys.libc.STDERR); + sys.libc._exit(errno); +}; + +val _error = { -> _error2(sys.libc.errno()); }; diff --git a/chapter30/docs/examples/README.md b/chapter30/docs/examples/README.md new file mode 100644 index 00000000..767cdc3e --- /dev/null +++ b/chapter30/docs/examples/README.md @@ -0,0 +1,71 @@ +# Runnable examples + +These programs live beside the documentation so readers can find and run them. +Their native regression tests still run as part of `make tests`. + +From `chapter30`, build the tic-tac-toe executable (without starting it): + +```sh +make lib tictactoe CTAGS= +``` + +Run the Windows build from PowerShell: + +```powershell +.\build\examples\x86_64_v2_win64\TicTacToe.exe +``` + +Or from Cygwin: + +```sh +./build/examples/x86_64_v2_win64/TicTacToe.exe +``` + +The Windows executable uses Cygwin's runtime; its `bin` directory must be on +`PATH` (as it is when building with Cygwin GCC). + +For a native Linux build, run these commands from this same `chapter30` +directory in WSL. Separate Java and native output directories let the Windows +and Linux builds coexist: + +```sh +make lib tictactoe CLZDIR=build/classes-linux CTAGS= +./build/examples/x86_64_v2_SystemV/TicTacToe +``` + +From PowerShell, prefix either command with `wsl -d Ubuntu-24.04 --`. +On a standalone Linux checkout, `CLZDIR` can be omitted. +The compiler's `--link` option produces an executable without running it; +`--norun` continues to produce object files only. + +`TicTacToe.smp` currently prints the welcome, square numbering, and empty board, +then waits for a move. Press **1–9** without Enter to place X, or +**q**, Escape, Ctrl-C, or Ctrl-D to quit. Enter is silently ignored; other +invalid keys and occupied squares prompt again without taking a turn. The board +is printed after each player and CPU move; only accepted move digits are echoed. +Play continues until someone wins or the board is +full. The CPU takes an immediate win, blocks an immediate loss, then prefers +the center, corners, and edges. + +Other examples are Hello World (`A_helloWorld.smp`), `BubbleSort.smp`, +`Capitalize.smp`, `Dijkstra.smp`, and `FileIO.smp`. The latter creates, checks, +and removes `output.txt` in the working directory. + +## Single-keystroke input + +`sys.io.getch()` blocks for one byte without terminal editing or echo. It returns +0–255 for a byte, or -1 for EOF or an error. On -1, `sys.libc.errno()` is +`sys.libc.EOF` (-1) for end-of-input, otherwise the OS error code. +Terminal Ctrl-C and Ctrl-D are bytes 3 +and 4, so the application decides how to handle them. Unicode and function keys +may produce multiple bytes; this is an ASCII-oriented byte API, not a key-event +decoder. + +The small C runtime adapter uses [POSIX terminal modes](https://man7.org/linux/man-pages/man3/termios.3.html), +including Cygwin on Windows. It restores the saved mode before returning, +including after an interrupted read. Files and pipes use an ordinary one-byte +read. `make test CLASS=TerminalInputTest` runs the Java regression tests for +redirected input, errors, prompt output, Enter, and Escape. Check terminal +editing, echo, and mode restoration by running the executable in a terminal. +`make test CLASS=TicTacToeTest` checks complete games, all eight winning lines, +draws, occupied squares, and CPU blocking. diff --git a/chapter30/docs/examples/TicTacToe.smp b/chapter30/docs/examples/TicTacToe.smp new file mode 100644 index 00000000..a710e440 --- /dev/null +++ b/chapter30/docs/examples/TicTacToe.smp @@ -0,0 +1,133 @@ +// Player is X, CPU is O. Squares are numbered left-to-right, top-to-bottom. +val main = { u8[~][] args -> + sys.io.p("Tic-tac-toe!\nYou are X; the CPU is O. You go first.\n"); + sys.io.p("Press a square's number (no Enter needed), or q/Esc to quit.\n"); + sys.io.p("Squares:\n 1 | 2 | 3 \n---+---+---\n 4 | 5 | 6 \n---+---+---\n 7 | 8 | 9 \n\n"); + val board = new u8[9]; + for( int i=0; i + while( true ) { + int key = sys.io.getch(); + if( key == -1 && sys.libc.errno() != sys.libc.EOF ) { + sys.io.err("\nUnable to read the keyboard.\n"); + return 0; + } + // Various quitting conditions + if( key == -1 || key == 'q' || key == 'Q' || key == 27 || key == 3 || key == 4 ) { + sys.io.p("\nGoodbye!\n"); + return 0; + } + // Ignore CR/LF + if( key == '\r' || key == '\n' ) continue; + + // Valid choice + if( key < '1' || key > '9' ) { + sys.io.p("\nChoose a square from 1 to 9: "); + continue; + } + if( board[key-'1'] != ' ' ) { + sys.io.p("\nThat square is occupied. Your move (1-9): "); + continue; + } + sys.io.p("%c(key)"); + return key-'0'; + } +}; + +// Return the winning mark, or zero. +val _winner = { u8[~] board -> + for( int i=0; i<3; i++ ) { + int row = i*3; + if( board[row]!=' ' && board[row]==board[row+1] && board[row]==board[row+2] ) + return board[row]; + if( board[i]!=' ' && board[i]==board[i+3] && board[i]==board[i+6] ) + return board[i]; + } + if( board[4]!=' ' ) { + if( board[0]==board[4] && board[4]==board[8] ) return board[4]; + if( board[2]==board[4] && board[4]==board[6] ) return board[4]; + } + return 0; +}; + +val _finished = { u8[~] board -> + int winner = _winner(board); + if( winner=='X' ) { sys.io.p("You win!\n"); return true; } + if( winner=='O' ) { sys.io.p("CPU wins!\n"); return true; } + for( int i=0; i + for( int i=0; i + // If O can win, do so + int win = _threat(board,'O'); + if( win>=0 ) return win; + // If X will win next move, block + int block = _threat(board,'X'); + if( block>=0 ) return block; + // If center is open, take it + if( board[4]==' ' ) return 4; + // If a corner is open, take it + for( int i=0; i + val out = new sys.aryu8(); + for( int row=0; row<3; row++ ) { + for( int col=0; col<3; col++ ) { + out.add(' ').add(board[row*3+col]).add(' '); + if( col<2 ) out.add('|'); + } + out.add('\n'); + if( row<2 ) out.addAry("---+---+---\n"); + } + out.write(sys.libc.STDOUT); +}; diff --git a/chapter30/src/main/c/simple_crt.c b/chapter30/src/main/c/simple_crt.c index 8656a3cc..438ae5f9 100644 --- a/chapter30/src/main/c/simple_crt.c +++ b/chapter30/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/chapter30/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter30/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java index 0b314978..8d0237ea 100644 --- a/chapter30/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java +++ b/chapter30/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/chapter30/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/chapter30/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java index 689ceceb..dbde2f8b 100644 --- a/chapter30/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java +++ b/chapter30/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