249 lines
7.9 KiB
Python
249 lines
7.9 KiB
Python
# 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,
|
|
)
|
|
|
|
# Optional: import builtins loader if you have one
|
|
# from .builtin import load_builtins # (expected: Callable[[InterpreterState], bool])
|
|
|
|
|
|
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, 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)
|