Compare commits
96 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 |
|
|
@ -1,6 +1,69 @@
|
||||||
# SE 3250 Progress Checkpoints
|
# SLS Changelog
|
||||||
|
|
||||||
## Checkpoint 3
|
## 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*
|
*28 Nov 2025*
|
||||||
|
|
||||||
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
|
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
|
||||||
|
|
@ -28,7 +91,7 @@ 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
|
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.
|
main structures of the ports, I hope it will be enough for me to finish.
|
||||||
|
|
||||||
## Checkpoint 2
|
## SE Checkpoint 2
|
||||||
*20 Nov 2025*
|
*20 Nov 2025*
|
||||||
|
|
||||||
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
|
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
|
||||||
|
|
@ -58,7 +121,7 @@ features moved to the backlog include:
|
||||||
- Type tuples, function defs
|
- Type tuples, function defs
|
||||||
- Structs, unions, etc.
|
- Structs, unions, etc.
|
||||||
|
|
||||||
## Checkpoint 1
|
## SE Checkpoint 1
|
||||||
*07 Nov 2025*
|
*07 Nov 2025*
|
||||||
|
|
||||||
**intended user**:
|
**intended user**:
|
||||||
193
README.md
193
README.md
|
|
@ -1,97 +1,128 @@
|
||||||
# YREA SLS
|
# YREA SLS
|
||||||
*Kyler Olsen*
|
*Kyler Olsen*
|
||||||
*October 2025*
|
*October 2025*
|
||||||
*Snow College*
|
|
||||||
*SE 3250 Survey of Languages Final Project*
|
|
||||||
|
|
||||||
Language Code Name: YREA **SLS** (*Stack Language Specification*)
|
SLS is a statically-typed, stack-based language with pure postfix notation
|
||||||
Language Specifications: [sls.purplecello.org](https://sls.purplecello.org)
|
combining the execution model of HP's RPL, the type system of C and Rust, and
|
||||||
Language Specification Repository (Private): [git.purplecello.org](https://git.purplecello.org/KylerOlsen/LangsFinalPlaning)
|
modern array operations from Uiua.
|
||||||
Language Implementation Repository (Private): [git.purplecello.org](https://git.purplecello.org/KylerOlsen/YREA-SLS)
|
|
||||||
Language Implementation Repository (Mirror on GitHub) (Private): [github.com](https://github.com/SnowSE/final-project-KylerOlsen)
|
|
||||||
Assignment Page (Private): [snow.instructure.com](https://snow.instructure.com/courses/1154808/assignments/16233203)
|
|
||||||
|
|
||||||
## Assignment Description
|
## Build Commands
|
||||||
|
|
||||||
**Overview**
|
**Linux**
|
||||||
|
|
||||||
In your final project for this course, you will do the following:
|
C
|
||||||
|
```bash
|
||||||
|
cd SLS_C
|
||||||
|
python3 build.py build
|
||||||
|
./bin/sls
|
||||||
|
```
|
||||||
|
|
||||||
- Decide on a modest personal project to complete in a new-to-you
|
Python run module
|
||||||
programming language (during class time, we will randomize the list of
|
```bash
|
||||||
people in the class and allow languages to be selected uniquely on a
|
cd SLS_Python
|
||||||
first-come, first-served basis; be prepared to pick the language you want
|
python3 -m sls_py
|
||||||
and to advocate for the languages that you don't want).
|
python3 -m sls_py.calc
|
||||||
- Without using AI generated or suggested code, complete your project in
|
```
|
||||||
your assigned language including automated testing. Find a way to make your
|
|
||||||
useful implementation concise and elegant enough that you would be able to
|
|
||||||
recreate it reasonably quickly without AI assistance (be prepared to
|
|
||||||
demonstrate this ability during a one-on-one interview to ensure credit).
|
|
||||||
- Using AI help as desired, create a faithful port of your implementation
|
|
||||||
(including tests) to:
|
|
||||||
- Rust
|
|
||||||
- Python
|
|
||||||
- Java (optional, but it might be interesting and userful for your
|
|
||||||
resume)
|
|
||||||
- C# (optional, but it might be interesting)
|
|
||||||
- typescript (optional, but it might be interesting)
|
|
||||||
- For each ported implementation, add one addition feature (not present in
|
|
||||||
the original or other ports).
|
|
||||||
- Prepare a report that describes:
|
|
||||||
- the problem that your project solves and why you are interested in it
|
|
||||||
- a brief description of how the language that you chose and the port
|
|
||||||
languages were or were not each a good fit for the project
|
|
||||||
- three examples of ideas you learned in the course that you leveraged
|
|
||||||
to the implementation in your assigned language elegant, efficient, and
|
|
||||||
concise/maintainable (at least one idea should specifically come from
|
|
||||||
functional programming) -- make sure to be specific and teach the reader
|
|
||||||
something valuable for each of these
|
|
||||||
- at least one struggle that you faced (or that you imagine that others
|
|
||||||
would likely face) for each language implementation, and what you
|
|
||||||
learned from working with the port languages during the porting process.
|
|
||||||
- Prepare slides that help you teach the key ideas from your report to the
|
|
||||||
class.
|
|
||||||
- In a 5-8 minute presentation during the final exam slot, use your slides
|
|
||||||
to teach the class and demonstrate your running project including the
|
|
||||||
addition port features.
|
|
||||||
|
|
||||||
Project Scope
|
Python Setup
|
||||||
|
```bash
|
||||||
|
cd SLS_Python
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install build wheel "setuptools>=61.0"
|
||||||
|
pip install -e sls_build_backend
|
||||||
|
```
|
||||||
|
|
||||||
- Please do something that will be fun and interesting for you!
|
Python build module
|
||||||
- Make sure that the project actually solves a problem that you care about.
|
```bash
|
||||||
(Entertainment counts, but if that is the problem you care about, then make
|
cd SLS_Python
|
||||||
sure that you can make something that actually is entertaining!)
|
source .venv/bin/activate
|
||||||
- I'm expecting you to dedicate about 15-30 hours to the entire project
|
python3 -m build --no-isolation
|
||||||
(including all three implementations and the report/presentation
|
pip install ./dist/sls_python-0.0.2a0-py3-none-any.whl
|
||||||
preparation). I'm expecting that your initial implmentation should feel
|
python3 -m sls_py
|
||||||
like about twice the scope of implementing the game of life in PostScript
|
python3 -m sls_py.calc
|
||||||
or K.
|
```
|
||||||
|
|
||||||
**Languages**
|
Rust
|
||||||
|
```bash
|
||||||
|
cd SLS_Rust/sls
|
||||||
|
cargo build
|
||||||
|
./target/debug/sls_rs
|
||||||
|
```
|
||||||
|
|
||||||
For what it is worth, here are a few resources that rank or compare different
|
**Windows**
|
||||||
programming languages in terms of popularity or jobs (but they don't equally
|
|
||||||
capture how programming in the languages will give you tools for thinking
|
|
||||||
about and solving problems):
|
|
||||||
|
|
||||||
- https://spectrum.ieee.org/top-programming-languages-2025
|
C (Using Visual Studio Developer PowerShell)
|
||||||
- https://www.tiobe.com/tiobe-index/
|
```shell
|
||||||
- https://survey.stackoverflow.co/2025/technology#admired-and-desired
|
cd SLS_C
|
||||||
- https://survey.stackoverflow.co/2025/technology#most-popular-technologies-language-prof
|
python build.py build
|
||||||
- https://tjpalmer.github.io/languish/
|
.\bin\sls.exe
|
||||||
- https://www.geeksforgeeks.org/blogs/top-programming-languages/
|
```
|
||||||
- https://github.com/breck7/pldb
|
|
||||||
|
|
||||||
Here is the list of programming languages that you will be able to choose from
|
Python run module
|
||||||
for your initial implementation (each language will be chosen by at most one
|
```bat
|
||||||
student; we will choose during class time on Oct 27):
|
cd SLS_Python
|
||||||
|
python -m sls_py
|
||||||
|
python -m sls_py.calc
|
||||||
|
```
|
||||||
|
|
||||||
- **C** Kyler
|
Python Setup
|
||||||
|
```bat
|
||||||
|
cd SLS_Python
|
||||||
|
python -m venv .venv
|
||||||
|
.venv\Scripts\activate.bat
|
||||||
|
pip install build wheel "setuptools>=61.0"
|
||||||
|
pip install -e sls_build_backend
|
||||||
|
```
|
||||||
|
|
||||||
**Submission**
|
Python build module
|
||||||
|
```bat
|
||||||
|
cd SLS_Python
|
||||||
|
.venv\Scripts\activate.bat
|
||||||
|
python -m build --no-isolation
|
||||||
|
pip install .\dist\sls_python-0.0.2a0-py3-none-any.whl
|
||||||
|
python -m sls_py
|
||||||
|
python -m sls_py.calc
|
||||||
|
```
|
||||||
|
|
||||||
Submit all material (including report and slides) via github classrooms repo.
|
Rust
|
||||||
Also, submit your github repo link and the path to the report and slides file
|
```bat
|
||||||
in the canvas text box for this assignment. The grace period for submission
|
cd SLS_Rust\sls
|
||||||
ends at 3pm on Monday, Dec 8. Presentations will be 3:30pm - 5:30pm. For full
|
cargo build
|
||||||
credit, you must present and watch the presentations of your classmates.
|
.\target\debug\sls_rs.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
**MacOS**
|
||||||
|
|
||||||
|
Reference Linux build instructions.
|
||||||
|
|
||||||
|
For C there is the `python3 build.py macos` command, but it is untested as I
|
||||||
|
didn't test it on a Mac. I also don't know if `python3 build.py build` would also
|
||||||
|
work on a Mac.
|
||||||
|
|
||||||
|
Python and Rust should just be the same or similar to Linux.
|
||||||
|
|
||||||
|
**RP2040**
|
||||||
|
|
||||||
|
Only tested on Linux. Only SLS_C supports RP2040.
|
||||||
|
|
||||||
|
Install Pico SDK to `~/pico/pico-sdk`, or set environment variable
|
||||||
|
`PICO_SDK_PATH` to your installation path. You will also need to install the
|
||||||
|
appropriate compiler.
|
||||||
|
|
||||||
|
Debian GNU/Linux
|
||||||
|
```bash
|
||||||
|
sudo apt install gcc-arm-none-eabi
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd SLS_C
|
||||||
|
python3 build.py rp2040
|
||||||
|
```
|
||||||
|
|
||||||
|
Flash Raspberry Pi Pico:
|
||||||
|
Copy `.\build_pico\sls.elf.uf2` to your Pico in BOOTSEL mode
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
[sls.purplecello.org/contributing](https://sls.purplecello.org/contributing.html)
|
||||||
|
|
|
||||||
|
|
@ -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,4 +1,9 @@
|
||||||
obj/
|
obj/
|
||||||
bin/
|
bin/
|
||||||
|
build_pico/
|
||||||
*.o
|
*.o
|
||||||
*.pdb
|
*.pdb
|
||||||
|
CMakeLists.txt
|
||||||
|
pico_arm_gcc_toolchain.cmake
|
||||||
|
generated/
|
||||||
|
pico-sdk.txt
|
||||||
|
|
|
||||||
328
SLS_C/build.py
328
SLS_C/build.py
|
|
@ -4,6 +4,7 @@ import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import shutil
|
import shutil
|
||||||
|
import platform
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# CONFIG
|
# CONFIG
|
||||||
|
|
@ -16,22 +17,136 @@ BIN_DIR = Path("bin")
|
||||||
TARGET = BIN_DIR / "sls"
|
TARGET = BIN_DIR / "sls"
|
||||||
TEST_TARGET = BIN_DIR / "sls_tests"
|
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
|
# Unix gcc/clang flags
|
||||||
COMMON_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Werror", "-Iinclude", "-g"]
|
COMMON_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Werror", "-Iinclude", "-g"]
|
||||||
TEST_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Wno-unused-function", "-Werror",
|
TEST_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Wno-unused-function", "-Werror",
|
||||||
"-Iinclude", "-g", "-O0"]
|
"-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
|
# Windows MSVC flags
|
||||||
MSVC_FLAGS = ["/std:c11", "/Zi", "/Iinclude"]
|
MSVC_FLAGS = ["/std:c11", "/Zi", "/Iinclude"]
|
||||||
MSVC_TEST_FLAGS = MSVC_FLAGS + []
|
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
|
# COMPILER DETECTION
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
def detect_compiler():
|
def detect_compiler(target_platform=None):
|
||||||
if os.name == "nt":
|
"""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")
|
return ("cl", "msvc")
|
||||||
return ("gcc", "gcc")
|
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()
|
CC, CC_KIND = detect_compiler()
|
||||||
|
|
||||||
|
|
@ -79,11 +194,13 @@ def mkdir(p: Path):
|
||||||
|
|
||||||
|
|
||||||
def run(cmd):
|
def run(cmd):
|
||||||
print(">>", " ".join(cmd))
|
print(">>", " ".join(str(c) for c in cmd))
|
||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
def is_up_to_date(src, obj, dep):
|
def is_up_to_date(src, obj, dep):
|
||||||
|
if "meta" in [src.stem, obj.stem, dep.stem]:
|
||||||
|
return False
|
||||||
if not obj.exists():
|
if not obj.exists():
|
||||||
return False
|
return False
|
||||||
if dep.exists() and dep.stat().st_mtime > obj.stat().st_mtime:
|
if dep.exists() and dep.stat().st_mtime > obj.stat().st_mtime:
|
||||||
|
|
@ -94,7 +211,7 @@ def is_up_to_date(src, obj, dep):
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# BUILD RULES
|
# BUILD RULES
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
def compile_source(src: Path, is_test=False):
|
def compile_source(src: Path, is_test=False, target_platform=None):
|
||||||
mkdir(OBJ_DIR)
|
mkdir(OBJ_DIR)
|
||||||
obj = OBJ_DIR / (src.stem + ".o")
|
obj = OBJ_DIR / (src.stem + ".o")
|
||||||
dep = OBJ_DIR / (src.stem + ".d")
|
dep = OBJ_DIR / (src.stem + ".d")
|
||||||
|
|
@ -102,13 +219,19 @@ def compile_source(src: Path, is_test=False):
|
||||||
if is_up_to_date(src, obj, dep):
|
if is_up_to_date(src, obj, dep):
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
if CC_KIND == "msvc":
|
compiler, kind = detect_compiler(target_platform)
|
||||||
|
|
||||||
|
if kind == "msvc":
|
||||||
flags = MSVC_TEST_FLAGS if is_test else MSVC_FLAGS
|
flags = MSVC_TEST_FLAGS if is_test else MSVC_FLAGS
|
||||||
cmd = [CC] + flags + ["/Fo" + str(obj), "/c", str(src),
|
cmd = [compiler] + flags + ["/Fo" + str(obj), "/c", str(src),
|
||||||
f"/DGIT_COMMIT_HASH=\"{GIT_HASH}\""]
|
f"/DGIT_COMMIT_HASH=\"{GIT_HASH}\""]
|
||||||
else:
|
else:
|
||||||
flags = TEST_FLAGS if is_test else COMMON_FLAGS
|
if target_platform == "macos":
|
||||||
cmd = [CC] + flags + [
|
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}\"",
|
f"-DGIT_COMMIT_HASH=\"{GIT_HASH}\"",
|
||||||
"-MMD", "-MP",
|
"-MMD", "-MP",
|
||||||
"-c", str(src), "-o", str(obj)
|
"-c", str(src), "-o", str(obj)
|
||||||
|
|
@ -118,12 +241,14 @@ def compile_source(src: Path, is_test=False):
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def link_executable(objects, output: Path):
|
def link_executable(objects, output: Path, target_platform=None):
|
||||||
mkdir(BIN_DIR)
|
mkdir(BIN_DIR)
|
||||||
if CC_KIND == "msvc":
|
compiler, kind = detect_compiler(target_platform)
|
||||||
cmd = [CC] + list(map(str, objects)) + ["/Fe" + str(output)]
|
|
||||||
|
if kind == "msvc":
|
||||||
|
cmd = [compiler] + list(map(str, objects)) + ["/Fe" + str(output)]
|
||||||
else:
|
else:
|
||||||
cmd = [CC] + list(map(str, objects)) + ["-lm", "-o", str(output)]
|
cmd = [compiler] + list(map(str, objects)) + ["-lm", "-o", str(output)]
|
||||||
run(cmd)
|
run(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -146,12 +271,136 @@ def generate_tests():
|
||||||
return True
|
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
|
# TARGETS
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
def build_main():
|
def build_main():
|
||||||
sources = list(SRC_DIR.glob("*.c"))
|
sources = list(SRC_DIR.glob("*.c"))
|
||||||
objects = [compile_source(s, is_test=False) for s in sources]
|
objects = [compile_source(s, is_test=False) for s in sources if s.name != 'pico_main.c']
|
||||||
link_executable(objects, TARGET)
|
link_executable(objects, TARGET)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -169,6 +418,13 @@ def build_tests():
|
||||||
def clean():
|
def clean():
|
||||||
shutil.rmtree(OBJ_DIR, ignore_errors=True)
|
shutil.rmtree(OBJ_DIR, ignore_errors=True)
|
||||||
shutil.rmtree(BIN_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.")
|
print("Cleaned.")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -192,20 +448,49 @@ def debug_tests():
|
||||||
subprocess.call(["gdb", str(TEST_TARGET)])
|
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
|
# ENTRY POINT
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
print("Usage: python3 build.py [all|build|test|run|debug|clean]")
|
show_help()
|
||||||
return
|
return
|
||||||
|
|
||||||
cmd = sys.argv[1]
|
cmd = sys.argv[1]
|
||||||
|
|
||||||
match cmd:
|
match cmd:
|
||||||
case "all" | "main":
|
case "all" | "main" | "build":
|
||||||
build_main()
|
|
||||||
case "build":
|
|
||||||
build_main()
|
build_main()
|
||||||
case "run":
|
case "run":
|
||||||
run_main()
|
run_main()
|
||||||
|
|
@ -215,8 +500,15 @@ def main():
|
||||||
debug_tests()
|
debug_tests()
|
||||||
case "clean":
|
case "clean":
|
||||||
clean()
|
clean()
|
||||||
|
case "macos":
|
||||||
|
build_macos()
|
||||||
|
case "pico" | "rp2040":
|
||||||
|
build_rp2040()
|
||||||
|
case "help" | "-h" | "--help":
|
||||||
|
show_help()
|
||||||
case _:
|
case _:
|
||||||
print(f"Unknown target: {cmd}")
|
print(f"Unknown target: {cmd}")
|
||||||
|
print("Run 'python3 build.py help' for usage information")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__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
|
||||||
|
|
@ -9,6 +9,23 @@
|
||||||
#include "sls/bool.h"
|
#include "sls/bool.h"
|
||||||
#include "sls/interpreter.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);
|
Boolean load_builtins(InterpreterState *interpreter_state);
|
||||||
|
|
||||||
#endif // SLS_BUILTIN_FUNCTIONS_H
|
#endif // SLS_BUILTIN_FUNCTIONS_H
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,11 @@
|
||||||
#ifndef SLS_FILE_H
|
#ifndef SLS_FILE_H
|
||||||
#define 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
|
#endif // SLS_FILE_H
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,11 @@ typedef enum {
|
||||||
STACK_CHARACTER,
|
STACK_CHARACTER,
|
||||||
STACK_BOOLEAN,
|
STACK_BOOLEAN,
|
||||||
STACK_TOKEN_STRING,
|
STACK_TOKEN_STRING,
|
||||||
|
STACK_CALLABLE,
|
||||||
} StackType;
|
} StackType;
|
||||||
|
|
||||||
extern const char *STACK_TYPES_NAMES[];
|
extern const char *STACK_TYPES_NAMES[];
|
||||||
|
extern const char *STACK_TYPES_TYPES[];
|
||||||
extern const size_t STACK_TYPE_COUNT;
|
extern const size_t STACK_TYPE_COUNT;
|
||||||
|
|
||||||
typedef struct StackItem {
|
typedef struct StackItem {
|
||||||
|
|
@ -74,6 +76,8 @@ typedef struct {
|
||||||
Boolean push_token(InterpreterState *interpreter_state, Token token);
|
Boolean push_token(InterpreterState *interpreter_state, Token token);
|
||||||
void clean_stack(StackItem *item);
|
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);
|
Boolean execute(InterpreterState *interpreter_state, LexerTokenResult *token);
|
||||||
InterpreterState *interpreter_create();
|
InterpreterState *interpreter_create();
|
||||||
void interpreter_delete(InterpreterState *interpreter_state);
|
void interpreter_delete(InterpreterState *interpreter_state);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,20 @@
|
||||||
#include "sls/bool.h"
|
#include "sls/bool.h"
|
||||||
#include "sls/errors.h"
|
#include "sls/errors.h"
|
||||||
|
|
||||||
|
#if __SIZEOF_POINTER__ == 4
|
||||||
|
// 32-bit system
|
||||||
|
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I32
|
||||||
|
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
|
||||||
|
#elif __SIZEOF_POINTER__ == 8
|
||||||
|
// 64-bit system
|
||||||
|
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I64
|
||||||
|
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F64
|
||||||
|
#else
|
||||||
|
// Fallback
|
||||||
|
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I16
|
||||||
|
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
|
||||||
|
#endif
|
||||||
|
|
||||||
extern const size_t TYPE_NAMES_SAFE_LENGTH;
|
extern const size_t TYPE_NAMES_SAFE_LENGTH;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#define SLS_MAIN_H
|
#define SLS_MAIN_H
|
||||||
|
|
||||||
#define SLS_NAME "SLS_C"
|
#define SLS_NAME "SLS_C"
|
||||||
#define SLS_VER "a.0.0"
|
#define SLS_VER "0.0.2-alpha"
|
||||||
|
|
||||||
#ifndef GIT_COMMIT_HASH
|
#ifndef GIT_COMMIT_HASH
|
||||||
#define GIT_COMMIT_HASH "UNKNOWN"
|
#define GIT_COMMIT_HASH "UNKNOWN"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,6 @@
|
||||||
#ifndef SLS_REPL_H
|
#ifndef SLS_REPL_H
|
||||||
#define SLS_REPL_H
|
#define SLS_REPL_H
|
||||||
|
|
||||||
int repl(int argc, char *argv[]);
|
int repl();
|
||||||
|
|
||||||
#endif // SLS_REPL_H
|
#endif // SLS_REPL_H
|
||||||
|
|
|
||||||
2319
SLS_C/src/builtin.c
2319
SLS_C/src/builtin.c
File diff suppressed because it is too large
Load Diff
|
|
@ -3,4 +3,66 @@
|
||||||
// File
|
// File
|
||||||
// November 2025
|
// November 2025
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "sls/file.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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,25 @@ const char *STACK_TYPES_NAMES[] = {
|
||||||
"Character",
|
"Character",
|
||||||
"Boolean",
|
"Boolean",
|
||||||
"Token String",
|
"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);
|
const size_t STACK_TYPE_COUNT = sizeof(STACK_TYPES_NAMES) / sizeof(*STACK_TYPES_NAMES);
|
||||||
|
|
@ -146,6 +165,7 @@ Boolean push_token(InterpreterState *interpreter_state, Token token) {
|
||||||
item->boolean = token.boolean_literal;
|
item->boolean = token.boolean_literal;
|
||||||
break;
|
break;
|
||||||
case STACK_TOKEN_STRING:
|
case STACK_TOKEN_STRING:
|
||||||
|
case STACK_CALLABLE:
|
||||||
item->token_string = copy_token_string(token.token_string);
|
item->token_string = copy_token_string(token.token_string);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -153,18 +173,30 @@ Boolean push_token(InterpreterState *interpreter_state, Token token) {
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Boolean execute_func(InterpreterState *interpreter_state, SlsStr key) {
|
Boolean execute_func(InterpreterState *interpreter_state, SlsStr key) {
|
||||||
FunctionItem *func = hash_table_get_funcs(interpreter_state->functions, key, NULL);
|
FunctionItem *func = hash_table_get_funcs(interpreter_state->functions, key, NULL);
|
||||||
if (func == NULL) return FALSE;
|
if (func == NULL) return FALSE;
|
||||||
switch (func->type) {
|
switch (func->type) {
|
||||||
case FUNCTION_BUILTIN:
|
case FUNCTION_BUILTIN:
|
||||||
return func->builtin(interpreter_state);
|
return func->builtin(interpreter_state);
|
||||||
case FUNCTION_TOKEN_STRING:
|
case FUNCTION_TOKEN_STRING:
|
||||||
return FALSE;
|
return execute_token_string(interpreter_state, func->token_string);
|
||||||
}
|
}
|
||||||
return FALSE;
|
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) {
|
Boolean execute(InterpreterState *interpreter_state, LexerTokenResult *token) {
|
||||||
if (token->result.type == TOKEN_IDENTIFIER && !token->result.identifier.is_literal)
|
if (token->result.type == TOKEN_IDENTIFIER && !token->result.identifier.is_literal)
|
||||||
return execute_func(interpreter_state, token->result.identifier.name);
|
return execute_func(interpreter_state, token->result.identifier.name);
|
||||||
|
|
|
||||||
|
|
@ -35,21 +35,21 @@ const char *TOKEN_TYPES_NAMES[] = {
|
||||||
const size_t TOKEN_TYPE_COUNT = sizeof(TOKEN_TYPES_NAMES) / sizeof(*TOKEN_TYPES_NAMES);
|
const size_t TOKEN_TYPE_COUNT = sizeof(TOKEN_TYPES_NAMES) / sizeof(*TOKEN_TYPES_NAMES);
|
||||||
|
|
||||||
const char *ARRAY_TYPES_NAMES[] = {
|
const char *ARRAY_TYPES_NAMES[] = {
|
||||||
"Identifier",
|
"Identifier",
|
||||||
"i64",
|
"i64",
|
||||||
"i32",
|
"i32",
|
||||||
"i16",
|
"i16",
|
||||||
"i8",
|
"i8",
|
||||||
"u64",
|
"u64",
|
||||||
"u32",
|
"u32",
|
||||||
"u16",
|
"u16",
|
||||||
"u8",
|
"u8",
|
||||||
"Float",
|
"Float",
|
||||||
"Double",
|
"Double",
|
||||||
"Character",
|
"Character",
|
||||||
"String",
|
"String",
|
||||||
"Boolean",
|
"Boolean",
|
||||||
"Inline Struct",
|
"Inline Struct",
|
||||||
};
|
};
|
||||||
|
|
||||||
const size_t ARRAY_TYPE_COUNT = sizeof(ARRAY_TYPES_NAMES) / sizeof(*ARRAY_TYPES_NAMES);
|
const size_t ARRAY_TYPE_COUNT = sizeof(ARRAY_TYPES_NAMES) / sizeof(*ARRAY_TYPES_NAMES);
|
||||||
|
|
@ -521,7 +521,7 @@ static LexerResult parse_binary_integer(LexerInfo *lexer_info, char c, size_t st
|
||||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_BINARY);
|
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_BINARY);
|
||||||
if (isspace(c) || c == '/' || c == '\0') {
|
if (isspace(c) || c == '/' || c == '\0') {
|
||||||
uint64_t value = create_binary_integer(lexer_info, start);
|
uint64_t value = create_binary_integer(lexer_info, start);
|
||||||
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
|
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
|
||||||
}
|
}
|
||||||
SlsStr error_msg = sls_format(SLS_STR("Invalid binary literal: unexpected '%c' in binary integer."), c);
|
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}};
|
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||||
|
|
@ -533,7 +533,7 @@ static LexerResult parse_octal_integer(LexerInfo *lexer_info, char c, size_t sta
|
||||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_OCTAL);
|
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_OCTAL);
|
||||||
if (isspace(c) || c == '/' || c == '\0') {
|
if (isspace(c) || c == '/' || c == '\0') {
|
||||||
uint64_t value = create_octal_integer(lexer_info, start);
|
uint64_t value = create_octal_integer(lexer_info, start);
|
||||||
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
|
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
|
||||||
}
|
}
|
||||||
SlsStr error_msg = sls_format(SLS_STR("Invalid octal literal: unexpected '%c' in octal integer."), c);
|
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}};
|
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||||
|
|
@ -550,7 +550,7 @@ static LexerResult parse_float(LexerInfo *lexer_info, char c, size_t start, size
|
||||||
if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line);
|
if (c == '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 (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_FLOAT);
|
||||||
if (isspace(c) || c == '/' || c == '\0')
|
if (isspace(c) || c == '/' || c == '\0')
|
||||||
return create_float_token(lexer_info, NUMERIC_F64, start, start_line);
|
return create_float_token(lexer_info, SLS_FLOAT_TYPE_DEFAULT, start, start_line);
|
||||||
SlsStr error_msg = sls_format(SLS_STR("Invalid float literal: unexpected '%c' in float."), c);
|
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}};
|
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);
|
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||||
|
|
@ -565,7 +565,7 @@ static LexerResult parse_decimal_integer(LexerInfo *lexer_info, char c, size_t s
|
||||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_DECIMAL);
|
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_DECIMAL);
|
||||||
if (isspace(c) || c == '/' || c == '\0') {
|
if (isspace(c) || c == '/' || c == '\0') {
|
||||||
uint64_t value = create_decimal_integer(lexer_info, start);
|
uint64_t value = create_decimal_integer(lexer_info, start);
|
||||||
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
|
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
|
||||||
}
|
}
|
||||||
SlsStr error_msg = sls_format(SLS_STR("Invalid decimal literal: unexpected '%c' in decimal integer."), c);
|
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}};
|
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||||
|
|
@ -577,7 +577,7 @@ static LexerResult parse_hexadecimal_integer(LexerInfo *lexer_info, char c, size
|
||||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_HEXADECIMAL);
|
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_HEXADECIMAL);
|
||||||
if (isspace(c) || c == '/' || c == '\0') {
|
if (isspace(c) || c == '/' || c == '\0') {
|
||||||
uint64_t value = create_hexadecimal_integer(lexer_info, start);
|
uint64_t value = create_hexadecimal_integer(lexer_info, start);
|
||||||
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
|
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
|
||||||
}
|
}
|
||||||
SlsStr error_msg = sls_format(SLS_STR("Invalid hexadecimal literal: unexpected '%c' in hexadecimal integer."), c);
|
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}};
|
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||||
|
|
|
||||||
|
|
@ -9,27 +9,32 @@
|
||||||
|
|
||||||
#include "sls/meta.h"
|
#include "sls/meta.h"
|
||||||
#include "sls/bool.h"
|
#include "sls/bool.h"
|
||||||
|
#include "sls/string.h"
|
||||||
#include "sls/repl.h"
|
#include "sls/repl.h"
|
||||||
|
#include "sls/file.h"
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
Boolean version = FALSE;
|
Boolean version = FALSE;
|
||||||
Boolean file = FALSE;
|
|
||||||
char *filename = NULL;
|
char *filename = NULL;
|
||||||
|
|
||||||
(void)file;
|
if (argc == 2) {
|
||||||
(void)filename;
|
if (strcmp(argv[1], "--version") == 0) version = TRUE;
|
||||||
|
else if (strcmp(argv[1], "-v") == 0) version = TRUE;
|
||||||
for (int i = 0; i < argc; i++) {
|
else filename = argv[1];
|
||||||
if (strcmp(argv[i], "--version") == 0) version = TRUE;
|
} else if (argc > 2) {
|
||||||
if (strcmp(argv[i], "-v") == 0) version = TRUE;
|
printf("To many arguments!");
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version) {
|
if (version) {
|
||||||
print_version();
|
print_version();
|
||||||
return 0;
|
return 0;
|
||||||
} else if (file) {
|
} else if (filename != NULL) {
|
||||||
return 1;
|
SlsStr sls_filename = sls_str_malloc(filename, strlen(filename));
|
||||||
|
int rv = file(sls_filename);
|
||||||
|
sls_str_free(&sls_filename);
|
||||||
|
return rv;
|
||||||
} else {
|
} else {
|
||||||
return repl(0, NULL);
|
return repl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -65,13 +65,13 @@ void print_top_of_stack(InterpreterState *interpreter_state) {
|
||||||
case STACK_TOKEN_STRING:
|
case STACK_TOKEN_STRING:
|
||||||
printf("#0: <TOKEN STRING>\n");
|
printf("#0: <TOKEN STRING>\n");
|
||||||
break;
|
break;
|
||||||
|
case STACK_CALLABLE:
|
||||||
|
printf("#0: <CALLABLE>\n");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int repl(int argc, char *argv[]) {
|
int repl() {
|
||||||
(void)argc;
|
|
||||||
(void)argv;
|
|
||||||
|
|
||||||
print_version();
|
print_version();
|
||||||
printf("===== YREA SLS REPL =====\n");
|
printf("===== YREA SLS REPL =====\n");
|
||||||
printf("Type `#exit` to exit.\n");
|
printf("Type `#exit` to exit.\n");
|
||||||
|
|
@ -79,6 +79,7 @@ int repl(int argc, char *argv[]) {
|
||||||
|
|
||||||
LexerInfo lexer_info;
|
LexerInfo lexer_info;
|
||||||
InterpreterState *interpreter_state = interpreter_create();
|
InterpreterState *interpreter_state = interpreter_create();
|
||||||
|
if (interpreter_state == NULL) return 1;
|
||||||
char buf[256];
|
char buf[256];
|
||||||
while (fgets(buf, sizeof(buf), stdin)) {
|
while (fgets(buf, sizeof(buf), stdin)) {
|
||||||
size_t len = strlen(buf);
|
size_t len = strlen(buf);
|
||||||
|
|
@ -100,11 +101,10 @@ int repl(int argc, char *argv[]) {
|
||||||
if (head->type == SLS_ERROR) {
|
if (head->type == SLS_ERROR) {
|
||||||
printf("%s\n", head->error.message.str);
|
printf("%s\n", head->error.message.str);
|
||||||
break;
|
break;
|
||||||
} else
|
} else if (!execute(interpreter_state, head)) {
|
||||||
if (!execute(interpreter_state, head)) {
|
printf("A runtime error occurred!\n");
|
||||||
printf("A runtime error occurred!\n");
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
head = head->next;
|
head = head->next;
|
||||||
}
|
}
|
||||||
clean_token_result(result.result);
|
clean_token_result(result.result);
|
||||||
|
|
|
||||||
|
|
@ -1803,8 +1803,8 @@ static TestResult test_Float_f32_Negative_Zero() {
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestResult test_Char_Simple_Letter_A() {
|
static TestResult test_Char_Simple_Letter_Uppercase_A() {
|
||||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter A"), SLS_STR("'A'"));
|
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Uppercase A"), SLS_STR("'A'"));
|
||||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
@ -1813,8 +1813,8 @@ static TestResult test_Char_Simple_Letter_A() {
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestResult test_Char_Simple_Letter_a() {
|
static TestResult test_Char_Simple_Letter_Lowercase_a() {
|
||||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter a"), SLS_STR("'a'"));
|
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Lowercase a"), SLS_STR("'a'"));
|
||||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
@ -1823,8 +1823,8 @@ static TestResult test_Char_Simple_Letter_a() {
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestResult test_Char_Simple_Letter_Z() {
|
static TestResult test_Char_Simple_Letter_Uppercase_Z() {
|
||||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Z"), SLS_STR("'Z'"));
|
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Uppercase Z"), SLS_STR("'Z'"));
|
||||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
@ -1833,8 +1833,8 @@ static TestResult test_Char_Simple_Letter_Z() {
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestResult test_Char_Simple_Letter_z() {
|
static TestResult test_Char_Simple_Letter_Lowercase_z() {
|
||||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter z"), SLS_STR("'z'"));
|
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Lowercase z"), SLS_STR("'z'"));
|
||||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
@ -2073,22 +2073,22 @@ static TestResult test_Char_Right_Brace() {
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestResult test_Char_Escape_Tab() {
|
static TestResult test_Char_Escape_Single_quote() {
|
||||||
LexerTest test = start_up_test(SLS_STR("Char Escape Tab"), SLS_STR("'\\t'"));
|
LexerTest test = start_up_test(SLS_STR("Char Escape Single quote"), SLS_STR("'\\''"));
|
||||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
if (test_character_value(&test, result, i++, &(uint8_t){9})) return test.result;
|
if (test_character_value(&test, result, i++, &(uint8_t){39})) return test.result;
|
||||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestResult test_Char_Escape_Backslash() {
|
static TestResult test_Char_Escape_Newline() {
|
||||||
LexerTest test = start_up_test(SLS_STR("Char Escape Backslash"), SLS_STR("'\\\\'"));
|
LexerTest test = start_up_test(SLS_STR("Char Escape Newline"), SLS_STR("'\\n'"));
|
||||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
if (test_character_value(&test, result, i++, &(uint8_t){92})) return test.result;
|
if (test_character_value(&test, result, i++, &(uint8_t){10})) return test.result;
|
||||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
@ -2103,12 +2103,22 @@ static TestResult test_Char_Escape_Null_character() {
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestResult test_Char_Escape_Single_quote() {
|
static TestResult test_Char_Escape_Backslash() {
|
||||||
LexerTest test = start_up_test(SLS_STR("Char Escape Single quote"), SLS_STR("'\\''"));
|
LexerTest test = start_up_test(SLS_STR("Char Escape Backslash"), SLS_STR("'\\\\'"));
|
||||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
if (test_character_value(&test, result, i++, &(uint8_t){39})) return test.result;
|
if (test_character_value(&test, result, i++, &(uint8_t){92})) return test.result;
|
||||||
|
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||||
|
return pass_test(&test, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TestResult test_Char_Escape_Tab() {
|
||||||
|
LexerTest test = start_up_test(SLS_STR("Char Escape Tab"), SLS_STR("'\\t'"));
|
||||||
|
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_character_value(&test, result, i++, &(uint8_t){9})) return test.result;
|
||||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
@ -2123,16 +2133,6 @@ static TestResult test_Char_Escape_Carriage_return() {
|
||||||
return pass_test(&test, result);
|
return pass_test(&test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestResult test_Char_Escape_Newline() {
|
|
||||||
LexerTest test = start_up_test(SLS_STR("Char Escape Newline"), SLS_STR("'\\n'"));
|
|
||||||
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_character_value(&test, result, i++, &(uint8_t){10})) return test.result;
|
|
||||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
|
||||||
return pass_test(&test, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
static TestResult test_Char_With_Leading_Whitespace() {
|
static TestResult test_Char_With_Leading_Whitespace() {
|
||||||
LexerTest test = start_up_test(SLS_STR("Char With Leading Whitespace"), SLS_STR(" 'A'"));
|
LexerTest test = start_up_test(SLS_STR("Char With Leading Whitespace"), SLS_STR(" 'A'"));
|
||||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||||
|
|
@ -3845,10 +3845,10 @@ TestsReport run_lexer_tests() {
|
||||||
test_report.tests[i++] = test_Float_Default_Negative_Zero();
|
test_report.tests[i++] = test_Float_Default_Negative_Zero();
|
||||||
test_report.tests[i++] = test_Float_f32_Positive_Zero();
|
test_report.tests[i++] = test_Float_f32_Positive_Zero();
|
||||||
test_report.tests[i++] = test_Float_f32_Negative_Zero();
|
test_report.tests[i++] = test_Float_f32_Negative_Zero();
|
||||||
test_report.tests[i++] = test_Char_Simple_Letter_A();
|
test_report.tests[i++] = test_Char_Simple_Letter_Uppercase_A();
|
||||||
test_report.tests[i++] = test_Char_Simple_Letter_a();
|
test_report.tests[i++] = test_Char_Simple_Letter_Lowercase_a();
|
||||||
test_report.tests[i++] = test_Char_Simple_Letter_Z();
|
test_report.tests[i++] = test_Char_Simple_Letter_Uppercase_Z();
|
||||||
test_report.tests[i++] = test_Char_Simple_Letter_z();
|
test_report.tests[i++] = test_Char_Simple_Letter_Lowercase_z();
|
||||||
test_report.tests[i++] = test_Char_Digit_0();
|
test_report.tests[i++] = test_Char_Digit_0();
|
||||||
test_report.tests[i++] = test_Char_Digit_5();
|
test_report.tests[i++] = test_Char_Digit_5();
|
||||||
test_report.tests[i++] = test_Char_Digit_9();
|
test_report.tests[i++] = test_Char_Digit_9();
|
||||||
|
|
@ -3872,12 +3872,12 @@ TestsReport run_lexer_tests() {
|
||||||
test_report.tests[i++] = test_Char_Right_Bracket();
|
test_report.tests[i++] = test_Char_Right_Bracket();
|
||||||
test_report.tests[i++] = test_Char_Left_Brace();
|
test_report.tests[i++] = test_Char_Left_Brace();
|
||||||
test_report.tests[i++] = test_Char_Right_Brace();
|
test_report.tests[i++] = test_Char_Right_Brace();
|
||||||
test_report.tests[i++] = test_Char_Escape_Tab();
|
|
||||||
test_report.tests[i++] = test_Char_Escape_Backslash();
|
|
||||||
test_report.tests[i++] = test_Char_Escape_Null_character();
|
|
||||||
test_report.tests[i++] = test_Char_Escape_Single_quote();
|
test_report.tests[i++] = test_Char_Escape_Single_quote();
|
||||||
test_report.tests[i++] = test_Char_Escape_Carriage_return();
|
|
||||||
test_report.tests[i++] = test_Char_Escape_Newline();
|
test_report.tests[i++] = test_Char_Escape_Newline();
|
||||||
|
test_report.tests[i++] = test_Char_Escape_Null_character();
|
||||||
|
test_report.tests[i++] = test_Char_Escape_Backslash();
|
||||||
|
test_report.tests[i++] = test_Char_Escape_Tab();
|
||||||
|
test_report.tests[i++] = test_Char_Escape_Carriage_return();
|
||||||
test_report.tests[i++] = test_Char_With_Leading_Whitespace();
|
test_report.tests[i++] = test_Char_With_Leading_Whitespace();
|
||||||
test_report.tests[i++] = test_Char_With_Trailing_Whitespace();
|
test_report.tests[i++] = test_Char_With_Trailing_Whitespace();
|
||||||
test_report.tests[i++] = test_Char_With_Both_Whitespace();
|
test_report.tests[i++] = test_Char_With_Both_Whitespace();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
__pycache__/
|
||||||
|
.venv/
|
||||||
|
sls_python.egg-info/
|
||||||
|
dist/
|
||||||
|
|
@ -1,3 +1,19 @@
|
||||||
# SLS Python
|
# SLS Python
|
||||||
|
|
||||||
This is the Python implementation for the YREA SLS interpreter.
|
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 @@
|
||||||
|
/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1993,7 +1993,7 @@
|
||||||
stack_final:
|
stack_final:
|
||||||
- type: f32
|
- type: f32
|
||||||
value: -0.0
|
value: -0.0
|
||||||
- name: Char Simple Letter A
|
- name: Char Simple Letter Uppercase A
|
||||||
code: '''A'''
|
code: '''A'''
|
||||||
tokens:
|
tokens:
|
||||||
- type: char
|
- type: char
|
||||||
|
|
@ -2005,7 +2005,7 @@
|
||||||
stack_final:
|
stack_final:
|
||||||
- type: char
|
- type: char
|
||||||
value: A
|
value: A
|
||||||
- name: Char Simple Letter a
|
- name: Char Simple Letter Lowercase a
|
||||||
code: '''a'''
|
code: '''a'''
|
||||||
tokens:
|
tokens:
|
||||||
- type: char
|
- type: char
|
||||||
|
|
@ -2017,7 +2017,7 @@
|
||||||
stack_final:
|
stack_final:
|
||||||
- type: char
|
- type: char
|
||||||
value: a
|
value: a
|
||||||
- name: Char Simple Letter Z
|
- name: Char Simple Letter Uppercase Z
|
||||||
code: '''Z'''
|
code: '''Z'''
|
||||||
tokens:
|
tokens:
|
||||||
- type: char
|
- type: char
|
||||||
|
|
@ -2029,7 +2029,7 @@
|
||||||
stack_final:
|
stack_final:
|
||||||
- type: char
|
- type: char
|
||||||
value: Z
|
value: Z
|
||||||
- name: Char Simple Letter z
|
- name: Char Simple Letter Lowercase z
|
||||||
code: '''z'''
|
code: '''z'''
|
||||||
tokens:
|
tokens:
|
||||||
- type: char
|
- type: char
|
||||||
|
|
@ -2317,42 +2317,6 @@
|
||||||
stack_final:
|
stack_final:
|
||||||
- type: char
|
- type: char
|
||||||
value: '}'
|
value: '}'
|
||||||
- name: Char Escape Tab
|
|
||||||
code: '''\\t'''
|
|
||||||
tokens:
|
|
||||||
- type: char
|
|
||||||
value: "\t"
|
|
||||||
operations:
|
|
||||||
- function: push
|
|
||||||
type: char
|
|
||||||
value: "\t"
|
|
||||||
stack_final:
|
|
||||||
- type: char
|
|
||||||
value: "\t"
|
|
||||||
- name: Char Escape Backslash
|
|
||||||
code: '''\\\\'''
|
|
||||||
tokens:
|
|
||||||
- type: char
|
|
||||||
value: \
|
|
||||||
operations:
|
|
||||||
- function: push
|
|
||||||
type: char
|
|
||||||
value: \
|
|
||||||
stack_final:
|
|
||||||
- type: char
|
|
||||||
value: \
|
|
||||||
- name: Char Escape Null character
|
|
||||||
code: '''\\0'''
|
|
||||||
tokens:
|
|
||||||
- type: char
|
|
||||||
value: "\0"
|
|
||||||
operations:
|
|
||||||
- function: push
|
|
||||||
type: char
|
|
||||||
value: "\0"
|
|
||||||
stack_final:
|
|
||||||
- type: char
|
|
||||||
value: "\0"
|
|
||||||
- name: Char Escape Single quote
|
- name: Char Escape Single quote
|
||||||
code: '''\\'''''
|
code: '''\\'''''
|
||||||
tokens:
|
tokens:
|
||||||
|
|
@ -2365,18 +2329,6 @@
|
||||||
stack_final:
|
stack_final:
|
||||||
- type: char
|
- type: char
|
||||||
value: ''''
|
value: ''''
|
||||||
- name: Char Escape Carriage return
|
|
||||||
code: '''\\r'''
|
|
||||||
tokens:
|
|
||||||
- type: char
|
|
||||||
value: "\r"
|
|
||||||
operations:
|
|
||||||
- function: push
|
|
||||||
type: char
|
|
||||||
value: "\r"
|
|
||||||
stack_final:
|
|
||||||
- type: char
|
|
||||||
value: "\r"
|
|
||||||
- name: Char Escape Newline
|
- name: Char Escape Newline
|
||||||
code: '''\\n'''
|
code: '''\\n'''
|
||||||
tokens:
|
tokens:
|
||||||
|
|
@ -2395,6 +2347,54 @@
|
||||||
value: '
|
value: '
|
||||||
|
|
||||||
'
|
'
|
||||||
|
- name: Char Escape Null character
|
||||||
|
code: '''\\0'''
|
||||||
|
tokens:
|
||||||
|
- type: char
|
||||||
|
value: "\0"
|
||||||
|
operations:
|
||||||
|
- function: push
|
||||||
|
type: char
|
||||||
|
value: "\0"
|
||||||
|
stack_final:
|
||||||
|
- type: char
|
||||||
|
value: "\0"
|
||||||
|
- name: Char Escape Backslash
|
||||||
|
code: '''\\\\'''
|
||||||
|
tokens:
|
||||||
|
- type: char
|
||||||
|
value: \
|
||||||
|
operations:
|
||||||
|
- function: push
|
||||||
|
type: char
|
||||||
|
value: \
|
||||||
|
stack_final:
|
||||||
|
- type: char
|
||||||
|
value: \
|
||||||
|
- name: Char Escape Tab
|
||||||
|
code: '''\\t'''
|
||||||
|
tokens:
|
||||||
|
- type: char
|
||||||
|
value: "\t"
|
||||||
|
operations:
|
||||||
|
- function: push
|
||||||
|
type: char
|
||||||
|
value: "\t"
|
||||||
|
stack_final:
|
||||||
|
- type: char
|
||||||
|
value: "\t"
|
||||||
|
- name: Char Escape Carriage return
|
||||||
|
code: '''\\r'''
|
||||||
|
tokens:
|
||||||
|
- type: char
|
||||||
|
value: "\r"
|
||||||
|
operations:
|
||||||
|
- function: push
|
||||||
|
type: char
|
||||||
|
value: "\r"
|
||||||
|
stack_final:
|
||||||
|
- type: char
|
||||||
|
value: "\r"
|
||||||
- name: Char With Leading Whitespace
|
- name: Char With Leading Whitespace
|
||||||
code: ' ''A'''
|
code: ' ''A'''
|
||||||
tokens:
|
tokens:
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,10 @@ class CharTestGenerator(BaseTestGenerator):
|
||||||
def generate_basic_tests(self):
|
def generate_basic_tests(self):
|
||||||
"""Generate basic character literal tests."""
|
"""Generate basic character literal tests."""
|
||||||
# Simple ASCII letters
|
# Simple ASCII letters
|
||||||
self.make_success_test("Char Simple Letter A", "'A'", "char", 'A')
|
self.make_success_test("Char Simple Letter Uppercase A", "'A'", "char", 'A')
|
||||||
self.make_success_test("Char Simple Letter a", "'a'", "char", 'a')
|
self.make_success_test("Char Simple Letter Lowercase a", "'a'", "char", 'a')
|
||||||
self.make_success_test("Char Simple Letter Z", "'Z'", "char", 'Z')
|
self.make_success_test("Char Simple Letter Uppercase Z", "'Z'", "char", 'Z')
|
||||||
self.make_success_test("Char Simple Letter z", "'z'", "char", 'z')
|
self.make_success_test("Char Simple Letter Lowercase z", "'z'", "char", 'z')
|
||||||
|
|
||||||
# Digits
|
# Digits
|
||||||
self.make_success_test("Char Digit 0", "'0'", "char", '0')
|
self.make_success_test("Char Digit 0", "'0'", "char", '0')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue