From e41b124320011cb1451f9869710a110058ee95aa Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Sat, 11 Mar 2023 10:21:51 -0500 Subject: [PATCH] update documentation --- README.md | 18 +++++++------ libalee/corewords.cpp | 2 +- libalee/corewords.hpp | 15 ++++++++--- libalee/ctype.hpp | 4 +++ libalee/dictionary.hpp | 58 +++++++++++++++++++++++++++++++++++++----- libalee/parser.cpp | 2 +- libalee/parser.hpp | 18 +++++++++++++ libalee/state.hpp | 36 +++++++++++++++++++------- libalee/types.hpp | 19 +++++++++----- 9 files changed, 138 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 2eade1c..d262a87 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Alee implements a large majority of the "core" and "core extension" [word sets]( Running Alee without `core.fth` or `core-ext.fth` passed as arguments will leave you with a minimal word set. The `standalone` target will package the `core.fth` dictionary into the program. **Missing** core features: -* Pictured numeric output conversion `<# #>` +* Pictured numeric output conversion (e.g. `<# #>`) * Words for unsigned integers: `U. U< UM* UM/MOD` * `>NUMBER` * `FIND` @@ -31,12 +31,14 @@ Alee aims for compliance with common Forth standards like Forth 2012 and ANS For ## Building -Alee requires `make` and a C++17-compatible compiler. +Alee requires `make` and a C++17-compatible compiler. Simply running `make` will produce the `libalee.a` library and a REPL binary named `alee`. Note that this binary has no built-in libraries; these can be passed in by calling `./alee core.fth core-ext.fth`. -To compile, simply run the `make` command. This will produce a library, `libalee.a`, as well as a REPL binary named `alee`. -A `small` target exists that optimizes the build for size. -A `fast` target exists that optimizes for maximum performance on the host system. -The `standalone` target will produce a `alee-standalone` binary that has the core dictionary built in. -The `msp430` target builds Alee for the [MSP430G2553](https://www.ti.com/product/MSP430G2553) microcontroller. This target requires `standalone` for the core dictionary. +There are other build targets: + +* `small`: Optimize for minimal binary size. +* `fast`: Optimize for maximum performance on the host system. +* `standalone`: Builds the core dictionary (`core.fth`) into the binary. +* `msp430`: Builds a binary for the [MSP430G2553](https://www.ti.com/product/MSP430G2553) microcontroller. The `standalone` target must be built first for the core dictionary. + +If building for a new platform, see `Makefile`, `types.hpp`, and `state.hpp` for available configuration options. -Configurable constants and types are defined either in the Makefile or in `types.hpp`. diff --git a/libalee/corewords.cpp b/libalee/corewords.cpp index b8a415e..1a09936 100644 --- a/libalee/corewords.cpp +++ b/libalee/corewords.cpp @@ -44,7 +44,7 @@ void tick(State& state) auto word = getword(state); if (auto j = state.dict.find(word); j > 0) { state.push(state.dict.getexec(j)); - auto imm = state.dict.read(j) & CoreWords::Immediate; + auto imm = state.dict.read(j) & Dictionary::Immediate; state.push(imm ? 1 : -1); } else if (auto i = CoreWords::findi(state, word); i >= 0) { state.push(i); diff --git a/libalee/corewords.hpp b/libalee/corewords.hpp index 33f4fc5..5798f59 100644 --- a/libalee/corewords.hpp +++ b/libalee/corewords.hpp @@ -22,19 +22,28 @@ #include "types.hpp" #include "state.hpp" +/** + * To be implemented by the user, this function is called when the `sys` word + * is executed. + */ void user_sys(State&); class CoreWords { public: constexpr static std::size_t WordCount = 33; - - constexpr static Cell Immediate = (1 << 5); - constexpr static int Semicolon = 26; + /** + * Finds execution token that corresponds to the given word. + * Returns -1 if not found. + */ static int findi(const char *); static int findi(State&, Word); + + /** + * Executes the given CoreWord execution token using the given state. + */ static void run(unsigned int, State&); constexpr static char wordsarr[] = diff --git a/libalee/ctype.hpp b/libalee/ctype.hpp index d1a1d0b..dfc6f50 100644 --- a/libalee/ctype.hpp +++ b/libalee/ctype.hpp @@ -19,6 +19,10 @@ #ifndef ALEEFORTH_CTYPE_HPP #define ALEEFORTH_CTYPE_HPP +/** + * We implement our own character comparison functions to keep them lean. + */ + #include bool isspace(uint8_t); diff --git a/libalee/dictionary.hpp b/libalee/dictionary.hpp index 53172d2..02fb1cc 100644 --- a/libalee/dictionary.hpp +++ b/libalee/dictionary.hpp @@ -39,6 +39,9 @@ class Dictionary { public: + /** + * The beginning of the dictionary is used for "internal" variables. + */ constexpr static Addr Base = 0; constexpr static Addr Here = sizeof(Cell); constexpr static Addr Latest = sizeof(Cell) * 2; @@ -49,6 +52,22 @@ public: constexpr static Addr InputCells = 80; // bytes! constexpr static Addr Begin = sizeof(Cell) * 7 + InputCells; + constexpr static Cell Immediate = (1 << 5); + + /** + * Dictionary data can be stored on any read-write interface. + * You must create a dictionary class that inherits Dictionary and + * implement these functions. See `memdict.hpp` for a simple block-of- + * memory implementation. + */ + virtual Cell read(Addr) const noexcept = 0; + virtual void write(Addr, Cell) noexcept = 0; + virtual uint8_t readbyte(Addr) const noexcept = 0; + virtual void writebyte(Addr, uint8_t) noexcept = 0; + + /** + * Does initial dictionary setup, required before use for execution. + */ void initialize(); Addr here() const noexcept { return read(Here); } @@ -57,24 +76,50 @@ public: Addr latest() const noexcept { return read(Latest); } void latest(Addr l) noexcept { write(Latest, l); } - virtual Cell read(Addr) const noexcept = 0; - virtual void write(Addr, Cell) noexcept = 0; - virtual uint8_t readbyte(Addr) const noexcept = 0; - virtual void writebyte(Addr, uint8_t) noexcept = 0; - - Addr alignhere() noexcept; + // Aligns the given address. Addr aligned(Addr) const noexcept; + // Aligns `here`. + Addr alignhere() noexcept; + // Advances `here` by the given number of bytes. Addr allot(Cell) noexcept; + // Stores value to `here`, then adds sizeof(Cell) to `here`. void add(Cell) noexcept; + + /** + * Uses add() to store a new definition entry starting at `here`. + * The entry does not become active until a semicolon is executed. + */ void addDefinition(Word) noexcept; + /** + * Searches the dictionary for an entry for the given word. + * Returns zero if not found. + */ Addr find(Word) noexcept; + + /** + * Given the address of a dictionary entry, produces the execution token + * for that entry. + */ Addr getexec(Addr) noexcept; + + /** + * Reads the next word from the input buffer. + * Returns an empty word if the buffer is empty or entirely read. + */ Word input() noexcept; + /** + * Checks if this dictionary's word is equivalent to the given string/size. + */ bool equal(Word, const char *, unsigned) const noexcept; + + /** + * Checks if two words in this dictionary's word are equivalent. + */ bool equal(Word, Word) const noexcept; + // Used for case-insensitive comparison between two iterators. template static bool equal(Iter1 b1, Iter1 e1, Iter2 b2) { return std::equal(b1, e1, b2, eqchars); @@ -83,6 +128,7 @@ public: virtual ~Dictionary() = default; private: + // Case-insensitive comparison. static bool eqchars(char c1, char c2); }; diff --git a/libalee/parser.cpp b/libalee/parser.cpp index 39695c4..9981b3e 100644 --- a/libalee/parser.cpp +++ b/libalee/parser.cpp @@ -65,7 +65,7 @@ Error Parser::parseWord(State& state, Word word) else imm = ins == CoreWords::Semicolon; } else { - imm = state.dict.read(ins) & CoreWords::Immediate; + imm = state.dict.read(ins) & Dictionary::Immediate; ins = state.dict.getexec(ins); } diff --git a/libalee/parser.hpp b/libalee/parser.hpp index be6f3f7..6af3ef9 100644 --- a/libalee/parser.hpp +++ b/libalee/parser.hpp @@ -26,11 +26,29 @@ class Parser { public: + /** + * Parses (and evaluates) the given string using the given state. + * The string is stored in the state's input buffer, then parseSource() + * works through that using parseWord(). parseWord() will compile or + * execute as necessary. + */ static Error parse(State&, const char *); + + /** + * Parses (and evaluates) through the words stored in the state's input + * buffer. + */ static Error parseSource(State&); private: + /** + * Parses the given word using the given state. + */ static Error parseWord(State&, Word); + + /** + * Attempts to parse the given word into a number. + */ static Error parseNumber(State&, Word); }; diff --git a/libalee/state.hpp b/libalee/state.hpp index 325f405..4c74f9a 100644 --- a/libalee/state.hpp +++ b/libalee/state.hpp @@ -34,9 +34,8 @@ class State public: Addr ip = 0; Dictionary& dict; - void (*input)(State&); - - std::jmp_buf jmpbuf = {}; + void (*input)(State&); // User-provided function to collect "stdin" input. + std::jmp_buf jmpbuf = {}; // Used when catching execution errors. constexpr State(Dictionary& d, void (*i)(State&)): dict(d), input(i) {} @@ -44,10 +43,29 @@ public: bool compiling() const; void compiling(bool); + /** + * Saves execution state so that a new execution can begin. + * Used for EVALUATE. + */ std::pair save(); + + /** + * Reloads the given execution state. + */ void load(const std::pair&); + /** + * Begins execution at the given execution token. + * If the token is a CoreWord, this function exits after its execution. + * Otherwise, execution continues until the word's execution completes. + * Encountering an error will cause this function to exit immediately. + */ Error execute(Addr); + + /** + * Clears the data and return stacks, sets ip to zero, and clears the + * compiling flag. + */ void reset(); std::size_t size() const noexcept; @@ -65,11 +83,6 @@ public: return *--dsp; } - inline Cell beyondip() { - ip += sizeof(Cell); - return dict.read(ip); - } - inline void pushr(Cell value) { if (rsp == rstack + ReturnStackSize) std::longjmp(jmpbuf, static_cast(Error::pushr)); @@ -94,12 +107,17 @@ public: return *(dsp - i - 1); } + // Advances the instruction pointer and returns that cell's contents. + inline Cell beyondip() { + ip += sizeof(Cell); + return dict.read(ip); + } + private: Cell dstack[DataStackSize] = {}; Cell rstack[ReturnStackSize] = {}; Cell *dsp = dstack; Cell *rsp = rstack; - }; #endif // ALEEFORTH_STATE_HPP diff --git a/libalee/types.hpp b/libalee/types.hpp index 0a7f7ee..29c93e8 100644 --- a/libalee/types.hpp +++ b/libalee/types.hpp @@ -22,15 +22,17 @@ #include #include -struct Dictionary; -struct State; - +/** + * Configure the below three types to match your platform. + */ using Addr = uint16_t; using Cell = int16_t; using DoubleCell = int32_t; -using Func = void (*)(State&); -constexpr unsigned int MaxCellNumberChars = 6; // -32768 +struct Dictionary; +struct State; + +using Func = void (*)(State&); enum class Error : int { none = 0, @@ -44,6 +46,9 @@ enum class Error : int { noword }; +/** + * Stores the start and (past-the-)end addresses of a dictionary's word. + */ struct Word { struct iterator; @@ -51,9 +56,11 @@ struct Word Addr start = 0; Addr wend = 0; + unsigned size() const noexcept; + + // Iterators provided for std::equal. iterator begin(const Dictionary *); iterator end(const Dictionary *); - unsigned size() const noexcept; struct iterator { using iterator_category = std::input_iterator_tag;