Compare commits
185 Commits
SE3250-Che
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
d775ab6067 | |
|
|
abc2d2e24a | |
|
|
7b35803e8f | |
|
|
3773e76c95 | |
|
|
c091ffacbd | |
|
|
7aa2a36e1a | |
|
|
56dcf72241 | |
|
|
24154387ec | |
|
|
0f4b851958 | |
|
|
60e2f40816 | |
|
|
102c5b418a | |
|
|
7c0f20a1af | |
|
|
634c63d294 | |
|
|
a20e8a4d54 | |
|
|
494639d805 | |
|
|
4c9aa78dc8 | |
|
|
f5c786c061 | |
|
|
8890216457 | |
|
|
d7164599f0 | |
|
|
dc5d75cf75 | |
|
|
7b57807fc7 | |
|
|
d8e38ada85 | |
|
|
0b37a4210f | |
|
|
2ac3c1ac26 | |
|
|
642c277388 | |
|
|
c1c54277a7 | |
|
|
c89acbbdfb | |
|
|
3a57fd7373 | |
|
|
60c6e3900f | |
|
|
b98012488a | |
|
|
c74ba18067 | |
|
|
5b5e24e633 | |
|
|
35a6f4537f | |
|
|
64f620b2ef | |
|
|
8e918dcf34 | |
|
|
2dabd4bf30 | |
|
|
835132577f | |
|
|
1c62f064a4 | |
|
|
e76287b9a4 | |
|
|
3a054bc211 | |
|
|
2c8e459cac | |
|
|
a754cd4df4 | |
|
|
4a2ee88328 | |
|
|
1a11569e9a | |
|
|
ac9bbc0415 | |
|
|
c1db0937a2 | |
|
|
3c288e9165 | |
|
|
3dc5455323 | |
|
|
081930e6f5 | |
|
|
6f4be5a929 | |
|
|
a154741176 | |
|
|
a26a1c0d4a | |
|
|
08f0437136 | |
|
|
08a8aadf16 | |
|
|
ae077ef433 | |
|
|
d17687e5a6 | |
|
|
1875f2debd | |
|
|
483e0c3d52 | |
|
|
e1c43f7b2e | |
|
|
a15490b521 | |
|
|
6f81cbdf15 | |
|
|
b70634b450 | |
|
|
58f41e6bda | |
|
|
5094e8b4ab | |
|
|
749d5b4185 | |
|
|
7f46fd7f84 | |
|
|
90492053f2 | |
|
|
53983d8e92 | |
|
|
1fa6f4ec2a | |
|
|
c9f15fceb9 | |
|
|
8067b93e62 | |
|
|
5ea2d8fe2a | |
|
|
024af6a778 | |
|
|
50f90dcf84 | |
|
|
ae3483c612 | |
|
|
721384d400 | |
|
|
4ef109bcec | |
|
|
15b3565ee9 | |
|
|
69c211ec06 | |
|
|
b287c00c65 | |
|
|
8a5e3494e6 | |
|
|
2f36271439 | |
|
|
aa8a69b261 | |
|
|
0512147e6d | |
|
|
b930e2c23b | |
|
|
f91c63b37f | |
|
|
6a82cde8f2 | |
|
|
8e67857c95 | |
|
|
2086ee503d | |
|
|
b402f32e68 | |
|
|
b2e2b91f2c | |
|
|
f4a7627d7e | |
|
|
9e0675cecc | |
|
|
bfeb4c6444 | |
|
|
d7107b3fc5 | |
|
|
3d419f071c | |
|
|
873211ace7 | |
|
|
433cc3620a | |
|
|
2ddb0ca4d6 | |
|
|
b935325eb4 | |
|
|
ffc73e773c | |
|
|
0bbb43d5a3 | |
|
|
521bd9907a | |
|
|
d333cdfad5 | |
|
|
382842540a | |
|
|
c100bfcf7b | |
|
|
ba8aee6d78 | |
|
|
390fc1981d | |
|
|
95dba7e478 | |
|
|
63541aac85 | |
|
|
20434b20ab | |
|
|
e94d316af8 | |
|
|
404588d491 | |
|
|
fb4fe5ad66 | |
|
|
d8f5ad44b5 | |
|
|
fd80b5f69d | |
|
|
98f6ba8eab | |
|
|
8ff968d209 | |
|
|
b0be7f0c0b | |
|
|
528f5aa3ab | |
|
|
dc6be59fab | |
|
|
46a855abd0 | |
|
|
c9aceac591 | |
|
|
f3ae278e53 | |
|
|
08205ea6bc | |
|
|
4c07271aaf | |
|
|
a6cfe15a29 | |
|
|
727f461fb6 | |
|
|
76a89fe03f | |
|
|
3512f00f30 | |
|
|
6f202602ec | |
|
|
3a61e250a9 | |
|
|
2a03107e94 | |
|
|
a080dbc2fb | |
|
|
b49130bce7 | |
|
|
54a098a21f | |
|
|
8db2b0f06f | |
|
|
bc4b65ed2c | |
|
|
ec36f36713 | |
|
|
a193006061 | |
|
|
febf34a737 | |
|
|
eacef33cf8 | |
|
|
b2f4b8d850 | |
|
|
e80e1756e0 | |
|
|
d303995587 | |
|
|
d166eb5289 | |
|
|
1566c7bf60 | |
|
|
ad26c41463 | |
|
|
6d586852d4 | |
|
|
d2e990fe9b | |
|
|
ee8b7a8f45 | |
|
|
58b5e61740 | |
|
|
68665a82ae | |
|
|
f2033e30e9 | |
|
|
f8894ea4c0 | |
|
|
ffe8008bb9 | |
|
|
77b43b9595 | |
|
|
204a819b52 | |
|
|
2d7616e7c6 | |
|
|
f52ea00c34 | |
|
|
74d53b3a8d | |
|
|
c5ab140f5b | |
|
|
4398b3a4bc | |
|
|
690155b9a7 | |
|
|
60925dab53 | |
|
|
db14867474 | |
|
|
87ba892839 | |
|
|
25d2202ebd | |
|
|
375c3f2422 | |
|
|
cf5f51ccd8 | |
|
|
d480321014 | |
|
|
2c550c4662 | |
|
|
a62008a3e0 | |
|
|
2ec9d1d1a9 | |
|
|
beae4f0b9d | |
|
|
2b44aad1c7 | |
|
|
cceedd2e46 | |
|
|
1617f6945a | |
|
|
2ebe34a9a6 | |
|
|
a5b119807d | |
|
|
f3af20aa36 | |
|
|
40007c27a6 | |
|
|
329a71ca24 | |
|
|
9f616d3e87 | |
|
|
3b1e05241e |
|
|
@ -1 +1,2 @@
|
|||
.vscode/
|
||||
__pycache__/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
# SLS Changelog
|
||||
|
||||
## 0.0.2-alpha
|
||||
*08 Dec 2025*
|
||||
|
||||
- Added Rust Port
|
||||
- Added Interpreter State Serialization to Rust port
|
||||
- Added `#load <file>` and `#save <file>` directives to the REPL
|
||||
- Added Python Port
|
||||
- Added Calculator app
|
||||
- Added sls_py.calc module
|
||||
- Added RP2040 build target for the C implementation
|
||||
|
||||
## 0.0.1-alpha
|
||||
*01 Dec 2025*
|
||||
|
||||
- Added executing a file
|
||||
- Implemented the following builtin operators:
|
||||
- `for`
|
||||
- `logb`
|
||||
- `max`
|
||||
- `min`
|
||||
- `rot`
|
||||
- `const`
|
||||
- `atan2`
|
||||
- `roll`
|
||||
- `while`
|
||||
- `type_of`
|
||||
- `eval`
|
||||
- `lambda`
|
||||
- `if`
|
||||
- `pick`
|
||||
- `dup`
|
||||
- bitwise `and`
|
||||
- bitwise `not`
|
||||
- bitwise `or`
|
||||
- bitwise `xor`
|
||||
- boolean `and`
|
||||
- boolean `not`
|
||||
- boolean `or`
|
||||
- `shl`
|
||||
- `shr`
|
||||
- comparisons
|
||||
- `ceil`
|
||||
- `floor`
|
||||
- `round`
|
||||
- `swap`
|
||||
- `seed`
|
||||
- `rand`
|
||||
- `acos`
|
||||
- `asin`
|
||||
- `atan`
|
||||
- `cos`
|
||||
- `ln`
|
||||
- `log`
|
||||
- `sin`
|
||||
- `sqrt`
|
||||
- `tan`
|
||||
- `abs`
|
||||
- modulus
|
||||
- exponential
|
||||
- addition
|
||||
- subtraction
|
||||
- multiplication
|
||||
|
||||
## SE Checkpoint 3
|
||||
*28 Nov 2025*
|
||||
|
||||
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
|
||||
|
||||
[Compare SE3250-Checkpoint2..SE3250-Checkpoint3](https://github.com/SnowSE/final-project-KylerOlsen/compare/SE3250-Checkpoint2...SE3250-Checkpoint3)
|
||||
|
||||
**Report**:
|
||||
|
||||
Since checkpoint 2, I completed `token string` parsing and started working on
|
||||
the REPL. The lexer and REPL are basically finished as much as I am going to do
|
||||
for this assignment. I did start to implement builtin functions and operators.
|
||||
Currently only
|
||||
[`depth`](https://sls.purplecello.org/complete_operator_reference.html#depth),
|
||||
[`drop`](https://sls.purplecello.org/complete_operator_reference.html#drop), and
|
||||
[`/`](https://sls.purplecello.org/complete_operator_reference.html#_5)
|
||||
|
||||
**Plan**:
|
||||
|
||||
By the final turn-in on December 6th, I plan on getting all arithmetic
|
||||
operations working. And the python and rust ports with help from AI.
|
||||
|
||||
**Stuck**:
|
||||
|
||||
I'm not really stuck on anything, but I am running out of time, plus I am
|
||||
worried about not having enough AI usage to do the ports. If I at least get the
|
||||
main structures of the ports, I hope it will be enough for me to finish.
|
||||
|
||||
## SE Checkpoint 2
|
||||
*20 Nov 2025*
|
||||
|
||||
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
|
||||
|
||||
[Compare SE3250-Checkpoint1..SE3250-Checkpoint2](https://github.com/SnowSE/final-project-KylerOlsen/compare/SE3250-Checkpoint1...SE3250-Checkpoint2)
|
||||
|
||||
**Report**:
|
||||
|
||||
I completed my string type for the interpreter. `floats`, `characters`,
|
||||
`identifiers`, and `boolean` token lexing has been completed. `integer` tokens
|
||||
were working at checkpoint 1.
|
||||
|
||||
**Plan**:
|
||||
|
||||
By checkpoint 3 I plan to complete `token string` token parsing and basic
|
||||
arithmetic execution.
|
||||
|
||||
**Stuck**:
|
||||
|
||||
I'm not really stuck on anything, but I have been dropping things from the scope
|
||||
of this assignment. I do hope to add them when I have time in the future. These
|
||||
features moved to the backlog include:
|
||||
|
||||
- Unicode support
|
||||
- Exponential literals
|
||||
- Arrays and array functions
|
||||
- Type tuples, function defs
|
||||
- Structs, unions, etc.
|
||||
|
||||
## SE Checkpoint 1
|
||||
*07 Nov 2025*
|
||||
|
||||
**intended user**:
|
||||
Programmers
|
||||
|
||||
**detailed functional requirements**:
|
||||
A stack based language interpreter inspired by RPL, C, Rust, and Uiua
|
||||
([sls.purplecello.org](https://sls.purplecello.org))
|
||||
|
||||
**motivation for wanting to work on the project**:
|
||||
It is cool, and I have had this idea for a while and learning new languages this
|
||||
semester has given me more ideas and motivation for this.
|
||||
|
||||
**motivation for language selection**:
|
||||
C is a lower level language that can run on embedded systems and is well suited
|
||||
for writing language interpreters like Python and Lua.
|
||||
|
||||
**non-functional requirements / constraints**:
|
||||
Efficient enough for use on a modern micro controller
|
||||
(ie. Raspberry Pi Pico, ESP32)
|
||||
191
README.md
191
README.md
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
obj/
|
||||
bin/
|
||||
build_pico/
|
||||
*.o
|
||||
*.pdb
|
||||
CMakeLists.txt
|
||||
pico_arm_gcc_toolchain.cmake
|
||||
generated/
|
||||
pico-sdk.txt
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Implementation Mismatches
|
||||
|
||||
This is a list of things defined as currently in the specification, but not included in this implementation to reduce scope for the assignment.
|
||||
|
||||
- Unicode support
|
||||
- Exponential literals
|
||||
- Arrays and array functions
|
||||
- Type tuples, function defs
|
||||
- Structs, unions, etc.
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
# Makefile for SLS project with automatic header dependencies
|
||||
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -std=c11 -Wall -Wextra -g -Iinclude -MMD -MP
|
||||
LDFLAGS ?=
|
||||
CTESTFLAGS ?= -std=c11 -Wall -Wextra -Wno-unused-function -g -O0 -Iinclude -MMD -MP
|
||||
|
||||
SRCDIR := src
|
||||
OBJDIR := obj
|
||||
BINDIR := bin
|
||||
TESTDIR := tests
|
||||
|
||||
TARGET := $(BINDIR)/sls
|
||||
TEST_TARGET := $(BINDIR)/sls_tests
|
||||
|
||||
SOURCES := $(wildcard $(SRCDIR)/*.c)
|
||||
OBJECTS := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SOURCES))
|
||||
NON_MAIN_OBJECTS := $(filter-out $(OBJDIR)/main.o,$(OBJECTS))
|
||||
|
||||
TEST_SOURCES := $(wildcard $(TESTDIR)/*.c)
|
||||
TEST_OBJECTS := $(patsubst $(TESTDIR)/%.c,$(OBJDIR)/%.o,$(TEST_SOURCES))
|
||||
|
||||
# Include dependency files if they exist
|
||||
-include $(OBJECTS:.o=.d) $(TEST_OBJECTS:.o=.d)
|
||||
|
||||
.PHONY: all build run test clean
|
||||
|
||||
# Default: build main program
|
||||
all: $(TARGET)
|
||||
|
||||
# Compile object files
|
||||
build: $(OBJECTS)
|
||||
|
||||
# Rule to compile .c -> .o (handles both src and tests)
|
||||
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(OBJDIR)/%.o: $(TESTDIR)/%.c | $(OBJDIR)
|
||||
$(CC) $(CTESTFLAGS) -c $< -o $@
|
||||
|
||||
# Link main program
|
||||
$(TARGET): $(OBJECTS) | $(BINDIR)
|
||||
$(CC) $(LDFLAGS) $^ -o $@ -lm
|
||||
|
||||
# Run main program
|
||||
run: $(TARGET)
|
||||
@echo "Running $(TARGET)..."
|
||||
./$(TARGET)
|
||||
|
||||
test_cases: ../SLS_Tests/yaml_to_c_tests.py ../SLS_Tests/cases.yaml
|
||||
python3 ../SLS_Tests/yaml_to_c_tests.py ../SLS_Tests/cases.yaml ./tests/lexer_tests.c
|
||||
|
||||
# Build test runner executable
|
||||
$(TEST_TARGET): $(TEST_OBJECTS) $(NON_MAIN_OBJECTS) | $(BINDIR)
|
||||
$(CC) $(LDFLAGS) $^ -o $@ -lm
|
||||
|
||||
build_tests: test_cases $(TEST_TARGET)
|
||||
|
||||
# Run tests
|
||||
debug: build_tests
|
||||
gdb ./$(TEST_TARGET)
|
||||
|
||||
# Run tests
|
||||
test: test_cases $(TEST_TARGET)
|
||||
@echo "Running tests..."
|
||||
./$(TEST_TARGET)
|
||||
|
||||
# Create directories if missing
|
||||
$(BINDIR):
|
||||
mkdir -p $(BINDIR)
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
# Remove build artifacts
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(BINDIR)
|
||||
|
|
@ -1,11 +1,23 @@
|
|||
# SLS C
|
||||
# SLS C
|
||||
|
||||
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`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# Sls String
|
||||
*string.h*
|
||||
|
||||
## Formats
|
||||
|
||||
- `y` char\*
|
||||
- `c` char
|
||||
- `d` int32_t
|
||||
- `l` int64_t
|
||||
- `u` uint64_t
|
||||
- `z` size_t
|
||||
- `f` double
|
||||
- `n` unicode uint8_t\[4\] (Not Implemented)
|
||||
- `s` string.h SlsStr
|
||||
- `t` lexer.h TokenType
|
||||
- `a` lexer.h ArrayType
|
||||
- `i` lexer.h IntegerBuiltInType
|
||||
- `e` error.h SlsError
|
||||
- `b` bool.h Boolean
|
||||
|
|
@ -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()
|
||||
|
|
@ -0,0 +1 @@
|
|||
{ 2 - dup 0 <= { drop 1 } { 1 1 rot 0 swap { drop swap 1 pick + } for swap drop } if } lambda ::fib const
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Kyler Olsen
|
||||
// YREA SLS
|
||||
// SLS Boolean Header
|
||||
// November 2025
|
||||
|
||||
#ifndef SLS_BOOLEAN_H
|
||||
#define SLS_BOOLEAN_H
|
||||
|
||||
typedef enum {
|
||||
FALSE,
|
||||
TRUE,
|
||||
} Boolean;
|
||||
|
||||
#endif // SLS_BOOLEAN_H
|
||||
|
|
@ -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
|
||||
|
|
@ -7,14 +7,12 @@
|
|||
#define SLS_ERROR_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum {
|
||||
FALSE,
|
||||
TRUE,
|
||||
} Boolean;
|
||||
#include "./string.h"
|
||||
|
||||
typedef struct {
|
||||
const char *message;
|
||||
SlsStr message;
|
||||
int32_t code;
|
||||
} SlsError;
|
||||
|
||||
|
|
@ -24,7 +22,7 @@ typedef enum {
|
|||
} SlsResultType;
|
||||
|
||||
typedef struct {
|
||||
const char *filename;
|
||||
SlsStr filename;
|
||||
size_t line;
|
||||
size_t column;
|
||||
size_t length;
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -8,13 +8,28 @@
|
|||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "sls/sls_errors.h"
|
||||
#include "sls/bool.h"
|
||||
#include "sls/errors.h"
|
||||
|
||||
#if __SIZEOF_POINTER__ == 4
|
||||
// 32-bit system
|
||||
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I32
|
||||
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
|
||||
#elif __SIZEOF_POINTER__ == 8
|
||||
// 64-bit system
|
||||
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I64
|
||||
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F64
|
||||
#else
|
||||
// Fallback
|
||||
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I16
|
||||
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
|
||||
#endif
|
||||
|
||||
extern const size_t TYPE_NAMES_SAFE_LENGTH;
|
||||
|
||||
typedef struct {
|
||||
const char *filename;
|
||||
const char *source_code;
|
||||
SlsStr filename;
|
||||
SlsStr source_code;
|
||||
size_t pos;
|
||||
size_t column;
|
||||
size_t line;
|
||||
|
|
@ -26,6 +41,7 @@ typedef enum {
|
|||
TOKEN_INTEGER,
|
||||
TOKEN_FLOAT,
|
||||
TOKEN_DOUBLE,
|
||||
TOKEN_CHARACTER,
|
||||
TOKEN_STRING,
|
||||
TOKEN_BOOLEAN,
|
||||
TOKEN_ARRAY,
|
||||
|
|
@ -34,6 +50,7 @@ typedef enum {
|
|||
} TokenType;
|
||||
|
||||
extern const char *TOKEN_TYPES_NAMES[];
|
||||
extern const size_t TOKEN_TYPE_COUNT;
|
||||
|
||||
typedef enum {
|
||||
ARRAY_IDENTIFIER,
|
||||
|
|
@ -47,16 +64,17 @@ typedef enum {
|
|||
ARRAY_U8,
|
||||
ARRAY_FLOAT,
|
||||
ARRAY_DOUBLE,
|
||||
ARRAY_CHARACTER,
|
||||
ARRAY_STRING,
|
||||
ARRAY_BOOLEAN,
|
||||
ARRAY_STRUCT_INLINE,
|
||||
} ArrayType;
|
||||
|
||||
extern const char *ARRAY_TYPES_NAMES[];
|
||||
extern const size_t ARRAY_TYPE_COUNT;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
size_t length;
|
||||
SlsStr name;
|
||||
Boolean is_literal;
|
||||
} Identifier;
|
||||
|
||||
|
|
@ -72,17 +90,13 @@ typedef enum {
|
|||
} IntegerBuiltInType;
|
||||
|
||||
extern const char *INTEGER_TYPES_NAMES[];
|
||||
extern const size_t INTEGER_TYPE_COUNT;
|
||||
|
||||
typedef struct {
|
||||
uint64_t value;
|
||||
IntegerBuiltInType type;
|
||||
} IntegerLiteral;
|
||||
|
||||
typedef struct {
|
||||
const char *value;
|
||||
size_t length;
|
||||
} StringLiteral;
|
||||
|
||||
typedef struct Token Token;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -99,7 +113,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
void **values;
|
||||
const char *name;
|
||||
SlsStr name;
|
||||
} StructInline;
|
||||
|
||||
typedef struct ArrayLiteral {
|
||||
|
|
@ -109,7 +123,8 @@ typedef struct ArrayLiteral {
|
|||
uint64_t *integer_literals; // type in { ARRAY_I64, ARRAY_I32, ARRAY_I16, ARRAY_I8, ARRAY_U64, ARRAY_U32, ARRAY_U16, ARRAY_U8, }
|
||||
float *float_literals; // type == ARRAY_FLOAT
|
||||
double *double_literals; // type == ARRAY_DOUBLE
|
||||
StringLiteral *string_literals; // type == ARRAY_STRING
|
||||
uint8_t *character_literal; // type == ARRAY_CHARACTER
|
||||
SlsStr *string_literals; // type == ARRAY_STRING
|
||||
Boolean *boolean_literals; // type == ARRAY_BOOLEAN
|
||||
TokenString *token_strings; // type == ARRAY_TOKEN_STRING
|
||||
TypeTuple *type_tuples; // type == ARRAY_TYPE_TUPLE
|
||||
|
|
@ -126,7 +141,8 @@ struct Token {
|
|||
IntegerLiteral integer_literal; // type == TOKEN_INTEGER
|
||||
float float_literal; // type == TOKEN_FLOAT
|
||||
double double_literal; // type == TOKEN_DOUBLE
|
||||
StringLiteral string_literal; // type == TOKEN_STRING
|
||||
uint8_t character_literal; // type == TOKEN_CHARACTER
|
||||
SlsStr string_literal; // type == TOKEN_STRING
|
||||
Boolean boolean_literal; // type == TOKEN_BOOLEAN
|
||||
ArrayLiteral array_literal; // type == TOKEN_ARRAY
|
||||
TokenString token_string; // type == TOKEN_TOKEN_STRING
|
||||
|
|
@ -152,8 +168,10 @@ typedef struct {
|
|||
};
|
||||
} LexerResult;
|
||||
|
||||
void init_lexer(LexerInfo *lexer_info, const char *filename, const char *source_code);
|
||||
TokenString copy_token_string(TokenString token_string);
|
||||
void init_lexer(LexerInfo *lexer_info, SlsStr filename, SlsStr source_code);
|
||||
LexerTokenResult *get_token(LexerTokenResult *head, size_t i);
|
||||
void clean_token_string(TokenString token_string);
|
||||
void clean_token_result(LexerTokenResult *head);
|
||||
LexerResult lexical_analysis(LexerInfo *lexer_info);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -7,8 +7,29 @@
|
|||
#define SLS_STRING_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
int isascii(unsigned char c);
|
||||
size_t strnlen(const char *s, size_t maxlen);
|
||||
#include "bool.h"
|
||||
|
||||
typedef struct {
|
||||
size_t len; // Number of useable characters (does not include trailing null character)
|
||||
char *str;
|
||||
Boolean allocated;
|
||||
} SlsStr;
|
||||
|
||||
#define SLS_STR_CONST(s) {sizeof(s) - 1, (s), FALSE}
|
||||
#define SLS_STR_NULL_CONST {0, NULL, FALSE}
|
||||
#define SLS_STR(s) (SlsStr) SLS_STR_CONST(s)
|
||||
#define SLS_STR_NULL (SlsStr) SLS_STR_NULL_CONST
|
||||
|
||||
int sls_isascii(unsigned char c);
|
||||
size_t sls_str_nlen(const char *s, size_t maxlen);
|
||||
SlsStr sls_str_malloc(const char *s, size_t maxlen);
|
||||
SlsStr sls_str_new(size_t length);
|
||||
SlsStr sls_str_cpy(SlsStr s);
|
||||
int32_t sls_str_cmp(SlsStr a, SlsStr b);
|
||||
void sls_str_free(SlsStr *s);
|
||||
SlsStr sls_format(const SlsStr s, ...);
|
||||
|
||||
#endif // SLS_STRING_H
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@
|
|||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "sls/sls_errors.h"
|
||||
#include "sls/errors.h"
|
||||
#include "sls/bool.h"
|
||||
#include "sls/lexer.h"
|
||||
#include "sls/string.h"
|
||||
#include "tests/lexer_test_helpers.h"
|
||||
|
|
@ -27,8 +28,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
Boolean is_literal;
|
||||
size_t length;
|
||||
const char *name;
|
||||
SlsStr name;
|
||||
} TestIdentifierValue;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -36,11 +36,6 @@ typedef struct {
|
|||
uint64_t value;
|
||||
} TestIntegerValue;
|
||||
|
||||
typedef struct {
|
||||
size_t length;
|
||||
const char *string;
|
||||
} TestStringValue;
|
||||
|
||||
typedef struct {
|
||||
size_t dimensions;
|
||||
size_t *shape;
|
||||
|
|
@ -68,7 +63,7 @@ typedef struct {
|
|||
typedef struct {
|
||||
size_t dimensions;
|
||||
size_t *shape;
|
||||
TestStringValue *values;
|
||||
SlsStr *values;
|
||||
} TestArrayStringValue;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -80,8 +75,7 @@ typedef struct {
|
|||
typedef struct {
|
||||
size_t dimensions;
|
||||
size_t *shape;
|
||||
size_t struct_name_length;
|
||||
const char *struct_name;
|
||||
SlsStr struct_name;
|
||||
Boolean (*struct_handler)(LexerTest *, LexerResult, size_t, size_t, void *, void *);
|
||||
void **values;
|
||||
} TestArrayStructInlineValue;
|
||||
|
|
@ -103,15 +97,11 @@ typedef struct {
|
|||
TestIdentifierValue *output_values;
|
||||
} TestTypeTupleValue;
|
||||
|
||||
typedef struct {
|
||||
size_t length;
|
||||
const char *message;
|
||||
} TestErrorMessage;
|
||||
|
||||
LexerTest start_up_test(const char *test_name, const char *test_code);
|
||||
LexerTest start_up_test(SlsStr test_name, SlsStr test_code);
|
||||
void clean_up_test(LexerResult result);
|
||||
TestResult error_test_out_of_mem(LexerTest *test);
|
||||
TestResult error_test(LexerTest *test, LexerResult result, SlsError error);
|
||||
TestResult logic_fail_test(LexerTest *test, LexerResult result, char *message);
|
||||
TestResult logic_fail_test(LexerTest *test, LexerResult result, SlsStr message);
|
||||
TestResult error_fail_test(LexerTest *test, LexerResult result, SlsError error);
|
||||
TestResult skip_test(LexerTest *test, LexerResult result);
|
||||
TestResult skip_test_no_result(LexerTest *test);
|
||||
|
|
@ -120,9 +110,10 @@ TestResult pass_test(LexerTest *test, LexerResult result);
|
|||
Boolean test_eof_value(LexerTest *test, LexerResult result, size_t i, void *_);
|
||||
Boolean test_identifier_value(LexerTest *test, LexerResult result, size_t i, TestIdentifierValue *value);
|
||||
Boolean test_integer_value(LexerTest *test, LexerResult result, size_t i, TestIntegerValue *value);
|
||||
Boolean test_character_value(LexerTest *test, LexerResult result, size_t i, uint8_t *value);
|
||||
Boolean test_float_value(LexerTest *test, LexerResult result, size_t i, float *value);
|
||||
Boolean test_double_value(LexerTest *test, LexerResult result, size_t i, double *value);
|
||||
Boolean test_string_value(LexerTest *test, LexerResult result, size_t i, TestStringValue *value);
|
||||
Boolean test_string_value(LexerTest *test, LexerResult result, size_t i, SlsStr *value);
|
||||
Boolean test_boolean_value(LexerTest *test, LexerResult result, size_t i, Boolean *value);
|
||||
Boolean test_array_identifier_value(LexerTest *test, LexerResult result, size_t i, TestArrayIdentifierValue *values);
|
||||
Boolean test_array_integer_value(LexerTest *test, LexerResult result, size_t i, TestArrayIntegerValue *values);
|
||||
|
|
@ -133,6 +124,6 @@ Boolean test_array_boolean_value(LexerTest *test, LexerResult result, size_t i,
|
|||
Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size_t i, TestArrayStructInlineValue *values);
|
||||
Boolean test_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, TestErrorMessage *error);
|
||||
Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr *error);
|
||||
|
||||
#endif // SLS_LEXER_TEST_HELPERS_H
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "sls/sls_errors.h"
|
||||
#include "sls/errors.h"
|
||||
#include "sls/string.h"
|
||||
|
||||
extern const char *TEST_FILE_NAME;
|
||||
extern const SlsStr TEST_FILE_NAME;
|
||||
|
||||
typedef enum {
|
||||
TEST_ERROR, // The test encountered an error
|
||||
|
|
@ -22,20 +23,23 @@ typedef enum {
|
|||
} TestResultType;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
SlsStr name;
|
||||
TestResultType status;
|
||||
union {
|
||||
char *message; // status in { TEST_LOGIC_FAIL, }
|
||||
SlsStr message; // status in { TEST_LOGIC_FAIL, }
|
||||
SlsError error; // status in { TEST_ERROR, TEST_ERROR_FAIL, }
|
||||
};
|
||||
} TestResult;
|
||||
|
||||
typedef struct {
|
||||
const char *section;
|
||||
SlsStr section;
|
||||
size_t count;
|
||||
TestResult* tests;
|
||||
} TestsReport;
|
||||
|
||||
TestsReport run_string_tests();
|
||||
TestsReport run_lexer_tests();
|
||||
TestsReport run_hash_table_tests();
|
||||
TestsReport run_extra_tests();
|
||||
|
||||
#endif // SLS_TESTS_H
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -9,8 +9,10 @@
|
|||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "sls/sls_errors.h"
|
||||
#include "sls/errors.h"
|
||||
#include "sls/bool.h"
|
||||
#include "sls/lexer.h"
|
||||
#include "sls/string.h"
|
||||
|
||||
|
|
@ -22,6 +24,7 @@ const char *TOKEN_TYPES_NAMES[] = {
|
|||
"Integer",
|
||||
"Float",
|
||||
"Double",
|
||||
"Character",
|
||||
"String",
|
||||
"Boolean",
|
||||
"Array",
|
||||
|
|
@ -29,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",
|
||||
|
|
@ -41,11 +46,14 @@ const char *ARRAY_TYPES_NAMES[] = {
|
|||
"u8",
|
||||
"Float",
|
||||
"Double",
|
||||
"Character",
|
||||
"String",
|
||||
"Boolean",
|
||||
"Inline Struct",
|
||||
};
|
||||
|
||||
const size_t ARRAY_TYPE_COUNT = sizeof(ARRAY_TYPES_NAMES) / sizeof(*ARRAY_TYPES_NAMES);
|
||||
|
||||
const char *INTEGER_TYPES_NAMES[] = {
|
||||
"i64",
|
||||
"i32",
|
||||
|
|
@ -57,7 +65,9 @@ const char *INTEGER_TYPES_NAMES[] = {
|
|||
"u8",
|
||||
};
|
||||
|
||||
void init_lexer(LexerInfo *lexer_info, const char *filename, const char *source_code) {
|
||||
const size_t INTEGER_TYPE_COUNT = sizeof(INTEGER_TYPES_NAMES) / sizeof(*INTEGER_TYPES_NAMES);
|
||||
|
||||
void init_lexer(LexerInfo *lexer_info, SlsStr filename, SlsStr source_code) {
|
||||
// Initializes a LexerInfo struct with file info and source code
|
||||
lexer_info->filename = filename;
|
||||
lexer_info->source_code = source_code;
|
||||
|
|
@ -79,22 +89,27 @@ static FileInfo get_file_info(LexerInfo *lexer_info, size_t start, size_t start_
|
|||
|
||||
static const char *get_token_text(LexerInfo *lexer_info, size_t start) {
|
||||
// Returns the current character from the source code
|
||||
return lexer_info->source_code + start;
|
||||
return lexer_info->source_code.str + start;
|
||||
}
|
||||
|
||||
static char peek(LexerInfo *lexer_info) {
|
||||
// Returns the current character from the source code
|
||||
return lexer_info->source_code[lexer_info->pos];
|
||||
return lexer_info->source_code.str[lexer_info->pos];
|
||||
}
|
||||
|
||||
static char far_peek(LexerInfo *lexer_info, size_t index) {
|
||||
// Returns the character index away from the current char in the source code
|
||||
return lexer_info->source_code[lexer_info->pos + index];
|
||||
return lexer_info->source_code.str[lexer_info->pos + index];
|
||||
}
|
||||
|
||||
static char seek(LexerInfo *lexer_info, size_t index) {
|
||||
// Returns the character from the given index from the source code
|
||||
return lexer_info->source_code.str[index];
|
||||
}
|
||||
|
||||
static char advance(LexerInfo *lexer_info) {
|
||||
// Advances lexer_info to the next character
|
||||
if (lexer_info->source_code[lexer_info->pos] == '\n') {
|
||||
if (lexer_info->source_code.str[lexer_info->pos] == '\n') {
|
||||
// If a new line is encountered, advance line and reset column
|
||||
lexer_info->line++;
|
||||
lexer_info->column = 1;
|
||||
|
|
@ -103,14 +118,14 @@ static char advance(LexerInfo *lexer_info) {
|
|||
lexer_info->column++;
|
||||
}
|
||||
// Advance to and return the next character
|
||||
return lexer_info->source_code[++lexer_info->pos];
|
||||
return lexer_info->source_code.str[++lexer_info->pos];
|
||||
}
|
||||
|
||||
static LexerResult lexer_result(LexerInfo *lexer_info, Token token, size_t start, size_t start_line) {
|
||||
// Create a LexerTokenResult to store the results of lexing the current token
|
||||
LexerTokenResult *result = (LexerTokenResult *)malloc(sizeof(LexerTokenResult));
|
||||
if (result == NULL)
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Failed to allocate memory.", 1}};
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Failed to allocate memory."), 1}};
|
||||
result->type = SLS_RESULT;
|
||||
result->result = token;
|
||||
result->file_info = get_file_info(lexer_info, start, start_line);
|
||||
|
|
@ -118,11 +133,11 @@ static LexerResult lexer_result(LexerInfo *lexer_info, Token token, size_t start
|
|||
return (LexerResult){SLS_RESULT, .result = result};
|
||||
}
|
||||
|
||||
static LexerResult lexer_error(LexerInfo *lexer_info, const char* message, size_t start, size_t start_line) {
|
||||
static LexerResult lexer_error(LexerInfo *lexer_info, SlsStr message, size_t start, size_t start_line) {
|
||||
// Create a LexerTokenResult to store an error from lexing the current token
|
||||
LexerTokenResult *result = (LexerTokenResult *)malloc(sizeof(LexerTokenResult));
|
||||
if (result == NULL)
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Failed to allocate memory.", 1}};
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Failed to allocate memory."), 1}};
|
||||
result->type = SLS_ERROR;
|
||||
result->error.message = message;
|
||||
result->error.code = 1;
|
||||
|
|
@ -190,7 +205,7 @@ static IntegerTypeResult get_integer_type(NumericTypes numeric_type) {
|
|||
integer_type = INTEGER_U8;
|
||||
break;
|
||||
default:
|
||||
return (IntegerTypeResult){SLS_ERROR, .error = (SlsError){.message = "Lexer Error: Encountered a Float where there should not be one.", .code = 1}};
|
||||
return (IntegerTypeResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer Error: Encountered a Float where there should not be one."), 1}};
|
||||
}
|
||||
return (IntegerTypeResult){SLS_RESULT, .integer_type = integer_type};
|
||||
}
|
||||
|
|
@ -212,7 +227,8 @@ static uint64_t create_binary_integer(LexerInfo *lexer_info, size_t start) {
|
|||
case '1': value += 1; break;
|
||||
}
|
||||
}
|
||||
return value * (negative ? -1 : 1);
|
||||
if (negative) value = (~value) + 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint64_t create_octal_integer(LexerInfo *lexer_info, size_t start) {
|
||||
|
|
@ -238,7 +254,8 @@ static uint64_t create_octal_integer(LexerInfo *lexer_info, size_t start) {
|
|||
case '7': value += 7; break;
|
||||
}
|
||||
}
|
||||
return value * (negative ? -1 : 1);
|
||||
if (negative) value = (~value) + 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint64_t create_decimal_integer(LexerInfo *lexer_info, size_t start) {
|
||||
|
|
@ -252,7 +269,7 @@ static uint64_t create_decimal_integer(LexerInfo *lexer_info, size_t start) {
|
|||
}
|
||||
for (; i < lexer_info->pos - start; i++) {
|
||||
if (isspace(token[i]) || token[i] == '/' || token[i] == '\0' || token[i] == ':') break;
|
||||
if (token[i] == '.' || token[i] == '_') continue;
|
||||
if (token[i] == '_') continue;
|
||||
value *= 10;
|
||||
switch (token[i]) {
|
||||
case '1': value += 1; break;
|
||||
|
|
@ -266,7 +283,8 @@ static uint64_t create_decimal_integer(LexerInfo *lexer_info, size_t start) {
|
|||
case '9': value += 9; break;
|
||||
}
|
||||
}
|
||||
return value * (negative ? -1 : 1);
|
||||
if (negative) value = (~value) + 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint64_t create_hexadecimal_integer(LexerInfo *lexer_info, size_t start) {
|
||||
|
|
@ -306,7 +324,99 @@ static uint64_t create_hexadecimal_integer(LexerInfo *lexer_info, size_t start)
|
|||
case 'f': value += 15; break;
|
||||
}
|
||||
}
|
||||
return value * (negative ? -1 : 1);
|
||||
if (negative) value = (~value) + 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
static LexerResult create_integer_token(LexerInfo *lexer_info, IntegerBuiltInType type, uint64_t value, size_t start, size_t start_line) {
|
||||
switch (type) {
|
||||
case INTEGER_I64: break;
|
||||
case INTEGER_I32:
|
||||
if ((int64_t)value < INT32_MIN || (int64_t)value > INT32_MAX) {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i32."), start, start_line);
|
||||
}
|
||||
break;
|
||||
case INTEGER_I16:
|
||||
if ((int64_t)value < INT16_MIN || (int64_t)value > INT16_MAX) {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i16."), start, start_line);
|
||||
}
|
||||
break;
|
||||
case INTEGER_I8:
|
||||
if ((int64_t)value < INT8_MIN || (int64_t)value > INT8_MAX) {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i8."), start, start_line);
|
||||
}
|
||||
break;
|
||||
case INTEGER_U64:
|
||||
if (seek(lexer_info, start) == '-') {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u64."), start, start_line);
|
||||
}
|
||||
break;
|
||||
case INTEGER_U32:
|
||||
if (seek(lexer_info, start) == '-') {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u32."), start, start_line);
|
||||
} else if (value > (uint64_t)UINT32_MAX) {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u32."), start, start_line);
|
||||
}
|
||||
break;
|
||||
case INTEGER_U16:
|
||||
if (seek(lexer_info, start) == '-') {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u16."), start, start_line);
|
||||
} else if (value > (uint64_t)UINT16_MAX) {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u16."), start, start_line);
|
||||
}
|
||||
break;
|
||||
case INTEGER_U8:
|
||||
if (seek(lexer_info, start) == '-') {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u8."), start, start_line);
|
||||
} else if (value > (uint64_t)UINT8_MAX) {
|
||||
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u8."), start, start_line);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = type, .value = value}}, start, start_line);
|
||||
}
|
||||
|
||||
static double create_float(LexerInfo *lexer_info, size_t start) {
|
||||
double value = 0;
|
||||
Boolean negative = FALSE;
|
||||
uint64_t fractional = 0;
|
||||
const char *token = get_token_text(lexer_info, start);
|
||||
size_t i = 0;
|
||||
if (token[0] == '-') {
|
||||
negative = TRUE;
|
||||
i += 1;
|
||||
}
|
||||
for (; i < lexer_info->pos - start; i++) {
|
||||
if (isspace(token[i]) || token[i] == '/' || token[i] == '\0' || token[i] == ':') break;
|
||||
if (token[i] == '_') continue;
|
||||
if (token[i] == '.') {
|
||||
fractional = 1;
|
||||
continue;
|
||||
}
|
||||
if (fractional == 0) value *= 10;
|
||||
else fractional *= 10;
|
||||
switch (token[i]) {
|
||||
case '1': value += 1.0 / (fractional == 0 ? 1 : fractional); break;
|
||||
case '2': value += 2.0 / (fractional == 0 ? 1 : fractional); break;
|
||||
case '3': value += 3.0 / (fractional == 0 ? 1 : fractional); break;
|
||||
case '4': value += 4.0 / (fractional == 0 ? 1 : fractional); break;
|
||||
case '5': value += 5.0 / (fractional == 0 ? 1 : fractional); break;
|
||||
case '6': value += 6.0 / (fractional == 0 ? 1 : fractional); break;
|
||||
case '7': value += 7.0 / (fractional == 0 ? 1 : fractional); break;
|
||||
case '8': value += 8.0 / (fractional == 0 ? 1 : fractional); break;
|
||||
case '9': value += 9.0 / (fractional == 0 ? 1 : fractional); break;
|
||||
}
|
||||
}
|
||||
if (negative) value = -value;
|
||||
return value;
|
||||
}
|
||||
|
||||
static LexerResult create_float_token(LexerInfo *lexer_info, NumericTypes type, size_t start, size_t start_line) {
|
||||
double value = create_float(lexer_info, start);
|
||||
if (type == NUMERIC_F64)
|
||||
return lexer_result(lexer_info, (Token){TOKEN_DOUBLE, .double_literal = value}, start, start_line);
|
||||
else
|
||||
return lexer_result(lexer_info, (Token){TOKEN_FLOAT, .float_literal = (float){value}}, start, start_line);
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
|
|
@ -334,16 +444,14 @@ static LexerResult parse_numeric_type(LexerInfo *lexer_info, char c, size_t star
|
|||
c = advance(lexer_info);
|
||||
c = advance(lexer_info);
|
||||
} else {
|
||||
char *error_message = (char *)malloc(sizeof(char) * 52);
|
||||
strncpy(error_message, "Invalid float type: must be of type 'f64' or 'f32'.", 52);
|
||||
return lexer_error(lexer_info, error_message, start, start_line);
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid float type: must be of type 'f64' or 'f32'."), start, start_line);
|
||||
}
|
||||
} else {
|
||||
char *error_message = (char *)malloc(sizeof(char) * 49);
|
||||
strncpy(error_message, "Invalid numeric literal: float type not allowed.", 49);
|
||||
return lexer_error(lexer_info, error_message, start, start_line);
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid numeric literal: float type not allowed."), start, start_line);
|
||||
}
|
||||
} else if (c == 'i' || c == 'u') {
|
||||
if (numeric_literal_type == NUMERIC_FLOAT || numeric_literal_type == NUMERIC_EXPONENTIAL)
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid float type: must be of type 'f64' or 'f32'."), start, start_line);
|
||||
if (c == 'u') numeric_type |= NUMERIC_UNSIGNED;
|
||||
c = advance(lexer_info);
|
||||
if (c == '6' && far_peek(lexer_info, 1) == '4') {
|
||||
|
|
@ -363,24 +471,16 @@ static LexerResult parse_numeric_type(LexerInfo *lexer_info, char c, size_t star
|
|||
c = advance(lexer_info);
|
||||
} else {
|
||||
if (numeric_type & NUMERIC_UNSIGNED) {
|
||||
char *error_message = (char *)malloc(sizeof(char) * 78);
|
||||
strncpy(error_message, "Invalid unsigned integer type: must be of type 'u64', 'u32', 'u16', and 'u8'.", 78);
|
||||
return lexer_error(lexer_info, error_message, start, start_line);
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid unsigned integer type: must be of type 'u64', 'u32', 'u16', and 'u8'."), start, start_line);
|
||||
} else {
|
||||
char *error_message = (char *)malloc(sizeof(char) * 76);
|
||||
strncpy(error_message, "Invalid signed integer type: must be of type 'i64', 'i32', 'i16', and 'i8'.", 76);
|
||||
return lexer_error(lexer_info, error_message, start, start_line);
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid signed integer type: must be of type 'i64', 'i32', 'i16', and 'i8'."), start, start_line);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (numeric_literal_type == NUMERIC_DECIMAL || numeric_literal_type == NUMERIC_FLOAT || numeric_literal_type == NUMERIC_EXPONENTIAL) {
|
||||
char *error_message = (char *)malloc(sizeof(char) * 61);
|
||||
strncpy(error_message, "Invalid numeric type: type must start with 'f', 'i', or 'u'.", 61);
|
||||
return lexer_error(lexer_info, error_message, start, start_line);
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid numeric type: type must start with 'f', 'i', or 'u'."), start, start_line);
|
||||
} else {
|
||||
char *error_message = (char *)malloc(sizeof(char) * 55);
|
||||
strncpy(error_message, "Invalid integer type: type must start with 'i' or 'u'.", 55);
|
||||
return lexer_error(lexer_info, error_message, start, start_line);
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid integer type: type must start with 'i' or 'u'."), start, start_line);
|
||||
}
|
||||
} if (isspace(c) || c == '/' || c == '\0') {
|
||||
IntegerTypeResult integer_type = get_integer_type(numeric_type);
|
||||
|
|
@ -391,142 +491,369 @@ static LexerResult parse_numeric_type(LexerInfo *lexer_info, char c, size_t star
|
|||
case NUMERIC_BINARY:
|
||||
if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error};
|
||||
value = create_binary_integer(lexer_info, start);
|
||||
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = integer_type.integer_type, .value = value}}, start, start_line);
|
||||
return create_integer_token(lexer_info, integer_type.integer_type, value, start, start_line);
|
||||
case NUMERIC_OCTAL:
|
||||
if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error};
|
||||
value = create_octal_integer(lexer_info, start);
|
||||
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = integer_type.integer_type, .value = value}}, start, start_line);
|
||||
return create_integer_token(lexer_info, integer_type.integer_type, value, start, start_line);
|
||||
case NUMERIC_DECIMAL:
|
||||
if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error};
|
||||
value = create_decimal_integer(lexer_info, start);
|
||||
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = integer_type.integer_type, .value = value}}, start, start_line);
|
||||
return create_integer_token(lexer_info, integer_type.integer_type, value, start, start_line);
|
||||
case NUMERIC_HEXADECIMAL:
|
||||
if (integer_type.type == SLS_ERROR) return (LexerResult){SLS_ERROR, .error = integer_type.error};
|
||||
value = create_hexadecimal_integer(lexer_info, start);
|
||||
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = integer_type.integer_type, .value = value}}, start, start_line);
|
||||
return create_integer_token(lexer_info, integer_type.integer_type, value, start, start_line);
|
||||
case NUMERIC_FLOAT:
|
||||
break;
|
||||
return create_float_token(lexer_info, numeric_type, start, start_line);
|
||||
case NUMERIC_EXPONENTIAL:
|
||||
break;
|
||||
}
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Numeric Literal Not Implemented Error.", 1}};
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Numeric Literal Not Implemented Error."), 1}};
|
||||
}
|
||||
char *error_message = (char *)malloc(sizeof(char) * 57);
|
||||
snprintf(error_message, 57, "Invalid numeric literal: unexpected '%c' in numeric type.", c);
|
||||
return lexer_error(lexer_info, error_message, start, start_line);
|
||||
SlsStr error_msg = sls_format(SLS_STR("Invalid numeric literal: unexpected '%c' in numeric type."), c);
|
||||
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult parse_binary_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
do {c = advance(lexer_info);} while (c == '0' || c == '1' || c == '_');
|
||||
while (c == '0' || c == '1' || c == '_') c = advance(lexer_info);
|
||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_BINARY);
|
||||
if (isspace(c) || c == '/' || c == '\0') {
|
||||
uint64_t value = create_binary_integer(lexer_info, start);
|
||||
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = INTEGER_I64, .value = value}}, start, start_line);
|
||||
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
|
||||
}
|
||||
char *error_message = (char *)malloc(sizeof(char) * 58);
|
||||
snprintf(error_message, 58, "Invalid binary literal: unexpected '%c' in binary integer.", c);
|
||||
return lexer_error(lexer_info, error_message, 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}};
|
||||
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult parse_octal_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
do {c = advance(lexer_info);} while ((isdigit(c) || c == '_') && !(c == '8' || c == '9'));
|
||||
while ((isdigit(c) || c == '_') && !(c == '8' || c == '9')) c = advance(lexer_info);
|
||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_OCTAL);
|
||||
if (isspace(c) || c == '/' || c == '\0') {
|
||||
uint64_t value = create_octal_integer(lexer_info, start);
|
||||
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = INTEGER_I64, .value = value}}, start, start_line);
|
||||
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
|
||||
}
|
||||
char *error_message = (char *)malloc(sizeof(char) * 56);
|
||||
snprintf(error_message, 56, "Invalid octal literal: unexpected '%c' in octal integer.", c);
|
||||
return lexer_error(lexer_info, error_message, 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}};
|
||||
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult parse_exponential(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
(void)lexer_info; (void)c; (void)start; (void)start_line;
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Float Exponential Not Implemented Error.", 1}};
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Float Exponential Not Implemented Error."), 1}};
|
||||
}
|
||||
|
||||
static LexerResult parse_float(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
(void)lexer_info; (void)c; (void)start; (void)start_line;
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Float Not Implemented Error.", 1}};
|
||||
while (isdigit(c) || c == '_') c = advance(lexer_info);
|
||||
if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line);
|
||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_FLOAT);
|
||||
if (isspace(c) || c == '/' || c == '\0')
|
||||
return create_float_token(lexer_info, SLS_FLOAT_TYPE_DEFAULT, start, start_line);
|
||||
SlsStr error_msg = sls_format(SLS_STR("Invalid float literal: unexpected '%c' in float."), c);
|
||||
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult parse_decimal_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
do {c = advance(lexer_info);} while (isdigit(c) || c == '_');
|
||||
if (c == '.') return parse_float(lexer_info, c, start, start_line);
|
||||
if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line);
|
||||
while (isdigit(c) || c == '_') c = advance(lexer_info);
|
||||
if (c == '.') {
|
||||
c = advance(lexer_info);
|
||||
return parse_float(lexer_info, c, start, start_line);
|
||||
} if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line);
|
||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_DECIMAL);
|
||||
if (isspace(c) || c == '/' || c == '\0') {
|
||||
uint64_t value = create_decimal_integer(lexer_info, start);
|
||||
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = INTEGER_I64, .value = value}}, start, start_line);
|
||||
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
|
||||
}
|
||||
char *error_message = (char *)malloc(sizeof(char) * 60);
|
||||
snprintf(error_message, 60, "Invalid decimal literal: unexpected '%c' in decimal integer.", c);
|
||||
return lexer_error(lexer_info, error_message, 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}};
|
||||
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult parse_hexadecimal_integer(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
do {c = advance(lexer_info);} while (isxdigit(c) || c == '_');
|
||||
while (isxdigit(c) || c == '_') c = advance(lexer_info);
|
||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_HEXADECIMAL);
|
||||
if (isspace(c) || c == '/' || c == '\0') {
|
||||
uint64_t value = create_hexadecimal_integer(lexer_info, start);
|
||||
return lexer_result(lexer_info, (Token){TOKEN_INTEGER, .integer_literal = (IntegerLiteral){.type = INTEGER_I64, .value = value}}, start, start_line);
|
||||
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
|
||||
}
|
||||
char *error_message = (char *)malloc(sizeof(char) * 68);
|
||||
snprintf(error_message, 68, "Invalid hexadecimal literal: unexpected '%c' in hexadecimal integer.", c);
|
||||
return lexer_error(lexer_info, error_message, 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}};
|
||||
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult parse_numeric_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
if (c == '-') c = advance(lexer_info);
|
||||
if (c == '0') {
|
||||
c = advance(lexer_info);
|
||||
if (c == 'b' || c == 'B') return parse_binary_integer(lexer_info, c, start, start_line);
|
||||
else if (c == 'o' || c == 'O') return parse_octal_integer(lexer_info, c, start, start_line);
|
||||
else if (c == 'x' || c == 'X') return parse_hexadecimal_integer(lexer_info, c, start, start_line);
|
||||
if (c == 'b' || c == 'B') {
|
||||
c = advance(lexer_info);
|
||||
return parse_binary_integer(lexer_info, c, start, start_line);
|
||||
} else if (c == 'o' || c == 'O') {
|
||||
c = advance(lexer_info);
|
||||
return parse_octal_integer(lexer_info, c, start, start_line);
|
||||
} else if (c == 'x' || c == 'X') {
|
||||
c = advance(lexer_info);
|
||||
return parse_hexadecimal_integer(lexer_info, c, start, start_line);
|
||||
}
|
||||
}
|
||||
return parse_decimal_integer(lexer_info, c, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult parse_character_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
(void)lexer_info; (void)c; (void)start; (void)start_line;
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Character Literals Not Implemented Error.", 1}};
|
||||
if (c == '\'')
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid character literal: empty character literal."), start, start_line);
|
||||
char value = '\0';
|
||||
if (c == '\\') {
|
||||
c = advance(lexer_info);
|
||||
switch (c) {
|
||||
case 'n':
|
||||
value = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
value = '\r';
|
||||
break;
|
||||
case 't':
|
||||
value = '\t';
|
||||
break;
|
||||
case '\\':
|
||||
value = '\\';
|
||||
break;
|
||||
case '\'':
|
||||
value = '\'';
|
||||
break;
|
||||
case '0':
|
||||
value = '\0';
|
||||
break;
|
||||
default:
|
||||
SlsStr error_msg = sls_format(SLS_STR("Invalid character literal: unknown escape sequence '\\%c'."), c);
|
||||
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||
}
|
||||
} else if (c == '\n' || c == '\r')
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid character literal: unclosed character literal."), start, start_line);
|
||||
else value = c;
|
||||
c = advance(lexer_info);
|
||||
if (isspace(c) || c == '/' || c == '\0')
|
||||
return lexer_error(lexer_info, SLS_STR("Invalid character literal: unclosed character literal."), start, start_line);
|
||||
else if (c != '\'') {
|
||||
SlsStr error_msg = sls_format(SLS_STR("Invalid character literal: unexpected '%c' in character."), c);
|
||||
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||
}
|
||||
advance(lexer_info);
|
||||
return lexer_result(lexer_info, (Token){TOKEN_CHARACTER, .character_literal = (uint8_t){value}}, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult parse_string_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
(void)lexer_info; (void)c; (void)start; (void)start_line;
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: String Literals Not Implemented Error.", 1}};
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: String Literals Not Implemented Error."), 1}};
|
||||
}
|
||||
|
||||
static void skip_comments_and_whitespace(LexerInfo *lexer_info) {
|
||||
while (isspace(peek(lexer_info)) || (peek(lexer_info) == '/' && far_peek(lexer_info, 1) == '/') || peek(lexer_info) == '#') {
|
||||
// Skip Comments
|
||||
if ((peek(lexer_info) == '/' && far_peek(lexer_info, 1) == '/') || peek(lexer_info) == '#')
|
||||
while (!(peek(lexer_info) == '\n' || peek(lexer_info) == '\0'))
|
||||
advance(lexer_info);
|
||||
// Skip whitespace
|
||||
while (isspace(peek(lexer_info))) advance(lexer_info);
|
||||
}
|
||||
}
|
||||
|
||||
static LexerResult lexer_next(LexerInfo *lexer_info);
|
||||
|
||||
TokenString copy_token_string(TokenString token_string) {
|
||||
TokenString new_string = (TokenString){
|
||||
.length = token_string.length,
|
||||
.tokens = (Token *)malloc(sizeof(Token) * token_string.length)
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < token_string.length; i++) {
|
||||
if (token_string.tokens[i].type == TOKEN_STRING) {
|
||||
new_string.tokens[i].type = TOKEN_STRING;
|
||||
new_string.tokens[i].string_literal = sls_str_cpy(token_string.tokens[i].string_literal);
|
||||
} else if (token_string.tokens[i].type == TOKEN_TOKEN_STRING) {
|
||||
new_string.tokens[i].type = TOKEN_TOKEN_STRING;
|
||||
new_string.tokens[i].token_string = copy_token_string(token_string.tokens[i].token_string);
|
||||
} else {
|
||||
new_string.tokens[i] = token_string.tokens[i];
|
||||
}
|
||||
}
|
||||
return new_string;
|
||||
}
|
||||
|
||||
static LexerResult convert_to_token_string(LexerInfo *lexer_info, LexerTokenResult *head, size_t start, size_t start_line) {
|
||||
TokenString token_string = {
|
||||
.length = 0,
|
||||
.tokens = NULL
|
||||
};
|
||||
LexerTokenResult *current = head;
|
||||
|
||||
while (current != NULL) {
|
||||
token_string.length += 1;
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
current = head;
|
||||
token_string.tokens = (Token *)malloc(sizeof(Token) * token_string.length);
|
||||
|
||||
for (size_t i = 0; i < token_string.length; i++) {
|
||||
if (current->result.type == TOKEN_STRING) {
|
||||
token_string.tokens[i].type = TOKEN_STRING;
|
||||
token_string.tokens[i].string_literal = sls_str_cpy(current->result.string_literal);
|
||||
} else if (current->result.type == TOKEN_TOKEN_STRING) {
|
||||
token_string.tokens[i].type = TOKEN_TOKEN_STRING;
|
||||
token_string.tokens[i].token_string = copy_token_string(current->result.token_string);
|
||||
memcpy(token_string.tokens[i].token_string.tokens, current->result.token_string.tokens, current->result.token_string.length);
|
||||
} else token_string.tokens[i] = current->result;
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
clean_token_result(head);
|
||||
|
||||
return lexer_result(lexer_info, (Token){TOKEN_TOKEN_STRING, .token_string = token_string}, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult parse_token_string(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
(void)lexer_info; (void)c; (void)start; (void)start_line;
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"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) {
|
||||
(void)lexer_info; (void)c; (void)start; (void)start_line;
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Array Literals Not Implemented Error.", 1}};
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Array Literals Not Implemented Error."), 1}};
|
||||
}
|
||||
|
||||
static LexerResult parse_type_tuples(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
(void)lexer_info; (void)c; (void)start; (void)start_line;
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Type Tuples Not Implemented Error.", 1}};
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Type Tuples Not Implemented Error."), 1}};
|
||||
}
|
||||
|
||||
Boolean is_identifier_continue(LexerInfo *lexer_info, char c) {
|
||||
// If the current character and its context are a valid identifier character
|
||||
if (!isprint(c)) return FALSE;
|
||||
if (c == '/' && far_peek(lexer_info, 1) == '/') return FALSE;
|
||||
if (c == '{' || c == '}') return FALSE;
|
||||
if (c == '[' || c == ']') return FALSE;
|
||||
if (c == '(' || c == ')') return FALSE;
|
||||
if (c == '\'' || c == '"' || c == '#') return FALSE;
|
||||
if (isspace(c) || c == '\0') return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Boolean is_identifier_start(LexerInfo *lexer_info) {
|
||||
// If the current character and its context are a valid identifier start
|
||||
char c = peek(lexer_info);
|
||||
if (c == ':' && far_peek(lexer_info, 1) == ':') c = far_peek(lexer_info, 2);
|
||||
if ((!isdigit(c)) && is_identifier_continue(lexer_info, c)) return TRUE;
|
||||
else return FALSE;
|
||||
}
|
||||
|
||||
static LexerResult parse_identifiers_and_booleans(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
|
||||
(void)lexer_info; (void)c; (void)start; (void)start_line;
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Identifiers and Booleans Not Implemented Error.", 1}};
|
||||
// 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++;
|
||||
}
|
||||
char *name_value = (char *)calloc(length+1, sizeof(char));
|
||||
for (size_t i = 0; i < length; i++)
|
||||
name_value[i] = lexer_info->source_code.str[start + i + (2 * literal)];
|
||||
SlsStr name = sls_str_malloc(name_value, length);
|
||||
free(name_value);
|
||||
|
||||
// Return as identifier or boolean tokens
|
||||
if (sls_str_cmp(name, SLS_STR("false")) == 0)
|
||||
return lexer_result(lexer_info, (Token){TOKEN_BOOLEAN, .boolean_literal = FALSE}, start, start_line);
|
||||
else if (sls_str_cmp(name, SLS_STR("true")) == 0)
|
||||
return lexer_result(lexer_info, (Token){TOKEN_BOOLEAN, .boolean_literal = TRUE}, start, start_line);
|
||||
else
|
||||
return lexer_result(lexer_info, (Token){TOKEN_IDENTIFIER, .identifier = (Identifier){.is_literal = literal, .name = name}}, start, start_line);
|
||||
}
|
||||
|
||||
static LexerResult lexer_next(LexerInfo *lexer_info) {
|
||||
// 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);
|
||||
|
|
@ -536,32 +863,75 @@ static LexerResult lexer_next(LexerInfo *lexer_info) {
|
|||
// End of file tokens
|
||||
if (c == '\0') return lexer_result(lexer_info, (Token){.type = TOKEN_EOF}, start, start_line);
|
||||
// Integers and Floats
|
||||
if (isdigit(c) || c == '.' || (c == '-' && isdigit(far_peek(lexer_info, 1)))) return parse_numeric_literal(lexer_info, c, start, start_line);
|
||||
if (isdigit(c) || (c == '.' && isdigit(far_peek(lexer_info, 1))) || (c == '-' && isdigit(far_peek(lexer_info, 1))))
|
||||
return parse_numeric_literal(lexer_info, c, start, start_line);
|
||||
// Character Literals
|
||||
if (c == '\'') return parse_character_literal(lexer_info, c, start, start_line);
|
||||
if (c == '\'') {
|
||||
c = advance(lexer_info);
|
||||
return parse_character_literal(lexer_info, c, start, start_line);
|
||||
}
|
||||
// String Literals
|
||||
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 (isascii(c)) return parse_identifiers_and_booleans(lexer_info, c, start, start_line);
|
||||
// Lexing Error
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Lexer: Unknown Character Error.", 1}};
|
||||
if (is_identifier_start(lexer_info))
|
||||
return parse_identifiers_and_booleans(lexer_info, c, start, start_line);
|
||||
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->type == SLS_ERROR) free(head->error.message);
|
||||
if (head) free(head);
|
||||
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->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);
|
||||
}
|
||||
|
||||
LexerTokenResult *get_token(LexerTokenResult *head, size_t i) {
|
||||
|
|
@ -600,7 +970,7 @@ LexerResult lexical_analysis(LexerInfo *lexer_info) {
|
|||
// Current should not be null_ptr
|
||||
if (current == 0) {
|
||||
clean_token_result(head);
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){"Unknown Error.", 1}};
|
||||
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Unknown Error."), 1}};
|
||||
}
|
||||
|
||||
} while (current->type != SLS_ERROR && current->result.type != TOKEN_EOF);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,37 @@
|
|||
// October 2025
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(void) {
|
||||
printf("Hello, world!\n");
|
||||
#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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -4,14 +4,328 @@
|
|||
// November 2025
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int isascii(unsigned char c) {
|
||||
#include "sls/string.h"
|
||||
#include "sls/lexer.h"
|
||||
|
||||
int sls_isascii(unsigned char c) {
|
||||
return c < 128;
|
||||
}
|
||||
|
||||
size_t strnlen(const char *s, size_t maxlen) {
|
||||
size_t sls_str_nlen(const char *s, size_t maxlen) {
|
||||
size_t i;
|
||||
for (i = 0; i < maxlen; i++)
|
||||
if (s[i] == '\0') break;
|
||||
return i;
|
||||
}
|
||||
|
||||
SlsStr sls_str_malloc(const char *s, size_t maxlen) {
|
||||
size_t length = sls_str_nlen(s, maxlen);
|
||||
char *new_str = (char *)malloc(sizeof(char) * (length + 1));
|
||||
if (new_str == NULL) return SLS_STR_NULL;
|
||||
memcpy(new_str, s, length);
|
||||
new_str[length] = '\0';
|
||||
return (SlsStr){length, new_str, TRUE};
|
||||
}
|
||||
|
||||
SlsStr sls_str_new(size_t length) {
|
||||
char *new_str = (char *)calloc(length + 1, sizeof(char));
|
||||
if (new_str == NULL) return SLS_STR_NULL;
|
||||
return (SlsStr){length, new_str, TRUE};
|
||||
}
|
||||
|
||||
SlsStr sls_str_cpy(const SlsStr s) {
|
||||
return sls_str_malloc(s.str, s.len);
|
||||
}
|
||||
|
||||
int32_t sls_str_cmp(const SlsStr a, const SlsStr b) {
|
||||
int cmp = strncmp(a.str, b.str, (a.len < b.len) ? a.len : b.len);
|
||||
if (cmp != 0) return cmp;
|
||||
return (a.len > b.len) - (a.len < b.len);
|
||||
}
|
||||
|
||||
void sls_str_free(SlsStr *s) {
|
||||
if (s->allocated) {
|
||||
free((void *)s->str);
|
||||
s->len = 0;
|
||||
s->str = NULL;
|
||||
s->allocated = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
FORMAT_PERCENT_ESCAPE,
|
||||
FORMAT_C_STRINGS,
|
||||
FORMAT_CHARACTER,
|
||||
FORMAT_INTEGER_32,
|
||||
FORMAT_INTEGER_64,
|
||||
FORMAT_UNSIGNED_INTEGER_64,
|
||||
FORMAT_SIZE_INTEGER,
|
||||
FORMAT_FLOAT,
|
||||
FORMAT_SLS_STR,
|
||||
FORMAT_SLS_TOKEN_TYPE,
|
||||
FORMAT_SLS_ARRAY_TYPE,
|
||||
FORMAT_SLS_BUILTIN_INTEGER,
|
||||
FORMAT_SLS_ERROR,
|
||||
FORMAT_SLS_BOOLEAN,
|
||||
} FormatStringTypes;
|
||||
|
||||
typedef struct {
|
||||
FormatStringTypes type;
|
||||
union {
|
||||
const char *c_string;
|
||||
char character;
|
||||
int32_t integer_32;
|
||||
int64_t integer_64;
|
||||
uint64_t unsigned_integer_64;
|
||||
size_t size_integer;
|
||||
double ffloat;
|
||||
SlsStr sls_str;
|
||||
TokenType token_type;
|
||||
ArrayType array_type;
|
||||
IntegerBuiltInType builtin_integer;
|
||||
SlsError error;
|
||||
Boolean boolean;
|
||||
};
|
||||
ptrdiff_t str_index;
|
||||
size_t self_length;
|
||||
} FormatStringItem;
|
||||
|
||||
static size_t number_length(int64_t i) {
|
||||
if (i == 0) return 1;
|
||||
size_t len = (i < 0 ? 1 : 0);
|
||||
while (i) { len++; i /= 10; }
|
||||
return len;
|
||||
}
|
||||
|
||||
static size_t unsigned_number_length(uint64_t i) {
|
||||
if (i == 0) return 1;
|
||||
size_t len = 0;
|
||||
while (i) { len++; i /= 10; }
|
||||
return len;
|
||||
}
|
||||
|
||||
SlsStr sls_format(const SlsStr s, ...) {
|
||||
va_list args;
|
||||
va_start(args, s);
|
||||
size_t count = 0;
|
||||
const char *current = strchr(s.str, '%');
|
||||
do {
|
||||
if (!current) break;
|
||||
if (!current[1]) break;
|
||||
switch (current[1]) {
|
||||
case '%':
|
||||
case 'y':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'l':
|
||||
case 'u':
|
||||
case 'z':
|
||||
case 'f':
|
||||
case 's':
|
||||
case 't':
|
||||
case 'a':
|
||||
case 'i':
|
||||
case 'e':
|
||||
case 'b':
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
current = strchr(current + 2, '%');
|
||||
} while (current);
|
||||
|
||||
FormatStringItem *items = (FormatStringItem *)malloc(sizeof(FormatStringItem) * count);
|
||||
if (items == NULL) return SLS_STR_NULL;
|
||||
size_t i = 0;
|
||||
const char *last_index = s.str;
|
||||
size_t length = s.len;
|
||||
current = strchr(s.str, '%');
|
||||
do {
|
||||
switch (current[1]) {
|
||||
case '%':
|
||||
items[i].type = FORMAT_PERCENT_ESCAPE;
|
||||
length += items[i].self_length = 1;
|
||||
length -= 2;
|
||||
break;
|
||||
case 'y':
|
||||
items[i].type = FORMAT_C_STRINGS;
|
||||
items[i].c_string = va_arg(args, const char *);
|
||||
length += items[i].self_length = strlen(items[i].c_string);
|
||||
length -= 2;
|
||||
break;
|
||||
case 'c':
|
||||
items[i].type = FORMAT_CHARACTER;
|
||||
items[i].character = va_arg(args, int);
|
||||
length += items[i].self_length = 1;
|
||||
length -= 2;
|
||||
break;
|
||||
case 'd':
|
||||
items[i].type = FORMAT_INTEGER_32;
|
||||
items[i].integer_32 = va_arg(args, int32_t);
|
||||
length += items[i].self_length = number_length(items[i].integer_32);
|
||||
length -= 2;
|
||||
break;
|
||||
case 'l':
|
||||
items[i].type = FORMAT_INTEGER_64;
|
||||
items[i].integer_64 = va_arg(args, int64_t);
|
||||
length += items[i].self_length = number_length(items[i].integer_64);
|
||||
length -= 2;
|
||||
break;
|
||||
case 'u':
|
||||
items[i].type = FORMAT_UNSIGNED_INTEGER_64;
|
||||
items[i].unsigned_integer_64 = va_arg(args, uint64_t);
|
||||
length += items[i].self_length = unsigned_number_length(items[i].unsigned_integer_64);
|
||||
length -= 2;
|
||||
break;
|
||||
case 'z':
|
||||
items[i].type = FORMAT_SIZE_INTEGER;
|
||||
items[i].size_integer = va_arg(args, size_t);
|
||||
length += items[i].self_length = unsigned_number_length(items[i].size_integer);
|
||||
length -= 2;
|
||||
break;
|
||||
case 'f':
|
||||
items[i].type = FORMAT_FLOAT;
|
||||
items[i].ffloat = va_arg(args, double);
|
||||
length += items[i].self_length = snprintf(NULL, 0, "%.2f", items[i].ffloat);
|
||||
length -= 2;
|
||||
break;
|
||||
case 's':
|
||||
items[i].type = FORMAT_SLS_STR;
|
||||
items[i].sls_str = va_arg(args, SlsStr);
|
||||
length += items[i].self_length = items[i].sls_str.len;
|
||||
length -= 2;
|
||||
break;
|
||||
case 't':
|
||||
items[i].type = FORMAT_SLS_TOKEN_TYPE;
|
||||
items[i].token_type = va_arg(args, TokenType);
|
||||
if (items[i].token_type >= TOKEN_TYPE_COUNT) {
|
||||
free(items);
|
||||
return SLS_STR_NULL;
|
||||
}
|
||||
length += items[i].self_length = sls_str_nlen(TOKEN_TYPES_NAMES[items[i].token_type], TYPE_NAMES_SAFE_LENGTH);
|
||||
length -= 2;
|
||||
break;
|
||||
case 'a':
|
||||
items[i].type = FORMAT_SLS_ARRAY_TYPE;
|
||||
items[i].array_type = va_arg(args, ArrayType);
|
||||
if (items[i].array_type >= ARRAY_TYPE_COUNT) {
|
||||
free(items);
|
||||
return SLS_STR_NULL;
|
||||
}
|
||||
length += items[i].self_length = sls_str_nlen(ARRAY_TYPES_NAMES[items[i].array_type], TYPE_NAMES_SAFE_LENGTH);
|
||||
length -= 2;
|
||||
break;
|
||||
case 'i':
|
||||
items[i].type = FORMAT_SLS_BUILTIN_INTEGER;
|
||||
items[i].builtin_integer = va_arg(args, IntegerBuiltInType);
|
||||
if (items[i].builtin_integer >= INTEGER_TYPE_COUNT) {
|
||||
free(items);
|
||||
return SLS_STR_NULL;
|
||||
}
|
||||
length += items[i].self_length = sls_str_nlen(INTEGER_TYPES_NAMES[items[i].builtin_integer], TYPE_NAMES_SAFE_LENGTH);
|
||||
length -= 2;
|
||||
break;
|
||||
case 'e':
|
||||
items[i].type = FORMAT_SLS_ERROR;
|
||||
items[i].error = va_arg(args, SlsError);
|
||||
length += items[i].self_length = items[i].error.message.len;
|
||||
length -= 2;
|
||||
break;
|
||||
case 'b':
|
||||
items[i].type = FORMAT_SLS_BOOLEAN;
|
||||
items[i].boolean = va_arg(args, Boolean);
|
||||
length += items[i].self_length = (items[i].boolean ? 4 : 5);
|
||||
length -= 2;
|
||||
break;
|
||||
}
|
||||
items[i].str_index = (ptrdiff_t)(current - last_index);
|
||||
last_index = current + 2;
|
||||
i++;
|
||||
current = strchr(current + 2, '%');
|
||||
} while (current);
|
||||
va_end(args);
|
||||
|
||||
char *temp = (char *)malloc(sizeof(char) * length);
|
||||
if (temp == NULL) {
|
||||
free(items);
|
||||
return SLS_STR_NULL;
|
||||
}
|
||||
SlsStr str_new = sls_str_new(length);
|
||||
if (str_new.str == NULL) {
|
||||
free(items);
|
||||
free(temp);
|
||||
return SLS_STR_NULL;
|
||||
}
|
||||
char *str = (char *)str_new.str;
|
||||
size_t item_i = 0;
|
||||
ptrdiff_t target_i = 0;
|
||||
ptrdiff_t source_i = 0;
|
||||
|
||||
while (item_i < count) {
|
||||
memcpy(str + target_i, s.str + source_i, items[item_i].str_index);
|
||||
target_i += items[item_i].str_index;
|
||||
source_i += items[item_i].str_index + 2;
|
||||
|
||||
switch (items[item_i].type) {
|
||||
case FORMAT_PERCENT_ESCAPE:
|
||||
memcpy(temp, "%", items[item_i].self_length + 1);
|
||||
break;
|
||||
case FORMAT_C_STRINGS:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].c_string);
|
||||
break;
|
||||
case FORMAT_CHARACTER:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%c", items[item_i].character);
|
||||
break;
|
||||
case FORMAT_INTEGER_32:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%d", items[item_i].integer_32);
|
||||
break;
|
||||
case FORMAT_INTEGER_64:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%ld", items[item_i].integer_64);
|
||||
break;
|
||||
case FORMAT_UNSIGNED_INTEGER_64:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%lu", items[item_i].unsigned_integer_64);
|
||||
break;
|
||||
case FORMAT_SIZE_INTEGER:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%zu", items[item_i].size_integer);
|
||||
break;
|
||||
case FORMAT_FLOAT:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%.2f", items[item_i].ffloat); // Fixed-point decimal display
|
||||
break;
|
||||
case FORMAT_SLS_STR:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].sls_str.str);
|
||||
break;
|
||||
case FORMAT_SLS_TOKEN_TYPE:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%s", TOKEN_TYPES_NAMES[items[item_i].token_type]);
|
||||
break;
|
||||
case FORMAT_SLS_ARRAY_TYPE:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%s", ARRAY_TYPES_NAMES[items[item_i].array_type]);
|
||||
break;
|
||||
case FORMAT_SLS_BUILTIN_INTEGER:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%s", INTEGER_TYPES_NAMES[items[item_i].builtin_integer]);
|
||||
break;
|
||||
case FORMAT_SLS_ERROR:
|
||||
snprintf(temp, items[item_i].self_length + 1, "%s", items[item_i].error.message.str);
|
||||
break;
|
||||
case FORMAT_SLS_BOOLEAN:
|
||||
memcpy(temp, (items[item_i].boolean ? "TRUE" : "FALSE"), items[item_i].self_length + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(str + target_i, temp, items[item_i].self_length);
|
||||
target_i += items[item_i].self_length;
|
||||
item_i++;
|
||||
}
|
||||
if (s.len > (size_t)source_i)
|
||||
memcpy(str + target_i, s.str + source_i, s.len - source_i);
|
||||
str[str_new.len] = '\0';
|
||||
|
||||
free(items);
|
||||
free(temp);
|
||||
return str_new;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
// Kyler Olsen
|
||||
// YREA SLS
|
||||
// Extra Tests
|
||||
// November 2025
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "sls/string.h"
|
||||
#include "sls/lexer.h"
|
||||
#include "sls/errors.h"
|
||||
#include "tests/lexer_test_helpers.h"
|
||||
#include "tests/tests.h"
|
||||
|
||||
static const size_t NUM_EXTRA_TESTS = 2;
|
||||
|
||||
static TestResult test_Identifier_Addition() {
|
||||
LexerTest test = start_up_test(SLS_STR("Identifier Addition"), SLS_STR("+"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
if (test_identifier_value(&test, result, i++, &(TestIdentifierValue){FALSE, SLS_STR("+")})) return test.result;
|
||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Identifier_Division() {
|
||||
LexerTest test = start_up_test(SLS_STR("Identifier Division"), SLS_STR("/"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
if (test_identifier_value(&test, result, i++, &(TestIdentifierValue){FALSE, SLS_STR("/")})) return test.result;
|
||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
// Run all extra tests
|
||||
TestsReport run_extra_tests()
|
||||
{
|
||||
TestsReport report = {
|
||||
.section = SLS_STR("extra_tests"),
|
||||
.count = NUM_EXTRA_TESTS,
|
||||
.tests = malloc(sizeof(TestResult) * NUM_EXTRA_TESTS)};
|
||||
|
||||
size_t i = 0;
|
||||
(void)i;
|
||||
report.tests[i++] = test_Identifier_Addition();
|
||||
report.tests[i++] = test_Identifier_Division();
|
||||
return report;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -9,17 +9,18 @@
|
|||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "sls/sls_errors.h"
|
||||
#include "sls/errors.h"
|
||||
#include "sls/bool.h"
|
||||
#include "sls/lexer.h"
|
||||
#include "sls/string.h"
|
||||
#include "tests/lexer_test_helpers.h"
|
||||
#include "tests/tests.h"
|
||||
|
||||
static const double FLOAT_TEST_PRECISION = 0.01;
|
||||
static const double FLOAT_TEST_PRECISION = 0.0078125;
|
||||
|
||||
// Test start and end helpers
|
||||
|
||||
LexerTest start_up_test(const char *test_name, const char *test_code) {
|
||||
LexerTest start_up_test(SlsStr test_name, SlsStr test_code) {
|
||||
LexerTest test = (LexerTest) {
|
||||
.result = (TestResult) {
|
||||
.name = test_name, .status = TEST_NOT_IMPLEMENTED } };
|
||||
|
|
@ -32,45 +33,63 @@ void clean_up_test(LexerResult result) {
|
|||
clean_token_result(result.result);
|
||||
}
|
||||
|
||||
TestResult error_test(LexerTest *test, LexerResult result, SlsError error) {
|
||||
TestResult error_test_out_of_mem(LexerTest *test) {
|
||||
test->result.status = TEST_ERROR;
|
||||
test->result.error = error;
|
||||
clean_up_test(result);
|
||||
test->result.error = (SlsError){SLS_STR("Out Of Memory Error."), 1};
|
||||
return test->result;
|
||||
}
|
||||
|
||||
TestResult logic_fail_test(LexerTest *test, LexerResult result, char *message) {
|
||||
if (message == 0) return error_test(test, result, (SlsError) {
|
||||
.message = "Out of Memory Error!", .code = 1, });
|
||||
TestResult error_test(LexerTest *test, LexerResult result, SlsError error) {
|
||||
clean_up_test(result);
|
||||
|
||||
if (error.message.str == NULL) return error_test_out_of_mem(test);
|
||||
|
||||
test->result.status = TEST_ERROR;
|
||||
test->result.error = error;
|
||||
test->result.error.message = sls_str_cpy(error.message);
|
||||
|
||||
if (test->result.error.message.str == NULL) return error_test_out_of_mem(test);
|
||||
return test->result;
|
||||
}
|
||||
|
||||
TestResult logic_fail_test(LexerTest *test, LexerResult result, SlsStr message) {
|
||||
clean_up_test(result);
|
||||
|
||||
test->result.status = TEST_LOGIC_FAIL;
|
||||
test->result.message = message;
|
||||
clean_up_test(result);
|
||||
return test->result;
|
||||
}
|
||||
|
||||
TestResult logic_error_fail_test(LexerTest *test, LexerResult result, SlsError error) {
|
||||
if (error.message.str == NULL) return error_test_out_of_mem(test);
|
||||
|
||||
test->result.status = TEST_LOGIC_ERROR_FAIL;
|
||||
test->result.error = error;
|
||||
|
||||
size_t message_length = strlen(error.message) + 1;
|
||||
const char *message = (char *)malloc(sizeof(char) * message_length);
|
||||
strncpy(message, error.message, message_length);
|
||||
test->result.error.message = message;
|
||||
test->result.error.message = sls_str_cpy(error.message);
|
||||
|
||||
clean_up_test(result);
|
||||
|
||||
if (test->result.error.message.str == NULL) return error_test_out_of_mem(test);
|
||||
return test->result;
|
||||
}
|
||||
|
||||
TestResult error_fail_test(LexerTest *test, LexerResult result, SlsError error) {
|
||||
clean_up_test(result);
|
||||
|
||||
if (error.message.str == NULL) return error_test_out_of_mem(test);
|
||||
|
||||
test->result.status = TEST_ERROR_FAIL;
|
||||
test->result.error = error;
|
||||
clean_up_test(result);
|
||||
test->result.error.message = sls_str_cpy(error.message);
|
||||
|
||||
if (test->result.error.message.str == NULL) return error_test_out_of_mem(test);
|
||||
return test->result;
|
||||
}
|
||||
|
||||
TestResult skip_test(LexerTest *test, LexerResult result) {
|
||||
test->result.status = TEST_NOT_IMPLEMENTED;
|
||||
clean_up_test(result);
|
||||
|
||||
test->result.status = TEST_NOT_IMPLEMENTED;
|
||||
return test->result;
|
||||
}
|
||||
|
||||
|
|
@ -80,198 +99,120 @@ TestResult skip_test_no_result(LexerTest *test) {
|
|||
}
|
||||
|
||||
TestResult pass_test(LexerTest *test, LexerResult result) {
|
||||
test->result.status = TEST_PASS;
|
||||
clean_up_test(result);
|
||||
|
||||
test->result.status = TEST_PASS;
|
||||
return test->result;
|
||||
}
|
||||
|
||||
// Test messages
|
||||
|
||||
static char *unexpected_end_of_token_stream(size_t i) {
|
||||
size_t length = ceil(log10(i)) + 47;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Unexpected end of token stream (%zu tokens found)", i - 1);
|
||||
return string;
|
||||
static SlsStr unexpected_end_of_token_stream(size_t i) {
|
||||
return sls_format(SLS_STR("Unexpected end of token stream (%z tokens found)"), i - 1);
|
||||
}
|
||||
|
||||
static char *expected_end_of_token_stream(size_t i) {
|
||||
size_t length = ceil(log10(i)) + 47;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Expected end of token stream (more than %zu tokens found)", i - 1);
|
||||
return string;
|
||||
static SlsStr expected_end_of_token_stream(size_t i) {
|
||||
return sls_format(SLS_STR("Expected end of token stream (more than %z tokens found)"), i - 1);
|
||||
}
|
||||
|
||||
static char *token_should_be(size_t i, TokenType should, TokenType found) {
|
||||
size_t length = ceil(log10(i + 1)) + strnlen(TOKEN_TYPES_NAMES[should], TYPE_NAMES_SAFE_LENGTH) + strnlen(TOKEN_TYPES_NAMES[found], TYPE_NAMES_SAFE_LENGTH) + 35;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu should be a %s, but found a %s", i, TOKEN_TYPES_NAMES[should], TOKEN_TYPES_NAMES[found]);
|
||||
return string;
|
||||
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 char *integer_type_should_be(size_t i, IntegerBuiltInType should, IntegerBuiltInType found) {
|
||||
size_t length = ceil(log10(i + 1)) + strnlen(INTEGER_TYPES_NAMES[should], 5) + strnlen(INTEGER_TYPES_NAMES[found], 5) + 48;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu integer type should be a %s, but found a %s", i, TOKEN_TYPES_NAMES[should], TOKEN_TYPES_NAMES[found]);
|
||||
return string;
|
||||
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 char *integer_value_should_be(size_t i, uint64_t should, uint64_t found) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(should + 1)) + ceil(log10(found + 1)) + 45;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu integer value should be %lu, but found %lu", i, should, found);
|
||||
return string;
|
||||
static 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 %u, but found %u"), i, should, found);
|
||||
}
|
||||
|
||||
static char *float_value_should_be(size_t i, double should, double found) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(should + 1) + 3) + ceil(log10(found + 1) + 3) + 43;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu float value should be %.2f, but found %.2f", i, should, found);
|
||||
return string;
|
||||
static 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);
|
||||
}
|
||||
|
||||
static char *identifier_should_be_literal(size_t i) {
|
||||
size_t length = ceil(log10(i + 1)) + 51;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu identifier should be an identifier literal", i);
|
||||
return string;
|
||||
static SlsStr float_value_should_be(size_t i, double should, double found) {
|
||||
return sls_format(SLS_STR("Token #%z float value should be %f, but found %f"), i, should, found);
|
||||
}
|
||||
|
||||
static char *identifier_should_not_be_literal(size_t i) {
|
||||
size_t length = ceil(log10(i + 1)) + 55;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu identifier should not be an identifier literal", i);
|
||||
return string;
|
||||
static SlsStr identifier_should_be_literal(size_t i) {
|
||||
return sls_format(SLS_STR("Token #%z identifier should be an identifier literal"), i);
|
||||
}
|
||||
|
||||
static char *token_length_should_be(size_t i, TokenType type, uint64_t should, uint64_t found) {
|
||||
size_t length = ceil(log10(i + 1)) + strnlen(TOKEN_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + ceil(log10(should + 1)) + ceil(log10(found + 1)) + 47;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu of type %s length should be %lu, but found %lu", i, TOKEN_TYPES_NAMES[type], should, found);
|
||||
return string;
|
||||
static SlsStr identifier_should_not_be_literal(size_t i) {
|
||||
return sls_format(SLS_STR("Token #%z identifier should not be an identifier literal"), i);
|
||||
}
|
||||
|
||||
static char *token_value_string_should_be(size_t i, TokenType type, size_t value_length, const char *should, const char *found) {
|
||||
size_t length = ceil(log10(i + 1)) + strnlen(TOKEN_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + strnlen(should, value_length) + strnlen(found, value_length) + 53;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu of type %s string value should be %s, but found %s", i, TOKEN_TYPES_NAMES[type], should, found);
|
||||
return string;
|
||||
static SlsStr token_length_should_be(size_t i, TokenType type, uint64_t should, uint64_t found) {
|
||||
return sls_format(SLS_STR("Token #%z of type %t length should be %u, but found %u"), i, type, should, found);
|
||||
}
|
||||
|
||||
static char *boolean_should_be(size_t i, Boolean value) {
|
||||
size_t length = ceil(log10(i + 1)) + 45;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
if (value) snprintf(string, length, "Token #%zu boolean should be true, but is false", i);
|
||||
else snprintf(string, length, "Token #%zu boolean should be false, but is true", i);
|
||||
return string;
|
||||
static SlsStr token_value_string_should_be(size_t i, TokenType type, SlsStr should, SlsStr found) {
|
||||
return sls_format(SLS_STR("Token #%z of type %t string value should be %s, but found %s"), i, type, should, found);
|
||||
}
|
||||
|
||||
static char *array_type_should_be(size_t i, ArrayType should, ArrayType found) {
|
||||
size_t length = ceil(log10(i + 1)) + strnlen(ARRAY_TYPES_NAMES[should], TYPE_NAMES_SAFE_LENGTH) + strnlen(ARRAY_TYPES_NAMES[found], TYPE_NAMES_SAFE_LENGTH) + 35;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu should be a %s, but found a %s", i, ARRAY_TYPES_NAMES[should], ARRAY_TYPES_NAMES[found]);
|
||||
return string;
|
||||
static SlsStr boolean_should_be(size_t i, Boolean value) {
|
||||
if (value) return sls_format(SLS_STR("Token #%z boolean should be true, but is false"), i);
|
||||
else return sls_format(SLS_STR("Token #%z boolean should be false, but is true"), i);
|
||||
}
|
||||
|
||||
static char *array_dimensions_should_be(size_t i, size_t should, size_t found) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(should + 1)) + ceil(log10(found + 1)) + 48;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu array dimensions should be %zu, but found %zu", i, should, found);
|
||||
return string;
|
||||
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);
|
||||
}
|
||||
|
||||
static char *array_element_shape_should_be(size_t i, size_t j, ArrayType type, uint64_t should, uint64_t found) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + ceil(log10(should + 1) + 3) + ceil(log10(found + 1) + 3) + 63;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu dimension %zu of array type %s should be shape %lu, but found %lu", i, j, TOKEN_TYPES_NAMES[type], should, found);
|
||||
return string;
|
||||
static SlsStr array_dimensions_should_be(size_t i, size_t should, size_t found) {
|
||||
return sls_format(SLS_STR("Token #%z array dimensions should be %z, but found %z"), i, should, found);
|
||||
}
|
||||
|
||||
static char *array_element_integer_should_be(size_t i, size_t j, ArrayType type, uint64_t should, uint64_t found) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + ceil(log10(should + 1) + 3) + ceil(log10(found + 1) + 3) + 55;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu element %zu of array type %s should be %lu, but found %lu", i, j, TOKEN_TYPES_NAMES[type], should, found);
|
||||
return string;
|
||||
static SlsStr array_dimension_shape_should_be(size_t i, size_t j, ArrayType type, uint64_t should, uint64_t found) {
|
||||
return sls_format(SLS_STR("Token #%z dimension %z of array type %a should be shape %u, but found %u"), i, j, type, should, found);
|
||||
}
|
||||
|
||||
static char *array_element_float_should_be(size_t i, size_t j, ArrayType type, double should, double found) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + ceil(log10(should + 1)) + ceil(log10(found + 1)) + 55;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu element %zu of array type %s should be %.2f, but found %.2f", i, j, TOKEN_TYPES_NAMES[type], should, found);
|
||||
return string;
|
||||
static SlsStr array_element_integer_should_be(size_t i, size_t j, ArrayType type, uint64_t should, uint64_t found) {
|
||||
return sls_format(SLS_STR("Token #%z element %z of array type %a should be %u, but found %u"), i, j, type, should, found);
|
||||
}
|
||||
|
||||
static char *array_element_string_should_be(size_t i, size_t j, ArrayType type, size_t value_length, const char *should, const char *found) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + strnlen(should, value_length) + strnlen(found, value_length) + 55;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu element %zu of array type %s should be %s, but found %s", i, j, TOKEN_TYPES_NAMES[type], should, found);
|
||||
return string;
|
||||
static SlsStr array_element_float_should_be(size_t i, size_t j, ArrayType type, double should, double found) {
|
||||
return sls_format(SLS_STR("Token #%z element %z of array type %a should be %f, but found %f"), i, j, type, should, found);
|
||||
}
|
||||
|
||||
static char *array_element_boolean_should_be(size_t i, size_t j, ArrayType type, Boolean value) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(ARRAY_TYPES_NAMES[type], TYPE_NAMES_SAFE_LENGTH) + 64;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
if (value) snprintf(string, length, "Token #%zu element %zu of array type %s should be true, but found false", i, j, TOKEN_TYPES_NAMES[type]);
|
||||
else snprintf(string, length, "Token #%zu element %zu of array type %s should be false, but found true", i, j, TOKEN_TYPES_NAMES[type]);
|
||||
return string;
|
||||
static SlsStr array_element_string_should_be(size_t i, size_t j, ArrayType type, SlsStr should, SlsStr found) {
|
||||
return sls_format(SLS_STR("Token #%z element %z of array type %a should be %s, but found %s"), i, j, type, should, found);
|
||||
}
|
||||
|
||||
static char *type_tuple_element_integer_should_be(size_t i, size_t j, uint64_t should, uint64_t found) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + ceil(log10(should + 1) + 3) + ceil(log10(found + 1) + 3) + 54;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu element %zu of type tuple should be %lu, but found %lu", i, j, should, found);
|
||||
return string;
|
||||
static SlsStr array_element_boolean_should_be(size_t i, size_t j, ArrayType type, Boolean value) {
|
||||
if (value) return sls_format(SLS_STR("Token #%z element %z of array type %a should be true, but is false"), i, j, type);
|
||||
else return sls_format(SLS_STR("Token #%z element %z of array type %a should be false, but is true"), i, j, type);
|
||||
}
|
||||
|
||||
static char *type_tuple_element_string_should_be(size_t i, size_t j, size_t value_length, const char *should, const char *found) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + strnlen(should, value_length) + strnlen(found, value_length) + 54;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu element %zu of type tuple should be %s, but found %s", i, j, should, found);
|
||||
return string;
|
||||
static SlsStr type_tuple_element_integer_should_be(size_t i, size_t j, uint64_t should, uint64_t found) {
|
||||
return sls_format(SLS_STR("Token #%z element %u of type tuple should be %u, but found %u"), i, j, should, found);
|
||||
}
|
||||
|
||||
static char *type_tuple_element_boolean_should_be(size_t i, size_t j, Boolean value) {
|
||||
size_t length = ceil(log10(i + 1)) + ceil(log10(j + 1)) + 63;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
if (value) snprintf(string, length, "Token #%zu element %zu of type tuple should be true, but found false", i, j);
|
||||
else snprintf(string, length, "Token #%zu element %zu of type tuple should be false, but found true", i, j);
|
||||
return string;
|
||||
static SlsStr type_tuple_element_string_should_be(size_t i, size_t j, SlsStr should, SlsStr found) {
|
||||
return sls_format(SLS_STR("Token #%z element %z of type tuple should be %s, but found %s"), i, j, should, found);
|
||||
}
|
||||
|
||||
static char *token_should_be_error(size_t i, TestErrorMessage should, TokenType found) {
|
||||
size_t length = ceil(log10(i + 1)) + should.length + strnlen(TOKEN_TYPES_NAMES[found], TYPE_NAMES_SAFE_LENGTH) + 72;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu should be an error with a message of %s, but found token of type %s", i, should.message, TOKEN_TYPES_NAMES[found]);
|
||||
return string;
|
||||
static SlsStr type_tuple_element_boolean_should_be(size_t i, size_t j, Boolean value) {
|
||||
if (value) return sls_format(SLS_STR("Token #%z element %z of type tuple should be true, but is false"), i, j);
|
||||
else return sls_format(SLS_STR("Token #%z element %z of type tuple should be false, but is true"), i, j);
|
||||
}
|
||||
|
||||
static char *error_should_be(size_t i, TestErrorMessage should, SlsError found) {
|
||||
size_t length = ceil(log10(i + 1)) + should.length + strlen(found.message) + 77;
|
||||
char *string = (char *)malloc(sizeof(char) * length);
|
||||
if (string == 0) return string;
|
||||
snprintf(string, length, "Token #%zu should be an error with a message of %s, but found error with message %s", i, should.message, found.message);
|
||||
return string;
|
||||
static SlsStr token_should_be_error(size_t i, SlsStr should, TokenType found) {
|
||||
return sls_format(SLS_STR("Token #%z should be an error with a message of \"%s\", but found token of type %t"), i, should, found);
|
||||
}
|
||||
|
||||
static SlsStr error_should_be(size_t i, SlsStr should, SlsError found) {
|
||||
return sls_format(SLS_STR("Token #%z should be an error with a message of \"%s\", but found error with message \"%e\""), i, should, found);
|
||||
}
|
||||
|
||||
// Test parts
|
||||
|
|
@ -304,7 +245,7 @@ static Boolean test_array_type(LexerTest *test, LexerResult result, size_t i, Ar
|
|||
}
|
||||
for (size_t j = 0; j < dimensions; j++) {
|
||||
if (head->result.array_literal.shape[j] != shape[j]) {
|
||||
logic_fail_test(test, result, array_element_shape_should_be(i + 1, j, array_type, shape[j], head->result.array_literal.shape[j]));
|
||||
logic_fail_test(test, result, array_dimension_shape_should_be(i + 1, j, array_type, shape[j], head->result.array_literal.shape[j]));
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
|
@ -332,11 +273,11 @@ Boolean test_identifier_value(LexerTest *test, LexerResult result, size_t i, Tes
|
|||
} if (head->result.identifier.is_literal != value->is_literal) {
|
||||
logic_fail_test(test, result, value->is_literal ? identifier_should_be_literal(i + 1) : identifier_should_not_be_literal(i + 1));
|
||||
return TRUE;
|
||||
} if (head->result.identifier.length == strnlen(value->name, value->length)) {
|
||||
logic_fail_test(test, result, token_length_should_be(i + 1, token_type, strnlen(value->name, value->length), head->result.identifier.length));
|
||||
} if (head->result.identifier.name.len != value->name.len) {
|
||||
logic_fail_test(test, result, token_length_should_be(i + 1, token_type, value->name.len, head->result.identifier.name.len));
|
||||
return TRUE;
|
||||
} if (strncmp(head->result.identifier.name, value->name, value->length) != 0) {
|
||||
logic_fail_test(test, result, token_value_string_should_be(i + 1, token_type, strnlen(value->name, value->length), head->result.identifier.name, value->name));
|
||||
} if (sls_str_cmp(head->result.identifier.name, value->name) != 0) {
|
||||
logic_fail_test(test, result, token_value_string_should_be(i + 1, token_type, head->result.identifier.name, value->name));
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
|
@ -357,6 +298,18 @@ Boolean test_integer_value(LexerTest *test, LexerResult result, size_t i, TestIn
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
Boolean test_character_value(LexerTest *test, LexerResult result, size_t i, uint8_t *value) {
|
||||
static const TokenType token_type = TOKEN_CHARACTER;
|
||||
LexerTokenResult *head = get_token(result.result, i);
|
||||
if (test_token_type(test, result, i, token_type)) {
|
||||
return TRUE;
|
||||
} if (head->result.integer_literal.value != *value) {
|
||||
logic_fail_test(test, result, character_value_should_be(i + 1, *value, head->result.float_literal));
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Boolean test_float_value(LexerTest *test, LexerResult result, size_t i, float *value) {
|
||||
static const TokenType token_type = TOKEN_FLOAT;
|
||||
LexerTokenResult *head = get_token(result.result, i);
|
||||
|
|
@ -374,23 +327,23 @@ Boolean test_double_value(LexerTest *test, LexerResult result, size_t i, double
|
|||
LexerTokenResult *head = get_token(result.result, i);
|
||||
if (test_token_type(test, result, i, token_type)) {
|
||||
return TRUE;
|
||||
} if (fabs(head->result.float_literal - *value) >= FLOAT_TEST_PRECISION) {
|
||||
logic_fail_test(test, result, float_value_should_be(i + 1, *value, head->result.float_literal));
|
||||
} if (fabs(head->result.double_literal - *value) >= FLOAT_TEST_PRECISION) {
|
||||
logic_fail_test(test, result, float_value_should_be(i + 1, *value, head->result.double_literal));
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Boolean test_string_value(LexerTest *test, LexerResult result, size_t i, TestStringValue *value) {
|
||||
Boolean test_string_value(LexerTest *test, LexerResult result, size_t i, SlsStr *value) {
|
||||
static const TokenType token_type = TOKEN_STRING;
|
||||
LexerTokenResult *head = get_token(result.result, i);
|
||||
if (test_token_type(test, result, i, token_type)) {
|
||||
return TRUE;
|
||||
} if (head->result.string_literal.length == strnlen(value->string, value->length)) {
|
||||
logic_fail_test(test, result, token_length_should_be(i + 1, token_type, strnlen(value->string, value->length), head->result.string_literal.length));
|
||||
} if (head->result.string_literal.len == value->len) {
|
||||
logic_fail_test(test, result, token_length_should_be(i + 1, token_type, value->len, head->result.string_literal.len));
|
||||
return TRUE;
|
||||
} if (strncmp(head->result.string_literal.value, value->string, value->length) != 0) {
|
||||
logic_fail_test(test, result, token_value_string_should_be(i + 1, token_type, fmax(strnlen(value->string, value->length), strnlen(head->result.string_literal.value, value->length)), value->string, head->result.string_literal.value));
|
||||
} if (sls_str_cmp(head->result.string_literal, *value) != 0) {
|
||||
logic_fail_test(test, result, token_value_string_should_be(i + 1, token_type, *value, head->result.string_literal));
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
|
@ -417,11 +370,11 @@ Boolean test_array_identifier_value(LexerTest *test, LexerResult result, size_t
|
|||
size_t length = 1;
|
||||
for (size_t j = 0; j < values->dimensions; j++) length *= values->shape[j];
|
||||
for (size_t j = 0; j < length; j++) {
|
||||
if (head->result.array_literal.identifiers[j].length == values->values[j].length) {
|
||||
logic_fail_test(test, result, array_element_integer_should_be(i + 1, j, array_type, values->values[j].length, head->result.array_literal.identifiers[j].length));
|
||||
if (head->result.array_literal.identifiers[j].name.len == values->values[j].name.len) {
|
||||
logic_fail_test(test, result, array_element_integer_should_be(i + 1, j, array_type, values->values[j].name.len, head->result.array_literal.identifiers[j].name.len));
|
||||
return TRUE;
|
||||
} if (strncmp(head->result.array_literal.identifiers[j].name, values->values[j].name, values->values[j].length)) {
|
||||
logic_fail_test(test, result, array_element_string_should_be(i + 1, j, array_type, values->values[j].length, values->values[j].name, head->result.array_literal.identifiers[j].name));
|
||||
} if (sls_str_cmp(head->result.array_literal.identifiers[j].name, values->values[j].name)) {
|
||||
logic_fail_test(test, result, array_element_string_should_be(i + 1, j, array_type, values->values[j].name, head->result.array_literal.identifiers[j].name));
|
||||
return TRUE;
|
||||
} if (head->result.array_literal.identifiers[j].is_literal) {
|
||||
logic_fail_test(test, result, array_element_boolean_should_be(i + 1, j, array_type, TRUE));
|
||||
|
|
@ -491,11 +444,11 @@ Boolean test_array_string_value(LexerTest *test, LexerResult result, size_t i, T
|
|||
size_t length = 1;
|
||||
for (size_t j = 0; j < values->dimensions; j++) length *= values->shape[j];
|
||||
for (size_t j = 0; j < length; j++) {
|
||||
if (head->result.array_literal.string_literals[j].length == values->values[j].length) {
|
||||
logic_fail_test(test, result, array_element_integer_should_be(i + 1, j, array_type, values->values[j].length, head->result.array_literal.string_literals[j].length));
|
||||
if (head->result.array_literal.string_literals[j].len == values->values[j].len) {
|
||||
logic_fail_test(test, result, array_element_integer_should_be(i + 1, j, array_type, values->values[j].len, head->result.array_literal.string_literals[j].len));
|
||||
return TRUE;
|
||||
} if (strncmp(head->result.array_literal.string_literals[j].value, values->values[j].string, values->values[j].length)) {
|
||||
logic_fail_test(test, result, array_element_string_should_be(i + 1, j, array_type, values->values[j].length, values->values[j].string, head->result.array_literal.string_literals[j].value));
|
||||
} if (sls_str_cmp(head->result.array_literal.string_literals[j], values->values[j])) {
|
||||
logic_fail_test(test, result, array_element_string_should_be(i + 1, j, array_type, values->values[j], head->result.array_literal.string_literals[j]));
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
|
@ -524,8 +477,8 @@ Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size
|
|||
LexerTokenResult *head = get_token(result.result, i);
|
||||
if (test_array_type(test, result, i, array_type, values->shape, values->dimensions)) {
|
||||
return TRUE;
|
||||
} if (strncmp(head->result.array_literal.struct_inline.name, values->struct_name, values->struct_name_length)) {
|
||||
logic_fail_test(test, result, token_value_string_should_be(i + 1, TOKEN_IDENTIFIER, values->struct_name_length, values->struct_name, head->result.array_literal.struct_inline.name));
|
||||
} if (sls_str_cmp(head->result.array_literal.struct_inline.name, values->struct_name)) {
|
||||
logic_fail_test(test, result, token_value_string_should_be(i + 1, TOKEN_IDENTIFIER, values->struct_name, head->result.array_literal.struct_inline.name));
|
||||
return TRUE;
|
||||
}
|
||||
size_t length = 1;
|
||||
|
|
@ -541,9 +494,38 @@ Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size
|
|||
static LexerResult token_string_to_lexer_result(TokenString token_string, FileInfo file_info) {
|
||||
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 };
|
||||
|
|
@ -561,7 +543,8 @@ Boolean test_token_string_value(LexerTest *test, LexerResult result, size_t i, T
|
|||
for (size_t j = 0; j < values->tokens; j++) {
|
||||
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);
|
||||
|
|
@ -581,22 +564,22 @@ Boolean test_type_tuple_value(LexerTest *test, LexerResult result, size_t i, Tes
|
|||
logic_fail_test(test, result, token_length_should_be(i + 1, token_type, values->output_length, head->result.type_tuple.output_length));
|
||||
return TRUE;
|
||||
} for (size_t j = 0; j < values->input_length; j++) {
|
||||
if (head->result.type_tuple.input_identifiers[j].length == values->input_values[j].length) {
|
||||
logic_fail_test(test, result, type_tuple_element_integer_should_be(i + 1, j, values->input_values[j].length, head->result.type_tuple.input_identifiers[j].length));
|
||||
if (head->result.type_tuple.input_identifiers[j].name.len == values->input_values[j].name.len) {
|
||||
logic_fail_test(test, result, type_tuple_element_integer_should_be(i + 1, j, values->input_values[j].name.len, head->result.type_tuple.input_identifiers[j].name.len));
|
||||
return TRUE;
|
||||
} if (strncmp(head->result.type_tuple.input_identifiers[j].name, values->input_values[j].name, values->input_values[j].length)) {
|
||||
logic_fail_test(test, result, type_tuple_element_string_should_be(i + 1, j, values->input_values[j].length, values->input_values[j].name, head->result.type_tuple.input_identifiers[j].name));
|
||||
} if (sls_str_cmp(head->result.type_tuple.input_identifiers[j].name, values->input_values[j].name)) {
|
||||
logic_fail_test(test, result, type_tuple_element_string_should_be(i + 1, j, values->input_values[j].name, head->result.type_tuple.input_identifiers[j].name));
|
||||
return TRUE;
|
||||
} if (head->result.type_tuple.input_identifiers[j].is_literal) {
|
||||
logic_fail_test(test, result, type_tuple_element_boolean_should_be(i + 1, j, TRUE));
|
||||
return TRUE;
|
||||
}
|
||||
} for (size_t j = 0; j < values->output_length; j++) {
|
||||
if (head->result.type_tuple.output_identifiers[j].length == values->output_values[j].length) {
|
||||
logic_fail_test(test, result, type_tuple_element_integer_should_be(i + 1, j, values->output_values[j].length, head->result.type_tuple.output_identifiers[j].length));
|
||||
if (head->result.type_tuple.output_identifiers[j].name.len == values->output_values[j].name.len) {
|
||||
logic_fail_test(test, result, type_tuple_element_integer_should_be(i + 1, j, values->output_values[j].name.len, head->result.type_tuple.output_identifiers[j].name.len));
|
||||
return TRUE;
|
||||
} if (strncmp(head->result.type_tuple.output_identifiers[j].name, values->output_values[j].name, values->input_values[j].length)) {
|
||||
logic_fail_test(test, result, type_tuple_element_string_should_be(i + 1, j, values->output_values[j].length, values->output_values[j].name, head->result.type_tuple.output_identifiers[j].name));
|
||||
} if (sls_str_cmp(head->result.type_tuple.output_identifiers[j].name, values->output_values[j].name)) {
|
||||
logic_fail_test(test, result, type_tuple_element_string_should_be(i + 1, j, values->output_values[j].name, head->result.type_tuple.output_identifiers[j].name));
|
||||
return TRUE;
|
||||
} if (head->result.type_tuple.output_identifiers[j].is_literal) {
|
||||
logic_fail_test(test, result, type_tuple_element_boolean_should_be(i + 1, j, TRUE));
|
||||
|
|
@ -606,12 +589,12 @@ 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, TestErrorMessage *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));
|
||||
return TRUE;
|
||||
} if (strncmp(head->error.message, error->message, error->length+1) != 0) {
|
||||
} if (sls_str_cmp(head->error.message, *error) != 0) {
|
||||
logic_fail_test(test, result, error_should_be(i + 1, *error, head->error));
|
||||
return TRUE;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,224 @@
|
|||
// Kyler Olsen
|
||||
// YREA SLS
|
||||
// String Tests
|
||||
// November 2025
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "sls/string.h"
|
||||
#include "sls/lexer.h"
|
||||
#include "sls/errors.h"
|
||||
#include "tests/tests.h"
|
||||
|
||||
static const size_t NUM_STRING_TESTS = 10;
|
||||
|
||||
static TestResult pass_string_test(SlsStr test_name) {
|
||||
TestResult result = {.name = test_name, .status = TEST_NOT_IMPLEMENTED};
|
||||
result.status = TEST_PASS;
|
||||
return result;
|
||||
}
|
||||
|
||||
static TestResult fail_string_test(SlsStr test_name, SlsStr message) {
|
||||
TestResult result = { .name = test_name, .status = TEST_NOT_IMPLEMENTED };
|
||||
result.status = TEST_ERROR;
|
||||
result.message = sls_str_cpy(message);
|
||||
|
||||
if (result.message.str == NULL) {
|
||||
result.error = (SlsError){ SLS_STR("Out Of Memory Error."), 1 };
|
||||
result.status = TEST_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Test sls_str_malloc and sls_str_cpy
|
||||
static TestResult test_malloc_and_copy() {
|
||||
const char* original = "Hello, SLS!";
|
||||
SlsStr s = sls_str_malloc(original, strlen(original));
|
||||
if (!s.str) return fail_string_test(SLS_STR("test_malloc_and_copy"), SLS_STR("Allocation failed"));
|
||||
|
||||
if (strcmp(s.str, original) != 0) {
|
||||
sls_str_free(&s);
|
||||
return fail_string_test(SLS_STR("test_malloc_and_copy"), SLS_STR("Copied string mismatch"));
|
||||
}
|
||||
|
||||
SlsStr copy = sls_str_cpy(s);
|
||||
if (!copy.str || strcmp(copy.str, original) != 0) {
|
||||
sls_str_free(&s);
|
||||
sls_str_free(©);
|
||||
return fail_string_test(SLS_STR("test_malloc_and_copy"), SLS_STR("sls_str_cpy failed"));
|
||||
}
|
||||
|
||||
sls_str_free(&s);
|
||||
sls_str_free(©);
|
||||
return pass_string_test(SLS_STR("test_malloc_and_copy"));
|
||||
}
|
||||
|
||||
// Test sls_str_cmp
|
||||
static TestResult test_compare_strings() {
|
||||
SlsStr a = sls_str_malloc("abc", 3);
|
||||
SlsStr b = sls_str_malloc("abc", 3);
|
||||
SlsStr c = sls_str_malloc("abd", 3);
|
||||
|
||||
if (sls_str_cmp(a, b) != 0) goto fail;
|
||||
if (sls_str_cmp(a, c) >= 0) goto fail;
|
||||
if (sls_str_cmp(c, a) <= 0) goto fail;
|
||||
|
||||
sls_str_free(&a);
|
||||
sls_str_free(&b);
|
||||
sls_str_free(&c);
|
||||
return pass_string_test(SLS_STR("test_compare_strings"));
|
||||
|
||||
fail:
|
||||
sls_str_free(&a);
|
||||
sls_str_free(&b);
|
||||
sls_str_free(&c);
|
||||
return fail_string_test(SLS_STR("test_compare_strings"), SLS_STR("Comparison test failed"));
|
||||
}
|
||||
|
||||
// Test sls_format for basic placeholders
|
||||
static TestResult test_format_basic_placeholders() {
|
||||
SlsStr fmt = SLS_STR("Char: %c, Int: %d, Long: %l, Unsigned: %u, Size: %z, Float: %f, C-String: %y");
|
||||
const char *s = "Test";
|
||||
SlsStr result = sls_format(fmt, 'X', -42, (int64_t)1234567890123, (uint64_t)9876543210, (size_t)1024, 3.14, s);
|
||||
|
||||
if (!result.str) return fail_string_test(SLS_STR("test_format_basic_placeholders"), SLS_STR("Formatting returned NULL"));
|
||||
|
||||
const char* expected = "Char: X, Int: -42, Long: 1234567890123, Unsigned: 9876543210, Size: 1024, Float: 3.14, C-String: Test";
|
||||
if (strcmp(result.str, expected) != 0) {
|
||||
sls_str_free(&result);
|
||||
return fail_string_test(SLS_STR("test_format_basic_placeholders"), SLS_STR("Formatted string mismatch"));
|
||||
}
|
||||
|
||||
sls_str_free(&result);
|
||||
return pass_string_test(SLS_STR("test_format_basic_placeholders"));
|
||||
}
|
||||
|
||||
// Test sls_format for SLS types: %s, %t, %a, %i, %e, %b
|
||||
static TestResult test_format_sls_types() {
|
||||
// Placeholder values
|
||||
SlsStr sls_str_val = SLS_STR("SLS_STRING");
|
||||
TokenType tok = 0; // Assuming TOKEN_TYPES_NAMES[0] exists
|
||||
ArrayType arr = 0; // Assuming ARRAY_TYPES_NAMES[0] exists
|
||||
IntegerBuiltInType ib = 0; // Assuming INTEGER_TYPES_NAMES[0] exists
|
||||
SlsError err = {SLS_STR("ErrorMessage"), 1};
|
||||
Boolean boolean_true = TRUE;
|
||||
|
||||
SlsStr fmt = SLS_STR("Str:%s Tok:%t Arr:%a IntType:%i Err:%e Bool:%b");
|
||||
SlsStr result = sls_format(fmt, sls_str_val, tok, arr, ib, err, boolean_true);
|
||||
|
||||
if (!result.str) return fail_string_test(SLS_STR("test_format_sls_types"), SLS_STR("Formatting returned NULL"));
|
||||
|
||||
char expected[256];
|
||||
snprintf(expected, 256, "Str:%s Tok:%s Arr:%s IntType:%s Err:%s Bool:TRUE",
|
||||
sls_str_val.str,
|
||||
TOKEN_TYPES_NAMES[tok],
|
||||
ARRAY_TYPES_NAMES[arr],
|
||||
INTEGER_TYPES_NAMES[ib],
|
||||
err.message.str);
|
||||
|
||||
if (strcmp(result.str, expected) != 0) {
|
||||
sls_str_free(&result);
|
||||
return fail_string_test(SLS_STR("test_format_sls_types"), SLS_STR("Formatted string mismatch for SLS types"));
|
||||
}
|
||||
|
||||
sls_str_free(&result);
|
||||
return pass_string_test(SLS_STR("test_format_sls_types"));
|
||||
}
|
||||
|
||||
// Test sls_format with %% escape
|
||||
static TestResult test_format_percent_escape() {
|
||||
SlsStr fmt = SLS_STR("Progress: 100%% complete");
|
||||
SlsStr result = sls_format(fmt);
|
||||
|
||||
if (!result.str) return fail_string_test(SLS_STR("test_format_percent_escape"), SLS_STR("Formatting returned NULL"));
|
||||
if (strcmp(result.str, "Progress: 100% complete") != 0) {
|
||||
sls_str_free(&result);
|
||||
return fail_string_test(SLS_STR("test_format_percent_escape"), SLS_STR("Percent escape failed"));
|
||||
}
|
||||
|
||||
sls_str_free(&result);
|
||||
return pass_string_test(SLS_STR("test_format_percent_escape"));
|
||||
}
|
||||
|
||||
// Test sls_str_new allocation and zero-init
|
||||
static TestResult test_new_string_allocation() {
|
||||
SlsStr s = sls_str_new(10);
|
||||
if (!s.str) return fail_string_test(SLS_STR("test_new_string_allocation"), SLS_STR("Allocation failed"));
|
||||
|
||||
for (size_t i = 0; i < s.len; i++) {
|
||||
if (s.str[i] != 0) {
|
||||
sls_str_free(&s);
|
||||
return fail_string_test(SLS_STR("test_new_string_allocation"), SLS_STR("Memory not zero-initialized"));
|
||||
}
|
||||
}
|
||||
|
||||
sls_str_free(&s);
|
||||
return pass_string_test(SLS_STR("test_new_string_allocation"));
|
||||
}
|
||||
|
||||
// Test empty string
|
||||
static TestResult test_empty_string() {
|
||||
SlsStr s = sls_str_malloc("", 0);
|
||||
if (!s.str || s.len != 0) return fail_string_test(SLS_STR("test_empty_string"), SLS_STR("Empty string allocation failed"));
|
||||
sls_str_free(&s);
|
||||
return pass_string_test(SLS_STR("test_empty_string"));
|
||||
}
|
||||
|
||||
// Test long string
|
||||
static TestResult test_long_string() {
|
||||
size_t len = 1024;
|
||||
char* long_str = malloc(len + 1);
|
||||
for (size_t i = 0; i < len; i++) long_str[i] = 'A';
|
||||
long_str[len] = '\0';
|
||||
|
||||
SlsStr s = sls_str_malloc(long_str, len);
|
||||
if (!s.str || s.len != len) {
|
||||
free(long_str);
|
||||
return fail_string_test(SLS_STR("test_long_string"), SLS_STR("Long string allocation failed"));
|
||||
}
|
||||
|
||||
sls_str_free(&s);
|
||||
free(long_str);
|
||||
return pass_string_test(SLS_STR("test_long_string"));
|
||||
}
|
||||
|
||||
// Test NULL pointer handling in sls_str_free
|
||||
static TestResult test_free_null() {
|
||||
SlsStr s = SLS_STR_NULL;
|
||||
sls_str_free(&s); // Should safely do nothing
|
||||
return pass_string_test(SLS_STR("test_free_null"));
|
||||
}
|
||||
|
||||
// Test sls_str_nlen edge cases
|
||||
static TestResult test_str_nlen_edge() {
|
||||
const char* str = "ABCDE";
|
||||
if (sls_str_nlen(str, 0) != 0) return fail_string_test(SLS_STR("test_str_nlen_edge"), SLS_STR("Maxlen=0 failed"));
|
||||
if (sls_str_nlen(str, 3) != 3) return fail_string_test(SLS_STR("test_str_nlen_edge"), SLS_STR("Maxlen=3 failed"));
|
||||
if (sls_str_nlen(str, 10) != 5) return fail_string_test(SLS_STR("test_str_nlen_edge"), SLS_STR("Maxlen>strlen failed"));
|
||||
return pass_string_test(SLS_STR("test_str_nlen_edge"));
|
||||
}
|
||||
|
||||
// Run all string tests
|
||||
TestsReport run_string_tests() {
|
||||
TestsReport report = {
|
||||
.section = SLS_STR("string_tests"),
|
||||
.count = NUM_STRING_TESTS,
|
||||
.tests = malloc(sizeof(TestResult) * NUM_STRING_TESTS)
|
||||
};
|
||||
|
||||
size_t i = 0;
|
||||
report.tests[i++] = test_malloc_and_copy();
|
||||
report.tests[i++] = test_compare_strings();
|
||||
report.tests[i++] = test_format_basic_placeholders();
|
||||
report.tests[i++] = test_format_sls_types();
|
||||
report.tests[i++] = test_format_percent_escape();
|
||||
report.tests[i++] = test_new_string_allocation();
|
||||
report.tests[i++] = test_empty_string();
|
||||
report.tests[i++] = test_long_string();
|
||||
report.tests[i++] = test_free_null();
|
||||
report.tests[i++] = test_str_nlen_edge();
|
||||
return report;
|
||||
}
|
||||
|
|
@ -7,9 +7,12 @@
|
|||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sls/errors.h"
|
||||
#include "tests/tests.h"
|
||||
|
||||
const char *TEST_FILE_NAME = "TEST_FILE.SLS";
|
||||
static const Boolean PRINT_SUCCESSFUL_TESTS = FALSE;
|
||||
|
||||
const SlsStr TEST_FILE_NAME = SLS_STR_CONST("TEST_FILE.SLS");
|
||||
|
||||
typedef struct {
|
||||
uint16_t errored;
|
||||
|
|
@ -21,45 +24,46 @@ typedef struct {
|
|||
uint16_t total;
|
||||
} TestCounts;
|
||||
|
||||
static void lexer_test_report(TestsReport reports, TestCounts *counts) {
|
||||
static void test_report(TestsReport reports, TestCounts *counts) {
|
||||
counts->total += reports.count;
|
||||
for (size_t i = 0; i < reports.count; i++) {
|
||||
switch (reports.tests[i].status) {
|
||||
case TEST_ERROR:
|
||||
// Bright Red
|
||||
printf("\x1b[91mTest errored: %s\n\t%s\n\x1b[0m", reports.tests[i].name, reports.tests[i].error.message);
|
||||
printf("\x1b[91mTest errored: %s\n\t%s\n\x1b[0m", reports.tests[i].name.str, reports.tests[i].error.message.str);
|
||||
counts->errored += 1;
|
||||
break;
|
||||
case TEST_ERROR_FAIL:
|
||||
// Magenta
|
||||
printf("\x1b[35mLexing errored: %s\n\t%s\n\x1b[0m", reports.tests[i].name, reports.tests[i].error.message);
|
||||
printf("\x1b[35mLexing errored: %s\n\t%s\n\x1b[0m", reports.tests[i].name.str, reports.tests[i].error.message.str);
|
||||
counts->error_failed += 1;
|
||||
break;
|
||||
case TEST_LOGIC_ERROR_FAIL:
|
||||
// Red
|
||||
printf("\x1b[31mTest failed with lexical error: %s\n\t%s\n\x1b[0m", reports.tests[i].name, reports.tests[i].error.message);
|
||||
printf("\x1b[31mTest failed with lexical error: %s\n\t%s\n\x1b[0m", reports.tests[i].name.str, reports.tests[i].error.message.str);
|
||||
counts->logic_error_failed += 1;
|
||||
free(reports.tests[i].message);
|
||||
sls_str_free(&reports.tests[i].message);
|
||||
break;
|
||||
case TEST_LOGIC_FAIL:
|
||||
// Red
|
||||
printf("\x1b[31mTest failed: %s\n\t%s\n\x1b[0m", reports.tests[i].name, reports.tests[i].message);
|
||||
printf("\x1b[31mTest failed: %s\n\t%s\n\x1b[0m", reports.tests[i].name.str, reports.tests[i].message.str);
|
||||
counts->logic_failed += 1;
|
||||
free(reports.tests[i].message);
|
||||
sls_str_free(&reports.tests[i].message);
|
||||
break;
|
||||
case TEST_PASS:
|
||||
// Green
|
||||
printf("\x1b[32mTest passed: %s\n\x1b[0m", reports.tests[i].name);
|
||||
if (PRINT_SUCCESSFUL_TESTS)
|
||||
printf("\x1b[32mTest passed: %s\n\x1b[0m", reports.tests[i].name.str);
|
||||
counts->passed += 1;
|
||||
break;
|
||||
case TEST_NOT_IMPLEMENTED:
|
||||
// Blue
|
||||
printf("\x1b[34mTest not implemented: %s\n\x1b[0m", reports.tests[i].name);
|
||||
printf("\x1b[34mTest not implemented: %s\n\x1b[0m", reports.tests[i].name.str);
|
||||
counts->not_implemented += 1;
|
||||
break;
|
||||
default:
|
||||
// Bright Red
|
||||
printf("\x1b[91mTest errored: %s\n\tUnknown test result status.\n\x1b[0m", reports.tests[i].name);
|
||||
printf("\x1b[91mTest errored: %s\n\tUnknown test result status.\n\x1b[0m", reports.tests[i].name.str);
|
||||
counts->errored += 1;
|
||||
break;
|
||||
}
|
||||
|
|
@ -77,10 +81,16 @@ int main(void) {
|
|||
.total = 0,
|
||||
};
|
||||
|
||||
TestsReport lexer_reports = run_lexer_tests();
|
||||
lexer_test_report(lexer_reports, &counts);
|
||||
printf(" ========== SlsStr Tests ==========\n");
|
||||
test_report(run_string_tests(), &counts);
|
||||
printf(" ========== Lexer Tests ==========\n");
|
||||
test_report(run_lexer_tests(), &counts);
|
||||
printf(" ========== Hash Table Tests ==========\n");
|
||||
test_report(run_hash_table_tests(), &counts);
|
||||
printf(" ========== Extra Tests ==========\n");
|
||||
test_report(run_extra_tests(), &counts);
|
||||
|
||||
printf(" ===== Tests Overview =====\n");
|
||||
printf(" ========== Tests Overview ==========\n");
|
||||
if (counts.errored > 0)
|
||||
// Bright Red
|
||||
printf("\x1b[91m%d of %d tests encountered an error.\n\x1b[0m", counts.errored, counts.total);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
__pycache__/
|
||||
.venv/
|
||||
sls_python.egg-info/
|
||||
dist/
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
"""Build backend for sls_python package."""
|
||||
|
||||
from .build_hooks import build_wheel, build_sdist
|
||||
|
||||
__all__ = ["build_wheel", "build_sdist"]
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# Auto-generated during build
|
||||
version = "{version}"
|
||||
commit = "{commit}"
|
||||
timestamp = "{timestamp}"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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 = {"" = ".."}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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: ...
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -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())
|
||||
|
|
@ -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"
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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), ('x²', 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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# SLS Rust
|
||||
|
||||
This is the Rust implementation for the YREA SLS interpreter.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
|
|
@ -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"] }
|
||||
|
|
@ -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);
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5090
SLS_Tests/cases.yaml
5090
SLS_Tests/cases.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,20 @@
|
|||
from .base_tests import BaseTestGenerator
|
||||
from .general_tests import GeneralTestGenerator
|
||||
from .integer_tests import IntegerTestGenerator
|
||||
from .float_tests import FloatTestGenerator
|
||||
from .char_tests import CharTestGenerator
|
||||
from .string_tests import StringTestGenerator
|
||||
from .idents_and_bools_tests import IdentifierTestGenerator, BooleanTestGenerator
|
||||
from .token_strings import TokenStringTestGenerator
|
||||
|
||||
__all__ = [
|
||||
"BaseTestGenerator",
|
||||
"GeneralTestGenerator",
|
||||
"IntegerTestGenerator",
|
||||
"FloatTestGenerator",
|
||||
"CharTestGenerator",
|
||||
"StringTestGenerator",
|
||||
"IdentifierTestGenerator",
|
||||
"BooleanTestGenerator",
|
||||
"TokenStringTestGenerator",
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
Test case generator for integer literals in the Stack Language.
|
||||
Generates comprehensive test cases for all integer types and bases.
|
||||
"""
|
||||
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
if __name__ == "__main__":
|
||||
import yaml
|
||||
|
||||
# Generate tests
|
||||
tests = BaseTestGenerator.run_all_generators()
|
||||
# tests = []
|
||||
# tests += GeneralTestGenerator.generate_tests()
|
||||
# tests += IntegerTestGenerator.generate_tests()
|
||||
# tests += FloatTestGenerator.generate_tests()
|
||||
|
||||
# Print summary
|
||||
print(f"Generated {len(tests)} test cases")
|
||||
|
||||
# Save as YAML
|
||||
with open("cases.yaml", "w") as f:
|
||||
yaml.dump(tests, f, default_flow_style=False, sort_keys=False)
|
||||
|
|
@ -0,0 +1,405 @@
|
|||
from typing import ClassVar, List, Dict, Any, Optional, Type
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
@dataclass
|
||||
class Token:
|
||||
type: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class Operation:
|
||||
function: str
|
||||
type: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class StackItem:
|
||||
type: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuntimeError:
|
||||
message: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestCase:
|
||||
name: str
|
||||
code: str
|
||||
tokens: List[Dict[str, Any]]
|
||||
operations: Optional[List[Dict[str, Any]]] = None
|
||||
stack_final: Optional[List[Dict[str, Any]]] = None
|
||||
runtime_error: Optional[Dict[str, str]] = None
|
||||
|
||||
|
||||
def to_dict(obj) -> Dict[str, Any]:
|
||||
"""Convert dataclass to dict, removing None values."""
|
||||
if obj is None:
|
||||
raise ValueError("Obj cannot be None")
|
||||
d = asdict(obj) if hasattr(obj, '__dataclass_fields__') else obj
|
||||
return {k: v for k, v in d.items() if v is not None}
|
||||
|
||||
|
||||
class BaseTestGenerator(ABC):
|
||||
"""
|
||||
Abstract base class for test case generators.
|
||||
|
||||
Provides common functionality for generating test cases including
|
||||
test creation, operation building, and error handling.
|
||||
"""
|
||||
|
||||
ENABLE_UNICODE = False
|
||||
ENABLE_EXPONENTIAL_LITERALS = False
|
||||
ENABLE_CHAR_HEX_ESCAPE = False
|
||||
ENABLE_STRINGS = False
|
||||
|
||||
__generators: "ClassVar[List[Type[BaseTestGenerator]]]" = []
|
||||
|
||||
def __init_subclass__(cls):
|
||||
BaseTestGenerator.__generators.append(cls)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the test generator with an empty test list."""
|
||||
self.tests: List[Dict[str, Any]] = []
|
||||
|
||||
# =========================================================================
|
||||
# Test Case Management
|
||||
# =========================================================================
|
||||
|
||||
def add_test(self, name: str, code: str, tokens: List[Token],
|
||||
operations: Optional[List[Operation]] = None,
|
||||
stack_final: Optional[List[StackItem]] = None,
|
||||
runtime_error: Optional[RuntimeError] = None):
|
||||
"""
|
||||
Add a test case to the test suite.
|
||||
|
||||
Args:
|
||||
name: Descriptive name for the test
|
||||
code: Source code being tested
|
||||
tokens: List of expected tokens from lexing
|
||||
operations: Optional list of operations for evaluation phase
|
||||
stack_final: Optional final stack state after execution
|
||||
runtime_error: Optional runtime error if test should fail
|
||||
"""
|
||||
test = TestCase(
|
||||
name=name,
|
||||
code=code,
|
||||
tokens=[to_dict(t) for t in tokens],
|
||||
operations=[to_dict(o) for o in operations] if operations else None,
|
||||
stack_final=[to_dict(s) for s in stack_final] if stack_final else None,
|
||||
runtime_error=to_dict(runtime_error) if runtime_error else None
|
||||
)
|
||||
self.tests.append(to_dict(test))
|
||||
|
||||
# =========================================================================
|
||||
# Factory Methods
|
||||
# =========================================================================
|
||||
|
||||
def make_push_op(self, type_name: str, value: Any) -> Operation:
|
||||
"""
|
||||
Create a push operation.
|
||||
|
||||
Args:
|
||||
type_name: Type of the value being pushed
|
||||
value: The value to push
|
||||
|
||||
Returns:
|
||||
Operation object representing a push
|
||||
"""
|
||||
return Operation(function="push", type=type_name, value=value)
|
||||
|
||||
def make_stack_item(self, type_name: str, value: Any) -> StackItem:
|
||||
"""
|
||||
Create a stack item.
|
||||
|
||||
Args:
|
||||
type_name: Type of the stack item
|
||||
value: The value of the stack item
|
||||
|
||||
Returns:
|
||||
StackItem object
|
||||
"""
|
||||
return StackItem(type=type_name, value=value)
|
||||
|
||||
def make_error_token(self, message: str) -> Token:
|
||||
"""
|
||||
Create an error token.
|
||||
|
||||
Args:
|
||||
message: Error message
|
||||
|
||||
Returns:
|
||||
Token object with type "error"
|
||||
"""
|
||||
return Token(type="error", value=message)
|
||||
|
||||
def make_runtime_error(self, message: str) -> RuntimeError:
|
||||
"""
|
||||
Create a runtime error.
|
||||
|
||||
Args:
|
||||
message: Error message
|
||||
|
||||
Returns:
|
||||
RuntimeError object
|
||||
"""
|
||||
return RuntimeError(message=message)
|
||||
|
||||
# =========================================================================
|
||||
# Convenience Test Creators
|
||||
# =========================================================================
|
||||
|
||||
def make_success_test(self, name: str, code: str, type_name: str, value: Any):
|
||||
"""
|
||||
Create a successful test case with standard push operation.
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
type_name: Type of the value
|
||||
value: The value
|
||||
"""
|
||||
token = Token(type=type_name, value=value)
|
||||
op = self.make_push_op(type_name, value)
|
||||
stack = self.make_stack_item(type_name, value)
|
||||
self.add_test(name, code, [token], [op], [stack])
|
||||
|
||||
def make_error_test(self, name: str, code: str, error_msg: str):
|
||||
"""
|
||||
Create an error test case (lexing error).
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
error_msg: Expected error message
|
||||
"""
|
||||
token = self.make_error_token(error_msg)
|
||||
self.add_test(name, code, [token])
|
||||
|
||||
def make_runtime_error_test(self, name: str, code: str, tokens: List[Token],
|
||||
operations: List[Operation], error_msg: str):
|
||||
"""
|
||||
Create a runtime error test case.
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
tokens: Tokens that were successfully lexed
|
||||
operations: Operations that led to the error
|
||||
error_msg: Expected runtime error message
|
||||
"""
|
||||
runtime_error = self.make_runtime_error(error_msg)
|
||||
self.add_test(name, code, tokens, operations, None, runtime_error)
|
||||
|
||||
def make_empty_test(self, name: str, code: str):
|
||||
"""
|
||||
Create a test case with empty result (e.g., comments, whitespace).
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
"""
|
||||
self.add_test(name, code, [], [], [])
|
||||
|
||||
# =========================================================================
|
||||
# Multi-Value Test Helpers
|
||||
# =========================================================================
|
||||
|
||||
def make_multi_value_test(self, name: str, code: str,
|
||||
values: List[tuple[str, Any]]):
|
||||
"""
|
||||
Create a test with multiple values on the stack.
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
values: List of (type_name, value) tuples in stack order
|
||||
"""
|
||||
tokens = [Token(type=t, value=v) for t, v in values]
|
||||
operations = [self.make_push_op(t, v) for t, v in values]
|
||||
stack = [self.make_stack_item(t, v) for t, v in values]
|
||||
self.add_test(name, code, tokens, operations, stack)
|
||||
|
||||
# =========================================================================
|
||||
# Test Suite Generation
|
||||
# =========================================================================
|
||||
|
||||
@abstractmethod
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Generate all test cases for this generator.
|
||||
|
||||
Must be implemented by subclasses to define their specific test suite.
|
||||
|
||||
Returns:
|
||||
List of test case dictionaries ready for serialization
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_tests(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get the current list of tests.
|
||||
|
||||
Returns:
|
||||
List of test case dictionaries
|
||||
"""
|
||||
return self.tests
|
||||
|
||||
def clear_tests(self):
|
||||
"""Clear all tests from the generator."""
|
||||
self.tests = []
|
||||
|
||||
def test_count(self) -> int:
|
||||
"""
|
||||
Get the number of tests generated.
|
||||
|
||||
Returns:
|
||||
Number of tests
|
||||
"""
|
||||
return len(self.tests)
|
||||
|
||||
@classmethod
|
||||
def generate_tests(cls) -> List[Dict[str, Any]]:
|
||||
gen = cls()
|
||||
tests = gen.generate_all_tests()
|
||||
gen.print_statistics()
|
||||
return tests
|
||||
|
||||
@classmethod
|
||||
def run_all_generators(cls) -> List[Dict[str, Any]]:
|
||||
tests = []
|
||||
for sub_cls in cls.__generators:
|
||||
tests += sub_cls.generate_tests()
|
||||
return tests
|
||||
|
||||
# =========================================================================
|
||||
# Test Organization Helpers
|
||||
# =========================================================================
|
||||
|
||||
def add_test_group_comment(self, comment: str):
|
||||
"""
|
||||
Add a comment to organize test groups (for documentation).
|
||||
|
||||
Note: This doesn't add an actual test, just tracks organization
|
||||
in subclass implementations.
|
||||
|
||||
Args:
|
||||
comment: Comment describing the test group
|
||||
"""
|
||||
# Subclasses can override to add metadata or logging
|
||||
pass
|
||||
|
||||
# =========================================================================
|
||||
# Validation Helpers
|
||||
# =========================================================================
|
||||
|
||||
def validate_test_names_unique(self) -> bool:
|
||||
"""
|
||||
Check if all test names are unique.
|
||||
|
||||
Returns:
|
||||
True if all test names are unique, False otherwise
|
||||
"""
|
||||
names = [test['name'] for test in self.tests]
|
||||
return len(names) == len(set(names))
|
||||
|
||||
def get_duplicate_test_names(self) -> List[str]:
|
||||
"""
|
||||
Get list of duplicate test names.
|
||||
|
||||
Returns:
|
||||
List of test names that appear more than once
|
||||
"""
|
||||
names = [test['name'] for test in self.tests]
|
||||
seen = set()
|
||||
duplicates = set()
|
||||
for name in names:
|
||||
if name in seen:
|
||||
duplicates.add(name)
|
||||
seen.add(name)
|
||||
return list(duplicates)
|
||||
|
||||
# =========================================================================
|
||||
# Statistics
|
||||
# =========================================================================
|
||||
|
||||
def get_test_statistics(self) -> Dict[str, int]:
|
||||
"""
|
||||
Get statistics about the generated tests.
|
||||
|
||||
Returns:
|
||||
Dictionary with test statistics
|
||||
"""
|
||||
stats = {
|
||||
'total': len(self.tests),
|
||||
'success': 0,
|
||||
'lex_error': 0,
|
||||
'runtime_error': 0,
|
||||
'empty': 0,
|
||||
}
|
||||
|
||||
for test in self.tests:
|
||||
if test.get('runtime_error'):
|
||||
stats['runtime_error'] += 1
|
||||
elif test.get('tokens') and test['tokens'][0].get('type') == 'error':
|
||||
stats['lex_error'] += 1
|
||||
elif not test.get('tokens'):
|
||||
stats['empty'] += 1
|
||||
else:
|
||||
stats['success'] += 1
|
||||
|
||||
return stats
|
||||
|
||||
def print_statistics(self):
|
||||
"""Print test statistics to console."""
|
||||
stats = self.get_test_statistics()
|
||||
print(f"Test Statistics for {self.__class__.__name__}:")
|
||||
print(f" Total tests: {stats['total']}")
|
||||
print(f" Success tests: {stats['success']}")
|
||||
print(f" Lex error tests: {stats['lex_error']}")
|
||||
print(f" Runtime error tests: {stats['runtime_error']}")
|
||||
print(f" Empty tests: {stats['empty']}")
|
||||
|
||||
if not self.validate_test_names_unique():
|
||||
duplicates = self.get_duplicate_test_names()
|
||||
print(f" WARNING: Duplicate test names found: {duplicates}")
|
||||
|
||||
# =========================================================================
|
||||
# Token String Helpers
|
||||
# =========================================================================
|
||||
|
||||
def make_token_string_token(self, inner_tokens: List[Token]) -> Token:
|
||||
"""
|
||||
Create a token string token containing inner tokens.
|
||||
|
||||
Args:
|
||||
inner_tokens: List of tokens inside the token string
|
||||
|
||||
Returns:
|
||||
Token with type "token_string" and tokens array
|
||||
"""
|
||||
return Token(type="token_string", value=[t.__dict__ for t in inner_tokens])
|
||||
|
||||
def make_token_string_test(self, name: str, code: str, inner_tokens: List[Token]):
|
||||
"""
|
||||
Create a successful token string test.
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
inner_tokens: Tokens inside the token string
|
||||
"""
|
||||
token = self.make_token_string_token(inner_tokens)
|
||||
|
||||
# For operations, we push the token string as a value
|
||||
op_value = {"tokens": [t.__dict__ for t in inner_tokens]}
|
||||
op = self.make_push_op("token_string", op_value)
|
||||
|
||||
# On the stack, it's a token_string value
|
||||
stack = self.make_stack_item("token_string", op_value)
|
||||
|
||||
self.add_test(name, code, [token], [op], [stack])
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
from typing import List, Dict, Any
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
|
||||
class CharTestGenerator(BaseTestGenerator):
|
||||
"""Generate test cases for character literals."""
|
||||
|
||||
# Common escape sequences
|
||||
ESCAPE_SEQUENCES = {
|
||||
("Newline", '\\\\n', '\n',), # Newline
|
||||
("Carriage return", '\\\\r', '\r',), # Carriage return
|
||||
("Tab", '\\\\t', '\t',), # Tab
|
||||
("Backslash", '\\\\\\\\', '\\',), # Backslash
|
||||
("Single quote", "\\\\'", "'",), # Single quote
|
||||
("Null character", '\\\\0', '\0',), # Null character
|
||||
}
|
||||
|
||||
# Unicode escape examples
|
||||
UNICODE_ESCAPES = {
|
||||
'\\u{41}': 'A',
|
||||
'\\u{1F600}': '😀',
|
||||
'\\u{2764}': '❤',
|
||||
'\\u{0041}': 'A',
|
||||
'\\u{03B1}': 'α',
|
||||
'\\u{4E2D}': '中',
|
||||
}
|
||||
|
||||
def generate_basic_tests(self):
|
||||
"""Generate basic character literal tests."""
|
||||
# Simple ASCII letters
|
||||
self.make_success_test("Char Simple Letter Uppercase A", "'A'", "char", 'A')
|
||||
self.make_success_test("Char Simple Letter Lowercase a", "'a'", "char", 'a')
|
||||
self.make_success_test("Char Simple Letter Uppercase Z", "'Z'", "char", 'Z')
|
||||
self.make_success_test("Char Simple Letter Lowercase z", "'z'", "char", 'z')
|
||||
|
||||
# Digits
|
||||
self.make_success_test("Char Digit 0", "'0'", "char", '0')
|
||||
self.make_success_test("Char Digit 5", "'5'", "char", '5')
|
||||
self.make_success_test("Char Digit 9", "'9'", "char", '9')
|
||||
|
||||
# Special characters
|
||||
self.make_success_test("Char Space", "' '", "char", ' ')
|
||||
self.make_success_test("Char Exclamation", "'!'", "char", '!')
|
||||
self.make_success_test("Char Question Mark", "'?'", "char", '?')
|
||||
self.make_success_test("Char Period", "'.'", "char", '.')
|
||||
self.make_success_test("Char Comma", "','", "char", ',')
|
||||
self.make_success_test("Char Semicolon", "';'", "char", ';')
|
||||
self.make_success_test("Char Colon", "':'", "char", ':')
|
||||
|
||||
# Operators and symbols
|
||||
self.make_success_test("Char Plus", "'+'", "char", '+')
|
||||
self.make_success_test("Char Minus", "'-'", "char", '-')
|
||||
self.make_success_test("Char Asterisk", "'*'", "char", '*')
|
||||
self.make_success_test("Char Slash", "'/'", "char", '/')
|
||||
self.make_success_test("Char Equals", "'='", "char", '=')
|
||||
self.make_success_test("Char Less Than", "'<'", "char", '<')
|
||||
self.make_success_test("Char Greater Than", "'>'", "char", '>')
|
||||
|
||||
# Brackets and braces
|
||||
self.make_success_test("Char Left Paren", "'('", "char", '(')
|
||||
self.make_success_test("Char Right Paren", "')'", "char", ')')
|
||||
self.make_success_test("Char Left Bracket", "'['", "char", '[')
|
||||
self.make_success_test("Char Right Bracket", "']'", "char", ']')
|
||||
self.make_success_test("Char Left Brace", "'{'", "char", '{')
|
||||
self.make_success_test("Char Right Brace", "'}'", "char", '}')
|
||||
|
||||
def generate_escape_sequence_tests(self):
|
||||
"""Generate tests for escape sequences."""
|
||||
# Standard escape sequences
|
||||
for escape_name, escape_str, char_val in self.ESCAPE_SEQUENCES:
|
||||
name = f"Char Escape {escape_name}"
|
||||
code = f"'{escape_str}'"
|
||||
self.make_success_test(name, code, "char", char_val)
|
||||
|
||||
def generate_hexadecimal_escape_tests(self):
|
||||
"""Generate tests for hexadecimal escape sequences."""
|
||||
if self.ENABLE_CHAR_HEX_ESCAPE:
|
||||
# Additional specific hex escapes
|
||||
self.make_success_test("Char Hex Lowercase A", "'\\\\x61'", "char", 'a')
|
||||
self.make_success_test("Char Hex Uppercase A", "'\\\\x41'", "char", 'A')
|
||||
self.make_success_test("Char Hex Space", "'\\\\x20'", "char", ' ')
|
||||
self.make_success_test("Char Hex Tab", "'\\\\x09'", "char", '\t')
|
||||
self.make_success_test("Char Hex Newline", "'\\\\x0A'", "char", '\n')
|
||||
self.make_success_test("Char Hex Max ASCII", "'\\\\x7F'", "char", '\x7F')
|
||||
|
||||
def generate_unicode_escape_tests(self):
|
||||
"""Generate tests for Unicode escape sequences."""
|
||||
for escape_str, char_val in self.UNICODE_ESCAPES.items():
|
||||
name = f"Char Unicode {escape_str}"
|
||||
code = f"'{escape_str}'"
|
||||
self.make_success_test(name, code, "char", char_val)
|
||||
|
||||
# Additional Unicode tests
|
||||
self.make_success_test("Char Unicode Smiley", "'\\u{1F600}'", "char", '😀')
|
||||
self.make_success_test("Char Unicode Heart", "'\\u{2764}'", "char", '❤')
|
||||
self.make_success_test("Char Unicode Star", "'\\u{2B50}'", "char", '⭐')
|
||||
self.make_success_test("Char Unicode Greek Alpha", "'\\u{03B1}'", "char", 'α')
|
||||
self.make_success_test("Char Unicode Greek Beta", "'\\u{03B2}'", "char", 'β')
|
||||
self.make_success_test("Char Unicode Chinese", "'\\u{4E2D}'", "char", '中')
|
||||
self.make_success_test("Char Unicode Arabic", "'\\u{0639}'", "char", 'ع')
|
||||
self.make_success_test("Char Unicode Cyrillic", "'\\u{0410}'", "char", 'А')
|
||||
|
||||
def generate_unicode_direct_tests(self):
|
||||
"""Generate tests for direct Unicode characters."""
|
||||
# Emoji
|
||||
self.make_success_test("Char Direct Emoji Smiley", "'😀'", "char", '😀')
|
||||
self.make_success_test("Char Direct Emoji Heart", "'❤'", "char", '❤')
|
||||
self.make_success_test("Char Direct Emoji Star", "'⭐'", "char", '⭐')
|
||||
self.make_success_test("Char Direct Emoji Thumbs Up", "'👍'", "char", '👍')
|
||||
|
||||
# Greek letters
|
||||
self.make_success_test("Char Direct Greek Alpha", "'α'", "char", 'α')
|
||||
self.make_success_test("Char Direct Greek Beta", "'β'", "char", 'β')
|
||||
self.make_success_test("Char Direct Greek Pi", "'π'", "char", 'π')
|
||||
self.make_success_test("Char Direct Greek Omega", "'Ω'", "char", 'Ω')
|
||||
|
||||
# Chinese characters
|
||||
self.make_success_test("Char Direct Chinese Middle", "'中'", "char", '中')
|
||||
self.make_success_test("Char Direct Chinese Character", "'文'", "char", '文')
|
||||
|
||||
# Arabic
|
||||
self.make_success_test("Char Direct Arabic Letter", "'ع'", "char", 'ع')
|
||||
|
||||
# Cyrillic
|
||||
self.make_success_test("Char Direct Cyrillic A", "'А'", "char", 'А')
|
||||
self.make_success_test("Char Direct Cyrillic Ya", "'Я'", "char", 'Я')
|
||||
|
||||
# Mathematical symbols
|
||||
self.make_success_test("Char Direct Infinity", "'∞'", "char", '∞')
|
||||
self.make_success_test("Char Direct Sum", "'∑'", "char", '∑')
|
||||
self.make_success_test("Char Direct Integral", "'∫'", "char", '∫')
|
||||
|
||||
def generate_whitespace_tests(self):
|
||||
"""Generate tests with whitespace around character literals."""
|
||||
self.make_success_test("Char With Leading Whitespace", " 'A'", "char", 'A')
|
||||
self.make_success_test("Char With Trailing Whitespace", "'A' ", "char", 'A')
|
||||
self.make_success_test("Char With Both Whitespace", " 'A' ", "char", 'A')
|
||||
self.make_success_test("Char Tab Before", "\\t'B'", "char", 'B')
|
||||
self.make_success_test("Char Newline Before", "\\n'C'", "char", 'C')
|
||||
|
||||
def generate_error_tests(self):
|
||||
"""Generate error test cases."""
|
||||
# Empty character literal
|
||||
self.make_error_test("Char Empty Literal",
|
||||
"''",
|
||||
"Invalid character literal: empty character literal.")
|
||||
|
||||
# Multiple characters (no escape)
|
||||
self.make_error_test("Char Multiple Characters",
|
||||
"'AB'",
|
||||
"Invalid character literal: unexpected 'B' in character.")
|
||||
|
||||
# Unclosed quote
|
||||
self.make_error_test("Char Unclosed Quote",
|
||||
"'A",
|
||||
"Invalid character literal: unclosed character literal.")
|
||||
|
||||
# Unescaped newline
|
||||
self.make_error_test("Char Unescaped Newline",
|
||||
"'\\n'",
|
||||
"Invalid character literal: unclosed character literal.")
|
||||
|
||||
# Invalid escape sequence
|
||||
self.make_error_test("Char Invalid Escape",
|
||||
"'\\\\q'",
|
||||
"Invalid character literal: unknown escape sequence '\\\\q'.")
|
||||
|
||||
# Invalid hex escape (not 2 digits)
|
||||
if self.ENABLE_CHAR_HEX_ESCAPE:
|
||||
self.make_error_test("Char Hex Escape Too Short",
|
||||
"'\\\\x4'",
|
||||
"Invalid character literal: hexadecimal escape must have exactly 2 digits.")
|
||||
|
||||
self.make_error_test("Char Hex Escape Too Long",
|
||||
"'\\\\x414'",
|
||||
"Invalid character literal: hexadecimal escape must have exactly 2 digits.")
|
||||
|
||||
# Invalid hex digits
|
||||
self.make_error_test("Char Hex Invalid Digit",
|
||||
"'\\\\xGG'",
|
||||
"Invalid character literal: invalid hexadecimal digit 'G'.")
|
||||
|
||||
if self.ENABLE_UNICODE:
|
||||
# Invalid Unicode escape (no braces)
|
||||
self.make_error_test("Char Unicode No Braces",
|
||||
"'\\u1F600'",
|
||||
"Invalid character literal: Unicode escape must use braces \\u{...}.")
|
||||
|
||||
# Invalid Unicode escape (empty)
|
||||
self.make_error_test("Char Unicode Empty",
|
||||
"'\\u{}'",
|
||||
"Invalid character literal: empty Unicode escape sequence.")
|
||||
|
||||
# Invalid Unicode escape (too many digits)
|
||||
self.make_error_test("Char Unicode Too Many Digits",
|
||||
"'\\u{1234567}'",
|
||||
"Invalid character literal: Unicode escape sequence too long (max 6 hex digits).")
|
||||
|
||||
# Invalid Unicode escape (invalid code point)
|
||||
self.make_error_test("Char Unicode Invalid Code Point",
|
||||
"'\\u{D800}'",
|
||||
"Invalid character literal: invalid Unicode code point (surrogate range).")
|
||||
|
||||
self.make_error_test("Char Unicode Out Of Range",
|
||||
"'\\u{110000}'",
|
||||
"Invalid character literal: Unicode code point out of range (max 0x10FFFF).")
|
||||
|
||||
# Invalid Unicode escape (non-hex digits)
|
||||
self.make_error_test("Char Unicode Invalid Hex",
|
||||
"'\\u{GGGG}'",
|
||||
"Invalid character literal: invalid hexadecimal digit 'G' in Unicode escape.")
|
||||
|
||||
# Unclosed Unicode escape
|
||||
self.make_error_test("Char Unicode Unclosed",
|
||||
"'\\u{1F600'",
|
||||
"Invalid character literal: unclosed Unicode escape sequence.")
|
||||
|
||||
def generate_edge_case_tests(self):
|
||||
"""Generate edge case tests."""
|
||||
if self.ENABLE_CHAR_HEX_ESCAPE:
|
||||
# ASCII control characters
|
||||
self.make_success_test("Char ASCII Control SOH", "'\\\\x01'", "char", '\x01')
|
||||
self.make_success_test("Char ASCII Control BEL", "'\\\\x07'", "char", '\x07')
|
||||
self.make_success_test("Char ASCII Control ESC", "'\\\\x1B'", "char", '\x1B')
|
||||
self.make_success_test("Char ASCII Control DEL", "'\\\\x7F'", "char", '\x7F')
|
||||
|
||||
# Extended ASCII
|
||||
self.make_success_test("Char Extended ASCII Lower", "'\\\\x80'", "char", '\x80')
|
||||
self.make_success_test("Char Extended ASCII Upper", "'\\\\xFF'", "char", '\xFF')
|
||||
|
||||
if self.ENABLE_UNICODE:
|
||||
# Zero-width characters
|
||||
self.make_success_test("Char Zero Width Space", "'\\u{200B}'", "char", '\u200B')
|
||||
self.make_success_test("Char Zero Width Joiner", "'\\u{200D}'", "char", '\u200D')
|
||||
|
||||
# Right-to-left marks
|
||||
self.make_success_test("Char RTL Mark", "'\\u{200F}'", "char", '\u200F')
|
||||
|
||||
# Combining characters
|
||||
self.make_success_test("Char Combining Acute", "'\\u{0301}'", "char", '\u0301')
|
||||
|
||||
# Low Unicode values
|
||||
self.make_success_test("Char Unicode Zero", "'\\u{0}'", "char", '\0')
|
||||
self.make_success_test("Char Unicode One", "'\\u{1}'", "char", '\x01')
|
||||
|
||||
# High Unicode values (but valid)
|
||||
self.make_success_test("Char Unicode High Valid", "'\\u{10FFFF}'", "char", '\U0010FFFF')
|
||||
|
||||
def generate_case_sensitivity_tests(self):
|
||||
"""Generate tests for case sensitivity in escape sequences."""
|
||||
if self.ENABLE_CHAR_HEX_ESCAPE:
|
||||
# Hex escapes - lowercase x
|
||||
self.make_success_test("Char Hex Lowercase x", "'\\\\x41'", "char", 'A')
|
||||
|
||||
# Hex digits - both cases
|
||||
self.make_success_test("Char Hex Digits Uppercase", "'\\\\xFF'", "char", '\xFF')
|
||||
self.make_success_test("Char Hex Digits Lowercase", "'\\\\xff'", "char", '\xff')
|
||||
self.make_success_test("Char Hex Digits Mixed", "'\\\\xAb'", "char", '\xAB')
|
||||
|
||||
if self.ENABLE_UNICODE:
|
||||
# Unicode escapes - lowercase u
|
||||
self.make_success_test("Char Unicode Lowercase u", "'\\u{41}'", "char", 'A')
|
||||
|
||||
# Unicode hex digits - both cases
|
||||
self.make_success_test("Char Unicode Hex Uppercase", "'\\u{1F600}'", "char", '😀')
|
||||
self.make_success_test("Char Unicode Hex Lowercase", "'\\u{1f600}'", "char", '😀')
|
||||
self.make_success_test("Char Unicode Hex Mixed", "'\\u{1F60a}'", "char", '😊')
|
||||
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""Generate all character literal test cases."""
|
||||
# Basic tests
|
||||
self.generate_basic_tests()
|
||||
|
||||
# Escape sequences
|
||||
self.generate_escape_sequence_tests()
|
||||
|
||||
# Hexadecimal escapes
|
||||
self.generate_hexadecimal_escape_tests()
|
||||
|
||||
if self.ENABLE_UNICODE:
|
||||
# Unicode escapes
|
||||
self.generate_unicode_escape_tests()
|
||||
|
||||
# Direct Unicode characters
|
||||
self.generate_unicode_direct_tests()
|
||||
|
||||
# Whitespace handling
|
||||
self.generate_whitespace_tests()
|
||||
|
||||
# Error cases
|
||||
self.generate_error_tests()
|
||||
|
||||
# Edge cases
|
||||
self.generate_edge_case_tests()
|
||||
|
||||
# Case sensitivity
|
||||
self.generate_case_sensitivity_tests()
|
||||
|
||||
return self.get_tests()
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
from typing import List, Dict, Any
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
class FloatTestGenerator(BaseTestGenerator):
|
||||
"""Generate test cases for floating point literals."""
|
||||
|
||||
# Special float values
|
||||
SPECIAL_VALUES = {
|
||||
'f32': {
|
||||
'min': -3.4028235e38,
|
||||
'max': 3.4028235e38,
|
||||
'min_positive': 1.1754944e-38,
|
||||
'epsilon': 1.1920929e-7,
|
||||
},
|
||||
'f64': {
|
||||
'min': -1.7976931348623157e308,
|
||||
'max': 1.7976931348623157e308,
|
||||
'min_positive': 2.2250738585072014e-308,
|
||||
'epsilon': 2.220446049250313e-16,
|
||||
}
|
||||
}
|
||||
|
||||
def generate_basic_tests(self):
|
||||
"""Generate basic test cases."""
|
||||
# Simple default floats (f64)
|
||||
self.make_success_test("Float Default Simple", "3.14", "f64", 3.14)
|
||||
self.make_success_test("Float Default Zero", "0.0", "f64", 0.0)
|
||||
self.make_success_test("Float Default Negative", "-2.5", "f64", -2.5)
|
||||
self.make_success_test("Float Default One", "1.0", "f64", 1.0)
|
||||
|
||||
# Simple with type annotation
|
||||
self.make_success_test("Float f32 Simple", "3.14:f32", "f32", 3.14)
|
||||
self.make_success_test("Float f64 Simple", "2.718:f64", "f64", 2.718)
|
||||
|
||||
def generate_format_tests(self):
|
||||
"""Generate tests for different float formats."""
|
||||
# Leading zeros
|
||||
self.make_success_test("Float Default Leading Zeros", "00042.5", "f64", 42.5)
|
||||
self.make_success_test("Float Default Leading Zero Decimal", "0.5", "f64", 0.5)
|
||||
|
||||
# Trailing zeros
|
||||
self.make_success_test("Float Default Trailing Zeros", "3.1400", "f64", 3.14)
|
||||
|
||||
# No leading digit
|
||||
self.make_success_test("Float Default No Leading Digit", ".5", "f64", 0.5)
|
||||
self.make_success_test("Float Default No Leading Digit Negative", "-.25", "f64", -0.25)
|
||||
|
||||
# No trailing digits
|
||||
self.make_success_test("Float Default No Trailing Digits", "42.", "f64", 42.0)
|
||||
self.make_success_test("Float Default No Trailing Digits Negative", "-7.", "f64", -7.0)
|
||||
|
||||
if self.ENABLE_EXPONENTIAL_LITERALS:
|
||||
# Scientific notation
|
||||
self.make_success_test("Float Default Scientific Positive Exp", "1.5e10", "f64", 1.5e10)
|
||||
self.make_success_test("Float Default Scientific Negative Exp", "2.5e-5", "f64", 2.5e-5)
|
||||
self.make_success_test("Float Default Scientific Capital E", "3.14E8", "f64", 3.14e8)
|
||||
self.make_success_test("Float Default Scientific Plus Sign", "1.0e+3", "f64", 1000.0)
|
||||
|
||||
# Very small numbers
|
||||
self.make_success_test("Float Default Very Small", "0.000001", "f64", 0.000001)
|
||||
if self.ENABLE_EXPONENTIAL_LITERALS:
|
||||
self.make_success_test("Float Default Scientific Very Small", "1.0e-20", "f64", 1.0e-20)
|
||||
|
||||
# Very large numbers
|
||||
self.make_success_test("Float Default Very Large", "1000000.0", "f64", 1000000.0)
|
||||
if self.ENABLE_EXPONENTIAL_LITERALS:
|
||||
self.make_success_test("Float Default Scientific Very Large", "1.0e20", "f64", 1.0e20)
|
||||
|
||||
def generate_underscore_tests(self):
|
||||
"""Generate tests for underscores in floats."""
|
||||
# Underscores in integer part
|
||||
self.make_success_test("Float Default Underscore Integer Part",
|
||||
"1_000_000.5", "f64", 1000000.5)
|
||||
|
||||
# Underscores in decimal part
|
||||
self.make_success_test("Float Default Underscore Decimal Part",
|
||||
"3.141_592_653", "f64", 3.141592653)
|
||||
|
||||
# Underscores in both parts
|
||||
self.make_success_test("Float Default Underscore Both Parts",
|
||||
"1_234.567_89", "f64", 1234.56789)
|
||||
|
||||
if self.ENABLE_EXPONENTIAL_LITERALS:
|
||||
# Underscores in scientific notation
|
||||
self.make_success_test("Float Default Underscore Scientific Mantissa",
|
||||
"1_000.5e10", "f64", 1000.5e10)
|
||||
self.make_success_test("Float Default Underscore Scientific Exponent",
|
||||
"1.5e1_0", "f64", 1.5e10)
|
||||
|
||||
# Trailing underscore
|
||||
self.make_success_test("Float Default Underscore Trailing", "42.5_", "f64", 42.5)
|
||||
|
||||
# Double underscore
|
||||
self.make_success_test("Float Default Underscore Double", "4__2.5", "f64", 42.5)
|
||||
|
||||
# With type annotation
|
||||
self.make_success_test("Float f32 With Underscores",
|
||||
"1_234.567_89:f32", "f32", 1234.56789)
|
||||
|
||||
def generate_special_value_tests(self):
|
||||
"""Generate tests for special float values."""
|
||||
# Infinity
|
||||
self.make_success_test("Float Default Positive Infinity", "inf", "f64", float('inf'))
|
||||
self.make_success_test("Float Default Negative Infinity", "-inf", "f64", float('-inf'))
|
||||
self.make_success_test("Float f32 Positive Infinity", "inf:f32", "f32", float('inf'))
|
||||
self.make_success_test("Float f32 Negative Infinity", "-inf:f32", "f32", float('-inf'))
|
||||
|
||||
# NaN
|
||||
self.make_success_test("Float Default NaN", "nan", "f64", float('nan'))
|
||||
self.make_success_test("Float f32 NaN", "nan:f32", "f32", float('nan'))
|
||||
|
||||
# Note: NaN comparison is special - NaN != NaN, so these tests may need
|
||||
# special handling in the test runner
|
||||
|
||||
def generate_edge_case_tests(self, type_name: str):
|
||||
"""Generate edge case tests for a specific float type."""
|
||||
values = self.SPECIAL_VALUES[type_name]
|
||||
|
||||
if self.ENABLE_EXPONENTIAL_LITERALS:
|
||||
# Maximum value
|
||||
self.make_success_test(f"Float {type_name} Max Value",
|
||||
f"{values['max']}:{type_name}", type_name, values['max'])
|
||||
|
||||
# Minimum value (most negative)
|
||||
self.make_success_test(f"Float {type_name} Min Value",
|
||||
f"{values['min']}:{type_name}", type_name, values['min'])
|
||||
|
||||
# Smallest positive normalized value
|
||||
self.make_success_test(f"Float {type_name} Min Positive",
|
||||
f"{values['min_positive']}:{type_name}",
|
||||
type_name, values['min_positive'])
|
||||
|
||||
# Machine epsilon
|
||||
self.make_success_test(f"Float {type_name} Epsilon",
|
||||
f"{values['epsilon']}:{type_name}",
|
||||
type_name, values['epsilon'])
|
||||
|
||||
# Near zero
|
||||
self.make_success_test(f"Float {type_name} Near Zero Positive",
|
||||
f"1e-30:{type_name}", type_name, 1e-30)
|
||||
self.make_success_test(f"Float {type_name} Near Zero Negative",
|
||||
f"-1e-30:{type_name}", type_name, -1e-30)
|
||||
|
||||
# Subnormal numbers
|
||||
if type_name == 'f64':
|
||||
self.make_success_test("Float f64 Subnormal",
|
||||
"1e-320:f64", "f64", 1e-320)
|
||||
elif type_name == 'f32':
|
||||
self.make_success_test("Float f32 Subnormal",
|
||||
"1e-40:f32", "f32", 1e-40)
|
||||
|
||||
def generate_overflow_tests(self):
|
||||
"""Generate overflow tests."""
|
||||
if self.ENABLE_EXPONENTIAL_LITERALS:
|
||||
# f32 overflow
|
||||
self.make_error_test("Float f32 Overflow Positive",
|
||||
"1e40:f32",
|
||||
"Float overflow: value exceeds range for f32.")
|
||||
self.make_error_test("Float f32 Overflow Negative",
|
||||
"-1e40:f32",
|
||||
"Float overflow: value exceeds range for f32.")
|
||||
|
||||
# f64 overflow (extremely large values)
|
||||
self.make_error_test("Float f64 Overflow Positive",
|
||||
"1e310:f64",
|
||||
"Float overflow: value exceeds range for f64.")
|
||||
self.make_error_test("Float f64 Overflow Negative",
|
||||
"-1e310:f64",
|
||||
"Float overflow: value exceeds range for f64.")
|
||||
|
||||
def generate_precision_tests(self):
|
||||
"""Generate tests for precision limits."""
|
||||
# f32 precision (~7 decimal digits)
|
||||
self.make_success_test("Float f32 Precision Limit",
|
||||
"1.2345678:f32", "f32", 1.2345678)
|
||||
self.make_success_test("Float f32 High Precision",
|
||||
"3.141592653589793:f32", "f32", 3.141592653589793)
|
||||
|
||||
# f64 precision (~15 decimal digits)
|
||||
self.make_success_test("Float f64 Precision Limit",
|
||||
"1.234567890123456:f64", "f64", 1.234567890123456)
|
||||
self.make_success_test("Float f64 High Precision",
|
||||
"3.141592653589793238:f64", "f64", 3.141592653589793238)
|
||||
|
||||
# Very close numbers
|
||||
self.make_success_test("Float f64 Close Numbers 1",
|
||||
"1.0000000000000001:f64", "f64", 1.0000000000000001)
|
||||
self.make_success_test("Float f64 Close Numbers 2",
|
||||
"1.0000000000000002:f64", "f64", 1.0000000000000002)
|
||||
|
||||
def generate_error_tests(self):
|
||||
"""Generate error tests."""
|
||||
# Invalid formats
|
||||
self.make_error_test("Float Invalid Multiple Decimal Points",
|
||||
"3.14.159",
|
||||
"Invalid float literal: unexpected '.' in float.")
|
||||
self.make_error_test("Float Invalid Characters",
|
||||
"3.1a4",
|
||||
"Invalid float literal: unexpected 'a' in float.")
|
||||
|
||||
if self.ENABLE_EXPONENTIAL_LITERALS:
|
||||
# Invalid scientific notation
|
||||
self.make_error_test("Float Invalid Scientific No Exponent",
|
||||
"3.14e",
|
||||
"Invalid float literal: missing exponent value.")
|
||||
self.make_error_test("Float Invalid Scientific Double E",
|
||||
"3.14e10e5",
|
||||
"Invalid float literal: unexpected 'e' in float.")
|
||||
self.make_error_test("Float Invalid Scientific Invalid Exponent",
|
||||
"3.14eX",
|
||||
"Invalid float literal: unexpected 'X' in float.")
|
||||
|
||||
# Invalid type annotations
|
||||
self.make_error_test("Float Invalid Type Annotation",
|
||||
"3.14:i32",
|
||||
"Invalid float type: must be of type 'f64' or 'f32'.")
|
||||
self.make_error_test("Float Invalid Type Name",
|
||||
"3.14:f16",
|
||||
"Invalid float type: must be of type 'f64' or 'f32'.")
|
||||
|
||||
# Comma separators not allowed
|
||||
self.make_error_test("Float Invalid Comma Separator",
|
||||
"1,234.56",
|
||||
"Invalid decimal literal: unexpected ',' in decimal integer.")
|
||||
|
||||
def generate_whitespace_tests(self):
|
||||
"""Generate tests with whitespace."""
|
||||
self.make_success_test("Float Default Leading Whitespace",
|
||||
" 3.14", "f64", 3.14)
|
||||
self.make_success_test("Float Default Trailing Whitespace",
|
||||
"3.14 ", "f64", 3.14)
|
||||
self.make_success_test("Float Default Both Whitespace",
|
||||
" 3.14 ", "f64", 3.14)
|
||||
self.make_success_test("Float f32 With Whitespace",
|
||||
" 2.718:f32 ", "f32", 2.718)
|
||||
|
||||
def generate_mathematical_constants_tests(self):
|
||||
"""Generate tests for common mathematical constants."""
|
||||
# Pi
|
||||
self.make_success_test("Float Default Pi Approximate",
|
||||
"3.141592653589793", "f64", 3.141592653589793)
|
||||
self.make_success_test("Float f32 Pi Approximate",
|
||||
"3.1415927:f32", "f32", 3.1415927)
|
||||
|
||||
# Euler's number
|
||||
self.make_success_test("Float Default Euler Approximate",
|
||||
"2.718281828459045", "f64", 2.718281828459045)
|
||||
self.make_success_test("Float f32 Euler Approximate",
|
||||
"2.7182817:f32", "f32", 2.7182817)
|
||||
|
||||
# Golden ratio
|
||||
self.make_success_test("Float Default Golden Ratio",
|
||||
"1.618033988749895", "f64", 1.618033988749895)
|
||||
|
||||
# Square root of 2
|
||||
self.make_success_test("Float Default Sqrt2",
|
||||
"1.4142135623730951", "f64", 1.4142135623730951)
|
||||
|
||||
def generate_signed_zero_tests(self):
|
||||
"""Generate tests for signed zeros."""
|
||||
self.make_success_test("Float Default Positive Zero", "0.0", "f64", 0.0)
|
||||
self.make_success_test("Float Default Negative Zero", "-0.0", "f64", -0.0)
|
||||
self.make_success_test("Float f32 Positive Zero", "0.0:f32", "f32", 0.0)
|
||||
self.make_success_test("Float f32 Negative Zero", "-0.0:f32", "f32", -0.0)
|
||||
|
||||
# Note: In IEEE 754, +0.0 and -0.0 are distinct values but compare equal
|
||||
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""Generate all test cases."""
|
||||
# Basic tests
|
||||
self.generate_basic_tests()
|
||||
|
||||
# Format variations
|
||||
self.generate_format_tests()
|
||||
|
||||
# Underscores
|
||||
self.generate_underscore_tests()
|
||||
|
||||
# Special values (inf, nan)
|
||||
# self.generate_special_value_tests()
|
||||
|
||||
# Edge cases for each type
|
||||
for type_name in ['f32', 'f64']:
|
||||
self.generate_edge_case_tests(type_name)
|
||||
|
||||
# Overflow tests
|
||||
self.generate_overflow_tests()
|
||||
|
||||
# Precision tests
|
||||
self.generate_precision_tests()
|
||||
|
||||
# Error tests
|
||||
self.generate_error_tests()
|
||||
|
||||
# Whitespace tests
|
||||
self.generate_whitespace_tests()
|
||||
|
||||
# Mathematical constants
|
||||
self.generate_mathematical_constants_tests()
|
||||
|
||||
# Signed zeros
|
||||
self.generate_signed_zero_tests()
|
||||
|
||||
return self.tests
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
from typing import List, Dict, Any
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
class GeneralTestGenerator(BaseTestGenerator):
|
||||
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""Generate all test cases."""
|
||||
|
||||
self.add_test("Empty_Statement", "", [], [], [])
|
||||
|
||||
return self.tests
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
from typing import List, Dict, Any
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
|
||||
class IdentifierTestGenerator(BaseTestGenerator):
|
||||
"""Generate test cases for identifiers and identifier literals."""
|
||||
|
||||
# Reserved words that might be operators or keywords
|
||||
RESERVED_WORDS = [
|
||||
'if', 'while', 'for', 'match', 'break', 'continue',
|
||||
'fn', 'struct', 'union', 'enum', 'trait', 'impl', 'inher',
|
||||
'dup', 'drop', 'swap', 'over', 'rot', 'pick', 'roll', 'depth',
|
||||
]
|
||||
|
||||
def generate_basic_identifier_tests(self):
|
||||
"""Generate basic identifier tests."""
|
||||
# Simple identifiers
|
||||
self.make_success_test("Identifier Simple Lowercase", "hello",
|
||||
"identifier", "hello")
|
||||
self.make_success_test("Identifier Simple Uppercase", "HELLO",
|
||||
"identifier", "HELLO")
|
||||
self.make_success_test("Identifier Mixed Case", "HelloWorld",
|
||||
"identifier", "HelloWorld")
|
||||
self.make_success_test("Identifier Single Letter", "x",
|
||||
"identifier", "x")
|
||||
self.make_success_test("Identifier Single Letter Upper", "X",
|
||||
"identifier", "X")
|
||||
|
||||
# Identifiers with numbers
|
||||
self.make_success_test("Identifier With Numbers", "var123",
|
||||
"identifier", "var123")
|
||||
self.make_success_test("Identifier Numbers End", "myVar2",
|
||||
"identifier", "myVar2")
|
||||
self.make_success_test("Identifier Mixed Numbers", "a1b2c3",
|
||||
"identifier", "a1b2c3")
|
||||
|
||||
# Identifiers with underscores
|
||||
self.make_success_test("Identifier With Underscore", "hello_world",
|
||||
"identifier", "hello_world")
|
||||
self.make_success_test("Identifier Leading Underscore", "_private",
|
||||
"identifier", "_private")
|
||||
self.make_success_test("Identifier Multiple Underscores", "my_long_var_name",
|
||||
"identifier", "my_long_var_name")
|
||||
self.make_success_test("Identifier Double Underscore", "my__var",
|
||||
"identifier", "my__var")
|
||||
self.make_success_test("Identifier Trailing Underscore", "var_",
|
||||
"identifier", "var_")
|
||||
self.make_success_test("Identifier Only Underscores", "___",
|
||||
"identifier", "___")
|
||||
|
||||
# Snake case
|
||||
self.make_success_test("Identifier Snake Case", "my_variable_name",
|
||||
"identifier", "my_variable_name")
|
||||
|
||||
# Camel case
|
||||
self.make_success_test("Identifier Camel Case", "myVariableName",
|
||||
"identifier", "myVariableName")
|
||||
|
||||
# Pascal case
|
||||
self.make_success_test("Identifier Pascal Case", "MyClassName",
|
||||
"identifier", "MyClassName")
|
||||
|
||||
# All caps with underscores
|
||||
self.make_success_test("Identifier All Caps", "MY_CONSTANT",
|
||||
"identifier", "MY_CONSTANT")
|
||||
|
||||
# Others
|
||||
self.make_success_test("Identifier With Dash", "my-var",
|
||||
"identifier", "my-var")
|
||||
|
||||
def generate_identifier_literal_tests(self):
|
||||
"""Generate identifier literal tests (with :: prefix)."""
|
||||
# Simple identifier literals
|
||||
self.make_success_test("Identifier Literal Simple", "::hello",
|
||||
"identifier_literal", "hello")
|
||||
self.make_success_test("Identifier Literal Uppercase", "::Point",
|
||||
"identifier_literal", "Point")
|
||||
self.make_success_test("Identifier Literal Snake Case", "::my_var",
|
||||
"identifier_literal", "my_var")
|
||||
|
||||
# Type names
|
||||
self.make_success_test("Identifier Literal Type i64", "::i64",
|
||||
"identifier_literal", "i64")
|
||||
self.make_success_test("Identifier Literal Type String", "::String",
|
||||
"identifier_literal", "String")
|
||||
self.make_success_test("Identifier Literal Type Point", "::Point",
|
||||
"identifier_literal", "Point")
|
||||
|
||||
# Trait names
|
||||
self.make_success_test("Identifier Literal Trait Addable", "::Addable",
|
||||
"identifier_literal", "Addable")
|
||||
self.make_success_test("Identifier Literal Trait Drawable", "::Drawable",
|
||||
"identifier_literal", "Drawable")
|
||||
|
||||
# Field names
|
||||
self.make_success_test("Identifier Literal Field x", "::x",
|
||||
"identifier_literal", "x")
|
||||
self.make_success_test("Identifier Literal Field width", "::width",
|
||||
"identifier_literal", "width")
|
||||
|
||||
# With underscores
|
||||
self.make_success_test("Identifier Literal With Underscore", "::_private",
|
||||
"identifier_literal", "_private")
|
||||
self.make_success_test("Identifier Literal Multiple Underscores", "::my_long_name",
|
||||
"identifier_literal", "my_long_name")
|
||||
|
||||
# With numbers
|
||||
self.make_success_test("Identifier Literal With Numbers", "::var123",
|
||||
"identifier_literal", "var123")
|
||||
|
||||
def generate_whitespace_tests(self):
|
||||
"""Generate tests with whitespace around identifiers."""
|
||||
# Regular identifiers with whitespace
|
||||
self.make_success_test("Identifier Leading Whitespace", " hello",
|
||||
"identifier", "hello")
|
||||
self.make_success_test("Identifier Trailing Whitespace", "hello ",
|
||||
"identifier", "hello")
|
||||
self.make_success_test("Identifier Both Whitespace", " hello ",
|
||||
"identifier", "hello")
|
||||
self.make_success_test("Identifier Tab Before", "\\thello",
|
||||
"identifier", "hello")
|
||||
|
||||
# Identifier literals with whitespace
|
||||
self.make_success_test("Identifier Literal Leading Whitespace", " ::hello",
|
||||
"identifier_literal", "hello")
|
||||
self.make_success_test("Identifier Literal Trailing Whitespace", "::hello ",
|
||||
"identifier_literal", "hello")
|
||||
self.make_success_test("Identifier Literal Both Whitespace", " ::hello ",
|
||||
"identifier_literal", "hello")
|
||||
|
||||
def generate_long_identifier_tests(self):
|
||||
"""Generate tests for longer identifiers."""
|
||||
# Moderately long
|
||||
self.make_success_test("Identifier Moderate Length",
|
||||
"thisIsAReasonablyLongVariableName",
|
||||
"identifier", "thisIsAReasonablyLongVariableName")
|
||||
|
||||
# Very long
|
||||
long_name = "this_is_a_very_long_identifier_name_that_someone_might_use_for_some_reason"
|
||||
self.make_success_test("Identifier Very Long", long_name,
|
||||
"identifier", long_name)
|
||||
|
||||
# Long with numbers
|
||||
long_with_nums = "variable_with_many_numbers_123_456_789_000"
|
||||
self.make_success_test("Identifier Long With Numbers", long_with_nums,
|
||||
"identifier", long_with_nums)
|
||||
|
||||
def generate_error_tests(self):
|
||||
"""Generate error test cases for identifiers."""
|
||||
# Starting with number
|
||||
self.make_error_test("Identifier Starting With Number",
|
||||
"123abc",
|
||||
"Invalid decimal literal: unexpected 'a' in decimal integer.")
|
||||
|
||||
# Invalid characters
|
||||
self.make_success_test("Identifier With Octothorpe", "my#var",
|
||||
"identifier", "my")
|
||||
|
||||
# self.make_error_test("Identifier With Space",
|
||||
# "my var",
|
||||
# "Invalid identifier: whitespace not allowed in identifiers.")
|
||||
|
||||
self.make_error_test("Identifier With Colon",
|
||||
"my:var",
|
||||
"Invalid identifier: ':' is not allowed in identifiers.")
|
||||
|
||||
# Note: :: is allowed only as prefix for identifier literals
|
||||
self.make_error_test("Identifier Double Colon Inside",
|
||||
"my::var",
|
||||
"Invalid identifier: ':' is not allowed in identifiers.")
|
||||
|
||||
# Special characters
|
||||
# self.make_error_test("Identifier With At",
|
||||
# "@variable",
|
||||
# "Invalid identifier: '@' is not allowed in identifiers.")
|
||||
|
||||
# self.make_error_test("Identifier With Dollar",
|
||||
# "$variable",
|
||||
# "Invalid identifier: '$' is not allowed in identifiers.")
|
||||
|
||||
# self.make_error_test("Identifier With Percent",
|
||||
# "%variable",
|
||||
# "Invalid identifier: '%' is not allowed in identifiers.")
|
||||
|
||||
# Brackets not allowed
|
||||
# self.make_error_test("Identifier With Brackets",
|
||||
# "my[var]",
|
||||
# "Invalid identifier: brackets not allowed in identifiers.")
|
||||
|
||||
# self.make_error_test("Identifier With Braces",
|
||||
# "my{var}",
|
||||
# "Invalid identifier: braces not allowed in identifiers.")
|
||||
|
||||
# self.make_error_test("Identifier With Parens",
|
||||
# "my(var)",
|
||||
# "Invalid identifier: parentheses not allowed in identifiers.")
|
||||
|
||||
# Quotes not allowed
|
||||
# self.make_error_test("Identifier With Single Quote",
|
||||
# "my'var",
|
||||
# "Invalid identifier: quotes not allowed in identifiers.")
|
||||
|
||||
# self.make_error_test("Identifier With Double Quote",
|
||||
# 'my"var',
|
||||
# "Invalid identifier: quotes not allowed in identifiers.")
|
||||
|
||||
# Only numbers (not valid identifier)
|
||||
self.make_success_test("Identifier Only Numbers", "123", "i64", 123)
|
||||
|
||||
# Empty identifier literal
|
||||
self.make_error_test("Identifier Literal Empty",
|
||||
"::",
|
||||
"Invalid identifier literal: empty identifier after '::'.")
|
||||
|
||||
def generate_case_sensitivity_tests(self):
|
||||
"""Generate tests showing case sensitivity."""
|
||||
# These should all be different identifiers
|
||||
self.make_success_test("Identifier Case Lower", "variable",
|
||||
"identifier", "variable")
|
||||
self.make_success_test("Identifier Case Upper", "VARIABLE",
|
||||
"identifier", "VARIABLE")
|
||||
self.make_success_test("Identifier Case Mixed", "Variable",
|
||||
"identifier", "Variable")
|
||||
self.make_success_test("Identifier Case Camel", "variableName",
|
||||
"identifier", "variableName")
|
||||
self.make_success_test("Identifier Case Pascal", "VariableName",
|
||||
"identifier", "VariableName")
|
||||
|
||||
def generate_reserved_word_tests(self):
|
||||
"""Generate tests for words that might be reserved."""
|
||||
# Note: In the spec, these are treated as identifiers/operators
|
||||
# This tests that they're recognized correctly
|
||||
for word in self.RESERVED_WORDS:
|
||||
self.make_success_test(f"Identifier Reserved Word {word}",
|
||||
word, "identifier", word)
|
||||
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""Generate all identifier test cases."""
|
||||
# Basic identifiers
|
||||
self.generate_basic_identifier_tests()
|
||||
|
||||
# Identifier literals
|
||||
self.generate_identifier_literal_tests()
|
||||
|
||||
# Whitespace handling
|
||||
self.generate_whitespace_tests()
|
||||
|
||||
# Long identifiers
|
||||
self.generate_long_identifier_tests()
|
||||
|
||||
# Error cases
|
||||
self.generate_error_tests()
|
||||
|
||||
# Case sensitivity
|
||||
self.generate_case_sensitivity_tests()
|
||||
|
||||
# Reserved words
|
||||
self.generate_reserved_word_tests()
|
||||
|
||||
return self.get_tests()
|
||||
|
||||
|
||||
class BooleanTestGenerator(BaseTestGenerator):
|
||||
"""Generate test cases for boolean literals."""
|
||||
|
||||
def generate_basic_tests(self):
|
||||
"""Generate basic boolean literal tests."""
|
||||
# True
|
||||
self.make_success_test("Bool True", "true", "bool", True)
|
||||
|
||||
# False
|
||||
self.make_success_test("Bool False", "false", "bool", False)
|
||||
|
||||
def generate_whitespace_tests(self):
|
||||
"""Generate tests with whitespace around booleans."""
|
||||
# True with whitespace
|
||||
self.make_success_test("Bool True Leading Whitespace", " true",
|
||||
"bool", True)
|
||||
self.make_success_test("Bool True Trailing Whitespace", "true ",
|
||||
"bool", True)
|
||||
self.make_success_test("Bool True Both Whitespace", " true ",
|
||||
"bool", True)
|
||||
self.make_success_test("Bool True Tab Before", "\\ttrue",
|
||||
"bool", True)
|
||||
|
||||
# False with whitespace
|
||||
self.make_success_test("Bool False Leading Whitespace", " false",
|
||||
"bool", False)
|
||||
self.make_success_test("Bool False Trailing Whitespace", "false ",
|
||||
"bool", False)
|
||||
self.make_success_test("Bool False Both Whitespace", " false ",
|
||||
"bool", False)
|
||||
self.make_success_test("Bool False Tab Before", "\\tfalse",
|
||||
"bool", False)
|
||||
|
||||
def generate_error_tests(self):
|
||||
"""Generate error test cases for booleans."""
|
||||
# Capitalized (case sensitive)
|
||||
self.make_success_test("Bool True Capitalized", "True",
|
||||
"identifier", "True")
|
||||
|
||||
self.make_success_test("Bool False Capitalized", "False",
|
||||
"identifier", "False")
|
||||
|
||||
# All caps
|
||||
self.make_success_test("Bool True All Caps", "TRUE",
|
||||
"identifier", "TRUE")
|
||||
|
||||
self.make_success_test("Bool False All Caps", "FALSE",
|
||||
"identifier", "FALSE")
|
||||
|
||||
# Mixed case
|
||||
self.make_success_test("Bool True Mixed Case", "tRuE",
|
||||
"identifier", "tRuE")
|
||||
|
||||
self.make_success_test("Bool False Mixed Case", "fAlSe",
|
||||
"identifier", "fAlSe")
|
||||
|
||||
# Numeric representations
|
||||
self.make_success_test("Bool Numeric 1", "1", "i64", 1)
|
||||
|
||||
self.make_success_test("Bool Numeric 0", "0", "i64", 0)
|
||||
|
||||
# String representations
|
||||
if self.ENABLE_STRINGS:
|
||||
self.make_success_test("Bool String True", '"true"',
|
||||
"string", "true")
|
||||
|
||||
self.make_success_test("Bool String False", '"false"',
|
||||
"string", "false")
|
||||
|
||||
# Other languages
|
||||
self.make_success_test("Bool Yes", "yes",
|
||||
"identifier", "yes")
|
||||
|
||||
self.make_success_test("Bool No", "no",
|
||||
"identifier", "no")
|
||||
|
||||
# Typos
|
||||
self.make_success_test("Bool Typo Ture", "ture",
|
||||
"identifier", "ture")
|
||||
|
||||
self.make_success_test("Bool Typo Flase", "flase",
|
||||
"identifier", "flase")
|
||||
|
||||
def generate_multiple_bool_tests(self):
|
||||
"""Generate tests with multiple boolean values."""
|
||||
# Multiple values on stack
|
||||
self.make_multi_value_test("Bool Multiple True False",
|
||||
"true false",
|
||||
[("bool", True), ("bool", False)])
|
||||
|
||||
self.make_multi_value_test("Bool Multiple Same",
|
||||
"true true",
|
||||
[("bool", True), ("bool", True)])
|
||||
|
||||
self.make_multi_value_test("Bool Three Values",
|
||||
"true false true",
|
||||
[("bool", True), ("bool", False), ("bool", True)])
|
||||
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""Generate all boolean test cases."""
|
||||
# Basic tests
|
||||
self.generate_basic_tests()
|
||||
|
||||
# Whitespace handling
|
||||
self.generate_whitespace_tests()
|
||||
|
||||
# Error cases
|
||||
self.generate_error_tests()
|
||||
|
||||
# Multiple booleans
|
||||
self.generate_multiple_bool_tests()
|
||||
|
||||
return self.get_tests()
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
from typing import List, Dict, Any
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
class IntegerTestGenerator(BaseTestGenerator):
|
||||
"""Generate test cases for integer literals."""
|
||||
|
||||
# Type ranges
|
||||
TYPE_RANGES = {
|
||||
'i8': (-128, 127),
|
||||
'i16': (-32768, 32767),
|
||||
'i32': (-2147483648, 2147483647),
|
||||
'i64': (-9223372036854775808, 9223372036854775807),
|
||||
'u8': (0, 255),
|
||||
'u16': (0, 65535),
|
||||
'u32': (0, 4294967295),
|
||||
'u64': (0, 18446744073709551615),
|
||||
}
|
||||
|
||||
TYPE_VALUES = {
|
||||
'i8': (-128, 127),
|
||||
'i16': (-32768, 32767),
|
||||
'i32': (-2147483648, 2147483647),
|
||||
'i64': ("INT64_MIN", 9223372036854775807),
|
||||
'u8': (0, 255),
|
||||
'u16': (0, 65535),
|
||||
'u32': (0, 4294967295),
|
||||
'u64': (0, "UINT64_MAX"),
|
||||
}
|
||||
|
||||
def generate_basic_tests(self):
|
||||
"""Generate basic test cases."""
|
||||
|
||||
# Simple default integers
|
||||
self.make_success_test("Integer Default Decimal 0", "0", "i64", 0)
|
||||
self.make_success_test("Integer Default Decimal -1", "-1", "i64", -1)
|
||||
self.make_success_test("Integer Default Decimal 42", "42", "i64", 42)
|
||||
self.make_success_test("Integer Default Decimal Leading Zeros", "00042", "i64", 42)
|
||||
|
||||
def generate_default_base_tests(self):
|
||||
"""Generate tests for default type with different bases."""
|
||||
# Hexadecimal
|
||||
self.make_success_test("Integer Default Hex 0xFF", "0xFF", "i64", 255)
|
||||
self.make_success_test("Integer Default Hex 0xdeadbeef", "0xdeadbeef", "i64", 3735928559)
|
||||
self.make_success_test("Integer Default Hex Max", "0x7FFFFFFFFFFFFFFF", "i64", 9223372036854775807)
|
||||
|
||||
# Binary
|
||||
self.make_success_test("Integer Default Binary 0b1010", "0b1010", "i64", 10)
|
||||
self.make_success_test("Integer Default Binary All Ones", "0b1111111111111111", "i64", 65535)
|
||||
|
||||
# Octal
|
||||
self.make_success_test("Integer Default Octal 0o755", "0o755", "i64", 493)
|
||||
self.make_success_test("Integer Default Octal Max Three Digits", "0o777", "i64", 511)
|
||||
|
||||
def generate_default_edge_cases(self):
|
||||
"""Generate edge case tests for default type."""
|
||||
# Min/max values
|
||||
self.make_success_test("Integer Default Decimal Max i64",
|
||||
"9223372036854775807", "i64", 9223372036854775807)
|
||||
self.make_success_test("Integer Default Decimal Min i64",
|
||||
"-9223372036854775808", "i64", "INT64_MIN")
|
||||
|
||||
# Underscores
|
||||
self.make_success_test("Integer Default Decimal with Underscore",
|
||||
"1_000_000", "i64", 1000000)
|
||||
self.make_success_test("Integer Default Underscore End", "42_", "i64", 42)
|
||||
self.make_success_test("Integer Default Underscore Double", "4__2", "i64", 42)
|
||||
|
||||
# Whitespace
|
||||
self.make_success_test("Integer Default Whitespace", " 42 ", "i64", 42)
|
||||
|
||||
# Zeros in different bases
|
||||
self.make_success_test("Integer Default Hex Zero", "0x0", "i64", 0)
|
||||
self.make_success_test("Integer Default Binary Zero", "0b0", "i64", 0)
|
||||
self.make_success_test("Integer Default Octal Zero", "0o0", "i64", 0)
|
||||
|
||||
def generate_default_error_tests(self):
|
||||
"""Generate error tests for default type."""
|
||||
self.make_error_test("Integer Default Decimal with Commas Invalid",
|
||||
"1,000,000",
|
||||
"Invalid decimal literal: unexpected ',' in decimal integer.")
|
||||
self.make_error_test("Integer Default Invalid Characters",
|
||||
"12a3",
|
||||
"Invalid decimal literal: unexpected 'a' in decimal integer.")
|
||||
self.make_error_test("Integer Default Invalid Prefix",
|
||||
"0b2",
|
||||
"Invalid binary literal: unexpected '2' in binary integer.")
|
||||
|
||||
def generate_typed_tests(self, type_name: str):
|
||||
"""Generate tests for a specific type across all bases."""
|
||||
min_range, max_range = self.TYPE_RANGES[type_name]
|
||||
min_value, max_value = self.TYPE_VALUES[type_name]
|
||||
is_unsigned = type_name.startswith('u')
|
||||
|
||||
# Basic decimal
|
||||
test_val = 42 if max_range >= 42 else max_range
|
||||
self.make_success_test(f"Integer {type_name} Decimal Positive",
|
||||
f"{test_val}:{type_name}", type_name, test_val)
|
||||
|
||||
# Zero
|
||||
self.make_success_test(f"Integer {type_name} Zero",
|
||||
f"0:{type_name}", type_name, 0)
|
||||
|
||||
# Negative (only for signed types)
|
||||
if not is_unsigned:
|
||||
neg_val = -100 if min_range <= -100 else min_range
|
||||
self.make_success_test(f"Integer {type_name} Decimal Negative",
|
||||
f"{neg_val}:{type_name}", type_name, neg_val)
|
||||
|
||||
# Hexadecimal
|
||||
hex_val = min(255, max_range)
|
||||
self.make_success_test(f"Integer {type_name} Hex",
|
||||
f"0x{hex_val:X}:{type_name}", type_name, hex_val)
|
||||
|
||||
# Binary
|
||||
bin_val = min(15, max_range)
|
||||
self.make_success_test(f"Integer {type_name} Binary",
|
||||
f"0b{bin_val:b}:{type_name}", type_name, bin_val)
|
||||
|
||||
# Octal
|
||||
oct_val = min(63, max_range)
|
||||
self.make_success_test(f"Integer {type_name} Octal",
|
||||
f"0o{oct_val:o}:{type_name}", type_name, oct_val)
|
||||
|
||||
# Max value
|
||||
self.make_success_test(f"Integer {type_name} Max Value",
|
||||
f"{max_range}:{type_name}", type_name, max_value)
|
||||
|
||||
# Min value
|
||||
self.make_success_test(f"Integer {type_name} Min Value",
|
||||
f"{min_range}:{type_name}", type_name, min_value)
|
||||
|
||||
# Overflow
|
||||
overflow_val = max_range + 1
|
||||
self.make_error_test(f"Integer {type_name} Overflow",
|
||||
f"{overflow_val}:{type_name}",
|
||||
f"Integer overflow: value exceeds range for {type_name}.")
|
||||
|
||||
# Underflow
|
||||
underflow_val = min_range - 1
|
||||
self.make_error_test(f"Integer {type_name} Underflow",
|
||||
f"{underflow_val}:{type_name}",
|
||||
f"Integer overflow: value exceeds range for {type_name}.")
|
||||
|
||||
# Underscores with type annotation
|
||||
if max_range >= 1000000:
|
||||
self.make_success_test(f"Integer {type_name} With Underscores",
|
||||
f"1_000_000:{type_name}", type_name, 1000000)
|
||||
|
||||
def generate_special_typed_tests(self):
|
||||
"""Generate special tests for specific types."""
|
||||
|
||||
# Special values for specific types
|
||||
self.make_success_test("Integer i8 Hex Max", "0x7F:i8", "i8", 127)
|
||||
self.make_success_test("Integer i8 Binary Max", "0b01111111:i8", "i8", 127)
|
||||
self.make_success_test("Integer i8 Octal Max", "0o177:i8", "i8", 127)
|
||||
self.make_success_test("Integer i8 Negative Hex", "-0x80:i8", "i8", -128)
|
||||
|
||||
self.make_success_test("Integer u8 Hex Max", "0xFF:u8", "u8", 255)
|
||||
self.make_success_test("Integer u8 Binary Max", "0b11111111:u8", "u8", 255)
|
||||
self.make_success_test("Integer u8 Octal Max", "0o377:u8", "u8", 255)
|
||||
|
||||
self.make_success_test("Integer i16 Hex Sample", "0x1234:i16", "i16", 4660)
|
||||
self.make_success_test("Integer i16 Binary Sample",
|
||||
"0b1111111100000000:i16", "i16", -256)
|
||||
self.make_success_test("Integer i16 Octal Sample", "0o1234:i16", "i16", 668)
|
||||
|
||||
self.make_success_test("Integer u16 Hex Max", "0xFFFF:u16", "u16", 65535)
|
||||
self.make_success_test("Integer u16 Binary Max",
|
||||
"0b1111111111111111:u16", "u16", 65535)
|
||||
self.make_success_test("Integer u16 Octal Max", "0o177777:u16", "u16", 65535)
|
||||
self.make_success_test("Integer u16 Decimal Mid", "50000:u16", "u16", 50000)
|
||||
|
||||
self.make_success_test("Integer i32 Hex Sample", "0xABCD:i32", "i32", 43981)
|
||||
self.make_success_test("Integer i32 Binary Sample",
|
||||
"0b11110000:i32", "i32", 240)
|
||||
|
||||
self.make_success_test("Integer u32 Hex Max", "0xFFFFFFFF:u32", "u32", 4294967295)
|
||||
self.make_success_test("Integer u32 Binary Sample",
|
||||
"0b11111111000000001111111100000000:u32",
|
||||
"u32", 4278255360)
|
||||
self.make_success_test("Integer u32 Octal Max",
|
||||
"0o37777777777:u32", "u32", 4294967295)
|
||||
self.make_success_test("Integer u32 Decimal Mid", "1000000:u32", "u32", 1000000)
|
||||
|
||||
self.make_success_test("Integer i64 Decimal Positive 42", "42:i64", "i64", 42)
|
||||
self.make_success_test("Integer i64 Hex 0xFF", "0xFF:i64", "i64", 255)
|
||||
self.make_success_test("Integer i64 Binary 0b1010", "0b1010:i64", "i64", 10)
|
||||
self.make_success_test("Integer i64 Octal 0o755", "0o755:i64", "i64", 493)
|
||||
|
||||
self.make_success_test("Integer u64 Hex Max",
|
||||
"0xFFFFFFFFFFFFFFFF:u64",
|
||||
"u64", "UINT64_MAX")
|
||||
self.make_success_test("Integer u64 Binary Sample",
|
||||
"0b1010101010101010:u64", "u64", 43690)
|
||||
self.make_success_test("Integer u64 Octal Sample", "0o7777:u64", "u64", 4095)
|
||||
self.make_success_test("Integer u64 Decimal", "42:u64", "u64", 42)
|
||||
|
||||
def generate_underscore_tests(self):
|
||||
"""Generate tests for underscores in different bases."""
|
||||
self.make_success_test("Integer Hex With Underscores",
|
||||
"0xDEAD_BEEF:i64", "i64", 3735928559)
|
||||
self.make_success_test("Integer Binary With Underscores",
|
||||
"0b1111_0000_1010_0101:i32", "i32", 61605)
|
||||
self.make_success_test("Integer Octal With Underscores",
|
||||
"0o7_7_7:i16", "i16", 511)
|
||||
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""Generate all test cases."""
|
||||
# Basic tests
|
||||
self.generate_basic_tests()
|
||||
|
||||
# Default type (i64) comprehensive tests
|
||||
self.generate_default_base_tests()
|
||||
self.generate_default_edge_cases()
|
||||
self.generate_default_error_tests()
|
||||
|
||||
# Tests for each specific type
|
||||
for type_name in ['i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64']:
|
||||
self.generate_typed_tests(type_name)
|
||||
self.generate_special_typed_tests()
|
||||
|
||||
# Additional edge cases
|
||||
self.generate_underscore_tests()
|
||||
|
||||
return self.tests
|
||||
|
|
@ -0,0 +1,477 @@
|
|||
from typing import List, Dict, Any
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
|
||||
class StringTestGenerator(BaseTestGenerator):
|
||||
"""Generate test cases for string literals."""
|
||||
|
||||
# Common escape sequences
|
||||
ESCAPE_SEQUENCES = {
|
||||
'\\n': '\n', # Newline
|
||||
'\\r': '\r', # Carriage return
|
||||
'\\t': '\t', # Tab
|
||||
'\\\\': '\\', # Backslash
|
||||
'\\"': '"', # Double quote
|
||||
"\\'": "'", # Single quote
|
||||
'\\0': '\0', # Null character
|
||||
}
|
||||
|
||||
def generate_basic_tests(self):
|
||||
"""Generate basic string literal tests."""
|
||||
# Empty string
|
||||
self.make_success_test("String Empty", '""', "string", "")
|
||||
|
||||
# Simple strings
|
||||
self.make_success_test("String Simple", '"hello"', "string", "hello")
|
||||
self.make_success_test("String With Space", '"hello world"', "string", "hello world")
|
||||
self.make_success_test("String Single Char", '"A"', "string", "A")
|
||||
|
||||
# Multiple words
|
||||
self.make_success_test("String Multiple Words",
|
||||
'"The quick brown fox"', "string", "The quick brown fox")
|
||||
|
||||
# Numbers in strings
|
||||
self.make_success_test("String With Numbers", '"abc123"', "string", "abc123")
|
||||
self.make_success_test("String Only Numbers", '"12345"', "string", "12345")
|
||||
|
||||
# Mixed case
|
||||
self.make_success_test("String Mixed Case", '"HeLLo WoRLd"', "string", "HeLLo WoRLd")
|
||||
|
||||
# Special characters
|
||||
self.make_success_test("String With Punctuation",
|
||||
'"Hello, World!"', "string", "Hello, World!")
|
||||
self.make_success_test("String With Symbols",
|
||||
'"@#$%^&*()"', "string", "@#$%^&*()")
|
||||
|
||||
# Spaces
|
||||
self.make_success_test("String Multiple Spaces",
|
||||
'"hello world"', "string", "hello world")
|
||||
self.make_success_test("String Leading Space", '" hello"', "string", " hello")
|
||||
self.make_success_test("String Trailing Space", '"hello "', "string", "hello ")
|
||||
self.make_success_test("String Only Spaces", '" "', "string", " ")
|
||||
|
||||
def generate_escape_sequence_tests(self):
|
||||
"""Generate tests for escape sequences."""
|
||||
# Individual escape sequences
|
||||
self.make_success_test("String Newline", '"hello\\nworld"',
|
||||
"string", "hello\\nworld")
|
||||
self.make_success_test("String Tab", '"hello\\tworld"',
|
||||
"string", "hello\\tworld")
|
||||
self.make_success_test("String Carriage Return", '"hello\\rworld"',
|
||||
"string", "hello\\rworld")
|
||||
self.make_success_test("String Backslash", '"hello\\\\world"',
|
||||
"string", "hello\\\\world")
|
||||
self.make_success_test("String Double Quote", '"say \\\\"hello\\\\""',
|
||||
"string", 'say \\"hello\\"')
|
||||
self.make_success_test("String Single Quote", '"it\\\'s"',
|
||||
"string", "it's")
|
||||
self.make_success_test("String Null Char", '"hello\\0world"',
|
||||
"string", "hello\\0world")
|
||||
|
||||
# Multiple escape sequences
|
||||
self.make_success_test("String Multiple Escapes",
|
||||
'"line1\\nline2\\nline3"',
|
||||
"string", "line1\\nline2\\nline3")
|
||||
self.make_success_test("String Mixed Escapes",
|
||||
'"tab\\there\\nnewline\\\\backslash"',
|
||||
"string", "tab\\there\\nnewline\\\\backslash")
|
||||
|
||||
# Escape at start/end
|
||||
self.make_success_test("String Escape At Start", '"\\nhello"',
|
||||
"string", "\\nhello")
|
||||
self.make_success_test("String Escape At End", '"hello\\n"',
|
||||
"string", "hello\\n")
|
||||
|
||||
# Consecutive escapes
|
||||
self.make_success_test("String Consecutive Escapes", '"\\n\\n\\n"',
|
||||
"string", "\\n\\n\\n")
|
||||
self.make_success_test("String All Escapes", '"\\n\\r\\t\\\\"\\\'\\0"',
|
||||
"string", "\\n\\r\\t\\\\\\\"\\'\\0")
|
||||
|
||||
def generate_hexadecimal_escape_tests(self):
|
||||
"""Generate tests for hexadecimal escape sequences."""
|
||||
# Basic hex escapes
|
||||
self.make_success_test("String Hex Letter A", '"\\x41"', "string", "A")
|
||||
self.make_success_test("String Hex Letter a", '"\\x61"', "string", "a")
|
||||
self.make_success_test("String Hex Space", '"\\x20"', "string", " ")
|
||||
self.make_success_test("String Hex Tab", '"\\x09"', "string", "\\t")
|
||||
self.make_success_test("String Hex Newline", '"\\x0A"', "string", "\\n")
|
||||
|
||||
# Multiple hex escapes
|
||||
self.make_success_test("String Multiple Hex", '"\\x48\\x65\\x6C\\x6C\\x6F"',
|
||||
"string", "Hello")
|
||||
|
||||
# Hex with regular text
|
||||
self.make_success_test("String Hex Mixed", '"Hello\\x20World"',
|
||||
"string", "Hello World")
|
||||
|
||||
# Extended ASCII
|
||||
self.make_success_test("String Hex Extended ASCII", '"\\xA9\\xAE"',
|
||||
"string", "\xA9\xAE")
|
||||
|
||||
# Case variations
|
||||
self.make_success_test("String Hex Uppercase", '"\\xFF"', "string", "\\xFF")
|
||||
self.make_success_test("String Hex Lowercase", '"\\xff"', "string", "\\xff")
|
||||
self.make_success_test("String Hex Mixed Case", '"\\xAb"', "string", "\\xAb")
|
||||
|
||||
# All hex values
|
||||
self.make_success_test("String Hex Zero", '"\\x00"', "string", "\\0")
|
||||
self.make_success_test("String Hex Max", '"\\xFF"', "string", "\\xFF")
|
||||
|
||||
def generate_unicode_escape_tests(self):
|
||||
"""Generate tests for Unicode escape sequences."""
|
||||
# Basic Unicode escapes
|
||||
self.make_success_test("String Unicode Letter A", '"\\u{41}"', "string", "A")
|
||||
self.make_success_test("String Unicode Space", '"\\u{20}"', "string", " ")
|
||||
|
||||
# Emoji
|
||||
self.make_success_test("String Unicode Smiley", '"\\u{1F600}"',
|
||||
"string", "😀")
|
||||
self.make_success_test("String Unicode Heart", '"\\u{2764}"',
|
||||
"string", "❤")
|
||||
self.make_success_test("String Unicode Star", '"\\u{2B50}"',
|
||||
"string", "⭐")
|
||||
|
||||
# Multiple emoji
|
||||
self.make_success_test("String Multiple Emoji",
|
||||
'"\\u{1F600}\\u{2764}\\u{2B50}"',
|
||||
"string", "😀❤⭐")
|
||||
|
||||
# Greek letters
|
||||
self.make_success_test("String Unicode Greek Alpha", '"\\u{03B1}"',
|
||||
"string", "α")
|
||||
self.make_success_test("String Unicode Greek Beta", '"\\u{03B2}"',
|
||||
"string", "β")
|
||||
|
||||
# Chinese characters
|
||||
self.make_success_test("String Unicode Chinese", '"\\u{4E2D}\\u{6587}"',
|
||||
"string", "中文")
|
||||
|
||||
# Arabic
|
||||
self.make_success_test("String Unicode Arabic", '"\\u{0639}\\u{0631}\\u{0628}"',
|
||||
"string", "عرب")
|
||||
|
||||
# Cyrillic
|
||||
self.make_success_test("String Unicode Cyrillic", '"\\u{0420}\\u{0443}\\u{0441}"',
|
||||
"string", "Рус")
|
||||
|
||||
# Mathematical symbols
|
||||
self.make_success_test("String Unicode Math", '"\\u{221E}\\u{2211}\\u{222B}"',
|
||||
"string", "∞∑∫")
|
||||
|
||||
# Mixed with regular text
|
||||
self.make_success_test("String Unicode Mixed", '"Hello \\u{1F600} World"',
|
||||
"string", "Hello 😀 World")
|
||||
|
||||
# Case variations in hex digits
|
||||
self.make_success_test("String Unicode Hex Uppercase", '"\\u{1F600}"',
|
||||
"string", "😀")
|
||||
self.make_success_test("String Unicode Hex Lowercase", '"\\u{1f600}"',
|
||||
"string", "😀")
|
||||
self.make_success_test("String Unicode Hex Mixed", '"\\u{1F60a}"',
|
||||
"string", "😊")
|
||||
|
||||
# Variable length code points
|
||||
self.make_success_test("String Unicode 2 Digits", '"\\u{41}"', "string", "A")
|
||||
self.make_success_test("String Unicode 4 Digits", '"\\u{03B1}"', "string", "α")
|
||||
self.make_success_test("String Unicode 5 Digits", '"\\u{1F600}"', "string", "😀")
|
||||
self.make_success_test("String Unicode 6 Digits", '"\\u{10FFFF}"',
|
||||
"string", "\U0010FFFF")
|
||||
|
||||
def generate_direct_unicode_tests(self):
|
||||
"""Generate tests for direct Unicode characters in strings."""
|
||||
# Direct emoji
|
||||
self.make_success_test("String Direct Emoji", '"Hello 😀 World"',
|
||||
"string", "Hello 😀 World")
|
||||
self.make_success_test("String Multiple Direct Emoji", '"😀❤⭐👍"',
|
||||
"string", "😀❤⭐👍")
|
||||
|
||||
# Direct Greek
|
||||
self.make_success_test("String Direct Greek", '"αβγδ"', "string", "αβγδ")
|
||||
|
||||
# Direct Chinese
|
||||
self.make_success_test("String Direct Chinese", '"你好世界"',
|
||||
"string", "你好世界")
|
||||
|
||||
# Direct Arabic
|
||||
self.make_success_test("String Direct Arabic", '"مرحبا"', "string", "مرحبا")
|
||||
|
||||
# Direct Cyrillic
|
||||
self.make_success_test("String Direct Cyrillic", '"Привет"',
|
||||
"string", "Привет")
|
||||
|
||||
# Direct mathematical
|
||||
self.make_success_test("String Direct Math", '"∞∑∫√π"', "string", "∞∑∫√π")
|
||||
|
||||
# Mixed scripts
|
||||
self.make_success_test("String Mixed Scripts", '"Hello 世界 Привет"',
|
||||
"string", "Hello 世界 Привет")
|
||||
|
||||
def generate_multiline_tests(self):
|
||||
"""Generate tests for strings with embedded newlines."""
|
||||
# Strings with escape newlines
|
||||
self.make_success_test("String With Escaped Newlines",
|
||||
'"line1\\nline2\\nline3"',
|
||||
"string", "line1\\nline2\\nline3")
|
||||
|
||||
# Paragraph-like text
|
||||
self.make_success_test("String Paragraph",
|
||||
'"First line.\\nSecond line.\\nThird line."',
|
||||
"string", "First line.\\nSecond line.\\nThird line.")
|
||||
|
||||
# Mixed line endings
|
||||
self.make_success_test("String Mixed Line Endings",
|
||||
'"Windows\\r\\nUnix\\nMac\\r"',
|
||||
"string", "Windows\\r\\nUnix\\nMac\\r")
|
||||
|
||||
def generate_whitespace_tests(self):
|
||||
"""Generate tests with various whitespace."""
|
||||
# Leading/trailing whitespace outside quotes
|
||||
self.make_success_test("String Leading Whitespace Outside",
|
||||
' "hello"', "string", "hello")
|
||||
self.make_success_test("String Trailing Whitespace Outside",
|
||||
'"hello" ', "string", "hello")
|
||||
self.make_success_test("String Both Whitespace Outside",
|
||||
' "hello" ', "string", "hello")
|
||||
|
||||
# Tabs outside quotes
|
||||
self.make_success_test("String Tab Before", '\t"hello"', "string", "hello")
|
||||
|
||||
# Mixed whitespace
|
||||
self.make_success_test("String Tabs And Newlines Inside",
|
||||
'"hello\\t\\tworld\\n\\ntest"',
|
||||
"string", "hello\\t\\tworld\\n\\ntest")
|
||||
|
||||
# Only whitespace inside
|
||||
self.make_success_test("String Only Tabs", '"\\t\\t\\t"', "string", "\\t\\t\\t")
|
||||
self.make_success_test("String Only Newlines", '"\\n\\n\\n"', "string", "\\n\\n\\n")
|
||||
self.make_success_test("String Mixed Whitespace", '" \\t\\n\\r "',
|
||||
"string", " \\t\\n\\r ")
|
||||
|
||||
def generate_long_string_tests(self):
|
||||
"""Generate tests for longer strings."""
|
||||
# Sentence
|
||||
self.make_success_test("String Sentence",
|
||||
'"The quick brown fox jumps over the lazy dog."',
|
||||
"string", "The quick brown fox jumps over the lazy dog.")
|
||||
|
||||
# Multiple sentences
|
||||
self.make_success_test("String Multiple Sentences",
|
||||
'"First sentence. Second sentence. Third sentence."',
|
||||
"string", "First sentence. Second sentence. Third sentence.")
|
||||
|
||||
# Long string with escapes
|
||||
long_str = '"This is a long string.\\nIt has multiple lines.\\nAnd some tabs\\there.\\nPlus quotes "like this"."'
|
||||
expected = "This is a long string.\\nIt has multiple lines.\\nAnd some tabs\\there.\\nPlus quotes \\\"like this\\\"."
|
||||
self.make_success_test("String Long With Escapes", long_str,
|
||||
"string", expected)
|
||||
|
||||
# Repetitive string
|
||||
self.make_success_test("String Repetitive", '"aaaaaaaaaa"',
|
||||
"string", "aaaaaaaaaa")
|
||||
|
||||
# All ASCII printable characters
|
||||
printable = "".join(chr(i) for i in range(32, 127) if chr(i) not in ['"', '\\'])
|
||||
self.make_success_test("String ASCII Printable",
|
||||
f'"{printable}"', "string", printable)
|
||||
|
||||
def generate_special_content_tests(self):
|
||||
"""Generate tests for strings with special content."""
|
||||
# Code-like strings
|
||||
self.make_success_test("String Code Like",
|
||||
'"int main() { return 0; }"',
|
||||
"string", "int main() { return 0; }")
|
||||
|
||||
# JSON-like strings
|
||||
self.make_success_test("String JSON Like",
|
||||
'"{{\\\\"key\\\\": \\\\"value\\\\"}}"',
|
||||
"string", '{\\"key\\": \\"value\\"}')
|
||||
|
||||
# URL
|
||||
self.make_success_test("String URL",
|
||||
'"https://example.com/path?query=value"',
|
||||
"string", "https://example.com/path?query=value")
|
||||
|
||||
# Email
|
||||
self.make_success_test("String Email",
|
||||
'"user@example.com"', "string", "user@example.com")
|
||||
|
||||
# File path (Unix)
|
||||
self.make_success_test("String Unix Path",
|
||||
'"/home/user/file.txt"',
|
||||
"string", "/home/user/file.txt")
|
||||
|
||||
# File path (Windows)
|
||||
self.make_success_test("String Windows Path",
|
||||
'"C:\\\\Users\\\\file.txt"',
|
||||
"string", "C:\\\\Users\\\\file.txt")
|
||||
|
||||
# SQL-like
|
||||
self.make_success_test("String SQL Like",
|
||||
'"SELECT * FROM users WHERE id = 1"',
|
||||
"string", "SELECT * FROM users WHERE id = 1")
|
||||
|
||||
# Regular expression
|
||||
self.make_success_test("String Regex Like",
|
||||
'"[a-zA-Z0-9]+"', "string", "[a-zA-Z0-9]+")
|
||||
|
||||
def generate_error_tests(self):
|
||||
"""Generate error test cases."""
|
||||
# Unclosed string
|
||||
self.make_error_test("String Unclosed",
|
||||
'"hello',
|
||||
"Invalid string literal: unclosed string literal.")
|
||||
|
||||
# Unescaped newline
|
||||
self.make_error_test("String Unescaped Newline",
|
||||
'"hello\\nworld"',
|
||||
"Invalid string literal: unescaped newline in string literal.")
|
||||
|
||||
# Invalid escape sequence
|
||||
self.make_error_test("String Invalid Escape",
|
||||
'"hello\\\\qworld"',
|
||||
"Invalid string literal: unknown escape sequence '\\\\q'.")
|
||||
|
||||
# Hex escape too short
|
||||
self.make_error_test("String Hex Too Short",
|
||||
'"\\x4"',
|
||||
"Invalid string literal: hexadecimal escape must have exactly 2 digits.")
|
||||
|
||||
# Hex escape too long
|
||||
self.make_error_test("String Hex Too Long",
|
||||
'"\\\\x414"',
|
||||
"Invalid string literal: hexadecimal escape must have exactly 2 digits.")
|
||||
|
||||
# Invalid hex digits
|
||||
self.make_error_test("String Hex Invalid Digit",
|
||||
'"\\\\xGG"',
|
||||
"Invalid string literal: invalid hexadecimal digit 'G'.")
|
||||
|
||||
if self.ENABLE_UNICODE:
|
||||
# Unicode no braces
|
||||
self.make_error_test("String Unicode No Braces",
|
||||
'"\\u1F600"',
|
||||
"Invalid string literal: Unicode escape must use braces \\u{...}.")
|
||||
|
||||
# Unicode empty
|
||||
self.make_error_test("String Unicode Empty",
|
||||
'"\\u{}"',
|
||||
"Invalid string literal: empty Unicode escape sequence.")
|
||||
|
||||
# Unicode too long
|
||||
self.make_error_test("String Unicode Too Long",
|
||||
'"\\u{1234567}"',
|
||||
"Invalid string literal: Unicode escape sequence too long (max 6 hex digits).")
|
||||
|
||||
# Unicode invalid code point (surrogate)
|
||||
self.make_error_test("String Unicode Surrogate",
|
||||
'"\\u{D800}"',
|
||||
"Invalid string literal: invalid Unicode code point (surrogate range).")
|
||||
|
||||
# Unicode out of range
|
||||
self.make_error_test("String Unicode Out Of Range",
|
||||
'"\\u{110000}"',
|
||||
"Invalid string literal: Unicode code point out of range (max 0x10FFFF).")
|
||||
|
||||
# Unicode invalid hex
|
||||
self.make_error_test("String Unicode Invalid Hex",
|
||||
'"\\u{GGGG}"',
|
||||
"Invalid string literal: invalid hexadecimal digit 'G' in Unicode escape.")
|
||||
|
||||
# Unclosed Unicode escape
|
||||
self.make_error_test("String Unicode Unclosed",
|
||||
'"\\u{1F600"',
|
||||
"Invalid string literal: unclosed Unicode escape sequence.")
|
||||
|
||||
# Single quotes instead of double
|
||||
self.make_error_test("String Single Quotes",
|
||||
"'hello'",
|
||||
"Invalid string literal: string literals must use double quotes.")
|
||||
|
||||
# Backslash at end
|
||||
self.make_error_test("String Backslash At End",
|
||||
'"hello\\\\"',
|
||||
"Invalid string literal: incomplete escape sequence at end.")
|
||||
|
||||
def generate_edge_case_tests(self):
|
||||
"""Generate edge case tests."""
|
||||
# String with only escape sequences
|
||||
self.make_success_test("String Only Escapes",
|
||||
'"\\n\\t\\r"', "string", "\\n\\t\\r")
|
||||
|
||||
# String with null characters
|
||||
self.make_success_test("String With Nulls",
|
||||
'"a\\0b\\0c"', "string", "a\\0b\\0c")
|
||||
|
||||
# Very long escape sequence
|
||||
self.make_success_test("String Many Escapes",
|
||||
'"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n"',
|
||||
"string", "\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n")
|
||||
|
||||
if self.ENABLE_UNICODE:
|
||||
# Mixed escape types
|
||||
self.make_success_test("String All Escape Types",
|
||||
'"\\n\\x41\\u{42}test"',
|
||||
"string", "\nABtest")
|
||||
|
||||
# Zero-width characters
|
||||
self.make_success_test("String Zero Width",
|
||||
'"hello\\u{200B}world"',
|
||||
"string", "hello\u200Bworld")
|
||||
|
||||
# Combining characters
|
||||
self.make_success_test("String Combining",
|
||||
'"e\\u{0301}"', # é as e + combining acute
|
||||
"string", "e\u0301")
|
||||
|
||||
# Right-to-left
|
||||
self.make_success_test("String RTL Mark",
|
||||
'"\\u{200F}hello"',
|
||||
"string", "\u200Fhello")
|
||||
|
||||
# Byte order mark
|
||||
self.make_success_test("String BOM",
|
||||
'"\\u{FEFF}hello"',
|
||||
"string", "\uFEFFhello")
|
||||
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""Generate all string literal test cases."""
|
||||
|
||||
if not self.ENABLE_STRINGS:
|
||||
return []
|
||||
|
||||
# Basic tests
|
||||
self.generate_basic_tests()
|
||||
|
||||
# Escape sequences
|
||||
self.generate_escape_sequence_tests()
|
||||
|
||||
# Hexadecimal escapes
|
||||
self.generate_hexadecimal_escape_tests()
|
||||
|
||||
if self.ENABLE_UNICODE:
|
||||
# Unicode escapes
|
||||
self.generate_unicode_escape_tests()
|
||||
|
||||
# Direct Unicode
|
||||
self.generate_direct_unicode_tests()
|
||||
|
||||
# Multiline strings
|
||||
self.generate_multiline_tests()
|
||||
|
||||
# Whitespace handling
|
||||
self.generate_whitespace_tests()
|
||||
|
||||
# Long strings
|
||||
self.generate_long_string_tests()
|
||||
|
||||
# Special content
|
||||
self.generate_special_content_tests()
|
||||
|
||||
# Error cases
|
||||
self.generate_error_tests()
|
||||
|
||||
# Edge cases
|
||||
self.generate_edge_case_tests()
|
||||
|
||||
return self.get_tests()
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
@ -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)
|
||||
============================================================
|
||||
```
|
||||
|
|
@ -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
|
||||
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
|
|
@ -16,7 +16,7 @@ file_headers = """\
|
|||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "sls/sls_errors.h"
|
||||
#include "sls/errors.h"
|
||||
#include "sls/lexer.h"
|
||||
#include "sls/string.h"
|
||||
#include "tests/lexer_test_helpers.h"
|
||||
|
|
@ -27,7 +27,7 @@ file_headers = """\
|
|||
main_header = """\
|
||||
TestsReport run_lexer_tests() {
|
||||
TestsReport test_report = (TestsReport) {
|
||||
.section = "lexer_tests",
|
||||
.section = SLS_STR("lexer_tests"),
|
||||
.count = NUM_OF_TESTS,
|
||||
.tests = (TestResult *)malloc(sizeof(TestResult) * NUM_OF_TESTS),
|
||||
};
|
||||
|
|
@ -68,28 +68,179 @@ def _token_to_c_call(token: dict, idx_var="i") -> str:
|
|||
return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U16, {value}}})'
|
||||
elif ttype == "u8":
|
||||
return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U8, {value}}})'
|
||||
elif ttype == "f64":
|
||||
return f'test_double_value(&test, result, {idx_var}++, &(double){{{value}}})'
|
||||
elif ttype == "f32":
|
||||
return f'test_float_value(&test, result, {idx_var}++, &(float){{{value}}})'
|
||||
elif ttype == "char":
|
||||
return f'test_character_value(&test, result, {idx_var}++, &(uint8_t){{{ord(value)}}})' # type: ignore
|
||||
elif ttype == "string":
|
||||
return f'test_string_value(&test, result, {idx_var}++, &SLS_STR("{value}"))' # type: ignore
|
||||
elif ttype == "identifier":
|
||||
return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{FALSE, {len(value)}, "{value}"}})' # type: ignore
|
||||
return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{FALSE, SLS_STR("{value}")}})' # type: ignore
|
||||
elif ttype == "identifier_literal":
|
||||
return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{TRUE, {len(value)}, "{value}"}})' # type: ignore
|
||||
return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{TRUE, SLS_STR("{value}")}})' # type: ignore
|
||||
elif ttype == "bool":
|
||||
return f'test_boolean_value(&test, result, {idx_var}++, &(Boolean){{{"TRUE" if value else "FALSE"}}})' # type: ignore
|
||||
elif ttype == "error":
|
||||
return f'test_for_error(&test, result, i++, &(TestErrorMessage){{{len(value)+1}, "{c_string_literal(value)}"}})' # type: ignore
|
||||
return f'test_for_error(&test, result, i++, &SLS_STR("{c_string_literal(value)}"))' # type: ignore
|
||||
elif ttype == "token_string":
|
||||
return _token_string_c_call(idx_var, value) # type: ignore
|
||||
else:
|
||||
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;"
|
||||
|
||||
def generate_c_test(test: dict) -> str:
|
||||
"""Convert a single YAML test entry to a C test function."""
|
||||
name = sanitize_name(test["name"])
|
||||
name = test["name"]
|
||||
c_name = sanitize_name(name)
|
||||
code = c_string_literal(test["code"])
|
||||
tokens = test.get("tokens", [])
|
||||
|
||||
# Function header
|
||||
c_code = [f"static TestResult {name}() {{",
|
||||
f' LexerTest test = start_up_test("{name}", "{code}");',
|
||||
c_code = [f"static TestResult {c_name}() {{",
|
||||
f' LexerTest test = start_up_test(SLS_STR("{name}"), SLS_STR("{code}"));',
|
||||
" LexerResult result = lexical_analysis(&test.lexer_info);",
|
||||
" if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);",
|
||||
" size_t i = 0;"]
|
||||
|
|
|
|||
Loading…
Reference in New Issue