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.
FlamePL/Python Interpreter
Jump to navigation
Jump to search
Due to the source code was too long, the author also posted it on PasteBin.
Do you really want to see the interpreter right here? Or can't you access PasteBin? No worries, just click 'Expand' on the right to see the code here.
############################################################################
# Python interpreter for FlamePL. #
# Current Version: 1.2. #
# Made by PrySigneToFry (Zweide-Altunik on Pastebin). #
# Document: https://esolangs.org/wiki/FlamePL #
############################################################################
import re
import sys
import os
import math as pymath
from decimal import Decimal, getcontext
getcontext().prec = 80
# ----------------------------------------------------------------------
# Data types
# ----------------------------------------------------------------------
class FlamePLType:
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
INT_TYPE = FlamePLType("int")
FLOAT_TYPE = FlamePLType("float")
COMPLEX_TYPE= FlamePLType("complex")
LIST_TYPE = FlamePLType("list")
PAIR_TYPE = FlamePLType("pair")
DICT_TYPE = FlamePLType("dict")
STR_TYPE = FlamePLType("str")
NULL_TYPE = FlamePLType("null")
FUNCTION_TYPE= FlamePLType("function")
CLASS_TYPE = FlamePLType("class")
BOOL_TYPE = FlamePLType("bool")
MODULE_TYPE = FlamePLType("module")
class FlamePLComplex:
def __init__(self, real, imag):
self.real = Decimal(real)
self.imag = Decimal(imag)
def __repr__(self):
if self.imag < 0:
return f"{self.real} - {abs(self.imag)}i"
return f"{self.real} + {self.imag}i"
def __add__(self, other):
if isinstance(other, FlamePLComplex):
return FlamePLComplex(self.real + other.real, self.imag + other.imag)
return FlamePLComplex(self.real + Decimal(other), self.imag)
def __sub__(self, other):
if isinstance(other, FlamePLComplex):
return FlamePLComplex(self.real - other.real, self.imag - other.imag)
return FlamePLComplex(self.real - Decimal(other), self.imag)
def __mul__(self, other):
if isinstance(other, FlamePLComplex):
r = self.real * other.real - self.imag * other.imag
i = self.real * other.imag + self.imag * other.real
return FlamePLComplex(r, i)
return FlamePLComplex(self.real * Decimal(other), self.imag * Decimal(other))
def __truediv__(self, other):
if isinstance(other, FlamePLComplex):
denom = other.real**2 + other.imag**2
r = (self.real * other.real + self.imag * other.imag) / denom
i = (self.imag * other.real - self.real * other.imag) / denom
return FlamePLComplex(r, i)
return FlamePLComplex(self.real / Decimal(other), self.imag / Decimal(other))
def __eq__(self, other):
if not isinstance(other, FlamePLComplex):
return False
return self.real == other.real and self.imag == other.imag
def __mod__(self, other):
if isinstance(other, FlamePLComplex):
return FlamePLComplex(self.real % other.real, self.imag % other.imag)
return FlamePLComplex(self.real % Decimal(other), self.imag % Decimal(other))
class FlamePLPair:
def __init__(self, first, second):
self.first = first
self.second = second
def __repr__(self):
return f"({self.first}, {self.second})"
class FlamePLModule:
def __init__(self, env, name):
self.env = env
self.name = name
def __getattr__(self, name):
if name == "env":
return self.env
try:
return self.env.get(name)
except NameError:
raise AttributeError(f"module {self.name} has no attribute {name}")
def __repr__(self):
return f"<module {self.name}>"
# ----------------------------------------------------------------------
# Environment
# ----------------------------------------------------------------------
class Environment:
def __init__(self, outer=None):
self.outer = outer
self.data = {}
def get(self, name):
if name in self.data:
return self.data[name]
if self.outer:
return self.outer.get(name)
raise NameError(f"Undefined variable: {name}")
def set(self, name, value):
self.data[name] = value
def define(self, name, value):
self.data[name] = value
def extend(self):
return Environment(self)
# ----------------------------------------------------------------------
# AST nodes
# ----------------------------------------------------------------------
class ASTNode:
pass
class Number(ASTNode):
def __init__(self, value):
self.value = Decimal(value) if isinstance(value, (int, float, str)) else value
class ImagLiteral(ASTNode):
def __init__(self, value):
self.value = Decimal(value)
class String(ASTNode):
def __init__(self, value):
self.value = value
class BooleanLiteral(ASTNode):
def __init__(self, value):
self.value = value
class ListLiteral(ASTNode):
def __init__(self, elements):
self.elements = elements
class PairLiteral(ASTNode):
def __init__(self, first, second):
self.first = first
self.second = second
class DictLiteral(ASTNode):
def __init__(self, items):
self.items = items
class NullLiteral(ASTNode):
pass
class Var(ASTNode):
def __init__(self, name):
self.name = name
class BinaryOp(ASTNode):
def __init__(self, op, left, right):
self.op = op
self.left = left
self.right = right
class UnaryOp(ASTNode):
def __init__(self, op, expr):
self.op = op
self.expr = expr
class Assign(ASTNode):
def __init__(self, name, expr):
self.name = name
self.expr = expr
class If(ASTNode):
def __init__(self, cond, then_block, elif_clauses, else_block):
self.cond = cond
self.then_block = then_block
self.elif_clauses = elif_clauses
self.else_block = else_block
class While(ASTNode):
def __init__(self, cond, body):
self.cond = cond
self.body = body
class ForLoop(ASTNode):
def __init__(self, var_name, iterable, body):
self.var_name = var_name
self.iterable = iterable
self.body = body
class Return(ASTNode):
def __init__(self, expr):
self.expr = expr
class FunctionDef(ASTNode):
def __init__(self, name, params, body):
self.name = name
self.params = params
self.body = body
class LambdaDef(ASTNode):
def __init__(self, params, body):
self.params = params
self.body = body
class ClassDef(ASTNode):
def __init__(self, name, parent_name, methods):
self.name = name
self.parent_name = parent_name
self.methods = methods
class Call(ASTNode):
def __init__(self, func, args):
self.func = func
self.args = args
class MethodCall(ASTNode):
def __init__(self, obj, method, args):
self.obj = obj
self.method = method
self.args = args
class Index(ASTNode):
def __init__(self, obj, index):
self.obj = obj
self.index = index
class Print(ASTNode):
def __init__(self, expr):
self.expr = expr
class Input(ASTNode):
def __init__(self, prompt):
self.prompt = prompt
class TypeOf(ASTNode):
def __init__(self, expr):
self.expr = expr
class Convert(ASTNode):
def __init__(self, kind, expr):
self.kind = kind
self.expr = expr
class Import(ASTNode):
def __init__(self, module_name, alias=None):
self.module_name = module_name
self.alias = alias
class FromImport(ASTNode):
def __init__(self, module_name, imports):
self.module_name = module_name
self.imports = imports
# ----------------------------------------------------------------------
# Callable objects
# ----------------------------------------------------------------------
class FlamePLFunction:
def __init__(self, params, body, env, name=None):
self.params = params
self.body = body
self.env = env
self.name = name or "<lambda>"
def call(self, args, evaluator):
if len(args) != len(self.params):
raise TypeError(f"{self.name} expects {len(self.params)} args, got {len(args)}")
new_env = Environment(self.env)
for p, a in zip(self.params, args):
new_env.define(p, a)
try:
evaluator.evaluate_block(self.body, new_env)
except ReturnException as e:
return e.value
return None
class FlamePLBoundMethod:
def __init__(self, obj, method_fn):
self.obj = obj
self.method_fn = method_fn
def call(self, args, evaluator):
return self.method_fn.call([self.obj] + args, evaluator)
class FlamePLClass:
def __init__(self, name, methods, parent=None):
self.name = name
self.methods = methods
self.parent = parent
def call(self, args, evaluator):
instance = FlamePLInstance(self)
init = self.get_method("init")
if init:
init.call([instance] + args, evaluator)
return instance
def get_method(self, name):
if name in self.methods:
return self.methods[name]
if self.parent:
return self.parent.get_method(name)
return None
class FlamePLInstance:
def __init__(self, klass):
self.klass = klass
self.fields = {}
def get_method(self, name):
method_fn = self.klass.get_method(name)
if method_fn:
return FlamePLBoundMethod(self, method_fn)
return None
# ----------------------------------------------------------------------
# Exception for return
# ----------------------------------------------------------------------
class ReturnException(Exception):
def __init__(self, value):
self.value = value
# ----------------------------------------------------------------------
# Lexer
# ----------------------------------------------------------------------
class Token:
def __init__(self, type_, value, line):
self.type = type_
self.value = value
self.line = line
def __repr__(self):
return f"Token({self.type}, {repr(self.value)})"
class Lexer:
def __init__(self, source):
self.source = source
self.pos = 0
self.line = 1
self.tokens = []
def tokenize(self):
while self.pos < len(self.source):
ch = self.source[self.pos]
if ch in ' \t\r':
self.pos += 1
continue
if ch == '\n':
self.line += 1
self.pos += 1
continue
# Comments
if ch == '#':
self.pos += 1
while self.pos < len(self.source) and self.source[self.pos] != '\n':
self.pos += 1
continue
# Multi‑line string
if ch == '"' and self.pos + 2 < len(self.source) and self.source[self.pos:self.pos+3] == '"""':
self.pos += 3
start_line = self.line
start_pos = self.pos
while self.pos < len(self.source):
if self.source[self.pos:self.pos+3] == '"""':
self.pos += 3
content = self.source[start_pos:self.pos-3]
self.tokens.append(Token('STRING', content, start_line))
break
if self.source[self.pos] == '\n':
self.line += 1
self.pos += 1
continue
# Single‑line string
if ch == '"':
self.pos += 1
start_pos = self.pos
while self.pos < len(self.source) and self.source[self.pos] != '"':
if self.source[self.pos] == '\n':
self.line += 1
self.pos += 1
if self.pos >= len(self.source):
raise SyntaxError("Unterminated string")
content = self.source[start_pos:self.pos]
self.pos += 1
self.tokens.append(Token('STRING', content, self.line))
continue
# Assignment <-
if ch == '<' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '-':
self.tokens.append(Token('ASSIGN', '<-', self.line))
self.pos += 2
continue
# Integer division //
if ch == '/' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '/':
self.tokens.append(Token('OP', '//', self.line))
self.pos += 2
continue
# Multi‑character comparison operators: <=, >=, !=
if ch == '<' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '=':
self.tokens.append(Token('OP', '<=', self.line))
self.pos += 2
continue
if ch == '>' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '=':
self.tokens.append(Token('OP', '>=', self.line))
self.pos += 2
continue
if ch == '!' and self.pos + 1 < len(self.source) and self.source[self.pos+1] == '=':
self.tokens.append(Token('OP', '!=', self.line))
self.pos += 2
continue
# Support Unicode ≠ as synonym for !=
if ch == '≠':
self.tokens.append(Token('OP', '!=', self.line))
self.pos += 1
continue
# Exponent ^
if ch == '^':
self.tokens.append(Token('OP', '^', self.line))
self.pos += 1
continue
# Imaginary literal
imag_match = re.match(r'^(\d+)i', self.source[self.pos:])
if imag_match:
val = imag_match.group(1)
self.tokens.append(Token('IMAG', val, self.line))
self.pos += len(imag_match.group(0))
continue
# Number
num_match = re.match(r'^(\d+\.\d+|\d+)', self.source[self.pos:])
if num_match:
val = num_match.group(1)
self.tokens.append(Token('NUMBER', val, self.line))
self.pos += len(num_match.group(0))
continue
# Single‑character operators and delimiters
if ch in '+-*/=(){}[],:<>.':
if ch == '=':
self.tokens.append(Token('OP', '=', self.line))
elif ch == '<':
self.tokens.append(Token('OP', '<', self.line))
elif ch == '>':
self.tokens.append(Token('OP', '>', self.line))
else:
self.tokens.append(Token('OP', ch, self.line))
self.pos += 1
continue
# Keywords and identifiers
ident_match = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)', self.source[self.pos:])
if ident_match:
word = ident_match.group(1)
keywords = {
'fn', 'class', 'if', 'then', 'else', 'elseif', 'end',
'while', 'do', 'return', 'null', 'lambda', 'and', 'or',
'not', 'true', 'false', 'for', 'in', 'import', 'from', 'as'
}
if word in keywords:
self.tokens.append(Token('KEYWORD', word, self.line))
else:
self.tokens.append(Token('IDENTIFIER', word, self.line))
self.pos += len(word)
continue
raise SyntaxError(f"Unexpected char '{ch}' at line {self.line}")
self.tokens.append(Token('EOF', None, self.line))
return self.tokens
# ----------------------------------------------------------------------
# Parser
# ----------------------------------------------------------------------
class Parser:
def __init__(self, tokens):
self.tokens = tokens
self.pos = 0
def peek(self):
return self.tokens[self.pos]
def consume(self, expected_type=None, expected_value=None):
tok = self.peek()
if expected_type is not None and tok.type != expected_type:
raise SyntaxError(f"Expected {expected_type}, got {tok.type} at line {tok.line}")
if expected_value is not None and tok.value != expected_value:
raise SyntaxError(f"Expected '{expected_value}', got '{tok.value}' at line {tok.line}")
self.pos += 1
return tok
def match(self, type_, value=None):
tok = self.peek()
if tok.type == type_ and (value is None or tok.value == value):
self.pos += 1
return True
return False
def parse(self):
statements = []
while not self.match('EOF'):
statements.append(self.parse_statement())
return statements
def parse_statement(self):
tok = self.peek()
if tok.type == 'KEYWORD':
if tok.value == 'fn':
return self.parse_function_def()
elif tok.value == 'class':
return self.parse_class_def()
elif tok.value == 'if':
return self.parse_if()
elif tok.value == 'while':
return self.parse_while()
elif tok.value == 'for':
return self.parse_for()
elif tok.value == 'return':
self.consume('KEYWORD', 'return')
expr = self.parse_expression()
return Return(expr)
elif tok.value == 'print':
self.consume('KEYWORD', 'print')
self.consume('OP', '(')
expr = self.parse_expression()
self.consume('OP', ')')
return Print(expr)
elif tok.value == 'input':
self.consume('KEYWORD', 'input')
self.consume('OP', '(')
prompt = self.parse_expression()
self.consume('OP', ')')
return Input(prompt)
elif tok.value == 'import':
return self.parse_import()
elif tok.value == 'from':
return self.parse_from_import()
# If not a keyword, try expression (which may be assignment)
left = self.parse_expression()
if self.match('ASSIGN'):
if not isinstance(left, Var):
raise SyntaxError("Left side of assignment must be a variable")
right = self.parse_expression()
return Assign(left.name, right)
return left
def parse_import(self):
self.consume('KEYWORD', 'import')
tok = self.peek()
if tok.type == 'STRING':
name_node = self.consume('STRING')
module_name = name_node.value
elif tok.type == 'IDENTIFIER':
name_node = self.consume('IDENTIFIER')
module_name = name_node.value
else:
raise SyntaxError("Expected string or identifier after import")
alias = None
if self.match('KEYWORD', 'as'):
alias_tok = self.consume('IDENTIFIER')
alias = alias_tok.value
return Import(module_name, alias)
def parse_from_import(self):
self.consume('KEYWORD', 'from')
tok = self.peek()
if tok.type == 'STRING':
name_node = self.consume('STRING')
module_name = name_node.value
elif tok.type == 'IDENTIFIER':
name_node = self.consume('IDENTIFIER')
module_name = name_node.value
else:
raise SyntaxError("Expected string or identifier after from")
self.consume('KEYWORD', 'import')
imports = []
while True:
name_tok = self.consume('IDENTIFIER')
orig = name_tok.value
alias = None
if self.match('KEYWORD', 'as'):
alias_tok = self.consume('IDENTIFIER')
alias = alias_tok.value
imports.append((orig, alias))
if not self.match('OP', ','):
break
return FromImport(module_name, imports)
def parse_function_def(self):
self.consume('KEYWORD', 'fn')
name_tok = self.consume('IDENTIFIER')
self.consume('OP', '(')
params = []
if not self.match('OP', ')'):
while True:
p = self.consume('IDENTIFIER')
params.append(p.value)
if self.match('OP', ','):
continue
self.consume('OP', ')')
break
body = self.parse_block()
self.consume('KEYWORD', 'end')
return FunctionDef(name_tok.value, params, body)
def parse_lambda(self):
self.consume('KEYWORD', 'lambda')
self.consume('OP', '(')
params = []
if not self.match('OP', ')'):
while True:
p = self.consume('IDENTIFIER')
params.append(p.value)
if self.match('OP', ','):
continue
self.consume('OP', ')')
break
self.consume('OP', '->')
body = self.parse_expression()
return LambdaDef(params, [body])
def parse_class_def(self):
self.consume('KEYWORD', 'class')
name_tok = self.consume('IDENTIFIER')
parent_name = None
if self.match('OP', ':'):
parent_tok = self.consume('IDENTIFIER')
parent_name = parent_tok.value
methods = {}
while True:
tok = self.peek()
if tok.type == 'KEYWORD' and tok.value == 'end':
self.consume('KEYWORD', 'end')
break
if tok.type == 'KEYWORD' and tok.value == 'fn':
fn_def = self.parse_function_def()
methods[fn_def.name] = fn_def
else:
raise SyntaxError(f"Expected method definition inside class, got {tok}")
return ClassDef(name_tok.value, parent_name, methods)
def parse_block(self):
stmts = []
while True:
tok = self.peek()
if tok.type == 'KEYWORD' and tok.value in ('end', 'else', 'elseif'):
break
if tok.type == 'EOF':
break
stmts.append(self.parse_statement())
return stmts
def parse_if(self):
self.consume('KEYWORD', 'if')
cond = self.parse_expression()
self.consume('KEYWORD', 'then')
then_block = []
while True:
tok = self.peek()
if tok.type == 'KEYWORD' and tok.value in ('else', 'elseif', 'end'):
break
then_block.append(self.parse_statement())
elif_clauses = []
while self.match('KEYWORD', 'elseif'):
elif_cond = self.parse_expression()
self.consume('KEYWORD', 'then')
elif_block = []
while True:
tok = self.peek()
if tok.type == 'KEYWORD' and tok.value in ('else', 'elseif', 'end'):
break
elif_block.append(self.parse_statement())
elif_clauses.append((elif_cond, elif_block))
else_block = []
if self.match('KEYWORD', 'else'):
while True:
tok = self.peek()
if tok.type == 'KEYWORD' and tok.value == 'end':
break
else_block.append(self.parse_statement())
self.consume('KEYWORD', 'end')
return If(cond, then_block, elif_clauses, else_block)
def parse_while(self):
self.consume('KEYWORD', 'while')
cond = self.parse_expression()
self.consume('KEYWORD', 'do')
body = []
while True:
tok = self.peek()
if tok.type == 'KEYWORD' and tok.value == 'end':
break
body.append(self.parse_statement())
self.consume('KEYWORD', 'end')
return While(cond, body)
def parse_for(self):
self.consume('KEYWORD', 'for')
var_tok = self.consume('IDENTIFIER')
self.consume('KEYWORD', 'in')
iterable = self.parse_expression()
self.consume('KEYWORD', 'do')
body = []
while True:
tok = self.peek()
if tok.type == 'KEYWORD' and tok.value == 'end':
break
body.append(self.parse_statement())
self.consume('KEYWORD', 'end')
return ForLoop(var_tok.value, iterable, body)
def parse_expression(self):
return self.parse_or()
def parse_or(self):
left = self.parse_and()
while self.match('KEYWORD', 'or'):
right = self.parse_and()
left = BinaryOp('or', left, right)
return left
def parse_and(self):
left = self.parse_not()
while self.match('KEYWORD', 'and'):
right = self.parse_not()
left = BinaryOp('and', left, right)
return left
def parse_not(self):
if self.match('KEYWORD', 'not'):
expr = self.parse_not()
return UnaryOp('not', expr)
return self.parse_comparison()
def parse_comparison(self):
left = self.parse_additive()
# Accept =, <, >, <=, >=, !=
if self.match('OP', '=') or self.match('OP', '<') or self.match('OP', '>') or \
self.match('OP', '<=') or self.match('OP', '>=') or self.match('OP', '!='):
op = self.tokens[self.pos-1].value
right = self.parse_additive()
return BinaryOp(op, left, right)
return left
def parse_additive(self):
left = self.parse_multiplicative()
while self.match('OP', '+') or self.match('OP', '-'):
op = self.tokens[self.pos-1].value
right = self.parse_multiplicative()
left = BinaryOp(op, left, right)
return left
def parse_multiplicative(self):
left = self.parse_power()
while self.match('OP', '*') or self.match('OP', '/') or self.match('OP', '//') or self.match('OP', '%'):
op = self.tokens[self.pos-1].value
right = self.parse_power()
left = BinaryOp(op, left, right)
return left
def parse_power(self):
left = self.parse_unary()
if self.match('OP', '^'):
right = self.parse_power()
return BinaryOp('^', left, right)
return left
def parse_unary(self):
if self.match('OP', '-') or self.match('OP', '+'):
op = self.tokens[self.pos-1].value
expr = self.parse_unary()
return UnaryOp(op, expr)
return self.parse_postfix()
def parse_postfix(self):
expr = self.parse_atom()
while True:
if self.match('OP', '['):
idx = self.parse_expression()
self.consume('OP', ']')
expr = Index(expr, idx)
elif self.match('OP', '('):
args = []
if not self.match('OP', ')'):
while True:
args.append(self.parse_expression())
if self.match('OP', ','):
continue
self.consume('OP', ')')
break
expr = Call(expr, args)
elif self.match('OP', '.'):
method_tok = self.consume('IDENTIFIER')
self.consume('OP', '(')
args = []
if not self.match('OP', ')'):
while True:
args.append(self.parse_expression())
if self.match('OP', ','):
continue
self.consume('OP', ')')
break
expr = MethodCall(expr, method_tok.value, args)
else:
break
return expr
def parse_atom(self):
tok = self.peek()
if tok.type == 'NUMBER':
self.consume('NUMBER')
return Number(tok.value)
if tok.type == 'IMAG':
self.consume('IMAG')
return ImagLiteral(tok.value)
if tok.type == 'STRING':
self.consume('STRING')
return String(tok.value)
if tok.type == 'KEYWORD' and tok.value in ('true', 'false'):
self.consume('KEYWORD')
return BooleanLiteral(tok.value == 'true')
if tok.type == 'IDENTIFIER':
self.consume('IDENTIFIER')
if tok.value == 'null':
return NullLiteral()
if tok.value == 'to_int':
self.consume('OP', '(')
expr = self.parse_expression()
self.consume('OP', ')')
return Convert('int', expr)
if tok.value == 'to_float':
self.consume('OP', '(')
expr = self.parse_expression()
self.consume('OP', ')')
return Convert('float', expr)
if tok.value == 'to_str':
self.consume('OP', '(')
expr = self.parse_expression()
self.consume('OP', ')')
return Convert('str', expr)
if tok.value == 'type_of':
self.consume('OP', '(')
expr = self.parse_expression()
self.consume('OP', ')')
return TypeOf(expr)
return Var(tok.value)
if tok.type == 'OP' and tok.value == '(':
self.consume('OP', '(')
expr = self.parse_expression()
if self.match('OP', ','):
second = self.parse_expression()
self.consume('OP', ')')
return PairLiteral(expr, second)
self.consume('OP', ')')
return expr
if tok.type == 'OP' and tok.value == '[':
self.consume('OP', '[')
elements = []
if not self.match('OP', ']'):
while True:
elements.append(self.parse_expression())
if self.match('OP', ','):
continue
self.consume('OP', ']')
break
return ListLiteral(elements)
if tok.type == 'OP' and tok.value == '{':
self.consume('OP', '{')
items = []
if not self.match('OP', '}'):
while True:
key = self.parse_expression()
self.consume('OP', ':')
value = self.parse_expression()
items.append((key, value))
if self.match('OP', ','):
continue
self.consume('OP', '}')
break
return DictLiteral(items)
if tok.type == 'KEYWORD' and tok.value == 'lambda':
return self.parse_lambda()
raise SyntaxError(f"Unexpected token {tok}")
# ----------------------------------------------------------------------
# Evaluator
# ----------------------------------------------------------------------
class Evaluator:
def __init__(self, global_env):
self.global_env = global_env
self.loaded_modules = {}
self.loading = set()
self.current_file_dir = "."
def resolve_module_path(self, name, base_dir):
if isinstance(name, str) and name.endswith('.flpl'):
if os.path.isabs(name):
return name
return os.path.normpath(os.path.join(base_dir, name))
return name
def load_builtin_module(self, name):
if name == "math":
env = Environment(self.global_env)
def sqrt(x):
if isinstance(x, FlamePLComplex):
raise TypeError("sqrt not supported for complex")
return Decimal(pymath.sqrt(float(x)))
def sin(x):
return Decimal(pymath.sin(float(x)))
def cos(x):
return Decimal(pymath.cos(float(x)))
env.define("sqrt", sqrt)
env.define("sin", sin)
env.define("cos", cos)
env.define("pi", Decimal(pymath.pi))
return env
elif name == "io":
env = Environment(self.global_env)
return env
else:
raise ImportError(f"Unknown built-in module: {name}")
def load_module(self, name, base_dir):
path = self.resolve_module_path(name, base_dir)
if path in self.loaded_modules:
return self.loaded_modules[path]
if path in self.loading:
raise RuntimeError(f"Circular import detected: {name}")
self.loading.add(path)
if not isinstance(path, str) or '.' not in path or os.path.sep not in path:
try:
mod_env = self.load_builtin_module(path)
self.loaded_modules[path] = mod_env
self.loading.remove(path)
return mod_env
except ImportError:
pass
if not os.path.exists(path):
if not path.endswith('.flpl'):
path2 = path + '.flpl'
if os.path.exists(path2):
path = path2
else:
raise ImportError(f"Module not found: {name} (resolved as {path})")
else:
raise ImportError(f"Module not found: {name} (resolved as {path})")
try:
with open(path, 'r') as f:
source = f.read()
except Exception as e:
raise ImportError(f"Could not read module {name}: {e}")
mod_env = Environment(self.global_env)
lexer = Lexer(source)
tokens = lexer.tokenize()
parser = Parser(tokens)
ast = parser.parse()
for stmt in ast:
self.evaluate(stmt, mod_env)
self.loaded_modules[path] = mod_env
self.loading.remove(path)
return mod_env
def evaluate(self, node, env):
if isinstance(node, Number):
return node.value
if isinstance(node, ImagLiteral):
return FlamePLComplex(0, node.value)
if isinstance(node, String):
return node.value
if isinstance(node, BooleanLiteral):
return node.value
if isinstance(node, NullLiteral):
return None
if isinstance(node, Var):
return env.get(node.name)
if isinstance(node, Assign):
val = self.evaluate(node.expr, env)
env.set(node.name, val)
return val
if isinstance(node, BinaryOp):
left = self.evaluate(node.left, env)
right = self.evaluate(node.right, env)
op = node.op
if op == 'and':
return bool(left and right)
if op == 'or':
return bool(left or right)
if op == '^':
return left ** right
if op == '//':
return left // right
if op == '%':
return left % right
if op == '+':
return left + right
if op == '-':
return left - right
if op == '*':
return left * right
if op == '/':
return left / right
if op == '=':
return left == right
if op == '<':
return left < right
if op == '>':
return left > right
if op == '<=':
return left <= right
if op == '>=':
return left >= right
if op == '!=':
return left != right
raise RuntimeError(f"Unknown binary op: {op}")
if isinstance(node, UnaryOp):
val = self.evaluate(node.expr, env)
if node.op == '-':
return -val
if node.op == '+':
return val
if node.op == 'not':
return not bool(val)
raise RuntimeError(f"Unknown unary op: {node.op}")
if isinstance(node, If):
if self.evaluate(node.cond, env):
return self.evaluate_block(node.then_block, env)
for elif_cond, elif_block in node.elif_clauses:
if self.evaluate(elif_cond, env):
return self.evaluate_block(elif_block, env)
return self.evaluate_block(node.else_block, env)
if isinstance(node, While):
while self.evaluate(node.cond, env):
self.evaluate_block(node.body, env)
return None
if isinstance(node, ForLoop):
iterable_val = self.evaluate(node.iterable, env)
if isinstance(iterable_val, list):
items = iterable_val
elif isinstance(iterable_val, dict):
items = list(iterable_val.keys())
elif isinstance(iterable_val, str):
items = list(iterable_val)
else:
raise TypeError(f"Cannot iterate over {type(iterable_val)}")
for item in items:
env.set(node.var_name, item)
self.evaluate_block(node.body, env)
return None
if isinstance(node, Return):
raise ReturnException(self.evaluate(node.expr, env))
if isinstance(node, FunctionDef):
func = FlamePLFunction(node.params, node.body, env, node.name)
env.set(node.name, func)
return func
if isinstance(node, LambdaDef):
return FlamePLFunction(node.params, node.body, env)
if isinstance(node, ClassDef):
parent = None
if node.parent_name:
parent = env.get(node.parent_name)
if not isinstance(parent, FlamePLClass):
raise TypeError(f"Parent {node.parent_name} is not a class")
methods = {}
for mname, fn_def in node.methods.items():
methods[mname] = FlamePLFunction(fn_def.params, fn_def.body, env, f"{node.name}.{mname}")
klass = FlamePLClass(node.name, methods, parent)
env.set(node.name, klass)
return klass
if isinstance(node, Call):
func = self.evaluate(node.func, env)
args = [self.evaluate(a, env) for a in node.args]
if isinstance(func, FlamePLFunction):
return func.call(args, self)
if isinstance(func, FlamePLClass):
return func.call(args, self)
if callable(func):
return func(*args)
raise TypeError(f"{func} is not callable")
if isinstance(node, MethodCall):
obj = self.evaluate(node.obj, env)
args = [self.evaluate(a, env) for a in node.args]
if isinstance(obj, FlamePLInstance):
method = obj.get_method(node.method)
if method:
return method.call(args, self)
raise TypeError(f"No method {node.method} on {obj}")
if isinstance(node, Index):
obj = self.evaluate(node.obj, env)
idx = self.evaluate(node.index, env)
if isinstance(obj, list):
return obj[idx]
if isinstance(obj, dict):
return obj[idx]
raise TypeError(f"Cannot index {obj}")
if isinstance(node, ListLiteral):
return [self.evaluate(e, env) for e in node.elements]
if isinstance(node, PairLiteral):
return FlamePLPair(self.evaluate(node.first, env), self.evaluate(node.second, env))
if isinstance(node, DictLiteral):
d = {}
for k, v in node.items:
d[self.evaluate(k, env)] = self.evaluate(v, env)
return d
if isinstance(node, Print):
val = self.evaluate(node.expr, env)
print(val)
return None
if isinstance(node, Input):
prompt = self.evaluate(node.prompt, env)
result = input(str(prompt))
return result
if isinstance(node, TypeOf):
val = self.evaluate(node.expr, env)
if isinstance(val, bool):
return BOOL_TYPE
if isinstance(val, int) or isinstance(val, Decimal):
return INT_TYPE if not isinstance(val, Decimal) or val.as_tuple().exponent >= 0 else FLOAT_TYPE
if isinstance(val, float):
return FLOAT_TYPE
if isinstance(val, FlamePLComplex):
return COMPLEX_TYPE
if isinstance(val, list):
return LIST_TYPE
if isinstance(val, FlamePLPair):
return PAIR_TYPE
if isinstance(val, dict):
return DICT_TYPE
if isinstance(val, str):
return STR_TYPE
if val is None:
return NULL_TYPE
if isinstance(val, (FlamePLFunction, FlamePLBoundMethod)):
return FUNCTION_TYPE
if isinstance(val, (FlamePLClass, FlamePLInstance)):
return CLASS_TYPE
if isinstance(val, FlamePLModule):
return MODULE_TYPE
return FlamePLType(type(val).__name__)
if isinstance(node, Convert):
val = self.evaluate(node.expr, env)
if node.kind == 'int':
return int(val)
if node.kind == 'float':
return Decimal(str(val))
if node.kind == 'str':
return str(val)
raise RuntimeError(f"Unknown conversion: {node.kind}")
if isinstance(node, Import):
mod_env = self.load_module(node.module_name, self.current_file_dir)
mod_obj = FlamePLModule(mod_env, node.module_name)
alias = node.alias if node.alias else node.module_name
if not node.alias and isinstance(node.module_name, str):
base = os.path.basename(node.module_name)
if base.endswith('.flpl'):
alias = base[:-3]
else:
alias = base
env.set(alias, mod_obj)
return mod_obj
if isinstance(node, FromImport):
mod_env = self.load_module(node.module_name, self.current_file_dir)
for orig, alias in node.imports:
try:
value = mod_env.get(orig)
except NameError:
raise ImportError(f"Module {node.module_name} has no attribute {orig}")
target = alias if alias else orig
env.set(target, value)
return None
raise RuntimeError(f"Unknown AST node: {type(node)}")
def evaluate_block(self, block, env):
result = None
for stmt in block:
result = self.evaluate(stmt, env)
return result
# ----------------------------------------------------------------------
# Main entry
# ----------------------------------------------------------------------
def run_flame(source, filename="<stdin>"):
base_dir = os.path.dirname(os.path.abspath(filename)) if filename != "<stdin>" else os.getcwd()
lexer = Lexer(source)
tokens = lexer.tokenize()
parser = Parser(tokens)
ast = parser.parse()
global_env = Environment()
def print_fn(*args):
print(' '.join(str(a) for a in args))
def input_fn(prompt):
return input(str(prompt))
global_env.define('print', print_fn)
global_env.define('input', input_fn)
evaluator = Evaluator(global_env)
evaluator.current_file_dir = base_dir
evaluator.evaluate_block(ast, global_env)
# ----------------------------------------------------------------------
# Main runner
# ----------------------------------------------------------------------
if __name__ == "__main__":
if len(sys.argv) > 1:
filename = sys.argv[1]
try:
with open(filename, 'r') as f:
source = f.read()
run_flame(source, filename)
except FileNotFoundError:
print(f"Error: File '{filename}' not found.", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
else:
source = sys.stdin.read()
if not source:
print("FlamePL Interpreter - usage:", file=sys.stderr)
print(" python flame.py <filename> to run a file", file=sys.stderr)
print(" python flame.py to read from stdin (pipe/redirect)", file=sys.stderr)
sys.exit(0)
run_flame(source, "<stdin>")