worked on filling in the project

This commit is contained in:
Kyler Olsen 2025-12-02 21:38:27 -07:00
parent 3dc5455323
commit 3c288e9165
13 changed files with 663 additions and 95 deletions

View File

@ -1,2 +1,5 @@
__pycache__/ __pycache__/
.venv/ .venv/
sls/_version.py
sls_python.egg-info/
dist/

2
SLS_Python/MANIFEST.in Normal file
View File

@ -0,0 +1,2 @@
include scripts/*.py
include scripts/*.in

View File

@ -1,18 +1,20 @@
# Build this file using 'python -m build' (from 'build' package)
[build-system] [build-system]
requires = ["setuptools>=61.0", "wheel"] requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta" build-backend = "sls.build_hooks"
[project] [project]
name = "sls-python" name = "sls_python"
version = "0.1.0" version = "0.1.0"
description = "Python reimplementation skeleton of the SLS C project" description = "Python reimplementation skeleton of the SLS C project"
authors = [ { name = "Your Name" } ] authors = [ { name = "Kyler Olsen" } ]
readme = "README.md" readme = "README.md"
license = { text = "MIT" } license = { text = "MIT" }
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = []
"click>=8.0"
] [tool.setuptools]
packages = ["sls"]
[project.scripts] [project.scripts]
sls = "sls.cli:main" sls = "sls.__main__:main"

View File

@ -1,4 +0,0 @@
pytest
click
mypy
black

View File

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

View File

@ -0,0 +1,39 @@
import subprocess
import datetime
import pathlib
root = pathlib.Path(__file__).resolve().parents[1]
template = root / "scripts" / "_version.py.in"
output = root / "sls" / "_version.py"
def get_commit():
try:
result_hash = subprocess.check_output(
["git", "describe", "--always", "--dirty", "--abbrev=7"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
result_date = subprocess.check_output(
["git", "show", "-s", "--format=%ci"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
return f"{result_hash} {result_date}"
except Exception:
return "unknown"
def get_timestamp():
return datetime.datetime.now(datetime.timezone.utc).isoformat() + "Z"
def main():
commit = get_commit()
timestamp = get_timestamp()
text = template.read_text()
text = text.format(commit=commit, timestamp=timestamp)
output.write_text(text)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,16 @@
import subprocess
from setuptools.build_meta import build_wheel as _build_wheel
from setuptools.build_meta import build_sdist as _build_sdist
def _generate_version():
subprocess.check_call(["python", "scripts/write_version.py"])
def build_wheel(*args, **kwargs):
_generate_version()
return _build_wheel(*args, **kwargs)
def build_sdist(*args, **kwargs):
_generate_version()
return _build_sdist(*args, **kwargs)

View File

@ -1,7 +0,0 @@
"""Builtins and native functions for SLS."""
def load_builtins():
"""Return a dict of builtin names to callables."""
return {
"print": print,
}

View File

@ -1,29 +0,0 @@
"""Command-line entry point for SLS Python."""
import click
from . import __version__
@click.group()
@click.version_option(__version__)
def main():
"""sls: Small Lisp-like language (Python reimplementation)."""
pass
@main.command()
def repl():
"""Start a minimal REPL."""
from .repl import repl_loop
repl_loop()
@main.command()
@click.argument("file", required=False)
def run(file):
"""Run an SLS source file (placeholder)."""
if not file:
click.echo("No file provided; start with `sls repl`")
return
click.echo(f"Would run: {file} (not implemented yet)")

View File

@ -1,12 +1,248 @@
"""Interpreter module placeholder for SLS.""" # sls/interpreter.py
# Kyler Olsen
# YREA SLS
# Interpreter (Python OOP port)
# November 2025
from typing import Any 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,
)
# Optional: import builtins loader if you have one
# from .builtin import load_builtins # (expected: Callable[[InterpreterState], bool])
class Interpreter: class StackType(Enum):
def __init__(self): IDENTIFIER = auto()
self.env = {} 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()
def eval(self, ast: Any):
"""Evaluate the AST and return a result. Not implemented yet.""" @dataclass
return None 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, load_builtins: Optional[Callable[["InterpreterState"], bool]] = None):
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)

View File

@ -1,25 +1,232 @@
"""Lexer module placeholder for SLS. from __future__ import annotations
from dataclasses import dataclass, field
Provide a Lexer class that will later be expanded to match the C implementation. from enum import Enum, auto
""" from typing import List, Optional, Any
from dataclasses import dataclass
from typing import Iterator, NamedTuple
class Token(NamedTuple): # =====================================================================
type: str # Basic Types
value: str # =====================================================================
lineno: int
col: int 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 @dataclass
class Lexer: class IntegerLiteral:
source: str value: int # Python int is arbitrary precision
type: IntegerBuiltInType
def tokenize(self) -> Iterator[Token]:
"""Yield tokens from `source`. Currently yields a single EOF token. # =====================================================================
Replace with full logic when porting the C lexer. # TokenString, TypeTuple, StructInline
""" # =====================================================================
yield Token("EOF", "", 1, 0)
@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 []

43
SLS_Python/sls/meta.py Normal file
View File

@ -0,0 +1,43 @@
# Kyler Olsen
# YREA SLS
# Meta File (Python port)
# November 2025
import sys
from datetime import datetime, timezone
try:
from ._version import commit, timestamp # type: ignore
except ImportError:
try:
import subprocess
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 = f"{result_hash} {result_date}"
timestamp = datetime.now(timezone.utc).isoformat() + "Z"
except Exception:
commit = "unknown"
timestamp = "unknown"
# Equivalent of SLS_NAME and SLS_VER
SLS_NAME = "SLS_PYTHON"
SLS_VER = "a.0.0"
# 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}) {SLS_VER} ({commit})")
print(f"Running on {INTERPRETER_NAME} {INTERPRETER_VER} at {timestamp}")

View File

@ -1,25 +1,82 @@
"""Simple REPL for SLS skeleton.""" from .meta import print_version
from .lexer import Lexer from .lexer import Lexer
from .parser import Parser from .interpreter import InterpreterState
from .interpreter import Interpreter from .errors import SlsError
def repl_loop(): REPL_FILE_NAME = "<STDIN>"
interp = Interpreter()
print("sls-python REPL (very minimal). Type 'quit' or Ctrl-D to exit.")
try: def print_top_of_stack(interpreter: InterpreterState) -> None:
while True: """Pretty-print top of the stack."""
src = input("sls> ")
if not src or src.strip() in ("quit", "exit"): 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 = Lexer(REPL_FILE_NAME, line)
try:
tokens = lexer.lexical_analysis()
except SlsError as err:
print(err.message)
continue
# Execute tokens in order
for token in tokens:
try:
ok = interpreter.execute(token)
if not ok:
print("A runtime error occurred!")
break
except SlsError as err:
print(err.message)
break break
# minimal echo behaviour for now
lexer = Lexer(src) print_top_of_stack(interpreter)
tokens = list(lexer.tokenize())
ast = Parser(tokens).parse() return 1
result = interp.eval(ast)
if result is not None:
print(result)
except (EOFError, KeyboardInterrupt):
print()
print("Bye")