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

From Esolang
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}")