]> code.bitgloo.com Git - clyne/sprit-forth.git/commitdiff
initial upload
authorClyne Sullivan <clyne@bitgloo.com>
Fri, 24 Nov 2023 21:23:40 +0000 (16:23 -0500)
committerClyne Sullivan <clyne@bitgloo.com>
Fri, 24 Nov 2023 21:23:40 +0000 (16:23 -0500)
13 files changed:
.gitignore [new file with mode: 0644]
LICENSE
Makefile [new file with mode: 0644]
README.md
core.fth [new file with mode: 0644]
source/core.cpp [new file with mode: 0644]
source/core.hpp [new file with mode: 0644]
source/parse.cpp [new file with mode: 0644]
source/parse.hpp [new file with mode: 0644]
source/state.cpp [new file with mode: 0644]
source/state.hpp [new file with mode: 0644]
source/types.hpp [new file with mode: 0644]
sprit.cpp [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..2ea588d
--- /dev/null
@@ -0,0 +1,4 @@
+.*
+*.a
+*.o
+sprit
diff --git a/LICENSE b/LICENSE
index eb3a4cd1dba061409b6ac37fa50327f428740c8d..02c9011185e8eabf95cf1374e7d6c823133df3eb 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -145,30 +145,3 @@ NO WARRANTY
 
 END OF TERMS AND CONDITIONS
 
-How to Apply These Terms to Your New Libraries
-
-If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License).
-
-To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
-
-     one line to give the library's name and an idea of what it does.
-     Copyright (C) year  name of author
-
-     This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
-
-     This library 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 the GNU Library General Public License for more details.
-
-     You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names:
-
-Yoyodyne, Inc., hereby disclaims all copyright interest in
-the library `Frob' (a library for tweaking knobs) written
-by James Random Hacker.
-
-signature of Ty Coon, 1 April 1990
-Ty Coon, President of Vice
-
-That's all there is to it!
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..677edec
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+SRC := $(wildcard source/*.cpp)
+OBJ := $(subst .cpp,.o,$(SRC))
+
+CXXFLAGS += -std=c++20 -ggdb -g3 -Os \
+           -Wall -Wextra -pedantic \
+           -Isource
+
+all: sprit
+
+msp430: CXX      := msp430-elf-g++
+msp430: AR       := msp430-elf-ar
+msp430: CXXFLAGS += -mmcu=msp430fr2476 -Os #-flto
+msp430: LDFLAGS  += -L/usr/msp430-elf/usr/include
+msp430: sprit
+
+x86: CXXFLAGS += -m32
+x86: sprit
+
+clean:
+       rm -f sprit source/libsprit.a $(OBJ)
+
+sprit: source/libsprit.a
+
+source/libsprit.a: $(OBJ)
+       $(AR) rcu $@ $^
+
index f0905bf0511d4d9c8285ffb9407d5135a28d1020..d2035be25eb9966bb6399573a62c577073d97cab 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,3 +1,18 @@
 # sprit-forth
 
-A tiny and portable subroutine-threaded Forth.
\ No newline at end of file
+Sprit Forth is a tiny and portable subroutine-threaded Forth written in modern C++. It is inspired by [Alee Forth](https://code.bitgloo.com/bitgloo/alee-forth) and [milliForth](https://github.com/fuzzballcat/milliForth).
+
+Subroutine-threading is an improvement over Alee Forth's tokenized threading: words are compiled as lists of function pointers such that their execution is direct and "native". By sticking to subroutines instead of directly compiling assembly instructions, Sprit Forth maintains entirely portable.
+
+In terms of size, the `libsprit.a` library is only around 4kB on x86\_64 without optimizations, down to around 2kB with size optimizations, and *under 1kB* on a 16-bit MSP430 microcontroller.
+
+**Sprit Forth is early in development.** The project won't be considered "stable" until at least the [core standard word-set](https://forth-standard.org/standard/core) is implemented.
+
+## Requirements
+
+A C++ compiler that supports the C++20 standard.
+
+## Building
+
+Run `make`. There are `x86` and `msp430` targets available to target those architectures.
+
diff --git a/core.fth b/core.fth
new file mode 100644 (file)
index 0000000..d267d08
--- /dev/null
+++ b/core.fth
@@ -0,0 +1,95 @@
+: cell+    [ 1 cells ] literal + ;
+: char+    1 + ;
+: chars    ;
+
+: 1+       1 + ;
+: 1-       1 - ;
+
+: over     1 pick ;
+: rot      >r swap r> swap ;
+: -rot     rot rot ;
+
+: +!       dup >r swap r> @ + swap ! ;
+
+: imm      immediate ;
+
+: base     [ 0 _d ] literal ;
+: here     [ 1 cells _d ] literal @ ;
+: allot    [ 1 cells _d ] literal +! ;
+: state    [ 3 cells _d ] literal ;
+: _compxt  [ 4 cells _d ] literal ;
+: _source  [ 5 cells _d ] literal ;
+: _sourceu [ 6 cells _d ] literal ;
+: >in      [ 7 cells _d ] literal ;
+: _begin   [ 8 cells 80 chars + _d ] literal ;
+
+: c,       here c! 1 allot ;
+
+: if       ['] _jmp0 compile, here 0 , ; imm
+: then     here swap ! ; imm
+: else     ['] _jmp compile, here 0 , swap here swap ! ; imm
+
+: postpone ' dup _i swap [ ' literal compile, ]
+           if ['] execute else ['] , then compile, ; imm
+
+: 2drop    drop drop ;
+: 2dup     over over ;
+: 2over    3 pick 3 pick ;
+: 2swap    rot >r rot r> ;
+
+: decimal  10 base ! ;
+
+: 2r>      ['] r> compile, ['] r> compile, ['] swap compile, ; imm
+: 2>r      ['] swap compile, ['] >r compile, ['] >r compile, ; imm
+: r@       ['] r> compile, ['] dup compile, ['] >r compile, ; imm
+
+: 2!       swap over ! cell+ ! ;
+: 2@       dup cell+ @ swap @ ;
+
+: 0=       0 = ;
+: 0<       0 < ;
+: <=       2dup < >r = r> or ;
+: >        swap < ;
+: <>       = 0= ;
+
+: begin    0 here ; imm
+: while    swap 1+ swap postpone if -rot ; imm
+: repeat   ['] _jmp compile, , if postpone then then ; imm
+: until    ['] _jmp0 compile, , drop ; imm
+
+: do       0 postpone literal here 1 cells -
+           ['] >r compile, postpone 2>r here ; imm
+: unloop   postpone 2r> ['] 2drop compile,
+           ['] r> compile, ['] drop compile, ; imm
+: leave    postpone 2r> ['] 2drop compile, ['] exit compile, ; imm
+: +loop    ['] r> compile,   ['] 2dup compile, ['] + compile,
+           postpone r@       ['] swap compile, ['] >r compile,
+           ['] - compile,    ['] 2dup compile, ['] + compile,
+           ['] over compile, ['] xor compile,  ['] rot compile,
+           ['] rot compile,  ['] xor compile,  ['] and compile,
+           0 postpone literal ['] < compile, ['] _jmp0 compile, ,
+           postpone unloop here 1 cells - swap ! ; imm
+: loop     postpone 2r> ['] 1+ compile, ['] 2dup compile,
+           postpone 2>r ['] = compile, ['] _jmp0 compile, ,
+           postpone unloop here 1 cells - swap ! ; imm
+: i        postpone r@ ; imm
+: j        postpone 2r> ['] r> compile, postpone r@ ['] swap compile,
+           ['] >r compile, ['] -rot compile, postpone 2>r ; imm
+
+: invert   -1 ^ ;
+: 2*       2 * ;
+: _msb     [ 1 1 cells 8 * 1- lshift ] literal ;
+: 2/       dup 1 rshift swap 0< if _msb or then ;
+
+: cr       10 emit ;
+: bl       32 ;
+: space    bl emit ;
+: spaces   begin dup 0 > while space 1- repeat drop ;
+
+: ?dup     dup if dup then ;
+
+: negate   -1 * ;
+: abs      dup 0< if negate then ;
+: min      2dup <= if drop else swap drop then ;
+: max      2dup <= if swap drop else drop then ;
+
diff --git a/source/core.cpp b/source/core.cpp
new file mode 100644 (file)
index 0000000..7daf1ae
--- /dev/null
@@ -0,0 +1,151 @@
+// sprit-forth: A portable subroutine-threaded Forth.
+// Copyright (C) 2023  Clyne Sullivan <clyne@bitgloo.com>
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Library General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or (at your
+// option) any later version.
+//
+// This library 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 the GNU Library General Public License for
+// more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#include "core.hpp"
+#include "state.hpp"
+
+#include <cctype>
+
+void jump(FuncList ip)
+{
+    IP = ip - 1;
+}
+
+static auto literall = WordWrap<[] {
+    *++SP = (Cell)*++IP;
+}>();
+
+void compileliteral()
+{
+    comma((Cell)literall);
+    comma(*SP--);
+}
+
+bool haskey()
+{
+    return DICT[DIdxSrcLen] > 0;
+}
+
+void addkey(int k)
+{
+    --DICT[DIdxSource];
+    ++DICT[DIdxSrcLen];
+
+    auto ptr = reinterpret_cast<char *>(DICT[DIdxSource]);
+    *ptr = static_cast<char>(k);
+}
+
+int key()
+{
+    while (!haskey())
+        getinput();
+
+    auto ptr = reinterpret_cast<char *>(DICT[DIdxSource]);
+    ++DICT[DIdxSource];
+    --DICT[DIdxSrcLen];
+
+    return *ptr;
+}
+
+Cell *comma(Cell n)
+{
+    const auto ptr = reinterpret_cast<Cell *>(HERE);
+    *ptr = n;
+    HERE += sizeof(Cell);
+    return ptr;
+}
+
+Addr aligned(Addr addr)
+{
+    return (addr + (sizeof(Cell) - 1)) & ~(sizeof(Cell) - 1);
+}
+
+void align()
+{
+    HERE = aligned(HERE);
+}
+
+void word()
+{
+    int k;
+    do {
+        k = key();
+    } while (isspace(k));
+
+    char *ptr;
+    do {
+        ptr = reinterpret_cast<char *>(HERE);
+        *ptr = k;
+        ++HERE;
+
+        if (!haskey())
+            break;
+
+        k = key();
+    } while (!isspace(k));
+    addkey(k);
+    ptr = reinterpret_cast<char *>(HERE);
+    *ptr = '\0';
+    ++HERE;
+}
+
+void colon()
+{
+    align();
+    auto name = HERE;
+    word();
+    align();
+
+    comma(HERE + 4 * sizeof(Cell)); // exec ptr
+    comma(name); // name ptr
+    *++SP = (Cell)comma(0); // link (filled by latest)
+    comma(0); // immediate
+
+    comma((Cell)+[](FuncList *ip) {
+        ++ip;
+        *++RP = (Cell)IP;
+        jump((FuncList)*ip);
+    });
+    comma(HERE + sizeof(Cell));
+
+    STATE = -1;
+}
+
+void semic()
+{
+    comma((Cell)fexit);
+
+    auto link = (Cell *)*SP--;
+    *link = LATEST;
+    LATEST = (Cell)(link - 2);
+
+    STATE = 0;
+}
+
+// : ' bl word find drop ;
+void tick()
+{
+    auto name = (char *)HERE;
+    word();
+
+    int len = HERE - (Cell)name - 1;
+    auto word = find(name, len);
+    *++SP = (Cell)word;
+
+    HERE = (Cell)name;
+}
+
diff --git a/source/core.hpp b/source/core.hpp
new file mode 100644 (file)
index 0000000..a134804
--- /dev/null
@@ -0,0 +1,48 @@
+// sprit-forth: A portable subroutine-threaded Forth.
+// Copyright (C) 2023  Clyne Sullivan <clyne@bitgloo.com>
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Library General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or (at your
+// option) any later version.
+//
+// This library 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 the GNU Library General Public License for
+// more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#ifndef CORE_HPP
+#define CORE_HPP
+
+#include "types.hpp"
+
+extern void getinput();
+
+constexpr auto fexit = WordWrap<[] {
+    extern FuncList IP;
+    extern Cell *RP;
+    IP = reinterpret_cast<FuncList>(*RP--);
+}>();
+
+void jump(FuncList ip);
+void jumper();
+void compileliteral();
+
+bool haskey();
+void addkey(int k);
+
+int key();
+Cell *comma(Cell n);
+Addr aligned(Addr addr);
+void align();
+void word();
+void colon();
+void semic();
+void tick();
+
+#endif // CORE_HPP
+
diff --git a/source/parse.cpp b/source/parse.cpp
new file mode 100644 (file)
index 0000000..012d708
--- /dev/null
@@ -0,0 +1,70 @@
+// sprit-forth: A portable subroutine-threaded Forth.
+// Copyright (C) 2023  Clyne Sullivan <clyne@bitgloo.com>
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Library General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or (at your
+// option) any later version.
+//
+// This library 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 the GNU Library General Public License for
+// more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#include "core.hpp"
+#include "state.hpp"
+#include "types.hpp"
+
+#include <cctype>
+#include <cstdlib>
+
+static void parseword(const char *start, const char *end)
+{
+    if (start != end) {
+        if (auto word = find(start, end - start); word) {
+            if (!word->immediate() && STATE) {
+                comma((Cell)word->list);
+            } else {
+                execute1(word);
+            }
+        } else if (isdigit(*start)) {
+            *++SP = std::atoi(start);
+
+            if (STATE)
+                compileliteral();
+        }
+    }
+}
+
+void parseSource()
+{
+    auto start = (char *)DICT[DIdxSource];
+    auto end = start;
+
+    while (haskey()) {
+        end = (char *)++DICT[DIdxSource];
+        --DICT[DIdxSrcLen];
+
+        if (isspace(*end)) {
+            parseword(start, end);
+            start = (char *)(DICT[DIdxSource] + 1);
+        }
+    }
+
+    if (start != end)
+        parseword(start, end);
+}
+
+void parse()
+{
+    DICT[DIdxSource] = (Cell)&DICT[DIdxBegin];
+    DICT[DIdxSrcLen] = 0;
+    getinput();
+
+    parseSource();
+}
+
diff --git a/source/parse.hpp b/source/parse.hpp
new file mode 100644 (file)
index 0000000..7bf0c27
--- /dev/null
@@ -0,0 +1,18 @@
+// sprit-forth: A portable subroutine-threaded Forth.
+// Copyright (C) 2023  Clyne Sullivan <clyne@bitgloo.com>
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Library General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or (at your
+// option) any later version.
+//
+// This library 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 the GNU Library General Public License for
+// more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+
+void parse();
diff --git a/source/state.cpp b/source/state.cpp
new file mode 100644 (file)
index 0000000..27d624c
--- /dev/null
@@ -0,0 +1,71 @@
+// sprit-forth: A portable subroutine-threaded Forth.
+// Copyright (C) 2023  Clyne Sullivan <clyne@bitgloo.com>
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Library General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or (at your
+// option) any later version.
+//
+// This library 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 the GNU Library General Public License for
+// more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#include "core.hpp"
+#include "state.hpp"
+
+//#include <csetjmp>
+#include <cstring>
+
+//static std::jmp_buf jmpbuf;
+Cell *SP = DICT.data() + DICT.size() - DS;
+Cell *RP = DICT.data() + DICT.size() - DS - RS;
+FuncList IP = nullptr;
+
+std::array<Cell, 2048> DICT;
+
+Cell& HERE   = DICT[DIdxHere];
+Cell& LATEST = DICT[DIdxLatest];
+Cell& STATE  = DICT[DIdxState];
+
+void executor(FuncList *list)
+{
+    /*if (setjmp(jmpbuf) == 0)*/ {
+        // Execute the first bit of "word".
+        // If it is a "WordWrap", it will exit without changing IP.
+        // If it is a defined word, IP will be set to the word's body.
+        auto ip = (FuncList)list;
+        auto po = (void (**)(FuncList))*ip;
+        // Pass in po's location so the call can fetch beyond itself.
+        (*po)((FuncList)*ip);
+
+        while (IP) {
+            ++IP;
+            auto po = (void (**)(FuncList))*IP;
+            (*po)((FuncList)*IP);
+        }
+
+        //std::longjmp(jmpbuf, 1);
+    }
+}
+
+void execute1(Word *word)
+{
+    IP = 0;
+    executor(&word->list);
+}
+
+Word *find(const char *s, int len)
+{
+    for (auto w = (Word *)LATEST; w; w = w->link) {
+        if (len == (int)strlen(w->name) && strncmp(s, w->name, len) == 0)
+            return w;
+    }
+
+    return nullptr;
+}
+
diff --git a/source/state.hpp b/source/state.hpp
new file mode 100644 (file)
index 0000000..91ce12f
--- /dev/null
@@ -0,0 +1,57 @@
+// sprit-forth: A portable subroutine-threaded Forth.
+// Copyright (C) 2023  Clyne Sullivan <clyne@bitgloo.com>
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Library General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or (at your
+// option) any later version.
+//
+// This library 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 the GNU Library General Public License for
+// more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#ifndef STATE_HPP
+#define STATE_HPP
+
+#include "types.hpp"
+
+constexpr Addr DS = 16;
+constexpr Addr RS = 16;
+
+constexpr Addr DIdxBase   = 0;
+constexpr Addr DIdxHere   = 1;
+constexpr Addr DIdxLatest = 2;
+constexpr Addr DIdxState  = 3;
+constexpr Addr DIdxSource = 4;
+constexpr Addr DIdxSrcLen = 5;
+constexpr Addr DIdxInBuf  = 6;
+constexpr Addr DIdxBegin  = DIdxInBuf + 80 * sizeof(char);
+
+extern std::array<Cell, 2048> DICT;
+
+extern Cell& HERE;
+extern Cell& LATEST;
+extern Cell& STATE;
+
+extern Cell *SP;
+extern Cell *RP;
+extern FuncList IP;
+
+inline void initialize(const auto& wordset)
+{
+    LATEST = (Cell)wordset.latest;
+    HERE = (Cell)&DICT[DIdxBegin];
+    STATE = 0;
+}
+
+void executor(FuncList *list);
+void execute1(Word *word);
+Word *find(const char *s, int len);
+
+#endif // STATE_HPP
+
diff --git a/source/types.hpp b/source/types.hpp
new file mode 100644 (file)
index 0000000..03e9f2a
--- /dev/null
@@ -0,0 +1,85 @@
+// sprit-forth: A portable subroutine-threaded Forth.
+// Copyright (C) 2023  Clyne Sullivan <clyne@bitgloo.com>
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Library General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or (at your
+// option) any later version.
+//
+// This library 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 the GNU Library General Public License for
+// more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+
+#ifndef TYPES_HPP
+#define TYPES_HPP
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+
+using Cell = intptr_t;
+using Addr = uintptr_t;
+using Func = void (*)();
+using FuncList = Func const *;
+
+static_assert(sizeof(Cell) == sizeof(Addr));
+static_assert(sizeof(Cell) == sizeof(Func));
+
+struct Word {
+    FuncList list;
+    const char *name;
+    Word *link = nullptr;
+    Cell imm = 0;
+
+    constexpr Word(const char *n, FuncList l):
+        list(l), name(n) {}
+
+    constexpr Word& markImmediate() noexcept {
+        imm = -1;
+        return *this;
+    }
+
+    constexpr bool immediate() const noexcept {
+        return imm;
+    }
+};
+
+static_assert(offsetof(Word, list) == 0);
+static_assert(offsetof(Word, name) == 1 * sizeof(Cell));
+static_assert(offsetof(Word, link) == 2 * sizeof(Cell));
+static_assert(offsetof(Word, imm)  == 3 * sizeof(Cell));
+static_assert(sizeof(Word)         == 4 * sizeof(Cell));
+
+template<typename... Words>
+struct WordSet
+{
+    std::array<Word, sizeof...(Words)> words;
+    Word *latest;
+
+    constexpr WordSet(Words... ws):
+        words {ws...}
+    {
+        auto it = words.begin();
+        while (++it != words.end())
+            it->link = it - 1;
+
+        latest = &*words.rbegin();
+    }
+};
+
+template<auto... funcs>
+auto WordWrap = [] {
+    constexpr static Func list[1] = {
+        +[] { (funcs(), ...); }
+    };
+
+    return list;
+};
+
+#endif // TYPES_HPP
+
diff --git a/sprit.cpp b/sprit.cpp
new file mode 100644 (file)
index 0000000..263766b
--- /dev/null
+++ b/sprit.cpp
@@ -0,0 +1,93 @@
+#include <algorithm>
+#include <iostream>
+#include <string>
+
+#include "core.hpp"
+#include "parse.hpp"
+#include "state.hpp"
+#include "types.hpp"
+
+// TODO:
+// sys m* _/ _% _' depth _rdepth _in _ev find _uma u< um/mod
+
+static void peek()           { *SP = *(Cell *)(*SP); }
+static void commaSP()        { comma(*SP--); }
+static void push(Cell value) { *++SP = value; }
+static void pop()            { --SP; }
+static void tobool()         { if (*SP) *SP = -1; }
+
+constinit WordSet words (
+    Word("[",         WordWrap<[] { STATE = 0; }>()).markImmediate(),
+    Word("]",         WordWrap<[] { STATE = -1; }>()),
+    Word("@",         WordWrap<peek>()),
+    Word("c@",        WordWrap<peek, [] { *SP &= 0xFF; }>()),
+    Word("!",         WordWrap<[] { auto a = (Cell *)*SP--; *a = *SP--; }>()),
+    Word("c!",        WordWrap<[] { auto a = (char *)*SP--; *a = *SP--; }>()),
+    Word("_d",        WordWrap<[] { *SP += (Cell)DICT.data(); }>()),
+    Word("_jmp",      WordWrap<[] { jump((FuncList)*++IP); }>()),
+    Word("_jmp0",     WordWrap<[] {
+        ++IP;
+        if (*SP-- == 0)
+            jump((FuncList)*IP);
+    }>()),
+    Word(",",         WordWrap<commaSP>()),
+    Word("emit",      WordWrap<[] { std::putchar(*SP); }, pop>()),
+    Word("key",       WordWrap<[] { push(key()); }>()),
+    Word("key?",      WordWrap<[] { push(haskey()); }, tobool>()),
+    Word("execute",   WordWrap<[] { executor((FuncList *)*SP--); }>()),
+    Word(":",         WordWrap<colon>()),
+    Word(";",         WordWrap<semic>()).markImmediate(),
+    Word("exit",      fexit),
+    Word("drop",      WordWrap<pop>()),
+    Word("dup",       WordWrap<[] { push(*SP); }>()),
+    Word("swap",      WordWrap<[] { std::swap(*SP, *(SP - 1)); }>()),
+    Word("pick",      WordWrap<[] { auto t = *(SP - *SP - 1); *SP = t; }>()),
+    Word("cells",     WordWrap<[] { *SP *= sizeof(Cell); }>()),
+    Word("+",         WordWrap<[] { *(SP - 1) += *SP; }, pop>()),
+    Word("-",         WordWrap<[] { *(SP - 1) -= *SP; }, pop>()),
+    Word("*",         WordWrap<[] { *(SP - 1) *= *SP; }, pop>()),
+    Word("/",         WordWrap<[] { *(SP - 1) /= *SP; }, pop>()),
+    Word("mod",       WordWrap<[] { *(SP - 1) %= *SP; }, pop>()),
+    Word("=",         WordWrap<[] { *(SP - 1) = *(SP - 1) == *SP; }, pop, tobool>()),
+    Word("<",         WordWrap<[] { *(SP - 1) = *(SP - 1) < *SP; }, pop, tobool>()),
+    Word("or",        WordWrap<[] { *(SP - 1) |= *SP; }, pop>()),
+    Word("and",       WordWrap<[] { *(SP - 1) &= *SP; }, pop>()),
+    Word("xor",       WordWrap<[] { *(SP - 1) ^= *SP; }, pop>()),
+    Word("lshift",    WordWrap<[] { *(SP - 1) <<= *SP; }, pop>()),
+    Word("rshift",    WordWrap<[] { *(SP - 1) >>= *SP; }, pop>()),
+    Word(">r",        WordWrap<[] { *++RP = *SP; }, pop>()),
+    Word("r>",        WordWrap<[] { push(*RP--); }>()),
+    Word("immediate", WordWrap<[] { ((Word *)LATEST)->markImmediate(); }>()),
+    Word("aligned",   WordWrap<[] { *SP = aligned(*SP); }>()),
+    Word("align",     WordWrap<align>()),
+    Word("literal",   WordWrap<[] { if (STATE) compileliteral(); }>()).markImmediate(),
+    Word("\'",        WordWrap<tick>()),
+    Word("_i",        WordWrap<[] { *SP = ((Word *)*SP)->immediate(); }, tobool>()),
+    Word("[']",       WordWrap<tick, compileliteral>()).markImmediate(),
+    Word("compile,",  WordWrap<peek, commaSP>()),
+    Word("_b",        WordWrap<[] {
+        std::putchar('#'); // Gives a good breakpoint spot for gdb
+    }>()),
+    Word(".",         WordWrap<[] { std::cout << *SP << ' '; }, pop>())
+);
+
+void getinput()
+{
+    std::string line;
+
+    if (std::cin.good()) {
+        std::getline(std::cin, line);
+        std::for_each(line.rbegin(), line.rend(), addkey);
+    }
+}
+
+int main()
+{
+    initialize(words);
+
+    while (std::cin.good()) {
+        parse();
+        std::cout << (STATE ? "compiled" : "ok") << std::endl;
+    }
+}
+