Compare commits

...

63 Commits

Author SHA1 Message Date
Kyler Olsen d775ab6067 Updated .gitignore 2025-12-16 22:19:46 -07:00
Kyler Olsen abc2d2e24a Added contributing link to readme 2025-12-10 00:10:49 -07:00
Kyler Olsen 7b35803e8f Deleted slides (they are in docs) 2025-12-09 23:43:31 -07:00
Kyler Olsen 3773e76c95 Cleaned up some of the stuff from the assignment 2025-12-09 23:21:33 -07:00
Kyler Olsen c091ffacbd Saving stuff from presentation 2025-12-09 23:06:51 -07:00
Kyler Olsen 7aa2a36e1a Moved sls_py.calc 2025-12-08 00:07:27 -07:00
Kyler Olsen 56dcf72241 updated build instructions 2025-12-08 00:02:02 -07:00
Kyler Olsen 24154387ec Added build instructions 2025-12-07 23:59:59 -07:00
Kyler Olsen 0f4b851958 Built python package 2025-12-07 23:59:49 -07:00
Kyler Olsen 60e2f40816 Version Bump 2025-12-07 23:22:53 -07:00
Kyler Olsen 102c5b418a Fixed normal builds 2025-12-07 23:18:11 -07:00
Kyler Olsen 7c0f20a1af Just some final stuff 2025-12-07 23:09:54 -07:00
Kyler Olsen 634c63d294 Added readme to slides 2025-12-07 00:20:50 -07:00
Kyler Olsen a20e8a4d54 Created slides 2025-12-07 00:17:32 -07:00
Kyler Olsen 494639d805 Worked on report 2025-12-06 22:21:31 -07:00
Kyler Olsen 4c9aa78dc8 Listed special features in report 2025-12-05 00:01:00 -07:00
Kyler Olsen f5c786c061 sls_calc module polishing 2025-12-04 23:57:48 -07:00
Kyler Olsen 8890216457 Updated sls_calc module 2025-12-04 23:52:42 -07:00
Kyler Olsen d7164599f0 Fixed circular imports 2025-12-04 23:52:24 -07:00
Kyler Olsen dc5d75cf75 Made interpreter state json serializable in Rust port 2025-12-04 23:06:42 -07:00
Kyler Olsen 7b57807fc7 Started sls_calc app 2025-12-04 22:12:48 -07:00
Kyler Olsen d8e38ada85 Added info string constants 2025-12-04 21:51:22 -07:00
Kyler Olsen 0b37a4210f Python module polishing 2025-12-04 21:47:25 -07:00
Kyler Olsen 2ac3c1ac26 Added pico support 2025-12-04 01:09:30 -07:00
Kyler Olsen 642c277388 It compiles now 2025-12-04 00:20:43 -07:00
Kyler Olsen c1c54277a7 Fixed path error 2025-12-03 23:57:45 -07:00
Kyler Olsen c89acbbdfb Started Raspberry Pi Pico build target 2025-12-03 23:57:33 -07:00
Kyler Olsen 3a57fd7373 Added builtin operators to python port 2025-12-03 23:43:32 -07:00
Kyler Olsen 60c6e3900f More on report 2025-12-03 23:28:08 -07:00
Kyler Olsen b98012488a Worked on report 2025-12-03 16:45:37 -07:00
Kyler Olsen c74ba18067 Fixed version numbers 2025-12-03 15:14:29 -07:00
Kyler Olsen 5b5e24e633 Cleaned up stuff from porting 2025-12-03 15:07:52 -07:00
Kyler Olsen 35a6f4537f Fixes to file and repl 2025-12-03 14:58:37 -07:00
Kyler Olsen 64f620b2ef Claude implementation of lexer.py 2025-12-03 14:57:15 -07:00
Kyler Olsen 8e918dcf34 Merge branch 'master' into python 2025-12-03 11:14:04 -07:00
Kyler Olsen 2dabd4bf30 Renamed python module 2025-12-03 11:11:08 -07:00
Kyler Olsen 835132577f renaming rust bin 2025-12-03 11:03:28 -07:00
Kyler Olsen 1c62f064a4 Fixed errors in builtin and interpreter 2025-12-03 10:53:46 -07:00
Kyler Olsen e76287b9a4 Claude builtin implementation 2025-12-03 00:40:22 -07:00
Kyler Olsen 3a054bc211 Updated python .gitignore 2025-12-02 23:39:38 -07:00
Kyler Olsen 2c8e459cac Added file and main 2025-12-02 23:34:29 -07:00
Kyler Olsen a754cd4df4 Refactor version generation and error handling in versioning scripts 2025-12-02 23:18:41 -07:00
Kyler Olsen 4a2ee88328 Refactor versioning and metadata handling in the Python module 2025-12-02 22:51:34 -07:00
Kyler Olsen 1a11569e9a Readded introductory description to README.md 2025-12-02 22:24:04 -07:00
Kyler Olsen ac9bbc0415 Update build configuration and enhance versioning process 2025-12-02 22:23:11 -07:00
Kyler Olsen c1db0937a2 Created sls_build_backend module to help build the python module 2025-12-02 22:22:53 -07:00
Kyler Olsen 3c288e9165 worked on filling in the project 2025-12-02 21:38:27 -07:00
Kyler Olsen 3dc5455323 Python Project Skeleton 2025-12-02 19:10:11 -07:00
Kyler Olsen 081930e6f5 Fixed warnings 2025-12-02 11:52:20 -07:00
Kyler Olsen 6f4be5a929 Small fixes 2025-12-02 11:31:46 -07:00
Kyler Olsen a154741176 Added numeric type annotations 2025-12-02 11:31:27 -07:00
Kyler Olsen a26a1c0d4a Merge branch 'rust' into rust-claude 2025-12-02 10:45:31 -07:00
Kyler Olsen 08f0437136 It compiles 2025-12-02 00:37:53 -07:00
Kyler Olsen 08a8aadf16 Claude attempt at lexer.rs 2025-12-01 23:46:46 -07:00
Kyler Olsen ae077ef433 All reimplemented, lexer still needs to be finished 2025-12-01 23:37:10 -07:00
Kyler Olsen d17687e5a6 Changed test function names to be snake case 2025-12-01 22:33:51 -07:00
Kyler Olsen 1875f2debd Refactor token_match_expectation to improve numeric and char handling 2025-12-01 17:27:25 -07:00
Kyler Olsen 483e0c3d52 Add YAML to Rust test case generator 2025-12-01 16:19:07 -07:00
Kyler Olsen e1c43f7b2e Refactor lexer to implement token types and enhance token generation logic 2025-12-01 09:14:35 -07:00
Kyler Olsen a15490b521 Remove unneeded HashTable and SlsStr implementations 2025-12-01 09:12:04 -07:00
Kyler Olsen 6f81cbdf15 Implement core modules and initial interpreter setup for SLS Rust 2025-12-01 09:08:27 -07:00
Kyler Olsen b70634b450 Started rust port 2025-12-01 08:53:14 -07:00
Kyler Olsen 58f41e6bda added .gitignores for projects 2025-12-01 08:50:13 -07:00
47 changed files with 6629 additions and 222 deletions

