Compare commits
10 Commits
835132577f
...
8e918dcf34
| Author | SHA1 | Date |
|---|---|---|
|
|
8e918dcf34 | |
|
|
2dabd4bf30 | |
|
|
2c8e459cac | |
|
|
a754cd4df4 | |
|
|
4a2ee88328 | |
|
|
1a11569e9a | |
|
|
ac9bbc0415 | |
|
|
c1db0937a2 | |
|
|
3c288e9165 | |
|
|
3dc5455323 |
|
|
@ -1,4 +1,4 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.venv/
|
.venv/
|
||||||
dist/
|
|
||||||
sls_python.egg-info/
|
sls_python.egg-info/
|
||||||
|
dist/
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,19 @@
|
||||||
# SLS Python
|
# SLS Python
|
||||||
|
|
||||||
This is the Python implementation for the YREA SLS interpreter.
|
This is the Python implementation for the YREA SLS interpreter.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd SLS_Python
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
pip install build wheel "setuptools>=61.0"
|
||||||
|
|
||||||
|
# Install the backend (one-time setup)
|
||||||
|
pip install -e sls_build_backend
|
||||||
|
|
||||||
|
# Build with --no-isolation (required because backend is local)
|
||||||
|
python -m build --no-isolation
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Build this file using 'python -m build --no-isolation'
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel", "sls_build_backend"]
|
||||||
|
build-backend = "sls_build_backend"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "sls_python"
|
||||||
|
version = "0.0.1-alpha"
|
||||||
|
description = "Python reimplementation of the SLS C project"
|
||||||
|
authors = [ { name = "Kyler Olsen" } ]
|
||||||
|
readme = "README.md"
|
||||||
|
license = { text = "MIT" }
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["sls_py"]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
sls_py = "sls_py.__main__:main"
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""Build backend for sls_python package."""
|
||||||
|
|
||||||
|
from .build_hooks import build_wheel, build_sdist
|
||||||
|
|
||||||
|
__all__ = ["build_wheel", "build_sdist"]
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Auto-generated during build
|
||||||
|
version = "{version}"
|
||||||
|
commit = "{commit}"
|
||||||
|
timestamp = "{timestamp}"
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
version = "{version}"
|
||||||
|
try:
|
||||||
|
__result_hash = subprocess.check_output(
|
||||||
|
["git", "describe", "--always", "--dirty", "--abbrev=7"],
|
||||||
|
cwd=".",
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True
|
||||||
|
).strip()
|
||||||
|
__result_date = subprocess.check_output(
|
||||||
|
["git", "show", "-s", "--format=%ci"],
|
||||||
|
cwd=".",
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True
|
||||||
|
).strip()
|
||||||
|
commit = __result_hash + " " + __result_date
|
||||||
|
except:
|
||||||
|
commit = "unknown"
|
||||||
|
try:
|
||||||
|
timestamp = datetime.now(timezone.utc).isoformat() + "Z"
|
||||||
|
except:
|
||||||
|
timestamp = "unknown"
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
from setuptools.build_meta import build_wheel as _build_wheel
|
||||||
|
from setuptools.build_meta import build_sdist as _build_sdist
|
||||||
|
from .write_version import generate_version, generate_version_dev
|
||||||
|
|
||||||
|
|
||||||
|
def build_wheel(*args, **kwargs):
|
||||||
|
generate_version()
|
||||||
|
o = _build_wheel(*args, **kwargs)
|
||||||
|
generate_version_dev()
|
||||||
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
def build_sdist(*args, **kwargs):
|
||||||
|
generate_version()
|
||||||
|
o = _build_sdist(*args, **kwargs)
|
||||||
|
generate_version_dev()
|
||||||
|
return o
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "sls-build-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Build backend for sls_python"
|
||||||
|
authors = [{name = "Kyler Olsen"}]
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["sls_build_backend"]
|
||||||
|
package-dir = {"" = ".."}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import datetime
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
try:
|
||||||
|
import tomllib # Python 3.11+
|
||||||
|
except Exception: # pragma: no cover - older Pythons
|
||||||
|
tomllib = None
|
||||||
|
|
||||||
|
root = pathlib.Path(__file__).resolve().parents[1]
|
||||||
|
# templates live inside the backend package
|
||||||
|
template = root / "sls_build_backend" / "_version.py.in"
|
||||||
|
template_dev = root / "sls_build_backend" / "_version_dev.py.in"
|
||||||
|
output = root / "sls_py" / "_version.py"
|
||||||
|
|
||||||
|
|
||||||
|
def get_commit():
|
||||||
|
try:
|
||||||
|
result_hash = subprocess.check_output(
|
||||||
|
["git", "describe", "--always", "--dirty", "--abbrev=7"],
|
||||||
|
cwd=str(root),
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
).strip()
|
||||||
|
result_date = subprocess.check_output(
|
||||||
|
["git", "show", "-s", "--format=%ci"],
|
||||||
|
cwd=str(root),
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
).strip()
|
||||||
|
return f"{result_hash} {result_date}"
|
||||||
|
except Exception:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def get_timestamp():
|
||||||
|
return datetime.datetime.now(datetime.timezone.utc).isoformat() + "Z"
|
||||||
|
|
||||||
|
|
||||||
|
def _get_version_from_pyproject(root_path: pathlib.Path):
|
||||||
|
py = root_path / "pyproject.toml"
|
||||||
|
if not py.exists() or tomllib is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
data = tomllib.loads(py.read_text())
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
# PEP 621
|
||||||
|
version = data.get("project", {}).get("version")
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
# Some projects put metadata under tool.setuptools
|
||||||
|
version = data.get("tool", {}).get("setuptools", {}).get("version")
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def _determine_version(root_path: pathlib.Path):
|
||||||
|
v = _get_version_from_pyproject(root_path)
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def generate_version_dev():
|
||||||
|
version = _determine_version(root)
|
||||||
|
if not template_dev.exists():
|
||||||
|
# write a minimal generated file if dev template missing
|
||||||
|
output.write_text(f"version = \"{version}\"\ncommit = \"unknown\"\ntimestamp = \"unknown\"\n")
|
||||||
|
return
|
||||||
|
text = template_dev.read_text()
|
||||||
|
text = text.format(version=version)
|
||||||
|
output.write_text(text)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_version():
|
||||||
|
version = _determine_version(root)
|
||||||
|
|
||||||
|
commit = get_commit()
|
||||||
|
timestamp = get_timestamp()
|
||||||
|
|
||||||
|
if not template.exists():
|
||||||
|
# fallback: write a minimal file
|
||||||
|
output.write_text(
|
||||||
|
f"version = \"{version}\"\ncommit = \"{commit}\"\ntimestamp = \"{timestamp}\"\n"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = template.read_text()
|
||||||
|
text = text.format(version=version, commit=commit, timestamp=timestamp)
|
||||||
|
output.write_text(text)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
"""sls — Python skeleton for SLS reimplementation
|
||||||
|
|
||||||
|
Expose package version and small helpers here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__all__ = ["__version__", "cli"]
|
||||||
|
|
||||||
|
__version__ = "0.0.1"
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import sys
|
||||||
|
from .meta import print_version
|
||||||
|
from .repl import repl
|
||||||
|
from .file import run_file
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
version = False
|
||||||
|
filename = None
|
||||||
|
|
||||||
|
if len(args) == 1:
|
||||||
|
if args[0] in ("--version", "-v"):
|
||||||
|
version = True
|
||||||
|
else:
|
||||||
|
filename = args[0]
|
||||||
|
elif len(args) > 1:
|
||||||
|
print("Too many arguments!")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if version:
|
||||||
|
print_version()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if filename is not None:
|
||||||
|
return run_file(filename)
|
||||||
|
|
||||||
|
return repl()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
version = "0.0.1-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"
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .interpreter import InterpreterState
|
||||||
|
|
||||||
|
|
||||||
|
def load_builtins(interpreter_state: "InterpreterState") -> bool:
|
||||||
|
return True
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from sls.lexer import LexerInfo, lexical_analysis, Token
|
||||||
|
from sls.interpreter import InterpreterState
|
||||||
|
|
||||||
|
|
||||||
|
def exec_file(interpreter_state: InterpreterState, filename: str) -> bool:
|
||||||
|
path = Path(filename)
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
print(f"Cannot read file: {filename}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
code = path.read_text()
|
||||||
|
except Exception:
|
||||||
|
print(f"Cannot read file: {filename}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
lexer_info = LexerInfo(filename=filename, source_code=code)
|
||||||
|
|
||||||
|
tokens: list[Token] = lexical_analysis(lexer_info)
|
||||||
|
|
||||||
|
for tok in tokens:
|
||||||
|
if not interpreter_state.execute(tok):
|
||||||
|
print("A runtime error occurred!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def run_file(filename: str) -> int:
|
||||||
|
print(f"Executing file: {filename}")
|
||||||
|
|
||||||
|
interpreter_state = InterpreterState()
|
||||||
|
|
||||||
|
success = exec_file(interpreter_state, filename)
|
||||||
|
|
||||||
|
return 0 if success else 1
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
# sls/interpreter.py
|
||||||
|
# Kyler Olsen
|
||||||
|
# YREA SLS
|
||||||
|
# Interpreter (Python OOP port)
|
||||||
|
# November 2025
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum, auto
|
||||||
|
from typing import Callable, Dict, List, Optional
|
||||||
|
|
||||||
|
# import the types from your lexer module
|
||||||
|
# adjust the import path to where you put the lexer module
|
||||||
|
from .lexer import (
|
||||||
|
Token,
|
||||||
|
TokenType,
|
||||||
|
TokenString,
|
||||||
|
IntegerLiteral,
|
||||||
|
IntegerBuiltInType,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .builtin import load_builtins
|
||||||
|
|
||||||
|
|
||||||
|
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 # Identifier | int | float | bool | TokenString | FunctionItem etc.
|
||||||
|
|
||||||
|
|
||||||
|
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":
|
||||||
|
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] = {}
|
||||||
|
# Optionally load builtins; caller can pass a loader function
|
||||||
|
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: FunctionItem) -> None:
|
||||||
|
self.functions[name] = item
|
||||||
|
|
||||||
|
def get_function(self, name: str) -> Optional[FunctionItem]:
|
||||||
|
return self.functions.get(name)
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# execution primitives
|
||||||
|
# -------------------------
|
||||||
|
def push_token(self, token: Token) -> bool:
|
||||||
|
"""
|
||||||
|
Push a token onto the stack. Returns True on success, False on failure.
|
||||||
|
Mirrors the logic from the C implementation:
|
||||||
|
- disallow TOKEN_STRING, TOKEN_ARRAY, TOKEN_TYPE_TUPLE
|
||||||
|
- return True for TOKEN_EOF (no-op)
|
||||||
|
"""
|
||||||
|
ttype = token.type
|
||||||
|
|
||||||
|
if ttype == TokenType.EOF:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# map token -> stack type + extract value
|
||||||
|
if ttype == TokenType.IDENTIFIER:
|
||||||
|
entry = StackEntry(StackType.IDENTIFIER, token.identifier)
|
||||||
|
self.push(entry)
|
||||||
|
return True
|
||||||
|
|
||||||
|
if ttype == TokenType.INTEGER:
|
||||||
|
ilit: IntegerLiteral = token.integer_literal # type: ignore
|
||||||
|
itype = ilit.type
|
||||||
|
# choose stack type explicitly to mirror C widths
|
||||||
|
if itype == IntegerBuiltInType.I64:
|
||||||
|
st = StackType.I64
|
||||||
|
val = int(ilit.value)
|
||||||
|
elif itype == IntegerBuiltInType.I32:
|
||||||
|
st = StackType.I32
|
||||||
|
val = int(ilit.value) & 0xFFFFFFFF # mimic truncation if desired
|
||||||
|
elif itype == IntegerBuiltInType.I16:
|
||||||
|
st = StackType.I16
|
||||||
|
val = int(ilit.value) & 0xFFFF
|
||||||
|
elif itype == IntegerBuiltInType.I8:
|
||||||
|
st = StackType.I8
|
||||||
|
val = int(ilit.value) & 0xFF
|
||||||
|
elif itype == IntegerBuiltInType.U64:
|
||||||
|
st = StackType.U64
|
||||||
|
val = int(ilit.value)
|
||||||
|
elif itype == IntegerBuiltInType.U32:
|
||||||
|
st = StackType.U32
|
||||||
|
val = int(ilit.value) & 0xFFFFFFFF
|
||||||
|
elif itype == IntegerBuiltInType.U16:
|
||||||
|
st = StackType.U16
|
||||||
|
val = int(ilit.value) & 0xFFFF
|
||||||
|
elif itype == IntegerBuiltInType.U8:
|
||||||
|
st = StackType.U8
|
||||||
|
val = int(ilit.value) & 0xFF
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.push(StackEntry(st, val))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if ttype == TokenType.FLOAT:
|
||||||
|
self.push(StackEntry(StackType.FLOAT, token.float_literal))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if ttype == TokenType.DOUBLE:
|
||||||
|
self.push(StackEntry(StackType.DOUBLE, token.double_literal))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if ttype == TokenType.CHARACTER:
|
||||||
|
self.push(StackEntry(StackType.CHARACTER, token.character_literal))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if ttype == TokenType.STRING:
|
||||||
|
# C returned FALSE for TOKEN_STRING
|
||||||
|
return False
|
||||||
|
|
||||||
|
if ttype == TokenType.BOOLEAN:
|
||||||
|
self.push(StackEntry(StackType.BOOLEAN, token.boolean_literal))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if ttype == TokenType.ARRAY:
|
||||||
|
# C returned FALSE for arrays
|
||||||
|
return False
|
||||||
|
|
||||||
|
if ttype == TokenType.TOKEN_STRING:
|
||||||
|
# copy token string to mimic C copy semantics
|
||||||
|
ts_copy = token.token_string.deep_copy() # type: ignore
|
||||||
|
self.push(StackEntry(StackType.TOKEN_STRING, ts_copy))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if ttype == TokenType.TYPE_TUPLE:
|
||||||
|
# C returned FALSE for type tuples
|
||||||
|
return False
|
||||||
|
|
||||||
|
# unknown token type
|
||||||
|
return False
|
||||||
|
|
||||||
|
def execute_func(self, key: str) -> bool:
|
||||||
|
"""
|
||||||
|
Look up a function by name and execute it.
|
||||||
|
Builtin functions are callables that accept InterpreterState and return bool.
|
||||||
|
Token-string functions are executed via execute_token_string.
|
||||||
|
"""
|
||||||
|
func = self.get_function(key)
|
||||||
|
if func is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if func.type == FunctionType.BUILTIN:
|
||||||
|
if func.builtin is None:
|
||||||
|
return False
|
||||||
|
return func.builtin(self)
|
||||||
|
|
||||||
|
if func.type == FunctionType.TOKEN_STRING:
|
||||||
|
if func.token_string is None:
|
||||||
|
return False
|
||||||
|
return self.execute_token_string(func.token_string)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def execute_token_string(self, token_string: TokenString) -> bool:
|
||||||
|
"""
|
||||||
|
Execute each token in a TokenString.
|
||||||
|
If token is an identifier and not `is_literal`, treat it as a function call.
|
||||||
|
Otherwise push token onto the stack.
|
||||||
|
"""
|
||||||
|
for tok in token_string.tokens:
|
||||||
|
if tok.type == TokenType.IDENTIFIER and tok.identifier is not None and not tok.identifier.is_literal:
|
||||||
|
rv = self.execute_func(tok.identifier.name)
|
||||||
|
else:
|
||||||
|
rv = self.push_token(tok)
|
||||||
|
if not rv:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, token: Token) -> bool:
|
||||||
|
"""
|
||||||
|
Execute a single Token (this corresponds to processing a single lexer result).
|
||||||
|
If the token is an un-literal identifier -> function call, otherwise push it.
|
||||||
|
"""
|
||||||
|
if token.type == TokenType.IDENTIFIER and token.identifier is not None and not token.identifier.is_literal:
|
||||||
|
return self.execute_func(token.identifier.name)
|
||||||
|
else:
|
||||||
|
return self.push_token(token)
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from enum import Enum, auto
|
||||||
|
from typing import List, Optional, Any
|
||||||
|
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# 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 = 0
|
||||||
|
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 # Python int is arbitrary precision
|
||||||
|
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] # Python can store anything
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# ArrayLiteral (replaces C unions with Optional lists)
|
||||||
|
# =====================================================================
|
||||||
|
|
||||||
|
@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 (Python “union” via Optional fields)
|
||||||
|
# =====================================================================
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# Lexer Token Result / Lexer Result
|
||||||
|
# =====================================================================
|
||||||
|
|
||||||
|
class SlsResultType(Enum):
|
||||||
|
RESULT = auto()
|
||||||
|
ERROR = auto()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FileInfo:
|
||||||
|
filename: str
|
||||||
|
line: int
|
||||||
|
column: int
|
||||||
|
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# Function Stubs (to be implemented in Python version)
|
||||||
|
# =====================================================================
|
||||||
|
|
||||||
|
def lexical_analysis(lexer_info: LexerInfo) -> list[Token]:
|
||||||
|
return []
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Kyler Olsen
|
||||||
|
# YREA SLS
|
||||||
|
# Meta File (Python port)
|
||||||
|
# November 2025
|
||||||
|
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
from ._version import version, commit, timestamp # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
version = "unknown"
|
||||||
|
commit = "unknown"
|
||||||
|
timestamp = "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
SLS_NAME = "SLS_PYTHON"
|
||||||
|
|
||||||
|
# Runtime interpreter info (Python equivalent of compiler)
|
||||||
|
_impl = sys.implementation
|
||||||
|
INTERPRETER_NAME = _impl.name.capitalize()
|
||||||
|
INTERPRETER_VER = _impl.version.major
|
||||||
|
|
||||||
|
def print_version() -> None:
|
||||||
|
print(f"YREA SLS ({SLS_NAME}) {version} ({commit})")
|
||||||
|
print(f"Running on {INTERPRETER_NAME} {INTERPRETER_VER} at {timestamp}")
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
from .meta import print_version
|
||||||
|
from .lexer import LexerInfo, lexical_analysis
|
||||||
|
from .interpreter import InterpreterState
|
||||||
|
|
||||||
|
|
||||||
|
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 == "Identifier":
|
||||||
|
print(f"#0: ::{item.value}")
|
||||||
|
elif t in {"i64", "i32", "i16", "i8"}:
|
||||||
|
print(f"#0: {item.value}:{t}")
|
||||||
|
elif t in {"u64", "u32", "u16", "u8"}:
|
||||||
|
print(f"#0: {item.value}:{t}")
|
||||||
|
elif t == "f32":
|
||||||
|
print(f"#0: {item.value}:f32")
|
||||||
|
elif t == "f64":
|
||||||
|
print(f"#0: {item.value}")
|
||||||
|
elif t == "char":
|
||||||
|
print(f"#0: {item.value}")
|
||||||
|
elif t == "bool":
|
||||||
|
print("#0: TRUE" if item.value else "#0: FALSE")
|
||||||
|
elif t == "TokenString":
|
||||||
|
print("#0: <TOKEN STRING>")
|
||||||
|
elif t == "Callable":
|
||||||
|
print("#0: <CALLABLE>")
|
||||||
|
else:
|
||||||
|
print(f"#0: <UNKNOWN {t}>")
|
||||||
|
|
||||||
|
|
||||||
|
def repl() -> int:
|
||||||
|
"""Interactive interpreter loop."""
|
||||||
|
|
||||||
|
print_version()
|
||||||
|
print("===== YREA SLS REPL =====")
|
||||||
|
print("Type `#exit` to exit.")
|
||||||
|
|
||||||
|
interpreter = InterpreterState()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
line = input("> ")
|
||||||
|
except EOFError:
|
||||||
|
break
|
||||||
|
|
||||||
|
if line.strip() == "#exit":
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Create a fresh lexer each iteration
|
||||||
|
lexer_info = LexerInfo(filename=REPL_FILE_NAME, source_code=line)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tokens = lexical_analysis(lexer_info)
|
||||||
|
except Exception as err:
|
||||||
|
print(err)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Execute tokens in order
|
||||||
|
for token in tokens:
|
||||||
|
try:
|
||||||
|
ok = interpreter.execute(token)
|
||||||
|
if not ok:
|
||||||
|
print("A runtime error occurred!")
|
||||||
|
break
|
||||||
|
except Exception as err:
|
||||||
|
print(err)
|
||||||
|
break
|
||||||
|
|
||||||
|
print_top_of_stack(interpreter)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
def test_import_package():
|
||||||
|
import sls
|
||||||
|
|
||||||
|
assert hasattr(sls, "__version__")
|
||||||
|
assert isinstance(sls.__version__, str)
|
||||||
Loading…
Reference in New Issue