diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce59d1885..96fafa8ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -211,7 +211,7 @@ jobs: - name: Install Host toolchain run: | apt-get update - apt-get install --assume-yes build-essential ruby-full + apt-get install --assume-yes build-essential gcc-multilib ruby-full - name: Prebuild run: | @@ -275,7 +275,7 @@ jobs: - name: Install Host toolchain run: | apt-get update - apt-get install --assume-yes build-essential ruby-full + apt-get install --assume-yes build-essential gcc-multilib ruby-full - name: Prebuild run: | @@ -343,7 +343,7 @@ jobs: - name: Install Host toolchain run: | apt-get update - apt-get install --assume-yes build-essential ruby-full + apt-get install --assume-yes build-essential gcc-multilib ruby-full - name: Build run: | @@ -372,7 +372,7 @@ jobs: - name: Install Host toolchain run: | apt-get update - apt-get install --assume-yes build-essential ruby-full + apt-get install --assume-yes build-essential gcc-multilib ruby-full - name: Build run: | @@ -404,7 +404,7 @@ jobs: run: | cd build cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC=ON -DBUILD_SDLGPU=On -DBUILD_WITH_ALL=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. - cmake --build . --parallel + cmake --build . --parallel 3 - name: Deploy uses: actions/upload-artifact@v6 @@ -418,7 +418,7 @@ jobs: run: | cd build cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SDLGPU=On -DBUILD_PRO=On -DBUILD_WITH_ALL=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. - cmake --build . --parallel + cmake --build . --parallel 3 # === MacOS 15 / x86_64 === macos: @@ -477,6 +477,9 @@ jobs: distribution: 'temurin' cache: gradle + - name: Install 32-bit build tools + run: sudo apt-get update && sudo apt-get install -y gcc-multilib + - name: Build run: | cd build/android @@ -509,6 +512,9 @@ jobs: with: ruby-version: 2.6 + - name: Install 32-bit build tools + run: sudo apt-get update && sudo apt-get install -y gcc-multilib + - name: Build lua run: | cd build diff --git a/.github/workflows/webapp.yml b/.github/workflows/webapp.yml index 1f1d6684e..4a9327b65 100644 --- a/.github/workflows/webapp.yml +++ b/.github/workflows/webapp.yml @@ -20,6 +20,9 @@ jobs: submodules: recursive fetch-depth: 0 + - name: Install 32-bit build tools + run: sudo apt-get update && sudo apt-get install -y gcc-multilib + - name: Build run: | cd build diff --git a/.gitmodules b/.gitmodules index d446e1c30..322871338 100644 --- a/.gitmodules +++ b/.gitmodules @@ -89,3 +89,6 @@ [submodule "vendor/lpeg"] path = vendor/lpeg url = https://github.com/roberto-ieru/LPeg.git +[submodule "vendor/pforth"] + path = vendor/pforth + url = https://github.com/philburk/pforth.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 39f9bba67..1d1e51cbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,7 @@ include(cmake/squirrel.cmake) include(cmake/pocketpy.cmake) include(cmake/quickjs.cmake) include(cmake/janet.cmake) +include(cmake/forth.cmake) include(cmake/core.cmake) include(cmake/wave.cmake) diff --git a/assets.bat b/assets.bat index 9b553cb8f..7b1a4e092 100644 --- a/assets.bat +++ b/assets.bat @@ -20,12 +20,14 @@ build\bin\prj2cart demos\schemedemo.scm build\schemedemo.tic build\bin\prj2cart demos\sfx.lua build\sfx.tic build\bin\prj2cart demos\squirreldemo.nut build\squirreldemo.tic build\bin\prj2cart demos\tetris.lua build\tetris.tic +build\bin\prj2cart demos\forthdemo.fth build\forthdemo.tic build\bin\prj2cart demos\wrendemo.wren build\wrendemo.tic build\bin\prj2cart demos\yuedemo.yue build\yuedemo.tic build\bin\wasmp2cart demos\wasm\wasmdemo.wasmp build\wasmdemo.tic --binary demos\wasm\wasmdemo.wasm build\bin\wasmp2cart demos\bunny\wasmmark\wasmmark.wasmp build\wasmmark.tic --binary demos\bunny\wasmmark\wasmmark.wasm +build\bin\prj2cart demos\bunny\forthmark.fth build\forthmark.tic build\bin\prj2cart demos\bunny\janetmark.janet build\janetmark.tic build\bin\prj2cart demos\bunny\jsmark.js build\jsmark.tic build\bin\prj2cart demos\bunny\luamark.lua build\luamark.tic @@ -59,7 +61,9 @@ build\bin\bin2txt build\schemedemo.tic build\assets\schemedemo.tic.dat -z build\bin\bin2txt build\sfx.tic build\assets\sfx.tic.dat -z build\bin\bin2txt build\squirreldemo.tic build\assets\squirreldemo.tic.dat -z build\bin\bin2txt build\tetris.tic build\assets\tetris.tic.dat -z +build\bin\bin2txt build\forthdemo.tic build\assets\forthdemo.tic.dat -z build\bin\bin2txt build\wrendemo.tic build\assets\wrendemo.tic.dat -z +build\bin\bin2txt build\forthmark.tic build\assets\forthmark.tic.dat -z build\bin\bin2txt build\janetmark.tic build\assets\janetmark.tic.dat -z build\bin\bin2txt build\jsmark.tic build\assets\jsmark.tic.dat -z build\bin\bin2txt build\luamark.tic build\assets\luamark.tic.dat -z diff --git a/build/assets/forthdemo.tic.dat b/build/assets/forthdemo.tic.dat new file mode 100644 index 000000000..9b1067751 --- /dev/null +++ b/build/assets/forthdemo.tic.dat @@ -0,0 +1 @@ +0x78, 0xda, 0xed, 0x92, 0xc1, 0x4e, 0xdb, 0x40, 0x10, 0x86, 0x37, 0xd0, 0x1e, 0xea, 0x03, 0x12, 0x6f, 0xf0, 0x87, 0x53, 0x2b, 0x0a, 0x8a, 0x43, 0x40, 0xd4, 0x95, 0xaa, 0x26, 0x34, 0x11, 0x96, 0xd2, 0x24, 0x4a, 0x2c, 0x4e, 0x5c, 0x82, 0x59, 0xb0, 0x55, 0x13, 0x47, 0xb6, 0xa1, 0xc9, 0x1b, 0xf8, 0xc0, 0x43, 0xf4, 0x58, 0x59, 0x7e, 0x88, 0x3d, 0x57, 0x2b, 0x9e, 0x24, 0xf2, 0x33, 0x30, 0xbb, 0x09, 0x85, 0x20, 0x44, 0x0f, 0x1c, 0x7a, 0x61, 0xc6, 0xab, 0xdd, 0xd9, 0x6f, 0xfe, 0x9d, 0xb5, 0xc7, 0xeb, 0x8c, 0xb1, 0xd2, 0xaf, 0x15, 0xf6, 0x2f, 0xbb, 0x91, 0x64, 0x69, 0x9a, 0xe6, 0x59, 0x96, 0xe5, 0x6a, 0xa6, 0x30, 0x97, 0xe2, 0xef, 0x90, 0x72, 0x56, 0xa4, 0xa9, 0x2c, 0xb2, 0x4c, 0x14, 0xe9, 0x6f, 0x51, 0xc8, 0x5c, 0x48, 0x91, 0x0b, 0x71, 0x37, 0x9e, 0xd6, 0xcb, 0x67, 0xf5, 0xf2, 0x81, 0x9e, 0xbd, 0xda, 0x7f, 0x35, 0xdd, 0x2b, 0xd5, 0x3b, 0xed, 0x7f, 0xf4, 0xfa, 0x9a, 0xfa, 0x28, 0x99, 0x90, 0xd4, 0xdb, 0x1b, 0x99, 0x33, 0x49, 0xbd, 0x9b, 0xe5, 0xd4, 0xbf, 0x6c, 0x31, 0x54, 0x3f, 0x99, 0x98, 0x15, 0x52, 0x16, 0xc5, 0x4b, 0xf5, 0xef, 0xbe, 0xbc, 0xec, 0xfe, 0xab, 0x6f, 0xd6, 0x4a, 0xf4, 0xa7, 0xbf, 0x2d, 0xad, 0xb0, 0x63, 0x24, 0x7e, 0x12, 0x70, 0x0b, 0xc0, 0xf9, 0xf0, 0x82, 0xcf, 0x23, 0xe3, 0x18, 0xc3, 0xcb, 0xc4, 0x0b, 0x23, 0x6b, 0xb1, 0x7b, 0xca, 0xaf, 0x78, 0x10, 0x8e, 0x79, 0xf4, 0x11, 0xfc, 0x62, 0xe8, 0x07, 0x34, 0x25, 0xee, 0x36, 0xa5, 0x9d, 0xf2, 0xd8, 0x55, 0x5a, 0xc4, 0x94, 0x9d, 0xe8, 0x30, 0xf2, 0xc7, 0x89, 0x1f, 0x8e, 0x08, 0xc6, 0x7e, 0xa2, 0x0f, 0xc6, 0x4f, 0x7e, 0xa2, 0xd6, 0x08, 0xfc, 0xd1, 0x0f, 0xda, 0x0f, 0x7c, 0x97, 0x8f, 0x62, 0x42, 0xdf, 0x6d, 0x07, 0xed, 0x79, 0x80, 0xf7, 0xae, 0x37, 0x1c, 0x9d, 0xd3, 0x05, 0x3c, 0x3f, 0x46, 0x12, 0x62, 0x1a, 0x5e, 0x46, 0x77, 0x99, 0x08, 0xcf, 0xe0, 0x7a, 0x21, 0x05, 0x1f, 0x48, 0x7e, 0xc5, 0xa3, 0x98, 0x0a, 0x58, 0xa8, 0x6c, 0x9b, 0xaa, 0x8a, 0xae, 0x68, 0xe1, 0x8c, 0x2e, 0xe0, 0x19, 0xc6, 0x51, 0xbd, 0x6f, 0xd7, 0x1b, 0xed, 0x26, 0xc6, 0x13, 0x55, 0x9a, 0xf8, 0x38, 0x52, 0xb5, 0x27, 0x0f, 0xc8, 0x74, 0x89, 0x4c, 0x0d, 0xc3, 0x42, 0xa3, 0xdb, 0x75, 0x0c, 0xe0, 0xd3, 0x9e, 0xd2, 0x95, 0x81, 0x6a, 0x4d, 0xa5, 0x95, 0x8d, 0xcf, 0x0a, 0x3a, 0xf6, 0x01, 0xb1, 0x0a, 0x1a, 0x4e, 0x07, 0x76, 0x0b, 0x8a, 0x7c, 0x85, 0xb9, 0xa5, 0x33, 0xe0, 0x1c, 0x36, 0x3b, 0x44, 0xcd, 0x47, 0x74, 0x73, 0x89, 0x56, 0xef, 0xe9, 0x64, 0xa1, 0x9d, 0xdc, 0xd3, 0x9d, 0x47, 0x74, 0x73, 0x89, 0x9a, 0x3b, 0x38, 0x68, 0x0f, 0x74, 0x09, 0x8d, 0xe7, 0xe7, 0xd7, 0x48, 0x55, 0x21, 0xaf, 0x92, 0x0f, 0x7a, 0x7d, 0xc2, 0x83, 0x0d, 0x1c, 0xf2, 0x20, 0x08, 0xd1, 0x52, 0x5f, 0xa2, 0xbc, 0x81, 0xfd, 0x9a, 0x7a, 0xcc, 0x5d, 0xb4, 0xea, 0xed, 0x41, 0x93, 0xe4, 0xf3, 0xb9, 0xd7, 0xb7, 0x3b, 0x0e, 0xbe, 0xf5, 0xbb, 0x3d, 0xf5, 0x76, 0xb7, 0xd4, 0x68, 0x44, 0x96, \ No newline at end of file diff --git a/build/assets/forthmark.tic.dat b/build/assets/forthmark.tic.dat new file mode 100644 index 000000000..beeb4612e --- /dev/null +++ b/build/assets/forthmark.tic.dat @@ -0,0 +1 @@ +0x78, 0xda, 0xed, 0x58, 0x4b, 0x6f, 0xdb, 0xd8, 0x15, 0x56, 0x16, 0xed, 0x82, 0xa8, 0x01, 0x1b, 0xdd, 0x15, 0x5d, 0x9c, 0x4c, 0xa6, 0xa8, 0x1c, 0x9b, 0xb6, 0xa4, 0xc8, 0x1a, 0xd5, 0x71, 0x5c, 0x4b, 0xb6, 0x1c, 0x1b, 0xb1, 0x25, 0x41, 0x52, 0xec, 0x11, 0xc6, 0x41, 0x40, 0x4b, 0xd7, 0x16, 0x11, 0x9a, 0x52, 0xf9, 0x50, 0xc4, 0x45, 0xb7, 0x59, 0x17, 0x45, 0x7f, 0x40, 0x57, 0xed, 0xb6, 0x5d, 0x75, 0xdf, 0x5d, 0x01, 0x07, 0xc8, 0x0f, 0x88, 0x82, 0xe9, 0xec, 0xeb, 0xe9, 0x22, 0x68, 0x97, 0xed, 0x77, 0x78, 0x49, 0x8a, 0x94, 0xec, 0x64, 0x90, 0x2c, 0xa6, 0x05, 0x72, 0x8d, 0xab, 0x90, 0xe7, 0x9e, 0xd7, 0x3d, 0x6f, 0xe6, 0x47, 0x99, 0x54, 0xea, 0x27, 0x3f, 0x5d, 0x7e, 0xf2, 0xf3, 0x27, 0x7f, 0xde, 0x6c, 0x7e, 0xfb, 0xeb, 0xe3, 0xff, 0xbc, 0x74, 0xff, 0xf8, 0xcf, 0x41, 0xf1, 0x2f, 0xdd, 0x9f, 0xfd, 0xca, 0x5b, 0x2c, 0xf4, 0xef, 0x3f, 0xf9, 0x5b, 0xe9, 0x0f, 0xff, 0xb2, 0xbf, 0xfd, 0xf7, 0xdb, 0xb7, 0x6f, 0x7f, 0xf7, 0xa7, 0xbf, 0x1e, 0x19, 0x2f, 0xee, 0x6d, 0x1c, 0xdf, 0xfa, 0xfd, 0x0f, 0x53, 0xef, 0x5b, 0x0b, 0x0b, 0x0b, 0xd8, 0xb7, 0xc6, 0xd8, 0x2f, 0xa7, 0x36, 0xc3, 0xc6, 0xa9, 0x79, 0xfc, 0xcc, 0x2d, 0xbc, 0xba, 0x9c, 0xda, 0xaf, 0xc7, 0xa9, 0x31, 0x2f, 0xe6, 0x30, 0x1e, 0xcf, 0x2f, 0x5c, 0xbe, 0x4e, 0xee, 0x97, 0x80, 0x31, 0xfc, 0x7d, 0x2b, 0xf5, 0x69, 0x7d, 0xaf, 0x2b, 0xf4, 0x73, 0xb8, 0x6f, 0xa5, 0x5e, 0x8d, 0xff, 0x7e, 0xf5, 0x06, 0xfb, 0x0a, 0xcf, 0x5f, 0xbf, 0x99, 0x83, 0x8f, 0xe7, 0xc6, 0xc1, 0x9a, 0xc3, 0xe1, 0xf8, 0x72, 0x2c, 0xb7, 0x5c, 0xaf, 0xe0, 0xe3, 0x57, 0x81, 0xaf, 0xe5, 0x9e, 0x4b, 0x45, 0x87, 0x78, 0x9e, 0xf6, 0x37, 0x47, 0xc4, 0x5c, 0xb0, 0xe7, 0x3f, 0xf9, 0xff, 0x7f, 0xc1, 0xff, 0x57, 0xef, 0xda, 0xe3, 0x97, 0x97, 0x97, 0xe3, 0xcb, 0xa9, 0xcd, 0xb0, 0x68, 0xcd, 0x2f, 0xbc, 0x9e, 0xda, 0xe3, 0xd8, 0xfe, 0x94, 0xff, 0xff, 0xcf, 0xfe, 0x5f, 0x48, 0x8d, 0xff, 0x81, 0x42, 0x70, 0x75, 0x75, 0x35, 0x07, 0xdc, 0xf9, 0x20, 0x67, 0xc3, 0xcd, 0xeb, 0x0d, 0x7c, 0x7c, 0x85, 0xfd, 0xf5, 0x1b, 0x34, 0x8a, 0xab, 0xe4, 0x5e, 0x48, 0xbd, 0xd7, 0xfd, 0x0b, 0x3f, 0x50, 0x7f, 0x9c, 0x3a, 0x21, 0x47, 0x77, 0x0c, 0xb1, 0x4e, 0x44, 0x65, 0xd7, 0x34, 0xbd, 0x0b, 0xcd, 0x7a, 0x46, 0xba, 0x49, 0xbb, 0x7d, 0xcb, 0xe9, 0x29, 0x27, 0xa4, 0xb9, 0x4e, 0xaf, 0x6f, 0xe1, 0xd8, 0x70, 0xcf, 0x75, 0xf3, 0x0c, 0x90, 0xae, 0xb0, 0x3b, 0x8c, 0x4e, 0x65, 0x61, 0x76, 0x7a, 0x8c, 0xaf, 0x9b, 0xe7, 0xe4, 0xf4, 0xfb, 0x06, 0x7e, 0xc8, 0x16, 0x82, 0x7a, 0xfd, 0xe7, 0x74, 0xa1, 0x99, 0x1e, 0x9d, 0x82, 0xa3, 0x2e, 0x6c, 0xea, 0x68, 0x26, 0x9d, 0x19, 0x1e, 0x69, 0x56, 0xdf, 0x35, 0xbb, 0xe4, 0xf4, 0x04, 0xd9, 0x1d, 0x4b, 0x08, 0x73, 0x99, 0x5c, 0x9b, 0xa9, 0x7d, 0x69, 0x2b, 0x60, 0x6e, 0xe8, 0x1d, 0x61, 0xda, 0x50, 0xe7, 0x70, 0xbf, 0x45, 0x07, 0xf2, 0x05, 0x60, 0xdd, 0x1c, 0xb8, 0x0e, 0x0b, 0x3d, 0xd7, 0x2e, 0xc4, 0x40, 0xeb, 0x02, 0x04, 0x06, 0xfa, 0x80, 0x61, 0x67, 0x81, 0xa6, 0x43, 0x61, 0xd9, 0x7a, 0xdf, 0x5c, 0xa7, 0xec, 0x4a, 0x76, 0x25, 0xa3, 0x28, 0xb9, 0x7c, 0x86, 0xb6, 0x6b, 0xd5, 0x66, 0xab, 0x54, 0x6d, 0x51, 0x73, 0xbb, 0x51, 0xa9, 0x54, 0xd5, 0x63, 0x25, 0x7b, 0xaf, 0x30, 0x03, 0xdd, 0x53, 0x88, 0x62, 0xd0, 0x56, 0xad, 0x76, 0x50, 0x2e, 0x35, 0x18, 0x9c, 0x8b, 0x81, 0xcb, 0x8f, 0xab, 0xd5, 0x36, 0x38, 0xd0, 0xbd, 0xdc, 0x34, 0x70, 0x4f, 0x81, 0x7c, 0x55, 0x55, 0xc9, 0xd2, 0x9e, 0xd3, 0xa9, 0xe7, 0x08, 0xd2, 0x3a, 0x1d, 0x61, 0xdb, 0x34, 0xd4, 0x35, 0x3a, 0x17, 0xa6, 0xb0, 0xf4, 0x0e, 0xd5, 0x2b, 0x95, 0x47, 0xab, 0xf5, 0xda, 0xa3, 0x0a, 0xa5, 0x4f, 0x75, 0xc7, 0x7e, 0x50, 0x5c, 0x64, 0x92, 0xd9, 0x05, 0x56, 0x8c, 0x4a, 0x69, 0xd2, 0xba, 0x5d, 0x8b, 0x18, 0x17, 0x88, 0x34, 0xd4, 0x0c, 0x5a, 0x84, 0x05, 0x24, 0x07, 0x79, 0xc6, 0xb0, 0xf0, 0x7c, 0x51, 0x59, 0xa7, 0x2d, 0xb7, 0x48, 0xe1, 0x19, 0x40, 0x78, 0x63, 0x8a, 0xa2, 0xe4, 0x77, 0x1f, 0x08, 0xb7, 0x25, 0x02, 0x7e, 0x43, 0x1c, 0x46, 0x68, 0x1e, 0x97, 0xea, 0x8c, 0xc5, 0x9c, 0xef, 0xf3, 0x55, 0xdc, 0x6c, 0x01, 0x9e, 0x70, 0x10, 0x17, 0xaa, 0x30, 0xbb, 0x3a, 0xbc, 0xa7, 0x39, 0xc1, 0xbd, 0x40, 0x86, 0x8b, 0xf9, 0xb2, 0x80, 0x14, 0x93, 0x85, 0xb7, 0x45, 0xd8, 0x71, 0xe7, 0x71, 0x5d, 0xaa, 0xe1, 0x33, 0xcd, 0x2e, 0xc9, 0x97, 0xdc, 0x5a, 0x81, 0xee, 0x52, 0xad, 0x11, 0x28, 0xe1, 0x53, 0xf2, 0x6f, 0xa4, 0x05, 0x28, 0x6b, 0x47, 0x95, 0x06, 0x10, 0xd7, 0xa8, 0x54, 0xdd, 0x09, 0xde, 0xa0, 0xae, 0x12, 0x70, 0x62, 0x0e, 0xab, 0x44, 0x13, 0xbe, 0x7c, 0x15, 0x5f, 0x59, 0x5b, 0x3f, 0x37, 0x55, 0x31, 0x72, 0xa0, 0x29, 0x5f, 0x2c, 0x9d, 0x51, 0xc1, 0x64, 0xd1, 0x8f, 0x45, 0x9c, 0x88, 0x2e, 0x82, 0xc7, 0x11, 0xe7, 0xc2, 0x62, 0x9d, 0xed, 0x62, 0x4c, 0x65, 0xd3, 0xbf, 0x3c, 0xeb, 0xc7, 0x4a, 0x67, 0x73, 0x5f, 0xd0, 0x26, 0xed, 0xef, 0xfa, 0x92, 0x54, 0x6a, 0xed, 0x55, 0xaa, 0x52, 0x5b, 0x9f, 0xc6, 0x4c, 0x18, 0x2c, 0x92, 0xcd, 0x3e, 0xe7, 0x20, 0xf7, 0xa8, 0xab, 0x39, 0x1a, 0xe7, 0x4d, 0x6b, 0x7f, 0x5b, 0x2d, 0x66, 0xa8, 0x51, 0x3a, 0xa4, 0xb4, 0x3d, 0xb0, 0x74, 0x07, 0xe1, 0x7f, 0xaa, 0x99, 0xcf, 0x28, 0x4b, 0x4b, 0x48, 0x8a, 0xc1, 0x32, 0x9d, 0xf6, 0x9d, 0x1e, 0xb9, 0xa6, 0x6b, 0x43, 0xb5, 0x9e, 0xb0, 0x04, 0x87, 0x81, 0x72, 0x02, 0x66, 0x31, 0x52, 0x43, 0xf3, 0xfa, 0xae, 0x43, 0x69, 0x4b, 0x18, 0x62, 0xa8, 0x99, 0x0e, 0x59, 0xe2, 0x1c, 0xb1, 0x6d, 0x2f, 0xae, 0x03, 0x8f, 0xe8, 0xf3, 0x4c, 0x3e, 0x93, 0xc9, 0xb0, 0x67, 0x1f, 0x95, 0x09, 0x09, 0x6c, 0x84, 0x42, 0x00, 0xfb, 0xe6, 0xc5, 0x6f, 0x02, 0x95, 0xa4, 0x78, 0x38, 0x72, 0x88, 0x63, 0x96, 0xb4, 0x0c, 0x36, 0x5a, 0x17, 0x8e, 0xa4, 0x66, 0xbd, 0x11, 0x30, 0x2a, 0xc4, 0x18, 0x4d, 0xe9, 0x8b, 0x0b, 0x88, 0x4e, 0x1f, 0x56, 0x15, 0x5d, 0xdd, 0xe9, 0x5b, 0xe4, 0x68, 0xa7, 0xcb, 0x64, 0xf6, 0x69, 0x03, 0xd4, 0xfb, 0xad, 0x4a, 0x73, 0x93, 0xef, 0x7b, 0x06, 0xe1, 0x8b, 0x01, 0xaf, 0x22, 0xf3, 0x42, 0x76, 0x30, 0x2f, 0x5c, 0x95, 0x62, 0x2b, 0xcd, 0x74, 0x87, 0xa5, 0x7a, 0x9c, 0x06, 0x54, 0xcd, 0xa4, 0x44, 0x0d, 0xc2, 0x98, 0x50, 0xb3, 0x04, 0x41, 0xb2, 0xa3, 0x9f, 0xbb, 0x7d, 0xd7, 0xf6, 0xc1, 0xe2, 0x62, 0xe0, 0x78, 0xeb, 0x94, 0xcf, 0x14, 0x21, 0x80, 0x43, 0x11, 0xe0, 0xa1, 0xa6, 0x1b, 0xda, 0xa9, 0x21, 0xb8, 0x70, 0x1c, 0x0b, 0x1a, 0x68, 0x9d, 0x67, 0x94, 0x47, 0xa1, 0x41, 0xb0, 0x6a, 0x96, 0xa5, 0x79, 0x36, 0x57, 0x1a, 0xb0, 0x62, 0xe1, 0x5d, 0xbd, 0xe3, 0xc0, 0x82, 0x9a, 0xe5, 0x51, 0xe9, 0xe0, 0xa0, 0xd6, 0x22, 0x53, 0x88, 0xae, 0xe8, 0x06, 0x06, 0x3d, 0x1d, 0xd1, 0x57, 0xfa, 0x13, 0xe2, 0x30, 0x2f, 0x7f, 0xa9, 0x96, 0x4b, 0xcd, 0x0a, 0xc1, 0x5d, 0xfa, 0xdd, 0x1c, 0xce, 0x38, 0x4e, 0x8b, 0x2b, 0x45, 0xf5, 0x0c, 0x17, 0x4a, 0x73, 0x70, 0xb8, 0x26, 0x52, 0x6e, 0x75, 0xa0, 0x8f, 0x84, 0x21, 0x6f, 0x0e, 0x83, 0x46, 0xd4, 0xed, 0x1b, 0xa9, 0x25, 0xea, 0x70, 0x14, 0xa1, 0x1e, 0x05, 0x92, 0x80, 0xea, 0xdb, 0xc8, 0xe6, 0x44, 0x59, 0x29, 0x48, 0x41, 0x85, 0x7c, 0x5c, 0xce, 0xea, 0x99, 0x85, 0xc2, 0x17, 0x48, 0x1b, 0x7a, 0x13, 0x16, 0xed, 0x9b, 0x58, 0xf8, 0xa8, 0x4e, 0xdf, 0xd1, 0x8c, 0x75, 0x2a, 0xc0, 0x31, 0x48, 0xbf, 0x02, 0x3d, 0x60, 0xfb, 0xe1, 0xd9, 0xb7, 0x9f, 0xa2, 0xf8, 0xf0, 0xa8, 0x94, 0x1d, 0x96, 0xa0, 0x0f, 0xca, 0xd9, 0x7e, 0xa5, 0xa9, 0x28, 0x9f, 0xcb, 0xb8, 0xb8, 0x6e, 0x4d, 0x6a, 0x9f, 0xd4, 0x5f, 0x89, 0x2c, 0x16, 0xe3, 0x40, 0x39, 0x08, 0x5c, 0x8a, 0xe1, 0xb6, 0x03, 0xdc, 0xf6, 0x77, 0xc0, 0x3d, 0x0a, 0x19, 0x87, 0x16, 0x8a, 0x23, 0x2f, 0x4d, 0x29, 0x11, 0x98, 0x40, 0x41, 0xaa, 0x9e, 0x8e, 0x54, 0x3f, 0x4b, 0x91, 0xaf, 0x3a, 0xa7, 0xaa, 0xff, 0x82, 0x7c, 0x65, 0xfe, 0x31, 0xb7, 0x72, 0x56, 0x9f, 0x7a, 0xef, 0x40, 0x6d, 0x27, 0x51, 0x87, 0x01, 0xdb, 0x29, 0xd4, 0x98, 0xff, 0x24, 0x9a, 0x77, 0x03, 0x5a, 0x7b, 0x82, 0x06, 0xaf, 0x74, 0x0c, 0xed, 0x62, 0xc0, 0x3d, 0xcf, 0xd0, 0x2f, 0xb8, 0x74, 0x23, 0x1f, 0x10, 0x1f, 0x04, 0x8f, 0x85, 0x3d, 0x2a, 0xec, 0x34, 0xa8, 0x42, 0xb2, 0x6e, 0x46, 0x97, 0xfd, 0x52, 0x65, 0x4b, 0xec, 0xd6, 0x43, 0xd4, 0xbd, 0xb0, 0xff, 0xcc, 0xa2, 0xb6, 0x43, 0xd4, 0xa8, 0x9b, 0x45, 0x0e, 0x9c, 0xc5, 0xdc, 0xaf, 0x32, 0xa6, 0x72, 0x54, 0x6a, 0xec, 0x97, 0xca, 0x07, 0x15, 0x32, 0xc3, 0xc6, 0x8d, 0x82, 0x12, 0x3d, 0xdf, 0x0e, 0x0b, 0xde, 0xc1, 0xf6, 0x43, 0xaa, 0x37, 0xaa, 0x0f, 0xaf, 0x6f, 0x5f, 0xdf, 0x6d, 0x4d, 0x64, 0x61, 0x60, 0xe8, 0xc2, 0x7c, 0x96, 0xd9, 0x55, 0x75, 0x04, 0x3c, 0xf4, 0x6b, 0xed, 0x1f, 0x4a, 0x30, 0xdd, 0xf6, 0x2d, 0x8b, 0xa3, 0x50, 0x75, 0x1f, 0xba, 0x45, 0xd9, 0x42, 0x21, 0xbf, 0x96, 0x5b, 0xc3, 0x2d, 0xb2, 0x99, 0xec, 0xbd, 0x5f, 0x64, 0xf2, 0xb9, 0xdc, 0x3d, 0xd8, 0x97, 0xcb, 0x78, 0x92, 0x4e, 0x35, 0x83, 0xf2, 0x83, 0x02, 0xce, 0xed, 0x99, 0x1d, 0xc2, 0xec, 0x4a, 0xe5, 0xa6, 0x6c, 0x24, 0x87, 0xb5, 0x9d, 0x08, 0xd7, 0x1e, 0x30, 0x2d, 0x70, 0x81, 0x69, 0xcb, 0xd6, 0x99, 0x43, 0x12, 0x48, 0x36, 0x59, 0x3c, 0xa9, 0x84, 0xac, 0x44, 0x1e, 0x65, 0xd0, 0x89, 0xa2, 0xf2, 0x6f, 0x0f, 0xb4, 0xe7, 0x26, 0x00, 0x96, 0xb8, 0xe8, 0x0f, 0xc5, 0x07, 0xda, 0x04, 0x1a, 0xf8, 0x7c, 0x54, 0x59, 0xb9, 0xd3, 0x61, 0x4f, 0x8c, 0x8c, 0xbf, 0x95, 0xc8, 0x81, 0x0d, 0xf4, 0x29, 0x85, 0xef, 0x15, 0x3b, 0xdf, 0x6c, 0xf8, 0x90, 0x6b, 0x82, 0x28, 0x6e, 0x88, 0x99, 0x25, 0x83, 0x81, 0x1a, 0x5b, 0x51, 0xf6, 0x70, 0x7b, 0x8e, 0xb3, 0x8a, 0x07, 0xd9, 0x24, 0x98, 0xd4, 0x80, 0xeb, 0x04, 0xb2, 0x14, 0xe7, 0xe5, 0x4d, 0xf1, 0x8a, 0xd9, 0x97, 0x8f, 0xc3, 0x94, 0x42, 0x73, 0xbd, 0xf6, 0xd8, 0x4b, 0x1e, 0x37, 0x36, 0x69, 0xa7, 0x51, 0xab, 0x4f, 0x5f, 0x19, 0x53, 0x40, 0x2c, 0x3a, 0xc9, 0xef, 0xd9, 0xca, 0x7d, 0xae, 0x04, 0xd2, 0x19, 0xef, 0xb0, 0x66, 0xc6, 0x6f, 0xf5, 0x71, 0x5e, 0x6a, 0x8c, 0x57, 0xc4, 0x49, 0xba, 0x78, 0x20, 0xac, 0x80, 0xd5, 0xa0, 0xe7, 0xd9, 0x7a, 0xc7, 0xfe, 0x10, 0x2f, 0x83, 0xd7, 0x99, 0xa1, 0x0f, 0xd4, 0xe1, 0x68, 0x75, 0x88, 0x46, 0x66, 0x8a, 0x73, 0x0d, 0xcd, 0xd9, 0x46, 0x63, 0xc5, 0xad, 0x87, 0xc2, 0xe8, 0x77, 0x74, 0xc7, 0xe3, 0x61, 0x36, 0xe8, 0xde, 0xfa, 0x32, 0x19, 0x42, 0x1b, 0x72, 0xa5, 0xd0, 0xa9, 0x6f, 0x02, 0x13, 0xed, 0x0d, 0x57, 0x0b, 0x98, 0x84, 0x65, 0x46, 0xe7, 0x38, 0xe5, 0xc0, 0x8f, 0x6c, 0xca, 0x43, 0x4e, 0xb5, 0xf2, 0xb0, 0xd4, 0xaa, 0xc8, 0x21, 0x2a, 0x6e, 0x6c, 0x3f, 0xd6, 0x25, 0x07, 0xef, 0x1a, 0x0e, 0xde, 0x4d, 0x1c, 0xbc, 0x38, 0x07, 0xb0, 0x70, 0x07, 0x98, 0x77, 0x26, 0xf6, 0xd5, 0x43, 0x0b, 0xfb, 0x41, 0x78, 0x42, 0x23, 0xd2, 0x46, 0xba, 0xcd, 0x97, 0x7c, 0xfe, 0x74, 0x84, 0xae, 0x33, 0x7a, 0x8a, 0x9e, 0xb6, 0x44, 0xc3, 0xd1, 0x53, 0x30, 0xb8, 0x8b, 0x1e, 0x4d, 0x69, 0xb4, 0x77, 0x0c, 0xec, 0x0e, 0x65, 0x57, 0x91, 0x53, 0xdf, 0xbc, 0xf8, 0x2d, 0x1e, 0xa2, 0xde, 0xca, 0x9c, 0xe2, 0x51, 0xc2, 0xfa, 0xe4, 0xa7, 0xa2, 0xd4, 0x1f, 0x3f, 0x97, 0x82, 0x81, 0x33, 0xac, 0x8b, 0x72, 0x80, 0xf3, 0x63, 0x65, 0x02, 0x63, 0xaa, 0xd0, 0x68, 0xfe, 0x89, 0xf4, 0xae, 0xa4, 0xcc, 0x6c, 0x44, 0x99, 0x10, 0x51, 0xc6, 0xfa, 0xde, 0x4d, 0xa4, 0xb3, 0xe9, 0x72, 0x42, 0x9e, 0x7f, 0x69, 0x25, 0x19, 0xc1, 0x09, 0xd5, 0xbd, 0x6b, 0x54, 0x6f, 0x5f, 0xa3, 0x7a, 0x7b, 0x56, 0x75, 0x6f, 0x56, 0xf5, 0xb0, 0x6e, 0xfb, 0xd5, 0x60, 0x42, 0x29, 0x61, 0x37, 0x52, 0xce, 0x26, 0x67, 0x98, 0x5c, 0xbe, 0x63, 0xbb, 0xf8, 0x82, 0xb9, 0xc1, 0xad, 0x59, 0x65, 0xd6, 0x01, 0xfe, 0x54, 0xae, 0xcc, 0xde, 0x2e, 0x84, 0x67, 0xf1, 0x97, 0xc1, 0x5f, 0x9e, 0xf2, 0x3c, 0xc8, 0xd7, 0x1b, 0x49, 0x79, 0x32, 0xbd, 0x76, 0xeb, 0x4d, 0x4c, 0x7b, 0x2e, 0xa6, 0x74, 0x8b, 0x3e, 0xbe, 0x9f, 0x9c, 0x0d, 0x6c, 0x15, 0xdf, 0x45, 0xae, 0x48, 0x82, 0xfc, 0x01, 0xca, 0x4e, 0xc2, 0x0c, 0xcd, 0x76, 0xf8, 0xd2, 0xfc, 0x1c, 0xf4, 0x9e, 0xcc, 0x84, 0x1e, 0x65, 0x20, 0x78, 0x95, 0xb4, 0xfc, 0xee, 0xb7, 0xa6, 0x90, 0xd4, 0x6f, 0x33, 0x01, 0xb9, 0x4c, 0x88, 0x49, 0xa9, 0x49, 0x22, 0x6e, 0xa1, 0x5c, 0x66, 0x79, 0xa0, 0xda, 0x78, 0x10, 0x56, 0xee, 0x18, 0x5f, 0xbf, 0x90, 0xc5, 0xe5, 0x00, 0xa1, 0x72, 0x80, 0x91, 0x66, 0x06, 0x2f, 0xa6, 0x9b, 0x7f, 0x98, 0x99, 0x26, 0x9b, 0xd1, 0x30, 0x56, 0x15, 0x83, 0x76, 0xe5, 0x58, 0x5c, 0x52, 0x7a, 0xc2, 0x40, 0x55, 0xb3, 0xe9, 0x43, 0xbb, 0x95, 0xb9, 0x09, 0x46, 0x51, 0x5f, 0xed, 0x48, 0xd7, 0xbb, 0x5c, 0x49, 0x9a, 0x9b, 0x3b, 0xb4, 0x71, 0x87, 0xee, 0x34, 0xe9, 0xce, 0xa6, 0x5f, 0x6e, 0x80, 0xa8, 0x62, 0x56, 0x4d, 0x4f, 0xb0, 0x46, 0xc8, 0x15, 0xf9, 0x25, 0x95, 0xcd, 0xd2, 0x6e, 0x09, 0x57, 0xa5, 0xf0, 0x5f, 0x7c, 0x59, 0x60, 0x24, 0xf1, 0x23, 0x36, 0x52, 0x79, 0xef, 0xf1, 0xce, 0xc7, 0x8c, 0x1a, 0xa1, 0xca, 0xf8, 0xd4, 0x30, 0x1d, 0xb5, 0xe7, 0x76, 0x27, 0x5e, 0xe2, 0xd0, 0x8c, 0x1a, 0xe6, 0xa4, 0x93, 0xe1, 0x1b, 0xac, 0xb2, 0xdd, 0xe2, 0x80, 0xfd, 0xcc, 0xff, 0x4f, 0x11, 0xf4, 0x84, 0x75, 0xfa, 0x6c, 0x52, 0x15, 0xb2, 0xd3, 0x4d, 0x34, 0x13, 0x5c, 0x32, 0xd9, 0x63, 0x02, 0x1b, 0xc9, 0x85, 0xef, 0xda, 0x1b, 0x69, 0x20, 0x06, 0x39, 0x90, 0x10, 0x91, 0x68, 0xe5, 0xb9, 0xd5, 0x19, 0x92, 0x49, 0x24, 0x24, 0xe5, 0xc4, 0x69, 0x30, 0x20, 0x2d, 0x4d, 0x68, 0x22, 0x73, 0x96, 0x6b, 0xf8, 0xfa, 0x59, 0xe5, 0xaf, 0x4d, 0xfa, 0x18, 0x73, 0x32, 0x1b, 0x85, 0xa2, 0xb9, 0x2d, 0xd0, 0x29, 0x78, 0x8c, 0x0d, 0x33, 0xb2, 0xae, 0x40, 0x9a, 0x6f, 0xef, 0x72, 0xab, 0xca, 0xd5, 0x6a, 0x0d, 0x8f, 0x3b, 0xb5, 0xc4, 0xcc, 0x43, 0x07, 0x35, 0x38, 0x3d, 0xa8, 0x52, 0xd9, 0x29, 0xc4, 0x44, 0x3f, 0x8f, 0x23, 0xc6, 0x5b, 0x3a, 0xfd, 0x12, 0x98, 0xfb, 0xc9, 0xde, 0xc4, 0xb8, 0x81, 0x6a, 0x12, 0xce, 0xcc, 0xd7, 0x68, 0xfb, 0xa0, 0x79, 0x3d, 0x71, 0xac, 0xfe, 0x05, 0xa4, 0x51, 0xd8, 0xe0, 0x22, 0xff, 0x05, 0xa0, 0x56, 0xbc, 0x10, \ No newline at end of file diff --git a/build/baremetalpi/Makefile b/build/baremetalpi/Makefile index 1cae06e99..d394d75e2 100644 --- a/build/baremetalpi/Makefile +++ b/build/baremetalpi/Makefile @@ -25,6 +25,7 @@ CFLAGS += -I "$(NEWLIBDIR)/include" -I $(STDDEF_INCPATH) -I $(CIRCLESTDLIB)/incl LIBS := \ $(TIC80LIB)/libtic80studio.a \ $(TIC80LIB)/libtic80core.a \ + $(TIC80LIB)/libforth.a \ $(TIC80LIB)/libgiflib.a \ $(TIC80LIB)/liblpeg.a \ $(TIC80LIB)/libluaapi.a \ diff --git a/cmake/core.cmake b/cmake/core.cmake index 894b535d8..da1ddec28 100644 --- a/cmake/core.cmake +++ b/cmake/core.cmake @@ -113,6 +113,10 @@ if(BUILD_STATIC) target_link_libraries(tic80core PRIVATE wasm) endif() + if(BUILD_WITH_FORTH) + target_link_libraries(tic80core PRIVATE forth) + endif() + target_link_libraries(tic80core PRIVATE runtime) endif() diff --git a/cmake/forth.cmake b/cmake/forth.cmake new file mode 100644 index 000000000..4159b83e0 --- /dev/null +++ b/cmake/forth.cmake @@ -0,0 +1,265 @@ +################################ +# Forth (pForth) +################################ + +option(BUILD_WITH_FORTH "Forth Enabled" ${BUILD_WITH_ALL}) +message("BUILD_WITH_FORTH: ${BUILD_WITH_FORTH}") + +if(BUILD_WITH_FORTH) + + set(PFORTH_DIR ${THIRDPARTY_DIR}/pforth/csrc) + set(PFORTH_FTH_DIR ${THIRDPARTY_DIR}/pforth/fth) + + # ------------------------------------------------------------------------- + # pforth C sources — all files from sources.cmake, excluding the default + # pfcustom.c (our forth.c serves as the replacement). + # ------------------------------------------------------------------------- + set(PFORTH_KERNEL_SOURCES + ${PFORTH_DIR}/pf_cglue.c + ${PFORTH_DIR}/pf_clib.c + ${PFORTH_DIR}/pf_core.c + ${PFORTH_DIR}/pf_inner.c + ${PFORTH_DIR}/pf_io.c + ${PFORTH_DIR}/pf_io_none.c + ${PFORTH_DIR}/pf_mem.c + ${PFORTH_DIR}/pf_save.c + ${PFORTH_DIR}/pf_text.c + ${PFORTH_DIR}/pf_words.c + ${PFORTH_DIR}/pfcompil.c + ${PFORTH_DIR}/paging/pagedmem.c + ${PFORTH_DIR}/paging/lockpage.c + ${PFORTH_DIR}/paging/qadmpage.c + ) + + # ------------------------------------------------------------------------- + # pfdicdat.h — pforth standard dictionary, bootstrapped at configure time. + # + # vendor/pforth/csrc/pfdicdat.h is gitignored; it is generated here by + # building pforth natively on the host. This guarantees the dictionary + # stays in sync with the pinned pforth submodule automatically — no + # manual copy step needed. + # + # For WASM/Emscripten the dictionary must reflect a 32-bit cell size, so + # pforth is built with -m32 (requires gcc-multilib on Linux hosts). + # + # To force regeneration after a pforth submodule update: + # rm vendor/pforth/csrc/pfdicdat.h && cmake + # ------------------------------------------------------------------------- + set(PFORTH_DICDAT ${PFORTH_DIR}/pfdicdat.h) + + # Always regenerate: a stale 32-bit pfdicdat.h in a 64-bit build (or vice + # versa) causes a silent segfault at Forth VM startup. Regeneration runs + # at cmake configure time (not every build) so the cost is acceptable. + file(REMOVE ${PFORTH_DICDAT}) + if(NOT EXISTS ${PFORTH_DICDAT}) + message(STATUS "Forth: bootstrapping pforth to generate pfdicdat.h...") + set(_PFORTH_BOOTSTRAP_DIR "${CMAKE_BINARY_DIR}/pforth_bootstrap") + + if(CMAKE_CROSSCOMPILING) + # Cross-compilation: CC may be set to the cross-compiler + # (emcc, arm-none-eabi-gcc, …). Always force the host native + # gcc so pforth runs on the build machine. + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + # 32-bit target (WASM, 3DS, RPI bare-metal, …): build a + # 32-bit host pforth to produce a matching dictionary. + # Requires gcc-multilib on Linux hosts. + set(_PFORTH_EXTRA_ARGS + -DCMAKE_C_COMPILER=gcc + -DCMAKE_C_FLAGS=-m32 + -DCMAKE_C_COMPILER_WORKS=TRUE + -DCMAKE_CXX_COMPILER_WORKS=TRUE) + else() + # 64-bit cross target (Switch ARM64, …): native host pforth. + set(_PFORTH_EXTRA_ARGS + -DCMAKE_C_COMPILER=gcc + -DCMAKE_C_COMPILER_WORKS=TRUE + -DCMAKE_CXX_COMPILER_WORKS=TRUE) + endif() + else() + # Native build: let cmake pick the host compiler automatically. + set(_PFORTH_EXTRA_ARGS + -DCMAKE_C_COMPILER_WORKS=TRUE + -DCMAKE_CXX_COMPILER_WORKS=TRUE) + endif() + + # Write a custom CMakeLists.txt for the bootstrap that uses the portable + # stdio I/O module (getchar/putchar) instead of win32_console (_getch). + # win32_console reads from the Windows console input buffer (CONIN$) via + # _getch(), which blocks indefinitely on CI runners that have a console + # but no keyboard input. The stdio module reads from stdin (fd 0) which + # respects the INPUT_FILE redirect to the null device. + set(_PFORTH_BOOTSTRAP_SRC "${CMAKE_BINARY_DIR}/pforth_bootstrap_src") + file(MAKE_DIRECTORY "${_PFORTH_BOOTSTRAP_SRC}") + file(WRITE "${_PFORTH_BOOTSTRAP_SRC}/CMakeLists.txt" [=[ +cmake_minimum_required(VERSION 3.6) +project(PForth) +message(STATUS "Configuring pforth bootstrap (stdio I/O)...") + +if(NOT DEFINED PFORTH_VENDOR_DIR) + message(FATAL_ERROR "PFORTH_VENDOR_DIR must be defined") +endif() + +set(PFORTH_CSRC "${PFORTH_VENDOR_DIR}/csrc") +set(PFORTH_FTH "${PFORTH_VENDOR_DIR}/fth") + +file(STRINGS "${PFORTH_CSRC}/sources.cmake" _raw) +set(_c_srcs) +foreach(_f ${_raw}) + if(_f MATCHES "\\.c$") + list(APPEND _c_srcs "${PFORTH_CSRC}/${_f}") + endif() +endforeach() +list(APPEND _c_srcs + "${PFORTH_CSRC}/stdio/pf_fileio_stdio.c" + "${PFORTH_CSRC}/stdio/pf_io_stdio.c") + +if(MSVC) + add_compile_options(/W3 /wd4701 /wd4244 /wd4267 /wd4127 /wd4100 /wd4456) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +elseif(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + add_compile_options(-w) +endif() + +add_library(PForth_lib STATIC ${_c_srcs}) +target_compile_definitions(PForth_lib PRIVATE PF_SUPPORT_FP) +target_include_directories(PForth_lib PRIVATE "${PFORTH_CSRC}") + +# Put pforth.exe directly in fth/ (no Debug/ subdir) on all generators. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PFORTH_FTH}") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${PFORTH_FTH}") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PFORTH_FTH}") + +add_executable(pforth "${PFORTH_CSRC}/pf_main.c") +target_link_libraries(pforth PForth_lib) +target_include_directories(pforth PRIVATE "${PFORTH_CSRC}") +if(UNIX OR APPLE) + target_link_libraries(pforth m) +endif() +]=]) + + execute_process( + COMMAND ${CMAKE_COMMAND} + -DPFORTH_VENDOR_DIR=${THIRDPARTY_DIR}/pforth + -DCMAKE_BUILD_TYPE=Release + ${_PFORTH_EXTRA_ARGS} + -S "${_PFORTH_BOOTSTRAP_SRC}" -B "${_PFORTH_BOOTSTRAP_DIR}" + RESULT_VARIABLE _pforth_cfg_result + ) + # Build in Release to avoid MSVC Debug overhead (runtime checks on every + # function call make pforth ~10-20x slower, causing the 120s timeout). + execute_process( + COMMAND ${CMAKE_COMMAND} --build "${_PFORTH_BOOTSTRAP_DIR}" + --target pforth --config Release + RESULT_VARIABLE _pforth_build_result + TIMEOUT 180 + ) + + # pforth.exe is directly in PFORTH_FTH_DIR on all platforms/generators + # because we force CMAKE_RUNTIME_OUTPUT_DIRECTORY* in the bootstrap. + if(CMAKE_HOST_WIN32) + set(_pforth_exe "${PFORTH_FTH_DIR}/pforth.exe") + set(_pforth_null "NUL") + else() + set(_pforth_exe "${PFORTH_FTH_DIR}/pforth") + set(_pforth_null "/dev/null") + endif() + + # Build pforth.dic (initialise the full standard dictionary). + # INPUT_FILE redirects stdin to the null device so pforth's getchar() + # returns EOF immediately instead of waiting for interactive input. + execute_process( + COMMAND "${_pforth_exe}" -i "${PFORTH_FTH_DIR}/system.fth" + WORKING_DIRECTORY "${PFORTH_FTH_DIR}" + INPUT_FILE "${_pforth_null}" + RESULT_VARIABLE _pforth_dic_result + OUTPUT_QUIET + ERROR_QUIET + TIMEOUT 300 + ) + + # Export the dictionary as a C header. + execute_process( + COMMAND "${_pforth_exe}" "${PFORTH_FTH_DIR}/mkdicdat.fth" + WORKING_DIRECTORY "${PFORTH_FTH_DIR}" + INPUT_FILE "${_pforth_null}" + RESULT_VARIABLE _pforth_dicdat_result + OUTPUT_QUIET + ERROR_QUIET + TIMEOUT 120 + ) + execute_process( + COMMAND ${CMAKE_COMMAND} -E rename + "${PFORTH_FTH_DIR}/pfdicdat.h" + "${PFORTH_DIR}/pfdicdat.h" + ) + + if(NOT EXISTS ${PFORTH_DICDAT}) + if(CMAKE_SIZEOF_VOID_P EQUAL 4 AND CMAKE_CROSSCOMPILING) + message(FATAL_ERROR + "Forth: could not generate 32-bit pfdicdat.h.\n" + "A 32-bit host gcc is required. On Linux:\n" + " sudo apt-get install gcc-multilib\n" + "Then delete the build directory and re-run cmake.") + else() + message(FATAL_ERROR + "Forth: failed to generate pfdicdat.h.\n" + "Try manually:\n" + " cd vendor/pforth && cmake . && make pforth_dic_header") + endif() + endif() + + message(STATUS "Forth: pfdicdat.h generated successfully") + endif() + + # ------------------------------------------------------------------------- + # forthdemo.tic.dat — demo cartridge for 'new forth' command. + # Source: demos/forthdemo.fth + # Regenerate (after building prj2cart and bin2txt): + # prj2cart demos/forthdemo.fth /tmp/forthdemo.tic + # bin2txt /tmp/forthdemo.tic build/assets/forthdemo.tic.dat -z + # ------------------------------------------------------------------------- + + # ------------------------------------------------------------------------- + # TIC-80 forth library + # ------------------------------------------------------------------------- + set(FORTH_SRC + ${PFORTH_KERNEL_SOURCES} + ${CMAKE_SOURCE_DIR}/src/api/forth.c + ${CMAKE_SOURCE_DIR}/src/api/parse_note.c + ) + + add_library(forth ${TIC_RUNTIME} ${FORTH_SRC}) + + if(NOT BUILD_STATIC) + set_target_properties(forth PROPERTIES PREFIX "") + endif() + + target_compile_definitions(forth INTERFACE TIC_BUILD_WITH_FORTH=1) + + target_compile_definitions(forth PRIVATE + PF_STATIC_DIC # load the pre-compiled dictionary from pfdicdat.h + PF_SUPPORT_FP # enable floating-point word set + PF_NO_FILEIO # stub out file I/O (no filesystem in cartridges) + PF_DEMAND_PAGING=0 + ) + + + target_link_libraries(forth PRIVATE runtime) + + target_include_directories(forth + PRIVATE + # pfdicdat.h lives here; must come before any other include that + # could shadow it. + ${PFORTH_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/src + ) + + # Suppress warnings from pforth's own C files to avoid noise in TIC-80 + # build logs (pforth was not written to match TIC-80's warning flags). + foreach(SRC ${PFORTH_KERNEL_SOURCES}) + set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS + "-w") + endforeach() + +endif(BUILD_WITH_FORTH) diff --git a/demos/bunny/forthmark.fth b/demos/bunny/forthmark.fth new file mode 100644 index 000000000..517ba7d25 --- /dev/null +++ b/demos/bunny/forthmark.fth @@ -0,0 +1,187 @@ +\ title: Bunnymark in Forth +\ author: luginf +\ desc: Benchmarking tool to see how many bunnies can fly around the screen, using Forth. +\ license: MIT License +\ input: gamepad +\ script: forth +\ version: 1.1.0 + +240 CONSTANT SCREEN-W +136 CONSTANT SCREEN-H + 6 CONSTANT TOOLBAR-H + 26 CONSTANT BUNNY-W + 32 CONSTANT BUNNY-H + +\ --- raw byte access via generic PEEK/POKE (bits=8) --------------------- +\ PEEK ( addr bits -- val ) POKE ( addr val bits -- ) +: @u8 ( addr -- u8 ) 8 PEEK ; +: !u8 ( u8 addr -- ) SWAP 8 POKE ; + +\ u16 little-endian at byte address +: @u16 ( addr -- u16 ) + DUP @u8 SWAP 1+ @u8 256 * OR ; +: !u16 ( u16 addr -- ) + OVER 255 AND OVER !u8 + SWAP 256 / SWAP 1+ !u8 ; + +\ sign-extend u8 (0-255) to signed integer +: @s8 ( addr -- n ) @u8 DUP 127 > IF 256 - THEN ; +: !s8 ( n addr -- ) !u8 ; + +\ --- bunny data in TIC-80 RAM (sprites bank 1 + map, both unused here) -- +\ +\ TIC-80 RAM layout (relevant regions): +\ $04000 8 KB tiles bank 0 ← bunny sprite lives here, read by SPR +\ $06000 8 KB sprites bank 1 (second editor tab, no in file) +\ $08000 32 KB map (no in file) +\ +\ Sprites bank 1 and map are contiguous and empty: 40832 bytes available. +\ We pack 4 flat arrays there (no dictionary ALLOT needed): +\ bx [i] at BX-BASE + i*2 u16 8.8-fp (256 units/pixel) +\ by [i] at BY-BASE + i*2 u16 8.8-fp +\ bvx[i] at BVX-BASE + i s8 2.6-fp (64 units/pixel/frame) +\ bvy[i] at BVY-BASE + i s8 2.6-fp +\ total: 6800 * 6 = 40800 bytes + +6800 CONSTANT MAX-BUNNIES + +$6000 CONSTANT BX-BASE +BX-BASE MAX-BUNNIES 2 * + CONSTANT BY-BASE +BY-BASE MAX-BUNNIES 2 * + CONSTANT BVX-BASE +BVX-BASE MAX-BUNNIES + CONSTANT BVY-BASE + +: bx-addr ( i -- addr ) 2 * BX-BASE + ; +: by-addr ( i -- addr ) 2 * BY-BASE + ; +: bvx-addr ( i -- addr ) BVX-BASE + ; +: bvy-addr ( i -- addr ) BVY-BASE + ; + +\ clamping limits in 8.8 fp +SCREEN-W BUNNY-W - 256 * CONSTANT X-MAX-FP +SCREEN-H BUNNY-H - 256 * CONSTANT Y-MAX-FP +TOOLBAR-H 256 * CONSTANT Y-MIN-FP + +VARIABLE nbunnies 0 nbunnies ! + +\ --- LCG PRNG ----------------------------------------------------------- +VARIABLE seed +: rnd-init TIME seed ! ; +: rnd seed @ 1664525 * 1013904223 + DUP seed ! ; +: rnd-n ( n -- r ) rnd ABS SWAP MOD ; +: rnd-speed ( -- s8 ) 200 rnd-n 100 - 64 * 60 / ; + +\ --- spawn / remove ------------------------------------------------------ +: spawn-bunny ( -- ) + nbunnies @ MAX-BUNNIES < IF + nbunnies @ >R + SCREEN-W BUNNY-W - rnd-n 256 * R@ bx-addr !u16 + SCREEN-H BUNNY-H - TOOLBAR-H - rnd-n TOOLBAR-H + 256 * R@ by-addr !u16 + rnd-speed R@ bvx-addr !s8 + rnd-speed R@ bvy-addr !s8 + R> DROP + nbunnies @ 1+ nbunnies ! + THEN +; + +: remove-bunny ( -- ) + nbunnies @ 0> IF nbunnies @ 1- nbunnies ! THEN +; + +\ --- per-bunny physics --------------------------------------------------- +\ flip-vx/vy: negate stored velocity for bunny i, leaving i on stack +: flip-vx ( i -- i ) DUP bvx-addr @s8 NEGATE OVER bvx-addr !s8 ; +: flip-vy ( i -- i ) DUP bvy-addr @s8 NEGATE OVER bvy-addr !s8 ; + +: update-bunny ( i -- ) + >R + \ x axis: new_x = x_fp + vx_s8 * 4 (convert 1/64 → 1/256 units) + R@ bvx-addr @s8 4 * R@ bx-addr @u16 + + DUP X-MAX-FP > IF DROP X-MAX-FP R@ flip-vx DROP THEN + DUP 0< IF DROP 0 R@ flip-vx DROP THEN + R@ bx-addr !u16 + \ y axis + R@ bvy-addr @s8 4 * R@ by-addr @u16 + + DUP Y-MAX-FP > IF DROP Y-MAX-FP R@ flip-vy DROP THEN + DUP Y-MIN-FP < IF DROP Y-MIN-FP R@ flip-vy DROP THEN + R@ by-addr !u16 + R> DROP +; + +: draw-bunny ( i -- ) + >R + 1 + R@ bx-addr @u16 256 / + R@ by-addr @u16 256 / + 1 1 0 0 4 4 + SPR + R> DROP +; + +\ --- FPS counter -------------------------------------------------------- +VARIABLE fps-value +VARIABLE fps-frames +VARIABLE fps-last + +: fps-init 0 fps-value ! 0 fps-frames ! TIME fps-last ! ; + +: fps-update ( -- ) + TIME fps-last @ - 1000 <= IF + fps-frames @ 1+ fps-frames ! + ELSE + fps-frames @ fps-value ! + 0 fps-frames ! + TIME fps-last ! + THEN +; + +\ --- string helpers ----------------------------------------------------- +: n>str ( n -- c-addr u ) S>D <# #S #> ; +: str-at ( c-addr u x y -- ) 11 FALSE 1 FALSE PRINT DROP ; + +\ --- HUD ---------------------------------------------------------------- +: print-hud ( -- ) + 0 0 SCREEN-W TOOLBAR-H 0 RECT + S" Bunnies: " 1 0 str-at + nbunnies @ n>str 55 0 str-at + S" FPS: " SCREEN-W 2/ 0 str-at + fps-value @ n>str SCREEN-W 2/ 25 + 0 str-at +; + +\ --- BOOT / TIC --------------------------------------------------------- +: BOOT + rnd-init + fps-init + spawn-bunny +; + +: TIC + 0 BTN IF 5 0 DO spawn-bunny LOOP THEN + 1 BTN IF 5 0 DO remove-bunny LOOP THEN + nbunnies @ 0 ?DO I update-bunny LOOP + fps-update + 15 CLS + nbunnies @ 0 ?DO I draw-bunny LOOP + print-hud +; + +\ +\ 001:11111100111110dd111110dc111110dc111110dc111110dc111110dd111110dd +\ 002:00011110ddd0110dccd0110dccd0110dccd0110dccd0110dcddd00dddddddddd +\ 003:00001111dddd0111cccd0111cccd0111cccd0111cccd0111dcdd0111dddd0111 +\ 004:1111111111111111111111111111111111111111111111111111111111111111 +\ 017:111110dd111110dd111110dd111110dd10000ddd1eeeeddd1eeeeedd10000eed +\ 018:d0ddddddd0ddddddddddddddddd0000dddddccddddddccdddddddddddddddddd +\ 019:0ddd01110ddd0111dddd0111dddd0111ddddd000ddddddddddddddddddddd000 +\ 020:1111111111111111111111111111111101111111d0111111d011111101111111 +\ 033:111110ee111110ee111110ee111110ee111110ee111110ee111110ee111110ee +\ 034:dddcccccddccccccddccccccddccccccddccccccdddcccccdddddddddddddddd +\ 035:dddd0111cddd0111cddd0111cddd0111cddd0111dddd0111dddd0111dddd0111 +\ 036:1111111111111111111111111111111111111111111111111111111111111111 +\ 049:111110ee111110ee111110ee111110ee111110ee111110ee111110ee11111100 +\ 050:dddeeeeeddeeeeeed00000000111111101111111011111110111111111111111 +\ 051:eddd0111eedd01110eed011110ee011110ee011110ee011110ee011111001111 +\ 052:1111111111111111111111111111111111111111111111111111111111111111 +\ + +\ +\ 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57 +\ diff --git a/demos/forthdemo.fth b/demos/forthdemo.fth new file mode 100644 index 000000000..87bdac9df --- /dev/null +++ b/demos/forthdemo.fth @@ -0,0 +1,55 @@ +\ title: game title +\ author: game developer, email, etc. +\ desc: short description +\ site: website link +\ license: MIT License (change this to your license of choice) +\ version: 0.1 +\ script: forth + +VARIABLE px \ sprite x +VARIABLE py \ sprite y + +: BOOT + 96 px ! 24 py ! +; + +: TIC + 0 BTN IF py @ 1- py ! THEN + 1 BTN IF py @ 1+ py ! THEN + 2 BTN IF px @ 1- px ! THEN + 3 BTN IF px @ 1+ px ! THEN + 13 CLS + 1 px @ py @ 14 3 0 0 2 2 SPR + S" Hello Forth!" 84 84 15 FALSE 1 FALSE PRINT DROP +; + + +\ +\ 001:eccccccccc888888caaaaaaaca888888cacccccccacc0ccccacc0ccccacc0ccc +\ 002:ccccceee8888cceeaaaa0cee888a0ceeccca0ccc0cca0c0c0cca0c0c0cca0c0c +\ 003:eccccccccc888888caaaaaaaca888888cacccccccacccccccacc0ccccacc0ccc +\ 004:ccccceee8888cceeaaaa0cee888a0ceeccca0cccccca0c0c0cca0c0c0cca0c0c +\ 017:cacccccccaaaaaaacaaacaaacaaaaccccaaaaaaac8888888cc000cccecccccec +\ 018:ccca00ccaaaa0ccecaaa0ceeaaaa0ceeaaaa0cee8888ccee000cceeecccceeee +\ 019:cacccccccaaaaaaacaaacaaacaaaaccccaaaaaaac8888888cc000cccecccccec +\ 020:ccca00ccaaaa0ccecaaa0ceeaaaa0ceeaaaa0cee8888ccee000cceeecccceeee +\ + +\ +\ 000:00000000ffffffff00000000ffffffff +\ 001:0123456789abcdeffedcba9876543210 +\ 002:0123456789abcdef0123456789abcdef +\ + +\ +\ 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000304000000000 +\ + +\ +\ 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57 +\ + +\ +\ 000:100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +\ + diff --git a/src/api/forth.c b/src/api/forth.c new file mode 100644 index 000000000..e45ca886d --- /dev/null +++ b/src/api/forth.c @@ -0,0 +1,1263 @@ +// MIT License + +// Copyright (c) 2024 TIC-80 contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Forth language integration for TIC-80 using pForth. +// +// This file serves three roles: +// 1. pforth I/O layer: routes pforth output to TIC-80 trace, stubs file I/O. +// 2. Replacement for pForth's pfcustom.c: defines CustomFunctionTable[] and +// CompileCustomFunctions() which register TIC-80 API words in the Forth +// dictionary. +// 3. TIC-80 tic_script implementation: lifecycle (init/close/tick/boot/etc.) +// and the exported tic_script descriptor. +// +// Stack convention used by all wrappers: +// All words are registered with NumParams=0. Each wrapper pops its own +// arguments from the pforth data stack with pfPopFromStack(). Arguments are +// popped in reverse order (TOS = last/rightmost parameter in Forth notation). +// Example: for ( x y color -- ), pop color first, then y, then x. + +#include "core/core.h" + +// pf_all.h is pforth's internal umbrella header; it defines Err, cell_t, +// CFunc0 and everything needed by pfcustom.c replacements. +// Undefine TIC-80's MIN/MAX first to avoid redefinition warnings from +// pforth's pf_guts.h, which defines its own versions. +#undef MIN +#undef MAX +#include "pf_all.h" + +#include +#include +#include +#include + +extern bool parse_note(const char* noteStr, s32* note, s32* octave); + +// ============================================================================= +// pforth I/O layer +// +// Routes all pforth terminal output to the TIC-80 trace callback. +// File I/O is stubbed out (not needed for cartridge execution). +// ============================================================================= + +#define FORTH_IO_BUF 256 + +static char gOutBuf[FORTH_IO_BUF]; +static int gOutLen = 0; +static tic_tick_data* gTickData = NULL; + +static void flushOut(void) +{ + if (gOutLen > 0 && gTickData && gTickData->trace) + { + gOutBuf[gOutLen] = '\0'; + gTickData->trace(gTickData->data, gOutBuf, 15); + gOutLen = 0; + } +} + +static void forthInitIO(tic_tick_data* tickData) +{ + gTickData = tickData; + gOutLen = 0; +} + +static void forthTermIO(void) +{ + flushOut(); + gTickData = NULL; +} + +// Returns buffered output as a C string; used for error reporting. +static const char* forthGetOutputBuffer(void) +{ + gOutBuf[gOutLen] = '\0'; + return gOutBuf; +} + +static void forthClearOutputBuffer(void) +{ + gOutLen = 0; +} + +static void forthFlushOutput(void) +{ + flushOut(); +} + +// ---- pforth terminal I/O callbacks ------------------------------------------ + +int sdTerminalOut(char c) +{ + if (gOutLen < FORTH_IO_BUF - 1) + gOutBuf[gOutLen++] = c; + + if (c == '\n' || gOutLen >= FORTH_IO_BUF - 1) + flushOut(); + + return 0; +} + +int sdTerminalEcho(char c) +{ + return sdTerminalOut(c); +} + +int sdTerminalIn(void) +{ + return -1; +} + +int sdTerminalFlush(void) +{ + flushOut(); + return 0; +} + +int sdQueryTerminal(void) +{ + return 0; +} + +void sdTerminalInit(void) {} +void sdTerminalTerm(void) {} + +cell_t sdSleepMillis(cell_t msec) +{ + (void)msec; + return 0; +} + +// ============================================================================= +// Global state +// pforth is single-instance (global variables in its C implementation), so one +// global pointer to the active TIC-80 core is sufficient. +// ============================================================================= + +static tic_core* gForthCore = NULL; +static PForthTask gForthTask = NULL; + +// Scratch buffer for converting Forth counted strings ( c-addr u ) to C strings +#define FORTH_STR_BUF 1024 +static char gStrBuf[FORTH_STR_BUF]; + +// pfInterpretText is limited to TIB_SIZE (256) characters per call, but pforth +// preserves compiler state (gVarState, dictionary position) across calls, so +// feeding one line at a time works correctly for multi-line definitions. +static ThrowCode forthInterpretLines(const char* source) +{ + char line[TIB_SIZE]; + + const char* p = source; + while (*p) + { + const char* end = strchr(p, '\n'); + if (!end) end = p + strlen(p); + + // Strip trailing CR so Windows line endings (\r\n) work too. + const char* lineEnd = end; + if (lineEnd > p && *(lineEnd - 1) == '\r') lineEnd--; + + size_t len = (size_t)(lineEnd - p); + if (len >= TIB_SIZE) len = TIB_SIZE - 1; + + memcpy(line, p, len); + line[len] = '\0'; + + if (len > 0) + { + ThrowCode r = pfInterpretText(line); + if (r != 0) return r; + } + + p = (*end == '\n') ? end + 1 : end; + } + return 0; +} + +static const char* forthCountedToC(cell_t addr, cell_t len) +{ + if (len < 0) len = 0; + if (len >= FORTH_STR_BUF) len = FORTH_STR_BUF - 1; + memcpy(gStrBuf, (const char*)(uintptr_t)addr, (size_t)len); + gStrBuf[len] = '\0'; + return gStrBuf; +} + +// ============================================================================= +// TIC-80 API wrappers +// +// Each wrapper function is registered with NumParams=0 in CompileCustomFunctions +// and therefore receives no C arguments. It pops all Forth stack arguments +// manually via pfPopFromStack(). +// +// Return value: non-zero values are pushed to the data stack by CreateGlueToC +// when C_RETURNS_VALUE is used; void wrappers return 0 but are registered with +// C_RETURNS_VOID so no push occurs. +// ============================================================================= + +// cls ( color -- ) +static cell_t tic_forth_cls(void) +{ + u8 color = (u8)pfPopFromStack(); + gForthCore->api.cls((tic_mem*)gForthCore, color); + return 0; +} + +// print ( c-addr u x y color fixed scale alt -- width ) +static cell_t tic_forth_print(void) +{ + bool alt = (bool)pfPopFromStack(); + s32 scale = (s32)pfPopFromStack(); + bool fixed = (bool)pfPopFromStack(); + u8 color = (u8)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + cell_t len = pfPopFromStack(); + cell_t addr = pfPopFromStack(); + const char* text = forthCountedToC(addr, len); + return (cell_t)gForthCore->api.print((tic_mem*)gForthCore, + text, x, y, color, fixed, scale, alt); +} + +// pix ( x y -- color ) read pixel +static cell_t tic_forth_pix_get(void) +{ + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.pix((tic_mem*)gForthCore, x, y, 0, true); +} + +// pix! ( x y color -- ) write pixel +static cell_t tic_forth_pix_set(void) +{ + u8 color = (u8)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.pix((tic_mem*)gForthCore, x, y, color, false); + return 0; +} + +// line ( x0 y0 x1 y1 color -- ) +static cell_t tic_forth_line(void) +{ + u8 color = (u8)pfPopFromStack(); + float y1 = (float)(s32)pfPopFromStack(); + float x1 = (float)(s32)pfPopFromStack(); + float y0 = (float)(s32)pfPopFromStack(); + float x0 = (float)(s32)pfPopFromStack(); + gForthCore->api.line((tic_mem*)gForthCore, x0, y0, x1, y1, color); + return 0; +} + +// rect ( x y w h color -- ) +static cell_t tic_forth_rect(void) +{ + u8 color = (u8)pfPopFromStack(); + s32 height = (s32)pfPopFromStack(); + s32 width = (s32)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.rect((tic_mem*)gForthCore, x, y, width, height, color); + return 0; +} + +// rectb ( x y w h color -- ) +static cell_t tic_forth_rectb(void) +{ + u8 color = (u8)pfPopFromStack(); + s32 height = (s32)pfPopFromStack(); + s32 width = (s32)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.rectb((tic_mem*)gForthCore, x, y, width, height, color); + return 0; +} + +// spr ( id x y colorkey scale flip rotate w h -- ) +// colorkey: -1 = opaque, 0..15 = transparent color index +static cell_t tic_forth_spr(void) +{ + s32 h = (s32)pfPopFromStack(); + s32 w = (s32)pfPopFromStack(); + tic_rotate rotate = (tic_rotate)pfPopFromStack(); + tic_flip flip = (tic_flip)pfPopFromStack(); + s32 scale = (s32)pfPopFromStack(); + s32 colorkey = (s32)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + s32 id = (s32)pfPopFromStack(); + + static u8 trans[1]; + u8 count = 0; + if (colorkey >= 0) { trans[0] = (u8)colorkey; count = 1; } + + gForthCore->api.spr((tic_mem*)gForthCore, + id, x, y, w, h, trans, count, scale, flip, rotate); + return 0; +} + +// btn ( id -- pressed ) +static cell_t tic_forth_btn(void) +{ + s32 id = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.btn((tic_mem*)gForthCore, id); +} + +// btnp ( id hold period -- pressed ) +static cell_t tic_forth_btnp(void) +{ + s32 period = (s32)pfPopFromStack(); + s32 hold = (s32)pfPopFromStack(); + s32 id = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.btnp((tic_mem*)gForthCore, id, hold, period); +} + +// sfx ( id note octave duration channel volume speed -- ) +// note: 0..11 (C=0..B=11), octave: 0..8, or both -1 to use stored note +static cell_t tic_forth_sfx(void) +{ + s32 speed = (s32)pfPopFromStack(); + s32 volume = (s32)pfPopFromStack(); + s32 channel = (s32)pfPopFromStack(); + s32 duration = (s32)pfPopFromStack(); + s32 octave = (s32)pfPopFromStack(); + s32 note = (s32)pfPopFromStack(); + s32 id = (s32)pfPopFromStack(); + gForthCore->api.sfx((tic_mem*)gForthCore, + id, note, octave, duration, channel, volume, volume, speed); + return 0; +} + +// map ( cellx celly cellw cellh sx sy colorkey scale -- ) +// No remap callback in this version. +static cell_t tic_forth_map(void) +{ + s32 scale = (s32)pfPopFromStack(); + s32 colorkey = (s32)pfPopFromStack(); + s32 sy = (s32)pfPopFromStack(); + s32 sx = (s32)pfPopFromStack(); + s32 cellh = (s32)pfPopFromStack(); + s32 cellw = (s32)pfPopFromStack(); + s32 celly = (s32)pfPopFromStack(); + s32 cellx = (s32)pfPopFromStack(); + + static u8 trans[1]; + u8 count = 0; + if (colorkey >= 0) { trans[0] = (u8)colorkey; count = 1; } + + gForthCore->api.map((tic_mem*)gForthCore, + cellx, celly, cellw, cellh, sx, sy, + trans, count, scale, NULL, NULL); + return 0; +} + +// mget ( x y -- tile_id ) +static cell_t tic_forth_mget(void) +{ + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.mget((tic_mem*)gForthCore, x, y); +} + +// mset ( x y tile_id -- ) +static cell_t tic_forth_mset(void) +{ + u8 tile = (u8)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.mset((tic_mem*)gForthCore, x, y, tile); + return 0; +} + +// peek ( addr bits -- value ) +static cell_t tic_forth_peek(void) +{ + s32 bits = (s32)pfPopFromStack(); + s32 addr = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.peek((tic_mem*)gForthCore, addr, bits); +} + +// poke ( addr value bits -- ) +static cell_t tic_forth_poke(void) +{ + s32 bits = (s32)pfPopFromStack(); + u8 value = (u8)pfPopFromStack(); + s32 addr = (s32)pfPopFromStack(); + gForthCore->api.poke((tic_mem*)gForthCore, addr, value, bits); + return 0; +} + +// peek1 ( addr -- value ) +static cell_t tic_forth_peek1(void) +{ + s32 addr = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.peek1((tic_mem*)gForthCore, addr); +} + +// poke1 ( addr value -- ) +static cell_t tic_forth_poke1(void) +{ + u8 value = (u8)pfPopFromStack(); + s32 addr = (s32)pfPopFromStack(); + gForthCore->api.poke1((tic_mem*)gForthCore, addr, value); + return 0; +} + +// peek2 ( addr -- value ) +static cell_t tic_forth_peek2(void) +{ + s32 addr = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.peek2((tic_mem*)gForthCore, addr); +} + +// poke2 ( addr value -- ) +static cell_t tic_forth_poke2(void) +{ + u8 value = (u8)pfPopFromStack(); + s32 addr = (s32)pfPopFromStack(); + gForthCore->api.poke2((tic_mem*)gForthCore, addr, value); + return 0; +} + +// peek4 ( addr -- value ) +static cell_t tic_forth_peek4(void) +{ + s32 addr = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.peek4((tic_mem*)gForthCore, addr); +} + +// poke4 ( addr value -- ) +static cell_t tic_forth_poke4(void) +{ + u8 value = (u8)pfPopFromStack(); + s32 addr = (s32)pfPopFromStack(); + gForthCore->api.poke4((tic_mem*)gForthCore, addr, value); + return 0; +} + +// memcpy ( dst src size -- ) +static cell_t tic_forth_memcpy(void) +{ + s32 size = (s32)pfPopFromStack(); + s32 src = (s32)pfPopFromStack(); + s32 dst = (s32)pfPopFromStack(); + gForthCore->api.memcpy((tic_mem*)gForthCore, dst, src, size); + return 0; +} + +// memset ( dst value size -- ) +static cell_t tic_forth_memset(void) +{ + s32 size = (s32)pfPopFromStack(); + u8 value = (u8)pfPopFromStack(); + s32 dst = (s32)pfPopFromStack(); + gForthCore->api.memset((tic_mem*)gForthCore, dst, value, size); + return 0; +} + +// trace ( c-addr u color -- ) +static cell_t tic_forth_trace(void) +{ + u8 color = (u8)pfPopFromStack(); + cell_t len = pfPopFromStack(); + cell_t addr = pfPopFromStack(); + const char* text = forthCountedToC(addr, len); + gForthCore->api.trace((tic_mem*)gForthCore, text, color); + return 0; +} + +// pmem ( index -- value ) get persistent memory slot +static cell_t tic_forth_pmem_get(void) +{ + s32 index = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.pmem((tic_mem*)gForthCore, index, 0, false); +} + +// pmem! ( value index -- ) set persistent memory slot +static cell_t tic_forth_pmem_set(void) +{ + s32 index = (s32)pfPopFromStack(); + u32 value = (u32)pfPopFromStack(); + gForthCore->api.pmem((tic_mem*)gForthCore, index, value, true); + return 0; +} + +// time ( -- ms ) +static cell_t tic_forth_time(void) +{ + return (cell_t)(s32)gForthCore->api.time((tic_mem*)gForthCore); +} + +// tstamp ( -- seconds ) +static cell_t tic_forth_tstamp(void) +{ + return (cell_t)gForthCore->api.tstamp((tic_mem*)gForthCore); +} + +// exit ( -- ) +static cell_t tic_forth_exit(void) +{ + gForthCore->api.exit((tic_mem*)gForthCore); + return 0; +} + +// font ( c-addr u x y chromakey cw ch fixed scale alt -- width ) +static cell_t tic_forth_font(void) +{ + bool alt = (bool)pfPopFromStack(); + s32 scale = (s32)pfPopFromStack(); + bool fixed = (bool)pfPopFromStack(); + s32 ch = (s32)pfPopFromStack(); + s32 cw = (s32)pfPopFromStack(); + u8 chromakey = (u8)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + cell_t len = pfPopFromStack(); + cell_t addr = pfPopFromStack(); + const char* text = forthCountedToC(addr, len); + + static u8 trans[1]; + trans[0] = chromakey; + + return (cell_t)gForthCore->api.font((tic_mem*)gForthCore, + text, x, y, trans, 1, cw, ch, fixed, scale, alt); +} + +// mouse ( -- x y left mid right scrollx scrolly ) +// All 7 values are pushed onto the stack. +static cell_t tic_forth_mouse(void) +{ + tic_point pos = gForthCore->api.mouse((tic_mem*)gForthCore); + const tic80_mouse* m = &gForthCore->memory.ram->input.mouse; + + pfPushToStack((cell_t)pos.x); + pfPushToStack((cell_t)pos.y); + pfPushToStack((cell_t)m->left); + pfPushToStack((cell_t)m->middle); + pfPushToStack((cell_t)m->right); + pfPushToStack((cell_t)m->scrollx); + pfPushToStack((cell_t)m->scrolly); + return 0; // C_RETURNS_VOID: the 7 pushes above are the return values +} + +// circ ( x y radius color -- ) +static cell_t tic_forth_circ(void) +{ + u8 color = (u8)pfPopFromStack(); + s32 radius = (s32)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.circ((tic_mem*)gForthCore, x, y, radius, color); + return 0; +} + +// circb ( x y radius color -- ) +static cell_t tic_forth_circb(void) +{ + u8 color = (u8)pfPopFromStack(); + s32 radius = (s32)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.circb((tic_mem*)gForthCore, x, y, radius, color); + return 0; +} + +// elli ( x y a b color -- ) +static cell_t tic_forth_elli(void) +{ + u8 color = (u8)pfPopFromStack(); + s32 b = (s32)pfPopFromStack(); + s32 a = (s32)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.elli((tic_mem*)gForthCore, x, y, a, b, color); + return 0; +} + +// ellib ( x y a b color -- ) +static cell_t tic_forth_ellib(void) +{ + u8 color = (u8)pfPopFromStack(); + s32 b = (s32)pfPopFromStack(); + s32 a = (s32)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.ellib((tic_mem*)gForthCore, x, y, a, b, color); + return 0; +} + +// paint ( x y color bordercolor -- ) +static cell_t tic_forth_paint(void) +{ + u8 bordercolor = (u8)pfPopFromStack(); + u8 color = (u8)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.paint((tic_mem*)gForthCore, x, y, color, bordercolor); + return 0; +} + +// tri ( x1 y1 x2 y2 x3 y3 color -- ) +// Integer coordinates are cast to float (sub-pixel precision not required here). +static cell_t tic_forth_tri(void) +{ + u8 color = (u8)pfPopFromStack(); + float y3 = (float)(s32)pfPopFromStack(); + float x3 = (float)(s32)pfPopFromStack(); + float y2 = (float)(s32)pfPopFromStack(); + float x2 = (float)(s32)pfPopFromStack(); + float y1 = (float)(s32)pfPopFromStack(); + float x1 = (float)(s32)pfPopFromStack(); + gForthCore->api.tri((tic_mem*)gForthCore, x1, y1, x2, y2, x3, y3, color); + return 0; +} + +// trib ( x1 y1 x2 y2 x3 y3 color -- ) +static cell_t tic_forth_trib(void) +{ + u8 color = (u8)pfPopFromStack(); + float y3 = (float)(s32)pfPopFromStack(); + float x3 = (float)(s32)pfPopFromStack(); + float y2 = (float)(s32)pfPopFromStack(); + float x2 = (float)(s32)pfPopFromStack(); + float y1 = (float)(s32)pfPopFromStack(); + float x1 = (float)(s32)pfPopFromStack(); + gForthCore->api.trib((tic_mem*)gForthCore, x1, y1, x2, y2, x3, y3, color); + return 0; +} + +// ttri ( x1 y1 x2 y2 x3 y3 u1 v1 u2 v2 u3 v3 texsrc colorkey z1 z2 z3 depth -- ) +// texsrc: 0=tiles, 1=map, 2=vbank; colorkey: -1=opaque, 0..15=transparent index +static cell_t tic_forth_ttri(void) +{ + bool depth = (bool)pfPopFromStack(); + float z3 = (float)(s32)pfPopFromStack(); + float z2 = (float)(s32)pfPopFromStack(); + float z1 = (float)(s32)pfPopFromStack(); + s32 colorkey = (s32)pfPopFromStack(); + tic_texture_src texsrc = (tic_texture_src)pfPopFromStack(); + float v3 = (float)(s32)pfPopFromStack(); + float u3 = (float)(s32)pfPopFromStack(); + float v2 = (float)(s32)pfPopFromStack(); + float u2 = (float)(s32)pfPopFromStack(); + float v1 = (float)(s32)pfPopFromStack(); + float u1 = (float)(s32)pfPopFromStack(); + float y3 = (float)(s32)pfPopFromStack(); + float x3 = (float)(s32)pfPopFromStack(); + float y2 = (float)(s32)pfPopFromStack(); + float x2 = (float)(s32)pfPopFromStack(); + float y1 = (float)(s32)pfPopFromStack(); + float x1 = (float)(s32)pfPopFromStack(); + + static u8 trans[1]; + u8 count = 0; + if (colorkey >= 0) { trans[0] = (u8)colorkey; count = 1; } + + gForthCore->api.ttri((tic_mem*)gForthCore, + x1, y1, x2, y2, x3, y3, + u1, v1, u2, v2, u3, v3, + texsrc, trans, count, z1, z2, z3, depth); + return 0; +} + +// clip ( x y w h -- ) +static cell_t tic_forth_clip(void) +{ + s32 h = (s32)pfPopFromStack(); + s32 w = (s32)pfPopFromStack(); + s32 y = (s32)pfPopFromStack(); + s32 x = (s32)pfPopFromStack(); + gForthCore->api.clip((tic_mem*)gForthCore, x, y, w, h); + return 0; +} + +// clip0 ( -- ) reset clip region to full screen +static cell_t tic_forth_clip0(void) +{ + gForthCore->api.clip((tic_mem*)gForthCore, 0, 0, TIC80_WIDTH, TIC80_HEIGHT); + return 0; +} + +// music ( track frame row loop sustain tempo speed -- ) +// Pass -1 for any value to use defaults. Call with track=-1 to stop. +static cell_t tic_forth_music(void) +{ + s32 speed = (s32)pfPopFromStack(); + s32 tempo = (s32)pfPopFromStack(); + bool sustain = (bool)pfPopFromStack(); + bool loop = (bool)pfPopFromStack(); + s32 row = (s32)pfPopFromStack(); + s32 frame = (s32)pfPopFromStack(); + s32 track = (s32)pfPopFromStack(); + gForthCore->api.music((tic_mem*)gForthCore, + track, frame, row, loop, sustain, tempo, speed); + return 0; +} + +// sync ( mask bank tocart -- ) +static cell_t tic_forth_sync(void) +{ + bool toCart = (bool)pfPopFromStack(); + s32 bank = (s32)pfPopFromStack(); + u32 mask = (u32)pfPopFromStack(); + gForthCore->api.sync((tic_mem*)gForthCore, mask, bank, toCart); + return 0; +} + +// vbank ( bank -- prev ) +static cell_t tic_forth_vbank(void) +{ + s32 bank = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.vbank((tic_mem*)gForthCore, bank); +} + +// reset ( -- ) +static cell_t tic_forth_reset(void) +{ + gForthCore->api.reset((tic_mem*)gForthCore); + return 0; +} + +// key ( keycode -- pressed ) +static cell_t tic_forth_key(void) +{ + tic_key k = (tic_key)pfPopFromStack(); + return (cell_t)gForthCore->api.key((tic_mem*)gForthCore, k); +} + +// keyp ( keycode hold period -- pressed ) +static cell_t tic_forth_keyp(void) +{ + s32 period = (s32)pfPopFromStack(); + s32 hold = (s32)pfPopFromStack(); + tic_key k = (tic_key)pfPopFromStack(); + return (cell_t)gForthCore->api.keyp((tic_mem*)gForthCore, k, hold, period); +} + +// fget ( sprite_id flag -- bool ) +static cell_t tic_forth_fget(void) +{ + u8 flag = (u8)pfPopFromStack(); + s32 id = (s32)pfPopFromStack(); + return (cell_t)gForthCore->api.fget((tic_mem*)gForthCore, id, flag); +} + +// fset ( sprite_id flag value -- ) +static cell_t tic_forth_fset(void) +{ + bool value = (bool)pfPopFromStack(); + u8 flag = (u8)pfPopFromStack(); + s32 id = (s32)pfPopFromStack(); + gForthCore->api.fset((tic_mem*)gForthCore, id, flag, value); + return 0; +} + +// fft ( start_freq end_freq -- value ) value scaled to 0..65535 +static cell_t tic_forth_fft(void) +{ + s32 endFreq = (s32)pfPopFromStack(); + s32 startFreq = (s32)pfPopFromStack(); + double v = gForthCore->api.fft((tic_mem*)gForthCore, startFreq, endFreq); + return (cell_t)(s32)(v * 65535.0); +} + +// ffts ( start_freq end_freq -- value ) smoothed, value scaled to 0..65535 +static cell_t tic_forth_ffts(void) +{ + s32 endFreq = (s32)pfPopFromStack(); + s32 startFreq = (s32)pfPopFromStack(); + double v = gForthCore->api.ffts((tic_mem*)gForthCore, startFreq, endFreq); + return (cell_t)(s32)(v * 65535.0); +} + + +// ============================================================================= +// pForth custom function table +// +// IMPORTANT: the order here MUST match the index assignments in +// CompileCustomFunctions() below. Adding a function requires updating both. +// ============================================================================= + +CFunc0 CustomFunctionTable[] = +{ + (CFunc0)tic_forth_cls, // 0 CLS + (CFunc0)tic_forth_print, // 1 PRINT + (CFunc0)tic_forth_pix_get, // 2 PIX + (CFunc0)tic_forth_pix_set, // 3 PIX! + (CFunc0)tic_forth_line, // 4 LINE + (CFunc0)tic_forth_rect, // 5 RECT + (CFunc0)tic_forth_rectb, // 6 RECTB + (CFunc0)tic_forth_spr, // 7 SPR + (CFunc0)tic_forth_btn, // 8 BTN + (CFunc0)tic_forth_btnp, // 9 BTNP + (CFunc0)tic_forth_sfx, // 10 SFX + (CFunc0)tic_forth_map, // 11 MAP + (CFunc0)tic_forth_mget, // 12 MGET + (CFunc0)tic_forth_mset, // 13 MSET + (CFunc0)tic_forth_peek, // 14 PEEK + (CFunc0)tic_forth_poke, // 15 POKE + (CFunc0)tic_forth_peek1, // 16 PEEK1 + (CFunc0)tic_forth_poke1, // 17 POKE1 + (CFunc0)tic_forth_peek2, // 18 PEEK2 + (CFunc0)tic_forth_poke2, // 19 POKE2 + (CFunc0)tic_forth_peek4, // 20 PEEK4 + (CFunc0)tic_forth_poke4, // 21 POKE4 + (CFunc0)tic_forth_memcpy, // 22 MEMCPY + (CFunc0)tic_forth_memset, // 23 MEMSET + (CFunc0)tic_forth_trace, // 24 TRACE + (CFunc0)tic_forth_pmem_get, // 25 PMEM + (CFunc0)tic_forth_pmem_set, // 26 PMEM! + (CFunc0)tic_forth_time, // 27 TIME + (CFunc0)tic_forth_tstamp, // 28 TSTAMP + (CFunc0)tic_forth_exit, // 29 EXIT + (CFunc0)tic_forth_font, // 30 FONT + (CFunc0)tic_forth_mouse, // 31 MOUSE + (CFunc0)tic_forth_circ, // 32 CIRC + (CFunc0)tic_forth_circb, // 33 CIRCB + (CFunc0)tic_forth_elli, // 34 ELLI + (CFunc0)tic_forth_ellib, // 35 ELLIB + (CFunc0)tic_forth_paint, // 36 PAINT + (CFunc0)tic_forth_tri, // 37 TRI + (CFunc0)tic_forth_trib, // 38 TRIB + (CFunc0)tic_forth_ttri, // 39 TTRI + (CFunc0)tic_forth_clip, // 40 CLIP + (CFunc0)tic_forth_clip0, // 41 CLIP0 + (CFunc0)tic_forth_music, // 42 MUSIC + (CFunc0)tic_forth_sync, // 43 SYNC + (CFunc0)tic_forth_vbank, // 44 VBANK + (CFunc0)tic_forth_reset, // 45 RESET + (CFunc0)tic_forth_key, // 46 KEY + (CFunc0)tic_forth_keyp, // 47 KEYP + (CFunc0)tic_forth_fget, // 48 FGET + (CFunc0)tic_forth_fset, // 49 FSET + (CFunc0)tic_forth_fft, // 50 FFT + (CFunc0)tic_forth_ffts, // 51 FFTS +}; + +// Called during dictionary build (pfBuildDictionary) and manually after +// pfLoadStaticDictionary to add TIC-80 API words to the dictionary. +// Index values MUST match the CustomFunctionTable order above. +Err CompileCustomFunctions(void) +{ + int i = 0; + // All words use NumParams=0: wrappers pop arguments themselves. + if (CreateGlueToC("CLS", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("PRINT", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("PIX", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("PIX!", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("LINE", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("RECT", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("RECTB", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("SPR", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("BTN", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("BTNP", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("SFX", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("MAP", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("MGET", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("MSET", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("PEEK", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("POKE", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("PEEK1", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("POKE1", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("PEEK2", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("POKE2", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("PEEK4", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("POKE4", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("MEMCPY", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("MEMSET", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("TRACE", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("PMEM", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("PMEM!", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("TIME", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("TSTAMP", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("EXIT", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("FONT", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("MOUSE", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("CIRC", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("CIRCB", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("ELLI", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("ELLIB", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("PAINT", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("TRI", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("TRIB", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("TTRI", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("CLIP", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("CLIP0", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("MUSIC", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("SYNC", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("VBANK", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("RESET", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("KEY", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("KEYP", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("FGET", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("FSET", i++, C_RETURNS_VOID, 0) < 0) return -1; + if (CreateGlueToC("FFT", i++, C_RETURNS_VALUE, 0) < 0) return -1; + if (CreateGlueToC("FFTS", i++, C_RETURNS_VALUE, 0) < 0) return -1; + return 0; +} + +// Required when PF_NO_GLOBAL_INIT is defined (rare embedded loaders). +Err LoadCustomFunctionTable(void) +{ + return 0; +} + +// ============================================================================= +// Error reporting helpers +// ============================================================================= + +static void reportForthError(tic_core* core, ThrowCode code) +{ + if (!core->data) return; + + const char* msg = forthGetOutputBuffer(); + if (msg && msg[0]) + core->data->error(core->data->data, msg); + else + { + // pforth throw codes are negative integers; format a basic message. + char buf[64]; + snprintf(buf, sizeof(buf), "Forth error: THROW %ld", (long)code); + core->data->error(core->data->data, buf); + } + forthClearOutputBuffer(); +} + +// ============================================================================= +// TIC-80 callback helpers +// ============================================================================= + +// Validate that the data stack depth is exactly 'expected' after a Forth call. +// Stack imbalance in SCN (called 240 times/frame) would corrupt state. +static void checkStackBalance(tic_core* core, cell_t before, const char* name) +{ + cell_t after = pfGetStackDepth(); + if (after != before && core->data) + { + char buf[128]; + cell_t delta = after - before; + if (delta > 0) + snprintf(buf, sizeof(buf), + "Forth: stack overflow in %s (%ld extra item%s left)", + name, (long)delta, delta == 1 ? "" : "s"); + else + snprintf(buf, sizeof(buf), + "Forth: stack underflow in %s (%ld item%s missing)", + name, (long)-delta, -delta == 1 ? "" : "s"); + core->data->error(core->data->data, buf); + // Discard leftover stack items to avoid cascading errors. + while (pfGetStackDepth() > before) + pfPopFromStack(); + } +} + +static void callForthWord(tic_mem* tic, const char* name, s32 param, bool hasParam) +{ + tic_core* core = (tic_core*)tic; + if (!core->currentVM) return; + + cell_t depthBefore = pfGetStackDepth(); + + if (hasParam) + pfPushToStack((cell_t)param); + + forthClearOutputBuffer(); + ThrowCode result = pfExecIfDefined(name); + if (result != 0) + reportForthError(core, result); + + cell_t depthAfter = pfGetStackDepth(); + + // If the word was not defined, pfExecIfDefined is a no-op and the pushed + // param is still on the stack (depthAfter == depthBefore + 1). That is + // not a user error — silently clean up and return. + bool paramLeaked = hasParam && (depthAfter == depthBefore + 1); + if (paramLeaked) + { + pfPopFromStack(); + return; + } + + // Any other imbalance is a real stack error in the user's word. + checkStackBalance(core, depthBefore, name); +} + +// ============================================================================= +// TIC-80 language lifecycle +// ============================================================================= + +static void closeForth(tic_mem* tic) +{ + tic_core* core = (tic_core*)tic; + if (core->currentVM) + { + forthTermIO(); + // pfTerminate() frees both the current task and the dictionary. + // Do NOT call pfDeleteTask separately — that would double-free. + pfTerminate(); + gForthTask = NULL; + core->currentVM = NULL; + gForthCore = NULL; + } +} + +static bool initForth(tic_mem* tic, const char* code) +{ + tic_core* core = (tic_core*)tic; + closeForth(tic); + + gForthCore = core; + forthInitIO(core->data); + pfSetQuiet(1); + + // pfInitialize(NULL, 0, NULL): + // - calls pfInitSystem() which sets gVarBase=10 and inits the allocator + // - creates the execution task and calls pfSetCurrentTask + // - loads the pre-compiled static dictionary (pfdicdat.h) + // - runs AUTO.INIT if defined (no-op in standard pforth) + // Using pfInitialize instead of the individual calls is required because + // pfInitSystem() is private to pf_core.c. + ThrowCode initResult = pfInitialize(NULL, 0, NULL); + if (initResult != 0) + { + if (core->data) + core->data->error(core->data->data, "Forth: pfInitialize failed"); + gForthCore = NULL; + forthTermIO(); + return false; + } + + gForthTask = pfGetCurrentTask(); + core->currentVM = gForthTask; + + // Add TIC-80 API words to the already-loaded dictionary. + if (CompileCustomFunctions() < 0) + { + closeForth(tic); + if (core->data) + core->data->error(core->data->data, + "Forth: failed to compile API words"); + return false; + } + + // Interpret the cartridge source code. + forthClearOutputBuffer(); + ThrowCode result = forthInterpretLines(code); + if (result != 0) + { + reportForthError(core, result); + closeForth(tic); + return false; + } + + return true; +} + +static void callForthTick(tic_mem* tic) +{ + callForthWord(tic, TIC_FN, 0, false); + forthFlushOutput(); // flush '.' output that has no trailing CR +} + +static void callForthBoot(tic_mem* tic) +{ + callForthWord(tic, BOOT_FN, 0, false); +} + +static void callForthScanline(tic_mem* tic, s32 row, void* data) +{ + (void)data; + callForthWord(tic, SCN_FN, row, true); +} + +static void callForthBorder(tic_mem* tic, s32 row, void* data) +{ + (void)data; + callForthWord(tic, BDR_FN, row, true); +} + +static void callForthMenu(tic_mem* tic, s32 index, void* data) +{ + (void)data; + callForthWord(tic, MENU_FN, index, true); +} + +static void evalForth(tic_mem* tic, const char* code) +{ + tic_core* core = (tic_core*)tic; + if (!core->currentVM) + { + if (!initForth(tic, "")) + return; + } + forthClearOutputBuffer(); + ThrowCode result = forthInterpretLines(code); + if (result != 0) + reportForthError(core, result); +} + +// ============================================================================= +// Editor outline: extract word names from ': NAME ... ;' definitions +// ============================================================================= + +static const tic_outline_item* getForthOutline(const char* code, s32* size) +{ + // Pattern: lines starting with ': ' introduce a new word definition. + static tic_outline_item items[128]; + *size = 0; + + const char* p = code; + while (*p) + { + // Skip to next line that starts with ': ' + while (*p && !(*p == ':' && (p == code || *(p-1) == '\n'))) + p++; + if (!*p) break; + + p++; // skip ':' + while (*p == ' ' || *p == '\t') p++; + + const char* start = p; + while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') + p++; + const char* end = p; + + if (end > start && *size < 128) + { + items[*size].pos = start; + items[*size].size = (s32)(end - start); + (*size)++; + } + + // Skip to end of line. + while (*p && *p != '\n') p++; + if (*p) p++; + } + + return *size ? items : NULL; +} + +// ============================================================================= +// Keyword lists for syntax highlighting +// ============================================================================= + +static const char* const ForthKeywords[] = { + // ANS Forth core control flow + "IF", "THEN", "ELSE", "BEGIN", "UNTIL", "WHILE", "REPEAT", "DO", "LOOP", + "+LOOP", "LEAVE", "EXIT", "RECURSE", + // Defining words + ":", ";", "VARIABLE", "CONSTANT", "CREATE", "DOES>", "VALUE", "TO", + // Stack manipulation + "DUP", "DROP", "SWAP", "OVER", "ROT", "-ROT", "NIP", "TUCK", + "2DUP", "2DROP", "2SWAP", "2OVER", "PICK", "ROLL", + // Arithmetic + "+", "-", "*", "/", "MOD", "/MOD", "*/", "*/MOD", + "MAX", "MIN", "ABS", "NEGATE", "1+", "1-", "2*", "2/", + // Logical / bitwise + "AND", "OR", "XOR", "INVERT", "LSHIFT", "RSHIFT", + // Comparison + "=", "<>", "<", ">", "<=", ">=", "0=", "0<", "0>", + // Memory + "@", "!", "C@", "C!", "+!", "W@", "W!", "MOVE", + // I/O + ".", ".\"", "EMIT", "CR", "SPACE", "SPACES", "TYPE", + // String + "S\"", "C\"", "CHAR", + // Misc + "TRUE", "FALSE", "CELL", "HERE", "ALLOT", "CELLS", "CHARS", + "LITERAL", "POSTPONE", "IMMEDIATE", +}; + +static const char* ForthAPIKeywords[] = { +#define TIC_CALLBACK_DEF(name, ...) #name, + TIC_CALLBACK_LIST(TIC_CALLBACK_DEF) +#undef TIC_CALLBACK_DEF + // TIC-80 API words in UPPERCASE (Forth convention) + "CLS", "PRINT", "PIX", "PIX!", "LINE", "RECT", "RECTB", + "SPR", "BTN", "BTNP", "SFX", "MAP", "MGET", "MSET", + "PEEK", "POKE", "PEEK1", "POKE1", "PEEK2", "POKE2", "PEEK4", "POKE4", + "MEMCPY", "MEMSET", "TRACE", "PMEM", "PMEM!", "TIME", "TSTAMP", "EXIT", + "FONT", "MOUSE", "CIRC", "CIRCB", "ELLI", "ELLIB", "PAINT", + "TRI", "TRIB", "TTRI", "CLIP", "CLIP0", + "MUSIC", "SYNC", "VBANK", "RESET", + "KEY", "KEYP", "FGET", "FSET", "FFT", "FFTS", +}; + +// ============================================================================= +// ============================================================================= + +static const u8 DemoRom[] = +{ + #include "../build/assets/forthdemo.tic.dat" +}; + +static const u8 MarkRom[] = +{ + #include "../build/assets/forthmark.tic.dat" +}; + +// ============================================================================= +// tic_script descriptor +// ============================================================================= + +TIC_EXPORT const tic_script EXPORT_SCRIPT(Forth) = +{ + .id = 21, + .name = "forth", + .fileExtension = ".fth", + .projectComment = "\\", + { + .init = initForth, + .close = closeForth, + .tick = callForthTick, + .boot = callForthBoot, + .callback = + { + .scanline = callForthScanline, + .border = callForthBorder, + .menu = callForthMenu, + }, + }, + .getOutline = getForthOutline, + .eval = evalForth, + + .blockCommentStart = NULL, + .blockCommentEnd = NULL, + .blockCommentStart2 = NULL, + .blockCommentEnd2 = NULL, + .singleComment = "\\", + .blockStringStart = NULL, + .blockStringEnd = NULL, + .stdStringStartEnd = NULL, + .blockEnd = NULL, + + .keywords = ForthKeywords, + .keywordsCount = COUNT_OF(ForthKeywords), + + .api_keywords = ForthAPIKeywords, + .api_keywordsCount = COUNT_OF(ForthAPIKeywords), + + .demo = { DemoRom, sizeof DemoRom, "forthdemo.tic" }, + .mark = { MarkRom, sizeof MarkRom, "forthmark.tic" }, +}; diff --git a/src/script.c b/src/script.c index 136dba3e1..6bb612f4a 100644 --- a/src/script.c +++ b/src/script.c @@ -77,6 +77,10 @@ extern tic_script EXPORT_SCRIPT(Janet); extern tic_script EXPORT_SCRIPT(Python); #endif +#if defined(TIC_BUILD_WITH_FORTH) +extern tic_script EXPORT_SCRIPT(Forth); +#endif + #endif static const tic_script *Scripts[MAX_SUPPORTED_LANGS + 1] = @@ -130,6 +134,10 @@ static const tic_script *Scripts[MAX_SUPPORTED_LANGS + 1] = &EXPORT_SCRIPT(Python), #endif + #if defined(TIC_BUILD_WITH_FORTH) + &EXPORT_SCRIPT(Forth), + #endif + #endif NULL, diff --git a/vendor/pforth b/vendor/pforth new file mode 160000 index 000000000..3e637b5ac --- /dev/null +++ b/vendor/pforth @@ -0,0 +1 @@ +Subproject commit 3e637b5ac6ad665f851f31e33ad4c44caf4e25ab