# AMC Forth #### A C# based implementation of Chuck Moore's Forth computer language running inside the [Godot game engine](https://godotengine.org/). AMC Forth is a C#-based implementation of Chuck Moore's Forth computer language, running inside the [Godot game engine](https://godotengine.org/). It was created to be the in-flight computer for the AMC SkyDart, a fictional spacecraft featured in [Tungsten Moon](https://store.steampowered.com/app/3104900/Tungsten_Moon/). Spun off into its own project on [Github](https://github.com/Eccentric-Anomalies/AMC-Forth), AMC Forth is available for public use under the generous [MIT License](https://github.com/Eccentric-Anomalies/AMC-Forth/blob/main/LICENSE). ## How to Use AMC Forth AMC Forth is suited to inclusion in your Godot project using the Godot add-on path: `addons/amc_forth`. The addon also includes two terminal scripts, a local DEC VT-100 subset script, `forth_term_local.gd`, and a simple telnet script, `forth_term_telnet.gd`. The local terminal implementation can be used together with a 2D canvas shader, `terminal_canvas.gdshader`, or a 3D shader, `terminal_3d.gdshader`, to reproduce the appearance of a VT-100 terminal screen. Example 2D and 3D terminal+Forth scenes are available at the root of the [Github repository for AMC Forth](https://github.com/Eccentric-Anomalies/AMC-Forth). Please refer to those examples when trying to use AMC Forth in your own 2D or 3D Godot project! ## AMCForth Word List AMCForth currently implements an extensive set of [built-in words](./docs/builtins.md). Each word links to a page with a short description of what it does how it uses the Forth data stack. For debugging purposes, there is also a link to the C# class that implement's the word's functionality, as well as the values of its execution tokens for both interpreted and compiled contexts. You can automatically regenerate the built-in words documentation by instantiating the `Docs` class in your scene. For example: ```gdscript add_child(Docs.new()) ``` ## AMCForth Test Suite This repository also includes a [Forth test suite](./tests.fth) that can be [INCLUDE](./docs/Include.md)-ed to validate the correctness of the implementation. When executed, a successful run produces a list of tested words. Certain words additional text that the user can verify for correctness. You can automatically perform the AMCForth test suite and update an output file by instantiating the `Test` class in your scene. For example: ```gdscript add_child(Test.new()) ``` Add the [test output file](tests/tests_out.txt) in your version control system to alert you when the test results have changed. ## The AMCForth Class (C#) Steps to use the AMC Forth engine in your project: Copy or import the `addons/amc_forth` directory into your C# Godot project. You may use gdscript or C# with AMC Forth. In your scene file, instantiate and initialize the AMCForth class from within _init() or equivalent: ```gdscript var forth = AMCForth.new() forth.Initialize(self) ``` You may see system warnings when your Godot app closes because AMC Forth typically will have a thread waiting on a semaphore when the Forth instance is destroyed. To avoid this problem, call the `Cleanup` method before closing your app. You can do this by explicitly handling the window close request for your app: ```gdscript func _notification(what: int) -> void: if what == NOTIFICATION_WM_CLOSE_REQUEST: forth.Cleanup() # call Cleanup on the forth instance get_tree().quit() # default window close behavior ``` Output from AMC Forth is handled through a signal named `TerminalOut`. Your code would include something like: ```gdscript forth.TerminalOut.connect(_on_forth_output) ``` connect the output signal to your own code, where `_on_forth_output` looks like: ```gdscript func _on_forth_output(_text: String) -> void: ``` To send terminal commands to the Forth engine, call the `TerminalIn` method. For example, to issue the [WORDS](docs/Words.md) command: ```gdsript forth.TerminalIn("WORDS" + Terminal.CR) ``` You must follow any single command with the `CR` character. ### Executing External Code Use the `TerminalIn` method to execute an external Forth source file using the [INCLUDE](docs/Include.md) command: ```gdsript forth.TerminalIn("system.fth" + Terminal.CR) ``` Note that the `INCLUDE` word will look for your file in `user://` first, then `res://`. To check for existence of a source file, use [FILE-STATUS](docs/FileStatus.md) first. ## AMCForth Input and Output The AMC Forth engine mimics an abstract microcomputer, with memory-mapped I/O. By default, there are 128 32-bit input ports and 128 32-bit output ports. To use this, define your own input and output signals, e.g.: ```gdscript signal my_output_signal(value: int) signal my_input_signal(value: int) ``` ### Configuring AMCForth Inputs Before sending data to an AMCForth input port, connect the port to your signal: ```gdscript # connect my_input_signal to input port 100 forth.AddInputSignal(100, my_input_signal) ``` Then when you have data to send to AMCForth, emit the signal: ```gdscript my_input_signal.emit(<32-bit integer>) ``` In your Forth code, you can register input ports and route them to custom handlers using the two custom AMC Forth words: [LISTEN](docs/Listen.md) and [LISTENX](docs/ListenX.md). #### Using LISTEN outside of colon definitions Use LISTEN when you want to establish input port routing at the time your Forth code is parsed. In other words, use it *outside* of a colon definition. There are three input parameters for LISTEN (in the order they appear or are pushed on the stack): 1. The input port (0-128) that will generate events when data is received. 2. The Queue Mode determines how received data are enqueued. 3. The word (following LISTEN) that will execute when a data value is received. The Forth word that is named as the event handler will execute with the received data value already on the stack. The Queue Mode is an integer value 0, 1, or 2, which controls how the incoming event is enqueued before executing the handler. Queue Modes values are: * 0 - Incoming values are always enqueued, even there are unprocessed events with the same port and/or value waiting to be processed. This may result in multiple consecutive executions of the handler function with the exact same data (ideal for things like handling an incoming character stream). * 1 - Incoming values are always enqueued unless there is already an unprocessed event for the port with the same value as the new event, or if there is no previously queued event but the last stored value is the same as the new value. Multiple events with the same value will not cause multiple executions of the event handler (ideal when the receiver only needs to know when a value has changed). * 2 - Incoming values replace any previous enqueued values on this port. A new event is enqueued if there is no previously queued value and the new value is different from the last stored value (ideal when the receiver only needs to know what the current value is). ```forth : PRINTEVENT . ; \ Define PRINTEVENT to just print an integer on the terminal. 100 0 LISTEN PRINTEVENT \ Values on input #100 will always be queued and printed to the terminal. ``` With this, every time you send an integer on `my_input_signal`, the value will be displayed on the AMC Forth terminal. #### Using LISTENX inside of colon definitions If you need to establish an input connection from *inside* a colon definition, then you should use LISTENX. There are three input parameters for LISTENX (in the order they are pushed on the stack): 1. The *execution token* of the word that will execute when a data value is received. 2. The input port (0-128) that will generate events when data is received. 3. The Queue Mode determines how received data are enqueued (see the description for LISTEN, above). The previous example using LISTENX: ```forth \ Define PRINTEVENT to just print an integer on the terminal. : PRINTEVENT . ; \ Some definition in which input #100 must be connected to PRINTEVENT : EXAMPLE-DEFINITION ['] PRINTEVENT 100 0 LISTENX ; ``` Note that you can also use LISTENX *outside* of colon definitions: ```forth : PRINTEVENT . ; \ Define PRINTEVENT to just print an integer on the terminal. ' PRINTEVENT 100 0 LISTENX \ Values on input #100 will route to PRINTEVENT ``` ### Receiving Input Data Without Listening Even if LISTEN is not called for an input port, new values may still be sent to the port. Values sent in this way are retrieved by polling the port using the [IN](docs/In.md) word. ### Configuring AMCForth Outputs Receiving data from AMCForth works in a similar fashion. First connect your signal to the output port: ```gdscript # connect my_output_signal to output port 99 forth.AddOutputSignal(99, my_output_signal) # and connect the signal to a gdscript handler function my_output_signal.connect(my_signal_handler) ``` where `my_signal_handler` looks like this: ```gdscript func my_signal_handler(value: int): print(value) # something more interesting, perhaps? ``` Then when AMCForth code sends an integer to output port 99, the value is printed to the Godot console. In your Forth code, you can send data to an output port using the custom AMC Forth word: [OUT](docs/Out.md) ```forth 123 99 OUT \ Sends the value 123 to port 99 ``` With this, every time you `OUT` a value to port 99 from inside AMCForth, it will print to the Godot console. ## Timers AMCForth also includes custom words for creating and stopping periodic timers. For example, [P-TIMER](docs/PTimer.md) is used *outside* of a colon definition, creates a periodic timer and associates it with a built-in or custom word: ```forth : TICK ." tick" ; \ A word that displays 'tick' to the Forth terminal. 5 1000 P-TIMER TICK \ Create timer with ID=5, that prints 'tick' once per second. ``` To do the same thing from *inside* a colon definition, use [P-TIMERX](docs/PTimerX.md): ```forth : TICK ." tick" ; \ A word that displays 'tick' to the Forth terminal. \ Some definition in which timer ID=5 will execute TICK once per second : EXAMPLE-DEFINITION ['] TICK 5 1000 P-TIMERX ; ``` To cancel a timer, use the custom word: [P-STOP](docs/PStop.md) ```forth 5 P-STOP \ Stops the timer with ID=5 ``` ### One-Shot Timers If you need a timer that only works once, just cancel it inside the handler function: ```forth : ONE-SHOT 5 P-STOP ." time is up!" ; \ Create timer with ID=5, that execute once after a 1-sec. delay. 5 1000 P-TIMER ONE-SHOT ``` ## Saving and Restoring Runtime State AMCForth `LoadSnapshot` and `SaveSnapshot` methods read and write an image of the system RAM to `user://ForthState.cfg`. Within Forth itself, the custom built-in words [LOAD-SNAP](docs/LoadSnap.md) and [SAVE-SNAP](docs/SaveSnap.md) do the same thing. If you are using AMCForth within your own application, you may want to use the application ConfigFile object by using: ```c# LoadState(ConfigFile Config, string Section = "ram", string Key = "image") ``` and ```c# SaveState(ConfigFile Config, string Section = "ram", string Key = "image") ``` You can use the default Section and Key identifiers or define your own. So, for example: ```gdscript var _forth: AMCForth var _config = ConfigFile.new() var _path = "user://appdata.cfg" func _ready() -> void: _forth = AMCForth.new() _forth.Initialize(self) func load() -> void: var err = _config.load(_path) _forth.LoadState(_config, "computer", "memory") func save() -> void: _forth.SaveState(_config, "computer", "memory") _config.save(_path) ``` ## Forth Terminals ### Local Terminal The script `forth_term_local.gd` implements a local implementation of a VT-100 command subset, to use inside a Godot scene (see the example 2D and 3D scenes at the root of the [AMC Forth Github Repository](https://github.com/Eccentric-Anomalies/AMC-Forth)). ### Telnet Terminal The script `forth_term_telnet.gd` implements a simple telnet server that should work seamlessly with the PuTTY telnet client. Telnet and local terminals may be used by themselves, or together at the same time.