Compare commits

...

138 commits

Author SHA1 Message Date
Anthony Rabine
d14935dd53 Multiple compiler and node fixes
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
2025-10-28 00:57:30 +01:00
Anthony Rabine
07849e3e69 tac implementation f new nodes function entry and exit with parameters
Some checks are pending
Build-StoryEditor / build_win32 (push) Waiting to run
Build-StoryEditor / build_linux (push) Waiting to run
2025-10-27 14:06:55 +01:00
anthony@rabine.fr
397da70d83 Tiny robust json wrapper to get values
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
2025-10-26 15:41:23 +01:00
Anthony Rabine
c594e01912 new function entry/exit nodes with parameters
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
2025-10-24 21:00:15 +02:00
Anthony Rabine
8aa18fa5af Add module properties popup.
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
2025-10-21 15:55:40 +02:00
Anthony Rabine
dc11cb33dd Add loops nodes !
Some checks are pending
Build-StoryEditor / build_win32 (push) Waiting to run
Build-StoryEditor / build_linux (push) Waiting to run
2025-10-20 23:34:17 +02:00
Anthony Rabine
c29d099ec9 Modified TAC for all new syscall widgets
Some checks are pending
Build-StoryEditor / build_win32 (push) Waiting to run
Build-StoryEditor / build_linux (push) Waiting to run
2025-10-20 19:44:53 +02:00
anthony@rabine.fr
765f78fbc6 Add all system calls as dedicated nodes
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
2025-10-19 22:00:39 +02:00
anthony@rabine.fr
149a8b6276 merged jump/call implementation, removed jumpr
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
2025-10-18 23:14:30 +02:00
Anthony Rabine
ee6958a729 (wip) Instructions unit test (complete)
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
2025-10-18 14:23:58 +02:00
Anthony Rabine
9db4bae9fd Add branch node, unit test and fixes
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
2025-10-17 15:24:47 +02:00
Anthony Rabine
41914362d5 viariables not clear before project load 2025-10-17 10:17:11 +02:00
Anthony Rabine
a308714302 fix ram view
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
2025-10-17 00:59:29 +02:00
Anthony Rabine
2b6d9946df Make editor full use of Machine class 2025-10-17 00:45:12 +02:00
Anthony Rabine
da1cb31ac2 Using same objets between labels and instructions + fix load and store
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
2025-10-16 14:58:01 +02:00
Anthony Rabine
0c3809657e Big update on machine class, fix addressing lcons, load etc.
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
2025-10-16 00:11:01 +02:00
anthony@rabine.fr
c6da4b891a (WIP) new binary format
Some checks failed
Build-StoryEditor / build_win32 (push) Has been cancelled
Build-StoryEditor / build_linux (push) Has been cancelled
2025-10-12 20:39:32 +02:00
anthony@rabine.fr
9ab7b9bb14 Add DATA and BSS RAM sections, crate binary format with header
Some checks failed
Build-StoryEditor / build_win32 (push) Has been cancelled
Build-StoryEditor / build_linux (push) Has been cancelled
2025-10-07 08:50:31 +02:00
anthony@rabine.fr
741a3c633e Fixed data connection, compilation order and print format
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
2025-10-06 15:00:37 +02:00
anthony@rabine.fr
eb08627029 Add TAC in compile pipeline
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
2025-10-06 10:24:25 +02:00
anthony@rabine.fr
b76add6793 fix win32 build (tentative) 2025-10-05 10:59:59 +02:00
anthony@rabine.fr
9c6df3c9b6 merged tests all in core now, added print test
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-10-04 10:51:14 +02:00
anthony@rabine.fr
88b9bc6b3e Add VM console output !
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-10-03 18:12:29 +02:00
anthony@rabine.fr
07f4288748 Compilation and load of module 2025-10-03 17:34:15 +02:00
anthony@rabine.fr
20bb0aca57 Working custom socket 2025-10-03 14:35:25 +02:00
anthony@rabine.fr
d58967710a Added localization API and helpers
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-10-02 21:52:28 +02:00
anthony@rabine.fr
663fa47004 cleaning + label from node base
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-10-02 18:07:14 +02:00
anthony@rabine.fr
0be3ff2203 missing files 2025-10-02 17:48:25 +02:00
anthony@rabine.fr
6d544f5879 removed exit node, nex function widget 2025-10-02 17:41:42 +02:00
anthony@rabine.fr
3e00fb1c83 Add error list dock window, cleaned some nodes 2025-10-02 14:10:50 +02:00
anthony@rabine.fr
8111f0a362 Merge remote-tracking branch 'origin/fix-build' + better toolbar
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-10-01 18:09:13 +02:00
Anthony Rabine
31e76ce6d7 fix build of modules 2025-10-01 17:38:52 +02:00
anthony@rabine.fr
479497f1df load/save module success, better toaster 2025-10-01 17:25:56 +02:00
anthony@rabine.fr
883257fd78 custom socket styling 2025-10-01 11:00:31 +02:00
anthony@rabine.fr
d06f05d207 Function entry/exit nodes + custom socket (wip)
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-09-07 23:14:55 +02:00
anthony@rabine.fr
fc37a9ffa1 Add selection for property display 2025-09-04 13:17:26 +02:00
anthony@rabine.fr
600a80d529 Replace ImGuiNodeEditor by ImNodeFlow (build and execute ok, missing widget content)
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-09-02 22:46:03 +02:00
Anthony Rabine
da04c38dec (wip) compilation of module
Some checks failed
Build-StoryEditor / build_win32 (push) Has been cancelled
Build-StoryEditor / build_linux (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-08-28 22:07:21 +02:00
Anthony Rabine
6459dba5c3 Working variable edior for modules
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-08-26 22:49:37 +02:00
Anthony Rabine
6ec0740345 Build and run ok
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-08-24 15:39:20 +02:00
anthony@rabine.fr
143bbfa8bc wip move out logic from ui
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-08-15 21:52:08 +02:00
anthony@rabine.fr
8a2f70ea01 Intermediatre commit: separation between logic and UI
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-08-07 22:51:23 +02:00
anthony@rabine.fr
d2fac4d79e Fix open exiting module
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-07-28 20:39:04 +02:00
anthony@rabine.fr
03fc21dd17 (WIP) Refactoring MainWindow, open existing module for edit (still buggy)
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-07-27 23:25:57 +02:00
anthony@rabine.fr
65094d88a3 Module save/load
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-07-27 16:09:48 +02:00
anthony@rabine.fr
8aec974f89 Separated call function module, better module support with type
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-07-16 12:15:00 +02:00
anthony@rabine.fr
81f7e9343b Start of module editor with pre version of independant factory
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-07-06 14:25:41 +02:00
anthony@rabine.fr
b962d303c5 fix Flutter build
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-06-23 15:18:05 +02:00
anthony@rabine.fr
1fd58e4c30 Created Node Factory in dedicated class to allow custom nodes not linked to one project 2025-06-20 16:41:27 +02:00
anthony@rabine.fr
6a92fe745d Variables in context, fix variable and print node save data in JSON
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-04-26 21:14:20 +02:00
anthony@rabine.fr
6ec1f39db7 Remove story in library + save/load variables
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-04-25 23:40:21 +02:00
anthony@rabine.fr
47552d4719 Multiple runtime fixes, first build+execution with graphical nodes 2025-04-24 11:39:56 +02:00
anthony@rabine.fr
d6df8b65ab Build splitted to allow multi pages compilation, more widgets
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-04-23 13:28:22 +02:00
anthony@rabine.fr
51eac85360 make story editor build
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-04-23 00:01:09 +02:00
anthony@rabine.fr
a42fdc81ea First working assembly from nodes (cli), youpi 2025-04-22 23:20:44 +02:00
anthony@rabine.fr
3c1224e937 Better custom AST builder using kahn algorithm 2025-04-22 16:35:50 +02:00
anthony@rabine.fr
5c16e2bd94 Good architecture between AST and assembly generation, still WIP
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-04-17 21:14:44 +02:00
anthony@rabine.fr
183e5d0727 First version of AST/flow/assembly generation base classes, arch seems ok 2025-04-09 10:32:26 +02:00
anthony@rabine.fr
b9a946eab4 Add main entry point, fix some bugs, continue AST compilation
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-04-06 22:43:34 +02:00
Anthony Rabine
af859d7085 ast unit test
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-03-09 14:36:44 +01:00
Anthony Rabine
d3a8b10120 (wip) compiler with ast 2025-03-09 14:36:28 +01:00
Anthony Rabine
bbf149dedd Chip32: added two instructions and a macro pre-processor
Some checks are pending
Build-StoryEditor / build_linux (push) Waiting to run
Build-StoryEditor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-03-08 13:07:50 +01:00
anthony@rabine.fr
9cd7956630 Fix CI (link and missing libfuse in base image
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-02-18 08:32:34 +01:00
anthony@rabine.fr
5918b610f8 fix CI (tentative)
Some checks failed
Build-StoryEditor / build_linux (push) Has been cancelled
Build-StoryEditor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-02-16 21:40:15 +01:00
anthony@rabine.fr
b9ad742e43 Add mbed submodule to version 3.6.2
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy-Documentation / deploy (push) Has been cancelled
2025-02-03 08:30:28 +01:00
anthony@rabine.fr
5016cc365d Add MBedTLS as a submodule 2025-02-03 08:23:02 +01:00
anthony@rabine.fr
44ba940b85 fix deprecated version of gh action
Some checks are pending
build-story-editor / build_linux (push) Waiting to run
build-story-editor / build_win32 (push) Waiting to run
Deploy-Documentation / deploy (push) Waiting to run
2025-02-02 20:52:07 +01:00
anthony@rabine.fr
09637be998 fix ci and nsis package generation 2025-02-02 20:38:25 +01:00
anthony@rabine.fr
7e6460cf4c fix mingw64 build, add docker image for linux build 2025-02-01 22:58:04 +01:00
anthony@rabine.fr
8c165ca0a9 civetweb options 2025-01-28 22:32:08 +01:00
anthony@rabine.fr
157c5a5a01 usage of submodules instead of git fetch
multiple fixes/file moves
2025-01-28 22:31:05 +01:00
anthony@rabine.fr
59114c2a80 fix unit tests for chip32, better debugger 2025-01-21 09:59:13 +01:00
anthony@rabine.fr
7973aa4709 Fix win32 build, fix duplicate resource files, fix VM stop
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2025-01-20 17:37:50 +01:00
anthony@rabine.fr
35c9fba323 working import commercial stories
Some checks are pending
build-story-editor / build_linux (push) Waiting to run
build-story-editor / build_win32 (push) Waiting to run
Deploy / deploy (push) Waiting to run
2025-01-19 22:51:21 +01:00
anthony@rabine.fr
6c76307f1b many many fixes for commercial story import, wip on transitions
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2025-01-17 14:44:13 +01:00
anthony@rabine.fr
879f5fbdbc Import commercial nearly complete
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2025-01-14 23:40:18 +01:00
anthony@rabine.fr
59921fe9fd Multiple fixes on project management, fix resource copy bugs
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2025-01-13 17:52:53 +01:00
anthony@rabine.fr
c23176796f fetch stories list from libraryes ok 2025-01-13 11:58:53 +01:00
anthony@rabine.fr
0ad614699c dedicated directory for story web version
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2025-01-10 22:04:39 +01:00
anthony@rabine.fr
432d72c80c First web server route, use of preact for raylib html version
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2025-01-09 11:13:56 +01:00
anthony@rabine.fr
b2fdf5c03b editor: fix build
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2025-01-07 10:49:58 +01:00
anthony@rabine.fr
318547f351 Raylib build for web + minimal front-end page, stand alone story-vm project 2025-01-07 10:26:24 +01:00
anthony@rabine.fr
19b78b17a1 Usage of CPM package manager for CMake, add CivetWeb
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2025-01-04 22:02:35 +01:00
anthony@rabine.fr
fe920f4b15 (WIP) new Download manager, download of commercial DB
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2025-01-03 13:21:47 +01:00
anthony@rabine.fr
3d9b60cb32 (WIP) Commercial import
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2024-12-30 15:50:14 +01:00
anthony@rabine.fr
6305b06d53 update imgui, home-made code viewer (not editor!)
Some checks are pending
build-story-editor / build_linux (push) Waiting to run
build-story-editor / build_win32 (push) Waiting to run
Deploy / deploy (push) Waiting to run
2024-12-29 00:29:21 +01:00
anthony@rabine.fr
6e3143c880 new Kicad audio hat 2024-12-29 00:26:11 +01:00
anthony@rabine.fr
f538148a9a Create separated core (WIP) GUI agnostic, new sub pages for functions
Some checks failed
build-story-editor / build_linux (push) Has been cancelled
build-story-editor / build_win32 (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2024-08-30 13:25:40 +02:00
anthony@rabine.fr
f0ffb62867 Add function node with sub editor 2024-08-06 17:18:01 +02:00
anthony@rabine.fr
cd26f407fc Add syscall 3, home button exit VM on choice media 2024-05-28 12:27:56 +02:00
anthony@rabine.fr
86ab93211e (WIP) Story machine specific Chip32 management 2024-05-28 11:53:18 +02:00
anthony@rabine.fr
e65b506ce1 story-player flutter: integrated new event mask + shared preferences 2024-05-28 11:52:33 +02:00
anthony@rabine.fr
26c917fe27 Chip32 asm: supports hex and binary numbers, audio can be cancelled, add support to event masks 2024-05-28 11:02:19 +02:00
anthony@rabine.fr
fcc3562ecd Add comparison chip32 instructions, add debugger, add CPU window 2024-05-23 15:33:04 +02:00
anthony@rabine.fr
5ed9233778 (WIP) Code debug: add breakpoint button 2024-05-20 16:35:27 +02:00
anthony@rabine.fr
52f0312c5c (wip) Flutter App v1.1: fix logo, fix build warning (updated to last), freezed SDL libraries to a tag 2024-05-20 15:41:30 +02:00
anthony@rabine.fr
d0988a7e6b Files for version 1.0 2024-05-17 21:12:39 +02:00
anthony@rabine.fr
c954563472 first working app 2024-05-17 09:28:59 +02:00
anthony@rabine.fr
bb0c18e433 editor: Fix node model initialization, add binary load 2024-05-17 09:02:35 +02:00
anthony@rabine.fr
016282064e Fix media output format, fix link creation 2024-05-15 15:44:38 +02:00
anthony@rabine.fr
be16a68e85 switched audio from miniaudio to SDL_mixer 2024-05-14 17:18:26 +02:00
anthony@rabine.fr
9accbbeea7 Fix project loading duplicate nodes, formats moved 2024-05-14 11:05:17 +02:00
Anthony Rabine
fcbc802265 fix TLV, add docs, editor: add title name and sound, new story player app in flutter 2024-05-06 23:33:53 +02:00
Anthony Rabine
3490cf0050 add logger, fix mem leak, pack copy to device finished. 2024-05-02 13:22:17 +02:00
Anthony Rabine
73aefa7100 Big overall architecture change: separation between display and story model 2024-05-01 00:09:15 +02:00
Anthony Rabine
4fc34d5521 librarymanager: (wip) selection and copy to device 2024-04-27 20:57:43 +02:00
Anthony Rabine
bd59867bc3 fix windows build 2024-04-25 23:07:52 +02:00
Anthony
4698040979 (wip) library story selection and export to device 2024-04-24 22:20:31 +02:00
Anthony
eb521eda71 tentative: fix SDL link 2024-04-24 17:33:25 +02:00
Anthony
59e96befd0 Add OGG converter 2024-04-24 16:38:53 +02:00
Anthony
a6dbc90b78 more separation between node UI logic and editor, working Studio format support 2024-04-24 14:15:57 +02:00
Anthony
6c4ffbbd62 delete legacy editor 2024-04-23 11:32:16 +02:00
Anthony
4da377e429 moved somes files, add vorbis playback support 2024-04-22 22:22:14 +02:00
Anthony
83a6a3bc15 (wip) Studio format import, (wip) ogg decoder files, (ipgrade) imguifiledialog 2024-04-22 12:07:34 +02:00
Anthony Rabine
09ad937f1e Download manager from sharepoint 2024-04-02 08:15:55 +02:00
Anthony Rabine
79f946a4a5 Use SDL3_Image + multiple bug fixes 2024-04-01 21:24:36 +02:00
Anthony
42c3d9d215 SDL 3 + curl thread download manager 2024-04-01 15:29:00 +02:00
Anthony
001034db61 (WIP library store manager) 2024-03-26 17:45:21 +01:00
Anthony
3669841045 Pico audio with 3 audio chips (complex) 2024-03-26 17:43:26 +01:00
Anthony
830fa5fbfc fix regression 2024-03-11 00:08:23 +01:00
Anthony
afa6846041 fix ci2 2024-03-10 23:53:44 +01:00
Anthony
0df8f9c42a fix ci 2024-03-10 23:37:18 +01:00
Anthony
19c77fac96 Add win32 build to CI 2024-03-10 22:49:14 +01:00
Anthony
86337466c8 fix linux build 2024-03-10 22:34:34 +01:00
Anthony Rabine
37d79c5e33 fix windows build, new build script for win32 setup executable generation 2024-03-10 14:56:41 +01:00
Anthony Rabine
8581dbf253 mac dmg (wip) 2024-01-22 16:59:09 +01:00
Anthony Rabine
78836d0a2e Merge remote-tracking branch 'origin/main' into main 2024-01-22 16:57:38 +01:00
Anthony Rabine
7c9f56cd3b Add MacOS bundle generation (WIP) + Macos build fixes 2024-01-22 16:56:14 +01:00
Anthony Rabine
c057443aa0 fix dir 2024-01-21 14:39:04 +01:00
Anthony Rabine
56612c731d chg build system 2024-01-21 14:28:49 +01:00
Anthony Rabine
d290affe17 removed unecessary package 2024-01-21 13:48:22 +01:00
Anthony Rabine
ad99aaef33 gh actions fix 2024-01-20 14:55:15 +01:00
Anthony Rabine
636a32af67 gh actions fix 2024-01-20 14:51:34 +01:00
Anthony Rabine
3b95e3c171 gh actions test 2024-01-20 14:50:35 +01:00
Anthony Rabine
b1c53c84c3 cpack update + github workflow (linux) 2024-01-20 14:41:20 +01:00
Anthony Rabine
6a610200d0 Factorised more code in LabraryManager and StoryProject, used widely 2024-01-16 13:18:59 +01:00
Anthony
4bcb8e4490 (WIP) Library Manager 2024-01-15 11:56:07 +01:00
Anthony
ce26db3aaa More docs on the story formats 2024-01-07 23:02:55 +01:00
1310 changed files with 301599 additions and 214738 deletions

32
.github/workflows/story_editor.yml vendored Normal file
View 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

View file

@ -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
View file

@ -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
View 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
View file

@ -0,0 +1,10 @@
{
"configurations": [
{
"name": "Linux",
"includePath": ["${workspaceFolder}/**", "~/.conan2/**"]
}
],
"version": 4,
"enableConfigurationSquiggles": true
}

100
.vscode/launch.json vendored
View file

@ -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
View file

@ -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"
}
}

View file

@ -1,6 +1,11 @@
# Open Story Teller (OST)
[![continous_build](https://github.com/arabine/open-story-teller/actions/workflows/build.yml/badge.svg)](https://github.com/arabine/open-story-teller/actions/workflows/build.yml)
| Job | Status |
|---|---|
| Story Editor | [![continous_build](https://github.com/arabine/open-story-teller/actions/workflows/story_editor.yml/badge.svg)](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
![editor](art/story_player.png)
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

View file

@ -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

Binary file not shown.

View 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"
}]
}
}

View 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"
}

