diff --git a/.gitignore b/.gitignore index a877dd4..0c82925 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .* *.o +*.dat alee alee-msp430 +alee-standalone libalee.a +core.fth.h diff --git a/Makefile b/Makefile index 66d9132..60b4922 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ all: alee msp430: CXX := msp430-elf32-g++ msp430: AR := msp430-elf32-ar -msp430: CXXFLAGS += -Os -mmcu=msp430g2553 -ffunction-sections -fdata-sections -DMEMDICTSIZE=200 +msp430: CXXFLAGS += -Os -mmcu=msp430g2553 -ffunction-sections -fdata-sections +msp430: CXXFLAGS += -DMEMDICTSIZE=200 msp430: LDFLAGS += -L/opt/msp430-elf32/include -Wl,-gc-sections msp430: alee-msp430 @@ -20,13 +21,25 @@ small: alee fast: CXXFLAGS += -O3 -march=native -mtune=native -flto fast: alee -alee: $(LIBFILE) +standalone: core.fth.h alee-standalone +alee: $(LIBFILE) alee-msp430: $(LIBFILE) +alee-standalone: $(LIBFILE) $(LIBFILE): $(OBJFILES) $(AR) cr $@ $(OBJFILES) +core.fth.h: alee.dat + xxd -i $< > $@ + sed -i "s/unsigned /static const &/" $@ + +alee.dat: alee core.fth + echo "2 sys" | ./alee core.fth + clean: - rm -f alee alee-msp430 $(LIBFILE) $(OBJFILES) + rm -f alee alee-msp430 alee-standalone + rm -f $(LIBFILE) $(OBJFILES) alee.dat core.fth.h + +.PHONY: all msp430 small fast standalone clean diff --git a/alee-standalone.cpp b/alee-standalone.cpp new file mode 100644 index 0000000..1602641 --- /dev/null +++ b/alee-standalone.cpp @@ -0,0 +1,176 @@ +/** + * Alee Forth: A portable and concise Forth implementation in modern C++. + * Copyright (C) 2023 Clyne Sullivan + * + * 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 the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "alee.hpp" +#include "splitmemdict.hpp" + +#include +#include +#include +#include + +#include "core.fth.h" + +static bool okay = false; + +static void readchar(State& state); +static void parseLine(Parser&, State&, const std::string&); +static void parseFile(Parser&, State&, std::istream&); + +static Parser parser; + +int main(int argc, char *argv[]) +{ + SplitMemDict dict (alee_dat); + State state (dict, readchar); + + std::vector args (argv + 1, argv + argc); + for (const auto& a : args) { + std::ifstream file (a); + parseFile(parser, state, file); + } + + okay = true; + parseFile(parser, state, std::cin); + + return 0; +} + +static void readchar(State& state) +{ + auto idx = state.dict.read(Dictionary::Input); + Addr addr = Dictionary::Input + sizeof(Cell) + idx; + + auto c = std::cin.get(); + if (isupper(c)) + c += 32; + state.dict.writebyte(addr, c ? c : ' '); +} + +static void save(State& state) +{ + std::ofstream file ("alee.dat", std::ios::binary); + + if (file.good()) { + for (Addr i = 0; i < state.dict.here(); ++i) + file.put(state.dict.readbyte(i)); + } +} + +static void load(State& state) +{ + std::ifstream file ("alee.dat", std::ios::binary); + + Addr i = 0; + while (file.good()) + state.dict.writebyte(i++, file.get()); +} + +#include +void user_sys(State& state) +{ + char buf[32] = {0}; + + switch (state.pop()) { + case 0: // . + std::to_chars(buf, buf + sizeof(buf), state.pop(), + state.dict.read(Dictionary::Base)); + std::cout << buf << ' '; + break; + case 1: // emit + std::cout << static_cast(state.pop()); + break; + case 2: // save + save(state); + break; + case 3: // load + load(state); + break; + case 4: // u. + { + Addr ucell = static_cast(state.pop()); + std::to_chars(buf, buf + sizeof(buf), ucell, + state.dict.read(Dictionary::Base)); + std::cout << buf << ' '; + } + break; + case 5: // eval + { + auto oldip = state.ip; + std::jmp_buf oldjb; + memcpy(oldjb, state.jmpbuf, sizeof(std::jmp_buf)); + state.ip = 0; + parser.parseSource(state); + memcpy(state.jmpbuf, oldjb, sizeof(std::jmp_buf)); + state.ip = oldip; + } + break; + } +} + +void parseLine(Parser& parser, State& state, const std::string& line) +{ + if (auto r = parser.parse(state, line.c_str()); r == 0) { + if (okay) + std::cout << (state.compiling() ? "compiled" : "ok") << std::endl; + } else { + switch (r) { + case Parser::UnknownWord: + std::cout << "word not found in: " << line << std::endl; + break; + case static_cast(State::Error::push): + std::cout << "stack overflow" << std::endl; + break; + case static_cast(State::Error::pushr): + std::cout << "return stack overflow" << std::endl; + break; + case static_cast(State::Error::popr): + std::cout << "return stack underflow" << std::endl; + break; + case static_cast(State::Error::pop): + case static_cast(State::Error::top): + case static_cast(State::Error::pick): + std::cout << "stack underflow" << std::endl; + break; + default: + std::cout << "error: " << r << std::endl; + break; + } + + while (state.size()) + state.pop(); + while (state.rsize()) + state.popr(); + state.dict.write(Dictionary::Compiling, 0); + state.ip = 0; + } +} + +void parseFile(Parser& parser, State& state, std::istream& file) +{ + while (file.good()) { + std::string line; + std::getline(file, line); + + if (line == "bye") + exit(0); + + parseLine(parser, state, line); + } +} + diff --git a/splitmemdict.hpp b/splitmemdict.hpp new file mode 100644 index 0000000..083ce53 --- /dev/null +++ b/splitmemdict.hpp @@ -0,0 +1,85 @@ +/** + * Alee Forth: A portable and concise Forth implementation in modern C++. + * Copyright (C) 2023 Clyne Sullivan + * + * 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 the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef ALEEFORTH_SPLITMEMDICT_HPP +#define ALEEFORTH_SPLITMEMDICT_HPP + +#include "dictionary.hpp" + +#include + +#ifndef MEMDICTSIZE +#define MEMDICTSIZE (65536) +#endif +constexpr unsigned long int MemDictSize = MEMDICTSIZE; + +template +class SplitMemDict : public Dictionary +{ + const uint8_t *rodict; + uint8_t rwdict[MemDictSize]; + uint8_t extra[Dictionary::Begin]; + +public: + constexpr SplitMemDict(const uint8_t *rod): + rodict(rod) + { + std::memcpy(extra, rodict, sizeof(extra)); + } + + virtual Cell read(Addr addr) const noexcept final { + const uint8_t *dict; + if (addr < RON) { + dict = addr < Dictionary::Begin ? extra : rodict; + } else { + dict = rwdict; + addr -= RON; + } + + return *reinterpret_cast(dict + addr); + } + + virtual void write(Addr addr, Cell value) noexcept final { + if (addr >= RON) + *reinterpret_cast(rwdict + addr - RON) = value; + else if (addr < Dictionary::Begin) + *reinterpret_cast(extra + addr) = value; + } + + virtual uint8_t readbyte(Addr addr) const noexcept final { + const uint8_t *dict; + if (addr < RON) { + dict = addr < Dictionary::Begin ? extra : rodict; + } else { + dict = rwdict; + addr -= RON; + } + + return dict[addr]; + } + + virtual void writebyte(Addr addr, uint8_t value) noexcept final { + if (addr >= RON) + rwdict[addr - RON] = value; + else if (addr < Dictionary::Begin) + extra[addr] = value; + } +}; + +#endif // ALEEFORTH_SPLITMEMDICT_HPP +