We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.
Lingua Indeterminatum/Implementation Draft
Jump to navigation
Jump to search
← Back to Lingua Indeterminatum
- This is still a work in progress. It may be changed in the future.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
A Tree-Walking Interpreter Framework for the Custom Language.
EBNF-compliant core architecture.
"""
from __future__ import annotations
import re
import math
import cmath
from enum import Enum, auto
from dataclasses import dataclass, field
from typing import Any, Optional, List, Dict, Callable
# ============================================================
# 1. LEXER
# ============================================================
class TokenType(Enum):
# Literals
INTEGER = auto()
FLOAT = auto()
IMAGINARY = auto()
STRING = auto()
RUNE = auto()
BOOL = auto()
TYPE = auto()
# Identifiers
IDENTIFIER = auto()
# Keywords
VAR = auto(); CONST = auto(); FUNC = auto(); CLASS = auto()
EXTENDS = auto(); NEW = auto(); SELF = auto()
IF = auto(); ELIF = auto(); ELSE = auto()
WHILE = auto(); FOR = auto(); IN = auto(); FOREVER = auto()
SKIP = auto(); STOP = auto(); HALT = auto(); RETURN = auto()
IMPORT = auto(); AS = auto(); PRINT = auto(); PASS = auto()
PUBLIC = auto(); PROTECTED = auto(); PRIVATE = auto()
# Operators
ARROW = auto() # <-
EQ = auto() # =
NE = auto() # !=
LT = auto() # <
LE = auto() # <=
GT = auto() # >
GE = auto() # >=
OR = auto() # or
XOR = auto() # xor
AND = auto() # and
BOR = auto() # |
BXOR = auto() # `
BAND = auto() # &
LSHIFT = auto() # <<
RSHIFT = auto() # >>
PLUS = auto() # +
MINUS = auto() # -
MUL = auto() # *
DIV = auto() # /
IDIV = auto() # //
MOD = auto() # %
POW = auto() # ^
NOT = auto() # not
BNOT = auto() # ~
# Delimiters
LPAREN = auto(); RPAREN = auto()
LBRACKET = auto(); RBRACKET = auto()
LBRACE = auto(); RBRACE = auto()
COMMA = auto(); DOT = auto(); SEMI = auto()
COLON = auto()
EOF = auto()
KEYWORDS = {
'var': TokenType.VAR, 'const': TokenType.CONST,
'func': TokenType.FUNC, 'class': TokenType.CLASS,
'extends': TokenType.EXTENDS, 'new': TokenType.NEW,
'self': TokenType.SELF, 'true': TokenType.BOOL, 'false': TokenType.BOOL,
'if': TokenType.IF, 'elif': TokenType.ELIF, 'else': TokenType.ELSE,
'while': TokenType.WHILE, 'for': TokenType.FOR, 'in': TokenType.IN,
'forever': TokenType.FOREVER, 'skip': TokenType.SKIP, 'stop': TokenType.STOP,
'halt': TokenType.HALT, 'return': TokenType.RETURN,
'import': TokenType.IMPORT, 'as': TokenType.AS, 'print': TokenType.PRINT,
'pass': TokenType.PASS, 'public': TokenType.PUBLIC,
'protected': TokenType.PROTECTED, 'private': TokenType.PRIVATE,
'or': TokenType.OR, 'xor': TokenType.XOR, 'and': TokenType.AND,
'not': TokenType.NOT,
# Types as values
'Int': TokenType.TYPE, 'Float': TokenType.TYPE, 'Doc': TokenType.TYPE,
'Rune': TokenType.TYPE, 'Bool': TokenType.TYPE, 'List': TokenType.TYPE,
'Pair': TokenType.TYPE, 'Complex': TokenType.TYPE,
}
@dataclass
class Token:
type: TokenType
value: Any
line: int
col: int
class LexerError(Exception):
pass
class Lexer:
def __init__(self, source: str):
self.source = source
self.pos = 0
self.line = 1
self.col = 1
self.tokens: List[Token] = []
def error(self, msg: str):
raise LexerError(f"[Line {self.line}, Col {self.col}] {msg}")
def peek(self, offset: int = 0) -> str:
p = self.pos + offset
return self.source[p] if p < len(self.source) else '\0'
def advance(self) -> str:
ch = self.peek()
self.pos += 1
if ch == '\n':
self.line += 1
self.col = 1
else:
self.col += 1
return ch
def match(self, expected: str) -> bool:
if self.peek() == expected:
self.advance()
return True
return False
def skip_whitespace(self):
while self.peek() in ' \t\n\r':
self.advance()
def read_string(self) -> str:
# Handles "..." with interpolation {expr} and escapes
parts = []
self.advance() # consume opening "
while self.peek() != '"' and self.peek() != '\0':
if self.peek() == '\\':
self.advance()
ch = self.advance()
escapes = {'n': '\n', 't': '\t', 'r': '\r', '\\': '\\', "'": "'", '"': '"', '0': '\0'}
if ch in escapes:
parts.append(escapes[ch])
elif ch == 'x':
hex_val = self.source[self.pos:self.pos+4]
self.pos += 4; self.col += 4
parts.append(chr(int(hex_val, 16)))
elif ch.isdigit():
# \dddddd (5 digits)
digits = ch + self.source[self.pos:self.pos+4]
self.pos += 4; self.col += 4
parts.append(chr(int(digits)))
else:
parts.append(ch)
elif self.peek() == '{':
self.advance()
# Interpolation: collect until }
expr_chars = []
depth = 1
while depth > 0 and self.peek() not in ('\0',):
if self.peek() == '{':
depth += 1
elif self.peek() == '}':
depth -= 1
if depth == 0:
self.advance()
break
expr_chars.append(self.advance())
parts.append(('interp', ''.join(expr_chars)))
else:
parts.append(self.advance())
if self.peek() != '"':
self.error("Unterminated string literal")
self.advance() # consume closing "
return parts
def read_comment(self):
# [[!? ... ?]]
if self.source[self.pos:self.pos+4] != '[[!?':
return False
self.pos += 4; self.col += 4
while True:
if self.peek() == '?' and self.peek(1) == ']' and self.peek(2) == ']':
self.pos += 3; self.col += 3
return True
if self.peek() == '\0':
self.error("Unterminated comment")
self.advance()
def number(self) -> Token:
start = self.pos
line, col = self.line, self.col
while self.peek().isdigit():
self.advance()
is_float = False
if self.peek() == '.':
is_float = True
self.advance()
while self.peek().isdigit():
self.advance()
if self.peek() in 'eE':
self.advance()
if self.peek() in '+-':
self.advance()
while self.peek().isdigit():
self.advance()
num_str = self.source[start:self.pos]
if self.peek() == 'i':
self.advance()
val = float(num_str) if is_float else int(num_str)
return Token(TokenType.IMAGINARY, val, line, col)
if is_float:
return Token(TokenType.FLOAT, float(num_str), line, col)
return Token(TokenType.INTEGER, int(num_str), line, col)
def identifier(self) -> Token:
start = self.pos
line, col = self.line, self.col
while self.peek().isalnum() or self.peek() == '_':
self.advance()
name = self.source[start:self.pos]
tt = KEYWORDS.get(name, TokenType.IDENTIFIER)
if tt == TokenType.BOOL:
return Token(tt, name == 'true', line, col)
return Token(tt, name, line, col)
def tokenize(self) -> List[Token]:
while self.pos < len(self.source):
self.skip_whitespace()
if self.pos >= len(self.source):
break
ch = self.peek()
line, col = self.line, self.col
# Comments
if ch == '[' and self.peek(1) == '[' and self.peek(2) == '!' and self.peek(3) == '?':
self.read_comment()
continue
# String
if ch == '"':
raw = self.read_string()
self.tokens.append(Token(TokenType.STRING, raw, line, col))
continue
# Rune @
if ch == '@':
self.advance()
if self.peek() == '\\':
self.advance()
esc = self.advance()
mapping = {'n': '\n', 't': '\t', 'r': '\r', '0': '\0'}
val = mapping.get(esc, esc)
else:
val = self.advance()
self.tokens.append(Token(TokenType.RUNE, val, line, col))
continue
# Number
if ch.isdigit():
self.tokens.append(self.number())
continue
# Identifier
if ch.isalpha() or ch == '_':
self.tokens.append(self.identifier())
continue
# Two-char operators
two = ch + self.peek(1)
two_map = {
'<-': TokenType.ARROW, '!=': TokenType.NE, '<=': TokenType.LE,
'>=': TokenType.GE, '<<': TokenType.LSHIFT, '>>': TokenType.RSHIFT,
'//': TokenType.IDIV,
}
if two in two_map:
self.advance(); self.advance()
self.tokens.append(Token(two_map[two], two, line, col))
continue
# Single-char operators and delimiters
self.advance()
single_map = {
'=': TokenType.EQ, '<': TokenType.LT, '>': TokenType.GT,
'+': TokenType.PLUS, '-': TokenType.MINUS, '*': TokenType.MUL,
'/': TokenType.DIV, '%': TokenType.MOD, '^': TokenType.POW,
'|': TokenType.BOR, '&': TokenType.BAND, '~': TokenType.BNOT,
'`': TokenType.BXOR,
'(': TokenType.LPAREN, ')': TokenType.RPAREN,
'[': TokenType.LBRACKET, ']': TokenType.RBRACKET,
'{': TokenType.LBRACE, '}': TokenType.RBRACE,
',': TokenType.COMMA, '.': TokenType.DOT, ';': TokenType.SEMI,
':': TokenType.COLON,
}
if ch in single_map:
self.tokens.append(Token(single_map[ch], ch, line, col))
else:
self.error(f"Unexpected character: {ch}")
self.tokens.append(Token(TokenType.EOF, None, self.line, self.col))
return self.tokens
# ============================================================
# 2. AST NODES
# ============================================================
class ASTNode:
pass
# --- Expressions ---
@dataclass
class Literal(ASTNode):
value: Any
literal_type: str # 'int', 'float', 'string', 'rune', 'bool', 'complex', 'list', 'pair', 'type'
@dataclass
class StringInterpolation(ASTNode):
parts: List[Any] # Mix of strings and AST expressions
@dataclass
class Identifier(ASTNode):
name: str
@dataclass
class BinaryOp(ASTNode):
op: str
left: ASTNode
right: ASTNode
@dataclass
class UnaryOp(ASTNode):
op: str
operand: ASTNode
@dataclass
class Call(ASTNode):
callee: ASTNode
args: List[ASTNode]
@dataclass
class MemberAccess(ASTNode):
obj: ASTNode
member: str
is_call: bool = False
args: List[ASTNode] = field(default_factory=list)
@dataclass
class InputExpr(ASTNode):
prompt: ASTNode
@dataclass
class SelfExpr(ASTNode):
pass
@dataclass
class ListLiteral(ASTNode):
elements: List[ASTNode]
@dataclass
class PairLiteral(ASTNode):
first: ASTNode
second: ASTNode
# --- Statements ---
@dataclass
class Block(ASTNode):
statements: List[ASTNode]
@dataclass
class ExpressionStmt(ASTNode):
expr: ASTNode
@dataclass
class Assignment(ASTNode):
name: str
value: ASTNode
@dataclass
class Declaration(ASTNode):
visibility: Optional[str]
kind: str # 'var' or 'const'
name: str
type_hint: Optional[str]
value: Optional[ASTNode]
@dataclass
class IfStmt(ASTNode):
condition: ASTNode
then_branch: Block
elif_branches: List[tuple] # [(cond, block)]
else_branch: Optional[Block]
@dataclass
class WhileStmt(ASTNode):
condition: ASTNode
body: Block
@dataclass
class ForStmt(ASTNode):
var_name: str
iterable: ASTNode
body: Block
@dataclass
class ForeverStmt(ASTNode):
body: Block
@dataclass
class SkipStmt(ASTNode):
pass
@dataclass
class StopStmt(ASTNode):
pass
@dataclass
class HaltStmt(ASTNode):
pass
@dataclass
class ReturnStmt(ASTNode):
value: Optional[ASTNode]
@dataclass
class PrintStmt(ASTNode):
value: Optional[ASTNode]
@dataclass
class ImportStmt(ASTNode):
path: List[str]
alias: Optional[str]
@dataclass
class PassStmt(ASTNode):
pass
# --- Definitions ---
@dataclass
class Parameter(ASTNode):
name: str
type_hint: Optional[str]
@dataclass
class FunctionDef(ASTNode):
visibility: Optional[str]
name: str
params: List[Parameter]
return_type: Optional[str]
body: Block
@dataclass
class ConstructorDef(ASTNode):
params: List[Parameter]
return_type: Optional[str]
body: Block
@dataclass
class ClassDef(ASTNode):
visibility: Optional[str]
name: str
parent: Optional[str]
members: List[Any] # Declaration | FunctionDef | ConstructorDef
# ============================================================
# 3. PARSER (Recursive Descent)
# ============================================================
class ParseError(Exception):
pass
class Parser:
def __init__(self, tokens: List[Token]):
self.tokens = tokens
self.pos = 0
def error(self, msg: str):
tok = self.peek()
raise ParseError(f"[Line {tok.line}] {msg}, got {tok.type.name}")
def peek(self, offset: int = 0) -> Token:
p = self.pos + offset
return self.tokens[p] if p < len(self.tokens) else self.tokens[-1]
def advance(self) -> Token:
tok = self.peek()
if self.peek().type != TokenType.EOF:
self.pos += 1
return tok
def match(self, *types: TokenType) -> bool:
return self.peek().type in types
def consume(self, ttype: TokenType, msg: str) -> Token:
if self.peek().type == ttype:
return self.advance()
self.error(msg)
# -----------------------
# Entry Point
# -----------------------
def parse(self) -> List[ASTNode]:
nodes = []
while not self.match(TokenType.EOF):
nodes.append(self.top_level())
if self.match(TokenType.SEMI):
self.advance()
return nodes
def top_level(self) -> ASTNode:
vis = self.parse_visibility()
if self.match(TokenType.VAR, TokenType.CONST):
return self.declaration(vis)
if self.match(TokenType.FUNC):
return self.function_def(vis)
if self.match(TokenType.CLASS):
return self.class_def(vis)
return self.statement()
def parse_visibility(self) -> Optional[str]:
if self.match(TokenType.PUBLIC, TokenType.PROTECTED, TokenType.PRIVATE):
return self.advance().value
return None
# -----------------------
# Definitions
# -----------------------
def declaration(self, vis: Optional[str] = None) -> Declaration:
kind = self.advance().value # var or const
name = self.consume(TokenType.IDENTIFIER, "Expected identifier").value
type_hint = None
if self.match(TokenType.COLON):
self.advance()
type_hint = self.consume(TokenType.TYPE, "Expected type").value
value = None
if self.match(TokenType.ARROW):
self.advance()
value = self.expression()
return Declaration(vis, kind, name, type_hint, value)
def function_def(self, vis: Optional[str] = None) -> FunctionDef:
self.consume(TokenType.FUNC, "Expected 'func'")
name = self.consume(TokenType.IDENTIFIER, "Expected function name").value
self.consume(TokenType.LPAREN, "Expected '('")
params = self.param_list()
self.consume(TokenType.RPAREN, "Expected ')'")
ret_type = None
if self.match(TokenType.ARROW): # -> type (return type, not assignment)
self.advance()
ret_type = self.consume(TokenType.TYPE, "Expected return type").value
body = self.block()
return FunctionDef(vis, name, params, ret_type, body)
def param_list(self) -> List[Parameter]:
params = []
if not self.match(TokenType.RPAREN):
params.append(self.parameter())
while self.match(TokenType.COMMA):
self.advance()
params.append(self.parameter())
return params
def parameter(self) -> Parameter:
name = self.consume(TokenType.IDENTIFIER, "Expected parameter name").value
type_hint = None
if self.match(TokenType.COLON):
self.advance()
type_hint = self.consume(TokenType.TYPE, "Expected type").value
return Parameter(name, type_hint)
def class_def(self, vis: Optional[str] = None) -> ClassDef:
self.consume(TokenType.CLASS, "Expected 'class'")
name = self.consume(TokenType.IDENTIFIER, "Expected class name").value
parent = None
if self.match(TokenType.EXTENDS):
self.advance()
parent = self.consume(TokenType.IDENTIFIER, "Expected parent class").value
self.consume(TokenType.LBRACE, "Expected '{'")
members = []
while not self.match(TokenType.RBRACE):
mvis = self.parse_visibility()
if self.match(TokenType.VAR, TokenType.CONST):
members.append(self.declaration(mvis))
elif self.match(TokenType.FUNC):
self.advance()
if self.match(TokenType.NEW):
members.append(self.constructor())
else:
# Backtrack: we consumed FUNC, so parse rest as function
fname = self.consume(TokenType.IDENTIFIER, "Expected method name").value
self.consume(TokenType.LPAREN, "Expected '('")
params = self.param_list()
self.consume(TokenType.RPAREN, "Expected ')'")
ret = None
if self.match(TokenType.ARROW):
self.advance()
ret = self.consume(TokenType.TYPE, "Expected type").value
body = self.block()
members.append(FunctionDef(mvis, fname, params, ret, body))
else:
self.error("Expected class member")
if self.match(TokenType.SEMI):
self.advance()
self.consume(TokenType.RBRACE, "Expected '}'")
return ClassDef(vis, name, parent, members)
def constructor(self) -> ConstructorDef:
self.consume(TokenType.NEW, "Expected 'new'")
self.consume(TokenType.LPAREN, "Expected '('")
params = self.param_list()
self.consume(TokenType.RPAREN, "Expected ')'")
ret = None
if self.match(TokenType.ARROW):
self.advance()
ret = self.consume(TokenType.TYPE, "Expected type").value
body = self.block()
return ConstructorDef(params, ret, body)
# -----------------------
# Statements
# -----------------------
def statement(self) -> ASTNode:
if self.match(TokenType.LBRACE):
return self.block()
if self.match(TokenType.IF):
return self.if_stmt()
if self.match(TokenType.WHILE):
return self.while_stmt()
if self.match(TokenType.FOR):
return self.for_stmt()
if self.match(TokenType.FOREVER):
return self.forever_stmt()
if self.match(TokenType.SKIP):
self.advance(); return SkipStmt()
if self.match(TokenType.STOP):
self.advance(); return StopStmt()
if self.match(TokenType.HALT):
self.advance(); return HaltStmt()
if self.match(TokenType.RETURN):
return self.return_stmt()
if self.match(TokenType.PRINT):
return self.print_stmt()
if self.match(TokenType.IMPORT):
return self.import_stmt()
if self.match(TokenType.PASS):
self.advance(); return PassStmt()
if self.match(TokenType.IDENTIFIER) and self.peek(1).type == TokenType.ARROW:
return self.assignment()
# Expression statement
expr = self.expression()
return ExpressionStmt(expr)
def block(self) -> Block:
self.consume(TokenType.LBRACE, "Expected '{'")
stmts = []
while not self.match(TokenType.RBRACE, TokenType.EOF):
stmts.append(self.statement())
if self.match(TokenType.SEMI):
self.advance()
self.consume(TokenType.RBRACE, "Expected '}'")
return Block(stmts)
def assignment(self) -> Assignment:
name = self.advance().value
self.consume(TokenType.ARROW, "Expected '<-'")
value = self.expression()
return Assignment(name, value)
def if_stmt(self) -> IfStmt:
self.consume(TokenType.IF, "Expected 'if'")
self.consume(TokenType.LPAREN, "Expected '('")
cond = self.expression()
self.consume(TokenType.RPAREN, "Expected ')'")
then_b = self.block()
elifs = []
while self.match(TokenType.ELIF):
self.advance()
self.consume(TokenType.LPAREN, "Expected '('")
c = self.expression()
self.consume(TokenType.RPAREN, "Expected ')'")
b = self.block()
elifs.append((c, b))
else_b = None
if self.match(TokenType.ELSE):
self.advance()
else_b = self.block()
return IfStmt(cond, then_b, elifs, else_b)
def while_stmt(self) -> WhileStmt:
self.consume(TokenType.WHILE, "Expected 'while'")
self.consume(TokenType.LPAREN, "Expected '('")
cond = self.expression()
self.consume(TokenType.RPAREN, "Expected ')'")
body = self.block()
return WhileStmt(cond, body)
def for_stmt(self) -> ForStmt:
self.consume(TokenType.FOR, "Expected 'for'")
name = self.consume(TokenType.IDENTIFIER, "Expected identifier").value
self.consume(TokenType.IN, "Expected 'in'")
iterable = self.expression()
body = self.block()
return ForStmt(name, iterable, body)
def forever_stmt(self) -> ForeverStmt:
self.consume(TokenType.FOREVER, "Expected 'forever'")
return ForeverStmt(self.block())
def return_stmt(self) -> ReturnStmt:
self.consume(TokenType.RETURN, "Expected 'return'")
val = None
if not self.match(TokenType.SEMI, TokenType.RBRACE, TokenType.EOF):
val = self.expression()
return ReturnStmt(val)
def print_stmt(self) -> PrintStmt:
self.consume(TokenType.PRINT, "Expected 'print'")
val = None
if self.match(TokenType.LPAREN):
self.advance()
val = self.expression()
self.consume(TokenType.RPAREN, "Expected ')'")
return PrintStmt(val)
def import_stmt(self) -> ImportStmt:
self.consume(TokenType.IMPORT, "Expected 'import'")
path = [self.consume(TokenType.IDENTIFIER, "Expected module").value]
while self.match(TokenType.DOT):
self.advance()
path.append(self.consume(TokenType.IDENTIFIER, "Expected module").value)
alias = None
if self.match(TokenType.AS):
self.advance()
alias = self.consume(TokenType.IDENTIFIER, "Expected alias").value
return ImportStmt(path, alias)
# -----------------------
# Expressions (Precedence Climbing)
# -----------------------
def expression(self) -> ASTNode:
return self.logical_or()
def logical_or(self) -> ASTNode:
node = self.logical_xor()
while self.match(TokenType.OR):
op = self.advance().value
node = BinaryOp(op, node, self.logical_xor())
return node
def logical_xor(self) -> ASTNode:
node = self.logical_and()
while self.match(TokenType.XOR):
op = self.advance().value
node = BinaryOp(op, node, self.logical_and())
return node
def logical_and(self) -> ASTNode:
node = self.comparison()
while self.match(TokenType.AND):
op = self.advance().value
node = BinaryOp(op, node, self.comparison())
return node
def comparison(self) -> ASTNode:
node = self.bitwise_or()
while self.match(TokenType.EQ, TokenType.NE, TokenType.LT, TokenType.LE, TokenType.GT, TokenType.GE):
op = self.advance().value
node = BinaryOp(op, node, self.bitwise_or())
return node
def bitwise_or(self) -> ASTNode:
node = self.bitwise_xor()
while self.match(TokenType.BOR):
op = self.advance().value
node = BinaryOp(op, node, self.bitwise_xor())
return node
def bitwise_xor(self) -> ASTNode:
node = self.bitwise_and()
while self.match(TokenType.BXOR):
op = self.advance().value
node = BinaryOp(op, node, self.bitwise_and())
return node
def bitwise_and(self) -> ASTNode:
node = self.shift()
while self.match(TokenType.BAND):
op = self.advance().value
node = BinaryOp(op, node, self.shift())
return node
def shift(self) -> ASTNode:
node = self.additive()
while self.match(TokenType.LSHIFT, TokenType.RSHIFT):
op = self.advance().value
node = BinaryOp(op, node, self.additive())
return node
def additive(self) -> ASTNode:
node = self.multiplicative()
while self.match(TokenType.PLUS, TokenType.MINUS):
op = self.advance().value
node = BinaryOp(op, node, self.multiplicative())
return node
def multiplicative(self) -> ASTNode:
node = self.exponent()
while self.match(TokenType.MUL, TokenType.DIV, TokenType.IDIV, TokenType.MOD):
op = self.advance().value
node = BinaryOp(op, node, self.exponent())
return node
def exponent(self) -> ASTNode:
# Right-associative
node = self.unary()
if self.match(TokenType.POW):
op = self.advance().value
right = self.exponent() # recurse for right-assoc
return BinaryOp(op, node, right)
return node
def unary(self) -> ASTNode:
if self.match(TokenType.PLUS, TokenType.MINUS, TokenType.BNOT, TokenType.NOT):
op = self.advance().value
return UnaryOp(op, self.unary())
return self.primary()
def primary(self) -> ASTNode:
if self.match(TokenType.INTEGER):
return Literal(self.advance().value, 'int')
if self.match(TokenType.FLOAT):
return Literal(self.advance().value, 'float')
if self.match(TokenType.IMAGINARY):
return Literal(self.advance().value, 'imaginary')
if self.match(TokenType.BOOL):
return Literal(self.advance().value, 'bool')
if self.match(TokenType.TYPE):
return Literal(self.advance().value, 'type')
if self.match(TokenType.STRING):
parts = self.advance().value
# Convert interpolation parts
result_parts = []
for p in parts:
if isinstance(p, tuple) and p[0] == 'interp':
# Parse the expression inside interpolation
sub_lexer = Lexer(p[1])
sub_tokens = sub_lexer.tokenize()
sub_parser = Parser(sub_tokens[:-1]) # exclude EOF
result_parts.append(sub_parser.expression())
else:
result_parts.append(p)
if len(result_parts) == 1 and isinstance(result_parts[0], str):
return Literal(result_parts[0], 'string')
return StringInterpolation(result_parts)
if self.match(TokenType.RUNE):
return Literal(self.advance().value, 'rune')
if self.match(TokenType.IDENTIFIER):
node = Identifier(self.advance().value)
return self.finish_primary(node)
if self.match(TokenType.SELF):
self.advance()
node = SelfExpr()
return self.finish_primary(node)
if self.match(TokenType.LPAREN):
self.advance()
expr = self.expression()
self.consume(TokenType.RPAREN, "Expected ')'")
# Check if it's a pair (expr, expr)
if self.match(TokenType.COMMA):
self.advance()
second = self.expression()
self.consume(TokenType.RPAREN, "Expected ')'")
return PairLiteral(expr, second)
return expr # Grouping
if self.match(TokenType.INPUT):
self.advance()
self.consume(TokenType.LPAREN, "Expected '('")
prompt = self.expression()
self.consume(TokenType.RPAREN, "Expected ')'")
return InputExpr(prompt)
if self.match(TokenType.LBRACKET):
return self.list_literal()
self.error("Unexpected token in expression")
def finish_primary(self, node: ASTNode) -> ASTNode:
while True:
if self.match(TokenType.LPAREN):
self.advance()
args = []
if not self.match(TokenType.RPAREN):
args.append(self.expression())
while self.match(TokenType.COMMA):
self.advance()
args.append(self.expression())
self.consume(TokenType.RPAREN, "Expected ')'")
node = Call(node, args)
elif self.match(TokenType.DOT):
self.advance()
member = self.consume(TokenType.IDENTIFIER, "Expected member name").value
if self.match(TokenType.LPAREN):
self.advance()
args = []
if not self.match(TokenType.RPAREN):
args.append(self.expression())
while self.match(TokenType.COMMA):
self.advance()
args.append(self.expression())
self.consume(TokenType.RPAREN, "Expected ')'")
node = MemberAccess(node, member, True, args)
else:
node = MemberAccess(node, member, False)
else:
break
return node
def list_literal(self) -> ListLiteral:
self.consume(TokenType.LBRACKET, "Expected '['")
elems = []
if not self.match(TokenType.RBRACKET):
elems.append(self.expression())
while self.match(TokenType.COMMA):
self.advance()
elems.append(self.expression())
self.consume(TokenType.RBRACKET, "Expected ']'")
return ListLiteral(elems)
# ============================================================
# 4. RUNTIME VALUES & ENVIRONMENT
# ============================================================
class ReturnException(Exception):
def __init__(self, value: Any):
self.value = value
class BreakException(Exception):
pass
class ContinueException(Exception):
pass
class HaltException(Exception):
pass
class Instance:
def __init__(self, klass: 'Class'):
self.klass = klass
self.fields: Dict[str, Any] = {}
self.private: Dict[str, Any] = {}
self.protected: Dict[str, Any] = {}
def get(self, name: str) -> Any:
if name in self.fields:
return self.fields[name]
method = self.klass.find_method(name)
if method:
return method.bind(self)
raise RuntimeError(f"Undefined property '{name}'")
def set(self, name: str, value: Any):
self.fields[name] = value
class Class:
def __init__(self, name: str, parent: Optional['Class'], methods: Dict[str, 'Function']):
self.name = name
self.parent = parent
self.methods = methods
def find_method(self, name: str) -> Optional['Function']:
if name in self.methods:
return self.methods[name]
if self.parent:
return self.parent.find_method(name)
return None
class Function:
def __init__(self, name: str, params: List[str], body: Block, closure: Environment):
self.name = name
self.params = params
self.body = body
self.closure = closure
def bind(self, instance: Instance) -> 'Function':
env = Environment(self.closure)
env.define('self', instance)
return Function(self.name, self.params, self.body, env)
def __repr__(self):
return f"<func {self.name}>"
class Environment:
def __init__(self, parent: Optional['Environment'] = None):
self.vars: Dict[str, Any] = {}
self.consts: set = set()
self.parent = parent
def define(self, name: str, value: Any, is_const: bool = False):
self.vars[name] = value
if is_const:
self.consts.add(name)
def get(self, name: str) -> Any:
if name in self.vars:
return self.vars[name]
if self.parent:
return self.parent.get(name)
raise RuntimeError(f"Undefined variable '{name}'")
def set(self, name: str, value: Any):
if name in self.vars:
if name in self.consts:
raise RuntimeError(f"Cannot assign to constant '{name}'")
self.vars[name] = value
return
if self.parent:
self.parent.set(name, value)
return
raise RuntimeError(f"Undefined variable '{name}'")
# ============================================================
# 5. INTERPRETER
# ============================================================
class Interpreter:
def __init__(self):
self.globals = Environment()
self.env = self.globals
self.output: List[str] = []
self._setup_builtins()
def _setup_builtins(self):
self.globals.define('print', self._builtin_print)
self.globals.define('input', self._builtin_input)
# Type constructors
for t in ['Int', 'Float', 'Doc', 'Rune', 'Bool', 'List', 'Pair', 'Complex']:
self.globals.define(t, t)
def _builtin_print(self, *args):
val = args[0] if args else None
text = self._stringify(val)
self.output.append(text)
print(text)
return None
def _builtin_input(self, prompt: Any) -> str:
# In real implementation, use input()
return f"input({self._stringify(prompt)})"
def _stringify(self, obj: Any) -> str:
if obj is None:
return "nope"
if isinstance(obj, bool):
return "true" if obj else "false"
if isinstance(obj, complex):
return f"{obj.real}+{obj.imag}i"
if isinstance(obj, list):
return "[" + ", ".join(self._stringify(x) for x in obj) + "]"
if isinstance(obj, tuple) and len(obj) == 2:
return f"({self._stringify(obj[0])}, {self._stringify(obj[1])})"
if isinstance(obj, Instance):
return f"<instance of {obj.klass.name}>"
if isinstance(obj, Function):
return f"<func {obj.name}>"
if isinstance(obj, Class):
return f"<class {obj.name}>"
return str(obj)
# -----------------------
# Evaluation
# -----------------------
def evaluate(self, node: ASTNode) -> Any:
method_name = f'eval_{type(node).__name__}'
method = getattr(self, method_name, self._eval_unsupported)
return method(node)
def _eval_unsupported(self, node: ASTNode):
raise RuntimeError(f"Unsupported node: {type(node).__name__}")
def eval_Literal(self, node: Literal) -> Any:
if node.literal_type == 'imaginary':
return complex(0, node.value)
return node.value
def eval_StringInterpolation(self, node: StringInterpolation) -> str:
result = []
for part in node.parts:
if isinstance(part, str):
result.append(part)
else:
result.append(self._stringify(self.evaluate(part)))
return ''.join(result)
def eval_Identifier(self, node: Identifier) -> Any:
return self.env.get(node.name)
def eval_SelfExpr(self, node: SelfExpr) -> Any:
return self.env.get('self')
def eval_BinaryOp(self, node: BinaryOp) -> Any:
# Short-circuit for logical ops
if node.op == 'and':
left = self.evaluate(node.left)
if not self._is_truthy(left):
return left
return self.evaluate(node.right)
if node.op == 'or':
left = self.evaluate(node.left)
if self._is_truthy(left):
return left
return self.evaluate(node.right)
if node.op == 'xor':
left = self._is_truthy(self.evaluate(node.left))
right = self._is_truthy(self.evaluate(node.right))
return left != right
left = self.evaluate(node.left)
right = self.evaluate(node.right)
# Complex number construction: real + imaginary
if isinstance(left, (int, float)) and isinstance(right, complex) and node.op == '+':
return complex(left, right.imag)
# Arithmetic
if node.op == '+':
if isinstance(left, str) or isinstance(right, str):
return self._stringify(left) + self._stringify(right)
return left + right
if node.op == '-': return left - right
if node.op == '*': return left * right
if node.op == '/': return left / right
if node.op == '//': return left // right
if node.op == '%': return left % right
if node.op == '^': return left ** right
# Comparison
if node.op == '=': return left == right
if node.op == '!=': return left != right
if node.op == '<': return left < right
if node.op == '<=': return left <= right
if node.op == '>': return left > right
if node.op == '>=': return left >= right
# Bitwise
if node.op == '|': return left | right
if node.op == '`': return left ^ right
if node.op == '&': return left & right
if node.op == '<<': return left << right
if node.op == '>>': return left >> right
raise RuntimeError(f"Unknown binary operator: {node.op}")
def eval_UnaryOp(self, node: UnaryOp) -> Any:
operand = self.evaluate(node.operand)
if node.op == '-': return -operand
if node.op == '+': return +operand
if node.op == '~': return ~operand
if node.op == 'not': return not self._is_truthy(operand)
raise RuntimeError(f"Unknown unary operator: {node.op}")
def eval_Call(self, node: Call) -> Any:
callee = self.evaluate(node.callee)
args = [self.evaluate(a) for a in node.args]
if callable(callee) and not isinstance(callee, Function):
return callee(*args)
if isinstance(callee, Function):
if len(args) != len(callee.params):
raise RuntimeError(f"Expected {len(callee.params)} args, got {len(args)}")
env = Environment(callee.closure)
for p, a in zip(callee.params, args):
env.define(p, a)
try:
self.execute_block(callee.body, env)
except ReturnException as ret:
return ret.value
return None
if isinstance(callee, Class):
instance = Instance(callee)
constructor = callee.find_method('new')
if constructor:
bound = constructor.bind(instance)
# Call constructor
env = Environment(bound.closure)
for p, a in zip(bound.params, args):
env.define(p, a)
try:
self.execute_block(bound.body, env)
except ReturnException:
pass
return instance
raise RuntimeError(f"Cannot call {callee}")
def eval_MemberAccess(self, node: MemberAccess) -> Any:
obj = self.evaluate(node.obj)
if isinstance(obj, Instance):
if node.is_call:
method = obj.get(node.member)
args = [self.evaluate(a) for a in node.args]
if isinstance(method, Function):
env = Environment(method.closure)
for p, a in zip(method.params, args):
env.define(p, a)
try:
self.execute_block(method.body, env)
except ReturnException as ret:
return ret.value
return None
raise RuntimeError(f"Property '{node.member}' is not callable")
return obj.get(node.member)
if isinstance(obj, dict):
return obj.get(node.member)
raise RuntimeError(f"Cannot access member of {type(obj)}")
def eval_InputExpr(self, node: InputExpr) -> str:
prompt = self.evaluate(node.prompt)
return self._builtin_input(prompt)
def eval_ListLiteral(self, node: ListLiteral) -> list:
return [self.evaluate(e) for e in node.elements]
def eval_PairLiteral(self, node: PairLiteral) -> tuple:
return (self.evaluate(node.first), self.evaluate(node.second))
# -----------------------
# Statements
# -----------------------
def execute(self, node: ASTNode) -> Any:
method_name = f'exec_{type(node).__name__}'
method = getattr(self, method_name, self._exec_unsupported)
return method(node)
def _exec_unsupported(self, node: ASTNode):
raise RuntimeError(f"Unsupported statement: {type(node).__name__}")
def exec_Block(self, node: Block):
for stmt in node.statements:
self.execute(stmt)
def exec_ExpressionStmt(self, node: ExpressionStmt):
self.evaluate(node.expr)
def exec_Assignment(self, node: Assignment):
val = self.evaluate(node.value)
self.env.set(node.name, val)
def exec_Declaration(self, node: Declaration):
val = None
if node.value:
val = self.evaluate(node.value)
self.env.define(node.name, val, is_const=(node.kind == 'const'))
def exec_IfStmt(self, node: IfStmt):
if self._is_truthy(self.evaluate(node.condition)):
self.execute(node.then_branch)
return
for cond, block in node.elif_branches:
if self._is_truthy(self.evaluate(cond)):
self.execute(block)
return
if node.else_branch:
self.execute(node.else_branch)
def exec_WhileStmt(self, node: WhileStmt):
while self._is_truthy(self.evaluate(node.condition)):
try:
self.execute(node.body)
except ContinueException:
continue
except BreakException:
break
def exec_ForStmt(self, node: ForStmt):
iterable = self.evaluate(node.iterable)
if isinstance(iterable, str):
iterable = list(iterable)
if not hasattr(iterable, '__iter__'):
raise RuntimeError("Cannot iterate over non-iterable")
for item in iterable:
self.env.define(node.var_name, item)
try:
self.execute(node.body)
except ContinueException:
continue
except BreakException:
break
def exec_ForeverStmt(self, node: ForeverStmt):
while True:
try:
self.execute(node.body)
except ContinueException:
continue
except BreakException:
break
def exec_SkipStmt(self, node: SkipStmt):
raise ContinueException()
def exec_StopStmt(self, node: StopStmt):
raise BreakException()
def exec_HaltStmt(self, node: HaltStmt):
raise HaltException()
def exec_ReturnStmt(self, node: ReturnStmt):
val = None
if node.value:
val = self.evaluate(node.value)
raise ReturnException(val)
def exec_PrintStmt(self, node: PrintStmt):
val = None
if node.value:
val = self.evaluate(node.value)
self._builtin_print(val)
def exec_ImportStmt(self, node: ImportStmt):
# Placeholder: real implementation would load modules
name = node.alias or node.path[-1]
self.env.define(name, f"<module {'/'.join(node.path)}>")
def exec_PassStmt(self, node: PassStmt):
pass
def exec_FunctionDef(self, node: FunctionDef):
params = [p.name for p in node.params]
func = Function(node.name, params, node.body, self.env)
self.env.define(node.name, func)
def exec_ClassDef(self, node: ClassDef):
parent_class = None
if node.parent:
parent_val = self.env.get(node.parent)
if isinstance(parent_val, Class):
parent_class = parent_val
else:
raise RuntimeError(f"'{node.parent}' is not a class")
methods = {}
# First pass: collect methods
for member in node.members:
if isinstance(member, FunctionDef):
params = [p.name for p in member.params]
methods[member.name] = Function(member.name, params, member.body, self.env)
elif isinstance(member, ConstructorDef):
params = [p.name for p in member.params]
methods['new'] = Function('new', params, member.body, self.env)
elif isinstance(member, Declaration):
# Class field defaults would be handled during instantiation
pass
klass = Class(node.name, parent_class, methods)
self.env.define(node.name, klass)
# -----------------------
# Helpers
# -----------------------
def _is_truthy(self, obj: Any) -> bool:
if obj is None:
return False
if isinstance(obj, bool):
return obj
if isinstance(obj, (int, float)):
return obj != 0
if isinstance(obj, complex):
return obj != 0
if isinstance(obj, (str, list, tuple, dict)):
return len(obj) > 0
return True
def execute_block(self, block: Block, env: Environment):
prev = self.env
self.env = env
try:
self.exec_Block(block)
finally:
self.env = prev
def run(self, nodes: List[ASTNode]):
try:
for node in nodes:
self.execute(node)
except HaltException:
pass
# ============================================================
# 6. DRIVER / REPL
# ============================================================
def run_source(source: str) -> Interpreter:
lexer = Lexer(source)
tokens = lexer.tokenize()
parser = Parser(tokens)
ast = parser.parse()
interpreter = Interpreter()
interpreter.run(ast)
return interpreter
# ============================================================
# 7. EXAMPLE USAGE
# ============================================================
if __name__ == "__main__":
source = r'''
[[! This is a comment ?]]
public var x: Int <- 10;
const PI <- 3.14;
func greet(name) {
print("Hello, {name}!");
return name
}
func factorial(n) {
if (n <= 1) {
return 1
};
return n * factorial(n - 1)
}
class Point {
private var x;
private var y;
func new(a, b) {
self.x <- a;
self.y <- b
}
func dist() {
return (self.x * self.x + self.y * self.y) ^ 0.5
}
}
print(factorial(5));
var p <- new Point(3, 4);
print(p.dist())
'''
try:
interp = run_source(source)
print("\n--- Output ---")
for line in interp.output:
print(line)
except (LexerError, ParseError, RuntimeError) as e:
print(f"Error: {e}")