mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-10 10:28:09 +01:00
Compare commits
138 commits
pico-v1.0.
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d14935dd53 | ||
|
|
07849e3e69 | ||
|
|
397da70d83 | ||
|
|
c594e01912 | ||
|
|
8aa18fa5af | ||
|
|
dc11cb33dd | ||
|
|
c29d099ec9 | ||
|
|
765f78fbc6 | ||
|
|
149a8b6276 | ||
|
|
ee6958a729 | ||
|
|
9db4bae9fd | ||
|
|
41914362d5 | ||
|
|
a308714302 | ||
|
|
2b6d9946df | ||
|
|
da1cb31ac2 | ||
|
|
0c3809657e | ||
|
|
c6da4b891a | ||
|
|
9ab7b9bb14 | ||
|
|
741a3c633e | ||
|
|
eb08627029 | ||
|
|
b76add6793 | ||
|
|
9c6df3c9b6 | ||
|
|
88b9bc6b3e | ||
|
|
07f4288748 | ||
|
|
20bb0aca57 | ||
|
|
d58967710a | ||
|
|
663fa47004 | ||
|
|
0be3ff2203 | ||
|
|
6d544f5879 | ||
|
|
3e00fb1c83 | ||
|
|
8111f0a362 | ||
|
|
31e76ce6d7 | ||
|
|
479497f1df | ||
|
|
883257fd78 | ||
|
|
d06f05d207 | ||
|
|
fc37a9ffa1 | ||
|
|
600a80d529 | ||
|
|
da04c38dec | ||
|
|
6459dba5c3 | ||
|
|
6ec0740345 | ||
|
|
143bbfa8bc | ||
|
|
8a2f70ea01 | ||
|
|
d2fac4d79e | ||
|
|
03fc21dd17 | ||
|
|
65094d88a3 | ||
|
|
8aec974f89 | ||
|
|
81f7e9343b | ||
|
|
b962d303c5 | ||
|
|
1fd58e4c30 | ||
|
|
6a92fe745d | ||
|
|
6ec1f39db7 | ||
|
|
47552d4719 | ||
|
|
d6df8b65ab | ||
|
|
51eac85360 | ||
|
|
a42fdc81ea | ||
|
|
3c1224e937 | ||
|
|
5c16e2bd94 | ||
|
|
183e5d0727 | ||
|
|
b9a946eab4 | ||
|
|
af859d7085 | ||
|
|
d3a8b10120 | ||
|
|
bbf149dedd | ||
|
|
9cd7956630 | ||
|
|
5918b610f8 | ||
|
|
b9ad742e43 | ||
|
|
5016cc365d | ||
|
|
44ba940b85 | ||
|
|
09637be998 | ||
|
|
7e6460cf4c | ||
|
|
8c165ca0a9 | ||
|
|
157c5a5a01 | ||
|
|
59114c2a80 | ||
|
|
7973aa4709 | ||
|
|
35c9fba323 | ||
|
|
6c76307f1b | ||
|
|
879f5fbdbc | ||
|
|
59921fe9fd | ||
|
|
c23176796f | ||
|
|
0ad614699c | ||
|
|
432d72c80c | ||
|
|
b2fdf5c03b | ||
|
|
318547f351 | ||
|
|
19b78b17a1 | ||
|
|
fe920f4b15 | ||
|
|
3d9b60cb32 | ||
|
|
6305b06d53 | ||
|
|
6e3143c880 | ||
|
|
f538148a9a | ||
|
|
f0ffb62867 | ||
|
|
cd26f407fc | ||
|
|
86ab93211e | ||
|
|
e65b506ce1 | ||
|
|
26c917fe27 | ||
|
|
fcc3562ecd | ||
|
|
5ed9233778 | ||
|
|
52f0312c5c | ||
|
|
d0988a7e6b | ||
|
|
c954563472 | ||
|
|
bb0c18e433 | ||
|
|
016282064e | ||
|
|
be16a68e85 | ||
|
|
9accbbeea7 | ||
|
|
fcbc802265 | ||
|
|
3490cf0050 | ||
|
|
73aefa7100 | ||
|
|
4fc34d5521 | ||
|
|
bd59867bc3 | ||
|
|
4698040979 | ||
|
|
eb521eda71 | ||
|
|
59e96befd0 | ||
|
|
a6dbc90b78 | ||
|
|
6c4ffbbd62 | ||
|
|
4da377e429 | ||
|
|
83a6a3bc15 | ||
|
|
09ad937f1e | ||
|
|
79f946a4a5 | ||
|
|
42c3d9d215 | ||
|
|
001034db61 | ||
|
|
3669841045 | ||
|
|
830fa5fbfc | ||
|
|
afa6846041 | ||
|
|
0df8f9c42a | ||
|
|
19c77fac96 | ||
|
|
86337466c8 | ||
|
|
37d79c5e33 | ||
|
|
8581dbf253 | ||
|
|
78836d0a2e | ||
|
|
7c9f56cd3b | ||
|
|
c057443aa0 | ||
|
|
56612c731d | ||
|
|
d290affe17 | ||
|
|
ad99aaef33 | ||
|
|
636a32af67 | ||
|
|
3b95e3c171 | ||
|
|
b1c53c84c3 | ||
|
|
6a610200d0 | ||
|
|
4bcb8e4490 | ||
|
|
ce26db3aaa |
1310 changed files with 301599 additions and 214738 deletions
32
.github/workflows/story_editor.yml
vendored
Normal file
32
.github/workflows/story_editor.yml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
name: Build-StoryEditor
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
build_linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: build
|
||||
working-directory: ./story-editor/delivery
|
||||
run : |
|
||||
./linux_01_docker_build_image.sh
|
||||
./linux_02_docker_launch_build.sh
|
||||
./linux_03_docker_launch_package.sh
|
||||
build_win32:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: build
|
||||
working-directory: ./story-editor/delivery
|
||||
run : |
|
||||
./mingw64_01_docker_build_image.sh
|
||||
./mingw64_02_docker_launch_build.sh
|
||||
|
||||
36
.github/workflows/vitepress.yml
vendored
36
.github/workflows/vitepress.yml
vendored
|
|
@ -1,36 +0,0 @@
|
|||
name: Deploy
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: npm
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
- run: npm ci
|
||||
working-directory: docs
|
||||
- name: Build
|
||||
run: npm run docs:build
|
||||
working-directory: docs
|
||||
- uses: actions/configure-pages@v2
|
||||
- uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
path: docs/.vitepress/dist
|
||||
- name: Deploy
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
67
.gitignore
vendored
67
.gitignore
vendored
|
|
@ -5,8 +5,6 @@ www-docs/node_modules/
|
|||
|
||||
*.lock
|
||||
|
||||
build-story-editor-Desktop_Qt_6_4_1_GCC_64bit-Debug/
|
||||
|
||||
docs/resources/
|
||||
|
||||
docs/node_modules/
|
||||
|
|
@ -21,23 +19,11 @@ hardware/kicad/ost-pico-addon/fp-info-cache
|
|||
|
||||
*.png~
|
||||
|
||||
build-story-editor-Desktop_Qt_6_5_1_GCC_64bit-Debug/
|
||||
|
||||
build-story-editor-Desktop_Qt_6_5_1_MinGW_64_bit-Debug/
|
||||
|
||||
build-story-editor-Desktop_Qt_6_5_1_MinGW_64_bit-Release/
|
||||
|
||||
build-story-editor-Desktop_Qt_6_5_1_MSVC2019_64bit-Debug/
|
||||
|
||||
release/
|
||||
|
||||
story-editor/windows-setup/Output/
|
||||
|
||||
build-story-editor-Desktop_Qt_6_5_1_GCC_64bit-Release/
|
||||
|
||||
build-story-player-Desktop_Qt_6_5_1_GCC_64bit-Debug/
|
||||
|
||||
build-story-player-Desktop_Qt_GCC_64bit-Debug/
|
||||
|
||||
*.user
|
||||
|
||||
|
|
@ -47,23 +33,58 @@ software/.vscode/.cortex-debug.registers.state.json
|
|||
|
||||
story-player/build/
|
||||
|
||||
story-editor/tools/build-audio-System_Qt5_15_8-Debug/
|
||||
|
||||
|
||||
software/.vscode/.cortex-debug.peripherals.state.json
|
||||
story-editor/build
|
||||
|
||||
build-story-editor-v2-System_Qt5_15_8-Debug/
|
||||
|
||||
story-editor-v2/src/CMakeSettings.json
|
||||
|
||||
story-editor-v2/imgui.ini
|
||||
|
||||
story-editor-v2/src/.vscode/
|
||||
|
||||
build-story-editor-v2-Desktop-Debug/
|
||||
|
||||
build-story-editor-Desktop-Debug/
|
||||
|
||||
docs/.vitepress/cache/
|
||||
|
||||
docs/.vitepress/dist/
|
||||
|
||||
software/.cache/
|
||||
|
||||
.DS_Store
|
||||
|
||||
story-editor/.idea/
|
||||
|
||||
|
||||
|
||||
firmware/build/
|
||||
|
||||
story-player/stories/
|
||||
|
||||
story-player/storyvm/build/
|
||||
|
||||
story-player-sdl/build/
|
||||
|
||||
story-player-raylib/build/
|
||||
|
||||
story-player/linux/flutter/generated_plugin_registrant.cc
|
||||
|
||||
story-player/linux/flutter/generated_plugins.cmake
|
||||
|
||||
story-player-raylib/build-web
|
||||
|
||||
story-vm/build
|
||||
|
||||
story-player-raylib/bin/story-player.data
|
||||
story-player-raylib/bin/story-player.js
|
||||
story-player-raylib/bin/story-player.wasm
|
||||
|
||||
core/chip32/tests/build
|
||||
story-editor/build-linux
|
||||
story-editor/buildxcode/
|
||||
story-editor/build-win32/
|
||||
story-editor/cmake-build-debug/
|
||||
story-editor/.flatpak-builder
|
||||
story-editor/flatpak_build/
|
||||
story-editor/AppDir/
|
||||
|
||||
story-player/android/build/reports/problems/problems-report.html
|
||||
core/tests/build
|
||||
core/story-manager/tests/build
|
||||
24
.gitmodules
vendored
Normal file
24
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[submodule "story-editor/externals/SDL_mixer"]
|
||||
path = story-editor/externals/SDL_mixer
|
||||
url = https://github.com/libsdl-org/SDL_mixer
|
||||
[submodule "story-editor/externals/SDL"]
|
||||
path = story-editor/externals/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
[submodule "story-editor/externals/SDL_image"]
|
||||
path = story-editor/externals/SDL_image
|
||||
url = https://github.com/libsdl-org/SDL_image
|
||||
[submodule "story-editor/externals/civetweb"]
|
||||
path = story-editor/externals/civetweb
|
||||
url = https://github.com/civetweb/civetweb
|
||||
[submodule "story-editor/externals/curl"]
|
||||
path = story-editor/externals/curl
|
||||
url = https://github.com/curl/curl
|
||||
[submodule "story-editor/externals/mbedtls"]
|
||||
path = story-editor/externals/mbedtls
|
||||
url = https://github.com/Mbed-TLS/mbedtls/
|
||||
[submodule "libs/ImGuiColorTextEdit"]
|
||||
path = libs/ImGuiColorTextEdit
|
||||
url = https://github.com/santaclose/ImGuiColorTextEdit
|
||||
[submodule "story-editor/externals/ImNodeFlow"]
|
||||
path = story-editor/externals/ImNodeFlow
|
||||
url = https://github.com/arabine/ImNodeFlow
|
||||
10
.vscode/c_cpp_properties.json
vendored
Normal file
10
.vscode/c_cpp_properties.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": ["${workspaceFolder}/**", "~/.conan2/**"]
|
||||
}
|
||||
],
|
||||
"version": 4,
|
||||
"enableConfigurationSquiggles": true
|
||||
}
|
||||
100
.vscode/launch.json
vendored
100
.vscode/launch.json
vendored
|
|
@ -4,6 +4,14 @@
|
|||
// Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter (debug)",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "lib/main.dart",
|
||||
"flutterMode": "debug",
|
||||
"cwd": "${workspaceFolder}/story-player"
|
||||
},
|
||||
{
|
||||
"name": "Debug Story Editor (GDB)",
|
||||
"type": "cppdbg",
|
||||
|
|
@ -14,7 +22,15 @@
|
|||
"cwd": "${workspaceFolder}/story-editor",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"linux": {
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"osx": {
|
||||
"MIMode": "lldb",
|
||||
"miDebuggerPath": "/Users/user936511/.vscode/extensions/ms-vscode.cpptools-1.18.5-darwin-arm64/debugAdapters/lldb-mi/bin/lldb-mi"
|
||||
},
|
||||
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
|
|
@ -23,6 +39,88 @@
|
|||
}
|
||||
]
|
||||
|
||||
},
|
||||
{
|
||||
"name": "Debug Story Player (Raylib)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/story-player-raylib/build/story-player", // Remplacez par le chemin de votre exécutable
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/story-player-raylib",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"linux": {
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"osx": {
|
||||
"MIMode": "lldb",
|
||||
"miDebuggerPath": "/Users/user936511/.vscode/extensions/ms-vscode.cpptools-1.18.5-darwin-arm64/debugAdapters/lldb-mi/bin/lldb-mi"
|
||||
},
|
||||
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
|
||||
},
|
||||
{
|
||||
"name": "Debug tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/core/tests/build/core_tests", // Remplacez par le chemin de votre exécutable
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/core/tests/build",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"linux": {
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"osx": {
|
||||
"MIMode": "lldb",
|
||||
"miDebuggerPath": "/Users/user936511/.vscode/extensions/ms-vscode.cpptools-1.18.5-darwin-arm64/debugAdapters/lldb-mi/bin/lldb-mi"
|
||||
},
|
||||
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
|
||||
},
|
||||
{
|
||||
"name": "Black Magic Probe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"executable": "${workspaceRoot}/software/build/RaspberryPico/open-story-teller.elf",
|
||||
"request": "launch",
|
||||
"type": "cortex-debug",
|
||||
"showDevDebugOutput": "raw",
|
||||
"BMPGDBSerialPort": "/dev/ttyACM0",
|
||||
"servertype": "bmp",
|
||||
"interface": "swd",
|
||||
"gdbPath": "gdb-multiarch",
|
||||
"svdFile": "${workspaceRoot}/software/platform/raspberry-pico-w/rp2040.svd",
|
||||
// "device": "STM32L431VC",
|
||||
"runToEntryPoint": "main",
|
||||
"preRestartCommands": [
|
||||
"cd ${workspaceRoot}/software/build/RaspberryPico",
|
||||
"file open-story-teller.elf",
|
||||
// "target extended-remote /dev/ttyACM0",
|
||||
"set mem inaccessible-by-default off",
|
||||
"enable breakpoint",
|
||||
"monitor reset",
|
||||
"monitor swdp_scan",
|
||||
"attach 1",
|
||||
"load"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
108
.vscode/settings.json
vendored
108
.vscode/settings.json
vendored
|
|
@ -1,16 +1,118 @@
|
|||
{
|
||||
|
||||
"cmake.sourceDirectory": [
|
||||
"${workspaceFolder}/core/tests",
|
||||
"${workspaceFolder}/story-editor",
|
||||
"${workspaceFolder}/story-player",
|
||||
"${workspaceFolder}/story-player-raylib",
|
||||
"${workspaceFolder}/software"
|
||||
],
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss",
|
||||
"**/frontmatter.json": "jsonc",
|
||||
"**/.frontmatter/config/*.json": "jsonc",
|
||||
"*.css": "tailwindcss",
|
||||
"pico_i2s.pio.h": "c",
|
||||
"pico_i2s.h": "c"
|
||||
"pico_i2s.h": "c",
|
||||
"cctype": "cpp",
|
||||
"cmath": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"chrono": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"list": "cpp",
|
||||
"map": "cpp",
|
||||
"set": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"ratio": "cpp",
|
||||
"regex": "cpp",
|
||||
"string_view": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"fstream": "cpp",
|
||||
"future": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"mutex": "cpp",
|
||||
"ostream": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"thread": "cpp",
|
||||
"variant": "cpp",
|
||||
"bitset": "cpp",
|
||||
"*.ipp": "cpp",
|
||||
"deque": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"ranges": "cpp",
|
||||
"span": "cpp",
|
||||
"valarray": "cpp",
|
||||
"format": "cpp",
|
||||
"samd21.h": "c",
|
||||
"ost_hal.h": "c",
|
||||
"debug.h": "c",
|
||||
"numbers": "cpp",
|
||||
"qtconcurrentdepends": "cpp",
|
||||
"qtcoredepends": "cpp",
|
||||
"qtguidepends": "cpp",
|
||||
"qtnetworkdepends": "cpp",
|
||||
"qtopengldepends": "cpp",
|
||||
"qtqmldepends": "cpp",
|
||||
"qtwidgetsdepends": "cpp",
|
||||
"complex": "cpp",
|
||||
"system_error": "cpp",
|
||||
"typeindex": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"compare": "cpp",
|
||||
"any": "cpp",
|
||||
"filesystem": "cpp",
|
||||
"stdbool.h": "c",
|
||||
"bit": "cpp",
|
||||
"charconv": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"clocale": "cpp",
|
||||
"codecvt": "cpp",
|
||||
"concepts": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"new": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"shared_mutex": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"cfenv": "cpp",
|
||||
"source_location": "cpp",
|
||||
"stdfloat": "cpp",
|
||||
"text_encoding": "cpp",
|
||||
"serializers.h": "c",
|
||||
"ni_parser.h": "c",
|
||||
"*.m": "cpp",
|
||||
"*.inc": "cpp",
|
||||
"chip32_binary_format.h": "c",
|
||||
"hash_map": "c",
|
||||
"csignal": "cpp"
|
||||
}
|
||||
|
||||
}
|
||||
23
README.md
23
README.md
|
|
@ -1,6 +1,11 @@
|
|||
# Open Story Teller (OST)
|
||||
|
||||
[](https://github.com/arabine/open-story-teller/actions/workflows/build.yml)
|
||||
|
||||
| Job | Status |
|
||||
|---|---|
|
||||
| Story Editor | [](https://github.com/arabine/open-story-teller/actions/workflows/story_editor.yml) |
|
||||
|
||||
|
||||
|
||||
Open Story Teller is an Open Source project to provide guidelines and software to build your own story teller box (electronics, mechanical).
|
||||
|
||||
|
|
@ -63,6 +68,22 @@ The Story plater is a purely software implementation of a simple story player. I
|
|||
|
||||

|
||||
|
||||
There are multiple implementations in this directory (Flutter, SDL, Raylib). The one that will be maintain is probably the Raylib version besause this framework is the easiest one to port on multiple platforms.
|
||||
|
||||
|
||||
# Developer corner
|
||||
|
||||
## Build the Story Player
|
||||
|
||||
### Web version
|
||||
|
||||
Emscripten must be installed.
|
||||
|
||||
```
|
||||
mkdir build-web && cd build-web
|
||||
emcmake cmake .. -DPLATFORM=Web -DCMAKE_BUILD_TYPE=Debug
|
||||
emmake make
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 BalazsJako
|
||||
Copyright (c) 2023 Anthony Rabine
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
BIN
core/chip32/chip32-syntax/chip32-syntax-0.0.1.vsix
Normal file
BIN
core/chip32/chip32-syntax/chip32-syntax-0.0.1.vsix
Normal file
Binary file not shown.
29
core/chip32/chip32-syntax/package.json
Normal file
29
core/chip32/chip32-syntax/package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "chip32-syntax",
|
||||
"displayName": "Chip32 Syntax Highlighting",
|
||||
"description": "Syntax highlighting for Chip32 assembly language",
|
||||
"version": "0.0.1",
|
||||
"publisher": "Anthony Rabine",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/arabine/open-story-teller"},
|
||||
"engines": {
|
||||
"vscode": "^1.0.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages"
|
||||
],
|
||||
"contributes": {
|
||||
"languages": [{
|
||||
"id": "chip32",
|
||||
"extensions": [".chip32"],
|
||||
"aliases": ["Chip32", "chip32"]
|
||||
}],
|
||||
"grammars": [{
|
||||
"language": "chip32",
|
||||
"scopeName": "source.assembly",
|
||||
"path": "./syntaxes/chip32.tmLanguage.json"
|
||||
}]
|
||||
}
|
||||
}
|
||||
27
core/chip32/chip32-syntax/syntaxes/chip32.tmLanguage.json
Normal file
27
core/chip32/chip32-syntax/syntaxes/chip32.tmLanguage.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||
"name": "Chip32",
|
||||
"patterns": [
|
||||
{
|
||||
"match": "\\b(nop|halt|syscall|lcons|mov|push|pop|store|load|add|sub|mul|div|shiftl|shiftr|ishiftr|and|or|xor|not|call|ret|jump|jumpr|skipz|skipnz|eq|gt|lt)\\b",
|
||||
"name": "keyword.control.assembly"
|
||||
},
|
||||
{
|
||||
"match": "\\b(r[0-9]+|t[0-9]+|pc|sp|ra)\\b",
|
||||
"name": "variable.parameter.register.assembly"
|
||||
},
|
||||
{
|
||||
"match": "@",
|
||||
"name": "keyword.operator.assembly"
|
||||
},
|
||||
{
|
||||
"match": "\\b(0x[0-9a-fA-F]+)\\b",
|
||||
"name": "constant.numeric.hex.assembly"
|
||||
},
|
||||
{
|
||||
"match": ";.*",
|
||||
"name": "comment.line.assembly"
|
||||
}
|
||||
],
|
||||
"scopeName": "source.assembly"
|
||||
}
|
||||
626
core/chip32/chip32_assembler.cpp
Normal file
626
core/chip32/chip32_assembler.cpp
Normal file
|
|
@ -0,0 +1,626 @@
|
|||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2022 Anthony Rabine
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
|
||||
#include "chip32_assembler.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
namespace Chip32
|
||||
{
|
||||
|
||||
// =============================================================================
|
||||
// GLOBAL UTILITY FUNCTIONS
|
||||
// =============================================================================
|
||||
static std::string ToLower(const std::string &text)
|
||||
{
|
||||
std::string newText = text;
|
||||
std::transform(newText.begin(), newText.end(), newText.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
return newText;
|
||||
}
|
||||
|
||||
static const RegNames AllRegs[] = { { R0, "r0" }, { R1, "r1" }, { R2, "r2" }, { R3, "r3" }, { R4, "r4" }, { R5, "r5" },
|
||||
{ R6, "r6" }, { R7, "r7" }, { R8, "r8" }, { R9, "r9" }, { T0, "t0" }, { T1, "t1" }, { T2, "t2" }, { T3, "t3" }, { T4, "t4" },
|
||||
{ T5, "t5" }, { T6, "t6" }, { T7, "t7" }, { T8, "t8" }, { T9, "t9" },{ PC, "pc" }, { SP, "sp" }, { RA, "ra" }
|
||||
};
|
||||
|
||||
static const uint32_t NbRegs = sizeof(AllRegs) / sizeof(AllRegs[0]);
|
||||
|
||||
// Keep same order than the opcodes list!!
|
||||
static const std::string Mnemonics[] = {
|
||||
"nop", "halt", "syscall", "lcons", "mov", "push", "pop", "store", "load", "add", "addi", "sub", "subi", "mul", "div",
|
||||
"shiftl", "shiftr", "ishiftr", "and", "or", "xor", "not", "call", "ret", "jump", "skipz", "skipnz",
|
||||
"eq", "gt", "lt"
|
||||
};
|
||||
|
||||
static OpCode OpCodes[] = OPCODES_LIST;
|
||||
|
||||
static const uint32_t nbOpCodes = sizeof(OpCodes) / sizeof(OpCodes[0]);
|
||||
|
||||
static bool IsOpCode(const std::string &label, OpCode &op)
|
||||
{
|
||||
bool success = false;
|
||||
std::string lowLabel = ToLower(label);
|
||||
|
||||
for (uint32_t i = 0; i < nbOpCodes; i++)
|
||||
{
|
||||
if (Mnemonics[i] == lowLabel)
|
||||
{
|
||||
success = true;
|
||||
op = OpCodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
static inline void leu32_put(std::vector<std::uint8_t> &container, uint32_t data)
|
||||
{
|
||||
container.push_back(data & 0xFFU);
|
||||
container.push_back((data >> 8U) & 0xFFU);
|
||||
container.push_back((data >> 16U) & 0xFFU);
|
||||
container.push_back((data >> 24U) & 0xFFU);
|
||||
}
|
||||
|
||||
static inline void leu16_put(std::vector<std::uint8_t> &container, uint16_t data)
|
||||
{
|
||||
container.push_back(data & 0xFFU);
|
||||
container.push_back((data >> 8U) & 0xFFU);
|
||||
}
|
||||
|
||||
#define GET_REG(name, ra) if (!GetRegister(name, ra)) {\
|
||||
m_lastError.line = -1; \
|
||||
m_lastError.message = "ERROR! Bad register name: " + name; \
|
||||
return false; }
|
||||
|
||||
#define CHIP32_CHECK(instr, cond, error) if (!(cond)) { \
|
||||
m_lastError.line = instr->line; \
|
||||
m_lastError.message = error; \
|
||||
return false; } \
|
||||
|
||||
|
||||
static uint32_t convertStringToLong(const std::string& str) {
|
||||
char* end;
|
||||
if (str.compare(0, 2, "0x") == 0 || str.compare(0, 2, "0X") == 0) {
|
||||
return static_cast<uint32_t>(strtol(str.c_str(), &end, 16));
|
||||
} else if (str.compare(0, 2, "0b") == 0 || str.compare(0, 2, "0B") == 0) {
|
||||
return static_cast<uint32_t>(strtol(str.c_str() + 2, &end, 2));
|
||||
} else {
|
||||
return static_cast<uint32_t>(strtol(str.c_str(), &end, 10));
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ASSEMBLER CLASS
|
||||
// =============================================================================
|
||||
bool Assembler::GetRegister(const std::string ®Name, uint8_t ®)
|
||||
{
|
||||
std::string lowReg = ToLower(regName);
|
||||
for (uint32_t i = 0; i < NbRegs; i++)
|
||||
{
|
||||
if (lowReg == AllRegs[i].name)
|
||||
{
|
||||
reg = AllRegs[i].reg;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> Assembler::Split(const std::string &line)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::string current;
|
||||
bool inQuotes = false;
|
||||
|
||||
for (char c : line) {
|
||||
if (c == '"') {
|
||||
// Si on rencontre un guillemet, on change l'état
|
||||
inQuotes = !inQuotes;
|
||||
current += c;
|
||||
}
|
||||
else if ((c == ' ' || c == ',') && !inQuotes) {
|
||||
// Si on rencontre un espace ou une virgule en dehors des guillemets
|
||||
if (!current.empty()) {
|
||||
result.push_back(current);
|
||||
current.clear();
|
||||
}
|
||||
} else {
|
||||
// Sinon, on ajoute le caractère au "current"
|
||||
current += c;
|
||||
}
|
||||
}
|
||||
|
||||
// Ajout du dernier morceau, s'il existe
|
||||
if (!current.empty()) {
|
||||
result.push_back(current);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Assembler::GetRegisterName(uint8_t reg, std::string ®Name)
|
||||
{
|
||||
for (uint32_t i = 0; i < NbRegs; i++)
|
||||
{
|
||||
if (reg == AllRegs[i].reg)
|
||||
{
|
||||
regName = AllRegs[i].name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Assembler::CompileMnemonicArguments(std::shared_ptr<Instr> instr)
|
||||
{
|
||||
uint8_t ra, rb, rc;
|
||||
|
||||
switch(instr->code.opcode)
|
||||
{
|
||||
case OP_NOP:
|
||||
case OP_HALT:
|
||||
case OP_RET:
|
||||
// no arguments, just use the opcode
|
||||
break;
|
||||
case OP_SYSCALL:
|
||||
instr->compiledArgs.push_back(static_cast<uint8_t>(strtol(instr->args[0].c_str(), NULL, 0)));
|
||||
break;
|
||||
case OP_LCONS:
|
||||
GET_REG(instr->args[0], ra);
|
||||
instr->compiledArgs.push_back(ra);
|
||||
// Detect address or immedate value
|
||||
if ((instr->args[1].at(0) == '$') || (instr->args[1].at(0) == '.')) {
|
||||
instr->labelIndex = 1;
|
||||
leu32_put(instr->compiledArgs, 0); // reserve 4 bytes
|
||||
} else { // immediate value
|
||||
leu32_put(instr->compiledArgs, convertStringToLong(instr->args[1]));
|
||||
}
|
||||
break;
|
||||
case OP_POP:
|
||||
case OP_PUSH:
|
||||
case OP_SKIPZ:
|
||||
case OP_SKIPNZ:
|
||||
case OP_NOT:
|
||||
GET_REG(instr->args[0], ra);
|
||||
instr->compiledArgs.push_back(ra);
|
||||
break;
|
||||
case OP_MOV:
|
||||
case OP_ADD:
|
||||
case OP_SUB:
|
||||
case OP_MUL:
|
||||
case OP_DIV:
|
||||
case OP_SHL:
|
||||
case OP_SHR:
|
||||
case OP_ISHR:
|
||||
case OP_AND:
|
||||
case OP_OR:
|
||||
case OP_XOR:
|
||||
GET_REG(instr->args[0], ra);
|
||||
GET_REG(instr->args[1], rb);
|
||||
instr->compiledArgs.push_back(ra);
|
||||
instr->compiledArgs.push_back(rb);
|
||||
break;
|
||||
case OP_ADDI:
|
||||
case OP_SUBI:
|
||||
{
|
||||
GET_REG(instr->args[0], ra);
|
||||
instr->compiledArgs.push_back(ra);
|
||||
|
||||
uint32_t op = convertStringToLong(instr->args[1]);
|
||||
if (op > 255) {
|
||||
return false;
|
||||
}
|
||||
leu32_put(instr->compiledArgs, op);
|
||||
break;
|
||||
}
|
||||
case OP_CALL:
|
||||
case OP_JUMP:
|
||||
{
|
||||
// 5 bytes
|
||||
// first byte is option: register based or address
|
||||
// Then 4 bytes (address or just one byte for register)
|
||||
|
||||
// We allow two forms of writing:
|
||||
// - call @r0 ; call the address located in R0
|
||||
// - jump .myFunction ; jump to the label
|
||||
//
|
||||
char prefix = instr->args[0].at(0);
|
||||
|
||||
if (prefix == '@')
|
||||
{
|
||||
instr->compiledArgs.push_back(0); // option zero
|
||||
GET_REG(instr->args[0], ra);
|
||||
instr->compiledArgs.push_back(ra);
|
||||
// Three more bytes to keep same size
|
||||
instr->compiledArgs.push_back(0);
|
||||
instr->compiledArgs.push_back(0);
|
||||
instr->compiledArgs.push_back(0);
|
||||
}
|
||||
else if (prefix == '.')
|
||||
{
|
||||
// Reserve 4 bytes for address, it will be filled at the end
|
||||
instr->labelIndex = 1;
|
||||
instr->compiledArgs.push_back(1); // option 1
|
||||
leu32_put(instr->compiledArgs, 0); // reserve 4 bytes
|
||||
}
|
||||
else
|
||||
{
|
||||
CHIP32_CHECK(instr, false, "Jump/Call argument must be @R0 or .myLabel")
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_STORE: // store @r4, r1, 2
|
||||
CHIP32_CHECK(instr, instr->args[0].at(0) == '@', "Missing @ sign before register")
|
||||
instr->args[0].erase(0, 1);
|
||||
GET_REG(instr->args[0], ra);
|
||||
GET_REG(instr->args[1], rb);
|
||||
instr->compiledArgs.push_back(ra);
|
||||
instr->compiledArgs.push_back(rb);
|
||||
instr->compiledArgs.push_back(static_cast<uint32_t>(strtol(instr->args[2].c_str(), NULL, 0)));
|
||||
break;
|
||||
case OP_LOAD:
|
||||
{
|
||||
// We allow two forms of writing:
|
||||
// - load r0, @R1, 4 ; the address is located in a register
|
||||
// - load r0, $variable, 2 ; we use the variable address to get the value
|
||||
//
|
||||
char prefix = instr->args[1].at(0);
|
||||
|
||||
// Register based
|
||||
if (prefix == '@')
|
||||
{
|
||||
// 3 bytes total for arguments
|
||||
instr->args[1].erase(0, 1); // delete @ character
|
||||
GET_REG(instr->args[0], ra);
|
||||
GET_REG(instr->args[1], rb);
|
||||
instr->compiledArgs.push_back(ra);
|
||||
instr->compiledArgs.push_back(rb);
|
||||
instr->compiledArgs.push_back(static_cast<uint32_t>(strtol(instr->args[2].c_str(), NULL, 0)));
|
||||
// Three more bytes to keep same size
|
||||
instr->compiledArgs.push_back(0);
|
||||
instr->compiledArgs.push_back(0);
|
||||
instr->compiledArgs.push_back(0);
|
||||
}
|
||||
// Variable based
|
||||
else if (prefix == '$')
|
||||
{
|
||||
// 6 bytes
|
||||
instr->labelIndex = 1;
|
||||
GET_REG(instr->args[0], ra);
|
||||
instr->compiledArgs.push_back(ra | 0x80); // Flag this register with a bit to indicate an immediate address is following
|
||||
leu32_put(instr->compiledArgs, 0); // reserve 4 bytes
|
||||
instr->compiledArgs.push_back(static_cast<uint32_t>(strtol(instr->args[2].c_str(), NULL, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHIP32_CHECK(instr, false, "Load source address must be @reg or $variable");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OP_CMP_EQ:
|
||||
case OP_CMP_GT:
|
||||
case OP_CMP_LT:
|
||||
GET_REG(instr->args[0], ra);
|
||||
GET_REG(instr->args[1], rb);
|
||||
GET_REG(instr->args[2], rc);
|
||||
instr->compiledArgs.push_back(ra);
|
||||
instr->compiledArgs.push_back(rb);
|
||||
instr->compiledArgs.push_back(rc);
|
||||
break;
|
||||
default:
|
||||
CHIP32_CHECK(instr, false, "Unsupported mnemonic: " + instr->mnemonic);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assembler::CompileConstantArgument(std::shared_ptr<Instr> instr, const std::string &a)
|
||||
{
|
||||
instr->compiledArgs.clear(); instr->args.clear(); instr->labelIndex = -1;
|
||||
|
||||
// Check string
|
||||
if (a.size() > 2)
|
||||
{
|
||||
// Detected string
|
||||
if ((a[0] == '"') && (a[a.size() - 1] == '"'))
|
||||
{
|
||||
for (unsigned int i = 1; i < (a.size() - 1); i++)
|
||||
{
|
||||
instr->compiledArgs.push_back(a[i]);
|
||||
}
|
||||
instr->compiledArgs.push_back(0);
|
||||
return true;
|
||||
}
|
||||
// Detect label
|
||||
else if (a[0] == '.')
|
||||
{
|
||||
// Label must be 32-bit, throw an error if not the case
|
||||
CHIP32_CHECK(instr, instr->dataTypeSize == 32, "Labels must be stored in a 32-bit area (DC32)")
|
||||
instr->labelIndex = 1;
|
||||
instr->args.push_back(a);
|
||||
leu32_put(instr->compiledArgs, 0); // reserve 4 bytes
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// here, we check if the intergers are correct
|
||||
uint32_t intVal = static_cast<uint32_t>(strtol(a.c_str(), NULL, 0));
|
||||
|
||||
bool sizeOk = false;
|
||||
if (((intVal <= UINT8_MAX) && (instr->dataTypeSize == 8)) ||
|
||||
((intVal <= UINT16_MAX) && (instr->dataTypeSize == 16)) ||
|
||||
((intVal <= UINT32_MAX) && (instr->dataTypeSize == 32))) {
|
||||
sizeOk = true;
|
||||
}
|
||||
CHIP32_CHECK(instr, sizeOk, "integer too high: " + std::to_string(intVal));
|
||||
if (instr->dataTypeSize == 8) {
|
||||
instr->compiledArgs.push_back(intVal);
|
||||
} else if (instr->dataTypeSize == 16) {
|
||||
leu16_put(instr->compiledArgs, intVal);
|
||||
} else {
|
||||
leu32_put(instr->compiledArgs, intVal);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assembler::Parse(const std::string &data)
|
||||
{
|
||||
std::stringstream data_stream(data);
|
||||
std::string line;
|
||||
|
||||
Clear();
|
||||
int code_addr = 0;
|
||||
int dz_ram_addr = 0; // For DZ
|
||||
int dv_ram_addr = 0; // For DV
|
||||
int lineNum = 0;
|
||||
while(std::getline(data_stream, line))
|
||||
{
|
||||
lineNum++;
|
||||
auto instr = std::make_shared<Instr>();
|
||||
instr->line = lineNum;
|
||||
size_t pos = line.find_first_of(";");
|
||||
if (pos != std::string::npos) {
|
||||
line.erase(pos);
|
||||
}
|
||||
line.erase(0, line.find_first_not_of("\t\n\v\f\r ")); // left trim
|
||||
line.erase(line.find_last_not_of("\t\n\v\f\r ") + 1); // right trim
|
||||
|
||||
if (std::all_of(line.begin(), line.end(), ::isspace)) continue;
|
||||
|
||||
// Split the line
|
||||
std::vector<std::string> lineParts = Split(line);
|
||||
CHIP32_CHECK(instr, (lineParts.size() > 0), " not a valid line");
|
||||
|
||||
// Ok until now
|
||||
std::string opcode = lineParts[0];
|
||||
|
||||
// =======================================================================================
|
||||
// LABEL
|
||||
// =======================================================================================
|
||||
if (opcode[0] == '.')
|
||||
{
|
||||
CHIP32_CHECK(instr, (opcode[opcode.length() - 1] == ':') && (lineParts.size() == 1), "label must end with ':'");
|
||||
// Label
|
||||
opcode.pop_back(); // remove the colon character
|
||||
instr->mnemonic = opcode;
|
||||
instr->isLabel = true;
|
||||
instr->addr = code_addr;
|
||||
CHIP32_CHECK(instr, m_labels.count(opcode) == 0, "duplicated label : " + opcode);
|
||||
m_labels[opcode] = instr;
|
||||
m_instructions.push_back(instr);
|
||||
}
|
||||
|
||||
// =======================================================================================
|
||||
// INSTRUCTIONS
|
||||
// =======================================================================================
|
||||
else if (IsOpCode(opcode, instr->code))
|
||||
{
|
||||
instr->mnemonic = opcode;
|
||||
bool nbArgsSuccess = false;
|
||||
// Test nedded arguments
|
||||
if ((instr->code.nbAargs == 0) && (lineParts.size() == 1))
|
||||
{
|
||||
nbArgsSuccess = true; // no arguments, solo mnemonic
|
||||
}
|
||||
else if ((instr->code.nbAargs > 0) && (lineParts.size() >= 2))
|
||||
{
|
||||
instr->args.insert(instr->args.begin(), lineParts.begin() + 1, lineParts.end());
|
||||
CHIP32_CHECK(instr, instr->args.size() == instr->code.nbAargs,
|
||||
"Bad number of parameters. Required: " + std::to_string(static_cast<int>(instr->code.nbAargs)) + ", got: " + std::to_string(instr->args.size()));
|
||||
nbArgsSuccess = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CHIP32_CHECK(instr, false, "Bad number of parameters");
|
||||
}
|
||||
|
||||
if (nbArgsSuccess)
|
||||
{
|
||||
CHIP32_CHECK(instr, CompileMnemonicArguments(instr) == true, "Compile failure, mnemonic or arguments");
|
||||
instr->addr = code_addr;
|
||||
code_addr += 1 + instr->compiledArgs.size();
|
||||
m_instructions.push_back(instr);
|
||||
}
|
||||
}
|
||||
// =======================================================================================
|
||||
// CONSTANTS IN ROM OR RAM (eg: $yourLabel DC8 "a string", 5, 4, 8 (DV32 for RAM)
|
||||
// C for Constant, V stands for Volatile
|
||||
// =======================================================================================
|
||||
else if (opcode[0] == '$')
|
||||
{
|
||||
instr->mnemonic = opcode;
|
||||
CHIP32_CHECK(instr, (lineParts.size() >= 3), "bad number of parameters");
|
||||
|
||||
std::string type = lineParts[1];
|
||||
|
||||
CHIP32_CHECK(instr, (type.size() >= 3), "bad data type size");
|
||||
CHIP32_CHECK(instr, (type[0] == 'D') && ((type[1] == 'C') || (type[1] == 'V') || (type[1] == 'Z')),
|
||||
"bad data type (must be DCxx, DVxx or DZxx)");
|
||||
CHIP32_CHECK(instr, m_labels.count(opcode) == 0, "duplicated label : " + opcode);
|
||||
|
||||
// Parse data type size (8, 16, or 32)
|
||||
type.erase(0, 2);
|
||||
instr->dataTypeSize = static_cast<uint32_t>(strtol(type.c_str(), NULL, 0));
|
||||
|
||||
// Determine data type
|
||||
char typeChar = lineParts[1][1];
|
||||
instr->isRomData = (typeChar == 'C');
|
||||
instr->isRamData = (typeChar == 'V' || typeChar == 'Z');
|
||||
instr->isZeroData = (typeChar == 'Z');
|
||||
|
||||
// =======================================================================================
|
||||
// DC - ROM Constants (read-only data in program memory)
|
||||
// =======================================================================================
|
||||
if (instr->isRomData)
|
||||
{
|
||||
instr->addr = code_addr;
|
||||
m_labels[opcode] = instr; // location of the start of the data
|
||||
|
||||
// Generate one instruction per argument
|
||||
// Reason: arguments may be labels, easier to replace later
|
||||
for (unsigned int i = 2; i < lineParts.size(); i++)
|
||||
{
|
||||
// Create a new cloned instruction for each argument
|
||||
auto clonedInstr = std::make_shared<Instr>(*instr);
|
||||
|
||||
CHIP32_CHECK(clonedInstr, CompileConstantArgument(clonedInstr, lineParts[i]),
|
||||
"Compile argument error, stopping.");
|
||||
m_instructions.push_back(clonedInstr);
|
||||
code_addr += clonedInstr->compiledArgs.size();
|
||||
clonedInstr->addr = code_addr;
|
||||
}
|
||||
}
|
||||
// =======================================================================================
|
||||
// DV - RAM Variables with initial values (data stored in ROM, copied to RAM at startup)
|
||||
// =======================================================================================
|
||||
else if (!instr->isZeroData) // DV
|
||||
{
|
||||
// DV behaves like DC for data storage
|
||||
|
||||
instr->addr = dv_ram_addr;
|
||||
m_labels[opcode] = instr; // RAM address for this variable
|
||||
|
||||
// Process all initial values (like DC)
|
||||
for (unsigned int i = 2; i < lineParts.size(); i++)
|
||||
{
|
||||
// Create a new cloned instruction for each argument
|
||||
auto clonedInstr = std::make_shared<Instr>(*instr);
|
||||
|
||||
CHIP32_CHECK(clonedInstr, CompileConstantArgument(clonedInstr, lineParts[i]),
|
||||
"Compile argument error, stopping.");
|
||||
m_instructions.push_back(clonedInstr);
|
||||
dv_ram_addr += clonedInstr->compiledArgs.size();
|
||||
clonedInstr->addr = dv_ram_addr;
|
||||
|
||||
}
|
||||
}
|
||||
// =======================================================================================
|
||||
// DZ - Zero-initialized RAM zones (no data in ROM, just reserve space)
|
||||
// =======================================================================================
|
||||
else // DZ
|
||||
{
|
||||
// DZ only takes ONE argument: the number of elements
|
||||
CHIP32_CHECK(instr, lineParts.size() == 3,
|
||||
"DZ directive requires exactly one argument (number of elements)");
|
||||
|
||||
instr->addr = dz_ram_addr;
|
||||
|
||||
// Calculate size in bytes: num_elements * (type_size / 8)
|
||||
uint32_t numElements = static_cast<uint32_t>(strtol(lineParts[2].c_str(), NULL, 0));
|
||||
instr->dataLen = static_cast<uint16_t>(numElements * (instr->dataTypeSize / 8));
|
||||
|
||||
dz_ram_addr += instr->dataLen;
|
||||
m_labels[opcode] = instr;
|
||||
m_instructions.push_back(instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Second pass: Now that the RAM DV size is known, compute DZ real data location after DV
|
||||
/*
|
||||
|
||||
Position of Data in RAM
|
||||
|
||||
----------------------------
|
||||
| DV |
|
||||
----------------------------
|
||||
| DZ |
|
||||
----------------------------
|
||||
*/
|
||||
for (auto &instr : m_instructions)
|
||||
{
|
||||
if (instr->isZeroData)
|
||||
{
|
||||
instr->addr += dv_ram_addr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 3. Third pass: replace all label or RAM data by the real address in memory
|
||||
for (auto &instr : m_instructions)
|
||||
{
|
||||
if ((instr->labelIndex >=0 ) && (instr->args.size() > 0))
|
||||
{
|
||||
// label is the first argument for JUMP and CALL, second position for LCONS and LOAD
|
||||
|
||||
// Look where is the label
|
||||
uint16_t argsIndex = 0;
|
||||
for (auto &arg : instr->args)
|
||||
{
|
||||
if ((arg[0] == '.') || (arg[0] == '$'))
|
||||
{
|
||||
break;
|
||||
}
|
||||
argsIndex++;
|
||||
}
|
||||
|
||||
std::string label = instr->args[argsIndex];
|
||||
CHIP32_CHECK(instr, m_labels.count(label) > 0, "label not found: " + label);
|
||||
uint32_t addr = m_labels[label]->addr;
|
||||
std::cout << "LABEL: " << label << " , addr: " << addr << std::endl;
|
||||
if (m_labels[label]->isRamData)
|
||||
{
|
||||
addr |= CHIP32_RAM_OFFSET;
|
||||
}
|
||||
instr->compiledArgs[instr->labelIndex] = (uint8_t)(addr & 0xFF);
|
||||
instr->compiledArgs[instr->labelIndex + 1] = (uint8_t)((addr >> 8) & 0xFF);
|
||||
instr->compiledArgs[instr->labelIndex + 2] = (uint8_t)((addr >> 16) & 0xFF);
|
||||
instr->compiledArgs[instr->labelIndex + 3] = (uint8_t)((addr >> 24) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Chip32
|
||||
|
|
@ -31,6 +31,7 @@ THE SOFTWARE.
|
|||
#include <string>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
namespace Chip32
|
||||
{
|
||||
|
|
@ -49,13 +50,14 @@ struct Instr {
|
|||
std::string mnemonic;
|
||||
uint16_t dataTypeSize{0};
|
||||
uint16_t dataLen{0};
|
||||
int8_t labelIndex{-1}; // index of label in compiled argument (-1 means no label)
|
||||
|
||||
bool isLabel{false}; //!< If true, this is a label, otherwise it is an instruction
|
||||
bool useLabel{false}; //!< If true, the instruction uses a label
|
||||
bool isRomData{false}; //!< True is constant data in program
|
||||
bool isRamData{false}; //!< True is constant data in program
|
||||
bool isLabel{false}; //!< If true, this is a label, otherwise it is an instruction
|
||||
bool isRomData{false}; //!< True if constant data in ROM (DC)
|
||||
bool isRamData{false}; //!< True if RAM variable (DV or DZ)
|
||||
bool isZeroData{false}; //!< True if zero-initialized RAM (DZ only)
|
||||
|
||||
uint16_t addr{0}; //!< instruction address when assembled in program memory
|
||||
uint32_t addr{0}; //!< Instruction/data address (ROM for DC, RAM for DV/DZ)
|
||||
|
||||
bool isRomCode() const { return !(isLabel || isRomData || isRamData); }
|
||||
};
|
||||
|
|
@ -66,45 +68,33 @@ struct RegNames
|
|||
std::string name;
|
||||
};
|
||||
|
||||
struct Result
|
||||
{
|
||||
int ramUsageSize{0};
|
||||
int romUsageSize{0};
|
||||
int constantsSize{0};
|
||||
|
||||
void Print()
|
||||
{
|
||||
std::cout << "RAM usage: " << ramUsageSize << " bytes\n"
|
||||
<< "IMAGE size: " << romUsageSize << " bytes\n"
|
||||
<< " -> ROM DATA: " << constantsSize << " bytes\n"
|
||||
<< " -> ROM CODE: " << romUsageSize - constantsSize << "\n"
|
||||
<< std::endl;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
class Assembler
|
||||
{
|
||||
public:
|
||||
|
||||
struct Error {
|
||||
std::string message;
|
||||
int line;
|
||||
std::string ToString() const { return "Error line " + std::to_string(line) + ", " + message; }
|
||||
int line{-1};
|
||||
std::string ToString() const {
|
||||
if (line < 0)
|
||||
return "No error";
|
||||
else
|
||||
return "Error line " + std::to_string(line) + ", " + message;
|
||||
}
|
||||
};
|
||||
|
||||
// Separated parser to allow only code check
|
||||
bool Parse(const std::string &data);
|
||||
// Generate the executable binary after the parse pass
|
||||
bool BuildBinary(std::vector<uint8_t> &program, Result &result);
|
||||
|
||||
void Clear() {
|
||||
m_labels.clear();
|
||||
m_instructions.clear();
|
||||
}
|
||||
|
||||
std::vector<Instr>::const_iterator Begin() { return m_instructions.begin(); }
|
||||
std::vector<Instr>::const_iterator End() { return m_instructions.end(); }
|
||||
static std::vector<std::string> Split(const std::string &line);
|
||||
|
||||
std::vector<std::shared_ptr<Instr>>::const_iterator begin() { return m_instructions.begin(); }
|
||||
std::vector<std::shared_ptr<Instr>>::const_iterator end() { return m_instructions.end(); }
|
||||
|
||||
// Returns the register number from the name
|
||||
bool GetRegister(const std::string ®Name, uint8_t ®);
|
||||
|
|
@ -112,18 +102,31 @@ public:
|
|||
|
||||
Error GetLastError() { return m_lastError; }
|
||||
|
||||
private:
|
||||
bool CompileMnemonicArguments(Instr &instr);
|
||||
bool GetMain(std::shared_ptr<Instr> &instr) const {
|
||||
|
||||
// label, address
|
||||
std::map<std::string, Instr> m_labels;
|
||||
// Find the main label
|
||||
bool success = m_labels.count(".main") == 1;
|
||||
|
||||
if (success)
|
||||
{
|
||||
instr = m_labels.at(".main");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private:
|
||||
bool CompileMnemonicArguments(std::shared_ptr<Instr> instr);
|
||||
|
||||
// label, shared pointer to instruction
|
||||
std::map<std::string, std::shared_ptr<Instr>> m_labels;
|
||||
|
||||
Error m_lastError;
|
||||
|
||||
std::vector<Instr> m_instructions;
|
||||
bool CompileConstantArgument(Instr &instr, const std::string &a);
|
||||
std::vector<std::shared_ptr<Instr>> m_instructions;
|
||||
bool CompileConstantArgument(std::shared_ptr<Instr> instr, const std::string &a);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CHIP32_ASSEMBLER_H
|
||||
#endif // CHIP32_ASSEMBLER_H
|
||||
259
core/chip32/chip32_binary_format.c
Normal file
259
core/chip32/chip32_binary_format.c
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* Chip32 Binary Format - Implementation
|
||||
*/
|
||||
|
||||
#include "chip32_binary_format.h"
|
||||
#include <stdio.h>
|
||||
|
||||
// Verify header size at compile time
|
||||
_Static_assert(sizeof(chip32_binary_header_t) == 28, "Header must be 28 bytes");
|
||||
|
||||
// ============================================================================
|
||||
// LOADING FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
chip32_binary_error_t chip32_binary_load(
|
||||
chip32_ctx_t *ctx,
|
||||
uint8_t* binary,
|
||||
uint32_t binary_size,
|
||||
uint8_t* ram,
|
||||
uint32_t ram_size,
|
||||
chip32_binary_header_t *header
|
||||
)
|
||||
{
|
||||
// Initialize VM
|
||||
chip32_initialize(ctx);
|
||||
ctx->stack_size = 512; // Can be changed outside this function
|
||||
|
||||
// Clear RAM
|
||||
memset(ram, 0, ram_size);
|
||||
|
||||
if (!binary || !ctx) {
|
||||
return CHIP32_BIN_ERR_NULL_POINTER;
|
||||
}
|
||||
|
||||
// Check minimum size
|
||||
if (binary_size < sizeof(chip32_binary_header_t)) {
|
||||
return CHIP32_BIN_ERR_TOO_SMALL;
|
||||
}
|
||||
|
||||
// Copy header
|
||||
memcpy(header, binary, sizeof(chip32_binary_header_t));
|
||||
|
||||
// Verify magic number
|
||||
if (header->magic != CHIP32_MAGIC) {
|
||||
return CHIP32_BIN_ERR_INVALID_MAGIC;
|
||||
}
|
||||
|
||||
// Check version
|
||||
if (header->version > CHIP32_VERSION) {
|
||||
return CHIP32_BIN_ERR_UNSUPPORTED_VERSION;
|
||||
}
|
||||
|
||||
// Calculate expected size
|
||||
uint32_t expected_size = sizeof(chip32_binary_header_t) +
|
||||
header->const_size +
|
||||
header->code_size +
|
||||
header->data_size;
|
||||
|
||||
if (binary_size != expected_size) {
|
||||
return CHIP32_BIN_ERR_SIZE_MISMATCH;
|
||||
}
|
||||
|
||||
// Set section pointers
|
||||
uint32_t offset = sizeof(chip32_binary_header_t);
|
||||
|
||||
// Skip header for ROM executable (must start at a valide code address)
|
||||
ctx->rom.mem = binary + offset;
|
||||
ctx->rom.size = binary_size - offset;
|
||||
ctx->rom.addr = 0;
|
||||
|
||||
// RAM and ROM are in the same logical memory plane
|
||||
// So we set it begin after the ROM (why not)
|
||||
ctx->ram.mem = ram;
|
||||
ctx->ram.addr = ctx->rom.size;
|
||||
ctx->ram.size = ram_size;
|
||||
|
||||
// Set entry point (DATA size + entry point offset in CODE)
|
||||
ctx->registers[PC] = header->entry_point;
|
||||
// Stack pointer at the end of the RAM
|
||||
ctx->registers[SP] = ctx->ram.size;
|
||||
|
||||
// Load data initialized values
|
||||
const uint8_t *data = binary + offset + header->const_size + header->code_size;
|
||||
memcpy(ram, data, header->data_size);
|
||||
|
||||
return CHIP32_BIN_OK;
|
||||
}
|
||||
|
||||
const char* chip32_binary_error_string(chip32_binary_error_t error)
|
||||
{
|
||||
switch (error) {
|
||||
case CHIP32_BIN_OK:
|
||||
return "No error";
|
||||
case CHIP32_BIN_ERR_TOO_SMALL:
|
||||
return "Binary too small (less than header size)";
|
||||
case CHIP32_BIN_ERR_INVALID_MAGIC:
|
||||
return "Invalid magic number (not a Chip32 binary)";
|
||||
case CHIP32_BIN_ERR_UNSUPPORTED_VERSION:
|
||||
return "Unsupported binary version";
|
||||
case CHIP32_BIN_ERR_SIZE_MISMATCH:
|
||||
return "Binary size mismatch (corrupted file?)";
|
||||
case CHIP32_BIN_ERR_NULL_POINTER:
|
||||
return "NULL pointer argument";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BUILDING FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
void chip32_binary_header_init(chip32_binary_header_t* header)
|
||||
{
|
||||
if (!header) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(header, 0, sizeof(chip32_binary_header_t));
|
||||
header->magic = CHIP32_MAGIC;
|
||||
header->version = CHIP32_VERSION;
|
||||
header->flags = 0;
|
||||
}
|
||||
|
||||
uint32_t chip32_binary_calculate_size(const chip32_binary_header_t* header)
|
||||
{
|
||||
if (!header) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sizeof(chip32_binary_header_t) +
|
||||
header->const_size +
|
||||
header->code_size +
|
||||
header->data_size;
|
||||
}
|
||||
|
||||
uint32_t chip32_binary_write(
|
||||
const chip32_binary_header_t* header,
|
||||
const uint8_t* const_section,
|
||||
const uint8_t* code_section,
|
||||
const uint8_t* init_data_section,
|
||||
uint8_t* out_buffer,
|
||||
uint32_t buffer_size
|
||||
)
|
||||
{
|
||||
if (!header || !out_buffer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate required size
|
||||
uint32_t required_size = chip32_binary_calculate_size(header);
|
||||
|
||||
if (buffer_size < required_size) {
|
||||
return 0; // Buffer too small
|
||||
}
|
||||
|
||||
// Write header
|
||||
memcpy(out_buffer, header, sizeof(chip32_binary_header_t));
|
||||
uint32_t offset = sizeof(chip32_binary_header_t);
|
||||
|
||||
// Write CONST section
|
||||
if (header->const_size > 0) {
|
||||
if (!const_section) {
|
||||
return 0; // Data expected but NULL pointer
|
||||
}
|
||||
memcpy(out_buffer + offset, const_section, header->const_size);
|
||||
offset += header->const_size;
|
||||
}
|
||||
|
||||
// Write CODE section
|
||||
if (header->code_size > 0) {
|
||||
if (!code_section) {
|
||||
return 0; // Code expected but NULL pointer
|
||||
}
|
||||
memcpy(out_buffer + offset, code_section, header->code_size);
|
||||
offset += header->code_size;
|
||||
}
|
||||
|
||||
// Write INIT DATA section
|
||||
if (header->data_size > 0) {
|
||||
if (!init_data_section) {
|
||||
return 0; // Init data expected but NULL pointer
|
||||
}
|
||||
memcpy(out_buffer + offset, init_data_section, header->data_size);
|
||||
offset += header->data_size;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// DEBUG/UTILITY FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
|
||||
void chip32_binary_build_stats(const chip32_binary_header_t *header, chip32_binary_stats_t* out_stats)
|
||||
{
|
||||
out_stats->const_size = header->const_size;
|
||||
out_stats->bss_size = header->bss_size;
|
||||
out_stats->code_size = header->code_size;
|
||||
out_stats->data_size = header->data_size;
|
||||
|
||||
out_stats->total_file_size = sizeof(chip32_binary_header_t) +
|
||||
header->const_size +
|
||||
header->code_size +
|
||||
header->data_size;
|
||||
|
||||
out_stats->total_rom_size = header->const_size +
|
||||
header->code_size;
|
||||
|
||||
out_stats->total_ram_size = header->bss_size;
|
||||
|
||||
}
|
||||
|
||||
void chip32_binary_print_header(const chip32_binary_header_t* header)
|
||||
{
|
||||
if (!header) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf("=== Chip32 Binary Header ===\n");
|
||||
printf("Magic: 0x%08X", header->magic);
|
||||
if (header->magic == CHIP32_MAGIC) {
|
||||
printf(" (valid)\n");
|
||||
} else {
|
||||
printf(" (INVALID!)\n");
|
||||
}
|
||||
printf("Version: %u\n", header->version);
|
||||
printf("Flags: 0x%04X", header->flags);
|
||||
if (header->flags & CHIP32_FLAG_HAS_INIT_DATA) {
|
||||
printf(" (has init data)");
|
||||
}
|
||||
printf("\n");
|
||||
printf("CONST section: %u bytes (ROM constants)\n", header->const_size);
|
||||
printf("BSS section: %u bytes (Total RAM: DV+DZ)\n", header->bss_size);
|
||||
printf("CODE section: %u bytes\n", header->code_size);
|
||||
printf("Entry point: 0x%08X\n", header->entry_point);
|
||||
printf("Init data: %u bytes (DV values + DZ zeros)\n", header->data_size);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void chip32_binary_print_stats(const chip32_binary_stats_t* stats)
|
||||
{
|
||||
if (!stats) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf("=== Chip32 Binary Statistics ===\n");
|
||||
printf("CONST section: %u bytes (ROM, initialized)\n", stats->const_size);
|
||||
printf("BSS section: %u bytes (RAM, DZ)\n", stats->bss_size);
|
||||
printf("CODE section: %u bytes (ROM, executable)\n", stats->code_size);
|
||||
printf("Init data: %u bytes (RAM initialization)\n", stats->data_size);
|
||||
printf("---\n");
|
||||
printf("File size: %u bytes\n", stats->total_file_size);
|
||||
printf("ROM usage: %u bytes (DATA + CODE)\n", stats->total_rom_size);
|
||||
printf("RAM usage: %u bytes (BSS)\n", stats->total_ram_size);
|
||||
printf("\n");
|
||||
}
|
||||
200
core/chip32/chip32_binary_format.h
Normal file
200
core/chip32/chip32_binary_format.h
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Chip32 Binary Format - Pure C Implementation
|
||||
* Compatible with embedded systems
|
||||
*
|
||||
* Variable types:
|
||||
* DC8, DC32 : ROM constants (initialized data in ROM)
|
||||
* DV8, DV32 : RAM variables with initial value
|
||||
* DZ8, DZ32 : RAM zero-initialized arrays/buffers
|
||||
*
|
||||
* Examples:
|
||||
* $romString DC8 "Hello" ; String in ROM
|
||||
* $romValue DC32 42 ; Constant in ROM
|
||||
* $ramCounter DV32 100 ; RAM variable initialized to 100
|
||||
* $ramMessage DV8 "Test" ; RAM string initialized to "Test"
|
||||
* $ramBuffer DZ8 256 ; RAM buffer of 256 bytes (zeroed)
|
||||
* $ramArray DZ32 100 ; RAM array of 100 x 32-bit (zeroed)
|
||||
*
|
||||
* Binary file structure:
|
||||
*
|
||||
* [HEADER - 28 bytes]
|
||||
* - Magic number: "C32\0" (4 bytes)
|
||||
* - Version: uint16_t (2 bytes)
|
||||
* - Flags: uint16_t (2 bytes)
|
||||
* - Data section size: uint32_t (4 bytes)
|
||||
* - BSS section size: uint32_t (4 bytes)
|
||||
* - Code section size: uint32_t (4 bytes)
|
||||
* - Entry point: uint32_t (4 bytes)
|
||||
* - Init data size: uint32_t (4 bytes)
|
||||
*
|
||||
* [DATA SECTION]
|
||||
* - ROM constants (DC8, DC32, etc.)
|
||||
*
|
||||
* [CODE SECTION]
|
||||
* - Executable instructions
|
||||
*
|
||||
* [INIT DATA SECTION]
|
||||
* - Initial RAM values: DV values + zeros for DZ areas
|
||||
* - Size = sum of all DV and DZ declarations
|
||||
* - Layout matches RAM layout exactly
|
||||
*
|
||||
*
|
||||
|
||||
+--------------------------------------+
|
||||
| Chip32 Header | <-- Début du fichier
|
||||
| (Magic, Versions, Sizes, Entry...) |
|
||||
+--------------------------------------+
|
||||
| CONST Section (DV) | <-- Données initialisées
|
||||
| (Variables globales initialisées) | e.g., const int x = 5;
|
||||
+--------------------------------------+
|
||||
| CODE Section | <-- Instructions du programme
|
||||
| (Les opcodes et leurs opérandes) |
|
||||
+--------------------------------------+
|
||||
| DATA Section (Optional) | <-- Données pour l'initialisation de la RAM (copie de DV)
|
||||
| (Contient les valeurs initiales pour la RAM)
|
||||
+--------------------------------------+
|
||||
|
||||
*/
|
||||
|
||||
#ifndef CHIP32_BINARY_FORMAT_H
|
||||
#define CHIP32_BINARY_FORMAT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "chip32_vm.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Magic number: "C32\0" in little-endian
|
||||
#define CHIP32_MAGIC 0x00323343
|
||||
|
||||
// Current version
|
||||
#define CHIP32_VERSION 1
|
||||
|
||||
// Flags
|
||||
#define CHIP32_FLAG_HAS_INIT_DATA 0x0001 // Binary contains RAM init data
|
||||
|
||||
// Error codes
|
||||
typedef enum {
|
||||
CHIP32_BIN_OK = 0,
|
||||
CHIP32_BIN_ERR_TOO_SMALL,
|
||||
CHIP32_BIN_ERR_INVALID_MAGIC,
|
||||
CHIP32_BIN_ERR_UNSUPPORTED_VERSION,
|
||||
CHIP32_BIN_ERR_SIZE_MISMATCH,
|
||||
CHIP32_BIN_ERR_NULL_POINTER
|
||||
} chip32_binary_error_t;
|
||||
|
||||
// Binary header structure (28 bytes, packed)
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint32_t magic; // Magic number "C32\0"
|
||||
uint16_t version; // Format version
|
||||
uint16_t flags; // Feature flags
|
||||
uint32_t const_size; // Size of CONST section in bytes (ROM constants)
|
||||
uint32_t bss_size; // Total RAM size (DV + DZ)
|
||||
uint32_t code_size; // Size of CODE section in bytes
|
||||
uint32_t entry_point; // Entry point offset in CODE section
|
||||
uint32_t data_size; // Size of INIT DATA section (DV values + DZ zeros)
|
||||
} chip32_binary_header_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
// Statistics
|
||||
typedef struct {
|
||||
uint32_t const_size; // ROM constants
|
||||
uint32_t bss_size; // Total RAM needed
|
||||
uint32_t code_size; // Executable code
|
||||
uint32_t data_size; // RAM initialization data
|
||||
uint32_t total_file_size; // File size on disk
|
||||
uint32_t total_rom_size; // DATA + CODE
|
||||
uint32_t total_ram_size; // RAM needed
|
||||
} chip32_binary_stats_t;
|
||||
|
||||
// ============================================================================
|
||||
// LOADING FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Load and validate a Chip32 binary
|
||||
* @param binary Pointer to binary data
|
||||
* @param size Size of binary data in bytes
|
||||
* @param out_loaded Output structure (filled on success)
|
||||
* @return Error code
|
||||
*/
|
||||
chip32_binary_error_t chip32_binary_load(
|
||||
chip32_ctx_t *ctx,
|
||||
uint8_t* binary,
|
||||
uint32_t binary_size,
|
||||
uint8_t* ram,
|
||||
uint32_t ram_size,
|
||||
chip32_binary_header_t *header
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Get error string from error code
|
||||
* @param error Error code
|
||||
* @return Human-readable error message
|
||||
*/
|
||||
const char* chip32_binary_error_string(chip32_binary_error_t error);
|
||||
|
||||
// ============================================================================
|
||||
// BUILDING FUNCTIONS (for assembler/compiler)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initialize a binary header
|
||||
* @param header Header to initialize
|
||||
*/
|
||||
void chip32_binary_header_init(chip32_binary_header_t* header);
|
||||
|
||||
/**
|
||||
* Calculate total binary size from header
|
||||
* @param header Binary header
|
||||
* @return Total size in bytes
|
||||
*/
|
||||
uint32_t chip32_binary_calculate_size(const chip32_binary_header_t* header);
|
||||
|
||||
/**
|
||||
* Write a complete binary to memory
|
||||
* @param header Binary header
|
||||
* @param const_section DATA section content (can be NULL if const_size is 0)
|
||||
* @param code_section CODE section content (can be NULL if code_size is 0)
|
||||
* @param init_data_section INIT DATA section (can be NULL if data_size is 0)
|
||||
* @param out_buffer Output buffer (must be large enough)
|
||||
* @param buffer_size Size of output buffer
|
||||
* @return Number of bytes written, or 0 on error
|
||||
*/
|
||||
uint32_t chip32_binary_write(
|
||||
const chip32_binary_header_t* header,
|
||||
const uint8_t* const_section,
|
||||
const uint8_t* code_section,
|
||||
const uint8_t* init_data_section,
|
||||
uint8_t* out_buffer,
|
||||
uint32_t buffer_size
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// DEBUG/UTILITY FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
void chip32_binary_build_stats(const chip32_binary_header_t *header, chip32_binary_stats_t* out_stats);
|
||||
|
||||
/**
|
||||
* Print binary header information
|
||||
* @param header Binary header
|
||||
*/
|
||||
void chip32_binary_print_header(const chip32_binary_header_t* header);
|
||||
|
||||
/**
|
||||
* Print binary statistics
|
||||
* @param stats Binary statistics
|
||||
*/
|
||||
void chip32_binary_print_stats(const chip32_binary_stats_t* stats);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CHIP32_BINARY_FORMAT_H
|
||||
609
core/chip32/chip32_machine.h
Normal file
609
core/chip32/chip32_machine.h
Normal file
|
|
@ -0,0 +1,609 @@
|
|||
/*
|
||||
* Chip32 Machine - VM wrapper with binary format support
|
||||
* Updated to use chip32_binary_format for proper DV/DZ handling
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
|
||||
#include "chip32_assembler.h"
|
||||
#include "chip32_binary_format.h"
|
||||
#include "chip32_macros.h"
|
||||
|
||||
namespace Chip32
|
||||
{
|
||||
|
||||
class Machine
|
||||
{
|
||||
public:
|
||||
bool parseResult{false};
|
||||
bool buildResult{false};
|
||||
chip32_result_t runResult{VM_OK};
|
||||
std::string printOutput;
|
||||
chip32_ctx_t ctx; // Public pour accès aux registres dans les tests
|
||||
std::vector<uint8_t> ram; // RAM storage
|
||||
std::vector<uint8_t> program;
|
||||
Chip32::Assembler assembler;
|
||||
chip32_binary_header_t header;
|
||||
chip32_binary_error_t error;
|
||||
|
||||
Machine() {
|
||||
// Bind syscall handler to this instance
|
||||
m_syscallHandler = std::bind(&Machine::HandleSyscall, this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2);
|
||||
ram.resize(2048);
|
||||
}
|
||||
|
||||
|
||||
bool BuildBinary(Assembler &assembler, std::vector<uint8_t> &program, chip32_binary_stats_t &stats)
|
||||
{
|
||||
program.clear();
|
||||
|
||||
// ========================================================================
|
||||
// PHASE 1: Créer les sections temporaires
|
||||
// ========================================================================
|
||||
std::vector<uint8_t> constSection; // DC - ROM constants only
|
||||
std::vector<uint8_t> codeSection; // Executable code
|
||||
std::vector<uint8_t> initDataSection; // DV values + DZ zeros (RAM init)
|
||||
|
||||
|
||||
chip32_binary_header_init(&header);
|
||||
|
||||
std::shared_ptr<Chip32::Instr> mainInstr;
|
||||
if (assembler.GetMain(mainInstr))
|
||||
{
|
||||
header.entry_point = mainInstr->addr;
|
||||
}
|
||||
// else: no main found, we try to start at address zero...
|
||||
|
||||
for (auto instr : assembler)
|
||||
{
|
||||
// ====================================================================
|
||||
// CODE INSTRUCTIONS
|
||||
// ====================================================================
|
||||
if (instr->isRomCode())
|
||||
{
|
||||
// Ajouter l'opcode
|
||||
codeSection.push_back(instr->code.opcode);
|
||||
|
||||
// Ajouter les arguments compilés
|
||||
std::copy(instr->compiledArgs.begin(),
|
||||
instr->compiledArgs.end(),
|
||||
std::back_inserter(codeSection));
|
||||
}
|
||||
// ====================================================================
|
||||
// ROM DATA - Distinguer DC (vraies constantes) de DV init data
|
||||
// ====================================================================
|
||||
else if (instr->isRomData && !instr->isRamData)
|
||||
{
|
||||
std::copy(instr->compiledArgs.begin(),
|
||||
instr->compiledArgs.end(),
|
||||
std::back_inserter(constSection));
|
||||
}
|
||||
// ====================================================================
|
||||
// RAM VARIABLES
|
||||
// ====================================================================
|
||||
else if (instr->isRamData)
|
||||
{
|
||||
|
||||
// ====================================================================
|
||||
// ZEROED DATA (DZ)
|
||||
// ====================================================================
|
||||
if (instr->isZeroData)
|
||||
{
|
||||
header.bss_size += instr->dataLen;
|
||||
}
|
||||
// ====================================================================
|
||||
// INITIALIZED RAM VARIABLES (DV)
|
||||
// ====================================================================
|
||||
else
|
||||
{
|
||||
// Ces données appartiennent à cette variable DV
|
||||
initDataSection.insert(initDataSection.end(),
|
||||
instr->compiledArgs.begin(),
|
||||
instr->compiledArgs.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// PHASE 5: Créer le header binaire
|
||||
// ========================================================================
|
||||
|
||||
header.const_size = static_cast<uint32_t>(constSection.size());
|
||||
header.code_size = static_cast<uint32_t>(codeSection.size());
|
||||
header.data_size = static_cast<uint32_t>(initDataSection.size());
|
||||
|
||||
if (initDataSection.size() > 0)
|
||||
{
|
||||
header.flags |= CHIP32_FLAG_HAS_INIT_DATA;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// PHASE 6: Assembler le binaire final
|
||||
// ========================================================================
|
||||
uint32_t totalSize = chip32_binary_calculate_size(&header);
|
||||
program.resize(totalSize);
|
||||
|
||||
uint32_t bytesWritten = chip32_binary_write(
|
||||
&header,
|
||||
constSection.empty() ? nullptr : constSection.data(),
|
||||
codeSection.empty() ? nullptr : codeSection.data(),
|
||||
initDataSection.empty() ? nullptr : initDataSection.data(),
|
||||
program.data(),
|
||||
static_cast<uint32_t>(program.size())
|
||||
);
|
||||
|
||||
if (bytesWritten == 0 || bytesWritten != totalSize)
|
||||
{
|
||||
// Erreur lors de l'écriture
|
||||
program.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// PHASE 7: Remplir les statistiques
|
||||
// ========================================================================
|
||||
chip32_binary_build_stats(&header, &stats);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetAssemblyLine(uint32_t pointer_counter, uint32_t &line)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
// On recherche quelle est la ligne qui possède une instruction à cette adresse
|
||||
for (auto instr : assembler)
|
||||
{
|
||||
if ((instr->addr == pointer_counter) && instr->isRomCode())
|
||||
{
|
||||
line = instr->line;
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool LoadBinary(const std::string &filename)
|
||||
{
|
||||
bool success = false;
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
|
||||
if (file)
|
||||
{
|
||||
// Méthode 1 : La plus simple avec les itérateurs
|
||||
program = std::vector<uint8_t>(
|
||||
std::istreambuf_iterator<char>(file),
|
||||
std::istreambuf_iterator<char>()
|
||||
);
|
||||
success = true;
|
||||
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void SaveBinary(const std::string &filename)
|
||||
{
|
||||
std::ofstream o(filename , std::ios::out | std::ios::binary);
|
||||
o.write(reinterpret_cast<const char*>(program.data()), program.size());
|
||||
o.close();
|
||||
}
|
||||
|
||||
bool Build(const std::string &assemblyCode)
|
||||
{
|
||||
chip32_binary_stats_t stats;
|
||||
|
||||
// Parse
|
||||
parseResult = assembler.Parse(assemblyCode);
|
||||
|
||||
if (!parseResult) {
|
||||
std::cout << "Parse error: " << assembler.GetLastError().ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build binary with new format
|
||||
buildResult = BuildBinary(assembler, program, stats);
|
||||
chip32_binary_print_stats(&stats);
|
||||
|
||||
if (!buildResult) {
|
||||
std::cout << "Build error: " << assembler.GetLastError().ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadCurrentProgram()) {
|
||||
std::cout << "Binary load error: " << chip32_binary_error_string(error) << std::endl;
|
||||
buildResult = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
chip32_binary_build_stats(&header, &stats);
|
||||
chip32_binary_print_stats(&stats);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetEvent(uint32_t ev) {
|
||||
ctx.registers[R0] = ev;
|
||||
}
|
||||
|
||||
bool LoadCurrentProgram()
|
||||
{
|
||||
// Load binary using executable format
|
||||
error = chip32_binary_load(
|
||||
&ctx,
|
||||
program.data(),
|
||||
static_cast<uint32_t>(program.size()),
|
||||
ram.data(),
|
||||
static_cast<uint32_t>(ram.size()),
|
||||
&header
|
||||
);
|
||||
|
||||
return error == CHIP32_BIN_OK;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Méthode principale : Parse, Build, Execute
|
||||
// ========================================================================
|
||||
void QuickExecute(const std::string &assemblyCode)
|
||||
{
|
||||
Build(assemblyCode);
|
||||
|
||||
// Set syscall handler using wrapper
|
||||
ctx.syscall = SyscallWrapper;
|
||||
ctx.user_data = this;
|
||||
|
||||
std::cout << "Starting execution at PC=0x" << std::hex << ctx.registers[PC]
|
||||
<< std::dec << std::endl;
|
||||
|
||||
// Run
|
||||
runResult = chip32_run(&ctx);
|
||||
|
||||
std::cout << "Execution finished with result: " << runResult << std::endl;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Helper functions
|
||||
// ========================================================================
|
||||
|
||||
static std::string GetStringFromMemory(chip32_ctx_t *ctx, uint32_t addr)
|
||||
{
|
||||
if (!ctx) {
|
||||
throw std::runtime_error("Invalid context in GetStringFromMemory");
|
||||
}
|
||||
|
||||
bool isRam = (addr & 0x80000000) != 0;
|
||||
addr &= 0xFFFF;
|
||||
|
||||
const uint8_t* source_mem = nullptr;
|
||||
size_t mem_size = 0;
|
||||
|
||||
if (isRam) {
|
||||
if (addr >= ctx->ram.size) {
|
||||
throw std::out_of_range("RAM address out of bounds: " + std::to_string(addr));
|
||||
}
|
||||
source_mem = ctx->ram.mem;
|
||||
mem_size = ctx->ram.size;
|
||||
} else {
|
||||
if (addr >= ctx->rom.size) {
|
||||
throw std::out_of_range("ROM address out of bounds: " + std::to_string(addr));
|
||||
}
|
||||
source_mem = ctx->rom.mem;
|
||||
mem_size = ctx->rom.size;
|
||||
}
|
||||
|
||||
size_t max_len = mem_size - addr;
|
||||
const char* str_start = reinterpret_cast<const char*>(&source_mem[addr]);
|
||||
|
||||
const char* null_pos = static_cast<const char*>(
|
||||
std::memchr(str_start, '\0', max_len)
|
||||
);
|
||||
|
||||
if (null_pos) {
|
||||
return std::string(str_start, null_pos - str_start);
|
||||
} else {
|
||||
return std::string(str_start, max_len);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string FormatStringWithPlaceholders(chip32_ctx_t *ctx,
|
||||
const std::string& format,
|
||||
const std::vector<uint32_t>& args)
|
||||
{
|
||||
std::ostringstream result;
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < format.length()) {
|
||||
// Chercher le prochain placeholder '{'
|
||||
if (format[pos] == '{' && pos + 1 < format.length()) {
|
||||
char nextChar = format[pos + 1];
|
||||
|
||||
// Vérifier si c'est un placeholder valide {0} à {3}
|
||||
if (nextChar >= '0' && nextChar <= '3') {
|
||||
int argIndex = nextChar - '0';
|
||||
|
||||
// Vérifier si on a assez d'arguments
|
||||
if (argIndex >= static_cast<int>(args.size())) {
|
||||
result << "{" << argIndex << ":?}";
|
||||
pos += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Vérifier le type (i ou s)
|
||||
if (pos + 2 < format.length() && format[pos + 2] == ':') {
|
||||
if (pos + 3 < format.length()) {
|
||||
char typeChar = format[pos + 3];
|
||||
uint32_t argValue = args[argIndex];
|
||||
|
||||
if (typeChar == 's') {
|
||||
// String: argValue est une adresse
|
||||
try {
|
||||
std::string str = GetStringFromMemory(ctx, argValue);
|
||||
result << str;
|
||||
pos += 5; // Avancer de "{0:s}"
|
||||
continue;
|
||||
} catch (const std::exception& e) {
|
||||
result << "{str_error}";
|
||||
pos += 5;
|
||||
continue;
|
||||
}
|
||||
} else if (typeChar == 'i' || typeChar == 'd') {
|
||||
// Integer
|
||||
result << static_cast<int32_t>(argValue);
|
||||
pos += 5;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (pos + 2 < format.length() && format[pos + 2] == '}') {
|
||||
// Format simple {0} - traiter comme int par défaut
|
||||
uint32_t argValue = args[argIndex];
|
||||
result << static_cast<int32_t>(argValue);
|
||||
pos += 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Caractère normal, copier tel quel
|
||||
result << format[pos];
|
||||
pos++;
|
||||
}
|
||||
|
||||
return result.str();
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Syscall handler
|
||||
// ========================================================================
|
||||
|
||||
uint8_t HandleSyscall(chip32_ctx_t *ctx, uint8_t code)
|
||||
{
|
||||
try {
|
||||
if (code == 4) // Printf
|
||||
{
|
||||
std::string format = GetStringFromMemory(ctx, ctx->registers[R0]);
|
||||
int arg_count = ctx->registers[R1];
|
||||
|
||||
std::vector<uint32_t> args;
|
||||
for (int i = 0; i < arg_count && i < 4; ++i) {
|
||||
args.push_back(ctx->registers[R2 + i]);
|
||||
}
|
||||
|
||||
printOutput = FormatStringWithPlaceholders(ctx, format, args);
|
||||
std::cout << "[SYSCALL PRINT] " << printOutput << std::endl;
|
||||
}
|
||||
else if (code == 5) // WAIT
|
||||
{
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(ctx->registers[R0])
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Unknown syscall code: " << static_cast<int>(code) << std::endl;
|
||||
return SYSCALL_RET_ERROR;
|
||||
}
|
||||
|
||||
return SYSCALL_RET_OK;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Syscall error: " << e.what() << std::endl;
|
||||
return SYSCALL_RET_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Hexdump utilities
|
||||
// ========================================================================
|
||||
|
||||
void HexDump(const uint8_t* data, uint32_t size, uint32_t base_addr = 0, const std::string& title = "")
|
||||
{
|
||||
if (!title.empty()) {
|
||||
std::cout << "\n=== " << title << " ===" << std::endl;
|
||||
}
|
||||
|
||||
const int bytes_per_line = 16;
|
||||
|
||||
for (uint32_t i = 0; i < size; i += bytes_per_line) {
|
||||
// Adresse
|
||||
std::cout << std::hex << std::setfill('0') << std::setw(8)
|
||||
<< (base_addr + i) << ": ";
|
||||
|
||||
// Octets en hexadécimal
|
||||
for (int j = 0; j < bytes_per_line; j++) {
|
||||
if (i + j < size) {
|
||||
std::cout << std::setw(2) << static_cast<int>(data[i + j]) << " ";
|
||||
} else {
|
||||
std::cout << " ";
|
||||
}
|
||||
|
||||
// Séparateur au milieu
|
||||
if (j == 7) {
|
||||
std::cout << " ";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << " |";
|
||||
|
||||
// Représentation ASCII
|
||||
for (int j = 0; j < bytes_per_line && i + j < size; j++) {
|
||||
uint8_t byte = data[i + j];
|
||||
if (byte >= 32 && byte <= 126) {
|
||||
std::cout << static_cast<char>(byte);
|
||||
} else {
|
||||
std::cout << '.';
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "|" << std::dec << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void DumpRom()
|
||||
{
|
||||
if (ctx.rom.mem == nullptr || ctx.rom.size == 0) {
|
||||
std::cout << "ROM is empty or not loaded" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "\n" << std::string(80, '=') << std::endl;
|
||||
std::cout << "ROM DUMP (Total: " << ctx.rom.size << " bytes)" << std::endl;
|
||||
std::cout << std::string(80, '=') << std::endl;
|
||||
|
||||
uint32_t offset = 0;
|
||||
|
||||
// Dump CONST section
|
||||
if (header.const_size > 0) {
|
||||
HexDump(ctx.rom.mem + offset, header.const_size, offset,
|
||||
"CONST Section (" + std::to_string(header.const_size) + " bytes)");
|
||||
offset += header.const_size;
|
||||
}
|
||||
|
||||
// Dump CODE section
|
||||
if (header.code_size > 0) {
|
||||
HexDump(ctx.rom.mem + offset, header.code_size, offset,
|
||||
"CODE Section (" + std::to_string(header.code_size) + " bytes)");
|
||||
offset += header.code_size;
|
||||
}
|
||||
|
||||
// Dump INIT DATA section
|
||||
if (header.data_size > 0) {
|
||||
HexDump(ctx.rom.mem + offset, header.data_size, offset,
|
||||
"INIT DATA Section (" + std::to_string(header.data_size) + " bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
void DumpRam()
|
||||
{
|
||||
if (ctx.ram.mem == nullptr || ctx.ram.size == 0) {
|
||||
std::cout << "RAM is empty or not allocated" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "\n" << std::string(80, '=') << std::endl;
|
||||
std::cout << "RAM DUMP (Total: " << ctx.ram.size << " bytes)" << std::endl;
|
||||
std::cout << std::string(80, '=') << std::endl;
|
||||
|
||||
uint32_t offset = 0;
|
||||
|
||||
// Dump DATA section (initialized variables)
|
||||
if (header.data_size > 0) {
|
||||
HexDump(ctx.ram.mem + offset, header.data_size, 0x80000000 + offset,
|
||||
"DATA Section (DV - " + std::to_string(header.data_size) + " bytes)");
|
||||
offset += header.data_size;
|
||||
}
|
||||
|
||||
// Dump BSS section (zero-initialized variables)
|
||||
if (header.bss_size > 0) {
|
||||
HexDump(ctx.ram.mem + offset, header.bss_size, 0x80000000 + offset,
|
||||
"BSS Section (DZ - " + std::to_string(header.bss_size) + " bytes)");
|
||||
offset += header.bss_size;
|
||||
}
|
||||
|
||||
// Dump remaining RAM (heap/stack area)
|
||||
uint32_t remaining = ctx.ram.size - offset;
|
||||
if (remaining > 0) {
|
||||
// Limiter l'affichage à 256 bytes pour le reste
|
||||
uint32_t display_size = std::min(remaining, 256u);
|
||||
HexDump(ctx.ram.mem + offset, display_size, 0x80000000 + offset,
|
||||
"Heap/Stack area (showing first " + std::to_string(display_size) +
|
||||
" of " + std::to_string(remaining) + " bytes)");
|
||||
|
||||
if (remaining > display_size) {
|
||||
std::cout << "... (" << (remaining - display_size)
|
||||
<< " more bytes not shown)" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DumpMemory()
|
||||
{
|
||||
DumpRom();
|
||||
DumpRam();
|
||||
}
|
||||
|
||||
// Variante pour dumper une région spécifique de RAM
|
||||
void DumpRamRegion(uint32_t start_addr, uint32_t size)
|
||||
{
|
||||
// Enlever le bit RAM si présent
|
||||
uint32_t addr = start_addr & 0x7FFFFFFF;
|
||||
|
||||
if (ctx.ram.mem == nullptr || ctx.ram.size == 0) {
|
||||
std::cout << "RAM is not allocated" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (addr >= ctx.ram.size) {
|
||||
std::cout << "Start address 0x" << std::hex << addr
|
||||
<< " is out of RAM bounds (size: 0x" << ctx.ram.size
|
||||
<< ")" << std::dec << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t actual_size = std::min(size, ctx.ram.size - addr);
|
||||
|
||||
std::cout << "\n" << std::string(80, '=') << std::endl;
|
||||
std::cout << "RAM Region Dump" << std::endl;
|
||||
std::cout << "Address: 0x" << std::hex << (0x80000000 | addr)
|
||||
<< " - 0x" << (0x80000000 | (addr + actual_size - 1))
|
||||
<< std::dec << std::endl;
|
||||
std::cout << std::string(80, '=') << std::endl;
|
||||
|
||||
HexDump(ctx.ram.mem + addr, actual_size, 0x80000000 | addr, "");
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
std::function<uint8_t(chip32_ctx_t*, uint8_t)> m_syscallHandler;
|
||||
|
||||
// Wrapper statique qui récupère l'instance et appelle la méthode membre
|
||||
static uint8_t SyscallWrapper(chip32_ctx_t *ctx, uint8_t code)
|
||||
{
|
||||
if (!ctx || !ctx->user_data) {
|
||||
return SYSCALL_RET_ERROR;
|
||||
}
|
||||
|
||||
Machine* instance = static_cast<Machine*>(ctx->user_data);
|
||||
return instance->HandleSyscall(ctx, code);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Chip32
|
||||
197
core/chip32/chip32_macros.h
Normal file
197
core/chip32/chip32_macros.h
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2022 Anthony Rabine
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "chip32_assembler.h"
|
||||
|
||||
namespace Chip32
|
||||
{
|
||||
|
||||
struct Macro {
|
||||
int param_count;
|
||||
std::vector<std::string> body;
|
||||
};
|
||||
|
||||
class ScriptProcessor {
|
||||
public:
|
||||
void process(const std::string &text) {
|
||||
|
||||
resultAsm.clear();
|
||||
|
||||
std::stringstream data_stream(text);
|
||||
std::string line;
|
||||
|
||||
while(std::getline(data_stream, line))
|
||||
{
|
||||
line = trim(line);
|
||||
parseLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the expanded assembly file
|
||||
// without any macro directives
|
||||
std::string GetResult() const {
|
||||
return resultAsm;
|
||||
}
|
||||
|
||||
void generate_assembly() {
|
||||
|
||||
std::stringstream out;
|
||||
|
||||
for (const std::string& line : text_section) {
|
||||
out << line << "\n";
|
||||
}
|
||||
|
||||
for (const std::string& line : data_section) {
|
||||
out << line << "\n";
|
||||
}
|
||||
resultAsm = out.str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::stack<int> conditionStack;
|
||||
std::stack<int> loopStack;
|
||||
std::string resultAsm;
|
||||
Macro* current_macro = nullptr;
|
||||
|
||||
|
||||
std::unordered_map<std::string, Macro> macros;
|
||||
std::vector<std::string> text_section;
|
||||
std::vector<std::string> data_section;
|
||||
bool in_data_section = false;
|
||||
bool in_macro_section = false;
|
||||
|
||||
|
||||
int labelCounter = 0;
|
||||
int printCounter = 0;
|
||||
|
||||
std::string trim(const std::string& str) {
|
||||
size_t first = str.find_first_not_of(" \t");
|
||||
if (first == std::string::npos) return "";
|
||||
size_t last = str.find_last_not_of(" \t");
|
||||
return str.substr(first, (last - first + 1));
|
||||
}
|
||||
|
||||
|
||||
std::string expand_macro(const std::string& line, const std::vector<std::string>& args) {
|
||||
std::string expanded = line;
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
std::string placeholder = "%" + std::to_string(i + 1);
|
||||
expanded = std::regex_replace(expanded, std::regex(placeholder), args[i]);
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
void createLabel()
|
||||
{
|
||||
int label = labelCounter++;
|
||||
loopStack.push(label);
|
||||
std::cout << "L" << label << "_START:" << std::endl;
|
||||
}
|
||||
|
||||
void parseLine(std::string& line) {
|
||||
line = std::regex_replace(line, std::regex("^ +| +$"), ""); // Trim spaces
|
||||
if (line.empty() || line[0] == ';') return;
|
||||
|
||||
if ((line.find("%macro") == 0)) {
|
||||
|
||||
if (!in_macro_section) {
|
||||
std::cerr << "Error: Macros must be located in macro section\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::istringstream ss(line);
|
||||
std::string _, name;
|
||||
int param_count;
|
||||
ss >> _ >> name >> param_count;
|
||||
macros[name] = { param_count, {} };
|
||||
current_macro = ¯os[name];
|
||||
} else if (line.find("%endmacro") == 0) {
|
||||
current_macro = nullptr;
|
||||
} else if (current_macro) {
|
||||
current_macro->body.push_back(line);
|
||||
} else if (line.find("%section_macro") == 0) {
|
||||
in_macro_section = true;
|
||||
|
||||
} else if (line.find("%section_text") == 0) {
|
||||
|
||||
in_macro_section = false;
|
||||
in_data_section = false;
|
||||
} else if (line.find("%section_data") == 0) {
|
||||
|
||||
in_macro_section = false;
|
||||
in_data_section = true;
|
||||
} else {
|
||||
std::vector<std::string> args = Assembler::Split(line);
|
||||
|
||||
if (args.size() > 0) {
|
||||
std::string name = args[0];
|
||||
args.erase(args.begin());
|
||||
|
||||
if ((name.find("DC") == 0 || name.find("DV") == 0) && !in_data_section) {
|
||||
std::cerr << "Error: Data declarations (DC/DV) must be inside section .data\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (macros.find(name) != macros.end()) {
|
||||
Macro& macro = macros[name];
|
||||
|
||||
if (args.size() != macro.param_count) {
|
||||
std::cerr << "Error: Macro " << name << " expects " << macro.param_count << " arguments, got " << args.size() << "\n";
|
||||
return;
|
||||
}
|
||||
for (const std::string& body_line : macro.body) {
|
||||
text_section.push_back(expand_macro(body_line, args));
|
||||
}
|
||||
} else {
|
||||
if (in_data_section) {
|
||||
data_section.push_back(line);
|
||||
} else {
|
||||
text_section.push_back(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Macro problem with this line\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace Chip32
|
||||
|
|
@ -35,6 +35,23 @@ THE SOFTWARE.
|
|||
|
||||
#define _NEXT_BYTE ctx->rom.mem[++ctx->registers[PC]]
|
||||
|
||||
|
||||
static inline uint32_t deserialize_le32(const uint8_t *ptr)
|
||||
{
|
||||
return (uint32_t)ptr[0] |
|
||||
((uint32_t)ptr[1] << 8) |
|
||||
((uint32_t)ptr[2] << 16) |
|
||||
((uint32_t)ptr[3] << 24);
|
||||
}
|
||||
static inline void serialize_le32(uint8_t *ptr, uint32_t value)
|
||||
{
|
||||
ptr[0] = (uint8_t)(value & 0xFF);
|
||||
ptr[1] = (uint8_t)((value >> 8) & 0xFF);
|
||||
ptr[2] = (uint8_t)((value >> 16) & 0xFF);
|
||||
ptr[3] = (uint8_t)((value >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
static inline uint16_t _NEXT_SHORT (chip32_ctx_t *ctx)
|
||||
{
|
||||
ctx->registers[PC] += 2;
|
||||
|
|
@ -45,8 +62,7 @@ static inline uint32_t _NEXT_INT (chip32_ctx_t *ctx)
|
|||
{
|
||||
ctx->registers[PC] += 4;
|
||||
|
||||
return ctx->rom.mem[ctx->registers[PC] - 3] | ctx->rom.mem[ctx->registers[PC] - 2] << 8 |
|
||||
ctx->rom.mem[ctx->registers[PC] - 1] << 16 | ctx->rom.mem[ctx->registers[PC]] << 24;
|
||||
return deserialize_le32(&ctx->rom.mem[ctx->registers[PC] - 3]);
|
||||
}
|
||||
|
||||
#define _CHECK_SKIP if (skip) continue;
|
||||
|
|
@ -65,14 +81,14 @@ static inline uint32_t _NEXT_INT (chip32_ctx_t *ctx)
|
|||
#define _CHECK_REGISTER_VALID(r) \
|
||||
if (r >= REGISTER_COUNT) \
|
||||
return VM_ERR_INVALID_REGISTER;
|
||||
|
||||
#define _CHECK_CAN_PUSH(n) \
|
||||
if (ctx->registers[SP] - (n * sizeof(uint32_t)) > ctx->ram.addr) \
|
||||
if (ctx->registers[SP] - (n * sizeof(uint32_t)) < 0) \
|
||||
return VM_ERR_STACK_OVERFLOW;
|
||||
|
||||
#define _CHECK_CAN_POP(n) \
|
||||
if (ctx->registers[SP] + (n * sizeof(uint32_t)) > (ctx->ram.addr + ctx->ram.size)) \
|
||||
return VM_ERR_STACK_UNDERFLOW; \
|
||||
if (ctx->registers[SP] < ctx->prog_size) \
|
||||
return VM_ERR_STACK_OVERFLOW;
|
||||
if ((ctx->registers[SP] + (n * sizeof(uint32_t))) > (ctx->ram.size)) \
|
||||
return VM_ERR_STACK_UNDERFLOW;
|
||||
#else
|
||||
#define _CHECK_ROM_ADDR_VALID(a)
|
||||
#define _CHECK_BYTES_AVAIL(n)
|
||||
|
|
@ -85,23 +101,38 @@ static inline uint32_t _NEXT_INT (chip32_ctx_t *ctx)
|
|||
static const OpCode OpCodes[] = OPCODES_LIST;
|
||||
static const uint16_t OpCodesSize = sizeof(OpCodes) / sizeof(OpCodes[0]);
|
||||
|
||||
|
||||
static void push(chip32_ctx_t *ctx, uint32_t val) {
|
||||
ctx->registers[SP] -= 4;
|
||||
serialize_le32(&ctx->ram.mem[ctx->registers[SP]], val);
|
||||
}
|
||||
|
||||
static uint32_t pop(chip32_ctx_t *ctx) {
|
||||
uint32_t val = deserialize_le32(&ctx->ram.mem[ctx->registers[SP]]);
|
||||
ctx->registers[SP] += 4;
|
||||
return val;
|
||||
}
|
||||
|
||||
// =======================================================================================
|
||||
// FUNCTIONS
|
||||
// =======================================================================================
|
||||
void chip32_initialize(chip32_ctx_t *ctx)
|
||||
{
|
||||
memset(ctx->ram.mem, 0, ctx->ram.size);
|
||||
memset(ctx->registers, 0, REGISTER_COUNT * sizeof(uint32_t));
|
||||
ctx->instrCount = 0;
|
||||
ctx->registers[SP] = ctx->ram.size;
|
||||
ctx->instr_count = 0;
|
||||
}
|
||||
|
||||
chip32_result_t chip32_run(chip32_ctx_t *ctx)
|
||||
{
|
||||
chip32_result_t result = VM_OK;
|
||||
while ((ctx->max_instr == 0) || (ctx->instrCount < ctx->max_instr))
|
||||
while ((ctx->max_instr == 0) || (ctx->instr_count < ctx->max_instr))
|
||||
{
|
||||
chip32_step(ctx);
|
||||
result = chip32_step(ctx);
|
||||
|
||||
if ((result > VM_OK) || (result == VM_FINISHED))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -162,8 +193,7 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
const uint8_t reg = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg)
|
||||
_CHECK_CAN_PUSH(1)
|
||||
ctx->registers[SP] -= 4;
|
||||
memcpy(&ctx->ram.mem[ctx->registers[SP]], &ctx->registers[reg], sizeof(uint32_t));
|
||||
push(ctx, ctx->registers[reg]);
|
||||
break;
|
||||
}
|
||||
case OP_POP:
|
||||
|
|
@ -171,21 +201,19 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
const uint8_t reg = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg)
|
||||
_CHECK_CAN_POP(1)
|
||||
memcpy(&ctx->registers[reg], &ctx->ram.mem[ctx->registers[SP]], sizeof(uint32_t));
|
||||
ctx->registers[SP] += 4;
|
||||
break;
|
||||
}
|
||||
case OP_CALL:
|
||||
{
|
||||
ctx->registers[RA] = ctx->registers[PC] + 2; // set return address to next instruction after CALL
|
||||
const uint8_t reg = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg)
|
||||
ctx->registers[PC] = ctx->registers[reg] - 1;
|
||||
ctx->registers[reg] = pop(ctx);
|
||||
break;
|
||||
}
|
||||
case OP_RET:
|
||||
{
|
||||
ctx->registers[PC] = ctx->registers[RA] - 1;
|
||||
_CHECK_CAN_POP(11)
|
||||
// restore Tx registers from stack
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ctx->registers[T9 - i] = pop(ctx);
|
||||
}
|
||||
// restore previous RA
|
||||
ctx->registers[RA] = pop(ctx);
|
||||
break;
|
||||
}
|
||||
case OP_STORE:
|
||||
|
|
@ -197,29 +225,45 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
_CHECK_REGISTER_VALID(reg2)
|
||||
// address is located in reg1 reg
|
||||
uint32_t addr = ctx->registers[reg1];
|
||||
bool isRam = addr & 0x80000000;
|
||||
addr &= 0xFFFF; // mask the RAM/ROM bit, ensure 16-bit addressing
|
||||
bool isRam = addr & CHIP32_RAM_OFFSET;
|
||||
addr &= ~CHIP32_RAM_OFFSET; // mask the RAM/ROM bit, ensure 31-bit addressing
|
||||
if (isRam) {
|
||||
_CHECK_RAM_ADDR_VALID(addr)
|
||||
memcpy(&ctx->ram.mem[addr], &ctx->registers[reg2], size);
|
||||
} else {
|
||||
_CHECK_ROM_ADDR_VALID(addr)
|
||||
memcpy(&ctx->rom.mem[addr], &ctx->registers[reg2], size);
|
||||
// Invalid, cannot write in memory!
|
||||
return VM_ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OP_LOAD:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
const uint8_t reg2 = _NEXT_BYTE;
|
||||
const uint8_t size = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
_CHECK_REGISTER_VALID(reg2)
|
||||
uint8_t reg1 = _NEXT_BYTE;
|
||||
uint32_t addr = 0;
|
||||
uint8_t size = 0;
|
||||
|
||||
// Variable based, validity of reg will be verified after
|
||||
if (reg1 & 0x80)
|
||||
{
|
||||
reg1 &= 0x7F;
|
||||
_CHECK_REGISTER_VALID(reg1);
|
||||
addr = _NEXT_INT(ctx);
|
||||
size = _NEXT_BYTE;
|
||||
}
|
||||
// address is located in reg2 reg
|
||||
uint32_t addr = ctx->registers[reg2];
|
||||
bool isRam = addr & 0x80000000;
|
||||
addr &= 0xFFFF; // mask the RAM/ROM bit, ensure 16-bit addressing
|
||||
else
|
||||
{
|
||||
_CHECK_REGISTER_VALID(reg1);
|
||||
const uint8_t reg2 = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg2);
|
||||
size = _NEXT_BYTE;
|
||||
addr = ctx->registers[reg2];
|
||||
ctx->registers[PC] += 3; // skip unused 3 bytes
|
||||
}
|
||||
|
||||
bool isRam = addr & CHIP32_RAM_OFFSET;
|
||||
addr &= ~CHIP32_RAM_OFFSET; // mask the RAM/ROM bit, ensure 31-bit addressing
|
||||
if (isRam) {
|
||||
_CHECK_RAM_ADDR_VALID(addr)
|
||||
memcpy(&ctx->registers[reg1], &ctx->ram.mem[addr], size);
|
||||
|
|
@ -238,6 +282,14 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
ctx->registers[reg1] = ctx->registers[reg1] + ctx->registers[reg2];
|
||||
break;
|
||||
}
|
||||
case OP_ADDI:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
const uint8_t val = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
ctx->registers[reg1] = ctx->registers[reg1] + val;
|
||||
break;
|
||||
}
|
||||
case OP_SUB:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
|
|
@ -247,6 +299,14 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
ctx->registers[reg1] = ctx->registers[reg1] - ctx->registers[reg2];
|
||||
break;
|
||||
}
|
||||
case OP_SUBI:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
const uint8_t val = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
ctx->registers[reg1] = ctx->registers[reg1] - val;
|
||||
break;
|
||||
}
|
||||
case OP_MUL:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
|
|
@ -262,7 +322,14 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
const uint8_t reg2 = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
_CHECK_REGISTER_VALID(reg2)
|
||||
ctx->registers[reg1] = ctx->registers[reg1] / ctx->registers[reg2];
|
||||
if (ctx->registers[reg2] != 0)
|
||||
{
|
||||
ctx->registers[reg1] = ctx->registers[reg1] / ctx->registers[reg2];
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx->registers[reg1] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_SHL:
|
||||
|
|
@ -326,18 +393,48 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
ctx->registers[reg1] = ~ctx->registers[reg1];
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_CALL:
|
||||
case OP_JUMP:
|
||||
{
|
||||
ctx->registers[PC] = _NEXT_SHORT(ctx) - 1;
|
||||
break;
|
||||
}
|
||||
case OP_JUMPR:
|
||||
{
|
||||
const uint8_t reg = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg)
|
||||
ctx->registers[PC] = ctx->registers[reg] - 1;
|
||||
const uint8_t option = _NEXT_BYTE;
|
||||
uint32_t target_addr;
|
||||
|
||||
if (option == 0)
|
||||
{
|
||||
// Register-based: @R0
|
||||
const uint8_t reg = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg)
|
||||
target_addr = ctx->registers[reg];
|
||||
|
||||
// Skip 3 padding bytes
|
||||
ctx->registers[PC] += 3;
|
||||
}
|
||||
else // option == 1
|
||||
{
|
||||
// Address-based: .myLabel
|
||||
target_addr = _NEXT_INT(ctx);
|
||||
}
|
||||
|
||||
// If CALL, we have more work to do, saving state to prepare a ret
|
||||
if (instr == OP_CALL)
|
||||
{
|
||||
_CHECK_CAN_PUSH(1)
|
||||
push(ctx, ctx->registers[RA]); // save previous RA
|
||||
ctx->registers[RA] = ctx->registers[PC] + 1;
|
||||
// Save Tx registers on stack
|
||||
_CHECK_CAN_PUSH(10)
|
||||
for (int i = 0; i < 10; i++) {
|
||||
push(ctx, ctx->registers[T0 + i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform jump
|
||||
ctx->registers[PC] = target_addr - 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SKIPZ:
|
||||
case OP_SKIPNZ:
|
||||
{
|
||||
|
|
@ -353,10 +450,44 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_CMP_EQ:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
const uint8_t reg2 = _NEXT_BYTE;
|
||||
const uint8_t reg3 = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
_CHECK_REGISTER_VALID(reg2)
|
||||
_CHECK_REGISTER_VALID(reg3)
|
||||
ctx->registers[reg1] = ctx->registers[reg2] == ctx->registers[reg3] ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
case OP_CMP_GT:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
const uint8_t reg2 = _NEXT_BYTE;
|
||||
const uint8_t reg3 = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
_CHECK_REGISTER_VALID(reg2)
|
||||
_CHECK_REGISTER_VALID(reg3)
|
||||
ctx->registers[reg1] = ctx->registers[reg2] > ctx->registers[reg3] ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
case OP_CMP_LT:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
const uint8_t reg2 = _NEXT_BYTE;
|
||||
const uint8_t reg3 = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
_CHECK_REGISTER_VALID(reg2)
|
||||
_CHECK_REGISTER_VALID(reg3)
|
||||
ctx->registers[reg1] = ctx->registers[reg2] < ctx->registers[reg3] ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->registers[PC]++;
|
||||
ctx->instrCount++;
|
||||
ctx->instr_count++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -35,6 +35,8 @@ extern "C" {
|
|||
#include <stdbool.h>
|
||||
|
||||
|
||||
#define CHIP32_RAM_OFFSET 0x80000000
|
||||
|
||||
// General form: instruction destination, source
|
||||
// coded as: instr dst, src
|
||||
typedef enum
|
||||
|
|
@ -59,26 +61,32 @@ typedef enum
|
|||
|
||||
// arithmetic:
|
||||
OP_ADD = 9, ///< sum and store in first reg, e.g.: add r0, r2
|
||||
OP_SUB = 10, ///< subtract and store in first reg, e.g.: sub r0, r2
|
||||
OP_MUL = 11, ///< multiply and store in first reg, e.g.: mul r0, r2
|
||||
OP_DIV = 12, ///< divide and store in first reg, remain in second, e.g.: div r0, r2
|
||||
OP_ADDI = 10, ///< Add immediate value (8 bits max), e.g.: addi r4, 2 ; r4 = r4 + 2
|
||||
OP_SUB = 11, ///< subtract and store in first reg, e.g.: sub r0, r2
|
||||
OP_SUBI = 12, ///< substract immediate value (8 bits max), e.g.: subi r2, 8 ; r2 = r2 - 8
|
||||
OP_MUL = 13, ///< multiply and store in first reg, e.g.: mul r0, r2
|
||||
OP_DIV = 14, ///< divide and store in first reg, remain in second, e.g.: div r0, r2
|
||||
|
||||
OP_SHL = 13, ///< logical shift left, e.g.: shl r0, r1
|
||||
OP_SHR = 14, ///< logical shift right, e.g.: shr r0, r1
|
||||
OP_ISHR = 15, ///< arithmetic shift right (for signed values), e.g.: ishr r0, r1
|
||||
OP_SHL = 15, ///< logical shift left, e.g.: shl r0, r1
|
||||
OP_SHR = 16, ///< logical shift right, e.g.: shr r0, r1
|
||||
OP_ISHR = 17, ///< arithmetic shift right (for signed values), e.g.: ishr r0, r1
|
||||
|
||||
OP_AND = 16, ///< and two registers and store result in the first one, e.g.: and r0, r1
|
||||
OP_OR = 17, ///< or two registers and store result in the first one, e.g.: or r0, r1
|
||||
OP_XOR = 18, ///< xor two registers and store result in the first one, e.g.: xor r0, r1
|
||||
OP_NOT = 19, ///< not a register and store result, e.g.: not r0
|
||||
OP_AND = 18, ///< and two registers and store result in the first one, e.g.: and r0, r1
|
||||
OP_OR = 19, ///< or two registers and store result in the first one, e.g.: or r0, r1
|
||||
OP_XOR = 20, ///< xor two registers and store result in the first one, e.g.: xor r0, r1
|
||||
OP_NOT = 21, ///< not a register and store result, e.g.: not r0
|
||||
|
||||
// branching/functions
|
||||
OP_CALL = 20, ///< set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00
|
||||
OP_RET = 21, ///< return to the address of last callee (RA), e.g.: ret
|
||||
OP_JUMP = 22, ///< jump to address (can use label or address), e.g.: jump .my_label
|
||||
OP_JUMPR = 23, ///< jump to address contained in a register, e.g.: jumpr t9
|
||||
OP_SKIPZ = 24, ///< skip next instruction if zero, e.g.: skipz r0
|
||||
OP_SKIPNZ = 25, ///< skip next instruction if not zero, e.g.: skipnz r2
|
||||
OP_CALL = 22, ///< set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00
|
||||
OP_RET = 23, ///< return to the address of last callee (RA), e.g.: ret
|
||||
OP_JUMP = 24, ///< jump to address (can use label or address), e.g.: jump .my_label
|
||||
OP_SKIPZ = 25, ///< skip next instruction if zero, e.g.: skipz r0
|
||||
OP_SKIPNZ = 26, ///< skip next instruction if not zero, e.g.: skipnz r2
|
||||
|
||||
// Comparison
|
||||
OP_CMP_EQ = 27, ///< compare two registers for equality, result in first e.g.: eq r4, r0, r1 ; similar to: r4 = (r0 == r1 ? 1 : 0
|
||||
OP_CMP_GT = 28, ///< compare if first register is greater than the second, result in first e.g.: gt r4, r0, r1 ; similar to: r4 = (r0 > r1 ? 1 : 0
|
||||
OP_CMP_LT = 29, ///< compare if first register is less than the second, result in first e.g.: lt r4, r0, r1 ; similar to: r4 = (r0 < r1 ? 1 : 0
|
||||
|
||||
INSTRUCTION_COUNT
|
||||
} chip32_instruction_t;
|
||||
|
|
@ -86,10 +94,10 @@ typedef enum
|
|||
|
||||
/*
|
||||
|
||||
| name | number | type | preserved |
|
||||
| name | number | type | preserved on function call |
|
||||
|-------|--------|----------------------------------|-----------|
|
||||
| r0-r9 | 0-9 | general-purpose | Y |
|
||||
| t0-t9 | 10-19 | temporary registers | N |
|
||||
| r0-r9 | 0-9 | general-purpose | N |
|
||||
| t0-t9 | 10-19 | temporary registers | Y |
|
||||
| pc | 20 | program counter | Y |
|
||||
| sp | 21 | stack pointer | Y |
|
||||
| ra | 22 | return address | N |
|
||||
|
|
@ -122,7 +130,7 @@ typedef enum
|
|||
// special
|
||||
PC,
|
||||
SP,
|
||||
RA,
|
||||
RA,
|
||||
// count
|
||||
REGISTER_COUNT
|
||||
} chip32_register_t;
|
||||
|
|
@ -152,10 +160,12 @@ typedef struct {
|
|||
|
||||
#define OPCODES_LIST { { OP_NOP, 0, 0 }, { OP_HALT, 0, 0 }, { OP_SYSCALL, 1, 1 }, { OP_LCONS, 2, 5 }, \
|
||||
{ OP_MOV, 2, 2 }, { OP_PUSH, 1, 1 }, {OP_POP, 1, 1 }, \
|
||||
{ OP_STORE, 3, 4 }, { OP_LOAD, 3, 4 }, { OP_ADD, 2, 2 }, { OP_SUB, 2, 2 }, { OP_MUL, 2, 2 }, \
|
||||
{ OP_STORE, 3, 3 }, { OP_LOAD, 3, 6 }, \
|
||||
{ OP_ADD, 2, 2 }, { OP_ADDI, 2, 2 }, { OP_SUB, 2, 2 }, { OP_SUBI, 2, 2 }, { OP_MUL, 2, 2 }, \
|
||||
{ OP_DIV, 2, 2 }, { OP_SHL, 2, 2 }, { OP_SHR, 2, 2 }, { OP_ISHR, 2, 2 }, { OP_AND, 2, 2 }, \
|
||||
{ OP_OR, 2, 2 }, { OP_XOR, 2, 2 }, { OP_NOT, 1, 1 }, { OP_CALL, 1, 1 }, { OP_RET, 0, 0 }, \
|
||||
{ OP_JUMP, 1, 2 }, { OP_JUMPR, 1, 1 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 } }
|
||||
{ OP_OR, 2, 2 }, { OP_XOR, 2, 2 }, { OP_NOT, 1, 1 }, { OP_CALL, 1, 5 }, { OP_RET, 0, 0 }, \
|
||||
{ OP_JUMP, 1, 5 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 }, \
|
||||
{ OP_CMP_EQ, 3, 3 }, { OP_CMP_GT, 3, 3 }, { OP_CMP_LT, 3, 3 } }
|
||||
|
||||
/**
|
||||
Whole memory is 64KB
|
||||
|
|
@ -192,18 +202,19 @@ typedef uint8_t (*syscall_t)(chip32_ctx_t *, uint8_t);
|
|||
|
||||
#define SYSCALL_RET_OK 0 ///< Default state, continue execution immediately
|
||||
#define SYSCALL_RET_WAIT_EV 1 ///< Sets the VM in wait for event state
|
||||
|
||||
#define SYSCALL_RET_ERROR 2
|
||||
|
||||
struct chip32_ctx_t
|
||||
{
|
||||
virtual_mem_t rom;
|
||||
virtual_mem_t ram;
|
||||
uint16_t stack_size;
|
||||
uint32_t instrCount;
|
||||
uint32_t instr_count;
|
||||
uint16_t prog_size;
|
||||
uint32_t max_instr;
|
||||
uint32_t registers[REGISTER_COUNT];
|
||||
syscall_t syscall;
|
||||
void* user_data;
|
||||
|
||||
};
|
||||
|
||||
16
core/story-manager/interfaces/i_audio_event.h
Normal file
16
core/story-manager/interfaces/i_audio_event.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef I_AUDIO_EVENT_H
|
||||
#define I_AUDIO_EVENT_H
|
||||
|
||||
// Interface pour gérer les événements audio, comme la fin d'une piste.
|
||||
// Ceci permet de découpler le lecteur audio de l'entité qui doit réagir à ces événements (par exemple, la VM).
|
||||
|
||||
class IAudioEvent
|
||||
{
|
||||
public:
|
||||
virtual ~IAudioEvent() = default;
|
||||
|
||||
// Appelé lorsque la lecture d'une piste audio est terminée.
|
||||
virtual void EndOfAudio() = 0;
|
||||
};
|
||||
|
||||
#endif // I_AUDIO_EVENT_H
|
||||
17
core/story-manager/interfaces/i_logger.h
Normal file
17
core/story-manager/interfaces/i_logger.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
class ILogSubject {
|
||||
public:
|
||||
virtual void LogEvent(const std::string &txt, bool critical = false) = 0;
|
||||
};
|
||||
|
||||
class ILogger
|
||||
{
|
||||
|
||||
public:
|
||||
virtual void Log(const std::string &txt, bool critical = false) = 0;
|
||||
virtual void RegisterSubject(std::shared_ptr<ILogSubject> subject) = 0;
|
||||
virtual ~ILogger() {}
|
||||
};
|
||||
14
core/story-manager/interfaces/i_script_node.h
Normal file
14
core/story-manager/interfaces/i_script_node.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "i_story_manager.h"
|
||||
#include "base_node.h"
|
||||
|
||||
class IScriptNode
|
||||
{
|
||||
public:
|
||||
virtual ~IScriptNode() {}
|
||||
|
||||
virtual std::string Build(IStoryManager &story, int nb_out_conns, BaseNode &base) = 0;
|
||||
virtual std::string GenerateConstants(IStoryManager &story, int nb_out_conns, BaseNode &base) = 0;
|
||||
};
|
||||
29
core/story-manager/interfaces/i_story_db.h
Normal file
29
core/story-manager/interfaces/i_story_db.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ranges>
|
||||
|
||||
class IStoryDb
|
||||
{
|
||||
|
||||
public:
|
||||
struct Info {
|
||||
int age;
|
||||
std::string title;
|
||||
std::string description;
|
||||
std::string download;
|
||||
std::string image_url;
|
||||
std::string sound;
|
||||
std::string uuid;
|
||||
std::string url;
|
||||
};
|
||||
|
||||
virtual ~IStoryDb() {}
|
||||
|
||||
using ViewType = std::ranges::ref_view<std::vector<IStoryDb::Info>>;
|
||||
|
||||
|
||||
virtual bool FindStory(const std::string &uuid, Info &info) = 0;
|
||||
virtual void AddStory(IStoryDb::Info &info, int origin) = 0;
|
||||
};
|
||||
69
core/story-manager/interfaces/i_story_manager.h
Normal file
69
core/story-manager/interfaces/i_story_manager.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef I_STORY_MANAGER_H
|
||||
#define I_STORY_MANAGER_H
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
#include <functional>
|
||||
|
||||
#include "resource.h"
|
||||
#include "connection.h"
|
||||
#include "base_node.h"
|
||||
#include "variable.h"
|
||||
|
||||
template <typename T>
|
||||
struct Callback;
|
||||
|
||||
template <typename Ret, typename... Params>
|
||||
struct Callback<Ret(Params...)> {
|
||||
template <typename... Args>
|
||||
static Ret callback(Args... args) {
|
||||
return func(args...);
|
||||
}
|
||||
static std::function<Ret(Params...)> func;
|
||||
};
|
||||
|
||||
template <typename Ret, typename... Params>
|
||||
std::function<Ret(Params...)> Callback<Ret(Params...)>::func;
|
||||
|
||||
class NodesFactory;
|
||||
|
||||
class IStoryManager
|
||||
{
|
||||
public:
|
||||
virtual ~IStoryManager() {}
|
||||
|
||||
virtual void OpenProject(const std::string &uuid) = 0;
|
||||
virtual void SaveProject() = 0;
|
||||
virtual void SaveModule() = 0;
|
||||
virtual void ImportProject(const std::string &fileName, int format) = 0;
|
||||
virtual void Log(const std::string &txt, bool critical = false) = 0;
|
||||
virtual void PlaySoundFile(const std::string &fileName) = 0;
|
||||
virtual std::string BuildFullAssetsPath(const std::string_view fileName) const = 0;
|
||||
virtual void OpenFunction(const std::string &uuid, const std::string &name) = 0;
|
||||
|
||||
virtual std::shared_ptr<IStoryProject> GetCurrentProject() = 0;
|
||||
virtual std::shared_ptr<IStoryProject> GetCurrentModule() = 0;
|
||||
|
||||
// Node interaction
|
||||
virtual void BuildCode(bool compileonly) = 0;
|
||||
virtual void SetExternalSourceFile(const std::string &filename) = 0;
|
||||
virtual void LoadBinaryStory(const std::string &filename) = 0;
|
||||
virtual void ToggleBreakpoint(int line) = 0;
|
||||
virtual uint32_t GetRegister(int reg) = 0;
|
||||
virtual NodesFactory& GetNodesFactory() = 0;
|
||||
|
||||
virtual void Play() = 0;
|
||||
virtual void Step() = 0;
|
||||
virtual void Run() = 0;
|
||||
virtual void Ok() = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual void Pause() = 0;
|
||||
virtual void Home() = 0;
|
||||
virtual void Next() = 0;
|
||||
virtual void Previous() = 0;
|
||||
virtual std::string VmState() const = 0;
|
||||
};
|
||||
|
||||
#endif // I_STORY_MANAGER_H
|
||||
12
core/story-manager/interfaces/i_story_page.h
Normal file
12
core/story-manager/interfaces/i_story_page.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include "connection.h"
|
||||
|
||||
class IStoryPage
|
||||
{
|
||||
public:
|
||||
virtual ~IStoryPage() {}
|
||||
|
||||
virtual void GetNodeConnections(std::list<std::shared_ptr<Connection>> &c, const std::string &nodeId) = 0;
|
||||
};
|
||||
54
core/story-manager/interfaces/i_story_project.h
Normal file
54
core/story-manager/interfaces/i_story_project.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include "connection.h"
|
||||
#include "story_options.h"
|
||||
#include "variable.h"
|
||||
|
||||
class IStoryProject
|
||||
{
|
||||
public:
|
||||
|
||||
enum Type
|
||||
{
|
||||
PROJECT_TYPE_STORY = 0,
|
||||
PROJECT_TYPE_MODULE = 1,
|
||||
PROJECT_TYPE_PRIMITIVE = 2
|
||||
};
|
||||
|
||||
struct FunctionInfo {
|
||||
std::string uuid;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
virtual ~IStoryProject() {};
|
||||
|
||||
virtual std::string GetName() const = 0;
|
||||
virtual std::string GetDescription() const = 0;
|
||||
virtual Type GetProjectType() const = 0;
|
||||
virtual bool IsModule() const = 0;
|
||||
virtual std::string GetUuid() const = 0;
|
||||
virtual std::string GetTitleImage() const = 0;
|
||||
virtual std::string GetTitleSound() const = 0;
|
||||
virtual std::vector<FunctionInfo> GetFunctionsList() const = 0;
|
||||
|
||||
virtual void SetTitleImage(const std::string &titleImage) = 0;
|
||||
virtual void SetTitleSound(const std::string &titleSound) = 0;
|
||||
virtual void SetDescription(const std::string &description) = 0;
|
||||
virtual void SetProjectType(Type type) = 0;
|
||||
virtual void SetImageFormat(Resource::ImageFormat format) = 0;
|
||||
virtual void SetSoundFormat(Resource::SoundFormat format) = 0;
|
||||
virtual void SetDisplayFormat(int w, int h) = 0;
|
||||
virtual void SetName(const std::string &name) = 0;
|
||||
virtual void SetUuid(const std::string &uuid) = 0;
|
||||
|
||||
// Callback can return false to break the loop
|
||||
virtual void ScanVariable(const std::function<bool(std::shared_ptr<Variable> element)>& operation) = 0;
|
||||
virtual void AddVariable() = 0;
|
||||
virtual void DeleteVariable(int i) = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
163
core/story-manager/src/compiler/assembly_generator.h
Normal file
163
core/story-manager/src/compiler/assembly_generator.h
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
|
||||
#include "ast_builder.h"
|
||||
#include "print_node.h"
|
||||
#include "function_entry_node.h"
|
||||
#include "variable_node.h"
|
||||
#include "branch_node.h"
|
||||
#include "operator_node.h"
|
||||
#include "base_node.h"
|
||||
|
||||
|
||||
class AssemblyGenerator : public IVariableVisitor {
|
||||
public:
|
||||
enum class Section {
|
||||
NONE,
|
||||
DATA,
|
||||
TEXT
|
||||
};
|
||||
|
||||
struct GeneratorContext
|
||||
{
|
||||
std::string timestamp;
|
||||
std::string compiler;
|
||||
bool debugOutput;
|
||||
bool optimizeCode;
|
||||
int stackSize;
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
GeneratorContext(std::vector<std::shared_ptr<Variable>> &v,
|
||||
const std::string& ts = "2025-04-08 12:09:01",
|
||||
const std::string& comp = "ost-v1",
|
||||
bool debug = true,
|
||||
bool optimize = true,
|
||||
int stack = 1024
|
||||
)
|
||||
: variables(v)
|
||||
, compiler(comp)
|
||||
, debugOutput(debug)
|
||||
, optimizeCode(optimize)
|
||||
, stackSize(stack)
|
||||
, timestamp(ts)
|
||||
{}
|
||||
|
||||
// Find variable by uuid
|
||||
std::shared_ptr<Variable> FindVariableByUuid(const std::string& uuid) const {
|
||||
for (const auto& var : variables) {
|
||||
if (var->GetUuid() == uuid) {
|
||||
return var;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
AssemblyGenerator(const GeneratorContext& context)
|
||||
: m_context(context)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
m_assembly.str("");
|
||||
m_labelCounter = 0;
|
||||
m_currentStackOffset = 0;
|
||||
m_depth = 0;
|
||||
m_currentSection = Section::NONE;
|
||||
}
|
||||
|
||||
void GenerateHeader() {
|
||||
AddComment("Assembly generated by Visual Node Editor");
|
||||
AddComment("Generation time: " + m_context.timestamp);
|
||||
AddComment("Generated by: " + m_context.compiler);
|
||||
AddComment("Optimization: " + m_context.optimizeCode ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
virtual void GenerateExit() = 0;
|
||||
|
||||
void StartSection(Section section) {
|
||||
if (m_currentSection == section) return;
|
||||
|
||||
m_currentSection = section;
|
||||
switch (section) {
|
||||
case Section::DATA:
|
||||
AddComment("======================= DATA =======================");
|
||||
break;
|
||||
case Section::TEXT:
|
||||
AddComment("======================= CODE =======================");
|
||||
GenerateMain();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateNodesVariables(const std::vector<std::shared_ptr<BaseNode>> &nodes)
|
||||
{
|
||||
// Generate all constants
|
||||
for (const auto& n : nodes) {
|
||||
n->Accept(*this);
|
||||
}
|
||||
|
||||
m_assembly << "\n\n";
|
||||
}
|
||||
|
||||
void GenerateGlobalVariables()
|
||||
{
|
||||
for (const auto& v : m_context.variables) {
|
||||
GenerateVariable(v);
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateTextSection(const std::vector<std::shared_ptr<ASTNode>>& orderedNodes)
|
||||
{
|
||||
for (const auto& node : orderedNodes)
|
||||
{
|
||||
GenerateNodeCode(node);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetAssembly() const {
|
||||
return m_assembly.str();
|
||||
}
|
||||
|
||||
GeneratorContext& Context() {
|
||||
return m_context;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
virtual void GenerateNodeCode(std::shared_ptr<ASTNode> node, bool isDataPath = false) = 0;
|
||||
|
||||
virtual void AddComment(const std::string& comment) = 0;
|
||||
|
||||
virtual void GenerateMain() = 0;
|
||||
|
||||
virtual void GenerateVariable(const std::shared_ptr<Variable> v) = 0;
|
||||
|
||||
|
||||
std::string GenerateUniqueLabel(const std::string& prefix)
|
||||
{
|
||||
return prefix + "_" + std::to_string(m_labelCounter++);
|
||||
}
|
||||
|
||||
GeneratorContext m_context;
|
||||
std::stringstream m_assembly;
|
||||
int m_labelCounter;
|
||||
int m_currentStackOffset;
|
||||
int m_depth{0};
|
||||
Section m_currentSection;
|
||||
|
||||
private:
|
||||
|
||||
|
||||
};
|
||||
813
core/story-manager/src/compiler/assembly_generator_chip32_tac.h
Normal file
813
core/story-manager/src/compiler/assembly_generator_chip32_tac.h
Normal file
|
|
@ -0,0 +1,813 @@
|
|||
// ===================================================================
|
||||
// assembly_generator_chip32_tac.h
|
||||
// IMPLÉMENTATION COMPLÈTE avec tous les types gérés
|
||||
// ===================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "assembly_generator.h"
|
||||
#include "tac.h"
|
||||
#include "print_node.h"
|
||||
#include "operator_node.h"
|
||||
#include "variable_node.h"
|
||||
#include "branch_node.h"
|
||||
#include "function_entry_node.h"
|
||||
#include "call_function_node.h"
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
class AssemblyGeneratorChip32TAC : public AssemblyGenerator
|
||||
{
|
||||
public:
|
||||
AssemblyGeneratorChip32TAC(const GeneratorContext& context)
|
||||
: AssemblyGenerator(context)
|
||||
, m_currentContext(FunctionContext::MAIN_PROGRAM)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~AssemblyGeneratorChip32TAC() = default;
|
||||
|
||||
// ===================================================================
|
||||
// WORKFLOW COMPLET : Cette méthode orchestre tout
|
||||
// ===================================================================
|
||||
void GenerateCompleteProgram(const std::vector<std::shared_ptr<BaseNode>>& nodes,
|
||||
const std::vector<std::shared_ptr<ASTNode>>& astNodes)
|
||||
{
|
||||
Reset();
|
||||
|
||||
// === ÉTAPE 1 : HEADER ===
|
||||
GenerateHeader();
|
||||
|
||||
// === ÉTAPE 2 : SECTION DATA ===
|
||||
StartSection(Section::DATA);
|
||||
|
||||
// Générer les variables globales
|
||||
GenerateGlobalVariables();
|
||||
|
||||
// Générer les variables des nœuds (format strings, etc.)
|
||||
GenerateNodesVariables(nodes);
|
||||
|
||||
// === ÉTAPE 3 : GÉNÉRATION TAC ===
|
||||
m_tacGenerator = std::make_unique<TACGenerator>();
|
||||
m_tacProgram = m_tacGenerator->Generate(astNodes, Context().variables);
|
||||
|
||||
// DEBUG : Afficher le TAC généré
|
||||
if (m_context.debugOutput) {
|
||||
std::cout << "\n" << m_tacProgram.ToString() << std::endl;
|
||||
}
|
||||
|
||||
// Générer les temporaires TAC qui seront en RAM
|
||||
GenerateTACTemporaries();
|
||||
|
||||
m_assembly << "\n";
|
||||
|
||||
// === ÉTAPE 4 : SECTION TEXT (à partir du TAC) ===
|
||||
StartSection(Section::TEXT);
|
||||
GenerateFromTAC();
|
||||
|
||||
// === ÉTAPE 5 : EXIT ===
|
||||
GenerateExit();
|
||||
}
|
||||
|
||||
|
||||
virtual void GenerateMain() override {
|
||||
m_assembly << ".main:\n";
|
||||
}
|
||||
|
||||
virtual void AddComment(const std::string& comment) override {
|
||||
m_assembly << std::string(m_depth * 4, ' ') << "; " << comment << "\n";
|
||||
}
|
||||
|
||||
virtual void GenerateExit() override {
|
||||
AddComment("Program exit");
|
||||
m_assembly << " halt\n";
|
||||
}
|
||||
|
||||
// Note: Cette méthode ne sera PAS utilisée car on génère depuis le TAC
|
||||
virtual void GenerateNodeCode(std::shared_ptr<ASTNode> node, bool isDataPath = false) override {
|
||||
// Cette méthode n'est plus utilisée avec l'approche TAC
|
||||
// Tout est géré via GenerateFromTAC()
|
||||
|
||||
// On peut laisser une implémentation vide ou lancer une exception
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("WARNING: GenerateNodeCode called but not used in TAC mode");
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateTACToAssembly(const TACProgram& tac) {
|
||||
m_tacProgram = tac;
|
||||
|
||||
// Générer les temporaires si nécessaire
|
||||
GenerateTACTemporaries();
|
||||
|
||||
// Convertir le TAC en assembleur
|
||||
for (const auto& instr : m_tacProgram.GetInstructions()) {
|
||||
GenerateTACInstruction(instr);
|
||||
}
|
||||
}
|
||||
|
||||
// Exposer l'assembly stream pour écrire directement
|
||||
std::stringstream& GetAssembly() { return m_assembly; }
|
||||
|
||||
private:
|
||||
enum class FunctionContext {
|
||||
MAIN_PROGRAM,
|
||||
SUB_FUNCTION
|
||||
};
|
||||
|
||||
FunctionContext m_currentContext;
|
||||
std::unique_ptr<TACGenerator> m_tacGenerator;
|
||||
TACProgram m_tacProgram;
|
||||
|
||||
// Map des temporaires TAC vers leur localisation
|
||||
struct TempLocation {
|
||||
enum Type { REGISTER, MEMORY };
|
||||
Type type;
|
||||
std::string location; // "t0" ou "$temp_label"
|
||||
};
|
||||
std::map<std::string, TempLocation> m_tempLocations;
|
||||
int m_nextTempReg = 0; // Prochain registre t0-t9 disponible
|
||||
int m_tempVarCounter = 0; // Compteur pour variables temporaires en RAM
|
||||
|
||||
// Function call management
|
||||
struct FunctionCallContext {
|
||||
int paramCount;
|
||||
std::string functionLabel;
|
||||
std::map<std::string, int> paramRegMap;
|
||||
};
|
||||
|
||||
std::shared_ptr<FunctionCallContext> m_currentFunctionCall;
|
||||
|
||||
// Flag pour distinguer PARAM pour syscall vs function call
|
||||
bool m_preparingFunctionCall = false;
|
||||
int m_functionCallParamIndex = 0;
|
||||
|
||||
// ===================================================================
|
||||
// GÉNÉRATION DE LA SECTION DATA
|
||||
// ===================================================================
|
||||
|
||||
void GenerateTACTemporaries() {
|
||||
// Analyser le TAC pour déterminer quels temporaires ont besoin de RAM
|
||||
std::set<std::string> allTemps;
|
||||
|
||||
for (const auto& instr : m_tacProgram.GetInstructions()) {
|
||||
if (instr->GetDest() &&
|
||||
instr->GetDest()->GetType() == TACOperand::Type::TEMPORARY) {
|
||||
allTemps.insert(instr->GetDest()->GetValue());
|
||||
}
|
||||
if (instr->GetOp1() &&
|
||||
instr->GetOp1()->GetType() == TACOperand::Type::TEMPORARY) {
|
||||
allTemps.insert(instr->GetOp1()->GetValue());
|
||||
}
|
||||
if (instr->GetOp2() &&
|
||||
instr->GetOp2()->GetType() == TACOperand::Type::TEMPORARY) {
|
||||
allTemps.insert(instr->GetOp2()->GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_context.debugOutput && !allTemps.empty()) {
|
||||
AddComment("TAC Temporaries: " + std::to_string(allTemps.size()) + " total");
|
||||
}
|
||||
|
||||
// Allouer les 10 premiers dans des registres, le reste en RAM
|
||||
int tempIndex = 0;
|
||||
for (const auto& temp : allTemps) {
|
||||
if (tempIndex < 10) {
|
||||
// Utiliser un registre t0-t9
|
||||
m_tempLocations[temp] = {
|
||||
TempLocation::REGISTER,
|
||||
"t" + std::to_string(tempIndex)
|
||||
};
|
||||
|
||||
if (m_context.debugOutput) {
|
||||
AddComment(" " + temp + " -> register t" + std::to_string(tempIndex));
|
||||
}
|
||||
} else {
|
||||
// Créer une variable en RAM
|
||||
std::string varLabel = "temp_" + std::to_string(m_tempVarCounter++);
|
||||
m_assembly << "$" << varLabel << " DV32, 0 ; TAC temporary "
|
||||
<< temp << "\n";
|
||||
m_tempLocations[temp] = {
|
||||
TempLocation::MEMORY,
|
||||
varLabel
|
||||
};
|
||||
|
||||
if (m_context.debugOutput) {
|
||||
AddComment(" " + temp + " -> memory $" + varLabel);
|
||||
}
|
||||
}
|
||||
tempIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// GÉNÉRATION DE LA SECTION TEXT À PARTIR DU TAC
|
||||
// ===================================================================
|
||||
|
||||
void GenerateFromTAC() {
|
||||
for (const auto& instr : m_tacProgram.GetInstructions()) {
|
||||
GenerateTACInstruction(instr);
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateTACInstruction(std::shared_ptr<TACInstruction> instr) {
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("TAC: " + instr->ToString());
|
||||
}
|
||||
|
||||
switch (instr->GetOpCode()) {
|
||||
case TACInstruction::OpCode::ADD:
|
||||
case TACInstruction::OpCode::SUB:
|
||||
case TACInstruction::OpCode::MUL:
|
||||
case TACInstruction::OpCode::DIV:
|
||||
GenerateBinaryOp(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::EQ:
|
||||
case TACInstruction::OpCode::NE:
|
||||
case TACInstruction::OpCode::GT:
|
||||
case TACInstruction::OpCode::LT:
|
||||
case TACInstruction::OpCode::GE:
|
||||
case TACInstruction::OpCode::LE:
|
||||
GenerateComparison(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::COPY:
|
||||
GenerateCopy(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::LOAD:
|
||||
GenerateLoad(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::STORE:
|
||||
GenerateStore(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::LABEL:
|
||||
m_assembly << instr->GetDest()->ToString() << ":\n";
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::GOTO:
|
||||
m_assembly << " jump " << instr->GetDest()->ToString() << "\n";
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::IF_FALSE:
|
||||
GenerateIfFalse(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::IF_TRUE:
|
||||
GenerateIfTrue(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::PARAM:
|
||||
GenerateParam(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::PRINT:
|
||||
GeneratePrint(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::CALL:
|
||||
GenerateCall(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::RETURN:
|
||||
// Utiliser FUNC_RETURN pour les fonctions avec FUNC_BEGIN
|
||||
if (m_currentFunctionCall) {
|
||||
GenerateFuncReturn(instr);
|
||||
} else {
|
||||
// RETURN normal (existant) - peut-être déjà géré ailleurs
|
||||
m_assembly << " ret\n";
|
||||
}
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::NOP:
|
||||
m_assembly << " nop\n";
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::FUNC_BEGIN:
|
||||
GenerateFuncBegin(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::FUNC_END:
|
||||
GenerateFuncEnd(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::FUNC_PARAM_IN:
|
||||
GenerateFuncParamIn(instr);
|
||||
break;
|
||||
|
||||
case TACInstruction::OpCode::RETURN_VAL:
|
||||
GenerateReturnVal(instr);
|
||||
break;
|
||||
|
||||
default:
|
||||
AddComment("WARNING: Unsupported TAC instruction");
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// HELPERS : Charger une opérande dans un registre
|
||||
// ===================================================================
|
||||
|
||||
std::string LoadOperand(std::shared_ptr<TACOperand> op, const std::string& targetReg) {
|
||||
if (!op) return targetReg;
|
||||
|
||||
switch (op->GetType()) {
|
||||
case TACOperand::Type::CONSTANT:
|
||||
m_assembly << " lcons " << targetReg << ", " << op->GetValue() << "\n";
|
||||
return targetReg;
|
||||
|
||||
case TACOperand::Type::VARIABLE: {
|
||||
std::shared_ptr<Variable> var = nullptr;
|
||||
|
||||
// Chercher la variable par son label
|
||||
for (const auto& v : m_context.variables) {
|
||||
if (v->GetLabel() == op->GetValue()) {
|
||||
var = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (var && var->GetValueType() == Variable::ValueType::STRING) {
|
||||
// Pour les strings, charger l'ADRESSE
|
||||
m_assembly << " lcons " << targetReg << ", $" << op->GetValue() << "\n";
|
||||
} else {
|
||||
// Pour les autres types, charger la VALEUR
|
||||
m_assembly << " load " << targetReg << ", $" << op->GetValue() << ", 4\n";
|
||||
}
|
||||
return targetReg;
|
||||
|
||||
}
|
||||
|
||||
case TACOperand::Type::TEMPORARY: {
|
||||
auto it = m_tempLocations.find(op->GetValue());
|
||||
if (it == m_tempLocations.end()) {
|
||||
throw std::runtime_error("Temporary not found: " + op->GetValue());
|
||||
}
|
||||
|
||||
if (it->second.type == TempLocation::REGISTER) {
|
||||
// Déjà dans un registre, copier si nécessaire
|
||||
if (it->second.location != targetReg) {
|
||||
m_assembly << " mov " << targetReg << ", "
|
||||
<< it->second.location << "\n";
|
||||
}
|
||||
return targetReg;
|
||||
} else {
|
||||
// Charger depuis la RAM
|
||||
m_assembly << " load " << targetReg << ", $"
|
||||
<< it->second.location << ", 4\n";
|
||||
return targetReg;
|
||||
}
|
||||
}
|
||||
|
||||
case TACOperand::Type::REGISTER:
|
||||
if (op->GetValue() != targetReg) {
|
||||
m_assembly << " mov " << targetReg << ", "
|
||||
<< op->GetValue() << "\n";
|
||||
}
|
||||
return targetReg;
|
||||
|
||||
case TACOperand::Type::LABEL:
|
||||
// Pour les labels, on utilise lcons avec l'adresse
|
||||
m_assembly << " lcons " << targetReg << ", "
|
||||
<< op->ToString() << "\n";
|
||||
return targetReg;
|
||||
|
||||
default:
|
||||
return targetReg;
|
||||
}
|
||||
}
|
||||
|
||||
void StoreResult(std::shared_ptr<TACOperand> dest, const std::string& sourceReg) {
|
||||
if (!dest) return;
|
||||
|
||||
if (dest->GetType() == TACOperand::Type::TEMPORARY) {
|
||||
auto it = m_tempLocations.find(dest->GetValue());
|
||||
if (it == m_tempLocations.end()) {
|
||||
throw std::runtime_error("Temporary not found: " + dest->GetValue());
|
||||
}
|
||||
|
||||
if (it->second.type == TempLocation::REGISTER) {
|
||||
// Stocker dans un registre
|
||||
if (it->second.location != sourceReg) {
|
||||
m_assembly << " mov " << it->second.location << ", "
|
||||
<< sourceReg << "\n";
|
||||
}
|
||||
} else {
|
||||
// Stocker en RAM
|
||||
m_assembly << " store $" << it->second.location << ", "
|
||||
<< sourceReg << ", 4\n";
|
||||
}
|
||||
} else if (dest->GetType() == TACOperand::Type::VARIABLE) {
|
||||
m_assembly << " store $" << dest->GetValue() << ", "
|
||||
<< sourceReg << ", 4\n";
|
||||
} else if (dest->GetType() == TACOperand::Type::REGISTER) {
|
||||
if (dest->GetValue() != sourceReg) {
|
||||
m_assembly << " mov " << dest->GetValue() << ", "
|
||||
<< sourceReg << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// GÉNÉRATION DES INSTRUCTIONS
|
||||
// ===================================================================
|
||||
|
||||
// Génération pour FUNC_BEGIN
|
||||
void GenerateFuncBegin(std::shared_ptr<TACInstruction> instr) {
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("Function Begin: " + instr->GetDest()->ToString());
|
||||
}
|
||||
|
||||
std::string functionLabel = instr->GetDest()->GetValue();
|
||||
|
||||
// Générer le label de la fonction
|
||||
m_assembly << "\n" << functionLabel << ":\n";
|
||||
|
||||
// Initialiser le contexte d'appel
|
||||
m_currentFunctionCall = std::make_shared<FunctionCallContext>();
|
||||
m_currentFunctionCall->functionLabel = functionLabel;
|
||||
m_currentFunctionCall->paramCount = 0;
|
||||
|
||||
AddComment("Parameters are passed in r0-r9");
|
||||
}
|
||||
|
||||
// Génération pour FUNC_PARAM_IN
|
||||
void GenerateFuncParamIn(std::shared_ptr<TACInstruction> instr) {
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("Read parameter: " + instr->GetOp1()->ToString());
|
||||
}
|
||||
|
||||
std::string paramName = instr->GetOp1()->GetValue();
|
||||
|
||||
// Le paramètre est dans r0, r1, r2, etc. selon l'ordre
|
||||
int paramReg = m_currentFunctionCall->paramCount;
|
||||
if (paramReg >= 10) {
|
||||
throw std::runtime_error("Too many parameters (max 10 with r0-r9)");
|
||||
}
|
||||
|
||||
m_currentFunctionCall->paramRegMap[paramName] = paramReg;
|
||||
m_currentFunctionCall->paramCount++;
|
||||
|
||||
// Le paramètre est déjà dans rX, le copier dans le temporaire destination
|
||||
std::string sourceReg = "r" + std::to_string(paramReg);
|
||||
StoreResult(instr->GetDest(), sourceReg);
|
||||
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("Parameter " + paramName + " from " + sourceReg);
|
||||
}
|
||||
}
|
||||
|
||||
// Génération pour FUNC_RETURN
|
||||
void GenerateFuncReturn(std::shared_ptr<TACInstruction> instr) {
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("Function return");
|
||||
}
|
||||
|
||||
// Nettoyer le contexte
|
||||
m_currentFunctionCall.reset();
|
||||
|
||||
// Retourner (le résultat est déjà dans r0 via RETURN_VAL)
|
||||
m_assembly << " ret\n";
|
||||
}
|
||||
|
||||
// Génération pour FUNC_END
|
||||
void GenerateFuncEnd(std::shared_ptr<TACInstruction> instr) {
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("Function End");
|
||||
}
|
||||
// Rien à faire, juste marquer la fin
|
||||
}
|
||||
|
||||
// Génération pour RETURN_VAL
|
||||
void GenerateReturnVal(std::shared_ptr<TACInstruction> instr) {
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("Set return value: " + instr->GetDest()->ToString());
|
||||
}
|
||||
|
||||
std::string returnName = instr->GetDest()->GetValue();
|
||||
|
||||
// Charger la valeur à retourner dans r0 (convention de retour)
|
||||
std::string valueReg = LoadOperand(instr->GetOp1(), "r0");
|
||||
|
||||
// Si la valeur est déjà dans r0, rien à faire
|
||||
if (valueReg != "r0") {
|
||||
m_assembly << " mov r0, " << valueReg << "\n";
|
||||
}
|
||||
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("Return value " + returnName + " in r0");
|
||||
}
|
||||
}
|
||||
|
||||
// Génération pour CALL (peut déjà exister - vérifier avant d'ajouter)
|
||||
void GenerateCall(std::shared_ptr<TACInstruction> instr) {
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("Call function: " + instr->GetOp1()->ToString());
|
||||
}
|
||||
|
||||
std::string functionLabel = instr->GetOp1()->GetValue();
|
||||
|
||||
// Réinitialiser le compteur de paramètres après l'appel
|
||||
m_functionCallParamIndex = 0;
|
||||
m_preparingFunctionCall = false;
|
||||
|
||||
// Effectuer l'appel
|
||||
m_assembly << " call " << functionLabel << "\n";
|
||||
|
||||
// Le résultat est dans r0 (convention)
|
||||
// Stocker dans le temporaire de destination
|
||||
if (instr->GetDest()) {
|
||||
StoreResult(instr->GetDest(), "r0");
|
||||
}
|
||||
}
|
||||
|
||||
// Génération pour PARAM - DISTINGUER syscall vs function call
|
||||
void GenerateParam(std::shared_ptr<TACInstruction> instr) {
|
||||
if (m_context.debugOutput) {
|
||||
AddComment("Parameter: " + instr->GetDest()->ToString());
|
||||
}
|
||||
|
||||
// Déterminer si c'est pour un syscall ou un appel de fonction
|
||||
// en regardant si on est dans un contexte d'appel de fonction
|
||||
if (m_preparingFunctionCall) {
|
||||
// C'est pour un appel de fonction → charger dans rN
|
||||
if (m_functionCallParamIndex >= 10) {
|
||||
throw std::runtime_error("Too many parameters for function call (max 10)");
|
||||
}
|
||||
|
||||
std::string targetReg = "r" + std::to_string(m_functionCallParamIndex);
|
||||
std::string paramReg = LoadOperand(instr->GetDest(), targetReg);
|
||||
|
||||
if (paramReg != targetReg) {
|
||||
m_assembly << " mov " << targetReg << ", " << paramReg << "\n";
|
||||
}
|
||||
|
||||
m_functionCallParamIndex++;
|
||||
} else {
|
||||
// C'est pour un syscall (Print, etc.) → empiler sur la stack
|
||||
// std::string paramReg = LoadOperand(instr->GetDest(), "r0");
|
||||
// m_assembly << " push " << paramReg << "\n";
|
||||
m_printParams.push_back(instr->GetDest());
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode helper pour détecter si les PARAM suivants sont pour un CALL
|
||||
void DetectFunctionCallContext(size_t currentInstrIndex) {
|
||||
// Regarder les instructions suivantes pour détecter un CALL de fonction
|
||||
const auto& instructions = m_tacProgram.GetInstructions();
|
||||
|
||||
// Compter les PARAM consécutifs après l'instruction courante
|
||||
size_t paramCount = 0;
|
||||
size_t idx = currentInstrIndex + 1;
|
||||
|
||||
while (idx < instructions.size() &&
|
||||
instructions[idx]->GetOpCode() == TACInstruction::OpCode::PARAM) {
|
||||
paramCount++;
|
||||
idx++;
|
||||
}
|
||||
|
||||
// Vérifier ce qui vient après les PARAM
|
||||
if (idx < instructions.size()) {
|
||||
auto nextInstr = instructions[idx];
|
||||
|
||||
// Si c'est un CALL de fonction utilisateur, activer le flag
|
||||
if (nextInstr->GetOpCode() == TACInstruction::OpCode::CALL) {
|
||||
m_preparingFunctionCall = true;
|
||||
m_functionCallParamIndex = 0;
|
||||
}
|
||||
// Sinon (PRINT, etc.), c'est pour un syscall
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GenerateBinaryOp(std::shared_ptr<TACInstruction> instr) {
|
||||
// Charger les opérandes
|
||||
LoadOperand(instr->GetOp1(), "r0");
|
||||
LoadOperand(instr->GetOp2(), "r1");
|
||||
|
||||
// Effectuer l'opération
|
||||
std::string op;
|
||||
switch (instr->GetOpCode()) {
|
||||
case TACInstruction::OpCode::ADD: op = "add"; break;
|
||||
case TACInstruction::OpCode::SUB: op = "sub"; break;
|
||||
case TACInstruction::OpCode::MUL: op = "mul"; break;
|
||||
case TACInstruction::OpCode::DIV: op = "div"; break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
m_assembly << " " << op << " r0, r1\n";
|
||||
|
||||
// Stocker le résultat
|
||||
StoreResult(instr->GetDest(), "r0");
|
||||
}
|
||||
|
||||
void GenerateComparison(std::shared_ptr<TACInstruction> instr) {
|
||||
// Charger les opérandes
|
||||
LoadOperand(instr->GetOp1(), "r0");
|
||||
LoadOperand(instr->GetOp2(), "r1");
|
||||
|
||||
// Effectuer la comparaison
|
||||
switch (instr->GetOpCode()) {
|
||||
case TACInstruction::OpCode::EQ:
|
||||
m_assembly << " eq r0, r0, r1\n";
|
||||
break;
|
||||
case TACInstruction::OpCode::NE:
|
||||
m_assembly << " eq r0, r0, r1\n";
|
||||
m_assembly << " lcons r2, 1\n";
|
||||
m_assembly << " xor r0, r2\n";
|
||||
break;
|
||||
case TACInstruction::OpCode::GT:
|
||||
m_assembly << " gt r0, r0, r1\n";
|
||||
break;
|
||||
case TACInstruction::OpCode::LT:
|
||||
m_assembly << " lt r0, r0, r1\n";
|
||||
break;
|
||||
case TACInstruction::OpCode::GE:
|
||||
// >= est NOT(<)
|
||||
m_assembly << " lt r0, r0, r1\n";
|
||||
m_assembly << " lcons r2, 1\n";
|
||||
m_assembly << " xor r0, r2\n";
|
||||
break;
|
||||
case TACInstruction::OpCode::LE:
|
||||
// <= est NOT(>)
|
||||
m_assembly << " gt r0, r0, r1\n";
|
||||
m_assembly << " lcons r2, 1\n";
|
||||
m_assembly << " xor r0, r2\n";
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
// Stocker le résultat
|
||||
StoreResult(instr->GetDest(), "r0");
|
||||
}
|
||||
|
||||
void GenerateCopy(std::shared_ptr<TACInstruction> instr) {
|
||||
LoadOperand(instr->GetOp1(), "r0");
|
||||
StoreResult(instr->GetDest(), "r0");
|
||||
}
|
||||
|
||||
void GenerateLoad(std::shared_ptr<TACInstruction> instr) {
|
||||
// dest = *op1
|
||||
LoadOperand(instr->GetOp1(), "r0"); // r0 contient l'adresse
|
||||
m_assembly << " load r1, @r0, 4\n";
|
||||
StoreResult(instr->GetDest(), "r1");
|
||||
}
|
||||
|
||||
void GenerateStore(std::shared_ptr<TACInstruction> instr) {
|
||||
// *dest = op1
|
||||
LoadOperand(instr->GetOp1(), "r0"); // r0 contient la valeur
|
||||
LoadOperand(instr->GetDest(), "r1"); // r1 contient l'adresse
|
||||
m_assembly << " store @r1, r0, 4\n";
|
||||
}
|
||||
|
||||
void GenerateIfFalse(std::shared_ptr<TACInstruction> instr) {
|
||||
LoadOperand(instr->GetOp1(), "r0");
|
||||
m_assembly << " skipnz r0\n";
|
||||
m_assembly << " jump " << instr->GetDest()->ToString() << "\n";
|
||||
}
|
||||
|
||||
void GenerateIfTrue(std::shared_ptr<TACInstruction> instr) {
|
||||
LoadOperand(instr->GetOp1(), "r0");
|
||||
m_assembly << " skipz r0\n";
|
||||
m_assembly << " jump " << instr->GetDest()->ToString() << "\n";
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<TACOperand>> m_printParams;
|
||||
|
||||
void GeneratePrint(std::shared_ptr<TACInstruction> instr) {
|
||||
// Sauvegarder r0, r1
|
||||
m_assembly << " push r0\n";
|
||||
m_assembly << " push r1\n";
|
||||
|
||||
// Sauvegarder les registres d'arguments selon le nombre de paramètres
|
||||
int paramCount = m_printParams.size();
|
||||
for (int i = 0; i < std::min(4, paramCount); i++) {
|
||||
m_assembly << " push r" << (i + 2) << "\n";
|
||||
}
|
||||
|
||||
// Charger les paramètres dans r2, r3, r4, r5
|
||||
for (size_t i = 0; i < m_printParams.size() && i < 4; i++) {
|
||||
LoadOperand(m_printParams[i], "r" + std::to_string(i + 2));
|
||||
}
|
||||
|
||||
// Charger la chaîne de format dans r0
|
||||
m_assembly << " lcons r0, $" << instr->GetDest()->GetValue() << "\n";
|
||||
|
||||
// Charger le nombre d'arguments dans r1
|
||||
m_assembly << " lcons r1, " << paramCount << "\n";
|
||||
|
||||
// Syscall
|
||||
m_assembly << " syscall 4\n";
|
||||
|
||||
// Restaurer les registres
|
||||
for (int i = std::min(4, paramCount) - 1; i >= 0; i--) {
|
||||
m_assembly << " pop r" << (i + 2) << "\n";
|
||||
}
|
||||
m_assembly << " pop r1\n";
|
||||
m_assembly << " pop r0\n";
|
||||
|
||||
// Vider la liste des paramètres
|
||||
m_printParams.clear();
|
||||
}
|
||||
|
||||
|
||||
// ===================================================================
|
||||
// HELPER : Obtenir la taille d'une variable
|
||||
// ===================================================================
|
||||
int GetVariableSize(std::shared_ptr<Variable> var) {
|
||||
if (!var) return 4;
|
||||
|
||||
switch (var->GetValueType()) {
|
||||
case Variable::ValueType::BOOL:
|
||||
return 1;
|
||||
case Variable::ValueType::INTEGER:
|
||||
case Variable::ValueType::FLOAT:
|
||||
return 4;
|
||||
case Variable::ValueType::STRING:
|
||||
return 1; // Par caractère
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// IMPLÉMENTATIONS DES MÉTHODES VIRTUELLES - SECTION DATA
|
||||
// ===================================================================
|
||||
|
||||
virtual void Visit(const std::shared_ptr<Variable> v) override {
|
||||
if (!v || !v->IsConstant()) return;
|
||||
|
||||
switch (v->GetValueType()) {
|
||||
case Variable::ValueType::STRING: {
|
||||
std::string value = v->GetValue<std::string>();
|
||||
|
||||
m_assembly << "$" << v->GetLabel() << " DC8, \""
|
||||
<< value << "\" ; " << v->GetVariableName() << "\n";
|
||||
break;
|
||||
}
|
||||
|
||||
case Variable::ValueType::INTEGER:
|
||||
m_assembly << "$" << v->GetLabel() << " DC32, "
|
||||
<< v->GetValue<int>() << " ; "
|
||||
<< v->GetVariableName() << "\n";
|
||||
break;
|
||||
|
||||
case Variable::ValueType::FLOAT:
|
||||
m_assembly << "$" << v->GetLabel() << " DC32, "
|
||||
<< v->GetValue<float>() << " ; "
|
||||
<< v->GetVariableName() << "\n";
|
||||
break;
|
||||
|
||||
case Variable::ValueType::BOOL:
|
||||
m_assembly << "$" << v->GetLabel() << " DCB, "
|
||||
<< (v->GetValue<bool>() ? "1" : "0") << " ; "
|
||||
<< v->GetVariableName() << "\n";
|
||||
break;
|
||||
|
||||
default:
|
||||
AddComment("WARNING: Unsupported constant variable type for " +
|
||||
v->GetVariableName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void GenerateVariable(const std::shared_ptr<Variable> v) override {
|
||||
if (!v) return;
|
||||
|
||||
switch (v->GetValueType()) {
|
||||
case Variable::ValueType::STRING:
|
||||
m_assembly << "$" << v->GetLabel() << " DV8, \""
|
||||
<< v->GetValue<std::string>() << "\" ; "
|
||||
<< v->GetVariableName() << "\n";
|
||||
break;
|
||||
|
||||
case Variable::ValueType::INTEGER:
|
||||
m_assembly << "$" << v->GetLabel() << " DV32, "
|
||||
<< v->GetValue<int>() << " ; "
|
||||
<< v->GetVariableName() << "\n";
|
||||
break;
|
||||
|
||||
case Variable::ValueType::FLOAT:
|
||||
m_assembly << "$" << v->GetLabel() << " DV32, 1 ; "
|
||||
<< v->GetVariableName() << "\n";
|
||||
break;
|
||||
|
||||
case Variable::ValueType::BOOL:
|
||||
m_assembly << "$" << v->GetLabel() << " DVB, 1 ; "
|
||||
<< v->GetVariableName() << "\n";
|
||||
break;
|
||||
|
||||
default:
|
||||
AddComment("WARNING: Unsupported variable type for " +
|
||||
v->GetVariableName());
|
||||
m_assembly << "$" << v->GetLabel() << " DV32, 1 ; "
|
||||
<< v->GetVariableName() << " (unknown type)\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
354
core/story-manager/src/compiler/ast_builder.h
Normal file
354
core/story-manager/src/compiler/ast_builder.h
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
#include "base_node.h"
|
||||
#include "connection.h"
|
||||
#include "function_entry_node.h"
|
||||
#include "variable_node.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <queue>
|
||||
|
||||
class ASTNode {
|
||||
public:
|
||||
// Structure to represent a data connection target
|
||||
struct DataTarget {
|
||||
std::shared_ptr<ASTNode> node;
|
||||
unsigned int portIndex;
|
||||
};
|
||||
|
||||
explicit ASTNode(std::shared_ptr<BaseNode> node)
|
||||
: node(node) {}
|
||||
|
||||
// The actual node from the visual editor
|
||||
std::shared_ptr<BaseNode> node;
|
||||
|
||||
// Execution flow children (for EXECUTION_LINK connections)
|
||||
std::vector<std::shared_ptr<ASTNode>> children;
|
||||
|
||||
// Data inputs: port_index -> source node
|
||||
std::unordered_map<unsigned int, std::shared_ptr<ASTNode>> dataInputs;
|
||||
|
||||
// Data outputs: output_port_index -> vector of (target node, target port)
|
||||
std::unordered_map<unsigned int, std::vector<DataTarget>> dataOutputs;
|
||||
|
||||
bool HasChildren(std::shared_ptr<ASTNode> c) const {
|
||||
return std::find(children.begin(), children.end(), c) != children.end();
|
||||
}
|
||||
|
||||
bool IsExecutionNode() const {
|
||||
return node->GetBehavior() == BaseNode::Behavior::BEHAVIOR_EXECUTION;
|
||||
}
|
||||
|
||||
bool IsDataNode() const {
|
||||
return node->GetBehavior() == BaseNode::Behavior::BEHAVIOR_DATA;
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
bool HasDataInput(unsigned int portIndex) const {
|
||||
return dataInputs.find(portIndex) != dataInputs.end();
|
||||
}
|
||||
|
||||
bool HasDataOutput(unsigned int portIndex) const {
|
||||
return dataOutputs.find(portIndex) != dataOutputs.end();
|
||||
}
|
||||
|
||||
std::shared_ptr<ASTNode> GetDataInput(unsigned int portIndex) const {
|
||||
auto it = dataInputs.find(portIndex);
|
||||
if (it != dataInputs.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::vector<DataTarget>& GetDataOutputs(unsigned int portIndex) const {
|
||||
static const std::vector<DataTarget> empty;
|
||||
auto it = dataOutputs.find(portIndex);
|
||||
if (it != dataOutputs.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
|
||||
// Add a data input connection
|
||||
void AddDataInput(unsigned int portIndex, std::shared_ptr<ASTNode> sourceNode) {
|
||||
dataInputs[portIndex] = sourceNode;
|
||||
}
|
||||
|
||||
// Add a data output connection
|
||||
void AddDataOutput(unsigned int sourcePort, std::shared_ptr<ASTNode> targetNode, unsigned int targetPort) {
|
||||
DataTarget target{targetNode, targetPort};
|
||||
dataOutputs[sourcePort].push_back(target);
|
||||
}
|
||||
|
||||
// Add an execution child
|
||||
void AddChild(std::shared_ptr<ASTNode> child) {
|
||||
children.push_back(child);
|
||||
}
|
||||
|
||||
// Get execution path child count
|
||||
size_t GetChildCount() const {
|
||||
return children.size();
|
||||
}
|
||||
|
||||
// Get child at specific index
|
||||
std::shared_ptr<ASTNode> GetChild(size_t index) const {
|
||||
if (index < children.size()) {
|
||||
return children[index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Utility method to check if this is a specific node type
|
||||
template<typename T>
|
||||
bool IsType() const {
|
||||
return dynamic_cast<const T*>(node.get()) != nullptr;
|
||||
}
|
||||
|
||||
// Get node as specific type
|
||||
template<typename T>
|
||||
T* GetAs() const {
|
||||
return dynamic_cast<T*>(node.get());
|
||||
}
|
||||
|
||||
std::string GetId() const {
|
||||
return node->GetId();
|
||||
}
|
||||
|
||||
int GetWeight() const {
|
||||
return node->GetWeight();
|
||||
}
|
||||
|
||||
// Debug information
|
||||
std::string GetDebugString() const {
|
||||
std::string result = "Node: " + node->GetTypeName() + " (ID: " + node->GetId() + ")\n";
|
||||
|
||||
// Add data inputs info
|
||||
for (const auto& [port, input] : dataInputs) {
|
||||
result += " Input port " + std::to_string(port) +
|
||||
" <- " + input->node->GetTypeName() + "\n";
|
||||
}
|
||||
|
||||
// Add data outputs info
|
||||
for (const auto& [port, outputs] : dataOutputs) {
|
||||
for (const auto& target : outputs) {
|
||||
result += " Output port " + std::to_string(port) +
|
||||
" -> " + target.node->node->GetTypeName() +
|
||||
" (port " + std::to_string(target.portIndex) + ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Add execution children info
|
||||
for (size_t i = 0; i < children.size(); ++i) {
|
||||
result += " Child " + std::to_string(i) + ": " +
|
||||
children[i]->node->GetTypeName() + "\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class ASTBuilder {
|
||||
public:
|
||||
|
||||
ASTBuilder(const std::vector<std::shared_ptr<BaseNode>>& nodes,
|
||||
const std::vector<std::shared_ptr<Connection>>& connections)
|
||||
: m_nodes(nodes), m_connections(connections) {}
|
||||
|
||||
std::vector<std::shared_ptr<ASTNode>> BuildAST()
|
||||
{
|
||||
// Create node map for quick lookups
|
||||
std::unordered_map<std::string, std::shared_ptr<ASTNode>> nodeMap;
|
||||
for (const auto& node : m_nodes) {
|
||||
nodeMap[node->GetId()] = std::make_shared<ASTNode>(node);
|
||||
}
|
||||
|
||||
|
||||
// Build adjacency list for the nodes from the connections
|
||||
for (const auto& conn : m_connections) {
|
||||
|
||||
// Don't add the variables nodes, as they are input data nodes.
|
||||
auto rawNode = nodeMap[conn->outNodeId].get()->node;
|
||||
|
||||
if (dynamic_cast<VariableNode*>(rawNode.get())) {
|
||||
continue;
|
||||
}
|
||||
m_adjList[conn->outNodeId].push_back(conn->inNodeId);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ASTNode>> topologicalOrder = ApplyKahnAlgorithm(nodeMap);
|
||||
|
||||
// Maintenant, on va ajouter les connexions de données
|
||||
for (const auto& conn : m_connections)
|
||||
{
|
||||
std::cout << ">>> ASTBuilder: Processing connection from " << conn->outNodeId
|
||||
<< " to " << conn->inNodeId
|
||||
<< " type=" << conn->type << " (0=EXEC, 1=DATA)" << std::endl;
|
||||
|
||||
// Ne traiter que les connexions DATA_LINK
|
||||
if (conn->type != Connection::DATA_LINK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout << " -> Adding DATA connection!" << std::endl;
|
||||
|
||||
auto outNode = nodeMap[conn->outNodeId];
|
||||
auto inNode = nodeMap[conn->inNodeId];
|
||||
|
||||
// Ajouter TOUTES les connexions de données, pas seulement celles depuis VariableNode
|
||||
inNode->AddDataInput(conn->inPortIndex, outNode);
|
||||
|
||||
// Ajouter aussi dans le sens inverse pour les dataOutputs
|
||||
outNode->AddDataOutput(conn->outPortIndex, inNode, conn->inPortIndex);
|
||||
}
|
||||
|
||||
// Build execution paths
|
||||
BuildExecutionPath(topologicalOrder, nodeMap);
|
||||
|
||||
return topologicalOrder;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<std::shared_ptr<BaseNode>>& m_nodes;
|
||||
const std::vector<std::shared_ptr<Connection>>& m_connections;
|
||||
|
||||
void BuildExecutionPath(std::vector<std::shared_ptr<ASTNode>>& tree,
|
||||
const std::unordered_map<std::string, std::shared_ptr<ASTNode>>& nodeMap)
|
||||
{
|
||||
// For each node in the tree, find its children based on the connections
|
||||
for (const auto& node : tree)
|
||||
{
|
||||
std::queue<std::shared_ptr<ASTNode>> queue;
|
||||
queue.push(node);
|
||||
|
||||
while (!queue.empty()) {
|
||||
auto current = queue.front();
|
||||
queue.pop();
|
||||
|
||||
// Find connections from this node
|
||||
for (const auto& conn : m_connections)
|
||||
{
|
||||
if (conn->outNodeId == current->node->GetId())
|
||||
{
|
||||
auto targetNode = nodeMap.find(conn->inNodeId);
|
||||
if (targetNode != nodeMap.end())
|
||||
{
|
||||
auto childNode = targetNode->second;
|
||||
|
||||
// Si le noeud n'a pas déjà cet enfant, on l'ajoute
|
||||
if (!current->HasChildren(childNode))
|
||||
{
|
||||
current->children.push_back(childNode);
|
||||
queue.push(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ASTNode>> ApplyKahnAlgorithm(const std::unordered_map<std::string, std::shared_ptr<ASTNode>> &nodeMap)
|
||||
{
|
||||
|
||||
// Pour le container de la queue, on utilise un comparateur pour trier les noeuds par poids
|
||||
// Cela permet de prioriser les noeuds avec un poids plus faible
|
||||
auto compare = [](const std::shared_ptr<ASTNode>& a, const std::shared_ptr<ASTNode>& b) {
|
||||
return a->GetWeight() < b->GetWeight();
|
||||
};
|
||||
std::priority_queue<std::shared_ptr<ASTNode>, std::vector<std::shared_ptr<ASTNode>>, decltype(compare)> queue(compare);
|
||||
|
||||
// std::queue<std::string> q;
|
||||
std::unordered_map<std::string, int> inDegree;
|
||||
|
||||
std::vector<std::shared_ptr<ASTNode>> res;
|
||||
int visitedCount = 0;
|
||||
|
||||
// Calculate indegree
|
||||
for (auto p: m_adjList)
|
||||
{
|
||||
std::string u = p.first;
|
||||
|
||||
// On initialise à zéro si le node n'est pas dans la liste
|
||||
if (inDegree.find(u) == inDegree.end())
|
||||
{
|
||||
inDegree[u] = 0;
|
||||
}
|
||||
|
||||
for (auto v: p.second)
|
||||
{
|
||||
inDegree[v]++;
|
||||
}
|
||||
}
|
||||
|
||||
// insert vertices with 0 indegree in queue
|
||||
for (auto i: inDegree)
|
||||
{
|
||||
if (i.second == 0)
|
||||
{
|
||||
queue.push(nodeMap.at(i.first));
|
||||
}
|
||||
}
|
||||
|
||||
// Process the queue
|
||||
while(!queue.empty())
|
||||
{
|
||||
auto x = queue.top();
|
||||
queue.pop();
|
||||
visitedCount++;
|
||||
|
||||
res.push_back(x);
|
||||
|
||||
// Reduce indegree of neighbours
|
||||
for (auto dest: m_adjList[x->GetId()])
|
||||
{
|
||||
inDegree[dest]--;
|
||||
if (inDegree[dest] == 0)
|
||||
{
|
||||
queue.push(nodeMap.at(dest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visitedCount != nodeMap.size()) {
|
||||
// cout << "There exists a cycle in the graph";
|
||||
// throw std::runtime_error("Graph has a cycle");
|
||||
}
|
||||
|
||||
// Debug: print in the console all the nodes in topological order
|
||||
std::cout << "Topological order: \n\n";
|
||||
for (const auto& a : res)
|
||||
{
|
||||
std::cout << a->node->GetTypeName() << " (" << a->GetId() << ") \n";
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
// Ids (UUID strings) of nodes
|
||||
std::unordered_map<std::string, std::vector<std::string>> m_adjList;
|
||||
|
||||
|
||||
bool AreAllInputsVariables(const std::shared_ptr<BaseNode>& node, const std::unordered_map<std::string, std::shared_ptr<BaseNode>>& nodeMap) const
|
||||
{
|
||||
for (const auto& conn : m_connections)
|
||||
{
|
||||
if (conn->type == Connection::DATA_LINK && conn->inNodeId == node->GetId())
|
||||
{
|
||||
auto sourceNode = nodeMap.find(conn->outNodeId);
|
||||
if (sourceNode != nodeMap.end() && !dynamic_cast<VariableNode*>(sourceNode->second.get()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
17
core/story-manager/src/compiler/compiler.cpp
Normal file
17
core/story-manager/src/compiler/compiler.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
#include "compiler.h"
|
||||
|
||||
|
||||
std::string Compiler::FileToConstant(const std::string &FileName, const std::string &extension, IStoryProject &project)
|
||||
{
|
||||
std::string label = "$" + FileName;
|
||||
|
||||
if (!project.UseResource(label))
|
||||
{
|
||||
std::string f = SysLib::RemoveFileExtension(FileName);
|
||||
return label + " DC8 \"" + FileName + "\", 8\r\n";
|
||||
}
|
||||
|
||||
// Label of file is already existing, so we do not add anything
|
||||
return "";
|
||||
}
|
||||
148
core/story-manager/src/compiler/compiler.h
Normal file
148
core/story-manager/src/compiler/compiler.h
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#pragma once
|
||||
|
||||
#include "sys_lib.h"
|
||||
#include "i_story_project.h"
|
||||
#include "base_node.h"
|
||||
#include "connection.h"
|
||||
#include "function_entry_node.h"
|
||||
|
||||
struct AstNode
|
||||
{
|
||||
std::shared_ptr<BaseNode> node; // pointeur vers le noeud en cours
|
||||
|
||||
std::vector<std::shared_ptr<AstNode>> inputs;
|
||||
std::shared_ptr<AstNode> condition; // Pour les boucles et les branchements
|
||||
std::vector<std::shared_ptr<AstNode>> body; // Pour les boucles et les branchements
|
||||
std::shared_ptr<AstNode> elseBody; // Pour les branchements
|
||||
|
||||
std::pair<int, int> position;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// AST (Arbre de syntaxe abstraite)
|
||||
struct AST {
|
||||
|
||||
std::map<std::string, int> variables; // Stockage des variables (adresses mémoire)
|
||||
std::string assemblyCode;
|
||||
int labelCounter = 0;
|
||||
std::map<std::string, std::shared_ptr<AstNode>> nodeMap;
|
||||
|
||||
// Fonction pour générer un label unique
|
||||
std::string generateLabel(AST& ast) {
|
||||
return "label" + std::to_string(ast.labelCounter++);
|
||||
}
|
||||
};
|
||||
|
||||
class Compiler
|
||||
{
|
||||
|
||||
public:
|
||||
Compiler() = default;
|
||||
~Compiler() = default;
|
||||
|
||||
|
||||
static std::string FileToConstant(const std::string &FileName, const std::string &extension, IStoryProject &project);
|
||||
|
||||
|
||||
// Fonction pour construire l'AST à partir des nœuds et des connexions
|
||||
void buildAST(std::vector<std::shared_ptr<BaseNode>>& nodes, std::vector<std::shared_ptr<Connection>>& connections) {
|
||||
|
||||
m_ast.nodeMap.clear();
|
||||
|
||||
// Créer une map pour accéder aux nœuds par ID
|
||||
int x = 0, y = 0;
|
||||
for (auto& node : nodes) {
|
||||
auto astNode = std::make_shared<AstNode>();
|
||||
|
||||
astNode->position = {x, y};
|
||||
x += 1; // Espacement horizontal
|
||||
if (x > 20) { // Nouvelle ligne
|
||||
x = 0;
|
||||
y += 1; // Espacement vertical
|
||||
}
|
||||
|
||||
astNode->node = node;
|
||||
m_ast.nodeMap[node->GetId()] = astNode;
|
||||
}
|
||||
|
||||
// Analyser les connexions pour établir les relations entre les nœuds
|
||||
for (const auto& connection : connections) {
|
||||
auto outNode = m_ast.nodeMap[connection->outNodeId];
|
||||
auto inNode = m_ast.nodeMap[connection->inNodeId];
|
||||
|
||||
// // Utiliser les indices des ports pour établir les connexions
|
||||
// if (outNode->inputs.size() <= connection->inPortIndex) {
|
||||
// outNode->inputs.resize(connection->inPortIndex + 1);
|
||||
// }
|
||||
// outNode->inputs[connection->inPortIndex] = inNode;
|
||||
|
||||
// Gérer les sorties en utilisant connection->outPortIndex
|
||||
if (outNode->body.size() <= connection->outPortIndex) {
|
||||
outNode->body.resize(connection->outPortIndex + 1);
|
||||
}
|
||||
outNode->body[connection->outPortIndex] = inNode;
|
||||
}
|
||||
}
|
||||
|
||||
void printAST(const AST& ast, const std::shared_ptr<AstNode>& node, const std::string& prefix = "", bool isLast = true) {
|
||||
// Afficher le nœud actuel
|
||||
std::cout << prefix;
|
||||
std::cout << (isLast ? "└──" : "├──");
|
||||
|
||||
// Afficher les informations du nœud
|
||||
std::cout << "Node: " << node->node->GetTypeName() << " (" << node->node->GetId() << ")" << std::endl;
|
||||
|
||||
// Préparer le préfixe pour les enfants
|
||||
std::string newPrefix = prefix + (isLast ? " " : "│ ");
|
||||
|
||||
// Parcourir les entrées (inputs) du nœud
|
||||
// for (size_t i = 0; i < node->inputs.size(); ++i) {
|
||||
// bool isLastInput = (i == node->inputs.size() - 1);
|
||||
// printAST(ast, node->inputs[i], newPrefix, isLastInput);
|
||||
// }
|
||||
|
||||
// Parcourir les sorties (body) du nœud
|
||||
for (size_t i = 0; i < node->body.size(); ++i) {
|
||||
bool isLastBody = (i == node->body.size() - 1);
|
||||
printAST(ast, node->body[i], newPrefix, isLastBody);
|
||||
}
|
||||
|
||||
// Parcourir le conditionnel (condition) du nœud, s'il existe
|
||||
if (node->condition) {
|
||||
printAST(ast, node->condition, newPrefix, true);
|
||||
}
|
||||
|
||||
// Parcourir le corps du else (elseBody) du nœud, s'il existe
|
||||
if (node->elseBody) {
|
||||
printAST(ast, node->elseBody, newPrefix, true);
|
||||
}
|
||||
}
|
||||
|
||||
void printAST() {
|
||||
// Trouver le nœud de type "function-entry-node"
|
||||
std::shared_ptr<AstNode> entryNode;
|
||||
for (const auto& pair : m_ast.nodeMap) {
|
||||
if (pair.second->node->GetType() == "function-entry-node") {
|
||||
entryNode = pair.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entryNode) {
|
||||
std::cerr << "No function-entry-node found in the AST." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Commencer l'affichage de l'arbre à partir du nœud d'entrée
|
||||
printAST(m_ast, entryNode);
|
||||
}
|
||||
|
||||
std::string GetCode() const {
|
||||
return m_ast.assemblyCode;
|
||||
}
|
||||
|
||||
private:
|
||||
AST m_ast;
|
||||
|
||||
};
|
||||
160
core/story-manager/src/compiler/flow_generator.h
Normal file
160
core/story-manager/src/compiler/flow_generator.h
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#pragma once
|
||||
|
||||
#include "assembly_generator.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
|
||||
class FlowVisualizer {
|
||||
public:
|
||||
static void PrintHeader(const std::string& user, const std::string& timestamp) {
|
||||
std::cout << "==========================================================\n";
|
||||
std::cout << "AST Flow Visualization\n";
|
||||
std::cout << "User: " << user << "\n";
|
||||
std::cout << "Time: " << timestamp << "\n";
|
||||
std::cout << "==========================================================\n\n" << std::endl;
|
||||
}
|
||||
|
||||
static void PrintNodeExecution(const std::string& nodeName, int depth = 0) {
|
||||
PrintTimestamp();
|
||||
std::cout << std::string(depth * 2, ' ') << "→ Executing: " << nodeName << "\n";
|
||||
}
|
||||
|
||||
static void PrintDataFlow(const std::string& from, const std::string& to,
|
||||
const std::string& value, int depth = 0) {
|
||||
PrintTimestamp();
|
||||
std::cout << std::string(depth * 2, ' ') << " ┌─ Data: " << from
|
||||
<< " → " << to << " (value: " << value << ")\n";
|
||||
}
|
||||
|
||||
static void PrintBranchDecision(bool condition, const std::string& value, int depth = 0) {
|
||||
PrintTimestamp();
|
||||
std::cout << std::string(depth * 2, ' ') << " ├─ Branch: "
|
||||
<< (condition ? "TRUE" : "FALSE") << " (condition: " << value << ")\n";
|
||||
}
|
||||
|
||||
private:
|
||||
static void PrintTimestamp() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto now_c = std::chrono::system_clock::to_time_t(now);
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()) % 1000;
|
||||
|
||||
std::cout << "[" << std::put_time(std::localtime(&now_c), "%H:%M:%S")
|
||||
<< "." << std::setfill('0') << std::setw(3) << ms.count() << "] ";
|
||||
}
|
||||
};
|
||||
|
||||
class VisualFlowGenerator : public AssemblyGenerator {
|
||||
|
||||
public:
|
||||
VisualFlowGenerator(const GeneratorContext& context)
|
||||
: AssemblyGenerator(context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
virtual void AddComment(const std::string& comment) {
|
||||
|
||||
}
|
||||
|
||||
virtual void GenerateMain() override {
|
||||
// Program entry point
|
||||
FlowVisualizer::PrintNodeExecution("Main Entry", m_depth);
|
||||
}
|
||||
|
||||
virtual void Visit(const std::shared_ptr<Variable> v) {
|
||||
|
||||
}
|
||||
|
||||
virtual void GenerateVariable(const std::shared_ptr<Variable> v) {
|
||||
|
||||
}
|
||||
|
||||
virtual void GenerateExit() {
|
||||
|
||||
}
|
||||
|
||||
void GenerateNodeCode(std::shared_ptr<ASTNode> node, bool isDataPath = false) override
|
||||
{
|
||||
if (!node) return;
|
||||
|
||||
FlowVisualizer::PrintNodeExecution(node->node->GetTypeName(), m_depth);
|
||||
|
||||
if (node->IsType<FunctionEntryNode>()) {
|
||||
m_depth++;
|
||||
for (auto& child : node->children) {
|
||||
GenerateNodeCode(child);
|
||||
}
|
||||
m_depth--;
|
||||
}
|
||||
else if (node->IsType<BranchNode>()) {
|
||||
m_depth++;
|
||||
// Get condition value
|
||||
auto conditionNode = node->GetDataInput(0);
|
||||
int conditionValue = 0;
|
||||
|
||||
FlowVisualizer::PrintBranchDecision(conditionValue > 7,
|
||||
std::to_string(conditionValue), m_depth);
|
||||
|
||||
// Execute appropriate path
|
||||
if (conditionValue > 7) {
|
||||
GenerateNodeCode(node->GetChild(0)); // True path
|
||||
} else {
|
||||
GenerateNodeCode(node->GetChild(1)); // False path
|
||||
}
|
||||
m_depth--;
|
||||
}
|
||||
else if (node->IsType<PrintNode>()) {
|
||||
auto* printNode = node->GetAs<PrintNode>();
|
||||
FlowVisualizer::PrintNodeExecution("Print: " + printNode->GetText(), m_depth);
|
||||
}
|
||||
else if (node->IsType<OperatorNode>()) {
|
||||
m_depth++;
|
||||
auto* opNode = node->GetAs<OperatorNode>();
|
||||
|
||||
|
||||
|
||||
FlowVisualizer::PrintDataFlow("Operand 1", opNode->GetOperatorSymbol(),
|
||||
std::to_string(0), m_depth);
|
||||
FlowVisualizer::PrintDataFlow("Operand 2", opNode->GetOperatorSymbol(),
|
||||
std::to_string(0), m_depth);
|
||||
|
||||
|
||||
FlowVisualizer::PrintDataFlow(opNode->GetOperatorSymbol(), "Result",
|
||||
std::to_string(0), m_depth);
|
||||
|
||||
for (const auto& [port, outputs] : node->dataOutputs) {
|
||||
for (const auto& target : outputs) {
|
||||
GenerateNodeCode(target.node, true);
|
||||
}
|
||||
}
|
||||
|
||||
m_depth--;
|
||||
}
|
||||
else if (node->IsType<VariableNode>()) {
|
||||
m_depth++;
|
||||
auto* varNode = node->GetAs<VariableNode>();
|
||||
// FlowVisualizer::PrintNodeExecution("Variable: " + varNode->GetVariableName(), m_depth);
|
||||
|
||||
// If we're processing a data path, traverse data outputs
|
||||
if (isDataPath) {
|
||||
for (const auto& [port, outputs] : node->dataOutputs) {
|
||||
for (const auto& target : outputs) {
|
||||
GenerateNodeCode(target.node, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_depth--;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
int m_depth = 0;
|
||||
|
||||
};
|
||||
1595
core/story-manager/src/compiler/tac.h
Normal file
1595
core/story-manager/src/compiler/tac.h
Normal file
File diff suppressed because it is too large
Load diff
176
core/story-manager/src/media_node.cpp
Normal file
176
core/story-manager/src/media_node.cpp
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
#include "media_node.h"
|
||||
#include "story_project.h"
|
||||
#include "connection.h"
|
||||
#include "sys_lib.h"
|
||||
|
||||
static std::string ChoiceLabel(const std::string &id)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "mediaChoice" << std::setw(4) << std::setfill('0') << id;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
MediaNode::MediaNode(const std::string &type)
|
||||
: BaseNode(type, "Media Node")
|
||||
{
|
||||
nlohmann::json j{ {"image", ""}, {"sound", ""}};
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void MediaNode::StoreInternalData()
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["image"] = m_image;
|
||||
j["sound"] = m_sound;
|
||||
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void MediaNode::Initialize()
|
||||
{
|
||||
nlohmann::json j = GetInternalData();
|
||||
m_image = j["image"].get<std::string>();
|
||||
m_sound = j["sound"].get<std::string>();
|
||||
}
|
||||
|
||||
std::string MediaNode::GenerateConstants(IStoryPage &page, IStoryProject &project, int nb_out_conns)
|
||||
{
|
||||
std::string s;
|
||||
|
||||
if (m_image.size() > 0)
|
||||
{
|
||||
s = Compiler::FileToConstant(m_image, Resource::ImageExtension(m_image, project.GetOptions().image_format), project);
|
||||
}
|
||||
if (m_sound.size() > 0)
|
||||
{
|
||||
s += Compiler::FileToConstant(m_sound, Resource::SoundExtension(m_sound, project.GetOptions().sound_format), project); // FIXME: Generate the extension setup in user option of output format
|
||||
}
|
||||
|
||||
// Generate choice table if needed (out ports > 1)
|
||||
std::stringstream ss;
|
||||
std::string label = ChoiceLabel(GetId());
|
||||
ss << "$" << label
|
||||
<< " DC32, "
|
||||
<< nb_out_conns << ", ";
|
||||
|
||||
std::list<std::shared_ptr<Connection>> conns;
|
||||
page.GetNodeConnections(conns, GetId());
|
||||
int i = 0;
|
||||
for (auto & c : conns)
|
||||
{
|
||||
std::stringstream ssChoice;
|
||||
|
||||
// On va chercher le label d'entrée du noeud connecté à l'autre bout
|
||||
ss << BaseNode::GetEntryLabel(c->inNodeId);
|
||||
if (i < (nb_out_conns - 1))
|
||||
{
|
||||
ss << ", ";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "\n";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
s += ss.str();
|
||||
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void MediaNode::SetImage(const std::string &image)
|
||||
{
|
||||
m_image = image;
|
||||
StoreInternalData();
|
||||
}
|
||||
|
||||
std::string_view MediaNode::GetImage() const
|
||||
{
|
||||
return m_image;
|
||||
}
|
||||
|
||||
void MediaNode::SetSound(const std::string &sound)
|
||||
{
|
||||
m_sound = sound;
|
||||
StoreInternalData();
|
||||
}
|
||||
|
||||
std::string_view MediaNode::GetSound() const
|
||||
{
|
||||
return m_sound;
|
||||
}
|
||||
|
||||
std::string MediaNode::Build(IStoryPage &page, const StoryOptions &options, int nb_out_conns)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << R"(; ---------------------------- )"
|
||||
<< GetTitle()
|
||||
<< " Type: "
|
||||
<< (nb_out_conns == 0 ? "End" : nb_out_conns == 1 ? "Transition" : "Choice")
|
||||
<< "\n";
|
||||
|
||||
std::string img = SysLib::RemoveFileExtension(m_image);
|
||||
std::string snd = SysLib::RemoveFileExtension(m_sound);
|
||||
|
||||
// Le label de ce noeud est généré de la façon suivante :
|
||||
// "media" + Node ID + id du noeud parent. Si pas de noeud parent, alors rien
|
||||
ss << BaseNode::GetEntryLabel(GetId()) << ":\n";
|
||||
|
||||
if (img.size() > 0)
|
||||
{
|
||||
ss << "lcons r0, $" << m_image << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "lcons r0, 0\n";
|
||||
}
|
||||
|
||||
if (snd.size() > 0)
|
||||
{
|
||||
ss << "lcons r1, $" << m_sound << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "lcons r1, 0\n";
|
||||
}
|
||||
// Call the media executor (image, sound)
|
||||
ss << "syscall 1\n";
|
||||
|
||||
// Check output connections number
|
||||
// == 0: end node : wait for home button or return du choice node at the end of the audio
|
||||
// == 1: transition node : image + sound on demand, jump directly to the other node when OK
|
||||
// > 1 : choice node : call the node choice manager
|
||||
|
||||
if (nb_out_conns == 0) // End node
|
||||
{
|
||||
ss << "ret\n";
|
||||
}
|
||||
else if (nb_out_conns == 1) // it is a transition node
|
||||
{
|
||||
std::list<std::shared_ptr<Connection>> conns;
|
||||
page.GetNodeConnections(conns, GetId());
|
||||
|
||||
for (auto c : conns)
|
||||
{
|
||||
if (c->outNodeId == GetId())
|
||||
{
|
||||
// On place dans R0 le prochain noeud à exécuter en cas de OK
|
||||
ss << "lcons r0, "
|
||||
<< BaseNode::GetEntryLabel(c->inNodeId) << "\n"
|
||||
<< "ret\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else // Choice node
|
||||
{
|
||||
ss << "lcons r0, 0b10000000000\n" // ; mask for end of audio, Ok, and home buttons
|
||||
<< "syscall 2\n" // ; wait for event (OK, home or end of audio), return to choice loop"
|
||||
<< "lcons r0, $" << ChoiceLabel(GetId()) << "\n"
|
||||
<< "jump .media ; no return possible, so a jump is enough";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
27
core/story-manager/src/media_node.h
Normal file
27
core/story-manager/src/media_node.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "i_story_manager.h"
|
||||
#include "base_node.h"
|
||||
#include "i_script_node.h"
|
||||
#include "i_story_project.h"
|
||||
|
||||
class MediaNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
MediaNode(const std::string &type);
|
||||
|
||||
virtual void Initialize() override;
|
||||
|
||||
|
||||
void SetImage(const std::string &image);
|
||||
std::string_view GetImage() const;
|
||||
void SetSound(const std::string &sound);
|
||||
std::string_view GetSound() const;
|
||||
void StoreInternalData();
|
||||
|
||||
private:
|
||||
std::string m_image;
|
||||
std::string m_sound;
|
||||
};
|
||||
|
||||
94
core/story-manager/src/nodes/base_node.cpp
Normal file
94
core/story-manager/src/nodes/base_node.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#include "base_node.h"
|
||||
#include "uuid.h"
|
||||
#include <iostream>
|
||||
|
||||
BaseNode::BaseNode(const std::string &type, const std::string &typeName, Behavior behavior)
|
||||
: m_behavior(behavior)
|
||||
{
|
||||
m_type = type;
|
||||
m_typeName = typeName;
|
||||
m_uuid = Uuid().String();
|
||||
|
||||
nlohmann::json obj{};
|
||||
m_internal_data = obj;
|
||||
}
|
||||
|
||||
|
||||
BaseNode::~BaseNode()
|
||||
{
|
||||
std::cout << "Deleted base node" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
void BaseNode::FromJson(const nlohmann::json &j)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_uuid = j["uuid"].get<std::string>();
|
||||
m_internal_data = j["internal-data"];
|
||||
m_type = j["type"].get<std::string>();
|
||||
m_title = JsonReader::get<std::string>(j, "title", "Default node");
|
||||
nlohmann::json posJson = j["position"];
|
||||
|
||||
SetPosition(posJson["x"].get<double>(), posJson["y"].get<double>());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
std::cout << "[BASE NODE] ERROR: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
nlohmann::json BaseNode::ToJson() const
|
||||
{
|
||||
nlohmann::json node;
|
||||
node["uuid"] = GetId();
|
||||
node["type"] = GetType();
|
||||
|
||||
nlohmann::json position;
|
||||
position["x"] = GetX();
|
||||
position["y"] = GetY();
|
||||
|
||||
node["position"] = position;
|
||||
node["internal-data"] = m_internal_data;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
void BaseNode::SetInternalData(const nlohmann::json &j)
|
||||
{
|
||||
m_internal_data = j;
|
||||
}
|
||||
|
||||
nlohmann::json BaseNode::GetInternalData() const
|
||||
{
|
||||
return m_internal_data;
|
||||
}
|
||||
|
||||
void BaseNode::SetPosition(float x, float y)
|
||||
{
|
||||
m_pos.x = x;
|
||||
m_pos.y = y;
|
||||
}
|
||||
|
||||
float BaseNode::GetX() const
|
||||
{
|
||||
return m_pos.x;
|
||||
}
|
||||
|
||||
float BaseNode::GetY() const
|
||||
{
|
||||
return m_pos.y;
|
||||
}
|
||||
|
||||
void BaseNode::Accept(IVariableVisitor &visitor)
|
||||
{
|
||||
|
||||
for (auto &v : m_variables)
|
||||
{
|
||||
visitor.Visit(v.second);
|
||||
}
|
||||
|
||||
}
|
||||
244
core/story-manager/src/nodes/base_node.h
Normal file
244
core/story-manager/src/nodes/base_node.h
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include "json.hpp"
|
||||
#include "json_reader.h"
|
||||
#include "i_story_page.h"
|
||||
#include "i_story_project.h"
|
||||
#include "story_options.h"
|
||||
#include "variable.h"
|
||||
|
||||
class IVariableVisitor {
|
||||
|
||||
public:
|
||||
virtual void Visit(const std::shared_ptr<Variable> v) = 0;
|
||||
|
||||
};
|
||||
|
||||
class BaseNode
|
||||
{
|
||||
public:
|
||||
enum Behavior
|
||||
{
|
||||
BEHAVIOR_EXECUTION,
|
||||
BEHAVIOR_DATA,
|
||||
};
|
||||
|
||||
struct Port
|
||||
{
|
||||
enum Type {
|
||||
EXECUTION_PORT,
|
||||
DATA_PORT
|
||||
};
|
||||
|
||||
Port::Type type{EXECUTION_PORT};
|
||||
std::string label;
|
||||
bool customSocketIcon{false};
|
||||
};
|
||||
|
||||
struct NodePosition
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
|
||||
struct ConstantValue
|
||||
{
|
||||
enum Type {
|
||||
TYPE_STRING,
|
||||
TYPE_INT,
|
||||
};
|
||||
std::string stringVal;
|
||||
int intVal{0};
|
||||
ConstantValue::Type type{TYPE_INT};
|
||||
};
|
||||
|
||||
struct Constant
|
||||
{
|
||||
std::string label;
|
||||
int elementSize; // in bits: 8, 16 or 32
|
||||
std::vector<ConstantValue> values;
|
||||
};
|
||||
|
||||
BaseNode(const std::string &type, const std::string &typeName, Behavior behavior = BEHAVIOR_EXECUTION);
|
||||
virtual ~BaseNode();
|
||||
|
||||
virtual void Initialize() = 0;
|
||||
|
||||
void SetPosition(float x, float y);
|
||||
|
||||
// make this virtual so that graphical node override the behavior
|
||||
virtual float GetX() const;
|
||||
virtual float GetY() const;
|
||||
|
||||
// Coded type, internal use
|
||||
std::string GetType() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
// Human readable type
|
||||
std::string GetTypeName() const
|
||||
{
|
||||
return m_typeName;
|
||||
}
|
||||
|
||||
|
||||
void SetWeight(int w) {
|
||||
m_weight = w;
|
||||
}
|
||||
|
||||
int GetWeight() const {
|
||||
return m_weight;
|
||||
}
|
||||
|
||||
void SetupExecutionPorts(bool hasInput = true,
|
||||
int numOutputs = 1,
|
||||
bool customRendering = true)
|
||||
{
|
||||
if (m_behavior != BEHAVIOR_EXECUTION) return;
|
||||
|
||||
if (hasInput) {
|
||||
AddInputPort(Port::Type::EXECUTION_PORT, ">", customRendering);
|
||||
}
|
||||
|
||||
for (int i = 0; i < numOutputs; ++i) {
|
||||
std::string label = (numOutputs == 1) ? ">" : std::to_string(i);
|
||||
AddOutputPort(Port::Type::EXECUTION_PORT, label, customRendering);
|
||||
}
|
||||
}
|
||||
|
||||
void SetId(const std::string &id) { m_uuid = id; }
|
||||
std::string GetId() const { return m_uuid; }
|
||||
|
||||
void SetTitle(const std::string &title) { m_title = title; }
|
||||
std::string GetTitle() const { return m_title; }
|
||||
|
||||
void FromJson(const nlohmann::json &);
|
||||
nlohmann::json ToJson() const;
|
||||
|
||||
|
||||
void SetInternalData(const nlohmann::json &j);
|
||||
nlohmann::json GetInternalData() const;
|
||||
|
||||
void ClearPorts() {
|
||||
m_inputPorts.clear();
|
||||
m_outputPorts.clear();
|
||||
}
|
||||
|
||||
// Clear only data input ports, keep execution ports
|
||||
void ClearDataInputPorts() {
|
||||
auto it = m_inputPorts.begin();
|
||||
while (it != m_inputPorts.end()) {
|
||||
if (it->type == Port::Type::DATA_PORT) {
|
||||
it = m_inputPorts.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear only data output ports, keep execution ports
|
||||
void ClearDataOutputPorts() {
|
||||
auto it = m_outputPorts.begin();
|
||||
while (it != m_outputPorts.end()) {
|
||||
if (it->type == Port::Type::DATA_PORT) {
|
||||
it = m_outputPorts.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClearAllInputPorts() {
|
||||
m_inputPorts.clear();
|
||||
}
|
||||
|
||||
void ClearAllOutputPorts() {
|
||||
m_outputPorts.clear();
|
||||
}
|
||||
|
||||
// Port management
|
||||
void AddInputPort(Port::Type type, const std::string& label, bool customRendering = false) {
|
||||
m_inputPorts.push_back({type, label, customRendering});
|
||||
}
|
||||
|
||||
void AddOutputPort(Port::Type type, const std::string& label, bool customRendering = false) {
|
||||
m_outputPorts.push_back({type, label, customRendering});
|
||||
}
|
||||
|
||||
bool HasOutputCustomRendering(uint32_t index) const {
|
||||
if (index < m_outputPorts.size()) {
|
||||
return m_outputPorts[index].customSocketIcon;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasInputCustomRendering(uint32_t index) const {
|
||||
if (index < m_inputPorts.size()) {
|
||||
return m_inputPorts[index].customSocketIcon;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Port GetInputPort(uint32_t index) const {
|
||||
if (index < m_inputPorts.size()) {
|
||||
return m_inputPorts[index];
|
||||
}
|
||||
return {Port::Type::EXECUTION_PORT, "?", false};
|
||||
}
|
||||
|
||||
Port GetOutputPort(uint32_t index) const {
|
||||
if (index < m_outputPorts.size()) {
|
||||
return m_outputPorts[index];
|
||||
}
|
||||
return {Port::Type::EXECUTION_PORT, "?", false};
|
||||
}
|
||||
|
||||
uint32_t OutputsCount() const {
|
||||
return m_outputPorts.size();
|
||||
}
|
||||
|
||||
uint32_t InputsCount() const {
|
||||
return m_inputPorts.size();
|
||||
}
|
||||
|
||||
void SetBehavior(Behavior behavior) {
|
||||
m_behavior = behavior;
|
||||
}
|
||||
Behavior GetBehavior() const {
|
||||
return m_behavior;
|
||||
}
|
||||
|
||||
bool IsExecutable() const {
|
||||
return m_behavior == BEHAVIOR_EXECUTION;
|
||||
}
|
||||
|
||||
void Accept(IVariableVisitor &visitor);
|
||||
|
||||
|
||||
protected:
|
||||
// Easy access the variables for children nodes
|
||||
// Key is the variable name, or whatever the node use to identify the variable
|
||||
std::map<std::string, std::shared_ptr<Variable>> m_variables;
|
||||
|
||||
private:
|
||||
std::string m_title{"Default title"};
|
||||
std::string m_type;
|
||||
std::string m_typeName;
|
||||
std::string m_uuid;
|
||||
NodePosition m_pos;
|
||||
int m_weight{0};
|
||||
Behavior m_behavior{BEHAVIOR_EXECUTION};
|
||||
|
||||
std::vector<Port> m_inputPorts;
|
||||
std::vector<Port> m_outputPorts;
|
||||
|
||||
nlohmann::json m_internal_data{{}};
|
||||
};
|
||||
|
||||
40
core/story-manager/src/nodes/branch_node.cpp
Normal file
40
core/story-manager/src/nodes/branch_node.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#include "branch_node.h"
|
||||
#include "story_project.h"
|
||||
#include "connection.h"
|
||||
#include "sys_lib.h"
|
||||
|
||||
BranchNode::BranchNode(const std::string &type)
|
||||
: BaseNode(type, "Branch Node")
|
||||
{
|
||||
// Configuration du nœud de branchement
|
||||
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
|
||||
|
||||
// Port d'entrée 0: Exécution (EXECUTION_PORT)
|
||||
// Port d'entrée 1: Condition (DATA_PORT)
|
||||
AddInputPort(Port::EXECUTION_PORT, ">", true);
|
||||
AddInputPort(Port::DATA_PORT, "condition");
|
||||
|
||||
// Port de sortie 0: Branche TRUE (EXECUTION_PORT)
|
||||
// Port de sortie 1: Branche FALSE (EXECUTION_PORT)
|
||||
AddOutputPort(Port::EXECUTION_PORT, "true", true);
|
||||
AddOutputPort(Port::EXECUTION_PORT, "false", true);
|
||||
|
||||
// Initialiser les données internes
|
||||
nlohmann::json j;
|
||||
j["node_type"] = "branch";
|
||||
j["version"] = 1;
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void BranchNode::Initialize()
|
||||
{
|
||||
// Charger les données sauvegardées
|
||||
nlohmann::json j = GetInternalData();
|
||||
|
||||
// Vérifier la version pour compatibilité future
|
||||
if (j.contains("version"))
|
||||
{
|
||||
int version = j["version"].get<int>();
|
||||
// Gérer les migrations de version si nécessaire
|
||||
}
|
||||
}
|
||||
37
core/story-manager/src/nodes/branch_node.h
Normal file
37
core/story-manager/src/nodes/branch_node.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "i_story_manager.h"
|
||||
#include "base_node.h"
|
||||
#include "i_script_node.h"
|
||||
#include "i_story_project.h"
|
||||
|
||||
/**
|
||||
* @brief Nœud de branchement conditionnel simple
|
||||
*
|
||||
* Ce nœud évalue une condition booléenne et exécute l'une des deux branches.
|
||||
* La condition provient d'un OperatorNode ou de toute autre source de données.
|
||||
*
|
||||
* Convention booléenne:
|
||||
* - 0 = false
|
||||
* - toute autre valeur = true
|
||||
*
|
||||
* Ports:
|
||||
* - Entrée 0: Flux d'exécution (EXECUTION_PORT)
|
||||
* - Entrée 1: Condition (DATA_PORT) - résultat d'un OperatorNode par exemple
|
||||
* - Sortie 0: Branche TRUE (EXECUTION_PORT)
|
||||
* - Sortie 1: Branche FALSE (EXECUTION_PORT)
|
||||
*
|
||||
* Utilisation typique:
|
||||
* [Variable A] ──┐
|
||||
* ├──> [OperatorNode >] ──> [BranchNode]
|
||||
* [Variable B] ──┘ ↓ ↓
|
||||
* TRUE FALSE
|
||||
*/
|
||||
class BranchNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
BranchNode(const std::string &type);
|
||||
|
||||
virtual void Initialize() override;
|
||||
};
|
||||
24
core/story-manager/src/nodes/break_node.cpp
Normal file
24
core/story-manager/src/nodes/break_node.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include "break_node.h"
|
||||
|
||||
BreakNode::BreakNode(const std::string &type)
|
||||
: BaseNode(type, "Break")
|
||||
{
|
||||
SetBehavior(Behavior::BEHAVIOR_EXECUTION);
|
||||
|
||||
// Port d'entrée 0: Exécution (EXECUTION_PORT)
|
||||
AddInputPort(Port::Type::EXECUTION_PORT, ">", true);
|
||||
|
||||
// Pas de port de sortie : le flux est redirigé vers la fin de la boucle
|
||||
// par le générateur TAC
|
||||
|
||||
// Initialiser les données internes
|
||||
nlohmann::json j;
|
||||
j["node_type"] = "break";
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void BreakNode::Initialize()
|
||||
{
|
||||
// Charger les données sauvegardées si nécessaire
|
||||
nlohmann::json j = GetInternalData();
|
||||
}
|
||||
12
core/story-manager/src/nodes/break_node.h
Normal file
12
core/story-manager/src/nodes/break_node.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "base_node.h"
|
||||
|
||||
class BreakNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
BreakNode(const std::string &type = "break-node");
|
||||
|
||||
void Initialize() override;
|
||||
};
|
||||
213
core/story-manager/src/nodes/call_function_node.h
Normal file
213
core/story-manager/src/nodes/call_function_node.h
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// call_function_node.h
|
||||
#pragma once
|
||||
|
||||
#include "base_node.h"
|
||||
#include <map>
|
||||
|
||||
class CallFunctionNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
enum InputBindingMode {
|
||||
MODE_CONNECTED, // Value comes from a connected pin
|
||||
MODE_CONSTANT // Value is a constant set in the UI
|
||||
};
|
||||
|
||||
struct InputBinding {
|
||||
std::string paramName;
|
||||
InputBindingMode mode;
|
||||
std::string constantValue;
|
||||
|
||||
nlohmann::json ToJson() const {
|
||||
return {
|
||||
{"paramName", paramName},
|
||||
{"mode", mode == MODE_CONNECTED ? "connected" : "constant"},
|
||||
{"constantValue", constantValue}
|
||||
};
|
||||
}
|
||||
|
||||
static InputBinding FromJson(const nlohmann::json& j) {
|
||||
InputBinding ib;
|
||||
ib.paramName = JsonReader::get<std::string>(j,"paramName", "");
|
||||
std::string modeStr = JsonReader::get<std::string>(j, "mode", "connected");
|
||||
ib.mode = (modeStr == "constant") ? MODE_CONSTANT : MODE_CONNECTED;
|
||||
ib.constantValue = JsonReader::get<std::string>(j, "constantValue", "");
|
||||
return ib;
|
||||
}
|
||||
};
|
||||
|
||||
struct OutputMapping {
|
||||
std::string returnValueName;
|
||||
std::string targetVariable; // Optional: map to a global variable
|
||||
|
||||
nlohmann::json ToJson() const {
|
||||
return {
|
||||
{"returnValueName", returnValueName},
|
||||
{"targetVariable", targetVariable}
|
||||
};
|
||||
}
|
||||
|
||||
static OutputMapping FromJson(const nlohmann::json& j) {
|
||||
OutputMapping om;
|
||||
om.returnValueName = JsonReader::get<std::string>(j, "returnValueName", "");
|
||||
om.targetVariable = JsonReader::get<std::string>(j, "targetVariable", "");
|
||||
return om;
|
||||
}
|
||||
};
|
||||
|
||||
CallFunctionNode(const std::string &type)
|
||||
: BaseNode(type, "Call Function Node")
|
||||
, m_functionName("")
|
||||
, m_functionUuid("")
|
||||
{
|
||||
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
|
||||
SetupExecutionPorts(true, 1, true); // 1 entrée, sorties dynamiques
|
||||
}
|
||||
|
||||
void Initialize() override {
|
||||
nlohmann::json j = GetInternalData();
|
||||
|
||||
if (j.contains("functionName")) {
|
||||
m_functionName = j["functionName"].get<std::string>();
|
||||
}
|
||||
if (j.contains("functionUuid")) {
|
||||
m_functionUuid = j["functionUuid"].get<std::string>();
|
||||
}
|
||||
|
||||
// Load input bindings
|
||||
m_inputBindings.clear();
|
||||
if (j.contains("inputBindings") && j["inputBindings"].is_array()) {
|
||||
for (const auto& ibJson : j["inputBindings"]) {
|
||||
m_inputBindings.push_back(InputBinding::FromJson(ibJson));
|
||||
}
|
||||
}
|
||||
|
||||
// Load output mappings
|
||||
m_outputMappings.clear();
|
||||
if (j.contains("outputMappings") && j["outputMappings"].is_array()) {
|
||||
for (const auto& omJson : j["outputMappings"]) {
|
||||
m_outputMappings.push_back(OutputMapping::FromJson(omJson));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetFunctionName() const {
|
||||
return m_functionName;
|
||||
}
|
||||
|
||||
std::string GetFunctionUuid() const {
|
||||
return m_functionUuid;
|
||||
}
|
||||
|
||||
void SetFunction(const std::string& uuid, const std::string& name) {
|
||||
m_functionUuid = uuid;
|
||||
m_functionName = name;
|
||||
SaveData();
|
||||
}
|
||||
|
||||
// Input bindings management
|
||||
const std::vector<InputBinding>& GetInputBindings() const {
|
||||
return m_inputBindings;
|
||||
}
|
||||
|
||||
void SetInputBindingMode(const std::string& paramName, InputBindingMode mode, const std::string& constantValue = "") {
|
||||
// Find or create binding
|
||||
auto it = std::find_if(m_inputBindings.begin(), m_inputBindings.end(),
|
||||
[¶mName](const InputBinding& ib) { return ib.paramName == paramName; });
|
||||
|
||||
if (it != m_inputBindings.end()) {
|
||||
it->mode = mode;
|
||||
it->constantValue = constantValue;
|
||||
} else {
|
||||
InputBinding ib;
|
||||
ib.paramName = paramName;
|
||||
ib.mode = mode;
|
||||
ib.constantValue = constantValue;
|
||||
m_inputBindings.push_back(ib);
|
||||
}
|
||||
|
||||
SaveData();
|
||||
}
|
||||
|
||||
InputBinding* GetInputBinding(const std::string& paramName) {
|
||||
auto it = std::find_if(m_inputBindings.begin(), m_inputBindings.end(),
|
||||
[¶mName](const InputBinding& ib) { return ib.paramName == paramName; });
|
||||
|
||||
return (it != m_inputBindings.end()) ? &(*it) : nullptr;
|
||||
}
|
||||
|
||||
// Output mappings management
|
||||
const std::vector<OutputMapping>& GetOutputMappings() const {
|
||||
return m_outputMappings;
|
||||
}
|
||||
|
||||
void SetOutputMapping(const std::string& returnValueName, const std::string& targetVariable) {
|
||||
auto it = std::find_if(m_outputMappings.begin(), m_outputMappings.end(),
|
||||
[&returnValueName](const OutputMapping& om) { return om.returnValueName == returnValueName; });
|
||||
|
||||
if (it != m_outputMappings.end()) {
|
||||
it->targetVariable = targetVariable;
|
||||
} else {
|
||||
OutputMapping om;
|
||||
om.returnValueName = returnValueName;
|
||||
om.targetVariable = targetVariable;
|
||||
m_outputMappings.push_back(om);
|
||||
}
|
||||
|
||||
SaveData();
|
||||
}
|
||||
|
||||
// Rebuild ports based on the module's interface
|
||||
void RebuildPortsFromModule(const std::vector<std::pair<std::string, std::string>>& parameters,
|
||||
const std::vector<std::pair<std::string, std::string>>& exitLabels,
|
||||
const std::map<std::string, std::vector<std::pair<std::string, std::string>>>& returnValuesByExit) {
|
||||
// Clear all ports except the main execution input
|
||||
ClearDataInputPorts();
|
||||
ClearAllOutputPorts();
|
||||
|
||||
// Add data input ports for each parameter
|
||||
for (const auto& param : parameters) {
|
||||
AddInputPort(Port::Type::DATA_PORT, param.first, false);
|
||||
}
|
||||
|
||||
// Add execution output ports for each exit
|
||||
for (const auto& exitLabel : exitLabels) {
|
||||
AddOutputPort(Port::Type::EXECUTION_PORT, exitLabel.first, true);
|
||||
}
|
||||
|
||||
// Add data output ports for all unique return values across all exits
|
||||
std::set<std::string> allReturnValues;
|
||||
for (const auto& exitPair : returnValuesByExit) {
|
||||
for (const auto& rv : exitPair.second) {
|
||||
allReturnValues.insert(rv.first);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& rvName : allReturnValues) {
|
||||
AddOutputPort(Port::Type::DATA_PORT, rvName, false);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_functionName;
|
||||
std::string m_functionUuid;
|
||||
std::vector<InputBinding> m_inputBindings;
|
||||
std::vector<OutputMapping> m_outputMappings;
|
||||
|
||||
void SaveData() {
|
||||
nlohmann::json j;
|
||||
j["functionName"] = m_functionName;
|
||||
j["functionUuid"] = m_functionUuid;
|
||||
|
||||
j["inputBindings"] = nlohmann::json::array();
|
||||
for (const auto& ib : m_inputBindings) {
|
||||
j["inputBindings"].push_back(ib.ToJson());
|
||||
}
|
||||
|
||||
j["outputMappings"] = nlohmann::json::array();
|
||||
for (const auto& om : m_outputMappings) {
|
||||
j["outputMappings"].push_back(om.ToJson());
|
||||
}
|
||||
|
||||
SetInternalData(j);
|
||||
}
|
||||
};
|
||||
26
core/story-manager/src/nodes/connection.cpp
Normal file
26
core/story-manager/src/nodes/connection.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#include "connection.h"
|
||||
|
||||
void to_json(nlohmann::json &j, const Connection &p) {
|
||||
j = nlohmann::json{
|
||||
{"outNodeId", p.outNodeId },
|
||||
{"outPortIndex", static_cast<int64_t>(p.outPortIndex)},
|
||||
{"inNodeId", p.inNodeId},
|
||||
{"inPortIndex", static_cast<int64_t>(p.inPortIndex)},
|
||||
{"type", static_cast<int>(p.type)}
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, Connection &p) {
|
||||
|
||||
p.outNodeId = j["outNodeId"].get<std::string>();
|
||||
p.inNodeId = j["inNodeId"].get<std::string>();
|
||||
p.outPortIndex = j["outPortIndex"].get<int>();
|
||||
p.inPortIndex = j["inPortIndex"].get<int>();
|
||||
|
||||
if (j.contains("type")) {
|
||||
p.type = static_cast<Connection::Type>(j["type"].get<int>());
|
||||
} else {
|
||||
// Par défaut EXECUTION_LINK pour les anciens fichiers
|
||||
p.type = Connection::EXECUTION_LINK;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,32 +5,56 @@
|
|||
|
||||
struct Connection
|
||||
{
|
||||
|
||||
enum Type
|
||||
{
|
||||
EXECUTION_LINK,
|
||||
DATA_LINK
|
||||
};
|
||||
|
||||
Connection()
|
||||
: outNodeId(0)
|
||||
, outPortIndex(0)
|
||||
, inNodeId(0)
|
||||
: outPortIndex(0)
|
||||
, inPortIndex(0)
|
||||
{
|
||||
|
||||
}
|
||||
unsigned int outNodeId{0};
|
||||
|
||||
~Connection() {
|
||||
|
||||
}
|
||||
|
||||
Connection::Type type{Connection::EXECUTION_LINK};
|
||||
std::string outNodeId;
|
||||
unsigned int outPortIndex{0};
|
||||
unsigned int inNodeId{0};
|
||||
std::string inNodeId;
|
||||
unsigned int inPortIndex{0};
|
||||
|
||||
Connection& operator=(const Connection& other) {
|
||||
this->outNodeId = other.outNodeId;
|
||||
this->outPortIndex = other.outPortIndex;
|
||||
this->inNodeId = other.inNodeId;
|
||||
this->inPortIndex = other.inPortIndex;
|
||||
return *this;
|
||||
Connection(const Connection &other)
|
||||
: type(other.type)
|
||||
, outNodeId(other.outNodeId)
|
||||
, outPortIndex(other.outPortIndex)
|
||||
, inNodeId(other.inNodeId)
|
||||
, inPortIndex(other.inPortIndex)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Connection& operator=(const Connection& other) {
|
||||
if (this != &other) {
|
||||
this->outNodeId = other.outNodeId;
|
||||
this->outPortIndex = other.outPortIndex;
|
||||
this->inNodeId = other.inNodeId;
|
||||
this->inPortIndex = other.inPortIndex;
|
||||
this->type = other.type;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
inline bool operator==(Connection const &a, Connection const &b)
|
||||
{
|
||||
return a.outNodeId == b.outNodeId && a.outPortIndex == b.outPortIndex
|
||||
&& a.inNodeId == b.inNodeId && a.inPortIndex == b.inPortIndex;
|
||||
&& a.inNodeId == b.inNodeId && a.inPortIndex == b.inPortIndex && a.type == b.type;
|
||||
}
|
||||
|
||||
inline bool operator!=(Connection const &a, Connection const &b)
|
||||
24
core/story-manager/src/nodes/continue_node.cpp
Normal file
24
core/story-manager/src/nodes/continue_node.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include "continue_node.h"
|
||||
|
||||
ContinueNode::ContinueNode(const std::string &type)
|
||||
: BaseNode(type, "Continue")
|
||||
{
|
||||
SetBehavior(Behavior::BEHAVIOR_EXECUTION);
|
||||
|
||||
// Port d'entrée 0: Exécution (EXECUTION_PORT)
|
||||
AddInputPort(Port::Type::EXECUTION_PORT, ">", true);
|
||||
|
||||
// Pas de port de sortie : le flux est redirigé vers le début de la boucle
|
||||
// par le générateur TAC
|
||||
|
||||
// Initialiser les données internes
|
||||
nlohmann::json j;
|
||||
j["node_type"] = "continue";
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void ContinueNode::Initialize()
|
||||
{
|
||||
// Charger les données sauvegardées si nécessaire
|
||||
nlohmann::json j = GetInternalData();
|
||||
}
|
||||
12
core/story-manager/src/nodes/continue_node.h
Normal file
12
core/story-manager/src/nodes/continue_node.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "base_node.h"
|
||||
|
||||
class ContinueNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
ContinueNode(const std::string &type = "continue-node");
|
||||
|
||||
void Initialize() override;
|
||||
};
|
||||
46
core/story-manager/src/nodes/for_loop_node.cpp
Normal file
46
core/story-manager/src/nodes/for_loop_node.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#include "for_loop_node.h"
|
||||
|
||||
ForLoopNode::ForLoopNode(const std::string &type)
|
||||
: BaseNode(type, "For Loop")
|
||||
{
|
||||
SetBehavior(Behavior::BEHAVIOR_EXECUTION);
|
||||
|
||||
// Port d'entrée 0: Exécution (EXECUTION_PORT)
|
||||
AddInputPort(Port::Type::EXECUTION_PORT, ">", true);
|
||||
|
||||
// Ports d'entrée pour les données
|
||||
AddInputPort(Port::Type::DATA_PORT, "start"); // Port 1
|
||||
AddInputPort(Port::Type::DATA_PORT, "end"); // Port 2
|
||||
AddInputPort(Port::Type::DATA_PORT, "step"); // Port 3
|
||||
|
||||
// Ports de sortie pour l'exécution
|
||||
AddOutputPort(Port::Type::EXECUTION_PORT, "body", true); // Port 0
|
||||
AddOutputPort(Port::Type::EXECUTION_PORT, "done", true); // Port 1
|
||||
|
||||
// Port de sortie pour l'index courant
|
||||
AddOutputPort(Port::Type::DATA_PORT, "index"); // Port 2
|
||||
|
||||
// Initialiser les données internes
|
||||
nlohmann::json j;
|
||||
j["start_index"] = m_startIndex;
|
||||
j["end_index"] = m_endIndex;
|
||||
j["step"] = m_step;
|
||||
j["node_type"] = "for_loop";
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void ForLoopNode::Initialize()
|
||||
{
|
||||
// Charger les données sauvegardées
|
||||
nlohmann::json j = GetInternalData();
|
||||
|
||||
if (j.contains("start_index")) {
|
||||
m_startIndex = j["start_index"].get<int>();
|
||||
}
|
||||
if (j.contains("end_index")) {
|
||||
m_endIndex = j["end_index"].get<int>();
|
||||
}
|
||||
if (j.contains("step")) {
|
||||
m_step = j["step"].get<int>();
|
||||
}
|
||||
}
|
||||
42
core/story-manager/src/nodes/for_loop_node.h
Normal file
42
core/story-manager/src/nodes/for_loop_node.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "base_node.h"
|
||||
|
||||
class ForLoopNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
ForLoopNode(const std::string &type = "for-loop-node");
|
||||
|
||||
void Initialize() override;
|
||||
|
||||
// Propriétés configurables (valeurs par défaut si non connectées)
|
||||
int GetStartIndex() const { return m_startIndex; }
|
||||
void SetStartIndex(int value) {
|
||||
m_startIndex = value;
|
||||
nlohmann::json j = GetInternalData();
|
||||
j["start_index"] = m_startIndex;
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
int GetEndIndex() const { return m_endIndex; }
|
||||
void SetEndIndex(int value) {
|
||||
m_endIndex = value;
|
||||
nlohmann::json j = GetInternalData();
|
||||
j["end_index"] = m_endIndex;
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
int GetStep() const { return m_step; }
|
||||
void SetStep(int value) {
|
||||
m_step = value;
|
||||
nlohmann::json j = GetInternalData();
|
||||
j["step"] = m_step;
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_startIndex{0};
|
||||
int m_endIndex{10};
|
||||
int m_step{1};
|
||||
};
|
||||
114
core/story-manager/src/nodes/function_entry_node.h
Normal file
114
core/story-manager/src/nodes/function_entry_node.h
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
#pragma once
|
||||
|
||||
#include "base_node.h"
|
||||
#include <vector>
|
||||
|
||||
class FunctionEntryNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
struct Parameter {
|
||||
std::string name;
|
||||
std::string type; // "int", "string", "bool", "float"
|
||||
std::string defaultValue;
|
||||
|
||||
nlohmann::json ToJson() const {
|
||||
return {
|
||||
{"name", name},
|
||||
{"type", type},
|
||||
{"defaultValue", defaultValue}
|
||||
};
|
||||
}
|
||||
|
||||
static Parameter FromJson(const nlohmann::json& j) {
|
||||
Parameter p;
|
||||
p.name = JsonReader::get<std::string>(j, "name", "");
|
||||
p.type = JsonReader::get<std::string>(j, "type", "int");
|
||||
p.defaultValue = JsonReader::get<std::string>(j, "defaultValue", "");
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
FunctionEntryNode(const std::string &type)
|
||||
: BaseNode(type, "Function Entry Node")
|
||||
{
|
||||
SetWeight(100);
|
||||
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
|
||||
SetupExecutionPorts(false, 1, true);
|
||||
}
|
||||
|
||||
void Initialize() override {
|
||||
// Load parameters from internal data
|
||||
nlohmann::json j = GetInternalData();
|
||||
m_parameters.clear();
|
||||
|
||||
if (j.contains("parameters") && j["parameters"].is_array()) {
|
||||
for (const auto& paramJson : j["parameters"]) {
|
||||
m_parameters.push_back(Parameter::FromJson(paramJson));
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild output ports for parameters
|
||||
RebuildParameterPorts();
|
||||
}
|
||||
|
||||
void AddParameter(const std::string& name, const std::string& type, const std::string& defaultValue = "") {
|
||||
Parameter param;
|
||||
param.name = name;
|
||||
param.type = type;
|
||||
param.defaultValue = defaultValue;
|
||||
m_parameters.push_back(param);
|
||||
|
||||
SaveParameters();
|
||||
RebuildParameterPorts();
|
||||
}
|
||||
|
||||
void RemoveParameter(size_t index) {
|
||||
if (index < m_parameters.size()) {
|
||||
m_parameters.erase(m_parameters.begin() + index);
|
||||
SaveParameters();
|
||||
RebuildParameterPorts();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateParameter(size_t index, const std::string& name, const std::string& type, const std::string& defaultValue) {
|
||||
if (index < m_parameters.size()) {
|
||||
m_parameters[index].name = name;
|
||||
m_parameters[index].type = type;
|
||||
m_parameters[index].defaultValue = defaultValue;
|
||||
SaveParameters();
|
||||
RebuildParameterPorts();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Parameter>& GetParameters() const {
|
||||
return m_parameters;
|
||||
}
|
||||
|
||||
size_t GetParameterCount() const {
|
||||
return m_parameters.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Parameter> m_parameters;
|
||||
|
||||
void SaveParameters() {
|
||||
nlohmann::json j;
|
||||
j["parameters"] = nlohmann::json::array();
|
||||
|
||||
for (const auto& param : m_parameters) {
|
||||
j["parameters"].push_back(param.ToJson());
|
||||
}
|
||||
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void RebuildParameterPorts() {
|
||||
// Clear all data output ports (keep execution port)
|
||||
ClearDataOutputPorts();
|
||||
|
||||
// Add a data output port for each parameter
|
||||
for (const auto& param : m_parameters) {
|
||||
AddOutputPort(Port::Type::DATA_PORT, param.name, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
125
core/story-manager/src/nodes/function_exit_node.h
Normal file
125
core/story-manager/src/nodes/function_exit_node.h
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#pragma once
|
||||
|
||||
#include "base_node.h"
|
||||
#include <vector>
|
||||
|
||||
class FunctionExitNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
struct ReturnValue {
|
||||
std::string name;
|
||||
std::string type; // "int", "string", "bool", "float"
|
||||
|
||||
nlohmann::json ToJson() const {
|
||||
return {
|
||||
{"name", name},
|
||||
{"type", type}
|
||||
};
|
||||
}
|
||||
|
||||
static ReturnValue FromJson(const nlohmann::json& j) {
|
||||
ReturnValue rv;
|
||||
rv.name = JsonReader::get<std::string>(j, "name", "");
|
||||
rv.type = JsonReader::get<std::string>(j, "type", "int");
|
||||
return rv;
|
||||
}
|
||||
};
|
||||
|
||||
FunctionExitNode(const std::string &type)
|
||||
: BaseNode(type, "Function Exit Node")
|
||||
, m_exitLabel("Return")
|
||||
{
|
||||
SetWeight(900); // High weight, near the end
|
||||
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
|
||||
SetupExecutionPorts(true, 0, true); // Has input, no output (it's the end)
|
||||
|
||||
SaveData();
|
||||
}
|
||||
|
||||
void Initialize() override {
|
||||
// Load return values and exit label from internal data
|
||||
nlohmann::json j = GetInternalData();
|
||||
m_returnValues.clear();
|
||||
|
||||
m_exitLabel = JsonReader::get<std::string>(j, "exitLabel", "Return");
|
||||
|
||||
if (j.contains("returnValues") && j["returnValues"].is_array()) {
|
||||
for (const auto& rvJson : j["returnValues"]) {
|
||||
m_returnValues.push_back(ReturnValue::FromJson(rvJson));
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild input ports for return values
|
||||
RebuildReturnValuePorts();
|
||||
}
|
||||
|
||||
void SetExitLabel(const std::string& label) {
|
||||
m_exitLabel = label;
|
||||
SaveData();
|
||||
}
|
||||
|
||||
std::string GetExitLabel() const {
|
||||
return m_exitLabel;
|
||||
}
|
||||
|
||||
void AddReturnValue(const std::string& name, const std::string& type) {
|
||||
ReturnValue rv;
|
||||
rv.name = name;
|
||||
rv.type = type;
|
||||
m_returnValues.push_back(rv);
|
||||
|
||||
SaveData();
|
||||
RebuildReturnValuePorts();
|
||||
}
|
||||
|
||||
void RemoveReturnValue(size_t index) {
|
||||
if (index < m_returnValues.size()) {
|
||||
m_returnValues.erase(m_returnValues.begin() + index);
|
||||
SaveData();
|
||||
RebuildReturnValuePorts();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateReturnValue(size_t index, const std::string& name, const std::string& type) {
|
||||
if (index < m_returnValues.size()) {
|
||||
m_returnValues[index].name = name;
|
||||
m_returnValues[index].type = type;
|
||||
SaveData();
|
||||
RebuildReturnValuePorts();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ReturnValue>& GetReturnValues() const {
|
||||
return m_returnValues;
|
||||
}
|
||||
|
||||
size_t GetReturnValueCount() const {
|
||||
return m_returnValues.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_exitLabel;
|
||||
std::vector<ReturnValue> m_returnValues;
|
||||
|
||||
void SaveData() {
|
||||
nlohmann::json j;
|
||||
j["exitLabel"] = m_exitLabel;
|
||||
j["returnValues"] = nlohmann::json::array();
|
||||
|
||||
for (const auto& rv : m_returnValues) {
|
||||
j["returnValues"].push_back(rv.ToJson());
|
||||
}
|
||||
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void RebuildReturnValuePorts() {
|
||||
// Clear all data input ports (keep execution port)
|
||||
ClearDataInputPorts();
|
||||
|
||||
// Add a data input port for each return value
|
||||
for (const auto& rv : m_returnValues) {
|
||||
AddInputPort(Port::Type::DATA_PORT, rv.name, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
20
core/story-manager/src/nodes/module_node.cpp
Normal file
20
core/story-manager/src/nodes/module_node.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#include "module_node.h"
|
||||
#include "story_project.h"
|
||||
#include "connection.h"
|
||||
#include "sys_lib.h"
|
||||
|
||||
|
||||
ModuleNode::ModuleNode(const std::string &type)
|
||||
: BaseNode(type, "Module Node")
|
||||
{
|
||||
nlohmann::json j{ {"module", ""} };
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void ModuleNode::Initialize()
|
||||
{
|
||||
nlohmann::json j = GetInternalData();
|
||||
// m_image = j["image"].get<std::string>();
|
||||
// m_sound = j["sound"].get<std::string>();
|
||||
}
|
||||
|
||||
23
core/story-manager/src/nodes/module_node.h
Normal file
23
core/story-manager/src/nodes/module_node.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "i_story_manager.h"
|
||||
#include "base_node.h"
|
||||
#include "i_script_node.h"
|
||||
#include "i_story_project.h"
|
||||
|
||||
class ModuleNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
ModuleNode(const std::string &type);
|
||||
|
||||
virtual void Initialize() override;
|
||||
|
||||
void SetContent(nlohmann::json &content) {
|
||||
m_content = content;
|
||||
}
|
||||
|
||||
private:
|
||||
nlohmann::json m_content;
|
||||
};
|
||||
|
||||
153
core/story-manager/src/nodes/operator_node.h
Normal file
153
core/story-manager/src/nodes/operator_node.h
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#pragma once
|
||||
|
||||
#include "base_node.h"
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
class OperatorNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
enum class OperationType {
|
||||
ADD, // Addition (+)
|
||||
SUBTRACT, // Subtraction (-)
|
||||
MULTIPLY, // Multiplication (*)
|
||||
DIVIDE, // Division (/)
|
||||
MODULO, // Modulo (%)
|
||||
AND, // Logical AND (&&)
|
||||
OR, // Logical OR (||)
|
||||
NOT, // Logical NOT (!)
|
||||
XOR, // Logical XOR (^)
|
||||
EQUAL, // Equal to (==)
|
||||
NOT_EQUAL, // Not equal to (!=)
|
||||
GREATER_THAN, // Greater than (>)
|
||||
LESS_THAN, // Less than (<)
|
||||
GREATER_EQUAL, // Greater than or equal (>=)
|
||||
LESS_EQUAL, // Less than or equal (<=)
|
||||
BITWISE_AND, // Bitwise AND (&)
|
||||
BITWISE_OR, // Bitwise OR (|)
|
||||
BITWISE_XOR, // Bitwise XOR (^)
|
||||
BITWISE_NOT, // Bitwise NOT (~)
|
||||
LEFT_SHIFT, // Left shift (<<)
|
||||
RIGHT_SHIFT // Right shift (>>)
|
||||
};
|
||||
|
||||
struct OperatorDesc {
|
||||
std::string name;
|
||||
std::string symbol;
|
||||
};
|
||||
|
||||
const std::unordered_map<OperationType, OperatorDesc>& GetOperators() const
|
||||
{
|
||||
static const std::unordered_map<OperationType, OperatorDesc> Operators = {
|
||||
{OperationType::ADD, {"Addition", "+"}},
|
||||
{OperationType::SUBTRACT, {"Subtraction", "-"}},
|
||||
{OperationType::MULTIPLY, {"Multiplication", "*"}},
|
||||
{OperationType::DIVIDE, {"Division", "/"}},
|
||||
{OperationType::MODULO, {"Modulo", "%"}},
|
||||
{OperationType::AND, {"Logical AND", "&&"}},
|
||||
{OperationType::OR, {"Logical OR", "||"}},
|
||||
{OperationType::NOT, {"Logical NOT", "!"}},
|
||||
{OperationType::XOR, {"Logical XOR", "^"}},
|
||||
{OperationType::EQUAL, {"Equal to", "=="}},
|
||||
{OperationType::NOT_EQUAL, {"Not equal to", "!="}},
|
||||
{OperationType::GREATER_THAN, {"Greater than", ">"}},
|
||||
{OperationType::LESS_THAN, {"Less than", "<"}},
|
||||
{OperationType::GREATER_EQUAL, {"Greater than or equal to", ">="}},
|
||||
{OperationType::LESS_EQUAL, {"Less than or equal to", "<="}},
|
||||
{OperationType::BITWISE_AND, {"Bitwise AND", "&"}},
|
||||
{OperationType::BITWISE_OR, {"Bitwise OR", "|"}},
|
||||
{OperationType::BITWISE_XOR, {"Bitwise XOR", "^"}},
|
||||
{OperationType::BITWISE_NOT, {"Bitwise NOT", "~"}},
|
||||
{OperationType::LEFT_SHIFT, {"Left shift", "<<"}},
|
||||
{OperationType::RIGHT_SHIFT, {"Right shift", ">>"}}
|
||||
};
|
||||
return Operators;
|
||||
}
|
||||
|
||||
OperatorNode(const std::string& type = "operator-node",
|
||||
const std::string& typeName = "Operator")
|
||||
: BaseNode(type, typeName, BaseNode::Behavior::BEHAVIOR_DATA)
|
||||
, m_operationType(OperationType::ADD)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Initialize() override {
|
||||
// Define input ports based on operator type
|
||||
nlohmann::json j = GetInternalData();
|
||||
std::string typeName = JsonReader::get<std::string>(j, "operation_type", "Equal to");
|
||||
m_operationType = TypeNameToOperationType(typeName);
|
||||
UpdatePorts();
|
||||
}
|
||||
|
||||
// Set the operator type and update ports accordingly
|
||||
void SetOperationType(OperationType type) {
|
||||
m_operationType = type;
|
||||
UpdatePorts();
|
||||
SetInternalData({"operation_type", OperatorTypeName(m_operationType)});
|
||||
}
|
||||
|
||||
OperationType GetOperationType() const {
|
||||
return m_operationType;
|
||||
}
|
||||
|
||||
// Get the symbol representation of the operator
|
||||
std::string GetOperatorSymbol() const {
|
||||
auto symbolMap = GetOperators();
|
||||
auto it = symbolMap.find(m_operationType);
|
||||
return it != symbolMap.end() ? it->second.symbol : "?";
|
||||
}
|
||||
|
||||
private:
|
||||
OperationType m_operationType;
|
||||
|
||||
bool IsUnaryOperator() const {
|
||||
return m_operationType == OperationType::NOT ||
|
||||
m_operationType == OperationType::BITWISE_NOT;
|
||||
}
|
||||
|
||||
void UpdatePorts() {
|
||||
// Clear existing ports
|
||||
ClearPorts();
|
||||
|
||||
// Add input ports based on operator type
|
||||
if (IsUnaryOperator()) {
|
||||
AddInputPort(Port::DATA_PORT, "in");
|
||||
} else {
|
||||
AddInputPort(Port::DATA_PORT, "in1");
|
||||
AddInputPort(Port::DATA_PORT, "in2");
|
||||
}
|
||||
|
||||
// Add output port
|
||||
AddOutputPort(Port::DATA_PORT, "out");
|
||||
}
|
||||
|
||||
std::string OperatorTypeName(OperationType type) const {
|
||||
auto symbolMap = GetOperators();
|
||||
auto it = symbolMap.find(type);
|
||||
return it != symbolMap.end() ? it->second.name : "Unknown";
|
||||
}
|
||||
|
||||
OperationType SymbolToOperationType(const std::string& str) const {
|
||||
auto symbolMap = GetOperators();
|
||||
|
||||
for (const auto& pair : symbolMap) {
|
||||
if (pair.second.symbol == str) {
|
||||
return pair.first;
|
||||
}
|
||||
}
|
||||
return OperationType::ADD; // Default to ADD if not found
|
||||
}
|
||||
|
||||
OperationType TypeNameToOperationType(const std::string& str) const {
|
||||
auto symbolMap = GetOperators();
|
||||
|
||||
for (const auto& pair : symbolMap) {
|
||||
if (pair.second.name == str) {
|
||||
return pair.first;
|
||||
}
|
||||
}
|
||||
return OperationType::ADD; // Default to ADD if not found
|
||||
}
|
||||
};
|
||||
35
core/story-manager/src/nodes/play_media_node.cpp
Normal file
35
core/story-manager/src/nodes/play_media_node.cpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// core/story-manager/src/nodes/play_media_node.cpp
|
||||
#include "play_media_node.h"
|
||||
|
||||
PlayMediaNode::PlayMediaNode(const std::string &type)
|
||||
: BaseNode(type, "Play Media")
|
||||
{
|
||||
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
|
||||
SetupExecutionPorts(true, 1, true); // IN 0 (exec), OUT 0 (exec)
|
||||
|
||||
// Ports data
|
||||
AddInputPort(Port::Type::DATA_PORT, "Image");
|
||||
AddInputPort(Port::Type::DATA_PORT, "Sound");
|
||||
|
||||
nlohmann::json j{
|
||||
{"status_variable_uuid", m_statusVariableUuid}
|
||||
};
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void PlayMediaNode::Initialize()
|
||||
{
|
||||
nlohmann::json j = GetInternalData();
|
||||
|
||||
if (j.contains("status_variable_uuid")) {
|
||||
m_statusVariableUuid = j["status_variable_uuid"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayMediaNode::SetStatusVariable(const std::string& varUuid)
|
||||
{
|
||||
m_statusVariableUuid = varUuid;
|
||||
nlohmann::json j = GetInternalData();
|
||||
j["status_variable_uuid"] = m_statusVariableUuid;
|
||||
SetInternalData(j);
|
||||
}
|
||||
21
core/story-manager/src/nodes/play_media_node.h
Normal file
21
core/story-manager/src/nodes/play_media_node.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// core/story-manager/src/nodes/play_media_node.h
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "base_node.h"
|
||||
#include "i_story_project.h"
|
||||
|
||||
class PlayMediaNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
PlayMediaNode(const std::string &type = "play-media-node");
|
||||
|
||||
void Initialize() override;
|
||||
|
||||
// Variable optionnelle pour le status de retour
|
||||
std::string GetStatusVariableUuid() const { return m_statusVariableUuid; }
|
||||
void SetStatusVariable(const std::string& varUuid);
|
||||
|
||||
private:
|
||||
std::string m_statusVariableUuid; // Optionnel
|
||||
};
|
||||
47
core/story-manager/src/nodes/print_node.cpp
Normal file
47
core/story-manager/src/nodes/print_node.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#include "print_node.h"
|
||||
#include "story_project.h"
|
||||
#include "connection.h"
|
||||
#include "sys_lib.h"
|
||||
#include "variable.h"
|
||||
|
||||
PrintNode::PrintNode(const std::string &type)
|
||||
: BaseNode(type, "Print Node")
|
||||
{
|
||||
// Create empty variable in memory for the format string
|
||||
auto v = std::make_shared<Variable>(m_label);
|
||||
v->SetConstant(true);
|
||||
m_label = v->GetLabel();
|
||||
m_variables[m_label] = v;
|
||||
|
||||
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
|
||||
SetupExecutionPorts(true, 1, true);
|
||||
|
||||
// Add 4 data input ports for arguments
|
||||
for (int i = 0; i < MAX_INPUT_COUNT; ++i) {
|
||||
AddInputPort(Port::DATA_PORT, "arg" + std::to_string(i));
|
||||
}
|
||||
|
||||
SetText("");
|
||||
}
|
||||
|
||||
void PrintNode::Initialize()
|
||||
{
|
||||
nlohmann::json j = GetInternalData();
|
||||
m_variables.at(m_label)->SetTextValue(j["text"].get<std::string>());
|
||||
}
|
||||
|
||||
void PrintNode::SetText(const std::string &text)
|
||||
{
|
||||
m_variables.at(m_label)->SetTextValue(text);
|
||||
SetInternalData({{"text", text}});
|
||||
}
|
||||
|
||||
std::string PrintNode::GetLabel() const
|
||||
{
|
||||
return m_label;
|
||||
}
|
||||
|
||||
std::string PrintNode::GetText() const
|
||||
{
|
||||
return m_variables.at(m_label)->GetValue<std::string>();
|
||||
}
|
||||
32
core/story-manager/src/nodes/print_node.h
Normal file
32
core/story-manager/src/nodes/print_node.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "i_story_manager.h"
|
||||
#include "base_node.h"
|
||||
#include "i_script_node.h"
|
||||
#include "i_story_project.h"
|
||||
|
||||
class PrintNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
PrintNode(const std::string &type);
|
||||
|
||||
virtual void Initialize() override;
|
||||
|
||||
void SetText(const std::string &text);
|
||||
std::string GetLabel() const;
|
||||
std::string GetText() const;
|
||||
|
||||
static constexpr int MAX_INPUT_COUNT = 4;
|
||||
|
||||
std::shared_ptr<Variable> GetVariable(const std::string& label) const {
|
||||
auto it = m_variables.find(label);
|
||||
if (it != m_variables.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_label; // Label for the string literal
|
||||
};
|
||||
29
core/story-manager/src/nodes/send_signal_node.cpp
Normal file
29
core/story-manager/src/nodes/send_signal_node.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// core/story-manager/src/nodes/send_signal_node.cpp
|
||||
#include "send_signal_node.h"
|
||||
|
||||
SendSignalNode::SendSignalNode(const std::string &type)
|
||||
: BaseNode(type, "Send Signal")
|
||||
{
|
||||
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
|
||||
SetupExecutionPorts(true, 1, true); // IN 0, OUT 0
|
||||
|
||||
AddInputPort(Port::Type::DATA_PORT, "Signal ID");
|
||||
|
||||
nlohmann::json j{{"signal_id", m_signalId}};
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void SendSignalNode::Initialize()
|
||||
{
|
||||
nlohmann::json j = GetInternalData();
|
||||
if (j.contains("signal_id")) {
|
||||
m_signalId = j["signal_id"].get<uint32_t>();
|
||||
}
|
||||
}
|
||||
|
||||
void SendSignalNode::SetSignalId(uint32_t id)
|
||||
{
|
||||
m_signalId = id;
|
||||
nlohmann::json j{{"signal_id", m_signalId}};
|
||||
SetInternalData(j);
|
||||
}
|
||||
21
core/story-manager/src/nodes/send_signal_node.h
Normal file
21
core/story-manager/src/nodes/send_signal_node.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// core/story-manager/src/nodes/send_signal_node.h
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "base_node.h"
|
||||
#include "i_story_project.h"
|
||||
|
||||
class SendSignalNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
SendSignalNode(const std::string &type = "send-signal-node");
|
||||
|
||||
void Initialize() override;
|
||||
|
||||
// ID du signal à envoyer
|
||||
uint32_t GetSignalId() const { return m_signalId; }
|
||||
void SetSignalId(uint32_t id);
|
||||
|
||||
private:
|
||||
uint32_t m_signalId{0};
|
||||
};
|
||||
77
core/story-manager/src/nodes/variable_node.cpp
Normal file
77
core/story-manager/src/nodes/variable_node.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#include "variable_node.h"
|
||||
#include "story_project.h"
|
||||
#include "connection.h"
|
||||
#include "sys_lib.h"
|
||||
|
||||
VariableNode::VariableNode(const std::string &type)
|
||||
: BaseNode(type, "Variable Node", BaseNode::BEHAVIOR_DATA)
|
||||
{
|
||||
nlohmann::json j{ {"uuid", ""} };
|
||||
SetInternalData(j);
|
||||
|
||||
// Add data output port
|
||||
AddOutputPort(Port::DATA_PORT, "value");
|
||||
}
|
||||
|
||||
void VariableNode::Initialize()
|
||||
{
|
||||
nlohmann::json j = GetInternalData();
|
||||
m_variableUuid = j["uuid"].get<std::string>();
|
||||
}
|
||||
|
||||
void VariableNode::SetVariableUuid(const std::string &uuid)
|
||||
{
|
||||
m_variableUuid = uuid;
|
||||
|
||||
nlohmann::json j;
|
||||
j["uuid"] = m_variableUuid;
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
std::string VariableNode::GetVariableUuid() const
|
||||
{
|
||||
return m_variableUuid;
|
||||
}
|
||||
|
||||
void VariableNode::SetVariable(std::shared_ptr<Variable> var)
|
||||
{
|
||||
m_variable = var;
|
||||
if (var) {
|
||||
SetVariableUuid(var->GetUuid());
|
||||
SetTitle(var->GetLabel());
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Variable> VariableNode::GetVariable() const
|
||||
{
|
||||
return m_variable;
|
||||
}
|
||||
|
||||
bool VariableNode::ResolveVariable(const std::vector<std::shared_ptr<Variable>>& variables)
|
||||
{
|
||||
// Si la variable est déjà résolue, pas besoin de chercher
|
||||
if (m_variable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Si pas d'UUID, impossible de résoudre
|
||||
if (m_variableUuid.empty()) {
|
||||
std::cout << "WARNING: VariableNode " << GetId()
|
||||
<< " has no variable UUID!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Chercher la variable correspondant à l'UUID
|
||||
for (const auto& var : variables) {
|
||||
if (var->GetUuid() == m_variableUuid) {
|
||||
m_variable = var;
|
||||
std::cout << "✓ Resolved variable '" << var->GetVariableName()
|
||||
<< "' for VariableNode " << GetId() << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "ERROR: Could not resolve variable UUID " << m_variableUuid
|
||||
<< " for VariableNode " << GetId() << std::endl;
|
||||
return false;
|
||||
}
|
||||
28
core/story-manager/src/nodes/variable_node.h
Normal file
28
core/story-manager/src/nodes/variable_node.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "i_story_manager.h"
|
||||
#include "base_node.h"
|
||||
#include "i_script_node.h"
|
||||
#include "i_story_project.h"
|
||||
#include "variable.h"
|
||||
|
||||
class VariableNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
VariableNode(const std::string &type);
|
||||
|
||||
virtual void Initialize() override;
|
||||
|
||||
void SetVariableUuid(const std::string &uuid);
|
||||
std::string GetVariableUuid() const;
|
||||
|
||||
void SetVariable(std::shared_ptr<Variable> var);
|
||||
std::shared_ptr<Variable> GetVariable() const;
|
||||
|
||||
bool ResolveVariable(const std::vector<std::shared_ptr<Variable>>& variables);
|
||||
|
||||
private:
|
||||
std::string m_variableUuid;
|
||||
std::shared_ptr<Variable> m_variable;
|
||||
};
|
||||
29
core/story-manager/src/nodes/wait_delay_node.cpp
Normal file
29
core/story-manager/src/nodes/wait_delay_node.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// core/story-manager/src/nodes/wait_delay_node.cpp
|
||||
#include "wait_delay_node.h"
|
||||
|
||||
WaitDelayNode::WaitDelayNode(const std::string &type)
|
||||
: BaseNode(type, "Wait Delay")
|
||||
{
|
||||
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
|
||||
SetupExecutionPorts(true, 1, true); // IN 0, OUT 0
|
||||
|
||||
AddInputPort(Port::Type::DATA_PORT, "Duration");
|
||||
|
||||
nlohmann::json j{{"duration_ms", m_durationMs}};
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void WaitDelayNode::Initialize()
|
||||
{
|
||||
nlohmann::json j = GetInternalData();
|
||||
if (j.contains("duration_ms")) {
|
||||
m_durationMs = j["duration_ms"].get<uint32_t>();
|
||||
}
|
||||
}
|
||||
|
||||
void WaitDelayNode::SetDuration(uint32_t ms)
|
||||
{
|
||||
m_durationMs = ms;
|
||||
nlohmann::json j{{"duration_ms", m_durationMs}};
|
||||
SetInternalData(j);
|
||||
}
|
||||
21
core/story-manager/src/nodes/wait_delay_node.h
Normal file
21
core/story-manager/src/nodes/wait_delay_node.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// core/story-manager/src/nodes/wait_delay_node.h
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "base_node.h"
|
||||
#include "i_story_project.h"
|
||||
|
||||
class WaitDelayNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
WaitDelayNode(const std::string &type = "wait-delay-node");
|
||||
|
||||
void Initialize() override;
|
||||
|
||||
// Durée en millisecondes
|
||||
uint32_t GetDuration() const { return m_durationMs; }
|
||||
void SetDuration(uint32_t ms);
|
||||
|
||||
private:
|
||||
uint32_t m_durationMs{1000};
|
||||
};
|
||||
74
core/story-manager/src/nodes/wait_event_node.cpp
Normal file
74
core/story-manager/src/nodes/wait_event_node.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// core/story-manager/src/nodes/wait_event_node.cpp
|
||||
#include "wait_event_node.h"
|
||||
|
||||
WaitEventNode::WaitEventNode(const std::string &type)
|
||||
: BaseNode(type, "Wait Event")
|
||||
{
|
||||
SetBehavior(BaseNode::BEHAVIOR_EXECUTION);
|
||||
SetupExecutionPorts(true, 1, true); // IN 0 (exec), OUT 0 (exec)
|
||||
|
||||
AddInputPort(Port::Type::DATA_PORT, "Event Mask");
|
||||
AddInputPort(Port::Type::DATA_PORT, "Timeout");
|
||||
|
||||
nlohmann::json j{
|
||||
{"event_mask", m_eventMask},
|
||||
{"timeout", m_timeout},
|
||||
{"result_variable_uuid", m_resultVariableUuid}
|
||||
};
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void WaitEventNode::Initialize()
|
||||
{
|
||||
nlohmann::json j = GetInternalData();
|
||||
|
||||
if (j.contains("event_mask")) {
|
||||
m_eventMask = j["event_mask"].get<uint32_t>();
|
||||
}
|
||||
if (j.contains("timeout")) {
|
||||
m_timeout = j["timeout"].get<uint32_t>();
|
||||
}
|
||||
if (j.contains("result_variable_uuid")) {
|
||||
m_resultVariableUuid = j["result_variable_uuid"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
void WaitEventNode::SetEventMask(uint32_t mask)
|
||||
{
|
||||
m_eventMask = mask;
|
||||
nlohmann::json j = GetInternalData();
|
||||
j["event_mask"] = m_eventMask;
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void WaitEventNode::SetTimeout(uint32_t ms)
|
||||
{
|
||||
m_timeout = ms;
|
||||
nlohmann::json j = GetInternalData();
|
||||
j["timeout"] = m_timeout;
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void WaitEventNode::SetResultVariable(const std::string& varUuid)
|
||||
{
|
||||
m_resultVariableUuid = varUuid;
|
||||
nlohmann::json j = GetInternalData();
|
||||
j["result_variable_uuid"] = m_resultVariableUuid;
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
std::shared_ptr<Variable> WaitEventNode::ResolveResultVariable(
|
||||
const std::vector<std::shared_ptr<Variable>>& variables) const
|
||||
{
|
||||
if (m_resultVariableUuid.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& var : variables) {
|
||||
if (var->GetUuid() == m_resultVariableUuid) {
|
||||
return var;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
35
core/story-manager/src/nodes/wait_event_node.h
Normal file
35
core/story-manager/src/nodes/wait_event_node.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// core/story-manager/src/nodes/wait_event_node.h
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "base_node.h"
|
||||
#include "i_story_project.h"
|
||||
#include "variable.h"
|
||||
|
||||
class WaitEventNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
WaitEventNode(const std::string &type = "wait-event-node");
|
||||
|
||||
void Initialize() override;
|
||||
|
||||
// Event mask (bits pour différents types d'événements)
|
||||
uint32_t GetEventMask() const { return m_eventMask; }
|
||||
void SetEventMask(uint32_t mask);
|
||||
|
||||
// Timeout en millisecondes (0 = infini)
|
||||
uint32_t GetTimeout() const { return m_timeout; }
|
||||
void SetTimeout(uint32_t ms);
|
||||
|
||||
// Variable de destination pour le code de retour
|
||||
std::string GetResultVariableUuid() const { return m_resultVariableUuid; }
|
||||
void SetResultVariable(const std::string& varUuid);
|
||||
|
||||
// Helper pour résoudre la variable (utilisé par TAC Generator)
|
||||
std::shared_ptr<Variable> ResolveResultVariable(const std::vector<std::shared_ptr<Variable>>& variables) const;
|
||||
|
||||
private:
|
||||
uint32_t m_eventMask{0b10000000000}; // Défaut: OK + Home + EndAudio
|
||||
uint32_t m_timeout{0}; // 0 = infini
|
||||
std::string m_resultVariableUuid;
|
||||
};
|
||||
28
core/story-manager/src/nodes/while_loop_node.cpp
Normal file
28
core/story-manager/src/nodes/while_loop_node.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#include "while_loop_node.h"
|
||||
|
||||
WhileLoopNode::WhileLoopNode(const std::string &type)
|
||||
: BaseNode(type, "While Loop")
|
||||
{
|
||||
SetBehavior(Behavior::BEHAVIOR_EXECUTION);
|
||||
|
||||
// Port d'entrée 0: Exécution (EXECUTION_PORT)
|
||||
AddInputPort(Port::Type::EXECUTION_PORT, ">", true);
|
||||
|
||||
// Port d'entrée 1: Condition (DATA_PORT)
|
||||
AddInputPort(Port::Type::DATA_PORT, "condition");
|
||||
|
||||
// Ports de sortie pour l'exécution
|
||||
AddOutputPort(Port::Type::EXECUTION_PORT, "body", true); // Port 0
|
||||
AddOutputPort(Port::Type::EXECUTION_PORT, "done", true); // Port 1
|
||||
|
||||
// Initialiser les données internes
|
||||
nlohmann::json j;
|
||||
j["node_type"] = "while_loop";
|
||||
SetInternalData(j);
|
||||
}
|
||||
|
||||
void WhileLoopNode::Initialize()
|
||||
{
|
||||
// Charger les données sauvegardées si nécessaire
|
||||
nlohmann::json j = GetInternalData();
|
||||
}
|
||||
12
core/story-manager/src/nodes/while_loop_node.h
Normal file
12
core/story-manager/src/nodes/while_loop_node.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "base_node.h"
|
||||
|
||||
class WhileLoopNode : public BaseNode
|
||||
{
|
||||
public:
|
||||
WhileLoopNode(const std::string &type = "while-loop-node");
|
||||
|
||||
void Initialize() override;
|
||||
};
|
||||
252
core/story-manager/src/nodes_factory.h
Normal file
252
core/story-manager/src/nodes_factory.h
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <filesystem>
|
||||
|
||||
#include "json.hpp"
|
||||
// #include "media_node.h"
|
||||
#include "call_function_node.h"
|
||||
#include "module_node.h"
|
||||
#include "variable_node.h"
|
||||
#include "operator_node.h"
|
||||
#include "print_node.h"
|
||||
#include "story_project.h"
|
||||
#include "story_primitive.h"
|
||||
#include "function_entry_node.h"
|
||||
#include "branch_node.h"
|
||||
#include "wait_delay_node.h"
|
||||
#include "wait_event_node.h"
|
||||
#include "play_media_node.h"
|
||||
#include "send_signal_node.h"
|
||||
#include "for_loop_node.h"
|
||||
#include "while_loop_node.h"
|
||||
#include "break_node.h"
|
||||
#include "continue_node.h"
|
||||
#include "function_exit_node.h"
|
||||
|
||||
|
||||
static constexpr const char* OperatorNodeUuid = "0226fdac-8f7a-47d7-8584-b23aceb712ec";
|
||||
static constexpr const char* CallFunctionNodeUuid = "02745f38-9b11-49fe-94b1-b2a6b78249fb";
|
||||
static constexpr const char* VariableNodeUuid = "020cca4e-9cdc-47e7-a6a5-53e4c9152ed0";
|
||||
static constexpr const char* PrintNodeUuid = "02ee27bc-ff1d-4f94-b700-eab55052ad1c";
|
||||
static constexpr const char* FunctionEntryNodeUuid = "02fd145a-b3a6-43c2-83ce-6a187e6d4b5b";
|
||||
static constexpr const char* BranchNodeUuid = "027b723d-2327-4646-a17a-79ddc2e016e4";
|
||||
static constexpr const char* WaitEventNodeUuid = "02225cff-4975-400e-8130-41524d8af773";
|
||||
static constexpr const char* WaitDelayNodeUuid = "02455ef0-4975-4546-94de-720cae6baae3";
|
||||
static constexpr const char* PlayMediaNodeUuid = "0285e90a-2eb7-4605-baa9-b3712a14dff8";
|
||||
static constexpr const char* SendSignalNodeUuid = "02c2ce4b-8783-47cb-a55f-90056bebd64b";
|
||||
static constexpr const char* ForLoopNodeUuid = "02a1b2c3-4d5e-6f7a-8b9c-0d1e2f3a4b5c";
|
||||
static constexpr const char* WhileLoopNodeUuid = "02b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
|
||||
static constexpr const char* BreakNodeUuid = "02c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e";
|
||||
static constexpr const char* ContinueNodeUuid = "02d4e5f6-7a8b-9c0d-1e2f-3a4b5c6d7e8f";
|
||||
static constexpr const char* FunctionExitNodeUuid = "02d78b65-9246-4108-91fc-03dfc142d9ee";
|
||||
|
||||
typedef std::shared_ptr<BaseNode> (*GenericCreator)(const std::string &type);
|
||||
|
||||
class NodesFactory
|
||||
{
|
||||
|
||||
public:
|
||||
NodesFactory(ILogger &log)
|
||||
: m_log(log)
|
||||
, m_rootPath("")
|
||||
{
|
||||
// Register node types
|
||||
// registerNode<MediaNode>("media-node");
|
||||
registerNode<OperatorNode>(OperatorNodeUuid, std::make_shared<StoryPrimitive>("Operator"));
|
||||
registerNode<CallFunctionNode>(CallFunctionNodeUuid, std::make_shared<StoryPrimitive>("Call function"));
|
||||
registerNode<VariableNode>(VariableNodeUuid, std::make_shared<StoryPrimitive>("Variable"));
|
||||
registerNode<PrintNode>(PrintNodeUuid, std::make_shared<StoryPrimitive>("Print"));
|
||||
registerNode<FunctionEntryNode>(FunctionEntryNodeUuid, std::make_shared<StoryPrimitive>("Function entry"));
|
||||
registerNode<BranchNode>(BranchNodeUuid, std::make_shared<StoryPrimitive>("Branch"));
|
||||
registerNode<WaitEventNode>(WaitEventNodeUuid, std::make_shared<StoryPrimitive>("Wait Event"));
|
||||
registerNode<WaitDelayNode>(WaitDelayNodeUuid, std::make_shared<StoryPrimitive>("Wait Delay"));
|
||||
registerNode<PlayMediaNode>(PlayMediaNodeUuid, std::make_shared<StoryPrimitive>("Play Media"));
|
||||
registerNode<SendSignalNode>(SendSignalNodeUuid, std::make_shared<StoryPrimitive>("Send Signal"));
|
||||
registerNode<ForLoopNode>(ForLoopNodeUuid, std::make_shared<StoryPrimitive>("For Loop"));
|
||||
registerNode<WhileLoopNode>(WhileLoopNodeUuid, std::make_shared<StoryPrimitive>("While Loop"));
|
||||
registerNode<BreakNode>(BreakNodeUuid, std::make_shared<StoryPrimitive>("Break"));
|
||||
registerNode<ContinueNode>(ContinueNodeUuid, std::make_shared<StoryPrimitive>("Continue"));
|
||||
registerNode<FunctionExitNode>(FunctionExitNodeUuid, std::make_shared<StoryPrimitive>("Function exit"));
|
||||
|
||||
}
|
||||
|
||||
~NodesFactory() = default;
|
||||
|
||||
struct NodeInfo {
|
||||
std::string uuid;
|
||||
std::string name;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
// key: uuid, value: node name
|
||||
std::vector<NodeInfo> ListOfNodes() const {
|
||||
std::vector<NodeInfo> l;
|
||||
for (auto const& imap : m_registry) {
|
||||
l.push_back(NodeInfo{imap.first, imap.second.first->GetName(), imap.second.first->GetDescription() });
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
std::vector<NodeInfo> LitOfModules() const {
|
||||
std::vector<NodeInfo> l;
|
||||
for (auto const& imap : m_registry) {
|
||||
if (imap.second.first->IsModule()) {
|
||||
l.push_back(NodeInfo{imap.first, imap.second.first->GetName(), imap.second.first->GetDescription() });
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
std::shared_ptr<BaseNode> CreateNode(const std::string &type)
|
||||
{
|
||||
typename Registry::const_iterator i = m_registry.find(type);
|
||||
if (i == m_registry.end()) {
|
||||
throw std::invalid_argument(std::string(__PRETTY_FUNCTION__) +
|
||||
": key not registered");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto n = i->second.second(type);
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<StoryProject> GetModule(const std::string &uuid)
|
||||
{
|
||||
std::shared_ptr<StoryProject> module;
|
||||
|
||||
// Scan all function nodes and find the one with that name
|
||||
for (auto n : m_registry)
|
||||
{
|
||||
if (n.first == uuid)
|
||||
{
|
||||
if (n.second.first)
|
||||
{
|
||||
auto p = dynamic_cast<StoryProject*>(n.second.first.get());
|
||||
if (p == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Node is not a StoryProject");
|
||||
}
|
||||
module = p->shared_from_this();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
void SaveAllModules(ResourceManager &manager)
|
||||
{
|
||||
for (const auto &n : m_registry)
|
||||
{
|
||||
auto p = dynamic_cast<StoryProject*>(n.second.first.get());
|
||||
if (p)
|
||||
{
|
||||
p->Save(manager);
|
||||
}
|
||||
// Only modules
|
||||
// auto module = std::dynamic_pointer_cast<StoryProject>(entry.second.first);
|
||||
// if (module)
|
||||
// {
|
||||
// module->Save(manager);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new empty module (StoryProject) and registers it as a module node.
|
||||
std::shared_ptr<StoryProject> NewModule(const std::string& moduleName = "Untitled Module") {
|
||||
// Create a new StoryProject with the given name
|
||||
auto module = std::make_shared<StoryProject>(m_log);
|
||||
module->New(Uuid().String(), m_rootPath);
|
||||
module->SetName(moduleName);
|
||||
module->SetTitleImage("");
|
||||
module->SetTitleSound("");
|
||||
module->SetDisplayFormat(320, 240);
|
||||
module->SetImageFormat(Resource::ImageFormat::IMG_SAME_FORMAT);
|
||||
module->SetSoundFormat(Resource::SoundFormat::SND_SAME_FORMAT);
|
||||
module->SetDescription("");
|
||||
module->SetProjectType(IStoryProject::PROJECT_TYPE_MODULE);
|
||||
|
||||
// Register as module node if not already in registry
|
||||
registerNode<ModuleNode>(module->GetUuid(), module);
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SetModulesRootDirectory(const std::string &rootPath) {
|
||||
m_rootPath = rootPath;
|
||||
}
|
||||
|
||||
void ScanModules() {
|
||||
std::cout << "Scanning modules in: " << m_rootPath << std::endl;
|
||||
|
||||
// Loop through files in m_rootPath
|
||||
// and register them as nodes if they match certain criteria
|
||||
for (const auto& entry : std::filesystem::directory_iterator(m_rootPath))
|
||||
{
|
||||
// Enter directory and look for .json files
|
||||
if (entry.is_directory())
|
||||
{
|
||||
std::cout << "Scanning directory: " << entry.path() << std::endl;
|
||||
auto uuid = entry.path().filename().string();
|
||||
for (const auto& subEntry : std::filesystem::directory_iterator(entry.path()))
|
||||
{
|
||||
if (subEntry.is_regular_file() && subEntry.path().extension() == ".json")
|
||||
{
|
||||
std::cout << "Found module file: " << subEntry.path() << std::endl;
|
||||
|
||||
auto p = std::make_shared<StoryProject>(m_log);
|
||||
|
||||
p->SetPaths(uuid, m_rootPath);
|
||||
// Load the JSON file and register a node based on its content
|
||||
std::ifstream file(subEntry.path());
|
||||
nlohmann::json j;
|
||||
file >> j;
|
||||
p->ParseStoryInformation(j);
|
||||
if (p->IsModule())
|
||||
{
|
||||
registerNode<ModuleNode>(p->GetUuid(), p);
|
||||
// For now, function node use only primitives nodes
|
||||
// FIXME: in the future, allow function node to use other function nodes
|
||||
// Need a list of required nodes to be registered
|
||||
|
||||
// Maybe this algorithm:
|
||||
// 1. load all function nodes
|
||||
// 2. for each function node, check if it has a "requires" field
|
||||
// 3. if it does, check if we have them
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Skipping non-module project: " << p->GetName() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ILogger &m_log;
|
||||
std::string m_rootPath;
|
||||
|
||||
template<class NodeType>
|
||||
struct Factory {
|
||||
static std::shared_ptr<BaseNode> create_func(const std::string &type) {
|
||||
return std::make_shared<NodeType>(type);
|
||||
}
|
||||
};
|
||||
|
||||
// UUID is the key, and the value is a function that creates the node
|
||||
typedef std::map<std::string, std::pair<std::shared_ptr<IStoryProject>, GenericCreator>> Registry;
|
||||
Registry m_registry;
|
||||
|
||||
template<class Derived>
|
||||
void registerNode(const std::string& uuid, std::shared_ptr<IStoryProject> moduleInfo) {
|
||||
m_registry.insert(std::make_pair(uuid, std::make_pair(moduleInfo, Factory<Derived>::create_func)));
|
||||
}
|
||||
};
|
||||
56
core/story-manager/src/resource.cpp
Normal file
56
core/story-manager/src/resource.cpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
|
||||
#include "resource.h"
|
||||
#include "sys_lib.h"
|
||||
|
||||
std::string Resource::ImageFormatToString(ImageFormat format)
|
||||
{
|
||||
std::string text = "SAME";
|
||||
switch (format)
|
||||
{
|
||||
case IMG_FORMAT_QOIF:
|
||||
text = "QOIF";
|
||||
break;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string Resource::SoundFormatToString(SoundFormat format)
|
||||
{
|
||||
std::string text = "SAME";
|
||||
switch (format)
|
||||
{
|
||||
case SND_FORMAT_WAV:
|
||||
text = "WAV";
|
||||
break;
|
||||
case SND_FORMAT_QOAF:
|
||||
text = "QOAF";
|
||||
break;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string Resource::ImageExtension(const std::string &filename, Resource::ImageFormat prefered_format)
|
||||
{
|
||||
std::string ext = SysLib::GetFileExtension(filename);
|
||||
if (prefered_format == Resource::ImageFormat::IMG_FORMAT_QOIF)
|
||||
{
|
||||
return "qoi";
|
||||
}
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
std::string Resource::SoundExtension(const std::string &filename, Resource::SoundFormat prefered_format)
|
||||
{
|
||||
std::string ext = SysLib::GetFileExtension(filename);
|
||||
if (prefered_format == Resource::SoundFormat::SND_FORMAT_QOAF)
|
||||
{
|
||||
return "qoa";
|
||||
}
|
||||
else if (prefered_format == Resource::SoundFormat::SND_FORMAT_WAV)
|
||||
{
|
||||
return "wav";
|
||||
}
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
|
@ -6,17 +6,27 @@
|
|||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
struct Resource
|
||||
{
|
||||
enum ImageFormat { IMG_SAME_FORMAT, IMG_FORMAT_QOIF, IMG_FORMAT_COUNT };
|
||||
enum SoundFormat { SND_SAME_FORMAT, SND_FORMAT_WAV, SND_FORMAT_QOAF, SND_FORMAT_COUNT };
|
||||
|
||||
std::string file;
|
||||
std::string description;
|
||||
std::string format;
|
||||
std::string type;
|
||||
|
||||
~Resource() {
|
||||
std::cout << "Res deleted" << std::endl;
|
||||
// std::cout << "Res deleted" << std::endl;
|
||||
}
|
||||
|
||||
static std::string ImageFormatToString(ImageFormat format);
|
||||
static std::string SoundFormatToString(SoundFormat format);
|
||||
static std::string ImageExtension(const std::string &filename, Resource::ImageFormat prefered_format);
|
||||
static std::string SoundExtension(const std::string &filename, Resource::SoundFormat prefered_format);
|
||||
};
|
||||
|
||||
// Itérateur pour parcourir les éléments filtrés
|
||||
|
|
@ -74,12 +84,4 @@ private:
|
|||
};
|
||||
|
||||
|
||||
class IResource
|
||||
{
|
||||
public:
|
||||
virtual ~IResource();
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // RESOURCE_H
|
||||
46
core/story-manager/src/story_db.cpp
Normal file
46
core/story-manager/src/story_db.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#include "story_db.h"
|
||||
|
||||
StoryDb::StoryDb()
|
||||
{
|
||||
}
|
||||
|
||||
StoryDb::~StoryDb()
|
||||
{
|
||||
}
|
||||
|
||||
bool StoryDb::FindStory(const std::string &uuid, Info &info)
|
||||
{
|
||||
for (const auto &s : m_store)
|
||||
{
|
||||
if (s.uuid == uuid)
|
||||
{
|
||||
info = s;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &s : m_commercialStore)
|
||||
{
|
||||
if (s.uuid == uuid)
|
||||
{
|
||||
info = s;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void StoryDb::AddStory(IStoryDb::Info &info, int origin)
|
||||
{
|
||||
if (origin == cCommercialStore)
|
||||
{
|
||||
m_commercialStore.push_back(info);
|
||||
}
|
||||
|
||||
if (origin == cCommunityStore)
|
||||
{
|
||||
m_store.push_back(info);
|
||||
}
|
||||
|
||||
}
|
||||
45
core/story-manager/src/story_db.h
Normal file
45
core/story-manager/src/story_db.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ranges>
|
||||
|
||||
#include "i_story_db.h"
|
||||
|
||||
class StoryDb : public IStoryDb
|
||||
{
|
||||
public:
|
||||
static const int cCommercialStore = 0;
|
||||
static const int cCommunityStore = 1;
|
||||
|
||||
StoryDb();
|
||||
~StoryDb();
|
||||
|
||||
bool FindStory(const std::string &uuid, Info &info) override;
|
||||
void AddStory(IStoryDb::Info &info, int origin) override;
|
||||
|
||||
void Clear() {
|
||||
m_store.clear();
|
||||
m_commercialStore.clear();
|
||||
}
|
||||
|
||||
void ClearCommercial() {
|
||||
m_commercialStore.clear();
|
||||
}
|
||||
|
||||
void ClearCommunity() {
|
||||
m_store.clear();
|
||||
}
|
||||
|
||||
auto CommercialDbView() const {
|
||||
return std::ranges::views::all(m_commercialStore);
|
||||
}
|
||||
|
||||
auto CommunityDbView() const {
|
||||
return std::ranges::views::all(m_store);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<IStoryDb::Info> m_store;
|
||||
std::vector<IStoryDb::Info> m_commercialStore;
|
||||
};
|
||||
11
core/story-manager/src/story_options.h
Normal file
11
core/story-manager/src/story_options.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
struct StoryOptions
|
||||
{
|
||||
Resource::ImageFormat image_format{Resource::IMG_SAME_FORMAT};
|
||||
Resource::SoundFormat sound_format{Resource::SND_SAME_FORMAT};
|
||||
int display_w{320};
|
||||
int display_h{240};
|
||||
};
|
||||
4
core/story-manager/src/story_page.cpp
Normal file
4
core/story-manager/src/story_page.cpp
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
#include "story_page.h"
|
||||
|
||||
|
||||
176
core/story-manager/src/story_page.h
Normal file
176
core/story-manager/src/story_page.h
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "i_story_page.h"
|
||||
#include "i_story_project.h"
|
||||
#include "base_node.h"
|
||||
#include "connection.h"
|
||||
#include "assembly_generator.h"
|
||||
|
||||
class StoryPage : public IStoryPage
|
||||
{
|
||||
|
||||
public:
|
||||
StoryPage(const std::string_view uuid)
|
||||
: m_uuid(uuid)
|
||||
, m_name("Unnamed Page")
|
||||
{
|
||||
}
|
||||
~StoryPage() {
|
||||
m_links.clear();
|
||||
m_nodes.clear();
|
||||
std::cout << "StoryPage destructor" << std::endl;
|
||||
}
|
||||
|
||||
std::string_view Uuid() const { return m_uuid; }
|
||||
|
||||
std::string_view GetName() const { return m_name; }
|
||||
void SetName(const std::string& name) { m_name = name; }
|
||||
|
||||
void AddNode(std::shared_ptr<BaseNode> node) {
|
||||
m_nodes.push_back(node);
|
||||
}
|
||||
|
||||
void AddLink(std::shared_ptr<Connection> link) {
|
||||
m_links.push_back(link);
|
||||
}
|
||||
|
||||
void PrintStats() const {
|
||||
std::cout << "Nodes: " << m_nodes.size() << ", links: " << m_links.size() << std::endl;
|
||||
}
|
||||
|
||||
std::pair<std::list<std::shared_ptr<BaseNode>>::iterator, std::list<std::shared_ptr<BaseNode>>::iterator> Nodes() {
|
||||
return std::make_pair(m_nodes.begin(), m_nodes.end());
|
||||
}
|
||||
|
||||
std::pair<std::list<std::shared_ptr<Connection>>::iterator, std::list<std::shared_ptr<Connection>>::iterator> Links() {
|
||||
return std::make_pair(m_links.begin(), m_links.end());
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
m_links.clear();
|
||||
m_nodes.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
virtual void GetNodeConnections(std::list<std::shared_ptr<Connection>> &c, const std::string &nodeId) override
|
||||
{
|
||||
for (const auto & l : m_links)
|
||||
{
|
||||
if (l->outNodeId == nodeId)
|
||||
{
|
||||
c.push_back(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string FindFirstNode() const
|
||||
{
|
||||
std::string id;
|
||||
|
||||
// First node is the one without connection on its input port
|
||||
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
bool foundConnection = false;
|
||||
|
||||
for (const auto& l : m_links)
|
||||
{
|
||||
if (l->inNodeId == n->GetId())
|
||||
{
|
||||
foundConnection = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundConnection)
|
||||
{
|
||||
id = n->GetId();
|
||||
std::cout << "First node is: " + id << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
int OutputsCount(const std::string &nodeId) const
|
||||
{
|
||||
int count = 0;
|
||||
for (const auto & l : m_links)
|
||||
{
|
||||
if (l->outNodeId == nodeId)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
nlohmann::json ToJson() const
|
||||
{
|
||||
nlohmann::json model;
|
||||
model["uuid"] = m_uuid;
|
||||
model["name"] = m_name;
|
||||
|
||||
nlohmann::json nodes = nlohmann::json::array();
|
||||
for (const auto & n : m_nodes)
|
||||
{
|
||||
nodes.push_back(n->ToJson());
|
||||
}
|
||||
|
||||
model["nodes"] = nodes;
|
||||
|
||||
// Save links
|
||||
nlohmann::json connections = nlohmann::json::array();
|
||||
for (const auto& cnx : m_links)
|
||||
{
|
||||
nlohmann::json c(*cnx);
|
||||
connections.push_back(c);
|
||||
}
|
||||
|
||||
model["connections"] = connections;
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
void DeleteLink(std::shared_ptr<Connection> c)
|
||||
{
|
||||
auto it = std::find_if(m_links.begin(),
|
||||
m_links.end(),
|
||||
[&c](std::shared_ptr<Connection> const &cnx) {
|
||||
return *cnx == *c;
|
||||
});
|
||||
|
||||
if ( it != m_links.end() )
|
||||
{
|
||||
it->reset();
|
||||
m_links.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteNode(const std::string &id)
|
||||
{
|
||||
|
||||
auto it = std::find_if(m_nodes.begin(),
|
||||
m_nodes.end(),
|
||||
[&id](std::shared_ptr<BaseNode> const &n) { return n->GetId() == id; });
|
||||
|
||||
if ( it != m_nodes.end() )
|
||||
{
|
||||
it->reset();
|
||||
m_nodes.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_uuid;
|
||||
std::string m_name;
|
||||
std::list<std::shared_ptr<Connection>> m_links;
|
||||
std::list<std::shared_ptr<BaseNode>> m_nodes;
|
||||
|
||||
};
|
||||
98
core/story-manager/src/story_primitive.h
Normal file
98
core/story-manager/src/story_primitive.h
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#pragma once
|
||||
|
||||
#include "i_story_project.h"
|
||||
|
||||
|
||||
class StoryPrimitive : public IStoryProject
|
||||
{
|
||||
|
||||
public:
|
||||
StoryPrimitive(const std::string &name, const std::string &description = "", const std::string &uuid = "00000000-0000-0000-0000-000000000000")
|
||||
: m_name(name)
|
||||
, m_description(description)
|
||||
, m_uuid(uuid)
|
||||
{
|
||||
|
||||
}
|
||||
virtual ~StoryPrimitive() {};
|
||||
|
||||
virtual std::string GetName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
virtual std::string GetDescription() const
|
||||
{
|
||||
return m_description;
|
||||
}
|
||||
|
||||
virtual bool IsModule() const
|
||||
{
|
||||
return false; // This is not a module, it's a primitive
|
||||
}
|
||||
|
||||
virtual std::string GetUuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
virtual std::vector<FunctionInfo> GetFunctionsList() const override {
|
||||
return std::vector<FunctionInfo>();
|
||||
}
|
||||
|
||||
|
||||
virtual std::string GetTitleImage() const {
|
||||
return ""; // No title image by default
|
||||
}
|
||||
virtual std::string GetTitleSound() const {
|
||||
return ""; // No title sound by default
|
||||
}
|
||||
virtual Type GetProjectType() const {
|
||||
return Type::PROJECT_TYPE_PRIMITIVE;
|
||||
}
|
||||
virtual void SetTitleImage(const std::string &titleImage) {
|
||||
(void)titleImage; // No title image to set
|
||||
}
|
||||
virtual void SetTitleSound(const std::string &titleSound) {
|
||||
(void)titleSound; // No title sound to set
|
||||
}
|
||||
virtual void SetDescription(const std::string &description) {
|
||||
m_description = description;
|
||||
}
|
||||
virtual void SetProjectType(Type type) {
|
||||
(void)type; // Project type is fixed for primitives
|
||||
}
|
||||
virtual void SetImageFormat(Resource::ImageFormat format) {
|
||||
(void)format; // Image format is fixed for primitives
|
||||
}
|
||||
virtual void SetSoundFormat(Resource::SoundFormat format) {
|
||||
(void)format; // Sound format is fixed for primitives
|
||||
}
|
||||
virtual void SetDisplayFormat(int w, int h) {
|
||||
(void)w; // Display width is fixed for primitives
|
||||
(void)h; // Display height is fixed for primitives
|
||||
}
|
||||
virtual void SetName(const std::string &name) {
|
||||
m_name = name;
|
||||
}
|
||||
virtual void SetUuid(const std::string &uuid) {
|
||||
m_uuid = uuid;
|
||||
}
|
||||
|
||||
virtual void ScanVariable(const std::function<bool(std::shared_ptr<Variable> element)>& operation) override {
|
||||
|
||||
}
|
||||
virtual void AddVariable() override {
|
||||
|
||||
}
|
||||
virtual void DeleteVariable(int i) override {
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::string m_description;
|
||||
std::string m_uuid;
|
||||
std::string m_titleImage;
|
||||
std::string m_titleSound;
|
||||
};
|
||||
737
core/story-manager/src/story_project.cpp
Normal file
737
core/story-manager/src/story_project.cpp
Normal file
|
|
@ -0,0 +1,737 @@
|
|||
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <regex>
|
||||
|
||||
#include "story_project.h"
|
||||
#include "json.hpp"
|
||||
#include "variable_node.h"
|
||||
#include "operator_node.h"
|
||||
#include "print_node.h"
|
||||
#include "sys_lib.h"
|
||||
#include "assembly_generator_chip32_tac.h"
|
||||
#include "nodes_factory.h"
|
||||
#include "json_reader.h"
|
||||
|
||||
StoryProject::StoryProject(ILogger &log)
|
||||
: m_log(log)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
StoryProject::~StoryProject()
|
||||
{
|
||||
}
|
||||
|
||||
void StoryProject::SetPaths(const std::string &uuid, const std::string &library_path)
|
||||
{
|
||||
m_uuid = uuid;
|
||||
m_project_file_path = std::filesystem::path(library_path) / uuid / std::filesystem::path("project.json");
|
||||
|
||||
m_working_dir = m_project_file_path.parent_path().generic_string();
|
||||
m_assetsPath = m_working_dir / std::filesystem::path("assets");
|
||||
|
||||
std::cout << "Working dir is: " << m_working_dir << std::endl;
|
||||
}
|
||||
|
||||
void StoryProject::CopyToDevice(const std::string &outputDir, NodesFactory &factory)
|
||||
{
|
||||
ResourceManager manager(m_log);
|
||||
|
||||
Load(manager, factory);
|
||||
|
||||
// Output dir is the root. Build an assets directory to the device location
|
||||
std::filesystem::path destRootDir = std::filesystem::path(outputDir) / m_uuid;
|
||||
std::filesystem::path destAssetsDir = destRootDir / "assets";
|
||||
std::filesystem::create_directories(destAssetsDir);
|
||||
|
||||
// Generate and copy binary
|
||||
std::string code;
|
||||
// GenerateScript(code); // FIXME
|
||||
|
||||
std::cout << code << std::endl;
|
||||
|
||||
// FIXME génération
|
||||
|
||||
// Chip32::Assembler::Error err;
|
||||
// if (GenerateBinary(code, err))
|
||||
// {
|
||||
// std::filesystem::copy(BinaryFileName(), destRootDir, std::filesystem::copy_options::overwrite_existing);
|
||||
|
||||
// // Convert resources (if necessary) and copy them to destination assets
|
||||
// manager.ConvertResources(AssetsPath(), destAssetsDir, m_storyOptions.image_format, m_storyOptions.sound_format);
|
||||
// }
|
||||
}
|
||||
|
||||
void StoryProject::New(const std::string &uuid, const std::string &library_path)
|
||||
{
|
||||
SetPaths(uuid, library_path);
|
||||
|
||||
// First try to create the working directory
|
||||
if (!std::filesystem::is_directory(m_working_dir))
|
||||
{
|
||||
std::filesystem::create_directories(m_working_dir);
|
||||
}
|
||||
|
||||
std::filesystem::create_directories(m_assetsPath);
|
||||
|
||||
CreatePage(MainUuid());
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
std::filesystem::path StoryProject::BinaryFileName() const
|
||||
{
|
||||
return m_working_dir / "story.c32";
|
||||
}
|
||||
|
||||
bool StoryProject::ParseStoryInformation(nlohmann::json &j)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
if (j.contains("project"))
|
||||
{
|
||||
nlohmann::json projectData = j["project"];
|
||||
|
||||
m_name = projectData["name"].get<std::string>();
|
||||
m_uuid = projectData["uuid"].get<std::string>();
|
||||
m_description = projectData["description"].get<std::string>();
|
||||
std::string typeString = projectData["type"].get<std::string>();
|
||||
|
||||
if (typeString == "story")
|
||||
{
|
||||
m_projectType = IStoryProject::PROJECT_TYPE_STORY;
|
||||
}
|
||||
else if (typeString == "module")
|
||||
{
|
||||
m_projectType = IStoryProject::PROJECT_TYPE_MODULE;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.Log("Unknown project type: " + typeString, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_titleImage = projectData.value("title_image", "");
|
||||
m_titleSound = projectData.value("title_sound", "");
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void StoryProject::ModelToJson(nlohmann::json &model)
|
||||
{
|
||||
for (const auto & p : m_pages)
|
||||
{
|
||||
nlohmann::json page = p->ToJson();
|
||||
model.push_back(page);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<StoryPage> StoryProject::CreatePage(const std::string_view uuid)
|
||||
{
|
||||
auto newPage = std::make_shared<StoryPage>(uuid);
|
||||
m_pages.push_back(newPage);
|
||||
return newPage;
|
||||
}
|
||||
|
||||
void StoryProject::AddConnection(const std::string_view &page_uuid, std::shared_ptr<Connection> c)
|
||||
{
|
||||
for (const auto & p : m_pages)
|
||||
{
|
||||
if (p->Uuid() == page_uuid)
|
||||
{
|
||||
p->AddLink(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StoryProject::AddNode(const std::string_view &page_uuid, std::shared_ptr<BaseNode> node)
|
||||
{
|
||||
for (const auto & p : m_pages)
|
||||
{
|
||||
if (p->Uuid() == page_uuid)
|
||||
{
|
||||
p->AddNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StoryProject::DeleteNode(const std::string_view &page_uuid, const std::string &id)
|
||||
{
|
||||
for (const auto & p : m_pages)
|
||||
{
|
||||
if (p->Uuid() == page_uuid)
|
||||
{
|
||||
p->DeleteNode(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<StoryPage> StoryProject::GetPage(const std::string_view &uuid)
|
||||
{
|
||||
for (const auto & p : m_pages)
|
||||
{
|
||||
if (p->Uuid() == uuid)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
}
|
||||
|
||||
void StoryProject::DeleteLink(const std::string_view &page_uuid, std::shared_ptr<Connection> c)
|
||||
{
|
||||
|
||||
for (const auto & p : m_pages)
|
||||
{
|
||||
if (p->Uuid() == page_uuid)
|
||||
{
|
||||
p->DeleteLink(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<std::list<std::shared_ptr<BaseNode>>::iterator, std::list<std::shared_ptr<BaseNode>>::iterator> StoryProject::Nodes(const std::string_view &page_uuid)
|
||||
{
|
||||
for (const auto & p : m_pages)
|
||||
{
|
||||
if (p->Uuid() == page_uuid)
|
||||
{
|
||||
return p->Nodes();
|
||||
}
|
||||
}
|
||||
|
||||
return std::pair<std::list<std::shared_ptr<BaseNode>>::iterator, std::list<std::shared_ptr<BaseNode>>::iterator>();
|
||||
}
|
||||
|
||||
std::pair<std::list<std::shared_ptr<Connection>>::iterator, std::list<std::shared_ptr<Connection>>::iterator> StoryProject::Links(const std::string_view &page_uuid)
|
||||
{
|
||||
for (const auto & p : m_pages)
|
||||
{
|
||||
if (p->Uuid() == page_uuid)
|
||||
{
|
||||
return p->Links();
|
||||
}
|
||||
}
|
||||
|
||||
return std::pair<std::list<std::shared_ptr<Connection>>::iterator, std::list<std::shared_ptr<Connection>>::iterator>();
|
||||
}
|
||||
|
||||
void StoryProject::ScanVariable(const std::function<bool(std::shared_ptr<Variable> element)>& operation)
|
||||
{
|
||||
for (auto &v : m_variables)
|
||||
{
|
||||
bool ret = operation(v);
|
||||
if (!ret)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void StoryProject::ScanNodes(const std::function<bool(std::shared_ptr<BaseNode>)>& callback) {
|
||||
for (auto& page : m_pages) {
|
||||
auto [nodesBegin, nodesEnd] = page->Nodes();
|
||||
std::vector<std::shared_ptr<BaseNode>> pageNodes(nodesBegin, nodesEnd);
|
||||
for (auto& node : pageNodes) {
|
||||
if (!callback(node)) {
|
||||
return; // Stop scanning if callback returns false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StoryProject::AddVariable()
|
||||
{
|
||||
auto v = std::make_shared<Variable>("var_" + std::to_string(m_variables.size()));
|
||||
|
||||
v->SetValue(0);
|
||||
v->SetValueType(Variable::ValueType::INTEGER);
|
||||
v->SetConstant(false);
|
||||
|
||||
m_variables.push_back(v);
|
||||
}
|
||||
|
||||
void StoryProject::DeleteVariable(int i)
|
||||
{
|
||||
m_variables.erase(m_variables.begin() + i);
|
||||
}
|
||||
|
||||
bool StoryProject::ModelFromJson(const nlohmann::json &model, NodesFactory &factory)
|
||||
{
|
||||
bool success = false;
|
||||
try {
|
||||
|
||||
nlohmann::json pagesJsonArray = model["pages"];
|
||||
m_pages.clear();
|
||||
|
||||
for (auto& pageModel : pagesJsonArray)
|
||||
{
|
||||
// 1. Create the page in memory
|
||||
auto p = std::make_shared<StoryPage>(pageModel["uuid"].get<std::string>());
|
||||
m_pages.push_back(p);
|
||||
p->SetName(pageModel.value("name", "Unnamed Page"));
|
||||
|
||||
// 2. Load the nodes
|
||||
nlohmann::json nodesJsonArray = pageModel["nodes"];
|
||||
for (auto& element : nodesJsonArray) {
|
||||
|
||||
std::string type = element["type"].get<std::string>();
|
||||
|
||||
auto n = factory.CreateNode(type);
|
||||
if (n)
|
||||
{
|
||||
AddNode(p->Uuid(), n);
|
||||
n->FromJson(element);
|
||||
n->Initialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error(std::string("No registered model with name ") + type);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Load the connections
|
||||
// std::cout << model.dump(4) << std::endl;
|
||||
|
||||
// Ici on reste flexible sur les connexions, cela permet de créer éventuellement des
|
||||
// projets sans fils (bon, l'élément devrait quand même exister dans le JSON)
|
||||
if (pageModel.contains("connections"))
|
||||
{
|
||||
nlohmann::json connectionJsonArray = pageModel["connections"];
|
||||
|
||||
// key: node UUID, value: output counts
|
||||
std::map<std::string, int> outputCounts;
|
||||
|
||||
for (auto& connection : connectionJsonArray)
|
||||
{
|
||||
p->AddLink(std::make_shared<Connection>(connection.get<Connection>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
catch(nlohmann::json::exception &e)
|
||||
{
|
||||
std::cout << "(StoryProject::ModelFromJson) " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
std::vector<IStoryProject::FunctionInfo> StoryProject::GetFunctionsList() const
|
||||
{
|
||||
std::vector<IStoryProject::FunctionInfo> functions;
|
||||
|
||||
// Parcourir toutes les pages du projet
|
||||
for (const auto& page : m_pages)
|
||||
{
|
||||
// Exclure la page main (MainUuid)
|
||||
if (page->Uuid() == MainUuid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ajouter la page à la liste des fonctions disponibles
|
||||
IStoryProject::FunctionInfo info;
|
||||
info.uuid = page->Uuid();
|
||||
info.name = page->GetName();
|
||||
|
||||
functions.push_back(info);
|
||||
}
|
||||
|
||||
return functions;
|
||||
}
|
||||
|
||||
|
||||
std::list<std::shared_ptr<Connection>> StoryProject::GetNodeConnections(const std::string &nodeId)
|
||||
{
|
||||
std::list<std::shared_ptr<Connection>> c;
|
||||
|
||||
for (const auto &p : m_pages)
|
||||
{
|
||||
p->GetNodeConnections(c, nodeId);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
int StoryProject::OutputsCount(const std::string &nodeId)
|
||||
{
|
||||
for (const auto &p : m_pages)
|
||||
{
|
||||
return p->OutputsCount(nodeId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StoryProject::UseResource(const std::string &label)
|
||||
{
|
||||
bool used = m_usedLabels.contains(label);
|
||||
|
||||
if (!used)
|
||||
{
|
||||
m_usedLabels.insert(label);
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
|
||||
// story_project.cpp
|
||||
|
||||
bool StoryProject::GenerateCompleteProgram(std::string &assembly)
|
||||
{
|
||||
// === PHASE 1 : COLLECTE ===
|
||||
// Collecter tous les nœuds et connexions de toutes les pages
|
||||
std::vector<std::shared_ptr<BaseNode>> allNodes;
|
||||
std::map<std::string, std::pair<std::vector<std::shared_ptr<BaseNode>>,
|
||||
std::vector<std::shared_ptr<Connection>>
|
||||
>> pageData;
|
||||
|
||||
for (const auto& page : m_pages) {
|
||||
auto [nodesBegin, nodesEnd] = page->Nodes();
|
||||
auto [linksBegin, linksEnd] = page->Links();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> pageNodes(nodesBegin, nodesEnd);
|
||||
std::vector<std::shared_ptr<Connection>> pageLinks(linksBegin, linksEnd);
|
||||
|
||||
// Ajouter tous les nœuds à la liste globale pour la section DATA
|
||||
allNodes.insert(allNodes.end(), pageNodes.begin(), pageNodes.end());
|
||||
|
||||
// Stocker les données de chaque page
|
||||
pageData[std::string(page->Uuid())] = {pageNodes, pageLinks};
|
||||
}
|
||||
|
||||
std::cout << "\n=== Resolving VariableNode references ===\n";
|
||||
for (const auto& baseNode : allNodes) {
|
||||
auto varNode = std::dynamic_pointer_cast<VariableNode>(baseNode);
|
||||
if (varNode) {
|
||||
varNode->ResolveVariable(m_variables);
|
||||
}
|
||||
}
|
||||
|
||||
// PHASE 2 : GÉNÉRATION DE TOUS LES TAC (avant la section DATA!)
|
||||
std::cout << "\n=== Generating all TAC programs ===\n";
|
||||
std::map<std::string, TACProgram> pageTACPrograms;
|
||||
|
||||
for (const auto& page : m_pages) {
|
||||
std::string pageUuid(page->Uuid());
|
||||
auto& [nodes, connections] = pageData[pageUuid];
|
||||
|
||||
// Construire l'AST pour cette page
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto astNodes = builder.BuildAST();
|
||||
|
||||
// Générer le TAC pour cette page
|
||||
TACGenerator tacGen;
|
||||
TACProgram pageTAC = tacGen.Generate(astNodes, m_variables);
|
||||
|
||||
// Stocker le TAC
|
||||
pageTACPrograms[pageUuid] = pageTAC;
|
||||
|
||||
std::cout << "Generated TAC for page: " << page->GetName() << std::endl;
|
||||
}
|
||||
std::cout << "=== All TAC programs generated ===\n\n";
|
||||
|
||||
// === PHASE 3 : GÉNÉRATION DE L'ASSEMBLEUR ===
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
m_variables,
|
||||
"2025-01-10 15:30:00",
|
||||
"story-project",
|
||||
true, // debug
|
||||
true, // optimize
|
||||
1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC generator(context);
|
||||
|
||||
// Header
|
||||
generator.Reset();
|
||||
generator.GenerateHeader();
|
||||
|
||||
// === SECTION DATA (maintenant les format strings sont corrects!) ===
|
||||
generator.StartSection(AssemblyGenerator::Section::DATA);
|
||||
|
||||
// Variables globales (partagées entre toutes les pages)
|
||||
generator.GenerateGlobalVariables();
|
||||
|
||||
// Constantes de tous les nœuds de toutes les pages
|
||||
// Les format strings ont déjà été modifiés par le TAC generator
|
||||
generator.GenerateNodesVariables(allNodes);
|
||||
|
||||
// === SECTION TEXT (chaque page = une fonction) ===
|
||||
generator.AddComment("======================= CODE =======================");
|
||||
|
||||
// Générer chaque page comme une fonction
|
||||
bool isFirstPage = true;
|
||||
for (const auto& page : m_pages) {
|
||||
std::string pageUuid(page->Uuid());
|
||||
|
||||
// Récupérer le TAC pré-généré
|
||||
TACProgram& pageTAC = pageTACPrograms[pageUuid];
|
||||
|
||||
// Générer le label de fonction
|
||||
std::string functionLabel;
|
||||
if (isFirstPage || pageUuid == MainUuid()) {
|
||||
functionLabel = ".main";
|
||||
isFirstPage = false;
|
||||
} else {
|
||||
functionLabel = ".page_" + pageUuid;
|
||||
}
|
||||
|
||||
generator.AddComment("========================================");
|
||||
generator.AddComment("Page: " + std::string(page->GetName()));
|
||||
generator.AddComment("UUID: " + pageUuid);
|
||||
generator.AddComment("========================================");
|
||||
generator.GetAssembly() << functionLabel << ":\n";
|
||||
|
||||
if (context.debugOutput) {
|
||||
std::cout << "\n=== TAC for page: " << page->GetName() << " ===\n";
|
||||
std::cout << pageTAC.ToString() << std::endl;
|
||||
}
|
||||
|
||||
// Convertir le TAC en assembleur
|
||||
generator.GenerateTACToAssembly(pageTAC);
|
||||
|
||||
// Retour de la fonction (sauf pour main)
|
||||
if (functionLabel != ".main") {
|
||||
generator.GetAssembly() << " ret\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Exit du programme
|
||||
generator.GenerateExit();
|
||||
|
||||
assembly = generator.GetAssembly().str();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StoryProject::Load(ResourceManager &manager, NodesFactory &factory)
|
||||
{
|
||||
try {
|
||||
std::ifstream f(m_project_file_path);
|
||||
nlohmann::json j = nlohmann::json::parse(f);
|
||||
|
||||
manager.Clear();
|
||||
m_variables.clear();
|
||||
|
||||
if (ParseStoryInformation(j))
|
||||
{
|
||||
if (j.contains("resources"))
|
||||
{
|
||||
nlohmann::json resourcesData = j["resources"];
|
||||
|
||||
for (const auto &obj : resourcesData)
|
||||
{
|
||||
auto rData = std::make_shared<Resource>();
|
||||
|
||||
rData->type = obj["type"].get<std::string>();
|
||||
rData->format = obj["format"].get<std::string>();
|
||||
rData->description = obj["description"].get<std::string>();
|
||||
rData->file = obj["file"].get<std::string>();
|
||||
manager.Add(rData);
|
||||
}
|
||||
|
||||
if (j.contains("pages"))
|
||||
{
|
||||
if (ModelFromJson(j, factory))
|
||||
{
|
||||
m_initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error("Model read error");
|
||||
}
|
||||
}
|
||||
|
||||
if (j.contains("variables"))
|
||||
{
|
||||
nlohmann::json variablesData = j["variables"];
|
||||
|
||||
for (const auto &obj : variablesData)
|
||||
{
|
||||
auto v = std::make_shared<Variable>(obj["label"].get<std::string>());
|
||||
v->SetUuid(obj["uuid"].get<std::string>());
|
||||
v->SetValueType(Variable::StringToValueType(obj["type"].get<std::string>()));
|
||||
v->SetScalePower(obj["scale"].get<int>());
|
||||
v->SetConstant(obj["constant"].get<bool>());
|
||||
v->SetVariableName(obj["name"].get<std::string>());
|
||||
|
||||
if (v->IsFloat())
|
||||
{
|
||||
v->SetFloatValue(std::stof(obj["value"].get<std::string>()));
|
||||
}
|
||||
else if (v->IsInteger())
|
||||
{
|
||||
v->SetIntegerValue(std::stoi(obj["value"].get<std::string>()));
|
||||
}
|
||||
else if (v->IsString())
|
||||
{
|
||||
v->SetTextValue(obj["value"].get<std::string>());
|
||||
}
|
||||
else if (v->IsBool())
|
||||
{
|
||||
v->SetBoolValue(obj["value"].get<std::string>() == "true");
|
||||
}
|
||||
|
||||
m_variables.push_back(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(nlohmann::json::exception &e)
|
||||
{
|
||||
m_log.Log(e.what(), true);
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
m_log.Log(e.what(), true);
|
||||
}
|
||||
|
||||
if (m_pages.size() == 0)
|
||||
{
|
||||
CreatePage(MainUuid());
|
||||
}
|
||||
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
void StoryProject::Save(ResourceManager &manager)
|
||||
{
|
||||
try
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["project"] = {
|
||||
{"name", m_name},
|
||||
{"uuid", m_uuid},
|
||||
{ "title_image", m_titleImage },
|
||||
{ "title_sound", m_titleSound },
|
||||
{"description", m_description},
|
||||
{"type", (m_projectType == IStoryProject::PROJECT_TYPE_STORY) ? "story" : "module"},
|
||||
{"version", m_version}
|
||||
};
|
||||
|
||||
{
|
||||
nlohmann::json resourcesData;
|
||||
|
||||
auto [b, e] = manager.Items();
|
||||
for (auto it = b; it != e; ++it)
|
||||
{
|
||||
nlohmann::json obj = {{"type", (*it)->type},
|
||||
{"format", (*it)->format},
|
||||
{"description", (*it)->description},
|
||||
{"file", (*it)->file}};
|
||||
|
||||
resourcesData.push_back(obj);
|
||||
}
|
||||
j["resources"] = resourcesData;
|
||||
}
|
||||
|
||||
nlohmann::json model;
|
||||
ModelToJson(model);
|
||||
j["pages"] = model;
|
||||
|
||||
nlohmann::json variablesData;
|
||||
for (const auto &v : m_variables)
|
||||
{
|
||||
std::string value;
|
||||
|
||||
if (v->IsFloat())
|
||||
{
|
||||
value = std::to_string(v->GetFloatValue());
|
||||
}
|
||||
else if (v->IsInteger())
|
||||
{
|
||||
value = std::to_string(v->GetIntegerValue());
|
||||
}
|
||||
else if (v->IsString())
|
||||
{
|
||||
value = v->GetStringValue();
|
||||
}
|
||||
else if (v->IsBool())
|
||||
{
|
||||
value = v->GetBoolValue() ? "true" : "false";
|
||||
}
|
||||
|
||||
nlohmann::json obj = {{"name", v->GetVariableName()},
|
||||
{"label", v->GetLabel()},
|
||||
{"uuid", v->GetUuid()},
|
||||
{"value", value},
|
||||
{"scale", v->GetScalePower()},
|
||||
{"constant", v->IsConstant()},
|
||||
{"type", Variable::ValueTypeToString(v->GetValueType())}};
|
||||
|
||||
variablesData.push_back(obj);
|
||||
}
|
||||
j["variables"] = variablesData;
|
||||
|
||||
std::ofstream o(m_project_file_path);
|
||||
o << std::setw(4) << j << std::endl;
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
std::cerr << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void StoryProject::Clear()
|
||||
{
|
||||
m_variables.clear();
|
||||
m_pages.clear();
|
||||
}
|
||||
|
||||
|
||||
void StoryProject::SetTitleImage(const std::string &titleImage)
|
||||
{
|
||||
m_titleImage = titleImage;
|
||||
}
|
||||
|
||||
void StoryProject::SetTitleSound(const std::string &titleSound)
|
||||
{
|
||||
m_titleSound = titleSound;
|
||||
}
|
||||
|
||||
void StoryProject::SetImageFormat(Resource::ImageFormat format)
|
||||
{
|
||||
m_storyOptions.image_format = format;
|
||||
}
|
||||
|
||||
void StoryProject::SetSoundFormat(Resource::SoundFormat format)
|
||||
{
|
||||
m_storyOptions.sound_format = format;
|
||||
}
|
||||
|
||||
void StoryProject::SetDisplayFormat(int w, int h)
|
||||
{
|
||||
m_storyOptions.display_w = w;
|
||||
m_storyOptions.display_h = h;
|
||||
}
|
||||
|
||||
std::string StoryProject::GetProjectFilePath() const
|
||||
{
|
||||
return m_project_file_path.generic_string();
|
||||
}
|
||||
|
||||
std::string StoryProject::GetWorkingDir() const
|
||||
{
|
||||
return m_working_dir.string();
|
||||
}
|
||||
|
||||
std::string StoryProject::BuildFullAssetsPath(const std::string_view fileName) const
|
||||
{
|
||||
return (AssetsPath() / fileName).generic_string();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
143
core/story-manager/src/story_project.h
Normal file
143
core/story-manager/src/story_project.h
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#ifndef STORY_PROJECT_H
|
||||
#define STORY_PROJECT_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <unordered_set>
|
||||
|
||||
|
||||
#include "json.hpp"
|
||||
#include "resource_manager.h"
|
||||
#include "connection.h"
|
||||
#include "base_node.h"
|
||||
#include "i_story_project.h"
|
||||
#include "chip32_assembler.h"
|
||||
#include "story_page.h"
|
||||
#include "story_options.h"
|
||||
#include "variable.h"
|
||||
#include "assembly_generator_chip32_tac.h"
|
||||
|
||||
class NodesFactory;
|
||||
|
||||
struct StoryProject : public std::enable_shared_from_this<StoryProject>, public IStoryProject
|
||||
{
|
||||
|
||||
public:
|
||||
StoryProject(ILogger &log);
|
||||
~StoryProject();
|
||||
|
||||
bool *Selected() {
|
||||
return &m_selected;
|
||||
}
|
||||
|
||||
std::string MainUuid() const {
|
||||
return "490745ab-df4d-476d-ae27-027e94b8ee0a";
|
||||
}
|
||||
|
||||
bool GenerateCompleteProgram(std::string &assembly);
|
||||
|
||||
void New(const std::string &uuid, const std::string &library_path);
|
||||
std::filesystem::path BinaryFileName() const;
|
||||
bool Load(ResourceManager &manager, NodesFactory &factory);
|
||||
void Save(ResourceManager &manager);
|
||||
void SetPaths(const std::string &uuid, const std::string &library_path);
|
||||
void CopyToDevice(const std::string &outputDir, NodesFactory &factory);
|
||||
|
||||
void ModelToJson(nlohmann::json &model);
|
||||
bool ModelFromJson(const nlohmann::json &model, NodesFactory &factory);
|
||||
|
||||
void Clear();
|
||||
|
||||
void Select(bool selected) { m_selected = selected; }
|
||||
bool IsSelected() const { return m_selected; }
|
||||
|
||||
std::string GetProjectFilePath() const;
|
||||
std::string GetWorkingDir() const;
|
||||
|
||||
std::string GetUuid() const { return m_uuid; }
|
||||
std::string GetDescription() const { return m_description; }
|
||||
uint32_t GetVersion() const { return m_version; }
|
||||
|
||||
std::string BuildFullAssetsPath(const std::string_view fileName) const;
|
||||
|
||||
std::filesystem::path AssetsPath() const { return m_assetsPath; }
|
||||
|
||||
void SetTitleImage(const std::string &titleImage);
|
||||
void SetTitleSound(const std::string &titleSound);
|
||||
void SetDescription(const std::string &description) { m_description = description; }
|
||||
|
||||
// Initialize with an existing project
|
||||
bool IsInitialized() const { return m_initialized; }
|
||||
bool IsModule() const override { return m_projectType == IStoryProject::PROJECT_TYPE_MODULE; }
|
||||
bool IsStory() const { return m_projectType == IStoryProject::PROJECT_TYPE_STORY; }
|
||||
|
||||
|
||||
bool ParseStoryInformation(nlohmann::json &j);
|
||||
|
||||
// From IStoryProject
|
||||
virtual std::list<std::shared_ptr<Connection>> GetNodeConnections(const std::string &nodeId);
|
||||
virtual int OutputsCount(const std::string &nodeId);
|
||||
StoryOptions GetOptions() { return m_storyOptions; }
|
||||
virtual bool UseResource(const std::string &label);
|
||||
virtual std::string GetName() const override { return m_name; }
|
||||
virtual std::string GetTitleImage() const override { return m_titleImage; }
|
||||
virtual std::string GetTitleSound() const override { return m_titleSound; }
|
||||
virtual void SetProjectType(IStoryProject::Type type) override { m_projectType = type; }
|
||||
virtual void SetImageFormat(Resource::ImageFormat format) override;
|
||||
virtual void SetSoundFormat(Resource::SoundFormat format) override;
|
||||
virtual void SetDisplayFormat(int w, int h) override;
|
||||
virtual void SetName(const std::string &name) override { m_name = name; }
|
||||
virtual void SetUuid(const std::string &uuid) override { m_uuid = uuid; }
|
||||
virtual Type GetProjectType() const override { return m_projectType; }
|
||||
std::vector<FunctionInfo> GetFunctionsList() const override;
|
||||
|
||||
// Node interaction
|
||||
std::shared_ptr<StoryPage> CreatePage(const std::string_view uuid);
|
||||
std::shared_ptr<StoryPage> GetPage(const std::string_view &uuid);
|
||||
void AddNode(const std::string_view &page, std::shared_ptr<BaseNode> node);
|
||||
|
||||
void AddConnection(const std::string_view &page, std::shared_ptr<Connection> c);
|
||||
void DeleteNode(const std::string_view &page, const std::string &id);
|
||||
void DeleteLink(const std::string_view &page, std::shared_ptr<Connection> c);
|
||||
std::pair<std::list<std::shared_ptr<BaseNode>>::iterator, std::list<std::shared_ptr<BaseNode>>::iterator> Nodes(const std::string_view &page_uuid);
|
||||
std::pair<std::list<std::shared_ptr<Connection>>::iterator, std::list<std::shared_ptr<Connection>>::iterator> Links(const std::string_view &page_uuid);
|
||||
|
||||
void ScanNodes(const std::function<bool(std::shared_ptr<BaseNode>)>& callback);
|
||||
void ScanVariable(const std::function<bool(std::shared_ptr<Variable> element)>& operation) override;
|
||||
void AddVariable() override;
|
||||
void DeleteVariable(int i) override;
|
||||
|
||||
private:
|
||||
ILogger &m_log;
|
||||
|
||||
// Project properties and location
|
||||
std::string m_name; /// human readable name
|
||||
std::string m_uuid;
|
||||
std::string m_titleImage;
|
||||
std::string m_titleSound;
|
||||
std::string m_description;
|
||||
|
||||
IStoryProject::Type m_projectType{IStoryProject::PROJECT_TYPE_STORY};
|
||||
|
||||
uint32_t m_version;
|
||||
bool m_selected{false};
|
||||
|
||||
std::unordered_set<std::string> m_usedLabels; // permet de ne pas générer un label qui existe déjà
|
||||
|
||||
std::filesystem::path m_assetsPath;
|
||||
std::list<std::shared_ptr<StoryPage>> m_pages;
|
||||
|
||||
std::vector<std::shared_ptr<Variable>> m_variables;
|
||||
|
||||
StoryOptions m_storyOptions;
|
||||
|
||||
bool m_initialized{false};
|
||||
|
||||
std::filesystem::path m_working_dir; /// Temporary folder based on the uuid, where the archive is unzipped
|
||||
std::filesystem::path m_project_file_path; /// JSON project file
|
||||
};
|
||||
|
||||
#endif // STORY_PROJECT_H
|
||||
|
||||
|
||||
134
core/story-manager/src/sys_lib.cpp
Normal file
134
core/story-manager/src/sys_lib.cpp
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
|
||||
#include "sys_lib.h"
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
void SysLib::EraseString(std::string &theString, const std::string &toErase)
|
||||
{
|
||||
std::size_t found;
|
||||
found = theString.find(toErase);
|
||||
if (found != std::string::npos)
|
||||
{
|
||||
theString.erase(found, toErase.size());
|
||||
}
|
||||
}
|
||||
|
||||
std::string SysLib::ToUpper(const std::string &input)
|
||||
{
|
||||
std::string str = input;
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string SysLib::ToLower(const std::string &input)
|
||||
{
|
||||
std::string str = input;
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string SysLib::ReadFile(const std::filesystem::path &filename)
|
||||
{
|
||||
// Open the stream to 'lock' the file.
|
||||
std::ifstream f(filename, std::ios::in | std::ios::binary);
|
||||
|
||||
// Obtain the size of the file.
|
||||
const auto sz = std::filesystem::file_size(filename);
|
||||
|
||||
// Create a buffer.
|
||||
std::string result(sz, '\0');
|
||||
|
||||
// Read the whole file into the buffer.
|
||||
f.read(result.data(), sz);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string SysLib::GetFileExtension(const std::string &fileName)
|
||||
{
|
||||
auto idx = fileName.find_last_of(".");
|
||||
if(idx != std::string::npos)
|
||||
{
|
||||
return fileName.substr(idx + 1);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string SysLib::GetFileName(const std::string &path)
|
||||
{
|
||||
if (path.size() > 0)
|
||||
{
|
||||
auto found = path.find_last_of("/\\");
|
||||
|
||||
if (found != std::string::npos)
|
||||
{
|
||||
return path.substr(found+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string SysLib::GetDirectory(const std::string &filePath)
|
||||
{
|
||||
try {
|
||||
std::filesystem::path absPath = std::filesystem::absolute(filePath);
|
||||
std::filesystem::path dirPath = absPath.parent_path(); // Récupère le chemin sans le nom du fichier
|
||||
return dirPath.string(); // Convertit le chemin en chaîne de caractères
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
// std::cerr << "Erreur: " << e.what() << std::endl;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SysLib::ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace)
|
||||
{
|
||||
std::size_t found;
|
||||
do
|
||||
{
|
||||
found = theString.find(toFind);
|
||||
if (found != std::string::npos)
|
||||
{
|
||||
theString.replace(found, 1, toReplace);
|
||||
}
|
||||
}
|
||||
while (found != std::string::npos);
|
||||
}
|
||||
|
||||
std::string SysLib::RemoveFileExtension(const std::string &filename)
|
||||
{
|
||||
// Trouver la dernière occurrence du point
|
||||
std::size_t dotPos = filename.rfind('.');
|
||||
if (dotPos == std::string::npos) {
|
||||
// Pas d'extension trouvée, retourner le nom tel quel
|
||||
return filename;
|
||||
}
|
||||
// Retourner la sous-chaîne avant le point
|
||||
return filename.substr(0, dotPos);
|
||||
}
|
||||
|
||||
std::string SysLib::Normalize(const std::string &input)
|
||||
{
|
||||
std::string valid_file = input;
|
||||
|
||||
std::replace(valid_file.begin(), valid_file.end(), '\\', '_');
|
||||
std::replace(valid_file.begin(), valid_file.end(), '/', '_');
|
||||
std::replace(valid_file.begin(), valid_file.end(), ':', '_');
|
||||
std::replace(valid_file.begin(), valid_file.end(), '?', '_');
|
||||
std::replace(valid_file.begin(), valid_file.end(), '\"', '_');
|
||||
std::replace(valid_file.begin(), valid_file.end(), '<', '_');
|
||||
std::replace(valid_file.begin(), valid_file.end(), '>', '_');
|
||||
std::replace(valid_file.begin(), valid_file.end(), '|', '_');
|
||||
std::replace(valid_file.begin(), valid_file.end(), ' ', '_');
|
||||
|
||||
return valid_file;
|
||||
}
|
||||
23
core/story-manager/src/sys_lib.h
Normal file
23
core/story-manager/src/sys_lib.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
class SysLib
|
||||
{
|
||||
public:
|
||||
|
||||
static std::string GetFileExtension(const std::string &FileName);
|
||||
static std::string GetFileName(const std::string &path);
|
||||
static std::string GetDirectory(const std::string &filePath);
|
||||
static std::string RemoveFileExtension(const std::string &FileName);
|
||||
static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace);
|
||||
static std::string Normalize(const std::string &input);
|
||||
static void EraseString(std::string &theString, const std::string &toErase);
|
||||
static std::string ToUpper(const std::string &input);
|
||||
static std::string ToLower(const std::string &input);
|
||||
static std::string ReadFile(const std::filesystem::path &filename);
|
||||
static bool FileExists(const std::string& path) {
|
||||
return std::filesystem::exists(path);
|
||||
}
|
||||
};
|
||||
64
core/story-manager/src/utils/json_reader.h
Normal file
64
core/story-manager/src/utils/json_reader.h
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include <json.hpp>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
class JsonReader {
|
||||
public:
|
||||
/**
|
||||
* @brief Récupère une valeur d'un objet JSON.
|
||||
* * Cette fonction est similaire à value(), mais ajoute un logging et
|
||||
* lance optionnellement une exception si la clé est manquante ou si la conversion échoue.
|
||||
* * @tparam ValueType Le type C++ de la valeur attendue.
|
||||
* @param j L'objet JSON dans lequel chercher.
|
||||
* @param key La clé (champ) à chercher.
|
||||
* @param defaultValue La valeur à retourner en cas d'échec ou de clé manquante.
|
||||
* @param logOnError Si true, affiche un message d'erreur sur cerr.
|
||||
* @param throwOnError Si true, lance une std::runtime_error en cas d'échec.
|
||||
* @return ValueType La valeur trouvée ou la defaultValue.
|
||||
*/
|
||||
template<typename ValueType>
|
||||
static ValueType get(
|
||||
const json& j,
|
||||
const std::string& key,
|
||||
const ValueType& defaultValue,
|
||||
bool logOnError = true,
|
||||
bool throwOnError = false
|
||||
) {
|
||||
// 1. Vérification de la présence de la clé
|
||||
if (!j.contains(key)) {
|
||||
const std::string error_msg = "Clé '" + key + "' manquante dans l'objet JSON. Utilisation de la valeur par défaut.";
|
||||
|
||||
if (logOnError) {
|
||||
std::cout << "⚠️ ERREUR (JsonGetter) : " << error_msg << std::endl;
|
||||
}
|
||||
if (throwOnError) {
|
||||
throw std::runtime_error(error_msg);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 2. Récupération de la valeur avec gestion de l'erreur de type
|
||||
try {
|
||||
// Utiliser 'at()' est plus sûr car il lève une exception en cas de type incorrect
|
||||
// si la conversion n'est pas possible, ce qui est mieux que value() pour le diagnostic.
|
||||
return j.at(key).get<ValueType>();
|
||||
|
||||
} catch (const nlohmann::json::type_error& e) {
|
||||
// Le type existe, mais il est incompatible (ex: string au lieu de int)
|
||||
const std::string error_msg = "Erreur de type pour la clé '" + key + "'. Attendu: " + typeid(ValueType).name() + ". Détails: " + e.what() + ". Utilisation de la valeur par défaut.";
|
||||
|
||||
if (logOnError) {
|
||||
std::cout << "❌ ERREUR (JsonGetter) : " << error_msg << std::endl;
|
||||
}
|
||||
if (throwOnError) {
|
||||
throw std::runtime_error(error_msg);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
// Note: l'exception out_of_range est gérée par le 'contains' ci-dessus.
|
||||
}
|
||||
};
|
||||
253
core/story-manager/src/variable.h
Normal file
253
core/story-manager/src/variable.h
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <variant>
|
||||
|
||||
#include "uuid.h"
|
||||
|
||||
class Variable
|
||||
{
|
||||
|
||||
public:
|
||||
enum class ValueType {
|
||||
INTEGER,
|
||||
FLOAT,
|
||||
BOOL,
|
||||
STRING
|
||||
};
|
||||
|
||||
enum RandomFlags
|
||||
{
|
||||
CHARSET_ALPHABET_LOWER = 0x1, // "abcdefghijklmnopqrstuvwxyz"
|
||||
CHARSET_ALPHABET_UPPER = 0x2, // "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
CHARSET_NUMBERS = 0x4, // "0123456789"
|
||||
CHARSET_SIGNS = 0x8, // "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
||||
ALL_CHARSETS = CHARSET_ALPHABET_LOWER | CHARSET_ALPHABET_UPPER |CHARSET_NUMBERS | CHARSET_SIGNS
|
||||
};
|
||||
|
||||
static const int NameMaxSize = 32; // Max size for the variable name
|
||||
|
||||
Variable() {
|
||||
m_uuid = Uuid().String();
|
||||
m_label = Variable::GenerateRandomString(10, Variable::CHARSET_ALPHABET_LOWER | Variable::CHARSET_ALPHABET_UPPER );
|
||||
}
|
||||
|
||||
Variable (const std::string &name)
|
||||
: Variable()
|
||||
{
|
||||
m_variableName = name;
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
void SetUuid(const std::string& uuid) {
|
||||
m_uuid = uuid;
|
||||
}
|
||||
|
||||
void SetVariableName(const std::string& name) {
|
||||
m_variableName = name;
|
||||
}
|
||||
|
||||
void SetConstant(bool isConstant) {
|
||||
m_isConstant = isConstant;
|
||||
}
|
||||
|
||||
void SetValueType(ValueType type) {
|
||||
m_valueType = type;
|
||||
// Reset value to default for new type
|
||||
switch (type) {
|
||||
case ValueType::INTEGER:
|
||||
m_value = 0;
|
||||
break;
|
||||
case ValueType::FLOAT:
|
||||
m_value = 0.0f;
|
||||
break;
|
||||
case ValueType::BOOL:
|
||||
m_value = false;
|
||||
break;
|
||||
case ValueType::STRING:
|
||||
m_value = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void SetValue(const T& value) {
|
||||
try {
|
||||
m_value = value;
|
||||
} catch (const std::bad_variant_access&) {
|
||||
throw std::runtime_error("[variable.h] SetValue(): Invalid value type for variable");
|
||||
}
|
||||
}
|
||||
|
||||
void SetTextValue(const std::string& value) {
|
||||
SetValue<std::string>(value);
|
||||
m_valueType = ValueType::STRING;
|
||||
}
|
||||
|
||||
void SetIntegerValue(int value) {
|
||||
SetValue<int>(value);
|
||||
m_valueType = ValueType::INTEGER;
|
||||
}
|
||||
|
||||
void SetFloatValue(float value) {
|
||||
SetValue<float>(value);
|
||||
m_valueType = ValueType::FLOAT;
|
||||
}
|
||||
|
||||
void SetBoolValue(bool value) {
|
||||
SetValue<bool>(value);
|
||||
m_valueType = ValueType::BOOL;
|
||||
}
|
||||
|
||||
void SetScalePower(int scalePower) {
|
||||
m_scalePower = scalePower;
|
||||
}
|
||||
|
||||
// Getters
|
||||
std::string GetVariableName() const {
|
||||
return m_variableName;
|
||||
}
|
||||
|
||||
bool IsConstant() const {
|
||||
return m_isConstant;
|
||||
}
|
||||
|
||||
ValueType GetValueType() const {
|
||||
return m_valueType;
|
||||
}
|
||||
|
||||
std::string GetLabel() const {
|
||||
return m_label;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T GetValue() const {
|
||||
try {
|
||||
return std::get<T>(m_value);
|
||||
} catch (const std::bad_variant_access&) {
|
||||
throw std::runtime_error("[variable.h] GetValue(): Invalid value type requested");
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetStringValue() const {
|
||||
return GetValue<std::string>();
|
||||
}
|
||||
|
||||
int GetIntegerValue() const {
|
||||
return GetValue<int>();
|
||||
}
|
||||
|
||||
float GetFloatValue() const {
|
||||
return GetValue<float>();
|
||||
}
|
||||
|
||||
bool GetBoolValue() const {
|
||||
return GetValue<bool>();
|
||||
}
|
||||
|
||||
std::string GetValueAsString() const {
|
||||
switch (m_valueType) {
|
||||
case ValueType::INTEGER:
|
||||
return std::to_string(GetValue<int>());
|
||||
case ValueType::FLOAT:
|
||||
return std::to_string(GetValue<float>());
|
||||
case ValueType::BOOL:
|
||||
return GetValue<bool>() ? "true" : "false";
|
||||
case ValueType::STRING:
|
||||
return GetValue<std::string>();
|
||||
default:
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
using VariableValue = std::variant<int, float, bool, std::string>;
|
||||
|
||||
std::string GetUuid() const {
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
bool IsString() const {
|
||||
return m_valueType == ValueType::STRING;
|
||||
}
|
||||
bool IsInteger() const {
|
||||
return m_valueType == ValueType::INTEGER;
|
||||
}
|
||||
bool IsFloat() const {
|
||||
return m_valueType == ValueType::FLOAT;
|
||||
}
|
||||
bool IsBool() const {
|
||||
return m_valueType == ValueType::BOOL;
|
||||
}
|
||||
|
||||
int GetScalePower() const {
|
||||
return m_scalePower;
|
||||
}
|
||||
|
||||
static std::string GenerateRandomString(size_t length, uint32_t flags)
|
||||
{
|
||||
std::string charset = "";
|
||||
|
||||
if (flags & CHARSET_ALPHABET_LOWER)
|
||||
{
|
||||
charset += "abcdefghijklmnopqrstuvwxyz";
|
||||
}
|
||||
|
||||
if (flags & CHARSET_ALPHABET_UPPER)
|
||||
{
|
||||
charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
}
|
||||
|
||||
if (flags & CHARSET_NUMBERS)
|
||||
{
|
||||
charset += "0123456789";
|
||||
}
|
||||
|
||||
if (flags & CHARSET_SIGNS)
|
||||
{
|
||||
charset += "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
||||
}
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 generator(rd());
|
||||
std::uniform_int_distribution<> distribution(0, charset.size() - 1);
|
||||
|
||||
std::string result;
|
||||
result.reserve(length);
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
result += charset[distribution(generator)];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string ValueTypeToString(ValueType type) {
|
||||
switch (type) {
|
||||
case ValueType::INTEGER: return "Integer";
|
||||
case ValueType::FLOAT: return "Float";
|
||||
case ValueType::BOOL: return "Bool";
|
||||
case ValueType::STRING: return "String";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static ValueType StringToValueType(const std::string& type) {
|
||||
if (type == "Integer") return ValueType::INTEGER;
|
||||
if (type == "Float") return ValueType::FLOAT;
|
||||
if (type == "Bool") return ValueType::BOOL;
|
||||
if (type == "String") return ValueType::STRING;
|
||||
throw std::runtime_error("[variable.h] StringToValueType(): Invalid value type string");
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_variableName; // nom humain
|
||||
ValueType m_valueType;
|
||||
VariableValue m_value;
|
||||
bool m_isConstant;
|
||||
std::string m_uuid; // pour identifier le variable dans le JSON du projet
|
||||
std::string m_label; // pour la génération assembleur
|
||||
int m_scalePower; // Nombre de bits pour la partie fractionnaire
|
||||
|
||||
};
|
||||
76
core/tests/CMakeLists.txt
Normal file
76
core/tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
cmake_minimum_required(VERSION 3.11)
|
||||
|
||||
project(core_tests LANGUAGES CXX C)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# ============================================================================
|
||||
# Téléchargement et configuration de Catch2 v3
|
||||
# ============================================================================
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
Catch2
|
||||
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||
GIT_TAG v3.7.1 # Dernière version stable de Catch2 v3
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(Catch2)
|
||||
|
||||
# Ajouter le module Catch2 pour la découverte automatique des tests
|
||||
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
|
||||
|
||||
# ============================================================================
|
||||
# Exécutable de tests
|
||||
# ============================================================================
|
||||
add_executable(${PROJECT_NAME}
|
||||
test_parser.cpp
|
||||
test_vm.cpp
|
||||
test_ast.cpp
|
||||
test_print_node.cpp
|
||||
test_branch.cpp
|
||||
test_loops.cpp
|
||||
test_json.cpp
|
||||
|
||||
../story-manager/src/nodes/base_node.cpp
|
||||
../story-manager/src/nodes/branch_node.cpp
|
||||
../story-manager/src/nodes/print_node.cpp
|
||||
../story-manager/src/nodes/variable_node.cpp
|
||||
../story-manager/src/nodes/connection.cpp
|
||||
../story-manager/src/nodes/wait_event_node.cpp
|
||||
../story-manager/src/nodes/wait_delay_node.cpp
|
||||
../story-manager/src/nodes/play_media_node.cpp
|
||||
../story-manager/src/nodes/send_signal_node.cpp
|
||||
../story-manager/src/nodes/for_loop_node.cpp
|
||||
../story-manager/src/nodes/while_loop_node.cpp
|
||||
../story-manager/src/nodes/break_node.cpp
|
||||
../story-manager/src/nodes/continue_node.cpp
|
||||
|
||||
../chip32/chip32_assembler.cpp
|
||||
../chip32/chip32_vm.c
|
||||
../chip32/chip32_binary_format.c
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
../chip32
|
||||
../story-manager/src
|
||||
../story-manager/src/nodes
|
||||
../story-manager/src/compiler
|
||||
../story-manager/src/utils
|
||||
../story-manager/interfaces
|
||||
../../shared
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# Linkage avec Catch2
|
||||
# ============================================================================
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Catch2::Catch2WithMain)
|
||||
|
||||
# ============================================================================
|
||||
# Découverte automatique des tests (optionnel mais recommandé)
|
||||
# ============================================================================
|
||||
include(CTest)
|
||||
include(Catch)
|
||||
catch_discover_tests(${PROJECT_NAME})
|
||||
1233
core/tests/test_ast.cpp
Normal file
1233
core/tests/test_ast.cpp
Normal file
File diff suppressed because it is too large
Load diff
672
core/tests/test_branch.cpp
Normal file
672
core/tests/test_branch.cpp
Normal file
|
|
@ -0,0 +1,672 @@
|
|||
// ===================================================================
|
||||
// test_branch_node.cpp
|
||||
// Tests complets pour BranchNode avec différents types de conditions
|
||||
// ===================================================================
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "branch_node.h"
|
||||
#include "variable_node.h"
|
||||
#include "function_entry_node.h"
|
||||
#include "operator_node.h"
|
||||
#include "print_node.h"
|
||||
#include "connection.h"
|
||||
#include "ast_builder.h"
|
||||
#include "assembly_generator_chip32_tac.h"
|
||||
#include "chip32_machine.h"
|
||||
#include "variable.h"
|
||||
|
||||
// ===================================================================
|
||||
// TEST 1 : BranchNode avec condition booléenne directe (variable)
|
||||
// ===================================================================
|
||||
TEST_CASE("Branch with direct boolean variable", "[branch][boolean][variable]") {
|
||||
std::cout << "\n=== Test: Branch with direct boolean variable ===\n";
|
||||
|
||||
// Variables
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
// Condition = 1 (true)
|
||||
auto varCondition = std::make_shared<Variable>("condition");
|
||||
varCondition->SetIntegerValue(1);
|
||||
variables.push_back(varCondition);
|
||||
|
||||
// Nodes
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto varNode = std::make_shared<VariableNode>("var-condition");
|
||||
varNode->SetVariable(varCondition);
|
||||
|
||||
auto branchNode = std::make_shared<BranchNode>("branch-node");
|
||||
branchNode->Initialize();
|
||||
|
||||
auto printTrue = std::make_shared<PrintNode>("print-true");
|
||||
printTrue->SetText("TRUE branch executed");
|
||||
printTrue->Initialize();
|
||||
|
||||
auto printFalse = std::make_shared<PrintNode>("print-false");
|
||||
printFalse->SetText("FALSE branch executed");
|
||||
printFalse->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry,
|
||||
varNode,
|
||||
branchNode,
|
||||
printTrue,
|
||||
printFalse
|
||||
};
|
||||
|
||||
// Connections
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
// Execution: Entry → Branch
|
||||
auto execConn = std::make_shared<Connection>();
|
||||
execConn->outNodeId = functionEntry->GetId();
|
||||
execConn->outPortIndex = 0;
|
||||
execConn->inNodeId = branchNode->GetId();
|
||||
execConn->inPortIndex = 0;
|
||||
execConn->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn);
|
||||
|
||||
// Data: Variable → Branch.condition (port 1)
|
||||
auto dataConn = std::make_shared<Connection>();
|
||||
dataConn->outNodeId = varNode->GetId();
|
||||
dataConn->outPortIndex = 0;
|
||||
dataConn->inNodeId = branchNode->GetId();
|
||||
dataConn->inPortIndex = 1;
|
||||
dataConn->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn);
|
||||
|
||||
// Execution: Branch.true → PrintTrue
|
||||
auto execTrue = std::make_shared<Connection>();
|
||||
execTrue->outNodeId = branchNode->GetId();
|
||||
execTrue->outPortIndex = 0;
|
||||
execTrue->inNodeId = printTrue->GetId();
|
||||
execTrue->inPortIndex = 0;
|
||||
execTrue->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execTrue);
|
||||
|
||||
// Execution: Branch.false → PrintFalse
|
||||
auto execFalse = std::make_shared<Connection>();
|
||||
execFalse->outNodeId = branchNode->GetId();
|
||||
execFalse->outPortIndex = 1;
|
||||
execFalse->inNodeId = printFalse->GetId();
|
||||
execFalse->inPortIndex = 0;
|
||||
execFalse->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execFalse);
|
||||
|
||||
// Build AST
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
// Generate Assembly using TAC
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables,
|
||||
"2025-01-10 10:00:00",
|
||||
"test-branch-bool-var",
|
||||
true,
|
||||
true,
|
||||
1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
std::cout << "\n--- Generated Assembly ---\n";
|
||||
std::cout << assembly << std::endl;
|
||||
|
||||
// Verify
|
||||
REQUIRE(assembly.find("DATA") != std::string::npos);
|
||||
REQUIRE(assembly.find("CODE") != std::string::npos);
|
||||
REQUIRE(assembly.find("branch_true") != std::string::npos);
|
||||
REQUIRE(assembly.find("branch_false") != std::string::npos);
|
||||
|
||||
// Execute
|
||||
std::cout << "\n--- Execution Output ---\n";
|
||||
std::cout << "Expected: TRUE branch executed (condition = 1)\n";
|
||||
std::cout << "Actual: ";
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(assembly);
|
||||
|
||||
std::cout << "\n✓ Test passed\n";
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// TEST 2 : BranchNode avec OperatorNode (comparaison)
|
||||
// ===================================================================
|
||||
TEST_CASE("Branch with OperatorNode comparison", "[branch][operator][comparison]") {
|
||||
std::cout << "\n=== Test: Branch with OperatorNode (A > B) ===\n";
|
||||
|
||||
// Variables
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
auto varA = std::make_shared<Variable>("A");
|
||||
varA->SetIntegerValue(10);
|
||||
variables.push_back(varA);
|
||||
|
||||
auto varB = std::make_shared<Variable>("B");
|
||||
varB->SetIntegerValue(5);
|
||||
variables.push_back(varB);
|
||||
|
||||
// Nodes
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto varNodeA = std::make_shared<VariableNode>("var-A");
|
||||
varNodeA->SetVariable(varA);
|
||||
|
||||
auto varNodeB = std::make_shared<VariableNode>("var-B");
|
||||
varNodeB->SetVariable(varB);
|
||||
|
||||
auto operatorNode = std::make_shared<OperatorNode>("operator-gt");
|
||||
operatorNode->SetOperationType(OperatorNode::OperationType::GREATER_THAN);
|
||||
operatorNode->Initialize();
|
||||
|
||||
auto branchNode = std::make_shared<BranchNode>("branch-node");
|
||||
branchNode->Initialize();
|
||||
|
||||
auto printTrue = std::make_shared<PrintNode>("print-true");
|
||||
printTrue->SetText("A is greater than B");
|
||||
printTrue->Initialize();
|
||||
|
||||
auto printFalse = std::make_shared<PrintNode>("print-false");
|
||||
printFalse->SetText("A is NOT greater than B");
|
||||
printFalse->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry,
|
||||
varNodeA,
|
||||
varNodeB,
|
||||
operatorNode,
|
||||
branchNode,
|
||||
printTrue,
|
||||
printFalse
|
||||
};
|
||||
|
||||
// Connections
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
// Execution: Entry → Branch
|
||||
auto execConn = std::make_shared<Connection>();
|
||||
execConn->outNodeId = functionEntry->GetId();
|
||||
execConn->outPortIndex = 0;
|
||||
execConn->inNodeId = branchNode->GetId();
|
||||
execConn->inPortIndex = 0;
|
||||
execConn->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn);
|
||||
|
||||
// Data: A → Operator.in1
|
||||
auto dataConn1 = std::make_shared<Connection>();
|
||||
dataConn1->outNodeId = varNodeA->GetId();
|
||||
dataConn1->outPortIndex = 0;
|
||||
dataConn1->inNodeId = operatorNode->GetId();
|
||||
dataConn1->inPortIndex = 0;
|
||||
dataConn1->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn1);
|
||||
|
||||
// Data: B → Operator.in2
|
||||
auto dataConn2 = std::make_shared<Connection>();
|
||||
dataConn2->outNodeId = varNodeB->GetId();
|
||||
dataConn2->outPortIndex = 0;
|
||||
dataConn2->inNodeId = operatorNode->GetId();
|
||||
dataConn2->inPortIndex = 1;
|
||||
dataConn2->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn2);
|
||||
|
||||
// Data: Operator.out → Branch.condition
|
||||
auto dataConn3 = std::make_shared<Connection>();
|
||||
dataConn3->outNodeId = operatorNode->GetId();
|
||||
dataConn3->outPortIndex = 0;
|
||||
dataConn3->inNodeId = branchNode->GetId();
|
||||
dataConn3->inPortIndex = 1;
|
||||
dataConn3->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn3);
|
||||
|
||||
// Execution: Branch.true → PrintTrue
|
||||
auto execTrue = std::make_shared<Connection>();
|
||||
execTrue->outNodeId = branchNode->GetId();
|
||||
execTrue->outPortIndex = 0;
|
||||
execTrue->inNodeId = printTrue->GetId();
|
||||
execTrue->inPortIndex = 0;
|
||||
execTrue->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execTrue);
|
||||
|
||||
// Execution: Branch.false → PrintFalse
|
||||
auto execFalse = std::make_shared<Connection>();
|
||||
execFalse->outNodeId = branchNode->GetId();
|
||||
execFalse->outPortIndex = 1;
|
||||
execFalse->inNodeId = printFalse->GetId();
|
||||
execFalse->inPortIndex = 0;
|
||||
execFalse->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execFalse);
|
||||
|
||||
// Build and generate
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables, "2025-01-10", "test-branch-operator", true, true, 1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
std::cout << "\n--- Generated Assembly ---\n";
|
||||
std::cout << assembly << std::endl;
|
||||
|
||||
// Verify TAC structure
|
||||
REQUIRE(assembly.find("gt") != std::string::npos); // Greater Than opcode
|
||||
REQUIRE(assembly.find("branch_true") != std::string::npos);
|
||||
REQUIRE(assembly.find("branch_false") != std::string::npos);
|
||||
|
||||
// Execute
|
||||
std::cout << "\n--- Execution Output ---\n";
|
||||
std::cout << "Expected: A is greater than B (10 > 5)\n";
|
||||
std::cout << "Actual: ";
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(assembly);
|
||||
|
||||
std::cout << "\n✓ Test passed\n";
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// TEST 3 : Tester différentes valeurs (0 = false, autres = true)
|
||||
// ===================================================================
|
||||
TEST_CASE("Branch with various condition values", "[branch][values][convention]") {
|
||||
std::cout << "\n=== Test: Branch with various condition values ===\n";
|
||||
|
||||
struct TestCase {
|
||||
int conditionValue;
|
||||
bool expectedTrue;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
std::vector<TestCase> testCases = {
|
||||
{0, false, "0 should be FALSE"},
|
||||
{1, true, "1 should be TRUE"},
|
||||
{42, true, "42 should be TRUE"},
|
||||
{-5, true, "-5 should be TRUE"},
|
||||
{100, true, "100 should be TRUE"}
|
||||
};
|
||||
|
||||
for (const auto& test : testCases) {
|
||||
std::cout << "\n--- Testing: " << test.description << " ---\n";
|
||||
|
||||
// Variables
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
auto varCondition = std::make_shared<Variable>("condition");
|
||||
varCondition->SetIntegerValue(test.conditionValue);
|
||||
variables.push_back(varCondition);
|
||||
|
||||
// Nodes
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto varNode = std::make_shared<VariableNode>("var-condition");
|
||||
varNode->SetVariable(varCondition);
|
||||
|
||||
auto branchNode = std::make_shared<BranchNode>("branch-node");
|
||||
branchNode->Initialize();
|
||||
|
||||
auto printTrue = std::make_shared<PrintNode>("print-true");
|
||||
printTrue->SetText("TRUE");
|
||||
printTrue->Initialize();
|
||||
|
||||
auto printFalse = std::make_shared<PrintNode>("print-false");
|
||||
printFalse->SetText("FALSE");
|
||||
printFalse->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry, varNode, branchNode, printTrue, printFalse
|
||||
};
|
||||
|
||||
// Connections (même pattern que TEST 1)
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
auto execConn = std::make_shared<Connection>();
|
||||
execConn->outNodeId = functionEntry->GetId();
|
||||
execConn->outPortIndex = 0;
|
||||
execConn->inNodeId = branchNode->GetId();
|
||||
execConn->inPortIndex = 0;
|
||||
execConn->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn);
|
||||
|
||||
auto dataConn = std::make_shared<Connection>();
|
||||
dataConn->outNodeId = varNode->GetId();
|
||||
dataConn->outPortIndex = 0;
|
||||
dataConn->inNodeId = branchNode->GetId();
|
||||
dataConn->inPortIndex = 1;
|
||||
dataConn->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn);
|
||||
|
||||
auto execTrue = std::make_shared<Connection>();
|
||||
execTrue->outNodeId = branchNode->GetId();
|
||||
execTrue->outPortIndex = 0;
|
||||
execTrue->inNodeId = printTrue->GetId();
|
||||
execTrue->inPortIndex = 0;
|
||||
execTrue->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execTrue);
|
||||
|
||||
auto execFalse = std::make_shared<Connection>();
|
||||
execFalse->outNodeId = branchNode->GetId();
|
||||
execFalse->outPortIndex = 1;
|
||||
execFalse->inNodeId = printFalse->GetId();
|
||||
execFalse->inPortIndex = 0;
|
||||
execFalse->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execFalse);
|
||||
|
||||
// Build and generate
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables, "2025-01-10", "test-branch-values", false, true, 1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
// Execute
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(assembly);
|
||||
|
||||
std::string output = machine.printOutput;
|
||||
std::string expected = test.expectedTrue ? "TRUE" : "FALSE";
|
||||
|
||||
std::cout << "Condition value: " << test.conditionValue << "\n";
|
||||
std::cout << "Expected: " << expected << ", Got: " << output << "\n";
|
||||
|
||||
REQUIRE(output.find(expected) != std::string::npos);
|
||||
}
|
||||
|
||||
std::cout << "\n✓ All value tests passed\n";
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// TEST 4 : Tester tous les opérateurs de comparaison
|
||||
// ===================================================================
|
||||
TEST_CASE("Branch with all comparison operators", "[branch][operators][all]") {
|
||||
std::cout << "\n=== Test: All comparison operators ===\n";
|
||||
|
||||
struct TestCase {
|
||||
OperatorNode::OperationType op;
|
||||
int a, b;
|
||||
bool expectedTrue;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
std::vector<TestCase> testCases = {
|
||||
{OperatorNode::OperationType::EQUAL, 5, 5, true, "5 == 5"},
|
||||
{OperatorNode::OperationType::EQUAL, 5, 10, false, "5 == 10"},
|
||||
{OperatorNode::OperationType::NOT_EQUAL, 5, 10, true, "5 != 10"},
|
||||
{OperatorNode::OperationType::NOT_EQUAL, 5, 5, false, "5 != 5"},
|
||||
{OperatorNode::OperationType::GREATER_THAN, 10, 5, true, "10 > 5"},
|
||||
{OperatorNode::OperationType::GREATER_THAN, 5, 10, false, "5 > 10"},
|
||||
{OperatorNode::OperationType::LESS_THAN, 5, 10, true, "5 < 10"},
|
||||
{OperatorNode::OperationType::LESS_THAN, 10, 5, false, "10 < 5"},
|
||||
{OperatorNode::OperationType::GREATER_EQUAL, 10, 5, true, "10 >= 5"},
|
||||
{OperatorNode::OperationType::GREATER_EQUAL, 5, 5, true, "5 >= 5"},
|
||||
{OperatorNode::OperationType::GREATER_EQUAL, 5, 10, false, "5 >= 10"},
|
||||
{OperatorNode::OperationType::LESS_EQUAL, 5, 10, true, "5 <= 10"},
|
||||
{OperatorNode::OperationType::LESS_EQUAL, 5, 5, true, "5 <= 5"},
|
||||
{OperatorNode::OperationType::LESS_EQUAL, 10, 5, false, "10 <= 5"}
|
||||
};
|
||||
|
||||
for (const auto& test : testCases) {
|
||||
std::cout << "\n--- Testing: " << test.description << " ---\n";
|
||||
|
||||
// Variables
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
auto varA = std::make_shared<Variable>("A");
|
||||
varA->SetIntegerValue(test.a);
|
||||
variables.push_back(varA);
|
||||
|
||||
auto varB = std::make_shared<Variable>("B");
|
||||
varB->SetIntegerValue(test.b);
|
||||
variables.push_back(varB);
|
||||
|
||||
// Nodes (même structure que TEST 2)
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto varNodeA = std::make_shared<VariableNode>("var-A");
|
||||
varNodeA->SetVariable(varA);
|
||||
|
||||
auto varNodeB = std::make_shared<VariableNode>("var-B");
|
||||
varNodeB->SetVariable(varB);
|
||||
|
||||
auto operatorNode = std::make_shared<OperatorNode>("operator");
|
||||
operatorNode->SetOperationType(test.op);
|
||||
operatorNode->Initialize();
|
||||
|
||||
auto branchNode = std::make_shared<BranchNode>("branch-node");
|
||||
branchNode->Initialize();
|
||||
|
||||
auto printTrue = std::make_shared<PrintNode>("print-true");
|
||||
printTrue->SetText("TRUE");
|
||||
printTrue->Initialize();
|
||||
|
||||
auto printFalse = std::make_shared<PrintNode>("print-false");
|
||||
printFalse->SetText("FALSE");
|
||||
printFalse->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry, varNodeA, varNodeB, operatorNode, branchNode, printTrue, printFalse
|
||||
};
|
||||
|
||||
// Connections (pattern complet)
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
auto execConn = std::make_shared<Connection>();
|
||||
execConn->outNodeId = functionEntry->GetId();
|
||||
execConn->outPortIndex = 0;
|
||||
execConn->inNodeId = branchNode->GetId();
|
||||
execConn->inPortIndex = 0;
|
||||
execConn->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn);
|
||||
|
||||
auto dataConn1 = std::make_shared<Connection>();
|
||||
dataConn1->outNodeId = varNodeA->GetId();
|
||||
dataConn1->outPortIndex = 0;
|
||||
dataConn1->inNodeId = operatorNode->GetId();
|
||||
dataConn1->inPortIndex = 0;
|
||||
dataConn1->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn1);
|
||||
|
||||
auto dataConn2 = std::make_shared<Connection>();
|
||||
dataConn2->outNodeId = varNodeB->GetId();
|
||||
dataConn2->outPortIndex = 0;
|
||||
dataConn2->inNodeId = operatorNode->GetId();
|
||||
dataConn2->inPortIndex = 1;
|
||||
dataConn2->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn2);
|
||||
|
||||
auto dataConn3 = std::make_shared<Connection>();
|
||||
dataConn3->outNodeId = operatorNode->GetId();
|
||||
dataConn3->outPortIndex = 0;
|
||||
dataConn3->inNodeId = branchNode->GetId();
|
||||
dataConn3->inPortIndex = 1;
|
||||
dataConn3->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn3);
|
||||
|
||||
auto execTrue = std::make_shared<Connection>();
|
||||
execTrue->outNodeId = branchNode->GetId();
|
||||
execTrue->outPortIndex = 0;
|
||||
execTrue->inNodeId = printTrue->GetId();
|
||||
execTrue->inPortIndex = 0;
|
||||
execTrue->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execTrue);
|
||||
|
||||
auto execFalse = std::make_shared<Connection>();
|
||||
execFalse->outNodeId = branchNode->GetId();
|
||||
execFalse->outPortIndex = 1;
|
||||
execFalse->inNodeId = printFalse->GetId();
|
||||
execFalse->inPortIndex = 0;
|
||||
execFalse->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execFalse);
|
||||
|
||||
// Build and execute
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables, "2025-01-10", "test-branch-op", false, true, 1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(assembly);
|
||||
|
||||
std::string output = machine.printOutput;
|
||||
std::string expected = test.expectedTrue ? "TRUE" : "FALSE";
|
||||
|
||||
std::cout << "Expected: " << expected << ", Got: " << output << "\n";
|
||||
REQUIRE(output.find(expected) != std::string::npos);
|
||||
}
|
||||
|
||||
std::cout << "\n✓ All operator tests passed\n";
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// TEST 5 : BranchNode avec résultat d'opération arithmétique
|
||||
// ===================================================================
|
||||
TEST_CASE("Branch with arithmetic operation result", "[branch][arithmetic]") {
|
||||
std::cout << "\n=== Test: Branch with arithmetic result (5 - 5 = 0) ===\n";
|
||||
|
||||
// Variables
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
auto varA = std::make_shared<Variable>("A");
|
||||
varA->SetIntegerValue(5);
|
||||
variables.push_back(varA);
|
||||
|
||||
auto varB = std::make_shared<Variable>("B");
|
||||
varB->SetIntegerValue(5);
|
||||
variables.push_back(varB);
|
||||
|
||||
// Nodes
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto varNodeA = std::make_shared<VariableNode>("var-A");
|
||||
varNodeA->SetVariable(varA);
|
||||
|
||||
auto varNodeB = std::make_shared<VariableNode>("var-B");
|
||||
varNodeB->SetVariable(varB);
|
||||
|
||||
// Soustraction: 5 - 5 = 0 (false)
|
||||
auto subNode = std::make_shared<OperatorNode>("operator-sub");
|
||||
subNode->SetOperationType(OperatorNode::OperationType::SUBTRACT);
|
||||
subNode->Initialize();
|
||||
|
||||
auto branchNode = std::make_shared<BranchNode>("branch-node");
|
||||
branchNode->Initialize();
|
||||
|
||||
auto printTrue = std::make_shared<PrintNode>("print-true");
|
||||
printTrue->SetText("Result is NON-ZERO");
|
||||
printTrue->Initialize();
|
||||
|
||||
auto printFalse = std::make_shared<PrintNode>("print-false");
|
||||
printFalse->SetText("Result is ZERO");
|
||||
printFalse->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry, varNodeA, varNodeB, subNode, branchNode, printTrue, printFalse
|
||||
};
|
||||
|
||||
// Connections
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
auto execConn = std::make_shared<Connection>();
|
||||
execConn->outNodeId = functionEntry->GetId();
|
||||
execConn->outPortIndex = 0;
|
||||
execConn->inNodeId = branchNode->GetId();
|
||||
execConn->inPortIndex = 0;
|
||||
execConn->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn);
|
||||
|
||||
auto dataConn1 = std::make_shared<Connection>();
|
||||
dataConn1->outNodeId = varNodeA->GetId();
|
||||
dataConn1->outPortIndex = 0;
|
||||
dataConn1->inNodeId = subNode->GetId();
|
||||
dataConn1->inPortIndex = 0;
|
||||
dataConn1->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn1);
|
||||
|
||||
auto dataConn2 = std::make_shared<Connection>();
|
||||
dataConn2->outNodeId = varNodeB->GetId();
|
||||
dataConn2->outPortIndex = 0;
|
||||
dataConn2->inNodeId = subNode->GetId();
|
||||
dataConn2->inPortIndex = 1;
|
||||
dataConn2->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn2);
|
||||
|
||||
auto dataConn3 = std::make_shared<Connection>();
|
||||
dataConn3->outNodeId = subNode->GetId();
|
||||
dataConn3->outPortIndex = 0;
|
||||
dataConn3->inNodeId = branchNode->GetId();
|
||||
dataConn3->inPortIndex = 1;
|
||||
dataConn3->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn3);
|
||||
|
||||
auto execTrue = std::make_shared<Connection>();
|
||||
execTrue->outNodeId = branchNode->GetId();
|
||||
execTrue->outPortIndex = 0;
|
||||
execTrue->inNodeId = printTrue->GetId();
|
||||
execTrue->inPortIndex = 0;
|
||||
execTrue->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execTrue);
|
||||
|
||||
auto execFalse = std::make_shared<Connection>();
|
||||
execFalse->outNodeId = branchNode->GetId();
|
||||
execFalse->outPortIndex = 1;
|
||||
execFalse->inNodeId = printFalse->GetId();
|
||||
execFalse->inPortIndex = 0;
|
||||
execFalse->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execFalse);
|
||||
|
||||
// Build and execute
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables, "2025-01-10", "test-branch-arithmetic", true, true, 1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
std::cout << "\n--- Generated Assembly ---\n";
|
||||
std::cout << assembly << std::endl;
|
||||
|
||||
// Verify subtraction is present
|
||||
REQUIRE(assembly.find("sub") != std::string::npos);
|
||||
|
||||
// Execute
|
||||
std::cout << "\n--- Execution Output ---\n";
|
||||
std::cout << "Expected: Result is ZERO (5 - 5 = 0)\n";
|
||||
std::cout << "Actual: ";
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(assembly);
|
||||
|
||||
std::string output = machine.printOutput;
|
||||
REQUIRE(output.find("ZERO") != std::string::npos);
|
||||
|
||||
std::cout << "\n✓ Test passed\n";
|
||||
}
|
||||
75
core/tests/test_json.cpp
Normal file
75
core/tests/test_json.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/matchers/catch_matchers_vector.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
#include "json_reader.h"
|
||||
#include "json.hpp"
|
||||
|
||||
using namespace Catch::Matchers;
|
||||
|
||||
TEST_CASE("JsonReader::get - Scénarios de base", "[JsonReader]") {
|
||||
json data = R"({
|
||||
"nom": "Bob",
|
||||
"age": 42,
|
||||
"is_admin": true
|
||||
})"_json;
|
||||
|
||||
// --- Scénario 1 : Succès (Clé présente, Type correct) ---
|
||||
SECTION("Succès - Récupération d'un entier") {
|
||||
int age = JsonReader::get<int>(data, "age", 0, false, false);
|
||||
REQUIRE(age == 42);
|
||||
}
|
||||
|
||||
SECTION("Succès - Récupération d'une chaîne") {
|
||||
std::string nom = JsonReader::get<std::string>(data, "nom", "N/A", false, false);
|
||||
REQUIRE(nom == "Bob");
|
||||
}
|
||||
|
||||
// --- Scénario 2 : Clé Manquante (Retourne la valeur par défaut) ---
|
||||
SECTION("Clé Manquante - Retourne la valeur par défaut") {
|
||||
// Log désactivé pour ne pas polluer la sortie de test
|
||||
double taux = JsonReader::get<double>(data, "taux_inexistant", 1.5, false, false);
|
||||
REQUIRE(taux == 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JsonReader::get - Gestion des Erreurs", "[JsonReader][Erreur]") {
|
||||
json data_err = R"({
|
||||
"taux_texte": "0.99",
|
||||
"liste": [1, 2]
|
||||
})"_json;
|
||||
|
||||
// --- Scénario 3 : Erreur de Type (Retourne la valeur par défaut) ---
|
||||
SECTION("Erreur de Type - Retourne la valeur par défaut (Int vs String)") {
|
||||
// Tente de lire "0.99" (string) comme un int.
|
||||
int taux = JsonReader::get<int>(data_err, "taux_texte", -1, false, false);
|
||||
REQUIRE(taux == -1);
|
||||
}
|
||||
|
||||
// --- Scénario 4 : Clé Manquante (Lance une exception) ---
|
||||
SECTION("Clé Manquante - Lance une runtime_error") {
|
||||
REQUIRE_THROWS_AS(
|
||||
JsonReader::get<std::string>(data_err, "cle_manquante_fatale", "N/A", false, true),
|
||||
std::runtime_error
|
||||
);
|
||||
// Vérifie le message pour s'assurer qu'il mentionne la clé
|
||||
REQUIRE_THROWS_WITH(
|
||||
JsonReader::get<std::string>(data_err, "cle_manquante_fatale", "N/A", false, true),
|
||||
ContainsSubstring("cle_manquante_fatale")
|
||||
);
|
||||
}
|
||||
|
||||
// --- Scénario 5 : Erreur de Type (Lance une exception) ---
|
||||
SECTION("Erreur de Type - Lance une runtime_error") {
|
||||
// Tente de lire une [array] comme un int
|
||||
REQUIRE_THROWS_AS(
|
||||
JsonReader::get<int>(data_err, "liste", 0, false, true),
|
||||
std::runtime_error
|
||||
);
|
||||
// Vérifie le message pour s'assurer qu'il mentionne la clé
|
||||
REQUIRE_THROWS_WITH(
|
||||
JsonReader::get<int>(data_err, "liste", 0, false, true),
|
||||
ContainsSubstring("liste") && ContainsSubstring("Erreur de type")
|
||||
);
|
||||
}
|
||||
}
|
||||
417
core/tests/test_loops.cpp
Normal file
417
core/tests/test_loops.cpp
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
// ===================================================================
|
||||
// core/tests/test_loops.cpp
|
||||
// Tests unitaires pour ForLoop, WhileLoop, Break et Continue
|
||||
// ===================================================================
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "for_loop_node.h"
|
||||
#include "while_loop_node.h"
|
||||
#include "break_node.h"
|
||||
#include "continue_node.h"
|
||||
#include "variable_node.h"
|
||||
#include "function_entry_node.h"
|
||||
#include "operator_node.h"
|
||||
#include "print_node.h"
|
||||
#include "connection.h"
|
||||
#include "ast_builder.h"
|
||||
#include "assembly_generator_chip32_tac.h"
|
||||
#include "chip32_machine.h"
|
||||
#include "variable.h"
|
||||
|
||||
// ===================================================================
|
||||
// TEST 1 : ForLoopNode simple (0 à 5)
|
||||
// ===================================================================
|
||||
TEST_CASE("Simple ForLoop counting 0 to 5", "[loop][for]") {
|
||||
std::cout << "\n=== Test: Simple ForLoop 0 to 5 ===\n";
|
||||
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
// Nodes
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto forLoop = std::make_shared<ForLoopNode>("for-loop");
|
||||
forLoop->SetStartIndex(0);
|
||||
forLoop->SetEndIndex(5);
|
||||
forLoop->SetStep(1);
|
||||
forLoop->Initialize();
|
||||
|
||||
auto printNode = std::make_shared<PrintNode>("print-loop");
|
||||
printNode->SetText("Iteration");
|
||||
printNode->Initialize();
|
||||
|
||||
auto printEnd = std::make_shared<PrintNode>("print-end");
|
||||
printEnd->SetText("Loop completed!");
|
||||
printEnd->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry,
|
||||
forLoop,
|
||||
printNode,
|
||||
printEnd
|
||||
};
|
||||
|
||||
// Connections
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
// Execution: Entry → ForLoop
|
||||
auto execConn1 = std::make_shared<Connection>();
|
||||
execConn1->outNodeId = functionEntry->GetId();
|
||||
execConn1->outPortIndex = 0;
|
||||
execConn1->inNodeId = forLoop->GetId();
|
||||
execConn1->inPortIndex = 0;
|
||||
execConn1->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn1);
|
||||
|
||||
// Execution: ForLoop.body → Print
|
||||
auto execConn2 = std::make_shared<Connection>();
|
||||
execConn2->outNodeId = forLoop->GetId();
|
||||
execConn2->outPortIndex = 0; // Loop Body
|
||||
execConn2->inNodeId = printNode->GetId();
|
||||
execConn2->inPortIndex = 0;
|
||||
execConn2->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn2);
|
||||
|
||||
// Execution: ForLoop.completed → PrintEnd
|
||||
auto execConn3 = std::make_shared<Connection>();
|
||||
execConn3->outNodeId = forLoop->GetId();
|
||||
execConn3->outPortIndex = 1; // Completed
|
||||
execConn3->inNodeId = printEnd->GetId();
|
||||
execConn3->inPortIndex = 0;
|
||||
execConn3->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn3);
|
||||
|
||||
// Build AST
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
// Generate Assembly
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables,
|
||||
"2025-01-20 10:00:00",
|
||||
"test-for-loop",
|
||||
true,
|
||||
true,
|
||||
1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
std::cout << "\n--- Generated Assembly ---\n";
|
||||
std::cout << assembly << std::endl;
|
||||
|
||||
// Verify
|
||||
REQUIRE(assembly.find("for_start") != std::string::npos);
|
||||
REQUIRE(assembly.find("for_end") != std::string::npos);
|
||||
|
||||
// Execute
|
||||
std::cout << "\n--- Execution Output ---\n";
|
||||
std::cout << "Expected: 5 iterations + completion message\n";
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(assembly);
|
||||
|
||||
std::cout << "\n✓ Test passed\n";
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// TEST 2 : WhileLoop avec condition variable
|
||||
// ===================================================================
|
||||
TEST_CASE("WhileLoop with variable condition", "[loop][while]") {
|
||||
std::cout << "\n=== Test: WhileLoop with condition ===\n";
|
||||
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
auto varCounter = std::make_shared<Variable>("counter");
|
||||
varCounter->SetIntegerValue(0);
|
||||
variables.push_back(varCounter);
|
||||
|
||||
auto varLimit = std::make_shared<Variable>("limit");
|
||||
varLimit->SetIntegerValue(3);
|
||||
variables.push_back(varLimit);
|
||||
|
||||
// Nodes
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto varNodeCounter = std::make_shared<VariableNode>("var-counter");
|
||||
varNodeCounter->SetVariable(varCounter);
|
||||
|
||||
auto varNodeLimit = std::make_shared<VariableNode>("var-limit");
|
||||
varNodeLimit->SetVariable(varLimit);
|
||||
|
||||
// Condition: counter < limit
|
||||
auto compareNode = std::make_shared<OperatorNode>("compare-lt");
|
||||
compareNode->SetOperationType(OperatorNode::OperationType::LESS_THAN);
|
||||
compareNode->Initialize();
|
||||
|
||||
auto whileLoop = std::make_shared<WhileLoopNode>("while-loop");
|
||||
whileLoop->Initialize();
|
||||
|
||||
auto printNode = std::make_shared<PrintNode>("print-loop");
|
||||
printNode->SetText("Counter value");
|
||||
printNode->Initialize();
|
||||
|
||||
auto printEnd = std::make_shared<PrintNode>("print-end");
|
||||
printEnd->SetText("While loop completed!");
|
||||
printEnd->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry,
|
||||
varNodeCounter,
|
||||
varNodeLimit,
|
||||
compareNode,
|
||||
whileLoop,
|
||||
printNode,
|
||||
printEnd
|
||||
};
|
||||
|
||||
// Connections
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
// Execution: Entry → While
|
||||
auto execConn1 = std::make_shared<Connection>();
|
||||
execConn1->outNodeId = functionEntry->GetId();
|
||||
execConn1->outPortIndex = 0;
|
||||
execConn1->inNodeId = whileLoop->GetId();
|
||||
execConn1->inPortIndex = 0;
|
||||
execConn1->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn1);
|
||||
|
||||
// Data: counter → compare.in1
|
||||
auto dataConn1 = std::make_shared<Connection>();
|
||||
dataConn1->outNodeId = varNodeCounter->GetId();
|
||||
dataConn1->outPortIndex = 0;
|
||||
dataConn1->inNodeId = compareNode->GetId();
|
||||
dataConn1->inPortIndex = 0;
|
||||
dataConn1->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn1);
|
||||
|
||||
// Data: limit → compare.in2
|
||||
auto dataConn2 = std::make_shared<Connection>();
|
||||
dataConn2->outNodeId = varNodeLimit->GetId();
|
||||
dataConn2->outPortIndex = 0;
|
||||
dataConn2->inNodeId = compareNode->GetId();
|
||||
dataConn2->inPortIndex = 1;
|
||||
dataConn2->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn2);
|
||||
|
||||
// Data: compare.out → while.condition
|
||||
auto dataConn3 = std::make_shared<Connection>();
|
||||
dataConn3->outNodeId = compareNode->GetId();
|
||||
dataConn3->outPortIndex = 0;
|
||||
dataConn3->inNodeId = whileLoop->GetId();
|
||||
dataConn3->inPortIndex = 1;
|
||||
dataConn3->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn3);
|
||||
|
||||
// Execution: While.body → Print
|
||||
auto execConn2 = std::make_shared<Connection>();
|
||||
execConn2->outNodeId = whileLoop->GetId();
|
||||
execConn2->outPortIndex = 0;
|
||||
execConn2->inNodeId = printNode->GetId();
|
||||
execConn2->inPortIndex = 0;
|
||||
execConn2->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn2);
|
||||
|
||||
// Execution: While.completed → PrintEnd
|
||||
auto execConn3 = std::make_shared<Connection>();
|
||||
execConn3->outNodeId = whileLoop->GetId();
|
||||
execConn3->outPortIndex = 1;
|
||||
execConn3->inNodeId = printEnd->GetId();
|
||||
execConn3->inPortIndex = 0;
|
||||
execConn3->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn3);
|
||||
|
||||
// Build and test
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables, "2025-01-20", "test-while-loop", true, true, 1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
std::cout << "\n--- Generated Assembly ---\n";
|
||||
std::cout << assembly << std::endl;
|
||||
|
||||
REQUIRE(assembly.find("while_start") != std::string::npos);
|
||||
REQUIRE(assembly.find("while_end") != std::string::npos);
|
||||
|
||||
std::cout << "\n✓ Test passed\n";
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// TEST 3 : ForLoop avec Break
|
||||
// ===================================================================
|
||||
TEST_CASE("ForLoop with Break at first iteration", "[loop][for][break]") {
|
||||
std::cout << "\n=== Test: ForLoop with Break ===\n";
|
||||
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
// Nodes
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto forLoop = std::make_shared<ForLoopNode>("for-loop");
|
||||
forLoop->SetStartIndex(0);
|
||||
forLoop->SetEndIndex(10);
|
||||
forLoop->SetStep(1);
|
||||
forLoop->Initialize();
|
||||
|
||||
auto printNode = std::make_shared<PrintNode>("print-iter");
|
||||
printNode->SetText("Iteration");
|
||||
printNode->Initialize();
|
||||
|
||||
auto breakNode = std::make_shared<BreakNode>("break-node");
|
||||
breakNode->Initialize();
|
||||
|
||||
auto printEnd = std::make_shared<PrintNode>("print-end");
|
||||
printEnd->SetText("Broke out of loop");
|
||||
printEnd->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry,
|
||||
forLoop,
|
||||
printNode,
|
||||
breakNode,
|
||||
printEnd
|
||||
};
|
||||
|
||||
// Connections
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
// Entry → ForLoop
|
||||
auto execConn1 = std::make_shared<Connection>();
|
||||
execConn1->outNodeId = functionEntry->GetId();
|
||||
execConn1->outPortIndex = 0;
|
||||
execConn1->inNodeId = forLoop->GetId();
|
||||
execConn1->inPortIndex = 0;
|
||||
execConn1->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn1);
|
||||
|
||||
// ForLoop.body → Print
|
||||
auto execConn2 = std::make_shared<Connection>();
|
||||
execConn2->outNodeId = forLoop->GetId();
|
||||
execConn2->outPortIndex = 0;
|
||||
execConn2->inNodeId = printNode->GetId();
|
||||
execConn2->inPortIndex = 0;
|
||||
execConn2->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn2);
|
||||
|
||||
// Print → Break
|
||||
auto execConn3 = std::make_shared<Connection>();
|
||||
execConn3->outNodeId = printNode->GetId();
|
||||
execConn3->outPortIndex = 0;
|
||||
execConn3->inNodeId = breakNode->GetId();
|
||||
execConn3->inPortIndex = 0;
|
||||
execConn3->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn3);
|
||||
|
||||
// ForLoop.completed → PrintEnd
|
||||
auto execConn4 = std::make_shared<Connection>();
|
||||
execConn4->outNodeId = forLoop->GetId();
|
||||
execConn4->outPortIndex = 1;
|
||||
execConn4->inNodeId = printEnd->GetId();
|
||||
execConn4->inPortIndex = 0;
|
||||
execConn4->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn4);
|
||||
|
||||
// Build and generate
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables, "2025-01-20", "test-break", true, true, 1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
std::cout << "\n--- Generated Assembly ---\n";
|
||||
std::cout << assembly << std::endl;
|
||||
|
||||
REQUIRE(assembly.find("for_end") != std::string::npos);
|
||||
REQUIRE(assembly.find("jump") != std::string::npos);
|
||||
|
||||
std::cout << "\n✓ Test passed\n";
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// TEST 4 : ForLoop avec Continue
|
||||
// ===================================================================
|
||||
TEST_CASE("ForLoop with Continue", "[loop][for][continue]") {
|
||||
std::cout << "\n=== Test: ForLoop with Continue ===\n";
|
||||
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto forLoop = std::make_shared<ForLoopNode>("for-loop");
|
||||
forLoop->SetStartIndex(0);
|
||||
forLoop->SetEndIndex(5);
|
||||
forLoop->Initialize();
|
||||
|
||||
auto continueNode = std::make_shared<ContinueNode>("continue-node");
|
||||
continueNode->Initialize();
|
||||
|
||||
auto printNode = std::make_shared<PrintNode>("print-odd");
|
||||
printNode->SetText("Should not print");
|
||||
printNode->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry,
|
||||
forLoop,
|
||||
continueNode,
|
||||
printNode
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
// Entry → ForLoop
|
||||
auto execConn1 = std::make_shared<Connection>();
|
||||
execConn1->outNodeId = functionEntry->GetId();
|
||||
execConn1->outPortIndex = 0;
|
||||
execConn1->inNodeId = forLoop->GetId();
|
||||
execConn1->inPortIndex = 0;
|
||||
execConn1->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn1);
|
||||
|
||||
// ForLoop.body → Continue
|
||||
auto execConn2 = std::make_shared<Connection>();
|
||||
execConn2->outNodeId = forLoop->GetId();
|
||||
execConn2->outPortIndex = 0;
|
||||
execConn2->inNodeId = continueNode->GetId();
|
||||
execConn2->inPortIndex = 0;
|
||||
execConn2->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn2);
|
||||
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables, "2025-01-20", "test-continue", true, true, 1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
std::cout << "\n--- Generated Assembly ---\n";
|
||||
std::cout << assembly << std::endl;
|
||||
|
||||
REQUIRE(assembly.find("for_continue") != std::string::npos);
|
||||
|
||||
std::cout << "\n✓ Test passed\n";
|
||||
}
|
||||
75
core/tests/test_macros.cpp
Normal file
75
core/tests/test_macros.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// ============================================================================
|
||||
// TEST 3: Macro language with DV/DZ
|
||||
// ============================================================================
|
||||
|
||||
static const std::string testMacro1 = R"(
|
||||
%section_macro
|
||||
|
||||
%macro incr 1
|
||||
push t0
|
||||
lcons t0, 1
|
||||
add %1, t0
|
||||
pop t0
|
||||
%endmacro
|
||||
|
||||
%macro print 2
|
||||
lcons r0, %1 ; string text
|
||||
lcons r1, 1 ; number of arguments
|
||||
mov r2, %2
|
||||
syscall 4
|
||||
%endmacro
|
||||
|
||||
%macro LOOP_START 3
|
||||
lcons %2, %3 ; Initialize loop counter
|
||||
%1_loop: ; Loop start label
|
||||
%endmacro
|
||||
|
||||
%macro LOOP_END 2
|
||||
subi %2, 1 ; Decrement counter
|
||||
skipz %2
|
||||
jump %1_loop ; Jump if not zero
|
||||
%endmacro
|
||||
|
||||
%section_text
|
||||
|
||||
lcons R3, 4
|
||||
incr R3
|
||||
|
||||
LOOP_START .myLoop, r6, 5
|
||||
print $printHello, r3
|
||||
LOOP_END .myLoop, r6
|
||||
|
||||
halt
|
||||
|
||||
%section_data
|
||||
|
||||
; ROM constant (DC)
|
||||
$printHello DC8 "Answer is %d"
|
||||
|
||||
; RAM zeroed buffer (DZ)
|
||||
$tempBuffer DZ8 32 ; 32 bytes buffer for temporary data
|
||||
)";
|
||||
|
||||
TEST_CASE("Check assembly macro language with DV/DZ")
|
||||
{
|
||||
std::cout << "\n=== Test 3: Macros with DV/DZ ===" << std::endl;
|
||||
|
||||
Chip32::ScriptProcessor processor;
|
||||
processor.process(testMacro1);
|
||||
processor.generate_assembly();
|
||||
|
||||
std::string resultAsm = processor.GetResult();
|
||||
|
||||
std::cout << "Generated Assembly:" << std::endl;
|
||||
std::cout << resultAsm << std::endl;
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(resultAsm);
|
||||
|
||||
REQUIRE(machine.parseResult == true);
|
||||
REQUIRE(machine.buildResult == true);
|
||||
REQUIRE(machine.runResult == VM_FINISHED);
|
||||
REQUIRE(machine.ctx.registers[R3] == 5);
|
||||
|
||||
std::cout << "✓ Test 3 passed: Macros with DV/DZ" << std::endl;
|
||||
}
|
||||
323
core/tests/test_parser.cpp
Normal file
323
core/tests/test_parser.cpp
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2022 Anthony Rabine
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "chip32_machine.h"
|
||||
#include "chip32_binary_format.h"
|
||||
|
||||
/*
|
||||
Purpose: grammar, ram usage and macros, rom code generation
|
||||
Tests updated with new DV/DZ syntax:
|
||||
- DV8, DV32 : RAM variables WITH initial value
|
||||
- DZ8, DZ32 : RAM zero-initialized areas (buffers)
|
||||
*/
|
||||
|
||||
void hexdump(void *ptr, int buflen);
|
||||
|
||||
// ============================================================================
|
||||
// TEST 1: Basic grammar with DV/DZ
|
||||
// ============================================================================
|
||||
static const std::string test1 = R"(
|
||||
; ============================================================================
|
||||
; Test grammar with new DV/DZ syntax
|
||||
; ============================================================================
|
||||
|
||||
; ROM constants (DC)
|
||||
$imageBird DC8 "example.bmp"
|
||||
$someConstant DC32 12456789
|
||||
|
||||
; RAM initialized variables (DV)
|
||||
$RamData1 DV32 0 ; Integer initialized to 0
|
||||
$counter DV32 10 ; Counter initialized to 10
|
||||
|
||||
; RAM zeroed areas (DZ)
|
||||
$MyArray DZ8 10 ; Array of 10 bytes (zeroed)
|
||||
$WorkBuffer DZ8 64 ; Buffer of 64 bytes (zeroed)
|
||||
|
||||
; ============================================================================
|
||||
; CODE
|
||||
; ============================================================================
|
||||
|
||||
.main:
|
||||
; We create a loop for RAM variable testing
|
||||
lcons r0, 4 ; prepare loop: 4 iterations
|
||||
lcons r2, $RamData1 ; save in R2 a ram address
|
||||
store @r2, r0, 4 ; save R0 in RAM
|
||||
lcons r1, 1
|
||||
|
||||
.loop:
|
||||
load r0, @r2, 4 ; load this variable
|
||||
sub r0, r1
|
||||
store @r2, r0, 4 ; save R0 in RAM
|
||||
skipz r0 ; skip loop if R0 == 0
|
||||
jump .loop
|
||||
|
||||
; Test spacing variations
|
||||
mov r0, r2 ; copy R2 into R0 (blank space)
|
||||
mov R0,R2 ; copy R2 into R0 (NO blank space)
|
||||
|
||||
halt
|
||||
)";
|
||||
|
||||
// ============================================================================
|
||||
// TEST CASE 1: Grammar and indentation with DV/DZ
|
||||
// ============================================================================
|
||||
|
||||
TEST_CASE("Check various indentations and typos with DV/DZ")
|
||||
{
|
||||
std::cout << "\n=== Test 1: Grammar and indentation ===" << std::endl;
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(test1);
|
||||
|
||||
// Hex Dump the memories
|
||||
machine.DumpMemory();
|
||||
|
||||
// Verify results
|
||||
REQUIRE(machine.parseResult == true);
|
||||
REQUIRE(machine.buildResult == true);
|
||||
REQUIRE(machine.runResult == VM_FINISHED);
|
||||
|
||||
std::cout << "✓ Test 1 passed: Grammar and DV/DZ syntax" << std::endl;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TEST 2: Printf with DV variable
|
||||
// ============================================================================
|
||||
|
||||
static const std::string testPrintf = R"(
|
||||
; ============================================================================
|
||||
; Test printf system call with DV variable
|
||||
; ============================================================================
|
||||
|
||||
; ROM constants (DC)
|
||||
$printHello DC8 "La réponse est %d"
|
||||
$answer DC32 42
|
||||
|
||||
; RAM initialized variable (DV)
|
||||
$counter DV32 10 ; Counter initialized to 10
|
||||
|
||||
; ============================================================================
|
||||
; CODE
|
||||
; ============================================================================
|
||||
|
||||
.main:
|
||||
; Simple test - print the counter value
|
||||
lcons r0, $printHello
|
||||
lcons r1, 1 ; 1 argument
|
||||
load r2, $counter, 4 ; Load counter value
|
||||
syscall 4 ; Printf
|
||||
|
||||
halt
|
||||
)";
|
||||
|
||||
TEST_CASE("Test printf with DV variable", "[printf][dv]")
|
||||
{
|
||||
std::cout << "\n=== Test 2: Printf with DV ===" << std::endl;
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(testPrintf);
|
||||
|
||||
REQUIRE(machine.parseResult == true);
|
||||
REQUIRE(machine.buildResult == true);
|
||||
REQUIRE(machine.runResult == VM_FINISHED);
|
||||
|
||||
std::cout << "✓ Test 2 passed: Printf with DV" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// TEST 4: DV vs DZ comprehensive test
|
||||
// ============================================================================
|
||||
|
||||
static const std::string testDvVsDz = R"(
|
||||
; ============================================================================
|
||||
; Comprehensive test: DV (initialized) vs DZ (zeroed)
|
||||
; ============================================================================
|
||||
|
||||
; ROM constants (DC)
|
||||
$appName DC8 "TestApp"
|
||||
$version DC32 100
|
||||
|
||||
; RAM initialized variables (DV) - WITH VALUES
|
||||
$counter DV32 42 ; int counter = 42
|
||||
$temperature DV32 20 ; int temp = 20
|
||||
$userName DV8 "Guest" ; char name[] = "Guest"
|
||||
$flags DV8 1, 0, 1 ; uint8_t flags[] = {1, 0, 1}
|
||||
|
||||
; RAM zeroed areas (DZ) - BUFFERS AND ARRAYS
|
||||
$rxBuffer DZ8 128 ; uint8_t buffer[128] = {0}
|
||||
$dataArray DZ32 50 ; int32_t array[50] = {0}
|
||||
$workArea DZ8 256 ; uint8_t work[256] = {0}
|
||||
|
||||
; ============================================================================
|
||||
; CODE
|
||||
; ============================================================================
|
||||
|
||||
.main:
|
||||
; Test 1: DV counter should be 42
|
||||
load r0, $counter, 4
|
||||
; r0 should be 42
|
||||
|
||||
; Test 2: DV temperature should be 20
|
||||
load r1, $temperature, 4
|
||||
; r1 should be 20
|
||||
|
||||
; Test 3: DZ rxBuffer[0] should be 0
|
||||
lcons r2, $rxBuffer
|
||||
load r3, @r2, 1
|
||||
; r3 should be 0
|
||||
|
||||
; Test 4: Write to DZ buffer
|
||||
lcons r4, 0x42
|
||||
store @r2, r4, 1
|
||||
; rxBuffer[0] = 0x42
|
||||
|
||||
; Test 5: Read back modified value
|
||||
load r5, @r2, 1
|
||||
; r5 should be 0x42
|
||||
|
||||
halt
|
||||
)";
|
||||
|
||||
TEST_CASE("DV vs DZ comprehensive test")
|
||||
{
|
||||
std::cout << "\n=== Test 4: DV vs DZ comprehensive ===" << std::endl;
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(testDvVsDz);
|
||||
|
||||
REQUIRE(machine.parseResult == true);
|
||||
REQUIRE(machine.buildResult == true);
|
||||
REQUIRE(machine.runResult == VM_FINISHED);
|
||||
|
||||
// Verify register values
|
||||
std::cout << "\nRegister values after execution:" << std::endl;
|
||||
std::cout << " R0 (counter) = " << machine.ctx.registers[R0]
|
||||
<< " (expected: 42)" << std::endl;
|
||||
std::cout << " R1 (temperature) = " << machine.ctx.registers[R1]
|
||||
<< " (expected: 20)" << std::endl;
|
||||
std::cout << " R3 (rxBuffer[0] initial) = " << machine.ctx.registers[R3]
|
||||
<< " (expected: 0)" << std::endl;
|
||||
std::cout << " R5 (rxBuffer[0] after write) = " << machine.ctx.registers[R5]
|
||||
<< " (expected: 66)" << std::endl;
|
||||
|
||||
REQUIRE(machine.ctx.registers[R0] == 42); // counter DV value
|
||||
REQUIRE(machine.ctx.registers[R1] == 20); // temperature DV value
|
||||
REQUIRE(machine.ctx.registers[R3] == 0); // rxBuffer DZ initial (zero)
|
||||
REQUIRE(machine.ctx.registers[R5] == 0x42); // rxBuffer after write
|
||||
|
||||
std::cout << "✓ Test 4 passed: DV vs DZ comprehensive" << std::endl;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TEST 5: Binary format validation
|
||||
// ============================================================================
|
||||
|
||||
static const std::string testBinaryFormat = R"(
|
||||
; Test binary format with all section types
|
||||
|
||||
; DC: ROM constants
|
||||
$romString DC8 "Hello"
|
||||
$romValue DC32 123
|
||||
|
||||
; DV: Initialized RAM variables
|
||||
$ramCounter DV32 99
|
||||
$ramMessage DV8 "OK"
|
||||
|
||||
; DZ: Zeroed RAM areas
|
||||
$ramBuffer DZ8 64
|
||||
$ramArray DZ32 20
|
||||
|
||||
.main:
|
||||
load r0, $ramCounter, 4
|
||||
halt
|
||||
)";
|
||||
|
||||
TEST_CASE("Binary format validation")
|
||||
{
|
||||
std::cout << "\n=== Test 5: Binary format validation ===" << std::endl;
|
||||
|
||||
// Parse and build
|
||||
Chip32::Assembler assembler;
|
||||
chip32_binary_stats_t stats;
|
||||
std::vector<uint8_t> program;
|
||||
Chip32::Machine machine;
|
||||
|
||||
REQUIRE(assembler.Parse(testBinaryFormat) == true);
|
||||
REQUIRE(machine.BuildBinary(assembler, program, stats) == true);
|
||||
|
||||
std::cout << "\nBinary statistics:" << std::endl;
|
||||
chip32_binary_print_stats(&stats);
|
||||
|
||||
uint8_t ram[1024];
|
||||
|
||||
// Validate binary format
|
||||
chip32_binary_header_t header;
|
||||
chip32_ctx_t ctx;
|
||||
chip32_binary_error_t error = chip32_binary_load(
|
||||
&ctx,
|
||||
program.data(),
|
||||
static_cast<uint32_t>(program.size()),
|
||||
ram,
|
||||
sizeof(ram),
|
||||
&header
|
||||
);
|
||||
|
||||
REQUIRE(error == CHIP32_BIN_OK);
|
||||
|
||||
std::cout << "\nBinary header:" << std::endl;
|
||||
chip32_binary_print_header(&header);
|
||||
|
||||
// Verify header values
|
||||
REQUIRE(header.magic == CHIP32_MAGIC);
|
||||
REQUIRE(header.version == CHIP32_VERSION);
|
||||
REQUIRE((header.flags & CHIP32_FLAG_HAS_INIT_DATA) != 0);
|
||||
REQUIRE(header.const_size > 0); // Has ROM constants
|
||||
REQUIRE(header.bss_size > 0); // Has RAM
|
||||
REQUIRE(header.code_size > 0); // Has code
|
||||
REQUIRE(header.bss_size == 144);
|
||||
REQUIRE(header.data_size == 7);
|
||||
|
||||
chip32_binary_build_stats(&header, &stats);
|
||||
|
||||
|
||||
std::cout << "\nBinary format validations:" << std::endl;
|
||||
std::cout << " ✓ Magic number correct" << std::endl;
|
||||
std::cout << " ✓ Version correct" << std::endl;
|
||||
std::cout << " ✓ Has init data flag set" << std::endl;
|
||||
std::cout << " ✓ All sections present" << std::endl;
|
||||
std::cout << " ✓ INIT DATA size matches BSS size" << std::endl;
|
||||
|
||||
// Verify DV ramCounter is initialized to 99
|
||||
uint32_t ramCounter = *reinterpret_cast<uint32_t*>(&ram[0]);
|
||||
std::cout << " ✓ DV ramCounter = " << ramCounter << " (expected: 99)" << std::endl;
|
||||
REQUIRE(ramCounter == 99);
|
||||
|
||||
std::cout << "\n✓ Test 5 passed: Binary format validation" << std::endl;
|
||||
}
|
||||
|
||||
228
core/tests/test_print_node.cpp
Normal file
228
core/tests/test_print_node.cpp
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
// test_print_node.cpp
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "print_node.h"
|
||||
#include "variable_node.h"
|
||||
#include "function_entry_node.h"
|
||||
#include "operator_node.h"
|
||||
#include "connection.h"
|
||||
#include "ast_builder.h"
|
||||
#include "assembly_generator_chip32_tac.h"
|
||||
#include "chip32_machine.h"
|
||||
#include "variable.h"
|
||||
|
||||
// ===================================================================
|
||||
// TEST 1 : Print simple sans argument
|
||||
// ===================================================================
|
||||
TEST_CASE("Print without arguments - TAC", "[print][simple][tac]") {
|
||||
std::cout << "\n=== Test: Print without arguments (TAC) ===\n";
|
||||
|
||||
// Variables (vide pour ce test)
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
// Nodes
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto printNode = std::make_shared<PrintNode>("print-node");
|
||||
printNode->SetText("Hello World!");
|
||||
printNode->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry,
|
||||
printNode
|
||||
};
|
||||
|
||||
// Connections
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
// Execution: Entry → Print
|
||||
auto execConn = std::make_shared<Connection>();
|
||||
execConn->outNodeId = functionEntry->GetId();
|
||||
execConn->outPortIndex = 0;
|
||||
execConn->inNodeId = printNode->GetId();
|
||||
execConn->inPortIndex = 0;
|
||||
execConn->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn);
|
||||
|
||||
// Build AST
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
// Generate Assembly using TAC
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables,
|
||||
"2025-01-10 10:00:00",
|
||||
"test-print-no-args",
|
||||
true, // debug (will show TAC)
|
||||
true, // optimize
|
||||
1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
std::cout << "\n--- Generated Assembly ---\n";
|
||||
std::cout << assembly << std::endl;
|
||||
|
||||
// Verify
|
||||
REQUIRE(assembly.find("DATA") != std::string::npos);
|
||||
REQUIRE(assembly.find("CODE") != std::string::npos);
|
||||
REQUIRE(assembly.find("Hello World!") != std::string::npos);
|
||||
REQUIRE(assembly.find("syscall 4") != std::string::npos);
|
||||
REQUIRE(assembly.find("halt") != std::string::npos);
|
||||
|
||||
// Execute
|
||||
std::cout << "\n--- Execution Output ---\n";
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(assembly);
|
||||
|
||||
std::cout << "\n✓ Test passed\n";
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// TEST 2 : Print avec 4 arguments (maximum supporté)
|
||||
// ===================================================================
|
||||
TEST_CASE("Print with 4 arguments - TAC", "[print][args][tac]") {
|
||||
std::cout << "\n=== Test: Print with 4 arguments (TAC) ===\n";
|
||||
|
||||
// Variables: A=10, B=5, C=3, D=2
|
||||
std::vector<std::shared_ptr<Variable>> variables;
|
||||
|
||||
auto var_A = std::make_shared<Variable>("A");
|
||||
var_A->SetIntegerValue(10);
|
||||
variables.push_back(var_A);
|
||||
|
||||
auto var_B = std::make_shared<Variable>("B");
|
||||
var_B->SetIntegerValue(5);
|
||||
variables.push_back(var_B);
|
||||
|
||||
auto var_C = std::make_shared<Variable>("C");
|
||||
var_C->SetIntegerValue(3);
|
||||
variables.push_back(var_C);
|
||||
|
||||
auto var_D = std::make_shared<Variable>("D");
|
||||
var_D->SetIntegerValue(2);
|
||||
variables.push_back(var_D);
|
||||
|
||||
// Nodes
|
||||
auto functionEntry = std::make_shared<FunctionEntryNode>("function-entry");
|
||||
functionEntry->SetWeight(100);
|
||||
|
||||
auto varNodeA = std::make_shared<VariableNode>("var-node-A");
|
||||
varNodeA->SetVariable(var_A);
|
||||
|
||||
auto varNodeB = std::make_shared<VariableNode>("var-node-B");
|
||||
varNodeB->SetVariable(var_B);
|
||||
|
||||
auto varNodeC = std::make_shared<VariableNode>("var-node-C");
|
||||
varNodeC->SetVariable(var_C);
|
||||
|
||||
auto varNodeD = std::make_shared<VariableNode>("var-node-D");
|
||||
varNodeD->SetVariable(var_D);
|
||||
|
||||
auto printNode = std::make_shared<PrintNode>("print-node");
|
||||
printNode->SetText("Values: A={0}, B={1}, C={2}, D={3}");
|
||||
printNode->Initialize();
|
||||
|
||||
std::vector<std::shared_ptr<BaseNode>> nodes = {
|
||||
functionEntry,
|
||||
varNodeA, varNodeB, varNodeC, varNodeD,
|
||||
printNode
|
||||
};
|
||||
|
||||
// Connections
|
||||
std::vector<std::shared_ptr<Connection>> connections;
|
||||
|
||||
// Execution: Entry → Print
|
||||
auto execConn = std::make_shared<Connection>();
|
||||
execConn->outNodeId = functionEntry->GetId();
|
||||
execConn->outPortIndex = 0;
|
||||
execConn->inNodeId = printNode->GetId();
|
||||
execConn->inPortIndex = 0;
|
||||
execConn->type = Connection::EXECUTION_LINK;
|
||||
connections.push_back(execConn);
|
||||
|
||||
// Data: A → Print.arg0 (port 1)
|
||||
auto dataConn1 = std::make_shared<Connection>();
|
||||
dataConn1->outNodeId = varNodeA->GetId();
|
||||
dataConn1->outPortIndex = 0;
|
||||
dataConn1->inNodeId = printNode->GetId();
|
||||
dataConn1->inPortIndex = 1;
|
||||
dataConn1->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn1);
|
||||
|
||||
// Data: B → Print.arg1 (port 2)
|
||||
auto dataConn2 = std::make_shared<Connection>();
|
||||
dataConn2->outNodeId = varNodeB->GetId();
|
||||
dataConn2->outPortIndex = 0;
|
||||
dataConn2->inNodeId = printNode->GetId();
|
||||
dataConn2->inPortIndex = 2;
|
||||
dataConn2->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn2);
|
||||
|
||||
// Data: C → Print.arg2 (port 3)
|
||||
auto dataConn3 = std::make_shared<Connection>();
|
||||
dataConn3->outNodeId = varNodeC->GetId();
|
||||
dataConn3->outPortIndex = 0;
|
||||
dataConn3->inNodeId = printNode->GetId();
|
||||
dataConn3->inPortIndex = 3;
|
||||
dataConn3->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn3);
|
||||
|
||||
// Data: D → Print.arg3 (port 4)
|
||||
auto dataConn4 = std::make_shared<Connection>();
|
||||
dataConn4->outNodeId = varNodeD->GetId();
|
||||
dataConn4->outPortIndex = 0;
|
||||
dataConn4->inNodeId = printNode->GetId();
|
||||
dataConn4->inPortIndex = 4;
|
||||
dataConn4->type = Connection::DATA_LINK;
|
||||
connections.push_back(dataConn4);
|
||||
|
||||
// Build AST
|
||||
ASTBuilder builder(nodes, connections);
|
||||
auto pathTree = builder.BuildAST();
|
||||
|
||||
// Generate Assembly using TAC
|
||||
AssemblyGenerator::GeneratorContext context(
|
||||
variables,
|
||||
"2025-01-10 10:00:00",
|
||||
"test-print-4-args",
|
||||
true, // debug (will show TAC)
|
||||
true, // optimize
|
||||
1024
|
||||
);
|
||||
|
||||
AssemblyGeneratorChip32TAC tacGen(context);
|
||||
tacGen.GenerateCompleteProgram(nodes, pathTree);
|
||||
|
||||
std::string assembly = tacGen.GetAssembly().str();
|
||||
|
||||
std::cout << "\n--- Generated Assembly ---\n";
|
||||
std::cout << assembly << std::endl;
|
||||
|
||||
// Verify
|
||||
REQUIRE(assembly.find("DATA") != std::string::npos);
|
||||
REQUIRE(assembly.find("CODE") != std::string::npos);
|
||||
|
||||
// Verify variable declarations
|
||||
REQUIRE(assembly.find("; A") != std::string::npos);
|
||||
REQUIRE(assembly.find("; B") != std::string::npos);
|
||||
REQUIRE(assembly.find("; C") != std::string::npos);
|
||||
REQUIRE(assembly.find("; D") != std::string::npos);
|
||||
|
||||
// Verify syscall with 4 arguments
|
||||
REQUIRE(assembly.find("lcons r1, 4") != std::string::npos);
|
||||
REQUIRE(assembly.find("syscall 4") != std::string::npos);
|
||||
|
||||
// Execute
|
||||
std::cout << "\n--- Execution Output ---\n";
|
||||
std::cout << "Expected: Values: A=10, B=5, C=3, D=2\n";
|
||||
std::cout << "Actual: ";
|
||||
|
||||
Chip32::Machine machine;
|
||||
machine.QuickExecute(assembly);
|
||||
|
||||
std::cout << "\n✓ Test passed\n";
|
||||
}
|
||||
703
core/tests/test_vm.cpp
Normal file
703
core/tests/test_vm.cpp
Normal file
|
|
@ -0,0 +1,703 @@
|
|||
// ===================================================================
|
||||
// test_vm.cpp - Tests exhaustifs de toutes les instructions Chip32
|
||||
// ===================================================================
|
||||
|
||||
#include <iostream>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "chip32_machine.h"
|
||||
|
||||
class VmTestContext
|
||||
{
|
||||
public:
|
||||
VmTestContext() {}
|
||||
|
||||
void Execute(const std::string &assemblyCode)
|
||||
{
|
||||
machine.QuickExecute(assemblyCode);
|
||||
REQUIRE(machine.parseResult == true);
|
||||
REQUIRE(machine.buildResult == true);
|
||||
REQUIRE(machine.runResult == VM_FINISHED);
|
||||
}
|
||||
|
||||
Chip32::Machine machine;
|
||||
};
|
||||
|
||||
// ===================================================================
|
||||
// ARITHMETIC OPERATIONS
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "ADD - Addition", "[vm][arithmetic][add]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 10
|
||||
lcons r1, 32
|
||||
add r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "ADDI - Addition immediate (if supported)", "[vm][arithmetic][addi]") {
|
||||
// Note: Vérifier si ADDI est implémenté dans votre VM
|
||||
static const std::string test = R"(
|
||||
lcons r0, 10
|
||||
addi r0, 32
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "SUB - Subtraction", "[vm][arithmetic][sub]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 50
|
||||
lcons r1, 8
|
||||
sub r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "SUBI - Subtraction immediate (if supported)", "[vm][arithmetic][subi]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 50
|
||||
subi r0, 8
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "MUL - Multiplication", "[vm][arithmetic][mul]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 37
|
||||
lcons r1, 0x695
|
||||
mul r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 37 * 0x695);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "DIV - Division", "[vm][arithmetic][div]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 84
|
||||
lcons r1, 2
|
||||
div r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "DIV - Division by zero protection", "[vm][arithmetic][div]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 42
|
||||
lcons r1, 0
|
||||
div r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
// Vérifier que la VM ne crash pas (comportement peut varier)
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// BITWISE OPERATIONS
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "AND - Bitwise AND", "[vm][bitwise][and]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0xFF
|
||||
lcons r1, 0x0F
|
||||
and r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0x0F);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "OR - Bitwise OR", "[vm][bitwise][or]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0xF0
|
||||
lcons r1, 0x0F
|
||||
or r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0xFF);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "XOR - Bitwise XOR", "[vm][bitwise][xor]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0xFF
|
||||
lcons r1, 0xAA
|
||||
xor r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0x55);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "NOT - Bitwise NOT", "[vm][bitwise][not]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0x00000000
|
||||
not r0
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "SHIFTL - Shift left", "[vm][bitwise][shiftl]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0x01
|
||||
lcons r1, 4
|
||||
shiftl r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0x10);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "SHIFTR - Shift right (logical)", "[vm][bitwise][shiftr]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0x80
|
||||
lcons r1, 4
|
||||
shiftr r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0x08);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "ISHIFTR - Shift right (arithmetic)", "[vm][bitwise][ishiftr]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0xFFFFFF80
|
||||
lcons r1, 2
|
||||
ishiftr r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
// Shift arithmétique conserve le signe
|
||||
REQUIRE((int32_t)machine.ctx.registers[R0] == -32);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// DATA MOVEMENT
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "MOV - Move register", "[vm][data][mov]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 42
|
||||
mov r1, r0
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R1] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "LCONS - Load constant", "[vm][data][lcons]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0x12345678
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0x12345678);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// MEMORY OPERATIONS
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "STORE and LOAD - Memory operations", "[vm][memory]") {
|
||||
static const std::string test = R"(
|
||||
$myVar DV32, 0
|
||||
|
||||
.main:
|
||||
lcons r0, 42
|
||||
lcons r1, $myVar
|
||||
store @r1, r0, 4
|
||||
|
||||
lcons r2, 0
|
||||
load r2, @r1, 4
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R2] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "LOAD - Direct address", "[vm][memory][load]") {
|
||||
static const std::string test = R"(
|
||||
$value DV32, 123
|
||||
|
||||
.main:
|
||||
load r0, $value, 4
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 123);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// STACK OPERATIONS
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "PUSH and POP", "[vm][stack]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 42
|
||||
lcons r1, 100
|
||||
|
||||
push r0
|
||||
push r1
|
||||
|
||||
lcons r0, 0
|
||||
lcons r1, 0
|
||||
|
||||
pop r1
|
||||
pop r0
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
REQUIRE(machine.ctx.registers[R1] == 100);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "PUSH/POP - Multiple values", "[vm][stack]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 1
|
||||
lcons r1, 2
|
||||
lcons r2, 3
|
||||
lcons r3, 4
|
||||
|
||||
push r0
|
||||
push r1
|
||||
push r2
|
||||
push r3
|
||||
|
||||
lcons r0, 0
|
||||
lcons r1, 0
|
||||
lcons r2, 0
|
||||
lcons r3, 0
|
||||
|
||||
pop r3
|
||||
pop r2
|
||||
pop r1
|
||||
pop r0
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 1);
|
||||
REQUIRE(machine.ctx.registers[R1] == 2);
|
||||
REQUIRE(machine.ctx.registers[R2] == 3);
|
||||
REQUIRE(machine.ctx.registers[R3] == 4);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// CONTROL FLOW - JUMP
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "JUMP - Unconditional jump", "[vm][control][jump]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0
|
||||
jump .target
|
||||
lcons r0, 99
|
||||
.target:
|
||||
lcons r0, 42
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// CONTROL FLOW - SKIP
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "SKIPZ - Skip if zero (condition true)", "[vm][control][skipz]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0
|
||||
skipz r0
|
||||
jump .non_zero
|
||||
lcons r1, 42
|
||||
halt
|
||||
.non_zero:
|
||||
lcons r1, 99
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R1] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "SKIPZ - Skip if zero (condition false)", "[vm][control][skipz]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 1
|
||||
skipz r0
|
||||
jump .non_zero
|
||||
lcons r1, 42
|
||||
halt
|
||||
.non_zero:
|
||||
lcons r1, 99
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R1] == 99);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "SKIPNZ - Skip if not zero (condition true)", "[vm][control][skipnz]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 1
|
||||
skipnz r0
|
||||
jump .it_is_zero
|
||||
lcons r1, 42
|
||||
halt
|
||||
.it_is_zero:
|
||||
lcons r1, 99
|
||||
halt
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R1] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "SKIPNZ - Skip if not zero (condition false)", "[vm][control][skipnz]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0
|
||||
skipnz r0
|
||||
jump .it_is_zero
|
||||
lcons r1, 42
|
||||
halt
|
||||
.it_is_zero:
|
||||
lcons r1, 99
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R1] == 99);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "SKIPZ - Multiple instructions", "[vm][control][skipz]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0
|
||||
lcons r1, 0
|
||||
skipz r0
|
||||
lcons r1, 10
|
||||
add r1, r0
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
// Skip lcons, donc r1 reste 0, puis add 0 + 0 = 0
|
||||
REQUIRE(machine.ctx.registers[R1] == 0);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// COMPARISONS
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "EQ - Equal (true)", "[vm][comparison][eq]") {
|
||||
static const std::string test = R"(
|
||||
lcons r1, 42
|
||||
lcons r2, 42
|
||||
eq r0, r1, r2
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 1);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "EQ - Equal (false)", "[vm][comparison][eq]") {
|
||||
static const std::string test = R"(
|
||||
lcons r1, 42
|
||||
lcons r2, 10
|
||||
eq r0, r1, r2
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "GT - Greater than (true)", "[vm][comparison][gt]") {
|
||||
static const std::string test = R"(
|
||||
lcons r1, 50
|
||||
lcons r2, 10
|
||||
gt r0, r1, r2
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 1);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "GT - Greater than (false)", "[vm][comparison][gt]") {
|
||||
static const std::string test = R"(
|
||||
lcons r1, 10
|
||||
lcons r2, 50
|
||||
gt r0, r1, r2
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "GT - Greater than (equal)", "[vm][comparison][gt]") {
|
||||
static const std::string test = R"(
|
||||
lcons r1, 42
|
||||
lcons r2, 42
|
||||
gt r0, r1, r2
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "LT - Less than (true)", "[vm][comparison][lt]") {
|
||||
static const std::string test = R"(
|
||||
lcons r1, 10
|
||||
lcons r2, 50
|
||||
lt r0, r1, r2
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 1);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "LT - Less than (false)", "[vm][comparison][lt]") {
|
||||
static const std::string test = R"(
|
||||
lcons r1, 50
|
||||
lcons r2, 10
|
||||
lt r0, r1, r2
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "LT - Less than (equal)", "[vm][comparison][lt]") {
|
||||
static const std::string test = R"(
|
||||
lcons r1, 42
|
||||
lcons r2, 42
|
||||
lt r0, r1, r2
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 0);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// FUNCTION CALLS
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "CALL and RET - Function call", "[vm][function]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 10
|
||||
call .myFunction
|
||||
lcons r1, 100
|
||||
halt
|
||||
|
||||
.myFunction:
|
||||
lcons r0, 42
|
||||
ret
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
REQUIRE(machine.ctx.registers[R1] == 100);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "CALL - Nested function calls", "[vm][function]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0
|
||||
call .func1
|
||||
halt
|
||||
|
||||
.func1:
|
||||
lcons r0, 1
|
||||
call .func2
|
||||
ret
|
||||
|
||||
.func2:
|
||||
lcons r0, 42
|
||||
ret
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// SPECIAL INSTRUCTIONS
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "NOP - No operation", "[vm][special][nop]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 42
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "HALT - Program termination", "[vm][special][halt]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 42
|
||||
halt
|
||||
lcons r0, 99
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// COMPLEX SCENARIOS
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "Complex - Factorial calculation", "[vm][complex]") {
|
||||
static const std::string test = R"(
|
||||
; Calculate 5! = 120
|
||||
lcons r0, 5 ; n
|
||||
lcons r1, 1 ; result
|
||||
|
||||
.loop:
|
||||
mul r1, r0
|
||||
lcons r2, 1
|
||||
sub r0, r2
|
||||
skipz r0
|
||||
jump .loop
|
||||
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R1] == 120);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "Complex - Fibonacci calculation", "[vm][complex]") {
|
||||
static const std::string test = R"(
|
||||
; Calculate 10th Fibonacci number = 55
|
||||
lcons r0, 0 ; fib(n-2)
|
||||
lcons r1, 1 ; fib(n-1)
|
||||
lcons r2, 10 ; counter
|
||||
|
||||
.loop:
|
||||
lcons r3, 1
|
||||
sub r2, r3
|
||||
skipz r2
|
||||
jump .continue
|
||||
jump .done
|
||||
|
||||
.continue:
|
||||
mov r3, r1
|
||||
add r1, r0
|
||||
mov r0, r3
|
||||
jump .loop
|
||||
|
||||
.done:
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R1] == 55);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "Complex - Array sum", "[vm][complex][array]") {
|
||||
static const std::string test = R"(
|
||||
$array DV32, 10, 20, 30, 40, 50
|
||||
|
||||
.main:
|
||||
lcons r0, 0 ; sum
|
||||
lcons r1, $array ; pointer
|
||||
lcons r2, 5 ; count
|
||||
|
||||
.loop:
|
||||
load r3, @r1, 4
|
||||
add r0, r3
|
||||
|
||||
lcons r4, 4
|
||||
add r1, r4
|
||||
|
||||
lcons r4, 1
|
||||
sub r2, r4
|
||||
skipz r2
|
||||
jump .loop
|
||||
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 150);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "Complex - Conditional branches", "[vm][complex][branch]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 10
|
||||
lcons r1, 5
|
||||
|
||||
gt r2, r0, r1
|
||||
skipz r2
|
||||
jump .greater
|
||||
|
||||
lcons r3, 1
|
||||
jump .end
|
||||
|
||||
.greater:
|
||||
lcons r3, 2
|
||||
|
||||
.end:
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R3] == 2);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// EDGE CASES
|
||||
// ===================================================================
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "Edge - Maximum positive integer", "[vm][edge]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0x7FFFFFFF
|
||||
lcons r1, 1
|
||||
add r0, r1
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
// Overflow vers valeur négative
|
||||
REQUIRE((int32_t)machine.ctx.registers[R0] < 0);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "Edge - All registers", "[vm][edge][registers]") {
|
||||
static const std::string test = R"(
|
||||
lcons r0, 0
|
||||
lcons r1, 1
|
||||
lcons r2, 2
|
||||
lcons r3, 3
|
||||
lcons r4, 4
|
||||
lcons r5, 5
|
||||
lcons r6, 6
|
||||
lcons r7, 7
|
||||
lcons r8, 8
|
||||
lcons r9, 9
|
||||
halt
|
||||
)";
|
||||
Execute(test);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
REQUIRE(machine.ctx.registers[i] == i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(VmTestContext, "Edge - Deep nested calls", "[vm][edge][stack]") {
|
||||
static const std::string test = R"(
|
||||
call .level1
|
||||
halt
|
||||
|
||||
.level1:
|
||||
call .level2
|
||||
ret
|
||||
|
||||
.level2:
|
||||
call .level3
|
||||
ret
|
||||
|
||||
.level3:
|
||||
lcons r0, 42
|
||||
ret
|
||||
)";
|
||||
Execute(test);
|
||||
REQUIRE(machine.ctx.registers[R0] == 42);
|
||||
}
|
||||
|
|
@ -14,6 +14,9 @@ Here is a description of that VM.
|
|||
| sp | 21 | stack pointer | Y |
|
||||
| ra | 22 | return address | N |
|
||||
|
||||
Registers that are preserved means that the VM will store them on the stack before the `call` operation. Then, after the return (`ret`), theses registers are restored.
|
||||
|
||||
Registers that are not preserved must be saved manually (by writing code) by the user before using them.
|
||||
|
||||
# Instructions
|
||||
|
||||
|
|
@ -45,6 +48,74 @@ Here is a description of that VM.
|
|||
| jumpr | 23 | 1 | jump to address contained in a register. | `jumpr t9` |
|
||||
| skipz | 24 | 1 | skip next instruction if zero. | `skipz r0` |
|
||||
| skipnz | 25 | 1 | skip next instruction if not zero. | `skipnz r2` |
|
||||
| eq | 26 | 2 | compare two registers for equality, result in first | `eq r4, r0, r1 ; equivalent to C code: r4 = (r0 == r1) ? 1 : 0` |
|
||||
| gt | 27 | 2 | compare if first register is greater than the second, result in first | `gt r4, r0, r1` |
|
||||
| lt | 28 | 2 | compare if first register is less than the second, result in first | `lt r4, r0, r1` |
|
||||
|
||||
|
||||
# System calls
|
||||
|
||||
The story player machine supports the following system calls:
|
||||
|
||||
## SYSCALL 1 (Play media)
|
||||
|
||||
This system call is used to play media. Use R0 and R1 to pass arguments:
|
||||
|
||||
- R0 is for the image:
|
||||
- if zero, clear the screen
|
||||
- Otherwise pass the address in ROM or RAM where is stored the image filename
|
||||
|
||||
- R1 is for the sound:
|
||||
- if zero, do not play anything
|
||||
- Otherwise pass the address in ROM or RAM where is stored the sound filename
|
||||
|
||||
The file must be stored in the `/assets` subdirectory of the current story.
|
||||
|
||||
The syscall do not blocks. User must wait for `end of sound` event if necessary.
|
||||
|
||||
## SYSCALL 2 (wait event)
|
||||
|
||||
This system call is used to wait for machine events. Use R0 to mask events to wait for (events not selected are ignored).
|
||||
|
||||
| Event | Bit |
|
||||
|------- |-------- |
|
||||
| OK button | 0 |
|
||||
| Previous button | 1 |
|
||||
| Next button | 2 |
|
||||
| Up button | 3 |
|
||||
| Down button | 4 |
|
||||
| Home button | 5 |
|
||||
| Select button | 6 |
|
||||
| Start button | 7 |
|
||||
| Stop button | 8 |
|
||||
| Pause button | 9 |
|
||||
| End of audio | 10 |
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
mov r0, 0b1000
|
||||
syscall 2
|
||||
```
|
||||
|
||||
## SYSCALL 3 (Send signal)
|
||||
|
||||
Send a specific signal event to the machine.
|
||||
Use R0 register to pass the signal argument.
|
||||
|
||||
```
|
||||
mov r0, 42
|
||||
syscall 3
|
||||
```
|
||||
|
||||
Supported signals:
|
||||
|
||||
| Signal | Number |
|
||||
|------- |-------- |
|
||||
| reserved | 0 |
|
||||
| Quit story | 1 |
|
||||
|
||||
|
||||
# Assembler
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# Stories architecture
|
||||
|
||||
## Story location
|
||||
|
||||
The stories are located on a SD-Card flash memory. The following file system formats are supported by openStoryTeller:
|
||||
|
|
@ -9,7 +8,7 @@ The stories are located on a SD-Card flash memory. The following file system for
|
|||
|
||||
Connect OpenStoryTeller to your computer using a USB cable, a USB-disk will show up on your file explorer.
|
||||
|
||||
## Story tree
|
||||
## Story root tree
|
||||
|
||||
A typical folder organisation is showed here:
|
||||
|
||||
|
|
@ -17,8 +16,53 @@ A typical folder organisation is showed here:
|
|||
|
||||
At the root of the SD-Card, a special index file named `index.ost` must be present. It contains the list of installed stories as well as other informations about each story.
|
||||
|
||||
## Index file format
|
||||
## Story folder organisation
|
||||
|
||||
TLV format.
|
||||
A story folder name must be a UUID v4 string.
|
||||
|
||||
It must contains:
|
||||
- A project file in JSON named `project.json`
|
||||
- A pre-compiled story binary name `story.c32`
|
||||
- A directory named `assets`
|
||||
|
||||
The assets directory must contains all the resource files for the story (sounds, images...).
|
||||
|
||||
# Index file format
|
||||
|
||||
## General principle
|
||||
|
||||
This binary file is encoded using a simple TLV format (Type Lenght Value) supporting the following types:
|
||||
|
||||
|
||||
| Type | encoding | Value size |
|
||||
| ----- | ----- | ------- |
|
||||
| Object | 0xE7 | Variable |
|
||||
| Array | 0xAB | Variable |
|
||||
| String | 0x3D | Variable |
|
||||
| Integer | 0x77 | 4 bytes |
|
||||
| Real | 0xB8 | 4 bytes |
|
||||
|
||||
Each Type is encoded on a byte. Serialization uses little endian fir bytes ordering.
|
||||
|
||||
The Length is encoded on two bytes. The length indicates the size of the following value.
|
||||
|
||||
## Stories index format V1
|
||||
|
||||
The Index file root type is an object containing the file format version and the list of stories.
|
||||
|
||||
Stories are stored in an array, each entry is the description of one story. A story is located in a directory wich name is the uuid (lower case).
|
||||
|
||||
|
||||
|
||||
- Object (2 elements)
|
||||
- Integer (format version)
|
||||
- Array (n elements)
|
||||
- Object (6 elements)
|
||||
- String (UUID, folder name of the story)
|
||||
- String (title image file name)
|
||||
- String (sound title file name)
|
||||
- String (Story name or title)
|
||||
- String (Story description)
|
||||
- Integer (Story version)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,36 @@ You'll need:
|
|||
- A C++ compiler
|
||||
- CMake build utility
|
||||
|
||||
Here is a list of packages for Ubuntu-like systems:
|
||||
|
||||
```
|
||||
sudo apt install cmake mesa-utils mesa-common-dev ninja-build libxext-dev libpipewire-0.3-dev libasound2-dev libpulse-dev
|
||||
```
|
||||
|
||||
## How to build
|
||||
|
||||
Open the CMakeLists.txt with your favorite IDE (ie, QtCreator, Visual Studio Code) or build from the command line.
|
||||
|
||||
## How to generate a Windows executable and setup executable on Ubuntu
|
||||
|
||||
The build system uses a Docker environment image for reproductible builds.
|
||||
|
||||
Run `build_win32.sh` script.
|
||||
|
||||
Output file is located here: `story-editor/build-win32/Open-Story-Editor-1.0.0-win64.exe`
|
||||
|
||||
## Linux build
|
||||
|
||||
```
|
||||
cd story-editor
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make -j4
|
||||
make package
|
||||
```
|
||||
|
||||
|
||||
# Architecture
|
||||
|
||||

|
||||
|
|
@ -28,3 +54,5 @@ Open the CMakeLists.txt with your favorite IDE (ie, QtCreator, Visual Studio Cod
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ This is the official development kit. It offers the following advantages:
|
|||
3. Low wiring/soldering: usage of a motherboard
|
||||
4. SWD debug port
|
||||
|
||||
Current status: ON DEVELOPMENT
|
||||
Current status: Firmware and software are completed. Build instructions under writing.
|
||||
|
||||

|
||||
|
||||
|
|
@ -57,17 +57,41 @@ The pins usage for this bundle is indicated by the folling wirering. OST pins ar
|
|||
|
||||

|
||||
|
||||
# Bundle build guide
|
||||
|
||||
|
||||
# Firmware flashing
|
||||
|
||||
## Get the firmware binary
|
||||
|
||||
Download the right file for your bundle on the Github project page : [Github project page](https://github.com/arabine/open-story-teller)
|
||||
|
||||
For the Raspberry Pi Pico bundle, the executable is a ELF file extension.
|
||||
|
||||
## Procedure
|
||||
|
||||
You can program the Pico without any software thanks to the UF2 file system driver embedded in the Pico bootloader.
|
||||
|
||||
- Press the Boot button located on the Marbe Pico
|
||||
- Without releasing the button, connect the Marbe Pico to your computer using a USB-C cable
|
||||
- Release the button
|
||||
- A new disk drive should popup on your file system
|
||||
|
||||
Button position:
|
||||
|
||||

|
||||
|
||||
|
||||
UF2 disk on your file explorer:
|
||||
|
||||

|
||||
|
||||
|
||||
Just drag and drop the firmware image in this directory, wait for the programming and firmware reboot.
|
||||
|
||||
# How to build from the source code
|
||||
# Bundle build guide
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
# How to build the firmare image from the source code
|
||||
|
||||
## Clone the repository
|
||||
|
||||
|
|
|
|||
BIN
docs/images/marble-pico.png
Normal file
BIN
docs/images/marble-pico.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 469 KiB |
|
|
@ -7,18 +7,17 @@ This documentation will guide you to make your own story teller. You can choose
|
|||
Before starting, here is a complete list of links where you'll find project information:
|
||||
|
||||
- :octopus: [Github](https://github.com/arabine/open-story-teller): source code for the firmware, the player and the story editor, issue tracking
|
||||
- [Itch.io](https://d8s-apps.itch.io/open-story-teller): Download page for the Story Editor and various player versions.
|
||||
- Free stories community (in French) [Discord](https://monurl.ca/lunii.creations)
|
||||
- My Discord channel for this project: https://discord.gg/4TW4B3R4Ye
|
||||
|
||||
## Helping
|
||||
|
||||
You can help in various ways:
|
||||
|
||||
- Report bugs on Github (open an new issue)
|
||||
- Propose patches, ideas, mechanical drawings, ports (open an issue)
|
||||
- Propose patches, ideas, mechanical drawings for enclosures, ports (open an issue)
|
||||
- Translate stuff
|
||||
|
||||
Reach me on Discord: [Discord](https://monurl.ca/DiscordLuniiYT)
|
||||
|
||||
## Where to start
|
||||
|
||||
Before building your own hardware, make sure you have a minimal knowledge of basic electronics. You may have to solder some wires, but nothing's complicated.
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue