Write You a Forth, 0x02 ----------------------- :date: 2018-02-22 10:48 :tags: wyaf, forth The basic framework will consist of two main parts: 1. A modular I/O subsystem: on Linux, it makes sense to use the operating system's terminal I/O features. On the MSP430, there won't be the luxury of any operating system and I'll have to build out the I/O facilities. The I/O interface will be defined in ``io.h``; the build system will eventually have to decide which interface implementation to bring in. 2. A toplevel function (the C++ ``main`` function, for example) that will handle starting up the Forth system and bring us into an interpreter. We'll put this in ``kforth.cc``. The project will also need a build system. For simplicity, I'll at least start with a basic Makefile:: # Makefile CXXSTD := c++14 CXXFLAGS := -std=$(CXXSTD) -Werror -Wall -g -O0 OBJS := linux/io.o \ kforth.o TARGET := kforth all: $(TARGET) $(TARGET): $(OBJS) $(CXX) $(CFLAGS) -o $@ $(OBJS) clean: rm -f $(OBJS) $(TARGET) A simple frontend ^^^^^^^^^^^^^^^^^ Starting out with the most basic front end; we'll first want to include our I/O interface:: #include "io.h" If kforth is running on Linux, and it will be for the first stage, the frontend should pull in Linux specific pieces. ``linux.h`` is the place to set up the Linux-specific pieces:: #ifdef __linux__ #include "linux.h" #endif // __linux__ The interpreter function takes an I/O interface instance, and reads lines in an infinite loop, printing "ok" after each line is read. I'll go over the methods called on the ``interface`` instance when I get to the I/O subsystem. Printing the line buffer right now helps to verify that the I/O subsystem is working correctly:: static char ok[] = "ok.\n"; static void interpreter(IO &interface) { static size_t buflen = 0; static char linebuf[81]; while (true) { buflen = interface.rdbuf(linebuf, 80, true, '\n'); interface.wrln(linebuf, buflen); interface.wrbuf(ok, 4); } } The main function, for right now, can just instantiate a new I/O interface and then call the interpreter:: static char banner[] = "kforth interpreter\n"; const size_t bannerlen = 19; int main(void) { #ifdef __linux__ Console interface; #endif interface.wrbuf(banner, bannerlen); interpreter(interface); return 0; } That gives a good interactive test framework that I can use to start playing with the system. I'm trying to avoid bringing in ``iostream`` directly in order to force writing and building useful tooling built around the I/O interface. This is, after all, the Forth ideal: start with a core system, then build your world on top of that. The I/O interface ^^^^^^^^^^^^^^^^^ In the truest of C++ fashions, the I/O interface is defined with the ``IO`` abstract base class:: #ifndef __KF_IO_H__ #define __KF_IO_H__ #include "defs.h" class IO { public: // Virtual destructor is required in all ABCs. virtual ~IO() {}; The two building block methods are the lowest-level. My original plan was to include these in the interface, but there's one snag with that: line endings. But, we'll get to that. :: // Building block methods. virtual char rdch(void) = 0; virtual void wrch(char c) = 0; I could have just made the buffer I/O methods functions inside the ``io.h`` header, but it seems easy to just include them here. I may move them outside the class later, though. :: // Buffer I/O. virtual size_t rdbuf(char *buf, size_t len, bool stopat, char stopch) = 0; virtual void wrbuf(char *buf, size_t len) = 0; Line I/O presents some challenges. On a serial console, it's the sequence 0x0d 0x0a; on the Linux terminal, it's 0x0a. Therefore, reading a line is platform-dependent, and I can't just make this a generic function unless I want to handle all the cases. And, *surprise surprise*, right now I don't. :: // Line I/O virtual bool rdln(char *buf, size_t len, size_t *readlen) = 0; virtual void wrln(char *buf, size_t len) = 0; }; #endif // __KF_IO_H__ The Linux implementation is the ``Console`` (as seen in ``main``). The header file isn't interesting; it's basically a copy of ``io.h`` in ``linux/io.h``. :: #include #include "../io.h" #include "io.h" The building blocks flush I/O. ``getchar`` is used instead of ``cin`` because the latter skips whitespace. Later, flushing may be removed but it's not a performance concern yet. :: char Console::rdch() { std::cout.flush(); return getchar(); } void Console::wrch(char c) { std::cout.flush(); std::cout << c; } The buffer read and write functions are straightforward, and are just built on top of the character read and write methods. :: size_t Console::rdbuf(char *buf, size_t len, bool stopat, char stopch) { size_t n = 0; char ch; while (n < len) { ch = this->rdch(); if (stopat && stopch == ch) { break; } buf[n++] = ch; } return n; } void Console::wrbuf(char *buf, size_t len) { for (size_t n = 0; n < len; n++) { this->wrch(buf[n]); } } Line reading doesn't reuse the buffer I/O functions, because the latter doesn't indicate whether the buffer ran out or the line has ended. I could add length checks and whatnot, but this is straightforward and gives me something to work with now. Again, the mantra is dumb and works rather than clever. For now. :: bool Console::rdln(char *buf, size_t len, size_t *readlen) { size_t n = 0; char ch; bool line = false; while (n < len) { ch = this->rdch(); if (ch == '\n') { line = true; break; } buf[n++] = ch; } if (nullptr != readlen) { *readlen = n; } return line; } Line writing, however, can absolutely reuse the buffer and character I/O methods. :: void Console::wrln(char *buf, size_t len) { this->wrbuf(buf, len); this->wrch(0x0a); } ``defs.h`` ^^^^^^^^^^ The common definition file ``defs.h`` is just a front for the actual platform definitions:: #ifndef __KF_DEFS_H__ #define __KF_DEFS_H__ #ifdef __linux__ #include "linux/defs.h" #endif #endif // __KF_DEFS_H__ The Linux definitions in ``linux/defs.h`` just bring in the standard definitions from the standard library:: #ifndef __KF_LINUX_DEFS_H__ #define __KF_LINUX_DEFS_H__ #include #endif Next steps ^^^^^^^^^^ I guess the next thing to do will be to start parsing. Some housekeeping: I'll keep the state of the code at each part in the tag ``part-$PART``; this part, for example is in the tag `part-0x02`_. .. _part-0x02: https://github.com/kisom/kforth/tree/part-0x02