Compare commits

..

No commits in common. "master" and "SE3250-Checkpoint2" have entirely different histories.

82 changed files with 1751 additions and 15265 deletions

View File

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

53
PROGRESS.md Normal file
View File

@ -0,0 +1,53 @@
# SE 3250 Progress Checkpoints
## Checkpoint 1
*07 Nov 2025*
**intended user**:
Programmers
**detailed functional requirements**:
A stack based language interpreter inspired by RPL, C, Rust, and Uiua
([sls.purplecello.org](https://sls.purplecello.org))
**motivation for wanting to work on the project**:
It is cool, and I have had this idea for a while and learning new languages this
semester has given me more ideas and motivation for this.
**motivation for language selection**:
C is a lower level language that can run on embedded systems and is well suited
for writing language interpreters like Python and Lua.
**non-functional requirements / constraints**:
Efficient enough for use on a modern micro controller
(ie. Raspberry Pi Pico, ESP32)
## Checkpoint 2
*20 Nov 2025*
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
[Compare SE3250-Checkpoint1..SE3250-Checkpoint2](https://github.com/SnowSE/final-project-KylerOlsen/compare/SE3250-Checkpoint1...SE3250-Checkpoint2)
**Report**:
I completed my string type for the interpreter. `floats`, `characters`,
`identifiers`, and `boolean` token lexing has been completed. `integer` tokens
were working at checkpoint 1.
**Plan**:
By checkpoint 3 I plan to complete `token string` token parsing and basic
arithmetic execution.
**Stuck**:
I'm not really stuck on anything, but I have been dropping things from the scope
of this assignment. I do hope to add them when I have time in the future. These
features moved to the backlog include:
- Unicode support
- Exponential literals
- Arrays and array functions
- Type tuples, function defs
- Structs, unions, etc.

191
README.md
View File

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

View File

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

6
SLS_C/.gitignore vendored
View File

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

View File

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

77
SLS_C/Makefile Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +0,0 @@
{
// 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

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

View File

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

View File

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

View File

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

View File

@ -11,20 +11,6 @@
#include "sls/bool.h" #include "sls/bool.h"
#include "sls/errors.h" #include "sls/errors.h"
#if __SIZEOF_POINTER__ == 4
// 32-bit system
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I32
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
#elif __SIZEOF_POINTER__ == 8
// 64-bit system
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I64
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F64
#else
// Fallback
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I16
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
#endif
extern const size_t TYPE_NAMES_SAFE_LENGTH; extern const size_t TYPE_NAMES_SAFE_LENGTH;
typedef struct { typedef struct {
@ -50,7 +36,6 @@ typedef enum {
} TokenType; } TokenType;
extern const char *TOKEN_TYPES_NAMES[]; extern const char *TOKEN_TYPES_NAMES[];
extern const size_t TOKEN_TYPE_COUNT;
typedef enum { typedef enum {
ARRAY_IDENTIFIER, ARRAY_IDENTIFIER,
@ -71,7 +56,6 @@ typedef enum {
} ArrayType; } ArrayType;
extern const char *ARRAY_TYPES_NAMES[]; extern const char *ARRAY_TYPES_NAMES[];
extern const size_t ARRAY_TYPE_COUNT;
typedef struct { typedef struct {
SlsStr name; SlsStr name;
@ -90,7 +74,6 @@ typedef enum {
} IntegerBuiltInType; } IntegerBuiltInType;
extern const char *INTEGER_TYPES_NAMES[]; extern const char *INTEGER_TYPES_NAMES[];
extern const size_t INTEGER_TYPE_COUNT;
typedef struct { typedef struct {
uint64_t value; uint64_t value;
@ -168,10 +151,8 @@ typedef struct {
}; };
} LexerResult; } LexerResult;
TokenString copy_token_string(TokenString token_string);
void init_lexer(LexerInfo *lexer_info, SlsStr filename, SlsStr source_code); void init_lexer(LexerInfo *lexer_info, SlsStr filename, SlsStr source_code);
LexerTokenResult *get_token(LexerTokenResult *head, size_t i); LexerTokenResult *get_token(LexerTokenResult *head, size_t i);
void clean_token_string(TokenString token_string);
void clean_token_result(LexerTokenResult *head); void clean_token_result(LexerTokenResult *head);
LexerResult lexical_analysis(LexerInfo *lexer_info); LexerResult lexical_analysis(LexerInfo *lexer_info);

View File

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

View File

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

View File

@ -18,10 +18,8 @@ typedef struct {
Boolean allocated; Boolean allocated;
} SlsStr; } SlsStr;
#define SLS_STR_CONST(s) {sizeof(s) - 1, (s), FALSE} #define SLS_STR(s) (SlsStr){ sizeof(s) - 1, (s), FALSE }
#define SLS_STR_NULL_CONST {0, NULL, FALSE} #define SLS_STR_NULL (SlsStr){0, NULL, FALSE}
#define SLS_STR(s) (SlsStr) SLS_STR_CONST(s)
#define SLS_STR_NULL (SlsStr) SLS_STR_NULL_CONST
int sls_isascii(unsigned char c); int sls_isascii(unsigned char c);
size_t sls_str_nlen(const char *s, size_t maxlen); size_t sls_str_nlen(const char *s, size_t maxlen);

View File

@ -124,6 +124,6 @@ Boolean test_array_boolean_value(LexerTest *test, LexerResult result, size_t i,
Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size_t i, TestArrayStructInlineValue *values); Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size_t i, TestArrayStructInlineValue *values);
Boolean test_token_string_value(LexerTest *test, LexerResult result, size_t i, TestTokenStringValue *values); Boolean test_token_string_value(LexerTest *test, LexerResult result, size_t i, TestTokenStringValue *values);
Boolean test_type_tuple_value(LexerTest *test, LexerResult result, size_t i, TestTypeTupleValue *values); Boolean test_type_tuple_value(LexerTest *test, LexerResult result, size_t i, TestTypeTupleValue *values);
Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr *error); Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr error);
#endif // SLS_LEXER_TEST_HELPERS_H #endif // SLS_LEXER_TEST_HELPERS_H

View File

@ -39,7 +39,6 @@ typedef struct {
TestsReport run_string_tests(); TestsReport run_string_tests();
TestsReport run_lexer_tests(); TestsReport run_lexer_tests();
TestsReport run_hash_table_tests();
TestsReport run_extra_tests(); TestsReport run_extra_tests();
#endif // SLS_TESTS_H #endif // SLS_TESTS_H

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -32,8 +32,6 @@ const char *TOKEN_TYPES_NAMES[] = {
"Type Tuple", "Type Tuple",
}; };
const size_t TOKEN_TYPE_COUNT = sizeof(TOKEN_TYPES_NAMES) / sizeof(*TOKEN_TYPES_NAMES);
const char *ARRAY_TYPES_NAMES[] = { const char *ARRAY_TYPES_NAMES[] = {
"Identifier", "Identifier",
"i64", "i64",
@ -52,8 +50,6 @@ const char *ARRAY_TYPES_NAMES[] = {
"Inline Struct", "Inline Struct",
}; };
const size_t ARRAY_TYPE_COUNT = sizeof(ARRAY_TYPES_NAMES) / sizeof(*ARRAY_TYPES_NAMES);
const char *INTEGER_TYPES_NAMES[] = { const char *INTEGER_TYPES_NAMES[] = {
"i64", "i64",
"i32", "i32",
@ -65,8 +61,6 @@ const char *INTEGER_TYPES_NAMES[] = {
"u8", "u8",
}; };
const size_t INTEGER_TYPE_COUNT = sizeof(INTEGER_TYPES_NAMES) / sizeof(*INTEGER_TYPES_NAMES);
void init_lexer(LexerInfo *lexer_info, SlsStr filename, SlsStr source_code) { void init_lexer(LexerInfo *lexer_info, SlsStr filename, SlsStr source_code) {
// Initializes a LexerInfo struct with file info and source code // Initializes a LexerInfo struct with file info and source code
lexer_info->filename = filename; lexer_info->filename = filename;
@ -331,44 +325,40 @@ static uint64_t create_hexadecimal_integer(LexerInfo *lexer_info, size_t start)
static LexerResult create_integer_token(LexerInfo *lexer_info, IntegerBuiltInType type, uint64_t value, size_t start, size_t start_line) { static LexerResult create_integer_token(LexerInfo *lexer_info, IntegerBuiltInType type, uint64_t value, size_t start, size_t start_line) {
switch (type) { switch (type) {
case INTEGER_I64: break; case INTEGER_I64: break;
case INTEGER_U64: break;
case INTEGER_I32: case INTEGER_I32:
if ((int64_t)value < INT32_MIN || (int64_t)value > INT32_MAX) { if (value > (uint64_t)UINT32_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i32."), start, start_line); return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i32."), start, start_line);
} }
break; break;
case INTEGER_I16: case INTEGER_I16:
if ((int64_t)value < INT16_MIN || (int64_t)value > INT16_MAX) { if (value > (uint64_t)UINT16_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i16."), start, start_line); return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i16."), start, start_line);
} }
break; break;
case INTEGER_I8: case INTEGER_I8:
if ((int64_t)value < INT8_MIN || (int64_t)value > INT8_MAX) { if (value > (uint64_t)UINT8_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i8."), start, start_line); return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for i8."), start, start_line);
} }
break; break;
case INTEGER_U64:
if (seek(lexer_info, start) == '-') {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u64."), start, start_line);
}
break;
case INTEGER_U32: case INTEGER_U32:
if (seek(lexer_info, start) == '-') { if (seek(lexer_info, start) == '-') {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u32."), start, start_line); return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u32."), start, start_line);
} else if (value > (uint64_t)UINT32_MAX) { } if (value > (uint64_t)UINT32_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u32."), start, start_line); return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u32."), start, start_line);
} }
break; break;
case INTEGER_U16: case INTEGER_U16:
if (seek(lexer_info, start) == '-') { if (seek(lexer_info, start) == '-') {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u16."), start, start_line); return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u16."), start, start_line);
} else if (value > (uint64_t)UINT16_MAX) { } if (value > (uint64_t)UINT16_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u16."), start, start_line); return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u16."), start, start_line);
} }
break; break;
case INTEGER_U8: case INTEGER_U8:
if (seek(lexer_info, start) == '-') { if (seek(lexer_info, start) == '-') {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u8."), start, start_line); return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u8."), start, start_line);
} else if (value > (uint64_t)UINT8_MAX) { } if (value > (uint64_t)UINT8_MAX) {
return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u8."), start, start_line); return lexer_error(lexer_info, SLS_STR("Integer overflow: value exceeds range for u8."), start, start_line);
} }
break; break;
@ -521,7 +511,7 @@ static LexerResult parse_binary_integer(LexerInfo *lexer_info, char c, size_t st
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_BINARY); if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_BINARY);
if (isspace(c) || c == '/' || c == '\0') { if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_binary_integer(lexer_info, start); uint64_t value = create_binary_integer(lexer_info, start);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line); return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
} }
SlsStr error_msg = sls_format(SLS_STR("Invalid binary literal: unexpected '%c' in binary integer."), c); SlsStr error_msg = sls_format(SLS_STR("Invalid binary literal: unexpected '%c' in binary integer."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}}; if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
@ -533,7 +523,7 @@ static LexerResult parse_octal_integer(LexerInfo *lexer_info, char c, size_t sta
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_OCTAL); if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_OCTAL);
if (isspace(c) || c == '/' || c == '\0') { if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_octal_integer(lexer_info, start); uint64_t value = create_octal_integer(lexer_info, start);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line); return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
} }
SlsStr error_msg = sls_format(SLS_STR("Invalid octal literal: unexpected '%c' in octal integer."), c); SlsStr error_msg = sls_format(SLS_STR("Invalid octal literal: unexpected '%c' in octal integer."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}}; if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
@ -550,7 +540,7 @@ static LexerResult parse_float(LexerInfo *lexer_info, char c, size_t start, size
if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line); if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line);
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_FLOAT); if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_FLOAT);
if (isspace(c) || c == '/' || c == '\0') if (isspace(c) || c == '/' || c == '\0')
return create_float_token(lexer_info, SLS_FLOAT_TYPE_DEFAULT, start, start_line); return create_float_token(lexer_info, NUMERIC_F64, start, start_line);
SlsStr error_msg = sls_format(SLS_STR("Invalid float literal: unexpected '%c' in float."), c); SlsStr error_msg = sls_format(SLS_STR("Invalid float literal: unexpected '%c' in float."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}}; if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_msg, start, start_line); return lexer_error(lexer_info, error_msg, start, start_line);
@ -565,7 +555,7 @@ static LexerResult parse_decimal_integer(LexerInfo *lexer_info, char c, size_t s
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_DECIMAL); if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_DECIMAL);
if (isspace(c) || c == '/' || c == '\0') { if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_decimal_integer(lexer_info, start); uint64_t value = create_decimal_integer(lexer_info, start);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line); return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
} }
SlsStr error_msg = sls_format(SLS_STR("Invalid decimal literal: unexpected '%c' in decimal integer."), c); SlsStr error_msg = sls_format(SLS_STR("Invalid decimal literal: unexpected '%c' in decimal integer."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}}; if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
@ -577,7 +567,7 @@ static LexerResult parse_hexadecimal_integer(LexerInfo *lexer_info, char c, size
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_HEXADECIMAL); if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_HEXADECIMAL);
if (isspace(c) || c == '/' || c == '\0') { if (isspace(c) || c == '/' || c == '\0') {
uint64_t value = create_hexadecimal_integer(lexer_info, start); uint64_t value = create_hexadecimal_integer(lexer_info, start);
return create_integer_token(lexer_info, SLS_INTEGER_TYPE_DEFAULT, value, start, start_line); return create_integer_token(lexer_info, INTEGER_I64, value, start, start_line);
} }
SlsStr error_msg = sls_format(SLS_STR("Invalid hexadecimal literal: unexpected '%c' in hexadecimal integer."), c); SlsStr error_msg = sls_format(SLS_STR("Invalid hexadecimal literal: unexpected '%c' in hexadecimal integer."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}}; if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
@ -652,136 +642,9 @@ static LexerResult parse_string_literal(LexerInfo *lexer_info, char c, size_t st
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: String Literals Not Implemented Error."), 1}}; return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: String Literals Not Implemented Error."), 1}};
} }
static void skip_comments_and_whitespace(LexerInfo *lexer_info) {
while (isspace(peek(lexer_info)) || (peek(lexer_info) == '/' && far_peek(lexer_info, 1) == '/') || peek(lexer_info) == '#') {
// Skip Comments
if ((peek(lexer_info) == '/' && far_peek(lexer_info, 1) == '/') || peek(lexer_info) == '#')
while (!(peek(lexer_info) == '\n' || peek(lexer_info) == '\0'))
advance(lexer_info);
// Skip whitespace
while (isspace(peek(lexer_info))) advance(lexer_info);
}
}
static LexerResult lexer_next(LexerInfo *lexer_info);
TokenString copy_token_string(TokenString token_string) {
TokenString new_string = (TokenString){
.length = token_string.length,
.tokens = (Token *)malloc(sizeof(Token) * token_string.length)
};
for (size_t i = 0; i < token_string.length; i++) {
if (token_string.tokens[i].type == TOKEN_STRING) {
new_string.tokens[i].type = TOKEN_STRING;
new_string.tokens[i].string_literal = sls_str_cpy(token_string.tokens[i].string_literal);
} else if (token_string.tokens[i].type == TOKEN_TOKEN_STRING) {
new_string.tokens[i].type = TOKEN_TOKEN_STRING;
new_string.tokens[i].token_string = copy_token_string(token_string.tokens[i].token_string);
} else {
new_string.tokens[i] = token_string.tokens[i];
}
}
return new_string;
}
static LexerResult convert_to_token_string(LexerInfo *lexer_info, LexerTokenResult *head, size_t start, size_t start_line) {
TokenString token_string = {
.length = 0,
.tokens = NULL
};
LexerTokenResult *current = head;
while (current != NULL) {
token_string.length += 1;
current = current->next;
}
current = head;
token_string.tokens = (Token *)malloc(sizeof(Token) * token_string.length);
for (size_t i = 0; i < token_string.length; i++) {
if (current->result.type == TOKEN_STRING) {
token_string.tokens[i].type = TOKEN_STRING;
token_string.tokens[i].string_literal = sls_str_cpy(current->result.string_literal);
} else if (current->result.type == TOKEN_TOKEN_STRING) {
token_string.tokens[i].type = TOKEN_TOKEN_STRING;
token_string.tokens[i].token_string = copy_token_string(current->result.token_string);
memcpy(token_string.tokens[i].token_string.tokens, current->result.token_string.tokens, current->result.token_string.length);
} else token_string.tokens[i] = current->result;
current = current->next;
}
clean_token_result(head);
return lexer_result(lexer_info, (Token){TOKEN_TOKEN_STRING, .token_string = token_string}, start, start_line);
}
static LexerResult parse_token_string(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_token_string(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
// Lexes a token string (void)lexer_info; (void)c; (void)start; (void)start_line;
LexerResult result; // For lexer_next returns return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Token Strings Not Implemented Error."), 1}};
LexerTokenResult *head = 0;
LexerTokenResult *current = 0;
size_t watchdog = 0;
c = advance(lexer_info);
while (c != '\0') {
skip_comments_and_whitespace(lexer_info);
c = peek(lexer_info);
// Stop at the end of the token string
if (c == '}') {
advance(lexer_info);
return convert_to_token_string(lexer_info, head, start, start_line);
}
// Get next token
result = lexer_next(lexer_info);
// Handle Errors
if (result.type == SLS_ERROR) {
clean_token_result(head);
return result;
}
// Save result
if (head == 0) {
head = result.result;
current = head;
} else {
current->next = result.result;
current = current->next;
}
// Current should not be null_ptr
if (current == 0) {
clean_token_result(head);
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Unknown Error."), 1}};
}
if (current->type == SLS_ERROR) {
LexerTokenResult *e = (LexerTokenResult *)malloc(sizeof(LexerTokenResult));
*e = (LexerTokenResult){
.type = SLS_ERROR,
.error = (SlsError){sls_str_cpy(current->error.message), 1},
.file_info = current->file_info,
.next = NULL
};
clean_token_result(head);
return (LexerResult){SLS_RESULT, .result = e};
}
if (current->result.type == TOKEN_EOF) break;
c = peek(lexer_info);
if (watchdog++ > 1000000) {
clean_token_result(head);
return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Watchdog Triggered in Token String."), 1}};
}
}
clean_token_result(head);
return lexer_error(lexer_info, SLS_STR("Unclosed token string: missing closing brace '}'."), start, start_line);
} }
static LexerResult parse_array_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_array_literal(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
@ -795,43 +658,33 @@ static LexerResult parse_type_tuples(LexerInfo *lexer_info, char c, size_t start
} }
Boolean is_identifier_continue(LexerInfo *lexer_info, char c) { Boolean is_identifier_continue(LexerInfo *lexer_info, char c) {
// If the current character and its context are a valid identifier character
if (!isprint(c)) return FALSE; if (!isprint(c)) return FALSE;
if (c == '/' && far_peek(lexer_info, 1) == '/') return FALSE; if (c == '/' && far_peek(lexer_info, 1) == '/') return FALSE;
if (c == '{' || c == '}') return FALSE; if (c == '{' || c == '}') return FALSE;
if (c == '[' || c == ']') return FALSE; if (c == '[' || c == ']') return FALSE;
if (c == '(' || c == ')') return FALSE; if (c == '(' || c == ')') return FALSE;
if (c == '\'' || c == '"' || c == '#') return FALSE; if (c == '\'' || c == '"') return FALSE;
if (c == '.' || c == ':' || c == '#') return FALSE;
if (isspace(c) || c == '\0') return FALSE; if (isspace(c) || c == '\0') return FALSE;
return TRUE; return TRUE;
} }
Boolean is_identifier_start(LexerInfo *lexer_info) { Boolean is_identifier_start(LexerInfo *lexer_info, char c) {
// If the current character and its context are a valid identifier start
char c = peek(lexer_info);
if (c == ':' && far_peek(lexer_info, 1) == ':') c = far_peek(lexer_info, 2); if (c == ':' && far_peek(lexer_info, 1) == ':') c = far_peek(lexer_info, 2);
if ((!isdigit(c)) && is_identifier_continue(lexer_info, c)) return TRUE; if ((!isdigit(c)) && is_identifier_continue(lexer_info, c)) return TRUE;
else return FALSE; else return FALSE;
} }
static LexerResult parse_identifiers_and_booleans(LexerInfo *lexer_info, char c, size_t start, size_t start_line) { static LexerResult parse_identifiers_and_booleans(LexerInfo *lexer_info, char c, size_t start, size_t start_line) {
// Parses identifier, identifier literals, and boolean tokens
Boolean literal = FALSE; Boolean literal = FALSE;
// Skip leading `::` for identifier literals
if (c == ':' && far_peek(lexer_info, 1) == ':') { if (c == ':' && far_peek(lexer_info, 1) == ':') {
literal = TRUE; literal = TRUE;
c = advance(lexer_info); c = advance(lexer_info);
c = advance(lexer_info); c = advance(lexer_info);
} }
// Read the name of the identifier
size_t length = 0; size_t length = 0;
while (is_identifier_continue(lexer_info, c)) { while (is_identifier_continue(lexer_info, c)) {
if (c == ':') // && !literal)
return lexer_error(lexer_info, SLS_STR("Invalid identifier: ':' is not allowed in identifiers."), start, start_line);
if (c == '.') // && !literal)
return lexer_error(lexer_info, SLS_STR("Invalid identifier: '.' is not allowed in identifiers."), start, start_line);
c = advance(lexer_info); c = advance(lexer_info);
length++; length++;
} }
@ -840,8 +693,6 @@ static LexerResult parse_identifiers_and_booleans(LexerInfo *lexer_info, char c,
name_value[i] = lexer_info->source_code.str[start + i + (2 * literal)]; name_value[i] = lexer_info->source_code.str[start + i + (2 * literal)];
SlsStr name = sls_str_malloc(name_value, length); SlsStr name = sls_str_malloc(name_value, length);
free(name_value); free(name_value);
// Return as identifier or boolean tokens
if (sls_str_cmp(name, SLS_STR("false")) == 0) if (sls_str_cmp(name, SLS_STR("false")) == 0)
return lexer_result(lexer_info, (Token){TOKEN_BOOLEAN, .boolean_literal = FALSE}, start, start_line); return lexer_result(lexer_info, (Token){TOKEN_BOOLEAN, .boolean_literal = FALSE}, start, start_line);
else if (sls_str_cmp(name, SLS_STR("true")) == 0) else if (sls_str_cmp(name, SLS_STR("true")) == 0)
@ -853,7 +704,13 @@ static LexerResult parse_identifiers_and_booleans(LexerInfo *lexer_info, char c,
static LexerResult lexer_next(LexerInfo *lexer_info) { static LexerResult lexer_next(LexerInfo *lexer_info) {
// Gets the next token from the source // Gets the next token from the source
skip_comments_and_whitespace(lexer_info); while (isspace(peek(lexer_info)) || peek(lexer_info) == '/' || peek(lexer_info) == '#') {
// Skip Comments
if ((peek(lexer_info) == '/' && far_peek(lexer_info, 1) == '/') || peek(lexer_info) == '#')
while (peek(lexer_info) != '\n') advance(lexer_info);
// Skip whitespace
while (isspace(peek(lexer_info))) advance(lexer_info);
}
// Initialize begining variables // Initialize begining variables
char c = peek(lexer_info); char c = peek(lexer_info);
@ -874,64 +731,29 @@ static LexerResult lexer_next(LexerInfo *lexer_info) {
if (c == '\"') return parse_string_literal(lexer_info, c, start, start_line); if (c == '\"') return parse_string_literal(lexer_info, c, start, start_line);
// Token Strings // Token Strings
if (c == '{') return parse_token_string(lexer_info, c, start, start_line); if (c == '{') return parse_token_string(lexer_info, c, start, start_line);
if (c == '}') {
advance(lexer_info);
return lexer_error(lexer_info, SLS_STR("Unexpected closing brace '}' without matching opening brace."), start, start_line);
}
// Array Literals // Array Literals
if (c == '[') return parse_array_literal(lexer_info, c, start, start_line); if (c == '[') return parse_array_literal(lexer_info, c, start, start_line);
if (c == ']') {
advance(lexer_info);
return lexer_error(lexer_info, SLS_STR("Unexpected closing bracket ']' without matching opening bracket."), start, start_line);
}
// Type Tuples // Type Tuples
if (c == '(') return parse_type_tuples(lexer_info, c, start, start_line); if (c == '(') return parse_type_tuples(lexer_info, c, start, start_line);
if (c == ')') {
advance(lexer_info);
return lexer_error(lexer_info, SLS_STR("Unexpected closing parentheses ')' without matching opening parentheses."), start, start_line);
}
// Identifiers and Booleans // Identifiers and Booleans
if (is_identifier_start(lexer_info)) if (is_identifier_start(lexer_info, c))
return parse_identifiers_and_booleans(lexer_info, c, start, start_line); return parse_identifiers_and_booleans(lexer_info, c, start, start_line);
if (c == ':') { // Lexing Error
advance(lexer_info); return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Lexer: Unknown Character Error."), 1}};
if (far_peek(lexer_info, 1) == ':')
return lexer_error(lexer_info, SLS_STR("Invalid identifier literal: empty identifier after '::'."), start, start_line);
else
return lexer_error(lexer_info, SLS_STR("Unexpected single colon ':'."), start, start_line);
}
// Random Characters
SlsStr error_msg = sls_format(SLS_STR("Unexpected character: unexpected '%c' during parsing."), c);
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
return lexer_error(lexer_info, error_msg, start, start_line);
}
void clean_token_string(TokenString token_string) {
if (token_string.tokens == NULL) return;
for (size_t i = 0; i < token_string.length; i++) {
if (token_string.tokens[i].type == TOKEN_STRING)
sls_str_free(&token_string.tokens[i].string_literal);
if (token_string.tokens[i].type == TOKEN_TOKEN_STRING)
clean_token_string(token_string.tokens[i].token_string);
}
free(token_string.tokens);
token_string.tokens = NULL;
} }
void clean_token_result(LexerTokenResult *head) { void clean_token_result(LexerTokenResult *head) {
// Deallocates a LexerTokenResult linked list // Deallocates a LexerTokenResult linked list
if (head == NULL) return; LexerTokenResult *next;
if (head->type == SLS_ERROR) sls_str_free(&head->error.message); while (head) {
else { next = head->next;
if (head->result.type == TOKEN_STRING) if (head->type == SLS_ERROR) sls_str_free(&head->error.message);
sls_str_free(&head->result.string_literal); else {
if (head->result.type == TOKEN_TOKEN_STRING) if (head->result.type == TOKEN_STRING) sls_str_free(&head->error.message);
clean_token_string(head->result.token_string); }
if (head) free(head);
head = next;
} }
clean_token_result(head->next);
head->next = NULL;
if (head) free(head);
} }
LexerTokenResult *get_token(LexerTokenResult *head, size_t i) { LexerTokenResult *get_token(LexerTokenResult *head, size_t i) {

View File

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

View File

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

View File

@ -1,205 +0,0 @@
// 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

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

View File

@ -204,30 +204,18 @@ SlsStr sls_format(const SlsStr s, ...) {
case 't': case 't':
items[i].type = FORMAT_SLS_TOKEN_TYPE; items[i].type = FORMAT_SLS_TOKEN_TYPE;
items[i].token_type = va_arg(args, TokenType); items[i].token_type = va_arg(args, TokenType);
if (items[i].token_type >= TOKEN_TYPE_COUNT) {
free(items);
return SLS_STR_NULL;
}
length += items[i].self_length = sls_str_nlen(TOKEN_TYPES_NAMES[items[i].token_type], TYPE_NAMES_SAFE_LENGTH); length += items[i].self_length = sls_str_nlen(TOKEN_TYPES_NAMES[items[i].token_type], TYPE_NAMES_SAFE_LENGTH);
length -= 2; length -= 2;
break; break;
case 'a': case 'a':
items[i].type = FORMAT_SLS_ARRAY_TYPE; items[i].type = FORMAT_SLS_ARRAY_TYPE;
items[i].array_type = va_arg(args, ArrayType); items[i].array_type = va_arg(args, ArrayType);
if (items[i].array_type >= ARRAY_TYPE_COUNT) {
free(items);
return SLS_STR_NULL;
}
length += items[i].self_length = sls_str_nlen(ARRAY_TYPES_NAMES[items[i].array_type], TYPE_NAMES_SAFE_LENGTH); length += items[i].self_length = sls_str_nlen(ARRAY_TYPES_NAMES[items[i].array_type], TYPE_NAMES_SAFE_LENGTH);
length -= 2; length -= 2;
break; break;
case 'i': case 'i':
items[i].type = FORMAT_SLS_BUILTIN_INTEGER; items[i].type = FORMAT_SLS_BUILTIN_INTEGER;
items[i].builtin_integer = va_arg(args, IntegerBuiltInType); items[i].builtin_integer = va_arg(args, IntegerBuiltInType);
if (items[i].builtin_integer >= INTEGER_TYPE_COUNT) {
free(items);
return SLS_STR_NULL;
}
length += items[i].self_length = sls_str_nlen(INTEGER_TYPES_NAMES[items[i].builtin_integer], TYPE_NAMES_SAFE_LENGTH); length += items[i].self_length = sls_str_nlen(INTEGER_TYPES_NAMES[items[i].builtin_integer], TYPE_NAMES_SAFE_LENGTH);
length -= 2; length -= 2;
break; break;
@ -321,7 +309,7 @@ SlsStr sls_format(const SlsStr s, ...) {
target_i += items[item_i].self_length; target_i += items[item_i].self_length;
item_i++; item_i++;
} }
if (s.len > (size_t)source_i) if (s.len > source_i)
memcpy(str + target_i, s.str + source_i, s.len - source_i); memcpy(str + target_i, s.str + source_i, s.len - source_i);
str[str_new.len] = '\0'; str[str_new.len] = '\0';

View File

@ -11,30 +11,9 @@
#include "sls/string.h" #include "sls/string.h"
#include "sls/lexer.h" #include "sls/lexer.h"
#include "sls/errors.h" #include "sls/errors.h"
#include "tests/lexer_test_helpers.h"
#include "tests/tests.h" #include "tests/tests.h"
static const size_t NUM_EXTRA_TESTS = 2; static const size_t NUM_EXTRA_TESTS = 0;
static TestResult test_Identifier_Addition() {
LexerTest test = start_up_test(SLS_STR("Identifier Addition"), SLS_STR("+"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
if (test_identifier_value(&test, result, i++, &(TestIdentifierValue){FALSE, SLS_STR("+")})) return test.result;
if (test_eof_value(&test, result, i++, 0)) return test.result;
return pass_test(&test, result);
}
static TestResult test_Identifier_Division() {
LexerTest test = start_up_test(SLS_STR("Identifier Division"), SLS_STR("/"));
LexerResult result = lexical_analysis(&test.lexer_info);
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
size_t i = 0;
if (test_identifier_value(&test, result, i++, &(TestIdentifierValue){FALSE, SLS_STR("/")})) return test.result;
if (test_eof_value(&test, result, i++, 0)) return test.result;
return pass_test(&test, result);
}
// Run all extra tests // Run all extra tests
TestsReport run_extra_tests() TestsReport run_extra_tests()
@ -45,8 +24,6 @@ TestsReport run_extra_tests()
.tests = malloc(sizeof(TestResult) * NUM_EXTRA_TESTS)}; .tests = malloc(sizeof(TestResult) * NUM_EXTRA_TESTS)};
size_t i = 0; size_t i = 0;
(void)i; // report.tests[i++] = test_malloc_and_copy();
report.tests[i++] = test_Identifier_Addition();
report.tests[i++] = test_Identifier_Division();
return report; return report;
} }

View File

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

View File

@ -116,25 +116,20 @@ static SlsStr expected_end_of_token_stream(size_t i) {
} }
static SlsStr token_should_be(size_t i, TokenType should, TokenType found) { static SlsStr token_should_be(size_t i, TokenType should, TokenType found) {
if (found >= TOKEN_TYPE_COUNT) return sls_format(SLS_STR("Token #%z should be a %t, but found a %t"), i, should, found);
return sls_format(SLS_STR("Token #%z should be a %t, but found invalid enum %u"), i, should, (uint64_t)found);
else
return sls_format(SLS_STR("Token #%z should be a %t, but found a %t"), i, should, found);
} }
static SlsStr integer_type_should_be(size_t i, IntegerBuiltInType should, IntegerBuiltInType found) { static SlsStr integer_type_should_be(size_t i, IntegerBuiltInType should, IntegerBuiltInType found) {
if (found >= INTEGER_TYPE_COUNT) return sls_format(SLS_STR("Token #%z integer type should be a %i, but found a %i"), i, should, found);
return sls_format(SLS_STR("Token #%z integer type should be a %i, but found invalid enum %u"), i, should, (uint64_t)found);
else
return sls_format(SLS_STR("Token #%z integer type should be a %i, but found a %i"), i, should, found);
} }
static SlsStr integer_value_should_be(size_t i, uint64_t should, uint64_t found) { static SlsStr integer_value_should_be(size_t i, uint64_t should, uint64_t found) {
return sls_format(SLS_STR("Token #%z integer value should be %u, but found %u"), i, should, found); return sls_format(SLS_STR("Token #%z integer value should be %i, but found %i"), i, should, found);
} }
static SlsStr character_value_should_be(size_t i, uint8_t should, uint8_t found) { static SlsStr character_value_should_be(size_t i, uint8_t should, uint8_t found) {
return sls_format(SLS_STR("Token #%z integer value should be %i, but found %i"), i, should, found); return sls_format(SLS_STR("Token #%z integer value should be %i, but found %i"), i, should, found);
// return sls_format(SLS_STR("Token #%z character value should be '', but found ''"), i, should[0], found[0]);
} }
static SlsStr float_value_should_be(size_t i, double should, double found) { static SlsStr float_value_should_be(size_t i, double should, double found) {
@ -163,10 +158,7 @@ static SlsStr boolean_should_be(size_t i, Boolean value) {
} }
static SlsStr array_type_should_be(size_t i, ArrayType should, ArrayType found) { static SlsStr array_type_should_be(size_t i, ArrayType should, ArrayType found) {
if (found >= ARRAY_TYPE_COUNT) return sls_format(SLS_STR("Token #%z should be a %a, but found a %a"), i, should, found);
return sls_format(SLS_STR("Token #%z should be a %a, but found invalid enum %u"), i, should, (uint64_t)found);
else
return sls_format(SLS_STR("Token #%z should be a %a, but found a %a"), i, should, found);
} }
static SlsStr array_dimensions_should_be(size_t i, size_t should, size_t found) { static SlsStr array_dimensions_should_be(size_t i, size_t should, size_t found) {
@ -494,38 +486,9 @@ Boolean test_array_struct_inline_value(LexerTest *test, LexerResult result, size
static LexerResult token_string_to_lexer_result(TokenString token_string, FileInfo file_info) { static LexerResult token_string_to_lexer_result(TokenString token_string, FileInfo file_info) {
LexerTokenResult *new, *head; LexerTokenResult *new, *head;
head = 0; head = 0;
for (size_t i = token_string.length; i > 0; i--) { for (size_t i = 0; i> token_string.length; i++) {
new = (LexerTokenResult *)malloc(sizeof(LexerTokenResult)); new = (LexerTokenResult *)malloc(sizeof(LexerTokenResult));
if (new == 0) return (LexerResult) { .type = SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1} }; *new = (LexerTokenResult) { .type = SLS_RESULT, .result = token_string.tokens[i], .file_info = file_info, .next = head };
if (token_string.tokens[i-1].type == TOKEN_STRING) {
*new = (LexerTokenResult) {
.type = SLS_RESULT,
.result = (Token){
.type = TOKEN_STRING,
.string_literal = sls_str_cpy(token_string.tokens[i-1].string_literal)
},
.file_info = file_info,
.next = head
};
} else if (token_string.tokens[i-1].type == TOKEN_TOKEN_STRING) {
*new = (LexerTokenResult) {
.type = SLS_RESULT,
.result = (Token){
.type = TOKEN_TOKEN_STRING,
.token_string = copy_token_string(token_string.tokens[i-1].token_string)
},
.file_info = file_info,
.next = head
};
memcpy(new->result.token_string.tokens, token_string.tokens[i-1].token_string.tokens, token_string.tokens[i-1].token_string.length);
} else {
*new = (LexerTokenResult) {
.type = SLS_RESULT,
.result = token_string.tokens[i-1],
.file_info = file_info,
.next = head
};
}
head = new; head = new;
} }
return (LexerResult) { .type = SLS_RESULT, .result = head }; return (LexerResult) { .type = SLS_RESULT, .result = head };
@ -543,8 +506,7 @@ Boolean test_token_string_value(LexerTest *test, LexerResult result, size_t i, T
for (size_t j = 0; j < values->tokens; j++) { for (size_t j = 0; j < values->tokens; j++) {
LexerResult token_string_result = token_string_to_lexer_result(head->result.token_string, head->file_info); LexerResult token_string_result = token_string_to_lexer_result(head->result.token_string, head->file_info);
if (values->values[j].token_handler(test, token_string_result, j, values->values[j].value)) { if (values->values[j].token_handler(test, token_string_result, j, values->values[j].value)) {
// We do not clean up here because if token_handler fails the test, it cleans up for us. clean_token_result(token_string_result.result);
// clean_token_result(token_string_result.result);
return TRUE; return TRUE;
} }
clean_token_result(token_string_result.result); clean_token_result(token_string_result.result);
@ -589,13 +551,13 @@ Boolean test_type_tuple_value(LexerTest *test, LexerResult result, size_t i, Tes
return FALSE; return FALSE;
} }
Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr *error) { Boolean test_for_error(LexerTest *test, LexerResult result, size_t i, SlsStr error) {
LexerTokenResult *head = get_token(result.result, i); LexerTokenResult *head = get_token(result.result, i);
if (head->type != SLS_ERROR) { if (head->type != SLS_ERROR) {
logic_fail_test(test, result, token_should_be_error(i + 1, *error, head->result.type)); logic_fail_test(test, result, token_should_be_error(i + 1, error, head->result.type));
return TRUE; return TRUE;
} if (sls_str_cmp(head->error.message, *error) != 0) { } if (sls_str_cmp(head->error.message, error) != 0) {
logic_fail_test(test, result, error_should_be(i + 1, *error, head->error)); logic_fail_test(test, result, error_should_be(i + 1, error, head->error));
return TRUE; return TRUE;
} }
return FALSE; return FALSE;

File diff suppressed because it is too large Load Diff

View File

@ -10,9 +10,9 @@
#include "sls/errors.h" #include "sls/errors.h"
#include "tests/tests.h" #include "tests/tests.h"
static const Boolean PRINT_SUCCESSFUL_TESTS = FALSE; static const Boolean PRINT_SUCCESSFUL_TESTS = TRUE;
const SlsStr TEST_FILE_NAME = SLS_STR_CONST("TEST_FILE.SLS"); const SlsStr TEST_FILE_NAME = SLS_STR("TEST_FILE.SLS");
typedef struct { typedef struct {
uint16_t errored; uint16_t errored;
@ -85,8 +85,6 @@ int main(void) {
test_report(run_string_tests(), &counts); test_report(run_string_tests(), &counts);
printf(" ========== Lexer Tests ==========\n"); printf(" ========== Lexer Tests ==========\n");
test_report(run_lexer_tests(), &counts); test_report(run_lexer_tests(), &counts);
printf(" ========== Hash Table Tests ==========\n");
test_report(run_hash_table_tests(), &counts);
printf(" ========== Extra Tests ==========\n"); printf(" ========== Extra Tests ==========\n");
test_report(run_extra_tests(), &counts); test_report(run_extra_tests(), &counts);

View File

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

View File

@ -1,19 +0,0 @@
# 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
```

View File

@ -1,20 +0,0 @@
# 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

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

View File

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

View File

@ -1,23 +0,0 @@
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

@ -1,17 +0,0 @@
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

@ -1,14 +0,0 @@
[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

@ -1,91 +0,0 @@
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)

View File

@ -1,142 +0,0 @@
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

@ -1,56 +0,0 @@
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

@ -1,33 +0,0 @@
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

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

File diff suppressed because it is too large Load Diff

View File

@ -1,268 +0,0 @@
#!/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()

View File

@ -1,37 +0,0 @@
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

@ -1,230 +0,0 @@
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

@ -1,18 +0,0 @@
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()

View File

@ -1,904 +0,0 @@
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()

View File

@ -1,25 +0,0 @@
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)

View File

@ -1,83 +0,0 @@
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

View File

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

View File

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

View File

@ -1,15 +0,0 @@
[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"] }

View File

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

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +0,0 @@
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

@ -1,170 +0,0 @@
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()
}
}

View File

@ -1,742 +0,0 @@
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)
}

View File

@ -1,63 +0,0 @@
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);
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,6 @@ from .float_tests import FloatTestGenerator
from .char_tests import CharTestGenerator from .char_tests import CharTestGenerator
from .string_tests import StringTestGenerator from .string_tests import StringTestGenerator
from .idents_and_bools_tests import IdentifierTestGenerator, BooleanTestGenerator from .idents_and_bools_tests import IdentifierTestGenerator, BooleanTestGenerator
from .token_strings import TokenStringTestGenerator
__all__ = [ __all__ = [
"BaseTestGenerator", "BaseTestGenerator",
@ -16,5 +15,4 @@ __all__ = [
"StringTestGenerator", "StringTestGenerator",
"IdentifierTestGenerator", "IdentifierTestGenerator",
"BooleanTestGenerator", "BooleanTestGenerator",
"TokenStringTestGenerator",
] ]

View File

@ -54,8 +54,6 @@ class BaseTestGenerator(ABC):
ENABLE_UNICODE = False ENABLE_UNICODE = False
ENABLE_EXPONENTIAL_LITERALS = False ENABLE_EXPONENTIAL_LITERALS = False
ENABLE_CHAR_HEX_ESCAPE = False
ENABLE_STRINGS = False
__generators: "ClassVar[List[Type[BaseTestGenerator]]]" = [] __generators: "ClassVar[List[Type[BaseTestGenerator]]]" = []
@ -367,39 +365,3 @@ class BaseTestGenerator(ABC):
if not self.validate_test_names_unique(): if not self.validate_test_names_unique():
duplicates = self.get_duplicate_test_names() duplicates = self.get_duplicate_test_names()
print(f" WARNING: Duplicate test names found: {duplicates}") print(f" WARNING: Duplicate test names found: {duplicates}")
# =========================================================================
# Token String Helpers
# =========================================================================
def make_token_string_token(self, inner_tokens: List[Token]) -> Token:
"""
Create a token string token containing inner tokens.
Args:
inner_tokens: List of tokens inside the token string
Returns:
Token with type "token_string" and tokens array
"""
return Token(type="token_string", value=[t.__dict__ for t in inner_tokens])
def make_token_string_test(self, name: str, code: str, inner_tokens: List[Token]):
"""
Create a successful token string test.
Args:
name: Test name
code: Source code
inner_tokens: Tokens inside the token string
"""
token = self.make_token_string_token(inner_tokens)
# For operations, we push the token string as a value
op_value = {"tokens": [t.__dict__ for t in inner_tokens]}
op = self.make_push_op("token_string", op_value)
# On the stack, it's a token_string value
stack = self.make_stack_item("token_string", op_value)
self.add_test(name, code, [token], [op], [stack])

View File

@ -28,10 +28,10 @@ class CharTestGenerator(BaseTestGenerator):
def generate_basic_tests(self): def generate_basic_tests(self):
"""Generate basic character literal tests.""" """Generate basic character literal tests."""
# Simple ASCII letters # Simple ASCII letters
self.make_success_test("Char Simple Letter Uppercase A", "'A'", "char", 'A') self.make_success_test("Char Simple Letter A", "'A'", "char", 'A')
self.make_success_test("Char Simple Letter Lowercase a", "'a'", "char", 'a') self.make_success_test("Char Simple Letter a", "'a'", "char", 'a')
self.make_success_test("Char Simple Letter Uppercase Z", "'Z'", "char", 'Z') self.make_success_test("Char Simple Letter Z", "'Z'", "char", 'Z')
self.make_success_test("Char Simple Letter Lowercase z", "'z'", "char", 'z') self.make_success_test("Char Simple Letter z", "'z'", "char", 'z')
# Digits # Digits
self.make_success_test("Char Digit 0", "'0'", "char", '0') self.make_success_test("Char Digit 0", "'0'", "char", '0')
@ -74,14 +74,13 @@ class CharTestGenerator(BaseTestGenerator):
def generate_hexadecimal_escape_tests(self): def generate_hexadecimal_escape_tests(self):
"""Generate tests for hexadecimal escape sequences.""" """Generate tests for hexadecimal escape sequences."""
if self.ENABLE_CHAR_HEX_ESCAPE: # Additional specific hex escapes
# Additional specific hex escapes self.make_success_test("Char Hex Lowercase A", "'\\\\x61'", "char", 'a')
self.make_success_test("Char Hex Lowercase A", "'\\\\x61'", "char", 'a') self.make_success_test("Char Hex Uppercase A", "'\\\\x41'", "char", 'A')
self.make_success_test("Char Hex Uppercase A", "'\\\\x41'", "char", 'A') self.make_success_test("Char Hex Space", "'\\\\x20'", "char", ' ')
self.make_success_test("Char Hex Space", "'\\\\x20'", "char", ' ') self.make_success_test("Char Hex Tab", "'\\\\x09'", "char", '\t')
self.make_success_test("Char Hex Tab", "'\\\\x09'", "char", '\t') self.make_success_test("Char Hex Newline", "'\\\\x0A'", "char", '\n')
self.make_success_test("Char Hex Newline", "'\\\\x0A'", "char", '\n') self.make_success_test("Char Hex Max ASCII", "'\\\\x7F'", "char", '\x7F')
self.make_success_test("Char Hex Max ASCII", "'\\\\x7F'", "char", '\x7F')
def generate_unicode_escape_tests(self): def generate_unicode_escape_tests(self):
"""Generate tests for Unicode escape sequences.""" """Generate tests for Unicode escape sequences."""
@ -166,19 +165,18 @@ class CharTestGenerator(BaseTestGenerator):
"Invalid character literal: unknown escape sequence '\\\\q'.") "Invalid character literal: unknown escape sequence '\\\\q'.")
# Invalid hex escape (not 2 digits) # Invalid hex escape (not 2 digits)
if self.ENABLE_CHAR_HEX_ESCAPE: self.make_error_test("Char Hex Escape Too Short",
self.make_error_test("Char Hex Escape Too Short", "'\\\\x4'",
"'\\\\x4'", "Invalid character literal: hexadecimal escape must have exactly 2 digits.")
"Invalid character literal: hexadecimal escape must have exactly 2 digits.")
self.make_error_test("Char Hex Escape Too Long", self.make_error_test("Char Hex Escape Too Long",
"'\\\\x414'", "'\\\\x414'",
"Invalid character literal: hexadecimal escape must have exactly 2 digits.") "Invalid character literal: hexadecimal escape must have exactly 2 digits.")
# Invalid hex digits # Invalid hex digits
self.make_error_test("Char Hex Invalid Digit", self.make_error_test("Char Hex Invalid Digit",
"'\\\\xGG'", "'\\\\xGG'",
"Invalid character literal: invalid hexadecimal digit 'G'.") "Invalid character literal: invalid hexadecimal digit 'G'.")
if self.ENABLE_UNICODE: if self.ENABLE_UNICODE:
# Invalid Unicode escape (no braces) # Invalid Unicode escape (no braces)
@ -217,16 +215,15 @@ class CharTestGenerator(BaseTestGenerator):
def generate_edge_case_tests(self): def generate_edge_case_tests(self):
"""Generate edge case tests.""" """Generate edge case tests."""
if self.ENABLE_CHAR_HEX_ESCAPE: # ASCII control characters
# ASCII control characters self.make_success_test("Char ASCII Control SOH", "'\\\\x01'", "char", '\x01')
self.make_success_test("Char ASCII Control SOH", "'\\\\x01'", "char", '\x01') self.make_success_test("Char ASCII Control BEL", "'\\\\x07'", "char", '\x07')
self.make_success_test("Char ASCII Control BEL", "'\\\\x07'", "char", '\x07') self.make_success_test("Char ASCII Control ESC", "'\\\\x1B'", "char", '\x1B')
self.make_success_test("Char ASCII Control ESC", "'\\\\x1B'", "char", '\x1B') self.make_success_test("Char ASCII Control DEL", "'\\\\x7F'", "char", '\x7F')
self.make_success_test("Char ASCII Control DEL", "'\\\\x7F'", "char", '\x7F')
# Extended ASCII # Extended ASCII
self.make_success_test("Char Extended ASCII Lower", "'\\\\x80'", "char", '\x80') self.make_success_test("Char Extended ASCII Lower", "'\\\\x80'", "char", '\x80')
self.make_success_test("Char Extended ASCII Upper", "'\\\\xFF'", "char", '\xFF') self.make_success_test("Char Extended ASCII Upper", "'\\\\xFF'", "char", '\xFF')
if self.ENABLE_UNICODE: if self.ENABLE_UNICODE:
# Zero-width characters # Zero-width characters
@ -248,14 +245,13 @@ class CharTestGenerator(BaseTestGenerator):
def generate_case_sensitivity_tests(self): def generate_case_sensitivity_tests(self):
"""Generate tests for case sensitivity in escape sequences.""" """Generate tests for case sensitivity in escape sequences."""
if self.ENABLE_CHAR_HEX_ESCAPE: # Hex escapes - lowercase x
# Hex escapes - lowercase x self.make_success_test("Char Hex Lowercase x", "'\\\\x41'", "char", 'A')
self.make_success_test("Char Hex Lowercase x", "'\\\\x41'", "char", 'A')
# Hex digits - both cases # Hex digits - both cases
self.make_success_test("Char Hex Digits Uppercase", "'\\\\xFF'", "char", '\xFF') self.make_success_test("Char Hex Digits Uppercase", "'\\\\xFF'", "char", '\xFF')
self.make_success_test("Char Hex Digits Lowercase", "'\\\\xff'", "char", '\xff') self.make_success_test("Char Hex Digits Lowercase", "'\\\\xff'", "char", '\xff')
self.make_success_test("Char Hex Digits Mixed", "'\\\\xAb'", "char", '\xAB') self.make_success_test("Char Hex Digits Mixed", "'\\\\xAb'", "char", '\xAB')
if self.ENABLE_UNICODE: if self.ENABLE_UNICODE:
# Unicode escapes - lowercase u # Unicode escapes - lowercase u

View File

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

View File

@ -7,7 +7,7 @@ class IdentifierTestGenerator(BaseTestGenerator):
# Reserved words that might be operators or keywords # Reserved words that might be operators or keywords
RESERVED_WORDS = [ RESERVED_WORDS = [
'if', 'while', 'for', 'match', 'break', 'continue', 'true', 'false', 'if', 'while', 'for', 'match', 'break', 'continue',
'fn', 'struct', 'union', 'enum', 'trait', 'impl', 'inher', 'fn', 'struct', 'union', 'enum', 'trait', 'impl', 'inher',
'dup', 'drop', 'swap', 'over', 'rot', 'pick', 'roll', 'depth', 'dup', 'drop', 'swap', 'over', 'rot', 'pick', 'roll', 'depth',
] ]
@ -66,7 +66,7 @@ class IdentifierTestGenerator(BaseTestGenerator):
# Others # Others
self.make_success_test("Identifier With Dash", "my-var", self.make_success_test("Identifier With Dash", "my-var",
"identifier", "my-var") "identifier_literal", "my-var")
def generate_identifier_literal_tests(self): def generate_identifier_literal_tests(self):
"""Generate identifier literal tests (with :: prefix).""" """Generate identifier literal tests (with :: prefix)."""
@ -153,12 +153,17 @@ class IdentifierTestGenerator(BaseTestGenerator):
"Invalid decimal literal: unexpected 'a' in decimal integer.") "Invalid decimal literal: unexpected 'a' in decimal integer.")
# Invalid characters # Invalid characters
self.make_success_test("Identifier With Octothorpe", "my#var", self.make_error_test("Identifier With Hash",
"identifier", "my") "my#var",
"Invalid identifier: '#' is not allowed in identifiers.")
# self.make_error_test("Identifier With Space", self.make_error_test("Identifier With Dot",
# "my var", "my.var",
# "Invalid identifier: whitespace not allowed in identifiers.") "Invalid identifier: '.' is not allowed in identifiers.")
self.make_error_test("Identifier With Space",
"my var",
"Invalid identifier: whitespace not allowed in identifiers.")
self.make_error_test("Identifier With Colon", self.make_error_test("Identifier With Colon",
"my:var", "my:var",
@ -167,45 +172,47 @@ class IdentifierTestGenerator(BaseTestGenerator):
# Note: :: is allowed only as prefix for identifier literals # Note: :: is allowed only as prefix for identifier literals
self.make_error_test("Identifier Double Colon Inside", self.make_error_test("Identifier Double Colon Inside",
"my::var", "my::var",
"Invalid identifier: ':' is not allowed in identifiers.") "Invalid identifier: '::' only allowed as prefix for identifier literals.")
# Special characters # Special characters
# self.make_error_test("Identifier With At", self.make_error_test("Identifier With At",
# "@variable", "@variable",
# "Invalid identifier: '@' is not allowed in identifiers.") "Invalid identifier: '@' is not allowed in identifiers.")
# self.make_error_test("Identifier With Dollar", self.make_error_test("Identifier With Dollar",
# "$variable", "$variable",
# "Invalid identifier: '$' is not allowed in identifiers.") "Invalid identifier: '$' is not allowed in identifiers.")
# self.make_error_test("Identifier With Percent", self.make_error_test("Identifier With Percent",
# "%variable", "%variable",
# "Invalid identifier: '%' is not allowed in identifiers.") "Invalid identifier: '%' is not allowed in identifiers.")
# Brackets not allowed # Brackets not allowed
# self.make_error_test("Identifier With Brackets", self.make_error_test("Identifier With Brackets",
# "my[var]", "my[var]",
# "Invalid identifier: brackets not allowed in identifiers.") "Invalid identifier: brackets not allowed in identifiers.")
# self.make_error_test("Identifier With Braces", self.make_error_test("Identifier With Braces",
# "my{var}", "my{var}",
# "Invalid identifier: braces not allowed in identifiers.") "Invalid identifier: braces not allowed in identifiers.")
# self.make_error_test("Identifier With Parens", self.make_error_test("Identifier With Parens",
# "my(var)", "my(var)",
# "Invalid identifier: parentheses not allowed in identifiers.") "Invalid identifier: parentheses not allowed in identifiers.")
# Quotes not allowed # Quotes not allowed
# self.make_error_test("Identifier With Single Quote", self.make_error_test("Identifier With Single Quote",
# "my'var", "my'var",
# "Invalid identifier: quotes not allowed in identifiers.") "Invalid identifier: quotes not allowed in identifiers.")
# self.make_error_test("Identifier With Double Quote", self.make_error_test("Identifier With Double Quote",
# 'my"var', 'my"var',
# "Invalid identifier: quotes not allowed in identifiers.") "Invalid identifier: quotes not allowed in identifiers.")
# Only numbers (not valid identifier) # Only numbers (not valid identifier)
self.make_success_test("Identifier Only Numbers", "123", "i64", 123) self.make_error_test("Identifier Only Numbers",
"123",
"Not an identifier: numeric literal.")
# Empty identifier literal # Empty identifier literal
self.make_error_test("Identifier Literal Empty", self.make_error_test("Identifier Literal Empty",
@ -322,12 +329,11 @@ class BooleanTestGenerator(BaseTestGenerator):
self.make_success_test("Bool Numeric 0", "0", "i64", 0) self.make_success_test("Bool Numeric 0", "0", "i64", 0)
# String representations # String representations
if self.ENABLE_STRINGS: self.make_success_test("Bool String True", '"true"',
self.make_success_test("Bool String True", '"true"', "string", "true")
"string", "true")
self.make_success_test("Bool String False", '"false"', self.make_success_test("Bool String False", '"false"',
"string", "false") "string", "false")
# Other languages # Other languages
self.make_success_test("Bool Yes", "yes", self.make_success_test("Bool Yes", "yes",

View File

@ -141,59 +141,69 @@ class IntegerTestGenerator(BaseTestGenerator):
f"{underflow_val}:{type_name}", f"{underflow_val}:{type_name}",
f"Integer overflow: value exceeds range for {type_name}.") f"Integer overflow: value exceeds range for {type_name}.")
def generate_special_typed_tests(self, type_name: str):
"""Generate special tests for specific types."""
min_val, max_val = self.TYPE_RANGES[type_name]
is_unsigned = type_name.startswith('u')
# Underscores with type annotation # Underscores with type annotation
if max_range >= 1000000: if max_val >= 1000000:
self.make_success_test(f"Integer {type_name} With Underscores", self.make_success_test(f"Integer {type_name} With Underscores",
f"1_000_000:{type_name}", type_name, 1000000) f"1_000_000:{type_name}", type_name, 1000000)
def generate_special_typed_tests(self):
"""Generate special tests for specific types."""
# Special values for specific types # Special values for specific types
self.make_success_test("Integer i8 Hex Max", "0x7F:i8", "i8", 127) if type_name == 'i8':
self.make_success_test("Integer i8 Binary Max", "0b01111111:i8", "i8", 127) self.make_success_test("Integer i8 Hex Max", "0x7F:i8", "i8", 127)
self.make_success_test("Integer i8 Octal Max", "0o177:i8", "i8", 127) self.make_success_test("Integer i8 Binary Max", "0b01111111:i8", "i8", 127)
self.make_success_test("Integer i8 Negative Hex", "-0x80:i8", "i8", -128) self.make_success_test("Integer i8 Octal Max", "0o177:i8", "i8", 127)
self.make_success_test("Integer i8 Negative Hex", "-0x80:i8", "i8", -128)
self.make_success_test("Integer u8 Hex Max", "0xFF:u8", "u8", 255) elif type_name == 'u8':
self.make_success_test("Integer u8 Binary Max", "0b11111111:u8", "u8", 255) self.make_success_test("Integer u8 Hex Max", "0xFF:u8", "u8", 255)
self.make_success_test("Integer u8 Octal Max", "0o377:u8", "u8", 255) self.make_success_test("Integer u8 Binary Max", "0b11111111:u8", "u8", 255)
self.make_success_test("Integer u8 Octal Max", "0o377:u8", "u8", 255)
self.make_success_test("Integer i16 Hex Sample", "0x1234:i16", "i16", 4660) elif type_name == 'i16':
self.make_success_test("Integer i16 Binary Sample", self.make_success_test("Integer i16 Hex Sample", "0x1234:i16", "i16", 4660)
"0b1111111100000000:i16", "i16", -256) self.make_success_test("Integer i16 Binary Sample",
self.make_success_test("Integer i16 Octal Sample", "0o1234:i16", "i16", 668) "0b1111111100000000:i16", "i16", 65280)
self.make_success_test("Integer i16 Octal Sample", "0o1234:i16", "i16", 668)
self.make_success_test("Integer u16 Hex Max", "0xFFFF:u16", "u16", 65535) elif type_name == 'u16':
self.make_success_test("Integer u16 Binary Max", self.make_success_test("Integer u16 Hex Max", "0xFFFF:u16", "u16", 65535)
"0b1111111111111111:u16", "u16", 65535) self.make_success_test("Integer u16 Binary Max",
self.make_success_test("Integer u16 Octal Max", "0o177777:u16", "u16", 65535) "0b1111111111111111:u16", "u16", 65535)
self.make_success_test("Integer u16 Decimal Mid", "50000:u16", "u16", 50000) self.make_success_test("Integer u16 Octal Max", "0o177777:u16", "u16", 65535)
self.make_success_test("Integer u16 Decimal Mid", "50000:u16", "u16", 50000)
self.make_success_test("Integer i32 Hex Sample", "0xABCD:i32", "i32", 43981) elif type_name == 'i32':
self.make_success_test("Integer i32 Binary Sample", self.make_success_test("Integer i32 Hex Sample", "0xABCD:i32", "i32", 43981)
"0b11110000:i32", "i32", 240) self.make_success_test("Integer i32 Binary Sample",
"0b11110000:i32", "i32", 240)
self.make_success_test("Integer u32 Hex Max", "0xFFFFFFFF:u32", "u32", 4294967295) elif type_name == 'u32':
self.make_success_test("Integer u32 Binary Sample", self.make_success_test("Integer u32 Hex Max", "0xFFFFFFFF:u32", "u32", 4294967295)
"0b11111111000000001111111100000000:u32", self.make_success_test("Integer u32 Binary Sample",
"u32", 4278255360) "0b11111111000000001111111100000000:u32",
self.make_success_test("Integer u32 Octal Max", "u32", 4278255360)
"0o37777777777:u32", "u32", 4294967295) self.make_success_test("Integer u32 Octal Max",
self.make_success_test("Integer u32 Decimal Mid", "1000000:u32", "u32", 1000000) "0o37777777777:u32", "u32", 4294967295)
self.make_success_test("Integer u32 Decimal Mid", "1000000:u32", "u32", 1000000)
self.make_success_test("Integer i64 Decimal Positive 42", "42:i64", "i64", 42) elif type_name == 'i64':
self.make_success_test("Integer i64 Hex 0xFF", "0xFF:i64", "i64", 255) self.make_success_test("Integer i64 Decimal Positive 42", "42:i64", "i64", 42)
self.make_success_test("Integer i64 Binary 0b1010", "0b1010:i64", "i64", 10) self.make_success_test("Integer i64 Hex 0xFF", "0xFF:i64", "i64", 255)
self.make_success_test("Integer i64 Octal 0o755", "0o755:i64", "i64", 493) self.make_success_test("Integer i64 Binary 0b1010", "0b1010:i64", "i64", 10)
self.make_success_test("Integer i64 Octal 0o755", "0o755:i64", "i64", 493)
self.make_success_test("Integer u64 Hex Max", elif type_name == 'u64':
"0xFFFFFFFFFFFFFFFF:u64", self.make_success_test("Integer u64 Hex Max",
"u64", "UINT64_MAX") "0xFFFFFFFFFFFFFFFF:u64",
self.make_success_test("Integer u64 Binary Sample", "u64", "UINT64_MAX")
"0b1010101010101010:u64", "u64", 43690) self.make_success_test("Integer u64 Binary Sample",
self.make_success_test("Integer u64 Octal Sample", "0o7777:u64", "u64", 4095) "0b1010101010101010:u64", "u64", 43690)
self.make_success_test("Integer u64 Decimal", "42:u64", "u64", 42) self.make_success_test("Integer u64 Octal Sample", "0o7777:u64", "u64", 4095)
self.make_success_test("Integer u64 Decimal", "42:u64", "u64", 42)
def generate_underscore_tests(self): def generate_underscore_tests(self):
"""Generate tests for underscores in different bases.""" """Generate tests for underscores in different bases."""
@ -217,7 +227,7 @@ class IntegerTestGenerator(BaseTestGenerator):
# Tests for each specific type # Tests for each specific type
for type_name in ['i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64']: for type_name in ['i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64']:
self.generate_typed_tests(type_name) self.generate_typed_tests(type_name)
self.generate_special_typed_tests() self.generate_special_typed_tests(type_name)
# Additional edge cases # Additional edge cases
self.generate_underscore_tests() self.generate_underscore_tests()

View File

@ -436,10 +436,6 @@ class StringTestGenerator(BaseTestGenerator):
def generate_all_tests(self) -> List[Dict[str, Any]]: def generate_all_tests(self) -> List[Dict[str, Any]]:
"""Generate all string literal test cases.""" """Generate all string literal test cases."""
if not self.ENABLE_STRINGS:
return []
# Basic tests # Basic tests
self.generate_basic_tests() self.generate_basic_tests()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -83,150 +83,10 @@ def _token_to_c_call(token: dict, idx_var="i") -> str:
elif ttype == "bool": elif ttype == "bool":
return f'test_boolean_value(&test, result, {idx_var}++, &(Boolean){{{"TRUE" if value else "FALSE"}}})' # type: ignore return f'test_boolean_value(&test, result, {idx_var}++, &(Boolean){{{"TRUE" if value else "FALSE"}}})' # type: ignore
elif ttype == "error": elif ttype == "error":
return f'test_for_error(&test, result, i++, &SLS_STR("{c_string_literal(value)}"))' # type: ignore return f'test_for_error(&test, result, i++, SLS_STR("{c_string_literal(value)}"))' # type: ignore
elif ttype == "token_string":
return _token_string_c_call(idx_var, value) # type: ignore
else: else:
raise ValueError(f' Unhandled token type: {ttype}') raise ValueError(f' Unhandled token type: {ttype}')
def _token_string_c_call(idx_var: str, value: list[dict]) -> str:
"""Generate C code for testing a token string value."""
if not value: # Empty token string
return (
f'test_token_string_value(&test, result, {idx_var}++, '
f'&(TestTokenStringValue){{0, NULL}})'
)
# Generate token handler calls for each token in the string
token_handlers = []
for i, inner_token in enumerate(value):
inner_type = inner_token.get("type")
inner_value = inner_token.get("value")
# Determine the handler function and value initialization
if inner_type == "i64":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I64, {inner_value}}}'
elif inner_type == "i32":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I32, {inner_value}}}'
elif inner_type == "i16":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I16, {inner_value}}}'
elif inner_type == "i8":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I8, {inner_value}}}'
elif inner_type == "u64":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_U64, {inner_value}}}'
elif inner_type == "u32":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_U32, {inner_value}}}'
elif inner_type == "u16":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_U16, {inner_value}}}'
elif inner_type == "u8":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_U8, {inner_value}}}'
elif inner_type == "f64":
handler = "test_double_value"
val_init = f'&(double){{{inner_value}}}'
elif inner_type == "f32":
handler = "test_float_value"
val_init = f'&(float){{{inner_value}}}'
elif inner_type == "char":
handler = "test_character_value"
val_init = f'&(uint8_t){{{ord(inner_value)}}}' # type: ignore
elif inner_type == "string":
handler = "test_string_value"
val_init = f'&SLS_STR("{c_string_literal(inner_value)}")' # type: ignore
elif inner_type == "identifier":
handler = "test_identifier_value"
val_init = f'&(TestIdentifierValue){{FALSE, SLS_STR("{inner_value}")}}'
elif inner_type == "identifier_literal":
handler = "test_identifier_value"
val_init = f'&(TestIdentifierValue){{TRUE, SLS_STR("{inner_value}")}}'
elif inner_type == "bool":
handler = "test_boolean_value"
val_init = f'&(Boolean){{{"TRUE" if inner_value else "FALSE"}}}'
elif inner_type == "error":
handler = "test_for_error"
val_init = f'&SLS_STR("{c_string_literal(inner_value)}")' # type: ignore
elif inner_type == "token_string":
# Nested token string - recursive call
handler = "test_token_string_value"
val_init = _token_string_value_init(inner_token.get("value", []))
else:
raise ValueError(f'Unhandled token type in token string: {inner_type}')
token_handlers.append(
f'{{(Boolean (*)(LexerTest *, LexerResult, size_t, void *)){handler}, '
f'{val_init}}}'
)
# Generate the array initialization
tokens_array = f'(TestTokenStringToken[]){{{", ".join(token_handlers)}}}'
return (
f'test_token_string_value(&test, result, {idx_var}++, '
f'&(TestTokenStringValue){{{len(value)}, {tokens_array}}})'
)
def _token_string_value_init(value: list[dict]) -> str:
"""Generate initialization code for a TestTokenStringValue (for nested token strings)."""
if not value:
return '&(TestTokenStringValue){0, NULL}'
token_handlers = []
for inner_token in value:
inner_type = inner_token.get("type")
inner_value = inner_token.get("value")
if inner_type == "i64":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I64, {inner_value}}}'
elif inner_type == "i32":
handler = "test_integer_value"
val_init = f'&(TestIntegerValue){{INTEGER_I32, {inner_value}}}'
elif inner_type == "f64":
handler = "test_double_value"
val_init = f'&(double){{{inner_value}}}'
elif inner_type == "f32":
handler = "test_float_value"
val_init = f'&(float){{{inner_value}}}'
elif inner_type == "char":
handler = "test_character_value"
val_init = f'&(uint8_t){{{ord(inner_value)}}}' # type: ignore
elif inner_type == "string":
handler = "test_string_value"
val_init = f'&SLS_STR("{c_string_literal(inner_value)}")' # type: ignore
elif inner_type == "identifier":
handler = "test_identifier_value"
val_init = f'&(TestIdentifierValue){{FALSE, SLS_STR("{inner_value}")}}'
elif inner_type == "identifier_literal":
handler = "test_identifier_value"
val_init = f'&(TestIdentifierValue){{TRUE, SLS_STR("{inner_value}")}}'
elif inner_type == "bool":
handler = "test_boolean_value"
val_init = f'&(Boolean){{{"TRUE" if inner_value else "FALSE"}}}'
elif inner_type == "error":
handler = "test_for_error"
val_init = f'&SLS_STR("{c_string_literal(inner_value)}")' # type: ignore
elif inner_type == "token_string":
handler = "test_token_string_value"
val_init = _token_string_value_init(inner_token.get("value", []))
# Add other types as needed
else:
raise ValueError(f'Unhandled token type in nested token string: {inner_type}')
token_handlers.append(
f'{{(Boolean (*)(LexerTest *, LexerResult, size_t, void *)){handler}, '
f'{val_init}}}'
)
tokens_array = f'(TestTokenStringToken[]){{{", ".join(token_handlers)}}}'
return f'&(TestTokenStringValue){{{len(value)}, {tokens_array}}}'
def token_to_c_call(token: dict, idx_var="i") -> str: def token_to_c_call(token: dict, idx_var="i") -> str:
"""Generate a C 'test_*_value' call based on token type.""" """Generate a C 'test_*_value' call based on token type."""
return f"if ({_token_to_c_call(token, idx_var)}) return test.result;" return f"if ({_token_to_c_call(token, idx_var)}) return test.result;"