diff options
| author | Cliff Click <cliffc@acm.org> | 2026-11-08 14:10:02 -0800 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-11-08 14:10:02 -0800 |
| commit | 06fb5aceeb65a75b1cd643debd667c52fdcafcb9 (patch) | |
| tree | c99813eaf27d98f840245302c4d6e923c964307e /chapter30/docs/examples | |
| parent | 6560a92433a38825fd1d44d9c66ffba9f26990ff (diff) | |
| download | Simple-ch30.tar.gz Simple-ch30.zip | |
Better error messagesch30
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.
Diffstat (limited to 'chapter30/docs/examples')
| -rw-r--r-- | chapter30/docs/examples/A_helloWorld.smp | 2 | ||||
| -rw-r--r-- | chapter30/docs/examples/BubbleSort.smp | 38 | ||||
| -rw-r--r-- | chapter30/docs/examples/Capitalize.smp | 23 | ||||
| -rw-r--r-- | chapter30/docs/examples/Dijkstra.smp | 74 | ||||
| -rw-r--r-- | chapter30/docs/examples/FileIO.smp | 52 | ||||
| -rw-r--r-- | chapter30/docs/examples/README.md | 71 | ||||
| -rw-r--r-- | chapter30/docs/examples/TicTacToe.smp | 133 |
7 files changed, 393 insertions, 0 deletions
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<nodes; i++ ) dist[i] = infinity; + // Starting point is zero far from starting point + dist[src] = 0; + + i64 result = -1; + int count = 0; + while( count < nodes && result < 0 ) { + int nearest = -1; + i64 best = infinity; + for( int node=0; node<nodes; node++ ) + if( !visited.get(node) && dist[node] < best ) { + nearest = node; + best = dist[node]; + } + if( nearest < 0 ) count = nodes; + else { + if( nearest == dst ) return best; + visited.set(nearest); + for( int node=0; node<nodes; node++ ) { + u32 index=nearest*nodes + node; + i64 weight = matrix.get(index); + if( weight > 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<matrix.len; i++ ) + if( matrix.get(i) < 0 ) return usage(); + // Matrix is square + int nodes = 0; + while( nodes*nodes < matrix.len ) nodes++; + if( nodes*nodes != matrix.len ) + return usage(); // Not a square matrix + + // src,dst are values within the matrix + i64 src = sys.aryu8.atoi(args[2]); + i64 dst = sys.aryu8.atoi(args[3]); + if( src < 0 || src >= 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<board#; i++ ) board[i]=' '; + // Draw empty board + _draw(board); + + while( true ) { + sys.io.p("Your move (1-9): "); + // Valid user move 1-9, or 0 for quitting + int square = _validMove(board) - 1; + if( square < 0 ) return 0; + // Set user choice; print board + board[square] = 'X'; + sys.io.p("\n"); + _draw(board); + // A winner? + if( _finished(board) ) return 0; + // Compute CPU move + int reply = _cpu(board); + // Set CPU choice; print board + board[reply]='O'; + sys.io.p("CPU plays %(reply+1).\n"); + _draw(board); + // A winner? + if( _finished(board) ) return 0; + } +}; + +// Return with a valid user move 1-9, or 0 to exit +val _validMove = { u8[~] board -> + 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<board#; i++ ) + if( board[i]==' ' ) return false; + sys.io.p("It's a draw!\n"); + return true; +}; + +// Try each empty square, restoring it before returning. +val _threat = { u8[] board, u8 mark -> + for( int i=0; i<board#; i++ ) { + if( board[i]==' ' ) { + board[i]=mark; + bool wins = _winner(board)==mark; + board[i]=' '; // Restore board + if( wins ) return i; + } + } + return -1; +}; + +// Win if possible, otherwise block, then prefer center, corners, and edges. +// Called only after _finished has ruled out a full board. +val _cpu = { u8[] board -> + // 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<board#; i+=2 ) + if( board[i]==' ' ) return i; + // Take whatever is available + for( int i=0; i<board#; i++ ) + if( board[i]==' ' ) return i; + return -1; +}; + +val _draw = { u8[~] board -> + 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); +}; |
