Compare commits

...

185 Commits

Author SHA1 Message Date
Kyler Olsen d775ab6067 Updated .gitignore 2025-12-16 22:19:46 -07:00
Kyler Olsen abc2d2e24a Added contributing link to readme 2025-12-10 00:10:49 -07:00
Kyler Olsen 7b35803e8f Deleted slides (they are in docs) 2025-12-09 23:43:31 -07:00
Kyler Olsen 3773e76c95 Cleaned up some of the stuff from the assignment 2025-12-09 23:21:33 -07:00
Kyler Olsen c091ffacbd Saving stuff from presentation 2025-12-09 23:06:51 -07:00
Kyler Olsen 7aa2a36e1a Moved sls_py.calc 2025-12-08 00:07:27 -07:00
Kyler Olsen 56dcf72241 updated build instructions 2025-12-08 00:02:02 -07:00
Kyler Olsen 24154387ec Added build instructions 2025-12-07 23:59:59 -07:00
Kyler Olsen 0f4b851958 Built python package 2025-12-07 23:59:49 -07:00
Kyler Olsen 60e2f40816 Version Bump 2025-12-07 23:22:53 -07:00
Kyler Olsen 102c5b418a Fixed normal builds 2025-12-07 23:18:11 -07:00
Kyler Olsen 7c0f20a1af Just some final stuff 2025-12-07 23:09:54 -07:00
Kyler Olsen 634c63d294 Added readme to slides 2025-12-07 00:20:50 -07:00
Kyler Olsen a20e8a4d54 Created slides 2025-12-07 00:17:32 -07:00
Kyler Olsen 494639d805 Worked on report 2025-12-06 22:21:31 -07:00
Kyler Olsen 4c9aa78dc8 Listed special features in report 2025-12-05 00:01:00 -07:00
Kyler Olsen f5c786c061 sls_calc module polishing 2025-12-04 23:57:48 -07:00
Kyler Olsen 8890216457 Updated sls_calc module 2025-12-04 23:52:42 -07:00
Kyler Olsen d7164599f0 Fixed circular imports 2025-12-04 23:52:24 -07:00
Kyler Olsen dc5d75cf75 Made interpreter state json serializable in Rust port 2025-12-04 23:06:42 -07:00
Kyler Olsen 7b57807fc7 Started sls_calc app 2025-12-04 22:12:48 -07:00
Kyler Olsen d8e38ada85 Added info string constants 2025-12-04 21:51:22 -07:00
Kyler Olsen 0b37a4210f Python module polishing 2025-12-04 21:47:25 -07:00
Kyler Olsen 2ac3c1ac26 Added pico support 2025-12-04 01:09:30 -07:00
Kyler Olsen 642c277388 It compiles now 2025-12-04 00:20:43 -07:00
Kyler Olsen c1c54277a7 Fixed path error 2025-12-03 23:57:45 -07:00
Kyler Olsen c89acbbdfb Started Raspberry Pi Pico build target 2025-12-03 23:57:33 -07:00
Kyler Olsen 3a57fd7373 Added builtin operators to python port 2025-12-03 23:43:32 -07:00
Kyler Olsen 60c6e3900f More on report 2025-12-03 23:28:08 -07:00
Kyler Olsen b98012488a Worked on report 2025-12-03 16:45:37 -07:00
Kyler Olsen c74ba18067 Fixed version numbers 2025-12-03 15:14:29 -07:00
Kyler Olsen 5b5e24e633 Cleaned up stuff from porting 2025-12-03 15:07:52 -07:00
Kyler Olsen 35a6f4537f Fixes to file and repl 2025-12-03 14:58:37 -07:00
Kyler Olsen 64f620b2ef Claude implementation of lexer.py 2025-12-03 14:57:15 -07:00
Kyler Olsen 8e918dcf34 Merge branch 'master' into python 2025-12-03 11:14:04 -07:00
Kyler Olsen 2dabd4bf30 Renamed python module 2025-12-03 11:11:08 -07:00
Kyler Olsen 835132577f renaming rust bin 2025-12-03 11:03:28 -07:00
Kyler Olsen 1c62f064a4 Fixed errors in builtin and interpreter 2025-12-03 10:53:46 -07:00
Kyler Olsen e76287b9a4 Claude builtin implementation 2025-12-03 00:40:22 -07:00
Kyler Olsen 3a054bc211 Updated python .gitignore 2025-12-02 23:39:38 -07:00
Kyler Olsen 2c8e459cac Added file and main 2025-12-02 23:34:29 -07:00
Kyler Olsen a754cd4df4 Refactor version generation and error handling in versioning scripts 2025-12-02 23:18:41 -07:00
Kyler Olsen 4a2ee88328 Refactor versioning and metadata handling in the Python module 2025-12-02 22:51:34 -07:00
Kyler Olsen 1a11569e9a Readded introductory description to README.md 2025-12-02 22:24:04 -07:00
Kyler Olsen ac9bbc0415 Update build configuration and enhance versioning process 2025-12-02 22:23:11 -07:00
Kyler Olsen c1db0937a2 Created sls_build_backend module to help build the python module 2025-12-02 22:22:53 -07:00
Kyler Olsen 3c288e9165 worked on filling in the project 2025-12-02 21:38:27 -07:00
Kyler Olsen 3dc5455323 Python Project Skeleton 2025-12-02 19:10:11 -07:00
Kyler Olsen 081930e6f5 Fixed warnings 2025-12-02 11:52:20 -07:00
Kyler Olsen 6f4be5a929 Small fixes 2025-12-02 11:31:46 -07:00
Kyler Olsen a154741176 Added numeric type annotations 2025-12-02 11:31:27 -07:00
Kyler Olsen a26a1c0d4a Merge branch 'rust' into rust-claude 2025-12-02 10:45:31 -07:00
Kyler Olsen 08f0437136 It compiles 2025-12-02 00:37:53 -07:00
Kyler Olsen 08a8aadf16 Claude attempt at lexer.rs 2025-12-01 23:46:46 -07:00
Kyler Olsen ae077ef433 All reimplemented, lexer still needs to be finished 2025-12-01 23:37:10 -07:00
Kyler Olsen d17687e5a6 Changed test function names to be snake case 2025-12-01 22:33:51 -07:00
Kyler Olsen 1875f2debd Refactor token_match_expectation to improve numeric and char handling 2025-12-01 17:27:25 -07:00
Kyler Olsen 483e0c3d52 Add YAML to Rust test case generator 2025-12-01 16:19:07 -07:00
Kyler Olsen e1c43f7b2e Refactor lexer to implement token types and enhance token generation logic 2025-12-01 09:14:35 -07:00
Kyler Olsen a15490b521 Remove unneeded HashTable and SlsStr implementations 2025-12-01 09:12:04 -07:00
Kyler Olsen 6f81cbdf15 Implement core modules and initial interpreter setup for SLS Rust 2025-12-01 09:08:27 -07:00
Kyler Olsen b70634b450 Started rust port 2025-12-01 08:53:14 -07:00
Kyler Olsen 58f41e6bda added .gitignores for projects 2025-12-01 08:50:13 -07:00
Kyler Olsen 5094e8b4ab implemented for 2025-12-01 00:56:36 -07:00
Kyler Olsen 749d5b4185 implemented logb, max, min, and rot 2025-12-01 00:39:25 -07:00
Kyler Olsen 7f46fd7f84 Adjusted indentations 2025-12-01 00:19:54 -07:00
Kyler Olsen 90492053f2 Added executing a file 2025-12-01 00:09:56 -07:00
Kyler Olsen 53983d8e92 implemented const 2025-11-30 23:18:40 -07:00
Kyler Olsen 1fa6f4ec2a Fixed type_of 2025-11-30 23:07:06 -07:00
Kyler Olsen c9f15fceb9 implemented atan2 2025-11-30 23:01:53 -07:00
Kyler Olsen 8067b93e62 implemented roll 2025-11-30 22:58:33 -07:00
Kyler Olsen 5ea2d8fe2a implemented while 2025-11-30 22:24:57 -07:00
Kyler Olsen 024af6a778 Added TRUTHY preprocessor macro 2025-11-30 22:10:28 -07:00
Kyler Olsen 50f90dcf84 implemented type_of 2025-11-30 21:11:55 -07:00
Kyler Olsen ae3483c612 match won't be implemented with the current implementation of token strings 2025-11-30 21:04:02 -07:00
Kyler Olsen 721384d400 implemented eval 2025-11-30 21:01:10 -07:00
Kyler Olsen 4ef109bcec implemented lambda 2025-11-30 20:54:48 -07:00
Kyler Olsen 15b3565ee9 implemented if 2025-11-30 20:50:32 -07:00
Kyler Olsen 69c211ec06 implemented pick 2025-11-29 15:36:16 -07:00
Kyler Olsen b287c00c65 implemented dup 2025-11-29 15:26:52 -07:00
Kyler Olsen 8a5e3494e6 implemented bitwise and, not, or, and xor 2025-11-29 15:15:24 -07:00
Kyler Olsen 2f36271439 implemented and, not, and or 2025-11-29 15:11:42 -07:00
Kyler Olsen aa8a69b261 Implemented shl and shr 2025-11-29 15:07:52 -07:00
Kyler Olsen 0512147e6d Small fix 2025-11-29 14:23:13 -07:00
Kyler Olsen b930e2c23b implemented comparisons 2025-11-29 14:21:53 -07:00
Kyler Olsen f91c63b37f implemented ceil, floor, and round 2025-11-29 14:19:34 -07:00
Kyler Olsen 6a82cde8f2 implemented swap 2025-11-29 14:18:36 -07:00
Kyler Olsen 8e67857c95 implemented seed and rand 2025-11-29 14:12:14 -07:00
Kyler Olsen 2086ee503d Error fixes 2025-11-29 14:03:22 -07:00
Kyler Olsen b402f32e68 Added out of domain checks 2025-11-29 13:57:36 -07:00
Kyler Olsen b2e2b91f2c Implemented acos, asin, atan, cos, ln, log, sin, sqrt, and tan 2025-11-29 13:54:39 -07:00
Kyler Olsen f4a7627d7e Implemented abs 2025-11-29 13:48:17 -07:00
Kyler Olsen 9e0675cecc Updated build.py to always recompile meta.c 2025-11-29 13:34:34 -07:00
Kyler Olsen bfeb4c6444 Removed builtins that won't be done for the assignment 2025-11-29 13:34:08 -07:00
Kyler Olsen d7107b3fc5 Reworked division, implemented modulus and exponential 2025-11-29 13:14:30 -07:00
Kyler Olsen 3d419f071c Implemented addition, subtraction, and multiplication 2025-11-29 13:09:22 -07:00
Kyler Olsen 873211ace7 Add progress update for Checkpoint 3 2025-11-29 00:09:29 -07:00
Kyler Olsen 433cc3620a Division works 2025-11-28 23:50:37 -07:00
Kyler Olsen 2ddb0ca4d6 implemented drop builtin function 2025-11-28 23:31:27 -07:00
Kyler Olsen b935325eb4 The interpreter works! More builtins to be implemented. 2025-11-28 23:25:25 -07:00
Kyler Olsen ffc73e773c Added pushing tokens to the stack 2025-11-28 22:23:35 -07:00
Kyler Olsen 0bbb43d5a3 Worked on code execution 2025-11-28 21:59:15 -07:00
Kyler Olsen 521bd9907a Small fixes 2025-11-28 21:41:36 -07:00
Kyler Olsen d333cdfad5 Add meta header and implementation for version printing 2025-11-28 21:27:39 -07:00
Kyler Olsen 382842540a Worked on interpreter 2025-11-28 21:19:59 -07:00
Kyler Olsen c100bfcf7b Set up interpreter in repl 2025-11-28 20:50:06 -07:00
Kyler Olsen ba8aee6d78 Merge branch 'interpreter' into terminal 2025-11-28 20:35:02 -07:00
Kyler Olsen 390fc1981d Created Interpreter Interface 2025-11-28 20:34:52 -07:00
Kyler Olsen 95dba7e478 Added Hash Table Tests 2025-11-28 20:07:32 -07:00
Kyler Olsen 63541aac85 Finished hash table 2025-11-28 20:02:27 -07:00
Kyler Olsen 20434b20ab Worked on hash table initialization 2025-11-28 16:20:35 -07:00
Kyler Olsen e94d316af8 Worked on hash functions 2025-11-28 16:20:11 -07:00
Kyler Olsen 404588d491 Started hash table 2025-11-28 15:52:40 -07:00
Kyler Olsen fb4fe5ad66 Merge branch 'master' into interpreter 2025-11-28 14:59:06 -07:00
Kyler Olsen d8f5ad44b5 Added linked-list based stack 2025-11-28 14:58:15 -07:00
Kyler Olsen fd80b5f69d Lexing in REPL 2025-11-28 00:02:13 -07:00
Kyler Olsen 98f6ba8eab Merge branch 'master' into terminal 2025-11-27 23:06:34 -07:00
Kyler Olsen 8ff968d209 Converted to new build system 2025-11-27 23:05:10 -07:00
Kyler Olsen b0be7f0c0b Created a python build script 2025-11-27 22:53:28 -07:00
Kyler Olsen 528f5aa3ab Updated nmake file 2025-11-27 21:49:32 -07:00
Kyler Olsen dc6be59fab Started working on argv parsing 2025-11-27 21:38:24 -07:00
Kyler Olsen 46a855abd0 REPL function declaration 2025-11-27 20:33:21 -07:00
Kyler Olsen c9aceac591 Created repl and file handling code files 2025-11-27 20:21:32 -07:00
Kyler Olsen f3ae278e53 Added stack types 2025-11-27 20:15:25 -07:00
Kyler Olsen 08205ea6bc Noted tests failing under MSVC on Windows 2025-11-27 19:45:19 -07:00
Kyler Olsen 4c07271aaf Added sls test cases 2025-11-27 18:06:58 -07:00
Kyler Olsen a6cfe15a29 Getting ready for testing the full interpreters 2025-11-27 17:48:14 -07:00
Kyler Olsen 727f461fb6 Fixed lexing error inside token string not being on heap 2025-11-27 17:12:29 -07:00
Kyler Olsen 76a89fe03f Updated memory management in clean_token_string and clean_token_result functions 2025-11-27 17:11:42 -07:00
Kyler Olsen 3512f00f30 Added copy_token_string for properly making deep copies of token strings 2025-11-27 16:47:26 -07:00
Kyler Olsen 6f202602ec Made note of error to be fixed 2025-11-27 13:23:52 -07:00
Kyler Olsen 3a61e250a9 Worked on token_string and memory errors 2025-11-27 01:16:37 -07:00
Kyler Olsen 2a03107e94 Fixed memory errors 2025-11-27 00:24:11 -07:00
Kyler Olsen a080dbc2fb worked on token_string 2025-11-26 23:57:58 -07:00
Kyler Olsen b49130bce7 Clarified compiler instructions 2025-11-26 23:30:18 -07:00
Kyler Olsen 54a098a21f Made MSVC Specific Additions 2025-11-26 23:11:24 -07:00
Kyler Olsen 8db2b0f06f Fixed missing compile rules 2025-11-26 22:52:21 -07:00
Kyler Olsen bc4b65ed2c Worked on nmake on windows 2025-11-24 15:23:57 -07:00
Kyler Olsen ec36f36713 Added nmake Makefile 2025-11-24 15:11:55 -07:00
Kyler Olsen a193006061 Fixed warnings 2025-11-24 15:02:46 -07:00
Kyler Olsen febf34a737 TokenString Empty test passes 2025-11-23 23:58:03 -07:00
Kyler Olsen eacef33cf8 Fixed empty identifier error reporting 2025-11-23 23:50:21 -07:00
Kyler Olsen b2f4b8d850 Fail compilation on warning 2025-11-23 23:47:41 -07:00
Kyler Olsen e80e1756e0 Reordered checkpoints 2025-11-23 23:34:00 -07:00
Kyler Olsen d303995587 Adjusted error handling 2025-11-23 23:32:42 -07:00
Kyler Olsen d166eb5289 Updated test cases 2025-11-23 23:27:13 -07:00
Kyler Olsen 1566c7bf60 Tests are compiling and running again 2025-11-23 21:26:38 -07:00
Kyler Olsen ad26c41463 Got token_string tests converting to C 2025-11-23 21:07:25 -07:00
Kyler Olsen 6d586852d4 Added token string test generator 2025-11-21 09:17:16 -07:00
Kyler Olsen d2e990fe9b Fixes and cleaning up 2025-11-21 00:13:47 -07:00
Kyler Olsen ee8b7a8f45 Very important change 2025-11-20 23:34:03 -07:00
Kyler Olsen 58b5e61740 Added PROGRESS.md 2025-11-20 23:17:22 -07:00
Kyler Olsen 68665a82ae Whitespace doesn't break stuff 2025-11-20 16:43:24 -07:00
Kyler Olsen f2033e30e9 White space breaks stuff, but other than that its good 2025-11-20 15:42:40 -07:00
Kyler Olsen f8894ea4c0 Characters 2025-11-17 22:53:15 -07:00
Kyler Olsen ffe8008bb9 Fixed some tests 2025-11-17 22:12:44 -07:00
Kyler Olsen 77b43b9595 Float literals are working 2025-11-17 21:53:25 -07:00
Kyler Olsen 204a819b52 Add Implementation Mismatches documentation 2025-11-17 11:10:18 -07:00
Kyler Olsen 2d7616e7c6 Added extra tests for additional tests that are not generated 2025-11-17 11:04:03 -07:00
Kyler Olsen f52ea00c34 Added PRINT_SUCCESSFUL_TESTS flag 2025-11-17 09:19:05 -07:00
Kyler Olsen 74d53b3a8d Got all current tests compile now 2025-11-16 20:55:59 -07:00
Kyler Olsen c5ab140f5b Fixed large integer literal warnings 2025-11-16 20:35:44 -07:00
Kyler Olsen 4398b3a4bc Worked on getting tests compiling 2025-11-16 20:34:38 -07:00
Kyler Olsen 690155b9a7 Added float and character tests 2025-11-14 17:22:45 -07:00
Kyler Olsen 60925dab53 Setting up float and character tests 2025-11-14 17:22:06 -07:00
Kyler Olsen db14867474 Added character token type 2025-11-14 17:20:09 -07:00
Kyler Olsen 87ba892839 Added more test generators 2025-11-12 15:22:20 -07:00
Kyler Olsen 25d2202ebd Enhance BaseTestGenerator with generator registration and test execution methods 2025-11-12 13:22:17 -07:00
Kyler Olsen 375c3f2422 Added base test generator class 2025-11-12 13:11:40 -07:00
Kyler Olsen cf5f51ccd8 Added float test cases generator 2025-11-12 11:15:31 -07:00
Kyler Olsen d480321014 Fixed test cases collecting 2025-11-12 11:10:32 -07:00
Kyler Olsen 2c550c4662 Broke up test case generation 2025-11-12 11:09:13 -07:00
Kyler Olsen a62008a3e0 Updated test reporting formatting 2025-11-11 20:12:14 -07:00
Kyler Olsen 2ec9d1d1a9 Added basic tests for SlsStr 2025-11-11 20:11:02 -07:00
Kyler Olsen beae4f0b9d Refactor string handling functions 2025-11-11 19:51:40 -07:00
Kyler Olsen 2b44aad1c7 Merge branch 'master' into strings 2025-11-11 18:48:41 -07:00
Kyler Olsen cceedd2e46 Update CFLAGS and CTESTFLAGS to use C99 standard 2025-11-11 18:44:29 -07:00
Kyler Olsen 1617f6945a Fixes for string.c 2025-11-10 21:29:01 -07:00
Kyler Olsen 2ebe34a9a6 Updated to new string type 2025-11-08 01:02:01 -07:00
Kyler Olsen a5b119807d Implemented string helper functions 2025-11-07 23:31:19 -07:00
Kyler Olsen f3af20aa36 Started reworking strings 2025-11-07 14:31:02 -07:00
Kyler Olsen 40007c27a6 Renamed errors.h and separated bool.h 2025-11-07 14:29:33 -07:00
Kyler Olsen 329a71ca24 Worked on numeric literal parsing 2025-11-06 22:07:04 -07:00
Kyler Olsen 9f616d3e87 Added tests generator 2025-11-06 18:59:18 -07:00
Kyler Olsen 3b1e05241e Fixed and added more test cases 2025-11-06 16:16:24 -07:00
89 changed files with 24533 additions and 773 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
.vscode/ .vscode/
__pycache__/

144
CHANGELOG.md Normal file
View File

@ -0,0 +1,144 @@
# SLS Changelog
## 0.0.2-alpha
*08 Dec 2025*
- Added Rust Port
- Added Interpreter State Serialization to Rust port
- Added `#load <file>` and `#save <file>` directives to the REPL
- Added Python Port
- Added Calculator app
- Added sls_py.calc module
- Added RP2040 build target for the C implementation
## 0.0.1-alpha
*01 Dec 2025*
- Added executing a file
- Implemented the following builtin operators:
- `for`
- `logb`
- `max`
- `min`
- `rot`
- `const`
- `atan2`
- `roll`
- `while`
- `type_of`
- `eval`
- `lambda`
- `if`
- `pick`
- `dup`
- bitwise `and`
- bitwise `not`
- bitwise `or`
- bitwise `xor`
- boolean `and`
- boolean `not`
- boolean `or`
- `shl`
- `shr`
- comparisons
- `ceil`
- `floor`
- `round`
- `swap`
- `seed`
- `rand`
- `acos`
- `asin`
- `atan`
- `cos`
- `ln`
- `log`
- `sin`
- `sqrt`
- `tan`
- `abs`
- modulus
- exponential
- addition
- subtraction
- multiplication
## SE Checkpoint 3
*28 Nov 2025*
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
[Compare SE3250-Checkpoint2..SE3250-Checkpoint3](https://github.com/SnowSE/final-project-KylerOlsen/compare/SE3250-Checkpoint2...SE3250-Checkpoint3)
**Report**:
Since checkpoint 2, I completed `token string` parsing and started working on
the REPL. The lexer and REPL are basically finished as much as I am going to do
for this assignment. I did start to implement builtin functions and operators.
Currently only
[`depth`](https://sls.purplecello.org/complete_operator_reference.html#depth),
[`drop`](https://sls.purplecello.org/complete_operator_reference.html#drop), and
[`/`](https://sls.purplecello.org/complete_operator_reference.html#_5)
**Plan**:
By the final turn-in on December 6th, I plan on getting all arithmetic
operations working. And the python and rust ports with help from AI.
**Stuck**:
I'm not really stuck on anything, but I am running out of time, plus I am
worried about not having enough AI usage to do the ports. If I at least get the
main structures of the ports, I hope it will be enough for me to finish.
## SE Checkpoint 2
*20 Nov 2025*
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
[Compare SE3250-Checkpoint1..SE3250-Checkpoint2](https://github.com/SnowSE/final-project-KylerOlsen/compare/SE3250-Checkpoint1...SE3250-Checkpoint2)
**Report**:
I completed my string type for the interpreter. `floats`, `characters`,
`identifiers`, and `boolean` token lexing has been completed. `integer` tokens
were working at checkpoint 1.
**Plan**:
By checkpoint 3 I plan to complete `token string` token parsing and basic
arithmetic execution.
**Stuck**:
I'm not really stuck on anything, but I have been dropping things from the scope
of this assignment. I do hope to add them when I have time in the future. These
features moved to the backlog include:
- Unicode support
- Exponential literals
- Arrays and array functions
- Type tuples, function defs
- Structs, unions, etc.
## SE Checkpoint 1
*07 Nov 2025*
**intended user**:
Programmers
**detailed functional requirements**:
A stack based language interpreter inspired by RPL, C, Rust, and Uiua
([sls.purplecello.org](https://sls.purplecello.org))
**motivation for wanting to work on the project**:
It is cool, and I have had this idea for a while and learning new languages this
semester has given me more ideas and motivation for this.
**motivation for language selection**:
C is a lower level language that can run on embedded systems and is well suited
for writing language interpreters like Python and Lua.
**non-functional requirements / constraints**:
Efficient enough for use on a modern micro controller
(ie. Raspberry Pi Pico, ESP32)

191
README.md
View File

@ -1,97 +1,128 @@
# YREA SLS # YREA SLS
*Kyler Olsen* *Kyler Olsen*
*October 2025* *October 2025*
*Snow College*
*SE 3250 Survey of Languages Final Project*
Language Code Name: YREA **SLS** (*Stack Language Specification*) SLS is a statically-typed, stack-based language with pure postfix notation
Language Specifications: [sls.purplecello.org](https://sls.purplecello.org) combining the execution model of HP's RPL, the type system of C and Rust, and
Language Specification Repository (Private): [git.purplecello.org](https://git.purplecello.org/KylerOlsen/LangsFinalPlaning) modern array operations from Uiua.
Language Implementation Repository (Private): [git.purplecello.org](https://git.purplecello.org/KylerOlsen/YREA-SLS)
Language Implementation Repository (Mirror on GitHub) (Private): [github.com](https://github.com/SnowSE/final-project-KylerOlsen)
Assignment Page (Private): [snow.instructure.com](https://snow.instructure.com/courses/1154808/assignments/16233203)
## Assignment Description ## Build Commands
**Overview** **Linux**
In your final project for this course, you will do the following: C
```bash
cd SLS_C
python3 build.py build
./bin/sls
```
- Decide on a modest personal project to complete in a new-to-you Python run module
programming language (during class time, we will randomize the list of ```bash
people in the class and allow languages to be selected uniquely on a cd SLS_Python
first-come, first-served basis; be prepared to pick the language you want python3 -m sls_py
and to advocate for the languages that you don't want). python3 -m sls_py.calc
- Without using AI generated or suggested code, complete your project in ```
your assigned language including automated testing. Find a way to make your
useful implementation concise and elegant enough that you would be able to
recreate it reasonably quickly without AI assistance (be prepared to
demonstrate this ability during a one-on-one interview to ensure credit).
- Using AI help as desired, create a faithful port of your implementation
(including tests) to:
- Rust
- Python
- Java (optional, but it might be interesting and userful for your
resume)
- C# (optional, but it might be interesting)
- typescript (optional, but it might be interesting)
- For each ported implementation, add one addition feature (not present in
the original or other ports).
- Prepare a report that describes:
- the problem that your project solves and why you are interested in it
- a brief description of how the language that you chose and the port
languages were or were not each a good fit for the project
- three examples of ideas you learned in the course that you leveraged
to the implementation in your assigned language elegant, efficient, and
concise/maintainable (at least one idea should specifically come from
functional programming) -- make sure to be specific and teach the reader
something valuable for each of these
- at least one struggle that you faced (or that you imagine that others
would likely face) for each language implementation, and what you
learned from working with the port languages during the porting process.
- Prepare slides that help you teach the key ideas from your report to the
class.
- In a 5-8 minute presentation during the final exam slot, use your slides
to teach the class and demonstrate your running project including the
addition port features.
Project Scope Python Setup
```bash
cd SLS_Python
python -m venv .venv
source .venv/bin/activate
pip install build wheel "setuptools>=61.0"
pip install -e sls_build_backend
```
- Please do something that will be fun and interesting for you! Python build module
- Make sure that the project actually solves a problem that you care about. ```bash
(Entertainment counts, but if that is the problem you care about, then make cd SLS_Python
sure that you can make something that actually is entertaining!) source .venv/bin/activate
- I'm expecting you to dedicate about 15-30 hours to the entire project python3 -m build --no-isolation
(including all three implementations and the report/presentation pip install ./dist/sls_python-0.0.2a0-py3-none-any.whl
preparation). I'm expecting that your initial implmentation should feel python3 -m sls_py
like about twice the scope of implementing the game of life in PostScript python3 -m sls_py.calc
or K. ```
**Languages** Rust
```bash
cd SLS_Rust/sls
cargo build
./target/debug/sls_rs
```
For what it is worth, here are a few resources that rank or compare different **Windows**
programming languages in terms of popularity or jobs (but they don't equally
capture how programming in the languages will give you tools for thinking
about and solving problems):
- https://spectrum.ieee.org/top-programming-languages-2025 C (Using Visual Studio Developer PowerShell)
- https://www.tiobe.com/tiobe-index/ ```shell
- https://survey.stackoverflow.co/2025/technology#admired-and-desired cd SLS_C
- https://survey.stackoverflow.co/2025/technology#most-popular-technologies-language-prof python build.py build
- https://tjpalmer.github.io/languish/ .\bin\sls.exe
- https://www.geeksforgeeks.org/blogs/top-programming-languages/ ```
- https://github.com/breck7/pldb
Here is the list of programming languages that you will be able to choose from Python run module
for your initial implementation (each language will be chosen by at most one ```bat
student; we will choose during class time on Oct 27): cd SLS_Python
python -m sls_py
python -m sls_py.calc
```
- **C** Kyler Python Setup
```bat
cd SLS_Python
python -m venv .venv
.venv\Scripts\activate.bat
pip install build wheel "setuptools>=61.0"
pip install -e sls_build_backend
```
**Submission** Python build module
```bat
cd SLS_Python
.venv\Scripts\activate.bat
python -m build --no-isolation
pip install .\dist\sls_python-0.0.2a0-py3-none-any.whl
python -m sls_py
python -m sls_py.calc
```
Submit all material (including report and slides) via github classrooms repo. Rust
Also, submit your github repo link and the path to the report and slides file ```bat
in the canvas text box for this assignment. The grace period for submission cd SLS_Rust\sls
ends at 3pm on Monday, Dec 8. Presentations will be 3:30pm - 5:30pm. For full cargo build
credit, you must present and watch the presentations of your classmates. .\target\debug\sls_rs.exe
```
**MacOS**
Reference Linux build instructions.
For C there is the `python3 build.py macos` command, but it is untested as I
didn't test it on a Mac. I also don't know if `python3 build.py build` would also
work on a Mac.
Python and Rust should just be the same or similar to Linux.
**RP2040**
Only tested on Linux. Only SLS_C supports RP2040.
Install Pico SDK to `~/pico/pico-sdk`, or set environment variable
`PICO_SDK_PATH` to your installation path. You will also need to install the
appropriate compiler.
Debian GNU/Linux
```bash
sudo apt install gcc-arm-none-eabi
```
```shell
cd SLS_C
python3 build.py rp2040
```
Flash Raspberry Pi Pico:
Copy `.\build_pico\sls.elf.uf2` to your Pico in BOOTSEL mode
## Contributing
[sls.purplecello.org/contributing](https://sls.purplecello.org/contributing.html)

92
REPORT.md Normal file
View File

@ -0,0 +1,92 @@
# YREA SLS
*Kyler Olsen*
*October-December 2025*
*Snow College*
*SE 3250*
*Survey of Languages*
*Final Project*
Language Code Name:
YREA **SLS** (*Stack Language Specification*)
Language Webpage:
[sls.purplecello.org](https://sls.purplecello.org)
Language Specification Repository (Private):
[git.purplecello.org](https://git.purplecello.org/KylerOlsen/LangsFinalPlaning)
Language Implementation Repository:
[git.purplecello.org](https://git.purplecello.org/KylerOlsen/YREA-SLS)
Language Implementation Repository (Mirror on GitHub) (Private):
[github.com](https://github.com/SnowSE/final-project-KylerOlsen)
Assignment Page (Private):
[snow.instructure.com](https://snow.instructure.com/courses/1154808/assignments/16233203)
In 1986, Hewlett-Packard released their HP-18C and HP-24C calculators, which
introduced their new RPL operating system and programming language. The language
was based on LISP and Forth (a stack-oriented language). RPL, aka Reverse Polish
Lisp, was used by HP in several of their calculators through the 1990s and
2000s, most famously on the HP-48 series of calculators. HP calculators have
been a favorite among engineers because of their post-fix notation.
When in my journeyings on the internet a few years ago, I discovered Uiua, a
pre-fix, stack-based, array-oriented language, which inspired me to create my
own, except more largely inspired by HP's RPL. I started planning out an idea,
but never made it far. This semester I was again inspired by looking at the
various programming languages to again make my own, this time throwing in C to
the mix. What resulted is my *Stack Language Specification*. Having difficulty
coming up with a name for the language, I went with SLS.
SLS is a stack-oriented language with C inspired syntax. While these have not
been implemented for the assignment, I do plan on adding a Rust inspired type
system, as well as Uiua and LISP inspired array operations. Memory management
follows the pattern in RPL, everything is on the stack. One of my goals with
this language is also to have it able to run on an embedded system, such as my
own custom calculator.
**C** was my selected language. It was an excellent choice for my project as it
is well suited for systems programming. It is a low level, yet powerful
language. While string utilities are not as robust as most modern languages, I
still feel like it was an excellent choice. Other major interpreted languages
also use C to implement their interpreters, such as Lua and Python.
Manual memory management was a slight difficulty for me. The only way I could
find what was causing a segmentation fault was to run the binary with `gdb`.
Still my experience programming C in a Linux environment was fun and rewarding.
I became accustomed to utilizing the man pages (or manual pages) for
documentation on the C standard library.
I did use structs and unions heavily which we learned about when we looked at C.
With them I was able to use polymorphism in defining tokens and data types.
I am able to successfully compile and run this implementation on a Raspberry Pi
Pico with a RP2040 microcontroller. You can interact with the REPL over a serial
connection.
**Rust** was the first language I tackled porting my project to. I am not as
familiar with Rust as I am Python, so thats why I wanted to get going on this
port first. I avoided using external libraries with C as they can be famously
difficult to link and compile. Rust's build system, cargo made that issue
basically non-existent. It is also memory safe with its barrow checker. With it
being another systems programming language, and these modern features, I feel
like it is just as good of a choice of a language for my project.
In the past I have often fought with the barrow checker but with this project,
either my experience, memory mindfulness in my original port, or the extensive
AI help, I had no fights with the barrow checker this time.
The special feature for the Rust port is being able to export and import the
interpreter state in the repl using `#save <filename>` and `#load <filename>`.
**Python** was my final port. With it being the language I am most comfortable
and experienced with, this was an easy port to make. While it is very easy, even
for someone without my experience, and it has many of the same modern
conveniences with package management as Rust. Where it is not a systems
programming language, I would not be able to run this project on an embedded
system very cleanly or easily, making Python not as good of a choice with my
goal of portability to embedded systems.
The special feature for the Python port is the SLS Calculator App.
---
https://en.wikipedia.org/wiki/HP_48_series
https://en.wikipedia.org/wiki/HP-28_series
https://en.wikipedia.org/wiki/RPL_(programming_language)

6
SLS_C/.gitignore vendored
View File

@ -1,3 +1,9 @@
obj/ obj/
bin/ bin/
build_pico/
*.o *.o
*.pdb
CMakeLists.txt
pico_arm_gcc_toolchain.cmake
generated/
pico-sdk.txt

60
SLS_C/Failing Tests.md Normal file
View File

@ -0,0 +1,60 @@
# Failing Tests
These tests fail and haven't been fixed yet as they would likely take major
refactoring to fix, which I may not have time to do before finals week.
## Integer i64 Overflow
code: `9223372036854775808:i64`
error: `Integer overflow: value exceeds range for i64.`
This test fails because the parsed integer has already overflowed inside the
interpreter, so an integer token is returned with no overflow error detected.
## Integer i64 Underflow
code: `-9223372036854775809:i64`
error: `Integer overflow: value exceeds range for i64.`
This test fails because the parsed integer has already overflowed inside the
interpreter, so an integer token is returned with no overflow error detected.
## Integer u64 Overflow
code: `18446744073709551616:u64`
error: `Integer overflow: value exceeds range for u64.`
This test fails because the parsed integer has already overflowed inside the
interpreter, so an integer token is returned with no overflow error detected.
## Integer i16 Binary Sample
code: `0b1111111100000000:i16`
value: `-256`
This test fails because the interpreter fails to recognize this as a negative
number, and parses is as `65280`, which is greater than the signed 16-bit max of
`32767`, resulting in an overflow error.
## Float Default No Leading Digit Negative
code: `-.25`
value: `-0.25`
This test fails because the interpreter fails to recognize a token starting with
both a `-` then a `.` then a digit, as a numeric literal, resulting in it being
interpreted as an identifier.
## TokenString Error Inside
code: `{ 2 3a + }`
error: `Invalid decimal literal: unexpected 'a' in decimal integer.`
I don't know why this test is being reported as failing. I think it may be
trying looking for the error inside the token string.
## TokenString Struct Fields
code: `{ x: y: }`
value: `TokenString`
All non-code token strings won't work properly unless they are also valid code
token strings. All non-code token string features won't be implemented yet.
## Windows Tests
`test_format_basic_placeholders` and `Integer i32 Min Value` also fail when
compiling with MSVC on Windows instead of GCC on Linux.

View File

@ -0,0 +1,9 @@
# Implementation Mismatches
This is a list of things defined as currently in the specification, but not included in this implementation to reduce scope for the assignment.
- Unicode support
- Exponential literals
- Arrays and array functions
- Type tuples, function defs
- Structs, unions, etc.

View File

@ -1,77 +0,0 @@
# Makefile for SLS project with automatic header dependencies
CC ?= gcc
CFLAGS ?= -std=c11 -Wall -Wextra -g -Iinclude -MMD -MP
LDFLAGS ?=
CTESTFLAGS ?= -std=c11 -Wall -Wextra -Wno-unused-function -g -O0 -Iinclude -MMD -MP
SRCDIR := src
OBJDIR := obj
BINDIR := bin
TESTDIR := tests
TARGET := $(BINDIR)/sls
TEST_TARGET := $(BINDIR)/sls_tests
SOURCES := $(wildcard $(SRCDIR)/*.c)
OBJECTS := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SOURCES))
NON_MAIN_OBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS))
TEST_SOURCES := $(wildcard $(TESTDIR)/*.c)
TEST_OBJECTS := $(patsubst $(TESTDIR)/%.c,$(OBJDIR)/%.o,$(TEST_SOURCES))
# Include dependency files if they exist
-include $(OBJECTS:.o=.d) $(TEST_OBJECTS:.o=.d)
.PHONY: all build run test clean
# Default: build main program
all: $(TARGET)
# Compile object files
build: $(OBJECTS)
# Rule to compile .c -> .o (handles both src and tests)
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/%.o: $(TESTDIR)/%.c | $(OBJDIR)
$(CC) $(CTESTFLAGS) -c $< -o $@
# Link main program
$(TARGET): $(OBJECTS) | $(BINDIR)
$(CC) $(LDFLAGS) $^ -o $@ -lm
# Run main program
run: $(TARGET)
@echo "Running $(TARGET)..."
./$(TARGET)
test_cases: ../SLS_Tests/yaml_to_c_tests.py ../SLS_Tests/cases.yaml
python3 ../SLS_Tests/yaml_to_c_tests.py ../SLS_Tests/cases.yaml ./tests/lexer_tests.c
# Build test runner executable
$(TEST_TARGET): $(TEST_OBJECTS) $(NON_MAIN_OBJECTS) | $(BINDIR)
$(CC) $(LDFLAGS) $^ -o $@ -lm
build_tests: test_cases $(TEST_TARGET)
# Run tests
debug: build_tests
gdb ./$(TEST_TARGET)
# Run tests
test: test_cases $(TEST_TARGET)
@echo "Running tests..."
./$(TEST_TARGET)
# Create directories if missing
$(BINDIR):
mkdir -p $(BINDIR)
$(OBJDIR):
mkdir -p $(OBJDIR)
# Remove build artifacts
clean:
rm -rf $(OBJDIR) $(BINDIR)

View File

@ -1,11 +1,23 @@
# SLS C # SLS C
This is the C implementation for the YREA SLS interpreter. This is the C implementation for the YREA SLS interpreter.
## Running ## Compiling, Running, and Testing
Build Project: `make build` Interpreter binary location:
Build and Run Project: `make run` - Linux: `./bin/sls`
Build and Run Tests: `make test` - Windows: `.\bin\sls.exe`
Interpreter binary location: `./bin/sls` ### Linux (GCC)
Build Project: `python3 build.py all`
Build and Run Project: `python3 build.py run`
Build and Run Tests: `python3 build.py test`
### Windows (MSVC)
*__For Windows users:__ Use a VS developer shell for all build commands.*
Build Project: `python build.py all`
Build and Run Project: `python build.py run`
Build and Run Tests: `python build.py test`

19
SLS_C/SlsStr.md Normal file
View File

@ -0,0 +1,19 @@
# Sls String
*string.h*
## Formats
- `y` char\*
- `c` char
- `d` int32_t
- `l` int64_t
- `u` uint64_t
- `z` size_t
- `f` double
- `n` unicode uint8_t\[4\] (Not Implemented)
- `s` string.h SlsStr
- `t` lexer.h TokenType
- `a` lexer.h ArrayType
- `i` lexer.h IntegerBuiltInType
- `e` error.h SlsError
- `b` bool.h Boolean

515
SLS_C/build.py Normal file
View File

@ -0,0 +1,515 @@
#!/usr/bin/env python3
import os
import sys
import subprocess
from pathlib import Path
import shutil
import platform
# ---------------------------------------------------------------------
# CONFIG
# ---------------------------------------------------------------------
SRC_DIR = Path("src")
TEST_DIR = Path("tests")
OBJ_DIR = Path("obj")
BIN_DIR = Path("bin")
TARGET = BIN_DIR / "sls"
TEST_TARGET = BIN_DIR / "sls_tests"
# Platform-specific settings
PICO_SDK_PATH = os.environ.get("PICO_SDK_PATH", Path.home() / "pico/pico-sdk")
PICO_BUILD_DIR = Path("build_pico")
PICO_TOOLCHAIN_PATH = Path("pico_arm_gcc_toolchain.cmake")
# Unix gcc/clang flags
COMMON_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Werror", "-Iinclude", "-g"]
TEST_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Wno-unused-function", "-Werror",
"-Iinclude", "-g", "-O0"]
# macOS-specific flags (for cross-compilation if needed)
MACOS_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Werror", "-Iinclude", "-g",
"-mmacosx-version-min=10.13"]
# Windows MSVC flags
MSVC_FLAGS = ["/std:c11", "/Zi", "/Iinclude"]
MSVC_TEST_FLAGS = MSVC_FLAGS + []
# RP2040 toolchain file template
PICO_TOOLCHAIN_TEMPLATE = """set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR cortex-m0plus)
# Specify the cross compiler
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
# Compiler flags for Cortex-M0+
set(CMAKE_C_FLAGS_INIT "-mcpu=cortex-m0plus -mthumb")
set(CMAKE_CXX_FLAGS_INIT "-mcpu=cortex-m0plus -mthumb")
set(CMAKE_ASM_FLAGS_INIT "-mcpu=cortex-m0plus -mthumb")
# Don't run the linker on compiler check
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
# Adjust the default behavior of the FIND_XXX() commands:
# search programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Search headers and libraries in the target environment
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
"""
# RP2040 settings
RP2040_CMAKE_TEMPLATE = """cmake_minimum_required(VERSION 3.13)
# Pico SDK initialization
set(PICO_SDK_PATH "{pico_sdk_path}")
include({pico_sdk_path}/external/pico_sdk_import.cmake)
project({project_name} C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
# Main executable
add_executable({project_name}
{source_files}
)
# Set output name with .elf extension
set_target_properties({project_name} PROPERTIES
OUTPUT_NAME "{project_name}.elf"
SUFFIX ""
)
# Add include directories
target_include_directories({project_name} PRIVATE
${{CMAKE_CURRENT_LIST_DIR}}/include
)
# Add compile definitions
target_compile_definitions({project_name} PRIVATE
PICO_BUILD=1
GIT_COMMIT_HASH="{git_hash}"
)
# Link libraries
target_link_libraries({project_name}
pico_stdlib
hardware_uart
hardware_gpio
)
# Enable USB/UART output
pico_enable_stdio_usb({project_name} 1)
pico_enable_stdio_uart({project_name} 1)
# Create map/bin/hex/uf2 files
pico_add_extra_outputs({project_name})
"""
# ---------------------------------------------------------------------
# PLATFORM DETECTION
# ---------------------------------------------------------------------
def detect_platform():
"""Detect the current operating system"""
system = platform.system()
if system == "Darwin":
return "macos"
elif system == "Windows":
return "windows"
elif system == "Linux":
return "linux"
return "unknown"
PLATFORM = detect_platform()
# ---------------------------------------------------------------------
# COMPILER DETECTION
# ---------------------------------------------------------------------
def detect_compiler(target_platform=None):
"""Detect appropriate compiler for the target platform"""
if target_platform == "rp2040":
return ("arm-none-eabi-gcc", "gcc")
target = target_platform or PLATFORM
if target == "windows" or os.name == "nt":
return ("cl", "msvc")
elif target == "macos":
# Prefer clang on macOS
if shutil.which("clang"):
return ("clang", "gcc")
return ("gcc", "gcc")
else:
return ("gcc", "gcc")
CC, CC_KIND = detect_compiler()
# ---------------------------------------------------------------------
# PYTHON DETECTION
# ---------------------------------------------------------------------
def detect_python():
if os.name == "nt":
return "python"
return "python3"
PYTHON = detect_python()
# ---------------------------------------------------------------------
# GIT COMMIT HASH
# ---------------------------------------------------------------------
def git_commit_hash():
try:
result_hash = subprocess.check_output(
["git", "describe", "--always", "--dirty", "--abbrev=7"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
result_date = subprocess.check_output(
["git", "show", "-s", "--format=%ci"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
return f"{result_hash} {result_date}"
except Exception:
return "unknown"
GIT_HASH = git_commit_hash()
# ---------------------------------------------------------------------
# UTILS
# ---------------------------------------------------------------------
def mkdir(p: Path):
p.mkdir(parents=True, exist_ok=True)
def run(cmd):
print(">>", " ".join(str(c) for c in cmd))
subprocess.check_call(cmd)
def is_up_to_date(src, obj, dep):
if "meta" in [src.stem, obj.stem, dep.stem]:
return False
if not obj.exists():
return False
if dep.exists() and dep.stat().st_mtime > obj.stat().st_mtime:
return False
return src.stat().st_mtime < obj.stat().st_mtime
# ---------------------------------------------------------------------
# BUILD RULES
# ---------------------------------------------------------------------
def compile_source(src: Path, is_test=False, target_platform=None):
mkdir(OBJ_DIR)
obj = OBJ_DIR / (src.stem + ".o")
dep = OBJ_DIR / (src.stem + ".d")
if is_up_to_date(src, obj, dep):
return obj
compiler, kind = detect_compiler(target_platform)
if kind == "msvc":
flags = MSVC_TEST_FLAGS if is_test else MSVC_FLAGS
cmd = [compiler] + flags + ["/Fo" + str(obj), "/c", str(src),
f"/DGIT_COMMIT_HASH=\"{GIT_HASH}\""]
else:
if target_platform == "macos":
flags = MACOS_FLAGS if not is_test else TEST_FLAGS + ["-mmacosx-version-min=10.13"]
else:
flags = TEST_FLAGS if is_test else COMMON_FLAGS
cmd = [compiler] + flags + [
f"-DGIT_COMMIT_HASH=\"{GIT_HASH}\"",
"-MMD", "-MP",
"-c", str(src), "-o", str(obj)
]
run(cmd)
return obj
def link_executable(objects, output: Path, target_platform=None):
mkdir(BIN_DIR)
compiler, kind = detect_compiler(target_platform)
if kind == "msvc":
cmd = [compiler] + list(map(str, objects)) + ["/Fe" + str(output)]
else:
cmd = [compiler] + list(map(str, objects)) + ["-lm", "-o", str(output)]
run(cmd)
# ---------------------------------------------------------------------
# TEST CASE GENERATION
# ---------------------------------------------------------------------
def generate_tests():
script = Path("../SLS_Tests/yaml_to_c_tests.py")
yaml = Path("../SLS_Tests/cases.yaml")
out = TEST_DIR / "lexer_tests.c"
if not script.exists() or not yaml.exists():
print("Test generation skipped (missing files).")
return False
if out.exists() and out.stat().st_mtime > yaml.stat().st_mtime:
return True
run([PYTHON, str(script), str(yaml), str(out)])
return True
# ---------------------------------------------------------------------
# RP2040 BUILD
# ---------------------------------------------------------------------
def check_pico_sdk():
"""Check if Pico SDK is available"""
sdk_path = Path(PICO_SDK_PATH)
if not sdk_path.exists():
print(f"ERROR: Pico SDK not found at {sdk_path}")
print("Please set PICO_SDK_PATH environment variable or install SDK at ~/pico/pico-sdk")
return False
# Check for ARM toolchain
if not shutil.which("arm-none-eabi-gcc"):
print("ERROR: ARM toolchain not found!")
print("Please install arm-none-eabi-gcc:")
print(" Ubuntu/Debian: sudo apt install gcc-arm-none-eabi")
print(" macOS: brew install arm-none-eabi-gcc")
return False
return True
def generate_pico_toolchain():
"""Generate ARM GCC toolchain file for CMake"""
with open(PICO_TOOLCHAIN_PATH, "w") as f:
f.write(PICO_TOOLCHAIN_TEMPLATE)
print(f"Generated {PICO_TOOLCHAIN_PATH}")
def generate_pico_cmake(project_name="sls"):
"""Generate CMakeLists.txt for RP2040 build"""
sources = list(SRC_DIR.glob("*.c"))
# Exclude main.c, repl.c, file.c and test files for Pico build
# pico_main.c will provide its own REPL implementation
sources = [s for s in sources
if s.name not in ["main.c", "repl.c", "file.c"]
and "test" not in s.stem.lower()]
# Check for pico_main.c
pico_main = SRC_DIR / "pico_main.c"
if not pico_main.exists():
print(f"WARNING: {pico_main} not found! Please create it.")
print("The Pico build requires a pico_main.c file.")
return False
sources.append(pico_main)
source_files = "\n".join(f" {s}" for s in sources)
cmake_content = RP2040_CMAKE_TEMPLATE.format(
project_name=project_name,
source_files=source_files,
git_hash=GIT_HASH,
pico_sdk_path=PICO_SDK_PATH
)
cmake_file = Path("CMakeLists.txt")
with open(cmake_file, "w") as f:
f.write(cmake_content)
print(f"Generated {cmake_file}")
return True
def build_rp2040():
"""Build for RP2040 using CMake"""
if not check_pico_sdk():
return False
print("\n=== Building for RP2040 ===\n")
# Generate toolchain file
generate_pico_toolchain()
# Generate CMakeLists.txt
if not generate_pico_cmake():
return False
# Create build directory
mkdir(PICO_BUILD_DIR)
# Run CMake with explicit toolchain
cmake_cmd = [
"cmake",
"-B", str(PICO_BUILD_DIR),
"-S", ".",
f"-DCMAKE_TOOLCHAIN_FILE={PICO_TOOLCHAIN_PATH}"
]
run(cmake_cmd)
# Build
build_cmd = ["cmake", "--build", str(PICO_BUILD_DIR), "-j4"]
run(build_cmd)
# Show output files
uf2_file = PICO_BUILD_DIR / "sls.uf2"
if uf2_file.exists():
print(f"\nBuild successful! UF2 file: {uf2_file}")
print("To flash: Copy this file to your Pico in BOOTSEL mode")
return True
# ---------------------------------------------------------------------
# MACOS BUILD
# ---------------------------------------------------------------------
def build_macos():
"""Build for macOS (can be run on macOS or cross-compile on Linux)"""
print("\n=== Building for macOS ===\n")
if PLATFORM != "macos" and PLATFORM != "linux":
print("ERROR: macOS builds require macOS or Linux with osxcross")
return False
sources = list(SRC_DIR.glob("*.c"))
objects = [compile_source(s, is_test=False, target_platform="macos") for s in sources]
macos_target = BIN_DIR / "sls_macos"
link_executable(objects, macos_target, target_platform="macos")
print(f"\nBuild successful! Binary: {macos_target}")
return True
# ---------------------------------------------------------------------
# TARGETS
# ---------------------------------------------------------------------
def build_main():
sources = list(SRC_DIR.glob("*.c"))
objects = [compile_source(s, is_test=False) for s in sources if s.name != 'pico_main.c']
link_executable(objects, TARGET)
def build_tests():
generate_tests()
test_sources = list(TEST_DIR.glob("*.c"))
main_sources = [s for s in SRC_DIR.glob("*.c") if s.name != "main.c"]
test_objects = [compile_source(s, is_test=True) for s in test_sources]
shared_objects = [compile_source(s, is_test=False) for s in main_sources]
link_executable(test_objects + shared_objects, TEST_TARGET)
def clean():
shutil.rmtree(OBJ_DIR, ignore_errors=True)
shutil.rmtree(BIN_DIR, ignore_errors=True)
shutil.rmtree(PICO_BUILD_DIR, ignore_errors=True)
cmake_file = Path("CMakeLists.txt")
if cmake_file.exists():
cmake_file.unlink()
toolchain_file = PICO_TOOLCHAIN_PATH
if toolchain_file.exists():
toolchain_file.unlink()
print("Cleaned.")
def run_main():
build_main()
print("\n--- Running program ---\n")
subprocess.call([str(TARGET)])
def run_tests():
build_tests()
print("\n--- Running tests ---\n")
subprocess.call([str(TEST_TARGET)])
def debug_tests():
build_tests()
if os.name == "nt":
print("Debugging on Windows requires Visual Studio debugger.")
else:
subprocess.call(["gdb", str(TEST_TARGET)])
def show_help():
help_text = """
Build Script for Multi-Platform Compilation
============================================
Usage: python3 build.py [command]
Commands:
all, main, build Build for current platform
run Build and run program
test Build and run tests
debug Build tests and run debugger
clean Remove build artifacts
macos Build for macOS
pico, rp2040 Build for RP2040 (Raspberry Pi Pico)
help Show this help message
Environment Variables:
PICO_SDK_PATH Path to Pico SDK (default: ~/pico/pico-sdk)
Examples:
python3 build.py build # Build for current platform
python3 build.py pico # Build for RP2040
python3 build.py macos # Build for macOS
python3 build.py clean # Clean all build files
"""
print(help_text)
# ---------------------------------------------------------------------
# ENTRY POINT
# ---------------------------------------------------------------------
def main():
if len(sys.argv) < 2:
show_help()
return
cmd = sys.argv[1]
match cmd:
case "all" | "main" | "build":
build_main()
case "run":
run_main()
case "test":
run_tests()
case "debug":
debug_tests()
case "clean":
clean()
case "macos":
build_macos()
case "pico" | "rp2040":
build_rp2040()
case "help" | "-h" | "--help":
show_help()
case _:
print(f"Unknown target: {cmd}")
print("Run 'python3 build.py help' for usage information")
if __name__ == "__main__":
main()

1
SLS_C/fib.min.sls Normal file
View File

@ -0,0 +1 @@
{ 2 - dup 0 <= { drop 1 } { 1 1 rot 0 swap { drop swap 1 pick + } for swap drop } if } lambda ::fib const

17
SLS_C/fib.sls Normal file
View File

@ -0,0 +1,17 @@
{
// Start with which Fibonacci number we want (Nth Fibonacci number)
2 - // We push the first two already
dup 0 <= { drop 1 } {
1 1 // Starting Fibonacci numbers
rot 0 swap // Setting up loop from zero to N
{
drop // Discard the loop counter
swap 1 pick // Swap n-1 and n-2 and copy n-1
+ // Add n-2 and the copy of n-1
// n and n-1 left on stack
} for
swap drop // Drop n-1 from the stack
} if
} lambda ::fib const
8 fib

14
SLS_C/include/sls/bool.h Normal file
View File

@ -0,0 +1,14 @@
// Kyler Olsen
// YREA SLS
// SLS Boolean Header
// November 2025
#ifndef SLS_BOOLEAN_H
#define SLS_BOOLEAN_H
typedef enum {
FALSE,
TRUE,
} Boolean;
#endif // SLS_BOOLEAN_H

View File

@ -0,0 +1,31 @@
// Kyler Olsen
// YREA SLS
// Builtin Functions Header
// November 2025
#ifndef SLS_BUILTIN_FUNCTIONS_H
#define SLS_BUILTIN_FUNCTIONS_H
#include "sls/bool.h"
#include "sls/interpreter.h"
#if __SIZEOF_POINTER__ == 4
// 32-bit system
#define SLS_INTEGER_BUILTIN_DEFAULT INTEGER_I32
#define SLS_UNSIGNED_INTEGER_BUILTIN_DEFAULT INTEGER_U32
#define SLS_FLOAT_BUILTIN_DEFAULT TOKEN_FLOAT
#elif __SIZEOF_POINTER__ == 8
// 64-bit system
#define SLS_INTEGER_BUILTIN_DEFAULT INTEGER_I64
#define SLS_UNSIGNED_INTEGER_BUILTIN_DEFAULT INTEGER_U64
#define SLS_FLOAT_BUILTIN_DEFAULT TOKEN_DOUBLE
#else
// Fallback
#define SLS_INTEGER_BUILTIN_DEFAULT INTEGER_I16
#define SLS_UNSIGNED_INTEGER_BUILTIN_DEFAULT INTEGER_U16
#define SLS_FLOAT_BUILTIN_DEFAULT TOKEN_FLOAT
#endif
Boolean load_builtins(InterpreterState *interpreter_state);
#endif // SLS_BUILTIN_FUNCTIONS_H

View File

@ -7,14 +7,12 @@
#define SLS_ERROR_H #define SLS_ERROR_H
#include <stdint.h> #include <stdint.h>
#include <stddef.h>
typedef enum { #include "./string.h"
FALSE,
TRUE,
} Boolean;
typedef struct { typedef struct {
const char *message; SlsStr message;
int32_t code; int32_t code;
} SlsError; } SlsError;
@ -24,7 +22,7 @@ typedef enum {
} SlsResultType; } SlsResultType;
typedef struct { typedef struct {
const char *filename; SlsStr filename;
size_t line; size_t line;
size_t column; size_t column;
size_t length; size_t length;

16
SLS_C/include/sls/file.h Normal file
View File

@ -0,0 +1,16 @@
// Kyler Olsen
// YREA SLS
// File Header
// November 2025
#ifndef SLS_FILE_H
#define SLS_FILE_H
#include "sls/bool.h"
#include "sls/string.h"
#include "sls/interpreter.h"
Boolean exec_file(InterpreterState *interpreter_state, SlsStr filename);
int file(SlsStr filename);
#endif // SLS_FILE_H

View File

@ -0,0 +1,37 @@
// Kyler Olsen
// YREA SLS
// Hash Table Header
// November 2025
#ifndef SLS_HASH_TABLE_H
#define SLS_HASH_TABLE_H
#include <stddef.h>
#include "sls/string.h"
typedef struct Bucket {
uint32_t hash_a;
uint32_t hash_b;
void *item;
struct Bucket *next;
} Bucket;
typedef struct {
size_t buckets_count;
Bucket *buckets[];
} HashTable;
// Initializes a HashTable with atleast buckets_count number of buckets. Returns NULL on Memory Allocation Error.
HashTable *init_hash_table(size_t min_buckets_count);
// Frees memory owned by the HashTable.
void del_hash_table(HashTable *ht);
// Inserts an item into the HashTable based on its key. Returns FALSE on Memory Allocation Error.
Boolean hash_table_put(HashTable *ht, SlsStr key, void *item);
// Gets the item associated with the given key. Returns default item if not found.
void *hash_table_get(const HashTable *ht, SlsStr key, void *default_item);
// Deletes the item associated with the given key. Returns FALSE if item is not found.
Boolean hash_table_del(HashTable *ht, SlsStr key);
#endif // SLS_HASH_TABLE_H

View File

@ -0,0 +1,85 @@
// Kyler Olsen
// YREA SLS
// Interpreter Header
// November 2025
#ifndef SLS_INTERPRETER_H
#define SLS_INTERPRETER_H
#include <stddef.h>
#include "sls/bool.h"
#include "sls/lexer.h"
#include "sls/hash_table.h"
typedef enum {
STACK_IDENTIFIER,
STACK_I64,
STACK_I32,
STACK_I16,
STACK_I8,
STACK_U64,
STACK_U32,
STACK_U16,
STACK_U8,
STACK_FLOAT,
STACK_DOUBLE,
STACK_CHARACTER,
STACK_BOOLEAN,
STACK_TOKEN_STRING,
STACK_CALLABLE,
} StackType;
extern const char *STACK_TYPES_NAMES[];
extern const char *STACK_TYPES_TYPES[];
extern const size_t STACK_TYPE_COUNT;
typedef struct StackItem {
StackType type;
union {
Identifier identifier; // type == STACK_IDENTIFIER
int64_t i64; // type == STACK_I64
int32_t i32; // type == STACK_I32
int16_t i16; // type == STACK_I16
int8_t i8; // type == STACK_I8
uint64_t u64; // type == STACK_U64
uint32_t u32; // type == STACK_U32
uint16_t u16; // type == STACK_U16
uint8_t u8; // type == STACK_U8
float f32; // type == STACK_FLOAT
double f64; // type == STACK_DOUBLE
uint8_t character; // type == STACK_CHARACTER
Boolean boolean; // type == STACK_BOOLEAN
TokenString token_string; // type == STACK_TOKEN_STRING
};
struct StackItem *next;
} StackItem;
typedef struct {
StackItem *stack;
HashTable *functions;
} InterpreterState;
typedef enum {
FUNCTION_TOKEN_STRING,
FUNCTION_BUILTIN,
} FunctionType;
typedef struct {
FunctionType type;
union {
TokenString token_string; // type == FUNCTION_TOKEN_STRING
Boolean (*builtin)(InterpreterState *); // type == FUNCTION_BUILTIN
};
} FunctionItem;
Boolean push_token(InterpreterState *interpreter_state, Token token);
void clean_stack(StackItem *item);
Boolean execute_func(InterpreterState *interpreter_state, SlsStr key);
Boolean execute_token_string(InterpreterState *interpreter_state, TokenString token_string);
Boolean execute(InterpreterState *interpreter_state, LexerTokenResult *token);
InterpreterState *interpreter_create();
void interpreter_delete(InterpreterState *interpreter_state);
#endif // SLS_INTERPRETER_H

View File

@ -8,13 +8,28 @@
#include <stddef.h> #include <stddef.h>
#include "sls/sls_errors.h" #include "sls/bool.h"
#include "sls/errors.h"
#if __SIZEOF_POINTER__ == 4
// 32-bit system
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I32
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
#elif __SIZEOF_POINTER__ == 8
// 64-bit system
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I64
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F64
#else
// Fallback
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I16
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
#endif
extern const size_t TYPE_NAMES_SAFE_LENGTH; extern const size_t TYPE_NAMES_SAFE_LENGTH;
typedef struct { typedef struct {
const char *filename; SlsStr filename;
const char *source_code; SlsStr source_code;
size_t pos; size_t pos;
size_t column; size_t column;
size_t line; size_t line;
@ -26,6 +41,7 @@ typedef enum {
TOKEN_INTEGER, TOKEN_INTEGER,
TOKEN_FLOAT, TOKEN_FLOAT,
TOKEN_DOUBLE, TOKEN_DOUBLE,
TOKEN_CHARACTER,
TOKEN_STRING, TOKEN_STRING,
TOKEN_BOOLEAN, TOKEN_BOOLEAN,
TOKEN_ARRAY, TOKEN_ARRAY,
@ -34,6 +50,7 @@ typedef enum {
} TokenType; } TokenType;
extern const char *TOKEN_TYPES_NAMES[]; extern const char *TOKEN_TYPES_NAMES[];
extern const size_t TOKEN_TYPE_COUNT;
typedef enum { typedef enum {
ARRAY_IDENTIFIER, ARRAY_IDENTIFIER,
@ -47,16 +64,17 @@ typedef enum {
ARRAY_U8, ARRAY_U8,
ARRAY_FLOAT, ARRAY_FLOAT,
ARRAY_DOUBLE, ARRAY_DOUBLE,
ARRAY_CHARACTER,
ARRAY_STRING, ARRAY_STRING,
ARRAY_BOOLEAN, ARRAY_BOOLEAN,
ARRAY_STRUCT_INLINE, ARRAY_STRUCT_INLINE,
} ArrayType; } ArrayType;
extern const char *ARRAY_TYPES_NAMES[]; extern const char *ARRAY_TYPES_NAMES[];
extern const size_t ARRAY_TYPE_COUNT;
typedef struct { typedef struct {
const char *name; SlsStr name;
size_t length;
Boolean is_literal; Boolean is_literal;
} Identifier; } Identifier;
@ -72,17 +90,13 @@ typedef enum {
} IntegerBuiltInType; } IntegerBuiltInType;
extern const char *INTEGER_TYPES_NAMES[]; extern const char *INTEGER_TYPES_NAMES[];
extern const size_t INTEGER_TYPE_COUNT;
typedef struct { typedef struct {
uint64_t value; uint64_t value;
IntegerBuiltInType type; IntegerBuiltInType type;
} IntegerLiteral; } IntegerLiteral;
typedef struct {
const char *value;
size_t length;
} StringLiteral;
typedef struct Token Token; typedef struct Token Token;
typedef struct { typedef struct {
@ -99,7 +113,7 @@ typedef struct {
typedef struct { typedef struct {
void **values; void **values;
const char *name; SlsStr name;
} StructInline; } StructInline;
typedef struct ArrayLiteral { typedef struct ArrayLiteral {
@ -109,7 +123,8 @@ typedef struct ArrayLiteral {
uint64_t *integer_literals; // type in { ARRAY_I64, ARRAY_I32, ARRAY_I16, ARRAY_I8, ARRAY_U64, ARRAY_U32, ARRAY_U16, ARRAY_U8, } uint64_t *integer_literals; // type in { ARRAY_I64, ARRAY_I32, ARRAY_I16, ARRAY_I8, ARRAY_U64, ARRAY_U32, ARRAY_U16, ARRAY_U8, }
float *float_literals; // type == ARRAY_FLOAT float *float_literals; // type == ARRAY_FLOAT
double *double_literals; // type == ARRAY_DOUBLE double *double_literals; // type == ARRAY_DOUBLE
StringLiteral *string_literals; // type == ARRAY_STRING uint8_t *character_literal; // type == ARRAY_CHARACTER
SlsStr *string_literals; // type == ARRAY_STRING
Boolean *boolean_literals; // type == ARRAY_BOOLEAN Boolean *boolean_literals; // type == ARRAY_BOOLEAN
TokenString *token_strings; // type == ARRAY_TOKEN_STRING TokenString *token_strings; // type == ARRAY_TOKEN_STRING
TypeTuple *type_tuples; // type == ARRAY_TYPE_TUPLE TypeTuple *type_tuples; // type == ARRAY_TYPE_TUPLE
@ -126,7 +141,8 @@ struct Token {
IntegerLiteral integer_literal; // type == TOKEN_INTEGER IntegerLiteral integer_literal; // type == TOKEN_INTEGER
float float_literal; // type == TOKEN_FLOAT float float_literal; // type == TOKEN_FLOAT
double double_literal; // type == TOKEN_DOUBLE double double_literal; // type == TOKEN_DOUBLE
StringLiteral string_literal; // type == TOKEN_STRING uint8_t character_literal; // type == TOKEN_CHARACTER
SlsStr string_literal; // type == TOKEN_STRING
Boolean boolean_literal; // type == TOKEN_BOOLEAN Boolean boolean_literal; // type == TOKEN_BOOLEAN
ArrayLiteral array_literal; // type == TOKEN_ARRAY ArrayLiteral array_literal; // type == TOKEN_ARRAY
TokenString token_string; // type == TOKEN_TOKEN_STRING TokenString token_string; // type == TOKEN_TOKEN_STRING
@ -152,8 +168,10 @@ typedef struct {
}; };
} LexerResult; } LexerResult;
void init_lexer(LexerInfo *lexer_info, const char *filename, const char *source_code); TokenString copy_token_string(TokenString token_string);
void init_lexer(LexerInfo *lexer_info, SlsStr filename, SlsStr source_code);
LexerTokenResult *get_token(LexerTokenResult *head, size_t i); LexerTokenResult *get_token(LexerTokenResult *head, size_t i);
void clean_token_string(TokenString token_string);
void clean_token_result(LexerTokenResult *head); void clean_token_result(LexerTokenResult *head);
LexerResult lexical_analysis(LexerInfo *lexer_info); LexerResult lexical_analysis(LexerInfo *lexer_info);

35
SLS_C/include/sls/meta.h Normal file
View File

@ -0,0 +1,35 @@
// Kyler Olsen
// YREA SLS
// Main Header
// November 2025
#ifndef SLS_MAIN_H
#define SLS_MAIN_H
#define SLS_NAME "SLS_C"
#define SLS_VER "0.0.2-alpha"
#ifndef GIT_COMMIT_HASH
#define GIT_COMMIT_HASH "UNKNOWN"
#endif
#if defined(__GNUC__)
#define COMPILER_NAME "GCC"
#define COMPILER_VER __GNUC__
#elif defined(__clang__)
#define COMPILER_NAME "Clang"
#define COMPILER_VER __clang_major__
#elif defined(_MSC_VER)
#define COMPILER_NAME "MSVC"
#define COMPILER_VER _MSC_VER
#else
#define COMPILER_NAME "Unknown compiler"
#define COMPILER_VER 0
#endif
void print_version();
#endif // SLS_MAIN_H

11
SLS_C/include/sls/repl.h Normal file
View File

@ -0,0 +1,11 @@
// Kyler Olsen
// YREA SLS
// REPL Header
// November 2025
#ifndef SLS_REPL_H
#define SLS_REPL_H
int repl();
#endif // SLS_REPL_H

View File

@ -7,8 +7,29 @@
#define SLS_STRING_H #define SLS_STRING_H
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <stdarg.h>
int isascii(unsigned char c); #include "bool.h"
size_t strnlen(const char *s, size_t maxlen);
typedef struct {
size_t len; // Number of useable characters (does not include trailing null character)
char *str;
Boolean allocated;
} SlsStr;
#define SLS_STR_CONST(s) {sizeof(s) - 1, (s), FALSE}
#define SLS_STR_NULL_CONST {0, NULL, FALSE}
#define SLS_STR(s) (SlsStr) SLS_STR_CONST(s)
#define SLS_STR_NULL (SlsStr) SLS_STR_NULL_CONST
int sls_isascii(unsigned char c);
size_t sls_str_nlen(const char *s, size_t maxlen);
SlsStr sls_str_malloc(const char *s, size_t maxlen);
SlsStr sls_str_new(size_t length);
SlsStr sls_str_cpy(SlsStr s);
int32_t sls_str_cmp(SlsStr a, SlsStr b);
void sls_str_free(SlsStr *s);
SlsStr sls_format(const SlsStr s, ...);
#endif // SLS_STRING_H #endif // SLS_STRING_H

View File

@ -14,7 +14,8 @@
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include "sls/sls_errors.h" #include "sls/errors.h"
#include "sls/bool.h"
#include "sls/lexer.h" #include "sls/lexer.h"
#include "sls/string.h" #include "sls/string.h"
#include "tests/lexer_test_helpers.h" #include "tests/lexer_test_helpers.h"
@ -27,8 +28,7 @@ typedef struct {
typedef struct { typedef struct {
Boolean is_literal; Boolean is_literal;
size_t length; SlsStr name;
const char *name;
} TestIdentifierValue; } TestIdentifierValue;
typedef struct { typedef struct {
@ -36,11 +36,6 @@ typedef struct {
uint64_t value; uint64_t value;
} TestIntegerValue; } TestIntegerValue;
typedef struct {
size_t length;
const char *string;
} TestStringValue;
typedef struct { typedef struct {
size_t dimensions; size_t dimensions;
size_t *shape; size_t *shape;
@ -68,7 +63,7 @@ typedef struct {
typedef struct { typedef struct {
size_t dimensions; size_t dimensions;
size_t *shape; size_t *shape;
TestStringValue *values; SlsStr *values;
} TestArrayStringValue; } TestArrayStringValue;
typedef struct { typedef struct {
@ -80,8 +75,7 @@ typedef struct {
typedef struct { typedef struct {
size_t dimensions; size_t dimensions;
size_t *shape; size_t *shape;
size_t struct_name_length; SlsStr struct_name;
const char *struct_name;
Boolean (*struct_handler)(LexerTest *, LexerResult, size_t, size_t, void *, void *); Boolean (*struct_handler)(LexerTest *, LexerResult, size_t, size_t, void *, void *);
void **values; void **values;
} TestArrayStructInlineValue; } TestArrayStructInlineValue;
@ -103,15 +97,11 @@ typedef struct {
TestIdentifierValue *output_values; TestIdentifierValue *output_values;
} TestTypeTupleValue; } TestTypeTupleValue;
typedef struct { LexerTest start_up_test(SlsStr test_name, SlsStr test_code);
size_t length;
const char *message;
} TestErrorMessage;
LexerTest start_up_test(const char *test_name, const char *test_code);
void clean_up_test(LexerResult result); void clean_up_test(LexerResult result);
TestResult error_test_out_of_mem(LexerTest *test);
TestResult error_test(LexerTest *test, LexerResult result, SlsError error); TestResult error_test(LexerTest *test, LexerResult result, SlsError error);
TestResult logic_fail_test(LexerTest *test, LexerResult result, char *message); TestResult logic_fail_test(LexerTest *test, LexerResult result, SlsStr message);
TestResult error_fail_test(LexerTest *test, LexerResult result, SlsError error); TestResult error_fail_test(LexerTest *test, LexerResult result, SlsError error);
TestResult skip_test(LexerTest *test, LexerResult result); TestResult skip_test(LexerTest *test, LexerResult result);
TestResult skip_test_no_result(LexerTest *test); TestResult skip_test_no_result(LexerTest *test);
@ -120,9 +110,10 @@ TestResult pass_test(LexerTest *test, LexerResult result);
Boolean test_eof_value(LexerTest *test, LexerResult result, size_t i, void *_); Boolean test_eof_value(LexerTest *test, LexerResult result, size_t i, void *_);
Boolean test_identifier_value(LexerTest *test, LexerResult result, size_t i, TestIdentifierValue *value); Boolean test_identifier_value(LexerTest *test, LexerResult result, size_t i, TestIdentifierValue *value);
Boolean test_integer_value(LexerTest *test, LexerResult result, size_t i, TestIntegerValue *value); Boolean test_integer_value(LexerTest *test, LexerResult result, size_t i, TestIntegerValue *value);
Boolean test_character_value(LexerTest *test, LexerResult result, size_t i, uint8_t *value);
Boolean test_float_value(LexerTest *test, LexerResult result, size_t i, float *value); Boolean test_float_value(LexerTest *test, LexerResult result, size_t i, float *value);
Boolean test_double_value(LexerTest *test, LexerResult result, size_t i, double *value); Boolean test_double_value(LexerTest *test, LexerResult result, size_t i, double *value);
Boolean test_string_value(LexerTest *test, LexerResult result, size_t i, TestStringValue *value); Boolean test_string_value(LexerTest *test, LexerResult result, size_t i, SlsStr *value);
Boolean test_boolean_value(LexerTest *test, LexerResult result, size_t i, Boolean *value); Boolean test_boolean_value(LexerTest *test, LexerResult result, size_t i, Boolean *value);
Boolean test_array_identifier_value(LexerTest *test, LexerResult result, size_t i, TestArrayIdentifierValue *values); Boolean test_array_identifier_value(LexerTest *test, LexerResult result, size_t i, TestArrayIdentifierValue *values);
Boolean test_array_integer_value(LexerTest *test, LexerResult result, size_t i, TestArrayIntegerValue *values); Boolean test_array_integer_value(LexerTest *test, LexerResult result, size_t i, TestArrayIntegerValue *values);
@ -133,6 +124,6 @@ Boolean test_array_boolean_value(LexerTest *test, LexerResult result, size_t i,
Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size_t i, TestArrayStructInlineValue *values); Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size_t i, TestArrayStructInlineValue *values);
Boolean test_token_string_value(LexerTest *test, LexerResult result, size_t i, TestTokenStringValue *values); Boolean test_token_string_value(LexerTest *test, LexerResult result, size_t i, TestTokenStringValue *values);
Boolean test_type_tuple_value(LexerTest *test, LexerResult result, size_t i, TestTypeTupleValue *values); Boolean test_type_tuple_value(LexerTest *test, LexerResult result, size_t i, TestTypeTupleValue *values);
Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, TestErrorMessage *error); Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr *error);
#endif // SLS_LEXER_TEST_HELPERS_H #endif // SLS_LEXER_TEST_HELPERS_H

View File

@ -8,9 +8,10 @@
#include <stddef.h> #include <stddef.h>
#include "sls/sls_errors.h" #include "sls/errors.h"
#include "sls/string.h"
extern const char *TEST_FILE_NAME; extern const SlsStr TEST_FILE_NAME;
typedef enum { typedef enum {
TEST_ERROR, // The test encountered an error TEST_ERROR, // The test encountered an error
@ -22,20 +23,23 @@ typedef enum {
} TestResultType; } TestResultType;
typedef struct { typedef struct {
const char *name; SlsStr name;
TestResultType status; TestResultType status;
union { union {
char *message; // status in { TEST_LOGIC_FAIL, } SlsStr message; // status in { TEST_LOGIC_FAIL, }
SlsError error; // status in { TEST_ERROR, TEST_ERROR_FAIL, } SlsError error; // status in { TEST_ERROR, TEST_ERROR_FAIL, }
}; };
} TestResult; } TestResult;
typedef struct { typedef struct {
const char *section; SlsStr section;
size_t count; size_t count;
TestResult* tests; TestResult* tests;
} TestsReport; } TestsReport;
TestsReport run_string_tests();
TestsReport run_lexer_tests(); TestsReport run_lexer_tests();
TestsReport run_hash_table_tests();
TestsReport run_extra_tests();
#endif // SLS_TESTS_H #endif // SLS_TESTS_H

2217
SLS_C/src/builtin.c Normal file

File diff suppressed because it is too large Load Diff

68
SLS_C/src/file.c Normal file
View File

@ -0,0 +1,68 @@
// Kyler Olsen
// YREA SLS
// File
// November 2025
#include <stdio.h>
#include "sls/file.h"
#include "sls/bool.h"
#include "sls/string.h"
#include "sls/interpreter.h"
Boolean exec_file(InterpreterState *interpreter_state, SlsStr filename) {
FILE *file_o = fopen(filename.str, "r");
if (file_o == NULL) {
printf("Cannot read file: %s\n", filename.str);
return FALSE;
}
if (fseek(file_o, 0, SEEK_END)) return FALSE;
size_t file_length = ftell(file_o);
rewind(file_o);
LexerInfo lexer_info;
SlsStr code = sls_str_new(file_length);
for (size_t i = 0; i < file_length; i++) {
int character = fgetc(file_o);
if (character == EOF) return FALSE;
else code.str[i] = (char)character;
}
init_lexer(&lexer_info, filename, code);
LexerResult result = lexical_analysis(&lexer_info);
if (result.type == SLS_ERROR) {
printf("%s\n", result.error.message.str);
sls_str_free(&result.error.message);
return FALSE;
} else {
LexerTokenResult *head = result.result;
while (head) {
if (head->type == SLS_ERROR) {
printf("%s\n", head->error.message.str);
sls_str_free(&result.error.message);
clean_token_result(result.result);
return FALSE;
} else if (!execute(interpreter_state, head)) {
printf("A runtime error occurred!\n");
break;
}
head = head->next;
}
clean_token_result(result.result);
}
sls_str_free(&code);
if (fclose(file_o)) return FALSE;
else return TRUE;
}
int file(SlsStr filename) {
printf("Executing file: %s\n", filename.str);
InterpreterState *interpreter_state = interpreter_create();
if (interpreter_state == NULL) return 1;
Boolean success = exec_file(interpreter_state, filename);
interpreter_delete(interpreter_state);
if (success) return 0;
else return 1;
}

155
SLS_C/src/hash_table.c Normal file
View File

@ -0,0 +1,155 @@
// Kyler Olsen
// YREA SLS
// Hash Table
// November 2025
#include <stdlib.h>
#include "sls/hash_table.h"
#include "sls/string.h"
static size_t next_power_of_two(size_t n) {
if (n < 2) return 2;
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
#if SIZE_MAX > UINT32_MAX
n |= n >> 32;
#endif
n++;
return n;
}
HashTable *init_hash_table(size_t min_buckets_count) {
size_t buckets_count = next_power_of_two(min_buckets_count);
size_t size = sizeof(HashTable) + buckets_count * sizeof(Bucket);
HashTable *ht = (HashTable *)malloc(size);
if (!ht) return NULL;
ht->buckets_count = buckets_count;
for (size_t i = 0; i < buckets_count; i++)
ht->buckets[i] = NULL;
return ht;
}
void del_hash_table(HashTable *ht) {
for (size_t i = 0; i < ht->buckets_count; i++) {
Bucket *head = ht->buckets[i];
while (head) {
Bucket *next = head->next;
// TODO: Does item need to be freed?
free(head);
head = next;
}
}
free(ht);
}
static inline uint32_t sls_hash_a(SlsStr key) {
// FNV-1a
uint32_t hash = 2166136261u;
const uint8_t *p = (const uint8_t *)key.str;
for (size_t i = 0; i < key.len; i++) {
hash ^= p[i];
hash *= 16777619u;
}
return hash;
}
static inline uint32_t sls_hash_b(SlsStr key) {
// Murmur2A
const uint32_t m = 0x5bd1e995;
uint32_t hash = 0;
while (key.len >= 4) {
uint32_t k = (uint32_t)key.str[0]
| (uint32_t)key.str[1] << 8
| (uint32_t)key.str[2] << 16
| (uint32_t)key.str[3] << 24;
k *= m;
k ^= k >> 24;
k *= m;
hash *= m;
hash ^= k;
key.str += 4;
key.len -= 4;
}
if (key.len >= 3)
hash ^= key.str[2] << 16;
if (key.len >= 2)
hash ^= key.str[1] << 8;
if (key.len >= 1)
hash ^= key.str[0];
hash *= m;
hash ^= hash >> 13;
hash *= m;
hash ^= hash >> 15;
return hash;
}
Boolean hash_table_put(HashTable *ht, SlsStr key, void *item) {
uint32_t hash_a = sls_hash_a(key);
uint32_t hash_b = sls_hash_b(key);
size_t bucket_index = hash_a & (ht->buckets_count - 1);
for (Bucket *node = ht->buckets[bucket_index]; node; node = node->next) {
if (node->hash_a == hash_a && node->hash_b == hash_b) {
// TODO: Does item need to be freed before being replaced?
node->item = item;
return TRUE;
}
}
Bucket *bucket = (Bucket *)malloc(sizeof(Bucket));
if (!bucket)
return FALSE;
bucket->hash_a = hash_a;
bucket->hash_b = hash_b;
bucket->item = item;
bucket->next = ht->buckets[bucket_index];
ht->buckets[bucket_index] = bucket;
return TRUE;
}
void *hash_table_get(const HashTable *ht, SlsStr key, void *default_item) {
uint32_t hash_a = sls_hash_a(key);
uint32_t hash_b = sls_hash_b(key);
size_t bucket_index = hash_a & (ht->buckets_count - 1);
for (Bucket *node = ht->buckets[bucket_index]; node; node = node->next)
if (node->hash_a == hash_a && node->hash_b == hash_b)
return node->item;
return default_item;
}
Boolean hash_table_del(HashTable *ht, SlsStr key) {
uint32_t hash_a = sls_hash_a(key);
uint32_t hash_b = sls_hash_b(key);
size_t bucket_index = hash_a & (ht->buckets_count - 1);
Bucket *prev = NULL;
for (Bucket *node = ht->buckets[bucket_index]; node; node = node->next) {
if (node->hash_a == hash_a && node->hash_b == hash_b) {
if (prev == NULL)
ht->buckets[bucket_index] = node->next;
else
prev->next = node->next;
// TODO: Does item need to be freed?
free(node);
return TRUE;
}
prev = node;
}
return FALSE;
}

232
SLS_C/src/interpreter.c Normal file
View File

@ -0,0 +1,232 @@
// Kyler Olsen
// YREA SLS
// Interpreter
// November 2025
#include <stdlib.h>
#include "sls/string.h"
#include "sls/interpreter.h"
#include "sls/lexer.h"
#include "sls/hash_table.h"
#include "sls/builtin.h"
static const size_t HASH_TABLE_BUCKET_COUNT = 256;
const char *STACK_TYPES_NAMES[] = {
"Identifier",
"64-bit Integer",
"32-bit Integer",
"16-bit Integer",
"8-bit Integer",
"64-bit U Integer",
"32-bit U Integer",
"16-bit U Integer",
"8-bit U Integer",
"Float",
"Double",
"Character",
"Boolean",
"Token String",
"Callable",
};
const char *STACK_TYPES_TYPES[] = {
"Identifier",
"i64",
"i32",
"i16",
"i8",
"u64",
"u32",
"u16",
"u8",
"f32",
"f64",
"char",
"bool",
"TokenString",
"Callable",
};
const size_t STACK_TYPE_COUNT = sizeof(STACK_TYPES_NAMES) / sizeof(*STACK_TYPES_NAMES);
static inline Boolean hash_table_put_funcs(HashTable *ht, SlsStr key, FunctionItem *item) {
return hash_table_put(ht, key, (void *)item);
}
static inline FunctionItem *hash_table_get_funcs(const HashTable *ht, SlsStr key, FunctionItem *default_item) {
return (FunctionItem*)hash_table_get(ht, key, (void *)default_item);
}
Boolean push_token(InterpreterState *interpreter_state, Token token) {
StackType type;
switch (token.type) {
case TOKEN_EOF:
return TRUE;
case TOKEN_IDENTIFIER:
type = STACK_IDENTIFIER;
break;
case TOKEN_INTEGER:
switch (token.integer_literal.type) {
case INTEGER_I64:
type = STACK_I64;
break;
case INTEGER_I32:
type = STACK_I32;
break;
case INTEGER_I16:
type = STACK_I16;
break;
case INTEGER_I8:
type = STACK_I8;
break;
case INTEGER_U64:
type = STACK_U64;
break;
case INTEGER_U32:
type = STACK_U32;
break;
case INTEGER_U16:
type = STACK_U16;
break;
case INTEGER_U8:
type = STACK_U8;
break;
}
break;
case TOKEN_FLOAT:
type = STACK_FLOAT;
break;
case TOKEN_DOUBLE:
type = STACK_DOUBLE;
break;
case TOKEN_CHARACTER:
type = STACK_CHARACTER;
break;
case TOKEN_STRING:
return FALSE;
case TOKEN_BOOLEAN:
type = STACK_BOOLEAN;
break;
case TOKEN_ARRAY:
return FALSE;
case TOKEN_TOKEN_STRING:
type = STACK_TOKEN_STRING;
break;
case TOKEN_TYPE_TUPLE:
return FALSE;
}
StackItem *item = (StackItem *)malloc(sizeof(StackItem));
if (item == NULL) return FALSE;
item->type = type;
item->next = interpreter_state->stack;
interpreter_state->stack = item;
switch (type) {
case STACK_IDENTIFIER:
item->identifier = token.identifier;
break;
case STACK_I64:
item->i64 = (int64_t)token.integer_literal.value;
break;
case STACK_I32:
item->i32 = (int32_t)token.integer_literal.value;
break;
case STACK_I16:
item->i16 = (int16_t)token.integer_literal.value;
break;
case STACK_I8:
item->i8 = (int8_t)token.integer_literal.value;
break;
case STACK_U64:
item->u64 = (uint64_t)token.integer_literal.value;
break;
case STACK_U32:
item->u32 = (uint32_t)token.integer_literal.value;
break;
case STACK_U16:
item->u16 = (uint16_t)token.integer_literal.value;
break;
case STACK_U8:
item->u8 = (uint8_t)token.integer_literal.value;
break;
case STACK_FLOAT:
item->f32 = token.float_literal;
break;
case STACK_DOUBLE:
item->f64 = token.double_literal;
break;
case STACK_CHARACTER:
item->character = token.character_literal;
break;
case STACK_BOOLEAN:
item->boolean = token.boolean_literal;
break;
case STACK_TOKEN_STRING:
case STACK_CALLABLE:
item->token_string = copy_token_string(token.token_string);
break;
}
return TRUE;
}
Boolean execute_func(InterpreterState *interpreter_state, SlsStr key) {
FunctionItem *func = hash_table_get_funcs(interpreter_state->functions, key, NULL);
if (func == NULL) return FALSE;
switch (func->type) {
case FUNCTION_BUILTIN:
return func->builtin(interpreter_state);
case FUNCTION_TOKEN_STRING:
return execute_token_string(interpreter_state, func->token_string);
}
return FALSE;
}
Boolean execute_token_string(InterpreterState *interpreter_state, TokenString token_string) {
Boolean return_value = FALSE;
for (size_t i = 0; i < token_string.length; i++) {
if (token_string.tokens[i].type == TOKEN_IDENTIFIER && !token_string.tokens[i].identifier.is_literal)
return_value = execute_func(interpreter_state, token_string.tokens[i].identifier.name);
else
return_value = push_token(interpreter_state, token_string.tokens[i]);
if (!return_value) break;
}
return return_value;
}
Boolean execute(InterpreterState *interpreter_state, LexerTokenResult *token) {
if (token->result.type == TOKEN_IDENTIFIER && !token->result.identifier.is_literal)
return execute_func(interpreter_state, token->result.identifier.name);
else
return push_token(interpreter_state, token->result);
}
InterpreterState *interpreter_create() {
InterpreterState *interpreter_state = (InterpreterState *)malloc(sizeof(InterpreterState));
if (interpreter_state == NULL)
return NULL;
interpreter_state->stack = NULL;
interpreter_state->functions = init_hash_table(HASH_TABLE_BUCKET_COUNT);
if (!load_builtins(interpreter_state)) {
interpreter_delete(interpreter_state);
return NULL;
}
return interpreter_state;
}
void clean_stack(StackItem *item) {
if (item == NULL) return;
if (item->type == STACK_TOKEN_STRING)
clean_token_string(item->token_string);
clean_stack(item->next);
free(item);
}
void interpreter_delete(InterpreterState *interpreter_state) {
clean_stack(interpreter_state->stack);
del_hash_table(interpreter_state->functions);
free(interpreter_state);
}

View File

@ -9,8 +9,10 @@
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <math.h>
#include "sls/sls_errors.h" #include "sls/errors.h"
#include "sls/bool.h"
#include "sls/lexer.h" #include "sls/lexer.h"
#include "sls/string.h" #include "sls/string.h"
@ -22,6 +24,7 @@ const char *TOKEN_TYPES_NAMES[] = {
"Integer", "Integer",
"Float", "Float",
"Double", "Double",
"Character",
"String", "String",
"Boolean", "Boolean",
"Array", "Array",
@ -29,6 +32,8 @@ const char *TOKEN_TYPES_NAMES[] = {
"Type Tuple", "Type Tuple",
}; };
const size_t TOKEN_TYPE_COUNT = sizeof(TOKEN_TYPES_NAMES) / sizeof(*TOKEN_TYPES_NAMES);
const char *ARRAY_TYPES_NAMES[] = { const char *ARRAY_TYPES_NAMES[] = {
"Identifier", "Identifier",
"i64", "i64",
@ -41,11 +46,14 @@ const char *ARRAY_TYPES_NAMES[] = {
"u8", "u8",
"Float", "Float",
"Double", "Double",
"Character",
"String", "String",
"Boolean", "Boolean",
"Inline Struct", "Inline Struct",
}; };
const size_t ARRAY_TYPE_COUNT = sizeof(ARRAY_TYPES_NAMES) / sizeof(*ARRAY_TYPES_NAMES);
const char *INTEGER_TYPES_NAMES[] = { const char *INTEGER_TYPES_NAMES[] = {
"i64", "i64",
"i32", "i32",
@ -57,7 +65,9 @@ const char *INTEGER_TYPES_NAMES[] = {
"u8", "u8",
}; };
void init_lexer(LexerInfo *lexer_info, const char *filename, const char *source_code) { const size_t INTEGER_TYPE_COUNT = sizeof(INTEGER_TYPES_NAMES) / sizeof(*INTEGER_TYPES_NAMES);
void init_lexer(LexerInfo *lexer_info, SlsStr filename, SlsStr source_code) {
// Initializes a LexerInfo struct with file info and source code // Initializes a LexerInfo struct with file info and source code
lexer_info->filename = filename; lexer_info->filename = filename;
lexer_info->source_code = source_code; lexer_info->source_code = source_code;
@ -79,22 +89,27 @@ static FileInfo get_file_info(LexerInfo *lexer_info, size_t start, size_t start_
static const char *get_token_text(LexerInfo *lexer_info, size_t start) { static const char *get_token_text(LexerInfo *lexer_info, size_t start) {
// Returns the current character from the source code // Returns the current character from the source code
return lexer_info->source_code + start; return lexer_info->source_code.str + start;
} }
static char peek(LexerInfo *lexer_info) { static char peek(LexerInfo *lexer_info) {
// Returns the current character from the source code // Returns the current character from the source code
return lexer_info->source_code[lexer_info->pos]; return lexer_info->source_code.str[lexer_info->pos];
} }
static char far_peek(LexerInfo *lexer_info, size_t index) { static char far_peek(LexerInfo *lexer_info, size_t index) {
// Returns the character index away from the current char in the source code // Returns the character index away from the current char in the source code
return lexer_info->source_code[lexer_info->pos + index]; return lexer_info->source_code.str[lexer_info->pos + index];
}
static char seek(LexerInfo *lexer_info, size_t index) {
// Returns the character from the given index from the source code
return lexer_info->source_code.str[index];
} }
static char advance(LexerInfo *lexer_info) { static char advance(LexerInfo *lexer_info) {
// Advances lexer_info to the next character // Advances lexer_info to the next character
if (lexer_info->source_code[lexer_info->pos] == '\n') { if (lexer_info->source_code.str[lexer_info->pos] == '\n') {
// If a new line is encountered, advance line and reset column // If a new line is encountered, advance line and reset column
lexer_info->line++; lexer_info->line++;
lexer_info->column = 1; lexer_info->column = 1;
@ -103,14 +118,14 @@ static char advance(LexerInfo *lexer_info) {
lexer_info->column++; lexer_info->column++;
} }
// Advance to and return the next character // Advance to and return the next character
return lexer_info->source_code[++lexer_info->pos]; return lexer_info->source_code.str[++lexer_info->pos];
} }
static LexerResult lexer_result(LexerInfo *lexer_info, Token token, size_t start, size_t start_line) { static LexerResult lexer_result(LexerInfo *lexer_info, Token token, size_t start, size_t start_line) {
// Create a LexerTokenResult to store the results of lexing the current token // Create a LexerTokenResult to store the results of lexing the current token
LexerTokenResult *result = (LexerTokenResult *)malloc(sizeof(LexerTokenResult)); LexerTokenResult *result = (LexerTokenResult *)malloc(sizeof(LexerTokenResult));
if (result == NULL) if (result == NULL)
return (LexerResult){SLS_ERROR, .error = (SlsError){"Failed to allocate memory.", 1}}; return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Failed to allocate memory."), 1}};
result->type = SLS_RESULT; result->type = SLS_RESULT;
result->result = token; result->result = token;
result->file_info = get_file_info(lexer_info, start, start_line); result->file_info = get_file_info(lexer_info, start, start_line);
@ -118,11 +133,11 @@ static LexerResult lexer_result(LexerInfo *lexer_info, Token token, size_t start
return (LexerResult){SLS_RESULT, .result = result}; return (LexerResult){SLS_RESULT, .result = result};
} }
static LexerResult lexer_error(LexerInfo *lexer_info, const char* message, size_t start, size_t start_line) { static LexerResult lexer_error(LexerInfo *lexer_info, SlsStr message, size_t start, size_t start_line) {
// Create a LexerTokenResult to store an error from lexing the current token // Create a LexerTokenResult to store an error from lexing the current token
LexerTokenResult *result = (LexerTokenResult *)malloc(sizeof(LexerTokenResult)); LexerTokenResult *result = (LexerTokenResult *)malloc(sizeof(LexerTokenResult));
if (result == NULL) if (result == NULL)
return (LexerResult){SLS_ERROR, .error = (SlsError){"Failed to allocate memory.", 1}}; return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Failed to allocate memory."), 1}};
result->type = SLS_ERROR; result->type = SLS_ERROR;
result->error.message = message; result->error.message = message;
result->error.code = 1; result->error.code = 1;
@ -190,7 +205,7 @@ static IntegerTypeResult get_integer_type(NumericTypes numeric_type) {
integer_type = INTEGER_U8; integer_type = INTEGER_U8;
break; break;
default: default:
return (IntegerTypeResult){SLS_ERROR, .error = (SlsError){.message = "Lexer Error: Encountered a Float where there should not be one.", .code = 1}}; return (IntegerTypeResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer Error: Encountered a Float where there should not be one."), 1}};
} }
return (IntegerTypeResult){SLS_RESULT, .integer_type = integer_type}; return (IntegerTypeResult){SLS_RESULT, .integer_type = integer_type};
} }
@ -212,7 +227,8 @@ static uint64_t create_binary_integer(LexerInfo *lexer_info, size_t start) {
case '1': value += 1; break; case '1': value += 1; break;
} }
} }
return value * (negative ? -1 : 1); if (negative) value = (~value) + 1;
return value;
} }
static uint64_t create_octal_integer(LexerInfo *lexer_info, size_t start) { static uint64_t create_octal_integer(LexerInfo *lexer_info, size_t start) {
@ -238,7 +254,8 @@ static uint64_t create_octal_integer(LexerInfo *lexer_info, size_t start) {
case '7': value += 7; break; case '7': value += 7; break;
} }
} }
return value * (negative ? -1 : 1); if (negative) value = (~value) + 1;
return value;
} }
static uint64_t create_decimal_integer(LexerInfo *lexer_info, size_t start) { static uint64_t create_decimal_integer(LexerInfo *lexer_info, size_t start) {
@ -252,7 +269,7 @@ static uint64_t create_decimal_integer(LexerInfo *lexer_info, size_t start) {
} }
for (; i < lexer_info->pos - start; i++) { for (; i < lexer_info->pos - start; i++) {
if (isspace(token[i]) || token[i] == '/' || token[i] == '\0' || token[i] == ':') break; if (isspace(token[i]) || token[i] == '/' || token[i] == '\0' || token[i] == ':') break;
if (token[i] == '.' || token[i] == '_') continue; if (token[i] == '_') continue;
value *= 10; value *= 10;
switch (token[i]) { switch (token[i]) {
case '1': value += 1; break; case '1': value += 1; break;
@ -266,7 +283,8 @@ static uint64_t create_decimal_integer(LexerInfo *lexer_info, size_t start) {
case '9': value += 9; break; case '9': value += 9; break;
} }
} }
return value * (negative ? -1 : 1); if (negative) value = (~value) + 1;
return value;
} }
static uint64_t create_hexadecimal_integer(LexerInfo *lexer_info, size_t start) { static uint64_t create_hexadecimal_integer(LexerInfo *lexer_info, size_t start) {
@ -306,7 +324,99 @@ static uint64_t create_hexadecimal_integer(LexerInfo *lexer_info, size_t start)
case 'f': value += 15; break; case 'f': value += 15; break;
} }
} }
return value * (negative ? -1 : 1); if (negative) value = (~value) + 1;
return value;
}
static LexerResult create_integer_token(LexerInfo *lexer_info, IntegerBuiltInType type, uint64_t value, size_t start, size_t start_line) {
switch (type) {
case INTEGER_I64: break;
case INTEGER_I32:
if ((int64_t)value < INT32_MIN || (int64_t)value > INT32_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i32."), start, start_line);
}
break;
case INTEGER_I16:
if ((int64_t)value < INT16_MIN || (int64_t)value > INT16_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i16."), start, start_line);
}
break;
case INTEGER_I8:
if ((int64_t)value < INT8_MIN || (int64_t)value > INT8_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i8."), start, start_line);
}
break;
case INTEGER_U64:
if (seek(lexer_info, start) == '-') {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u64."), start, start_line);
}
break;
case INTEGER_U32:
if (seek(lexer_info, start) == '-') {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u32."), start, start_line);
} else if (value > (uint64_t)UINT32_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u32."), start, start_line);
}
break;
case INTEGER_U16:
if (seek(lexer_info, start) == '-') {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u16."), start, start_line);
} else if (value > (uint64_t)UINT16_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u16."), start, start_line);
}
break;
case INTEGER_U8:
if (seek(lexer_info, start) == '-') {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u8."), start, start_line);
} else if (value > (uint64_t)UINT8_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u8."), start, start_line);
}
break;
}
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = type, .value = value}}, start, start_line);
}
static double create_float(LexerInfo *lexer_info, size_t start) {
double value = 0;
Boolean negative = FALSE;
uint64_t fractional = 0;
const char *token = get_token_text(lexer_info, start);
size_t i = 0;
if (token[0] == '-') {
negative = TRUE;
i += 1;
}
for (; i < lexer_info->pos - start; i++) {
if (isspace(token[i]) || token[i] == '/' || token[i] == '\0' || token[i] == ':') break;
if (token[i] == '_') continue;
if (token[i] == '.') {
fractional = 1;
continue;
}
if (fractional == 0) value *= 10;
else fractional *= 10;
switch (token[i]) {
case '1': value += 1.0 / (fractional == 0 ? 1 : fractional); break;
case '2': value += 2.0 / (fractional == 0 ? 1 : fractional); break;
case '3': value += 3.0 / (fractional == 0 ? 1 : fractional); break;
case '4': value += 4.0 / (fractional == 0 ? 1 : fractional); break;
case '5': value += 5.0 / (fractional == 0 ? 1 : fractional); break;
case '6': value += 6.0 / (fractional == 0 ? 1 : fractional); break;
case '7': value += 7.0 / (fractional == 0 ? 1 : fractional); break;
case '8': value += 8.0 / (fractional == 0 ? 1 : fractional); break;
case '9': value += 9.0 / (fractional == 0 ? 1 : fractional); break;
}
}
if (negative) value = -value;
return value;
}
static LexerResult create_float_token(LexerInfo *lexer_info, NumericTypes type, size_t start, size_t start_line) {
double value = create_float(lexer_info, start);
if (type == NUMERIC_F64)
return lexer_result(lexer_info, (Token){TOKEN_DOUBLE, .double_literal = value}, start, start_line);
else
return lexer_result(lexer_info, (Token){TOKEN_FLOAT, .float_literal = (float){value}}, start, start_line);
} }
typedef enum { typedef enum {
@ -334,16 +444,14 @@ static LexerResult parse_numeric_type(LexerInfo *lexer_info, char c, size_t star
c = advance(lexer_info); c = advance(lexer_info);
c = advance(lexer_info); c = advance(lexer_info);
} else { } else {
char *error_message = (char *)malloc(sizeof(char) * 52); return lexer_error(lexer_info, SLS_STR("Invalid float type: must be of type 'f64' or 'f32'."), start, start_line);
strncpy(error_message, "Invalid float type: must be of type 'f64' or 'f32'.", 52);
return lexer_error(lexer_info, error_message, start, start_line);
} }
} else { } else {
char *error_message = (char *)malloc(sizeof(char) * 49); return lexer_error(lexer_info, SLS_STR("Invalid numeric literal: float type not allowed."), start, start_line);
strncpy(error_message, "Invalid numeric literal: float type not allowed.", 49);
return lexer_error(lexer_info, error_message, start, start_line);
} }
} else if (c == 'i' || c == 'u') { } else if (c == 'i' || c == 'u') {
if (numeric_literal_type == NUMERIC_FLOAT || numeric_literal_type == NUMERIC_EXPONENTIAL)
return lexer_error(lexer_info, SLS_STR("Invalid float type: must be of type 'f64' or 'f32'."), start, start_line);
if (c == 'u') numeric_type |= NUMERIC_UNSIGNED; if (c == 'u') numeric_type |= NUMERIC_UNSIGNED;
c = advance(lexer_info); c = advance(lexer_info);
if (c == '6' && far_peek(lexer_info, 1) == '4') { if (c == '6' && far_peek(lexer_info, 1) == '4') {
@ -363,24 +471,16 @@ static LexerResult parse_numeric_type(LexerInfo *lexer_info, char c, size_t star
c = advance(lexer_info); c = advance(lexer_info);
} else { } else {
if (numeric_type & NUMERIC_UNSIGNED) { if (numeric_type & NUMERIC_UNSIGNED) {
char *error_message = (char *)malloc(sizeof(char) * 78); return lexer_error(lexer_info, SLS_STR("Invalid unsigned integer type: must be of type 'u64', 'u32', 'u16', and 'u8'."), start, start_line);
strncpy(error_message, "Invalid unsigned integer type: must be of type 'u64', 'u32', 'u16', and 'u8'.", 78);
return lexer_error(lexer_info, error_message, start, start_line);
} else { } else {
char *error_message = (char *)malloc(sizeof(char) * 76); return lexer_error(lexer_info, SLS_STR("Invalid signed integer type: must be of type 'i64', 'i32', 'i16', and 'i8'."), start, start_line);
strncpy(error_message, "Invalid signed integer type: must be of type 'i64', 'i32', 'i16', and 'i8'.", 76);
return lexer_error(lexer_info, error_message, start, start_line);
} }
} }
} else { } else {
if (numeric_literal_type == NUMERIC_DECIMAL || numeric_literal_type == NUMERIC_FLOAT || numeric_literal_type == NUMERIC_EXPONENTIAL) { if (numeric_literal_type == NUMERIC_DECIMAL || numeric_literal_type == NUMERIC_FLOAT || numeric_literal_type == NUMERIC_EXPONENTIAL) {
char *error_message = (char *)malloc(sizeof(char) * 61); return lexer_error(lexer_info, SLS_STR("Invalid numeric type: type must start with 'f', 'i', or 'u'."), start, start_line);
strncpy(error_message, "Invalid numeric type: type must start with 'f', 'i', or 'u'.", 61);
return lexer_error(lexer_info, error_message, start, start_line);
} else { } else {
char *error_message = (char *)malloc(sizeof(char) * 55); return lexer_error(lexer_info, SLS_STR("Invalid integer type: type must start with 'i' or 'u'."), start, start_line);
strncpy(error_message, "Invalid integer type: type must start with 'i' or 'u'.", 55);
return lexer_error(lexer_info, error_message, start, start_line);
} }
} if (isspace(c) || c == '/' || c == '\0') { } if (isspace(c) || c == '/' || c == '\0') {
IntegerTypeResult integer_type = get_integer_type(numeric_type); IntegerTypeResult integer_type = get_integer_type(numeric_type);
@ -391,142 +491,369 @@ static LexerResult parse_numeric_type(LexerInfo *lexer_info, char c, size_t star
case NUMERIC_BINARY: case NUMERIC_BINARY:
if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error}; if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error};
value = create_binary_integer(lexer_info, start); value = create_binary_integer(lexer_info, start);
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = integer_type.integer_type, .value = value}}, start, start_line); return create_integer_token(lexer_info, integer_type.integer_type, value, start, start_line);
case NUMERIC_OCTAL: case NUMERIC_OCTAL:
if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error}; if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error};
value = create_octal_integer(lexer_info, start); value = create_octal_integer(lexer_info, start);
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = integer_type.integer_type, .value = value}}, start, start_line); return create_integer_token(lexer_info, integer_type.integer_type, value, start, start_line);
case NUMERIC_DECIMAL: case NUMERIC_DECIMAL:
if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error}; if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error};
value = create_decimal_integer(lexer_info, start); value = create_decimal_integer(lexer_info, start);
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = integer_type.integer_type, .value = value}}, start, start_line); return create_integer_token(lexer_info, integer_type.integer_type, value, start, start_line);
case NUMERIC_HEXADECIMAL: case NUMERIC_HEXADECIMAL:
if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error}; if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error};
value = create_hexadecimal_integer(lexer_info, start); value = create_hexadecimal_integer(lexer_info, start);
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = integer_type.integer_type, .value = value}}, start, start_line); return create_integer_token(lexer_info, integer_type.integer_type, value, start, start_line);
case NUMERIC_FLOAT: case NUMERIC_FLOAT:
break; return create_float_token(lexer_info, numeric_type, start, start_line);
case NUMERIC_EXPONENTIAL: case NUMERIC_EXPONENTIAL:
break; break;
} }
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Numeric Literal Not Implemented Error.", 1}}; return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Numeric Literal Not Implemented Error."), 1}};
} }
char *error_message = (char *)malloc(sizeof(char) * 57); SlsStr error_msg = sls_format(SLS_STR("Invalid numeric literal: unexpected '%c' in numeric type."), c);
snprintf(error_message, 57, "Invalid numeric literal: unexpected '%c' in numeric type.", c); if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_message, start, start_line); return lexer_error(lexer_info, error_msg, start, start_line);
} }
static LexerResult parse_binary_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_binary_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
do {c = advance(lexer_info);} while (c == '0' || c == '1' || c == '_'); while (c == '0' || c == '1' || c == '_') c = advance(lexer_info);
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_BINARY); if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_BINARY);
if (isspace(c) || c == '/' || c == '\0') { if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_binary_integer(lexer_info, start); uint64_t value = create_binary_integer(lexer_info, start);
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = INTEGER_I64, .value = value}}, start, start_line); return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
} }
char *error_message = (char *)malloc(sizeof(char) * 58); SlsStr error_msg = sls_format(SLS_STR("Invalid binary literal: unexpected '%c' in binary integer."), c);
snprintf(error_message, 58, "Invalid binary literal: unexpected '%c' in binary integer.", c); if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_message, start, start_line); return lexer_error(lexer_info, error_msg, start, start_line);
} }
static LexerResult parse_octal_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_octal_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
do {c = advance(lexer_info);} while ((isdigit(c) || c == '_') && !(c == '8' || c == '9')); while ((isdigit(c) || c == '_') && !(c == '8' || c == '9')) c = advance(lexer_info);
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_OCTAL); if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_OCTAL);
if (isspace(c) || c == '/' || c == '\0') { if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_octal_integer(lexer_info, start); uint64_t value = create_octal_integer(lexer_info, start);
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = INTEGER_I64, .value = value}}, start, start_line); return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
} }
char *error_message = (char *)malloc(sizeof(char) * 56); SlsStr error_msg = sls_format(SLS_STR("Invalid octal literal: unexpected '%c' in octal integer."), c);
snprintf(error_message, 56, "Invalid octal literal: unexpected '%c' in octal integer.", c); if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_message, start, start_line); return lexer_error(lexer_info, error_msg, start, start_line);
} }
static LexerResult parse_exponential(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_exponential(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
(void)lexer_info; (void)c; (void)start; (void)start_line; (void)lexer_info; (void)c; (void)start; (void)start_line;
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Float Exponential Not Implemented Error.", 1}}; return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Float Exponential Not Implemented Error."), 1}};
} }
static LexerResult parse_float(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_float(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
(void)lexer_info; (void)c; (void)start; (void)start_line; while (isdigit(c) || c == '_') c = advance(lexer_info);
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Float Not Implemented Error.", 1}}; if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line);
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_FLOAT);
if (isspace(c) || c == '/' || c == '\0')
return create_float_token(lexer_info, SLS_FLOAT_TYPE_DEFAULT, start, start_line);
SlsStr error_msg = sls_format(SLS_STR("Invalid float literal: unexpected '%c' in float."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_msg, start, start_line);
} }
static LexerResult parse_decimal_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_decimal_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
do {c = advance(lexer_info);} while (isdigit(c) || c == '_'); while (isdigit(c) || c == '_') c = advance(lexer_info);
if (c == '.') return parse_float(lexer_info, c, start, start_line); if (c == '.') {
if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line); c = advance(lexer_info);
return parse_float(lexer_info, c, start, start_line);
} if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line);
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_DECIMAL); if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_DECIMAL);
if (isspace(c) || c == '/' || c == '\0') { if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_decimal_integer(lexer_info, start); uint64_t value = create_decimal_integer(lexer_info, start);
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = INTEGER_I64, .value = value}}, start, start_line); return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
} }
char *error_message = (char *)malloc(sizeof(char) * 60); SlsStr error_msg = sls_format(SLS_STR("Invalid decimal literal: unexpected '%c' in decimal integer."), c);
snprintf(error_message, 60, "Invalid decimal literal: unexpected '%c' in decimal integer.", c); if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_message, start, start_line); return lexer_error(lexer_info, error_msg, start, start_line);
} }
static LexerResult parse_hexadecimal_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_hexadecimal_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
do {c = advance(lexer_info);} while (isxdigit(c) || c == '_'); while (isxdigit(c) || c == '_') c = advance(lexer_info);
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_HEXADECIMAL); if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_HEXADECIMAL);
if (isspace(c) || c == '/' || c == '\0') { if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_hexadecimal_integer(lexer_info, start); uint64_t value = create_hexadecimal_integer(lexer_info, start);
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = INTEGER_I64, .value = value}}, start, start_line); return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
} }
char *error_message = (char *)malloc(sizeof(char) * 68); SlsStr error_msg = sls_format(SLS_STR("Invalid hexadecimal literal: unexpected '%c' in hexadecimal integer."), c);
snprintf(error_message, 68, "Invalid hexadecimal literal: unexpected '%c' in hexadecimal integer.", c); if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_message, start, start_line); return lexer_error(lexer_info, error_msg, start, start_line);
} }
static LexerResult parse_numeric_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_numeric_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
if (c == '-') c = advance(lexer_info); if (c == '-') c = advance(lexer_info);
if (c == '0') { if (c == '0') {
c = advance(lexer_info); c = advance(lexer_info);
if (c == 'b' || c == 'B') return parse_binary_integer(lexer_info, c, start, start_line); if (c == 'b' || c == 'B') {
else if (c == 'o' || c == 'O') return parse_octal_integer(lexer_info, c, start, start_line); c = advance(lexer_info);
else if (c == 'x' || c == 'X') return parse_hexadecimal_integer(lexer_info, c, start, start_line); return parse_binary_integer(lexer_info, c, start, start_line);
} else if (c == 'o' || c == 'O') {
c = advance(lexer_info);
return parse_octal_integer(lexer_info, c, start, start_line);
} else if (c == 'x' || c == 'X') {
c = advance(lexer_info);
return parse_hexadecimal_integer(lexer_info, c, start, start_line);
}
} }
return parse_decimal_integer(lexer_info, c, start, start_line); return parse_decimal_integer(lexer_info, c, start, start_line);
} }
static LexerResult parse_character_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_character_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
(void)lexer_info; (void)c; (void)start; (void)start_line; if (c == '\'')
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Character Literals Not Implemented Error.", 1}}; return lexer_error(lexer_info, SLS_STR("Invalid character literal: empty character literal."), start, start_line);
char value = '\0';
if (c == '\\') {
c = advance(lexer_info);
switch (c) {
case 'n':
value = '\n';
break;
case 'r':
value = '\r';
break;
case 't':
value = '\t';
break;
case '\\':
value = '\\';
break;
case '\'':
value = '\'';
break;
case '0':
value = '\0';
break;
default:
SlsStr error_msg = sls_format(SLS_STR("Invalid character literal: unknown escape sequence '\\%c'."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_msg, start, start_line);
}
} else if (c == '\n' || c == '\r')
return lexer_error(lexer_info, SLS_STR("Invalid character literal: unclosed character literal."), start, start_line);
else value = c;
c = advance(lexer_info);
if (isspace(c) || c == '/' || c == '\0')
return lexer_error(lexer_info, SLS_STR("Invalid character literal: unclosed character literal."), start, start_line);
else if (c != '\'') {
SlsStr error_msg = sls_format(SLS_STR("Invalid character literal: unexpected '%c' in character."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_msg, start, start_line);
}
advance(lexer_info);
return lexer_result(lexer_info, (Token){TOKEN_CHARACTER, .character_literal = (uint8_t){value}}, start, start_line);
} }
static LexerResult parse_string_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_string_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
(void)lexer_info; (void)c; (void)start; (void)start_line; (void)lexer_info; (void)c; (void)start; (void)start_line;
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: String Literals Not Implemented Error.", 1}}; return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: String Literals Not Implemented Error."), 1}};
}
static void skip_comments_and_whitespace(LexerInfo *lexer_info) {
while (isspace(peek(lexer_info)) || (peek(lexer_info) == '/' && far_peek(lexer_info, 1) == '/') || peek(lexer_info) == '#') {
// Skip Comments
if ((peek(lexer_info) == '/' && far_peek(lexer_info, 1) == '/') || peek(lexer_info) == '#')
while (!(peek(lexer_info) == '\n' || peek(lexer_info) == '\0'))
advance(lexer_info);
// Skip whitespace
while (isspace(peek(lexer_info))) advance(lexer_info);
}
}
static LexerResult lexer_next(LexerInfo *lexer_info);
TokenString copy_token_string(TokenString token_string) {
TokenString new_string = (TokenString){
.length = token_string.length,
.tokens = (Token *)malloc(sizeof(Token) * token_string.length)
};
for (size_t i = 0; i < token_string.length; i++) {
if (token_string.tokens[i].type == TOKEN_STRING) {
new_string.tokens[i].type = TOKEN_STRING;
new_string.tokens[i].string_literal = sls_str_cpy(token_string.tokens[i].string_literal);
} else if (token_string.tokens[i].type == TOKEN_TOKEN_STRING) {
new_string.tokens[i].type = TOKEN_TOKEN_STRING;
new_string.tokens[i].token_string = copy_token_string(token_string.tokens[i].token_string);
} else {
new_string.tokens[i] = token_string.tokens[i];
}
}
return new_string;
}
static LexerResult convert_to_token_string(LexerInfo *lexer_info, LexerTokenResult *head, size_t start, size_t start_line) {
TokenString token_string = {
.length = 0,
.tokens = NULL
};
LexerTokenResult *current = head;
while (current != NULL) {
token_string.length += 1;
current = current->next;
}
current = head;
token_string.tokens = (Token *)malloc(sizeof(Token) * token_string.length);
for (size_t i = 0; i < token_string.length; i++) {
if (current->result.type == TOKEN_STRING) {
token_string.tokens[i].type = TOKEN_STRING;
token_string.tokens[i].string_literal = sls_str_cpy(current->result.string_literal);
} else if (current->result.type == TOKEN_TOKEN_STRING) {
token_string.tokens[i].type = TOKEN_TOKEN_STRING;
token_string.tokens[i].token_string = copy_token_string(current->result.token_string);
memcpy(token_string.tokens[i].token_string.tokens, current->result.token_string.tokens, current->result.token_string.length);
} else token_string.tokens[i] = current->result;
current = current->next;
}
clean_token_result(head);
return lexer_result(lexer_info, (Token){TOKEN_TOKEN_STRING, .token_string = token_string}, start, start_line);
} }
static LexerResult parse_token_string(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_token_string(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
(void)lexer_info; (void)c; (void)start; (void)start_line; // Lexes a token string
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Token Strings Not Implemented Error.", 1}}; LexerResult result; // For lexer_next returns
LexerTokenResult *head = 0;
LexerTokenResult *current = 0;
size_t watchdog = 0;
c = advance(lexer_info);
while (c != '\0') {
skip_comments_and_whitespace(lexer_info);
c = peek(lexer_info);
// Stop at the end of the token string
if (c == '}') {
advance(lexer_info);
return convert_to_token_string(lexer_info, head, start, start_line);
}
// Get next token
result = lexer_next(lexer_info);
// Handle Errors
if (result.type == SLS_ERROR) {
clean_token_result(head);
return result;
}
// Save result
if (head == 0) {
head = result.result;
current = head;
} else {
current->next = result.result;
current = current->next;
}
// Current should not be null_ptr
if (current == 0) {
clean_token_result(head);
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Unknown Error."), 1}};
}
if (current->type == SLS_ERROR) {
LexerTokenResult *e = (LexerTokenResult *)malloc(sizeof(LexerTokenResult));
*e = (LexerTokenResult){
.type = SLS_ERROR,
.error = (SlsError){sls_str_cpy(current->error.message), 1},
.file_info = current->file_info,
.next = NULL
};
clean_token_result(head);
return (LexerResult){SLS_RESULT, .result = e};
}
if (current->result.type == TOKEN_EOF) break;
c = peek(lexer_info);
if (watchdog++ > 1000000) {
clean_token_result(head);
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Watchdog Triggered in Token String."), 1}};
}
}
clean_token_result(head);
return lexer_error(lexer_info, SLS_STR("Unclosed token string: missing closing brace '}'."), start, start_line);
} }
static LexerResult parse_array_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_array_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
(void)lexer_info; (void)c; (void)start; (void)start_line; (void)lexer_info; (void)c; (void)start; (void)start_line;
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Array Literals Not Implemented Error.", 1}}; return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Array Literals Not Implemented Error."), 1}};
} }
static LexerResult parse_type_tuples(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_type_tuples(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
(void)lexer_info; (void)c; (void)start; (void)start_line; (void)lexer_info; (void)c; (void)start; (void)start_line;
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Type Tuples Not Implemented Error.", 1}}; return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Type Tuples Not Implemented Error."), 1}};
}
Boolean is_identifier_continue(LexerInfo *lexer_info, char c) {
// If the current character and its context are a valid identifier character
if (!isprint(c)) return FALSE;
if (c == '/' && far_peek(lexer_info, 1) == '/') return FALSE;
if (c == '{' || c == '}') return FALSE;
if (c == '[' || c == ']') return FALSE;
if (c == '(' || c == ')') return FALSE;
if (c == '\'' || c == '"' || c == '#') return FALSE;
if (isspace(c) || c == '\0') return FALSE;
return TRUE;
}
Boolean is_identifier_start(LexerInfo *lexer_info) {
// If the current character and its context are a valid identifier start
char c = peek(lexer_info);
if (c == ':' && far_peek(lexer_info, 1) == ':') c = far_peek(lexer_info, 2);
if ((!isdigit(c)) && is_identifier_continue(lexer_info, c)) return TRUE;
else return FALSE;
} }
static LexerResult parse_identifiers_and_booleans(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_identifiers_and_booleans(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
(void)lexer_info; (void)c; (void)start; (void)start_line; // Parses identifier, identifier literals, and boolean tokens
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Identifiers and Booleans Not Implemented Error.", 1}};
Boolean literal = FALSE;
// Skip leading `::` for identifier literals
if (c == ':' && far_peek(lexer_info, 1) == ':') {
literal = TRUE;
c = advance(lexer_info);
c = advance(lexer_info);
}
// Read the name of the identifier
size_t length = 0;
while (is_identifier_continue(lexer_info, c)) {
if (c == ':') // && !literal)
return lexer_error(lexer_info, SLS_STR("Invalid identifier: ':' is not allowed in identifiers."), start, start_line);
if (c == '.') // && !literal)
return lexer_error(lexer_info, SLS_STR("Invalid identifier: '.' is not allowed in identifiers."), start, start_line);
c = advance(lexer_info);
length++;
}
char *name_value = (char *)calloc(length+1, sizeof(char));
for (size_t i = 0; i < length; i++)
name_value[i] = lexer_info->source_code.str[start + i + (2 * literal)];
SlsStr name = sls_str_malloc(name_value, length);
free(name_value);
// Return as identifier or boolean tokens
if (sls_str_cmp(name, SLS_STR("false")) == 0)
return lexer_result(lexer_info, (Token){TOKEN_BOOLEAN, .boolean_literal = FALSE}, start, start_line);
else if (sls_str_cmp(name, SLS_STR("true")) == 0)
return lexer_result(lexer_info, (Token){TOKEN_BOOLEAN, .boolean_literal = TRUE}, start, start_line);
else
return lexer_result(lexer_info, (Token){TOKEN_IDENTIFIER, .identifier = (Identifier){.is_literal = literal, .name = name}}, start, start_line);
} }
static LexerResult lexer_next(LexerInfo *lexer_info) { static LexerResult lexer_next(LexerInfo *lexer_info) {
// Gets the next token from the source // Gets the next token from the source
while (isspace(peek(lexer_info)) || peek(lexer_info) == '/' || peek(lexer_info) == '#') { skip_comments_and_whitespace(lexer_info);
// Skip Comments
if ((peek(lexer_info) == '/' && far_peek(lexer_info, 1) == '/') || peek(lexer_info) == '#')
while (peek(lexer_info) != '\n') advance(lexer_info);
// Skip whitespace
while (isspace(peek(lexer_info))) advance(lexer_info);
}
// Initialize begining variables // Initialize begining variables
char c = peek(lexer_info); char c = peek(lexer_info);
@ -536,32 +863,75 @@ static LexerResult lexer_next(LexerInfo *lexer_info) {
// End of file tokens // End of file tokens
if (c == '\0') return lexer_result(lexer_info, (Token){.type = TOKEN_EOF}, start, start_line); if (c == '\0') return lexer_result(lexer_info, (Token){.type = TOKEN_EOF}, start, start_line);
// Integers and Floats // Integers and Floats
if (isdigit(c) || c == '.' || (c == '-' && isdigit(far_peek(lexer_info, 1)))) return parse_numeric_literal(lexer_info, c, start, start_line); if (isdigit(c) || (c == '.' && isdigit(far_peek(lexer_info, 1))) || (c == '-' && isdigit(far_peek(lexer_info, 1))))
return parse_numeric_literal(lexer_info, c, start, start_line);
// Character Literals // Character Literals
if (c == '\'') return parse_character_literal(lexer_info, c, start, start_line); if (c == '\'') {
c = advance(lexer_info);
return parse_character_literal(lexer_info, c, start, start_line);
}
// String Literals // String Literals
if (c == '\"') return parse_string_literal(lexer_info, c, start, start_line); if (c == '\"') return parse_string_literal(lexer_info, c, start, start_line);
// Token Strings // Token Strings
if (c == '{') return parse_token_string(lexer_info, c, start, start_line); if (c == '{') return parse_token_string(lexer_info, c, start, start_line);
if (c == '}') {
advance(lexer_info);
return lexer_error(lexer_info, SLS_STR("Unexpected closing brace '}' without matching opening brace."), start, start_line);
}
// Array Literals // Array Literals
if (c == '[') return parse_array_literal(lexer_info, c, start, start_line); if (c == '[') return parse_array_literal(lexer_info, c, start, start_line);
if (c == ']') {
advance(lexer_info);
return lexer_error(lexer_info, SLS_STR("Unexpected closing bracket ']' without matching opening bracket."), start, start_line);
}
// Type Tuples // Type Tuples
if (c == '(') return parse_type_tuples(lexer_info, c, start, start_line); if (c == '(') return parse_type_tuples(lexer_info, c, start, start_line);
if (c == ')') {
advance(lexer_info);
return lexer_error(lexer_info, SLS_STR("Unexpected closing parentheses ')' without matching opening parentheses."), start, start_line);
}
// Identifiers and Booleans // Identifiers and Booleans
if (isascii(c)) return parse_identifiers_and_booleans(lexer_info, c, start, start_line); if (is_identifier_start(lexer_info))
// Lexing Error return parse_identifiers_and_booleans(lexer_info, c, start, start_line);
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Unknown Character Error.", 1}}; if (c == ':') {
advance(lexer_info);
if (far_peek(lexer_info, 1) == ':')
return lexer_error(lexer_info, SLS_STR("Invalid identifier literal: empty identifier after '::'."), start, start_line);
else
return lexer_error(lexer_info, SLS_STR("Unexpected single colon ':'."), start, start_line);
}
// Random Characters
SlsStr error_msg = sls_format(SLS_STR("Unexpected character: unexpected '%c' during parsing."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_msg, start, start_line);
}
void clean_token_string(TokenString token_string) {
if (token_string.tokens == NULL) return;
for (size_t i = 0; i < token_string.length; i++) {
if (token_string.tokens[i].type == TOKEN_STRING)
sls_str_free(&token_string.tokens[i].string_literal);
if (token_string.tokens[i].type == TOKEN_TOKEN_STRING)
clean_token_string(token_string.tokens[i].token_string);
}
free(token_string.tokens);
token_string.tokens = NULL;
} }
void clean_token_result(LexerTokenResult *head) { void clean_token_result(LexerTokenResult *head) {
// Deallocates a LexerTokenResult linked list // Deallocates a LexerTokenResult linked list
LexerTokenResult *next; if (head == NULL) return;
while (head) { if (head->type == SLS_ERROR) sls_str_free(&head->error.message);
next = head->next; else {
if (head->type == SLS_ERROR) free(head->error.message); if (head->result.type == TOKEN_STRING)
if (head) free(head); sls_str_free(&head->result.string_literal);
head = next; if (head->result.type == TOKEN_TOKEN_STRING)
clean_token_string(head->result.token_string);
} }
clean_token_result(head->next);
head->next = NULL;
if (head) free(head);
} }
LexerTokenResult *get_token(LexerTokenResult *head, size_t i) { LexerTokenResult *get_token(LexerTokenResult *head, size_t i) {
@ -600,7 +970,7 @@ LexerResult lexical_analysis(LexerInfo *lexer_info) {
// Current should not be null_ptr // Current should not be null_ptr
if (current == 0) { if (current == 0) {
clean_token_result(head); clean_token_result(head);
return (LexerResult){SLS_ERROR, .error = (SlsError){"Unknown Error.", 1}}; return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Unknown Error."), 1}};
} }
} while (current->type != SLS_ERROR && current->result.type != TOKEN_EOF); } while (current->type != SLS_ERROR && current->result.type != TOKEN_EOF);

View File

@ -4,8 +4,37 @@
// October 2025 // October 2025
#include <stdio.h> #include <stdio.h>
#include <stddef.h>
#include <string.h>
int main(void) { #include "sls/meta.h"
printf("Hello, world!\n"); #include "sls/bool.h"
#include "sls/string.h"
#include "sls/repl.h"
#include "sls/file.h"
int main(int argc, char *argv[]) {
Boolean version = FALSE;
char *filename = NULL;
if (argc == 2) {
if (strcmp(argv[1], "--version") == 0) version = TRUE;
else if (strcmp(argv[1], "-v") == 0) version = TRUE;
else filename = argv[1];
} else if (argc > 2) {
printf("To many arguments!");
return 1;
}
if (version) {
print_version();
return 0; return 0;
} else if (filename != NULL) {
SlsStr sls_filename = sls_str_malloc(filename, strlen(filename));
int rv = file(sls_filename);
sls_str_free(&sls_filename);
return rv;
} else {
return repl();
}
} }

13
SLS_C/src/meta.c Normal file
View File

@ -0,0 +1,13 @@
// Kyler Olsen
// YREA SLS
// MEta File
// November 2025
#include <stdio.h>
#include "sls/meta.h"
void print_version() {
printf("YREA SLS (" SLS_NAME ") " SLS_VER " (" GIT_COMMIT_HASH ")\n");
printf("Compiled with " COMPILER_NAME " %d at " __DATE__ " " __TIME__ "\n", COMPILER_VER);
}

205
SLS_C/src/pico_main.c Normal file
View File

@ -0,0 +1,205 @@
// Kyler Olsen
// YREA SLS
// Pico Main File
// December 2025
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/stdio_usb.h"
#include "hardware/uart.h"
#include "hardware/gpio.h"
#include "sls/meta.h"
#include "sls/bool.h"
#include "sls/string.h"
#include "sls/errors.h"
#include "sls/lexer.h"
#include "sls/interpreter.h"
// Pico onboard LED
#define LED_PIN 25
static const SlsStr PICO_REPL_NAME = SLS_STR_CONST("<PICO-REPL>");
void print_top_of_stack(InterpreterState *interpreter_state) {
if (interpreter_state->stack == NULL) {
printf("#0: <STACK IS EMPTY>\n");
return;
}
switch (interpreter_state->stack->type) {
case STACK_IDENTIFIER:
printf("#0: ::%s\n", interpreter_state->stack->identifier.name.str);
break;
case STACK_I64:
printf("#0: %ld:i64\n", interpreter_state->stack->i64);
break;
case STACK_I32:
printf("#0: %d\n", interpreter_state->stack->i32);
break;
case STACK_I16:
printf("#0: %d:i16\n", interpreter_state->stack->i16);
break;
case STACK_I8:
printf("#0: %d:i8\n", interpreter_state->stack->i8);
break;
case STACK_U64:
printf("#0: %lu:u64\n", interpreter_state->stack->u64);
break;
case STACK_U32:
printf("#0: %u:u32\n", interpreter_state->stack->u32);
break;
case STACK_U16:
printf("#0: %u:u16\n", interpreter_state->stack->u16);
break;
case STACK_U8:
printf("#0: %u:u8\n", interpreter_state->stack->u8);
break;
case STACK_FLOAT:
printf("#0: %f:f32\n", interpreter_state->stack->f32);
break;
case STACK_DOUBLE:
printf("#0: %f\n", interpreter_state->stack->f64);
break;
case STACK_CHARACTER:
printf("#0: %c\n", interpreter_state->stack->character);
break;
case STACK_BOOLEAN:
if (interpreter_state->stack->boolean) printf("#0: TRUE\n");
else printf("#0: FALSE\n");
break;
case STACK_TOKEN_STRING:
printf("#0: <TOKEN STRING>\n");
break;
case STACK_CALLABLE:
printf("#0: <CALLABLE>\n");
break;
}
}
void led_blink(int count) {
for (int i = 0; i < count; i++) {
gpio_put(LED_PIN, 1);
sleep_ms(100);
gpio_put(LED_PIN, 0);
sleep_ms(100);
}
}
int pico_repl() {
// Initialize LED
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
// Blink to show we're ready
led_blink(3);
// Wait for a key press to start the repl
while (true) {
int c = getchar_timeout_us(100000);
if (c == PICO_ERROR_TIMEOUT) {
continue;
} else if (c == '\r' || c == '\n' || c == 8 || (c >= 32 && c < 128)) {
printf("\n");
break;
}
}
led_blink(1);
print_version();
printf("===== YREA SLS PICO REPL =====\n");
printf("Type `#exit` to exit.\n");
fflush(stdout);
LexerInfo lexer_info;
InterpreterState *interpreter_state = interpreter_create();
if (interpreter_state == NULL) {
printf("ERROR: Failed to create interpreter!\n");
return 1;
}
char buf[256];
int buf_pos = 0;
while (true) {
// Read character by character for better embedded behavior
int c = getchar_timeout_us(100000); // 100ms timeout
if (c == PICO_ERROR_TIMEOUT) {
continue;
}
if (c == '\r' || c == '\n') {
if (buf_pos == 0) continue; // Empty line
buf[buf_pos] = '\0';
printf("\n"); // Echo newline
if (strncmp(buf, "#exit", 5) == 0) {
printf("Exiting REPL...\n");
interpreter_delete(interpreter_state);
led_blink(2);
return 0;
}
SlsStr code = sls_str_malloc(buf, buf_pos);
init_lexer(&lexer_info, PICO_REPL_NAME, code);
LexerResult result = lexical_analysis(&lexer_info);
if (result.type == SLS_ERROR) {
printf("%s\n", result.error.message.str);
sls_str_free(&result.error.message);
} else {
LexerTokenResult *head = result.result;
while (head) {
if (head->type == SLS_ERROR) {
printf("%s\n", head->error.message.str);
break;
} else if (!execute(interpreter_state, head)) {
printf("A runtime error occurred!\n");
break;
}
head = head->next;
}
clean_token_result(result.result);
print_top_of_stack(interpreter_state);
}
sls_str_free(&code);
buf_pos = 0;
} else if (c == 127 || c == 8) {
// Backspace or DEL
if (buf_pos > 0) {
buf_pos--;
printf("\b \b"); // Erase character from terminal
fflush(stdout);
}
} else if (c >= 32 && c < 127 && buf_pos < 255) {
// Printable character
buf[buf_pos++] = (char)c;
putchar(c); // Echo character
fflush(stdout);
}
}
interpreter_delete(interpreter_state);
return 1;
}
int main() {
// Initialize USB/UART stdio
stdio_init_all();
// Wait a bit for USB to enumerate
sleep_ms(5000);
printf("\n\n=============================\n");
printf("YREA SLS on Raspberry Pi Pico\n");
printf("=============================\n\n");
printf("Press any key to begin...\n");
return pico_repl();
}

118
SLS_C/src/repl.c Normal file
View File

@ -0,0 +1,118 @@
// Kyler Olsen
// YREA SLS
// REPL
// November 2025
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "sls/repl.h"
#include "sls/meta.h"
#include "sls/errors.h"
#include "sls/lexer.h"
#include "sls/string.h"
#include "sls/interpreter.h"
static const SlsStr REPL_FILE_NAME = SLS_STR_CONST("<STDIN>");
void print_top_of_stack(InterpreterState *interpreter_state) {
if (interpreter_state->stack == NULL) {
printf("#0: <STACK IS EMPTY>\n");
return;
}
switch (interpreter_state->stack->type) {
case STACK_IDENTIFIER:
printf("#0: ::%s\n", interpreter_state->stack->identifier.name.str);
break;
case STACK_I64:
printf("#0: %ld\n", interpreter_state->stack->i64);
break;
case STACK_I32:
printf("#0: %d:i32\n", interpreter_state->stack->i32);
break;
case STACK_I16:
printf("#0: %d:i16\n", interpreter_state->stack->i16);
break;
case STACK_I8:
printf("#0: %d:8\n", interpreter_state->stack->i8);
break;
case STACK_U64:
printf("#0: %lu:u64\n", interpreter_state->stack->u64);
break;
case STACK_U32:
printf("#0: %u:32\n", interpreter_state->stack->u32);
break;
case STACK_U16:
printf("#0: %u:16\n", interpreter_state->stack->u16);
break;
case STACK_U8:
printf("#0: %u:8\n", interpreter_state->stack->u8);
break;
case STACK_FLOAT:
printf("#0: %f:f32\n", interpreter_state->stack->f32);
break;
case STACK_DOUBLE:
printf("#0: %f\n", interpreter_state->stack->f64);
break;
case STACK_CHARACTER:
printf("#0: %c\n", interpreter_state->stack->character);
break;
case STACK_BOOLEAN:
if (interpreter_state->stack->boolean) printf("#0: TRUE\n");
else printf("#0: FALSE\n");
break;
case STACK_TOKEN_STRING:
printf("#0: <TOKEN STRING>\n");
break;
case STACK_CALLABLE:
printf("#0: <CALLABLE>\n");
break;
}
}
int repl() {
print_version();
printf("===== YREA SLS REPL =====\n");
printf("Type `#exit` to exit.\n");
fflush(stdout);
LexerInfo lexer_info;
InterpreterState *interpreter_state = interpreter_create();
if (interpreter_state == NULL) return 1;
char buf[256];
while (fgets(buf, sizeof(buf), stdin)) {
size_t len = strlen(buf);
if (len && buf[len - 1] == '\n') buf[len - 1] = '\0';
if (strncmp(buf, "#exit", 5) == 0) {
interpreter_delete(interpreter_state);
return 0;
}
SlsStr code = sls_str_malloc(buf, sizeof(buf));
init_lexer(&lexer_info, REPL_FILE_NAME, code);
LexerResult result = lexical_analysis(&lexer_info);
if (result.type == SLS_ERROR) {
printf("%s\n", result.error.message.str);
sls_str_free(&result.error.message);
} else {
LexerTokenResult *head = result.result;
while (head) {
if (head->type == SLS_ERROR) {
printf("%s\n", head->error.message.str);
break;
} else if (!execute(interpreter_state, head)) {
printf("A runtime error occurred!\n");
break;
}
head = head->next;
}
clean_token_result(result.result);
print_top_of_stack(interpreter_state);
}
sls_str_free(&code);
}
interpreter_delete(interpreter_state);
return 1;
}

View File

@ -4,14 +4,328 @@
// November 2025 // November 2025
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <stdlib.h>
int isascii(unsigned char c) { #include "sls/string.h"
#include "sls/lexer.h"
int sls_isascii(unsigned char c) {
return c < 128; return c < 128;
} }
size_t strnlen(const char *s, size_t maxlen) { size_t sls_str_nlen(const char *s, size_t maxlen) {
size_t i; size_t i;
for (i = 0; i < maxlen; i++) for (i = 0; i < maxlen; i++)
if (s[i] == '\0') break; if (s[i] == '\0') break;
return i; return i;
} }
SlsStr sls_str_malloc(const char *s, size_t maxlen) {
size_t length = sls_str_nlen(s, maxlen);
char *new_str = (char *)malloc(sizeof(char) * (length + 1));
if (new_str == NULL) return SLS_STR_NULL;
memcpy(new_str, s, length);
new_str[length] = '\0';
return (SlsStr){length, new_str, TRUE};
}
SlsStr sls_str_new(size_t length) {
char *new_str = (char *)calloc(length + 1, sizeof(char));
if (new_str == NULL) return SLS_STR_NULL;
return (SlsStr){length, new_str, TRUE};
}
SlsStr sls_str_cpy(const SlsStr s) {
return sls_str_malloc(s.str, s.len);
}
int32_t sls_str_cmp(const SlsStr a, const SlsStr b) {
int cmp = strncmp(a.str, b.str, (a.len < b.len) ? a.len : b.len);
if (cmp != 0) return cmp;
return (a.len > b.len) - (a.len < b.len);
}
void sls_str_free(SlsStr *s) {
if (s->allocated) {
free((void *)s->str);
s->len = 0;
s->str = NULL;
s->allocated = FALSE;
}
}
typedef enum {
FORMAT_PERCENT_ESCAPE,
FORMAT_C_STRINGS,
FORMAT_CHARACTER,
FORMAT_INTEGER_32,
FORMAT_INTEGER_64,
FORMAT_UNSIGNED_INTEGER_64,
FORMAT_SIZE_INTEGER,
FORMAT_FLOAT,
FORMAT_SLS_STR,
FORMAT_SLS_TOKEN_TYPE,
FORMAT_SLS_ARRAY_TYPE,
FORMAT_SLS_BUILTIN_INTEGER,
FORMAT_SLS_ERROR,
FORMAT_SLS_BOOLEAN,
} FormatStringTypes;
typedef struct {
FormatStringTypes type;
union {
const char *c_string;
char character;
int32_t integer_32;
int64_t integer_64;
uint64_t unsigned_integer_64;
size_t size_integer;
double ffloat;
SlsStr sls_str;
TokenType token_type;
ArrayType array_type;
IntegerBuiltInType builtin_integer;
SlsError error;
Boolean boolean;
};
ptrdiff_t str_index;
size_t self_length;
} FormatStringItem;
static size_t number_length(int64_t i) {
if (i == 0) return 1;
size_t len = (i < 0 ? 1 : 0);
while (i) { len++; i /= 10; }
return len;
}
static size_t unsigned_number_length(uint64_t i) {
if (i == 0) return 1;
size_t len = 0;
while (i) { len++; i /= 10; }
return len;
}
SlsStr sls_format(const SlsStr s, ...) {
va_list args;
va_start(args, s);
size_t count = 0;
const char *current = strchr(s.str, '%');
do {
if (!current) break;
if (!current[1]) break;
switch (current[1]) {
case '%':
case 'y':
case 'c':
case 'd':
case 'l':
case 'u':
case 'z':
case 'f':
case 's':
case 't':
case 'a':
case 'i':
case 'e':
case 'b':
count++;
break;
}
current = strchr(current + 2, '%');
} while (current);
FormatStringItem *items = (FormatStringItem *)malloc(sizeof(FormatStringItem) * count);
if (items == NULL) return SLS_STR_NULL;
size_t i = 0;
const char *last_index = s.str;
size_t length = s.len;
current = strchr(s.str, '%');
do {
switch (current[1]) {
case '%':
items[i].type = FORMAT_PERCENT_ESCAPE;
length += items[i].self_length = 1;
length -= 2;
break;
case 'y':
items[i].type = FORMAT_C_STRINGS;
items[i].c_string = va_arg(args, const char *);
length += items[i].self_length = strlen(items[i].c_string);
length -= 2;
break;
case 'c':
items[i].type = FORMAT_CHARACTER;
items[i].character = va_arg(args, int);
length += items[i].self_length = 1;
length -= 2;
break;
case 'd':
items[i].type = FORMAT_INTEGER_32;
items[i].integer_32 = va_arg(args, int32_t);
length += items[i].self_length = number_length(items[i].integer_32);
length -= 2;
break;
case 'l':
items[i].type = FORMAT_INTEGER_64;
items[i].integer_64 = va_arg(args, int64_t);
length += items[i].self_length = number_length(items[i].integer_64);
length -= 2;
break;
case 'u':
items[i].type = FORMAT_UNSIGNED_INTEGER_64;
items[i].unsigned_integer_64 = va_arg(args, uint64_t);
length += items[i].self_length = unsigned_number_length(items[i].unsigned_integer_64);
length -= 2;
break;
case 'z':
items[i].type = FORMAT_SIZE_INTEGER;
items[i].size_integer = va_arg(args, size_t);
length += items[i].self_length = unsigned_number_length(items[i].size_integer);
length -= 2;
break;
case 'f':
items[i].type = FORMAT_FLOAT;
items[i].ffloat = va_arg(args, double);
length += items[i].self_length = snprintf(NULL, 0, "%.2f", items[i].ffloat);
length -= 2;
break;
case 's':
items[i].type = FORMAT_SLS_STR;
items[i].sls_str = va_arg(args, SlsStr);
length += items[i].self_length = items[i].sls_str.len;
length -= 2;
break;
case 't':
items[i].type = FORMAT_SLS_TOKEN_TYPE;
items[i].token_type = va_arg(args, TokenType);
if (items[i].token_type >= TOKEN_TYPE_COUNT) {
free(items);
return SLS_STR_NULL;
}
length += items[i].self_length = sls_str_nlen(TOKEN_TYPES_NAMES[items[i].token_type], TYPE_NAMES_SAFE_LENGTH);
length -= 2;
break;
case 'a':
items[i].type = FORMAT_SLS_ARRAY_TYPE;
items[i].array_type = va_arg(args, ArrayType);
if (items[i].array_type >= ARRAY_TYPE_COUNT) {
free(items);
return SLS_STR_NULL;
}
length += items[i].self_length = sls_str_nlen(ARRAY_TYPES_NAMES[items[i].array_type], TYPE_NAMES_SAFE_LENGTH);
length -= 2;
break;
case 'i':
items[i].type = FORMAT_SLS_BUILTIN_INTEGER;
items[i].builtin_integer = va_arg(args, IntegerBuiltInType);
if (items[i].builtin_integer >= INTEGER_TYPE_COUNT) {
free(items);
return SLS_STR_NULL;
}
length += items[i].self_length = sls_str_nlen(INTEGER_TYPES_NAMES[items[i].builtin_integer], TYPE_NAMES_SAFE_LENGTH);
length -= 2;
break;
case 'e':
items[i].type = FORMAT_SLS_ERROR;
items[i].error = va_arg(args, SlsError);
length += items[i].self_length = items[i].error.message.len;
length -= 2;
break;
case 'b':
items[i].type = FORMAT_SLS_BOOLEAN;
items[i].boolean = va_arg(args, Boolean);
length += items[i].self_length = (items[i].boolean ? 4 : 5);
length -= 2;
break;
}
items[i].str_index = (ptrdiff_t)(current - last_index);
last_index = current + 2;
i++;
current = strchr(current + 2, '%');
} while (current);
va_end(args);
char *temp = (char *)malloc(sizeof(char) * length);
if (temp == NULL) {
free(items);
return SLS_STR_NULL;
}
SlsStr str_new = sls_str_new(length);
if (str_new.str == NULL) {
free(items);
free(temp);
return SLS_STR_NULL;
}
char *str = (char *)str_new.str;
size_t item_i = 0;
ptrdiff_t target_i = 0;
ptrdiff_t source_i = 0;
while (item_i < count) {
memcpy(str + target_i, s.str + source_i, items[item_i].str_index);
target_i += items[item_i].str_index;
source_i += items[item_i].str_index + 2;
switch (items[item_i].type) {
case FORMAT_PERCENT_ESCAPE:
memcpy(temp, "%", items[item_i].self_length + 1);
break;
case FORMAT_C_STRINGS:
snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].c_string);
break;
case FORMAT_CHARACTER:
snprintf(temp, items[item_i].self_length + 1, "%c", items[item_i].character);
break;
case FORMAT_INTEGER_32:
snprintf(temp, items[item_i].self_length + 1, "%d", items[item_i].integer_32);
break;
case FORMAT_INTEGER_64:
snprintf(temp, items[item_i].self_length + 1, "%ld", items[item_i].integer_64);
break;
case FORMAT_UNSIGNED_INTEGER_64:
snprintf(temp, items[item_i].self_length + 1, "%lu", items[item_i].unsigned_integer_64);
break;
case FORMAT_SIZE_INTEGER:
snprintf(temp, items[item_i].self_length + 1, "%zu", items[item_i].size_integer);
break;
case FORMAT_FLOAT:
snprintf(temp, items[item_i].self_length + 1, "%.2f", items[item_i].ffloat); // Fixed-point decimal display
break;
case FORMAT_SLS_STR:
snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].sls_str.str);
break;
case FORMAT_SLS_TOKEN_TYPE:
snprintf(temp, items[item_i].self_length + 1, "%s", TOKEN_TYPES_NAMES[items[item_i].token_type]);
break;
case FORMAT_SLS_ARRAY_TYPE:
snprintf(temp, items[item_i].self_length + 1, "%s", ARRAY_TYPES_NAMES[items[item_i].array_type]);
break;
case FORMAT_SLS_BUILTIN_INTEGER:
snprintf(temp, items[item_i].self_length + 1, "%s", INTEGER_TYPES_NAMES[items[item_i].builtin_integer]);
break;
case FORMAT_SLS_ERROR:
snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].error.message.str);
break;
case FORMAT_SLS_BOOLEAN:
memcpy(temp, (items[item_i].boolean ? "TRUE" : "FALSE"), items[item_i].self_length + 1);
break;
}
memcpy(str + target_i, temp, items[item_i].self_length);
target_i += items[item_i].self_length;
item_i++;
}
if (s.len > (size_t)source_i)
memcpy(str + target_i, s.str + source_i, s.len - source_i);
str[str_new.len] = '\0';
free(items);
free(temp);
return str_new;
}

52
SLS_C/tests/extra_tests.c Normal file
View File

@ -0,0 +1,52 @@
// Kyler Olsen
// YREA SLS
// Extra Tests
// November 2025
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "sls/string.h"
#include "sls/lexer.h"
#include "sls/errors.h"
#include "tests/lexer_test_helpers.h"
#include "tests/tests.h"
static const size_t NUM_EXTRA_TESTS = 2;
static TestResult test_Identifier_Addition() {
LexerTest test = start_up_test(SLS_STR("Identifier Addition"), SLS_STR("+"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
if (test_identifier_value(&test, result, i++, &(TestIdentifierValue){FALSE, SLS_STR("+")})) return test.result;
if (test_eof_value(&test, result, i++, 0)) return test.result;
return pass_test(&test, result);
}
static TestResult test_Identifier_Division() {
LexerTest test = start_up_test(SLS_STR("Identifier Division"), SLS_STR("/"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
if (test_identifier_value(&test, result, i++, &(TestIdentifierValue){FALSE, SLS_STR("/")})) return test.result;
if (test_eof_value(&test, result, i++, 0)) return test.result;
return pass_test(&test, result);
}
// Run all extra tests
TestsReport run_extra_tests()
{
TestsReport report = {
.section = SLS_STR("extra_tests"),
.count = NUM_EXTRA_TESTS,
.tests = malloc(sizeof(TestResult) * NUM_EXTRA_TESTS)};
size_t i = 0;
(void)i;
report.tests[i++] = test_Identifier_Addition();
report.tests[i++] = test_Identifier_Division();
return report;
}

View File

@ -0,0 +1,383 @@
// Kyler Olsen
// YREA SLS
// Hash Table Tests
// November 2025
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "sls/string.h"
#include "sls/hash_table.h"
#include "tests/tests.h"
static const size_t NUM_HASH_TABLE_TESTS = 12;
static TestResult pass_hash_table_test(SlsStr test_name) {
TestResult result = {.name = test_name, .status = TEST_NOT_IMPLEMENTED};
result.status = TEST_PASS;
return result;
}
static TestResult fail_hash_table_test(SlsStr test_name, SlsStr message) {
TestResult result = { .name = test_name, .status = TEST_NOT_IMPLEMENTED };
result.status = TEST_ERROR;
result.message = sls_str_cpy(message);
if (result.message.str == NULL) {
result.error = (SlsError){ SLS_STR("Out Of Memory Error."), 1 };
result.status = TEST_ERROR;
}
return result;
}
// Test init_hash_table and del_hash_table
static TestResult test_init_and_delete() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_init_and_delete"), SLS_STR("Initialization failed"));
if (ht->buckets_count < 10) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_init_and_delete"), SLS_STR("Bucket count less than minimum"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_init_and_delete"));
}
// Test basic put and get operations
static TestResult test_put_and_get() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Initialization failed"));
int value1 = 42;
int value2 = 84;
SlsStr key1 = SLS_STR("key1");
SlsStr key2 = SLS_STR("key2");
if (!hash_table_put(ht, key1, &value1)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Put operation failed"));
}
if (!hash_table_put(ht, key2, &value2)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Second put operation failed"));
}
int *retrieved1 = (int*)hash_table_get(ht, key1, NULL);
int *retrieved2 = (int*)hash_table_get(ht, key2, NULL);
if (!retrieved1 || *retrieved1 != 42) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Get operation failed for key1"));
}
if (!retrieved2 || *retrieved2 != 84) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Get operation failed for key2"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_put_and_get"));
}
// Test get with non-existent key returns default
static TestResult test_get_nonexistent_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_get_nonexistent_key"), SLS_STR("Initialization failed"));
int default_value = -1;
SlsStr key = SLS_STR("nonexistent");
int *result = (int*)hash_table_get(ht, key, &default_value);
if (result != &default_value) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_get_nonexistent_key"), SLS_STR("Did not return default value"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_get_nonexistent_key"));
}
// Test updating existing key
static TestResult test_update_existing_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_update_existing_key"), SLS_STR("Initialization failed"));
int value1 = 100;
int value2 = 200;
SlsStr key = SLS_STR("update_key");
hash_table_put(ht, key, &value1);
hash_table_put(ht, key, &value2);
int *result = (int*)hash_table_get(ht, key, NULL);
if (!result || *result != 200) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_update_existing_key"), SLS_STR("Update failed"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_update_existing_key"));
}
// Test delete operation
static TestResult test_delete_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Initialization failed"));
int value = 42;
SlsStr key = SLS_STR("delete_key");
hash_table_put(ht, key, &value);
if (!hash_table_del(ht, key)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Delete returned FALSE"));
}
int default_val = -1;
int *result = (int*)hash_table_get(ht, key, &default_val);
if (result != &default_val) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Key still exists after delete"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_delete_key"));
}
// Test delete non-existent key returns FALSE
static TestResult test_delete_nonexistent_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_delete_nonexistent_key"), SLS_STR("Initialization failed"));
SlsStr key = SLS_STR("nonexistent");
if (hash_table_del(ht, key)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_delete_nonexistent_key"), SLS_STR("Delete returned TRUE for nonexistent key"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_delete_nonexistent_key"));
}
// Test collision handling (multiple keys in same bucket)
static TestResult test_collision_handling() {
HashTable *ht = init_hash_table(1); // Force collisions with single bucket
if (!ht) return fail_hash_table_test(SLS_STR("test_collision_handling"), SLS_STR("Initialization failed"));
int val1 = 1, val2 = 2, val3 = 3;
SlsStr key1 = SLS_STR("key1");
SlsStr key2 = SLS_STR("key2");
SlsStr key3 = SLS_STR("key3");
hash_table_put(ht, key1, &val1);
hash_table_put(ht, key2, &val2);
hash_table_put(ht, key3, &val3);
int *r1 = (int*)hash_table_get(ht, key1, NULL);
int *r2 = (int*)hash_table_get(ht, key2, NULL);
int *r3 = (int*)hash_table_get(ht, key3, NULL);
if (!r1 || *r1 != 1 || !r2 || *r2 != 2 || !r3 || *r3 != 3) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_collision_handling"), SLS_STR("Collision handling failed"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_collision_handling"));
}
// Test empty string key
static TestResult test_empty_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Initialization failed"));
int value = 99;
SlsStr key = SLS_STR("");
if (!hash_table_put(ht, key, &value)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Put with empty key failed"));
}
int *result = (int*)hash_table_get(ht, key, NULL);
if (!result || *result != 99) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Get with empty key failed"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_empty_key"));
}
// Test long key
static TestResult test_long_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Initialization failed"));
char long_key_str[256];
for (size_t i = 0; i < 255; i++) long_key_str[i] = 'A';
long_key_str[255] = '\0';
SlsStr key = sls_str_malloc(long_key_str, 255);
int value = 777;
if (!hash_table_put(ht, key, &value)) {
sls_str_free(&key);
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Put with long key failed"));
}
int *result = (int*)hash_table_get(ht, key, NULL);
if (!result || *result != 777) {
sls_str_free(&key);
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Get with long key failed"));
}
sls_str_free(&key);
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_long_key"));
}
// Test multiple operations sequence
static TestResult test_multiple_operations() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Initialization failed"));
int vals[5] = {10, 20, 30, 40, 50};
SlsStr keys[5] = {SLS_STR("k1"), SLS_STR("k2"), SLS_STR("k3"), SLS_STR("k4"), SLS_STR("k5")};
// Insert all
for (int i = 0; i < 5; i++) {
if (!hash_table_put(ht, keys[i], &vals[i])) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Insert failed"));
}
}
// Delete some
hash_table_del(ht, keys[1]);
hash_table_del(ht, keys[3]);
// Verify remaining
int *r0 = (int*)hash_table_get(ht, keys[0], NULL);
int *r2 = (int*)hash_table_get(ht, keys[2], NULL);
int *r4 = (int*)hash_table_get(ht, keys[4], NULL);
if (!r0 || *r0 != 10 || !r2 || *r2 != 30 || !r4 || *r4 != 50) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Get after delete failed"));
}
// Verify deleted
int def = -1;
int *r1 = (int*)hash_table_get(ht, keys[1], &def);
int *r3 = (int*)hash_table_get(ht, keys[3], &def);
if (r1 != &def || r3 != &def) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Deleted keys still present"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_multiple_operations"));
}
// Test storing NULL pointers
static TestResult test_null_value() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("Initialization failed"));
SlsStr key = SLS_STR("null_key");
if (!hash_table_put(ht, key, NULL)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("Put with NULL value failed"));
}
int default_val = -1;
void *result = hash_table_get(ht, key, &default_val);
// Should retrieve NULL, not default
if (result == &default_val) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("NULL value not stored correctly"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_null_value"));
}
// Test large number of entries
static TestResult test_large_capacity() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Initialization failed"));
const size_t COUNT = 100;
int *values = malloc(sizeof(int) * COUNT);
SlsStr *keys = malloc(sizeof(SlsStr) * COUNT);
for (size_t i = 0; i < COUNT; i++) {
values[i] = (int)i;
char key_buf[32];
snprintf(key_buf, 32, "key_%zu", i);
keys[i] = sls_str_malloc(key_buf, strlen(key_buf));
if (!hash_table_put(ht, keys[i], &values[i])) {
for (size_t j = 0; j <= i; j++) sls_str_free(&keys[j]);
free(keys);
free(values);
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Put failed in bulk insert"));
}
}
// Verify all entries
for (size_t i = 0; i < COUNT; i++) {
int *result = (int*)hash_table_get(ht, keys[i], NULL);
if (!result || *result != (int)i) {
for (size_t j = 0; j < COUNT; j++) sls_str_free(&keys[j]);
free(keys);
free(values);
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Get failed in bulk verify"));
}
}
for (size_t i = 0; i < COUNT; i++) sls_str_free(&keys[i]);
free(keys);
free(values);
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_large_capacity"));
}
// Run all hash table tests
TestsReport run_hash_table_tests() {
TestsReport report = {
.section = SLS_STR("hash_table_tests"),
.count = NUM_HASH_TABLE_TESTS,
.tests = malloc(sizeof(TestResult) * NUM_HASH_TABLE_TESTS)
};
size_t i = 0;
report.tests[i++] = test_init_and_delete();
report.tests[i++] = test_put_and_get();
report.tests[i++] = test_get_nonexistent_key();
report.tests[i++] = test_update_existing_key();
report.tests[i++] = test_delete_key();
report.tests[i++] = test_delete_nonexistent_key();
report.tests[i++] = test_collision_handling();
report.tests[i++] = test_empty_key();
report.tests[i++] = test_long_key();
report.tests[i++] = test_multiple_operations();
report.tests[i++] = test_null_value();
report.tests[i++] = test_large_capacity();
return report;
}

View File

@ -9,17 +9,18 @@
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include "sls/sls_errors.h" #include "sls/errors.h"
#include "sls/bool.h"
#include "sls/lexer.h" #include "sls/lexer.h"
#include "sls/string.h" #include "sls/string.h"
#include "tests/lexer_test_helpers.h" #include "tests/lexer_test_helpers.h"
#include "tests/tests.h" #include "tests/tests.h"
static const double FLOAT_TEST_PRECISION = 0.01; static const double FLOAT_TEST_PRECISION = 0.0078125;
// Test start and end helpers // Test start and end helpers
LexerTest start_up_test(const char *test_name, const char *test_code) { LexerTest start_up_test(SlsStr test_name, SlsStr test_code) {
LexerTest test = (LexerTest) { LexerTest test = (LexerTest) {
.result = (TestResult) { .result = (TestResult) {
.name = test_name, .status = TEST_NOT_IMPLEMENTED } }; .name = test_name, .status = TEST_NOT_IMPLEMENTED } };
@ -32,45 +33,63 @@ void clean_up_test(LexerResult result) {
clean_token_result(result.result); clean_token_result(result.result);
} }
TestResult error_test(LexerTest *test, LexerResult result, SlsError error) { TestResult error_test_out_of_mem(LexerTest *test) {
test->result.status = TEST_ERROR; test->result.status = TEST_ERROR;
test->result.error = error; test->result.error = (SlsError){SLS_STR("Out Of Memory Error."), 1};
clean_up_test(result);
return test->result; return test->result;
} }
TestResult logic_fail_test(LexerTest *test, LexerResult result, char *message) { TestResult error_test(LexerTest *test, LexerResult result, SlsError error) {
if (message == 0) return error_test(test, result, (SlsError) { clean_up_test(result);
.message = "Out of Memory Error!", .code = 1, });
if (error.message.str == NULL) return error_test_out_of_mem(test);
test->result.status = TEST_ERROR;
test->result.error = error;
test->result.error.message = sls_str_cpy(error.message);
if (test->result.error.message.str == NULL) return error_test_out_of_mem(test);
return test->result;
}
TestResult logic_fail_test(LexerTest *test, LexerResult result, SlsStr message) {
clean_up_test(result);
test->result.status = TEST_LOGIC_FAIL; test->result.status = TEST_LOGIC_FAIL;
test->result.message = message; test->result.message = message;
clean_up_test(result);
return test->result; return test->result;
} }
TestResult logic_error_fail_test(LexerTest *test, LexerResult result, SlsError error) { TestResult logic_error_fail_test(LexerTest *test, LexerResult result, SlsError error) {
if (error.message.str == NULL) return error_test_out_of_mem(test);
test->result.status = TEST_LOGIC_ERROR_FAIL; test->result.status = TEST_LOGIC_ERROR_FAIL;
test->result.error = error; test->result.error = error;
test->result.error.message = sls_str_cpy(error.message);
size_t message_length = strlen(error.message) + 1;
const char *message = (char *)malloc(sizeof(char) * message_length);
strncpy(message, error.message, message_length);
test->result.error.message = message;
clean_up_test(result); clean_up_test(result);
if (test->result.error.message.str == NULL) return error_test_out_of_mem(test);
return test->result; return test->result;
} }
TestResult error_fail_test(LexerTest *test, LexerResult result, SlsError error) { TestResult error_fail_test(LexerTest *test, LexerResult result, SlsError error) {
clean_up_test(result);
if (error.message.str == NULL) return error_test_out_of_mem(test);
test->result.status = TEST_ERROR_FAIL; test->result.status = TEST_ERROR_FAIL;
test->result.error = error; test->result.error = error;
clean_up_test(result); test->result.error.message = sls_str_cpy(error.message);
if (test->result.error.message.str == NULL) return error_test_out_of_mem(test);
return test->result; return test->result;
} }
TestResult skip_test(LexerTest *test, LexerResult result) { TestResult skip_test(LexerTest *test, LexerResult result) {
test->result.status = TEST_NOT_IMPLEMENTED;
clean_up_test(result); clean_up_test(result);
test->result.status = TEST_NOT_IMPLEMENTED;
return test->result; return test->result;
} }
@ -80,198 +99,120 @@ TestResult skip_test_no_result(LexerTest *test) {
} }
TestResult pass_test(LexerTest *test, LexerResult result) { TestResult pass_test(LexerTest *test, LexerResult result) {
test->result.status = TEST_PASS;
clean_up_test(result); clean_up_test(result);
test->result.status = TEST_PASS;
return test->result; return test->result;
} }
// Test messages // Test messages
static char *unexpected_end_of_token_stream(size_t i) { static SlsStr unexpected_end_of_token_stream(size_t i) {
size_t length = ceil(log10(i)) + 47; return sls_format(SLS_STR("Unexpected end of token stream (%z tokens found)"), i - 1);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Unexpected end of token stream (%zu tokens found)", i - 1);
return string;
} }
static char *expected_end_of_token_stream(size_t i) { static SlsStr expected_end_of_token_stream(size_t i) {
size_t length = ceil(log10(i)) + 47; return sls_format(SLS_STR("Expected end of token stream (more than %z tokens found)"), i - 1);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Expected end of token stream (more than %zu tokens found)", i - 1);
return string;
} }
static char *token_should_be(size_t i, TokenType should, TokenType found) { static SlsStr token_should_be(size_t i, TokenType should, TokenType found) {
size_t length = ceil(log10(i + 1)) + strnlen(TOKEN_TYPES_NAMES[should], TYPE_NAMES_SAFE_LENGTH) + strnlen(TOKEN_TYPES_NAMES[found], TYPE_NAMES_SAFE_LENGTH) + 35; if (found >= TOKEN_TYPE_COUNT)
char *string = (char *)malloc(sizeof(char) * length); return sls_format(SLS_STR("Token #%z should be a %t, but found invalid enum %u"), i, should, (uint64_t)found);
if (string == 0) return string; else
snprintf(string, length, "Token #%zu should be a %s, but found a %s", i, TOKEN_TYPES_NAMES[should], TOKEN_TYPES_NAMES[found]); return sls_format(SLS_STR("Token #%z should be a %t, but found a %t"), i, should, found);
return string;
} }
static char *integer_type_should_be(size_t i, IntegerBuiltInType should, IntegerBuiltInType found) { static SlsStr integer_type_should_be(size_t i, IntegerBuiltInType should, IntegerBuiltInType found) {
size_t length = ceil(log10(i + 1)) + strnlen(INTEGER_TYPES_NAMES[should], 5) + strnlen(INTEGER_TYPES_NAMES[found], 5) + 48; if (found >= INTEGER_TYPE_COUNT)
char *string = (char *)malloc(sizeof(char) * length); return sls_format(SLS_STR("Token #%z integer type should be a %i, but found invalid enum %u"), i, should, (uint64_t)found);
if (string == 0) return string; else
snprintf(string, length, "Token #%zu integer type should be a %s, but found a %s", i, TOKEN_TYPES_NAMES[should], TOKEN_TYPES_NAMES[found]); return sls_format(SLS_STR("Token #%z integer type should be a %i, but found a %i"), i, should, found);
return string;
} }
static char *integer_value_should_be(size_t i, uint64_t should, uint64_t found) { static SlsStr integer_value_should_be(size_t i, uint64_t should, uint64_t found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(should + 1)) + ceil(log10(found + 1)) + 45; return sls_format(SLS_STR("Token #%z integer value should be %u, but found %u"), i, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu integer value should be %lu, but found %lu", i, should, found);
return string;
} }
static char *float_value_should_be(size_t i, double should, double found) { static SlsStr character_value_should_be(size_t i, uint8_t should, uint8_t found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(should + 1) + 3) + ceil(log10(found + 1) + 3) + 43; return sls_format(SLS_STR("Token #%z integer value should be %i, but found %i"), i, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu float value should be %.2f, but found %.2f", i, should, found);
return string;
} }
static char *identifier_should_be_literal(size_t i) { static SlsStr float_value_should_be(size_t i, double should, double found) {
size_t length = ceil(log10(i + 1)) + 51; return sls_format(SLS_STR("Token #%z float value should be %f, but found %f"), i, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu identifier should be an identifier literal", i);
return string;
} }
static char *identifier_should_not_be_literal(size_t i) { static SlsStr identifier_should_be_literal(size_t i) {
size_t length = ceil(log10(i + 1)) + 55; return sls_format(SLS_STR("Token #%z identifier should be an identifier literal"), i);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu identifier should not be an identifier literal", i);
return string;
} }
static char *token_length_should_be(size_t i, TokenType type, uint64_t should, uint64_t found) { static SlsStr identifier_should_not_be_literal(size_t i) {
size_t length = ceil(log10(i + 1)) + strnlen(TOKEN_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + ceil(log10(should + 1)) + ceil(log10(found + 1)) + 47; return sls_format(SLS_STR("Token #%z identifier should not be an identifier literal"), i);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu of type %s length should be %lu, but found %lu", i, TOKEN_TYPES_NAMES[type], should, found);
return string;
} }
static char *token_value_string_should_be(size_t i, TokenType type, size_t value_length, const char *should, const char *found) { static SlsStr token_length_should_be(size_t i, TokenType type, uint64_t should, uint64_t found) {
size_t length = ceil(log10(i + 1)) + strnlen(TOKEN_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + strnlen(should, value_length) + strnlen(found, value_length) + 53; return sls_format(SLS_STR("Token #%z of type %t length should be %u, but found %u"), i, type, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu of type %s string value should be %s, but found %s", i, TOKEN_TYPES_NAMES[type], should, found);
return string;
} }
static char *boolean_should_be(size_t i, Boolean value) { static SlsStr token_value_string_should_be(size_t i, TokenType type, SlsStr should, SlsStr found) {
size_t length = ceil(log10(i + 1)) + 45; return sls_format(SLS_STR("Token #%z of type %t string value should be %s, but found %s"), i, type, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
if (value) snprintf(string, length, "Token #%zu boolean should be true, but is false", i);
else snprintf(string, length, "Token #%zu boolean should be false, but is true", i);
return string;
} }
static char *array_type_should_be(size_t i, ArrayType should, ArrayType found) { static SlsStr boolean_should_be(size_t i, Boolean value) {
size_t length = ceil(log10(i + 1)) + strnlen(ARRAY_TYPES_NAMES[should], TYPE_NAMES_SAFE_LENGTH) + strnlen(ARRAY_TYPES_NAMES[found], TYPE_NAMES_SAFE_LENGTH) + 35; if (value) return sls_format(SLS_STR("Token #%z boolean should be true, but is false"), i);
char *string = (char *)malloc(sizeof(char) * length); else return sls_format(SLS_STR("Token #%z boolean should be false, but is true"), i);
if (string == 0) return string;
snprintf(string, length, "Token #%zu should be a %s, but found a %s", i, ARRAY_TYPES_NAMES[should], ARRAY_TYPES_NAMES[found]);
return string;
} }
static char *array_dimensions_should_be(size_t i, size_t should, size_t found) { static SlsStr array_type_should_be(size_t i, ArrayType should, ArrayType found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(should + 1)) + ceil(log10(found + 1)) + 48; if (found >= ARRAY_TYPE_COUNT)
char *string = (char *)malloc(sizeof(char) * length); return sls_format(SLS_STR("Token #%z should be a %a, but found invalid enum %u"), i, should, (uint64_t)found);
if (string == 0) return string; else
snprintf(string, length, "Token #%zu array dimensions should be %zu, but found %zu", i, should, found); return sls_format(SLS_STR("Token #%z should be a %a, but found a %a"), i, should, found);
return string;
} }
static char *array_element_shape_should_be(size_t i, size_t j, ArrayType type, uint64_t should, uint64_t found) { static SlsStr array_dimensions_should_be(size_t i, size_t should, size_t found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + ceil(log10(should + 1) + 3) + ceil(log10(found + 1) + 3) + 63; return sls_format(SLS_STR("Token #%z array dimensions should be %z, but found %z"), i, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu dimension %zu of array type %s should be shape %lu, but found %lu", i, j, TOKEN_TYPES_NAMES[type], should, found);
return string;
} }
static char *array_element_integer_should_be(size_t i, size_t j, ArrayType type, uint64_t should, uint64_t found) { static SlsStr array_dimension_shape_should_be(size_t i, size_t j, ArrayType type, uint64_t should, uint64_t found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + ceil(log10(should + 1) + 3) + ceil(log10(found + 1) + 3) + 55; return sls_format(SLS_STR("Token #%z dimension %z of array type %a should be shape %u, but found %u"), i, j, type, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu element %zu of array type %s should be %lu, but found %lu", i, j, TOKEN_TYPES_NAMES[type], should, found);
return string;
} }
static char *array_element_float_should_be(size_t i, size_t j, ArrayType type, double should, double found) { static SlsStr array_element_integer_should_be(size_t i, size_t j, ArrayType type, uint64_t should, uint64_t found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + ceil(log10(should + 1)) + ceil(log10(found + 1)) + 55; return sls_format(SLS_STR("Token #%z element %z of array type %a should be %u, but found %u"), i, j, type, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu element %zu of array type %s should be %.2f, but found %.2f", i, j, TOKEN_TYPES_NAMES[type], should, found);
return string;
} }
static char *array_element_string_should_be(size_t i, size_t j, ArrayType type, size_t value_length, const char *should, const char *found) { static SlsStr array_element_float_should_be(size_t i, size_t j, ArrayType type, double should, double found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + strnlen(should, value_length) + strnlen(found, value_length) + 55; return sls_format(SLS_STR("Token #%z element %z of array type %a should be %f, but found %f"), i, j, type, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu element %zu of array type %s should be %s, but found %s", i, j, TOKEN_TYPES_NAMES[type], should, found);
return string;
} }
static char *array_element_boolean_should_be(size_t i, size_t j, ArrayType type, Boolean value) { static SlsStr array_element_string_should_be(size_t i, size_t j, ArrayType type, SlsStr should, SlsStr found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + 64; return sls_format(SLS_STR("Token #%z element %z of array type %a should be %s, but found %s"), i, j, type, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
if (value) snprintf(string, length, "Token #%zu element %zu of array type %s should be true, but found false", i, j, TOKEN_TYPES_NAMES[type]);
else snprintf(string, length, "Token #%zu element %zu of array type %s should be false, but found true", i, j, TOKEN_TYPES_NAMES[type]);
return string;
} }
static char *type_tuple_element_integer_should_be(size_t i, size_t j, uint64_t should, uint64_t found) { static SlsStr array_element_boolean_should_be(size_t i, size_t j, ArrayType type, Boolean value) {
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + ceil(log10(should + 1) + 3) + ceil(log10(found + 1) + 3) + 54; if (value) return sls_format(SLS_STR("Token #%z element %z of array type %a should be true, but is false"), i, j, type);
char *string = (char *)malloc(sizeof(char) * length); else return sls_format(SLS_STR("Token #%z element %z of array type %a should be false, but is true"), i, j, type);
if (string == 0) return string;
snprintf(string, length, "Token #%zu element %zu of type tuple should be %lu, but found %lu", i, j, should, found);
return string;
} }
static char *type_tuple_element_string_should_be(size_t i, size_t j, size_t value_length, const char *should, const char *found) { static SlsStr type_tuple_element_integer_should_be(size_t i, size_t j, uint64_t should, uint64_t found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(should, value_length) + strnlen(found, value_length) + 54; return sls_format(SLS_STR("Token #%z element %u of type tuple should be %u, but found %u"), i, j, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
snprintf(string, length, "Token #%zu element %zu of type tuple should be %s, but found %s", i, j, should, found);
return string;
} }
static char *type_tuple_element_boolean_should_be(size_t i, size_t j, Boolean value) { static SlsStr type_tuple_element_string_should_be(size_t i, size_t j, SlsStr should, SlsStr found) {
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + 63; return sls_format(SLS_STR("Token #%z element %z of type tuple should be %s, but found %s"), i, j, should, found);
char *string = (char *)malloc(sizeof(char) * length);
if (string == 0) return string;
if (value) snprintf(string, length, "Token #%zu element %zu of type tuple should be true, but found false", i, j);
else snprintf(string, length, "Token #%zu element %zu of type tuple should be false, but found true", i, j);
return string;
} }
static char *token_should_be_error(size_t i, TestErrorMessage should, TokenType found) { static SlsStr type_tuple_element_boolean_should_be(size_t i, size_t j, Boolean value) {
size_t length = ceil(log10(i + 1)) + should.length + strnlen(TOKEN_TYPES_NAMES[found], TYPE_NAMES_SAFE_LENGTH) + 72; if (value) return sls_format(SLS_STR("Token #%z element %z of type tuple should be true, but is false"), i, j);
char *string = (char *)malloc(sizeof(char) * length); else return sls_format(SLS_STR("Token #%z element %z of type tuple should be false, but is true"), i, j);
if (string == 0) return string;
snprintf(string, length, "Token #%zu should be an error with a message of %s, but found token of type %s", i, should.message, TOKEN_TYPES_NAMES[found]);
return string;
} }
static char *error_should_be(size_t i, TestErrorMessage should, SlsError found) { static SlsStr token_should_be_error(size_t i, SlsStr should, TokenType found) {
size_t length = ceil(log10(i + 1)) + should.length + strlen(found.message) + 77; return sls_format(SLS_STR("Token #%z should be an error with a message of \"%s\", but found token of type %t"), i, should, found);
char *string = (char *)malloc(sizeof(char) * length); }
if (string == 0) return string;
snprintf(string, length, "Token #%zu should be an error with a message of %s, but found error with message %s", i, should.message, found.message); static SlsStr error_should_be(size_t i, SlsStr should, SlsError found) {
return string; return sls_format(SLS_STR("Token #%z should be an error with a message of \"%s\", but found error with message \"%e\""), i, should, found);
} }
// Test parts // Test parts
@ -304,7 +245,7 @@ static Boolean test_array_type(LexerTest *test, LexerResult result, size_t i, Ar
} }
for (size_t j = 0; j < dimensions; j++) { for (size_t j = 0; j < dimensions; j++) {
if (head->result.array_literal.shape[j] != shape[j]) { if (head->result.array_literal.shape[j] != shape[j]) {
logic_fail_test(test, result, array_element_shape_should_be(i + 1, j, array_type, shape[j], head->result.array_literal.shape[j])); logic_fail_test(test, result, array_dimension_shape_should_be(i + 1, j, array_type, shape[j], head->result.array_literal.shape[j]));
return TRUE; return TRUE;
} }
} }
@ -332,11 +273,11 @@ Boolean test_identifier_value(LexerTest *test, LexerResult result, size_t i, Tes
} if (head->result.identifier.is_literal != value->is_literal) { } if (head->result.identifier.is_literal != value->is_literal) {
logic_fail_test(test, result, value->is_literal ? identifier_should_be_literal(i + 1) : identifier_should_not_be_literal(i + 1)); logic_fail_test(test, result, value->is_literal ? identifier_should_be_literal(i + 1) : identifier_should_not_be_literal(i + 1));
return TRUE; return TRUE;
} if (head->result.identifier.length == strnlen(value->name, value->length)) { } if (head->result.identifier.name.len != value->name.len) {
logic_fail_test(test, result, token_length_should_be(i + 1, token_type, strnlen(value->name, value->length), head->result.identifier.length)); logic_fail_test(test, result, token_length_should_be(i + 1, token_type, value->name.len, head->result.identifier.name.len));
return TRUE; return TRUE;
} if (strncmp(head->result.identifier.name, value->name, value->length) != 0) { } if (sls_str_cmp(head->result.identifier.name, value->name) != 0) {
logic_fail_test(test, result, token_value_string_should_be(i + 1, token_type, strnlen(value->name, value->length), head->result.identifier.name, value->name)); logic_fail_test(test, result, token_value_string_should_be(i + 1, token_type, head->result.identifier.name, value->name));
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
@ -357,6 +298,18 @@ Boolean test_integer_value(LexerTest *test, LexerResult result, size_t i, TestIn
return FALSE; return FALSE;
} }
Boolean test_character_value(LexerTest *test, LexerResult result, size_t i, uint8_t *value) {
static const TokenType token_type = TOKEN_CHARACTER;
LexerTokenResult *head = get_token(result.result, i);
if (test_token_type(test, result, i, token_type)) {
return TRUE;
} if (head->result.integer_literal.value != *value) {
logic_fail_test(test, result, character_value_should_be(i + 1, *value, head->result.float_literal));
return TRUE;
}
return FALSE;
}
Boolean test_float_value(LexerTest *test, LexerResult result, size_t i, float *value) { Boolean test_float_value(LexerTest *test, LexerResult result, size_t i, float *value) {
static const TokenType token_type = TOKEN_FLOAT; static const TokenType token_type = TOKEN_FLOAT;
LexerTokenResult *head = get_token(result.result, i); LexerTokenResult *head = get_token(result.result, i);
@ -374,23 +327,23 @@ Boolean test_double_value(LexerTest *test, LexerResult result, size_t i, double
LexerTokenResult *head = get_token(result.result, i); LexerTokenResult *head = get_token(result.result, i);
if (test_token_type(test, result, i, token_type)) { if (test_token_type(test, result, i, token_type)) {
return TRUE; return TRUE;
} if (fabs(head->result.float_literal - *value) >= FLOAT_TEST_PRECISION) { } if (fabs(head->result.double_literal - *value) >= FLOAT_TEST_PRECISION) {
logic_fail_test(test, result, float_value_should_be(i + 1, *value, head->result.float_literal)); logic_fail_test(test, result, float_value_should_be(i + 1, *value, head->result.double_literal));
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
} }
Boolean test_string_value(LexerTest *test, LexerResult result, size_t i, TestStringValue *value) { Boolean test_string_value(LexerTest *test, LexerResult result, size_t i, SlsStr *value) {
static const TokenType token_type = TOKEN_STRING; static const TokenType token_type = TOKEN_STRING;
LexerTokenResult *head = get_token(result.result, i); LexerTokenResult *head = get_token(result.result, i);
if (test_token_type(test, result, i, token_type)) { if (test_token_type(test, result, i, token_type)) {
return TRUE; return TRUE;
} if (head->result.string_literal.length == strnlen(value->string, value->length)) { } if (head->result.string_literal.len == value->len) {
logic_fail_test(test, result, token_length_should_be(i + 1, token_type, strnlen(value->string, value->length), head->result.string_literal.length)); logic_fail_test(test, result, token_length_should_be(i + 1, token_type, value->len, head->result.string_literal.len));
return TRUE; return TRUE;
} if (strncmp(head->result.string_literal.value, value->string, value->length) != 0) { } if (sls_str_cmp(head->result.string_literal, *value) != 0) {
logic_fail_test(test, result, token_value_string_should_be(i + 1, token_type, fmax(strnlen(value->string, value->length), strnlen(head->result.string_literal.value, value->length)), value->string, head->result.string_literal.value)); logic_fail_test(test, result, token_value_string_should_be(i + 1, token_type, *value, head->result.string_literal));
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
@ -417,11 +370,11 @@ Boolean test_array_identifier_value(LexerTest *test, LexerResult result, size_t
size_t length = 1; size_t length = 1;
for (size_t j = 0; j < values->dimensions; j++) length *= values->shape[j]; for (size_t j = 0; j < values->dimensions; j++) length *= values->shape[j];
for (size_t j = 0; j < length; j++) { for (size_t j = 0; j < length; j++) {
if (head->result.array_literal.identifiers[j].length == values->values[j].length) { if (head->result.array_literal.identifiers[j].name.len == values->values[j].name.len) {
logic_fail_test(test, result, array_element_integer_should_be(i + 1, j, array_type, values->values[j].length, head->result.array_literal.identifiers[j].length)); logic_fail_test(test, result, array_element_integer_should_be(i + 1, j, array_type, values->values[j].name.len, head->result.array_literal.identifiers[j].name.len));
return TRUE; return TRUE;
} if (strncmp(head->result.array_literal.identifiers[j].name, values->values[j].name, values->values[j].length)) { } if (sls_str_cmp(head->result.array_literal.identifiers[j].name, values->values[j].name)) {
logic_fail_test(test, result, array_element_string_should_be(i + 1, j, array_type, values->values[j].length, values->values[j].name, head->result.array_literal.identifiers[j].name)); logic_fail_test(test, result, array_element_string_should_be(i + 1, j, array_type, values->values[j].name, head->result.array_literal.identifiers[j].name));
return TRUE; return TRUE;
} if (head->result.array_literal.identifiers[j].is_literal) { } if (head->result.array_literal.identifiers[j].is_literal) {
logic_fail_test(test, result, array_element_boolean_should_be(i + 1, j, array_type, TRUE)); logic_fail_test(test, result, array_element_boolean_should_be(i + 1, j, array_type, TRUE));
@ -491,11 +444,11 @@ Boolean test_array_string_value(LexerTest *test, LexerResult result, size_t i, T
size_t length = 1; size_t length = 1;
for (size_t j = 0; j < values->dimensions; j++) length *= values->shape[j]; for (size_t j = 0; j < values->dimensions; j++) length *= values->shape[j];
for (size_t j = 0; j < length; j++) { for (size_t j = 0; j < length; j++) {
if (head->result.array_literal.string_literals[j].length == values->values[j].length) { if (head->result.array_literal.string_literals[j].len == values->values[j].len) {
logic_fail_test(test, result, array_element_integer_should_be(i + 1, j, array_type, values->values[j].length, head->result.array_literal.string_literals[j].length)); logic_fail_test(test, result, array_element_integer_should_be(i + 1, j, array_type, values->values[j].len, head->result.array_literal.string_literals[j].len));
return TRUE; return TRUE;
} if (strncmp(head->result.array_literal.string_literals[j].value, values->values[j].string, values->values[j].length)) { } if (sls_str_cmp(head->result.array_literal.string_literals[j], values->values[j])) {
logic_fail_test(test, result, array_element_string_should_be(i + 1, j, array_type, values->values[j].length, values->values[j].string, head->result.array_literal.string_literals[j].value)); logic_fail_test(test, result, array_element_string_should_be(i + 1, j, array_type, values->values[j], head->result.array_literal.string_literals[j]));
return TRUE; return TRUE;
} }
} }
@ -524,8 +477,8 @@ Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size
LexerTokenResult *head = get_token(result.result, i); LexerTokenResult *head = get_token(result.result, i);
if (test_array_type(test, result, i, array_type, values->shape, values->dimensions)) { if (test_array_type(test, result, i, array_type, values->shape, values->dimensions)) {
return TRUE; return TRUE;
} if (strncmp(head->result.array_literal.struct_inline.name, values->struct_name, values->struct_name_length)) { } if (sls_str_cmp(head->result.array_literal.struct_inline.name, values->struct_name)) {
logic_fail_test(test, result, token_value_string_should_be(i + 1, TOKEN_IDENTIFIER, values->struct_name_length, values->struct_name, head->result.array_literal.struct_inline.name)); logic_fail_test(test, result, token_value_string_should_be(i + 1, TOKEN_IDENTIFIER, values->struct_name, head->result.array_literal.struct_inline.name));
return TRUE; return TRUE;
} }
size_t length = 1; size_t length = 1;
@ -541,9 +494,38 @@ Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size
static LexerResult token_string_to_lexer_result(TokenString token_string, FileInfo file_info) { static LexerResult token_string_to_lexer_result(TokenString token_string, FileInfo file_info) {
LexerTokenResult *new, *head; LexerTokenResult *new, *head;
head = 0; head = 0;
for (size_t i = 0; i> token_string.length; i++) { for (size_t i = token_string.length; i > 0; i--) {
new = (LexerTokenResult *)malloc(sizeof(LexerTokenResult)); new = (LexerTokenResult *)malloc(sizeof(LexerTokenResult));
*new = (LexerTokenResult) { .type = SLS_RESULT, .result = token_string.tokens[i], .file_info = file_info, .next = head }; if (new == 0) return (LexerResult) { .type = SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1} };
if (token_string.tokens[i-1].type == TOKEN_STRING) {
*new = (LexerTokenResult) {
.type = SLS_RESULT,
.result = (Token){
.type = TOKEN_STRING,
.string_literal = sls_str_cpy(token_string.tokens[i-1].string_literal)
},
.file_info = file_info,
.next = head
};
} else if (token_string.tokens[i-1].type == TOKEN_TOKEN_STRING) {
*new = (LexerTokenResult) {
.type = SLS_RESULT,
.result = (Token){
.type = TOKEN_TOKEN_STRING,
.token_string = copy_token_string(token_string.tokens[i-1].token_string)
},
.file_info = file_info,
.next = head
};
memcpy(new->result.token_string.tokens, token_string.tokens[i-1].token_string.tokens, token_string.tokens[i-1].token_string.length);
} else {
*new = (LexerTokenResult) {
.type = SLS_RESULT,
.result = token_string.tokens[i-1],
.file_info = file_info,
.next = head
};
}
head = new; head = new;
} }
return (LexerResult) { .type = SLS_RESULT, .result = head }; return (LexerResult) { .type = SLS_RESULT, .result = head };
@ -561,7 +543,8 @@ Boolean test_token_string_value(LexerTest *test, LexerResult result, size_t i, T
for (size_t j = 0; j < values->tokens; j++) { for (size_t j = 0; j < values->tokens; j++) {
LexerResult token_string_result = token_string_to_lexer_result(head->result.token_string, head->file_info); LexerResult token_string_result = token_string_to_lexer_result(head->result.token_string, head->file_info);
if (values->values[j].token_handler(test, token_string_result, j, values->values[j].value)) { if (values->values[j].token_handler(test, token_string_result, j, values->values[j].value)) {
clean_token_result(token_string_result.result); // We do not clean up here because if token_handler fails the test, it cleans up for us.
// clean_token_result(token_string_result.result);
return TRUE; return TRUE;
} }
clean_token_result(token_string_result.result); clean_token_result(token_string_result.result);
@ -581,22 +564,22 @@ Boolean test_type_tuple_value(LexerTest *test, LexerResult result, size_t i, Tes
logic_fail_test(test, result, token_length_should_be(i + 1, token_type, values->output_length, head->result.type_tuple.output_length)); logic_fail_test(test, result, token_length_should_be(i + 1, token_type, values->output_length, head->result.type_tuple.output_length));
return TRUE; return TRUE;
} for (size_t j = 0; j < values->input_length; j++) { } for (size_t j = 0; j < values->input_length; j++) {
if (head->result.type_tuple.input_identifiers[j].length == values->input_values[j].length) { if (head->result.type_tuple.input_identifiers[j].name.len == values->input_values[j].name.len) {
logic_fail_test(test, result, type_tuple_element_integer_should_be(i + 1, j, values->input_values[j].length, head->result.type_tuple.input_identifiers[j].length)); logic_fail_test(test, result, type_tuple_element_integer_should_be(i + 1, j, values->input_values[j].name.len, head->result.type_tuple.input_identifiers[j].name.len));
return TRUE; return TRUE;
} if (strncmp(head->result.type_tuple.input_identifiers[j].name, values->input_values[j].name, values->input_values[j].length)) { } if (sls_str_cmp(head->result.type_tuple.input_identifiers[j].name, values->input_values[j].name)) {
logic_fail_test(test, result, type_tuple_element_string_should_be(i + 1, j, values->input_values[j].length, values->input_values[j].name, head->result.type_tuple.input_identifiers[j].name)); logic_fail_test(test, result, type_tuple_element_string_should_be(i + 1, j, values->input_values[j].name, head->result.type_tuple.input_identifiers[j].name));
return TRUE; return TRUE;
} if (head->result.type_tuple.input_identifiers[j].is_literal) { } if (head->result.type_tuple.input_identifiers[j].is_literal) {
logic_fail_test(test, result, type_tuple_element_boolean_should_be(i + 1, j, TRUE)); logic_fail_test(test, result, type_tuple_element_boolean_should_be(i + 1, j, TRUE));
return TRUE; return TRUE;
} }
} for (size_t j = 0; j < values->output_length; j++) { } for (size_t j = 0; j < values->output_length; j++) {
if (head->result.type_tuple.output_identifiers[j].length == values->output_values[j].length) { if (head->result.type_tuple.output_identifiers[j].name.len == values->output_values[j].name.len) {
logic_fail_test(test, result, type_tuple_element_integer_should_be(i + 1, j, values->output_values[j].length, head->result.type_tuple.output_identifiers[j].length)); logic_fail_test(test, result, type_tuple_element_integer_should_be(i + 1, j, values->output_values[j].name.len, head->result.type_tuple.output_identifiers[j].name.len));
return TRUE; return TRUE;
} if (strncmp(head->result.type_tuple.output_identifiers[j].name, values->output_values[j].name, values->input_values[j].length)) { } if (sls_str_cmp(head->result.type_tuple.output_identifiers[j].name, values->output_values[j].name)) {
logic_fail_test(test, result, type_tuple_element_string_should_be(i + 1, j, values->output_values[j].length, values->output_values[j].name, head->result.type_tuple.output_identifiers[j].name)); logic_fail_test(test, result, type_tuple_element_string_should_be(i + 1, j, values->output_values[j].name, head->result.type_tuple.output_identifiers[j].name));
return TRUE; return TRUE;
} if (head->result.type_tuple.output_identifiers[j].is_literal) { } if (head->result.type_tuple.output_identifiers[j].is_literal) {
logic_fail_test(test, result, type_tuple_element_boolean_should_be(i + 1, j, TRUE)); logic_fail_test(test, result, type_tuple_element_boolean_should_be(i + 1, j, TRUE));
@ -606,12 +589,12 @@ Boolean test_type_tuple_value(LexerTest *test, LexerResult result, size_t i, Tes
return FALSE; return FALSE;
} }
Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, TestErrorMessage *error) { Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr *error) {
LexerTokenResult *head = get_token(result.result, i); LexerTokenResult *head = get_token(result.result, i);
if (head->type != SLS_ERROR) { if (head->type != SLS_ERROR) {
logic_fail_test(test, result, token_should_be_error(i + 1, *error, head->result.type)); logic_fail_test(test, result, token_should_be_error(i + 1, *error, head->result.type));
return TRUE; return TRUE;
} if (strncmp(head->error.message, error->message, error->length+1) != 0) { } if (sls_str_cmp(head->error.message, *error) != 0) {
logic_fail_test(test, result, error_should_be(i + 1, *error, head->error)); logic_fail_test(test, result, error_should_be(i + 1, *error, head->error));
return TRUE; return TRUE;
} }

File diff suppressed because it is too large Load Diff

224
SLS_C/tests/string_tests.c Normal file
View File

@ -0,0 +1,224 @@
// Kyler Olsen
// YREA SLS
// String Tests
// November 2025
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "sls/string.h"
#include "sls/lexer.h"
#include "sls/errors.h"
#include "tests/tests.h"
static const size_t NUM_STRING_TESTS = 10;
static TestResult pass_string_test(SlsStr test_name) {
TestResult result = {.name = test_name, .status = TEST_NOT_IMPLEMENTED};
result.status = TEST_PASS;
return result;
}
static TestResult fail_string_test(SlsStr test_name, SlsStr message) {
TestResult result = { .name = test_name, .status = TEST_NOT_IMPLEMENTED };
result.status = TEST_ERROR;
result.message = sls_str_cpy(message);
if (result.message.str == NULL) {
result.error = (SlsError){ SLS_STR("Out Of Memory Error."), 1 };
result.status = TEST_ERROR;
}
return result;
}
// Test sls_str_malloc and sls_str_cpy
static TestResult test_malloc_and_copy() {
const char* original = "Hello, SLS!";
SlsStr s = sls_str_malloc(original, strlen(original));
if (!s.str) return fail_string_test(SLS_STR("test_malloc_and_copy"), SLS_STR("Allocation failed"));
if (strcmp(s.str, original) != 0) {
sls_str_free(&s);
return fail_string_test(SLS_STR("test_malloc_and_copy"), SLS_STR("Copied string mismatch"));
}
SlsStr copy = sls_str_cpy(s);
if (!copy.str || strcmp(copy.str, original) != 0) {
sls_str_free(&s);
sls_str_free(&copy);
return fail_string_test(SLS_STR("test_malloc_and_copy"), SLS_STR("sls_str_cpy failed"));
}
sls_str_free(&s);
sls_str_free(&copy);
return pass_string_test(SLS_STR("test_malloc_and_copy"));
}
// Test sls_str_cmp
static TestResult test_compare_strings() {
SlsStr a = sls_str_malloc("abc", 3);
SlsStr b = sls_str_malloc("abc", 3);
SlsStr c = sls_str_malloc("abd", 3);
if (sls_str_cmp(a, b) != 0) goto fail;
if (sls_str_cmp(a, c) >= 0) goto fail;
if (sls_str_cmp(c, a) <= 0) goto fail;
sls_str_free(&a);
sls_str_free(&b);
sls_str_free(&c);
return pass_string_test(SLS_STR("test_compare_strings"));
fail:
sls_str_free(&a);
sls_str_free(&b);
sls_str_free(&c);
return fail_string_test(SLS_STR("test_compare_strings"), SLS_STR("Comparison test failed"));
}
// Test sls_format for basic placeholders
static TestResult test_format_basic_placeholders() {
SlsStr fmt = SLS_STR("Char: %c, Int: %d, Long: %l, Unsigned: %u, Size: %z, Float: %f, C-String: %y");
const char *s = "Test";
SlsStr result = sls_format(fmt, 'X', -42, (int64_t)1234567890123, (uint64_t)9876543210, (size_t)1024, 3.14, s);
if (!result.str) return fail_string_test(SLS_STR("test_format_basic_placeholders"), SLS_STR("Formatting returned NULL"));
const char* expected = "Char: X, Int: -42, Long: 1234567890123, Unsigned: 9876543210, Size: 1024, Float: 3.14, C-String: Test";
if (strcmp(result.str, expected) != 0) {
sls_str_free(&result);
return fail_string_test(SLS_STR("test_format_basic_placeholders"), SLS_STR("Formatted string mismatch"));
}
sls_str_free(&result);
return pass_string_test(SLS_STR("test_format_basic_placeholders"));
}
// Test sls_format for SLS types: %s, %t, %a, %i, %e, %b
static TestResult test_format_sls_types() {
// Placeholder values
SlsStr sls_str_val = SLS_STR("SLS_STRING");
TokenType tok = 0; // Assuming TOKEN_TYPES_NAMES[0] exists
ArrayType arr = 0; // Assuming ARRAY_TYPES_NAMES[0] exists
IntegerBuiltInType ib = 0; // Assuming INTEGER_TYPES_NAMES[0] exists
SlsError err = {SLS_STR("ErrorMessage"), 1};
Boolean boolean_true = TRUE;
SlsStr fmt = SLS_STR("Str:%s Tok:%t Arr:%a IntType:%i Err:%e Bool:%b");
SlsStr result = sls_format(fmt, sls_str_val, tok, arr, ib, err, boolean_true);
if (!result.str) return fail_string_test(SLS_STR("test_format_sls_types"), SLS_STR("Formatting returned NULL"));
char expected[256];
snprintf(expected, 256, "Str:%s Tok:%s Arr:%s IntType:%s Err:%s Bool:TRUE",
sls_str_val.str,
TOKEN_TYPES_NAMES[tok],
ARRAY_TYPES_NAMES[arr],
INTEGER_TYPES_NAMES[ib],
err.message.str);
if (strcmp(result.str, expected) != 0) {
sls_str_free(&result);
return fail_string_test(SLS_STR("test_format_sls_types"), SLS_STR("Formatted string mismatch for SLS types"));
}
sls_str_free(&result);
return pass_string_test(SLS_STR("test_format_sls_types"));
}
// Test sls_format with %% escape
static TestResult test_format_percent_escape() {
SlsStr fmt = SLS_STR("Progress: 100%% complete");
SlsStr result = sls_format(fmt);
if (!result.str) return fail_string_test(SLS_STR("test_format_percent_escape"), SLS_STR("Formatting returned NULL"));
if (strcmp(result.str, "Progress: 100% complete") != 0) {
sls_str_free(&result);
return fail_string_test(SLS_STR("test_format_percent_escape"), SLS_STR("Percent escape failed"));
}
sls_str_free(&result);
return pass_string_test(SLS_STR("test_format_percent_escape"));
}
// Test sls_str_new allocation and zero-init
static TestResult test_new_string_allocation() {
SlsStr s = sls_str_new(10);
if (!s.str) return fail_string_test(SLS_STR("test_new_string_allocation"), SLS_STR("Allocation failed"));
for (size_t i = 0; i < s.len; i++) {
if (s.str[i] != 0) {
sls_str_free(&s);
return fail_string_test(SLS_STR("test_new_string_allocation"), SLS_STR("Memory not zero-initialized"));
}
}
sls_str_free(&s);
return pass_string_test(SLS_STR("test_new_string_allocation"));
}
// Test empty string
static TestResult test_empty_string() {
SlsStr s = sls_str_malloc("", 0);
if (!s.str || s.len != 0) return fail_string_test(SLS_STR("test_empty_string"), SLS_STR("Empty string allocation failed"));
sls_str_free(&s);
return pass_string_test(SLS_STR("test_empty_string"));
}
// Test long string
static TestResult test_long_string() {
size_t len = 1024;
char* long_str = malloc(len + 1);
for (size_t i = 0; i < len; i++) long_str[i] = 'A';
long_str[len] = '\0';
SlsStr s = sls_str_malloc(long_str, len);
if (!s.str || s.len != len) {
free(long_str);
return fail_string_test(SLS_STR("test_long_string"), SLS_STR("Long string allocation failed"));
}
sls_str_free(&s);
free(long_str);
return pass_string_test(SLS_STR("test_long_string"));
}
// Test NULL pointer handling in sls_str_free
static TestResult test_free_null() {
SlsStr s = SLS_STR_NULL;
sls_str_free(&s); // Should safely do nothing
return pass_string_test(SLS_STR("test_free_null"));
}
// Test sls_str_nlen edge cases
static TestResult test_str_nlen_edge() {
const char* str = "ABCDE";
if (sls_str_nlen(str, 0) != 0) return fail_string_test(SLS_STR("test_str_nlen_edge"), SLS_STR("Maxlen=0 failed"));
if (sls_str_nlen(str, 3) != 3) return fail_string_test(SLS_STR("test_str_nlen_edge"), SLS_STR("Maxlen=3 failed"));
if (sls_str_nlen(str, 10) != 5) return fail_string_test(SLS_STR("test_str_nlen_edge"), SLS_STR("Maxlen>strlen failed"));
return pass_string_test(SLS_STR("test_str_nlen_edge"));
}
// Run all string tests
TestsReport run_string_tests() {
TestsReport report = {
.section = SLS_STR("string_tests"),
.count = NUM_STRING_TESTS,
.tests = malloc(sizeof(TestResult) * NUM_STRING_TESTS)
};
size_t i = 0;
report.tests[i++] = test_malloc_and_copy();
report.tests[i++] = test_compare_strings();
report.tests[i++] = test_format_basic_placeholders();
report.tests[i++] = test_format_sls_types();
report.tests[i++] = test_format_percent_escape();
report.tests[i++] = test_new_string_allocation();
report.tests[i++] = test_empty_string();
report.tests[i++] = test_long_string();
report.tests[i++] = test_free_null();
report.tests[i++] = test_str_nlen_edge();
return report;
}

View File

@ -7,9 +7,12 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include "sls/errors.h"
#include "tests/tests.h" #include "tests/tests.h"
const char *TEST_FILE_NAME = "TEST_FILE.SLS"; static const Boolean PRINT_SUCCESSFUL_TESTS = FALSE;
const SlsStr TEST_FILE_NAME = SLS_STR_CONST("TEST_FILE.SLS");
typedef struct { typedef struct {
uint16_t errored; uint16_t errored;
@ -21,45 +24,46 @@ typedef struct {
uint16_t total; uint16_t total;
} TestCounts; } TestCounts;
static void lexer_test_report(TestsReport reports, TestCounts *counts) { static void test_report(TestsReport reports, TestCounts *counts) {
counts->total += reports.count; counts->total += reports.count;
for (size_t i = 0; i < reports.count; i++) { for (size_t i = 0; i < reports.count; i++) {
switch (reports.tests[i].status) { switch (reports.tests[i].status) {
case TEST_ERROR: case TEST_ERROR:
// Bright Red // Bright Red
printf("\x1b[91mTest errored: %s\n\t%s\n\x1b[0m", reports.tests[i].name, reports.tests[i].error.message); printf("\x1b[91mTest errored: %s\n\t%s\n\x1b[0m", reports.tests[i].name.str, reports.tests[i].error.message.str);
counts->errored += 1; counts->errored += 1;
break; break;
case TEST_ERROR_FAIL: case TEST_ERROR_FAIL:
// Magenta // Magenta
printf("\x1b[35mLexing errored: %s\n\t%s\n\x1b[0m", reports.tests[i].name, reports.tests[i].error.message); printf("\x1b[35mLexing errored: %s\n\t%s\n\x1b[0m", reports.tests[i].name.str, reports.tests[i].error.message.str);
counts->error_failed += 1; counts->error_failed += 1;
break; break;
case TEST_LOGIC_ERROR_FAIL: case TEST_LOGIC_ERROR_FAIL:
// Red // Red
printf("\x1b[31mTest failed with lexical error: %s\n\t%s\n\x1b[0m", reports.tests[i].name, reports.tests[i].error.message); printf("\x1b[31mTest failed with lexical error: %s\n\t%s\n\x1b[0m", reports.tests[i].name.str, reports.tests[i].error.message.str);
counts->logic_error_failed += 1; counts->logic_error_failed += 1;
free(reports.tests[i].message); sls_str_free(&reports.tests[i].message);
break; break;
case TEST_LOGIC_FAIL: case TEST_LOGIC_FAIL:
// Red // Red
printf("\x1b[31mTest failed: %s\n\t%s\n\x1b[0m", reports.tests[i].name, reports.tests[i].message); printf("\x1b[31mTest failed: %s\n\t%s\n\x1b[0m", reports.tests[i].name.str, reports.tests[i].message.str);
counts->logic_failed += 1; counts->logic_failed += 1;
free(reports.tests[i].message); sls_str_free(&reports.tests[i].message);
break; break;
case TEST_PASS: case TEST_PASS:
// Green // Green
printf("\x1b[32mTest passed: %s\n\x1b[0m", reports.tests[i].name); if (PRINT_SUCCESSFUL_TESTS)
printf("\x1b[32mTest passed: %s\n\x1b[0m", reports.tests[i].name.str);
counts->passed += 1; counts->passed += 1;
break; break;
case TEST_NOT_IMPLEMENTED: case TEST_NOT_IMPLEMENTED:
// Blue // Blue
printf("\x1b[34mTest not implemented: %s\n\x1b[0m", reports.tests[i].name); printf("\x1b[34mTest not implemented: %s\n\x1b[0m", reports.tests[i].name.str);
counts->not_implemented += 1; counts->not_implemented += 1;
break; break;
default: default:
// Bright Red // Bright Red
printf("\x1b[91mTest errored: %s\n\tUnknown test result status.\n\x1b[0m", reports.tests[i].name); printf("\x1b[91mTest errored: %s\n\tUnknown test result status.\n\x1b[0m", reports.tests[i].name.str);
counts->errored += 1; counts->errored += 1;
break; break;
} }
@ -77,10 +81,16 @@ int main(void) {
.total = 0, .total = 0,
}; };
TestsReport lexer_reports = run_lexer_tests(); printf(" ========== SlsStr Tests ==========\n");
lexer_test_report(lexer_reports, &counts); test_report(run_string_tests(), &counts);
printf(" ========== Lexer Tests ==========\n");
test_report(run_lexer_tests(), &counts);
printf(" ========== Hash Table Tests ==========\n");
test_report(run_hash_table_tests(), &counts);
printf(" ========== Extra Tests ==========\n");
test_report(run_extra_tests(), &counts);
printf(" ===== Tests Overview =====\n"); printf(" ========== Tests Overview ==========\n");
if (counts.errored > 0) if (counts.errored > 0)
// Bright Red // Bright Red
printf("\x1b[91m%d of %d tests encountered an error.\n\x1b[0m", counts.errored, counts.total); printf("\x1b[91m%d of %d tests encountered an error.\n\x1b[0m", counts.errored, counts.total);

4
SLS_Python/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
__pycache__/
.venv/
sls_python.egg-info/
dist/

19
SLS_Python/README.md Normal file
View File

@ -0,0 +1,19 @@
# SLS Python
This is the Python implementation for the YREA SLS interpreter.
## Building
```bash
cd SLS_Python
python -m venv .venv
source .venv/bin/activate
pip install build wheel "setuptools>=61.0"
# Install the backend (one-time setup)
pip install -e sls_build_backend
# Build with --no-isolation (required because backend is local)
python -m build --no-isolation
```

20
SLS_Python/pyproject.toml Normal file
View File

@ -0,0 +1,20 @@
# Build this file using 'python -m build --no-isolation'
[build-system]
requires = ["setuptools>=61.0", "wheel", "sls_build_backend"]
build-backend = "sls_build_backend"
[project]
name = "sls_python"
version = "0.0.2-alpha"
description = "Python reimplementation of the SLS C project"
authors = [ { name = "Kyler Olsen" } ]
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.10"
dependencies = []
[tool.setuptools]
packages = ["sls_py"]
[project.scripts]
sls_py = "sls_py.__main__:main"

View File

@ -0,0 +1,5 @@
"""Build backend for sls_python package."""
from .build_hooks import build_wheel, build_sdist
__all__ = ["build_wheel", "build_sdist"]

View File

@ -0,0 +1,4 @@
# Auto-generated during build
version = "{version}"
commit = "{commit}"
timestamp = "{timestamp}"

View File

@ -0,0 +1,23 @@
import subprocess
from datetime import datetime, timezone
version = "{version}"
try:
__result_hash = subprocess.check_output(
["git", "describe", "--always", "--dirty", "--abbrev=7"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
__result_date = subprocess.check_output(
["git", "show", "-s", "--format=%ci"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
commit = __result_hash + " " + __result_date
except:
commit = "unknown"
try:
timestamp = datetime.now(timezone.utc).isoformat() + "Z"
except:
timestamp = "unknown"

View File

@ -0,0 +1,17 @@
from setuptools.build_meta import build_wheel as _build_wheel
from setuptools.build_meta import build_sdist as _build_sdist
from .write_version import generate_version, generate_version_dev
def build_wheel(*args, **kwargs):
generate_version()
o = _build_wheel(*args, **kwargs)
generate_version_dev()
return o
def build_sdist(*args, **kwargs):
generate_version()
o = _build_sdist(*args, **kwargs)
generate_version_dev()
return o

View File

@ -0,0 +1,14 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "sls-build-backend"
version = "0.1.0"
description = "Build backend for sls_python"
authors = [{name = "Kyler Olsen"}]
requires-python = ">=3.10"
[tool.setuptools]
packages = ["sls_build_backend"]
package-dir = {"" = ".."}

View File

@ -0,0 +1,91 @@
import subprocess
import datetime
import pathlib
try:
import tomllib # Python 3.11+
except Exception: # pragma: no cover - older Pythons
tomllib = None
root = pathlib.Path(__file__).resolve().parents[1]
# templates live inside the backend package
template = root / "sls_build_backend" / "_version.py.in"
template_dev = root / "sls_build_backend" / "_version_dev.py.in"
output = root / "sls_py" / "_version.py"
def get_commit():
try:
result_hash = subprocess.check_output(
["git", "describe", "--always", "--dirty", "--abbrev=7"],
cwd=str(root),
stderr=subprocess.DEVNULL,
text=True,
).strip()
result_date = subprocess.check_output(
["git", "show", "-s", "--format=%ci"],
cwd=str(root),
stderr=subprocess.DEVNULL,
text=True,
).strip()
return f"{result_hash} {result_date}"
except Exception:
return "unknown"
def get_timestamp():
return datetime.datetime.now(datetime.timezone.utc).isoformat() + "Z"
def _get_version_from_pyproject(root_path: pathlib.Path):
py = root_path / "pyproject.toml"
if not py.exists() or tomllib is None:
return None
try:
data = tomllib.loads(py.read_text())
except Exception:
return None
# PEP 621
version = data.get("project", {}).get("version")
if version:
return version
# Some projects put metadata under tool.setuptools
version = data.get("tool", {}).get("setuptools", {}).get("version")
return version
def _determine_version(root_path: pathlib.Path):
v = _get_version_from_pyproject(root_path)
if v:
return v
return "unknown"
def generate_version_dev():
version = _determine_version(root)
if not template_dev.exists():
# write a minimal generated file if dev template missing
output.write_text(f"version = \"{version}\"\ncommit = \"unknown\"\ntimestamp = \"unknown\"\n")
return
text = template_dev.read_text()
text = text.format(version=version)
output.write_text(text)
def generate_version():
version = _determine_version(root)
commit = get_commit()
timestamp = get_timestamp()
if not template.exists():
# fallback: write a minimal file
output.write_text(
f"version = \"{version}\"\ncommit = \"{commit}\"\ntimestamp = \"{timestamp}\"\n"
)
return
text = template.read_text()
text = text.format(version=version, commit=commit, timestamp=timestamp)
output.write_text(text)

142
SLS_Python/sls_py.pyi Normal file
View File

@ -0,0 +1,142 @@
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Callable, Dict, List, Optional
def main() -> int:
...
SLS_NAME: str
SLS_VERSION: str
SLS_COMMIT: str
INTERPRETER_NAME: str
INTERPRETER_VER: int
MODULE_TIMESTAMP: str
SLS_INFO_STRING_1: str
SLS_INFO_STRING_2: str
class LexerInfo:
filename: str
source_code: str
pos: int
column: int
line: int
def __init__(self, filename: str = "", source_code: str = ""): ...
class TokenType(Enum):
EOF = auto()
IDENTIFIER = auto()
INTEGER = auto()
FLOAT = auto()
DOUBLE = auto()
CHARACTER = auto()
STRING = auto()
BOOLEAN = auto()
ARRAY = auto()
TOKEN_STRING = auto()
TYPE_TUPLE = auto()
@dataclass
class Identifier:
name: str
is_literal: bool
class IntegerBuiltInType(Enum):
I64 = auto()
I32 = auto()
I16 = auto()
I8 = auto()
U64 = auto()
U32 = auto()
U16 = auto()
U8 = auto()
@dataclass
class IntegerLiteral:
value: int
type: IntegerBuiltInType
@dataclass
class TokenString:
tokens: List["Token"] = field(default_factory=list)
def deep_copy(self) -> TokenString: ...
@dataclass
class Token:
type: TokenType
identifier: Optional[Identifier] = None
integer_literal: Optional[IntegerLiteral] = None
float_literal: Optional[float] = None
double_literal: Optional[float] = None
character_literal: Optional[int] = None
string_literal: Optional[str] = None
boolean_literal: Optional[bool] = None
token_string: Optional[TokenString] = None
def lexical_analysis(lexer_info: LexerInfo) -> List[Token]:
...
class StackType(Enum):
IDENTIFIER = auto()
I64 = auto()
I32 = auto()
I16 = auto()
I8 = auto()
U64 = auto()
U32 = auto()
U16 = auto()
U8 = auto()
FLOAT = auto()
DOUBLE = auto()
CHARACTER = auto()
BOOLEAN = auto()
TOKEN_STRING = auto()
CALLABLE = auto()
@dataclass
class StackEntry:
type: StackType
value: object
class FunctionType(Enum):
TOKEN_STRING = auto()
BUILTIN = auto()
@dataclass
class FunctionItem:
type: FunctionType
token_string: Optional[TokenString] = None
builtin: Optional[Callable[["InterpreterState"], bool]] = None
@classmethod
def from_token_string(cls, ts: TokenString) -> "FunctionItem": ...
@classmethod
def from_builtin(cls, fn: Callable[["InterpreterState"], bool]) -> "FunctionItem": ...
class InterpreterState:
stack: List[StackEntry]
functions: Dict[str, FunctionItem]
def __init__(self): ...
def push(self, entry: StackEntry) -> None: ...
def pop(self) -> Optional[StackEntry]: ...
def top(self) -> Optional[StackEntry]: ...
def add_function(self, name: str, item: FunctionItem) -> None: ...
def get_function(self, name: str) -> Optional[FunctionItem]: ...
def push_token(self, token: Token) -> bool: ...
def execute_func(self, key: str) -> bool: ...
def execute_token_string(self, token_string: TokenString) -> bool: ...
def execute(self, token: Token) -> bool: ...

View File

@ -0,0 +1,56 @@
from .__main__ import main
from .meta import (
SLS_NAME,
SLS_VERSION,
SLS_COMMIT,
INTERPRETER_NAME,
INTERPRETER_VER,
MODULE_TIMESTAMP,
SLS_INFO_STRING_1,
SLS_INFO_STRING_2,
)
from .lexer import (
LexerInfo,
TokenType,
Identifier,
IntegerBuiltInType,
IntegerLiteral,
TokenString,
Token,
lexical_analysis,
)
from .interpreter import (
StackType,
StackEntry,
FunctionType,
FunctionItem,
InterpreterState,
)
__all__ = [
"main",
"SLS_NAME",
"SLS_VERSION",
"SLS_COMMIT",
"INTERPRETER_NAME",
"INTERPRETER_VER",
"MODULE_TIMESTAMP",
"SLS_INFO_STRING_1",
"SLS_INFO_STRING_2",
"LexerInfo",
"TokenType",
"Identifier",
"IntegerBuiltInType",
"IntegerLiteral",
"TokenString",
"Token",
"lexical_analysis",
"StackType",
"StackEntry",
"FunctionType",
"FunctionItem",
"InterpreterState",
]

View File

@ -0,0 +1,33 @@
import sys
from .meta import print_version
from .repl import repl
from .file import run_file
def main() -> int:
args = sys.argv[1:]
version = False
filename = None
if len(args) == 1:
if args[0] in ("--version", "-v"):
version = True
else:
filename = args[0]
elif len(args) > 1:
print("Too many arguments!")
return 1
if version:
print_version()
return 0
if filename is not None:
return run_file(filename)
return repl()
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,23 @@
import subprocess
from datetime import datetime, timezone
version = "0.0.2-alpha"
try:
__result_hash = subprocess.check_output(
["git", "describe", "--always", "--dirty", "--abbrev=7"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
__result_date = subprocess.check_output(
["git", "show", "-s", "--format=%ci"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
commit = __result_hash + " " + __result_date
except:
commit = "unknown"
try:
timestamp = datetime.now(timezone.utc).isoformat() + "Z"
except:
timestamp = "unknown"

1267
SLS_Python/sls_py/builtin.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,268 @@
#!/usr/bin/env python3
"""
HP-Style RPN Calculator using Stack Language Backend
Implements classic HP calculator interface with stack display
"""
import tkinter as tk
from tkinter import ttk, font as tkfont
from .. import (
InterpreterState,
LexerInfo,
lexical_analysis,
TokenType,
StackType,
)
class SlsCalculator:
def __init__(self, root):
self.root = root
self.root.title("SLS Calculator")
# self.root.geometry("450x650")
self.root.resizable(False, False)
# Initialize interpreter
self.interp = InterpreterState()
self.lexer = LexerInfo()
# Current entry buffer
self.entry_buffer = ""
self.is_new_entry = True
# Set up UI
self.create_widgets()
def create_widgets(self):
"""Create all calculator widgets"""
# Main container
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # type: ignore
self.create_stack_display(main_frame)
# Entry display
self.create_entry_display(main_frame)
# Button grid
self.create_button_grid(main_frame)
def create_stack_display(self, parent):
"""Create the 4-level stack display"""
stack_frame = ttk.LabelFrame(parent, text="Stack", padding="5")
stack_frame.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10)) # type: ignore
self.stack_labels = []
for i in range(4):
label = ttk.Label(stack_frame,
text=f"{3-i}:",
anchor='e',
width=30)
label.grid(row=i, column=0, sticky=(tk.W, tk.E), pady=2) # type: ignore
self.stack_labels.append(label)
def create_entry_display(self, parent):
"""Create the current entry display"""
entry_frame = ttk.Frame(parent)
entry_frame.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10)) # type: ignore
self.entry_label = ttk.Label(entry_frame,
text="0",
anchor='e',
font=('Courier', 16))
self.entry_label.grid(row=0, column=0, sticky=(tk.W, tk.E)) # type: ignore
def create_button_grid(self, parent):
"""Create the calculator button grid"""
buttons = [
[('', self.sqrt), ('', self.square), ('1/x', self.reciprocal), ('NULL', lambda: None)],
[('ENTER', lambda: self.enter(True)), ('SWAP', self.swap), ('<-', self.drop), ('NULL', lambda: None)],
[('7', lambda: self.digit('7')), ('8', lambda: self.digit('8')), ('9', lambda: self.digit('9')), ('÷', self.divide)],
[('4', lambda: self.digit('4')), ('5', lambda: self.digit('5')), ('6', lambda: self.digit('6')), ('×', self.multiply)],
[('1', lambda: self.digit('1')), ('2', lambda: self.digit('2')), ('3', lambda: self.digit('3')), ('', self.subtract)],
[('0', lambda: self.digit('0')), ('.', self.decimal), ('±', self.negate), ('+', self.add)],
]
for row_idx, row in enumerate(buttons, start=2):
for col_idx, (text, command) in enumerate(row):
if text != "NULL":
btn = ttk.Button(parent,
text=text,
command=command,
width=8)
btn.grid(row=row_idx, column=col_idx, padx=2, pady=2, sticky=(tk.W, tk.E, tk.N, tk.S)) # type: ignore
# Keyboard bindings
self.root.bind('<Delete>', lambda e: self.drop())
self.root.bind('<Return>', lambda e: self.enter(True))
self.root.bind('<KP_Enter>', lambda e: self.enter(True))
for i in range(10):
self.root.bind(str(i), lambda e, n=str(i): self.digit(n))
self.root.bind('.', lambda e: self.decimal())
self.root.bind('+', lambda e: self.add())
self.root.bind('-', lambda e: self.subtract())
self.root.bind('*', lambda e: self.multiply())
self.root.bind('/', lambda e: self.divide())
def digit(self, d):
"""Handle digit button press"""
if self.is_new_entry:
self.entry_buffer = d
self.is_new_entry = False
else:
self.entry_buffer += d
self.update_entry_display()
def decimal(self):
"""Handle decimal point"""
if self.is_new_entry:
self.entry_buffer = "0."
self.is_new_entry = False
elif '.' not in self.entry_buffer:
self.entry_buffer += '.'
self.update_entry_display()
def negate(self):
"""Toggle sign of current entry"""
if self.is_new_entry:
# Negate top of stack
self.execute_code("0 swap -")
else:
if self.entry_buffer.startswith('-'):
self.entry_buffer = self.entry_buffer[1:]
else:
self.entry_buffer = '-' + self.entry_buffer
self.update_entry_display()
def enter(self, key=False):
"""Push current entry onto stack"""
if self.entry_buffer:
self.execute_code(self.entry_buffer)
self.entry_buffer = ""
self.is_new_entry = True
elif key:
self.execute_code("dup")
self.update_displays()
def add(self):
"""Addition operation"""
self.enter()
self.execute_code("+")
self.update_displays()
def subtract(self):
"""Subtraction operation"""
self.enter()
self.execute_code("-")
self.update_displays()
def multiply(self):
"""Multiplication operation"""
self.enter()
self.execute_code("*")
self.update_displays()
def divide(self):
"""Division operation"""
self.enter()
self.execute_code("/")
self.update_displays()
def sqrt(self):
"""Square root operation"""
self.enter()
self.execute_code("sqrt")
self.update_displays()
def square(self):
"""Square operation"""
self.enter()
self.execute_code("dup *")
self.update_displays()
def reciprocal(self):
"""Reciprocal (1/x) operation"""
self.enter()
self.execute_code("1 swap /")
self.update_displays()
def swap(self):
"""Swap top two stack items"""
self.enter()
self.execute_code("swap")
self.update_displays()
def drop(self):
"""Drop top stack item"""
self.enter()
self.execute_code("drop")
self.update_displays()
def execute_code(self, code):
"""Execute stack language code"""
try:
self.lexer.source_code = code
self.lexer.pos = 0
self.lexer.column = 1
self.lexer.line = 1
tokens = lexical_analysis(self.lexer)
for token in tokens:
if token.type == TokenType.EOF:
break
if not self.interp.execute(token):
print(f"Error executing: {code}")
break
except Exception as e:
print(f"Exception: {e}")
def update_entry_display(self):
"""Update the entry display"""
display_text = self.entry_buffer if self.entry_buffer else "0"
self.entry_label.config(text=display_text)
def update_displays(self):
"""Update both entry and stack displays"""
self.update_entry_display()
self.update_stack_display()
def update_stack_display(self):
"""Update the 4-level stack display"""
stack_size = len(self.interp.stack)
for i in range(4):
level = 3 - i # T3, T2, T1, T0
if stack_size > level:
entry = self.interp.stack[-(level + 1)]
value_str = self.format_stack_entry(entry)
self.stack_labels[i].config(text=f"{level}: {value_str}")
else:
self.stack_labels[i].config(text=f"{level}:")
def format_stack_entry(self, entry):
"""Format a stack entry for display"""
if entry.type == StackType.I64:
return str(entry.value)
elif entry.type == StackType.DOUBLE:
val = entry.value
# Format with appropriate precision
if abs(val) < 1e-10 and val != 0:
return f"{val:.6e}"
elif abs(val) > 1e10:
return f"{val:.6e}"
else:
return f"{val:.10g}"
elif entry.type == StackType.BOOLEAN:
return str(entry.value)
else:
return str(entry.value)
def main():
root = tk.Tk()
app = SlsCalculator(root)
root.mainloop()
if __name__ == "__main__":
main()

37
SLS_Python/sls_py/file.py Normal file
View File

@ -0,0 +1,37 @@
from pathlib import Path
from .lexer import LexerInfo, lexical_analysis, Token
from .interpreter import InterpreterState
def exec_file(interpreter_state: InterpreterState, filename: str) -> bool:
path = Path(filename)
if not path.exists():
print(f"Cannot read file: {filename}")
return False
try:
code = path.read_text()
except Exception:
print(f"Cannot read file: {filename}")
return False
lexer_info = LexerInfo(filename=filename, source_code=code)
tokens: list[Token] = lexical_analysis(lexer_info)
for tok in tokens:
if not interpreter_state.execute(tok):
print("A runtime error occurred!")
return False
return True
def run_file(filename: str) -> int:
print(f"Executing file: {filename}")
interpreter_state = InterpreterState()
success = exec_file(interpreter_state, filename)
return 0 if success else 1

View File

@ -0,0 +1,230 @@
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum, auto
from typing import Callable, Dict, List, Optional
from .lexer import (
Token,
TokenType,
TokenString,
IntegerLiteral,
IntegerBuiltInType,
)
from .interpreter_types import StackType
from .builtin import load_builtins
@dataclass
class StackEntry:
type: StackType
value: object # Identifier | int | float | bool | TokenString | FunctionItem etc.
class FunctionType(Enum):
TOKEN_STRING = auto()
BUILTIN = auto()
type FunctionData = TokenString | Callable[["InterpreterState"], bool]
@dataclass
class FunctionItem:
type: FunctionType
token_string: Optional[TokenString] = None
builtin: Optional[Callable[["InterpreterState"], bool]] = None
@classmethod
def from_item(cls, item: FunctionData) -> "FunctionItem":
if isinstance(item, TokenString):
return cls.from_token_string(item)
else:
return cls.from_builtin(item)
@classmethod
def from_token_string(cls, ts: TokenString) -> "FunctionItem":
return cls(type=FunctionType.TOKEN_STRING, token_string=ts)
@classmethod
def from_builtin(cls, fn: Callable[["InterpreterState"], bool]) -> "FunctionItem":
return cls(type=FunctionType.BUILTIN, builtin=fn)
class InterpreterState:
"""
Interpreter state holds:
- stack: list of StackEntry (top of stack is stack[-1])
- functions: dict mapping names to FunctionItem
"""
def __init__(self):
self.stack: List[StackEntry] = []
self.functions: Dict[str, FunctionItem] = {}
if load_builtins is not None:
ok = load_builtins(self)
if not ok:
raise RuntimeError("Failed to load builtins")
# -------------------------
# stack helpers
# -------------------------
def push(self, entry: StackEntry) -> None:
self.stack.append(entry)
def pop(self) -> Optional[StackEntry]:
if not self.stack:
return None
return self.stack.pop()
def top(self) -> Optional[StackEntry]:
if not self.stack:
return None
return self.stack[-1]
# -------------------------
# function table
# -------------------------
def add_function(self, name: str, item: FunctionData) -> None:
self.functions[name] = FunctionItem.from_item(item)
def get_function(self, name: str) -> Optional[FunctionItem]:
return self.functions.get(name)
# -------------------------
# execution primitives
# -------------------------
def push_token(self, token: Token) -> bool:
"""
Push a token onto the stack. Returns True on success, False on failure.
Mirrors the logic from the C implementation:
- disallow TOKEN_STRING, TOKEN_ARRAY, TOKEN_TYPE_TUPLE
- return True for TOKEN_EOF (no-op)
"""
ttype = token.type
if ttype == TokenType.EOF:
return True
# map token -> stack type + extract value
if ttype == TokenType.IDENTIFIER:
entry = StackEntry(StackType.IDENTIFIER, token.identifier)
self.push(entry)
return True
if ttype == TokenType.INTEGER:
ilit: IntegerLiteral = token.integer_literal # type: ignore
itype = ilit.type
# choose stack type explicitly to mirror C widths
if itype == IntegerBuiltInType.I64:
st = StackType.I64
val = int(ilit.value)
elif itype == IntegerBuiltInType.I32:
st = StackType.I32
val = int(ilit.value) & 0xFFFFFFFF # mimic truncation if desired
elif itype == IntegerBuiltInType.I16:
st = StackType.I16
val = int(ilit.value) & 0xFFFF
elif itype == IntegerBuiltInType.I8:
st = StackType.I8
val = int(ilit.value) & 0xFF
elif itype == IntegerBuiltInType.U64:
st = StackType.U64
val = int(ilit.value)
elif itype == IntegerBuiltInType.U32:
st = StackType.U32
val = int(ilit.value) & 0xFFFFFFFF
elif itype == IntegerBuiltInType.U16:
st = StackType.U16
val = int(ilit.value) & 0xFFFF
elif itype == IntegerBuiltInType.U8:
st = StackType.U8
val = int(ilit.value) & 0xFF
else:
return False
self.push(StackEntry(st, val))
return True
if ttype == TokenType.FLOAT:
self.push(StackEntry(StackType.FLOAT, token.float_literal))
return True
if ttype == TokenType.DOUBLE:
self.push(StackEntry(StackType.DOUBLE, token.double_literal))
return True
if ttype == TokenType.CHARACTER:
self.push(StackEntry(StackType.CHARACTER, token.character_literal))
return True
if ttype == TokenType.STRING:
# C returned FALSE for TOKEN_STRING
return False
if ttype == TokenType.BOOLEAN:
self.push(StackEntry(StackType.BOOLEAN, token.boolean_literal))
return True
if ttype == TokenType.ARRAY:
# C returned FALSE for arrays
return False
if ttype == TokenType.TOKEN_STRING:
# copy token string to mimic C copy semantics
ts_copy = token.token_string.deep_copy() # type: ignore
self.push(StackEntry(StackType.TOKEN_STRING, ts_copy))
return True
if ttype == TokenType.TYPE_TUPLE:
# C returned FALSE for type tuples
return False
# unknown token type
return False
def execute_func(self, key: str) -> bool:
"""
Look up a function by name and execute it.
Builtin functions are callables that accept InterpreterState and return bool.
Token-string functions are executed via execute_token_string.
"""
func = self.get_function(key)
if func is None:
return False
if func.type == FunctionType.BUILTIN:
if func.builtin is None:
return False
return func.builtin(self)
if func.type == FunctionType.TOKEN_STRING:
if func.token_string is None:
return False
return self.execute_token_string(func.token_string)
return False
def execute_token_string(self, token_string: TokenString) -> bool:
"""
Execute each token in a TokenString.
If token is an identifier and not `is_literal`, treat it as a function call.
Otherwise push token onto the stack.
"""
for tok in token_string.tokens:
if tok.type == TokenType.IDENTIFIER and tok.identifier is not None and not tok.identifier.is_literal:
rv = self.execute_func(tok.identifier.name)
else:
rv = self.push_token(tok)
if not rv:
return False
return True
def execute(self, token: Token) -> bool:
"""
Execute a single Token (this corresponds to processing a single lexer result).
If the token is an un-literal identifier -> function call, otherwise push it.
"""
if token.type == TokenType.IDENTIFIER and token.identifier is not None and not token.identifier.is_literal:
return self.execute_func(token.identifier.name)
else:
return self.push_token(token)

View File

@ -0,0 +1,18 @@
from enum import Enum, auto
class StackType(Enum):
IDENTIFIER = auto()
I64 = auto()
I32 = auto()
I16 = auto()
I8 = auto()
U64 = auto()
U32 = auto()
U16 = auto()
U8 = auto()
FLOAT = auto()
DOUBLE = auto()
CHARACTER = auto()
BOOLEAN = auto()
TOKEN_STRING = auto()
CALLABLE = auto()

904
SLS_Python/sls_py/lexer.py Normal file
View File

@ -0,0 +1,904 @@
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import List, Optional, Any, Union
# =====================================================================
# Basic Types
# =====================================================================
class LexerInfo:
filename: str
source_code: str
pos: int
column: int
line: int
def __init__(self, filename: str = "", source_code: str = ""):
self.filename = filename
self.source_code = source_code
self.pos = 0
self.column = 1
self.line = 1
# =====================================================================
# Token Types
# =====================================================================
class TokenType(Enum):
EOF = auto()
IDENTIFIER = auto()
INTEGER = auto()
FLOAT = auto()
DOUBLE = auto()
CHARACTER = auto()
STRING = auto()
BOOLEAN = auto()
ARRAY = auto()
TOKEN_STRING = auto()
TYPE_TUPLE = auto()
# =====================================================================
# Array Literal Types
# =====================================================================
class ArrayType(Enum):
IDENTIFIER = auto()
I64 = auto()
I32 = auto()
I16 = auto()
I8 = auto()
U64 = auto()
U32 = auto()
U16 = auto()
U8 = auto()
FLOAT = auto()
DOUBLE = auto()
CHARACTER = auto()
STRING = auto()
BOOLEAN = auto()
STRUCT_INLINE = auto()
# =====================================================================
# Identifier
# =====================================================================
@dataclass
class Identifier:
name: str
is_literal: bool
# =====================================================================
# Integer Literal Type
# =====================================================================
class IntegerBuiltInType(Enum):
I64 = auto()
I32 = auto()
I16 = auto()
I8 = auto()
U64 = auto()
U32 = auto()
U16 = auto()
U8 = auto()
@dataclass
class IntegerLiteral:
value: int
type: IntegerBuiltInType
# =====================================================================
# TokenString, TypeTuple, StructInline
# =====================================================================
@dataclass
class TokenString:
tokens: List["Token"] = field(default_factory=list)
def deep_copy(self) -> TokenString:
copied_tokens = [Token(
type=token.type,
identifier=token.identifier,
integer_literal=token.integer_literal,
float_literal=token.float_literal,
double_literal=token.double_literal,
character_literal=token.character_literal,
string_literal=token.string_literal,
boolean_literal=token.boolean_literal,
array_literal=token.array_literal.deep_copy() if token.array_literal else None,
token_string=token.token_string.deep_copy() if token.token_string else None,
type_tuple=token.type_tuple.deep_copy() if token.type_tuple else None
) for token in self.tokens]
return TokenString(tokens=copied_tokens)
@dataclass
class TypeTuple:
input_identifiers: List[Identifier] = field(default_factory=list)
output_identifiers: List[Identifier] = field(default_factory=list)
def deep_copy(self) -> TypeTuple:
copied_input_ids = [Identifier(name=id.name, is_literal=id.is_literal) for id in self.input_identifiers]
copied_output_ids = [Identifier(name=id.name, is_literal=id.is_literal) for id in self.output_identifiers]
return TypeTuple(input_identifiers=copied_input_ids, output_identifiers=copied_output_ids)
@dataclass
class StructInline:
values: List[Any]
name: str
# =====================================================================
# ArrayLiteral
# =====================================================================
@dataclass
class ArrayLiteral:
type: ArrayType
identifiers: Optional[List[Identifier]] = None
integer_literals: Optional[List[int]] = None
float_literals: Optional[List[float]] = None
double_literals: Optional[List[float]] = None
character_literals: Optional[List[int]] = None
string_literals: Optional[List[str]] = None
boolean_literals: Optional[List[bool]] = None
token_strings: Optional[List[TokenString]] = None
type_tuples: Optional[List[TypeTuple]] = None
struct_inline: Optional[StructInline] = None
shape: Optional[List[int]] = None
dimensions: int = 0
def deep_copy(self) -> ArrayLiteral:
copied_array = ArrayLiteral(type=self.type, dimensions=self.dimensions, shape=list(self.shape) if self.shape else None)
if self.identifiers is not None:
copied_array.identifiers = [Identifier(name=id.name, is_literal=id.is_literal) for id in self.identifiers]
if self.integer_literals is not None:
copied_array.integer_literals = list(self.integer_literals)
if self.float_literals is not None:
copied_array.float_literals = list(self.float_literals)
if self.double_literals is not None:
copied_array.double_literals = list(self.double_literals)
if self.character_literals is not None:
copied_array.character_literals = list(self.character_literals)
if self.string_literals is not None:
copied_array.string_literals = list(self.string_literals)
if self.boolean_literals is not None:
copied_array.boolean_literals = list(self.boolean_literals)
if self.token_strings is not None:
copied_array.token_strings = [ts.deep_copy() for ts in self.token_strings]
if self.type_tuples is not None:
copied_array.type_tuples = [tt.deep_copy() for tt in self.type_tuples]
if self.struct_inline is not None:
copied_array.struct_inline = StructInline(
values=list(self.struct_inline.values),
name=self.struct_inline.name
)
return copied_array
# =====================================================================
# Token
# =====================================================================
@dataclass
class Token:
type: TokenType
identifier: Optional[Identifier] = None
integer_literal: Optional[IntegerLiteral] = None
float_literal: Optional[float] = None
double_literal: Optional[float] = None
character_literal: Optional[int] = None
string_literal: Optional[str] = None
boolean_literal: Optional[bool] = None
array_literal: Optional[ArrayLiteral] = None
token_string: Optional[TokenString] = None
type_tuple: Optional[TypeTuple] = None
# =====================================================================
# File Info and Results
# =====================================================================
@dataclass
class FileInfo:
filename: str
line: int
column: int
length: int = 0
lines: int = 0
@dataclass
class LexerError(Exception):
message: str
file_info: FileInfo
# =====================================================================
# Numeric Type Flags
# =====================================================================
class NumericType(Enum):
F64 = auto()
F32 = auto()
I64 = auto()
I32 = auto()
I16 = auto()
I8 = auto()
U64 = auto()
U32 = auto()
U16 = auto()
U8 = auto()
class NumericLiteralType(Enum):
BINARY = auto()
OCTAL = auto()
DECIMAL = auto()
HEXADECIMAL = auto()
FLOAT = auto()
EXPONENTIAL = auto()
# =====================================================================
# Lexer Implementation
# =====================================================================
class Lexer:
def __init__(self, info: LexerInfo):
self.info = info
def peek(self) -> str:
if self.info.pos >= len(self.info.source_code):
return '\0'
return self.info.source_code[self.info.pos]
def far_peek(self, offset: int) -> str:
pos = self.info.pos + offset
if pos >= len(self.info.source_code):
return '\0'
return self.info.source_code[pos]
def seek(self, index: int) -> str:
if index >= len(self.info.source_code):
return '\0'
return self.info.source_code[index]
def advance(self) -> str:
if self.info.pos < len(self.info.source_code):
if self.info.source_code[self.info.pos] == '\n':
self.info.line += 1
self.info.column = 1
else:
self.info.column += 1
self.info.pos += 1
return self.peek()
def get_file_info(self, start: int, start_line: int) -> FileInfo:
return FileInfo(
filename=self.info.filename,
line=self.info.line,
column=self.info.column,
length=self.info.pos - start,
lines=self.info.line - start_line
)
def get_token_text(self, start: int) -> str:
return self.info.source_code[start:self.info.pos]
def skip_comments_and_whitespace(self):
while True:
c = self.peek()
# Skip comments
if (c == '/' and self.far_peek(1) == '/') or c == '#':
while self.peek() not in ('\n', '\0'):
c = self.advance()
# Skip whitespace
if c.isspace():
self.advance()
continue
break
def is_identifier_continue(self, c: str) -> bool:
if not c.isprintable():
return False
if c == '/' and self.far_peek(1) == '/':
return False
if c in '{}[]()\'\"#':
return False
if c.isspace() or c == '\0':
return False
return True
def is_identifier_start(self) -> bool:
c = self.peek()
if c == ':' and self.far_peek(1) == ':':
c = self.far_peek(2)
return not c.isdigit() and self.is_identifier_continue(c)
# =====================================================================
# Integer Parsing Helpers
# =====================================================================
def create_binary_integer(self, start: int) -> int:
token = self.get_token_text(start)
negative = token[0] == '-'
i = 3 if negative else 2
value = 0
while i < len(token):
c = token[i]
if c.isspace() or c in '/:' or c == '\0':
break
if c in '._':
i += 1
continue
value *= 2
if c == '1':
value += 1
i += 1
if negative:
# Python handles negative integers naturally
value = -value
return value
def create_octal_integer(self, start: int) -> int:
token = self.get_token_text(start)
negative = token[0] == '-'
i = 3 if negative else 2
value = 0
while i < len(token):
c = token[i]
if c.isspace() or c in '/:' or c == '\0':
break
if c in '._':
i += 1
continue
value *= 8
if c.isdigit() and c < '8':
value += int(c)
i += 1
if negative:
value = -value
return value
def create_decimal_integer(self, start: int) -> int:
token = self.get_token_text(start)
negative = token[0] == '-'
i = 1 if negative else 0
value = 0
while i < len(token):
c = token[i]
if c.isspace() or c in '/:' or c == '\0':
break
if c == '_':
i += 1
continue
if c.isdigit():
value *= 10
value += int(c)
i += 1
if negative:
value = -value
return value
def create_hexadecimal_integer(self, start: int) -> int:
token = self.get_token_text(start)
negative = token[0] == '-'
i = 3 if negative else 2
value = 0
while i < len(token):
c = token[i]
if c.isspace() or c in '/:' or c == '\0':
break
if c in '._':
i += 1
continue
value *= 16
if c.isdigit():
value += int(c)
elif c.upper() in 'ABCDEF':
value += ord(c.upper()) - ord('A') + 10
i += 1
if negative:
value = -value
return value
def create_float(self, start: int) -> float:
token = self.get_token_text(start)
negative = token[0] == '-'
i = 1 if negative else 0
value = 0.0
fractional = 0
while i < len(token):
c = token[i]
if c.isspace() or c in '/:' or c == '\0':
break
if c == '_':
i += 1
continue
if c == '.':
fractional = 1
i += 1
continue
if fractional == 0:
value *= 10
else:
fractional *= 10
if c.isdigit():
digit = int(c)
if fractional == 0:
value += digit
else:
value += digit / fractional
i += 1
if negative:
value = -value
return value
# =====================================================================
# Integer Type Validation
# =====================================================================
def get_integer_type(self, numeric_type: NumericType) -> IntegerBuiltInType:
type_map = {
NumericType.I64: IntegerBuiltInType.I64,
NumericType.I32: IntegerBuiltInType.I32,
NumericType.I16: IntegerBuiltInType.I16,
NumericType.I8: IntegerBuiltInType.I8,
NumericType.U64: IntegerBuiltInType.U64,
NumericType.U32: IntegerBuiltInType.U32,
NumericType.U16: IntegerBuiltInType.U16,
NumericType.U8: IntegerBuiltInType.U8,
}
if numeric_type not in type_map:
raise ValueError("Encountered a Float where there should not be one.")
return type_map[numeric_type]
def validate_integer_range(self, value: int, int_type: IntegerBuiltInType, start: int, start_line: int):
ranges = {
IntegerBuiltInType.I64: (-2**63, 2**63 - 1),
IntegerBuiltInType.I32: (-2**31, 2**31 - 1),
IntegerBuiltInType.I16: (-2**15, 2**15 - 1),
IntegerBuiltInType.I8: (-2**7, 2**7 - 1),
IntegerBuiltInType.U64: (0, 2**64 - 1),
IntegerBuiltInType.U32: (0, 2**32 - 1),
IntegerBuiltInType.U16: (0, 2**16 - 1),
IntegerBuiltInType.U8: (0, 2**8 - 1),
}
min_val, max_val = ranges[int_type]
if value < min_val or value > max_val:
type_name = int_type.name.lower()
raise LexerError(
f"Integer overflow: value exceeds range for {type_name}.",
self.get_file_info(start, start_line)
)
def create_integer_token(self, int_type: IntegerBuiltInType, value: int, start: int, start_line: int) -> Token:
self.validate_integer_range(value, int_type, start, start_line)
return Token(
type=TokenType.INTEGER,
integer_literal=IntegerLiteral(value=value, type=int_type)
)
def create_float_token(self, numeric_type: NumericType, start: int, start_line: int) -> Token:
value = self.create_float(start)
if numeric_type == NumericType.F64:
return Token(type=TokenType.DOUBLE, double_literal=value)
else:
return Token(type=TokenType.FLOAT, float_literal=value)
# =====================================================================
# Numeric Type Parsing
# =====================================================================
def parse_numeric_type(self, start: int, start_line: int, literal_type: NumericLiteralType) -> NumericType:
c = self.advance()
if c == 'f':
if literal_type not in (NumericLiteralType.DECIMAL, NumericLiteralType.FLOAT, NumericLiteralType.EXPONENTIAL):
raise LexerError("Invalid numeric literal: float type not allowed.", self.get_file_info(start, start_line))
c = self.advance()
if c == '6' and self.far_peek(1) == '4':
self.advance()
self.advance()
return NumericType.F64
elif c == '3' and self.far_peek(1) == '2':
self.advance()
self.advance()
return NumericType.F32
else:
raise LexerError("Invalid float type: must be of type 'f64' or 'f32'.", self.get_file_info(start, start_line))
elif c in 'iu':
if literal_type in (NumericLiteralType.FLOAT, NumericLiteralType.EXPONENTIAL):
raise LexerError("Invalid float type: must be of type 'f64' or 'f32'.", self.get_file_info(start, start_line))
unsigned = c == 'u'
c = self.advance()
if c == '6' and self.far_peek(1) == '4':
self.advance()
self.advance()
return NumericType.U64 if unsigned else NumericType.I64
elif c == '3' and self.far_peek(1) == '2':
self.advance()
self.advance()
return NumericType.U32 if unsigned else NumericType.I32
elif c == '1' and self.far_peek(1) == '6':
self.advance()
self.advance()
return NumericType.U16 if unsigned else NumericType.I16
elif c == '8':
self.advance()
return NumericType.U8 if unsigned else NumericType.I8
else:
prefix = 'unsigned' if unsigned else 'signed'
raise LexerError(f"Invalid {prefix} integer type.", self.get_file_info(start, start_line))
else:
raise LexerError("Invalid numeric type: type must start with 'f', 'i', or 'u'.", self.get_file_info(start, start_line))
# =====================================================================
# Numeric Literal Parsing
# =====================================================================
def parse_binary_integer(self, start: int, start_line: int) -> Token:
c = self.peek()
while c in '01_':
c = self.advance()
if c == ':':
numeric_type = self.parse_numeric_type(start, start_line, NumericLiteralType.BINARY)
int_type = self.get_integer_type(numeric_type)
value = self.create_binary_integer(start)
return self.create_integer_token(int_type, value, start, start_line)
if c.isspace() or c in '/\0':
value = self.create_binary_integer(start)
return self.create_integer_token(IntegerBuiltInType.I64, value, start, start_line)
raise LexerError(f"Invalid binary literal: unexpected '{c}' in binary integer.", self.get_file_info(start, start_line))
def parse_octal_integer(self, start: int, start_line: int) -> Token:
c = self.peek()
while c.isdigit() and c not in '89' or c == '_':
c = self.advance()
if c == ':':
numeric_type = self.parse_numeric_type(start, start_line, NumericLiteralType.OCTAL)
int_type = self.get_integer_type(numeric_type)
value = self.create_octal_integer(start)
return self.create_integer_token(int_type, value, start, start_line)
if c.isspace() or c in '/\0':
value = self.create_octal_integer(start)
return self.create_integer_token(IntegerBuiltInType.I64, value, start, start_line)
raise LexerError(f"Invalid octal literal: unexpected '{c}' in octal integer.", self.get_file_info(start, start_line))
def parse_hexadecimal_integer(self, start: int, start_line: int) -> Token:
c = self.peek()
while c in '0123456789ABCDEFabcdef_':
c = self.advance()
if c == ':':
numeric_type = self.parse_numeric_type(start, start_line, NumericLiteralType.HEXADECIMAL)
int_type = self.get_integer_type(numeric_type)
value = self.create_hexadecimal_integer(start)
return self.create_integer_token(int_type, value, start, start_line)
if c.isspace() or c in '/\0':
value = self.create_hexadecimal_integer(start)
return self.create_integer_token(IntegerBuiltInType.I64, value, start, start_line)
raise LexerError(f"Invalid hexadecimal literal: unexpected '{c}' in hexadecimal integer.", self.get_file_info(start, start_line))
def parse_exponential(self, start: int, start_line: int) -> Token:
raise NotImplementedError("Float exponential not implemented yet.")
def parse_float(self, start: int, start_line: int) -> Token:
c = self.peek()
while c.isdigit() or c == '_':
c = self.advance()
if c in 'eE':
return self.parse_exponential(start, start_line)
if c == ':':
numeric_type = self.parse_numeric_type(start, start_line, NumericLiteralType.FLOAT)
return self.create_float_token(numeric_type, start, start_line)
if c.isspace() or c in '/\0':
return self.create_float_token(NumericType.F64, start, start_line)
raise LexerError(f"Invalid float literal: unexpected '{c}' in float.", self.get_file_info(start, start_line))
def parse_decimal_integer(self, start: int, start_line: int) -> Token:
c = self.peek()
while c.isdigit() or c == '_':
c = self.advance()
if c == '.':
self.advance()
return self.parse_float(start, start_line)
if c in 'eE':
return self.parse_exponential(start, start_line)
if c == ':':
numeric_type = self.parse_numeric_type(start, start_line, NumericLiteralType.DECIMAL)
int_type = self.get_integer_type(numeric_type)
value = self.create_decimal_integer(start)
return self.create_integer_token(int_type, value, start, start_line)
if c.isspace() or c in '/\0':
value = self.create_decimal_integer(start)
return self.create_integer_token(IntegerBuiltInType.I64, value, start, start_line)
raise LexerError(f"Invalid decimal literal: unexpected '{c}' in decimal integer.", self.get_file_info(start, start_line))
def parse_numeric_literal(self, start: int, start_line: int) -> Token:
c = self.peek()
if c == '-':
c = self.advance()
if c == '0':
c = self.advance()
if c in 'bB':
self.advance()
return self.parse_binary_integer(start, start_line)
elif c in 'oO':
self.advance()
return self.parse_octal_integer(start, start_line)
elif c in 'xX':
self.advance()
return self.parse_hexadecimal_integer(start, start_line)
return self.parse_decimal_integer(start, start_line)
# =====================================================================
# Character Literal Parsing
# =====================================================================
def parse_character_literal(self, start: int, start_line: int) -> Token:
c = self.peek()
if c == '\'':
raise LexerError("Invalid character literal: empty character literal.", self.get_file_info(start, start_line))
if c == '\\':
c = self.advance()
escape_map = {
'n': '\n',
'r': '\r',
't': '\t',
'\\': '\\',
'\'': '\'',
'0': '\0'
}
if c in escape_map:
value = ord(escape_map[c])
else:
raise LexerError(f"Invalid character literal: unknown escape sequence '\\{c}'.", self.get_file_info(start, start_line))
elif c in '\n\r':
raise LexerError("Invalid character literal: unclosed character literal.", self.get_file_info(start, start_line))
else:
value = ord(c)
c = self.advance()
if c.isspace() or c in '/\0':
raise LexerError("Invalid character literal: unclosed character literal.", self.get_file_info(start, start_line))
elif c != '\'':
raise LexerError(f"Invalid character literal: unexpected '{c}' in character.", self.get_file_info(start, start_line))
self.advance()
return Token(type=TokenType.CHARACTER, character_literal=value)
# =====================================================================
# String Literal Parsing (stub)
# =====================================================================
def parse_string_literal(self, start: int, start_line: int) -> Token:
raise NotImplementedError("String literals not implemented yet.")
# =====================================================================
# Token String Parsing
# =====================================================================
def parse_token_string(self, start: int, start_line: int) -> Token:
tokens = []
self.advance() # Skip opening '{'
watchdog = 0
while self.peek() != '\0':
self.skip_comments_and_whitespace()
c = self.peek()
if c == '}':
self.advance()
return Token(type=TokenType.TOKEN_STRING, token_string=TokenString(tokens=tokens))
token = self.lexer_next()
tokens.append(token)
if token.type == TokenType.EOF:
break
watchdog += 1
if watchdog > 1000000:
raise LexerError("Watchdog triggered in token string.", self.get_file_info(start, start_line))
raise LexerError("Unclosed token string: missing closing brace '}'.", self.get_file_info(start, start_line))
# =====================================================================
# Array and Type Tuple Parsing (stubs)
# =====================================================================
def parse_array_literal(self, start: int, start_line: int) -> Token:
raise NotImplementedError("Array literals not implemented yet.")
def parse_type_tuple(self, start: int, start_line: int) -> Token:
raise NotImplementedError("Type tuples not implemented yet.")
# =====================================================================
# Identifier and Boolean Parsing
# =====================================================================
def parse_identifiers_and_booleans(self, start: int, start_line: int) -> Token:
c = self.peek()
is_literal = False
# Check for identifier literal (::)
if c == ':' and self.far_peek(1) == ':':
is_literal = True
self.advance()
self.advance()
c = self.peek()
# Read identifier name
name_chars = []
while self.is_identifier_continue(c):
if c == ':':
raise LexerError("Invalid identifier: ':' is not allowed in identifiers.", self.get_file_info(start, start_line))
if c == '.':
raise LexerError("Invalid identifier: '.' is not allowed in identifiers.", self.get_file_info(start, start_line))
name_chars.append(c)
c = self.advance()
name = ''.join(name_chars)
# Check for boolean literals
if name == 'false':
return Token(type=TokenType.BOOLEAN, boolean_literal=False)
elif name == 'true':
return Token(type=TokenType.BOOLEAN, boolean_literal=True)
else:
return Token(type=TokenType.IDENTIFIER, identifier=Identifier(name=name, is_literal=is_literal))
# =====================================================================
# Main Lexer Logic
# =====================================================================
def lexer_next(self) -> Token:
self.skip_comments_and_whitespace()
c = self.peek()
start = self.info.pos
start_line = self.info.line
# End of file
if c == '\0':
return Token(type=TokenType.EOF)
# Numeric literals (integers and floats)
if c.isdigit() or (c == '.' and self.far_peek(1).isdigit()) or (c == '-' and self.far_peek(1).isdigit()):
return self.parse_numeric_literal(start, start_line)
# Character literals
if c == '\'':
self.advance()
return self.parse_character_literal(start, start_line)
# String literals
if c == '"':
return self.parse_string_literal(start, start_line)
# Token strings
if c == '{':
return self.parse_token_string(start, start_line)
if c == '}':
self.advance()
raise LexerError("Unexpected closing brace '}' without matching opening brace.", self.get_file_info(start, start_line))
# Array literals
if c == '[':
return self.parse_array_literal(start, start_line)
if c == ']':
self.advance()
raise LexerError("Unexpected closing bracket ']' without matching opening bracket.", self.get_file_info(start, start_line))
# Type tuples
if c == '(':
return self.parse_type_tuple(start, start_line)
if c == ')':
self.advance()
raise LexerError("Unexpected closing parentheses ')' without matching opening parentheses.", self.get_file_info(start, start_line))
# Identifiers and booleans
if self.is_identifier_start():
return self.parse_identifiers_and_booleans(start, start_line)
# Check for malformed identifier literal
if c == ':':
self.advance()
if self.far_peek(1) == ':':
raise LexerError("Invalid identifier literal: empty identifier after '::'.", self.get_file_info(start, start_line))
else:
raise LexerError("Unexpected single colon ':'.", self.get_file_info(start, start_line))
# Unknown character
raise LexerError(f"Unexpected character: unexpected '{c}' during parsing.", self.get_file_info(start, start_line))
def lexical_analysis(self) -> List[Token]:
"""Main entry point for lexical analysis."""
tokens = []
while True:
try:
token = self.lexer_next()
tokens.append(token)
if token.type == TokenType.EOF:
break
except LexerError as e:
# Re-raise lexer errors
raise
return tokens
# =====================================================================
# Public API
# =====================================================================
def lexical_analysis(lexer_info: LexerInfo) -> List[Token]:
"""Convenience function matching the original C API."""
lexer = Lexer(lexer_info)
return lexer.lexical_analysis()

25
SLS_Python/sls_py/meta.py Normal file
View File

@ -0,0 +1,25 @@
import sys
try:
from ._version import version, commit, timestamp # type: ignore
except ImportError:
version = "unknown"
commit = "unknown"
timestamp = "unknown"
SLS_NAME = "SLS_PYTHON"
SLS_VERSION = version
SLS_COMMIT = commit
# Runtime interpreter info (Python equivalent of compiler)
_impl = sys.implementation
INTERPRETER_NAME = _impl.name.capitalize()
INTERPRETER_VER = _impl.version.major
MODULE_TIMESTAMP = timestamp
SLS_INFO_STRING_1 = f"YREA SLS ({SLS_NAME}) {SLS_VERSION} ({SLS_COMMIT})"
SLS_INFO_STRING_2 = f"Running on {INTERPRETER_NAME} {INTERPRETER_VER} at {MODULE_TIMESTAMP}"
def print_version() -> None:
print(SLS_INFO_STRING_1)
print(SLS_INFO_STRING_2)

83
SLS_Python/sls_py/repl.py Normal file
View File

@ -0,0 +1,83 @@
from .meta import print_version
from .lexer import LexerInfo, lexical_analysis
from .interpreter import InterpreterState, StackType
REPL_FILE_NAME = "<STDIN>"
def print_top_of_stack(interpreter: InterpreterState) -> None:
"""Pretty-print top of the stack."""
item = interpreter.top()
if item is None:
print("#0: <STACK IS EMPTY>")
return
t = item.type
if t == StackType.IDENTIFIER:
print(f"#0: ::{item.value}")
elif t == StackType.I64:
print(f"#0: {item.value}")
elif t in {StackType.I32, StackType.I16, StackType.I8}:
print(f"#0: {item.value}:{t}")
elif t in {StackType.U64, StackType.U32, StackType.U16, StackType.U8}:
print(f"#0: {item.value}:{t}")
elif t == StackType.FLOAT:
print(f"#0: {item.value}:f32")
elif t == StackType.DOUBLE:
print(f"#0: {item.value}")
elif t == StackType.CHARACTER:
print(f"#0: {item.value}")
elif t == StackType.BOOLEAN:
print("#0: TRUE" if item.value else "#0: FALSE")
elif t == StackType.TOKEN_STRING:
print("#0: <TOKEN STRING>")
elif t == StackType.CALLABLE:
print("#0: <CALLABLE>")
else:
print(f"#0: <UNKNOWN {t}>")
def repl() -> int:
"""Interactive interpreter loop."""
print_version()
print("===== YREA SLS REPL =====")
print("Type `#exit` to exit.")
interpreter = InterpreterState()
while True:
try:
line = input("> ")
except EOFError:
break
if line.strip() == "#exit":
return 0
# Create a fresh lexer each iteration
lexer_info = LexerInfo(filename=REPL_FILE_NAME, source_code=line)
try:
tokens = lexical_analysis(lexer_info)
except Exception as err:
print(err)
continue
# Execute tokens in order
for token in tokens:
try:
ok = interpreter.execute(token)
if not ok:
print("A runtime error occurred!")
break
except Exception as err:
print(err)
break
print_top_of_stack(interpreter)
return 1

3
SLS_Rust/README.md Normal file
View File

@ -0,0 +1,3 @@
# SLS Rust
This is the Rust implementation for the YREA SLS interpreter.

3
SLS_Rust/sls/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

15
SLS_Rust/sls/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "sls_rs"
version = "0.0.2-alpha"
edition = "2021"
[dependencies]
bitflags = "2.4"
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[build-dependencies]
rustc_version = "0.4"
chrono = "0.4"
vergen = { version = "8", features = ["build"] }

45
SLS_Rust/sls/build.rs Normal file
View File

@ -0,0 +1,45 @@
use std::process::Command;
use vergen::EmitBuilder;
fn try_cmd(cmd: &mut Command) -> Option<String> {
let out = cmd.output().ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
fn main() {
// Emit all default vergen build info (BUILD_DATE / BUILD_TIME, etc.)
EmitBuilder::builder().all_build();
// Git describe + commit date (matches your Python logic)
let commit_info = (|| {
let hash = try_cmd(
Command::new("git")
.arg("describe")
.arg("--always")
.arg("--dirty")
.arg("--abbrev=7"),
)?;
let date = try_cmd(
Command::new("git")
.arg("show")
.arg("-s")
.arg("--format=%ci"),
)?;
Some(format!("{} {}", hash, date))
})()
.unwrap_or_else(|| "unknown".into());
println!("cargo:rustc-env=GIT_COMMIT_HASH={}", commit_info);
// Compiler info
println!("cargo:rustc-env=COMPILER_NAME=rustc");
let rustc_ver = try_cmd(Command::new("rustc").arg("--version"))
.unwrap_or_else(|| "unknown".into());
println!("cargo:rustc-env=COMPILER_VER={}", rustc_ver);
}

BIN
SLS_Rust/sls/core Normal file

Binary file not shown.

1186
SLS_Rust/sls/src/builtin.rs Normal file

File diff suppressed because it is too large Load Diff

52
SLS_Rust/sls/src/file.rs Normal file
View File

@ -0,0 +1,52 @@
use std::fs;
use crate::interpreter::InterpreterState;
use crate::lexer::{LexerInfo, lexical_analysis, LexResult};
/// Execute the contents of a script file.
pub fn exec_file(interpreter: &mut InterpreterState, filename: &str) -> bool {
// Read the whole file
let source = match fs::read_to_string(filename) {
Ok(s) => s,
Err(e) => {
eprintln!("Cannot read file: {} ({})", filename, e);
return false;
}
};
let mut lexer_info = LexerInfo::new(filename, source.clone());
let result = lexical_analysis(&mut lexer_info);
match result {
LexResult::Ok(tokens) => {
for token in tokens {
if !interpreter.execute(&token) {
return false;
}
}
true
}
LexResult::Err(err) => {
dbg!(err);
false
}
}
}
/// Stand-alone file execution entry point.
pub fn run_file(filename: &str) -> i32 {
println!("Executing file: {}", filename);
let mut interpreter = InterpreterState::new();
if !interpreter.init() {
return 1;
}
if exec_file(&mut interpreter, filename) {
0
} else {
1
}
}

View File

@ -0,0 +1,170 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use crate::lexer::*; // Identifier, Token, TokenString, etc.
use crate::builtin::{lookup_builtin, load_builtins};
pub type BuiltinFn = fn(&mut InterpreterState) -> bool;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StackValue {
Identifier(Identifier),
I64(i64),
I32(i32),
I16(i16),
I8(i8),
U64(u64),
U32(u32),
U16(u16),
U8(u8),
F32(f32),
F64(f64),
Character(u8),
Boolean(bool),
TokenString(TokenString),
Callable(TokenString),
}
#[derive(Debug, Clone)]
pub struct BuiltinFunction {
pub name: String,
pub function: BuiltinFn,
}
impl BuiltinFunction {
fn call(&self, interpreter: &mut InterpreterState) -> bool {
(self.function)(interpreter)
}
}
impl Serialize for BuiltinFunction {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.name)
}
}
impl<'de> Deserialize<'de> for BuiltinFunction {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let name = String::deserialize(deserializer)?;
let function = lookup_builtin(&name)
.ok_or_else(|| serde::de::Error::custom(format!("Unknown builtin: {}", name)))?;
Ok(BuiltinFunction { name, function })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FunctionItem {
TokenString(TokenString),
Builtin(BuiltinFunction),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct InterpreterState {
pub stack: Vec<StackValue>,
pub functions: HashMap<String, FunctionItem>,
}
impl InterpreterState {
pub fn new() -> Self {
Self {
stack: Vec::new(),
functions: HashMap::new(),
}
}
pub fn init(&mut self) -> bool {
load_builtins(self)
}
pub fn push_token(&mut self, token: &Token) -> bool {
let value = match token {
Token::Eof => return true,
Token::Identifier(id) => {
StackValue::Identifier(id.clone())
}
Token::I64(v) => StackValue::I64(*v),
Token::I32(v) => StackValue::I32(*v),
Token::I16(v) => StackValue::I16(*v),
Token::I8(v) => StackValue::I8(*v),
Token::U64(v) => StackValue::U64(*v),
Token::U32(v) => StackValue::U32(*v),
Token::U16(v) => StackValue::U16(*v),
Token::U8(v) => StackValue::U8(*v),
Token::Float(v) => StackValue::F32(*v),
Token::Double(v) => StackValue::F64(*v),
Token::Character(c) => StackValue::Character(*c),
Token::Boolean(b) => StackValue::Boolean(*b),
Token::TokenString(ts) => StackValue::TokenString(ts.clone()),
Token::StringLiteral(_) |
Token::Array(_) |
Token::TypeTuple(_) => return false,
};
self.stack.push(value);
true
}
pub fn execute_func(&mut self, key: &str) -> bool {
let item = match self.functions.get(key) {
Some(v) => v.clone(),
None => return false,
};
match item {
FunctionItem::Builtin(f) => f.call(self),
FunctionItem::TokenString(ts) => self.execute_token_string(&ts),
}
}
pub fn execute_token_string(&mut self, ts: &TokenString) -> bool {
for token in &ts.tokens {
if let Token::Identifier(id) = &token {
if !id.is_literal {
if !self.execute_func(&id.name) {
return false;
}
continue;
}
}
if !self.push_token(&token) {
return false;
}
}
true
}
pub fn execute(&mut self, token: &Token) -> bool {
match token {
Token::Identifier(id) if !id.is_literal => {
self.execute_func(&id.name)
}
_ => self.push_token(token),
}
}
pub fn stack_top(&self) -> Option<&StackValue> {
self.stack.last()
}
}

742
SLS_Rust/sls/src/lexer.rs Normal file
View File

@ -0,0 +1,742 @@
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone)]
pub struct LexerInfo {
pub filename: String,
pub source: String,
pub pos: usize,
pub column: usize,
pub line: usize,
}
impl LexerInfo {
pub fn new(filename: impl Into<String>, source: impl Into<String>) -> Self {
Self {
filename: filename.into(),
source: source.into(),
pos: 0,
column: 1,
line: 1,
}
}
fn peek(&self) -> char {
self.source.chars().nth(self.pos).unwrap_or('\0')
}
fn far_peek(&self, offset: usize) -> char {
self.source.chars().nth(self.pos + offset).unwrap_or('\0')
}
fn advance(&mut self) -> char {
if self.peek() == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
self.pos += 1;
self.peek()
}
fn skip_comments_and_whitespace(&mut self) {
loop {
let c = self.peek();
// Skip comments
if (c == '/' && self.far_peek(1) == '/') || c == '#' {
while self.peek() != '\n' && self.peek() != '\0' {
self.advance();
}
}
// Skip whitespace
if self.peek().is_whitespace() {
while self.peek().is_whitespace() {
self.advance();
}
} else {
break;
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Identifier {
pub name: String,
pub is_literal: bool,
}
#[derive(Debug, Clone)]
pub enum ArrayLiteral {
_Identifiers(Vec<Identifier>),
_I64(Vec<i64>),
_I32(Vec<i32>),
_I16(Vec<i16>),
_I8(Vec<i8>),
_U64(Vec<u64>),
_U32(Vec<u32>),
_U16(Vec<u16>),
_U8(Vec<u8>),
_Float(Vec<f32>),
_Double(Vec<f64>),
_Character(Vec<u8>),
_Strings(Vec<String>),
_Boolean(Vec<bool>),
_TokenStrings(Vec<TokenString>),
_TypeTuples(Vec<TypeTuple>),
_StructInline(StructInline),
}
#[derive(Debug, Clone)]
pub struct ShapedArray {
pub _array: ArrayLiteral,
pub _shape: Vec<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenString {
pub tokens: Vec<Token>,
}
#[derive(Debug, Clone)]
pub struct TypeTuple {
pub _inputs: Vec<Identifier>,
pub _outputs: Vec<Identifier>,
}
#[derive(Debug, Clone)]
pub struct StructInline {
pub _name: String,
pub _values: Vec<StructValue>,
}
#[derive(Debug, Clone)]
pub enum StructValue {
_Integer(i64),
_Float(f32),
_Double(f64),
_Boolean(bool),
_Character(u8),
_String(String),
_Token(Token),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Token {
Eof,
Identifier(Identifier),
I64(i64),
I32(i32),
I16(i16),
I8(i8),
U64(u64),
U32(u32),
U16(u16),
U8(u8),
Float(f32),
Double(f64),
Character(u8),
#[serde(skip)]
StringLiteral(String),
Boolean(bool),
#[serde(skip)]
Array(ShapedArray),
TokenString(TokenString),
#[serde(skip)]
TypeTuple(TypeTuple),
}
#[derive(Debug, Clone)]
pub struct LexError {
pub message: String,
pub file: String,
pub line: usize,
pub column: usize,
}
pub type LexResult<T> = Result<T, LexError>;
#[derive(Debug, Clone, Copy)]
enum NumericLiteralType {
Binary,
Octal,
Decimal,
Hexadecimal,
Float,
}
impl LexerInfo {
fn make_error(&self, message: impl Into<String>, start_line: usize, start_col: usize) -> LexError {
LexError {
message: message.into(),
file: self.filename.clone(),
line: start_line,
column: start_col,
}
}
fn is_identifier_continue(&self, c: char) -> bool {
if !c.is_ascii() || !c.is_ascii_graphic() {
return false;
}
if c == '/' && self.far_peek(1) == '/' {
return false;
}
!matches!(c, '{' | '}' | '[' | ']' | '(' | ')' | '\'' | '"' | '#') && !c.is_whitespace()
}
fn is_identifier_start(&self) -> bool {
let mut c = self.peek();
if c == ':' && self.far_peek(1) == ':' {
c = self.far_peek(2);
}
!c.is_ascii_digit() && self.is_identifier_continue(c)
}
fn parse_identifiers_and_booleans(&mut self, _start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
let mut c = self.peek();
let mut literal = false;
// Skip leading `::` for identifier literals
if c == ':' && self.far_peek(1) == ':' {
literal = true;
self.advance();
c = self.advance();
}
// Read the name
let name_start = self.pos;
while self.is_identifier_continue(c) {
if c == ':' {
return Err(self.make_error("Invalid identifier: ':' is not allowed in identifiers.", start_line, start_col));
}
if c == '.' {
return Err(self.make_error("Invalid identifier: '.' is not allowed in identifiers.", start_line, start_col));
}
c = self.advance();
}
let name = self.source[name_start..self.pos].to_string();
// Check for booleans
match name.as_str() {
"false" => Ok(Token::Boolean(false)),
"true" => Ok(Token::Boolean(true)),
_ => Ok(Token::Identifier(Identifier { name, is_literal: literal })),
}
}
fn parse_character_literal(&mut self, start_line: usize, start_col: usize) -> LexResult<Token> {
let mut c = self.peek();
if c == '\'' {
return Err(self.make_error("Invalid character literal: empty character literal.", start_line, start_col));
}
let value = if c == '\\' {
c = self.advance();
match c {
'n' => b'\n',
'r' => b'\r',
't' => b'\t',
'\\' => b'\\',
'\'' => b'\'',
'0' => b'\0',
_ => return Err(self.make_error(format!("Invalid character literal: unknown escape sequence '\\{}'.", c), start_line, start_col)),
}
} else if c == '\n' || c == '\r' {
return Err(self.make_error("Invalid character literal: unclosed character literal.", start_line, start_col));
} else {
c as u8
};
c = self.advance();
if c.is_whitespace() || c == '/' || c == '\0' {
return Err(self.make_error("Invalid character literal: unclosed character literal.", start_line, start_col));
} else if c != '\'' {
return Err(self.make_error(format!("Invalid character literal: unexpected '{}' in character.", c), start_line, start_col));
}
self.advance();
Ok(Token::Character(value))
}
fn parse_token_string(&mut self, _start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
let mut tokens = Vec::new();
self.advance(); // skip '{'
loop {
self.skip_comments_and_whitespace();
let c = self.peek();
if c == '}' {
self.advance();
return Ok(Token::TokenString(TokenString { tokens }));
}
if c == '\0' {
return Err(self.make_error("Unclosed token string: missing closing brace '}'.", start_line, start_col));
}
match get_token(self) {
Some(token) => {
if matches!(token, Token::Eof) {
break;
}
tokens.push(token);
}
None => return Err(self.make_error("Failed to parse token in token string.", start_line, start_col)),
}
}
Err(self.make_error("Unclosed token string: missing closing brace '}'.", start_line, start_col))
}
fn parse_numeric_literal(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
let mut c = self.peek();
if c == '-' {
c = self.advance();
}
if c == '0' {
c = self.advance();
match c {
'b' | 'B' => {
self.advance();
return self.parse_binary_integer(start, start_line, start_col);
}
'o' | 'O' => {
self.advance();
return self.parse_octal_integer(start, start_line, start_col);
}
'x' | 'X' => {
self.advance();
return self.parse_hexadecimal_integer(start, start_line, start_col);
}
_ => {}
}
}
self.parse_decimal_integer(start, start_line, start_col)
}
fn parse_binary_integer(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
let mut c = self.peek();
while c == '0' || c == '1' || c == '_' {
c = self.advance();
}
if c == ':' {
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Binary);
}
let value = self.create_binary_integer(start);
Ok(Token::I64(value as i64))
}
fn parse_octal_integer(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
let mut c = self.peek();
while c.is_ascii_digit() && c != '8' && c != '9' || c == '_' {
c = self.advance();
}
if c == ':' {
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Octal);
}
let value = self.create_octal_integer(start);
Ok(Token::I64(value as i64))
}
fn parse_decimal_integer(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
let mut c = self.peek();
while c.is_ascii_digit() || c == '_' {
c = self.advance();
}
if c == '.' {
self.advance();
return self.parse_float(start, start_line, start_col);
}
if c == ':' {
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Decimal);
}
let value = self.create_decimal_integer(start);
Ok(Token::I64(value as i64))
}
fn parse_hexadecimal_integer(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
let mut c = self.peek();
while c.is_ascii_hexdigit() || c == '_' {
c = self.advance();
}
if c == ':' {
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Hexadecimal);
}
let value = self.create_hexadecimal_integer(start);
Ok(Token::I64(value as i64))
}
fn parse_float(&mut self, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
let mut c = self.peek();
while c.is_ascii_digit() || c == '_' {
c = self.advance();
}
if c == ':' {
return self.parse_numeric_type(start, start_line, start_col, NumericLiteralType::Float);
}
let value = self.create_float(start);
Ok(Token::Double(value))
}
fn parse_numeric_type(&mut self, start: usize, start_line: usize, start_col: usize, literal_type: NumericLiteralType) -> LexResult<Token> {
let mut c = self.advance(); // skip ':'
let mut is_float = false;
let mut is_unsigned = false;
let bit_size: u32;
if c == 'f' {
is_float = true;
if !matches!(literal_type, NumericLiteralType::Decimal | NumericLiteralType::Float) {
return Err(self.make_error("Invalid numeric literal: float type not allowed.", start_line, start_col));
}
c = self.advance();
if c == '6' && self.far_peek(1) == '4' {
bit_size = 64;
self.advance();
self.advance();
} else if c == '3' && self.far_peek(1) == '2' {
bit_size = 32;
self.advance();
self.advance();
} else {
return Err(self.make_error("Invalid float type: must be of type 'f64' or 'f32'.", start_line, start_col));
}
} else if c == 'i' || c == 'u' {
if matches!(literal_type, NumericLiteralType::Float) {
return Err(self.make_error("Invalid float type: must be of type 'f64' or 'f32'.", start_line, start_col));
}
is_unsigned = c == 'u';
c = self.advance();
if c == '6' && self.far_peek(1) == '4' {
bit_size = 64;
self.advance();
self.advance();
} else if c == '3' && self.far_peek(1) == '2' {
bit_size = 32;
self.advance();
self.advance();
} else if c == '1' && self.far_peek(1) == '6' {
bit_size = 16;
self.advance();
self.advance();
} else if c == '8' {
bit_size = 8;
self.advance();
} else {
let type_name = if is_unsigned { "unsigned" } else { "signed" };
return Err(self.make_error(
format!("Invalid {} integer type: must be of type '{}64', '{}32', '{}16', or '{}8'.",
type_name, if is_unsigned { "u" } else { "i" },
if is_unsigned { "u" } else { "i" },
if is_unsigned { "u" } else { "i" },
if is_unsigned { "u" } else { "i" }),
start_line, start_col));
}
} else {
return Err(self.make_error("Invalid numeric type: type must start with 'f', 'i', or 'u'.", start_line, start_col));
}
// Create the token based on the parsed type
if is_float {
let value = self.create_float(start);
match bit_size {
32 => Ok(Token::Float(value as f32)),
64 => Ok(Token::Double(value)),
_ => unreachable!()
}
} else {
let value = match literal_type {
NumericLiteralType::Binary => self.create_binary_integer(start),
NumericLiteralType::Octal => self.create_octal_integer(start),
NumericLiteralType::Decimal => self.create_decimal_integer(start),
NumericLiteralType::Hexadecimal => self.create_hexadecimal_integer(start),
NumericLiteralType::Float => return Err(self.make_error("Internal error: float literal in integer path", start_line, start_col)),
};
self.create_integer_token(value, is_unsigned, bit_size, start, start_line, start_col)
}
}
fn create_integer_token(&self, value: u64, is_unsigned: bool, bit_size: u32, start: usize, start_line: usize, start_col: usize) -> LexResult<Token> {
let is_negative = self.source[start..].starts_with('-');
match (is_unsigned, bit_size) {
(false, 64) => Ok(Token::I64(value as i64)),
(false, 32) => {
let signed = value as i64;
if signed < i32::MIN as i64 || signed > i32::MAX as i64 {
return Err(self.make_error("Integer overflow: value exceeds range for i32.", start_line, start_col));
}
Ok(Token::I32(value as i32))
}
(false, 16) => {
let signed = value as i64;
if signed < i16::MIN as i64 || signed > i16::MAX as i64 {
return Err(self.make_error("Integer overflow: value exceeds range for i16.", start_line, start_col));
}
Ok(Token::I16(value as i16))
}
(false, 8) => {
let signed = value as i64;
if signed < i8::MIN as i64 || signed > i8::MAX as i64 {
return Err(self.make_error("Integer overflow: value exceeds range for i8.", start_line, start_col));
}
Ok(Token::I8(value as i8))
}
(true, 64) => {
if is_negative {
return Err(self.make_error("Integer overflow: value exceeds range for u64.", start_line, start_col));
}
Ok(Token::U64(value))
}
(true, 32) => {
if is_negative {
return Err(self.make_error("Integer overflow: value exceeds range for u32.", start_line, start_col));
}
if value > u32::MAX as u64 {
return Err(self.make_error("Integer overflow: value exceeds range for u32.", start_line, start_col));
}
Ok(Token::U32(value as u32))
}
(true, 16) => {
if is_negative {
return Err(self.make_error("Integer overflow: value exceeds range for u16.", start_line, start_col));
}
if value > u16::MAX as u64 {
return Err(self.make_error("Integer overflow: value exceeds range for u16.", start_line, start_col));
}
Ok(Token::U16(value as u16))
}
(true, 8) => {
if is_negative {
return Err(self.make_error("Integer overflow: value exceeds range for u8.", start_line, start_col));
}
if value > u8::MAX as u64 {
return Err(self.make_error("Integer overflow: value exceeds range for u8.", start_line, start_col));
}
Ok(Token::U8(value as u8))
}
_ => Err(self.make_error("Invalid bit size for integer type.", start_line, start_col))
}
}
fn create_binary_integer(&self, start: usize) -> u64 {
let token = &self.source[start..self.pos];
let mut value = 0u64;
let mut i = 2;
if token.starts_with('-') {
i += 1;
}
for c in token[i..].chars() {
if c == '_' || c == '.' {
continue;
}
if c.is_whitespace() || c == '/' || c == ':' {
break;
}
value *= 2;
if c == '1' {
value += 1;
}
}
if token.starts_with('-') {
(!value).wrapping_add(1)
} else {
value
}
}
fn create_octal_integer(&self, start: usize) -> u64 {
let token = &self.source[start..self.pos];
let mut value = 0u64;
let mut i = 2;
if token.starts_with('-') {
i += 1;
}
for c in token[i..].chars() {
if c == '_' || c == '.' {
continue;
}
if c.is_whitespace() || c == '/' || c == ':' {
break;
}
value *= 8;
value += c.to_digit(8).unwrap_or(0) as u64;
}
if token.starts_with('-') {
(!value).wrapping_add(1)
} else {
value
}
}
fn create_decimal_integer(&self, start: usize) -> u64 {
let token = &self.source[start..self.pos];
let mut value = 0u64;
let mut i = 0;
if token.starts_with('-') {
i += 1;
}
for c in token[i..].chars() {
if c == '_' {
continue;
}
if c.is_whitespace() || c == '/' || c == ':' {
break;
}
value *= 10;
value += c.to_digit(10).unwrap_or(0) as u64;
}
if token.starts_with('-') {
(!value).wrapping_add(1)
} else {
value
}
}
fn create_hexadecimal_integer(&self, start: usize) -> u64 {
let token = &self.source[start..self.pos];
let mut value = 0u64;
let mut i = 2;
if token.starts_with('-') {
i += 1;
}
for c in token[i..].chars() {
if c == '_' || c == '.' {
continue;
}
if c.is_whitespace() || c == '/' || c == ':' {
break;
}
value *= 16;
value += c.to_digit(16).unwrap_or(0) as u64;
}
if token.starts_with('-') {
(!value).wrapping_add(1)
} else {
value
}
}
fn create_float(&self, start: usize) -> f64 {
let token = &self.source[start..self.pos];
let mut value = 0.0;
let mut fractional = 0u64;
let mut i = 0;
if token.starts_with('-') {
i += 1;
}
for c in token[i..].chars() {
if c == '_' {
continue;
}
if c.is_whitespace() || c == '/' || c == ':' {
break;
}
if c == '.' {
fractional = 1;
continue;
}
if fractional == 0 {
value *= 10.0;
} else {
fractional *= 10;
}
let digit = c.to_digit(10).unwrap_or(0) as f64;
if fractional == 0 {
value += digit;
} else {
value += digit / fractional as f64;
}
}
if token.starts_with('-') {
-value
} else {
value
}
}
}
pub fn get_token(lexer: &mut LexerInfo) -> Option<Token> {
lexer.skip_comments_and_whitespace();
let c = lexer.peek();
let start = lexer.pos;
let start_line = lexer.line;
let start_col = lexer.column;
if c == '\0' {
return Some(Token::Eof);
}
let result = if c.is_ascii_digit() || (c == '.' && lexer.far_peek(1).is_ascii_digit()) || (c == '-' && lexer.far_peek(1).is_ascii_digit()) {
lexer.parse_numeric_literal(start, start_line, start_col)
} else if c == '\'' {
lexer.advance();
lexer.parse_character_literal(start_line, start_col)
} else if c == '{' {
lexer.parse_token_string(start, start_line, start_col)
} else if lexer.is_identifier_start() {
lexer.parse_identifiers_and_booleans(start, start_line, start_col)
} else {
Err(lexer.make_error(format!("Unexpected character: '{}'", c), start_line, start_col))
};
result.ok()
}
pub fn lexical_analysis(lexer: &mut LexerInfo) -> LexResult<Vec<Token>> {
let mut tokens = Vec::new();
loop {
match get_token(lexer) {
Some(Token::Eof) => {
tokens.push(Token::Eof);
break;
}
Some(token) => tokens.push(token),
None => break,
}
}
Ok(tokens)
}

63
SLS_Rust/sls/src/main.rs Normal file
View File

@ -0,0 +1,63 @@
mod builtin;
mod file;
mod interpreter;
mod lexer;
mod repl;
use std::env;
use std::process;
use file::run_file;
use repl::repl;
// These mirror the C macros.
const SLS_NAME: &str = "SLS_RUST";
const SLS_VER: &str = "0.0.2-alpha";
pub fn print_version() {
let git_hash = option_env!("GIT_COMMIT_HASH").unwrap_or("unknown");
let compiler = option_env!("COMPILER_NAME").unwrap_or("rustc");
let compiler_ver = option_env!("COMPILER_VER").unwrap_or("unknown");
let build_date = std::env::var("BUILD_DATE").unwrap_or_else(|_| "unknown".into());
let build_time = std::env::var("BUILD_TIME").unwrap_or_else(|_| "unknown".into());
println!("YREA SLS ({SLS_NAME}) {SLS_VER} ({git_hash})");
println!("Compiled with {compiler} {compiler_ver} at {build_date} {build_time}");
}
fn main() {
let mut args = env::args().skip(1);
let mut version_flag = false;
let mut filename: Option<String> = None;
match args.len() {
0 => {}
1 => {
let arg = args.next().unwrap();
if arg == "--version" || arg == "-v" {
version_flag = true;
} else {
filename = Some(arg);
}
}
_ => {
eprintln!("Too many arguments!");
process::exit(1);
}
}
if version_flag {
print_version();
process::exit(0);
}
if let Some(file) = filename {
let status = run_file(&file);
process::exit(status);
}
// Default to REPL
let status = repl();
process::exit(status);
}

116
SLS_Rust/sls/src/repl.rs Normal file
View File

@ -0,0 +1,116 @@
use std::io::{self, Write};
use std::fs;
use serde_json;
use crate::lexer::{LexerInfo, LexResult, lexical_analysis};
use crate::print_version;
use crate::interpreter::{InterpreterState, StackValue};
static REPL_FILE_NAME: &str = "<STDIN>";
fn print_top_of_stack(state: &InterpreterState) {
let Some(item) = state.stack_top() else {
println!("#0: <STACK IS EMPTY>");
return;
};
match &item {
StackValue::Identifier(id) => {
println!("#0: ::{}", id.name);
}
StackValue::I64(v) => println!("#0: {}", v),
StackValue::I32(v) => println!("#0: {}:i32", v),
StackValue::I16(v) => println!("#0: {}:i16", v),
StackValue::I8(v) => println!("#0: {}:i8", v),
StackValue::U64(v) => println!("#0: {}:u64", v),
StackValue::U32(v) => println!("#0: {}:u32", v),
StackValue::U16(v) => println!("#0: {}:u16", v),
StackValue::U8(v) => println!("#0: {}:u8", v),
StackValue::F32(v) => println!("#0: {}:f32", v),
StackValue::F64(v) => println!("#0: {}", v),
StackValue::Character(ch) => println!("#0: {}", ch),
StackValue::Boolean(b) => println!("#0: {}", if *b { "TRUE" } else { "FALSE" }),
StackValue::TokenString(_) => println!("#0: <TOKEN STRING>"),
StackValue::Callable(_) => println!("#0: <CALLABLE>"),
};
}
pub fn repl() -> i32 {
print_version();
println!("===== YREA SLS REPL =====");
println!("Type `#exit` to exit.");
io::stdout().flush().unwrap();
let mut interpreter = InterpreterState::new();
if !interpreter.init() {
return 1;
}
let stdin = io::stdin();
let mut buf = String::new();
loop {
buf.clear();
if stdin.read_line(&mut buf).is_err() {
return 1;
}
if buf.trim_end() == "#exit" {
return 0;
}
if let Some(file) = buf.trim_end().strip_prefix("#save ") {
match serde_json::to_string(&interpreter) {
Ok(json) => {
if let Err(err) = fs::write(file, json) {
eprintln!("Failed to save state: {}", err);
} else {
println!("State saved to {}", file);
}
}
Err(err) => eprintln!("Serialization error: {}", err),
}
continue;
}
if let Some(file) = buf.trim_end().strip_prefix("#load ") {
match fs::read_to_string(file) {
Ok(json) => match serde_json::from_str(&json) {
Ok(state) => {
interpreter = state;
println!("State restored from {}", file);
}
Err(err) => eprintln!("Deserialization error: {}", err),
},
Err(err) => eprintln!("Failed to read file: {}", err),
}
continue;
}
let code = buf.clone();
let mut lexer_info = LexerInfo::new(REPL_FILE_NAME, code.clone());
let result = lexical_analysis(&mut lexer_info);
match result {
LexResult::Ok(tokens) => {
for token in tokens {
if !interpreter.execute(&token) {
eprintln!("A runtime error occured!");
break;
}
}
print_top_of_stack(&interpreter);
}
LexResult::Err(err) => {
dbg!(err);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
from .base_tests import BaseTestGenerator
from .general_tests import GeneralTestGenerator
from .integer_tests import IntegerTestGenerator
from .float_tests import FloatTestGenerator
from .char_tests import CharTestGenerator
from .string_tests import StringTestGenerator
from .idents_and_bools_tests import IdentifierTestGenerator, BooleanTestGenerator
from .token_strings import TokenStringTestGenerator
__all__ = [
"BaseTestGenerator",
"GeneralTestGenerator",
"IntegerTestGenerator",
"FloatTestGenerator",
"CharTestGenerator",
"StringTestGenerator",
"IdentifierTestGenerator",
"BooleanTestGenerator",
"TokenStringTestGenerator",
]

View File

@ -0,0 +1,23 @@
"""
Test case generator for integer literals in the Stack Language.
Generates comprehensive test cases for all integer types and bases.
"""
from .base_tests import BaseTestGenerator
if __name__ == "__main__":
import yaml
# Generate tests
tests = BaseTestGenerator.run_all_generators()
# tests = []
# tests += GeneralTestGenerator.generate_tests()
# tests += IntegerTestGenerator.generate_tests()
# tests += FloatTestGenerator.generate_tests()
# Print summary
print(f"Generated {len(tests)} test cases")
# Save as YAML
with open("cases.yaml", "w") as f:
yaml.dump(tests, f, default_flow_style=False, sort_keys=False)

View File

@ -0,0 +1,405 @@
from typing import ClassVar, List, Dict, Any, Optional, Type
from abc import ABC, abstractmethod
from dataclasses import dataclass, asdict
@dataclass
class Token:
type: str
value: Any
@dataclass
class Operation:
function: str
type: str
value: Any
@dataclass
class StackItem:
type: str
value: Any
@dataclass
class RuntimeError:
message: str
@dataclass
class TestCase:
name: str
code: str
tokens: List[Dict[str, Any]]
operations: Optional[List[Dict[str, Any]]] = None
stack_final: Optional[List[Dict[str, Any]]] = None
runtime_error: Optional[Dict[str, str]] = None
def to_dict(obj) -> Dict[str, Any]:
"""Convert dataclass to dict, removing None values."""
if obj is None:
raise ValueError("Obj cannot be None")
d = asdict(obj) if hasattr(obj, '__dataclass_fields__') else obj
return {k: v for k, v in d.items() if v is not None}
class BaseTestGenerator(ABC):
"""
Abstract base class for test case generators.
Provides common functionality for generating test cases including
test creation, operation building, and error handling.
"""
ENABLE_UNICODE = False
ENABLE_EXPONENTIAL_LITERALS = False
ENABLE_CHAR_HEX_ESCAPE = False
ENABLE_STRINGS = False
__generators: "ClassVar[List[Type[BaseTestGenerator]]]" = []
def __init_subclass__(cls):
BaseTestGenerator.__generators.append(cls)
def __init__(self):
"""Initialize the test generator with an empty test list."""
self.tests: List[Dict[str, Any]] = []
# =========================================================================
# Test Case Management
# =========================================================================
def add_test(self, name: str, code: str, tokens: List[Token],
operations: Optional[List[Operation]] = None,
stack_final: Optional[List[StackItem]] = None,
runtime_error: Optional[RuntimeError] = None):
"""
Add a test case to the test suite.
Args:
name: Descriptive name for the test
code: Source code being tested
tokens: List of expected tokens from lexing
operations: Optional list of operations for evaluation phase
stack_final: Optional final stack state after execution
runtime_error: Optional runtime error if test should fail
"""
test = TestCase(
name=name,
code=code,
tokens=[to_dict(t) for t in tokens],
operations=[to_dict(o) for o in operations] if operations else None,
stack_final=[to_dict(s) for s in stack_final] if stack_final else None,
runtime_error=to_dict(runtime_error) if runtime_error else None
)
self.tests.append(to_dict(test))
# =========================================================================
# Factory Methods
# =========================================================================
def make_push_op(self, type_name: str, value: Any) -> Operation:
"""
Create a push operation.
Args:
type_name: Type of the value being pushed
value: The value to push
Returns:
Operation object representing a push
"""
return Operation(function="push", type=type_name, value=value)
def make_stack_item(self, type_name: str, value: Any) -> StackItem:
"""
Create a stack item.
Args:
type_name: Type of the stack item
value: The value of the stack item
Returns:
StackItem object
"""
return StackItem(type=type_name, value=value)
def make_error_token(self, message: str) -> Token:
"""
Create an error token.
Args:
message: Error message
Returns:
Token object with type "error"
"""
return Token(type="error", value=message)
def make_runtime_error(self, message: str) -> RuntimeError:
"""
Create a runtime error.
Args:
message: Error message
Returns:
RuntimeError object
"""
return RuntimeError(message=message)
# =========================================================================
# Convenience Test Creators
# =========================================================================
def make_success_test(self, name: str, code: str, type_name: str, value: Any):
"""
Create a successful test case with standard push operation.
Args:
name: Test name
code: Source code
type_name: Type of the value
value: The value
"""
token = Token(type=type_name, value=value)
op = self.make_push_op(type_name, value)
stack = self.make_stack_item(type_name, value)
self.add_test(name, code, [token], [op], [stack])
def make_error_test(self, name: str, code: str, error_msg: str):
"""
Create an error test case (lexing error).
Args:
name: Test name
code: Source code
error_msg: Expected error message
"""
token = self.make_error_token(error_msg)
self.add_test(name, code, [token])
def make_runtime_error_test(self, name: str, code: str, tokens: List[Token],
operations: List[Operation], error_msg: str):
"""
Create a runtime error test case.
Args:
name: Test name
code: Source code
tokens: Tokens that were successfully lexed
operations: Operations that led to the error
error_msg: Expected runtime error message
"""
runtime_error = self.make_runtime_error(error_msg)
self.add_test(name, code, tokens, operations, None, runtime_error)
def make_empty_test(self, name: str, code: str):
"""
Create a test case with empty result (e.g., comments, whitespace).
Args:
name: Test name
code: Source code
"""
self.add_test(name, code, [], [], [])
# =========================================================================
# Multi-Value Test Helpers
# =========================================================================
def make_multi_value_test(self, name: str, code: str,
values: List[tuple[str, Any]]):
"""
Create a test with multiple values on the stack.
Args:
name: Test name
code: Source code
values: List of (type_name, value) tuples in stack order
"""
tokens = [Token(type=t, value=v) for t, v in values]
operations = [self.make_push_op(t, v) for t, v in values]
stack = [self.make_stack_item(t, v) for t, v in values]
self.add_test(name, code, tokens, operations, stack)
# =========================================================================
# Test Suite Generation
# =========================================================================
@abstractmethod
def generate_all_tests(self) -> List[Dict[str, Any]]:
"""
Generate all test cases for this generator.
Must be implemented by subclasses to define their specific test suite.
Returns:
List of test case dictionaries ready for serialization
"""
pass
def get_tests(self) -> List[Dict[str, Any]]:
"""
Get the current list of tests.
Returns:
List of test case dictionaries
"""
return self.tests
def clear_tests(self):
"""Clear all tests from the generator."""
self.tests = []
def test_count(self) -> int:
"""
Get the number of tests generated.
Returns:
Number of tests
"""
return len(self.tests)
@classmethod
def generate_tests(cls) -> List[Dict[str, Any]]:
gen = cls()
tests = gen.generate_all_tests()
gen.print_statistics()
return tests
@classmethod
def run_all_generators(cls) -> List[Dict[str, Any]]:
tests = []
for sub_cls in cls.__generators:
tests += sub_cls.generate_tests()
return tests
# =========================================================================
# Test Organization Helpers
# =========================================================================
def add_test_group_comment(self, comment: str):
"""
Add a comment to organize test groups (for documentation).
Note: This doesn't add an actual test, just tracks organization
in subclass implementations.
Args:
comment: Comment describing the test group
"""
# Subclasses can override to add metadata or logging
pass
# =========================================================================
# Validation Helpers
# =========================================================================
def validate_test_names_unique(self) -> bool:
"""
Check if all test names are unique.
Returns:
True if all test names are unique, False otherwise
"""
names = [test['name'] for test in self.tests]
return len(names) == len(set(names))
def get_duplicate_test_names(self) -> List[str]:
"""
Get list of duplicate test names.
Returns:
List of test names that appear more than once
"""
names = [test['name'] for test in self.tests]
seen = set()
duplicates = set()
for name in names:
if name in seen:
duplicates.add(name)
seen.add(name)
return list(duplicates)
# =========================================================================
# Statistics
# =========================================================================
def get_test_statistics(self) -> Dict[str, int]:
"""
Get statistics about the generated tests.
Returns:
Dictionary with test statistics
"""
stats = {
'total': len(self.tests),
'success': 0,
'lex_error': 0,
'runtime_error': 0,
'empty': 0,
}
for test in self.tests:
if test.get('runtime_error'):
stats['runtime_error'] += 1
elif test.get('tokens') and test['tokens'][0].get('type') == 'error':
stats['lex_error'] += 1
elif not test.get('tokens'):
stats['empty'] += 1
else:
stats['success'] += 1
return stats
def print_statistics(self):
"""Print test statistics to console."""
stats = self.get_test_statistics()
print(f"Test Statistics for {self.__class__.__name__}:")
print(f" Total tests: {stats['total']}")
print(f" Success tests: {stats['success']}")
print(f" Lex error tests: {stats['lex_error']}")
print(f" Runtime error tests: {stats['runtime_error']}")
print(f" Empty tests: {stats['empty']}")
if not self.validate_test_names_unique():
duplicates = self.get_duplicate_test_names()
print(f" WARNING: Duplicate test names found: {duplicates}")
# =========================================================================
# Token String Helpers
# =========================================================================
def make_token_string_token(self, inner_tokens: List[Token]) -> Token:
"""
Create a token string token containing inner tokens.
Args:
inner_tokens: List of tokens inside the token string
Returns:
Token with type "token_string" and tokens array
"""
return Token(type="token_string", value=[t.__dict__ for t in inner_tokens])
def make_token_string_test(self, name: str, code: str, inner_tokens: List[Token]):
"""
Create a successful token string test.
Args:
name: Test name
code: Source code
inner_tokens: Tokens inside the token string
"""
token = self.make_token_string_token(inner_tokens)
# For operations, we push the token string as a value
op_value = {"tokens": [t.__dict__ for t in inner_tokens]}
op = self.make_push_op("token_string", op_value)
# On the stack, it's a token_string value
stack = self.make_stack_item("token_string", op_value)
self.add_test(name, code, [token], [op], [stack])

View File

@ -0,0 +1,299 @@
from typing import List, Dict, Any
from .base_tests import BaseTestGenerator
class CharTestGenerator(BaseTestGenerator):
"""Generate test cases for character literals."""
# Common escape sequences
ESCAPE_SEQUENCES = {
("Newline", '\\\\n', '\n',), # Newline
("Carriage return", '\\\\r', '\r',), # Carriage return
("Tab", '\\\\t', '\t',), # Tab
("Backslash", '\\\\\\\\', '\\',), # Backslash
("Single quote", "\\\\'", "'",), # Single quote
("Null character", '\\\\0', '\0',), # Null character
}
# Unicode escape examples
UNICODE_ESCAPES = {
'\\u{41}': 'A',
'\\u{1F600}': '😀',
'\\u{2764}': '',
'\\u{0041}': 'A',
'\\u{03B1}': 'α',
'\\u{4E2D}': '',
}
def generate_basic_tests(self):
"""Generate basic character literal tests."""
# Simple ASCII letters
self.make_success_test("Char Simple Letter Uppercase A", "'A'", "char", 'A')
self.make_success_test("Char Simple Letter Lowercase a", "'a'", "char", 'a')
self.make_success_test("Char Simple Letter Uppercase Z", "'Z'", "char", 'Z')
self.make_success_test("Char Simple Letter Lowercase z", "'z'", "char", 'z')
# Digits
self.make_success_test("Char Digit 0", "'0'", "char", '0')
self.make_success_test("Char Digit 5", "'5'", "char", '5')
self.make_success_test("Char Digit 9", "'9'", "char", '9')
# Special characters
self.make_success_test("Char Space", "' '", "char", ' ')
self.make_success_test("Char Exclamation", "'!'", "char", '!')
self.make_success_test("Char Question Mark", "'?'", "char", '?')
self.make_success_test("Char Period", "'.'", "char", '.')
self.make_success_test("Char Comma", "','", "char", ',')
self.make_success_test("Char Semicolon", "';'", "char", ';')
self.make_success_test("Char Colon", "':'", "char", ':')
# Operators and symbols
self.make_success_test("Char Plus", "'+'", "char", '+')
self.make_success_test("Char Minus", "'-'", "char", '-')
self.make_success_test("Char Asterisk", "'*'", "char", '*')
self.make_success_test("Char Slash", "'/'", "char", '/')
self.make_success_test("Char Equals", "'='", "char", '=')
self.make_success_test("Char Less Than", "'<'", "char", '<')
self.make_success_test("Char Greater Than", "'>'", "char", '>')
# Brackets and braces
self.make_success_test("Char Left Paren", "'('", "char", '(')
self.make_success_test("Char Right Paren", "')'", "char", ')')
self.make_success_test("Char Left Bracket", "'['", "char", '[')
self.make_success_test("Char Right Bracket", "']'", "char", ']')
self.make_success_test("Char Left Brace", "'{'", "char", '{')
self.make_success_test("Char Right Brace", "'}'", "char", '}')
def generate_escape_sequence_tests(self):
"""Generate tests for escape sequences."""
# Standard escape sequences
for escape_name, escape_str, char_val in self.ESCAPE_SEQUENCES:
name = f"Char Escape {escape_name}"
code = f"'{escape_str}'"
self.make_success_test(name, code, "char", char_val)
def generate_hexadecimal_escape_tests(self):
"""Generate tests for hexadecimal escape sequences."""
if self.ENABLE_CHAR_HEX_ESCAPE:
# Additional specific hex escapes
self.make_success_test("Char Hex Lowercase A", "'\\\\x61'", "char", 'a')
self.make_success_test("Char Hex Uppercase A", "'\\\\x41'", "char", 'A')
self.make_success_test("Char Hex Space", "'\\\\x20'", "char", ' ')
self.make_success_test("Char Hex Tab", "'\\\\x09'", "char", '\t')
self.make_success_test("Char Hex Newline", "'\\\\x0A'", "char", '\n')
self.make_success_test("Char Hex Max ASCII", "'\\\\x7F'", "char", '\x7F')
def generate_unicode_escape_tests(self):
"""Generate tests for Unicode escape sequences."""
for escape_str, char_val in self.UNICODE_ESCAPES.items():
name = f"Char Unicode {escape_str}"
code = f"'{escape_str}'"
self.make_success_test(name, code, "char", char_val)
# Additional Unicode tests
self.make_success_test("Char Unicode Smiley", "'\\u{1F600}'", "char", '😀')
self.make_success_test("Char Unicode Heart", "'\\u{2764}'", "char", '')
self.make_success_test("Char Unicode Star", "'\\u{2B50}'", "char", '')
self.make_success_test("Char Unicode Greek Alpha", "'\\u{03B1}'", "char", 'α')
self.make_success_test("Char Unicode Greek Beta", "'\\u{03B2}'", "char", 'β')
self.make_success_test("Char Unicode Chinese", "'\\u{4E2D}'", "char", '')
self.make_success_test("Char Unicode Arabic", "'\\u{0639}'", "char", 'ع')
self.make_success_test("Char Unicode Cyrillic", "'\\u{0410}'", "char", 'А')
def generate_unicode_direct_tests(self):
"""Generate tests for direct Unicode characters."""
# Emoji
self.make_success_test("Char Direct Emoji Smiley", "'😀'", "char", '😀')
self.make_success_test("Char Direct Emoji Heart", "''", "char", '')
self.make_success_test("Char Direct Emoji Star", "''", "char", '')
self.make_success_test("Char Direct Emoji Thumbs Up", "'👍'", "char", '👍')
# Greek letters
self.make_success_test("Char Direct Greek Alpha", "'α'", "char", 'α')
self.make_success_test("Char Direct Greek Beta", "'β'", "char", 'β')
self.make_success_test("Char Direct Greek Pi", "'π'", "char", 'π')
self.make_success_test("Char Direct Greek Omega", "'Ω'", "char", 'Ω')
# Chinese characters
self.make_success_test("Char Direct Chinese Middle", "''", "char", '')
self.make_success_test("Char Direct Chinese Character", "''", "char", '')
# Arabic
self.make_success_test("Char Direct Arabic Letter", "'ع'", "char", 'ع')
# Cyrillic
self.make_success_test("Char Direct Cyrillic A", "'А'", "char", 'А')
self.make_success_test("Char Direct Cyrillic Ya", "'Я'", "char", 'Я')
# Mathematical symbols
self.make_success_test("Char Direct Infinity", "''", "char", '')
self.make_success_test("Char Direct Sum", "''", "char", '')
self.make_success_test("Char Direct Integral", "''", "char", '')
def generate_whitespace_tests(self):
"""Generate tests with whitespace around character literals."""
self.make_success_test("Char With Leading Whitespace", " 'A'", "char", 'A')
self.make_success_test("Char With Trailing Whitespace", "'A' ", "char", 'A')
self.make_success_test("Char With Both Whitespace", " 'A' ", "char", 'A')
self.make_success_test("Char Tab Before", "\\t'B'", "char", 'B')
self.make_success_test("Char Newline Before", "\\n'C'", "char", 'C')
def generate_error_tests(self):
"""Generate error test cases."""
# Empty character literal
self.make_error_test("Char Empty Literal",
"''",
"Invalid character literal: empty character literal.")
# Multiple characters (no escape)
self.make_error_test("Char Multiple Characters",
"'AB'",
"Invalid character literal: unexpected 'B' in character.")
# Unclosed quote
self.make_error_test("Char Unclosed Quote",
"'A",
"Invalid character literal: unclosed character literal.")
# Unescaped newline
self.make_error_test("Char Unescaped Newline",
"'\\n'",
"Invalid character literal: unclosed character literal.")
# Invalid escape sequence
self.make_error_test("Char Invalid Escape",
"'\\\\q'",
"Invalid character literal: unknown escape sequence '\\\\q'.")
# Invalid hex escape (not 2 digits)
if self.ENABLE_CHAR_HEX_ESCAPE:
self.make_error_test("Char Hex Escape Too Short",
"'\\\\x4'",
"Invalid character literal: hexadecimal escape must have exactly 2 digits.")
self.make_error_test("Char Hex Escape Too Long",
"'\\\\x414'",
"Invalid character literal: hexadecimal escape must have exactly 2 digits.")
# Invalid hex digits
self.make_error_test("Char Hex Invalid Digit",
"'\\\\xGG'",
"Invalid character literal: invalid hexadecimal digit 'G'.")
if self.ENABLE_UNICODE:
# Invalid Unicode escape (no braces)
self.make_error_test("Char Unicode No Braces",
"'\\u1F600'",
"Invalid character literal: Unicode escape must use braces \\u{...}.")
# Invalid Unicode escape (empty)
self.make_error_test("Char Unicode Empty",
"'\\u{}'",
"Invalid character literal: empty Unicode escape sequence.")
# Invalid Unicode escape (too many digits)
self.make_error_test("Char Unicode Too Many Digits",
"'\\u{1234567}'",
"Invalid character literal: Unicode escape sequence too long (max 6 hex digits).")
# Invalid Unicode escape (invalid code point)
self.make_error_test("Char Unicode Invalid Code Point",
"'\\u{D800}'",
"Invalid character literal: invalid Unicode code point (surrogate range).")
self.make_error_test("Char Unicode Out Of Range",
"'\\u{110000}'",
"Invalid character literal: Unicode code point out of range (max 0x10FFFF).")
# Invalid Unicode escape (non-hex digits)
self.make_error_test("Char Unicode Invalid Hex",
"'\\u{GGGG}'",
"Invalid character literal: invalid hexadecimal digit 'G' in Unicode escape.")
# Unclosed Unicode escape
self.make_error_test("Char Unicode Unclosed",
"'\\u{1F600'",
"Invalid character literal: unclosed Unicode escape sequence.")
def generate_edge_case_tests(self):
"""Generate edge case tests."""
if self.ENABLE_CHAR_HEX_ESCAPE:
# ASCII control characters
self.make_success_test("Char ASCII Control SOH", "'\\\\x01'", "char", '\x01')
self.make_success_test("Char ASCII Control BEL", "'\\\\x07'", "char", '\x07')
self.make_success_test("Char ASCII Control ESC", "'\\\\x1B'", "char", '\x1B')
self.make_success_test("Char ASCII Control DEL", "'\\\\x7F'", "char", '\x7F')
# Extended ASCII
self.make_success_test("Char Extended ASCII Lower", "'\\\\x80'", "char", '\x80')
self.make_success_test("Char Extended ASCII Upper", "'\\\\xFF'", "char", '\xFF')
if self.ENABLE_UNICODE:
# Zero-width characters
self.make_success_test("Char Zero Width Space", "'\\u{200B}'", "char", '\u200B')
self.make_success_test("Char Zero Width Joiner", "'\\u{200D}'", "char", '\u200D')
# Right-to-left marks
self.make_success_test("Char RTL Mark", "'\\u{200F}'", "char", '\u200F')
# Combining characters
self.make_success_test("Char Combining Acute", "'\\u{0301}'", "char", '\u0301')
# Low Unicode values
self.make_success_test("Char Unicode Zero", "'\\u{0}'", "char", '\0')
self.make_success_test("Char Unicode One", "'\\u{1}'", "char", '\x01')
# High Unicode values (but valid)
self.make_success_test("Char Unicode High Valid", "'\\u{10FFFF}'", "char", '\U0010FFFF')
def generate_case_sensitivity_tests(self):
"""Generate tests for case sensitivity in escape sequences."""
if self.ENABLE_CHAR_HEX_ESCAPE:
# Hex escapes - lowercase x
self.make_success_test("Char Hex Lowercase x", "'\\\\x41'", "char", 'A')
# Hex digits - both cases
self.make_success_test("Char Hex Digits Uppercase", "'\\\\xFF'", "char", '\xFF')
self.make_success_test("Char Hex Digits Lowercase", "'\\\\xff'", "char", '\xff')
self.make_success_test("Char Hex Digits Mixed", "'\\\\xAb'", "char", '\xAB')
if self.ENABLE_UNICODE:
# Unicode escapes - lowercase u
self.make_success_test("Char Unicode Lowercase u", "'\\u{41}'", "char", 'A')
# Unicode hex digits - both cases
self.make_success_test("Char Unicode Hex Uppercase", "'\\u{1F600}'", "char", '😀')
self.make_success_test("Char Unicode Hex Lowercase", "'\\u{1f600}'", "char", '😀')
self.make_success_test("Char Unicode Hex Mixed", "'\\u{1F60a}'", "char", '😊')
def generate_all_tests(self) -> List[Dict[str, Any]]:
"""Generate all character literal test cases."""
# Basic tests
self.generate_basic_tests()
# Escape sequences
self.generate_escape_sequence_tests()
# Hexadecimal escapes
self.generate_hexadecimal_escape_tests()
if self.ENABLE_UNICODE:
# Unicode escapes
self.generate_unicode_escape_tests()
# Direct Unicode characters
self.generate_unicode_direct_tests()
# Whitespace handling
self.generate_whitespace_tests()
# Error cases
self.generate_error_tests()
# Edge cases
self.generate_edge_case_tests()
# Case sensitivity
self.generate_case_sensitivity_tests()
return self.get_tests()

View File

@ -0,0 +1,304 @@
from typing import List, Dict, Any
from .base_tests import BaseTestGenerator
class FloatTestGenerator(BaseTestGenerator):
"""Generate test cases for floating point literals."""
# Special float values
SPECIAL_VALUES = {
'f32': {
'min': -3.4028235e38,
'max': 3.4028235e38,
'min_positive': 1.1754944e-38,
'epsilon': 1.1920929e-7,
},
'f64': {
'min': -1.7976931348623157e308,
'max': 1.7976931348623157e308,
'min_positive': 2.2250738585072014e-308,
'epsilon': 2.220446049250313e-16,
}
}
def generate_basic_tests(self):
"""Generate basic test cases."""
# Simple default floats (f64)
self.make_success_test("Float Default Simple", "3.14", "f64", 3.14)
self.make_success_test("Float Default Zero", "0.0", "f64", 0.0)
self.make_success_test("Float Default Negative", "-2.5", "f64", -2.5)
self.make_success_test("Float Default One", "1.0", "f64", 1.0)
# Simple with type annotation
self.make_success_test("Float f32 Simple", "3.14:f32", "f32", 3.14)
self.make_success_test("Float f64 Simple", "2.718:f64", "f64", 2.718)
def generate_format_tests(self):
"""Generate tests for different float formats."""
# Leading zeros
self.make_success_test("Float Default Leading Zeros", "00042.5", "f64", 42.5)
self.make_success_test("Float Default Leading Zero Decimal", "0.5", "f64", 0.5)
# Trailing zeros
self.make_success_test("Float Default Trailing Zeros", "3.1400", "f64", 3.14)
# No leading digit
self.make_success_test("Float Default No Leading Digit", ".5", "f64", 0.5)
self.make_success_test("Float Default No Leading Digit Negative", "-.25", "f64", -0.25)
# No trailing digits
self.make_success_test("Float Default No Trailing Digits", "42.", "f64", 42.0)
self.make_success_test("Float Default No Trailing Digits Negative", "-7.", "f64", -7.0)
if self.ENABLE_EXPONENTIAL_LITERALS:
# Scientific notation
self.make_success_test("Float Default Scientific Positive Exp", "1.5e10", "f64", 1.5e10)
self.make_success_test("Float Default Scientific Negative Exp", "2.5e-5", "f64", 2.5e-5)
self.make_success_test("Float Default Scientific Capital E", "3.14E8", "f64", 3.14e8)
self.make_success_test("Float Default Scientific Plus Sign", "1.0e+3", "f64", 1000.0)
# Very small numbers
self.make_success_test("Float Default Very Small", "0.000001", "f64", 0.000001)
if self.ENABLE_EXPONENTIAL_LITERALS:
self.make_success_test("Float Default Scientific Very Small", "1.0e-20", "f64", 1.0e-20)
# Very large numbers
self.make_success_test("Float Default Very Large", "1000000.0", "f64", 1000000.0)
if self.ENABLE_EXPONENTIAL_LITERALS:
self.make_success_test("Float Default Scientific Very Large", "1.0e20", "f64", 1.0e20)
def generate_underscore_tests(self):
"""Generate tests for underscores in floats."""
# Underscores in integer part
self.make_success_test("Float Default Underscore Integer Part",
"1_000_000.5", "f64", 1000000.5)
# Underscores in decimal part
self.make_success_test("Float Default Underscore Decimal Part",
"3.141_592_653", "f64", 3.141592653)
# Underscores in both parts
self.make_success_test("Float Default Underscore Both Parts",
"1_234.567_89", "f64", 1234.56789)
if self.ENABLE_EXPONENTIAL_LITERALS:
# Underscores in scientific notation
self.make_success_test("Float Default Underscore Scientific Mantissa",
"1_000.5e10", "f64", 1000.5e10)
self.make_success_test("Float Default Underscore Scientific Exponent",
"1.5e1_0", "f64", 1.5e10)
# Trailing underscore
self.make_success_test("Float Default Underscore Trailing", "42.5_", "f64", 42.5)
# Double underscore
self.make_success_test("Float Default Underscore Double", "4__2.5", "f64", 42.5)
# With type annotation
self.make_success_test("Float f32 With Underscores",
"1_234.567_89:f32", "f32", 1234.56789)
def generate_special_value_tests(self):
"""Generate tests for special float values."""
# Infinity
self.make_success_test("Float Default Positive Infinity", "inf", "f64", float('inf'))
self.make_success_test("Float Default Negative Infinity", "-inf", "f64", float('-inf'))
self.make_success_test("Float f32 Positive Infinity", "inf:f32", "f32", float('inf'))
self.make_success_test("Float f32 Negative Infinity", "-inf:f32", "f32", float('-inf'))
# NaN
self.make_success_test("Float Default NaN", "nan", "f64", float('nan'))
self.make_success_test("Float f32 NaN", "nan:f32", "f32", float('nan'))
# Note: NaN comparison is special - NaN != NaN, so these tests may need
# special handling in the test runner
def generate_edge_case_tests(self, type_name: str):
"""Generate edge case tests for a specific float type."""
values = self.SPECIAL_VALUES[type_name]
if self.ENABLE_EXPONENTIAL_LITERALS:
# Maximum value
self.make_success_test(f"Float {type_name} Max Value",
f"{values['max']}:{type_name}", type_name, values['max'])
# Minimum value (most negative)
self.make_success_test(f"Float {type_name} Min Value",
f"{values['min']}:{type_name}", type_name, values['min'])
# Smallest positive normalized value
self.make_success_test(f"Float {type_name} Min Positive",
f"{values['min_positive']}:{type_name}",
type_name, values['min_positive'])
# Machine epsilon
self.make_success_test(f"Float {type_name} Epsilon",
f"{values['epsilon']}:{type_name}",
type_name, values['epsilon'])
# Near zero
self.make_success_test(f"Float {type_name} Near Zero Positive",
f"1e-30:{type_name}", type_name, 1e-30)
self.make_success_test(f"Float {type_name} Near Zero Negative",
f"-1e-30:{type_name}", type_name, -1e-30)
# Subnormal numbers
if type_name == 'f64':
self.make_success_test("Float f64 Subnormal",
"1e-320:f64", "f64", 1e-320)
elif type_name == 'f32':
self.make_success_test("Float f32 Subnormal",
"1e-40:f32", "f32", 1e-40)
def generate_overflow_tests(self):
"""Generate overflow tests."""
if self.ENABLE_EXPONENTIAL_LITERALS:
# f32 overflow
self.make_error_test("Float f32 Overflow Positive",
"1e40:f32",
"Float overflow: value exceeds range for f32.")
self.make_error_test("Float f32 Overflow Negative",
"-1e40:f32",
"Float overflow: value exceeds range for f32.")
# f64 overflow (extremely large values)
self.make_error_test("Float f64 Overflow Positive",
"1e310:f64",
"Float overflow: value exceeds range for f64.")
self.make_error_test("Float f64 Overflow Negative",
"-1e310:f64",
"Float overflow: value exceeds range for f64.")
def generate_precision_tests(self):
"""Generate tests for precision limits."""
# f32 precision (~7 decimal digits)
self.make_success_test("Float f32 Precision Limit",
"1.2345678:f32", "f32", 1.2345678)
self.make_success_test("Float f32 High Precision",
"3.141592653589793:f32", "f32", 3.141592653589793)
# f64 precision (~15 decimal digits)
self.make_success_test("Float f64 Precision Limit",
"1.234567890123456:f64", "f64", 1.234567890123456)
self.make_success_test("Float f64 High Precision",
"3.141592653589793238:f64", "f64", 3.141592653589793238)
# Very close numbers
self.make_success_test("Float f64 Close Numbers 1",
"1.0000000000000001:f64", "f64", 1.0000000000000001)
self.make_success_test("Float f64 Close Numbers 2",
"1.0000000000000002:f64", "f64", 1.0000000000000002)
def generate_error_tests(self):
"""Generate error tests."""
# Invalid formats
self.make_error_test("Float Invalid Multiple Decimal Points",
"3.14.159",
"Invalid float literal: unexpected '.' in float.")
self.make_error_test("Float Invalid Characters",
"3.1a4",
"Invalid float literal: unexpected 'a' in float.")
if self.ENABLE_EXPONENTIAL_LITERALS:
# Invalid scientific notation
self.make_error_test("Float Invalid Scientific No Exponent",
"3.14e",
"Invalid float literal: missing exponent value.")
self.make_error_test("Float Invalid Scientific Double E",
"3.14e10e5",
"Invalid float literal: unexpected 'e' in float.")
self.make_error_test("Float Invalid Scientific Invalid Exponent",
"3.14eX",
"Invalid float literal: unexpected 'X' in float.")
# Invalid type annotations
self.make_error_test("Float Invalid Type Annotation",
"3.14:i32",
"Invalid float type: must be of type 'f64' or 'f32'.")
self.make_error_test("Float Invalid Type Name",
"3.14:f16",
"Invalid float type: must be of type 'f64' or 'f32'.")
# Comma separators not allowed
self.make_error_test("Float Invalid Comma Separator",
"1,234.56",
"Invalid decimal literal: unexpected ',' in decimal integer.")
def generate_whitespace_tests(self):
"""Generate tests with whitespace."""
self.make_success_test("Float Default Leading Whitespace",
" 3.14", "f64", 3.14)
self.make_success_test("Float Default Trailing Whitespace",
"3.14 ", "f64", 3.14)
self.make_success_test("Float Default Both Whitespace",
" 3.14 ", "f64", 3.14)
self.make_success_test("Float f32 With Whitespace",
" 2.718:f32 ", "f32", 2.718)
def generate_mathematical_constants_tests(self):
"""Generate tests for common mathematical constants."""
# Pi
self.make_success_test("Float Default Pi Approximate",
"3.141592653589793", "f64", 3.141592653589793)
self.make_success_test("Float f32 Pi Approximate",
"3.1415927:f32", "f32", 3.1415927)
# Euler's number
self.make_success_test("Float Default Euler Approximate",
"2.718281828459045", "f64", 2.718281828459045)
self.make_success_test("Float f32 Euler Approximate",
"2.7182817:f32", "f32", 2.7182817)
# Golden ratio
self.make_success_test("Float Default Golden Ratio",
"1.618033988749895", "f64", 1.618033988749895)
# Square root of 2
self.make_success_test("Float Default Sqrt2",
"1.4142135623730951", "f64", 1.4142135623730951)
def generate_signed_zero_tests(self):
"""Generate tests for signed zeros."""
self.make_success_test("Float Default Positive Zero", "0.0", "f64", 0.0)
self.make_success_test("Float Default Negative Zero", "-0.0", "f64", -0.0)
self.make_success_test("Float f32 Positive Zero", "0.0:f32", "f32", 0.0)
self.make_success_test("Float f32 Negative Zero", "-0.0:f32", "f32", -0.0)
# Note: In IEEE 754, +0.0 and -0.0 are distinct values but compare equal
def generate_all_tests(self) -> List[Dict[str, Any]]:
"""Generate all test cases."""
# Basic tests
self.generate_basic_tests()
# Format variations
self.generate_format_tests()
# Underscores
self.generate_underscore_tests()
# Special values (inf, nan)
# self.generate_special_value_tests()
# Edge cases for each type
for type_name in ['f32', 'f64']:
self.generate_edge_case_tests(type_name)
# Overflow tests
self.generate_overflow_tests()
# Precision tests
self.generate_precision_tests()
# Error tests
self.generate_error_tests()
# Whitespace tests
self.generate_whitespace_tests()
# Mathematical constants
self.generate_mathematical_constants_tests()
# Signed zeros
self.generate_signed_zero_tests()
return self.tests

View File

@ -0,0 +1,11 @@
from typing import List, Dict, Any
from .base_tests import BaseTestGenerator
class GeneralTestGenerator(BaseTestGenerator):
def generate_all_tests(self) -> List[Dict[str, Any]]:
"""Generate all test cases."""
self.add_test("Empty_Statement", "", [], [], [])
return self.tests

View File

@ -0,0 +1,375 @@
from typing import List, Dict, Any
from .base_tests import BaseTestGenerator
class IdentifierTestGenerator(BaseTestGenerator):
"""Generate test cases for identifiers and identifier literals."""
# Reserved words that might be operators or keywords
RESERVED_WORDS = [
'if', 'while', 'for', 'match', 'break', 'continue',
'fn', 'struct', 'union', 'enum', 'trait', 'impl', 'inher',
'dup', 'drop', 'swap', 'over', 'rot', 'pick', 'roll', 'depth',
]
def generate_basic_identifier_tests(self):
"""Generate basic identifier tests."""
# Simple identifiers
self.make_success_test("Identifier Simple Lowercase", "hello",
"identifier", "hello")
self.make_success_test("Identifier Simple Uppercase", "HELLO",
"identifier", "HELLO")
self.make_success_test("Identifier Mixed Case", "HelloWorld",
"identifier", "HelloWorld")
self.make_success_test("Identifier Single Letter", "x",
"identifier", "x")
self.make_success_test("Identifier Single Letter Upper", "X",
"identifier", "X")
# Identifiers with numbers
self.make_success_test("Identifier With Numbers", "var123",
"identifier", "var123")
self.make_success_test("Identifier Numbers End", "myVar2",
"identifier", "myVar2")
self.make_success_test("Identifier Mixed Numbers", "a1b2c3",
"identifier", "a1b2c3")
# Identifiers with underscores
self.make_success_test("Identifier With Underscore", "hello_world",
"identifier", "hello_world")
self.make_success_test("Identifier Leading Underscore", "_private",
"identifier", "_private")
self.make_success_test("Identifier Multiple Underscores", "my_long_var_name",
"identifier", "my_long_var_name")
self.make_success_test("Identifier Double Underscore", "my__var",
"identifier", "my__var")
self.make_success_test("Identifier Trailing Underscore", "var_",
"identifier", "var_")
self.make_success_test("Identifier Only Underscores", "___",
"identifier", "___")
# Snake case
self.make_success_test("Identifier Snake Case", "my_variable_name",
"identifier", "my_variable_name")
# Camel case
self.make_success_test("Identifier Camel Case", "myVariableName",
"identifier", "myVariableName")
# Pascal case
self.make_success_test("Identifier Pascal Case", "MyClassName",
"identifier", "MyClassName")
# All caps with underscores
self.make_success_test("Identifier All Caps", "MY_CONSTANT",
"identifier", "MY_CONSTANT")
# Others
self.make_success_test("Identifier With Dash", "my-var",
"identifier", "my-var")
def generate_identifier_literal_tests(self):
"""Generate identifier literal tests (with :: prefix)."""
# Simple identifier literals
self.make_success_test("Identifier Literal Simple", "::hello",
"identifier_literal", "hello")
self.make_success_test("Identifier Literal Uppercase", "::Point",
"identifier_literal", "Point")
self.make_success_test("Identifier Literal Snake Case", "::my_var",
"identifier_literal", "my_var")
# Type names
self.make_success_test("Identifier Literal Type i64", "::i64",
"identifier_literal", "i64")
self.make_success_test("Identifier Literal Type String", "::String",
"identifier_literal", "String")
self.make_success_test("Identifier Literal Type Point", "::Point",
"identifier_literal", "Point")
# Trait names
self.make_success_test("Identifier Literal Trait Addable", "::Addable",
"identifier_literal", "Addable")
self.make_success_test("Identifier Literal Trait Drawable", "::Drawable",
"identifier_literal", "Drawable")
# Field names
self.make_success_test("Identifier Literal Field x", "::x",
"identifier_literal", "x")
self.make_success_test("Identifier Literal Field width", "::width",
"identifier_literal", "width")
# With underscores
self.make_success_test("Identifier Literal With Underscore", "::_private",
"identifier_literal", "_private")
self.make_success_test("Identifier Literal Multiple Underscores", "::my_long_name",
"identifier_literal", "my_long_name")
# With numbers
self.make_success_test("Identifier Literal With Numbers", "::var123",
"identifier_literal", "var123")
def generate_whitespace_tests(self):
"""Generate tests with whitespace around identifiers."""
# Regular identifiers with whitespace
self.make_success_test("Identifier Leading Whitespace", " hello",
"identifier", "hello")
self.make_success_test("Identifier Trailing Whitespace", "hello ",
"identifier", "hello")
self.make_success_test("Identifier Both Whitespace", " hello ",
"identifier", "hello")
self.make_success_test("Identifier Tab Before", "\\thello",
"identifier", "hello")
# Identifier literals with whitespace
self.make_success_test("Identifier Literal Leading Whitespace", " ::hello",
"identifier_literal", "hello")
self.make_success_test("Identifier Literal Trailing Whitespace", "::hello ",
"identifier_literal", "hello")
self.make_success_test("Identifier Literal Both Whitespace", " ::hello ",
"identifier_literal", "hello")
def generate_long_identifier_tests(self):
"""Generate tests for longer identifiers."""
# Moderately long
self.make_success_test("Identifier Moderate Length",
"thisIsAReasonablyLongVariableName",
"identifier", "thisIsAReasonablyLongVariableName")
# Very long
long_name = "this_is_a_very_long_identifier_name_that_someone_might_use_for_some_reason"
self.make_success_test("Identifier Very Long", long_name,
"identifier", long_name)
# Long with numbers
long_with_nums = "variable_with_many_numbers_123_456_789_000"
self.make_success_test("Identifier Long With Numbers", long_with_nums,
"identifier", long_with_nums)
def generate_error_tests(self):
"""Generate error test cases for identifiers."""
# Starting with number
self.make_error_test("Identifier Starting With Number",
"123abc",
"Invalid decimal literal: unexpected 'a' in decimal integer.")
# Invalid characters
self.make_success_test("Identifier With Octothorpe", "my#var",
"identifier", "my")
# self.make_error_test("Identifier With Space",
# "my var",
# "Invalid identifier: whitespace not allowed in identifiers.")
self.make_error_test("Identifier With Colon",
"my:var",
"Invalid identifier: ':' is not allowed in identifiers.")
# Note: :: is allowed only as prefix for identifier literals
self.make_error_test("Identifier Double Colon Inside",
"my::var",
"Invalid identifier: ':' is not allowed in identifiers.")
# Special characters
# self.make_error_test("Identifier With At",
# "@variable",
# "Invalid identifier: '@' is not allowed in identifiers.")
# self.make_error_test("Identifier With Dollar",
# "$variable",
# "Invalid identifier: '$' is not allowed in identifiers.")
# self.make_error_test("Identifier With Percent",
# "%variable",
# "Invalid identifier: '%' is not allowed in identifiers.")
# Brackets not allowed
# self.make_error_test("Identifier With Brackets",
# "my[var]",
# "Invalid identifier: brackets not allowed in identifiers.")
# self.make_error_test("Identifier With Braces",
# "my{var}",
# "Invalid identifier: braces not allowed in identifiers.")
# self.make_error_test("Identifier With Parens",
# "my(var)",
# "Invalid identifier: parentheses not allowed in identifiers.")
# Quotes not allowed
# self.make_error_test("Identifier With Single Quote",
# "my'var",
# "Invalid identifier: quotes not allowed in identifiers.")
# self.make_error_test("Identifier With Double Quote",
# 'my"var',
# "Invalid identifier: quotes not allowed in identifiers.")
# Only numbers (not valid identifier)
self.make_success_test("Identifier Only Numbers", "123", "i64", 123)
# Empty identifier literal
self.make_error_test("Identifier Literal Empty",
"::",
"Invalid identifier literal: empty identifier after '::'.")
def generate_case_sensitivity_tests(self):
"""Generate tests showing case sensitivity."""
# These should all be different identifiers
self.make_success_test("Identifier Case Lower", "variable",
"identifier", "variable")
self.make_success_test("Identifier Case Upper", "VARIABLE",
"identifier", "VARIABLE")
self.make_success_test("Identifier Case Mixed", "Variable",
"identifier", "Variable")
self.make_success_test("Identifier Case Camel", "variableName",
"identifier", "variableName")
self.make_success_test("Identifier Case Pascal", "VariableName",
"identifier", "VariableName")
def generate_reserved_word_tests(self):
"""Generate tests for words that might be reserved."""
# Note: In the spec, these are treated as identifiers/operators
# This tests that they're recognized correctly
for word in self.RESERVED_WORDS:
self.make_success_test(f"Identifier Reserved Word {word}",
word, "identifier", word)
def generate_all_tests(self) -> List[Dict[str, Any]]:
"""Generate all identifier test cases."""
# Basic identifiers
self.generate_basic_identifier_tests()
# Identifier literals
self.generate_identifier_literal_tests()
# Whitespace handling
self.generate_whitespace_tests()
# Long identifiers
self.generate_long_identifier_tests()
# Error cases
self.generate_error_tests()
# Case sensitivity
self.generate_case_sensitivity_tests()
# Reserved words
self.generate_reserved_word_tests()
return self.get_tests()
class BooleanTestGenerator(BaseTestGenerator):
"""Generate test cases for boolean literals."""
def generate_basic_tests(self):
"""Generate basic boolean literal tests."""
# True
self.make_success_test("Bool True", "true", "bool", True)
# False
self.make_success_test("Bool False", "false", "bool", False)
def generate_whitespace_tests(self):
"""Generate tests with whitespace around booleans."""
# True with whitespace
self.make_success_test("Bool True Leading Whitespace", " true",
"bool", True)
self.make_success_test("Bool True Trailing Whitespace", "true ",
"bool", True)
self.make_success_test("Bool True Both Whitespace", " true ",
"bool", True)
self.make_success_test("Bool True Tab Before", "\\ttrue",
"bool", True)
# False with whitespace
self.make_success_test("Bool False Leading Whitespace", " false",
"bool", False)
self.make_success_test("Bool False Trailing Whitespace", "false ",
"bool", False)
self.make_success_test("Bool False Both Whitespace", " false ",
"bool", False)
self.make_success_test("Bool False Tab Before", "\\tfalse",
"bool", False)
def generate_error_tests(self):
"""Generate error test cases for booleans."""
# Capitalized (case sensitive)
self.make_success_test("Bool True Capitalized", "True",
"identifier", "True")
self.make_success_test("Bool False Capitalized", "False",
"identifier", "False")
# All caps
self.make_success_test("Bool True All Caps", "TRUE",
"identifier", "TRUE")
self.make_success_test("Bool False All Caps", "FALSE",
"identifier", "FALSE")
# Mixed case
self.make_success_test("Bool True Mixed Case", "tRuE",
"identifier", "tRuE")
self.make_success_test("Bool False Mixed Case", "fAlSe",
"identifier", "fAlSe")
# Numeric representations
self.make_success_test("Bool Numeric 1", "1", "i64", 1)
self.make_success_test("Bool Numeric 0", "0", "i64", 0)
# String representations
if self.ENABLE_STRINGS:
self.make_success_test("Bool String True", '"true"',
"string", "true")
self.make_success_test("Bool String False", '"false"',
"string", "false")
# Other languages
self.make_success_test("Bool Yes", "yes",
"identifier", "yes")
self.make_success_test("Bool No", "no",
"identifier", "no")
# Typos
self.make_success_test("Bool Typo Ture", "ture",
"identifier", "ture")
self.make_success_test("Bool Typo Flase", "flase",
"identifier", "flase")
def generate_multiple_bool_tests(self):
"""Generate tests with multiple boolean values."""
# Multiple values on stack
self.make_multi_value_test("Bool Multiple True False",
"true false",
[("bool", True), ("bool", False)])
self.make_multi_value_test("Bool Multiple Same",
"true true",
[("bool", True), ("bool", True)])
self.make_multi_value_test("Bool Three Values",
"true false true",
[("bool", True), ("bool", False), ("bool", True)])
def generate_all_tests(self) -> List[Dict[str, Any]]:
"""Generate all boolean test cases."""
# Basic tests
self.generate_basic_tests()
# Whitespace handling
self.generate_whitespace_tests()
# Error cases
self.generate_error_tests()
# Multiple booleans
self.generate_multiple_bool_tests()
return self.get_tests()

View File

@ -0,0 +1,225 @@
from typing import List, Dict, Any
from .base_tests import BaseTestGenerator
class IntegerTestGenerator(BaseTestGenerator):
"""Generate test cases for integer literals."""
# Type ranges
TYPE_RANGES = {
'i8': (-128, 127),
'i16': (-32768, 32767),
'i32': (-2147483648, 2147483647),
'i64': (-9223372036854775808, 9223372036854775807),
'u8': (0, 255),
'u16': (0, 65535),
'u32': (0, 4294967295),
'u64': (0, 18446744073709551615),
}
TYPE_VALUES = {
'i8': (-128, 127),
'i16': (-32768, 32767),
'i32': (-2147483648, 2147483647),
'i64': ("INT64_MIN", 9223372036854775807),
'u8': (0, 255),
'u16': (0, 65535),
'u32': (0, 4294967295),
'u64': (0, "UINT64_MAX"),
}
def generate_basic_tests(self):
"""Generate basic test cases."""
# Simple default integers
self.make_success_test("Integer Default Decimal 0", "0", "i64", 0)
self.make_success_test("Integer Default Decimal -1", "-1", "i64", -1)
self.make_success_test("Integer Default Decimal 42", "42", "i64", 42)
self.make_success_test("Integer Default Decimal Leading Zeros", "00042", "i64", 42)
def generate_default_base_tests(self):
"""Generate tests for default type with different bases."""
# Hexadecimal
self.make_success_test("Integer Default Hex 0xFF", "0xFF", "i64", 255)
self.make_success_test("Integer Default Hex 0xdeadbeef", "0xdeadbeef", "i64", 3735928559)
self.make_success_test("Integer Default Hex Max", "0x7FFFFFFFFFFFFFFF", "i64", 9223372036854775807)
# Binary
self.make_success_test("Integer Default Binary 0b1010", "0b1010", "i64", 10)
self.make_success_test("Integer Default Binary All Ones", "0b1111111111111111", "i64", 65535)
# Octal
self.make_success_test("Integer Default Octal 0o755", "0o755", "i64", 493)
self.make_success_test("Integer Default Octal Max Three Digits", "0o777", "i64", 511)
def generate_default_edge_cases(self):
"""Generate edge case tests for default type."""
# Min/max values
self.make_success_test("Integer Default Decimal Max i64",
"9223372036854775807", "i64", 9223372036854775807)
self.make_success_test("Integer Default Decimal Min i64",
"-9223372036854775808", "i64", "INT64_MIN")
# Underscores
self.make_success_test("Integer Default Decimal with Underscore",
"1_000_000", "i64", 1000000)
self.make_success_test("Integer Default Underscore End", "42_", "i64", 42)
self.make_success_test("Integer Default Underscore Double", "4__2", "i64", 42)
# Whitespace
self.make_success_test("Integer Default Whitespace", " 42 ", "i64", 42)
# Zeros in different bases
self.make_success_test("Integer Default Hex Zero", "0x0", "i64", 0)
self.make_success_test("Integer Default Binary Zero", "0b0", "i64", 0)
self.make_success_test("Integer Default Octal Zero", "0o0", "i64", 0)
def generate_default_error_tests(self):
"""Generate error tests for default type."""
self.make_error_test("Integer Default Decimal with Commas Invalid",
"1,000,000",
"Invalid decimal literal: unexpected ',' in decimal integer.")
self.make_error_test("Integer Default Invalid Characters",
"12a3",
"Invalid decimal literal: unexpected 'a' in decimal integer.")
self.make_error_test("Integer Default Invalid Prefix",
"0b2",
"Invalid binary literal: unexpected '2' in binary integer.")
def generate_typed_tests(self, type_name: str):
"""Generate tests for a specific type across all bases."""
min_range, max_range = self.TYPE_RANGES[type_name]
min_value, max_value = self.TYPE_VALUES[type_name]
is_unsigned = type_name.startswith('u')
# Basic decimal
test_val = 42 if max_range >= 42 else max_range
self.make_success_test(f"Integer {type_name} Decimal Positive",
f"{test_val}:{type_name}", type_name, test_val)
# Zero
self.make_success_test(f"Integer {type_name} Zero",
f"0:{type_name}", type_name, 0)
# Negative (only for signed types)
if not is_unsigned:
neg_val = -100 if min_range <= -100 else min_range
self.make_success_test(f"Integer {type_name} Decimal Negative",
f"{neg_val}:{type_name}", type_name, neg_val)
# Hexadecimal
hex_val = min(255, max_range)
self.make_success_test(f"Integer {type_name} Hex",
f"0x{hex_val:X}:{type_name}", type_name, hex_val)
# Binary
bin_val = min(15, max_range)
self.make_success_test(f"Integer {type_name} Binary",
f"0b{bin_val:b}:{type_name}", type_name, bin_val)
# Octal
oct_val = min(63, max_range)
self.make_success_test(f"Integer {type_name} Octal",
f"0o{oct_val:o}:{type_name}", type_name, oct_val)
# Max value
self.make_success_test(f"Integer {type_name} Max Value",
f"{max_range}:{type_name}", type_name, max_value)
# Min value
self.make_success_test(f"Integer {type_name} Min Value",
f"{min_range}:{type_name}", type_name, min_value)
# Overflow
overflow_val = max_range + 1
self.make_error_test(f"Integer {type_name} Overflow",
f"{overflow_val}:{type_name}",
f"Integer overflow: value exceeds range for {type_name}.")
# Underflow
underflow_val = min_range - 1
self.make_error_test(f"Integer {type_name} Underflow",
f"{underflow_val}:{type_name}",
f"Integer overflow: value exceeds range for {type_name}.")
# Underscores with type annotation
if max_range >= 1000000:
self.make_success_test(f"Integer {type_name} With Underscores",
f"1_000_000:{type_name}", type_name, 1000000)
def generate_special_typed_tests(self):
"""Generate special tests for specific types."""
# Special values for specific types
self.make_success_test("Integer i8 Hex Max", "0x7F:i8", "i8", 127)
self.make_success_test("Integer i8 Binary Max", "0b01111111:i8", "i8", 127)
self.make_success_test("Integer i8 Octal Max", "0o177:i8", "i8", 127)
self.make_success_test("Integer i8 Negative Hex", "-0x80:i8", "i8", -128)
self.make_success_test("Integer u8 Hex Max", "0xFF:u8", "u8", 255)
self.make_success_test("Integer u8 Binary Max", "0b11111111:u8", "u8", 255)
self.make_success_test("Integer u8 Octal Max", "0o377:u8", "u8", 255)
self.make_success_test("Integer i16 Hex Sample", "0x1234:i16", "i16", 4660)
self.make_success_test("Integer i16 Binary Sample",
"0b1111111100000000:i16", "i16", -256)
self.make_success_test("Integer i16 Octal Sample", "0o1234:i16", "i16", 668)
self.make_success_test("Integer u16 Hex Max", "0xFFFF:u16", "u16", 65535)
self.make_success_test("Integer u16 Binary Max",
"0b1111111111111111:u16", "u16", 65535)
self.make_success_test("Integer u16 Octal Max", "0o177777:u16", "u16", 65535)
self.make_success_test("Integer u16 Decimal Mid", "50000:u16", "u16", 50000)
self.make_success_test("Integer i32 Hex Sample", "0xABCD:i32", "i32", 43981)
self.make_success_test("Integer i32 Binary Sample",
"0b11110000:i32", "i32", 240)
self.make_success_test("Integer u32 Hex Max", "0xFFFFFFFF:u32", "u32", 4294967295)
self.make_success_test("Integer u32 Binary Sample",
"0b11111111000000001111111100000000:u32",
"u32", 4278255360)
self.make_success_test("Integer u32 Octal Max",
"0o37777777777:u32", "u32", 4294967295)
self.make_success_test("Integer u32 Decimal Mid", "1000000:u32", "u32", 1000000)
self.make_success_test("Integer i64 Decimal Positive 42", "42:i64", "i64", 42)
self.make_success_test("Integer i64 Hex 0xFF", "0xFF:i64", "i64", 255)
self.make_success_test("Integer i64 Binary 0b1010", "0b1010:i64", "i64", 10)
self.make_success_test("Integer i64 Octal 0o755", "0o755:i64", "i64", 493)
self.make_success_test("Integer u64 Hex Max",
"0xFFFFFFFFFFFFFFFF:u64",
"u64", "UINT64_MAX")
self.make_success_test("Integer u64 Binary Sample",
"0b1010101010101010:u64", "u64", 43690)
self.make_success_test("Integer u64 Octal Sample", "0o7777:u64", "u64", 4095)
self.make_success_test("Integer u64 Decimal", "42:u64", "u64", 42)
def generate_underscore_tests(self):
"""Generate tests for underscores in different bases."""
self.make_success_test("Integer Hex With Underscores",
"0xDEAD_BEEF:i64", "i64", 3735928559)
self.make_success_test("Integer Binary With Underscores",
"0b1111_0000_1010_0101:i32", "i32", 61605)
self.make_success_test("Integer Octal With Underscores",
"0o7_7_7:i16", "i16", 511)
def generate_all_tests(self) -> List[Dict[str, Any]]:
"""Generate all test cases."""
# Basic tests
self.generate_basic_tests()
# Default type (i64) comprehensive tests
self.generate_default_base_tests()
self.generate_default_edge_cases()
self.generate_default_error_tests()
# Tests for each specific type
for type_name in ['i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64']:
self.generate_typed_tests(type_name)
self.generate_special_typed_tests()
# Additional edge cases
self.generate_underscore_tests()
return self.tests

View File

@ -0,0 +1,477 @@
from typing import List, Dict, Any
from .base_tests import BaseTestGenerator
class StringTestGenerator(BaseTestGenerator):
"""Generate test cases for string literals."""
# Common escape sequences
ESCAPE_SEQUENCES = {
'\\n': '\n', # Newline
'\\r': '\r', # Carriage return
'\\t': '\t', # Tab
'\\\\': '\\', # Backslash
'\\"': '"', # Double quote
"\\'": "'", # Single quote
'\\0': '\0', # Null character
}
def generate_basic_tests(self):
"""Generate basic string literal tests."""
# Empty string
self.make_success_test("String Empty", '""', "string", "")
# Simple strings
self.make_success_test("String Simple", '"hello"', "string", "hello")
self.make_success_test("String With Space", '"hello world"', "string", "hello world")
self.make_success_test("String Single Char", '"A"', "string", "A")
# Multiple words
self.make_success_test("String Multiple Words",
'"The quick brown fox"', "string", "The quick brown fox")
# Numbers in strings
self.make_success_test("String With Numbers", '"abc123"', "string", "abc123")
self.make_success_test("String Only Numbers", '"12345"', "string", "12345")
# Mixed case
self.make_success_test("String Mixed Case", '"HeLLo WoRLd"', "string", "HeLLo WoRLd")
# Special characters
self.make_success_test("String With Punctuation",
'"Hello, World!"', "string", "Hello, World!")
self.make_success_test("String With Symbols",
'"@#$%^&*()"', "string", "@#$%^&*()")
# Spaces
self.make_success_test("String Multiple Spaces",
'"hello world"', "string", "hello world")
self.make_success_test("String Leading Space", '" hello"', "string", " hello")
self.make_success_test("String Trailing Space", '"hello "', "string", "hello ")
self.make_success_test("String Only Spaces", '" "', "string", " ")
def generate_escape_sequence_tests(self):
"""Generate tests for escape sequences."""
# Individual escape sequences
self.make_success_test("String Newline", '"hello\\nworld"',
"string", "hello\\nworld")
self.make_success_test("String Tab", '"hello\\tworld"',
"string", "hello\\tworld")
self.make_success_test("String Carriage Return", '"hello\\rworld"',
"string", "hello\\rworld")
self.make_success_test("String Backslash", '"hello\\\\world"',
"string", "hello\\\\world")
self.make_success_test("String Double Quote", '"say \\\\"hello\\\\""',
"string", 'say \\"hello\\"')
self.make_success_test("String Single Quote", '"it\\\'s"',
"string", "it's")
self.make_success_test("String Null Char", '"hello\\0world"',
"string", "hello\\0world")
# Multiple escape sequences
self.make_success_test("String Multiple Escapes",
'"line1\\nline2\\nline3"',
"string", "line1\\nline2\\nline3")
self.make_success_test("String Mixed Escapes",
'"tab\\there\\nnewline\\\\backslash"',
"string", "tab\\there\\nnewline\\\\backslash")
# Escape at start/end
self.make_success_test("String Escape At Start", '"\\nhello"',
"string", "\\nhello")
self.make_success_test("String Escape At End", '"hello\\n"',
"string", "hello\\n")
# Consecutive escapes
self.make_success_test("String Consecutive Escapes", '"\\n\\n\\n"',
"string", "\\n\\n\\n")
self.make_success_test("String All Escapes", '"\\n\\r\\t\\\\"\\\'\\0"',
"string", "\\n\\r\\t\\\\\\\"\\'\\0")
def generate_hexadecimal_escape_tests(self):
"""Generate tests for hexadecimal escape sequences."""
# Basic hex escapes
self.make_success_test("String Hex Letter A", '"\\x41"', "string", "A")
self.make_success_test("String Hex Letter a", '"\\x61"', "string", "a")
self.make_success_test("String Hex Space", '"\\x20"', "string", " ")
self.make_success_test("String Hex Tab", '"\\x09"', "string", "\\t")
self.make_success_test("String Hex Newline", '"\\x0A"', "string", "\\n")
# Multiple hex escapes
self.make_success_test("String Multiple Hex", '"\\x48\\x65\\x6C\\x6C\\x6F"',
"string", "Hello")
# Hex with regular text
self.make_success_test("String Hex Mixed", '"Hello\\x20World"',
"string", "Hello World")
# Extended ASCII
self.make_success_test("String Hex Extended ASCII", '"\\xA9\\xAE"',
"string", "\xA9\xAE")
# Case variations
self.make_success_test("String Hex Uppercase", '"\\xFF"', "string", "\\xFF")
self.make_success_test("String Hex Lowercase", '"\\xff"', "string", "\\xff")
self.make_success_test("String Hex Mixed Case", '"\\xAb"', "string", "\\xAb")
# All hex values
self.make_success_test("String Hex Zero", '"\\x00"', "string", "\\0")
self.make_success_test("String Hex Max", '"\\xFF"', "string", "\\xFF")
def generate_unicode_escape_tests(self):
"""Generate tests for Unicode escape sequences."""
# Basic Unicode escapes
self.make_success_test("String Unicode Letter A", '"\\u{41}"', "string", "A")
self.make_success_test("String Unicode Space", '"\\u{20}"', "string", " ")
# Emoji
self.make_success_test("String Unicode Smiley", '"\\u{1F600}"',
"string", "😀")
self.make_success_test("String Unicode Heart", '"\\u{2764}"',
"string", "")
self.make_success_test("String Unicode Star", '"\\u{2B50}"',
"string", "")
# Multiple emoji
self.make_success_test("String Multiple Emoji",
'"\\u{1F600}\\u{2764}\\u{2B50}"',
"string", "😀❤⭐")
# Greek letters
self.make_success_test("String Unicode Greek Alpha", '"\\u{03B1}"',
"string", "α")
self.make_success_test("String Unicode Greek Beta", '"\\u{03B2}"',
"string", "β")
# Chinese characters
self.make_success_test("String Unicode Chinese", '"\\u{4E2D}\\u{6587}"',
"string", "中文")
# Arabic
self.make_success_test("String Unicode Arabic", '"\\u{0639}\\u{0631}\\u{0628}"',
"string", "عرب")
# Cyrillic
self.make_success_test("String Unicode Cyrillic", '"\\u{0420}\\u{0443}\\u{0441}"',
"string", "Рус")
# Mathematical symbols
self.make_success_test("String Unicode Math", '"\\u{221E}\\u{2211}\\u{222B}"',
"string", "∞∑∫")
# Mixed with regular text
self.make_success_test("String Unicode Mixed", '"Hello \\u{1F600} World"',
"string", "Hello 😀 World")
# Case variations in hex digits
self.make_success_test("String Unicode Hex Uppercase", '"\\u{1F600}"',
"string", "😀")
self.make_success_test("String Unicode Hex Lowercase", '"\\u{1f600}"',
"string", "😀")
self.make_success_test("String Unicode Hex Mixed", '"\\u{1F60a}"',
"string", "😊")
# Variable length code points
self.make_success_test("String Unicode 2 Digits", '"\\u{41}"', "string", "A")
self.make_success_test("String Unicode 4 Digits", '"\\u{03B1}"', "string", "α")
self.make_success_test("String Unicode 5 Digits", '"\\u{1F600}"', "string", "😀")
self.make_success_test("String Unicode 6 Digits", '"\\u{10FFFF}"',
"string", "\U0010FFFF")
def generate_direct_unicode_tests(self):
"""Generate tests for direct Unicode characters in strings."""
# Direct emoji
self.make_success_test("String Direct Emoji", '"Hello 😀 World"',
"string", "Hello 😀 World")
self.make_success_test("String Multiple Direct Emoji", '"😀❤⭐👍"',
"string", "😀❤⭐👍")
# Direct Greek
self.make_success_test("String Direct Greek", '"αβγδ"', "string", "αβγδ")
# Direct Chinese
self.make_success_test("String Direct Chinese", '"你好世界"',
"string", "你好世界")
# Direct Arabic
self.make_success_test("String Direct Arabic", '"مرحبا"', "string", "مرحبا")
# Direct Cyrillic
self.make_success_test("String Direct Cyrillic", '"Привет"',
"string", "Привет")
# Direct mathematical
self.make_success_test("String Direct Math", '"∞∑∫√π"', "string", "∞∑∫√π")
# Mixed scripts
self.make_success_test("String Mixed Scripts", '"Hello 世界 Привет"',
"string", "Hello 世界 Привет")
def generate_multiline_tests(self):
"""Generate tests for strings with embedded newlines."""
# Strings with escape newlines
self.make_success_test("String With Escaped Newlines",
'"line1\\nline2\\nline3"',
"string", "line1\\nline2\\nline3")
# Paragraph-like text
self.make_success_test("String Paragraph",
'"First line.\\nSecond line.\\nThird line."',
"string", "First line.\\nSecond line.\\nThird line.")
# Mixed line endings
self.make_success_test("String Mixed Line Endings",
'"Windows\\r\\nUnix\\nMac\\r"',
"string", "Windows\\r\\nUnix\\nMac\\r")
def generate_whitespace_tests(self):
"""Generate tests with various whitespace."""
# Leading/trailing whitespace outside quotes
self.make_success_test("String Leading Whitespace Outside",
' "hello"', "string", "hello")
self.make_success_test("String Trailing Whitespace Outside",
'"hello" ', "string", "hello")
self.make_success_test("String Both Whitespace Outside",
' "hello" ', "string", "hello")
# Tabs outside quotes
self.make_success_test("String Tab Before", '\t"hello"', "string", "hello")
# Mixed whitespace
self.make_success_test("String Tabs And Newlines Inside",
'"hello\\t\\tworld\\n\\ntest"',
"string", "hello\\t\\tworld\\n\\ntest")
# Only whitespace inside
self.make_success_test("String Only Tabs", '"\\t\\t\\t"', "string", "\\t\\t\\t")
self.make_success_test("String Only Newlines", '"\\n\\n\\n"', "string", "\\n\\n\\n")
self.make_success_test("String Mixed Whitespace", '" \\t\\n\\r "',
"string", " \\t\\n\\r ")
def generate_long_string_tests(self):
"""Generate tests for longer strings."""
# Sentence
self.make_success_test("String Sentence",
'"The quick brown fox jumps over the lazy dog."',
"string", "The quick brown fox jumps over the lazy dog.")
# Multiple sentences
self.make_success_test("String Multiple Sentences",
'"First sentence. Second sentence. Third sentence."',
"string", "First sentence. Second sentence. Third sentence.")
# Long string with escapes
long_str = '"This is a long string.\\nIt has multiple lines.\\nAnd some tabs\\there.\\nPlus quotes "like this"."'
expected = "This is a long string.\\nIt has multiple lines.\\nAnd some tabs\\there.\\nPlus quotes \\\"like this\\\"."
self.make_success_test("String Long With Escapes", long_str,
"string", expected)
# Repetitive string
self.make_success_test("String Repetitive", '"aaaaaaaaaa"',
"string", "aaaaaaaaaa")
# All ASCII printable characters
printable = "".join(chr(i) for i in range(32, 127) if chr(i) not in ['"', '\\'])
self.make_success_test("String ASCII Printable",
f'"{printable}"', "string", printable)
def generate_special_content_tests(self):
"""Generate tests for strings with special content."""
# Code-like strings
self.make_success_test("String Code Like",
'"int main() { return 0; }"',
"string", "int main() { return 0; }")
# JSON-like strings
self.make_success_test("String JSON Like",
'"{{\\\\"key\\\\": \\\\"value\\\\"}}"',
"string", '{\\"key\\": \\"value\\"}')
# URL
self.make_success_test("String URL",
'"https://example.com/path?query=value"',
"string", "https://example.com/path?query=value")
# Email
self.make_success_test("String Email",
'"user@example.com"', "string", "user@example.com")
# File path (Unix)
self.make_success_test("String Unix Path",
'"/home/user/file.txt"',
"string", "/home/user/file.txt")
# File path (Windows)
self.make_success_test("String Windows Path",
'"C:\\\\Users\\\\file.txt"',
"string", "C:\\\\Users\\\\file.txt")
# SQL-like
self.make_success_test("String SQL Like",
'"SELECT * FROM users WHERE id = 1"',
"string", "SELECT * FROM users WHERE id = 1")
# Regular expression
self.make_success_test("String Regex Like",
'"[a-zA-Z0-9]+"', "string", "[a-zA-Z0-9]+")
def generate_error_tests(self):
"""Generate error test cases."""
# Unclosed string
self.make_error_test("String Unclosed",
'"hello',
"Invalid string literal: unclosed string literal.")
# Unescaped newline
self.make_error_test("String Unescaped Newline",
'"hello\\nworld"',
"Invalid string literal: unescaped newline in string literal.")
# Invalid escape sequence
self.make_error_test("String Invalid Escape",
'"hello\\\\qworld"',
"Invalid string literal: unknown escape sequence '\\\\q'.")
# Hex escape too short
self.make_error_test("String Hex Too Short",
'"\\x4"',
"Invalid string literal: hexadecimal escape must have exactly 2 digits.")
# Hex escape too long
self.make_error_test("String Hex Too Long",
'"\\\\x414"',
"Invalid string literal: hexadecimal escape must have exactly 2 digits.")
# Invalid hex digits
self.make_error_test("String Hex Invalid Digit",
'"\\\\xGG"',
"Invalid string literal: invalid hexadecimal digit 'G'.")
if self.ENABLE_UNICODE:
# Unicode no braces
self.make_error_test("String Unicode No Braces",
'"\\u1F600"',
"Invalid string literal: Unicode escape must use braces \\u{...}.")
# Unicode empty
self.make_error_test("String Unicode Empty",
'"\\u{}"',
"Invalid string literal: empty Unicode escape sequence.")
# Unicode too long
self.make_error_test("String Unicode Too Long",
'"\\u{1234567}"',
"Invalid string literal: Unicode escape sequence too long (max 6 hex digits).")
# Unicode invalid code point (surrogate)
self.make_error_test("String Unicode Surrogate",
'"\\u{D800}"',
"Invalid string literal: invalid Unicode code point (surrogate range).")
# Unicode out of range
self.make_error_test("String Unicode Out Of Range",
'"\\u{110000}"',
"Invalid string literal: Unicode code point out of range (max 0x10FFFF).")
# Unicode invalid hex
self.make_error_test("String Unicode Invalid Hex",
'"\\u{GGGG}"',
"Invalid string literal: invalid hexadecimal digit 'G' in Unicode escape.")
# Unclosed Unicode escape
self.make_error_test("String Unicode Unclosed",
'"\\u{1F600"',
"Invalid string literal: unclosed Unicode escape sequence.")
# Single quotes instead of double
self.make_error_test("String Single Quotes",
"'hello'",
"Invalid string literal: string literals must use double quotes.")
# Backslash at end
self.make_error_test("String Backslash At End",
'"hello\\\\"',
"Invalid string literal: incomplete escape sequence at end.")
def generate_edge_case_tests(self):
"""Generate edge case tests."""
# String with only escape sequences
self.make_success_test("String Only Escapes",
'"\\n\\t\\r"', "string", "\\n\\t\\r")
# String with null characters
self.make_success_test("String With Nulls",
'"a\\0b\\0c"', "string", "a\\0b\\0c")
# Very long escape sequence
self.make_success_test("String Many Escapes",
'"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n"',
"string", "\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n")
if self.ENABLE_UNICODE:
# Mixed escape types
self.make_success_test("String All Escape Types",
'"\\n\\x41\\u{42}test"',
"string", "\nABtest")
# Zero-width characters
self.make_success_test("String Zero Width",
'"hello\\u{200B}world"',
"string", "hello\u200Bworld")
# Combining characters
self.make_success_test("String Combining",
'"e\\u{0301}"', # é as e + combining acute
"string", "e\u0301")
# Right-to-left
self.make_success_test("String RTL Mark",
'"\\u{200F}hello"',
"string", "\u200Fhello")
# Byte order mark
self.make_success_test("String BOM",
'"\\u{FEFF}hello"',
"string", "\uFEFFhello")
def generate_all_tests(self) -> List[Dict[str, Any]]:
"""Generate all string literal test cases."""
if not self.ENABLE_STRINGS:
return []
# Basic tests
self.generate_basic_tests()
# Escape sequences
self.generate_escape_sequence_tests()
# Hexadecimal escapes
self.generate_hexadecimal_escape_tests()
if self.ENABLE_UNICODE:
# Unicode escapes
self.generate_unicode_escape_tests()
# Direct Unicode
self.generate_direct_unicode_tests()
# Multiline strings
self.generate_multiline_tests()
# Whitespace handling
self.generate_whitespace_tests()
# Long strings
self.generate_long_string_tests()
# Special content
self.generate_special_content_tests()
# Error cases
self.generate_error_tests()
# Edge cases
self.generate_edge_case_tests()
return self.get_tests()

View File

@ -0,0 +1,623 @@
from typing import List, Dict, Any
from .base_tests import BaseTestGenerator, Token
class TokenStringTestGenerator(BaseTestGenerator):
"""Generate test cases for token strings (unparsed code blocks in braces)."""
def generate_basic_tests(self):
"""Generate basic token string tests."""
# Empty token string
self.make_token_string_test(
"TokenString Empty",
"{ }",
[]
)
# Single token
self.make_token_string_test(
"TokenString Single Integer",
"{ 42 }",
[Token(type="i64", value=42)]
)
self.make_token_string_test(
"TokenString Single Identifier",
"{ dup }",
[Token(type="identifier", value="dup")]
)
# Two tokens
self.make_token_string_test(
"TokenString Two Integers",
"{ 2 3 }",
[
Token(type="i64", value=2),
Token(type="i64", value=3)
]
)
# Simple expression
self.make_token_string_test(
"TokenString Simple Expression",
"{ 2 3 + }",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Stack manipulation
self.make_token_string_test(
"TokenString Stack Ops",
"{ dup * }",
[
Token(type="identifier", value="dup"),
Token(type="identifier", value="*")
]
)
def generate_literal_tests(self):
"""Generate tests with various literal types inside token strings."""
# Integer literals
self.make_token_string_test(
"TokenString Integer Literals",
"{ 0 42 -10 1000 }",
[
Token(type="i64", value=0),
Token(type="i64", value=42),
Token(type="i64", value=-10),
Token(type="i64", value=1000)
]
)
# Float literals
self.make_token_string_test(
"TokenString Float Literals",
"{ 3.14 -2.5 0.0 }",
[
Token(type="f64", value=3.14),
Token(type="f64", value=-2.5),
Token(type="f64", value=0.0)
]
)
# String literals
if self.ENABLE_STRINGS:
self.make_token_string_test(
"TokenString String Literal",
'{ "hello" }',
[Token(type="String", value="hello")]
)
self.make_token_string_test(
"TokenString Multiple Strings",
'{ "hello" "world" }',
[
Token(type="String", value="hello"),
Token(type="String", value="world")
]
)
# Character literals
self.make_token_string_test(
"TokenString Char Literal",
"{ 'A' }",
[Token(type="char", value='A')]
)
# Boolean literals
self.make_token_string_test(
"TokenString Boolean Literals",
"{ true false }",
[
Token(type="bool", value=True),
Token(type="bool", value=False)
]
)
# Mixed literals
if self.ENABLE_STRINGS:
self.make_token_string_test(
"TokenString Mixed Literals",
'{ 42 3.14 "hello" true \'A\' }',
[
Token(type="i64", value=42),
Token(type="f64", value=3.14),
Token(type="String", value="hello"),
Token(type="bool", value=True),
Token(type="char", value='A')
]
)
def generate_identifier_tests(self):
"""Generate tests with various identifiers inside token strings."""
# Multiple identifiers
self.make_token_string_test(
"TokenString Multiple Identifiers",
"{ dup swap over }",
[
Token(type="identifier", value="dup"),
Token(type="identifier", value="swap"),
Token(type="identifier", value="over")
]
)
# Identifier literals (with ::)
self.make_token_string_test(
"TokenString Identifier Literals",
"{ ::x ::y }",
[
Token(type="identifier_literal", value="x"),
Token(type="identifier_literal", value="y")
]
)
# Mixed identifiers and literals
self.make_token_string_test(
"TokenString Mixed Identifiers",
"{ ::Point get x swap }",
[
Token(type="identifier_literal", value="Point"),
Token(type="identifier", value="get"),
Token(type="identifier", value="x"),
Token(type="identifier", value="swap")
]
)
def generate_nested_tests(self):
"""Generate tests with nested token strings."""
# Single nesting
inner_tokens = [
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
inner_token_string = self.make_token_string_token(inner_tokens)
self.make_token_string_test(
"TokenString Nested Single",
"{ { 2 3 + } }",
[inner_token_string]
)
# Nested with other tokens
self.make_token_string_test(
"TokenString Nested With Others",
"{ x { dup * } }",
[
Token(type="identifier", value="x"),
self.make_token_string_token([
Token(type="identifier", value="dup"),
Token(type="identifier", value="*")
])
]
)
# Multiple nested
self.make_token_string_test(
"TokenString Multiple Nested",
"{ { 2 3 + } { 4 5 * } }",
[
self.make_token_string_token([
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]),
self.make_token_string_token([
Token(type="i64", value=4),
Token(type="i64", value=5),
Token(type="identifier", value="*")
])
]
)
# Double nesting
deepest = self.make_token_string_token([Token(type="i64", value=42)])
middle = self.make_token_string_token([deepest])
self.make_token_string_test(
"TokenString Double Nested",
"{ { { 42 } } }",
[middle]
)
# Complex nesting with mixed content
self.make_token_string_test(
"TokenString Complex Nesting",
"{ 1 { 2 { 3 } 4 } 5 }",
[
Token(type="i64", value=1),
self.make_token_string_token([
Token(type="i64", value=2),
self.make_token_string_token([
Token(type="i64", value=3)
]),
Token(type="i64", value=4)
]),
Token(type="i64", value=5)
]
)
def generate_whitespace_tests(self):
"""Generate tests with various whitespace patterns."""
# No whitespace inside
self.make_token_string_test(
"TokenString No Whitespace",
"{2 3 +}",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Extra whitespace
self.make_token_string_test(
"TokenString Extra Whitespace",
"{ 2 3 + }",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Leading whitespace outside
self.make_token_string_test(
"TokenString Leading Whitespace Outside",
" { 2 3 + }",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Trailing whitespace outside
self.make_token_string_test(
"TokenString Trailing Whitespace Outside",
"{ 2 3 + } ",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Tabs
self.make_token_string_test(
"TokenString With Tabs",
"{\\t2\\t3\\t+\\t}",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
def generate_multiline_tests(self):
"""Generate tests with multiline token strings."""
# Simple multiline
self.make_token_string_test(
"TokenString Multiline Simple",
"{\\n 2 3 +\\n}",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Multiple lines with tokens
self.make_token_string_test(
"TokenString Multiline Multiple",
"{\\n dup\\n *\\n 2\\n +\\n}",
[
Token(type="identifier", value="dup"),
Token(type="identifier", value="*"),
Token(type="i64", value=2),
Token(type="identifier", value="+")
]
)
# Mixed line breaks
self.make_token_string_test(
"TokenString Mixed Line Breaks",
"{ 1 2\\n3 4\\n\\n5 6 }",
[
Token(type="i64", value=1),
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="i64", value=4),
Token(type="i64", value=5),
Token(type="i64", value=6)
]
)
# Indented multiline
self.make_token_string_test(
"TokenString Indented Multiline",
"{\\n dup 0 >\\n { }\\n { 0 swap - }\\n if\\n}",
[
Token(type="identifier", value="dup"),
Token(type="i64", value=0),
Token(type="identifier", value=">"),
self.make_token_string_token([]),
self.make_token_string_token([
Token(type="i64", value=0),
Token(type="identifier", value="swap"),
Token(type="identifier", value="-")
]),
Token(type="identifier", value="if")
]
)
def generate_comment_tests(self):
"""Generate tests with comments inside token strings."""
# Comment at end of line inside token string
self.make_token_string_test(
"TokenString Comment End Of Line",
"{ 2 3 + // add them\\n}",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Multiple comments
self.make_token_string_test(
"TokenString Multiple Comments",
"{ 2 // first\\n3 // second\\n+ // add\\n}",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Comment on its own line
self.make_token_string_test(
"TokenString Comment Own Line",
"{\\n // This is a comment\\n 2 3 +\\n}",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Comment at start
self.make_token_string_test(
"TokenString Comment At Start",
"{ // comment\\n2 3 + }",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Multiple comment lines
self.make_token_string_test(
"TokenString Multiple Comment Lines",
"{\\n // First comment\\n // Second comment\\n 2 3 +\\n}",
[
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
]
)
# Comments in nested token strings
self.make_token_string_test(
"TokenString Comments Nested",
"{ { 2 3 + // inner comment\\n} // outer comment\\n}",
[
self.make_token_string_token([
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
])
]
)
def generate_error_tests(self):
"""Generate error test cases."""
# Unclosed token string
self.make_error_test(
"TokenString Unclosed",
"{ 2 3 +",
"Unclosed token string: missing closing brace '}'."
)
# Unclosed nested
self.make_error_test(
"TokenString Unclosed Nested",
"{ { 2 3 + }",
"Unclosed token string: missing closing brace '}'."
)
# Extra closing brace (generates two tokens: valid token string + error)
token = self.make_token_string_token([
Token(type="i64", value=2),
Token(type="i64", value=3),
Token(type="identifier", value="+")
])
error_token = self.make_error_token(
"Unexpected closing brace '}' without matching opening brace."
)
self.add_test(
"TokenString Extra Closing Brace",
"{ 2 3 + } }",
[token, error_token]
)
# Only closing brace
self.make_error_test(
"TokenString Only Closing Brace",
"}",
"Unexpected closing brace '}' without matching opening brace."
)
# Error inside token string (invalid literal)
error_inside = self.make_token_string_token([
Token(type="i64", value=2),
Token(type="error", value="Invalid decimal literal: unexpected 'a' in decimal integer."),
Token(type="identifier", value="+")
])
op_value = {"tokens": [
{"type": "i64", "value": 2},
{"type": "error", "value": "Invalid decimal literal: unexpected 'a' in decimal integer."},
{"type": "identifier", "value": "+"}
]}
op = self.make_push_op("token_string", op_value)
stack = self.make_stack_item("token_string", op_value)
self.add_test(
"TokenString Error Inside",
"{ 2 3a + }",
[error_inside],
[op],
[stack]
)
# Unclosed string inside token string
error_string = self.make_token_string_token([
Token(type="error", value="Invalid string literal: unclosed string literal.")
])
op_value_str = {"tokens": [
{"type": "error", "value": "Invalid string literal: unclosed string literal."}
]}
op_str = self.make_push_op("token_string", op_value_str)
stack_str = self.make_stack_item("token_string", op_value_str)
if self.ENABLE_STRINGS:
self.add_test(
"TokenString Unclosed String Inside",
'{ "hello }',
[error_string],
[op_str],
[stack_str]
)
def generate_complex_tests(self):
"""Generate complex realistic test cases."""
# Function body
self.make_token_string_test(
"TokenString Function Body",
"{ dup * }",
[
Token(type="identifier", value="dup"),
Token(type="identifier", value="*")
]
)
# If statement branches
if self.ENABLE_STRINGS:
self.make_token_string_test(
"TokenString If Branches",
'{ "positive" print }',
[
Token(type="String", value="positive"),
Token(type="identifier", value="print")
]
)
# Loop body
self.make_token_string_test(
"TokenString Loop Body",
"{ dup print 1 + }",
[
Token(type="identifier", value="dup"),
Token(type="identifier", value="print"),
Token(type="i64", value=1),
Token(type="identifier", value="+")
]
)
# Struct definition
self.make_token_string_test(
"TokenString Struct Fields",
"{ x: y: }",
[
Token(type="identifier", value="x"),
Token(type="identifier", value=":"),
Token(type="identifier", value="y"),
Token(type="identifier", value=":")
]
)
# Lambda expression
self.make_token_string_test(
"TokenString Lambda",
"{ 2 * }",
[
Token(type="i64", value=2),
Token(type="identifier", value="*")
]
)
# Array map operation
self.make_token_string_test(
"TokenString Array Map",
"{ dup * }",
[
Token(type="identifier", value="dup"),
Token(type="identifier", value="*")
]
)
# Conditional with nested token strings
self.make_token_string_test(
"TokenString Conditional Complex",
"{\\n dup 0 >\\n { dup * }\\n { drop 0 }\\n if\\n}",
[
Token(type="identifier", value="dup"),
Token(type="i64", value=0),
Token(type="identifier", value=">"),
self.make_token_string_token([
Token(type="identifier", value="dup"),
Token(type="identifier", value="*")
]),
self.make_token_string_token([
Token(type="identifier", value="drop"),
Token(type="i64", value=0)
]),
Token(type="identifier", value="if")
]
)
def generate_all_tests(self) -> List[Dict[str, Any]]:
"""Generate all token string test cases."""
# Basic tests
self.generate_basic_tests()
# Literal types
self.generate_literal_tests()
# Identifiers
self.generate_identifier_tests()
# Nested token strings
self.generate_nested_tests()
# Whitespace handling
self.generate_whitespace_tests()
# Multiline token strings
self.generate_multiline_tests()
# Comments inside token strings
self.generate_comment_tests()
# Error cases
self.generate_error_tests()
# Complex realistic cases
self.generate_complex_tests()
return self.get_tests()

View File

@ -0,0 +1,124 @@
# Interpreter Test Runner
Requires `pip install pyyaml`
Test SLS (UNIX):
`python3 test_runner.py ../../SLS_C/bin/sls tests.yaml`
`python3 test_runner.py "python3 -m ../../SLS_Python/sls" tests.yaml`
`python3 test_runner.py ../../SLS_Rust/target/debug/sls tests.yaml`
Test SLS (Windows):
`python test_runner.py ..\..\SLS_C\bin\sls.exe tests.yaml`
`python test_runner.py "python -m ..\..\SLS_Python\sls" tests.yaml`
`python test_runner.py ..\..\SLS_Rust\target\debug\sls.exe tests.yaml`
Python Examples:
`python test_runner.py python python_tests.yaml`
`python test_runner.py pypy python_tests.yaml`
```
>python test_runner.py python python_tests.yaml
✓ PASS: Simple print statement
✓ PASS: Basic arithmetic
✓ PASS: Variable assignment and usage
✗ FAIL: Syntax error detection
Reason: stderr mismatch
Expected:
SyntaxError: unterminated string literal (detected at line 1)
Got:
File "<stdin>", line 1
print("missing closing quote)
^
SyntaxError: unterminated string literal (detected at line 1)
✓ PASS: Import and use module
✓ PASS: List operations
✓ PASS: Execute Python file with args
✓ PASS: Division by zero error
✗ FAIL: Check Python version (using args instead of stdin)
Reason: stdout mismatch
Expected:
Python 3
Got:
Python 3.14.0
✓ PASS: Multi-line function definition
✓ PASS: Long running operation with custom timeout
============================================================
TEST SUMMARY
============================================================
Total tests: 11
Passed: 9
Failed: 2
Failed tests:
- Syntax error detection
- Check Python version (using args instead of stdin)
============================================================
```
```
>python test_runner.py pypy python_tests.yaml
✓ PASS: Simple print statement
✓ PASS: Basic arithmetic
✓ PASS: Variable assignment and usage
✗ FAIL: Syntax error detection
Reason: stderr mismatch
Expected:
SyntaxError: unterminated string literal (detected at line 1)
Got:
File "<stdin>", line 1
print("missing closing quote)
^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: unterminated string literal (detected at line 1)
✓ PASS: Import and use module
✓ PASS: List operations
✓ PASS: Execute Python file with args
✓ PASS: Division by zero error
✗ FAIL: Check Python version (using args instead of stdin)
Reason: stdout mismatch
Expected:
Python 3
Got:
Python 3.10.13 (f1607341da97ff5a1e93430b6e8c4af0ad1aa019, Sep 28 2023, 05:42:24)
[PyPy 7.3.13 with MSC v.1929 64 bit (AMD64)]
✓ PASS: Multi-line function definition
✓ PASS: Long running operation with custom timeout
============================================================
TEST SUMMARY
============================================================
Total tests: 11
Passed: 9
Failed: 2
Failed tests:
- Syntax error detection
- Check Python version (using args instead of stdin)
============================================================
```

View File

@ -0,0 +1,95 @@
tests:
- name: "Simple print statement"
stdin: |
print("Hello, World!")
stdout: |
Hello, World!
exit: success
- name: "Basic arithmetic"
stdin: |
print(2 + 2)
print(10 * 5)
stdout: |
4
50
exit: success
- name: "Variable assignment and usage"
stdin: |
x = 42
y = 8
print(x + y)
stdout: |
50
exit: success
- name: "Syntax error detection"
stdin: |
print("missing closing quote)
stderr: |
SyntaxError: unterminated string literal (detected at line 1)
exit: error
error_code: 1
- name: "Import and use module"
stdin: |
import math
print(math.pi)
stdout: |
3.141592653589793
exit: success
- name: "List operations"
stdin: |
numbers = [1, 2, 3, 4, 5]
print(sum(numbers))
print(len(numbers))
stdout: |
15
5
exit: success
- name: "Execute Python file with args"
args: ["-c", "import sys; print(f'Args: {sys.argv[1:]}'); print('Done')", "arg1", "arg2"]
stdout: |
Args: ['arg1', 'arg2']
Done
exit: success
- name: "Division by zero error"
stdin: |
print(1 / 0)
stderr: |
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
exit: error
- name: "Check Python version (using args instead of stdin)"
args: ["--version"]
stdout: "Python 3"
exit: success
timeout: 2.0
- name: "Multi-line function definition"
stdin: |
def greet(name):
return f"Hello, {name}!"
print(greet("Alice"))
print(greet("Bob"))
stdout: |
Hello, Alice!
Hello, Bob!
exit: success
- name: "Long running operation with custom timeout"
stdin: |
import time
time.sleep(0.5)
print("Done sleeping")
stdout: |
Done sleeping
exit: success
timeout: 2.0

View File

@ -0,0 +1,212 @@
#!/usr/bin/env python3
"""
CLI Testing Script - Test executables with stdin/stdout/stderr validation
"""
import argparse
import subprocess
import sys
import yaml
from pathlib import Path
from typing import Optional, Dict, Any, List
from dataclasses import dataclass
from enum import Enum
class ExitBehavior(Enum):
NONE = "none"
SUCCESS = "success"
ERROR = "error"
@dataclass
class TestResult:
name: str
passed: bool
reason: Optional[str] = None
class TestRunner:
def __init__(self, executable: str, default_timeout: float = 5.0):
self.executable = executable
self.default_timeout = default_timeout
self.results: List[TestResult] = []
def run_command(self, stdin: Optional[str], args: List[str], timeout: float) -> tuple:
"""Run the executable and capture output"""
try:
cmd = [self.executable] + args
result = subprocess.run(
cmd,
input=stdin,
capture_output=True,
text=True,
timeout=timeout
)
return result.stdout, result.stderr, result.returncode, None
except subprocess.TimeoutExpired:
return None, None, None, "Timeout"
except Exception as e:
return None, None, None, f"Error: {str(e)}"
def normalize_output(self, text: Optional[str]) -> str:
"""Normalize output by stripping trailing whitespace from each line"""
if text is None:
return ""
lines = text.split('\n')
return '\n'.join(line.rstrip() for line in lines)
def check_exit_behavior(self, returncode: int, expected: Dict[str, Any]) -> tuple[bool, Optional[str]]:
"""Check if exit behavior matches expectation"""
behavior = expected.get("exit", "none")
if behavior == "none":
return True, None
elif behavior == "success":
if returncode == 0:
return True, None
return False, f"Expected success (exit 0), got exit code {returncode}"
elif behavior == "error":
expected_code = expected.get("error_code")
if expected_code is not None:
if returncode == expected_code:
return True, None
return False, f"Expected error code {expected_code}, got {returncode}"
else:
if returncode != 0:
return True, None
return False, f"Expected error (non-zero exit), got exit code 0"
return False, f"Unknown exit behavior: {behavior}"
def run_test(self, test: Dict[str, Any]) -> TestResult:
"""Run a single test case"""
name = test.get("name", "Unnamed test")
timeout = test.get("timeout", self.default_timeout)
# Setup phase
setup_stdin = test.get("setup")
if setup_stdin:
setup_args = test.get("setup_args", [])
_, _, _, error = self.run_command(setup_stdin, setup_args, timeout)
if error:
return TestResult(name, False, f"Setup failed: {error}")
# Main test execution
stdin = test.get("stdin")
args = test.get("args", [])
stdout, stderr, returncode, error = self.run_command(stdin, args, timeout)
if error:
# Cleanup even on error
self.run_cleanup(test, timeout)
return TestResult(name, False, error)
# Check stdout
expected_stdout = test.get("stdout")
if expected_stdout is not None:
actual = self.normalize_output(stdout)
expected = self.normalize_output(expected_stdout)
if actual != expected:
self.run_cleanup(test, timeout)
return TestResult(name, False, f"stdout mismatch\nExpected:\n{expected}\nGot:\n{actual}")
# Check stderr
expected_stderr = test.get("stderr")
if expected_stderr is not None:
actual = self.normalize_output(stderr)
expected = self.normalize_output(expected_stderr)
if actual != expected:
self.run_cleanup(test, timeout)
return TestResult(name, False, f"stderr mismatch\nExpected:\n{expected}\nGot:\n{actual}")
# Check exit behavior
passed, reason = self.check_exit_behavior(returncode, test)
if not passed:
self.run_cleanup(test, timeout)
return TestResult(name, False, reason)
# Cleanup phase
cleanup_result = self.run_cleanup(test, timeout)
if cleanup_result:
return TestResult(name, False, f"Cleanup failed: {cleanup_result}")
return TestResult(name, True)
def run_cleanup(self, test: Dict[str, Any], timeout: float) -> Optional[str]:
"""Run cleanup if specified"""
cleanup_stdin = test.get("cleanup")
if cleanup_stdin:
cleanup_args = test.get("cleanup_args", [])
_, _, _, error = self.run_command(cleanup_stdin, cleanup_args, timeout)
return error
return None
def run_all_tests(self, tests: List[Dict[str, Any]]):
"""Run all tests and collect results"""
for test in tests:
result = self.run_test(test)
self.results.append(result)
# Print immediate result
status = "✓ PASS" if result.passed else "✗ FAIL"
print(f"{status}: {result.name}")
if not result.passed and result.reason:
print(f" Reason: {result.reason}")
print()
def print_summary(self):
"""Print test summary"""
total = len(self.results)
passed = sum(1 for r in self.results if r.passed)
failed = total - passed
print("=" * 60)
print("TEST SUMMARY")
print("=" * 60)
print(f"Total tests: {total}")
print(f"Passed: {passed}")
print(f"Failed: {failed}")
if failed > 0:
print("\nFailed tests:")
for result in self.results:
if not result.passed:
print(f" - {result.name}")
print("=" * 60)
return 0 if failed == 0 else 1
def main():
parser = argparse.ArgumentParser(description="Test CLI executables with YAML test definitions")
parser.add_argument("executable", help="Path to the executable to test")
parser.add_argument("tests", help="Path to YAML test file")
parser.add_argument("--timeout", type=float, default=5.0, help="Default timeout in seconds (default: 5.0)")
args = parser.parse_args()
# Load test file
try:
with open(args.tests, 'r') as f:
test_data = yaml.safe_load(f)
except Exception as e:
print(f"Error loading test file: {e}", file=sys.stderr)
return 1
tests = test_data.get("tests", [])
if not tests:
print("No tests found in test file", file=sys.stderr)
return 1
# Run tests
runner = TestRunner(args.executable, args.timeout)
runner.run_all_tests(tests)
# Print summary and return exit code
return runner.print_summary()
if __name__ == "__main__":
sys.exit(main())

View File

@ -0,0 +1,706 @@
tests:
# Basic Stack Operations
- name: "Simple value push"
stdin: |
42 print
stdout: |
42
exit: success
- name: "Multiple values and operations"
stdin: |
3 4 + print
10 5 - print
stdout: |
7
5
exit: success
- name: "Stack manipulation - dup"
stdin: |
5 dup print print
stdout: |
5
5
exit: success
- name: "Stack manipulation - swap"
stdin: |
10 20 swap print print
stdout: |
10
20
exit: success
- name: "Stack manipulation - over"
stdin: |
5 10 over print print print
stdout: |
5
10
5
exit: success
- name: "Stack manipulation - rot"
stdin: |
1 2 3 rot print print print
stdout: |
1
2
3
exit: success
- name: "Stack depth"
stdin: |
1 2 3 depth print
stdout: |
3
exit: success
# Arithmetic Operations
- name: "Basic arithmetic - all operators"
stdin: |
10 3 + print
10 3 - print
10 3 * print
10 3 / print
10 3 % print
stdout: |
13
7
30
3
1
exit: success
- name: "Exponentiation"
stdin: |
2 8 ^ print
3 3 ^ print
stdout: |
256
27
exit: success
- name: "Floating point arithmetic"
stdin: |
3.14 2.0 + print
10.5 2.5 * print
stdout: |
5.14
26.25
exit: success
- name: "Mathematical functions"
stdin: |
16 sqrt print
-42 abs print
3.7 round print
3.14 floor print
3.14 ceil print
stdout: |
4.0
42
4.0
3.0
4.0
exit: success
- name: "Min and max"
stdin: |
3 5 min print
3 5 max print
stdout: |
3
5
exit: success
# Comparison Operations
- name: "Comparison operators"
stdin: |
5 3 > print
5 3 < print
5 5 == print
5 3 != print
5 5 >= print
3 5 <= print
stdout: |
true
false
true
true
true
true
exit: success
# Logical Operations
- name: "Logical operators"
stdin: |
true false and print
true false or print
false not print
stdout: |
false
true
true
exit: success
# Bitwise Operations
- name: "Bitwise operations"
stdin: |
0xFF 0x0F bitand print
0xF0 0x0F bitor print
0xFF 0x0F bitxor print
4 2 shl print
16 2 shr print
stdout: |
15
255
240
16
4
exit: success
# String Operations
- name: "String concatenation"
stdin: |
"hello" " world" concat print
stdout: |
hello world
exit: success
- name: "String length and substring"
stdin: |
"hello" length print
"hello" 1 3 substr print
stdout: |
5
el
exit: success
- name: "String split and join"
stdin: |
"a,b,c" "," split print
stdout: |
["a" "b" "c"]
exit: success
- name: "String searching"
stdin: |
"hello" "hel" starts_with print
"hello" "lo" ends_with print
stdout: |
true
true
exit: success
- name: "String trimming and replacement"
stdin: |
" hello " trim print
"hello world" "world" "Stack" replace print
stdout: |
hello
hello Stack
exit: success
# Array Operations
- name: "Array literals and access"
stdin: |
[1 2 3 4 5] print
[1 2 3 4 5] 2 at print
[1 2 3 4 5] length print
stdout: |
[1 2 3 4 5]
3
5
exit: success
- name: "Array slice"
stdin: |
[10 20 30 40 50] 1 3 slice print
stdout: |
[20 30]
exit: success
- name: "Array map"
stdin: |
[1 2 3 4] { 2 * } map print
stdout: |
[2 4 6 8]
exit: success
- name: "Array filter"
stdin: |
[1 2 3 4 5] { 2 % 0 == } filter print
stdout: |
[2 4]
exit: success
- name: "Array reduce"
stdin: |
[1 2 3 4] 0 { + } reduce print
stdout: |
10
exit: success
- name: "Array sum and mean"
stdin: |
[1 2 3 4 5] sum print
[1 2 3 4 5] mean print
stdout: |
15
3.0
exit: success
- name: "Array zip and enumerate"
stdin: |
[1 2 3] [4 5 6] zip print
["a" "b" "c"] enumerate print
stdout: |
[[1 4] [2 5] [3 6]]
[[0 "a"] [1 "b"] [2 "c"]]
exit: success
- name: "Array reverse and transpose"
stdin: |
[1 2 3] reverse print
[[1 2] [3 4]] transpose print
stdout: |
[3 2 1]
[[1 3] [2 4]]
exit: success
# Function Definition and Calling
- name: "Simple function definition"
stdin: |
(Number -- Number) { dup * } ::square fn
5 square print
stdout: |
25
exit: success
- name: "Function with multiple operations"
stdin: |
(Number Number -- Number Number) {
over over / swap %
} ::divmod fn
10 3 divmod print print
stdout: |
3
1
exit: success
- name: "Recursive factorial"
stdin: |
(Number -- Number) {
dup 1 <=
{ drop 1 }
{ dup 1 - factorial * }
if
} ::factorial fn
5 factorial print
stdout: |
120
exit: success
# Control Flow - Conditionals
- name: "Simple if statement"
stdin: |
5 0 > { "positive" print } { "negative" print } if
stdout: |
positive
exit: success
- name: "If with else branch"
stdin: |
-3 0 > { "positive" print } { "non-positive" print } if
stdout: |
non-positive
exit: success
- name: "Nested conditionals"
stdin: |
5 0 >
{
5 10
{ "between 0 and 10" print }
{ "greater than 10" print }
if
}
{ "negative or zero" print }
if
stdout: |
between 0 and 10
exit: success
# Control Flow - Loops
- name: "For loop"
stdin: |
1 5 { dup print } for
stdout: |
1
2
3
4
5
exit: success
- name: "While loop"
stdin: |
0
{ dup 3 < }
{
dup print
1 +
}
while
drop
stdout: |
0
1
2
exit: success
- name: "Loop with break"
stdin: |
1 10 {
dup 5 ==
{ break }
{ dup print }
if
} for
stdout: |
1
2
3
4
exit: success
- name: "Loop with continue"
stdin: |
1 5 {
dup 2 % 0 ==
{ continue }
{ dup print }
if
} for
stdout: |
1
3
5
exit: success
# Structs
- name: "Struct definition and creation"
stdin: |
(Number Number --) { x: y: } ::Point struct
3.0 4.0 Point print
stdout: |
Point { x: 3.0, y: 4.0 }
exit: success
- name: "Struct field access"
stdin: |
(Number Number --) { x: y: } ::Point struct
3.0 4.0 Point
dup ::x get print
::y get print
stdout: |
3.0
4.0
exit: success
- name: "Struct field modification"
stdin: |
(Number Number --) { x: y: } ::Point struct
3.0 4.0 Point
10.0 ::x set
::x get print
stdout: |
10.0
exit: success
# Unions
- name: "Union creation - Option type"
stdin: |
(T --) { Some(T) None } ::Option union
42 Option::Some print
Option::None print
stdout: |
Option::Some(42)
Option::None
exit: success
- name: "Pattern matching with Option"
stdin: |
(T --) { Some(T) None } ::Option union
42 Option::Some {
Some(x) => { x print }
None => { "Nothing" print }
} match
stdout: |
42
exit: success
- name: "Result type pattern matching"
stdin: |
(T E --) { Ok(T) Err(E) } ::Result union
"success" Result::Ok {
Ok(val) => { val print }
Err(e) => { "Error: " e concat print }
} match
stdout: |
success
exit: success
# Enums
- name: "Enum definition and usage"
stdin: |
{ Pending: Active: Complete: } ::Status enum
Status::Active print
stdout: |
Status::Active
exit: success
# Traits
- name: "Trait implementation for struct"
stdin: |
(Number Number --) { x: y: } ::Point struct
::Addable {
(Self Self -- Self) {
over ::x get over ::x get +
swap ::y get swap ::y get +
Point
} +:
} ::Point impl
1.0 2.0 Point 3.0 4.0 Point + print
stdout: |
Point { x: 4.0, y: 6.0 }
exit: success
# Type Conversions
- name: "Type conversions"
stdin: |
42 to_f64 print
3.14 to_i32 print
42 to_str print
stdout: |
42.0
3
"42"
exit: success
- name: "String parsing"
stdin: |
"123" parse print
stdout: |
123
exit: success
# Constants
- name: "Constant definition and usage"
stdin: |
3.1415926535 ::pi const
5 dup * pi * print
stdout: |
78.53981633975
exit: success
# Lambda and Eval
- name: "Lambda function"
stdin: |
{ dup * } lambda ::square swap
5 square eval print
stdout: |
25
exit: success
- name: "Eval operator"
stdin: |
"2 3 +" eval print
stdout: |
5
exit: success
# Reflection
- name: "Type checking with type_of"
stdin: |
42 type_of print
"hello" type_of print
stdout: |
::i64
::String
exit: success
- name: "Trait checking with implements"
stdin: |
42 ::Addable implements print
42 ::Drawable implements print
stdout: |
true
false
exit: success
# Assertions
- name: "Passing assertion"
stdin: |
{ 2 3 + } { 5 == } assert
"Test passed" print
stdout: |
Test passed
exit: success
- name: "Failing assertion"
stdin: |
{ 2 3 + } { 6 == } assert
stderr: |
Assertion failed
exit: error
# Randomness
- name: "Random number generation with seed"
stdin: |
12345 seed
rand 0.0 >= print
rand 1.0 < print
stdout: |
true
true
exit: success
# Complex Examples
- name: "FizzBuzz (1-15)"
stdin: |
(Number -- ) {
dup 15 % 0 ==
{ drop "FizzBuzz" print }
{
dup 3 % 0 ==
{ drop "Fizz" print }
{
dup 5 % 0 ==
{ drop "Buzz" print }
{ print }
if
}
if
}
if
} ::fizzbuzz fn
1 15 { fizzbuzz } for
stdout: |
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
exit: success
- name: "Sum of squares of even numbers"
stdin: |
[1 2 3 4 5 6 7 8 9 10]
{ 2 % 0 == } filter
{ dup * } map
0 { + } reduce
print
stdout: |
220
exit: success
# Error Cases
- name: "Type mismatch error"
stdin: |
"hello" 5 +
stderr: |
Type error
exit: error
- name: "Stack underflow"
stdin: |
+
stderr: |
Stack underflow
exit: error
- name: "Undefined identifier"
stdin: |
undefined_function
stderr: |
Undefined identifier: undefined_function
exit: error
- name: "Division by zero"
stdin: |
10 0 /
stderr: |
Division by zero
exit: error
# Character Literals
- name: "Character literals"
stdin: |
'A' print
'\n' print
'\u{1F600}' print
stdout: |
'A'
'\n'
'😀'
exit: success
# Multiple test with timeout
- name: "Complex computation with timeout"
stdin: |
1 100 { dup * } map 0 { + } reduce print
stdout: |
338350
exit: success
timeout: 2.0
# Comments
- name: "Comments ignored"
stdin: |
// This is a comment
5 // inline comment
3 + // another comment
print
stdout: |
8
exit: success
# Array of structs
- name: "Array of structs"
stdin: |
(Number Number --) { x: y: } ::Point struct
[1 2 Point 3 4 Point] print
stdout: |
[Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]
exit: success
# Format strings
- name: "String formatting"
stdin: |
"x=%d, y=%d" [5 10] format print
stdout: |
x=5, y=10
exit: success
# Underscore separators in literals
- name: "Underscore separators in numbers"
stdin: |
1_000_000 print
3_1415.92_65 print
stdout: |
1000000
3141.9265
exit: success

View File

@ -16,7 +16,7 @@ file_headers = """\
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include "sls/sls_errors.h" #include "sls/errors.h"
#include "sls/lexer.h" #include "sls/lexer.h"
#include "sls/string.h" #include "sls/string.h"
#include "tests/lexer_test_helpers.h" #include "tests/lexer_test_helpers.h"
@ -27,7 +27,7 @@ file_headers = """\
main_header = """\ main_header = """\
TestsReport run_lexer_tests() { TestsReport run_lexer_tests() {
TestsReport test_report = (TestsReport) { TestsReport test_report = (TestsReport) {
.section = "lexer_tests", .section = SLS_STR("lexer_tests"),
.count = NUM_OF_TESTS, .count = NUM_OF_TESTS,
.tests = (TestResult *)malloc(sizeof(TestResult) * NUM_OF_TESTS), .tests = (TestResult *)malloc(sizeof(TestResult) * NUM_OF_TESTS),
}; };
@ -68,28 +68,179 @@ def _token_to_c_call(token: dict, idx_var="i") -> str:
return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U16, {value}}})' return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U16, {value}}})'
elif ttype == "u8": elif ttype == "u8":
return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U8, {value}}})' return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U8, {value}}})'
elif ttype == "f64":
return f'test_double_value(&test, result, {idx_var}++, &(double){{{value}}})'
elif ttype == "f32":
return f'test_float_value(&test, result, {idx_var}++, &(float){{{value}}})'
elif ttype == "char":
return f'test_character_value(&test, result, {idx_var}++, &(uint8_t){{{ord(value)}}})' # type: ignore
elif ttype == "string":
return f'test_string_value(&test, result, {idx_var}++, &SLS_STR("{value}"))' # type: ignore
elif ttype == "identifier": elif ttype == "identifier":
return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{FALSE, {len(value)}, "{value}"}})' # type: ignore return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{FALSE, SLS_STR("{value}")}})' # type: ignore
elif ttype == "identifier_literal": elif ttype == "identifier_literal":
return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{TRUE, {len(value)}, "{value}"}})' # type: ignore return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{TRUE, SLS_STR("{value}")}})' # type: ignore
elif ttype == "bool":
return f'test_boolean_value(&test, result, {idx_var}++, &(Boolean){{{"TRUE" if value else "FALSE"}}})' # type: ignore
elif ttype == "error": elif ttype == "error":
return f'test_for_error(&test, result, i++, &(TestErrorMessage){{{len(value)+1}, "{c_string_literal(value)}"}})' # type: ignore return f'test_for_error(&test, result, i++, &SLS_STR("{c_string_literal(value)}"))' # type: ignore
elif ttype == "token_string":
return _token_string_c_call(idx_var, value) # type: ignore
else: else:
raise ValueError(f' Unhandled token type: {ttype}') raise ValueError(f' Unhandled token type: {ttype}')
def _token_string_c_call(idx_var: str, value: list[dict]) -> str:
"""Generate C code for testing a token string value."""
if not value: # Empty token string
return (
f'test_token_string_value(&test, result, {idx_var}++, '
f'&(TestTokenStringValue){{0, NULL}})'
)
# Generate token handler calls for each token in the string
token_handlers = []
for i, inner_token in enumerate(value):
inner_type = inner_token.get("type")
inner_value = inner_token.get("value")
# Determine the handler function and value initialization
if inner_type == "i64":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I64, {inner_value}}}'
elif inner_type == "i32":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I32, {inner_value}}}'
elif inner_type == "i16":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I16, {inner_value}}}'
elif inner_type == "i8":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I8, {inner_value}}}'
elif inner_type == "u64":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_U64, {inner_value}}}'
elif inner_type == "u32":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_U32, {inner_value}}}'
elif inner_type == "u16":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_U16, {inner_value}}}'
elif inner_type == "u8":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_U8, {inner_value}}}'
elif inner_type == "f64":
handler = "test_double_value"
val_init = f'&(double){{{inner_value}}}'
elif inner_type == "f32":
handler = "test_float_value"
val_init = f'&(float){{{inner_value}}}'
elif inner_type == "char":
handler = "test_character_value"
val_init = f'&(uint8_t){{{ord(inner_value)}}}' # type: ignore
elif inner_type == "string":
handler = "test_string_value"
val_init = f'&SLS_STR("{c_string_literal(inner_value)}")' # type: ignore
elif inner_type == "identifier":
handler = "test_identifier_value"
val_init = f'&(TestIdentifierValue){{FALSE, SLS_STR("{inner_value}")}}'
elif inner_type == "identifier_literal":
handler = "test_identifier_value"
val_init = f'&(TestIdentifierValue){{TRUE, SLS_STR("{inner_value}")}}'
elif inner_type == "bool":
handler = "test_boolean_value"
val_init = f'&(Boolean){{{"TRUE" if inner_value else "FALSE"}}}'
elif inner_type == "error":
handler = "test_for_error"
val_init = f'&SLS_STR("{c_string_literal(inner_value)}")' # type: ignore
elif inner_type == "token_string":
# Nested token string - recursive call
handler = "test_token_string_value"
val_init = _token_string_value_init(inner_token.get("value", []))
else:
raise ValueError(f'Unhandled token type in token string: {inner_type}')
token_handlers.append(
f'{{(Boolean (*)(LexerTest *, LexerResult, size_t, void *)){handler}, '
f'{val_init}}}'
)
# Generate the array initialization
tokens_array = f'(TestTokenStringToken[]){{{", ".join(token_handlers)}}}'
return (
f'test_token_string_value(&test, result, {idx_var}++, '
f'&(TestTokenStringValue){{{len(value)}, {tokens_array}}})'
)
def _token_string_value_init(value: list[dict]) -> str:
"""Generate initialization code for a TestTokenStringValue (for nested token strings)."""
if not value:
return '&(TestTokenStringValue){0, NULL}'
token_handlers = []
for inner_token in value:
inner_type = inner_token.get("type")
inner_value = inner_token.get("value")
if inner_type == "i64":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I64, {inner_value}}}'
elif inner_type == "i32":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I32, {inner_value}}}'
elif inner_type == "f64":
handler = "test_double_value"
val_init = f'&(double){{{inner_value}}}'
elif inner_type == "f32":
handler = "test_float_value"
val_init = f'&(float){{{inner_value}}}'
elif inner_type == "char":
handler = "test_character_value"
val_init = f'&(uint8_t){{{ord(inner_value)}}}' # type: ignore
elif inner_type == "string":
handler = "test_string_value"
val_init = f'&SLS_STR("{c_string_literal(inner_value)}")' # type: ignore
elif inner_type == "identifier":
handler = "test_identifier_value"
val_init = f'&(TestIdentifierValue){{FALSE, SLS_STR("{inner_value}")}}'
elif inner_type == "identifier_literal":
handler = "test_identifier_value"
val_init = f'&(TestIdentifierValue){{TRUE, SLS_STR("{inner_value}")}}'
elif inner_type == "bool":
handler = "test_boolean_value"
val_init = f'&(Boolean){{{"TRUE" if inner_value else "FALSE"}}}'
elif inner_type == "error":
handler = "test_for_error"
val_init = f'&SLS_STR("{c_string_literal(inner_value)}")' # type: ignore
elif inner_type == "token_string":
handler = "test_token_string_value"
val_init = _token_string_value_init(inner_token.get("value", []))
# Add other types as needed
else:
raise ValueError(f'Unhandled token type in nested token string: {inner_type}')
token_handlers.append(
f'{{(Boolean (*)(LexerTest *, LexerResult, size_t, void *)){handler}, '
f'{val_init}}}'
)
tokens_array = f'(TestTokenStringToken[]){{{", ".join(token_handlers)}}}'
return f'&(TestTokenStringValue){{{len(value)}, {tokens_array}}}'
def token_to_c_call(token: dict, idx_var="i") -> str: def token_to_c_call(token: dict, idx_var="i") -> str:
"""Generate a C 'test_*_value' call based on token type.""" """Generate a C 'test_*_value' call based on token type."""
return f"if ({_token_to_c_call(token, idx_var)}) return test.result;" return f"if ({_token_to_c_call(token, idx_var)}) return test.result;"
def generate_c_test(test: dict) -> str: def generate_c_test(test: dict) -> str:
"""Convert a single YAML test entry to a C test function.""" """Convert a single YAML test entry to a C test function."""
name = sanitize_name(test["name"]) name = test["name"]
c_name = sanitize_name(name)
code = c_string_literal(test["code"]) code = c_string_literal(test["code"])
tokens = test.get("tokens", []) tokens = test.get("tokens", [])
# Function header # Function header
c_code = [f"static TestResult {name}() {{", c_code = [f"static TestResult {c_name}() {{",
f' LexerTest test = start_up_test("{name}", "{code}");', f' LexerTest test = start_up_test(SLS_STR("{name}"), SLS_STR("{code}"));',
" LexerResult result = lexical_analysis(&test.lexer_info);", " LexerResult result = lexical_analysis(&test.lexer_info);",
" if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);", " if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);",
" size_t i = 0;"] " size_t i = 0;"]