From 6e1eeeebfb453fa7c871869c19375ce60fbd7413 Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Sat, 27 Jul 2019 16:01:55 -0700 Subject: 5485 - promote SubX to top-level --- archive/1.vm.arc/Readme.md | 21 + archive/1.vm.arc/blocking.arc.t | 26 + archive/1.vm.arc/buffered-stdin.mu | 28 + archive/1.vm.arc/callcc.mu | 20 + archive/1.vm.arc/channel.mu | 49 + archive/1.vm.arc/charterm/charterm.rkt | 2798 +++++++++++ archive/1.vm.arc/charterm/demo.rkt | 306 ++ archive/1.vm.arc/charterm/doc.scrbl | 7 + archive/1.vm.arc/charterm/info.rkt | 29 + archive/1.vm.arc/charterm/main.rkt | 3 + .../1.vm.arc/charterm/planet-docs/doc/index.html | 117 + .../1.vm.arc/charterm/planet-docs/doc/racket.css | 234 + .../charterm/planet-docs/doc/scribble-common.js | 153 + .../charterm/planet-docs/doc/scribble-style.css | 0 .../1.vm.arc/charterm/planet-docs/doc/scribble.css | 487 ++ archive/1.vm.arc/charterm/test-charterm.rkt | 20 + archive/1.vm.arc/chessboard.arc.t | 239 + archive/1.vm.arc/chessboard.mu | 259 + archive/1.vm.arc/color-repl.mu | 498 ++ archive/1.vm.arc/counters.mu | 33 + archive/1.vm.arc/edit.arc.t | 33 + archive/1.vm.arc/edit.mu | 18 + archive/1.vm.arc/exuberant-ctags-rc | 7 + archive/1.vm.arc/factorial.mu | 22 + archive/1.vm.arc/fork.mu | 18 + archive/1.vm.arc/generic.mu | 30 + archive/1.vm.arc/graphics.mu | 23 + archive/1.vm.arc/highlights | 21 + archive/1.vm.arc/load.arc | 28 + archive/1.vm.arc/mu | 27 + archive/1.vm.arc/mu.arc | 3259 ++++++++++++ archive/1.vm.arc/mu.arc.t | 5208 ++++++++++++++++++++ archive/1.vm.arc/mu.arc.t.html | 4154 ++++++++++++++++ archive/1.vm.arc/render.vim | 93 + archive/1.vm.arc/scratch.vim | 50 + archive/1.vm.arc/stdin.mu | 27 + archive/1.vm.arc/tangle.mu | 35 + archive/1.vm.arc/trace.arc.t | 1659 +++++++ archive/1.vm.arc/trace.mu | 1092 ++++ archive/1.vm.arc/vimrc.vim | 8 + archive/1.vm.arc/x.mu | 6 + archive/2.vm/000organization.cc | 136 + archive/2.vm/001help.cc | 264 + archive/2.vm/002test.cc | 104 + archive/2.vm/003trace.cc | 501 ++ archive/2.vm/003trace.test.cc | 126 + archive/2.vm/010vm.cc | 900 ++++ archive/2.vm/011load.cc | 489 ++ archive/2.vm/012transform.cc | 102 + archive/2.vm/013update_operation.cc | 40 + archive/2.vm/014literal_string.cc | 274 + archive/2.vm/015literal_noninteger.cc | 51 + archive/2.vm/016dilated_reagent.cc | 166 + archive/2.vm/017parse_tree.cc | 124 + archive/2.vm/018constant.cc | 79 + archive/2.vm/019type_abbreviations.cc | 236 + archive/2.vm/020run.cc | 571 +++ archive/2.vm/021check_instruction.cc | 260 + archive/2.vm/022arithmetic.cc | 1071 ++++ archive/2.vm/023boolean.cc | 224 + archive/2.vm/024jump.cc | 237 + archive/2.vm/025compare.cc | 624 +++ archive/2.vm/026call.cc | 246 + archive/2.vm/027call_ingredient.cc | 220 + archive/2.vm/028call_return.cc | 197 + archive/2.vm/029tools.cc | 326 ++ archive/2.vm/030container.cc | 819 +++ archive/2.vm/031merge.cc | 270 + archive/2.vm/032array.cc | 635 +++ archive/2.vm/033exclusive_container.cc | 554 +++ archive/2.vm/034address.cc | 514 ++ archive/2.vm/035lookup.cc | 664 +++ archive/2.vm/036abandon.cc | 153 + archive/2.vm/038new_text.cc | 288 ++ archive/2.vm/040brace.cc | 566 +++ archive/2.vm/041jump_target.cc | 220 + archive/2.vm/042name.cc | 414 ++ archive/2.vm/043space.cc | 331 ++ archive/2.vm/044space_surround.cc | 79 + archive/2.vm/045closure_name.cc | 204 + archive/2.vm/046check_type_by_name.cc | 265 + archive/2.vm/050scenario.cc | 1039 ++++ archive/2.vm/051scenario_test.mu | 70 + archive/2.vm/052tangle.cc | 529 ++ archive/2.vm/053recipe_header.cc | 793 +++ archive/2.vm/054static_dispatch.cc | 683 +++ archive/2.vm/055shape_shifting_container.cc | 773 +++ archive/2.vm/056shape_shifting_recipe.cc | 1307 +++++ archive/2.vm/057immutable.cc | 715 +++ archive/2.vm/058to_text.cc | 24 + archive/2.vm/059to_text.mu | 48 + archive/2.vm/060rewrite_literal_string.cc | 81 + archive/2.vm/061text.mu | 1427 ++++++ archive/2.vm/062convert_ingredients_to_text.cc | 212 + archive/2.vm/063array.mu | 181 + archive/2.vm/064list.mu | 366 ++ archive/2.vm/065duplex_list.mu | 781 +++ archive/2.vm/066stream.mu | 80 + archive/2.vm/067random.cc | 34 + archive/2.vm/068random.mu | 75 + archive/2.vm/069hash.cc | 422 ++ archive/2.vm/070table.mu | 109 + archive/2.vm/072recipe.cc | 711 +++ archive/2.vm/073scheduler.cc | 709 +++ archive/2.vm/074wait.cc | 664 +++ archive/2.vm/075channel.mu | 510 ++ archive/2.vm/076continuation.cc | 406 ++ archive/2.vm/080display.cc | 462 ++ archive/2.vm/081print.mu | 914 ++++ archive/2.vm/082scenario_screen.cc | 458 ++ archive/2.vm/083scenario_screen_test.mu | 47 + archive/2.vm/084console.mu | 104 + archive/2.vm/085scenario_console.cc | 317 ++ archive/2.vm/086scenario_console_test.mu | 25 + archive/2.vm/087file.cc | 225 + archive/2.vm/088file.mu | 213 + archive/2.vm/089scenario_filesystem.cc | 245 + archive/2.vm/090scenario_filesystem_test.mu | 99 + archive/2.vm/091socket.cc | 348 ++ archive/2.vm/092socket.mu | 177 + archive/2.vm/099hardware_checks.cc | 67 + archive/2.vm/101run_sandboxed.cc | 711 +++ archive/2.vm/998check_type_pointers.cc | 36 + archive/2.vm/999spaces.cc | 86 + archive/2.vm/Readme.md | 449 ++ archive/2.vm/args.mu | 8 + archive/2.vm/build0 | 46 + archive/2.vm/build1 | 69 + archive/2.vm/build2 | 175 + archive/2.vm/build3 | 201 + archive/2.vm/build4 | 297 ++ archive/2.vm/build_and_test_until | 18 + archive/2.vm/cannot_write_tests_for | 17 + archive/2.vm/channel.mu | 45 + archive/2.vm/chessboard.mu | 572 +++ archive/2.vm/clean | 9 + archive/2.vm/console.mu | 16 + archive/2.vm/continuation1.mu | 25 + archive/2.vm/continuation2.mu | 37 + archive/2.vm/continuation3.mu | 34 + archive/2.vm/continuation4.mu | 47 + archive/2.vm/continuation5.mu | 49 + archive/2.vm/copy_mu | 11 + archive/2.vm/counters.mu | 29 + archive/2.vm/display.mu | 25 + archive/2.vm/edit/001-editor.mu | 464 ++ archive/2.vm/edit/002-typing.mu | 1144 +++++ archive/2.vm/edit/003-shortcuts.mu | 4462 +++++++++++++++++ archive/2.vm/edit/004-programming-environment.mu | 549 +++ archive/2.vm/edit/005-sandbox.mu | 1193 +++++ archive/2.vm/edit/006-sandbox-copy.mu | 395 ++ archive/2.vm/edit/007-sandbox-delete.mu | 342 ++ archive/2.vm/edit/008-sandbox-edit.mu | 325 ++ archive/2.vm/edit/009-sandbox-test.mu | 231 + archive/2.vm/edit/010-sandbox-trace.mu | 253 + archive/2.vm/edit/011-errors.mu | 886 ++++ archive/2.vm/edit/012-editor-undo.mu | 2111 ++++++++ archive/2.vm/edit/Readme.md | 49 + archive/2.vm/example1.mu | 7 + archive/2.vm/exception1.mu | 61 + archive/2.vm/exception2.mu | 62 + archive/2.vm/exuberant_ctags_rc | 11 + archive/2.vm/factorial.mu | 33 + archive/2.vm/filesystem.mu | 20 + archive/2.vm/fork.mu | 16 + archive/2.vm/git_log_filtered | 9 + archive/2.vm/http-client.mu | 29 + archive/2.vm/http-server.mu | 28 + archive/2.vm/immutable-error.mu | 13 + archive/2.vm/lambda-to-mu.mu | 590 +++ archive/2.vm/mu | 11 + archive/2.vm/mu.vim | 98 + archive/2.vm/mutable.mu | 13 + archive/2.vm/new_lesson | 15 + archive/2.vm/nqueens.mu | 101 + archive/2.vm/real-files.mu | 18 + archive/2.vm/relayout | 65 + archive/2.vm/same-fringe.mu | 89 + archive/2.vm/sandbox/001-editor.mu | 464 ++ archive/2.vm/sandbox/002-typing.mu | 1144 +++++ archive/2.vm/sandbox/003-shortcuts.mu | 2800 +++++++++++ .../2.vm/sandbox/004-programming-environment.mu | 268 + archive/2.vm/sandbox/005-sandbox.mu | 1081 ++++ archive/2.vm/sandbox/006-sandbox-copy.mu | 286 ++ archive/2.vm/sandbox/007-sandbox-delete.mu | 345 ++ archive/2.vm/sandbox/008-sandbox-edit.mu | 319 ++ archive/2.vm/sandbox/009-sandbox-test.mu | 233 + archive/2.vm/sandbox/010-sandbox-trace.mu | 243 + archive/2.vm/sandbox/011-errors.mu | 687 +++ archive/2.vm/sandbox/012-editor-undo.mu | 1907 +++++++ archive/2.vm/sandbox/Readme.md | 33 + archive/2.vm/sandbox/mu_run | 16 + archive/2.vm/sandbox/tmux.conf | 3 + archive/2.vm/screen.mu | 29 + archive/2.vm/snapshot_lesson | 12 + archive/2.vm/static-dispatch.mu | 29 + archive/2.vm/tangle.mu | 36 + archive/2.vm/termbox/COPYING | 19 + archive/2.vm/termbox/Readme | 2 + archive/2.vm/termbox/bytebuffer.inl | 79 + archive/2.vm/termbox/input.inl | 185 + archive/2.vm/termbox/output.inl | 320 ++ archive/2.vm/termbox/termbox.c | 397 ++ archive/2.vm/termbox/termbox.h | 190 + archive/2.vm/termbox/utf8.c | 79 + archive/2.vm/test_layers | 88 + archive/2.vm/vimrc.vim | 31 + archive/2.vm/x.mu | 8 + archive/3.transect/Readme | 6 + archive/3.transect/build | 109 + archive/3.transect/build_and_test_until | 18 + archive/3.transect/clean | 8 + archive/3.transect/compiler10 | 304 ++ archive/3.transect/compiler2 | 27 + archive/3.transect/compiler3 | 73 + archive/3.transect/compiler4 | 84 + archive/3.transect/compiler5 | 32 + archive/3.transect/compiler6 | 36 + archive/3.transect/compiler7 | 46 + archive/3.transect/compiler8 | 53 + archive/3.transect/compiler9 | 254 + archive/3.transect/ex3.k2 | 22 + archive/3.transect/ex4.k2 | 34 + archive/3.transect/ex5.k2 | 30 + archive/3.transect/ex6.k2 | 23 + archive/3.transect/ex7.k2 | 64 + archive/3.transect/ex8.k2 | 36 + archive/3.transect/factorial.k2 | 16 + archive/3.transect/vimrc.vim | 36 + 229 files changed, 80488 insertions(+) create mode 100644 archive/1.vm.arc/Readme.md create mode 100644 archive/1.vm.arc/blocking.arc.t create mode 100644 archive/1.vm.arc/buffered-stdin.mu create mode 100644 archive/1.vm.arc/callcc.mu create mode 100644 archive/1.vm.arc/channel.mu create mode 100644 archive/1.vm.arc/charterm/charterm.rkt create mode 100644 archive/1.vm.arc/charterm/demo.rkt create mode 100644 archive/1.vm.arc/charterm/doc.scrbl create mode 100644 archive/1.vm.arc/charterm/info.rkt create mode 100644 archive/1.vm.arc/charterm/main.rkt create mode 100644 archive/1.vm.arc/charterm/planet-docs/doc/index.html create mode 100644 archive/1.vm.arc/charterm/planet-docs/doc/racket.css create mode 100644 archive/1.vm.arc/charterm/planet-docs/doc/scribble-common.js create mode 100644 archive/1.vm.arc/charterm/planet-docs/doc/scribble-style.css create mode 100644 archive/1.vm.arc/charterm/planet-docs/doc/scribble.css create mode 100644 archive/1.vm.arc/charterm/test-charterm.rkt create mode 100644 archive/1.vm.arc/chessboard.arc.t create mode 100644 archive/1.vm.arc/chessboard.mu create mode 100644 archive/1.vm.arc/color-repl.mu create mode 100644 archive/1.vm.arc/counters.mu create mode 100644 archive/1.vm.arc/edit.arc.t create mode 100644 archive/1.vm.arc/edit.mu create mode 100644 archive/1.vm.arc/exuberant-ctags-rc create mode 100644 archive/1.vm.arc/factorial.mu create mode 100644 archive/1.vm.arc/fork.mu create mode 100644 archive/1.vm.arc/generic.mu create mode 100644 archive/1.vm.arc/graphics.mu create mode 100644 archive/1.vm.arc/highlights create mode 100644 archive/1.vm.arc/load.arc create mode 100755 archive/1.vm.arc/mu create mode 100644 archive/1.vm.arc/mu.arc create mode 100644 archive/1.vm.arc/mu.arc.t create mode 100644 archive/1.vm.arc/mu.arc.t.html create mode 100644 archive/1.vm.arc/render.vim create mode 100644 archive/1.vm.arc/scratch.vim create mode 100644 archive/1.vm.arc/stdin.mu create mode 100644 archive/1.vm.arc/tangle.mu create mode 100644 archive/1.vm.arc/trace.arc.t create mode 100644 archive/1.vm.arc/trace.mu create mode 100644 archive/1.vm.arc/vimrc.vim create mode 100644 archive/1.vm.arc/x.mu create mode 100644 archive/2.vm/000organization.cc create mode 100644 archive/2.vm/001help.cc create mode 100644 archive/2.vm/002test.cc create mode 100644 archive/2.vm/003trace.cc create mode 100644 archive/2.vm/003trace.test.cc create mode 100644 archive/2.vm/010vm.cc create mode 100644 archive/2.vm/011load.cc create mode 100644 archive/2.vm/012transform.cc create mode 100644 archive/2.vm/013update_operation.cc create mode 100644 archive/2.vm/014literal_string.cc create mode 100644 archive/2.vm/015literal_noninteger.cc create mode 100644 archive/2.vm/016dilated_reagent.cc create mode 100644 archive/2.vm/017parse_tree.cc create mode 100644 archive/2.vm/018constant.cc create mode 100644 archive/2.vm/019type_abbreviations.cc create mode 100644 archive/2.vm/020run.cc create mode 100644 archive/2.vm/021check_instruction.cc create mode 100644 archive/2.vm/022arithmetic.cc create mode 100644 archive/2.vm/023boolean.cc create mode 100644 archive/2.vm/024jump.cc create mode 100644 archive/2.vm/025compare.cc create mode 100644 archive/2.vm/026call.cc create mode 100644 archive/2.vm/027call_ingredient.cc create mode 100644 archive/2.vm/028call_return.cc create mode 100644 archive/2.vm/029tools.cc create mode 100644 archive/2.vm/030container.cc create mode 100644 archive/2.vm/031merge.cc create mode 100644 archive/2.vm/032array.cc create mode 100644 archive/2.vm/033exclusive_container.cc create mode 100644 archive/2.vm/034address.cc create mode 100644 archive/2.vm/035lookup.cc create mode 100644 archive/2.vm/036abandon.cc create mode 100644 archive/2.vm/038new_text.cc create mode 100644 archive/2.vm/040brace.cc create mode 100644 archive/2.vm/041jump_target.cc create mode 100644 archive/2.vm/042name.cc create mode 100644 archive/2.vm/043space.cc create mode 100644 archive/2.vm/044space_surround.cc create mode 100644 archive/2.vm/045closure_name.cc create mode 100644 archive/2.vm/046check_type_by_name.cc create mode 100644 archive/2.vm/050scenario.cc create mode 100644 archive/2.vm/051scenario_test.mu create mode 100644 archive/2.vm/052tangle.cc create mode 100644 archive/2.vm/053recipe_header.cc create mode 100644 archive/2.vm/054static_dispatch.cc create mode 100644 archive/2.vm/055shape_shifting_container.cc create mode 100644 archive/2.vm/056shape_shifting_recipe.cc create mode 100644 archive/2.vm/057immutable.cc create mode 100644 archive/2.vm/058to_text.cc create mode 100644 archive/2.vm/059to_text.mu create mode 100644 archive/2.vm/060rewrite_literal_string.cc create mode 100644 archive/2.vm/061text.mu create mode 100644 archive/2.vm/062convert_ingredients_to_text.cc create mode 100644 archive/2.vm/063array.mu create mode 100644 archive/2.vm/064list.mu create mode 100644 archive/2.vm/065duplex_list.mu create mode 100644 archive/2.vm/066stream.mu create mode 100644 archive/2.vm/067random.cc create mode 100644 archive/2.vm/068random.mu create mode 100644 archive/2.vm/069hash.cc create mode 100644 archive/2.vm/070table.mu create mode 100644 archive/2.vm/072recipe.cc create mode 100644 archive/2.vm/073scheduler.cc create mode 100644 archive/2.vm/074wait.cc create mode 100644 archive/2.vm/075channel.mu create mode 100644 archive/2.vm/076continuation.cc create mode 100644 archive/2.vm/080display.cc create mode 100644 archive/2.vm/081print.mu create mode 100644 archive/2.vm/082scenario_screen.cc create mode 100644 archive/2.vm/083scenario_screen_test.mu create mode 100644 archive/2.vm/084console.mu create mode 100644 archive/2.vm/085scenario_console.cc create mode 100644 archive/2.vm/086scenario_console_test.mu create mode 100644 archive/2.vm/087file.cc create mode 100644 archive/2.vm/088file.mu create mode 100644 archive/2.vm/089scenario_filesystem.cc create mode 100644 archive/2.vm/090scenario_filesystem_test.mu create mode 100644 archive/2.vm/091socket.cc create mode 100644 archive/2.vm/092socket.mu create mode 100644 archive/2.vm/099hardware_checks.cc create mode 100644 archive/2.vm/101run_sandboxed.cc create mode 100644 archive/2.vm/998check_type_pointers.cc create mode 100644 archive/2.vm/999spaces.cc create mode 100644 archive/2.vm/Readme.md create mode 100644 archive/2.vm/args.mu create mode 100755 archive/2.vm/build0 create mode 100755 archive/2.vm/build1 create mode 100755 archive/2.vm/build2 create mode 100755 archive/2.vm/build3 create mode 100755 archive/2.vm/build4 create mode 100755 archive/2.vm/build_and_test_until create mode 100644 archive/2.vm/cannot_write_tests_for create mode 100644 archive/2.vm/channel.mu create mode 100644 archive/2.vm/chessboard.mu create mode 100755 archive/2.vm/clean create mode 100644 archive/2.vm/console.mu create mode 100644 archive/2.vm/continuation1.mu create mode 100644 archive/2.vm/continuation2.mu create mode 100644 archive/2.vm/continuation3.mu create mode 100644 archive/2.vm/continuation4.mu create mode 100644 archive/2.vm/continuation5.mu create mode 100755 archive/2.vm/copy_mu create mode 100644 archive/2.vm/counters.mu create mode 100644 archive/2.vm/display.mu create mode 100644 archive/2.vm/edit/001-editor.mu create mode 100644 archive/2.vm/edit/002-typing.mu create mode 100644 archive/2.vm/edit/003-shortcuts.mu create mode 100644 archive/2.vm/edit/004-programming-environment.mu create mode 100644 archive/2.vm/edit/005-sandbox.mu create mode 100644 archive/2.vm/edit/006-sandbox-copy.mu create mode 100644 archive/2.vm/edit/007-sandbox-delete.mu create mode 100644 archive/2.vm/edit/008-sandbox-edit.mu create mode 100644 archive/2.vm/edit/009-sandbox-test.mu create mode 100644 archive/2.vm/edit/010-sandbox-trace.mu create mode 100644 archive/2.vm/edit/011-errors.mu create mode 100644 archive/2.vm/edit/012-editor-undo.mu create mode 100644 archive/2.vm/edit/Readme.md create mode 100644 archive/2.vm/example1.mu create mode 100644 archive/2.vm/exception1.mu create mode 100644 archive/2.vm/exception2.mu create mode 100644 archive/2.vm/exuberant_ctags_rc create mode 100644 archive/2.vm/factorial.mu create mode 100644 archive/2.vm/filesystem.mu create mode 100644 archive/2.vm/fork.mu create mode 100755 archive/2.vm/git_log_filtered create mode 100644 archive/2.vm/http-client.mu create mode 100644 archive/2.vm/http-server.mu create mode 100644 archive/2.vm/immutable-error.mu create mode 100644 archive/2.vm/lambda-to-mu.mu create mode 100755 archive/2.vm/mu create mode 100644 archive/2.vm/mu.vim create mode 100644 archive/2.vm/mutable.mu create mode 100755 archive/2.vm/new_lesson create mode 100644 archive/2.vm/nqueens.mu create mode 100644 archive/2.vm/real-files.mu create mode 100755 archive/2.vm/relayout create mode 100644 archive/2.vm/same-fringe.mu create mode 100644 archive/2.vm/sandbox/001-editor.mu create mode 100644 archive/2.vm/sandbox/002-typing.mu create mode 100644 archive/2.vm/sandbox/003-shortcuts.mu create mode 100644 archive/2.vm/sandbox/004-programming-environment.mu create mode 100644 archive/2.vm/sandbox/005-sandbox.mu create mode 100644 archive/2.vm/sandbox/006-sandbox-copy.mu create mode 100644 archive/2.vm/sandbox/007-sandbox-delete.mu create mode 100644 archive/2.vm/sandbox/008-sandbox-edit.mu create mode 100644 archive/2.vm/sandbox/009-sandbox-test.mu create mode 100644 archive/2.vm/sandbox/010-sandbox-trace.mu create mode 100644 archive/2.vm/sandbox/011-errors.mu create mode 100644 archive/2.vm/sandbox/012-editor-undo.mu create mode 100644 archive/2.vm/sandbox/Readme.md create mode 100755 archive/2.vm/sandbox/mu_run create mode 100644 archive/2.vm/sandbox/tmux.conf create mode 100644 archive/2.vm/screen.mu create mode 100755 archive/2.vm/snapshot_lesson create mode 100644 archive/2.vm/static-dispatch.mu create mode 100644 archive/2.vm/tangle.mu create mode 100644 archive/2.vm/termbox/COPYING create mode 100644 archive/2.vm/termbox/Readme create mode 100644 archive/2.vm/termbox/bytebuffer.inl create mode 100644 archive/2.vm/termbox/input.inl create mode 100644 archive/2.vm/termbox/output.inl create mode 100644 archive/2.vm/termbox/termbox.c create mode 100644 archive/2.vm/termbox/termbox.h create mode 100644 archive/2.vm/termbox/utf8.c create mode 100755 archive/2.vm/test_layers create mode 100644 archive/2.vm/vimrc.vim create mode 100644 archive/2.vm/x.mu create mode 100644 archive/3.transect/Readme create mode 100755 archive/3.transect/build create mode 100755 archive/3.transect/build_and_test_until create mode 100755 archive/3.transect/clean create mode 100644 archive/3.transect/compiler10 create mode 100644 archive/3.transect/compiler2 create mode 100644 archive/3.transect/compiler3 create mode 100644 archive/3.transect/compiler4 create mode 100644 archive/3.transect/compiler5 create mode 100644 archive/3.transect/compiler6 create mode 100644 archive/3.transect/compiler7 create mode 100644 archive/3.transect/compiler8 create mode 100644 archive/3.transect/compiler9 create mode 100644 archive/3.transect/ex3.k2 create mode 100644 archive/3.transect/ex4.k2 create mode 100644 archive/3.transect/ex5.k2 create mode 100644 archive/3.transect/ex6.k2 create mode 100644 archive/3.transect/ex7.k2 create mode 100644 archive/3.transect/ex8.k2 create mode 100644 archive/3.transect/factorial.k2 create mode 100644 archive/3.transect/vimrc.vim (limited to 'archive') diff --git a/archive/1.vm.arc/Readme.md b/archive/1.vm.arc/Readme.md new file mode 100644 index 00000000..b8292abb --- /dev/null +++ b/archive/1.vm.arc/Readme.md @@ -0,0 +1,21 @@ +Original prototype, last modified 2015-03-14 + +First install [Racket](http://racket-lang.org) (just for this prototype; +last tested with v6.3). Then: + + ```shell + $ cd mu/archives/1.vm + $ git clone http://github.com/arclanguage/anarki + $ cd anarki + $ git checkout d7290130a7 # last compatible snapshot + $ cd .. + $ ./mu test mu.arc.t # run tests + ``` + +Example programs: + + ```shell + $ ./mu factorial.mu # computes factorial of 5 + $ ./mu fork.mu # two threads print '33' and '34' forever + $ ./mu channel.mu # two threads in a producer/consumer relationship + ``` diff --git a/archive/1.vm.arc/blocking.arc.t b/archive/1.vm.arc/blocking.arc.t new file mode 100644 index 00000000..80f7f229 --- /dev/null +++ b/archive/1.vm.arc/blocking.arc.t @@ -0,0 +1,26 @@ +(selective-load "mu.arc" section-level) +(set allow-raw-addresses*) + +(reset) +(new-trace "blocking-example") +(add-code + '((function reader [ + (default-space:space-address <- new space:literal 30:literal/capacity) + (x:tagged-value 1:channel-address/space:global <- read 1:channel-address/space:global) + ]) + (function main [ + (default-space:space-address <- new space:literal 30:literal/capacity) + (1:channel-address <- init-channel 3:literal) + (2:integer/routine <- fork-helper reader:fn default-space:space-address/globals 50:literal/limit) + ; write nothing to the channel +;? (sleep until-routine-done:literal 2:integer/routine) + ]))) +;? (= dump-trace* (obj whitelist '("schedule" "run"))) +(run 'main) +;? (prn "completed:") +;? (each r completed-routines* +;? (prn " " r)) +(when (ran-to-completion 'reader) + (prn "F - reader waits for input")) + +(reset) diff --git a/archive/1.vm.arc/buffered-stdin.mu b/archive/1.vm.arc/buffered-stdin.mu new file mode 100644 index 00000000..9a7bc7ae --- /dev/null +++ b/archive/1.vm.arc/buffered-stdin.mu @@ -0,0 +1,28 @@ +; reads lines, prints them back when you hit 'enter' +; dies if you wait a while, because so far we never free memory +(function main [ + (default-space:space-address <- new space:literal 30:literal) + (cursor-mode) ;? 1 + ; hook up stdin + (stdin:channel-address <- init-channel 1:literal) + (fork-helper send-keys-to-stdin:fn nil:literal/globals nil:literal/limit nil:literal/keyboard stdin:channel-address) + ; buffer stdin + (buffered-stdin:channel-address <- init-channel 1:literal) + (fork-helper buffer-lines:fn nil:literal/globals nil:literal/limit stdin:channel-address buffered-stdin:channel-address) + { begin + ; now read characters from the buffer until 'enter' is typed + (s:string-address <- new "? ") + (print-string nil:literal/terminal s:string-address) + { begin + (x:tagged-value buffered-stdin:channel-address/deref <- read buffered-stdin:channel-address) + (c:character <- maybe-coerce x:tagged-value character:literal) +;? ($print (("AAA " literal))) ;? 1 +;? ($print c:character) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (print-character nil:literal/terminal c:character) + (line-done?:boolean <- equal c:character ((#\newline literal))) + (loop-unless line-done?:boolean) + } + (loop) + } +]) diff --git a/archive/1.vm.arc/callcc.mu b/archive/1.vm.arc/callcc.mu new file mode 100644 index 00000000..20dffeff --- /dev/null +++ b/archive/1.vm.arc/callcc.mu @@ -0,0 +1,20 @@ +; in mu, call-cc (http://en.wikipedia.org/wiki/Call-with-current-continuation) +; is constructed out of a combination of two primitives: +; 'current-continuation', which returns a continuation, and +; 'continue-from', which takes a continuation to + +(function g [ + (c:continuation <- current-continuation) ; <-- loop back to here + (print-character nil:literal/terminal ((#\a literal))) + (reply c:continuation) +]) + +(function f [ + (c:continuation <- g) + (reply c:continuation) +]) + +(function main [ + (c:continuation <- f) + (continue-from c:continuation) ; <-- ..when you hit this +]) diff --git a/archive/1.vm.arc/channel.mu b/archive/1.vm.arc/channel.mu new file mode 100644 index 00000000..61151833 --- /dev/null +++ b/archive/1.vm.arc/channel.mu @@ -0,0 +1,49 @@ +(function producer [ + ; produce numbers 1 to 5 on a channel + (default-space:space-address <- new space:literal 30:literal) + (chan:channel-address <- next-input) + ; n = 0 + (n:integer <- copy 0:literal) + { begin + (done?:boolean <- less-than n:integer 5:literal) + (break-unless done?:boolean) + ; other threads might get between these prints + ($print (("produce: " literal))) + (print-integer nil:literal/terminal n:integer) + ($print (("\n" literal))) + ; 'box' n into a dynamically typed 'tagged value' because that's what + ; channels take + (n2:integer <- copy n:integer) + (n3:tagged-value-address <- init-tagged-value integer:literal n2:integer) + (chan:channel-address/deref <- write chan:channel-address n3:tagged-value-address/deref) + (n:integer <- add n:integer 1:literal) + (loop) + } +]) + +(function consumer [ + ; consume and print integers from a channel + (default-space:space-address <- new space:literal 30:literal) + (chan:channel-address <- next-input) + { begin + ; read a tagged value from the channel + (x:tagged-value chan:channel-address/deref <- read chan:channel-address) + ; unbox the tagged value into an integer + (n2:integer <- maybe-coerce x:tagged-value integer:literal) + ; other threads might get between these prints + ($print (("consume: " literal))) + (print-integer nil:literal/terminal n2:integer) + ($print (("\n" literal))) + (loop) + } +]) + +(function main [ + (default-space:space-address <- new space:literal 30:literal) + (chan:channel-address <- init-channel 3:literal) + ; create two background 'routines' that communicate by a channel + (routine1:integer <- fork consumer:fn nil:literal/globals nil:literal/limit chan:channel-address) + (routine2:integer <- fork producer:fn nil:literal/globals nil:literal/limit chan:channel-address) + (sleep until-routine-done:literal routine1:integer) + (sleep until-routine-done:literal routine2:integer) +]) diff --git a/archive/1.vm.arc/charterm/charterm.rkt b/archive/1.vm.arc/charterm/charterm.rkt new file mode 100644 index 00000000..cae12098 --- /dev/null +++ b/archive/1.vm.arc/charterm/charterm.rkt @@ -0,0 +1,2798 @@ +#lang racket/base +;; Copyright (c) Neil Van Dyke. See file "info.rkt". + +(require (for-syntax racket/base + racket/syntax) + racket/system + (planet neil/mcfly)) + +(doc (section "Introduction") + + (para "The " + "CharTerm" + " package provides a Racket interface for character-cell video +display terminals on Unix-like systems -- such as for " + (as-index "GNU Screen") + " and " + (as-index (code "tmux")) + " sessions on " + (index '("cloud server" "server") "cloud servers") + ", " + (as-index "XTerm") + " windows on a workstation desktop, and some older hardware +terminals (even the venerable " + (as-index "DEC VT100") + "). Currently, it implements a subset of features available on most +terminals.") + + (para "This package could be used to implement a status/management console +for a Racket-based server process (perhaps run in GNU Screen or " + (code "tmux") + " on a server machine, to be detached and reattached from SSH +sessions), a lightweight user interface for a systems tool, a command-line +REPL, a text editor, creative retro uses of old equipment, and, perhaps most +importantly, a " + ;; (hyperlink "http://en.wikipedia.org/wiki/Rogue_%28computer_game%29" + "Rogue-like" + ;;) + " application.") + + (para "The " + "CharTerm" + " package does not include any native code (such as from " + (as-index (code "terminfo")) + ", " + (as-index (code "termcap")) + ", " + (as-index (code "curses")) + ", or " + (as-index (code "ncurses")) + ") in the Racket process, +such as through the Racket FFI or C extensions, so there is less potential for +a problem involving native code to threaten the reliability or security of a +program. " + "CharTerm" + " is implemented in pure Racket code except for executing " + (code "/bin/stty") + " for some purposes. Specifically, " + (code "/bin/stty") + " at startup time and shutdown time, to set modes, and (for terminal +types that don't seem to support a screen size report control sequence) when +getting screen size. Besides security and stability, lower dependence on +native code might also simplify porting to host platforms that don't have those +native code facilities.")) + +(doc (subsection "Demo") + + (para "For a demonstration, the following command, run from a terminal, should install the " + "CharTerm" + " package (if not already installed), and run the demo:") + + (commandline "racket -pm neil/charterm/demo") + + (para "This demo reports what keys you pressed, while letting you edit a +text field, and while displaying a clock. The clock is updated roughly once +per second, and is not updated during heavy keyboard input, such as when typing +fast. The demo responds to changing terminal sizes, such as when an XTerm is +window is resized. It also displays the determined terminal size, and some +small tests of the " + (racket #:width) + " argument to " + (racket charterm-display) + ". Exit the demo by pressing the " + (bold "Esc") + " key.") + + (para "Note: Although this demo includes an editable text field, as proof +of concept, the current version of " + "CharTerm" + " does not provide editable text fields as reusable functionality.")) + +(doc (subsection "Simple Example") + + (para "Here's your first " + "CharTerm" + " program:") + + (RACKETBLOCK + (UNSYNTAX (code "#lang racket/base")) + + (require (planet neil/charterm)) + + (with-charterm + (charterm-clear-screen) + (charterm-cursor 10 5) + (charterm-display "Hello, ") + (charterm-bold) + (charterm-display "you") + (charterm-normal) + (charterm-display ".") + (charterm-cursor 1 1) + (charterm-display "Press a key...") + (let ((key (charterm-read-key))) + (charterm-cursor 1 1) + (charterm-clear-line) + (printf "You pressed: ~S\r\n" key)))) + + (para "Now you're living the dream of the '70s.")) + +(doc (section "Terminal Diversity") + + (para "Like people, few terminals are exactly the same.") + + (para "Some key (ha) terms (ha) used by " + "CharTerm" + " are:") + + (itemlist (item (tech "termvar") + " --- a string value like from the Unix-like " + (code "TERM") + " environment variable, used to determine a default " + (tech "protocol") + " and " + (tech "keydec") + ".") + + (item (tech "protocol") + " --- how to control the display, query for information, etc.") + + (item (tech "keydec") + " --- how to decode key encodings of a particular +terminal. A keydec is constructed from one or more keysets, can produce " + (tech "keycode") + "s or " + (tech "keyinfo") + "s.") + + (item (tech "keyset") + " --- a specification of encoding some of the keys in a +particular terminal, including " + (tech "keylabel") + "s and " + (tech "keycode") + "s.") + + (item (tech "keylabel") + " --- a string for how a key is likely labeled on a +keyboard, such as the DEC VT100 " + (bold "PF1") + " key would have a keylabel " + (racket "PF1") + " for a " + (tech "keycode") + " " + (racket 'f1) + ".") + + (item (tech "keycode") + " --- a value produced by a decoded key, +such as a character for normal printable keys, like " + (racket #\a) + " and " + (racket #\space) + ", a symbol for some recognized unprintable keys, like " + (racket 'escape) + " and " + (racket 'f1) + ", or possibly a number for unrecognized keys.") + + (item (tech "keyinfo") + " --- an object that is used like a " + (tech "keycode") + ", except +bundles together a keycode and a " + (tech "keylabel") + ", as well as alternatate keycodes and +information about how the key was decoded (e.g., from which " + (tech "keyset") + ").")) + + (para "These terms are discussed in the following subsections.") + + (para "CharTerm" + " is developed with help of original documentation such as that +curated by Paul Williams at " + (hyperlink "http://vt100.net/" "vt100.net") + ", various commentary found on the Web, observed behavior with +modern software terminals like XTerm, various emulators for hardware terminals, +and sometimes original hardware terminals. Thanks to Mark Pearrow for +contributing a TeleVideo 950, and Paul McCabe for a Wyse S50 WinTerm.") + + (para "At time of this writing, the author is looking to acquire a DEC +VT525, circa 1994, for ongoing testing.") + + (para "The author welcomes feedback on useful improvements to " + "CharTerm" + "'s support for terminal diversity (no pun). If you have a terminal +that is sending an escape sequence not recognized by the demo, you can run the +demo with the " + (Flag "n") + " (aka " + (DFlag "no-escape") + ") argument to see the exact byte sequence:") + + (commandline "racket -pm- neil/charterm/demo -n") + + (para "When " + (Flag "n") + " is used, this will be indicated by the bottom-most scrolling line, +rather than saying ``" + (tt "To quit, press " (bold "Esc") ".") + "'' instead will say ``" + (tt "There is no escape from this demo.") + "'' You will have to kill the process through some other means.")) + +(doc (subsection "Protocol") + + (para "The first concept " + "CharTerm" + " has for distinguishing how to communicate with a terminal is what +is what is called here " + (deftech "protocol") + ", which concerns everything except how keyboard keys are decoded. +The following protocols are currently implemented:") + + (itemlist + + (item (deftech (code "ansi") " protocol") + " --- Terminals approximating [" + (tech "ANSI X3.64") + "], which is most terminals in use today, including software ones +like XTerm. This protocol is the emphasis of this package; the other protocols +are for unusual situations.") + + ;; (item (code "dec-vt100") + ;; " --- The DEC VT100 and compatibles that could be considered " + ;; (code "ansi") + ;; " except don't have insert-line and delete-line.") + + (item (deftech (code "wyse-wy50") " protocol") + " --- Terminals compatible with the Wyse WY-50. This support is +based on [" + (tech "WY-50-QRG") + "], [" + (tech "WY-60-UG") + "], [" + (tech "wy60") + "], and [" + (tech "PowerTerm") + "]. Note that video attributes are not supported, due to the WY-50's +model of having video attribute changes occupy character cells; you may wish +to run the Wyse terminal in an ANSI or VT100 mode.") + + (item (deftech (code "televideo-925") " protocol") + " --- Terminals compatible with the TeleVideo 925. This support is based on [" + (tech "TVI-925-IUG") + "] and behavior of [" + (tech "PowerTerm") + "]. Note that video attributes are not supported, due to the 925's +model of having video attribute changes occupy character cells; you may wish to +run your TeleVideo terminal in ANSI or VT100 mode, if it has one.") + + (item (deftech (code "ascii") " protocol") + " --- Terminals that support ASCII but not much else that we know about."))) + +(define-syntax (%charterm:protocol-case stx) + (syntax-case stx (else) + ((_ ERROR-NAME ACTUAL-PROTO (PART0 PART1 PARTn ...) ...) + (let loop-clauses ((clause-stxes (syntax->list #'((PART0 PART1 PARTn ...) ...))) + (reverse-out-clause-stxes '()) + (else-stx #f) + (need-protos-hash (make-immutable-hasheq (map (lambda (proto) + (cons proto #t)) + '(ansi + televideo-925 + wyse-wy50))))) + (if (null? clause-stxes) + (let ((missing-protos (hash-keys need-protos-hash))) + (if (or else-stx (null? missing-protos)) + (quasisyntax/loc stx + (let ((actual-proto ACTUAL-PROTO)) + (case actual-proto + #,@(reverse reverse-out-clause-stxes) + #,(or else-stx + (syntax/loc stx + (else (error ERROR-NAME + "unimplemented for protocol: ~S" + actual-proto))))))) + (raise-syntax-error '%charterm:protocol-case + (format "missing protocols ~S" missing-protos) + stx))) + (let* ((clause-stx (car clause-stxes)) + (clause-parts (syntax->list clause-stx)) + (part0-stx (car clause-parts)) + (part0-e (syntax-e part0-stx))) + (if (eq? 'else part0-e) + (if else-stx + (raise-syntax-error '%charterm:protocol-case + "else clause multiply defined" + clause-stx + #f + (list else-stx)) + (loop-clauses (cdr clause-stxes) + reverse-out-clause-stxes + clause-stx + need-protos-hash)) + (let loop-protos ((proto-stxes (syntax->list (car (syntax->list clause-stx)))) + (need-protos-hash need-protos-hash)) + (if (null? proto-stxes) + (loop-clauses (cdr clause-stxes) + (cons clause-stx reverse-out-clause-stxes) + else-stx + need-protos-hash) + (let* ((proto-stx (car proto-stxes)) + (proto-e (syntax-e proto-stx))) + (if (symbol? proto-e) + (if (hash-has-key? need-protos-hash proto-e) + (loop-protos (cdr proto-stxes) + (hash-remove need-protos-hash proto-e)) + (raise-syntax-error '%charterm:protocol-case + "protocol unrecognized or multiply defined" + proto-stx)) + (raise-syntax-error '%charterm:protocol-case + "invalid protocol symbol" + proto-stx)))))))))))) + +(define-syntax (%charterm:unimplemented stx) + (syntax-case stx () + ((_ CT ERROR-NAME) + (syntax/loc stx + (error ERROR-NAME + "unimplemented feature for protocol ~S" + (charterm-protocol CT)))))) + +(doc (subsection "Key Encoding") + + (para "While most video display control, they seem to vary more by key +encoding.") + + (para "The " + "CharTerm" + " author was motivated to increase the sophistication of its +keyboard handling after a series of revelations on the Sunday of the long +weekend in which " + "CharTerm" + " was initially written. The first was discovering that four of the +function keys that had been working fine in " + (code "rxvt") + " did not work in XTerm. Dave Gilbert somewhat demystified this by +pointing out that the original VT100 had only four function keys, which set +into motion an unfortunate series of bad decisions by various developers of +terminal software to be needlessly incompatible with each other. After +Googling, a horrifying 2005 Web post by Phil Gregory [" + (tech "Gregory") + "], which showed that key encoding among XTerm variants was even +worse than one could ever fear. Even if one already knew how much subtleties +of old terminals varied (e.g., auto-newline behavior, whether an attribute +change consumed a space, etc.), this incompatibility in newer software was +surprising. Then, on a hunch, I tried the Linux Console on a Debian Squeeze +machine, which surely is ANSI, and found, however, that it generated " + (italic "yet different") + " byte sequences, for the first " + (italic "five") + " (not four) function keys. Then I compared all to the [" + (tech "ECMA-48") + "] standard, which turns out to be nigh-inscrutable, so which might +help explain why everyone became so anti-social.") + + (para "CharTerm" + " now provides the abstractions of " + (tech "keysets") + " and " + (tech "keydecs") + " to deal with this diversity in a maintainable way.")) + +(doc (subsubsection "Keylabel") + + (para "A " + (deftech "keylabel") + " is a Racket string for how a key is likely labeled on a particular terminal's keyboard. Different keyboards may have different keylabels for the same " + (tech "keycode") + ". For example, a VT100 has a " + (bold "PF1") + " key (keylabel " + (racket "PF1") + ", keycode " + (racket 'f1) + "), while many other keyboards would label the key " + (bold "F1") + " (keylabel " + (racket "F1") + ", keycode " + (racket 'f1) + "). The keylabel currently is most useful for documenting and debugging, although it could later be used when giving instructions to the user, such as knowing whether to tell the user the " + (bold "Return") + " key or the " + (bold "Enter") + " key; the " + (bold "Backspace") + " or the " + (bold "Rubout") + " key; etc.")) + +(doc (subsubsection "Keycode") + + (para "A " + (deftech "keycode") + " is a value representing a key read from a terminal, which can be a Racket character, symbol, or number. Keys corresponding to printable characters have keycodes as Racket characters. Some keys corresponding to special non-printable characters can have keycodes of Racket symbols, such as " + (racket 'return) + ", " + (racket 'f1) + ", " + (racket 'up) + ", etc.")) + +;; TODO: Document here all the symbol keycodes we define. + +(doc (defproc (charterm-keycode? (x any/c)) + boolean? + "Predicate for whether or not " + (racket x) + " is a valid keycode.")) +(provide charterm-keycode?) +(define (charterm-keycode? x) + (if (or (symbol? x) + (char? x) + (exact-nonnegative-integer? x)) + #t + #f)) + +(doc (subsubsection "Keyinfo") + + (para "A " + (deftech "keyinfo") + " represents a " + (tech "keycode") + " for a key, a " + (tech "keylabel") + ", and how it is encoded as bytes. It is represented in Racket as +a " + (racket charterm-keyinfo) + " object.")) + +(define-struct charterm-keyinfo + (keyset-id + bytelang + bytelist + keylabel + keycode + all-keycodes) + #:transparent) + +(doc (defproc (charterm-keyinfo? (x any/c)) + boolean?) + "Predicate for whether or not " + (racket x) + " is a " + (racket charterm-keyinfo) + " object.") +(provide charterm-keyinfo?) + +(doc (defproc* + (((charterm-keyinfo-keyset-id (ki charterm-keyinfo?)) symbol?) + ((charterm-keyinfo-bytelang (ki charterm-keyinfo?)) string?) + ((charterm-keyinfo-bytelist (ki charterm-keyinfo?)) (listof byte?)) + ((charterm-keyinfo-keylabel (ki charterm-keyinfo?)) string?) + ((charterm-keyinfo-keycode (ki charterm-keyinfo?)) charterm-keycode?) + ((charterm-keyinfo-all-keycodes (ki charterm-keyinfo?)) (listof charterm-keycode?))) + (para "Get information from a " + (racket charterm-keyinfo) + " object."))) +(provide charterm-keyinfo-keyset-id + charterm-keyinfo-bytelang + charterm-keyinfo-bytelist + charterm-keyinfo-keylabel + charterm-keyinfo-keycode + charterm-keyinfo-all-keycodes) + +(define %charterm:bytestr-to-byte-hash + (make-hash + `(("nul" . 0) + ("null" . 0) + ("lf" . 10) + ("linefeed" . 10) + ("cr" . 13) + ("return" . 13) + ("ret" . 13) + ("esc" . 27) + ("^[" . 27) + ("sp" . 32) + ("space" . 32) + ,@(for/list ((n (in-range 1 26))) + (cons (string #\^ (integer->char (+ 96 n))) + n)) + ,@(for/list ((n (in-range 1 26))) + (cons (string-append "ctrl-" + (string (integer->char (+ 96 n)))) + n)) + ,@(for/list ((n (in-range 32 127))) + (cons (string (integer->char n)) + n)) + ,@(for/list ((n (in-range 0 255))) + (cons (string-append "(" + (number->string n) + ")") + n))))) + +(define (%charterm:bytestr->byte bytestr) + (hash-ref %charterm:bytestr-to-byte-hash bytestr)) + +(define (%charterm:bytelang->bytelist bytelang secondary?) + (let ((bytelist (map %charterm:bytestr->byte + (regexp-split #rx" +" bytelang)))) + (if (and secondary? (not (= 1 (length bytelist)))) + (error '%charterm:bytelang->bytelist + "bytelist for secondary keyset: ~S" + bytelist) + bytelist))) + +(define (%charterm:keycode->keylabel keycode) + (cond ((not keycode) #f) + ((symbol? keycode) (string-titlecase (symbol->string keycode))) + ((char? keycode) (string keycode)) + ((number? keycode) (number->string keycode)) + (else (error '%charterm:keycode->keylabel + "invalid keycode: ~S" + keycode)))) + +(define (%charterm:keylang->keyinfo keyset-id keylang secondary?) + (apply (lambda (bytelang . args) + (let-values (((bytelist) + (%charterm:bytelang->bytelist bytelang secondary?)) + ((keylabel keycode all-keycodes) + (let ((keylabel (car args))) + (if (or (string? keylabel) + (not keylabel)) + (values keylabel + (cadr args) + (cdr args)) + (let ((keycode (car args))) + (values (%charterm:keycode->keylabel keycode) + keycode + args)))))) + (make-charterm-keyinfo keyset-id + bytelang + bytelist + keylabel + keycode + all-keycodes))) + keylang)) + +(doc (subsubsection "Keyset") + + (para "A " + (deftech "keyset") + " is a specification of keys on a particular keyboard, including their " + (tech "keylabel") + ", encoding as bytes, and primary and alternate " + (tech #:key "keycode" "keycodes") + ".") + + ;; TODO: Expose ability to construct keysets, once it's finalized. + (para "The means of constructing a keyset is currently internal to this package.")) + +(define-struct charterm-keyset + (id primary-keyinfos secondary-keyinfos) + #:transparent) + +(doc (defproc (charterm-keyset? (x any/c)) + boolean? + (para "Predicate for whether or not " + (racket x) + " is a keyset."))) +(provide charterm-keyset?) + +(doc (defproc (charterm-keyset-id (ks charterm-keyset?)) + symbol?) + (para "Get a symbol identifying the keyset.")) +(provide charterm-keyset-id) + +;; (define (%charterm:keyinfos? x) +;; (for/and ((x (in-list x))) +;; (charterm-keyinfo? x))) +;; +;; (define (%charterm:assert-keyinfos keyinfos) +;; (or (%charterm:keyinfos? keyinfos) +;; (error '%charterm:assert-keyinfos +;; "assertion failed: ~S" +;; keyinfos))) + +(define (make-charterm-keyset-from-keylangs keyset-id + keylangs + (secondary-keylangs '())) + (let ((primary-keyinfos (map (lambda (keylang) + (%charterm:keylang->keyinfo keyset-id keylang #f)) + keylangs)) + (secondary-keyinfos (map (lambda (keylang) + (%charterm:keylang->keyinfo keyset-id keylang #t)) + secondary-keylangs))) + ;; (%charterm:assert-keyinfos primary-keyinfos) + ;; (%charterm:assert-keyinfos secondary-keyinfos) + (charterm-keyset keyset-id + primary-keyinfos + secondary-keyinfos))) + +(doc (defthing charterm-ascii-keyset charterm-keyset? + (para "From the old [" + (tech "ASCII") + "] standard. When defining a " + (tech "keydec") + ", this is good to have as a final keyset, after the others."))) +(define charterm-ascii-keyset + (let ((keylangs + `(("(0)" "NUL" nul null) + ("(1)" "Ctrl-A" ctrl-a start-of-heading soh) + ("(2)" "Ctrl-B" ctrl-b start-of-text stx) + ("(3)" "Ctrl-C" ctrl-c end-of-text etx) + ("(4)" "Ctrl-D" ctrl-d end-of-transmission eot) + ("(5)" "Ctrl-E" ctrl-e enquiry enq) + ("(6)" "Ctrl-F" ctrl-f acknowledge ack) + ("(7)" "Ctrl-G" ctrl-g bell bel) + ("(8)" "Backspace" backspace ctrl-h bs) + ("(9)" "Tab" tab ctrl-i horizontal-tab ht) + ("(10)" "Linefeed" linefeed ctrl-j line-feed lf) + ("(11)" "Ctrl-K" ctrl-k vertical-tab vt) + ("(12)" "Ctrl-L" ctrl-l formfeed form-feed ff) + ("(13)" "Return" return ctrl-m carriage-return cr) + ("(14)" "Ctrl-N" ctrl-n shift-out so) + ("(15)" "Ctrl-O" ctrl-o shift-in si) + ("(16)" "Ctrl-P" ctrl-p data-link-escape dle) + ("(17)" "Ctrl-Q" ctrl-q device-control-1 dc1) + ("(18)" "Ctrl-R" ctrl-r device-control-2 dc2) + ("(19)" "Ctrl-S" ctrl-s device-control-3 dc3) + ("(20)" "Ctrl-T" ctrl-t device-control-4 dc4) + ("(21)" "Ctrl-U" ctrl-u negative-acknowledgement nak) + ("(22)" "Ctrl-V" ctrl-v synchronous-idle syn) + ("(23)" "Ctrl-W" ctrl-w end-of-transmission-block etb) + ("(24)" "Ctrl-X" ctrl-x cancel can) + ("(25)" "Ctrl-Y" ctrl-y end-of-medium em) + ("(26)" "Ctrl-Z" ctrl-z substitute sub) + ("(27)" "Esc" escape esc) + ("(28)" "FS" file-separator fs) + ("(29)" "GS" group-separator gs) + ("(30)" "RS" record-separtor rs) + ("(31)" "US" unit-separator us) + ("(32)" "Space" #\space space sp) + ("(127)" "Delete" delete del) + ,@(for/list ((n (in-range 32 127))) + (let ((c (integer->char n))) + (list (string-append "(" (number->string n) ")") + (string c) + c)))))) + (make-charterm-keyset-from-keylangs + 'ascii + keylangs + keylangs))) + +(doc (defthing charterm-dec-vt100-keyset charterm-keyset? + (para "From the DEC VT100. This currently defines the four function +keys (labeled on the keyboard, " + (bold "PF1") + " through " + (bold "PF4") + ") as " + (racket 'f1) + " through " + (racket 'f4) + ", and the arrow keys. [" + (tech "VT100-UG") + "] and [" + (tech "PowerTerm") + "] were used as references."))) +(provide charterm-dec-vt100-keyset) +(define charterm-dec-vt100-keyset + (make-charterm-keyset-from-keylangs + 'dec-vt100 + '(("esc O P" "PF1" f1) + ("esc O Q" "PF2" f2) + ("esc O R" "PF3" f3) + ("esc O S" "PF4" f4) + + ("esc [ A" up) + ("esc [ B" down) + ("esc [ C" right) + ("esc [ D" left) + + ;; Note: PowerTerm does not map PC key F1 like VT100, etc. It maps all + ;; the PC F keys to other sequences that are like the VT220. + ))) + +(doc (defthing charterm-dec-vt220-keyset charterm-keyset? + (para "From the DEC VT220. This currently defines function keys " + (bold "F1") + " through " + (bold "F20") + "."))) +(provide charterm-dec-vt220-keyset) +(define charterm-dec-vt220-keyset + (make-charterm-keyset-from-keylangs + 'dec-vt220 + '( + ("esc [ 1 1 ~" f1) + ("esc [ 1 2 ~" f2) + ("esc [ 1 3 ~" f3) + ("esc [ 1 4 ~" f4) + ("esc [ 1 5 ~" f5) + ("esc [ 1 7 ~" f6) + ("esc [ 1 8 ~" f7) + ("esc [ 1 9 ~" f8) + ("esc [ 2 0 ~" f9) + ("esc [ 2 1 ~" f10) + ("esc [ 2 3 ~" f11) + ("esc [ 2 4 ~" f12) + ("esc [ 2 5 ~" f13) + ("esc [ 2 6 ~" f14) + ("esc [ 2 8 ~" f15) + ("esc [ 2 9 ~" f16) + ("esc [ 3 1 ~" f17) + ("esc [ 3 2 ~" f18) + ("esc [ 3 3 ~" f19) + ("esc [ 3 4 ~" f20) + + ;; TODO: Make the keylang expand to both "esc [" and "(155)" CSI or + ;; whatever. + + ("(155) 1 1 ~" f1) + ("(155) 1 2 ~" f2) + ("(155) 1 3 ~" f3) + ("(155) 1 4 ~" f4) + ("(155) 1 5 ~" f5) + ("(155) 1 7 ~" f6) + ("(155) 1 8 ~" f7) + ("(155) 1 9 ~" f8) + ("(155) 2 0 ~" f9) + ("(155) 2 1 ~" f10) + ("(155) 2 3 ~" f11) + ("(155) 2 4 ~" f12) + ("(155) 2 5 ~" f13) + ("(155) 2 6 ~" f14) + ("(155) 2 8 ~" f15) + ("(155) 2 9 ~" f16) + ("(155) 3 1 ~" f17) + ("(155) 3 2 ~" f18) + ("(155) 3 3 ~" f19) + ("(155) 3 4 ~" f20) + + ))) + +(doc (defthing charterm-screen-keyset charterm-keyset? + (para "From the " + (hyperlink "http://en.wikipedia.org/wiki/GNU_Screen" + "GNU Screen") + " terminal multiplexer, according to [" + (tech "Gregory") + "]. Also used by " + (hyperlink "http://en.wikipedia.org/wiki/Tmux" + (code "tmux")) + "."))) +(provide charterm-screen-keyset) +(define charterm-screen-keyset + (make-charterm-keyset-from-keylangs + 'screen + '(("esc O P" f1) + ("esc O Q" f2) + ("esc O R" f3) + ("esc O S" f4) + ("esc [ 1 5 ~" f5) + ("esc [ 1 7 ~" f6) + ("esc [ 1 8 ~" f7) + ("esc [ 1 9 ~" f8) + ("esc [ 2 0 ~" f9) + ("esc [ 2 1 ~" f10) + ("esc [ 2 3 ~" f11) + ("esc [ 2 4 ~" f12) + + ("esc [ 3 ~" "Delete" delete del) + ("esc [ 7 ~" "Home" home) + ("esc [ 8 ~" "End" end) + + ("(127)" "Backspace" backspace) + ))) + +(doc (defthing charterm-linux-keyset charterm-keyset? + (para "From the Linux console. Currently defines function keys " + (bold "F1") + " through " + (bold "F5") + " only, since the rest will be inherited from other keysets."))) +(provide charterm-linux-keyset) +(define charterm-linux-keyset + (make-charterm-keyset-from-keylangs + 'linux + '(("esc [ [ A" f1) + ("esc [ [ B" f2) + ("esc [ [ C" f3) + ("esc [ [ D" f4) + ("esc [ [ E" f5)))) + +(doc (defthing charterm-xterm-x11r6-keyset charterm-keyset? + (para "From the XTerm in X11R6, according to [" + (tech "Gregory") + "]."))) +(provide charterm-xterm-x11r6-keyset) +(define charterm-xterm-x11r6-keyset + (make-charterm-keyset-from-keylangs + 'xterm-x11r6 + '(("esc [ 1 1 ~" f1) + ("esc [ 1 2 ~" f2) + ("esc [ 1 3 ~" f3) + ("esc [ 1 4 ~" f4) + ("esc [ 1 5 ~" f5) + ("esc [ 1 7 ~" f6) + ("esc [ 1 8 ~" f7) + ("esc [ 1 9 ~" f8) + ("esc [ 2 0 ~" f9) + ("esc [ 2 1 ~" f10) + ("esc [ 2 3 ~" f11) + ("esc [ 2 4 ~" f12) + ("esc [ 1 1 ; 2 ~" f13) + ("esc [ 1 2 ; 2 ~" f14) + ("esc [ 1 3 ; 2 ~" f15) + ("esc [ 1 4 ; 2 ~" f16) + ("esc [ 1 5 ; 2 ~" f17) + ("esc [ 1 7 ; 2 ~" f18) + ("esc [ 1 8 ; 2 ~" f19) + ("esc [ 1 9 ; 2 ~" f20) + ("esc [ 2 0 ; 2 ~" f21) + ("esc [ 2 1 ; 2 ~" f22) + ("esc [ 2 3 ; 2 ~" f23) + ("esc [ 2 4 ; 2 ~" f24) + ("esc [ 1 1 ; 5 ~" f25) + ("esc [ 1 2 ; 5 ~" f26) + ("esc [ 1 3 ; 5 ~" f27) + ("esc [ 1 4 ; 5 ~" f28) + ("esc [ 1 5 ; 5 ~" f29) + ("esc [ 1 7 ; 5 ~" f30) + ("esc [ 1 8 ; 5 ~" f31) + ("esc [ 1 9 ; 5 ~" f32) + ("esc [ 2 0 ; 5 ~" f33) + ("esc [ 2 1 ; 5 ~" f34) + ("esc [ 2 3 ; 5 ~" f35) + ("esc [ 2 4 ; 5 ~" f36) + ("esc [ 1 1 ; 6 ~" f37) + ("esc [ 1 2 ; 6 ~" f38) + ("esc [ 1 3 ; 6 ~" f39) + ("esc [ 1 4 ; 6 ~" f40) + ("esc [ 1 5 ; 6 ~" f41) + ("esc [ 1 7 ; 6 ~" f42) + ("esc [ 1 8 ; 6 ~" f43) + ("esc [ 1 9 ; 6 ~" f44) + ("esc [ 2 0 ; 6 ~" f45) + ("esc [ 2 1 ; 6 ~" f46) + ("esc [ 2 3 ; 6 ~" f47) + ("esc [ 2 4 ; 6 ~" f48)))) + +(doc (defthing charterm-xterm-xfree86-keyset charterm-keyset? + (para "From the XFree86 XTerm, according to [" + (tech "Gregory") + "]."))) +(provide charterm-xterm-xfree86-keyset) +(define charterm-xterm-xfree86-keyset + (make-charterm-keyset-from-keylangs + 'xterm-xfree86 + '(("esc O P" f1) + ("esc O Q" f2) + ("esc O R" f3) + ("esc O S" f4) + ("esc [ 1 5 ~" f5) + ("esc [ 1 7 ~" f6) + ("esc [ 1 8 ~" f7) + ("esc [ 1 9 ~" f8) + ("esc [ 2 0 ~" f9) + ("esc [ 2 1 ~" f10) + ("esc [ 2 3 ~" f11) + ("esc [ 2 4 ~" f12) + ("esc O 2 P" f13) + ("esc O 2 Q" f14) + ("esc O 2 R" f15) + ("esc O 2 S" f16) + ("esc [ 1 5 ; 2 ~" f17) + ("esc [ 1 7 ; 2 ~" f18) + ("esc [ 1 8 ; 2 ~" f19) + ("esc [ 1 9 ; 2 ~" f20) + ("esc [ 2 0 ; 2 ~" f21) + ("esc [ 2 1 ; 2 ~" f22) + ("esc [ 2 3 ; 2 ~" f23) + ("esc [ 2 4 ; 2 ~" f24) + ("esc O 5 P" f25) + ("esc O 5 Q" f26) + ("esc O 5 R" f27) + ("esc O 5 S" f28) + ("esc [ 1 5 ; 5 ~" f29) + ("esc [ 1 7 ; 5 ~" f30) + ("esc [ 1 8 ; 5 ~" f31) + ("esc [ 1 9 ; 5 ~" f32) + ("esc [ 2 0 ; 5 ~" f33) + ("esc [ 2 1 ; 5 ~" f34) + ("esc [ 2 3 ; 5 ~" f35) + ("esc [ 2 4 ; 5 ~" f36) + ("esc O 6 P" f37) + ("esc O 6 Q" f38) + ("esc O 6 R" f39) + ("esc O 6 S" f40) + ("esc [ 1 5 ; 6 ~" f41) + ("esc [ 1 7 ; 6 ~" f42) + ("esc [ 1 8 ; 6 ~" f43) + ("esc [ 1 9 ; 6 ~" f44) + ("esc [ 2 0 ; 6 ~" f45) + ("esc [ 2 1 ; 6 ~" f46) + ("esc [ 2 3 ; 6 ~" f47) + ("esc [ 2 4 ; 6 ~" f48)))) + +(doc (defthing charterm-xterm-new-keyset charterm-keyset? + (para "From the current " + (code "xterm-new") + ", often called simply " + (code "xterm") + ", as developed by Thomas E. Dickey, and documented in [" + (tech "XTerm-ctlseqs") + "]. Several also came from decompiling a " + (code "terminfo") + " entry. Thanks to Dickey for his emailed help."))) +(provide charterm-xterm-new-keyset) +(define charterm-xterm-new-keyset + (make-charterm-keyset-from-keylangs + 'xterm-new + '( + + ;; CSI = "esc [" + ;; SS3 = "esc O" + + ("esc [ A" up) + ("esc [ B" down) + ("esc [ C" right) + ("esc [ D" left) + ("esc [ H" home) + ("esc [ F" end) + + ;; The following came from decompiling an xterm terminfo + ("esc O A" up) + ("esc O B" down) + ("esc O C" right) + ("esc O D" left) + ("esc O H" home) + ("esc O F" end) + + ("esc O P" f1) + ("esc O Q" f2) + ("esc O R" f3) + ("esc O S" f4) + ("esc [ 1 5 ~" f5) + ("esc [ 1 7 ~" f6) + ("esc [ 1 8 ~" f7) + ("esc [ 1 9 ~" f8) + ("esc [ 2 0 ~" f9) + ("esc [ 2 1 ~" f10) + ("esc [ 2 3 ~" f11) + ("esc [ 2 4 ~" f12) + + ("esc O I" tab kp-tab) + ("esc O M" "Enter" return enter kp-return kp-enter) + ("esc O P" "PF1" f1 kp-f1) + ("esc O Q" "PF2" f2 kp-f2) + ("esc O R" "PF3" f3 kp-f3) + ("esc O S" "PF4" f4 kp-f4) + ("esc [ 3 ~" "Delete" delete del kp-delete) + ("esc [ 2 ~" "Insert" insert ins kp-insert) + ("esc O F" "End" end kp-end) + ("esc [ B" "Down" down kp-down) + ("esc [ 6 ~" "PgDn" pgdn kp-pgdn) + ("esc [ D" "Left" left kp-left) + ("esc [ E" "Begin" begin kp-begin) + ("esc [ C" "Right" right kp-right) + ("esc O H" "Home" home kp-home) + ("esc [ A" "Up" up kp-up) + ("esc [ 5 ~" "PgUp" pgup kp-pgup) + + ("esc [ 1 1 ~" "F1" f1) + ("esc [ 1 2 ~" "F2" f2) + ("esc [ 1 3 ~" "F3" f3) + ("esc [ 1 4 ~" "F4" f4) + + ;; TODO: continue working on this from dickey's xterm control sequences doc + + ))) + +(doc (defthing charterm-rxvt-keyset charterm-keyset? + (para "From the " + (hyperlink "http://en.wikipedia.org/wiki/Rxvt" + (code "rxvt")) + " terminal emulator. These come from [" + (tech "Gregory") + "], and +currently define function keys " + (racket 'f1) + " through " + (racket 'f44) + "."))) +(define charterm-rxvt-keyset + (make-charterm-keyset-from-keylangs + 'rxvt + '(("esc [ 1 1 ~" f1) + ("esc [ 1 2 ~" f2) + ("esc [ 1 3 ~" f3) + ("esc [ 1 4 ~" f4) + ("esc [ 1 5 ~" f5) + ("esc [ 1 7 ~" f6) + ("esc [ 1 8 ~" f7) + ("esc [ 1 9 ~" f8) + ("esc [ 2 0 ~" f9) + ("esc [ 2 1 ~" f10) + ("esc [ 2 3 ~" shift-f1 f11) ;; TODO: These shift- and ctrl- are actually from termvar xterm in an rxvt + ("esc [ 2 4 ~" shift-f2 f12) + ("esc [ 2 5 ~" shift-f3 f13) + ("esc [ 2 6 ~" shift-f4 f14) + ("esc [ 2 8 ~" shift-f5 f15) + ("esc [ 2 9 ~" shift-f6 f16) + ("esc [ 3 1 ~" shift-f7 f17) + ("esc [ 3 2 ~" shift-f8 f18) + ("esc [ 3 3 ~" shift-f9 f19) + ("esc [ 3 4 ~" shift-f10 f20) + ("esc [ 2 3 $" shift-f11 f21) + ("esc [ 2 4 $" shift-f12 f22) + ("esc [ 1 1 ^" ctrl-f1 f23) + ("esc [ 1 2 ^" ctrl-f2 f24) + ("esc [ 1 3 ^" ctrl-f3 f25) + ("esc [ 1 4 ^" ctrl-f4 f26) + ("esc [ 1 5 ^" ctrl-f5 f27) + ("esc [ 1 7 ^" ctrl-f6 f28) + ("esc [ 1 8 ^" ctrl-f7 f29) + ("esc [ 1 9 ^" ctrl-f8 f30) + ("esc [ 2 0 ^" ctrl-f9 f31) + ("esc [ 2 1 ^" ctrl-f10 f32) + ("esc [ 2 3 ^" ctrl-f11 f33) + ("esc [ 2 4 ^" ctrl-f12 f34) + ("esc [ 2 5 ^" f35) + ("esc [ 2 6 ^" f36) + ("esc [ 2 8 ^" f37) + ("esc [ 2 9 ^" f38) + ("esc [ 3 1 ^" f39) + ("esc [ 3 2 ^" f40) + ("esc [ 3 3 ^" f41) + ("esc [ 3 4 ^" f42) + ("esc [ 2 3 @" f43) + ("esc [ 2 4 @" f44) + ("(127)" "Backspace" backspace) ; Override one from "ascii" keyset. + ;; TODO: actually, these arrow keys were observed in rxvt with termvar xterm. which keyset should they be in? + ("esc [ A" "Up" up) + ("esc [ B" "Down" down) + ("esc [ C" "Right" right) + ("esc [ D" "Left" left) + ("esc [ 5 ~" "PgUp" pgup page-up) + ("esc [ 6 ~" "PgDn" pgdn page-down) + ("esc [ 7 ~" "Home" home) + ("esc [ 8 ~" "End" end) + ("esc [ 3 ~" "Delete" delete del) + ("esc [ 2 ~" "Insert" insert ins) + ))) + +(doc (defthing charterm-wyse-wy50-keyset charterm-keyset? + (para "From the Wyse WY-50, based on [" + (tech "WY-50-QRG") + "] and looking at photos of WY-50 keyboard, and tested in [" + (tech "wy60") + "] and [" + (tech "PowerTerm") + "]. The shifted function keys are provided as both " + (racket 'shift-f1) + " through " + (racket 'shift-16) + ", and " + (racket 'f17) + " through " + (racket 'f31) + "."))) +(provide charterm-wyse-wy50-keyset) +(define charterm-wyse-wy50-keyset + (make-charterm-keyset-from-keylangs + 'wyse-wy50 + '(("^a @ cr" f1) + ("^a A cr" f2) + ("^a B cr" f3) + ("^a C cr" f4) + ("^a D cr" f5) + ("^a E cr" f6) + ("^a F cr" f7) + ("^a G cr" f8) + ("^a H cr" f9) + ("^a I cr" f10) + ("^a J cr" f11) + ("^a K cr" f12) + ("^a L cr" f13) + ("^a M cr" f14) + ("^a N cr" f15) + ("^a O cr" f16) + ("^a ` cr" "Shift-F1" shift-f1 f17) + ("^a a cr" "Shift-F2" shift-f2 f18) + ("^a b cr" "Shift-F3" shift-f3 f19) + ("^a c cr" "Shift-F4" shift-f4 f20) + ("^a d cr" "Shift-F5" shift-f5 f21) + ("^a e cr" "Shift-F6" shift-f6 f22) + ("^a f cr" "Shift-F7" shift-f7 f23) + ("^a g cr" "Shift-F8" shift-f8 f24) + ("^a h cr" "Shift-F9" shift-f9 f25) + ("^a i cr" "Shift-F10" shift-f10 f26) + ("^a j cr" "Shift-F11" shift-f11 f27) + ("^a k cr" "Shift-F12" shift-f12 f28) + ("^a l cr" "Shift-F13" shift-f13 f29) + ("^a m cr" "Shift-F14" shift-f14 f30) + ("^a n cr" "Shift-F15" shift-f15 f31) + ("^a o cr" "Shift-F16" shift-f16 f32) + ("ctrl-h" "Left" left) + ("linefeed" "Down" down) + ("(11)" "Up" up) + ("(12)" "Right" right) + ("esc W" "DEL Char" delete) + ("esc Q" "INS Char" insert-char) + ("esc q" "Ins" insert ins) + ("esc T" "CLR Line" clear-line) + ("esc r" "Repl" repl) + ("esc R" "DEL Line" delete-line) + ("esc J" "PAGE Prev" pgup page-up) + ("esc K" "PAGE Next" pgdn page-down) + ("esc P" "Print" print) + ("esc Y" "CLR Screen" clear-screen) + ("(30)" "Home" home record-separator rs) + ("(13)" "Return" return) + ("(127)" "Shift-Backspace" backspace shift-backspace) + ))) + +(doc (defthing charterm-televideo-925-keyset charterm-keyset? + (para "From the TeleVideo 925, based on [" + (tech "TVI-925-IUG") + "], [" + (tech "PowerTerm") + "], and from looking at a TeleVideo 950 keyboard."))) +(provide charterm-televideo-925-keyset charterm-keyset?) +(define charterm-televideo-925-keyset + (make-charterm-keyset-from-keylangs + 'televideo-925 + '(("ctrl-a @ cr" f1) + ("ctrl-a A cr" f2) + ("ctrl-a B cr" f3) + ("ctrl-a C cr" f4) + ("ctrl-a D cr" f5) + ("ctrl-a E cr" f6) + ("ctrl-a F cr" f7) + ("ctrl-a G cr" f8) + ("ctrl-a H cr" f9) + ("ctrl-a I cr" f10) + ("ctrl-a J cr" f11) + + ("ctrl-a \\ cr" "SHIFT-F1" shift-f1) + ("ctrl-a a cr" "SHIFT-F2" shift-f2) + ("ctrl-a b cr" "SHIFT-F3" shift-f3) + ("ctrl-a c cr" "SHIFT-F4" shift-f4) + ("ctrl-a d cr" "SHIFT-F5" shift-f5) + ("ctrl-a e cr" "SHIFT-F6" shift-f6) + ("ctrl-a f cr" "SHIFT-F7" shift-f7) + ("ctrl-a g cr" "SHIFT-F8" shift-f8) + ("ctrl-a h cr" "SHIFT-F9" shift-f9) + ("ctrl-a i cr" "SHIFT-F10" shift-f10) + ("ctrl-a j cr" "SHIFT-F11" shift-f11) + + ("ctrl-k" "Up" up ctrl-k) + ("ctrl-v" "Down" down ctrl-v) + ("ctrl-h" "Left" left ctrl-h) + ("ctrl-l" "Right" right ctrl-l) + + ("esc W" "CHAR DELETE" delete del char-delete) + + ("esc Q" "CHAR INSERT" insert ins char-insert) + + ("esc j" "Reverse Linefeed" reverse-linefeed reverse-lf reverse-line-feed) + + ("esc i" "BACK TAB" backtab back-tab) + ("ctrl-m" "RETURN" return ctrl-m) + ("ctrl-j" "LINEFEED" linefeed lf ctrl-j) + ("(127)" "DEL" delete del) + ;; ("esc Q" "CHAR INSERT" char-insert char-ins) + + ))) + +(doc (subsubsection "Keydec") + + (para "A " + (deftech "keydec") + " object is a key decoder for a specific variety of terminal, such +as for a specific " + (tech "termvar") + ". A keydec is used to turn received key encodings from a terminal into " + (tech "keycode") + " or " + (tech "keyinfo") + " values. A keydec is constructed from a prioritized list of " + (tech "keyset") + " objects, with earlier-listed keysets taking priority of +later-listed keysets when there is conflict between them as to how to decode a +particular byte sequence.")) + +(define (%charterm:make-keytree (alist '())) + (make-immutable-hasheqv alist)) + +(define (%charterm:keytree-add-keyinfo-if-can keytree keyinfo) + (let ((bytelist (charterm-keyinfo-bytelist keyinfo))) + (let loop-bytelist ((this-byte (car bytelist)) + (rest-bytes (cdr bytelist)) + (node keytree)) + (cond ((hash? node) + (cond ((hash-ref node this-byte #f) + => (lambda (existing-sub-node) + ;; Node has a match for this byte, so do we have another + ;; byte and can follow it? + (if (null? rest-bytes) + ;; Node has a match for this byte, but we have no + ;; more bytes, so can't add. + node + ;; Node has a match for this byte, and we have more + ;; bytes, so follow it. + (hash-set node + this-byte + (loop-bytelist (car rest-bytes) + (cdr rest-bytes) + existing-sub-node))))) + (else + ;; Node has no match for this byte, so add new path. + (hash-set node + this-byte + (let loop ((rest-bytes rest-bytes)) + (if (null? rest-bytes) + keyinfo + (%charterm:make-keytree + (cons (cons (car rest-bytes) + (loop (cdr rest-bytes))) + '())))))))) + + ((charterm-keyinfo? node) + ;; Node is already a keyinfo, so can't add. + node) + (else (error + '%charterm:keytree-add-keyinfo-if-can + "invalid node ~S with this-byte ~S, rest-bytes ~S, keyinfo ~S" + node + this-byte + rest-bytes + keyinfo)))))) + +(define (%charterm:keytree-add-any-keyinfos-can keytree keyinfos) + (let loop ((keyinfos keyinfos) + (keytree keytree)) + (if (null? keyinfos) + keytree + (loop (cdr keyinfos) + (%charterm:keytree-add-keyinfo-if-can keytree + (car keyinfos)))))) + +(define (%charterm:make-keytree-from-keyinfoses keyinfoses) + (let loop ((keyinfoses keyinfoses) + (keytree (%charterm:make-keytree))) + (if (null? keyinfoses) + keytree + (let ((keyinfos (car keyinfoses))) + ;; (and (not (null? keyinfos)) + ;; (not (charterm-keyinfo? (car keyinfos))) + ;; (error '%charterm:make-keytree-from-keyinfoses + ;; "bad keyinfos: ~S" + ;; keyinfos)) + (loop (cdr keyinfoses) + (%charterm:keytree-add-any-keyinfos-can keytree + keyinfos)))))) + +(doc (defproc (charterm-keydec-id (kd charterm-keydec?)) + symbol? + (para "Gets the ID symbol of the " + (tech "keydec") + " being used."))) +(provide charterm-keydec-id) + +(struct charterm-keydec + (id + primary-keytree + secondary-keytree) + #:transparent) + +(define (charterm-make-keydec keydec-id . keysets) + (charterm-keydec keydec-id + (%charterm:make-keytree-from-keyinfoses + (map charterm-keyset-primary-keyinfos keysets)) + (%charterm:make-keytree-from-keyinfoses + (map charterm-keyset-secondary-keyinfos keysets)))) + +(doc (subsubsub*section "ANSI Keydecs")) + +(doc (defthing charterm-vt100-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "vt100") + "."))) +(provide charterm-vt100-keydec) +(define charterm-vt100-keydec + (charterm-make-keydec 'vt100 + charterm-dec-vt100-keyset + charterm-dec-vt220-keyset + charterm-xterm-new-keyset + charterm-linux-keyset + charterm-rxvt-keyset + charterm-xterm-xfree86-keyset + charterm-xterm-x11r6-keyset + charterm-ascii-keyset)) + +(doc (defthing charterm-vt220-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "vt220") + "."))) +(provide charterm-vt220-keydec) +(define charterm-vt220-keydec + (charterm-make-keydec 'vt220 + charterm-dec-vt220-keyset + charterm-dec-vt100-keyset + charterm-ascii-keyset)) + +(doc (defthing charterm-screen-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "screen") + "."))) +(provide charterm-screen-keydec) +(define charterm-screen-keydec + (charterm-make-keydec 'screen + charterm-screen-keyset + charterm-linux-keyset + charterm-dec-vt220-keyset + charterm-dec-vt100-keyset + charterm-xterm-new-keyset + charterm-xterm-xfree86-keyset + charterm-xterm-x11r6-keyset + charterm-ascii-keyset)) + +(doc (defthing charterm-linux-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "linux") + "."))) +(provide charterm-linux-keydec) +(define charterm-linux-keydec + (charterm-make-keydec 'linux + charterm-linux-keyset + charterm-dec-vt220-keyset + charterm-dec-vt100-keyset + charterm-xterm-new-keyset + charterm-xterm-xfree86-keyset + charterm-xterm-x11r6-keyset + charterm-screen-keyset + charterm-ascii-keyset)) + +(doc (defthing charterm-xterm-new-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "xterm-new") + "."))) +(provide charterm-xterm-new-keydec) +(define charterm-xterm-new-keydec + (charterm-make-keydec 'xterm-new + charterm-xterm-new-keyset + charterm-xterm-xfree86-keyset + charterm-xterm-x11r6-keyset + charterm-rxvt-keyset + charterm-dec-vt220-keyset + charterm-dec-vt100-keyset + charterm-linux-keyset + charterm-ascii-keyset)) + +(doc (defthing charterm-xterm-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "xterm") + ". Currently same as the keydec for " + (code "xterm") + ", except for a different ID."))) +(provide charterm-xterm-keydec) +(define charterm-xterm-keydec + (charterm-make-keydec 'xterm + charterm-xterm-new-keyset + charterm-xterm-xfree86-keyset + charterm-xterm-x11r6-keyset + charterm-rxvt-keyset + charterm-dec-vt220-keyset + charterm-dec-vt100-keyset + charterm-linux-keyset + charterm-ascii-keyset)) + +(doc (defthing charterm-rxvt-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "rxvt") + "."))) +(provide charterm-rxvt-keydec) +(define charterm-rxvt-keydec + (charterm-make-keydec 'rxvt + charterm-rxvt-keyset + charterm-xterm-new-keyset + charterm-xterm-xfree86-keyset + charterm-xterm-x11r6-keyset + charterm-dec-vt220-keyset + charterm-dec-vt100-keyset + charterm-linux-keyset + charterm-ascii-keyset)) + +(doc (subsubsub*section "Wyse Keydecs")) + +(doc (defthing charterm-wy50-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "wy50") + "."))) +(provide charterm-wy50-keydec) +(define charterm-wy50-keydec + (charterm-make-keydec 'wy50 + charterm-wyse-wy50-keyset + charterm-ascii-keyset)) + +(doc (subsubsub*section "TeleVideo Keydecs")) + +(doc (defthing charterm-tvi925-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "tvi925") + "."))) +(provide charterm-tvi925-keydec) +(define charterm-tvi925-keydec + (charterm-make-keydec 'tvi925 + charterm-televideo-925-keyset + charterm-ascii-keyset)) + +(doc (subsubsub*section "ASCII Keydecs")) + +(doc (defthing charterm-ascii-keydec charterm-keydec? + (para (tech "Keydec") + " for " + (tech "termvar") + " " + (racket "ascii") + "."))) +(provide charterm-ascii-keydec) +(define charterm-ascii-keydec + (charterm-make-keydec 'ascii + charterm-ascii-keyset)) + +(doc (subsubsub*section "Default Keydecs")) + +(doc (defthing charterm-ansi-keydec charterm-keydec? + (para (tech "Keydec") + " for any presumed ANSI-ish terminal, combining many ANSI-ish " + (tech "keysets") + "."))) +(define charterm-ansi-keydec + (charterm-make-keydec 'ansi + charterm-dec-vt220-keyset + charterm-dec-vt100-keyset + charterm-xterm-new-keyset + charterm-linux-keyset + charterm-rxvt-keyset + charterm-xterm-xfree86-keyset + charterm-xterm-x11r6-keyset + charterm-ascii-keyset)) + +(doc (defthing charterm-insane-keydec charterm-keydec? + (para (tech "Keydec") + " for the uniquely desperate situation of wanting to possibly have +extensive key decoding for a terminal that might not even be ansi, but be +Wyse, TeleVideo, or some other ASCII."))) +(provide charterm-insane-keydec) +(define charterm-insane-keydec + (charterm-make-keydec 'insane + charterm-xterm-new-keyset + charterm-linux-keyset + charterm-dec-vt220-keyset + charterm-dec-vt100-keyset + charterm-linux-keyset + charterm-xterm-xfree86-keyset + charterm-xterm-x11r6-keyset + charterm-wyse-wy50-keyset + charterm-televideo-925-keyset + charterm-ascii-keyset)) + +(doc (subsection "Termvar") + + (para "A " + (deftech "termvar") + " is what the " + (code "charterm") + " package calls the value of the Unix-like " + (code "TERM") + " environment variable. Each " + (tech "termvar") + " has a default " + (tech "protocol") + " and " + (tech "keydec") + ". Note, however, that " + (code "TERM") + " is not always a precise indicator of the best protocol and keydec, +but by default we work with what we have.")) + +;; TODO: Document the termvars here? Move this subsection? + +(doc (section (code "charterm") " Object") + + (para "The " + (racket charterm) + " object captures the state of a session with a particular terminal.") + + (para "A " + (racket charterm) + " object is also a synchronizable event, so it can be used with +procedures such as " + (racket sync) + ". As an event, it becomes ready when there is at least one byte +available for reading from the terminal, and its synchronization result is +itself.")) + +(doc (defproc (charterm? (x any/c)) + boolean? + (para "Predicate for whether or not " + (var x) + " is a " + (racket charterm) + "."))) +(provide charterm?) + +(doc (defproc (charterm-termvar (ct charterm?)) + (or/c #f string?)) + (para "Gets the " + (tech "termvar") + ".")) +(provide charterm-termvar) + +(doc (defproc (charterm-protocol (ct charterm?)) + symbol?) + (para "Gets the " + (tech "protocol") + ".")) +(provide charterm-protocol) + +(doc (defproc (charterm-keydec (ct charterm?)) + symbol?) + (para "Gets the " + (tech "keydec") + ".")) +(provide (rename-out (charterm-keydec* charterm-keydec))) + +(define-struct charterm + (tty + in + out + evt + buf-size + buf + (buf-start #:mutable) + (buf-end #:mutable) + termvar + protocol + keydec* + (screensize #:mutable)) + #:property prop:evt (struct-field-index evt)) + +(define (%charterm:protocol-unimplemented error-name ct) + (error error-name + "protocol unimplemented: ~S" + (charterm-protocol ct))) + +(define (%charterm:protocol-unreachable error-name ct) + (error error-name + "internal error: protocol unreachable: ~S" + (charterm-protocol ct))) + +(define %charterm:stty-minus-f-arg-string + (case (system-type 'os) + ((macosx) "-f") + (else "-F"))) + +(doc (defparam current-charterm ct (or/c #f charterm?) + (para "This parameter provides the default " + (racket charterm) + " for most of the other procedures. It is usually set automatically by " + (racket call-with-charterm) + ", " + (racket with-charterm) + ", " + (racket open-charterm) + ", and " + (racket close-charterm) + "."))) +(provide current-charterm) +(define current-charterm (make-parameter #f)) + +(doc (defproc (open-charterm + (#:tty tty (or/c #f path-string?) #f) + (#:current? current? boolean? #t)) + charterm? + (para "Returns an open " + (racket charterm) + " object, by opening I/O ports on the terminal device at " + (racket tty) + " (or, if " + (racket #f) + ", file " + (filepath "/dev/tty") + "), and setting raw mode and disabling echo (via " + (filepath "/bin/stty") + "). If " + (racket current?) + " is true, the " + (racket current-charterm) + " parameter is also set to this object."))) +(provide open-charterm) +(define (open-charterm #:tty (tty #f) + #:current? (current? #t)) + (let* ((tty (cleanse-path (or tty "/dev/tty"))) + (tty-str (path->string tty))) + (or (system* "/bin/stty" + %charterm:stty-minus-f-arg-string + tty-str + "raw" + "-echo") + (error 'open-charterm + "stty ~S failed" + tty-str)) + (with-handlers ((exn:fail? (lambda (e) + (with-handlers ((exn:fail? void)) + (system* "/bin/stty" + %charterm:stty-minus-f-arg-string + tty-str + "sane")) + (raise e)))) + (let*-values (((in out) (open-input-output-file tty + #:exists 'update)) + ((buf-size) 2048)) + ;; TODO: Do we actually need to turn off buffering? + (file-stream-buffer-mode in 'none) + (file-stream-buffer-mode out 'none) + (let*-values + (((termvar) (getenv "TERM")) + ((termvar) (cond ((not termvar) #f) + ((equal? "" termvar) #f) + (else (string-downcase termvar)))) + ((protocol keydec) + ;; TODO: Once the patterns have been fleshed out, make the exact + ;; matches a hash, and optimize the regexps. + (cond ((not termvar) (values #f #f)) + ;; Exact Matches: + ((equal? "ascii" termvar) (values 'ascii charterm-ascii-keydec)) + ((equal? "dumb" termvar) (values 'ascii charterm-ascii-keydec)) + ((equal? "linux" termvar) (values 'ansi charterm-linux-keydec)) + ((equal? "rxvt" termvar) (values 'ansi charterm-rxvt-keydec)) + ((equal? "screen" termvar) (values 'ansi charterm-screen-keydec)) + ((equal? "tvi925" termvar) (values 'televideo-925 charterm-tvi925-keydec)) + ((equal? "tvi950" termvar) (values 'televideo-925 charterm-tvi925-keydec)) + ((equal? "vt100" termvar) (values 'ansi charterm-vt100-keydec)) + ((equal? "vt102" termvar) (values 'ansi charterm-vt100-keydec)) + ((equal? "vt220" termvar) (values 'ansi charterm-vt220-keydec)) + ((equal? "wy50" termvar) (values 'wyse-wy50 charterm-wy50-keydec)) + ((equal? "wy60" termvar) (values 'wyse-wy50 charterm-wy50-keydec)) + ((equal? "wy75" termvar) (values 'wyse-wy50 charterm-wy50-keydec)) + ((equal? "wyse50" termvar) (values 'wyse-wy50 charterm-wy50-keydec)) + ((equal? "wyse60" termvar) (values 'wyse-wy50 charterm-wy50-keydec)) + ((equal? "wyse75" termvar) (values 'wyse-wy50 charterm-wy50-keydec)) + ((equal? "xterm" termvar) (values 'ansi charterm-xterm-new-keydec)) + ((equal? "xterm-new" termvar) (values 'ansi charterm-xterm-new-keydec)) + ;; ANSI-ish Guesses: + ((regexp-match #rx"ansi$" termvar) (values 'ansi charterm-ansi-keydec)) + ((regexp-match #rx"^ansi" termvar) (values 'ansi charterm-ansi-keydec)) + ((regexp-match #rx"^xterm" termvar) (values 'ansi charterm-xterm-new-keydec)) + ((regexp-match #rx"^rxvt" termvar) (values 'ansi charterm-rxvt-keydec)) + ((regexp-match #rx"^vt" termvar) (values 'ansi charterm-rxvt-keydec)) + ;; Non-ANSI Guesses: + ((regexp-match #rx"^wy" termvar) (values 'wyse-wy50 charterm-wy50-keydec)) + ((regexp-match #rx"^tvi" termvar) (values 'televideo-925 charterm-tvi925-keydec)) + ;; Default: + (else (values #f #f)))) + ((protocol keydec) + (values (or protocol 'ansi) + (or keydec charterm-ansi-keydec)))) + (letrec ((wrapping-evt (wrap-evt in + (lambda (evt) ct))) + (ct (make-charterm tty-str ; tty + in ; in + out ; out + wrapping-evt ; evt + buf-size ; buf-size + (make-bytes buf-size) ; buf + 0 ; buf-start + 0 ; buf-end + termvar ; termvar + protocol ; protocol + keydec ; keydec + ; screensize + (if (and (eq? protocol 'ansi) + (not (member termvar '("screen")))) + 'control/stty/none + 'stty/none)))) + (and current? + (current-charterm ct)) + ct)))))) + +(doc (defproc (close-charterm (#:charterm ct charterm? (current-charterm))) + void? + (para "Closes " + (racket ct) + " by closing the I/O ports, and undoing " + (racket open-charterm) + "'s changes via " + (filepath "/bin/stty") + ". If " + (racket current-charterm) + " is set to " + (racket ct) + ", then that parameter will be changed to " + (racket #f) + " for good measure. You might wish to use " + (racket with-charterm) + " instead of worrying about calling " + (racket close-charterm) + " directly.") + (para "Note: If you exit your Racket process without properly closing the " + (racket charterm) + ", your terminal may be left in a crazy state. You can fix it with +the command:") + (commandline "stty sane"))) +(provide close-charterm) +(define (close-charterm #:charterm (ct (current-charterm))) + (with-handlers ((exn:fail? void)) (close-input-port (charterm-in ct))) + (with-handlers ((exn:fail? void)) (close-output-port (charterm-out ct))) + ;; TODO: Set the port fields of the struct to #f? + (if (with-handlers ((exn:fail? (lambda (e) #f))) + (system* "/bin/stty" + %charterm:stty-minus-f-arg-string + (charterm-tty ct) + "cooked" + "echo")) + (if (eq? ct (current-charterm)) + (current-charterm #f) + (void)) + (error 'close-charterm + "stty failed"))) + +;; (define (call-with-charterm proc #:tty (tty #f)) +;; (let* ((tty (cleanse-path tty)) +;; (ct (open-charterm #:tty tty #:current? #f))) +;; (dynamic-wind +;; void +;; (lambda () +;; (proc ct)) +;; (lambda () +;; (close-charterm #:charterm ct))))) + +(doc (defform (with-charterm expr? ...)) + (para "Opens a " + (racket charterm) + " and evaluates the body expressions in sequence with " + (racket current-charterm) + " set appropriately. When control jumps out of the body, in a +manner of speaking, the " + (racket charterm) + " is closed.")) +(provide with-charterm) +(define-syntax (with-charterm stx) + (syntax-case stx () + ((_ BODY0 BODYn ...) + #'(let ((ct #f)) + (dynamic-wind + (lambda () + (set! ct (open-charterm #:current? #t))) + (lambda () + BODY0 BODYn ...) + (lambda () + (close-charterm #:charterm ct) + (set! ct #f))))))) + +(doc (section "Terminal Information")) + +(doc (defproc (charterm-screen-size (#:charterm ct charterm? (current-charterm))) + (values (or/c #f exact-nonnegative-integer?) + (or/c #f exact-nonnegative-integer?)) + (para "Attempts to get the screen size, in character columns and rows. +It may do this through a control sequence or through " + (code "/bin/stty") + ". If unable to get a value, then default of (80,24) is used.") + (para "The current behavior in this version of " + (code "charterm") + " is to adaptively try different methods of getting screen size, +and to remember what worked for the next time this procedure is called for " + (racket ct) + ". For terminals that are identified as " + (code "screen") + " by the " + (code "TERM") + " environment variable (e.g., terminal emulators like GNU Screen +and " + (code "tmux") + "), the current behavior is to not try the control sequence (which +causes a 1-second delay waiting for a terminal response that never arrives), +and to just use " + (code "stty") + ". For all other terminals, the control sequence is tried first, before trying " + (code "stty") + ". If neither the control sequence nor " + (code "stty") + " work, then neither method is tried again for " + (racket ct) + ", and instead the procedure always returns (" + (racket #f) + ", " + (racket #f) + "). This behavior very well might change in future versions of " + (code "charterm") + ", and the author welcomes feedback on which methods work with +which terminals."))) +(provide charterm-screen-size) +(define (charterm-screen-size #:charterm (ct (current-charterm))) + ;; TODO: Make it store screen side in slots of charterm object too. Then + ;; create a "with-resizeable-charterm" form that has a resize handler (or + ;; maybe make the resize handler an argument to "with-charterm". + (let loop () + (case (charterm-screensize ct) + ((control) (%charterm:screen-size-via-control ct)) + ((stty) (%charterm:screen-size-via-stty ct)) + ;; TODO: Instead of (80,24), maybe be sensitive to termvar. + ((none) (values 80 24)) + ((control/stty/none) + (let-values (((cols rows) (%charterm:screen-size-via-control ct))) + (if (and cols rows) + (values cols rows) + (begin (set-charterm-screensize! ct 'stty/none) + (loop))))) + ((stty/none) + (let-values (((cols rows) (%charterm:screen-size-via-stty ct))) + (if (and cols rows) + (values cols rows) + (begin (set-charterm-screensize! ct 'none) + (loop))))) + (else (error 'charterm-screen-size + "invalid screensize ~S" + (charterm-screensize ct)))))) + +(define (%charterm:screen-size-via-control ct) + (%charterm:protocol-case + '%charterm:screen-size-via-control + (charterm-protocol ct) + ((ansi) + (%charterm:write-bytes ct #"\e[18t") + (cond ((%charterm:read-regexp-response ct #rx#"\e\\[8;([0-9]+);([0-9]+)t") + => (lambda (m) + (values (%charterm:bytes-ascii->nonnegative-integer (list-ref m 1)) + (%charterm:bytes-ascii->nonnegative-integer (list-ref m 0))))) + ;; TODO: We could do "ioctl" "TIOCGWINSZ", but that means FFI. + ;; + ;; TODO: We could execute "stty -a" (or perhaps "stty -g") to get + ;; around doing an FFI call. + (else (values #f #f)))) + ((wyse-wy50 televideo-925) + (%charterm:protocol-unreachable '%charterm:screen-size-via-control ct)))) + +(define (%charterm:screen-size-via-stty ct) + (let* ((stdout (open-output-bytes)) + (stderr (open-output-bytes)) + (proc (list-ref (process*/ports stdout + (open-input-bytes #"") + stderr + "/bin/stty" + %charterm:stty-minus-f-arg-string + (charterm-tty ct) + "-a") + 4)) + (bstr (begin (proc 'wait) + (get-output-bytes stdout)))) + (if (eq? 'done-ok (proc 'status)) + (let-values (((width height) + (cond ((regexp-match-positions + #rx#"rows +([0-9]+);.*columns +([0-9]+)" + bstr) + => (lambda (m) + (values (%charterm:bytes-ascii->nonnegative-integer + (subbytes bstr (caaddr m) (cdaddr m))) + (%charterm:bytes-ascii->nonnegative-integer + (subbytes bstr (caadr m) (cdadr m)))))) + ((regexp-match-positions + #rx#"columns +([0-9]+);.*rows +([0-9]+)" + bstr) + => (lambda (m) + (values (%charterm:bytes-ascii->nonnegative-integer + (subbytes bstr (caadr m) (cdadr m))) + (%charterm:bytes-ascii->nonnegative-integer + (subbytes bstr (caaddr m) (cdaddr m)))))) + (else #f #f)))) + ;; Note: These checks for 0 are for if "stty" returns 0, such as + ;; seems to happen in the emulator on the Wyse S50 when in SSH rather than Telnet. + (values (if (zero? width) #f width) + (if (zero? height) #f height))) + (values #f #f)))) + +(doc (section "Display Control")) + +(define (%charterm:shift-buf ct) + (let ((buf-start (charterm-buf-start ct)) + (buf-end (charterm-buf-end ct))) + (if (= buf-start buf-end) + ;; Buffer is empty, so are buf-start and buf-end at 0? + (if (zero? buf-end) + (void) + (begin (set-charterm-buf-start! ct 0) + (set-charterm-buf-end! ct 0))) + ;; Buffer is not empty, so is buf-start at 0? + ;; + ;; TODO: Maybe make this shift only if we need to to free N additional + ;; bytes at the end? + (if (zero? buf-start) + (void) + (let ((buf (charterm-buf ct))) + (bytes-copy! buf 0 buf buf-start buf-end) + (set-charterm-buf-start! ct 0) + (set-charterm-buf-end! ct (- buf-end buf-start))))))) + +(define (%charterm:read-into-buf/timeout ct timeout) + (let ((in (charterm-in ct))) + (let loop () + (let ((sync-result (sync/timeout/enable-break timeout in))) + (cond ((not sync-result) #f) + ((eq? sync-result in) + ;; TODO: if buf is empty, then read into start 0! + (let ((read-result (read-bytes-avail! (charterm-buf ct) + in + (charterm-buf-end ct) + (charterm-buf-size ct)))) + (if (zero? read-result) + ;; TODO: If there's a timeout, subtract from it? + (loop) + (begin (set-charterm-buf-end! ct (+ (charterm-buf-end ct) read-result)) + read-result)))) + (else (error '%charterm:read-into-buf/timeout + "*DEBUG* sync returned ~S" + sync-result))))))) + +(define (%charterm:read-regexp-response ct rx #:timeout-seconds (timeout-seconds 1.0)) + (let ((in (charterm-in ct))) + (%charterm:shift-buf ct) + ;; TODO: Implement timeout better, by checking clock and doing + ;; sync/timeout, or by setting timer. + (let loop ((timeout-seconds timeout-seconds)) + (if (= (charterm-buf-end ct) (charterm-buf-size ct)) + (begin + ;; TODO: Make this an exception instead of #f? + #f) + (begin (or (let ((buf (charterm-buf ct)) + (buf-start (charterm-buf-start ct)) + (buf-end (charterm-buf-end ct))) + (cond ((regexp-match-positions rx + buf + buf-start + buf-end) + => (lambda (m) + ;; TODO: Audit and test some of this buffer + ;; code here and elsewhere. + (let ((match-start (caar m)) + (match-end (cdar m))) + (if (= match-start buf-start) + (set-charterm-buf-start! ct match-end) + (if (= match-end buf-end) + (set-charterm-buf-end! ct match-start) + (begin (bytes-copy! buf + match-start + buf + match-end + buf-end) + (set-charterm-buf-end! ct + (+ match-start + (- buf-end + match-end))))))) + + (map (lambda (pos) + (subbytes buf (car pos) (cdr pos))) + (cdr m)))) + (else #f))) + (if (%charterm:read-into-buf/timeout ct timeout-seconds) + (loop timeout-seconds) + #f + ))))))) + +(define (%charterm:bytes-ascii->nonnegative-integer bstr) + (let ((bstr-len (bytes-length bstr))) + (let loop ((i 0) + (result 0)) + (if (= i bstr-len) + result + (let* ((b (bytes-ref bstr i)) + (b-num (- b 48))) + (if (<= 0 b-num 9) + (loop (+ 1 i) + (+ (* 10 result) b-num)) + (error '%charterm:bytes-ascii->nonnegative-integer + "invalid byte ~S" + b))))))) + +(doc (subsection "Cursor")) + +(doc (defproc (charterm-cursor (x exact-positive-integer?) + (y exact-positive-integer?) + (#:charterm ct charterm? (current-charterm))) + void? + (para "Positions the cursor at column " + (racket x) + ", row " + (racket y) + ", with the upper-left character cell being (1, 1)."))) +(provide charterm-cursor) +(define (charterm-cursor x y #:charterm (ct (current-charterm))) + (%charterm:position ct x y)) + +(doc (defproc (charterm-newline (#:charterm ct charterm? (current-charterm))) + void? + (para "Sends a newline to the terminal. This is typically a CR-LF +sequence."))) +(provide charterm-newline) +(define (charterm-newline #:charterm (ct (current-charterm))) + (%charterm:write-bytes ct #"\r\n")) + +(doc (subsection "Displaying")) + +(define %charterm:err-byte 63) + +(doc (defproc (charterm-display + (#:charterm ct charterm? (current-charterm)) + (#:width width (or/c #f exact-positive-integer?) #f) + (#:pad pad (or/c 'width boolean?) 'width) + (#:truncate truncate (or/c 'width boolean?) 'width) + ( arg any/c) ...) + void? + (para "Displays each " + (racket arg) + " on the terminal, as if formatted by " + (racket display) + ", with the exception that unprintable or non-ASCII characters +might not be displayed. (The exact behavior of what is permitted is expected +to change in a later version of " + "CharTerm" + ", so avoid trying to send your own control sequences or using +newlines, making assumptions about non-ASCII characters, etc.)") + (para "If " + (racket width) + " is a number, then " + (racket pad) + " and " + (racket truncate) + " specify whether or not to pad with spaces or truncate the output, respectively, to " + (racket width) + " characters. When " + (racket pad) + " or " + (racket width) + " is " + (racket 'width) + ", that is a convenience meaning ``true if, and only if, " + (racket width) + " is not " + (racket #f) + ".''"))) +(provide charterm-display) +(define (charterm-display #:charterm (ct (current-charterm)) + #:width (width #f) + #:pad (pad 'width) + #:truncate (truncate 'width) + . args) + ;; TODO: make it replace unprintable and non-ascii characters with "?". Even newlines, tabs, etc? + ;; + ;; TODO: Do we want buffering? + (let ((out (charterm-out ct)) + (pad (if (eq? 'width pad) (if width #t #f) pad)) + (truncate (if (eq? 'width truncate) (if width #t #f) truncate))) + (and pad (not width) (error 'charterm-display "#:pad cannot be true if #:width is not")) + (and truncate (not width) (error 'charterm-display "#:truncate cannot be true if #:width is not")) + (let loop ((args args) + (remaining-width (or width 0))) + (if (null? args) + (if (and pad (> remaining-width 0)) + ;; TODO: Get rid of this allocation. + (begin (%charterm:write-bytes ct (make-bytes remaining-width 32)) + (void)) + (void)) + (let* ((arg (car args)) + (bytes (cond ((bytes? arg) + arg) + ((string? arg) + (string->bytes/latin-1 arg + %charterm:err-byte + 0 + (if truncate + (min (string-length arg) + remaining-width) + (string-length arg)))) + ((number? arg) + (string->bytes/latin-1 (number->string arg) + %charterm:err-byte)) + (else (let ((arg (format "~A" arg))) + (string->bytes/latin-1 arg + %charterm:err-byte + 0 + (if truncate + (min (string-length arg) + remaining-width) + (string-length arg))))))) + (remaining-width (- remaining-width (bytes-length bytes)))) + (cond ((or (not truncate) (> remaining-width 0)) + (%charterm:write-bytes ct bytes) + (loop (cdr args) + remaining-width)) + ((zero? remaining-width) + (%charterm:write-bytes ct bytes) + (void)) + (else (%charterm:write-subbytes ct bytes 0 (+ (bytes-length bytes) + remaining-width)) + (void)))))))) + +(define (%charterm:send-code ct . args) + ;; TODO: Do we want buffering? + (let ((out (charterm-out ct))) + (let loop ((args args)) + (if (null? args) + (void) + (let ((arg (car args))) + (cond ((bytes? arg) + (write-bytes arg out)) + ((string? arg) + (write-string arg out)) + ((integer? arg) + (display (inexact->exact arg) out)) + ((pair? arg) + (loop (car arg)) + (loop (cdr arg))) + (else (error '%charterm:send-code + "don't know how to send ~S" + arg))) + (loop (cdr args))))))) + +;; (define %charterm:2-digit-bytes-vector +;; (vector #"00" #"01" #"02" #"03" #"04" #"05" #"06" #"07" +;; #"08" #"09" #"10" #"11" #"12" #"13" #"14" #"15" +;; #"16" #"17" #"18" #"19" #"20" #"21" #"22" #"23" +;; #"24" #"25" #"26" #"27" #"28" #"29" #"30" #"31" +;; #"32" #"33" #"34" #"35" #"36" #"37" #"38" #"39" +;; #"40" #"41" #"42" #"43" #"44" #"45" #"46" #"47" +;; #"48" #"49" #"50" #"51" #"52" #"53" #"54" #"55" +;; #"56" #"57" #"58" #"59" #"60" #"61" #"62" #"63" +;; #"64" #"65" #"66" #"67" #"68" #"68" #"69" #"70" +;; #"72" #"73" #"74" #"75" #"76" #"77" #"78" #"79" +;; #"80" #"81" #"82" #"83" #"84" #"85" #"86" #"87")) + +(define %charterm:televideo-925-cursor-position-to-byte-vector + (list->vector (cons #f + (for/list ((n (in-range 1 96))) + (+ 31 n))))) + +;; (provide/contract with error-checks on args +(define (%charterm:position ct x y) + (%charterm:protocol-case + '%charterm:position + (charterm-protocol ct) + ((ansi) + (if (and (= 1 x) (= 1 y)) + (%charterm:write-bytes ct #"\e[;H") + (%charterm:send-code ct #"\e[" y #";" x #"H"))) + ((wyse-wy50) + ;; Note: We are using the WY-50 long codes because we don't know + ;; confidently that we are an 80-column screen. + (if (and (= 1 x) (= 1 y)) + (%charterm:write-bytes ct #"\ea1R1C") + (%charterm:send-code ct #"\ea" y #"R" x #"C"))) + ((televideo-925) + (if (and (= 1 x) (= 1 y)) + (%charterm:write-bytes ct #"\e= ") + (begin (%charterm:write-bytes ct #"\e=") + (%charterm:write-byte ct (vector-ref %charterm:televideo-925-cursor-position-to-byte-vector y)) + (%charterm:write-byte ct (vector-ref %charterm:televideo-925-cursor-position-to-byte-vector x))))))) + +(doc (subsection "Video Attributes")) + +;; TODO: !!! document link to protocol section + +;; TODO: !!! define "charterm-has-video-attributes?" + +(doc (defproc* + (((charterm-normal (#:charterm ct charterm? (current-charterm))) void?) + ((charterm-inverse (#:charterm ct charterm? (current-charterm))) void?) + ((charterm-underline (#:charterm ct charterm? (current-charterm))) void?) + ((charterm-blink (#:charterm ct charterm? (current-charterm))) void?) + ((charterm-bold (#:charterm ct charterm? (current-charterm))) void?)) + (para "Sets the " + (deftech "video attributes") + " for subsequent writes to the terminal. In this version of " + (code "charterm") + ", each is mutually-exclusive, so, for example, setting " + (italic "bold") + " clears " + (italic "inverse") + ". Note that that video attributes are currently supported only for protocol " + (racket 'ansi) + ", due to limitations of the TeleVideo and Wyse models for +video attributes."))) + +(provide charterm-normal) +(define (charterm-normal #:charterm (ct (current-charterm))) + (%charterm:protocol-case + 'charterm-normal + (charterm-protocol ct) + ((ansi) (%charterm:write-bytes ct #"\e[m")) + ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA00")) + ((televideo-925) (void)))) + +(provide charterm-inverse) +(define (charterm-inverse #:charterm (ct (current-charterm))) + (%charterm:protocol-case + 'charterm-inverse + (charterm-protocol ct) + ((ansi) (%charterm:write-bytes ct #"\e[;7m")) + ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA04")) + ((televideo-925) (void)))) + +(provide charterm-underline) +(define (charterm-underline #:charterm (ct (current-charterm))) + (%charterm:protocol-case + 'charterm-underline + (charterm-protocol ct) + ((ansi) (%charterm:write-bytes ct #"\e[4m")) + ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA08")) + ((televideo-925) (void)))) + +(provide charterm-blink) +(define (charterm-blink #:charterm (ct (current-charterm))) + (%charterm:protocol-case + 'charterm-blink + (charterm-protocol ct) + ((ansi) (%charterm:write-bytes ct #"\e[5m")) + ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA02")) + ((televideo-925) (void)))) + +(provide charterm-bold) +(define (charterm-bold #:charterm (ct (current-charterm))) + (%charterm:protocol-case + 'charterm-bold + (charterm-protocol ct) + ((ansi) (%charterm:write-bytes ct #"\e[1m")) + ((wyse-wy50) (void)) ; (%charterm:write-bytes ct #"\eA0<")) + ((televideo-925) (void)))) + +(doc (subsection "Clearing")) + +(doc (defproc (charterm-clear-screen (#:charterm ct charterm? (current-charterm))) + void? + (para "Clears the screen, including first setting the video attributes to +normal, and positioning the cursor at (1, 1)."))) +(provide charterm-clear-screen) +(define (charterm-clear-screen #:charterm (ct (current-charterm))) + ;; TODO: Have a #:style argument? Or #:background argument? + (%charterm:protocol-case + 'charterm-clear-screen + (charterm-protocol ct) + ((ansi) (%charterm:write-bytes ct #"\e[m\e[2J\e[;H")) + ((wyse-wy50) (%charterm:write-bytes ct #"\e+\e*\ea1R1C")) + ((televideo-925) (%charterm:write-bytes ct #"\e+\e= ")))) + +(doc (defproc* + (((charterm-clear-line (#:charterm ct charterm? (current-charterm))) void?) + ((charterm-clear-line-left (#:charterm ct charterm? (current-charterm))) void?) + ((charterm-clear-line-right (#:charterm ct charterm? (current-charterm))) void?)) + (para "Clears text from the line with the cursor, or part of the line with the cursor."))) + +(provide charterm-clear-line) +(define (charterm-clear-line #:charterm (ct (current-charterm))) + (%charterm:protocol-case + 'charterm:clear-line + (charterm-protocol ct) + ((ansi) (%charterm:write-bytes ct #"\e[2K")) + ((televideo-925) (%charterm:write-bytes ct #"\r\eT")) + ;; TODO: wyse-wy50 is clearing to nulls, not spaces. + ((wyse-wy50) (%charterm:write-bytes ct #"\r\et")))) + +(provide charterm-clear-line-left) +(define (charterm-clear-line-left #:charterm (ct (current-charterm))) + (%charterm:protocol-case + 'charterm-clear-line-left + (charterm-protocol ct) + ((ansi) (%charterm:write-bytes ct #"\e[1K")) + ((televideo-925 wyse-wy50) + ;; TODO: Do this by getting cursor position, then reposition and write spaces? + (%charterm:unimplemented ct 'clearterm-clear-line-left)))) + +(provide charterm-clear-line-right) +(define (charterm-clear-line-right #:charterm (ct (current-charterm))) + (%charterm:protocol-case + 'charterm-clear-line-right + (charterm-protocol ct) + ((ansi) (%charterm:write-bytes ct #"\e[K")) + ((televideo-925) (%charterm:write-bytes ct #"\eT")) + ;; TODO: wyse-wy50 is clearing to nulls, not spaces. + ((wyse-wy50) (%charterm:write-bytes ct #"\et")))) + +(doc (subsection "Line Insert and Delete")) + +(doc (defproc (charterm-insert-line (count exact-positive-integer? 1) + (#:charterm ct charterm? (current-charterm))) + void? + (para "Inserts " + (racket count) + " blank lines at cursor. Note that not all terminals support +this."))) +(provide charterm-insert-line) +(define (charterm-insert-line (count 1) #:charterm (ct (current-charterm))) + (if (integer? count) + (cond ((= count 0) (void)) + ((> count 0) + (%charterm:protocol-case + 'charterm-insert-line + (charterm-protocol ct) + ((ansi) (%charterm:send-code ct #"\e[" count "L")) + ((wyse-wy50 televideo-925) (%charterm:write-bytes ct #"\eE")))) + (else (error 'charterm-insert-line + "invalid count: ~S" + count))) + (error 'charterm-insert-line + "invalid count: ~S" + count))) + +(doc (defproc (charterm-delete-line (count exact-positive-integer? 1) + (#:charterm ct charterm? (current-charterm))) + void? + (para "Deletes " + (racket count) + " blank lines at cursor. Note that not all terminals support +this."))) +(provide charterm-delete-line) +(define (charterm-delete-line (count 1) #:charterm (ct (current-charterm))) + (if (integer? count) + (cond ((= count 0) (void)) + ((> count 0) + (%charterm:protocol-case + 'charterm-delete-line + (charterm-protocol ct) + ((ansi) + (%charterm:send-code ct #"\e[" count "M")) + ((wyse-wy50 televideo-925) + (if (= 1 count) + (%charterm:write-bytes ct #"\eR") + (let ((bstr (make-bytes (* 2 count) 82))) + (let loop ((n (* 2 (- count 1)))) + (bytes-set! bstr n 27) + (if (zero? n) + (%charterm:write-bytes ct bstr) + (loop (- n 2))))))))) + (else (error 'charterm-delete-line + "invalid count: ~S" + count))) + (error 'charterm-delete-line + "invalid count: ~S" + count))) + +(doc (subsubsection "Misc. Output")) + +(doc (defproc (charterm-bell (#:charterm ct charterm? (current-charterm))) + void? + (para "Rings the terminal bell. This bell ringing might manifest as a +beep, a flash of the screen, or nothing."))) +(provide charterm-bell) +(define (charterm-bell #:charterm (ct (current-charterm))) + (%charterm:write-bytes ct #"\007")) + +(doc (section "Keyboard Input") + + ;; TODO: !!! document link to terminal diversity section + + (para "Normally you will get keyboard input using the " + (racket charterm-read-key) + " procedure.")) + +(doc (defproc (charterm-byte-ready? (#:charterm ct charterm? (current-charterm))) + boolean? + (para "Returns true/false for whether at least one byte is ready for +reading (either in a buffer or on the port) from " + (racket ct) + ". Note that, since some keys are encoded as multiple bytes, just +because this procedure returns true doesn't mean that " + (racket charterm-read-key) + " won't block temporarily because it sees part of a potential +multiple-byte key encoding."))) +(provide charterm-byte-ready?) +(define (charterm-byte-ready? #:charterm (ct (current-charterm))) + (or (> (charterm-buf-end ct) (charterm-buf-start ct)) + (byte-ready? (charterm-in ct)))) + +(doc (defproc (charterm-read-key + (#:charterm ct charterm? (current-charterm)) + (#:timeout timeout (or/c #f positive?) #f)) + (or #f char? symbol?) + (para "Reads a key from " + (racket ct) + ", blocking indefinitely or until sometime after " + (racket timeout) + " seconds has been reached, if " + (racket timeout) + " is non-" + (racket #f) + ". If timeout is reached, " + (racket #f) + " is returned.") + (para "Many keys are returned as characters, especially ones that +correspond to printable characters. For example, the unshifted " + (bold "Q") + " key is returned as character " + (racket #\q) + ". Some other keys are returned as symbols, such as " + (racket 'return) + ", " + (racket 'escape) + ", " + (racket 'f1) + ", " + (racket 'shift-f12) + ", " + (racket 'right) + ", and many others.") + (para "Since some keys are sent as ambiguous sequences, " + (racket charterm-read-key) + " employs separate timeouts internally, such as to disambuate +the " + (bold "Esc") + " key (byte sequence 27) from what on some terminals would be +the " + (bold "F10") + " key (bytes sequence 27, 91, 50, 49, 126)."))) +(provide charterm-read-key) +(define (charterm-read-key #:charterm (ct (current-charterm)) + #:timeout (timeout #f)) + (%charterm:read-keyinfo-or-key 'charterm-read-key ct timeout #f)) + +(doc (defproc (charterm-read-keyinfo + (#:charterm ct charterm? (current-charterm)) + (#:timeout timeout (or/c #f positive?) #f)) + charterm-keyinfo? + (para "Like " + (racket charterm-read-keyinfo) + " except instead of returning a " + (tech "keycode") + ", it returns a " + (tech "keyinfo") + "."))) +(provide charterm-read-keyinfo) +(define (charterm-read-keyinfo #:charterm (ct (current-charterm)) + #:timeout (timeout #f)) + (%charterm:read-keyinfo-or-key 'charterm-read-keyinfo ct timeout #t)) + +(define (%charterm:read-keyinfo-or-key error-name ct timeout keyinfo?) + ;; TODO: Maybe make this shift decision smarter -- compile the key tree ahead + ;; of time so we know the max depth, and then we know exactly the max space + ;; we will need for this call. + (and (< (- (charterm-buf-size ct) + (charterm-buf-start ct)) + 10) + (%charterm:shift-buf ct)) + (let ((buf (charterm-buf ct)) + (buf-start (charterm-buf-start ct)) + (buf-end (charterm-buf-end ct)) + (buf-size (charterm-buf-size ct)) + (keydec (charterm-keydec* ct)) + (b1 (%charterm:read-byte/timeout ct timeout))) + (if b1 + (or (let loop ((tree (charterm-keydec-primary-keytree keydec)) + (probe-start (+ 1 buf-start)) + (b b1)) + (cond ((hash-ref tree b #f) + => (lambda (code-or-subtree) + (cond ((hash? code-or-subtree) + ;; We have more subtree to search. + (if (or (< probe-start buf-end) + (and (< buf-end buf-size) + (%charterm:read-into-buf/timeout ct 0.5))) + ;; We have at least one more byte, so recurse. + (loop code-or-subtree + (+ 1 probe-start) + (bytes-ref buf probe-start)) + ;; We have hit timeout or end of buffer, so + ;; just accept the original byte. + #f)) + ((charterm-keyinfo? code-or-subtree) + ;; We found our keyinfo, so consume the input and return the value. + (begin (set-charterm-buf-start! ct probe-start) + (if keyinfo? + code-or-subtree + (charterm-keyinfo-keycode code-or-subtree)) + )) + (else (error error-name + "invalid object in keytree keyinfo position: ~S" + code-or-subtree))))) + (else #f))) + ;; We didn't find a key code, so try secondary keytree with initial byte. + (cond ((hash-ref (charterm-keydec-secondary-keytree keydec) b1 #f) + => (lambda (keyinfo) + (if keyinfo? + keyinfo + (charterm-keyinfo-keycode keyinfo)))) + (else (if keyinfo? + ;; TODO: Cache these keyinfos for unrecognized keys + ;; in the charterm object, or make a fallback + ;; keyset for them (although the fallback keyset, + ;; while it works for 8-bit characters, becomes + ;; less practical if we implement multibyte). + (make-charterm-keyinfo #f + #f + (list b1) + "???" + b1 + (list b1)) + (integer->char b1))))) + ;; Got a timeout, so return #f. + #f))) + +(define (%charterm:write-byte ct byt) + (write-byte byt (charterm-out ct))) + +(define (%charterm:write-bytes ct bstr . rest-bstrs) + (write-bytes bstr (charterm-out ct)) + (or (null? rest-bstrs) + (for-each (lambda (bstr) + (write-bytes bstr (charterm-out ct))) + rest-bstrs))) + +(define (%charterm:write-subbytes ct bstr start end) + (write-bytes bstr (charterm-out ct) start end)) + +(define (%charterm:read-byte/timeout ct timeout) + (let ((buf-start (charterm-buf-start ct))) + (if (or (< buf-start (charterm-buf-end ct)) + (%charterm:read-into-buf/timeout ct timeout)) + (begin0 (bytes-ref (charterm-buf ct) buf-start) + (set-charterm-buf-start! ct (+ 1 buf-start))) + #f))) + +(define (%charterm:read-byte ct) + (%charterm:read-byte/timeout ct #f)) + +(doc (section "References") + + (para "[" (deftech "ANSI X3.64") "] " + (url "http://en.wikipedia.org/wiki/ANSI_escape_code")) + + (para "[" (deftech "ASCII") "] " + (url "http://en.wikipedia.org/wiki/Ascii")) + + (para "[" (deftech "ECMA-43") "] " + (hyperlink "http://www.ecma-international.org/publications/standards/Ecma-043.htm" + (italic "Standard ECMA-43: 8-bit Coded Character Set Structure and Rules")) + ", 3rd Ed., 1991-12") + + (para "[" (deftech "ECMA-48") "] " + (hyperlink "http://www.ecma-international.org/publications/standards/Ecma-048.htm" + (italic "Standard ECMA-48: Control Functions for Coded Character Sets")) + ", 5th Ed., 1991-06") + + (para "[" (deftech "Gregory") "] " + "Phil Gregory, ``" + (hyperlink "http://aperiodic.net/phil/archives/Geekery/term-function-keys.html" + "Terminal Function Key Escape Codes") + ",'' 2005-12-13 Web post, as viewed on 2012-06") + + (para "[" (deftech "PowerTerm") "] " + "Ericom PowerTerm InterConnect 8.2.0.1000 terminal emulator, as run on Wyse S50 WinTerm") + + (para "[" (deftech "TVI-925-IUG") "] " + (hyperlink "http://vt100.net/televideo/tvi925_ig.pdf" + (italic "TeleVideo Model 925 CRT Terminal Installation and User's Guide"))) + + (para "[" (deftech "TVI-950-OM") "] " + (hyperlink "http://www.mirrorservice.org/sites/www.bitsavers.org/pdf/televideo/Operators_Manual_Model_950_1981.pdf" + (italic "TeleVideo Operator's Manual Model 950")) + ", 1981") + + (para "[" (deftech "VT100-TM") "] " + "Digital Equipment Corp., " + (hyperlink "http://vt100.net/docs/vt100-tm/" + (italic "VT100 Series Technical Manual")) + ", 2nd Ed., 1980-09") + + (para "[" (deftech "VT100-UG") "] " + "Digital Equipment Corp., " + (hyperlink "http://vt100.net/docs/vt100-ug/" + (italic "VT100 User Guide")) + ", 3rd Ed., 1981-06") + + (para "[" (deftech "VT100-WP") "] " + "Wikipedia, " + (hyperlink "http://en.wikipedia.org/wiki/VT100" + "VT100")) + + (para "[" (deftech "WY-50-QRG") "] " + (hyperlink "http://vt100.net/wyse/wy-50-qrg/wy-50-qrg.pdf" + (italic "Wyse WY-50 Display Terminal Quick-Reference Guide"))) + + (para "[" (deftech "WY-60-UG") "] " + (hyperlink "http://vt100.net/wyse/wy-60-ug/wy-60-ug.pdf" + (italic "Wyse WY-60 User's Guide"))) + + (para "[" (deftech "wy60") "] " + (hyperlink "http://code.google.com/p/wy60/" + (code "wy60") + " terminal emulator")) + + (para "[" (deftech "XTerm-ctlseqs") "] " + "Edward Moy, Stephen Gildea, Thomas Dickey, ``" + (hyperlink "http://invisible-island.net/xterm/ctlseqs/ctlseqs.html" + "Xterm Control Sequences") + ",'' 2012") + + (para "[" (deftech "XTerm-Dickey") "] " + (url "http://invisible-island.net/xterm/")) + + (para "[" (deftech "XTerm-FAQ") "] " + "Thomas E. Dickey, ``" + (hyperlink "http://invisible-island.net/xterm/xterm.faq.html" + "XTerm FAQ") + ",'' dated 2012") + + (para "[" (deftech "XTerm-WP") "] " + "Wikipedia, " + (hyperlink "http://en.wikipedia.org/wiki/Xterm" + "xterm")) + + ) + +(doc (section "Known Issues") + + (itemlist + + (item "Need to support ANSI alternate CSI for 8-bit terminals, even +before supporting 8-bit characters and multibyte.") + + (item "Only supports ASCII characters. Adding UTF-8 support, for terminal emulators +that support it, would be nice.") + + (item "Expose the character-decoding mini-language as a configurable +option. Perhaps wait until we implement timeout-based disambiguation at +arbitrary points in the the DFA rather than just at the top. Also, might be +better to resolve multi-byte characters first, in case that affects the +mini-language.") + + (item "More controls for terminal features can be added.") + + (item "Currently only implemented to work on Unix-like systems like +GNU/Linux.") + + (item "Implement text input controls, either as part of this library or +another, using " + (racket charterm-demo) + " as a starting point."))) + +;; Note: Different ways to test demo: +;; +;; racket -t demo.rkt -m +;; screen racket -t demo.rkt -m +;; tmux -c "racket -t demo.rkt -m" +;; xterm -e racket -t demo.rkt -m +;; rxvt -e racket -t demo.rkt -m +;; wy60 -c racket -t demo.rkt -m +;; +;; racket -t demo.rkt -m- -n + +;; TODO: Source for TeleVideo manuals: +;; http://www.mirrorservice.org/sites/www.bitsavers.org/pdf/televideo/ + +;; TODO: Add shifted function keys from T60 keyboard (not USB one). + +(doc history + + (#:planet 3:1 #:date "2013-05-13" + (itemlist + (item "Now uses lowercase " + (code "-f") + " argument on MacOS X. (Thanks to Jens Axel S\u00F8gaard for reporting.)") + (item "Documentation tweaks."))) + + (#:planet 3:0 #:date "2012-07-13" + (itemlist + (item "Changed ``" + (code "ansi-ish") + "'' in identifiers to ``" + (code "ansi") + "'', hence the PLaneT major version number change.") + (item "Documentation tweaks.") + (item "Renamed package from ``" + (code "charterm") + "'' to ``CharTerm''."))) + + (#:planet 2:5 #:date "2012-06-28" + (itemlist + (item "A " + (racket charterm) + " object is now a synchronizable event.") + (item "Documentation tweaks."))) + + (#:planet 2:4 #:date "2012-06-25" + (itemlist + (item "Documentation fix for return type of " + (racket charterm-read-keyinfo) + "."))) + + (#:planet 2:3 #:date "2012-06-25" + (itemlist + (item "Fixed problem determining screen size on some +XTerms. (Thanks to Eli Barzilay for reporting.)"))) + + (#:planet 2:2 #:date "2012-06-25" + (itemlist + (item "Added another variation of encoding for XTerm arrow, +Home, and End keys. (Thanks to Eli Barzilay.)"))) + + (#:planet 2:1 #:date "2012-06-24" + (itemlist + (item "Corrected PLaneT version number in " + (racket require) + " in an example."))) + + (#:planet 2:0 #:date "2012-06-24" + (itemlist + (item "Greatly increased the sophistication of handling of terminal diversity.") + (item "Added the " + (code "wyse-wy50") + " and " + (code "televideo-950") + " [Correction: " + (code "televideo-925") + "] protocols, for supporting the native modes of Wyse and +TeleVideo terminals, respectively, and compatibles.") + (item "More support for different key encodings and termvars.") + (item "Demo is now in a separate file, mainly for convenience +in giving command lines that run it. This breaks a command line example +previously documented, so changed PLaneT major version, although the +previously-published example will need to have " + (code ":1") + " added to it anyway.") + (item (racket charterm-screen-size) + " now defaults to (80,24) when all else fails.") + (item "Documentation changes."))) + + (#:planet 1:1 #:date "2012-06-17" + (itemlist + (item "For " + (code "screen") + " and " + (code "tmux") + ", now gets screen size via " + (code "stty") + ". This resolves the sluggishness reported with " + (code "screen") + ". [Correction: In version 1:1, this behavior is +adaptive for all terminals, with the shortcut for " + (tech "termvar") + " " + (code "screen") + " that it doesn't bother trying the control sequence.]") + (item "Documentation tweaks."))) + + (#:planet 1:0 #:date "2012-06-16" + (itemlist + (item "Initial version.")))) diff --git a/archive/1.vm.arc/charterm/demo.rkt b/archive/1.vm.arc/charterm/demo.rkt new file mode 100644 index 00000000..4cbff6e5 --- /dev/null +++ b/archive/1.vm.arc/charterm/demo.rkt @@ -0,0 +1,306 @@ +#lang racket/base +;; For legal info, see file "info.rkt" + +(require racket/cmdline + racket/date + "charterm.rkt") + +(define (%charterm:string-pad-or-truncate str width) + (let ((len (string-length str))) + (cond ((= len width) str) + ((< len width) (string-append str (make-string (- width len) #\space))) + (else (substring str 0 width))))) + +(define (%charterm:bytes-pad-or-truncate bstr width) + (let ((len (bytes-length bstr))) + (cond ((= len width) bstr) + ((< len width) + (let ((new-bstr (make-bytes width 32))) + (bytes-copy! new-bstr 0 bstr) + new-bstr)) + (else (subbytes bstr 0 width))))) + +(define-struct %charterm:demo-input + (x y width bytes used cursor) + #:mutable) + +(define (%charterm:make-demo-input x y width bstr) + (let ((new-bstr (%charterm:bytes-pad-or-truncate bstr width)) + (used (min (bytes-length bstr) width))) + (make-%charterm:demo-input x + y + width + new-bstr + used + used))) + +(define (%charterm:demo-input-redraw di) + (charterm-cursor (%charterm:demo-input-x di) + (%charterm:demo-input-y di)) + (charterm-normal) + (charterm-underline) + (charterm-display (%charterm:demo-input-bytes di) + #:width (%charterm:demo-input-width di)) + (charterm-normal)) + +(define (%charterm:demo-input-put-cursor di) + ;; Note: Commented-out debugging code: + ;; + ;; (and #t + ;; (begin (charterm-normal) + ;; (charterm-cursor (+ (%charterm:demo-input-x di) + ;; (%charterm:demo-input-width di) + ;; 1) + ;; (%charterm:demo-input-y di)) + ;; (charterm-display #" cursor: " + ;; (%charterm:demo-input-cursor di) + ;; #" used: " + ;; (%charterm:demo-input-used di)) + ;; (charterm-clear-line-right))) + (charterm-cursor (+ (%charterm:demo-input-x di) + (%charterm:demo-input-cursor di)) + (%charterm:demo-input-y di))) + +(define (%charterm:demo-input-cursor-left di) + (let ((cursor (%charterm:demo-input-cursor di))) + (if (zero? cursor) + (begin (charterm-bell) + (%charterm:demo-input-put-cursor di)) + (begin (set-%charterm:demo-input-cursor! di (- cursor 1)) + (%charterm:demo-input-put-cursor di))))) + +(define (%charterm:demo-input-cursor-right di) + (let ((cursor (%charterm:demo-input-cursor di))) + (if (= cursor (%charterm:demo-input-used di)) + (begin (charterm-bell) + (%charterm:demo-input-put-cursor di)) + (begin (set-%charterm:demo-input-cursor! di (+ cursor 1)) + (%charterm:demo-input-put-cursor di))))) + +(define (%charterm:demo-input-backspace di) + (let ((cursor (%charterm:demo-input-cursor di))) + (if (zero? cursor) + (begin (charterm-bell) + (%charterm:demo-input-put-cursor di)) + (let ((bstr (%charterm:demo-input-bytes di)) + (used (%charterm:demo-input-used di))) + ;; TODO: test beginning/end of buffer, of used, of width + (bytes-copy! bstr (- cursor 1) bstr cursor used) + (bytes-set! bstr (- used 1) 32) + (set-%charterm:demo-input-used! di (- used 1)) + (set-%charterm:demo-input-cursor! di (- cursor 1)) + (%charterm:demo-input-redraw di) + (%charterm:demo-input-put-cursor di))))) + +(define (%charterm:demo-input-delete di) + (let ((cursor (%charterm:demo-input-cursor di)) + (used (%charterm:demo-input-used di))) + (if (= cursor used) + (begin (charterm-bell) + (%charterm:demo-input-put-cursor di)) + (let ((bstr (%charterm:demo-input-bytes di))) + (or (= cursor used) + (bytes-copy! bstr cursor bstr (+ 1 cursor) used)) + (bytes-set! bstr (- used 1) 32) + (set-%charterm:demo-input-used! di (- used 1)) + (%charterm:demo-input-redraw di) + (%charterm:demo-input-put-cursor di))))) + +(define (%charterm:demo-input-insert-byte di new-byte) + (let ((used (%charterm:demo-input-used di)) + (width (%charterm:demo-input-width di))) + (if (= used width) + (begin (charterm-bell) + (%charterm:demo-input-put-cursor di)) + (let ((bstr (%charterm:demo-input-bytes di)) + (cursor (%charterm:demo-input-cursor di))) + (or (= cursor used) + (bytes-copy! bstr (+ cursor 1) bstr cursor used)) + (bytes-set! bstr cursor new-byte) + (set-%charterm:demo-input-used! di (+ 1 used)) + (set-%charterm:demo-input-cursor! di (+ cursor 1)) + (%charterm:demo-input-redraw di) + (%charterm:demo-input-put-cursor di))))) + +(provide charterm-demo) +(define (charterm-demo #:tty (tty #f) + #:escape? (escape? #t)) + (let ((data-row 4) + (di (%charterm:make-demo-input 10 2 18 #"Hello, world!"))) + (with-charterm + (let ((ct (current-charterm))) + (let/ec done-ec + (let loop-remember-read-screen-size ((last-read-col-count 0) + (last-read-row-count 0)) + + (let loop-maybe-check-screen-size () + (let*-values (((read-col-count read-row-count) + (if (or (equal? 0 last-read-col-count) + (equal? 0 last-read-row-count) + (not (charterm-byte-ready?))) + (charterm-screen-size) + (values last-read-col-count + last-read-row-count))) + ((read-screen-size? col-count row-count) + (if (and read-col-count read-row-count) + (values #t + read-col-count + read-row-count) + (values #f + (or read-col-count 80) + (or read-row-count 24)))) + ((read-screen-size-changed?) + (not (and (equal? read-col-count + last-read-col-count) + (equal? read-row-count + last-read-row-count)))) + ((clock-col) + (let ((clock-col (- col-count 8))) + (if (< clock-col 15) + #f + clock-col)))) + ;; Did screen size change? + (if read-screen-size-changed? + + ;; Screen size changed. + (begin (charterm-clear-screen) + (charterm-cursor 1 1) + (charterm-inverse) + (charterm-display (%charterm:string-pad-or-truncate " charterm Demo" + col-count)) + (charterm-normal) + + (charterm-cursor 1 2) + (charterm-inverse) + (charterm-display #" Input: ") + (charterm-normal) + (%charterm:demo-input-redraw di) + + (charterm-cursor 1 data-row) + (if escape? + (begin + (charterm-display "To quit, press ") + (charterm-bold) + (charterm-display "Esc") + (charterm-normal) + (charterm-display ".")) + (charterm-display "There is no escape from this demo.")) + + (charterm-cursor 1 data-row) + (charterm-insert-line) + (charterm-display "termvar ") + (charterm-bold) + (charterm-display (charterm-termvar ct)) + (charterm-normal) + (charterm-display ", protocol ") + (charterm-bold) + (charterm-display (charterm-protocol ct)) + (charterm-normal) + (charterm-display ", keydec ") + (charterm-bold) + (charterm-display (charterm-keydec-id (charterm-keydec ct))) + (charterm-normal) + + (charterm-cursor 1 data-row) + (charterm-insert-line) + (charterm-display #"Screen size: ") + (charterm-bold) + (charterm-display col-count) + (charterm-normal) + (charterm-display #" x ") + (charterm-bold) + (charterm-display row-count) + (charterm-normal) + (or read-screen-size? + (charterm-display #" (guessing; terminal would not tell us)")) + + (charterm-cursor 1 data-row) + (charterm-insert-line) + (charterm-display #"Widths:") + (for-each (lambda (bytes) + (charterm-display #" [") + (charterm-underline) + (charterm-display bytes #:width 3) + (charterm-normal) + (charterm-display #"]")) + '(#"" #"a" #"ab" #"abc" #"abcd")) + + ;; (and (eq? 'wy50 (charterm-protocol ct)) + ;; (begin + ;; (charterm-cursor 1 data-row) + ;; (charterm-insert-line) + ;; (charterm-display #"Wyse WY-50 delete character: ab*c\010\010\eW"))) + + (loop-remember-read-screen-size read-col-count + read-row-count)) + ;; Screen size didn't change (or we didn't check). + (begin + (and clock-col + (begin (charterm-inverse) + (charterm-cursor clock-col 1) + (charterm-display (parameterize ((date-display-format 'iso-8601)) + (substring (date->string (current-date) #t) + 11))) + (charterm-normal))) + + (let loop-fast-next-key () + (%charterm:demo-input-put-cursor di) + (let ((keyinfo (charterm-read-keyinfo #:timeout 1))) + (if keyinfo + (let ((keycode (charterm-keyinfo-keycode keyinfo))) + (charterm-cursor 1 data-row) + (charterm-insert-line) + (charterm-display "Read key: ") + (charterm-bold) + (charterm-display (or (charterm-keyinfo-keylabel keyinfo) "???")) + (charterm-normal) + (charterm-display (format " ~S" + `(,(charterm-keyinfo-keyset-id keyinfo) + ,(charterm-keyinfo-bytelang keyinfo) + ,(charterm-keyinfo-bytelist keyinfo) + ,@(charterm-keyinfo-all-keycodes keyinfo)))) + (if (char? keycode) + (let ((key-num (char->integer keycode))) + (if (<= 32 key-num 126) + (begin (%charterm:demo-input-insert-byte di key-num) + (loop-fast-next-key)) + (loop-fast-next-key))) + (case keycode + ((left) + (%charterm:demo-input-cursor-left di) + (loop-fast-next-key)) + ((right) + (%charterm:demo-input-cursor-right di) + (loop-fast-next-key)) + ((backspace) + (%charterm:demo-input-backspace di) + (loop-fast-next-key)) + ((delete) + (%charterm:demo-input-delete di) + (loop-fast-next-key)) + ((escape) + (if escape? + (begin + (charterm-clear-screen) + (charterm-display "You have escaped the charterm demo!") + (charterm-newline) + (done-ec)) + (loop-fast-next-key))) + (else (loop-fast-next-key))))) + (begin + ;; (charterm-display "Timeout.") + (loop-maybe-check-screen-size))))))))))))))) + +(provide main) +(define (main . args) + ;; TODO: Accept TTY as an argument. + (let ((tty #f) + (escape? #t)) + (command-line #:program "(charterm Demo)" + #:once-each + (("--tty" "-t") arg "The TTY to use (default: /dev/tty)." (set! tty arg)) + #:once-any + (("--escape" "-e") "Esc key quits program (default)." (set! escape? #t)) + (("--no-escape" "-n") "Esc key does not quit program." (set! escape? #f))) + (charterm-demo #:tty tty + #:escape? escape?))) diff --git a/archive/1.vm.arc/charterm/doc.scrbl b/archive/1.vm.arc/charterm/doc.scrbl new file mode 100644 index 00000000..67040691 --- /dev/null +++ b/archive/1.vm.arc/charterm/doc.scrbl @@ -0,0 +1,7 @@ +#lang scribble/manual +@; THIS-FILE-WAS-GENERATED-BY-MCFLY-TOOLS (planet neil/mcfly-tools:1:=12) +@(require (for-syntax racket/base) + (for-template racket/base) + (planet neil/mcfly:1:3/mcfly-scribble) + (planet neil/mcfly:1:3/mcfly-expand)) +@(mcfly-expand "charterm.rkt") diff --git a/archive/1.vm.arc/charterm/info.rkt b/archive/1.vm.arc/charterm/info.rkt new file mode 100644 index 00000000..64eeaefe --- /dev/null +++ b/archive/1.vm.arc/charterm/info.rkt @@ -0,0 +1,29 @@ +#lang setup/infotab + +(define mcfly-planet 'neil/charterm:3:1) +(define name "CharTerm") +(define mcfly-subtitle "Character-cell Terminal Interface in Racket") +(define blurb (list name ": Character-cell Terminal Interface")) +(define homepage "http://www.neilvandyke.org/racket-charterm/") +(define mcfly-author "Neil Van Dyke") +(define repositories '("4.x")) +(define categories '(misc)) +(define can-be-loaded-with 'all) +(define scribblings '(("doc.scrbl" () (library)))) +(define primary-file "main.rkt") +(define mcfly-start "charterm.rkt") +(define mcfly-files '(defaults + "charterm.rkt" + "demo.rkt" + "test-charterm.rkt")) +(define mcfly-license "LGPLv3") + +(define mcfly-legal + "Copyright 2012 -- 2013 Neil Van Dyke. This program is Free Software; you +can redistribute it and/or modify it under the terms of the GNU Lesser General +Public License as published by the Free Software Foundation; either version 3 +of the License, or (at your option) any later version. This program is +distributed in the hope that it will be useful, but without any warranty; +without even the implied warranty of merchantability or fitness for a +particular purpose. See http://www.gnu.org/licenses/ for details. For other +licenses and consulting, please contact the author.") diff --git a/archive/1.vm.arc/charterm/main.rkt b/archive/1.vm.arc/charterm/main.rkt new file mode 100644 index 00000000..5566a73f --- /dev/null +++ b/archive/1.vm.arc/charterm/main.rkt @@ -0,0 +1,3 @@ +#lang racket/base +(require "charterm.rkt") +(provide (all-from-out "charterm.rkt")) diff --git a/archive/1.vm.arc/charterm/planet-docs/doc/index.html b/archive/1.vm.arc/charterm/planet-docs/doc/index.html new file mode 100644 index 00000000..79d311c9 --- /dev/null +++ b/archive/1.vm.arc/charterm/planet-docs/doc/index.html @@ -0,0 +1,117 @@ + +CharTerm: Character-cell Terminal Interface in Racket
1 Introduction
1.1 Demo
1.2 Simple Example
2 Terminal Diversity
2.1 Protocol
2.2 Key Encoding
2.2.1 Keylabel
2.2.2 Keycode
charterm-keycode?
2.2.3 Keyinfo
charterm-keyinfo?
charterm-keyinfo-keyset-id
charterm-keyinfo-bytelang
charterm-keyinfo-bytelist
charterm-keyinfo-keylabel
charterm-keyinfo-keycode
charterm-keyinfo-all-keycodes
2.2.4 Keyset
charterm-keyset?
charterm-keyset-id
charterm-ascii-keyset
charterm-dec-vt100-keyset
charterm-dec-vt220-keyset
charterm-screen-keyset
charterm-linux-keyset
charterm-xterm-x11r6-keyset
charterm-xterm-xfree86-keyset
charterm-xterm-new-keyset
charterm-rxvt-keyset
charterm-wyse-wy50-keyset
charterm-televideo-925-keyset
2.2.5 Keydec
charterm-keydec-id
charterm-vt100-keydec
charterm-vt220-keydec
charterm-screen-keydec
charterm-linux-keydec
charterm-xterm-new-keydec
charterm-xterm-keydec
charterm-rxvt-keydec
charterm-wy50-keydec
charterm-tvi925-keydec
charterm-ascii-keydec
charterm-ansi-keydec
charterm-insane-keydec
2.3 Termvar
3 charterm Object
charterm?
charterm-termvar
charterm-protocol
charterm-keydec
current-charterm
open-charterm
close-charterm
with-charterm
4 Terminal Information
charterm-screen-size
5 Display Control
5.1 Cursor
charterm-cursor
charterm-newline
5.2 Displaying
charterm-display
5.3 Video Attributes
charterm-normal
charterm-inverse
charterm-underline
charterm-blink
charterm-bold
5.4 Clearing
charterm-clear-screen
charterm-clear-line
charterm-clear-line-left
charterm-clear-line-right
5.5 Line Insert and Delete
charterm-insert-line
charterm-delete-line
5.5.1 Misc. Output
charterm-bell
6 Keyboard Input
charterm-byte-ready?
charterm-read-key
charterm-read-keyinfo
7 References
8 Known Issues
9 History
10 Legal
3:1

CharTerm: Character-cell Terminal Interface in Racket

Neil Van Dyke

 (require (planet neil/charterm:3:1))

1 Introduction

The CharTerm package provides a Racket interface for character-cell video +display terminals on Unix-like systems – such as for GNU Screen and tmux sessions on cloud servers, XTerm windows on a workstation desktop, and some older hardware +terminals (even the venerable DEC VT100). Currently, it implements a subset of features available on most +terminals.
This package could be used to implement a status/management console +for a Racket-based server process (perhaps run in GNU Screen or tmux on a server machine, to be detached and reattached from SSH +sessions), a lightweight user interface for a systems tool, a command-line +REPL, a text editor, creative retro uses of old equipment, and, perhaps most +importantly, a Rogue-like application.
The CharTerm package does not include any native code (such as from terminfo, termcap, curses, or ncurses) in the Racket process, +such as through the Racket FFI or C extensions, so there is less potential for +a problem involving native code to threaten the reliability or security of a +program. CharTerm is implemented in pure Racket code except for executing /bin/stty for some purposes. Specifically, /bin/stty at startup time and shutdown time, to set modes, and (for terminal +types that don’t seem to support a screen size report control sequence) when +getting screen size. Besides security and stability, lower dependence on +native code might also simplify porting to host platforms that don’t have those +native code facilities.

1.1 Demo

For a demonstration, the following command, run from a terminal, should install the CharTerm package (if not already installed), and run the demo:
  racket -pm neil/charterm/demo
This demo reports what keys you pressed, while letting you edit a +text field, and while displaying a clock. The clock is updated roughly once +per second, and is not updated during heavy keyboard input, such as when typing +fast. The demo responds to changing terminal sizes, such as when an XTerm is +window is resized. It also displays the determined terminal size, and some +small tests of the #:width argument to charterm-display. Exit the demo by pressing the Esc key.
Note: Although this demo includes an editable text field, as proof +of concept, the current version of CharTerm does not provide editable text fields as reusable functionality.

1.2 Simple Example

Here’s your first CharTerm program:
#lang racket/base
 
(require (planet neil/charterm))
 
(with-charterm
 (charterm-clear-screen)
 (charterm-cursor 10 5)
 (charterm-display "Hello, ")
 (charterm-bold)
 (charterm-display "you")
 (charterm-normal)
 (charterm-display ".")
 (charterm-cursor 1 1)
 (charterm-display "Press a key...")
 (let ((key (charterm-read-key)))
   (charterm-cursor 1 1)
   (charterm-clear-line)
   (printf "You pressed: ~S\r\n" key)))
Now you’re living the dream of the ’70s.

2 Terminal Diversity

Like people, few terminals are exactly the same.
Some key (ha) terms (ha) used by CharTerm are:
  • termvar a string value like from the Unix-like TERM environment variable, used to determine a default protocol and keydec.

  • protocol how to control the display, query for information, etc.

  • keydec how to decode key encodings of a particular +terminal. A keydec is constructed from one or more keysets, can produce keycodes or keyinfos.

  • keyset a specification of encoding some of the keys in a +particular terminal, including keylabels and keycodes.

  • keylabel a string for how a key is likely labeled on a +keyboard, such as the DEC VT100 PF1 key would have a keylabel "PF1" for a keycode 'f1.

  • keycode a value produced by a decoded key, +such as a character for normal printable keys, like #\a and #\space, a symbol for some recognized unprintable keys, like 'escape and 'f1, or possibly a number for unrecognized keys.

  • keyinfo an object that is used like a keycode, except +bundles together a keycode and a keylabel, as well as alternatate keycodes and +information about how the key was decoded (e.g., from which keyset).

These terms are discussed in the following subsections.
CharTerm is developed with help of original documentation such as that +curated by Paul Williams at vt100.net, various commentary found on the Web, observed behavior with +modern software terminals like XTerm, various emulators for hardware terminals, +and sometimes original hardware terminals. Thanks to Mark Pearrow for +contributing a TeleVideo 950, and Paul McCabe for a Wyse S50 WinTerm.
At time of this writing, the author is looking to acquire a DEC +VT525, circa 1994, for ongoing testing.
The author welcomes feedback on useful improvements to CharTerm’s support for terminal diversity (no pun). If you have a terminal +that is sending an escape sequence not recognized by the demo, you can run the +demo with the -n (aka --no-escape) argument to see the exact byte sequence:
  racket -pm- neil/charterm/demo -n
When -n is used, this will be indicated by the bottom-most scrolling line, +rather than saying “To quit, press Esc.” instead will say “There is no escape from this demo.” You will have to kill the process through some other means.

2.1 Protocol

The first concept CharTerm has for distinguishing how to communicate with a terminal is what +is what is called here protocol, which concerns everything except how keyboard keys are decoded. +The following protocols are currently implemented:
  • ansi protocol Terminals approximating [ANSI X3.64], which is most terminals in use today, including software ones +like XTerm. This protocol is the emphasis of this package; the other protocols +are for unusual situations.

  • wyse-wy50 protocol Terminals compatible with the Wyse WY-50. This support is +based on [WY-50-QRG], [WY-60-UG], [wy60], and [PowerTerm]. Note that video attributes are not supported, due to the WY-50’s +model of having video attribute changes occupy character cells; you may wish +to run the Wyse terminal in an ANSI or VT100 mode.

  • televideo-925 protocol Terminals compatible with the TeleVideo 925. This support is based on [TVI-925-IUG] and behavior of [PowerTerm]. Note that video attributes are not supported, due to the 925’s +model of having video attribute changes occupy character cells; you may wish to +run your TeleVideo terminal in ANSI or VT100 mode, if it has one.

  • ascii protocol Terminals that support ASCII but not much else that we know about.

2.2 Key Encoding

While most video display control, they seem to vary more by key +encoding.
The CharTerm author was motivated to increase the sophistication of its +keyboard handling after a series of revelations on the Sunday of the long +weekend in which CharTerm was initially written. The first was discovering that four of the +function keys that had been working fine in rxvt did not work in XTerm. Dave Gilbert somewhat demystified this by +pointing out that the original VT100 had only four function keys, which set +into motion an unfortunate series of bad decisions by various developers of +terminal software to be needlessly incompatible with each other. After +Googling, a horrifying 2005 Web post by Phil Gregory [Gregory], which showed that key encoding among XTerm variants was even +worse than one could ever fear. Even if one already knew how much subtleties +of old terminals varied (e.g., auto-newline behavior, whether an attribute +change consumed a space, etc.), this incompatibility in newer software was +surprising. Then, on a hunch, I tried the Linux Console on a Debian Squeeze +machine, which surely is ANSI, and found, however, that it generated yet different byte sequences, for the first five (not four) function keys. Then I compared all to the [ECMA-48] standard, which turns out to be nigh-inscrutable, so which might +help explain why everyone became so anti-social.
CharTerm now provides the abstractions of keysets and keydecs to deal with this diversity in a maintainable way.

2.2.1 Keylabel

A keylabel is a Racket string for how a key is likely labeled on a particular terminal’s keyboard. Different keyboards may have different keylabels for the same keycode. For example, a VT100 has a PF1 key (keylabel "PF1", keycode 'f1), while many other keyboards would label the key F1 (keylabel "F1", keycode 'f1). The keylabel currently is most useful for documenting and debugging, although it could later be used when giving instructions to the user, such as knowing whether to tell the user the Return key or the Enter key; the Backspace or the Rubout key; etc.

2.2.2 Keycode

A keycode is a value representing a key read from a terminal, which can be a Racket character, symbol, or number. Keys corresponding to printable characters have keycodes as Racket characters. Some keys corresponding to special non-printable characters can have keycodes of Racket symbols, such as 'return, 'f1, 'up, etc.

procedure

(charterm-keycode? x)  boolean?

  x : any/c
Predicate for whether or not x is a valid keycode.

2.2.3 Keyinfo

A keyinfo represents a keycode for a key, a keylabel, and how it is encoded as bytes. It is represented in Racket as +a charterm-keyinfo object.

procedure

(charterm-keyinfo? x)  boolean?

  x : any/c
Predicate for whether or not x is a charterm-keyinfo object.

procedure

(charterm-keyinfo-keyset-id ki)  symbol?

  ki : charterm-keyinfo?
(charterm-keyinfo-bytelang ki)  string?
  ki : charterm-keyinfo?
(charterm-keyinfo-bytelist ki)  (listof byte?)
  ki : charterm-keyinfo?
(charterm-keyinfo-keylabel ki)  string?
  ki : charterm-keyinfo?
(charterm-keyinfo-keycode ki)  charterm-keycode?
  ki : charterm-keyinfo?
(charterm-keyinfo-all-keycodes ki)  (listof charterm-keycode?)
  ki : charterm-keyinfo?
Get information from a charterm-keyinfo object.

2.2.4 Keyset

A keyset is a specification of keys on a particular keyboard, including their keylabel, encoding as bytes, and primary and alternate keycodes.
The means of constructing a keyset is currently internal to this package.

procedure

(charterm-keyset? x)  boolean?

  x : any/c
Predicate for whether or not x is a keyset.

procedure

(charterm-keyset-id ks)  symbol?

  ks : charterm-keyset?
Get a symbol identifying the keyset.

value

charterm-ascii-keyset : charterm-keyset?

From the old [ASCII] standard. When defining a keydec, this is good to have as a final keyset, after the others.

value

charterm-dec-vt100-keyset : charterm-keyset?

From the DEC VT100. This currently defines the four function +keys (labeled on the keyboard, PF1 through PF4) as 'f1 through 'f4, and the arrow keys. [VT100-UG] and [PowerTerm] were used as references.

value

charterm-dec-vt220-keyset : charterm-keyset?

From the DEC VT220. This currently defines function keys F1 through F20.

value

charterm-screen-keyset : charterm-keyset?

From the GNU Screen terminal multiplexer, according to [Gregory]. Also used by tmux.

value

charterm-linux-keyset : charterm-keyset?

From the Linux console. Currently defines function keys F1 through F5 only, since the rest will be inherited from other keysets.

value

charterm-xterm-x11r6-keyset : charterm-keyset?

From the XTerm in X11R6, according to [Gregory].

value

charterm-xterm-xfree86-keyset : charterm-keyset?

From the XFree86 XTerm, according to [Gregory].

value

charterm-xterm-new-keyset : charterm-keyset?

From the current xterm-new, often called simply xterm, as developed by Thomas E. Dickey, and documented in [XTerm-ctlseqs]. Several also came from decompiling a terminfo entry. Thanks to Dickey for his emailed help.

value

charterm-rxvt-keyset : charterm-keyset?

From the rxvt terminal emulator. These come from [Gregory], and +currently define function keys 'f1 through 'f44.

value

charterm-wyse-wy50-keyset : charterm-keyset?

From the Wyse WY-50, based on [WY-50-QRG] and looking at photos of WY-50 keyboard, and tested in [wy60] and [PowerTerm]. The shifted function keys are provided as both 'shift-f1 through 'shift-16, and 'f17 through 'f31.

value

charterm-televideo-925-keyset : charterm-keyset?

From the TeleVideo 925, based on [TVI-925-IUG], [PowerTerm], and from looking at a TeleVideo 950 keyboard.

2.2.5 Keydec

A keydec object is a key decoder for a specific variety of terminal, such +as for a specific termvar. A keydec is used to turn received key encodings from a terminal into keycode or keyinfo values. A keydec is constructed from a prioritized list of keyset objects, with earlier-listed keysets taking priority of +later-listed keysets when there is conflict between them as to how to decode a +particular byte sequence.

procedure

(charterm-keydec-id kd)  symbol?

  kd : charterm-keydec?
Gets the ID symbol of the keydec being used.
ANSI Keydecs

value

charterm-vt100-keydec : charterm-keydec?

Keydec for termvar "vt100".

value

charterm-vt220-keydec : charterm-keydec?

Keydec for termvar "vt220".

value

charterm-screen-keydec : charterm-keydec?

Keydec for termvar "screen".

value

charterm-linux-keydec : charterm-keydec?

Keydec for termvar "linux".

value

charterm-xterm-new-keydec : charterm-keydec?

Keydec for termvar "xterm-new".

value

charterm-xterm-keydec : charterm-keydec?

Keydec for termvar "xterm". Currently same as the keydec for xterm, except for a different ID.

value

charterm-rxvt-keydec : charterm-keydec?

Keydec for termvar "rxvt".
Wyse Keydecs

value

charterm-wy50-keydec : charterm-keydec?

Keydec for termvar "wy50".
TeleVideo Keydecs

value

charterm-tvi925-keydec : charterm-keydec?

Keydec for termvar "tvi925".
ASCII Keydecs

value

charterm-ascii-keydec : charterm-keydec?

Keydec for termvar "ascii".
Default Keydecs

value

charterm-ansi-keydec : charterm-keydec?

Keydec for any presumed ANSI-ish terminal, combining many ANSI-ish keysets.

value

charterm-insane-keydec : charterm-keydec?

Keydec for the uniquely desperate situation of wanting to possibly have +extensive key decoding for a terminal that might not even be ansi, but be +Wyse, TeleVideo, or some other ASCII.

2.3 Termvar

A termvar is what the charterm package calls the value of the Unix-like TERM environment variable. Each termvar has a default protocol and keydec. Note, however, that TERM is not always a precise indicator of the best protocol and keydec, +but by default we work with what we have.

3 charterm Object

The charterm object captures the state of a session with a particular terminal.
A charterm object is also a synchronizable event, so it can be used with +procedures such as sync. As an event, it becomes ready when there is at least one byte +available for reading from the terminal, and its synchronization result is +itself.

procedure

(charterm? x)  boolean?

  x : any/c
Predicate for whether or not x is a charterm.

procedure

(charterm-termvar ct)  (or/c #f string?)

  ct : charterm?
Gets the termvar.

procedure

(charterm-protocol ct)  symbol?

  ct : charterm?
Gets the protocol.

procedure

(charterm-keydec ct)  symbol?

  ct : charterm?
Gets the keydec.

parameter

(current-charterm)  (or/c #f charterm?)

(current-charterm ct)  void?
  ct : (or/c #f charterm?)
This parameter provides the default charterm for most of the other procedures. It is usually set automatically by call-with-charterm, with-charterm, open-charterm, and close-charterm.

procedure

(open-charterm [#:tty tty    
  #:current? current?])  charterm?
  tty : (or/c #f path-string?) = #f
  current? : boolean? = #t
Returns an open charterm object, by opening I/O ports on the terminal device at tty (or, if #f, file "/dev/tty"), and setting raw mode and disabling echo (via "/bin/stty"). If current? is true, the current-charterm parameter is also set to this object.

procedure

(close-charterm [#:charterm ct])  void?

  ct : charterm? = (current-charterm)
Closes ct by closing the I/O ports, and undoing open-charterm’s changes via "/bin/stty". If current-charterm is set to ct, then that parameter will be changed to #f for good measure. You might wish to use with-charterm instead of worrying about calling close-charterm directly.
Note: If you exit your Racket process without properly closing the charterm, your terminal may be left in a crazy state. You can fix it with +the command:
  stty sane

syntax

(with-charterm expr? ...)

Opens a charterm and evaluates the body expressions in sequence with current-charterm set appropriately. When control jumps out of the body, in a +manner of speaking, the charterm is closed.

4 Terminal Information

procedure

(charterm-screen-size [#:charterm ct])

  
(or/c #f exact-nonnegative-integer?)
(or/c #f exact-nonnegative-integer?)
  ct : charterm? = (current-charterm)
Attempts to get the screen size, in character columns and rows. +It may do this through a control sequence or through /bin/stty. If unable to get a value, then default of (80,24) is used.
The current behavior in this version of charterm is to adaptively try different methods of getting screen size, +and to remember what worked for the next time this procedure is called for ct. For terminals that are identified as screen by the TERM environment variable (e.g., terminal emulators like GNU Screen +and tmux), the current behavior is to not try the control sequence (which +causes a 1-second delay waiting for a terminal response that never arrives), +and to just use stty. For all other terminals, the control sequence is tried first, before trying stty. If neither the control sequence nor stty work, then neither method is tried again for ct, and instead the procedure always returns (#f, #f). This behavior very well might change in future versions of charterm, and the author welcomes feedback on which methods work with +which terminals.

5 Display Control

5.1 Cursor

procedure

(charterm-cursor x y [#:charterm ct])  void?

  x : exact-positive-integer?
  y : exact-positive-integer?
  ct : charterm? = (current-charterm)
Positions the cursor at column x, row y, with the upper-left character cell being (1, 1).

procedure

(charterm-newline [#:charterm ct])  void?

  ct : charterm? = (current-charterm)
Sends a newline to the terminal. This is typically a CR-LF +sequence.

5.2 Displaying

procedure

(charterm-display [#:charterm ct    
  #:width width    
  #:pad pad    
  #:truncate truncate]    
  arg ...)  void?
  ct : charterm? = (current-charterm)
  width : (or/c #f exact-positive-integer?) = #f
  pad : (or/c 'width boolean?) = 'width
  truncate : (or/c 'width boolean?) = 'width
  arg : any/c
Displays each arg on the terminal, as if formatted by display, with the exception that unprintable or non-ASCII characters +might not be displayed. (The exact behavior of what is permitted is expected +to change in a later version of CharTerm, so avoid trying to send your own control sequences or using +newlines, making assumptions about non-ASCII characters, etc.)
If width is a number, then pad and truncate specify whether or not to pad with spaces or truncate the output, respectively, to width characters. When pad or width is 'width, that is a convenience meaning “true if, and only if, width is not #f.”

5.3 Video Attributes

procedure

(charterm-normal [#:charterm ct])  void?

  ct : charterm? = (current-charterm)
(charterm-inverse [#:charterm ct])  void?
  ct : charterm? = (current-charterm)
(charterm-underline [#:charterm ct])  void?
  ct : charterm? = (current-charterm)
(charterm-blink [#:charterm ct])  void?
  ct : charterm? = (current-charterm)
(charterm-bold [#:charterm ct])  void?
  ct : charterm? = (current-charterm)
Sets the video attributes for subsequent writes to the terminal. In this version of charterm, each is mutually-exclusive, so, for example, setting bold clears inverse. Note that that video attributes are currently supported only for protocol 'ansi, due to limitations of the TeleVideo and Wyse models for +video attributes.

5.4 Clearing

procedure

(charterm-clear-screen [#:charterm ct])  void?

  ct : charterm? = (current-charterm)
Clears the screen, including first setting the video attributes to +normal, and positioning the cursor at (1, 1).

procedure

(charterm-clear-line [#:charterm ct])  void?

  ct : charterm? = (current-charterm)
(charterm-clear-line-left [#:charterm ct])  void?
  ct : charterm? = (current-charterm)
(charterm-clear-line-right [#:charterm ct])  void?
  ct : charterm? = (current-charterm)
Clears text from the line with the cursor, or part of the line with the cursor.

5.5 Line Insert and Delete

procedure

(charterm-insert-line [count #:charterm ct])  void?

  count : exact-positive-integer? = 1
  ct : charterm? = (current-charterm)
Inserts count blank lines at cursor. Note that not all terminals support +this.

procedure

(charterm-delete-line [count #:charterm ct])  void?

  count : exact-positive-integer? = 1
  ct : charterm? = (current-charterm)
Deletes count blank lines at cursor. Note that not all terminals support +this.

5.5.1 Misc. Output

procedure

(charterm-bell [#:charterm ct])  void?

  ct : charterm? = (current-charterm)
Rings the terminal bell. This bell ringing might manifest as a +beep, a flash of the screen, or nothing.

6 Keyboard Input

Normally you will get keyboard input using the charterm-read-key procedure.

procedure

(charterm-byte-ready? [#:charterm ct])  boolean?

  ct : charterm? = (current-charterm)
Returns true/false for whether at least one byte is ready for +reading (either in a buffer or on the port) from ct. Note that, since some keys are encoded as multiple bytes, just +because this procedure returns true doesn’t mean that charterm-read-key won’t block temporarily because it sees part of a potential +multiple-byte key encoding.

procedure

(charterm-read-key [#:charterm ct    
  #:timeout timeout])  (or #f char? symbol?)
  ct : charterm? = (current-charterm)
  timeout : (or/c #f positive?) = #f
Reads a key from ct, blocking indefinitely or until sometime after timeout seconds has been reached, if timeout is non-#f. If timeout is reached, #f is returned.
Many keys are returned as characters, especially ones that +correspond to printable characters. For example, the unshifted Q key is returned as character #\q. Some other keys are returned as symbols, such as 'return, 'escape, 'f1, 'shift-f12, 'right, and many others.
Since some keys are sent as ambiguous sequences, charterm-read-key employs separate timeouts internally, such as to disambuate +the Esc key (byte sequence 27) from what on some terminals would be +the F10 key (bytes sequence 27, 91, 50, 49, 126).

procedure

(charterm-read-keyinfo [#:charterm ct    
  #:timeout timeout])  charterm-keyinfo?
  ct : charterm? = (current-charterm)
  timeout : (or/c #f positive?) = #f
Like charterm-read-keyinfo except instead of returning a keycode, it returns a keyinfo.

7 References

[Gregory] Phil Gregory, “Terminal Function Key Escape Codes,” 2005-12-13 Web post, as viewed on 2012-06
[PowerTerm] Ericom PowerTerm InterConnect 8.2.0.1000 terminal emulator, as run on Wyse S50 WinTerm
[VT100-TM] Digital Equipment Corp., VT100 Series Technical Manual, 2nd Ed., 1980-09
[VT100-UG] Digital Equipment Corp., VT100 User Guide, 3rd Ed., 1981-06
[VT100-WP] Wikipedia, VT100
[XTerm-ctlseqs] Edward Moy, Stephen Gildea, Thomas Dickey, “Xterm Control Sequences,” 2012
[XTerm-FAQ] Thomas E. Dickey, “XTerm FAQ,” dated 2012
[XTerm-WP] Wikipedia, xterm

8 Known Issues

  • Need to support ANSI alternate CSI for 8-bit terminals, even +before supporting 8-bit characters and multibyte.

  • Only supports ASCII characters. Adding UTF-8 support, for terminal emulators +that support it, would be nice.

  • Expose the character-decoding mini-language as a configurable +option. Perhaps wait until we implement timeout-based disambiguation at +arbitrary points in the the DFA rather than just at the top. Also, might be +better to resolve multi-byte characters first, in case that affects the +mini-language.

  • More controls for terminal features can be added.

  • Currently only implemented to work on Unix-like systems like +GNU/Linux.

  • Implement text input controls, either as part of this library or +another, using charterm-demo as a starting point.

9 History

  • PLaneT 3:1 — 2013-05-13
    • Now uses lowercase -f argument on MacOS X. (Thanks to Jens Axel Søgaard for reporting.)

    • Documentation tweaks.

  • PLaneT 3:0 — 2012-07-13
    • Changed “ansi-ish” in identifiers to “ansi”, hence the PLaneT major version number change.

    • Documentation tweaks.

    • Renamed package from “charterm” to “CharTerm”.

  • PLaneT 2:5 — 2012-06-28
    • A charterm object is now a synchronizable event.

    • Documentation tweaks.

  • PLaneT 2:4 — 2012-06-25
    • Documentation fix for return type of charterm-read-keyinfo.

  • PLaneT 2:3 — 2012-06-25
    • Fixed problem determining screen size on some +XTerms. (Thanks to Eli Barzilay for reporting.)

  • PLaneT 2:2 — 2012-06-25
    • Added another variation of encoding for XTerm arrow, +Home, and End keys. (Thanks to Eli Barzilay.)

  • PLaneT 2:1 — 2012-06-24
    • Corrected PLaneT version number in require in an example.

  • PLaneT 2:0 — 2012-06-24
    • Greatly increased the sophistication of handling of terminal diversity.

    • Added the wyse-wy50 and televideo-950 [Correction: televideo-925] protocols, for supporting the native modes of Wyse and +TeleVideo terminals, respectively, and compatibles.

    • More support for different key encodings and termvars.

    • Demo is now in a separate file, mainly for convenience +in giving command lines that run it. This breaks a command line example +previously documented, so changed PLaneT major version, although the +previously-published example will need to have :1 added to it anyway.

    • charterm-screen-size now defaults to (80,24) when all else fails.

    • Documentation changes.

  • PLaneT 1:1 — 2012-06-17
    • For screen and tmux, now gets screen size via stty. This resolves the sluggishness reported with screen. [Correction: In version 1:1, this behavior is +adaptive for all terminals, with the shortcut for termvar screen that it doesn’t bother trying the control sequence.]

    • Documentation tweaks.

  • PLaneT 1:0 — 2012-06-16
    • Initial version.

10 Legal

Copyright 2012 – 2013 Neil Van Dyke. This program is Free Software; you +can redistribute it and/or modify it under the terms of the GNU Lesser General +Public License as published by the Free Software Foundation; either version 3 +of the License, or (at your option) any later version. This program is +distributed in the hope that it will be useful, but without any warranty; +without even the implied warranty of merchantability or fitness for a +particular purpose. See http://www.gnu.org/licenses/ for details. For other +licenses and consulting, please contact the author.

 
\ No newline at end of file diff --git a/archive/1.vm.arc/charterm/planet-docs/doc/racket.css b/archive/1.vm.arc/charterm/planet-docs/doc/racket.css new file mode 100644 index 00000000..021e4da5 --- /dev/null +++ b/archive/1.vm.arc/charterm/planet-docs/doc/racket.css @@ -0,0 +1,234 @@ + +/* See the beginning of "scribble.css". */ + +/* Monospace: */ +.RktIn, .RktRdr, .RktPn, .RktMeta, +.RktMod, .RktKw, .RktVar, .RktSym, +.RktRes, .RktOut, .RktCmt, .RktVal, +.RktBlk { + font-family: monospace; + white-space: inherit; +} + +/* Serif: */ +.inheritedlbl { + font-family: serif; +} + +/* Sans-serif: */ +.RBackgroundLabelInner { + font-family: sans-serif; +} + +/* ---------------------------------------- */ +/* Inherited methods, left margin */ + +.inherited { + width: 100%; + margin-top: 0.5em; + text-align: left; + background-color: #ECF5F5; +} + +.inherited td { + font-size: 82%; + padding-left: 1em; + text-indent: -0.8em; + padding-right: 0.2em; +} + +.inheritedlbl { + font-style: italic; +} + +/* ---------------------------------------- */ +/* Racket text styles */ + +.RktIn { + color: #cc6633; + background-color: #eeeeee; +} + +.RktInBG { + background-color: #eeeeee; +} + +.RktRdr { +} + +.RktPn { + color: #843c24; +} + +.RktMeta { + color: black; +} + +.RktMod { + color: black; +} + +.RktOpt { + color: black; +} + +.RktKw { + color: black; + /* font-weight: bold; */ +} + +.RktErr { + color: red; + font-style: italic; +} + +.RktVar { + color: #262680; + font-style: italic; +} + +.RktSym { + color: #262680; +} + +.RktValLink { + text-decoration: none; + color: blue; +} + +.RktModLink { + text-decoration: none; + color: blue; +} + +.RktStxLink { + text-decoration: none; + color: black; + /* font-weight: bold; */ +} + +.RktRes { + color: #0000af; +} + +.RktOut { + color: #960096; +} + +.RktCmt { + color: #c2741f; +} + +.RktVal { + color: #228b22; +} + +/* ---------------------------------------- */ +/* Some inline styles */ + +.together { + width: 100%; +} + +.prototype, .argcontract, .RBoxed { + white-space: nowrap; +} + +.prototype td { + vertical-align: text-top; +} +.longprototype td { + vertical-align: bottom; +} + +.RktBlk { + white-space: inherit; + text-align: left; +} + +.RktBlk tr { + white-space: inherit; +} + +.RktBlk td { + vertical-align: baseline; + white-space: inherit; +} + +.argcontract td { + vertical-align: text-top; +} + +.highlighted { + background-color: #ddddff; +} + +.defmodule { + width: 100%; + background-color: #F5F5DC; +} + +.specgrammar { + float: right; +} + +.RBibliography td { + vertical-align: text-top; +} + +.leftindent { + margin-left: 1em; + margin-right: 0em; +} + +.insetpara { + margin-left: 1em; + margin-right: 1em; +} + +.Rfilebox { +} + +.Rfiletitle { + text-align: right; + margin: 0em 0em 0em 0em; +} + +.Rfilename { + border-top: 1px solid #6C8585; + border-right: 1px solid #6C8585; + padding-left: 0.5em; + padding-right: 0.5em; + background-color: #ECF5F5; +} + +.Rfilecontent { + margin: 0em 0em 0em 0em; +} + +/* ---------------------------------------- */ +/* For background labels */ + +.RBackgroundLabel { + float: right; + width: 0px; + height: 0px; +} + +.RBackgroundLabelInner { + position: relative; + width: 25em; + left: -25.5em; + top: 0px; + text-align: right; + color: white; + z-index: 0; + font-weight: bold; +} + +.RForeground { + position: relative; + left: 0px; + top: 0px; + z-index: 1; +} diff --git a/archive/1.vm.arc/charterm/planet-docs/doc/scribble-common.js b/archive/1.vm.arc/charterm/planet-docs/doc/scribble-common.js new file mode 100644 index 00000000..00eec767 --- /dev/null +++ b/archive/1.vm.arc/charterm/planet-docs/doc/scribble-common.js @@ -0,0 +1,153 @@ +// Common functionality for PLT documentation pages + +// Page Parameters ------------------------------------------------------------ + +var page_query_string = + (location.href.search(/\?([^#]+)(?:#|$)/) >= 0) && RegExp.$1; + +var page_args = + ((function(){ + if (!page_query_string) return []; + var args = page_query_string.split(/[&;]/); + for (var i=0; i= 0) args[i] = [a.substring(0,p), a.substring(p+1)]; + else args[i] = [a, false]; + } + return args; + })()); + +function GetPageArg(key, def) { + for (var i=0; i= 0 && cur.substring(0,eql) == key) + return unescape(cur.substring(eql+1)); + } + return def; +} + +function SetCookie(key, val) { + var d = new Date(); + d.setTime(d.getTime()+(365*24*60*60*1000)); + try { + document.cookie = + key + "=" + escape(val) + "; expires="+ d.toGMTString() + "; path=/"; + } catch (e) {} +} + +// note that this always stores a directory name, ending with a "/" +function SetPLTRoot(ver, relative) { + var root = location.protocol + "//" + location.host + + NormalizePath(location.pathname.replace(/[^\/]*$/, relative)); + SetCookie("PLT_Root."+ver, root); +} + +// adding index.html works because of the above +function GotoPLTRoot(ver, relative) { + var u = GetCookie("PLT_Root."+ver, null); + if (u == null) return true; // no cookie: use plain up link + // the relative path is optional, default goes to the toplevel start page + if (!relative) relative = "index.html"; + location = u + relative; + return false; +} + +// Utilities ------------------------------------------------------------------ + +var normalize_rxs = [/\/\/+/g, /\/\.(\/|$)/, /\/[^\/]*\/\.\.(\/|$)/]; +function NormalizePath(path) { + var tmp, i; + for (i = 0; i < normalize_rxs.length; i++) + while ((tmp = path.replace(normalize_rxs[i], "/")) != path) path = tmp; + return path; +} + +// `noscript' is problematic in some browsers (always renders as a +// block), use this hack instead (does not always work!) +// document.write(""); + +// Interactions --------------------------------------------------------------- + +function DoSearchKey(event, field, ver, top_path) { + var val = field.value; + if (event && event.keyCode == 13) { + var u = GetCookie("PLT_Root."+ver, null); + if (u == null) u = top_path; // default: go to the top path + u += "search/index.html?q=" + escape(val); + if (page_query_string) u += "&" + page_query_string; + location = u; + return false; + } + return true; +} + +function TocviewToggle(glyph, id) { + var s = document.getElementById(id).style; + var expand = s.display == "none"; + s.display = expand ? "block" : "none"; + glyph.innerHTML = expand ? "▼" : "►"; +} + +// Page Init ------------------------------------------------------------------ + +// Note: could make a function that inspects and uses window.onload to chain to +// a previous one, but this file needs to be required first anyway, since it +// contains utilities for all other files. +var on_load_funcs = []; +function AddOnLoad(fun) { on_load_funcs.push(fun); } +window.onload = function() { + for (var i=0; i + .techinside doesn't work with IE, so use both (and IE doesn't + work with inherit in the second one, so use blue directly) */ +.techinside { color: black; } +.techinside:hover { color: blue; } +.techoutside:hover>.techinside { color: inherit; } + +.SCentered { + text-align: center; +} + +.imageleft { + float: left; + margin-right: 0.3em; +} + +.Smaller{ + font-size: 82%; +} + +.Larger{ + font-size: 122%; +} + +/* A hack, inserted to break some Scheme ids: */ +.mywbr { + width: 0; + font-size: 1px; +} + +.compact li p { + margin: 0em; + padding: 0em; +} + +.noborder img { + border: 0; +} + +.SAuthorListBox { + position: relative; + float: right; + left: 2em; + top: -2.5em; + height: 0em; + width: 13em; + margin: 0em -13em 0em 0em; +} +.SAuthorList { + font-size: 82%; +} +.SAuthorList:before { + content: "by "; +} +.author { + display: inline; + white-space: nowrap; +} + +/* print styles : hide the navigation elements */ +@media print { + .tocset, + .navsettop, + .navsetbottom { display: none; } + .maincolumn { + width: auto; + margin-right: 13em; + margin-left: 0; + } +} diff --git a/archive/1.vm.arc/charterm/test-charterm.rkt b/archive/1.vm.arc/charterm/test-charterm.rkt new file mode 100644 index 00000000..04eb376f --- /dev/null +++ b/archive/1.vm.arc/charterm/test-charterm.rkt @@ -0,0 +1,20 @@ +#lang racket/base +;; For legal info, see file "charterm.rkt". + +;; (require (planet neil/charterm:1)) +(require "charterm.rkt") + +(with-charterm + (charterm-clear-screen) + (charterm-cursor 10 5) + (charterm-display "Hello, ") + (charterm-bold) + (charterm-display "you") + (charterm-normal) + (charterm-display ".") + (charterm-cursor 1 1) + (charterm-display "Press a key...") + (let ((key (charterm-read-key))) + (charterm-cursor 1 1) + (charterm-clear-line) + (printf "You pressed: ~S\r\n" key))) diff --git a/archive/1.vm.arc/chessboard.arc.t b/archive/1.vm.arc/chessboard.arc.t new file mode 100644 index 00000000..eb365b69 --- /dev/null +++ b/archive/1.vm.arc/chessboard.arc.t @@ -0,0 +1,239 @@ +(selective-load "mu.arc" section-level) +(set allow-raw-addresses*) +(add-code:readfile "chessboard.mu") +(freeze function*) +(load-system-functions) + +(reset2) +(new-trace "read-move-legal") +(run-code main + (default-space:space-address <- new space:literal 30:literal/capacity) + (stdin:channel-address <- init-channel 1:literal) + (r:integer/routine <- fork read-move:fn nil:literal/globals 2000:literal/limit stdin:channel-address) + (c:character <- copy ((#\a literal))) + (x:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address x:tagged-value) + (c:character <- copy ((#\2 literal))) + (x:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address x:tagged-value) + (c:character <- copy ((#\- literal))) + (x:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address x:tagged-value) + (c:character <- copy ((#\a literal))) + (x:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address x:tagged-value) + (c:character <- copy ((#\4 literal))) + (x:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address x:tagged-value) + (c:character <- copy ((#\newline literal))) + (x:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address x:tagged-value) + (sleep until-routine-done:literal r:integer/routine) +) +(each routine completed-routines* +;? (prn " " routine) + (awhen rep.routine!error + (prn "error - " it))) +(when (~ran-to-completion 'read-move) + (prn "F - chessboard accepts legal moves (-)")) +; todo: we can't test that keys pressed are printed to screen +; but that's at a lower level +;? (quit) + +(reset2) +(new-trace "read-move-incomplete") +; initialize some variables at specific raw locations +;? (prn "== init") +(run-code test-init + (1:channel-address/raw <- init-channel 1:literal) + (2:terminal-address/raw <- init-fake-terminal 20:literal 10:literal) + (3:string-address/raw <- get 2:terminal-address/raw/deref data:offset)) +(wipe completed-routines*) +; the component under test; we'll be running this repeatedly +(let read-move-routine (make-routine 'read-move memory*.1 memory*.2) +;? (prn "== first key") + (run-code send-first-key + (default-space:space-address <- new space:literal 30:literal/capacity) + (c:character <- copy ((#\a literal))) + (x:tagged-value <- save-type c:character) + (1:channel-address/raw/deref <- write 1:channel-address/raw x:tagged-value)) + (wipe completed-routines*) + ; check that read-move consumes it and then goes to sleep + (enq read-move-routine running-routines*) + (run-more) + (when (ran-to-completion 'read-move) + (prn "F - chessboard waits after first letter of move")) + (wipe completed-routines*) + ; send in a few more letters +;? (prn "== more keys") + (restart read-move-routine) + (run-code send-more-keys + (default-space:space-address <- new space:literal 30:literal/capacity) + (c:character <- copy ((#\2 literal))) + (x:tagged-value <- save-type c:character) + (1:channel-address/raw/deref <- write 1:channel-address/raw x:tagged-value) + (c:character <- copy ((#\- literal))) + (x:tagged-value <- save-type c:character) + (1:channel-address/raw/deref <- write 1:channel-address/raw x:tagged-value) + (c:character <- copy ((#\a literal))) + (x:tagged-value <- save-type c:character) + (1:channel-address/raw/deref <- write 1:channel-address/raw x:tagged-value) + (c:character <- copy ((#\4 literal))) + (x:tagged-value <- save-type c:character) + (1:channel-address/raw/deref <- write 1:channel-address/raw x:tagged-value)) + ; check that read-move consumes them and then goes to sleep + (when (ran-to-completion 'read-move) + (prn "F - chessboard waits after each subsequent letter of move until the last")) + (wipe completed-routines*) + ; send final key +;? (prn "== final key") + (restart read-move-routine) +;? (set dump-trace*) + (run-code send-final-key + (default-space:space-address <- new space:literal 30:literal/capacity) + (c:character <- copy ((#\newline literal))) + (x:tagged-value <- save-type c:character) + (1:channel-address/raw/deref <- write 1:channel-address/raw x:tagged-value)) + ; check that read-move consumes it and -- this time -- returns + (when (~ran-to-completion 'read-move) + (prn "F - 'read-move' completes after final letter of move")) +) + +(reset2) +(new-trace "read-move-quit") +(run-code main + (default-space:space-address <- new space:literal 30:literal/capacity) + (stdin:channel-address <- init-channel 1:literal) + (dummy:terminal-address <- init-fake-terminal 20:literal 10:literal) + (r:integer/routine <- fork-helper read-move:fn nil:literal/globals 2000:literal/limit stdin:channel-address dummy:terminal-address) + (c:character <- copy ((#\q literal))) + (x:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address x:tagged-value) + (sleep until-routine-done:literal r:integer/routine) +) +(when (~ran-to-completion 'read-move) + (prn "F - chessboard quits on move starting with 'q'")) + +(reset2) +(new-trace "read-illegal-file") +(run-code main + (default-space:space-address <- new space:literal 30:literal/capacity) + (stdin:channel-address <- init-channel 1:literal) + (dummy:terminal-address <- init-fake-terminal 20:literal 10:literal) + (r:integer/routine <- fork-helper read-file:fn nil:literal/globals 2000:literal/limit stdin:channel-address dummy:terminal-address) + (c:character <- copy ((#\i literal))) + (x:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address x:tagged-value) + (sleep until-routine-done:literal r:integer/routine) +) +;? (each routine completed-routines* +;? (prn " " routine)) +(when (or (ran-to-completion 'read-file) + (let routine routine-running!read-file + (~posmatch "file too high" rep.routine!error))) + (prn "F - 'read-file' checks that file lies between 'a' and 'h'")) + +(reset2) +(new-trace "read-illegal-rank") +(run-code main + (default-space:space-address <- new space:literal 30:literal/capacity) + (stdin:channel-address <- init-channel 1:literal) + (dummy:terminal-address <- init-fake-terminal 20:literal 10:literal) + (r:integer/routine <- fork-helper read-rank:fn nil:literal/globals 2000:literal/limit stdin:channel-address dummy:terminal-address) + (c:character <- copy ((#\9 literal))) + (x:tagged-value <- save-type c:character) + (stdin:channel-address/deref <- write stdin:channel-address x:tagged-value) + (sleep until-routine-done:literal r:integer/routine) +) +(when (or (ran-to-completion 'read-rank) + (let routine routine-running!read-rank + (~posmatch "rank too high" rep.routine!error))) + (prn "F - 'read-rank' checks that rank lies between '1' and '8'")) + +(reset2) +(new-trace "print-board") +(run-code main + (default-space:space-address <- new space:literal 30:literal/capacity) +;? ($print (("init-array\n" literal))) ;? 1 + (initial-position:integer-array-address <- init-array ((#\R literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\r literal)) + ((#\N literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\n literal)) + ((#\B literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\b literal)) + ((#\Q literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\q literal)) + ((#\K literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\k literal)) + ((#\B literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\b literal)) + ((#\N literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\n literal)) + ((#\R literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\r literal))) +;? ($print (("init-board\n" literal))) ;? 1 + (b:board-address <- init-board initial-position:integer-array-address) + (screen:terminal-address <- init-fake-terminal 20:literal 10:literal) + (print-board screen:terminal-address b:board-address) + (1:string-address/raw <- get screen:terminal-address/deref data:offset) +) +(each routine completed-routines* + (awhen rep.routine!error + (prn "error - " it))) +;? (prn memory*.1) +(when (~screen-contains memory*.1 20 + (+ "8 | r n b q k b n r " + "7 | p p p p p p p p " + "6 | _ _ _ _ _ _ _ _ " + "5 | _ _ _ _ _ _ _ _ " + "4 | _ _ _ _ _ _ _ _ " + "3 | _ _ _ _ _ _ _ _ " + "2 | P P P P P P P P " + "1 | R N B Q K B N R " + " +---------------- " + " a b c d e f g h ")) + (prn "F - print-board works; chessboard begins at @memory*.1")) + +; todo: how to fold this more elegantly with the previous test? +(reset2) +(new-trace "make-move") +(run-code main + (default-space:space-address <- new space:literal 30:literal/capacity) + ; fake screen + (screen:terminal-address <- init-fake-terminal 20:literal 10:literal) + ; initial position + (initial-position:integer-array-address <- init-array ((#\R literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\r literal)) + ((#\N literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\n literal)) + ((#\B literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\b literal)) + ((#\Q literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\q literal)) + ((#\K literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\k literal)) + ((#\B literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\b literal)) + ((#\N literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\n literal)) + ((#\R literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\r literal))) + (b:board-address <- init-board initial-position:integer-array-address) + ; move: a2-a4 + (m:move-address <- new move:literal) + (f:integer-integer-pair-address <- get-address m:move-address/deref from:offset) + (dest:integer-address <- get-address f:integer-integer-pair-address/deref 0:offset) + (dest:integer-address/deref <- copy 0:literal) ; from-file: a + (dest:integer-address <- get-address f:integer-integer-pair-address/deref 1:offset) + (dest:integer-address/deref <- copy 1:literal) ; from-rank: 2 + (t0:integer-integer-pair-address <- get-address m:move-address/deref to:offset) + (dest:integer-address <- get-address t0:integer-integer-pair-address/deref 0:offset) + (dest:integer-address/deref <- copy 0:literal) ; to-file: a + (dest:integer-address <- get-address t0:integer-integer-pair-address/deref 1:offset) + (dest:integer-address/deref <- copy 3:literal) ; to-rank: 4 + (b:board-address <- make-move b:board-address m:move-address) + (print-board screen:terminal-address b:board-address) + (1:string-address/raw <- get screen:terminal-address/deref data:offset) +) +(each routine completed-routines* + (awhen rep.routine!error + (prn "error - " it))) +;? (prn memory*.1) +(when (~screen-contains memory*.1 20 + (+ "8 | r n b q k b n r " + "7 | p p p p p p p p " + "6 | _ _ _ _ _ _ _ _ " + "5 | _ _ _ _ _ _ _ _ " + "4 | P _ _ _ _ _ _ _ " + "3 | _ _ _ _ _ _ _ _ " + "2 | _ P P P P P P P " + "1 | R N B Q K B N R " + " +---------------- " + " a b c d e f g h ")) + (prn "F - make-move works; chessboard begins at @memory*.1")) + +(reset2) diff --git a/archive/1.vm.arc/chessboard.mu b/archive/1.vm.arc/chessboard.mu new file mode 100644 index 00000000..45fc12da --- /dev/null +++ b/archive/1.vm.arc/chessboard.mu @@ -0,0 +1,259 @@ +;; data structure: board +(primitive square) +(address square-address (square)) ; pointer. verbose but sadly necessary for now +(array file (square)) ; ranks and files are arrays of squares +(address file-address (file)) +(address file-address-address (file-address)) ; pointer to a pointer +(array board (file-address)) +(address board-address (board)) + +(function init-board [ + (default-space:space-address <- new space:literal 30:literal) + (initial-position:integer-array-address <- next-input) + ; assert(length(initial-position) == 64) +;? ($print initial-position:integer-array-address/deref) ;? 1 + (len:integer <- length initial-position:integer-array-address/deref) +;? ($print len:integer) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (correct-length?:boolean <- equal len:integer 64:literal) + (assert correct-length?:boolean (("chessboard had incorrect size" literal))) + (b:board-address <- new board:literal 8:literal) + (col:integer <- copy 0:literal) + { begin + (done?:boolean <- equal col:integer 8:literal) + (break-if done?:boolean) + (file:file-address-address <- index-address b:board-address/deref col:integer) + (file:file-address-address/deref <- init-file initial-position:integer-array-address col:integer) + (col:integer <- add col:integer 1:literal) + (loop) + } + (reply b:board-address) +]) + +(function init-file [ + (default-space:space-address <- new space:literal 30:literal) + (position:integer-array-address <- next-input) + (index:integer <- next-input) + (index:integer <- multiply index:integer 8:literal) + (result:file-address <- new file:literal 8:literal) + (row:integer <- copy 0:literal) + { begin + (done?:boolean <- equal row:integer 8:literal) + (break-if done?:boolean) + (dest:square-address <- index-address result:file-address/deref row:integer) + (dest:square-address/deref <- index position:integer-array-address/deref index:integer) + (row:integer <- add row:integer 1:literal) + (index:integer <- add index:integer 1:literal) + (loop) + } + (reply result:file-address) +]) + +(function print-board [ + (default-space:space-address <- new space:literal 30:literal) + (screen:terminal-address <- next-input) + (b:board-address <- next-input) + (row:integer <- copy 7:literal) + ; print each row + { begin + (done?:boolean <- less-than row:integer 0:literal) + (break-if done?:boolean) + ; print rank number as a legend + (rank:integer <- add row:integer 1:literal) + (print-integer screen:terminal-address rank:integer) + (s:string-address <- new " | ") + (print-string screen:terminal-address s:string-address) + ; print each square in the row + (col:integer <- copy 0:literal) + { begin + (done?:boolean <- equal col:integer 8:literal) + (break-if done?:boolean) + (f:file-address <- index b:board-address/deref col:integer) + (s:square <- index f:file-address/deref row:integer) + (print-character screen:terminal-address s:square) + (print-character screen:terminal-address ((#\space literal))) + (col:integer <- add col:integer 1:literal) + (loop) + } + (row:integer <- subtract row:integer 1:literal) + (cursor-to-next-line screen:terminal-address) + (loop) + } + ; print file letters as legend + (s:string-address <- new " +----------------") + (print-string screen:terminal-address s:string-address) + (cursor-to-next-line screen:terminal-address) + (s:string-address <- new " a b c d e f g h") + (print-string screen:terminal-address s:string-address) + (cursor-to-next-line screen:terminal-address) +]) + +;; data structure: move +(and-record move [ + from:integer-integer-pair + to:integer-integer-pair +]) + +(address move-address (move)) + +(function read-move [ + (default-space:space-address <- new space:literal 30:literal) + (stdin:channel-address <- next-input) + (from-file:integer <- read-file stdin:channel-address) + { begin + (break-if from-file:integer) + (reply nil:literal) + } + (from-rank:integer <- read-rank stdin:channel-address) + (expect-stdin stdin:channel-address ((#\- literal))) + (to-file:integer <- read-file stdin:channel-address) + (to-rank:integer <- read-rank stdin:channel-address) + (expect-stdin stdin:channel-address ((#\newline literal))) + ; construct the move object + (result:move-address <- new move:literal) + (f:integer-integer-pair-address <- get-address result:move-address/deref from:offset) + (dest:integer-address <- get-address f:integer-integer-pair-address/deref 0:offset) + (dest:integer-address/deref <- copy from-file:integer) + (dest:integer-address <- get-address f:integer-integer-pair-address/deref 1:offset) + (dest:integer-address/deref <- copy from-rank:integer) + (t0:integer-integer-pair-address <- get-address result:move-address/deref to:offset) + (dest:integer-address <- get-address t0:integer-integer-pair-address/deref 0:offset) + (dest:integer-address/deref <- copy to-file:integer) + (dest:integer-address <- get-address t0:integer-integer-pair-address/deref 1:offset) + (dest:integer-address/deref <- copy to-rank:integer) + (reply result:move-address) +]) + +; todo: assumes stdin is always at raw address 1 +(function read-file [ + (default-space:space-address <- new space:literal 30:literal) + (stdin:channel-address <- next-input) + (x:tagged-value stdin:channel-address/deref <- read stdin:channel-address) +;? ($print x:tagged-value) ;? 1 +;? ($print (("\n" literal))) ;? 1 + (a:character <- copy ((#\a literal))) + (file-base:integer <- character-to-integer a:character) + (c:character <- maybe-coerce x:tagged-value character:literal) +;? ($print (("AAA " literal))) ;? 1 +;? ($print c:character) ;? 1 +;? ($print (("\n" literal))) ;? 1 + { begin + (quit:boolean <- equal c:character ((#\q literal))) + (break-unless quit:boolean) + (reply nil:literal) + } + (file:integer <- character-to-integer c:character) + (file:integer <- subtract file:integer file-base:integer) + ; assert('a' <= from-file <= 'h') + (above-min:boolean <- greater-or-equal file:integer 0:literal) + (assert above-min:boolean (("file too low" literal))) + (below-max:boolean <- lesser-or-equal file:integer 7:literal) + (assert below-max:boolean (("file too high" literal))) + (reply file:integer) +]) + +(function read-rank [ + (default-space:space-address <- new space:literal 30:literal) + (stdin:channel-address <- next-input) + (x:tagged-value stdin:channel-address/deref <- read stdin:channel-address) + (c:character <- maybe-coerce x:tagged-value character:literal) +;? ($print (("BBB " literal))) ;? 1 +;? ($print c:character) ;? 1 +;? ($print (("\n" literal))) ;? 1 + { begin + (quit:boolean <- equal c:character ((#\q literal))) + (break-unless quit:boolean) + (reply nil:literal) + } + (rank:integer <- character-to-integer c:character) + (one:character <- copy ((#\1 literal))) + (rank-base:integer <- character-to-integer one:character) + (rank:integer <- subtract rank:integer rank-base:integer) + ; assert('1' <= rank <= '8') + (above-min:boolean <- greater-or-equal rank:integer 0:literal) + (assert above-min:boolean (("rank too low" literal))) + (below-max:boolean <- lesser-or-equal rank:integer 7:literal) + (assert below-max:boolean (("rank too high" literal))) + (reply rank:integer) +]) + +; slurp a character and check that it matches +(function expect-stdin [ + (default-space:space-address <- new space:literal 30:literal) + (stdin:channel-address <- next-input) + (x:tagged-value stdin:channel-address/deref <- read stdin:channel-address) + (c:character <- maybe-coerce x:tagged-value character:literal) + (expected:character <- next-input) + (match?:boolean <- equal c:character expected:character) + (assert match?:boolean (("expected character not found" literal))) +]) + +(function make-move [ + (default-space:space-address <- new space:literal 30:literal) + (b:board-address <- next-input) + (m:move-address <- next-input) + (x:integer-integer-pair <- get m:move-address/deref from:offset) + (from-file:integer <- get x:integer-integer-pair 0:offset) + (from-rank:integer <- get x:integer-integer-pair 1:offset) + (f:file-address <- index b:board-address/deref from-file:integer) + (src:square-address <- index-address f:file-address/deref from-rank:integer) + (x:integer-integer-pair <- get m:move-address/deref to:offset) + (to-file:integer <- get x:integer-integer-pair 0:offset) + (to-rank:integer <- get x:integer-integer-pair 1:offset) + (f:file-address <- index b:board-address/deref to-file:integer) + (dest:square-address <- index-address f:file-address/deref to-rank:integer) + (dest:square-address/deref <- copy src:square-address/deref) + (src:square-address/deref <- copy ((#\_ literal))) + (reply b:board-address) +]) + +(function chessboard [ + (default-space:space-address <- new space:literal 30:literal) + (initial-position:integer-array-address <- init-array ((#\R literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\r literal)) + ((#\N literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\n literal)) + ((#\B literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\b literal)) + ((#\Q literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\q literal)) + ((#\K literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\k literal)) + ((#\B literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\b literal)) + ((#\N literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\n literal)) + ((#\R literal)) ((#\P literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\_ literal)) ((#\p literal)) ((#\r literal))) + (b:board-address <- init-board initial-position:integer-array-address) + (cursor-mode) + ; hook up stdin + (stdin:channel-address <- init-channel 1:literal) + (fork-helper send-keys-to-stdin:fn nil:literal/globals nil:literal/limit nil:literal/keyboard stdin:channel-address) + ; buffer stdin + (buffered-stdin:channel-address <- init-channel 1:literal) + (fork-helper buffer-lines:fn nil:literal/globals nil:literal/limit stdin:channel-address buffered-stdin:channel-address) + ($print (("Stupid text-mode chessboard. White pieces in uppercase; black pieces in lowercase. No checking for legal moves." literal))) + (cursor-to-next-line nil:literal/terminal) + { begin + (cursor-to-next-line nil:literal/terminal) + (print-board nil:literal/terminal b:board-address) + (cursor-to-next-line nil:literal/terminal) + ($print (("Type in your move as -. For example: 'a2-a4'. Then press ." literal))) + (cursor-to-next-line nil:literal/terminal) + ($print (("Hit 'q' to exit." literal))) + (cursor-to-next-line nil:literal/terminal) + ($print (("move: " literal))) + (m:move-address <- read-move buffered-stdin:channel-address) +;? (retro-mode) ;? 1 +;? ($print stdin:channel-address) ;? 1 +;? ($print (("\n" literal))) ;? 1 +;? ($print buffered-stdin:channel-address) ;? 1 +;? ($print (("\n" literal))) ;? 1 +;? ($dump-memory) ;? 1 +;? (cursor-mode) ;? 1 + (break-unless m:move-address) + (b:board-address <- make-move b:board-address m:move-address) + (loop) + } + (retro-mode) +]) + +(function main [ + (chessboard) +]) + +; todo: +; backspace, ctrl-u diff --git a/archive/1.vm.arc/color-repl.mu b/archive/1.vm.arc/color-repl.mu new file mode 100644 index 00000000..ced6a89f --- /dev/null +++ b/archive/1.vm.arc/color-repl.mu @@ -0,0 +1,498 @@ +; a simple line editor for reading lisp expressions. +; colors strings and comments. nested parens get different colors. +; +; needs to do its own raw keyboard/screen management since we need to decide +; how to color each key right as it is printed. +; lots of logic devoted to handling backspace correctly. + +; keyboard screen abort continuation -> string +(function read-expression [ + (default-space:space-address <- new space:literal 60:literal) + (k:keyboard-address <- next-input) + (screen:terminal-address <- next-input) + (abort:continuation <- next-input) + (history:buffer-address <- next-input) ; buffer of strings + (history-length:integer <- get history:buffer-address/deref length:offset) + (current-history-index:integer <- copy history-length:integer) + (result:buffer-address <- init-buffer 10:literal) ; string to maybe add to + (open-parens:integer <- copy 0:literal) ; for balancing parens and tracking nesting depth + ; we can change color when backspacing over parens or comments or strings, + ; but we need to know that they aren't escaped + (escapes:buffer-address <- init-buffer 5:literal) + ; to not return after just a comment + (not-empty?:boolean <- copy nil:literal) + { begin + ; repeatedly read keys from the keyboard + ; test: 34 + (done?:boolean <- process-key default-space:space-address k:keyboard-address screen:terminal-address) + (loop-unless done?:boolean) + } + ; trim trailing newline in result (easier history management below) + { begin + (l:character <- last result:buffer-address) + (trailing-newline?:boolean <- equal l:character ((#\newline literal))) + (break-unless trailing-newline?:boolean) + (len:integer-address <- get-address result:buffer-address/deref length:offset) + (len:integer-address/deref <- subtract len:integer-address/deref 1:literal) + } + ; test: 3 => size of s is 2 + (s:string-address <- to-array result:buffer-address) + (reply s:string-address) +]) + +(function process-key [ ; return t to signal end of expression + (default-space:space-address <- new space:literal 60:literal) + (0:space-address/names:read-expression <- next-input) + (k:keyboard-address <- next-input) + (screen:terminal-address <- next-input) + (c:character <- wait-for-key k:keyboard-address silent:literal/terminal) + (len:integer-address <- get-address result:buffer-address/space:1/deref length:offset) + (maybe-cancel-this-expression c:character abort:continuation/space:1) + ; check for ctrl-d and exit + { begin + (eof?:boolean <- equal c:character ((ctrl-d literal))) + (break-unless eof?:boolean) + ; return empty expression + (s:string-address-address <- get-address result:buffer-address/space:1/deref data:offset) + (s:string-address-address/deref <- copy nil:literal) + (reply t:literal) + } + ; check for backspace + ; test: 34 + ; todo: backspace past newline + { begin + (backspace?:boolean <- equal c:character ((#\backspace literal))) + (break-unless backspace?:boolean) + (print-character screen:terminal-address c:character/backspace) + { begin + ; delete last character if any + (zero?:boolean <- lesser-or-equal len:integer-address/deref 0:literal) + (break-if zero?:boolean) + (len:integer-address/deref <- subtract len:integer-address/deref 1:literal) + ; switch colors + ; test: "a"bc" + ; test: "a\"bc" + { begin + (backspaced-over-close-quote?:boolean <- backspaced-over-unescaped? result:buffer-address/space:1 ((#\" literal)) escapes:buffer-address/space:1) ; " + (break-unless backspaced-over-close-quote?:boolean) + (slurp-string result:buffer-address/space:1 escapes:buffer-address/space:1 abort:continuation/space:1 k:keyboard-address screen:terminal-address) + (reply nil:literal) + } + ; test: (+ 1 (2) + ; test: (+ 1 #\(2) + { begin + (backspaced-over-open-paren?:boolean <- backspaced-over-unescaped? result:buffer-address/space:1 ((#\( literal)) escapes:buffer-address/space:1) + (break-unless backspaced-over-open-paren?:boolean) + (open-parens:integer/space:1 <- subtract open-parens:integer/space:1 1:literal) + (reply nil:literal) + } + ; test: (+ 1 2) 3) + ; test: (+ 1 2#\) 3) + { begin + (backspaced-over-close-paren?:boolean <- backspaced-over-unescaped? result:buffer-address/space:1 ((#\) literal)) escapes:buffer-address/space:1) + (break-unless backspaced-over-close-paren?:boolean) + (open-parens:integer/space:1 <- add open-parens:integer/space:1 1:literal) + (reply nil:literal) + } + } + (reply nil:literal) + } + ; up arrow; switch to previous item in history + { begin + (up-arrow?:boolean <- equal c:character ((up literal))) + (break-unless up-arrow?:boolean) + ; if history exists + ; test: up without history has no effect + { begin + (empty-history?:boolean <- lesser-or-equal history-length:integer/space:1 0:literal) + (break-unless empty-history?:boolean) + (reply nil:literal) + } + ; if pointer not already at start of history + ; test: 34 up past history has no effect + { begin + (at-history-start?:boolean <- lesser-or-equal current-history-index:integer/space:1 0:literal) + (break-unless at-history-start?:boolean) + (reply nil:literal) + } + ; then update history index, copy into current buffer + ; test: 34 up restores previous command + ; test todo: 342334 up doesn't mess up typing on current line + ; test todo: 345 commands don't modify history + ; test todo: multi-line expressions + ; identify the history item + (current-history-index:integer/space:1 <- subtract current-history-index:integer/space:1 1:literal) + (switch-to-history 0:space-address screen:terminal-address) + ; is trimmed in the history expression, so wait for the human to + ; hit again or backspace to make edits + (reply nil:literal) + } + ; down arrow; switch to next item in history + { begin + (down-arrow?:boolean <- equal c:character ((down literal))) + (break-unless down-arrow?:boolean) + ; if history exists + ; test: down without history has no effect + { begin + (empty-history?:boolean <- lesser-or-equal history-length:integer/space:1 0:literal) + (break-unless empty-history?:boolean) + (reply nil:literal) + } + ; if pointer not already at end of history + ; test: 34 up past history has no effect + { begin + (x:integer <- subtract history-length:integer/space:1 1:literal) + (before-history-end?:boolean <- greater-or-equal current-history-index:integer/space:1 x:integer) + (break-unless before-history-end?:boolean) + (reply nil:literal) + } + ; then update history index, copy into current buffer + ; test: 34 up restores previous command + ; test todo: 342334 up doesn't mess up typing on current line + ; test todo: 345 commands don't modify history + ; test todo: multi-line expressions + ; identify the history item + (current-history-index:integer/space:1 <- add current-history-index:integer/space:1 1:literal) + (switch-to-history 0:space-address screen:terminal-address) + ; is trimmed in the history expression, so wait for the human to + ; hit again or backspace to make edits + (reply nil:literal) + } + ; if it's a newline, decide whether to return + ; test: 34 + { begin + (newline?:boolean <- equal c:character ((#\newline literal))) + (break-unless newline?:boolean) + (print-character screen:terminal-address c:character/newline) + (at-top-level?:boolean <- lesser-or-equal open-parens:integer/space:1 0:literal) + (end-expression?:boolean <- and at-top-level?:boolean not-empty?:boolean/space:1) + (reply end-expression?:boolean) + } + ; printable character; save +;? ($print (("append\n" literal))) ;? 2 + (result:buffer-address/space:1 <- append result:buffer-address/space:1 c:character) +;? ($print (("done\n" literal))) ;? 2 + ; if it's backslash, read, save and print one additional character + ; test: (prn #\() + { begin + (backslash?:boolean <- equal c:character ((#\\ literal))) + (break-unless backslash?:boolean) + (print-character screen:terminal-address c:character/backslash 7:literal/white) + (result:buffer-address/space:1 escapes:buffer-address/space:1 <- slurp-escaped-character result:buffer-address/space:1 7:literal/white escapes:buffer-address/space:1 abort:continuation/space:1 k:keyboard-address screen:terminal-address) + (reply nil:literal) + } + ; if it's a semi-colon, parse a comment + { begin + (comment?:boolean <- equal c:character ((#\; literal))) + (break-unless comment?:boolean) + (print-character screen:terminal-address c:character/semi-colon 4:literal/fg/blue) + (comment-read?:boolean <- slurp-comment result:buffer-address/space:1 escapes:buffer-address/space:1 abort:continuation/space:1 k:keyboard-address screen:terminal-address) + ; return if comment was read (i.e. consumed a newline) + ; test: ;a (shouldn't end command until ) + { begin + (break-if comment-read?:boolean) + (reply nil:literal) + } + ; and we're not within parens + ; test: (+ 1 2) ; comment + ; test: (+ 1; abc2) + ; test: ; comment(+ 1 2) + ; too expensive to build: 3; comment(+ 1 2) + (at-top-level?:boolean <- lesser-or-equal open-parens:integer/space:1 0:literal) + (end-expression?:boolean <- and at-top-level?:boolean not-empty?:boolean/space:1) + (reply end-expression?:boolean) + } + ; if it's not whitespace, set not-empty? and continue + { begin + (space?:boolean <- equal c:character ((#\space literal))) + (break-if space?:boolean) + (newline?:boolean <- equal c:character ((#\newline literal))) + (break-if newline?:boolean) + (tab?:boolean <- equal c:character ((tab literal))) + (break-if tab?:boolean) + (not-empty?:boolean/space:1 <- copy t:literal) + ; fall through + } + ; if it's a quote, parse a string + { begin + (string-started?:boolean <- equal c:character ((#\" literal))) ; for vim: " + (break-unless string-started?:boolean) + (print-character screen:terminal-address c:character/open-quote 6:literal/fg/cyan) + (slurp-string result:buffer-address/space:1 escapes:buffer-address/space:1 abort:continuation/space:1 k:keyboard-address screen:terminal-address) + (reply nil:literal) + } + ; color parens by depth, so they're easy to balance + ; test: (+ 1 1) + ; test: (def foo () (+ 1 (* 2 3))) + { begin + (open-paren?:boolean <- equal c:character ((#\( literal))) + (break-unless open-paren?:boolean) + (_ color-code:integer <- divide-with-remainder open-parens:integer/space:1 3:literal) ; 3 distinct colors for parens + (color-code:integer <- add color-code:integer 1:literal) + (print-character screen:terminal-address c:character/open-paren color-code:integer) + (open-parens:integer/space:1 <- add open-parens:integer/space:1 1:literal) +;? ($print open-parens:integer/space:1) ;? 2 + (reply nil:literal) + } + { begin + (close-paren?:boolean <- equal c:character ((#\) literal))) + (break-unless close-paren?:boolean) + (open-parens:integer/space:1 <- subtract open-parens:integer/space:1 1:literal) + (_ color-code:integer <- divide-with-remainder open-parens:integer/space:1 3:literal) ; 3 distinct colors for parens + (color-code:integer <- add color-code:integer 1:literal) + (print-character screen:terminal-address c:character/close-paren color-code:integer) +;? ($print open-parens:integer/space:1) ;? 2 + (reply nil:literal) + } + ; if all else fails, print the character without color + (print-character screen:terminal-address c:character/regular) + ; todo: error on space outside parens, like python + ; todo: [] + ; todo: history on up/down + (reply nil:literal) +]) + +(function switch-to-history [ + (default-space:space-address <- new space:literal 30:literal) + (0:space-address/names:read-expression <- next-input) + (screen:terminal-address <- next-input) + (clear-repl-state 0:space-address) + (curr-history:string-address <- buffer-index history:buffer-address/space:1 current-history-index:integer/space:1) + (curr-history-len:integer <- length curr-history:string-address/deref) + ; and retype it into the current expression + (hist:keyboard-address <- init-keyboard curr-history:string-address) + (hist-index:integer-address <- get-address hist:keyboard-address/deref index:offset) + { begin + (done?:boolean <- greater-or-equal hist-index:integer-address/deref curr-history-len:integer) + (break-if done?:boolean) + (sub-return:boolean <- process-key 0:space-address hist:keyboard-address screen:terminal-address) + (assert-false sub-return:boolean (("recursive call to process keys thought it was done" literal))) + (loop) + } +]) + +(function clear-repl-state [ + (default-space:space-address/names:read-expression <- next-input) + ; clear result + (len:integer-address <- get-address result:buffer-address/deref length:offset) + (backspace-over len:integer-address/deref screen:terminal-address) + (len:integer-address/deref <- copy 0:literal) + ; clear other state accumulated for the existing expression + (open-parens:integer <- copy 0:literal) + (escapes:buffer-address <- init-buffer 5:literal) + (not-empty?:boolean <- copy nil:literal) +]) + +(function backspace-over [ + (default-space:space-address <- new space:literal 30:literal) + (len:integer <- next-input) + (screen:terminal-address <- next-input) + { begin + (done?:boolean <- lesser-or-equal len:integer 0:literal) + (break-if done?:boolean) + (print-character screen:terminal-address ((#\backspace literal))) + (len:integer <- subtract len:integer 1:literal) + (loop) + } +]) + +; list of characters, list of indices of escaped characters, abort continuation +; -> whether a comment was consumed (can also return by backspacing past comment leader ';') +(function slurp-comment [ + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (escapes:buffer-address <- next-input) + (abort:continuation <- next-input) + (k:keyboard-address <- next-input) + (screen:terminal-address <- next-input) + ; test: ; abc + { begin + next-key-in-comment + (c:character <- wait-for-key k:keyboard-address silent:literal/terminal) + (maybe-cancel-this-expression c:character abort:continuation screen:terminal-address) ; test: check needs to come before print + (print-character screen:terminal-address c:character 4:literal/fg/blue) + ; handle backspace + ; test: ; abcdef + ; todo: how to exit comment? + { begin + (backspace?:boolean <- equal c:character ((#\backspace literal))) + (break-unless backspace?:boolean) + (len:integer-address <- get-address in:buffer-address/deref length:offset) + ; buffer has to have at least the semi-colon so can't be empty + (len:integer-address/deref <- subtract len:integer-address/deref 1:literal) + ; if we erase start of comment, return + (comment-deleted?:boolean <- backspaced-over-unescaped? in:buffer-address ((#\; literal)) escapes:buffer-address) ; " + (jump-unless comment-deleted?:boolean next-key-in-comment:offset) ; loop + (reply nil:literal/read-comment?) + } + (in:buffer-address <- append in:buffer-address c:character) + (newline?:boolean <- equal c:character ((#\newline literal))) + (loop-unless newline?:boolean) + } + (reply t:literal/read-comment?) +]) + +(function slurp-string [ + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (escapes:buffer-address <- next-input) + (abort:continuation <- next-input) + (k:keyboard-address <- next-input) + (screen:terminal-address <- next-input) + ; test: "abc" + { begin + next-key-in-string + (c:character <- wait-for-key k:keyboard-address silent:literal/terminal) + (maybe-cancel-this-expression c:character abort:continuation screen:terminal-address) ; test: check needs to come before print + (print-character screen:terminal-address c:character 6:literal/fg/cyan) + ; handle backspace + ; test: "abcd" + ; todo: how to exit string? + { begin + (backspace?:boolean <- equal c:character ((#\backspace literal))) + (break-unless backspace?:boolean) + (len:integer-address <- get-address in:buffer-address/deref length:offset) + ; typed a quote before calling slurp-string, so can't be empty + (len:integer-address/deref <- subtract len:integer-address/deref 1:literal) + ; if we erase start of string, return + ; test: "34 + (string-deleted?:boolean <- backspaced-over-unescaped? in:buffer-address ((#\" literal)) escapes:buffer-address) ; " +;? ($print string-deleted?:boolean) ;? 1 + (jump-if string-deleted?:boolean end:offset) ; break + (jump next-key-in-string:offset) ; loop + } + (in:buffer-address <- append in:buffer-address c:character) + ; break on quote -- unless escaped by backslash + ; test: "abc\"ef" + { begin + (backslash?:boolean <- equal c:character ((#\\ literal))) + (break-unless backslash?:boolean) + (in:buffer-address escapes:buffer-address <- slurp-escaped-character in:buffer-address 6:literal/cyan escapes:buffer-address abort:continuation k:keyboard-address screen:terminal-address) + (jump next-key-in-string:offset) ; loop + } + ; if not backslash + (end-quote?:boolean <- equal c:character ((#\" literal))) ; for vim: " + (loop-unless end-quote?:boolean) + } + end +]) + +; buffer to add character to, color to print it in to the screen, abort continuation +(function slurp-escaped-character [ + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (color-code:integer <- next-input) + (escapes:buffer-address <- next-input) + (abort:continuation <- next-input) + (k:keyboard-address <- next-input) + (screen:terminal-address <- next-input) + (c:character <- wait-for-key k:keyboard-address silent:literal/terminal) + (maybe-cancel-this-expression c:character abort:continuation screen:terminal-address) ; test: check needs to come before print + (print-character screen:terminal-address c:character color-code:integer) + (len:integer-address <- get-address in:buffer-address/deref length:offset) + (escapes:buffer-address <- append escapes:buffer-address len:integer-address/deref) +;? ($print (("+" literal))) ;? 1 + ; handle backspace + ; test: "abc\def" + ; test: #\ + { begin + (backspace?:boolean <- equal c:character ((#\backspace literal))) + (break-unless backspace?:boolean) + ; just typed a backslash, so buffer can't be empty + (len:integer-address/deref <- subtract len:integer-address/deref 1:literal) + (elen:integer-address <- get-address escapes:buffer-address/deref length:offset) + (elen:integer-address/deref <- subtract elen:integer-address/deref 1:literal) +;? ($print (("-" literal))) ;? 1 + (reply in:buffer-address/same-as-arg:0 escapes:buffer-address/same-as-arg:2) + } + ; if not backspace, save and return + (in:buffer-address <- append in:buffer-address c:character) + (reply in:buffer-address/same-as-arg:0 escapes:buffer-address/same-as-arg:2) +]) + +(function backspaced-over-unescaped? [ + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (expected:character <- next-input) + (escapes:buffer-address <- next-input) + ; char just backspaced over matches + { begin + (c:character <- past-last in:buffer-address) + (char-match?:boolean <- equal c:character expected:character) + (break-if char-match?:boolean) + (reply nil:literal) + } + ; and char before cursor is not an escape + { begin + (most-recent-escape:integer <- last escapes:buffer-address) + (last-idx:integer <- get in:buffer-address/deref length:offset) +;? ($print most-recent-escape:integer) ;? 1 +;? ($print last-idx:integer) ;? 1 + (was-unescaped?:boolean <- not-equal last-idx:integer most-recent-escape:integer) + (break-if was-unescaped?:boolean) + (reply nil:literal) + } + (reply t:literal) +]) + +; return the character past the end of the buffer, if there's room +(function past-last [ + (default-space:space-address <- new space:literal 30:literal) + (in:buffer-address <- next-input) + (n:integer <- get in:buffer-address/deref length:offset) + (s:string-address <- get in:buffer-address/deref data:offset) + (capacity:integer <- length s:string-address/deref) + { begin + (no-space?:boolean <- greater-or-equal n:integer capacity:integer) + (break-unless no-space?:boolean) + (reply ((#\null literal))) + } + (result:character <- index s:string-address/deref n:integer) + (reply result:character) +]) + +(function maybe-cancel-this-expression [ + ; check for ctrl-g and abort + (default-space:space-address <- new space:literal 30:literal) + (c:character <- next-input) + (abort:continuation <- next-input) + (screen:terminal-address <- next-input) + { begin + (interrupt?:boolean <- equal c:character ((ctrl-g literal))) + (break-unless interrupt?:boolean) + (print-character screen:terminal-address ((#\^ literal))) + (print-character screen:terminal-address ((#\G literal))) + (print-character screen:terminal-address ((#\newline literal))) + (continue-from abort:continuation) + } +]) + +(function main [ + (default-space:space-address <- new space:literal 30:literal) + (cursor-mode) + ($print (("connected to anarki! type in an expression, then hit enter. ctrl-d exits. ctrl-g clears the current expression." literal))) + (print-character nil:literal/terminal ((#\newline literal))) + ; todo: ctrl-g shouldn't clear history + (abort:continuation <- current-continuation) + (history:buffer-address <- init-buffer 5:literal) ; buffer of buffers of strings, one per expression typed in + { begin + (s:string-address <- read-expression nil:literal/keyboard nil:literal/terminal abort:continuation history:buffer-address) + (break-unless s:string-address) +;? (x:integer <- length s:string-address/deref) ;? 1 +;? ($print x:integer) ;? 1 +;? ($print ((#\newline literal))) ;? 1 + (history:buffer-address <- append history:buffer-address s:string-address) +;? (len:integer <- get history:buffer-address/deref length:offset) ;? 1 +;? ($print len:integer) ;? 1 +;? ($print ((#\newline literal))) ;? 1 + (retro-mode) ; print errors cleanly +;? (print-string nil:literal/terminal s:string-address) ;? 1 + (t:string-address <- $eval s:string-address) + (cursor-mode) + ($print (("=> " literal))) + (print-string nil:literal/terminal t:string-address) + (print-character nil:literal/terminal ((#\newline literal))) + (print-character nil:literal/terminal ((#\newline literal))) ; empty line separates each expression and result + (loop) + } +]) diff --git a/archive/1.vm.arc/counters.mu b/archive/1.vm.arc/counters.mu new file mode 100644 index 00000000..0e414513 --- /dev/null +++ b/archive/1.vm.arc/counters.mu @@ -0,0 +1,33 @@ +(function init-counter [ + (default-space:space-address <- new space:literal 30:literal) + (n:integer <- next-input) + (reply default-space:space-address) + ]) + +(function increment-counter [ + (default-space:space-address <- new space:literal 30:literal) + (0:space-address/names:init-counter <- next-input) ; setup outer space; it *must* come from 'init-counter' + (x:integer <- next-input) + (n:integer/space:1 <- add n:integer/space:1 x:integer) + (reply n:integer/space:1) + ]) + +(function main [ + (default-space:space-address <- new space:literal 30:literal) + ; counter A + (a:space-address <- init-counter 34:literal) + ; counter B + (b:space-address <- init-counter 23:literal) + ; increment both by 2 but in different ways + (increment-counter a:space-address 1:literal) + (bres:integer <- increment-counter b:space-address 2:literal) + (ares:integer <- increment-counter a:space-address 1:literal) + ; check results + ($print (("Contents of counters a: " literal))) + (print-integer nil:literal/terminal ares:integer) + ($print ((" b: " literal))) + (print-integer nil:literal/terminal bres:integer) + ($print (("\n" literal))) + ]) + +; compare http://www.paulgraham.com/accgen.html diff --git a/archive/1.vm.arc/edit.arc.t b/archive/1.vm.arc/edit.arc.t new file mode 100644 index 00000000..ff039602 --- /dev/null +++ b/archive/1.vm.arc/edit.arc.t @@ -0,0 +1,33 @@ +(selective-load "mu.arc" section-level) +(set allow-raw-addresses*) + +(section 100 + +(reset) +(new-trace "new-screen") +(add-code:readfile "edit.mu") +(add-code + '((function test-new-screen [ + (1:screen-address/global <- new-screen 5:literal 5:literal) + ]))) +;? (each stmt function*!new-screen +;? (prn stmt)) +(let routine make-routine!test-new-screen + (let before rep.routine!alloc +;? (= dump-trace* (obj blacklist '("sz" "m" "setm" "addr" "cvt0" "cvt1"))) + (run 'test-new-screen) +;? (prn memory*) +;? (prn memory*.2001) + (when (~is (memory* memory*.1) 5) ; number of rows + (prn "F - newly-allocated screen doesn't have the right number of rows: @(memory* memory*!2001)")) + (let row-pointers (let base (+ 1 memory*.1) + (range base (+ base 4))) + ;? (prn row-pointers) + (when (some nil (map memory* row-pointers)) + (prn "F - newly-allocated screen didn't initialize all of its row pointers")) + (when (~all 5 (map memory* (map memory* row-pointers))) + (prn "F - newly-allocated screen didn't initialize all of its row lengths"))))) + +(reset) + +) ; section 100 for all editor code diff --git a/archive/1.vm.arc/edit.mu b/archive/1.vm.arc/edit.mu new file mode 100644 index 00000000..ebf43161 --- /dev/null +++ b/archive/1.vm.arc/edit.mu @@ -0,0 +1,18 @@ +; a screen is an array of pointers to lines, in turn arrays of characters + +(function new-screen [ + (default-space:space-address <- new space:literal 30:literal) + (nrows:integer <- next-input) + (ncols:integer <- next-input) + (result:screen-address <- new screen:literal nrows:integer) + (rowidx:integer <- copy 0:literal) + { begin + (curr-line-address-address:line-address-address <- index-address result:screen-address/deref rowidx:integer) + (curr-line-address-address:line-address-address/deref <- new line:literal ncols:integer) + (curr-line-address:line-address <- copy curr-line-address-address:line-address-address/deref) + (rowidx:integer <- add rowidx:integer 1:literal) + (x:boolean <- not-equal rowidx:integer nrows:integer) + (loop-if x:boolean) + } + (reply result:screen-address) +]) diff --git a/archive/1.vm.arc/exuberant-ctags-rc b/archive/1.vm.arc/exuberant-ctags-rc new file mode 100644 index 00000000..7d99b0b8 --- /dev/null +++ b/archive/1.vm.arc/exuberant-ctags-rc @@ -0,0 +1,7 @@ +--langdef=mu +--langmap=mu:.mu +--regex-mu=/^\(function[ \t]+([^ \t\[]+)/\1/d,definition/ +--regex-mu=/^\(recipe[ \t]+([^ \t\[]+)/\1/d,definition/ +--regex-mu=/^\(and-record[ \t]+([^ \t\[]+)/\1/t,type/ +--regex-mu=/^\(address[ \t]+([^ \t\[]+)/\1/t,type/ +--regex-mu=/^\(array[ \t]+([^ \t\[]+)/\1/t,type/ diff --git a/archive/1.vm.arc/factorial.mu b/archive/1.vm.arc/factorial.mu new file mode 100644 index 00000000..96a28fd3 --- /dev/null +++ b/archive/1.vm.arc/factorial.mu @@ -0,0 +1,22 @@ +(function factorial [ + (default-space:space-address <- new space:literal 30:literal) + (n:integer <- next-input) + { begin + ; if n=0 return 1 + (zero?:boolean <- equal n:integer 0:literal) + (break-unless zero?:boolean) + (reply 1:literal) + } + ; return n*factorial(n-1) + (x:integer <- subtract n:integer 1:literal) + (subresult:integer <- factorial x:integer) + (result:integer <- multiply subresult:integer n:integer) + (reply result:integer) +]) + +(function main [ + (1:integer <- factorial 5:literal) + ($print (("result: " literal))) + (print-integer nil:literal/terminal 1:integer) + ($print (("\n" literal))) +]) diff --git a/archive/1.vm.arc/fork.mu b/archive/1.vm.arc/fork.mu new file mode 100644 index 00000000..8d6463a8 --- /dev/null +++ b/archive/1.vm.arc/fork.mu @@ -0,0 +1,18 @@ +(function main [ + (fork thread2:fn) + (default-space:space-address <- new space:literal 2:literal) + (x:integer <- copy 34:literal) + { begin + (print-integer nil:literal/terminal x:integer) + (loop) + } +]) + +(function thread2 [ + (default-space:space-address <- new space:literal 2:literal) + (y:integer <- copy 35:literal) + { begin + (print-integer nil:literal/terminal y:integer) + (loop) + } +]) diff --git a/archive/1.vm.arc/generic.mu b/archive/1.vm.arc/generic.mu new file mode 100644 index 00000000..1c4b9bb0 --- /dev/null +++ b/archive/1.vm.arc/generic.mu @@ -0,0 +1,30 @@ +; To demonstrate generic functions, we'll construct a factorial function with +; separate base and recursive clauses. Compare factorial.mu. + +; factorial n = n*factorial(n-1) +(function factorial [ + (default-space:space-address <- new space:literal 30:literal) + (n:integer <- input 0:literal) + (x:integer <- subtract n:integer 1:literal) + (subresult:integer <- factorial x:integer) + (result:integer <- multiply subresult:integer n:integer) + (reply result:integer) +]) + +; factorial 0 = 1 +(function factorial [ + (default-space:space-address <- new space:literal 30:literal) + (n:integer <- input 0:literal) + { begin + (zero?:boolean <- equal n:integer 0:literal) + (break-unless zero?:boolean) + (reply 1:literal) + } +]) + +(function main [ + (1:integer <- factorial 5:literal) + ($print (("result: " literal))) + (print-integer nil:literal/terminal 1:integer) + ($print (("\n" literal))) +]) diff --git a/archive/1.vm.arc/graphics.mu b/archive/1.vm.arc/graphics.mu new file mode 100644 index 00000000..f25395ef --- /dev/null +++ b/archive/1.vm.arc/graphics.mu @@ -0,0 +1,23 @@ +; open a viewport, print coordinates of mouse clicks +; currently need to ctrl-c to exit after closing the viewport +(function main [ + (window-on (("practice" literal)) 300:literal 300:literal) + { begin + (pos:integer-integer-pair click?:boolean <- mouse-position) + (loop-unless click?:boolean) + (x:integer <- get pos:integer-integer-pair 0:offset) + (y:integer <- get pos:integer-integer-pair 1:offset) +;? ($print (("AAA " literal))) +;? ($print x:integer) +;? ($print ((", " literal))) +;? ($print y:integer) +;? ($print (("\n" literal))) + (print-integer nil:literal/terminal x:integer) + (print-character nil:literal/terminal ((#\, literal))) + (print-character nil:literal/terminal ((#\space literal))) + (print-integer nil:literal/terminal y:integer) + (print-character nil:literal/terminal ((#\newline literal))) + (loop) + } + (window-off) +]) diff --git a/archive/1.vm.arc/highlights b/archive/1.vm.arc/highlights new file mode 100644 index 00000000..bb81fb56 --- /dev/null +++ b/archive/1.vm.arc/highlights @@ -0,0 +1,21 @@ +" vim: ft=vim +" Data-flow highlighting: http://www.reddit.com/r/programming/comments/1w76um/coding_in_color/cezpios + +highlight highlight_97a5a5e3 ctermfg=205 +call matchadd('highlight_97a5a5e3', '\') +highlight highlight_1f88e41c ctermfg=139 +call matchadd('highlight_1f88e41c', '\') +highlight highlight_6da20a96 ctermfg=141 +call matchadd('highlight_6da20a96', '\') +highlight highlight_ae83eebb ctermfg=149 +call matchadd('highlight_ae83eebb', 'curr-line-address-address') +highlight highlight_bb695e14 ctermfg=36 +call matchadd('highlight_bb695e14', '\') +highlight highlight_1e44ab4f ctermfg=208 +call matchadd('highlight_1e44ab4f', '\') +highlight highlight_3323f077 ctermfg=208 +call matchadd('highlight_3323f077', '\') +highlight highlight_74fc42b2 ctermfg=220 +call matchadd('highlight_74fc42b2', 'second-arg') +highlight highlight_ff6f0571 ctermfg=220 +call matchadd('highlight_ff6f0571', 'second-arg-box') diff --git a/archive/1.vm.arc/load.arc b/archive/1.vm.arc/load.arc new file mode 100644 index 00000000..b9037aa4 --- /dev/null +++ b/archive/1.vm.arc/load.arc @@ -0,0 +1,28 @@ +; support for dividing arc files into sections of different level, and +; selectively loading just sections at or less than a given level + +; usage: +; load.arc [level] [arc files] -- [mu files] + +(def selective-load (file (o level 999)) +;? (prn "loading @file at level @level") + (fromfile file + (whilet expr (read) +;? (prn car.expr) + (if (is 'section expr.0) + (when (<= expr.1 level) + (each x (cut expr 2) + (eval x))) + (eval expr)) +;? (prn car.expr " done") + ))) + +(= section-level 999) +(point break +(each x (map [fromstring _ (read)] cdr.argv) + (if (isa x 'int) + (= section-level x) + (is '-- x) + (break) ; later args are mu files + :else + (selective-load string.x section-level)))) diff --git a/archive/1.vm.arc/mu b/archive/1.vm.arc/mu new file mode 100755 index 00000000..858438b8 --- /dev/null +++ b/archive/1.vm.arc/mu @@ -0,0 +1,27 @@ +#!/bin/bash +# +# To run a program: +# $ mu [mu files] +# To run a file of tests (in arc): +# $ mu test [arc files] +# To start an interactive session: +# $ mu repl +# +# To mess with load levels and selectively run parts of the codebase, skip +# this script and call load.arc directly. + +if [[ $1 == "test" ]] +then + shift + ./anarki/arc load.arc "$@" # test currently assumed to be arc files rather than mu files +elif [[ $1 == "repl" ]] +then + if [ "$(type rlwrap)" ] + then + rlwrap -C mu ./anarki/arc mu.arc + else + ./anarki/arc mu.arc + fi +else + ./anarki/arc load.arc mu.arc -- "$@" # mu files from args +fi diff --git a/archive/1.vm.arc/mu.arc b/archive/1.vm.arc/mu.arc new file mode 100644 index 00000000..2aebd3d5 --- /dev/null +++ b/archive/1.vm.arc/mu.arc @@ -0,0 +1,3259 @@ +(ero "initializing mu.. (takes ~5s)") +;; profiler (http://arclanguage.org/item?id=11556) +; Keeping this right on top as a reminder to profile before guessing at why my +; program is slow. +(mac proc (name params . body) + `(def ,name ,params ,@body nil)) + +(mac filter-log (msg f x) + `(ret x@ ,x + (prn ,msg (,f x@)))) + +(= times* (table)) + +(mac deftimed (name args . body) + `(do + (def ,(sym (string name "_core")) ,args + ,@body) + (def ,name ,args + (let t0 (msec) + (ret ans ,(cons (sym (string name "_core")) args) + (update-time ,(string name) t0)))))) + +(proc update-time(name t0) ; call directly in recursive functions + (or= times*.name (list 0 0)) + (with ((a b) times*.name + timing (- (msec) t0)) + (= times*.name + (list + (+ a timing) + (+ b 1))))) + +(def print-times() + (prn (current-process-milliseconds)) + (prn "gc " (current-gc-milliseconds)) + (each (name time) (tablist times*) + (prn name " " time))) + +;; what happens when our virtual machine starts up +(= initialization-fns* (queue)) +(def reset () + (each f (as cons initialization-fns*) + (f))) + +(mac on-init body + `(enq (fn () ,@body) + initialization-fns*)) + +;; persisting and checking traces for each test +(= traces* (queue)) +(= trace-dir* ".traces/") +(ensure-dir trace-dir*) +(= curr-trace-file* nil) +(on-init + (awhen curr-trace-file* + (tofile (+ trace-dir* it) + (each (label trace) (as cons traces*) + (pr label ": " trace)))) + (= curr-trace-file* nil) + (= traces* (queue))) + +(def new-trace (filename) + (prn "== @filename") +;? ) + (= curr-trace-file* filename)) + +(= dump-trace* nil) +(def trace (label . args) + (when (or (is dump-trace* t) + (and dump-trace* (is label "-")) + (and dump-trace* (pos label dump-trace*!whitelist)) + (and dump-trace* (no dump-trace*!whitelist) (~pos label dump-trace*!blacklist))) + (apply prn label ": " args)) + (enq (list label (apply tostring:prn args)) + traces*) + (car args)) + +(on-init + (wipe dump-trace*)) + +(redef tr args ; why am I still returning to prn when debugging? Will this help? + (do1 nil + (apply trace "-" args))) + +(def tr2 (msg arg) + (tr msg arg) + arg) + +(def check-trace-contents (msg expected-contents) + (unless (trace-contents-match expected-contents) + (prn "F - " msg) + (prn " trace contents") + (print-trace-contents-mismatch expected-contents))) + +(def trace-contents-match (expected-contents) + (each (label msg) (as cons traces*) + (when (and expected-contents + (is label expected-contents.0.0) + (posmatch expected-contents.0.1 msg)) + (pop expected-contents))) + (no expected-contents)) + +(def print-trace-contents-mismatch (expected-contents) + (each (label msg) (as cons traces*) + (whenlet (expected-label expected-msg) expected-contents.0 + (if (and (is label expected-label) + (posmatch expected-msg msg)) + (do (pr " * ") + (pop expected-contents)) + (pr " ")) + (pr label ": " msg))) + (prn " couldn't find") + (each (expected-label expected-msg) expected-contents + (prn " ! " expected-label ": " expected-msg))) + +(def check-trace-doesnt-contain (msg (label unexpected-contents)) + (when (some (fn ((l s)) + (and (is l label) (posmatch unexpected-contents msg))) + (as cons traces*)) + (prn "F - " msg) + (prn " trace contents") + (each (l msg) (as cons traces*) + (if (and (is l label) + (posmatch unexpected-contents msg)) + (pr " X ") + (pr " ")) + (pr label ": " msg)))) + +;; virtual machine state + +; things that a future assembler will need separate memory for: +; code; types; args channel +; at compile time: mapping names to locations +(on-init + (= type* (table)) ; name -> type info + (= memory* (table)) ; address -> value (make this a vector?) + (= function* (table)) ; name -> [instructions] + ; transforming mu programs + (= location* (table)) ; function -> {name -> index into default-space} + (= next-space-generator* (table)) ; function -> name of function generating next space + ; each function's next space will usually always come from a single function + (= next-routine-id* 0) + (= continuation* (table)) + ) + +(on-init + (= type* (obj + ; Each type must be scalar or array, sum or product or primitive + type (obj size 1) ; implicitly scalar and primitive + type-address (obj size 1 address t elem '(type)) + type-array (obj array t elem '(type)) + type-array-address (obj size 1 address t elem '(type-array)) + location (obj size 1 address t elem '(location)) ; assume it points to an atom + integer (obj size 1) + boolean (obj size 1) + boolean-address (obj size 1 address t elem '(boolean)) + byte (obj size 1) + byte-address (obj size 1 address t elem '(byte)) + string (obj array t elem '(byte)) ; inspired by Go + ; an address contains the location of a specific type + string-address (obj size 1 address t elem '(string)) + string-address-address (obj size 1 address t elem '(string-address)) + string-address-array (obj array t elem '(string-address)) + string-address-array-address (obj size 1 address t elem '(string-address-array)) + string-address-array-address-address (obj size 1 address t elem '(string-address-array-address)) + ; 'character' will be of larger size when mu supports unicode + ; we're currently undisciplined about mixing 'byte' and 'character' + ; realistic test of indiscipline in general + character (obj size 1) ; int32 like a Go rune + character-address (obj size 1 address t elem '(character)) + ; a buffer makes it easy to append to a string/array + ; todo: make this generic + ; data isn't a 'real' array: its length is stored outside it, + ; so for example, 'print-string' won't work on it. + buffer (obj size 2 and-record t elems '((integer) (string-address)) fields '(length data)) + buffer-address (obj size 1 address t elem '(buffer)) + ; a stream makes it easy to read from a string/array + stream (obj size 2 and-record t elems '((integer) (string-address)) fields '(pointer data)) + stream-address (obj size 1 address t elem '(stream)) + ; isolating function calls + space (obj array t elem '(location)) ; by convention index 0 points to outer space + space-address (obj size 1 address t elem '(space)) + ; arrays consist of an integer length followed by that many + ; elements, all of the same type + integer-array (obj array t elem '(integer)) + integer-array-address (obj size 1 address t elem '(integer-array)) + integer-array-address-address (obj size 1 address t elem '(integer-array-address)) + integer-address (obj size 1 address t elem '(integer)) ; pointer to int + integer-address-address (obj size 1 address t elem '(integer-address)) + ; and-records consist of a multiple fields of different types + integer-boolean-pair (obj size 2 and-record t elems '((integer) (boolean)) fields '(int bool)) + integer-boolean-pair-address (obj size 1 address t elem '(integer-boolean-pair)) + integer-boolean-pair-array (obj array t elem '(integer-boolean-pair)) + integer-boolean-pair-array-address (obj size 1 address t elem '(integer-boolean-pair-array)) + integer-integer-pair (obj size 2 and-record t elems '((integer) (integer))) + integer-integer-pair-address (obj size 1 address t elem '(integer-integer-pair)) + integer-point-pair (obj size 2 and-record t elems '((integer) (integer-integer-pair))) + integer-point-pair-address (obj size 1 address t elem '(integer-point-pair)) + integer-point-pair-address-address (obj size 1 address t elem '(integer-point-pair-address)) + ; tagged-values are the foundation of dynamic types + tagged-value (obj size 2 and-record t elems '((type) (location)) fields '(type payload)) + tagged-value-address (obj size 1 address t elem '(tagged-value)) + tagged-value-array (obj array t elem '(tagged-value)) + tagged-value-array-address (obj size 1 address t elem '(tagged-value-array)) + tagged-value-array-address-address (obj size 1 address t elem '(tagged-value-array-address)) + ; heterogeneous lists + list (obj size 2 and-record t elems '((tagged-value) (list-address)) fields '(car cdr)) + list-address (obj size 1 address t elem '(list)) + list-address-address (obj size 1 address t elem '(list-address)) + ; parallel routines use channels to synchronize + channel (obj size 3 and-record t elems '((integer) (integer) (tagged-value-array-address)) fields '(first-full first-free circular-buffer)) + ; be careful of accidental copies to channels + channel-address (obj size 1 address t elem '(channel)) + ; opaque pointer to a call stack + ; todo: save properly in allocated memory + continuation (obj size 1) + ; editor + line (obj array t elem '(character)) + line-address (obj size 1 address t elem '(line)) + line-address-address (obj size 1 address t elem '(line-address)) + screen (obj array t elem '(line-address)) + screen-address (obj size 1 address t elem '(screen)) + ; fake screen + terminal (obj size 5 and-record t elems '((integer) (integer) (integer) (integer) (string-address)) fields '(num-rows num-cols cursor-row cursor-col data)) + terminal-address (obj size 1 address t elem '(terminal)) + ; fake keyboard + keyboard (obj size 2 and-record t elems '((integer) (string-address)) fields '(index data)) + keyboard-address (obj size 1 address t elem '(keyboard)) + ))) + +;; managing concurrent routines + +(on-init +;? (prn "-- resetting memory allocation") + (= Memory-allocated-until 1000) + (= Allocation-chunk 100000)) + +; routine = runtime state for a serial thread of execution +(def make-routine (fn-name . args) + (let curr-alloc Memory-allocated-until +;? (prn "-- allocating routine: @curr-alloc") + (++ Memory-allocated-until Allocation-chunk) + (annotate 'routine (obj alloc curr-alloc alloc-max Memory-allocated-until + call-stack + (list (obj fn-name fn-name pc 0 args args caller-arg-idx 0)))) + ; other fields we use in routine: + ; sleep: conditions + ; limit: number of cycles this routine can use + ; running-since: start of the clock for counting cycles this routine has used + + ; todo: do memory management in mu + )) + +(defextend empty (x) (isa x 'routine) + (no rep.x!call-stack)) + +(def stack (routine) + ((rep routine) 'call-stack)) + +(def push-stack (routine op) + (push (obj fn-name op pc 0 caller-arg-idx 0 t0 (msec)) + rep.routine!call-stack)) + +(def pop-stack (routine) +;? (update-time label.routine (msec)) ;? 1 + (pop rep.routine!call-stack)) + +(def top (routine) + stack.routine.0) + +(def label (routine) + (whenlet stack stack.routine + (or= stack.0!label + (label2 stack)))) +(def label2 (stack) + (string:intersperse "/" (map [_ 'fn-name] stack)));)) + +(def body (routine) + (function* stack.routine.0!fn-name)) + +(mac pc (routine (o idx 0)) ; assignable + `((((rep ,routine) 'call-stack) ,idx) 'pc)) + +(mac caller-arg-idx (routine (o idx 0)) ; assignable + `((((rep ,routine) 'call-stack) ,idx) 'caller-arg-idx)) + +(mac caller-args (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'args)) +(mac caller-operands (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'caller-operands)) +(mac caller-results (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'caller-results)) + +(mac results (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'results)) +(mac reply-args (routine) ; assignable + `((((rep ,routine) 'call-stack) 0) 'reply-args)) + +(def waiting-for-exact-cycle? (routine) + (is 'until rep.routine!sleep.0)) + +(def ready-to-wake-up (routine) + (assert no.routine*) + (case rep.routine!sleep.0 + until + (> curr-cycle* rep.routine!sleep.1) + until-location-changes + (~is rep.routine!sleep.2 (memory* rep.routine!sleep.1)) + until-routine-done + (find [and _ (is rep._!id rep.routine!sleep.1)] + completed-routines*) + )) + +(on-init + (= running-routines* (queue)) ; simple round-robin scheduler + ; set of sleeping routines; don't modify routines while they're in this table + (= sleeping-routines* (table)) + (= completed-routines* nil) ; audit trail + (= routine* nil) + (= abort-routine* (parameter nil)) + (= curr-cycle* 0) + (= scheduling-interval* 500) + (= scheduler-switch-table* nil) ; hook into scheduler for debugging + ) + +; like arc's 'point' but you can also call ((abort-routine*)) in nested calls +(mac routine-mark body + (w/uniq (g p) + `(ccc (fn (,g) + (parameterize abort-routine* (fn ((o ,p)) (,g ,p)) + ,@body))))) + +(def run fn-names + (freeze function*) +;? (prn function*!main) ;? 1 + (load-system-functions) + (apply run-more fn-names)) + +; assume we've already frozen; throw on a few more routines and continue scheduling +(def run-more fn-names + (each it fn-names + (enq make-routine.it running-routines*)) + (while (~empty running-routines*) + (= routine* deq.running-routines*) + (when rep.routine*!limit + ; start the clock if it wasn't already running + (or= rep.routine*!running-since curr-cycle*)) + (trace "schedule" label.routine*) + (routine-mark + (run-for-time-slice scheduling-interval*)) + (update-scheduler-state))) + +; prepare next iteration of round-robin scheduler +; +; state before: routine* running-routines* sleeping-routines* +; state after: running-routines* (with next routine to run at head) sleeping-routines* +; +; responsibilities: +; add routine* to either running-routines* or sleeping-routines* or completed-routines* +; wake up any necessary sleeping routines (which might be waiting for a +; particular time or for a particular memory location to change) +; detect termination: all non-helper routines completed +; detect deadlock: kill all sleeping routines when none can be woken +(def update-scheduler-state () + (when routine* +;? (prn "update scheduler state: " routine*) + (if + rep.routine*!sleep + (do (trace "schedule" "pushing " label.routine* " to sleep queue") + ; keep the clock ticking at rep.routine*!running-since + (set sleeping-routines*.routine*)) + rep.routine*!error + (do (trace "schedule" "done with dead routine " label.routine*) +;? (tr rep.routine*) + (push routine* completed-routines*)) + empty.routine* + (do (trace "schedule" "done with routine " label.routine*) + (push routine* completed-routines*)) + (no rep.routine*!limit) + (do (trace "schedule" "scheduling " label.routine* " for further processing") + (enq routine* running-routines*)) + (> rep.routine*!limit 0) + (do (trace "schedule" "scheduling " label.routine* " for further processing (limit)") + ; stop the clock and debit the time on it from the routine + (-- rep.routine*!limit (- curr-cycle* rep.routine*!running-since)) + (wipe rep.routine*!running-since) + (if (<= rep.routine*!limit 0) + (do (trace "schedule" "routine ran out of time") + (push routine* completed-routines*)) + (enq routine* running-routines*))) + :else + (err "illegal scheduler state")) + (= routine* nil)) + (each (routine _) routine-canon.sleeping-routines* + (when (aand rep.routine!limit (<= it (- curr-cycle* rep.routine!running-since))) + (trace "schedule" "routine timed out") + (wipe sleeping-routines*.routine) + (push routine completed-routines*) +;? (tr completed-routines*) + )) + (each (routine _) routine-canon.sleeping-routines* + (when (ready-to-wake-up routine) + (trace "schedule" "waking up " label.routine) + (wipe sleeping-routines*.routine) ; do this before modifying routine + (wipe rep.routine!sleep) + (++ pc.routine) + (enq routine running-routines*))) + ; optimization for simulated time + (when (empty running-routines*) + (whenlet exact-sleeping-routines (keep waiting-for-exact-cycle? keys.sleeping-routines*) + (let next-wakeup-cycle (apply min (map [rep._!sleep 1] exact-sleeping-routines)) + (= curr-cycle* (+ 1 next-wakeup-cycle))) + (trace "schedule" "skipping to cycle " curr-cycle*) + (update-scheduler-state))) + (when (and (or (~empty running-routines*) + (~empty sleeping-routines*)) + (all [rep._ 'helper] (as cons running-routines*)) + (all [rep._ 'helper] keys.sleeping-routines*)) + (trace "schedule" "just helpers left; stopping everything") + (until (empty running-routines*) + (push (deq running-routines*) completed-routines*)) + (each (routine _) sleeping-routines* +;? (prn " " label.routine) ;? 0 + (wipe sleeping-routines*.routine) + (push routine completed-routines*))) + (detect-deadlock) + ) + +(def detect-deadlock () + (when (and (empty running-routines*) + (~empty sleeping-routines*) + (~some 'literal (map (fn(_) rep._!sleep.1) + keys.sleeping-routines*))) + (each (routine _) sleeping-routines* + (wipe sleeping-routines*.routine) + (= rep.routine!error "deadlock detected") + (push routine completed-routines*)))) + +(def die (msg) + (tr "die: " msg) + (= rep.routine*!error msg) + (iflet abort-continuation (abort-routine*) + (abort-continuation))) + +;; running a single routine + +; value of an arg or oarg, stripping away all metadata +; wish I could have this flag an error when arg is incorrectly formed +(mac v (operand) ; for value + `((,operand 0) 0)) + +; routines consist of instrs +; instrs consist of oargs, op and args +(def parse-instr (instr) + (iflet delim (pos '<- instr) + (do (when (atom (instr (+ delim 1))) + (err "operator not tokenized in @instr; maybe you need to freeze functions*?")) + (list (cut instr 0 delim) ; oargs + (v (instr (+ delim 1))) ; op + (cut instr (+ delim 2)))) ; args + (list nil (v car.instr) cdr.instr))) + +(def metadata (operand) + cdr.operand) + +(def ty (operand) + (cdr operand.0)) + +(def literal? (operand) + (unless (acons ty.operand) + (err "no type in operand @operand")) + (in ty.operand.0 'literal 'offset 'fn)) + +(def typeinfo (operand) + (or (type* ty.operand.0) + (err "unknown type @(tostring prn.operand)"))) + +; operand accessors +(def nondummy (operand) ; precondition for helpers below + (~is '_ operand)) + +; just for convenience, 'new' instruction sometimes takes a raw string and +; allocates just enough space to store it +(def not-raw-string (operand) + (~isa operand 'string)) + +(def address? (operand) + (or (is ty.operand.0 'location) + typeinfo.operand!address)) + +($:require "charterm/main.rkt") +($:require graphics/graphics) +;? ($:require "terminal-color/terminal-color/main.rkt") ;? 1 +(= Viewport nil) +; http://rosettacode.org/wiki/Terminal_control/Coloured_text#Racket +($:define (tput . xs) (system (apply ~a 'tput " " (add-between xs " "))) (void)) +($:define (foreground color) (tput 'setaf color)) +($:define (background color) (tput 'setab color)) +($:define (reset) (tput 'sgr0)) + +(= new-string-foo* nil) +(= last-print* 0) + +; run instructions from 'routine*' for 'time-slice' +(def run-for-time-slice (time-slice) + (point return + (for ninstrs 0 (< ninstrs time-slice) (++ ninstrs) + (if (empty body.routine*) (err "@stack.routine*.0!fn-name not defined")) + ; falling out of end of function = implicit reply + (while (>= pc.routine* (len body.routine*)) + (pop-stack routine*) + (if empty.routine* (return ninstrs)) + (when (pos '<- (body.routine* pc.routine*)) + (die "No results returned: @(tostring:pr (body.routine* pc.routine*))")) + (++ pc.routine*)) + (++ curr-cycle*) + (when (no ($.current-charterm)) + (let curr (seconds) + (when (~is curr last-print*) + (prn curr " " curr-cycle* " " len.running-routines*) + (= last-print* curr)))) +;? (trace "run" "-- " int-canon.memory*) ;? 1 +;? (trace "run" curr-cycle*) + (trace "run" label.routine* " " pc.routine* ": " (body.routine* pc.routine*)) +;? (trace "run" routine*) + (when (atom (body.routine* pc.routine*)) ; label +;? (tr "label") ;? 1 + (when (aand scheduler-switch-table* + (alref it (body.routine* pc.routine*))) + (++ pc.routine*) + (trace "run" label.routine* " " pc.routine* ": " "context-switch forced " abort-routine*) + ((abort-routine*))) + (++ pc.routine*) + (continue)) + (let (oarg op arg) (parse-instr (body.routine* pc.routine*)) +;? (tr op) ;? 1 + (let results + (case op + ; arithmetic + add + (+ (m arg.0) (m arg.1)) + subtract + (- (m arg.0) (m arg.1)) + multiply + (* (m arg.0) (m arg.1)) + divide + (/ (real (m arg.0)) (m arg.1)) + divide-with-remainder + (list (trunc:/ (m arg.0) (m arg.1)) + (mod (m arg.0) (m arg.1))) + + ; boolean + and + (and (m arg.0) (m arg.1)) + or + (or (m arg.0) (m arg.1)) + not + (not (m arg.0)) + + ; comparison + equal +;? (do (prn (m arg.0) " vs " (m arg.1)) + (is (m arg.0) (m arg.1)) +;? ) + not-equal + (~is (m arg.0) (m arg.1)) + less-than + (< (m arg.0) (m arg.1)) + greater-than + (> (m arg.0) (m arg.1)) + lesser-or-equal + (<= (m arg.0) (m arg.1)) + greater-or-equal + (>= (m arg.0) (m arg.1)) + + ; control flow + jump + (do (= pc.routine* (+ 1 pc.routine* (v arg.0))) + (continue)) + jump-if + (when (m arg.0) + (= pc.routine* (+ 1 pc.routine* (v arg.1))) + (continue)) + jump-unless ; convenient helper + (unless (m arg.0) + (= pc.routine* (+ 1 pc.routine* (v arg.1))) + (continue)) + + ; data management: scalars, arrays, and-records (structs) + copy + (m arg.0) + get + (with (operand (canonize arg.0) + idx (v arg.1)) + (assert (iso '(offset) (ty arg.1)) "record index @arg.1 must have type 'offset'") + (assert (< -1 idx (len typeinfo.operand!elems)) "@idx is out of bounds of record @operand") + (m `((,(apply + v.operand + (map (fn(x) (sizeof `((_ ,@x)))) + (firstn idx typeinfo.operand!elems))) + ,@typeinfo.operand!elems.idx) + (raw)))) + get-address + (with (operand (canonize arg.0) + idx (v arg.1)) + (assert (iso '(offset) (ty arg.1)) "record index @arg.1 must have type 'offset'") + (assert (< -1 idx (len typeinfo.operand!elems)) "@idx is out of bounds of record @operand") + (apply + v.operand + (map (fn(x) (sizeof `((_ ,@x)))) + (firstn idx typeinfo.operand!elems)))) + index + (withs (operand (canonize arg.0) + elemtype typeinfo.operand!elem + idx (m arg.1)) +;? (write arg.0) +;? (pr " => ") +;? (write operand) +;? (prn) + (unless (< -1 idx array-len.operand) + (die "@idx is out of bounds of array @operand")) + (m `((,(+ v.operand + 1 ; for array size + (* idx (sizeof `((_ ,@elemtype))))) + ,@elemtype) + (raw)))) + index-address + (withs (operand (canonize arg.0) + elemtype typeinfo.operand!elem + idx (m arg.1)) + (unless (< -1 idx array-len.operand) + (die "@idx is out of bounds of array @operand")) + (+ v.operand + 1 ; for array size + (* idx (sizeof `((_ ,@elemtype)))))) + new + (if (isa arg.0 'string) + ; special-case: allocate space for a literal string + (new-string arg.0) + (let type (v arg.0) + (assert (iso '(literal) (ty arg.0)) "new: second arg @arg.0 must be literal") + (if (no type*.type) (err "no such type @type")) + ; todo: initialize memory. currently racket does it for us + (if type*.type!array + (new-array type (m arg.1)) + (new-scalar type)))) + sizeof + (sizeof `((_ ,(m arg.0)))) + length + (let base arg.0 + (if (or typeinfo.base!array address?.base) + array-len.base + -1)) + + ; tagged-values require one primitive + save-type + (annotate 'record `(,((ty arg.0) 0) ,(m arg.0))) + + ; code points for characters + character-to-integer + ($.char->integer (m arg.0)) + integer-to-character + ($.integer->char (m arg.0)) + + ; multiprocessing + fork + ; args: fn globals-table args ... + (let routine (apply make-routine (m arg.0) (map m (nthcdr 3 arg))) + (= rep.routine!id ++.next-routine-id*) + (= rep.routine!globals (when (len> arg 1) (m arg.1))) + (= rep.routine!limit (when (len> arg 2) (m arg.2))) + (enq routine running-routines*) + rep.routine!id) + fork-helper + ; args: fn globals-table args ... + (let routine (apply make-routine (m arg.0) (map m (nthcdr 3 arg))) + (= rep.routine!id ++.next-routine-id*) + (set rep.routine!helper) + (= rep.routine!globals (when (len> arg 1) (m arg.1))) + (= rep.routine!limit (when (len> arg 2) (m arg.2))) + (enq routine running-routines*) + rep.routine!id) + sleep + (do + (case (v arg.0) + for-some-cycles + (let wakeup-time (+ curr-cycle* (v arg.1)) + (trace "run" label.routine* " " pc.routine* ": " "sleeping until " wakeup-time) + (= rep.routine*!sleep `(until ,wakeup-time))) + until-location-changes + (= rep.routine*!sleep `(until-location-changes ,(addr arg.1) ,(m arg.1))) + until-routine-done + (= rep.routine*!sleep `(until-routine-done ,(m arg.1))) + ; else + (die "badly formed 'sleep' call @(tostring:prn (body.routine* pc.routine*))") + ) + ((abort-routine*))) + assert + (unless (m arg.0) + (die (v arg.1))) ; other routines will be able to look at the error status + assert-false + (when (m arg.0) + (die (v arg.1))) + + ; cursor-based (text mode) interaction + cursor-mode + ;(do1 nil (system "/bin/stty -F /dev/tty raw")) + (do1 nil (if (no ($.current-charterm)) ($.open-charterm))) + retro-mode + ;(do1 nil (system "/bin/stty -F /dev/tty sane")) + (do1 nil (if ($.current-charterm) ($.close-charterm))) + clear-host-screen + (do1 nil (pr "\e[m\e[2J\e[;H")) + clear-line-on-host + (do1 nil (pr "\e[2K")) + cursor-on-host + (do1 nil (pr (+ "\e[" (m arg.0) ";" (m arg.1) "H"))) + cursor-on-host-to-next-line + (do1 nil (pr "\r\n")) + cursor-up-on-host + (do1 nil (pr (+ "\e[" (aif (len> arg 0) (or m arg.0) 1) "A"))) + cursor-down-on-host + (do1 nil (pr (+ "\e[" (aif (len> arg 0) (or m arg.0) 1) "B"))) + cursor-right-on-host + (do1 nil (pr (+ "\e[" (aif (len> arg 0) (or m arg.0) 1) "C"))) + cursor-left-on-host + (do1 nil (pr (+ "\e[" (aif (len> arg 0) (or m arg.0) 1) "D"))) + print-character-to-host + (do1 nil + (assert (in (type:m arg.0) 'char 'sym) (rep (m arg.0))) +;? (write (m arg.0)) (pr " => ") (prn (type (m arg.0))) + (if (no ($.current-charterm)) + (pr (m arg.0)) + (caselet x (m arg.0) + ; todo: test these exceptions + #\newline + (pr "\r\n") + #\backspace + ; backspace doesn't clear after moving the cursor + (pr "\b \b") + ctrl-c + (do ($.close-charterm) + (die "interrupted")) + ;else + (if (and (len> arg 2) + (m arg.2)) + (do + ($.foreground (m arg.1)) + ($.background (m arg.2)) + (pr x) + ($.reset)) + (and (len> arg 1) + (m arg.1)) + (do + ($.foreground (m arg.1)) + (pr x) + ($.reset)) + :else + (pr x)))) + ) + read-key-from-host + (if ($.current-charterm) + (and ($.charterm-byte-ready?) + (ret result ($.charterm-read-key) + (case result + ; charterm exceptions + return + (= result #\newline) + backspace + (= result #\backspace) + ))) + ($.graphics-open?) + ($.ready-key-press Viewport)) + + ; graphics + window-on + (do1 nil + ($.open-graphics) + (= Viewport ($.open-viewport (m arg.0) ; name + (m arg.1) (m arg.2)))) ; width height + window-off + (do1 nil + ($.close-viewport Viewport) ; why doesn't this close the window? works in naked racket. not racket vs arc. + ($.close-graphics) + (= Viewport nil)) + mouse-position + (aif ($.ready-mouse-click Viewport) + (let posn ($.mouse-click-posn it) + (list (annotate 'record (list ($.posn-x posn) ($.posn-y posn))) t)) + (list nil nil)) + wait-for-mouse + (let posn ($.mouse-click-posn ($.get-mouse-click Viewport)) + (list (annotate 'record (list ($.posn-x posn) ($.posn-y posn))) t)) + ; clear-screen in cursor mode above + rectangle + (do1 nil + (($.draw-solid-rectangle Viewport) + ($.make-posn (m arg.0) (m arg.1)) ; origin + (m arg.2) (m arg.3) ; width height + (m arg.4))) ; color + point + (do1 nil + (($.draw-pixel Viewport) ($.make-posn (m arg.0) (m arg.1)) + (m arg.2))) ; color + + image + (do1 nil + (($.draw-pixmap Viewport) (m arg.0) ; filename + ($.make-posn (m arg.1) (m arg.2)))) + color-at + (let pixel (($.get-color-pixel Viewport) ($.make-posn (m arg.0) (m arg.1))) + (prn ($.rgb-red pixel) " " ($.rgb-blue pixel) " " ($.rgb-green pixel)) + ($:rgb-red pixel)) + + ; debugging aides + $dump-memory + (do1 nil + (prn:repr int-canon.memory*)) + $dump-trace + (tofile arg.0 + (each (label trace) (as cons traces*) + (pr label ": " trace))) + $start-tracing + (do1 nil + (set dump-trace*)) + $stop-tracing + (do1 nil + (wipe dump-trace*)) + $dump-routine + (do1 nil + ($.close-charterm) + (prn routine*) + ($.open-charterm) + ) + $dump-channel + (do1 nil + ($.close-charterm) + (withs (x (m arg.0) + y (memory* (+ x 2))) + (prn label.routine* " -- " x " -- " (list (memory* x) + (memory* (+ x 1)) + (memory* (+ x 2))) + " -- " (list (memory* y) + (memory* (+ y 1)) + (repr:memory* (+ y 2)) + (memory* (+ y 3)) + (repr:memory* (+ y 4))))) + ($.open-charterm) + ) + $quit + (quit) + $wait-for-key-from-host + (when ($.current-charterm) + (ret result ($.charterm-read-key) + (case result + ; charterm exceptions + return + (= result #\newline) + backspace + (= result #\backspace) + ))) + $print + (do1 nil +;? (write (m arg.0)) (pr " => ") (prn (type (m arg.0))) + (if (no ($.current-charterm)) + (pr (m arg.0)) + (unless disable-debug-prints-in-console-mode* + (caselet x (m arg.0) + #\newline + (pr "\r\n") + #\backspace + ; backspace doesn't clear after moving the cursor + (pr "\b \b") + ctrl-c + (do ($.close-charterm) + (die "interrupted")) + ;else + (pr x))) + )) + $write + (do1 nil + (write (m arg.0))) + $eval + (new-string:repr:eval:read:to-arc-string (m arg.0)) +;? (let x (to-arc-string (m arg.0)) ;? 1 +;? (prn x) ;? 1 +;? (new-string:repr:eval x)) ;? 1 + + $clear-trace + (do1 nil (wipe interactive-traces*)) + $save-trace + (let x (filter-log "CCC: " len + (string + (filter-log "BBB: " len + (map [string:intersperse ": " _] + (filter-log "AAA: " len + (as cons (interactive-traces* (m arg.0))))) + ))) +;? (let x (string:map [string:intersperse ": " _] +;? (apply join +;? (map [as cons _] rev.interactive-traces*))) + (prn "computed trace; now saving to memory\n") +;? (write x)(write #\newline) ;? 1 +;? (prn x) ;? 1 + (set new-string-foo*) + (do1 (new-string x) + (wipe new-string-foo*))) + + ; first-class continuations + current-continuation + (w/uniq continuation-name + (trace "continuation" "saving @(repr rep.routine*!call-stack) to @continuation-name") + (= continuation*.continuation-name (copy rep.routine*!call-stack)) + continuation-name) + continue-from + (let continuation-name (m arg.0) + (trace "continuation" "restoring @continuation-name") + (trace "continuation" continuation*.continuation-name) + (= rep.routine*!call-stack continuation*.continuation-name) + (trace "continuation" "call stack is now @(repr rep.routine*!call-stack)") +;? (++ pc.routine*) ;? 1 + (continue)) +;? ((abort-routine*))) ;? 1 + + ; user-defined functions + next-input + (let idx caller-arg-idx.routine* + (++ caller-arg-idx.routine*) + (trace "arg" repr.arg " " idx " " (repr caller-args.routine*)) + (if (len> caller-args.routine* idx) + (list caller-args.routine*.idx t) + (list nil nil))) + input + (do (assert (iso '(literal) (ty arg.0))) + (= caller-arg-idx.routine* (v arg.0)) + (let idx caller-arg-idx.routine* + (++ caller-arg-idx.routine*) + (trace "arg" repr.arg " " idx " " (repr caller-args.routine*)) + (if (len> caller-args.routine* idx) + (list caller-args.routine*.idx t) + (list nil nil)))) + rewind-inputs + (do1 nil + (= caller-arg-idx.routine* 0)) + ; type and otype won't always easily compile. be careful. + type + (ty (caller-operands.routine* (v arg.0))) + otype + (ty (caller-results.routine* (v arg.0))) + prepare-reply + (prepare-reply arg) + reply + (do (when arg + (prepare-reply arg)) + (with (results results.routine* + reply-args reply-args.routine*) + (pop-stack routine*) + (if empty.routine* (return ninstrs)) + (let (call-oargs _ call-args) (parse-instr (body.routine* pc.routine*)) +;? (trace "reply" repr.arg " " repr.call-oargs) ;? 1 + (each (dest reply-arg val) (zip call-oargs reply-args results) + (trace "run" label.routine* " " pc.routine* ": " repr.val " => " dest) + (when nondummy.dest + (whenlet argidx (alref metadata.reply-arg 'same-as-arg) + (unless (is v.dest (v call-args.argidx)) + (die "'same-as-arg' output arg in @repr.reply-args can't bind to @repr.call-oargs"))) + (setm dest val)))) + (++ pc.routine*) + (while (>= pc.routine* (len body.routine*)) + (pop-stack routine*) + (when empty.routine* (return ninstrs)) + (++ pc.routine*)) + (continue))) + ; else try to call as a user-defined function + (do (if function*.op + (with (callee-args (accum yield + (each a arg + (yield (m a)))) + callee-operands (accum yield + (each a arg + (yield a))) + callee-results (accum yield + (each a oarg + (yield a)))) + (push-stack routine* op) + (= caller-args.routine* callee-args) + (= caller-operands.routine* callee-operands) + (= caller-results.routine* callee-results)) + (err "no such op @op")) + (continue)) + ) + ; opcode generated some 'results' + ; copy to output args + (if (acons results) + (each (dest val) (zip oarg results) + (unless (is dest '_) + (trace "run" label.routine* " " pc.routine* ": " repr.val " => " dest) + (setm dest val))) + (when oarg ; must be a list + (trace "run" label.routine* " " pc.routine* ": " repr.results " => " oarg.0) + (setm oarg.0 results))) + ) + (++ pc.routine*))) + (return time-slice))) + +(def prepare-reply (args) + (= results.routine* + (accum yield + (each a args + (yield (m a))))) + (= reply-args.routine* args)) + +; helpers for memory access respecting +; immediate addressing - 'literal' and 'offset' +; direct addressing - default +; indirect addressing - 'deref' +; relative addressing - if routine* has 'default-space' + +(def m (loc) ; read memory, respecting metadata + (point return + (when (literal? loc) + (return v.loc)) + (when (is v.loc 'default-space) + (return rep.routine*!call-stack.0!default-space)) +;? (trace "mem" loc) ;? 1 + (assert (isa v.loc 'int) "addresses must be numeric (problem in convert-names?): @repr.loc") + (ret result + (with (n sizeof.loc + addr addr.loc) +;? (trace "mem" "reading " n " locations starting at " addr) ;? 1 + (if (is 1 n) + memory*.addr + :else + (annotate 'record + (map memory* (addrs addr n))))) + (trace "mem" loc " => " result)))) + +(def setm (loc val) ; set memory, respecting metadata +;? (tr 111) + (point return +;? (tr 112) + (when (is v.loc 'default-space) + (assert (is 1 sizeof.loc) "can't store compounds in default-space @loc") + (= rep.routine*!call-stack.0!default-space val) + (return)) +;? (tr 120) + (assert (isa v.loc 'int) "can't store to non-numeric address (problem in convert-names?)") +;? (trace "mem" loc " <= " repr.val) ;? 1 + (with (n (if (isa val 'record) (len rep.val) 1) + addr addr.loc + typ typeof.loc) +;? (trace "mem" "size of " loc " is " n) ;? 1 + (assert n "setm: can't compute type of @loc") + (assert addr "setm: null pointer @loc") + (if (is 1 n) + (do (assert (~isa val 'record) "setm: record of size 1 @(tostring prn.val)") + (trace "mem" loc ": " addr " <= " repr.val) + (= memory*.addr val)) + (do (if type*.typ!array + ; size check for arrays + (when (~is n + (+ 1 ; array length + (* rep.val.0 (sizeof `((_ ,@type*.typ!elem)))))) + (die "writing invalid array @(tostring prn.val)")) + ; size check for non-arrays + (when (~is sizeof.loc n) + (die "writing to incorrect size @(tostring pr.val) => @loc"))) + (let addrs (addrs addr n) + (each (dest src) (zip addrs rep.val) + (trace "mem" loc ": " dest " <= " repr.src) + (= memory*.dest src)))))))) + +(def typeof (operand) + (let loc absolutize.operand + (while (pos '(deref) metadata.loc) + (zap deref loc)) + ty.loc.0)) + +(def addr (operand) + (v canonize.operand)) + +(def addrs (n sz) + (accum yield + (repeat sz + (yield n) + (++ n)))) + +(def canonize (operand) +;? (tr "0: @operand") + (ret operand +;? (prn "1: " operand) +;? (tr "1: " operand) ; todo: why does this die? + (zap absolutize operand) +;? (tr "2: @repr.operand") + (while (pos '(deref) metadata.operand) + (zap deref operand) +;? (tr "3: @repr.operand") + ))) + +(def array-len (operand) + (trace "array-len" operand) + (zap canonize operand) + (if typeinfo.operand!array + (m `((,v.operand integer) ,@metadata.operand)) + :else + (err "can't take len of non-array @operand"))) + +(def sizeof (x) +;? (trace "sizeof" x) ;? 1 + (assert acons.x) + (zap canonize x) + (point return +;? (tr "sizeof: checking @x for array") + (when typeinfo.x!array +;? (tr "sizeof: @x is an array") + (assert (~is '_ v.x) "sizeof: arrays require a specific variable") + (return (+ 1 (* array-len.x (sizeof `((_ ,@typeinfo.x!elem))))))) +;? (tr "sizeof: not an array") + (when typeinfo.x!and-record +;? (tr "sizeof: @x is an and-record") + (return (sum idfn + (accum yield + (each elem typeinfo.x!elems + (yield (sizeof `((_ ,@elem))))))))) +;? (tr "sizeof: @x is a primitive") + (return typeinfo.x!size))) + +(def absolutize (operand) + (if (no routine*) + operand + (in v.operand '_ 'default-space) + operand + (pos '(raw) metadata.operand) + operand + (is 'global space.operand) + (aif rep.routine*!globals + `((,(+ it 1 v.operand) ,@(cdr operand.0)) + ,@(rem [caris _ 'space] metadata.operand) + (raw)) + (die "routine has no globals: @operand")) + :else + (iflet base rep.routine*!call-stack.0!default-space + (space-base (rem [caris _ 'space] operand) + base + space.operand) + operand))) + +(def space-base (operand base space) +;? (prn operand " " base) ;? 1 + (if (is 0 space) + ; base case + (if (< v.operand memory*.base) + `((,(+ base 1 v.operand) ,@(cdr operand.0)) + ,@metadata.operand + (raw)) + (die "no room for var @operand in routine of size @memory*.base")) + ; recursive case + (space-base operand (memory* (+ base 1)) ; location 0 points to next space + (- space 1)))) + +(def space (operand) + (or (alref metadata.operand 'space) + 0)) + +(def deref (operand) + (assert (pos '(deref) metadata.operand)) + (assert address?.operand) + (cons `(,(memory* v.operand) ,@typeinfo.operand!elem) + (drop-one '(deref) metadata.operand))) + +(def drop-one (f x) + (when acons.x ; proper lists only + (if (testify.f car.x) + cdr.x + (cons car.x (drop-one f cdr.x))))) + +; memory allocation + +(def alloc (sz) + (when (> sz (- rep.routine*!alloc-max rep.routine*!alloc)) + (let curr-alloc Memory-allocated-until + (= rep.routine*!alloc curr-alloc) + (++ Memory-allocated-until Allocation-chunk) + (= rep.routine*!alloc-max Memory-allocated-until))) + (ret result rep.routine*!alloc + (++ rep.routine*!alloc sz))) + +(def new-scalar (type) +;? (tr "new scalar: @type") + (alloc (sizeof `((_ ,type))))) + +(def new-array (type size) +;? (tr "new array: @type @size") + (ret result (alloc (+ 1 (* (sizeof `((_ ,@type*.type!elem))) size))) + (= memory*.result size))) + +(def new-string (literal-string) +;? (tr "new string: @literal-string") + (ret result (alloc (+ 1 len.literal-string)) + (= memory*.result len.literal-string) + (on c literal-string + (when (and new-string-foo* (is 0 (mod index 100))) + (prn index " " repr.c)) + (= (memory* (+ result 1 index)) c)))) + +(def to-arc-string (string-address) + (let len (memory* string-address) + (string:map memory* (range (+ string-address 1) + (+ string-address len))))) + +;; desugar structured assembly based on blocks + +(def convert-braces (instrs) +;? (prn "convert-braces " instrs) + (let locs () ; list of information on each brace: (open/close pc) + (let pc 0 + (loop (instrs instrs) + (each instr instrs +;? (tr instr) + (if (or atom.instr (~is 'begin instr.0)) ; label or regular instruction + (do + (trace "c{0" pc " " instr " -- " locs) + (++ pc)) + ; hack: racket replaces braces with parens, so we need the + ; keyword 'begin' to delimit blocks. + ; ultimately there'll be no nesting and braces will just be + ; in an instr by themselves. + :else ; brace + (do + (push `(open ,pc) locs) + (recur cdr.instr) + (push `(close ,pc) locs)))))) + (zap rev locs) +;? (tr "-") + (with (pc 0 + stack ()) ; elems are pcs + (accum yield + (loop (instrs instrs) + (each instr instrs +;? (tr "- " instr) + (point continue + (when (atom instr) ; label + (yield instr) + (++ pc) + (continue)) + (when (is car.instr 'begin) + (push pc stack) + (recur cdr.instr) + (pop stack) + (continue)) + (with ((oarg op arg) (parse-instr instr) + yield-new-instr (fn (new-instr) + (trace "c{1" "@pc X " instr " => " new-instr) + (yield new-instr)) + yield-unchanged (fn () + (trace "c{1" "@pc ✓ " instr) + (yield instr))) + (when (in op 'break 'break-if 'break-unless 'loop 'loop-if 'loop-unless) + (assert (is oarg nil) "@op: can't take oarg in @instr")) + (case op + break + (yield-new-instr `(((jump)) ((,(close-offset pc locs (and arg (v arg.0))) offset)))) + break-if + (yield-new-instr `(((jump-if)) ,arg.0 ((,(close-offset pc locs (and cdr.arg (v arg.1))) offset)))) + break-unless + (yield-new-instr `(((jump-unless)) ,arg.0 ((,(close-offset pc locs (and cdr.arg (v arg.1))) offset)))) + loop + (yield-new-instr `(((jump)) ((,(open-offset pc stack (and arg (v arg.0))) offset)))) + loop-if + (yield-new-instr `(((jump-if)) ,arg.0 ((,(open-offset pc stack (and cdr.arg (v arg.1))) offset)))) + loop-unless + (yield-new-instr `(((jump-unless)) ,arg.0 ((,(open-offset pc stack (and cdr.arg (v arg.1))) offset)))) + ;else + (yield-unchanged))) + (++ pc)))))))) + +(def close-offset (pc locs nblocks) + (or= nblocks 1) +;? (tr nblocks) + (point return +;? (tr "close " pc " " locs) + (let stacksize 0 + (each (state loc) locs + (point continue +;? (tr stacksize "/" done " " state " " loc) + (when (<= loc pc) + (continue)) +;? (tr "process " stacksize loc) + (if (is 'open state) (++ stacksize) (-- stacksize)) + ; last time +;? (tr "process2 " stacksize loc) + (when (is stacksize (* -1 nblocks)) +;? (tr "close now " loc) + (return (- loc pc 1)))))))) + +(def open-offset (pc stack nblocks) + (or= nblocks 1) + (- (stack (- nblocks 1)) 1 pc)) + +;; convert jump targets to offsets + +(def convert-labels (instrs) +;? (tr "convert-labels " instrs) + (let labels (table) + (let pc 0 + (each instr instrs + (when (~acons instr) +;? (tr "label " pc) + (= labels.instr pc)) + (++ pc))) + (let pc 0 + (each instr instrs + (when (and acons.instr + (acons car.instr) + (in (v car.instr) 'jump 'jump-if 'jump-unless)) + (each arg cdr.instr +;? (tr "trying " arg " " ty.arg ": " v.arg " => " (labels v.arg)) + (when (and (is ty.arg.0 'offset) + (isa v.arg 'sym) + (labels v.arg)) + (= v.arg (- (labels v.arg) pc 1))))) + (++ pc)))) + instrs) + +;; convert symbolic names to raw memory locations + +(def add-next-space-generator (instrs name) +;? (prn "== @name") + (each instr instrs + (when acons.instr + (let (oargs op args) (parse-instr instr) + (each oarg oargs + (when (and (nondummy oarg) + (is v.oarg 0) + (iso ty.oarg '(space-address))) + (assert (or (no next-space-generator*.name) + (is next-space-generator*.name (alref oarg 'names))) + "function can have only one next-space-generator environment") + (tr "next-space-generator of @name is @(alref oarg 'names)") + (= next-space-generator*.name (alref oarg 'names)))))))) + +; just a helper for testing; in practice we unbundle assign-names-to-location +; and replace-names-with-location. +(def convert-names (instrs (o name)) +;? (tr "convert-names " instrs) + (= location*.name (assign-names-to-location instrs name)) +;? (tr "save names for function @name: @(tostring:pr location*.name)") ;? 1 + (replace-names-with-location instrs name)) + +(def assign-names-to-location (instrs name (o init-locations)) + (trace "cn0" "convert-names in @name") +;? (prn name ": " location*) ;? 1 + (point return + (ret location (or init-locations (table)) + ; if default-space in first instruction has a name, begin with its bindings + (when (acons instrs.0) ; not a label + (let first-oarg-of-first-instr instrs.0.0 ; hack: assumes the standard default-space boilerplate + (when (and (nondummy first-oarg-of-first-instr) + (is 'default-space (v first-oarg-of-first-instr)) + (assoc 'names metadata.first-oarg-of-first-instr)) + (let old-names (location*:alref metadata.first-oarg-of-first-instr 'names) + (unless old-names +;? (prn "@name requires bindings for @(alref metadata.first-oarg-of-first-instr 'names) which aren't computed yet. Waiting.") ;? 1 + (return nil)) + (= location copy.old-names))))) ; assumption: we've already converted names for 'it' +;? (unless empty.location (prn location)) ;? 2 + (with (isa-field (table) + idx (+ 1 ; 0 always reserved for next space + (or (apply max vals.location) ; skip past bindings already shared from elsewhere + 0)) + already-location (copy location) + ) + (each instr instrs + (point continue + (when atom.instr + (continue)) + (trace "cn0" instr " " canon.location " " canon.isa-field) + (let (oargs op args) (parse-instr instr) +;? (tr "about to rename args: @op") + (when (in op 'get 'get-address) + ; special case: map field offset by looking up type table + (with (basetype (typeof args.0) + field (v args.1)) +;? (tr 111 " " args.0 " " basetype) + (assert type*.basetype!and-record "get on non-record @args.0") +;? (tr 112) + (trace "cn0" "field-access @field in @args.0 of type @basetype") + (when (isa field 'sym) + (unless (already-location field) + (assert (or (~location field) isa-field.field) "field @args.1 is also a variable")) + (when (~location field) + (trace "cn0" "new field; computing location") +;? (tr "aa " type*.basetype) + (assert type*.basetype!fields "no field names available for @instr") +;? (tr "bb") + (iflet idx (pos field type*.basetype!fields) + (do (set isa-field.field) + (trace "cn0" "field location @idx") + (= location.field idx)) + (assert nil "couldn't find field in @instr")))))) + ; map args to location indices + (each arg args + (trace "cn0" "checking arg " arg) + (when (and nondummy.arg not-raw-string.arg (~literal? arg)) + (assert (~isa-field v.arg) "arg @arg is also a field name") + (when (maybe-add arg location idx) + ; todo: test this + (err "use before set: @arg")))) +;? (tr "about to rename oargs") + ; map oargs to location indices + (each arg oargs + (trace "cn0" "checking oarg " arg) + (when (and nondummy.arg not-raw-string.arg) + (assert (~isa-field v.arg) "oarg @arg is also a field name") + (when (maybe-add arg location idx) + (trace "cn0" "location for oarg " arg ": " idx) + ; todo: can't allocate arrays on the stack + (++ idx (sizeof `((_ ,@ty.arg)))))))))))))) + +(def replace-names-with-location (instrs name) + (each instr instrs + (when (acons instr) + (let (oargs op args) (parse-instr instr) + (each arg args + (convert-name arg name)) + (each arg oargs + (convert-name arg name))))) + (each instr instrs + (trace "cn1" instr)) + instrs) + +(= allow-raw-addresses* nil) +(def check-default-space (instrs name) + (unless allow-raw-addresses* + (let oarg-names (accum yield + (each (oargs _ _) (map parse-instr (keep acons ; non-label + instrs)) + (each oarg oargs + (when nondummy.oarg + (yield v.oarg))))) + (when (~pos 'default-space oarg-names) + (prn "function @name has no default-space"))))) + +; assign an index to an arg +(def maybe-add (arg location idx) + (trace "maybe-add" arg) + (when (and nondummy.arg +;? (prn arg " " (assoc 'space arg)) + (~assoc 'space arg) + (~literal? arg) + (~location v.arg) + (isa v.arg 'sym) + (~in v.arg 'nil 'default-space) + (~pos '(raw) metadata.arg)) + (= (location v.arg) idx))) + +; convert the arg to corresponding index +(def convert-name (arg default-name) +;? (prn "111 @arg @default-name") + (when (and nondummy.arg not-raw-string.arg + (~is ty.arg.0 'literal)) ; can't use 'literal?' because we want to rename offsets +;? (prn "112 @arg") + (let name (space-to-name arg default-name) +;? (prn "113 @arg @name @keys.location* @(tostring:pr location*.name)") +;? (when (is arg '((y integer) (space 1))) +;? (prn "@arg => @name")) + (when (aand location*.name (it v.arg)) +;? (prn 114) + (zap location*.name v.arg)) +;? (prn 115) + ))) + +(def space-to-name (arg default-name) + (ret name default-name + (when (~is space.arg 'global) + (repeat space.arg + (zap next-space-generator* name))))) + +(proc check-numeric-address (instrs name) + (unless allow-raw-addresses* + (on instr instrs + (when acons.instr ; not a label + (let (oargs op args) (parse-instr instr) + (each arg oargs + (when (and acons.arg ; not dummy _ or raw string + (isa v.arg 'int) + (~is v.arg 0) + (~pos '(raw) metadata.arg) + (~literal? arg)) + (prn "using a raw integer address @repr.arg in @name (instruction #@index)"))) + (each arg args + (when (and acons.arg ; not dummy _ or raw string + (isa v.arg 'int) + (~is v.arg 0) + (~pos '(raw) metadata.arg) + (~literal? arg)) + (prn "using a raw integer address @repr.arg in @name (instruction #@index)")))))))) + +;; literate tangling system for reordering code + +(def convert-quotes (instrs) + (let deferred (queue) + (each instr instrs + (when (acons instr) + (case instr.0 + defer + (let (q qinstrs) instr.1 + (assert (is 'make-br-fn q) "defer: first arg must be [quoted]") + (each qinstr qinstrs + (enq qinstr deferred)))))) + (accum yield + (each instr instrs + (if atom.instr ; label + (yield instr) + (is instr.0 'defer) + nil ; skip + (is instr.0 'reply) + (do + (when cdr.instr ; return values + (= instr.0 'prepare-reply) + (yield instr)) + (each instr (as cons deferred) + (yield instr)) + (yield '(reply))) + :else + (yield instr))) + (each instr (as cons deferred) + (yield instr))))) + +(on-init + (= before* (table)) ; label -> queue of fragments + (= after* (table))) ; label -> list of fragments + +; see add-code below for adding to before* and after* + +(def insert-code (instrs (o name)) +;? (tr "insert-code " instrs) + (loop (instrs instrs) + (accum yield + (each instr instrs + (if (and (acons instr) (~is 'begin car.instr)) + ; simple instruction + (yield instr) + (and (acons instr) (is 'begin car.instr)) + ; block + (yield `{begin ,@(recur cdr.instr)}) + (atom instr) + ; label + (do +;? (prn "tangling " instr) + (each fragment (as cons (or (and name (before* (sym:string name '/ instr))) + before*.instr)) + (each instr fragment + (yield instr))) + (yield instr) + (each fragment (or (and name (after* (sym:string name '/ instr))) + after*.instr) + (each instr fragment + (yield instr))))))))) + +;; loading code into the virtual machine + +(def add-code (forms) + (each (op . rest) forms + (case op + ; function [ ] + ; don't apply our lightweight tools just yet + function! + (let (name (_make-br-fn body)) rest + (assert (is 'make-br-fn _make-br-fn)) + (= name (v tokenize-arg.name)) + (= function*.name body)) + function + (let (name (_make-br-fn body)) rest + (assert (is 'make-br-fn _make-br-fn)) + (= name (v tokenize-arg.name)) + (when function*.name + (prn "adding new clause to @name")) + (= function*.name (join body function*.name))) + + ; and-record [ ] + and-record + (let (name (_make-br-fn fields)) rest + (assert (is 'make-br-fn _make-br-fn)) + (= name (v tokenize-arg.name)) + (let fields (map tokenize-arg fields) + (= type*.name (obj size len.fields + and-record t + ; dump all metadata for now except field name and type + elems (map cdar fields) + fields (map caar fields))))) + + ; primitive + primitive + (let (name) rest + (= name (v tokenize-arg.name)) + (= type*.name (obj size 1))) + + ; address + address + (let (name types) rest + (= name (v tokenize-arg.name)) + (= type*.name (obj size 1 + address t + elem types))) + + ; array + array + (let (name types) rest + (= name (v tokenize-arg.name)) + (= type*.name (obj array t + elem types))) + + ; before