Compare commits

...

151 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
82 changed files with 15239 additions and 1725 deletions

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)

View File

@ -1,53 +0,0 @@
# SE 3250 Progress Checkpoints
## 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)
## 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.

191
README.md
View File

@ -1,97 +1,128 @@
# YREA SLS
*Kyler Olsen*
*October 2025*
*Snow College*
*SE 3250 Survey of Languages Final Project*
Language Code Name: YREA **SLS** (*Stack Language Specification*)
Language Specifications: [sls.purplecello.org](https://sls.purplecello.org)
Language Specification Repository (Private): [git.purplecello.org](https://git.purplecello.org/KylerOlsen/LangsFinalPlaning)
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)
SLS is a statically-typed, stack-based language with pure postfix notation
combining the execution model of HP's RPL, the type system of C and Rust, and
modern array operations from Uiua.
## 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
programming language (during class time, we will randomize the list of
people in the class and allow languages to be selected uniquely on a
first-come, first-served basis; be prepared to pick the language you want
and to advocate for the languages that you don't want).
- 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.
Python run module
```bash
cd SLS_Python
python3 -m sls_py
python3 -m sls_py.calc
```
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!
- Make sure that the project actually solves a problem that you care about.
(Entertainment counts, but if that is the problem you care about, then make
sure that you can make something that actually is entertaining!)
- I'm expecting you to dedicate about 15-30 hours to the entire project
(including all three implementations and the report/presentation
preparation). I'm expecting that your initial implmentation should feel
like about twice the scope of implementing the game of life in PostScript
or K.
Python build module
```bash
cd SLS_Python
source .venv/bin/activate
python3 -m build --no-isolation
pip install ./dist/sls_python-0.0.2a0-py3-none-any.whl
python3 -m sls_py
python3 -m sls_py.calc
```
**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
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):
**Windows**
- https://spectrum.ieee.org/top-programming-languages-2025
- https://www.tiobe.com/tiobe-index/
- https://survey.stackoverflow.co/2025/technology#admired-and-desired
- https://survey.stackoverflow.co/2025/technology#most-popular-technologies-language-prof
- https://tjpalmer.github.io/languish/
- https://www.geeksforgeeks.org/blogs/top-programming-languages/
- https://github.com/breck7/pldb
C (Using Visual Studio Developer PowerShell)
```shell
cd SLS_C
python build.py build
.\bin\sls.exe
```
Here is the list of programming languages that you will be able to choose from
for your initial implementation (each language will be chosen by at most one
student; we will choose during class time on Oct 27):
Python run module
```bat
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.
Also, submit your github repo link and the path to the report and slides file
in the canvas text box for this assignment. The grace period for submission
ends at 3pm on Monday, Dec 8. Presentations will be 3:30pm - 5:30pm. For full
credit, you must present and watch the presentations of your classmates.
Rust
```bat
cd SLS_Rust\sls
cargo build
.\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/
bin/
build_pico/
*.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

@ -1,77 +0,0 @@
# Makefile for SLS project with automatic header dependencies
CC ?= gcc
CFLAGS ?= -std=c99 -Wall -Wextra -g -Iinclude -MMD -MP
LDFLAGS ?=
CTESTFLAGS ?= -std=c99 -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

@ -2,10 +2,22 @@
This is the C implementation for the YREA SLS interpreter.
## Running
## Compiling, Running, and Testing
Build Project: `make build`
Build and Run Project: `make run`
Build and Run Tests: `make test`
Interpreter binary location:
- Linux: `./bin/sls`
- 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`

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

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

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

@ -11,6 +11,20 @@
#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;
typedef struct {
@ -36,6 +50,7 @@ typedef enum {
} TokenType;
extern const char *TOKEN_TYPES_NAMES[];
extern const size_t TOKEN_TYPE_COUNT;
typedef enum {
ARRAY_IDENTIFIER,
@ -56,6 +71,7 @@ typedef enum {
} ArrayType;
extern const char *ARRAY_TYPES_NAMES[];
extern const size_t ARRAY_TYPE_COUNT;
typedef struct {
SlsStr name;
@ -74,6 +90,7 @@ typedef enum {
} IntegerBuiltInType;
extern const char *INTEGER_TYPES_NAMES[];
extern const size_t INTEGER_TYPE_COUNT;
typedef struct {
uint64_t value;
@ -151,8 +168,10 @@ typedef struct {
};
} LexerResult;
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);
void clean_token_string(TokenString token_string);
void clean_token_result(LexerTokenResult *head);
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

@ -18,8 +18,10 @@ typedef struct {
Boolean allocated;
} SlsStr;
#define SLS_STR(s) (SlsStr){ sizeof(s) - 1, (s), FALSE }
#define SLS_STR_NULL (SlsStr){0, NULL, FALSE}
#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);

View File

@ -124,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_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_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr error);
Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr *error);
#endif // SLS_LEXER_TEST_HELPERS_H

View File

@ -39,6 +39,7 @@ typedef struct {
TestsReport run_string_tests();
TestsReport run_lexer_tests();
TestsReport run_hash_table_tests();
TestsReport run_extra_tests();
#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

@ -32,6 +32,8 @@ const char *TOKEN_TYPES_NAMES[] = {
"Type Tuple",
};
const size_t TOKEN_TYPE_COUNT = sizeof(TOKEN_TYPES_NAMES) / sizeof(*TOKEN_TYPES_NAMES);
const char *ARRAY_TYPES_NAMES[] = {
"Identifier",
"i64",
@ -50,6 +52,8 @@ const char *ARRAY_TYPES_NAMES[] = {
"Inline Struct",
};
const size_t ARRAY_TYPE_COUNT = sizeof(ARRAY_TYPES_NAMES) / sizeof(*ARRAY_TYPES_NAMES);
const char *INTEGER_TYPES_NAMES[] = {
"i64",
"i32",
@ -61,6 +65,8 @@ const char *INTEGER_TYPES_NAMES[] = {
"u8",
};
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
lexer_info->filename = filename;
@ -325,40 +331,44 @@ static uint64_t create_hexadecimal_integer(LexerInfo *lexer_info, size_t start)
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_U64: break;
case INTEGER_I32:
if (value > (uint64_t)UINT32_MAX) {
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 (value > (uint64_t)UINT16_MAX) {
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 (value > (uint64_t)UINT8_MAX) {
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);
} if (value > (uint64_t)UINT32_MAX) {
} 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);
} if (value > (uint64_t)UINT16_MAX) {
} 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);
} if (value > (uint64_t)UINT8_MAX) {
} 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;
@ -511,7 +521,7 @@ static LexerResult parse_binary_integer(LexerInfo *lexer_info, char c, size_t st
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_BINARY);
if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_binary_integer(lexer_info, start);
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
}
SlsStr error_msg = sls_format(SLS_STR("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}};
@ -523,7 +533,7 @@ static LexerResult parse_octal_integer(LexerInfo *lexer_info, char c, size_t sta
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_OCTAL);
if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_octal_integer(lexer_info, start);
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
}
SlsStr error_msg = sls_format(SLS_STR("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}};
@ -540,7 +550,7 @@ static LexerResult parse_float(LexerInfo *lexer_info, char c, size_t start, size
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, NUMERIC_F64, start, start_line);
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);
@ -555,7 +565,7 @@ static LexerResult parse_decimal_integer(LexerInfo *lexer_info, char c, size_t s
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_DECIMAL);
if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_decimal_integer(lexer_info, start);
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
}
SlsStr error_msg = sls_format(SLS_STR("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}};
@ -567,7 +577,7 @@ static LexerResult parse_hexadecimal_integer(LexerInfo *lexer_info, char c, size
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_HEXADECIMAL);
if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_hexadecimal_integer(lexer_info, start);
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
}
SlsStr error_msg = sls_format(SLS_STR("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}};
@ -642,9 +652,136 @@ static LexerResult parse_string_literal(LexerInfo *lexer_info, char c, size_t st
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) {
(void)lexer_info; (void)c; (void)start; (void)start_line;
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Token Strings Not Implemented Error."), 1}};
// Lexes a token string
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) {
@ -658,33 +795,43 @@ static LexerResult parse_type_tuples(LexerInfo *lexer_info, char c, size_t start
}
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 == '"') return FALSE;
if (c == '.' || 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, char c) {
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) {
// Parses identifier, identifier literals, and boolean tokens
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++;
}
@ -693,6 +840,8 @@ static LexerResult parse_identifiers_and_booleans(LexerInfo *lexer_info, char c,
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)
@ -704,13 +853,7 @@ static LexerResult parse_identifiers_and_booleans(LexerInfo *lexer_info, char c,
static LexerResult lexer_next(LexerInfo *lexer_info) {
// Gets the next token from the source
while (isspace(peek(lexer_info)) || peek(lexer_info) == '/' || peek(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);
}
skip_comments_and_whitespace(lexer_info);
// Initialize begining variables
char c = peek(lexer_info);
@ -731,29 +874,64 @@ static LexerResult lexer_next(LexerInfo *lexer_info) {
if (c == '\"') return parse_string_literal(lexer_info, c, start, start_line);
// Token Strings
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
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
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
if (is_identifier_start(lexer_info, c))
if (is_identifier_start(lexer_info))
return parse_identifiers_and_booleans(lexer_info, c, start, start_line);
// Lexing Error
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("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) {
// Deallocates a LexerTokenResult linked list
LexerTokenResult *next;
while (head) {
next = head->next;
if (head == NULL) return;
if (head->type == SLS_ERROR) sls_str_free(&head->error.message);
else {
if (head->result.type == TOKEN_STRING) sls_str_free(&head->error.message);
if (head->result.type == TOKEN_STRING)
sls_str_free(&head->result.string_literal);
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);
head = next;
}
}
LexerTokenResult *get_token(LexerTokenResult *head, size_t i) {

View File

@ -4,8 +4,37 @@
// October 2025
#include <stdio.h>
#include <stddef.h>
#include <string.h>
int main(void) {
printf("Hello, world!\n");
return 0;
#include "sls/meta.h"
#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;
} 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

@ -204,18 +204,30 @@ SlsStr sls_format(const SlsStr s, ...) {
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;
@ -309,7 +321,7 @@ SlsStr sls_format(const SlsStr s, ...) {
target_i += items[item_i].self_length;
item_i++;
}
if (s.len > source_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';

View File

@ -11,9 +11,30 @@
#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 = 0;
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()
@ -24,6 +45,8 @@ TestsReport run_extra_tests()
.tests = malloc(sizeof(TestResult) * NUM_EXTRA_TESTS)};
size_t i = 0;
// report.tests[i++] = test_malloc_and_copy();
(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

@ -116,20 +116,25 @@ static SlsStr expected_end_of_token_stream(size_t i) {
}
static SlsStr token_should_be(size_t i, TokenType should, TokenType found) {
if (found >= TOKEN_TYPE_COUNT)
return sls_format(SLS_STR("Token #%z should be a %t, but found invalid enum %u"), i, should, (uint64_t)found);
else
return sls_format(SLS_STR("Token #%z should be a %t, but found a %t"), i, should, found);
}
static SlsStr integer_type_should_be(size_t i, IntegerBuiltInType should, IntegerBuiltInType found) {
if (found >= INTEGER_TYPE_COUNT)
return sls_format(SLS_STR("Token #%z integer type should be a %i, but found invalid enum %u"), i, should, (uint64_t)found);
else
return sls_format(SLS_STR("Token #%z integer type should be a %i, but found a %i"), i, should, found);
}
static SlsStr integer_value_should_be(size_t i, uint64_t should, uint64_t found) {
return sls_format(SLS_STR("Token #%z integer value should be %i, but found %i"), i, should, found);
return sls_format(SLS_STR("Token #%z integer value should be %u, but found %u"), i, should, found);
}
static SlsStr character_value_should_be(size_t i, uint8_t should, uint8_t found) {
return sls_format(SLS_STR("Token #%z integer value should be %i, but found %i"), i, should, found);
// return sls_format(SLS_STR("Token #%z character value should be '', but found ''"), i, should[0], found[0]);
}
static SlsStr float_value_should_be(size_t i, double should, double found) {
@ -158,6 +163,9 @@ static SlsStr boolean_should_be(size_t i, Boolean value) {
}
static SlsStr array_type_should_be(size_t i, ArrayType should, ArrayType found) {
if (found >= ARRAY_TYPE_COUNT)
return sls_format(SLS_STR("Token #%z should be a %a, but found invalid enum %u"), i, should, (uint64_t)found);
else
return sls_format(SLS_STR("Token #%z should be a %a, but found a %a"), i, should, found);
}
@ -486,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) {
LexerTokenResult *new, *head;
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) { .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;
}
return (LexerResult) { .type = SLS_RESULT, .result = head };
@ -506,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++) {
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)) {
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;
}
clean_token_result(token_string_result.result);
@ -551,13 +589,13 @@ Boolean test_type_tuple_value(LexerTest *test, LexerResult result, size_t i, Tes
return FALSE;
}
Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr error) {
Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr *error) {
LexerTokenResult *head = get_token(result.result, i);
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;
} if (sls_str_cmp(head->error.message, error) != 0) {
logic_fail_test(test, result, error_should_be(i + 1, error, head->error));
} if (sls_str_cmp(head->error.message, *error) != 0) {
logic_fail_test(test, result, error_should_be(i + 1, *error, head->error));
return TRUE;
}
return FALSE;

File diff suppressed because it is too large Load Diff

View File

@ -10,9 +10,9 @@
#include "sls/errors.h"
#include "tests/tests.h"
static const Boolean PRINT_SUCCESSFUL_TESTS = TRUE;
static const Boolean PRINT_SUCCESSFUL_TESTS = FALSE;
const SlsStr TEST_FILE_NAME = SLS_STR("TEST_FILE.SLS");
const SlsStr TEST_FILE_NAME = SLS_STR_CONST("TEST_FILE.SLS");
typedef struct {
uint16_t errored;
@ -85,6 +85,8 @@ int main(void) {
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);

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

@ -5,6 +5,7 @@ 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",
@ -15,4 +16,5 @@ __all__ = [
"StringTestGenerator",
"IdentifierTestGenerator",
"BooleanTestGenerator",
"TokenStringTestGenerator",
]

View File

@ -54,6 +54,8 @@ class BaseTestGenerator(ABC):
ENABLE_UNICODE = False
ENABLE_EXPONENTIAL_LITERALS = False
ENABLE_CHAR_HEX_ESCAPE = False
ENABLE_STRINGS = False
__generators: "ClassVar[List[Type[BaseTestGenerator]]]" = []
@ -365,3 +367,39 @@ class BaseTestGenerator(ABC):
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

@ -28,10 +28,10 @@ class CharTestGenerator(BaseTestGenerator):
def generate_basic_tests(self):
"""Generate basic character literal tests."""
# Simple ASCII letters
self.make_success_test("Char Simple Letter A", "'A'", "char", 'A')
self.make_success_test("Char Simple Letter a", "'a'", "char", 'a')
self.make_success_test("Char Simple Letter Z", "'Z'", "char", 'Z')
self.make_success_test("Char Simple Letter z", "'z'", "char", 'z')
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')
@ -74,6 +74,7 @@ class CharTestGenerator(BaseTestGenerator):
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')
@ -165,6 +166,7 @@ class CharTestGenerator(BaseTestGenerator):
"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.")
@ -215,6 +217,7 @@ class CharTestGenerator(BaseTestGenerator):
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')
@ -245,6 +248,7 @@ class CharTestGenerator(BaseTestGenerator):
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')

View File

@ -221,7 +221,7 @@ class FloatTestGenerator(BaseTestGenerator):
# Comma separators not allowed
self.make_error_test("Float Invalid Comma Separator",
"1,234.56",
"Invalid float literal: unexpected ',' in float.")
"Invalid decimal literal: unexpected ',' in decimal integer.")
def generate_whitespace_tests(self):
"""Generate tests with whitespace."""

View File

@ -7,7 +7,7 @@ class IdentifierTestGenerator(BaseTestGenerator):
# Reserved words that might be operators or keywords
RESERVED_WORDS = [
'true', 'false', 'if', 'while', 'for', 'match', 'break', 'continue',
'if', 'while', 'for', 'match', 'break', 'continue',
'fn', 'struct', 'union', 'enum', 'trait', 'impl', 'inher',
'dup', 'drop', 'swap', 'over', 'rot', 'pick', 'roll', 'depth',
]
@ -66,7 +66,7 @@ class IdentifierTestGenerator(BaseTestGenerator):
# Others
self.make_success_test("Identifier With Dash", "my-var",
"identifier_literal", "my-var")
"identifier", "my-var")
def generate_identifier_literal_tests(self):
"""Generate identifier literal tests (with :: prefix)."""
@ -153,17 +153,12 @@ class IdentifierTestGenerator(BaseTestGenerator):
"Invalid decimal literal: unexpected 'a' in decimal integer.")
# Invalid characters
self.make_error_test("Identifier With Hash",
"my#var",
"Invalid identifier: '#' is not allowed in identifiers.")
self.make_success_test("Identifier With Octothorpe", "my#var",
"identifier", "my")
self.make_error_test("Identifier With Dot",
"my.var",
"Invalid identifier: '.' is not allowed in identifiers.")
self.make_error_test("Identifier With Space",
"my var",
"Invalid identifier: whitespace not allowed in identifiers.")
# 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",
@ -172,47 +167,45 @@ class IdentifierTestGenerator(BaseTestGenerator):
# Note: :: is allowed only as prefix for identifier literals
self.make_error_test("Identifier Double Colon Inside",
"my::var",
"Invalid identifier: '::' only allowed as prefix for identifier literals.")
"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 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 Dollar",
# "$variable",
# "Invalid identifier: '$' is not allowed in identifiers.")
self.make_error_test("Identifier With Percent",
"%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 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 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.")
# 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 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.")
# self.make_error_test("Identifier With Double Quote",
# 'my"var',
# "Invalid identifier: quotes not allowed in identifiers.")
# Only numbers (not valid identifier)
self.make_error_test("Identifier Only Numbers",
"123",
"Not an identifier: numeric literal.")
self.make_success_test("Identifier Only Numbers", "123", "i64", 123)
# Empty identifier literal
self.make_error_test("Identifier Literal Empty",
@ -329,6 +322,7 @@ class BooleanTestGenerator(BaseTestGenerator):
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")

View File

@ -141,47 +141,39 @@ class IntegerTestGenerator(BaseTestGenerator):
f"{underflow_val}:{type_name}",
f"Integer overflow: value exceeds range for {type_name}.")
def generate_special_typed_tests(self, type_name: str):
"""Generate special tests for specific types."""
min_val, max_val = self.TYPE_RANGES[type_name]
is_unsigned = type_name.startswith('u')
# Underscores with type annotation
if max_val >= 1000000:
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
if type_name == 'i8':
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)
elif type_name == 'u8':
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)
elif type_name == 'i16':
self.make_success_test("Integer i16 Hex Sample", "0x1234:i16", "i16", 4660)
self.make_success_test("Integer i16 Binary Sample",
"0b1111111100000000:i16", "i16", 65280)
"0b1111111100000000:i16", "i16", -256)
self.make_success_test("Integer i16 Octal Sample", "0o1234:i16", "i16", 668)
elif type_name == 'u16':
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)
elif type_name == 'i32':
self.make_success_test("Integer i32 Hex Sample", "0xABCD:i32", "i32", 43981)
self.make_success_test("Integer i32 Binary Sample",
"0b11110000:i32", "i32", 240)
elif type_name == 'u32':
self.make_success_test("Integer u32 Hex Max", "0xFFFFFFFF:u32", "u32", 4294967295)
self.make_success_test("Integer u32 Binary Sample",
"0b11111111000000001111111100000000:u32",
@ -190,13 +182,11 @@ class IntegerTestGenerator(BaseTestGenerator):
"0o37777777777:u32", "u32", 4294967295)
self.make_success_test("Integer u32 Decimal Mid", "1000000:u32", "u32", 1000000)
elif type_name == 'i64':
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)
elif type_name == 'u64':
self.make_success_test("Integer u64 Hex Max",
"0xFFFFFFFFFFFFFFFF:u64",
"u64", "UINT64_MAX")
@ -227,7 +217,7 @@ class IntegerTestGenerator(BaseTestGenerator):
# 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(type_name)
self.generate_special_typed_tests()
# Additional edge cases
self.generate_underscore_tests()

View File

@ -436,6 +436,10 @@ class StringTestGenerator(BaseTestGenerator):
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()

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

@ -83,10 +83,150 @@ def _token_to_c_call(token: dict, idx_var="i") -> str:
elif ttype == "bool":
return f'test_boolean_value(&test, result, {idx_var}++, &(Boolean){{{"TRUE" if value else "FALSE"}}})' # type: ignore
elif ttype == "error":
return f'test_for_error(&test, result, i++, SLS_STR("{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:
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:
"""Generate a C 'test_*_value' call based on token type."""
return f"if ({_token_to_c_call(token, idx_var)}) return test.result;"