View 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 &regName, uint8_t &reg)
{
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 &regName)
{
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

View file

@ -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 &regName, uint8_t &reg);
@ -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

View 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");
}

View 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

View 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
View 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 = &macros[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

View file

@ -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;
}

View file

@ -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;
};

View 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

View 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() {}
};

View 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;
};

View 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;
};

View 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

View 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;
};

View 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;
};

View 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:
};

View 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;
}
}
};

View 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;
}
};

View 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 "";
}

View 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;
};

View 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;
};

File diff suppressed because it is too large Load diff

View 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();
}

View 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;
};

View 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);
}
}

View 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{{}};
};

View 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
}
}

View 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;
};

View 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();
}

View 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;
};

View 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(),
[&paramName](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(),
[&paramName](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);
}
};

View 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;
}
}

View file

@ -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)

View 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();
}

View 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;
};

View 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>();
}
}

View 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};
};

View 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);
}
}
};

View 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);
}
}
};

View 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>();
}

View 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;
};

View 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
}
};

View 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);
}

View 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
};

View 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>();
}

View 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
};

View 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);
}

View 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};
};

View 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;
}

View 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;
};

View 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);
}

View 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};
};

View 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;
}

View 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;
};

View 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();
}

View 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;
};

View 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)));
}
};

View 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;
}

View file

@ -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

View 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);
}
}

View 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;
};

View 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};
};

View file

@ -0,0 +1,4 @@
#include "story_page.h"

View 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;
};

View 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;
};

View 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();
}

View 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

View 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;
}

View 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);
}
};

View 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.
}
};

View 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
View 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

File diff suppressed because it is too large Load diff

672
core/tests/test_branch.cpp Normal file
View 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
View 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
View 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";
}

View 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
View 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;
}

View 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
View 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);
}

View file

@ -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

View file

@ -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)

View file

@ -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
![arch](./images/story-editor-architecture.png)
@ -28,3 +54,5 @@ Open the CMakeLists.txt with your favorite IDE (ie, QtCreator, Visual Studio Cod

View file

@ -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.
![pico](./images/devkit-pico.jpg)
@ -57,17 +57,41 @@ The pins usage for this bundle is indicated by the folling wirering. OST pins ar
![pico](./images/picow-pinout_ost.png)
# 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:
![pico](./images/marble-pico.png)
UF2 disk on your file explorer:
![pico](./images/pico-uf2-ubuntu.png)
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

View file

@ -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