View File

@ -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*
[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
main structures of the ports, I hope it will be enough for me to finish.
## Checkpoint 2
## SE Checkpoint 2
*20 Nov 2025*
[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
- Structs, unions, etc.
## Checkpoint 1
## SE Checkpoint 1
*07 Nov 2025*
**intended user**:

191
README.md
View File

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

92
REPORT.md Normal file
View File

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

5
SLS_C/.gitignore vendored
View File

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

View File

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

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

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

17
SLS_C/fib.sls Normal file
View File

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

View File

@ -9,6 +9,23 @@
#include "sls/bool.h"
#include "sls/interpreter.h"
#if __SIZEOF_POINTER__ == 4
// 32-bit system
#define SLS_INTEGER_BUILTIN_DEFAULT INTEGER_I32
#define SLS_UNSIGNED_INTEGER_BUILTIN_DEFAULT INTEGER_U32
#define SLS_FLOAT_BUILTIN_DEFAULT TOKEN_FLOAT
#elif __SIZEOF_POINTER__ == 8
// 64-bit system
#define SLS_INTEGER_BUILTIN_DEFAULT INTEGER_I64
#define SLS_UNSIGNED_INTEGER_BUILTIN_DEFAULT INTEGER_U64
#define SLS_FLOAT_BUILTIN_DEFAULT TOKEN_DOUBLE
#else
// Fallback
#define SLS_INTEGER_BUILTIN_DEFAULT INTEGER_I16
#define SLS_UNSIGNED_INTEGER_BUILTIN_DEFAULT INTEGER_U16
#define SLS_FLOAT_BUILTIN_DEFAULT TOKEN_FLOAT
#endif
Boolean load_builtins(InterpreterState *interpreter_state);
#endif // SLS_BUILTIN_FUNCTIONS_H

View File

@ -11,6 +11,20 @@
#include "sls/bool.h"
#include "sls/errors.h"
#if __SIZEOF_POINTER__ == 4
// 32-bit system
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I32
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
#elif __SIZEOF_POINTER__ == 8
// 64-bit system
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I64
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F64
#else
// Fallback
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I16
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
#endif
extern const size_t TYPE_NAMES_SAFE_LENGTH;
typedef struct {

View File

@ -7,7 +7,7 @@
#define SLS_MAIN_H
#define SLS_NAME "SLS_C"
#define SLS_VER "a.0.0"
#define SLS_VER "0.0.2-alpha"
#ifndef GIT_COMMIT_HASH
#define GIT_COMMIT_HASH "UNKNOWN"

View File

@ -687,7 +687,7 @@ Boolean load_builtins(InterpreterState *interpreter_state) {
clean_stack(node); \
\
return push_token(interpreter_state, (Token){ \
.type = TOKEN_DOUBLE, \
.type = SLS_FLOAT_BUILTIN_DEFAULT, \
.double_literal = func(af), \
}); \
@ -897,7 +897,7 @@ Boolean builtin_division(InterpreterState *interpreter_state) {
clean_stack(node);
return push_token(interpreter_state, (Token){
.type = TOKEN_DOUBLE,
.type = SLS_FLOAT_BUILTIN_DEFAULT,
.double_literal = bf / af,
});
}
@ -997,7 +997,7 @@ Boolean builtin_exponential(InterpreterState *interpreter_state) {
clean_stack(node);
return push_token(interpreter_state, (Token){
.type = TOKEN_DOUBLE,
.type = SLS_FLOAT_BUILTIN_DEFAULT,
.double_literal = pow(bf, af),
});
}
@ -1210,7 +1210,7 @@ Boolean builtin_atan2(InterpreterState *interpreter_state) {
clean_stack(node);
return push_token(interpreter_state, (Token){
.type = TOKEN_DOUBLE,
.type = SLS_FLOAT_BUILTIN_DEFAULT,
.double_literal = atan2(bf, af),
});
}
@ -1573,7 +1573,7 @@ Boolean builtin_depth(InterpreterState *interpreter_state) {
return push_token(interpreter_state, (Token){
.type = TOKEN_INTEGER,
.integer_literal = (IntegerLiteral){
.type = INTEGER_U64,
.type = SLS_UNSIGNED_INTEGER_BUILTIN_DEFAULT,
.value = (uint64_t)depth,
},
});
@ -1695,7 +1695,7 @@ Boolean builtin_for(InterpreterState *interpreter_state) {
return_value = push_token(interpreter_state, (Token){
.type = TOKEN_INTEGER,
.integer_literal = (IntegerLiteral){
.type = INTEGER_I64,
.type = SLS_INTEGER_BUILTIN_DEFAULT,
.value = (uint64_t)i,
},
});
@ -1893,7 +1893,7 @@ Boolean builtin_pick(InterpreterState *interpreter_state) {
Boolean builtin_rand(InterpreterState *interpreter_state) {
return push_token(interpreter_state, (Token){
.type = TOKEN_DOUBLE,
.type = SLS_FLOAT_BUILTIN_DEFAULT,
.double_literal = rand() / RAND_MAX,
});
}

View File

@ -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 (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_binary_integer(lexer_info, start);
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
}
SlsStr error_msg = sls_format(SLS_STR("Invalid binary literal: unexpected '%c' in binary integer."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
@ -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 (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_octal_integer(lexer_info, start);
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
}
SlsStr error_msg = sls_format(SLS_STR("Invalid octal literal: unexpected '%c' in octal integer."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
@ -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 == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_FLOAT);
if (isspace(c) || c == '/' || c == '\0')
return create_float_token(lexer_info, NUMERIC_F64, start, start_line);
return create_float_token(lexer_info, SLS_FLOAT_TYPE_DEFAULT, start, start_line);
SlsStr error_msg = sls_format(SLS_STR("Invalid float literal: unexpected '%c' in float."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_msg, start, start_line);
@ -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 (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_decimal_integer(lexer_info, start);
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
}
SlsStr error_msg = sls_format(SLS_STR("Invalid decimal literal: unexpected '%c' in decimal integer."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
@ -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 (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_hexadecimal_integer(lexer_info, start);
return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line);
}
SlsStr error_msg = sls_format(SLS_STR("Invalid hexadecimal literal: unexpected '%c' in hexadecimal integer."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};

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

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

View File

@ -1803,8 +1803,8 @@ static TestResult test_Float_f32_Negative_Zero() {
return pass_test(&test, result);
}
static TestResult test_Char_Simple_Letter_A() {
LexerTest test = start_up_test(SLS_STR("Char Simple Letter A"), SLS_STR("'A'"));
static TestResult test_Char_Simple_Letter_Uppercase_A() {
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Uppercase A"), SLS_STR("'A'"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
@ -1813,8 +1813,8 @@ static TestResult test_Char_Simple_Letter_A() {
return pass_test(&test, result);
}
static TestResult test_Char_Simple_Letter_a() {
LexerTest test = start_up_test(SLS_STR("Char Simple Letter a"), SLS_STR("'a'"));
static TestResult test_Char_Simple_Letter_Lowercase_a() {
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Lowercase a"), SLS_STR("'a'"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
@ -1823,8 +1823,8 @@ static TestResult test_Char_Simple_Letter_a() {
return pass_test(&test, result);
}
static TestResult test_Char_Simple_Letter_Z() {
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Z"), SLS_STR("'Z'"));
static TestResult test_Char_Simple_Letter_Uppercase_Z() {
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Uppercase Z"), SLS_STR("'Z'"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
@ -1833,8 +1833,8 @@ static TestResult test_Char_Simple_Letter_Z() {
return pass_test(&test, result);
}
static TestResult test_Char_Simple_Letter_z() {
LexerTest test = start_up_test(SLS_STR("Char Simple Letter z"), SLS_STR("'z'"));
static TestResult test_Char_Simple_Letter_Lowercase_z() {
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Lowercase z"), SLS_STR("'z'"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
@ -2073,22 +2073,22 @@ static TestResult test_Char_Right_Brace() {
return pass_test(&test, result);
}
static TestResult test_Char_Escape_Tab() {
LexerTest test = start_up_test(SLS_STR("Char Escape Tab"), SLS_STR("'\\t'"));
static TestResult test_Char_Escape_Single_quote() {
LexerTest test = start_up_test(SLS_STR("Char Escape Single quote"), SLS_STR("'\\''"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
if (test_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;
return pass_test(&test, result);
}
static TestResult test_Char_Escape_Backslash() {
LexerTest test = start_up_test(SLS_STR("Char Escape Backslash"), SLS_STR("'\\\\'"));
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){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;
return pass_test(&test, result);
}
@ -2103,12 +2103,22 @@ static TestResult test_Char_Escape_Null_character() {
return pass_test(&test, result);
}
static TestResult test_Char_Escape_Single_quote() {
LexerTest test = start_up_test(SLS_STR("Char Escape Single quote"), SLS_STR("'\\''"));
static TestResult test_Char_Escape_Backslash() {
LexerTest test = start_up_test(SLS_STR("Char Escape Backslash"), SLS_STR("'\\\\'"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
if (test_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;
return pass_test(&test, result);
}
@ -2123,16 +2133,6 @@ static TestResult test_Char_Escape_Carriage_return() {
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() {
LexerTest test = start_up_test(SLS_STR("Char With Leading Whitespace"), SLS_STR(" 'A'"));
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_f32_Positive_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_a();
test_report.tests[i++] = test_Char_Simple_Letter_Z();
test_report.tests[i++] = test_Char_Simple_Letter_z();
test_report.tests[i++] = test_Char_Simple_Letter_Uppercase_A();
test_report.tests[i++] = test_Char_Simple_Letter_Lowercase_a();
test_report.tests[i++] = test_Char_Simple_Letter_Uppercase_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_5();
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_Left_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_Carriage_return();
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_Trailing_Whitespace();
test_report.tests[i++] = test_Char_With_Both_Whitespace();

4
SLS_Python/.gitignore vendored Normal file
View File

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

View File

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

20
SLS_Python/pyproject.toml Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

142
SLS_Python/sls_py.pyi Normal file
View File

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

View File

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

View File

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

View File

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

1267
SLS_Python/sls_py/builtin.py Normal file

File diff suppressed because it is too large Load Diff

View File

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

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

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

BIN
SLS_Rust/sls/core Normal file

Binary file not shown.

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

File diff suppressed because it is too large Load Diff

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

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

View File

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

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

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

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

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

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

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

View File

@ -1993,7 +1993,7 @@
stack_final:
- type: f32
value: -0.0
- name: Char Simple Letter A
- name: Char Simple Letter Uppercase A
code: '''A'''
tokens:
- type: char
@ -2005,7 +2005,7 @@
stack_final:
- type: char
value: A
- name: Char Simple Letter a
- name: Char Simple Letter Lowercase a
code: '''a'''
tokens:
- type: char
@ -2017,7 +2017,7 @@
stack_final:
- type: char
value: a
- name: Char Simple Letter Z
- name: Char Simple Letter Uppercase Z
code: '''Z'''
tokens:
- type: char
@ -2029,7 +2029,7 @@
stack_final:
- type: char
value: Z
- name: Char Simple Letter z
- name: Char Simple Letter Lowercase z
code: '''z'''
tokens:
- type: char
@ -2317,42 +2317,6 @@
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 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
code: '''\\'''''
tokens:
@ -2365,18 +2329,6 @@
stack_final:
- type: char
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
code: '''\\n'''
tokens:
@ -2395,6 +2347,54 @@
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
code: ' ''A'''
tokens:

View File

@ -28,10 +28,10 @@ class CharTestGenerator(BaseTestGenerator):
def generate_basic_tests(self):
"""Generate basic character literal tests."""
# Simple ASCII letters
self.make_success_test("Char Simple Letter A", "'A'", "char", 'A')
self.make_success_test("Char Simple Letter a", "'a'", "char", 'a')
self.make_success_test("Char Simple Letter Z", "'Z'", "char", 'Z')
self.make_success_test("Char Simple Letter z", "'z'", "char", 'z')
self.make_success_test("Char Simple Letter Uppercase A", "'A'", "char", 'A')
self.make_success_test("Char Simple Letter Lowercase a", "'a'", "char", 'a')
self.make_success_test("Char Simple Letter Uppercase Z", "'Z'", "char", 'Z')
self.make_success_test("Char Simple Letter Lowercase z", "'z'", "char", 'z')
# Digits
self.make_success_test("Char Digit 0", "'0'", "char", '0')