Compare commits
No commits in common. "master" and "SE3250-Checkpoint3" have entirely different histories.
master
...
SE3250-Che
|
|
@ -1,69 +1,6 @@
|
|||
# SLS Changelog
|
||||
# SE 3250 Progress Checkpoints
|
||||
|
||||
## 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
|
||||
## Checkpoint 3
|
||||
*28 Nov 2025*
|
||||
|
||||
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
|
||||
|
|
@ -91,7 +28,7 @@ I'm not really stuck on anything, but I am running out of time, plus I am
|
|||
worried about not having enough AI usage to do the ports. If I at least get the
|
||||
main structures of the ports, I hope it will be enough for me to finish.
|
||||
|
||||
## SE Checkpoint 2
|
||||
## Checkpoint 2
|
||||
*20 Nov 2025*
|
||||
|
||||
[github.com/SnowSE/final-project-KylerOlsen](https://github.com/SnowSE/final-project-KylerOlsen)
|
||||
|
|
@ -121,7 +58,7 @@ features moved to the backlog include:
|
|||
- Type tuples, function defs
|
||||
- Structs, unions, etc.
|
||||
|
||||
## SE Checkpoint 1
|
||||
## Checkpoint 1
|
||||
*07 Nov 2025*
|
||||
|
||||
**intended user**:
|
||||
191
README.md
191
README.md
|
|
@ -1,128 +1,97 @@
|
|||
# YREA SLS
|
||||
*Kyler Olsen*
|
||||
*October 2025*
|
||||
*Snow College*
|
||||
*SE 3250 Survey of Languages Final Project*
|
||||
|
||||
SLS is a statically-typed, stack-based language with pure postfix notation
|
||||
combining the execution model of HP's RPL, the type system of C and Rust, and
|
||||
modern array operations from Uiua.
|
||||
Language Code Name: YREA **SLS** (*Stack Language Specification*)
|
||||
Language Specifications: [sls.purplecello.org](https://sls.purplecello.org)
|
||||
Language Specification Repository (Private): [git.purplecello.org](https://git.purplecello.org/KylerOlsen/LangsFinalPlaning)
|
||||
Language Implementation Repository (Private): [git.purplecello.org](https://git.purplecello.org/KylerOlsen/YREA-SLS)
|
||||
Language Implementation Repository (Mirror on GitHub) (Private): [github.com](https://github.com/SnowSE/final-project-KylerOlsen)
|
||||
Assignment Page (Private): [snow.instructure.com](https://snow.instructure.com/courses/1154808/assignments/16233203)
|
||||
|
||||
## Build Commands
|
||||
## Assignment Description
|
||||
|
||||
**Linux**
|
||||
**Overview**
|
||||
|
||||
C
|
||||
```bash
|
||||
cd SLS_C
|
||||
python3 build.py build
|
||||
./bin/sls
|
||||
```
|
||||
In your final project for this course, you will do the following:
|
||||
|
||||
Python run module
|
||||
```bash
|
||||
cd SLS_Python
|
||||
python3 -m sls_py
|
||||
python3 -m sls_py.calc
|
||||
```
|
||||
- Decide on a modest personal project to complete in a new-to-you
|
||||
programming language (during class time, we will randomize the list of
|
||||
people in the class and allow languages to be selected uniquely on a
|
||||
first-come, first-served basis; be prepared to pick the language you want
|
||||
and to advocate for the languages that you don't want).
|
||||
- Without using AI generated or suggested code, complete your project in
|
||||
your assigned language including automated testing. Find a way to make your
|
||||
useful implementation concise and elegant enough that you would be able to
|
||||
recreate it reasonably quickly without AI assistance (be prepared to
|
||||
demonstrate this ability during a one-on-one interview to ensure credit).
|
||||
- Using AI help as desired, create a faithful port of your implementation
|
||||
(including tests) to:
|
||||
- Rust
|
||||
- Python
|
||||
- Java (optional, but it might be interesting and userful for your
|
||||
resume)
|
||||
- C# (optional, but it might be interesting)
|
||||
- typescript (optional, but it might be interesting)
|
||||
- For each ported implementation, add one addition feature (not present in
|
||||
the original or other ports).
|
||||
- Prepare a report that describes:
|
||||
- the problem that your project solves and why you are interested in it
|
||||
- a brief description of how the language that you chose and the port
|
||||
languages were or were not each a good fit for the project
|
||||
- three examples of ideas you learned in the course that you leveraged
|
||||
to the implementation in your assigned language elegant, efficient, and
|
||||
concise/maintainable (at least one idea should specifically come from
|
||||
functional programming) -- make sure to be specific and teach the reader
|
||||
something valuable for each of these
|
||||
- at least one struggle that you faced (or that you imagine that others
|
||||
would likely face) for each language implementation, and what you
|
||||
learned from working with the port languages during the porting process.
|
||||
- Prepare slides that help you teach the key ideas from your report to the
|
||||
class.
|
||||
- In a 5-8 minute presentation during the final exam slot, use your slides
|
||||
to teach the class and demonstrate your running project including the
|
||||
addition port features.
|
||||
|
||||
Python Setup
|
||||
```bash
|
||||
cd SLS_Python
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install build wheel "setuptools>=61.0"
|
||||
pip install -e sls_build_backend
|
||||
```
|
||||
Project Scope
|
||||
|
||||
Python build module
|
||||
```bash
|
||||
cd SLS_Python
|
||||
source .venv/bin/activate
|
||||
python3 -m build --no-isolation
|
||||
pip install ./dist/sls_python-0.0.2a0-py3-none-any.whl
|
||||
python3 -m sls_py
|
||||
python3 -m sls_py.calc
|
||||
```
|
||||
- Please do something that will be fun and interesting for you!
|
||||
- Make sure that the project actually solves a problem that you care about.
|
||||
(Entertainment counts, but if that is the problem you care about, then make
|
||||
sure that you can make something that actually is entertaining!)
|
||||
- I'm expecting you to dedicate about 15-30 hours to the entire project
|
||||
(including all three implementations and the report/presentation
|
||||
preparation). I'm expecting that your initial implmentation should feel
|
||||
like about twice the scope of implementing the game of life in PostScript
|
||||
or K.
|
||||
|
||||
Rust
|
||||
```bash
|
||||
cd SLS_Rust/sls
|
||||
cargo build
|
||||
./target/debug/sls_rs
|
||||
```
|
||||
**Languages**
|
||||
|
||||
**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)
|
||||
```shell
|
||||
cd SLS_C
|
||||
python build.py build
|
||||
.\bin\sls.exe
|
||||
```
|
||||
- https://spectrum.ieee.org/top-programming-languages-2025
|
||||
- https://www.tiobe.com/tiobe-index/
|
||||
- https://survey.stackoverflow.co/2025/technology#admired-and-desired
|
||||
- https://survey.stackoverflow.co/2025/technology#most-popular-technologies-language-prof
|
||||
- https://tjpalmer.github.io/languish/
|
||||
- https://www.geeksforgeeks.org/blogs/top-programming-languages/
|
||||
- https://github.com/breck7/pldb
|
||||
|
||||
Python run module
|
||||
```bat
|
||||
cd SLS_Python
|
||||
python -m sls_py
|
||||
python -m sls_py.calc
|
||||
```
|
||||
Here is the list of programming languages that you will be able to choose from
|
||||
for your initial implementation (each language will be chosen by at most one
|
||||
student; we will choose during class time on Oct 27):
|
||||
|
||||
Python Setup
|
||||
```bat
|
||||
cd SLS_Python
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate.bat
|
||||
pip install build wheel "setuptools>=61.0"
|
||||
pip install -e sls_build_backend
|
||||
```
|
||||
- **C** Kyler
|
||||
|
||||
Python build module
|
||||
```bat
|
||||
cd SLS_Python
|
||||
.venv\Scripts\activate.bat
|
||||
python -m build --no-isolation
|
||||
pip install .\dist\sls_python-0.0.2a0-py3-none-any.whl
|
||||
python -m sls_py
|
||||
python -m sls_py.calc
|
||||
```
|
||||
**Submission**
|
||||
|
||||
Rust
|
||||
```bat
|
||||
cd SLS_Rust\sls
|
||||
cargo build
|
||||
.\target\debug\sls_rs.exe
|
||||
```
|
||||
|
||||
**MacOS**
|
||||
|
||||
Reference Linux build instructions.
|
||||
|
||||
For C there is the `python3 build.py macos` command, but it is untested as I
|
||||
didn't test it on a Mac. I also don't know if `python3 build.py build` would also
|
||||
work on a Mac.
|
||||
|
||||
Python and Rust should just be the same or similar to Linux.
|
||||
|
||||
**RP2040**
|
||||
|
||||
Only tested on Linux. Only SLS_C supports RP2040.
|
||||
|
||||
Install Pico SDK to `~/pico/pico-sdk`, or set environment variable
|
||||
`PICO_SDK_PATH` to your installation path. You will also need to install the
|
||||
appropriate compiler.
|
||||
|
||||
Debian GNU/Linux
|
||||
```bash
|
||||
sudo apt install gcc-arm-none-eabi
|
||||
```
|
||||
|
||||
```shell
|
||||
cd SLS_C
|
||||
python3 build.py rp2040
|
||||
```
|
||||
|
||||
Flash Raspberry Pi Pico:
|
||||
Copy `.\build_pico\sls.elf.uf2` to your Pico in BOOTSEL mode
|
||||
|
||||
## Contributing
|
||||
|
||||
[sls.purplecello.org/contributing](https://sls.purplecello.org/contributing.html)
|
||||
Submit all material (including report and slides) via github classrooms repo.
|
||||
Also, submit your github repo link and the path to the report and slides file
|
||||
in the canvas text box for this assignment. The grace period for submission
|
||||
ends at 3pm on Monday, Dec 8. Presentations will be 3:30pm - 5:30pm. For full
|
||||
credit, you must present and watch the presentations of your classmates.
|
||||
|
|
|
|||
92
REPORT.md
92
REPORT.md
|
|
@ -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)
|
||||
|
|
@ -1,9 +1,4 @@
|
|||
obj/
|
||||
bin/
|
||||
build_pico/
|
||||
*.o
|
||||
*.pdb
|
||||
CMakeLists.txt
|
||||
pico_arm_gcc_toolchain.cmake
|
||||
generated/
|
||||
pico-sdk.txt
|
||||
|
|
|
|||
328
SLS_C/build.py
328
SLS_C/build.py
|
|
@ -4,7 +4,6 @@ import sys
|
|||
import subprocess
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import platform
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# CONFIG
|
||||
|
|
@ -17,136 +16,22 @@ 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":
|
||||
def detect_compiler():
|
||||
if 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")
|
||||
return ("gcc", "gcc")
|
||||
|
||||
CC, CC_KIND = detect_compiler()
|
||||
|
||||
|
|
@ -194,13 +79,11 @@ def mkdir(p: Path):
|
|||
|
||||
|
||||
def run(cmd):
|
||||
print(">>", " ".join(str(c) for c in cmd))
|
||||
print(">>", " ".join(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:
|
||||
|
|
@ -211,7 +94,7 @@ def is_up_to_date(src, obj, dep):
|
|||
# ---------------------------------------------------------------------
|
||||
# BUILD RULES
|
||||
# ---------------------------------------------------------------------
|
||||
def compile_source(src: Path, is_test=False, target_platform=None):
|
||||
def compile_source(src: Path, is_test=False):
|
||||
mkdir(OBJ_DIR)
|
||||
obj = OBJ_DIR / (src.stem + ".o")
|
||||
dep = OBJ_DIR / (src.stem + ".d")
|
||||
|
|
@ -219,19 +102,13 @@ def compile_source(src: Path, is_test=False, target_platform=None):
|
|||
if is_up_to_date(src, obj, dep):
|
||||
return obj
|
||||
|
||||
compiler, kind = detect_compiler(target_platform)
|
||||
|
||||
if kind == "msvc":
|
||||
if CC_KIND == "msvc":
|
||||
flags = MSVC_TEST_FLAGS if is_test else MSVC_FLAGS
|
||||
cmd = [compiler] + flags + ["/Fo" + str(obj), "/c", str(src),
|
||||
cmd = [CC] + 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 + [
|
||||
flags = TEST_FLAGS if is_test else COMMON_FLAGS
|
||||
cmd = [CC] + flags + [
|
||||
f"-DGIT_COMMIT_HASH=\"{GIT_HASH}\"",
|
||||
"-MMD", "-MP",
|
||||
"-c", str(src), "-o", str(obj)
|
||||
|
|
@ -241,14 +118,12 @@ def compile_source(src: Path, is_test=False, target_platform=None):
|
|||
return obj
|
||||
|
||||
|
||||
def link_executable(objects, output: Path, target_platform=None):
|
||||
def link_executable(objects, output: Path):
|
||||
mkdir(BIN_DIR)
|
||||
compiler, kind = detect_compiler(target_platform)
|
||||
|
||||
if kind == "msvc":
|
||||
cmd = [compiler] + list(map(str, objects)) + ["/Fe" + str(output)]
|
||||
if CC_KIND == "msvc":
|
||||
cmd = [CC] + list(map(str, objects)) + ["/Fe" + str(output)]
|
||||
else:
|
||||
cmd = [compiler] + list(map(str, objects)) + ["-lm", "-o", str(output)]
|
||||
cmd = [CC] + list(map(str, objects)) + ["-lm", "-o", str(output)]
|
||||
run(cmd)
|
||||
|
||||
|
||||
|
|
@ -271,136 +146,12 @@ def generate_tests():
|
|||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# RP2040 BUILD
|
||||
# ---------------------------------------------------------------------
|
||||
def check_pico_sdk():
|
||||
"""Check if Pico SDK is available"""
|
||||
sdk_path = Path(PICO_SDK_PATH)
|
||||
if not sdk_path.exists():
|
||||
print(f"ERROR: Pico SDK not found at {sdk_path}")
|
||||
print("Please set PICO_SDK_PATH environment variable or install SDK at ~/pico/pico-sdk")
|
||||
return False
|
||||
|
||||
# Check for ARM toolchain
|
||||
if not shutil.which("arm-none-eabi-gcc"):
|
||||
print("ERROR: ARM toolchain not found!")
|
||||
print("Please install arm-none-eabi-gcc:")
|
||||
print(" Ubuntu/Debian: sudo apt install gcc-arm-none-eabi")
|
||||
print(" macOS: brew install arm-none-eabi-gcc")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def generate_pico_toolchain():
|
||||
"""Generate ARM GCC toolchain file for CMake"""
|
||||
with open(PICO_TOOLCHAIN_PATH, "w") as f:
|
||||
f.write(PICO_TOOLCHAIN_TEMPLATE)
|
||||
print(f"Generated {PICO_TOOLCHAIN_PATH}")
|
||||
|
||||
|
||||
def generate_pico_cmake(project_name="sls"):
|
||||
"""Generate CMakeLists.txt for RP2040 build"""
|
||||
sources = list(SRC_DIR.glob("*.c"))
|
||||
# Exclude main.c, repl.c, file.c and test files for Pico build
|
||||
# pico_main.c will provide its own REPL implementation
|
||||
sources = [s for s in sources
|
||||
if s.name not in ["main.c", "repl.c", "file.c"]
|
||||
and "test" not in s.stem.lower()]
|
||||
|
||||
# Check for pico_main.c
|
||||
pico_main = SRC_DIR / "pico_main.c"
|
||||
if not pico_main.exists():
|
||||
print(f"WARNING: {pico_main} not found! Please create it.")
|
||||
print("The Pico build requires a pico_main.c file.")
|
||||
return False
|
||||
|
||||
sources.append(pico_main)
|
||||
|
||||
source_files = "\n".join(f" {s}" for s in sources)
|
||||
|
||||
cmake_content = RP2040_CMAKE_TEMPLATE.format(
|
||||
project_name=project_name,
|
||||
source_files=source_files,
|
||||
git_hash=GIT_HASH,
|
||||
pico_sdk_path=PICO_SDK_PATH
|
||||
)
|
||||
|
||||
cmake_file = Path("CMakeLists.txt")
|
||||
with open(cmake_file, "w") as f:
|
||||
f.write(cmake_content)
|
||||
|
||||
print(f"Generated {cmake_file}")
|
||||
return True
|
||||
|
||||
|
||||
def build_rp2040():
|
||||
"""Build for RP2040 using CMake"""
|
||||
if not check_pico_sdk():
|
||||
return False
|
||||
|
||||
print("\n=== Building for RP2040 ===\n")
|
||||
|
||||
# Generate toolchain file
|
||||
generate_pico_toolchain()
|
||||
|
||||
# Generate CMakeLists.txt
|
||||
if not generate_pico_cmake():
|
||||
return False
|
||||
|
||||
# Create build directory
|
||||
mkdir(PICO_BUILD_DIR)
|
||||
|
||||
# Run CMake with explicit toolchain
|
||||
cmake_cmd = [
|
||||
"cmake",
|
||||
"-B", str(PICO_BUILD_DIR),
|
||||
"-S", ".",
|
||||
f"-DCMAKE_TOOLCHAIN_FILE={PICO_TOOLCHAIN_PATH}"
|
||||
]
|
||||
run(cmake_cmd)
|
||||
|
||||
# Build
|
||||
build_cmd = ["cmake", "--build", str(PICO_BUILD_DIR), "-j4"]
|
||||
run(build_cmd)
|
||||
|
||||
# Show output files
|
||||
uf2_file = PICO_BUILD_DIR / "sls.uf2"
|
||||
if uf2_file.exists():
|
||||
print(f"\nBuild successful! UF2 file: {uf2_file}")
|
||||
print("To flash: Copy this file to your Pico in BOOTSEL mode")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# MACOS BUILD
|
||||
# ---------------------------------------------------------------------
|
||||
def build_macos():
|
||||
"""Build for macOS (can be run on macOS or cross-compile on Linux)"""
|
||||
print("\n=== Building for macOS ===\n")
|
||||
|
||||
if PLATFORM != "macos" and PLATFORM != "linux":
|
||||
print("ERROR: macOS builds require macOS or Linux with osxcross")
|
||||
return False
|
||||
|
||||
sources = list(SRC_DIR.glob("*.c"))
|
||||
objects = [compile_source(s, is_test=False, target_platform="macos") for s in sources]
|
||||
|
||||
macos_target = BIN_DIR / "sls_macos"
|
||||
link_executable(objects, macos_target, target_platform="macos")
|
||||
|
||||
print(f"\nBuild successful! Binary: {macos_target}")
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# TARGETS
|
||||
# ---------------------------------------------------------------------
|
||||
def build_main():
|
||||
sources = list(SRC_DIR.glob("*.c"))
|
||||
objects = [compile_source(s, is_test=False) for s in sources if s.name != 'pico_main.c']
|
||||
objects = [compile_source(s, is_test=False) for s in sources]
|
||||
link_executable(objects, TARGET)
|
||||
|
||||
|
||||
|
|
@ -418,13 +169,6 @@ def build_tests():
|
|||
def clean():
|
||||
shutil.rmtree(OBJ_DIR, ignore_errors=True)
|
||||
shutil.rmtree(BIN_DIR, ignore_errors=True)
|
||||
shutil.rmtree(PICO_BUILD_DIR, ignore_errors=True)
|
||||
cmake_file = Path("CMakeLists.txt")
|
||||
if cmake_file.exists():
|
||||
cmake_file.unlink()
|
||||
toolchain_file = PICO_TOOLCHAIN_PATH
|
||||
if toolchain_file.exists():
|
||||
toolchain_file.unlink()
|
||||
print("Cleaned.")
|
||||
|
||||
|
||||
|
|
@ -448,49 +192,20 @@ def debug_tests():
|
|||
subprocess.call(["gdb", str(TEST_TARGET)])
|
||||
|
||||
|
||||
def show_help():
|
||||
help_text = """
|
||||
Build Script for Multi-Platform Compilation
|
||||
============================================
|
||||
|
||||
Usage: python3 build.py [command]
|
||||
|
||||
Commands:
|
||||
all, main, build Build for current platform
|
||||
run Build and run program
|
||||
test Build and run tests
|
||||
debug Build tests and run debugger
|
||||
clean Remove build artifacts
|
||||
|
||||
macos Build for macOS
|
||||
pico, rp2040 Build for RP2040 (Raspberry Pi Pico)
|
||||
|
||||
help Show this help message
|
||||
|
||||
Environment Variables:
|
||||
PICO_SDK_PATH Path to Pico SDK (default: ~/pico/pico-sdk)
|
||||
|
||||
Examples:
|
||||
python3 build.py build # Build for current platform
|
||||
python3 build.py pico # Build for RP2040
|
||||
python3 build.py macos # Build for macOS
|
||||
python3 build.py clean # Clean all build files
|
||||
"""
|
||||
print(help_text)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# ENTRY POINT
|
||||
# ---------------------------------------------------------------------
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
show_help()
|
||||
print("Usage: python3 build.py [all|build|test|run|debug|clean]")
|
||||
return
|
||||
|
||||
cmd = sys.argv[1]
|
||||
|
||||
match cmd:
|
||||
case "all" | "main" | "build":
|
||||
case "all" | "main":
|
||||
build_main()
|
||||
case "build":
|
||||
build_main()
|
||||
case "run":
|
||||
run_main()
|
||||
|
|
@ -500,15 +215,8 @@ def main():
|
|||
debug_tests()
|
||||
case "clean":
|
||||
clean()
|
||||
case "macos":
|
||||
build_macos()
|
||||
case "pico" | "rp2040":
|
||||
build_rp2040()
|
||||
case "help" | "-h" | "--help":
|
||||
show_help()
|
||||
case _:
|
||||
print(f"Unknown target: {cmd}")
|
||||
print("Run 'python3 build.py help' for usage information")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
{ 2 - dup 0 <= { drop 1 } { 1 1 rot 0 swap { drop swap 1 pick + } for swap drop } if } lambda ::fib const
|
||||
|
|
@ -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
|
||||
|
|
@ -9,23 +9,6 @@
|
|||
#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
|
||||
|
|
|
|||
|
|
@ -6,11 +6,4 @@
|
|||
#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
|
||||
|
|
|
|||
|
|
@ -27,11 +27,9 @@ typedef enum {
|
|||
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 {
|
||||
|
|
@ -76,8 +74,6 @@ typedef struct {
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -11,20 +11,6 @@
|
|||
#include "sls/bool.h"
|
||||
#include "sls/errors.h"
|
||||
|
||||
#if __SIZEOF_POINTER__ == 4
|
||||
// 32-bit system
|
||||
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I32
|
||||
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
|
||||
#elif __SIZEOF_POINTER__ == 8
|
||||
// 64-bit system
|
||||
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I64
|
||||
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F64
|
||||
#else
|
||||
// Fallback
|
||||
#define SLS_INTEGER_TYPE_DEFAULT INTEGER_I16
|
||||
#define SLS_FLOAT_TYPE_DEFAULT NUMERIC_F32
|
||||
#endif
|
||||
|
||||
extern const size_t TYPE_NAMES_SAFE_LENGTH;
|
||||
|
||||
typedef struct {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#define SLS_MAIN_H
|
||||
|
||||
#define SLS_NAME "SLS_C"
|
||||
#define SLS_VER "0.0.2-alpha"
|
||||
#define SLS_VER "a.0.0"
|
||||
|
||||
#ifndef GIT_COMMIT_HASH
|
||||
#define GIT_COMMIT_HASH "UNKNOWN"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@
|
|||
#ifndef SLS_REPL_H
|
||||
#define SLS_REPL_H
|
||||
|
||||
int repl();
|
||||
int repl(int argc, char *argv[]);
|
||||
|
||||
#endif // SLS_REPL_H
|
||||
|
|
|
|||
2319
SLS_C/src/builtin.c
2319
SLS_C/src/builtin.c
File diff suppressed because it is too large
Load Diff
|
|
@ -3,66 +3,4 @@
|
|||
// 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,25 +28,6 @@ const char *STACK_TYPES_NAMES[] = {
|
|||
"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);
|
||||
|
|
@ -165,7 +146,6 @@ Boolean push_token(InterpreterState *interpreter_state, Token token) {
|
|||
item->boolean = token.boolean_literal;
|
||||
break;
|
||||
case STACK_TOKEN_STRING:
|
||||
case STACK_CALLABLE:
|
||||
item->token_string = copy_token_string(token.token_string);
|
||||
break;
|
||||
}
|
||||
|
|
@ -173,30 +153,18 @@ Boolean push_token(InterpreterState *interpreter_state, Token token) {
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
Boolean execute_func(InterpreterState *interpreter_state, SlsStr key) {
|
||||
static 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;
|
||||
}
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -35,21 +35,21 @@ const char *TOKEN_TYPES_NAMES[] = {
|
|||
const size_t TOKEN_TYPE_COUNT = sizeof(TOKEN_TYPES_NAMES) / sizeof(*TOKEN_TYPES_NAMES);
|
||||
|
||||
const char *ARRAY_TYPES_NAMES[] = {
|
||||
"Identifier",
|
||||
"i64",
|
||||
"i32",
|
||||
"i16",
|
||||
"i8",
|
||||
"u64",
|
||||
"u32",
|
||||
"u16",
|
||||
"u8",
|
||||
"Float",
|
||||
"Double",
|
||||
"Character",
|
||||
"String",
|
||||
"Boolean",
|
||||
"Inline Struct",
|
||||
"Identifier",
|
||||
"i64",
|
||||
"i32",
|
||||
"i16",
|
||||
"i8",
|
||||
"u64",
|
||||
"u32",
|
||||
"u16",
|
||||
"u8",
|
||||
"Float",
|
||||
"Double",
|
||||
"Character",
|
||||
"String",
|
||||
"Boolean",
|
||||
"Inline Struct",
|
||||
};
|
||||
|
||||
const size_t ARRAY_TYPE_COUNT = sizeof(ARRAY_TYPES_NAMES) / sizeof(*ARRAY_TYPES_NAMES);
|
||||
|
|
@ -521,7 +521,7 @@ static LexerResult parse_binary_integer(LexerInfo *lexer_info, char c, size_t st
|
|||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_BINARY);
|
||||
if (isspace(c) || c == '/' || c == '\0') {
|
||||
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);
|
||||
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||
|
|
@ -533,7 +533,7 @@ static LexerResult parse_octal_integer(LexerInfo *lexer_info, char c, size_t sta
|
|||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_OCTAL);
|
||||
if (isspace(c) || c == '/' || c == '\0') {
|
||||
uint64_t value = create_octal_integer(lexer_info, start);
|
||||
return create_integer_token(lexer_info, 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);
|
||||
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||
|
|
@ -550,7 +550,7 @@ static LexerResult parse_float(LexerInfo *lexer_info, char c, size_t start, size
|
|||
if (c == 'e' || c == 'E') return parse_exponential(lexer_info, c, start, start_line);
|
||||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_FLOAT);
|
||||
if (isspace(c) || c == '/' || c == '\0')
|
||||
return create_float_token(lexer_info, 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);
|
||||
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||
return lexer_error(lexer_info, error_msg, start, start_line);
|
||||
|
|
@ -565,7 +565,7 @@ static LexerResult parse_decimal_integer(LexerInfo *lexer_info, char c, size_t s
|
|||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_DECIMAL);
|
||||
if (isspace(c) || c == '/' || c == '\0') {
|
||||
uint64_t value = create_decimal_integer(lexer_info, start);
|
||||
return create_integer_token(lexer_info, 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);
|
||||
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||
|
|
@ -577,7 +577,7 @@ static LexerResult parse_hexadecimal_integer(LexerInfo *lexer_info, char c, size
|
|||
if (c == ':') return parse_numeric_type(lexer_info, c, start, start_line, NUMERIC_HEXADECIMAL);
|
||||
if (isspace(c) || c == '/' || c == '\0') {
|
||||
uint64_t value = create_hexadecimal_integer(lexer_info, start);
|
||||
return create_integer_token(lexer_info, 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);
|
||||
if (error_msg.str == NULL) return (LexerResult){SLS_ERROR, .error = (SlsError){SLS_STR("Out Of Memory Error."), 1}};
|
||||
|
|
|
|||
|
|
@ -9,32 +9,27 @@
|
|||
|
||||
#include "sls/meta.h"
|
||||
#include "sls/bool.h"
|
||||
#include "sls/string.h"
|
||||
#include "sls/repl.h"
|
||||
#include "sls/file.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
Boolean version = FALSE;
|
||||
Boolean file = 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;
|
||||
(void)file;
|
||||
(void)filename;
|
||||
|
||||
for (int i = 0; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--version") == 0) version = TRUE;
|
||||
if (strcmp(argv[i], "-v") == 0) version = TRUE;
|
||||
}
|
||||
|
||||
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 if (file) {
|
||||
return 1;
|
||||
} else {
|
||||
return repl();
|
||||
return repl(0, NULL);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -65,13 +65,13 @@ void print_top_of_stack(InterpreterState *interpreter_state) {
|
|||
case STACK_TOKEN_STRING:
|
||||
printf("#0: <TOKEN STRING>\n");
|
||||
break;
|
||||
case STACK_CALLABLE:
|
||||
printf("#0: <CALLABLE>\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int repl() {
|
||||
int repl(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
print_version();
|
||||
printf("===== YREA SLS REPL =====\n");
|
||||
printf("Type `#exit` to exit.\n");
|
||||
|
|
@ -79,7 +79,6 @@ int repl() {
|
|||
|
||||
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);
|
||||
|
|
@ -101,10 +100,11 @@ int repl() {
|
|||
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;
|
||||
}
|
||||
} else
|
||||
if (!execute(interpreter_state, head)) {
|
||||
printf("A runtime error occurred!\n");
|
||||
break;
|
||||
}
|
||||
head = head->next;
|
||||
}
|
||||
clean_token_result(result.result);
|
||||
|
|
|
|||
|
|
@ -1803,8 +1803,8 @@ static TestResult test_Float_f32_Negative_Zero() {
|
|||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Simple_Letter_Uppercase_A() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Uppercase A"), SLS_STR("'A'"));
|
||||
static TestResult test_Char_Simple_Letter_A() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter A"), SLS_STR("'A'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
|
|
@ -1813,8 +1813,8 @@ static TestResult test_Char_Simple_Letter_Uppercase_A() {
|
|||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Simple_Letter_Lowercase_a() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Lowercase a"), SLS_STR("'a'"));
|
||||
static TestResult test_Char_Simple_Letter_a() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter a"), SLS_STR("'a'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
|
|
@ -1823,8 +1823,8 @@ static TestResult test_Char_Simple_Letter_Lowercase_a() {
|
|||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Simple_Letter_Uppercase_Z() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Uppercase Z"), SLS_STR("'Z'"));
|
||||
static TestResult test_Char_Simple_Letter_Z() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Z"), SLS_STR("'Z'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
|
|
@ -1833,8 +1833,8 @@ static TestResult test_Char_Simple_Letter_Uppercase_Z() {
|
|||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Simple_Letter_Lowercase_z() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter Lowercase z"), SLS_STR("'z'"));
|
||||
static TestResult test_Char_Simple_Letter_z() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Simple Letter z"), SLS_STR("'z'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
|
|
@ -2073,32 +2073,12 @@ static TestResult test_Char_Right_Brace() {
|
|||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Escape_Single_quote() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Escape Single quote"), SLS_STR("'\\''"));
|
||||
static TestResult test_Char_Escape_Tab() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Escape Tab"), SLS_STR("'\\t'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
if (test_character_value(&test, result, i++, &(uint8_t){39})) return test.result;
|
||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Escape_Newline() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Escape Newline"), SLS_STR("'\\n'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
if (test_character_value(&test, result, i++, &(uint8_t){10})) return test.result;
|
||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Escape_Null_character() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Escape Null character"), SLS_STR("'\\0'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
if (test_character_value(&test, result, i++, &(uint8_t){0})) return test.result;
|
||||
if (test_character_value(&test, result, i++, &(uint8_t){9})) return test.result;
|
||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||
return pass_test(&test, result);
|
||||
}
|
||||
|
|
@ -2113,12 +2093,22 @@ static TestResult test_Char_Escape_Backslash() {
|
|||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Escape_Tab() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Escape Tab"), SLS_STR("'\\t'"));
|
||||
static TestResult test_Char_Escape_Null_character() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Escape Null character"), SLS_STR("'\\0'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
if (test_character_value(&test, result, i++, &(uint8_t){9})) return test.result;
|
||||
if (test_character_value(&test, result, i++, &(uint8_t){0})) return test.result;
|
||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Escape_Single_quote() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Escape Single quote"), SLS_STR("'\\''"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
if (test_character_value(&test, result, i++, &(uint8_t){39})) return test.result;
|
||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||
return pass_test(&test, result);
|
||||
}
|
||||
|
|
@ -2133,6 +2123,16 @@ static TestResult test_Char_Escape_Carriage_return() {
|
|||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_Escape_Newline() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char Escape Newline"), SLS_STR("'\\n'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);
|
||||
size_t i = 0;
|
||||
if (test_character_value(&test, result, i++, &(uint8_t){10})) return test.result;
|
||||
if (test_eof_value(&test, result, i++, 0)) return test.result;
|
||||
return pass_test(&test, result);
|
||||
}
|
||||
|
||||
static TestResult test_Char_With_Leading_Whitespace() {
|
||||
LexerTest test = start_up_test(SLS_STR("Char With Leading Whitespace"), SLS_STR(" 'A'"));
|
||||
LexerResult result = lexical_analysis(&test.lexer_info);
|
||||
|
|
@ -3845,10 +3845,10 @@ TestsReport run_lexer_tests() {
|
|||
test_report.tests[i++] = test_Float_Default_Negative_Zero();
|
||||
test_report.tests[i++] = test_Float_f32_Positive_Zero();
|
||||
test_report.tests[i++] = test_Float_f32_Negative_Zero();
|
||||
test_report.tests[i++] = test_Char_Simple_Letter_Uppercase_A();
|
||||
test_report.tests[i++] = test_Char_Simple_Letter_Lowercase_a();
|
||||
test_report.tests[i++] = test_Char_Simple_Letter_Uppercase_Z();
|
||||
test_report.tests[i++] = test_Char_Simple_Letter_Lowercase_z();
|
||||
test_report.tests[i++] = test_Char_Simple_Letter_A();
|
||||
test_report.tests[i++] = test_Char_Simple_Letter_a();
|
||||
test_report.tests[i++] = test_Char_Simple_Letter_Z();
|
||||
test_report.tests[i++] = test_Char_Simple_Letter_z();
|
||||
test_report.tests[i++] = test_Char_Digit_0();
|
||||
test_report.tests[i++] = test_Char_Digit_5();
|
||||
test_report.tests[i++] = test_Char_Digit_9();
|
||||
|
|
@ -3872,12 +3872,12 @@ TestsReport run_lexer_tests() {
|
|||
test_report.tests[i++] = test_Char_Right_Bracket();
|
||||
test_report.tests[i++] = test_Char_Left_Brace();
|
||||
test_report.tests[i++] = test_Char_Right_Brace();
|
||||
test_report.tests[i++] = test_Char_Escape_Single_quote();
|
||||
test_report.tests[i++] = test_Char_Escape_Newline();
|
||||
test_report.tests[i++] = test_Char_Escape_Null_character();
|
||||
test_report.tests[i++] = test_Char_Escape_Backslash();
|
||||
test_report.tests[i++] = test_Char_Escape_Tab();
|
||||
test_report.tests[i++] = test_Char_Escape_Backslash();
|
||||
test_report.tests[i++] = test_Char_Escape_Null_character();
|
||||
test_report.tests[i++] = test_Char_Escape_Single_quote();
|
||||
test_report.tests[i++] = test_Char_Escape_Carriage_return();
|
||||
test_report.tests[i++] = test_Char_Escape_Newline();
|
||||
test_report.tests[i++] = test_Char_With_Leading_Whitespace();
|
||||
test_report.tests[i++] = test_Char_With_Trailing_Whitespace();
|
||||
test_report.tests[i++] = test_Char_With_Both_Whitespace();
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
__pycache__/
|
||||
.venv/
|
||||
sls_python.egg-info/
|
||||
dist/
|
||||
|
|
@ -1,19 +1,3 @@
|
|||
# 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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
"""Build backend for sls_python package."""
|
||||
|
||||
from .build_hooks import build_wheel, build_sdist
|
||||
|
||||
__all__ = ["build_wheel", "build_sdist"]
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# Auto-generated during build
|
||||
version = "{version}"
|
||||
commit = "{commit}"
|
||||
timestamp = "{timestamp}"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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 = {"" = ".."}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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: ...
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -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())
|
||||
|
|
@ -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
|
|
@ -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), ('x²', self.square), ('1/x', self.reciprocal), ('NULL', lambda: None)],
|
||||
[('ENTER', lambda: self.enter(True)), ('SWAP', self.swap), ('<-', self.drop), ('NULL', lambda: None)],
|
||||
[('7', lambda: self.digit('7')), ('8', lambda: self.digit('8')), ('9', lambda: self.digit('9')), ('÷', self.divide)],
|
||||
[('4', lambda: self.digit('4')), ('5', lambda: self.digit('5')), ('6', lambda: self.digit('6')), ('×', self.multiply)],
|
||||
[('1', lambda: self.digit('1')), ('2', lambda: self.digit('2')), ('3', lambda: self.digit('3')), ('−', self.subtract)],
|
||||
[('0', lambda: self.digit('0')), ('.', self.decimal), ('±', self.negate), ('+', self.add)],
|
||||
]
|
||||
|
||||
for row_idx, row in enumerate(buttons, start=2):
|
||||
for col_idx, (text, command) in enumerate(row):
|
||||
if text != "NULL":
|
||||
btn = ttk.Button(parent,
|
||||
text=text,
|
||||
command=command,
|
||||
width=8)
|
||||
btn.grid(row=row_idx, column=col_idx, padx=2, pady=2, sticky=(tk.W, tk.E, tk.N, tk.S)) # type: ignore
|
||||
|
||||
# Keyboard bindings
|
||||
self.root.bind('<Delete>', lambda e: self.drop())
|
||||
self.root.bind('<Return>', lambda e: self.enter(True))
|
||||
self.root.bind('<KP_Enter>', lambda e: self.enter(True))
|
||||
for i in range(10):
|
||||
self.root.bind(str(i), lambda e, n=str(i): self.digit(n))
|
||||
self.root.bind('.', lambda e: self.decimal())
|
||||
self.root.bind('+', lambda e: self.add())
|
||||
self.root.bind('-', lambda e: self.subtract())
|
||||
self.root.bind('*', lambda e: self.multiply())
|
||||
self.root.bind('/', lambda e: self.divide())
|
||||
|
||||
def digit(self, d):
|
||||
"""Handle digit button press"""
|
||||
if self.is_new_entry:
|
||||
self.entry_buffer = d
|
||||
self.is_new_entry = False
|
||||
else:
|
||||
self.entry_buffer += d
|
||||
self.update_entry_display()
|
||||
|
||||
def decimal(self):
|
||||
"""Handle decimal point"""
|
||||
if self.is_new_entry:
|
||||
self.entry_buffer = "0."
|
||||
self.is_new_entry = False
|
||||
elif '.' not in self.entry_buffer:
|
||||
self.entry_buffer += '.'
|
||||
self.update_entry_display()
|
||||
|
||||
def negate(self):
|
||||
"""Toggle sign of current entry"""
|
||||
if self.is_new_entry:
|
||||
# Negate top of stack
|
||||
self.execute_code("0 swap -")
|
||||
else:
|
||||
if self.entry_buffer.startswith('-'):
|
||||
self.entry_buffer = self.entry_buffer[1:]
|
||||
else:
|
||||
self.entry_buffer = '-' + self.entry_buffer
|
||||
self.update_entry_display()
|
||||
|
||||
def enter(self, key=False):
|
||||
"""Push current entry onto stack"""
|
||||
if self.entry_buffer:
|
||||
self.execute_code(self.entry_buffer)
|
||||
self.entry_buffer = ""
|
||||
self.is_new_entry = True
|
||||
elif key:
|
||||
self.execute_code("dup")
|
||||
self.update_displays()
|
||||
|
||||
def add(self):
|
||||
"""Addition operation"""
|
||||
self.enter()
|
||||
self.execute_code("+")
|
||||
self.update_displays()
|
||||
|
||||
def subtract(self):
|
||||
"""Subtraction operation"""
|
||||
self.enter()
|
||||
self.execute_code("-")
|
||||
self.update_displays()
|
||||
|
||||
def multiply(self):
|
||||
"""Multiplication operation"""
|
||||
self.enter()
|
||||
self.execute_code("*")
|
||||
self.update_displays()
|
||||
|
||||
def divide(self):
|
||||
"""Division operation"""
|
||||
self.enter()
|
||||
self.execute_code("/")
|
||||
self.update_displays()
|
||||
|
||||
def sqrt(self):
|
||||
"""Square root operation"""
|
||||
self.enter()
|
||||
self.execute_code("sqrt")
|
||||
self.update_displays()
|
||||
|
||||
def square(self):
|
||||
"""Square operation"""
|
||||
self.enter()
|
||||
self.execute_code("dup *")
|
||||
self.update_displays()
|
||||
|
||||
def reciprocal(self):
|
||||
"""Reciprocal (1/x) operation"""
|
||||
self.enter()
|
||||
self.execute_code("1 swap /")
|
||||
self.update_displays()
|
||||
|
||||
def swap(self):
|
||||
"""Swap top two stack items"""
|
||||
self.enter()
|
||||
self.execute_code("swap")
|
||||
self.update_displays()
|
||||
|
||||
def drop(self):
|
||||
"""Drop top stack item"""
|
||||
self.enter()
|
||||
self.execute_code("drop")
|
||||
self.update_displays()
|
||||
|
||||
def execute_code(self, code):
|
||||
"""Execute stack language code"""
|
||||
try:
|
||||
self.lexer.source_code = code
|
||||
self.lexer.pos = 0
|
||||
self.lexer.column = 1
|
||||
self.lexer.line = 1
|
||||
|
||||
tokens = lexical_analysis(self.lexer)
|
||||
|
||||
for token in tokens:
|
||||
if token.type == TokenType.EOF:
|
||||
break
|
||||
if not self.interp.execute(token):
|
||||
print(f"Error executing: {code}")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Exception: {e}")
|
||||
|
||||
def update_entry_display(self):
|
||||
"""Update the entry display"""
|
||||
display_text = self.entry_buffer if self.entry_buffer else "0"
|
||||
self.entry_label.config(text=display_text)
|
||||
|
||||
def update_displays(self):
|
||||
"""Update both entry and stack displays"""
|
||||
self.update_entry_display()
|
||||
self.update_stack_display()
|
||||
|
||||
def update_stack_display(self):
|
||||
"""Update the 4-level stack display"""
|
||||
stack_size = len(self.interp.stack)
|
||||
|
||||
for i in range(4):
|
||||
level = 3 - i # T3, T2, T1, T0
|
||||
if stack_size > level:
|
||||
entry = self.interp.stack[-(level + 1)]
|
||||
value_str = self.format_stack_entry(entry)
|
||||
self.stack_labels[i].config(text=f"{level}: {value_str}")
|
||||
else:
|
||||
self.stack_labels[i].config(text=f"{level}:")
|
||||
|
||||
def format_stack_entry(self, entry):
|
||||
"""Format a stack entry for display"""
|
||||
if entry.type == StackType.I64:
|
||||
return str(entry.value)
|
||||
elif entry.type == StackType.DOUBLE:
|
||||
val = entry.value
|
||||
# Format with appropriate precision
|
||||
if abs(val) < 1e-10 and val != 0:
|
||||
return f"{val:.6e}"
|
||||
elif abs(val) > 1e10:
|
||||
return f"{val:.6e}"
|
||||
else:
|
||||
return f"{val:.10g}"
|
||||
elif entry.type == StackType.BOOLEAN:
|
||||
return str(entry.value)
|
||||
else:
|
||||
return str(entry.value)
|
||||
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = SlsCalculator(root)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
|
|
@ -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"] }
|
||||
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1993,7 +1993,7 @@
|
|||
stack_final:
|
||||
- type: f32
|
||||
value: -0.0
|
||||
- name: Char Simple Letter Uppercase A
|
||||
- name: Char Simple Letter A
|
||||
code: '''A'''
|
||||
tokens:
|
||||
- type: char
|
||||
|
|
@ -2005,7 +2005,7 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: A
|
||||
- name: Char Simple Letter Lowercase a
|
||||
- name: Char Simple Letter a
|
||||
code: '''a'''
|
||||
tokens:
|
||||
- type: char
|
||||
|
|
@ -2017,7 +2017,7 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: a
|
||||
- name: Char Simple Letter Uppercase Z
|
||||
- name: Char Simple Letter Z
|
||||
code: '''Z'''
|
||||
tokens:
|
||||
- type: char
|
||||
|
|
@ -2029,7 +2029,7 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: Z
|
||||
- name: Char Simple Letter Lowercase z
|
||||
- name: Char Simple Letter z
|
||||
code: '''z'''
|
||||
tokens:
|
||||
- type: char
|
||||
|
|
@ -2317,6 +2317,42 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: '}'
|
||||
- name: Char Escape Tab
|
||||
code: '''\\t'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\t"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\t"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\t"
|
||||
- name: Char Escape Backslash
|
||||
code: '''\\\\'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: \
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: \
|
||||
stack_final:
|
||||
- type: char
|
||||
value: \
|
||||
- name: Char Escape Null character
|
||||
code: '''\\0'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\0"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\0"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\0"
|
||||
- name: Char Escape Single quote
|
||||
code: '''\\'''''
|
||||
tokens:
|
||||
|
|
@ -2329,6 +2365,18 @@
|
|||
stack_final:
|
||||
- type: char
|
||||
value: ''''
|
||||
- name: Char Escape Carriage return
|
||||
code: '''\\r'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\r"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\r"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\r"
|
||||
- name: Char Escape Newline
|
||||
code: '''\\n'''
|
||||
tokens:
|
||||
|
|
@ -2347,54 +2395,6 @@
|
|||
value: '
|
||||
|
||||
'
|
||||
- name: Char Escape Null character
|
||||
code: '''\\0'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\0"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\0"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\0"
|
||||
- name: Char Escape Backslash
|
||||
code: '''\\\\'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: \
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: \
|
||||
stack_final:
|
||||
- type: char
|
||||
value: \
|
||||
- name: Char Escape Tab
|
||||
code: '''\\t'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\t"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\t"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\t"
|
||||
- name: Char Escape Carriage return
|
||||
code: '''\\r'''
|
||||
tokens:
|
||||
- type: char
|
||||
value: "\r"
|
||||
operations:
|
||||
- function: push
|
||||
type: char
|
||||
value: "\r"
|
||||
stack_final:
|
||||
- type: char
|
||||
value: "\r"
|
||||
- name: Char With Leading Whitespace
|
||||
code: ' ''A'''
|
||||
tokens:
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ class CharTestGenerator(BaseTestGenerator):
|
|||
def generate_basic_tests(self):
|
||||
"""Generate basic character literal tests."""
|
||||
# Simple ASCII letters
|
||||
self.make_success_test("Char Simple Letter Uppercase A", "'A'", "char", 'A')
|
||||
self.make_success_test("Char Simple Letter Lowercase a", "'a'", "char", 'a')
|
||||
self.make_success_test("Char Simple Letter Uppercase Z", "'Z'", "char", 'Z')
|
||||
self.make_success_test("Char Simple Letter Lowercase z", "'z'", "char", 'z')
|
||||
self.make_success_test("Char Simple Letter A", "'A'", "char", 'A')
|
||||
self.make_success_test("Char Simple Letter a", "'a'", "char", 'a')
|
||||
self.make_success_test("Char Simple Letter Z", "'Z'", "char", 'Z')
|
||||
self.make_success_test("Char Simple Letter z", "'z'", "char", 'z')
|
||||
|
||||
# Digits
|
||||
self.make_success_test("Char Digit 0", "'0'", "char", '0')
|
||||
|
|
|
|||
Loading…
Reference in New Issue