The “MiniPy” AI Programming Language

I have finally talked an artificial intelligence into making a programming language. It’s written in python, and this is a program below, written in the Mini language it’s called the language tutorial:
?? Welcome to the Mini Language Tutorial!
?? This tutorial is written entirely within Mini's comment system.
?? Mini comments start with '??' and extend to the end of the line.
?? --- 1. Basic Syntax & Comments ---
?? As you can see, lines starting with '??' are comments and are ignored by the interpreter.
?? Mini code is typically written one statement per line.
?? Blocks of code (for if, while, for, functions, classes, try/except) are enclosed in curly braces { }.
print "?? Starting Mini Language Tutorial..." ?? This line will actually print!
?? --- 2. Variables and Data Types ---
?? == Variables ==
?? Variables are created using assignment with '='. No explicit declaration is needed.
?? Mini is dynamically typed, meaning a variable can hold different types of data over its lifetime.
x = 10 ?? x is now a number (integer)
x = "Hello Mini" ?? x is now a string
x = True ?? x is now a boolean
?? == Data Types ==
?? Mini supports several built-in data types:
?? 2.1. Numbers:
?? Integers and floating-point numbers. Integers have arbitrary precision.
my_int = 123
my_float = 3.14159
big_number = 1000000000000000000000000000000 ?? Mini handles large integers!
print "?? A big number: " + str(big_number)
?? 2.2. Strings:
?? Sequences of characters, enclosed in double " " or single ' ' quotes.
greeting = "Hello, World!"
name = 'Mini'
message = greeting + " My name is " + name + "." ?? String concatenation with +
print message
?? 2.3. Booleans:
?? Represent truth values: True or False.
is_active = True
is_ready = False
print "?? is_active is " + str(is_active)
?? 2.4. Null:
?? Represents the absence of a value. Similar to Python's None.
no_value = Null
print "?? no_value is " + str(no_value)
?? 2.5. Lists:
?? Ordered collections of items, enclosed in square brackets [ ]. Items can be of mixed types.
my_list = [1, "two", True, 3.0, Null]
empty_list = []
print "?? my_list: " + str(my_list)
print "?? First item of my_list: " + str(my_list[0]) ?? Accessing elements by index (0-based)
my_list[1] = 2 ?? Modifying list elements
print "?? Modified my_list: " + str(my_list)
?? --- 3. Operators ---
?? == Arithmetic Operators ==
a = 10
b = 3
print "?? a + b = " + str(a + b) ?? Addition (and string/list concatenation)
print "?? a - b = " + str(a - b) ?? Subtraction
print "?? a * b = " + str(a * b) ?? Multiplication
print "?? a / b = " + str(a / b) ?? Division (results in a float if not perfectly divisible)
?? == Comparison Operators ==
?? These return True or False.
print "?? 5 == 5 is " + str(5 == 5) ?? Equal
print "?? 5 != 3 is " + str(5 != 3) ?? Not equal
print "?? 5 < 3 is " + str(5 < 3) ?? Less than
print "?? 5 > 3 is " + str(5 > 3) ?? Greater than
print "?? 5 <= 5 is " + str(5 <= 5) ?? Less than or equal
print "?? 5 >= 3 is " + str(5 >= 3) ?? Greater than or equal
?? == Logical Operators ==
?? Used to combine boolean expressions.
cond1 = True
cond2 = False
print "?? cond1 and cond2 is " + str(cond1 and cond2) ?? Logical AND (short-circuiting)
print "?? cond1 or cond2 is " + str(cond1 or cond2) ?? Logical OR (short-circuiting)
print "?? not cond2 is " + str(not cond2) ?? Logical NOT
?? --- 4. Control Flow ---
?? == If-Else Statements ==
?? Used for conditional execution.
score = 85
if (score >= 90) {
print "?? Grade: A"
} else if (score >= 80) {
print "?? Grade: B"
} else {
print "?? Grade: C or lower"
}
?? == While Loops ==
?? Repeat a block of code as long as a condition is True.
count = 0
while (count < 3) {
print "?? While loop count: " + str(count)
count = count + 1
}
?? == For Loops ==
?? Iterate over items in a list (or a sequence generated by range()).
print "?? For loop over a list:"
fruits = ["apple", "banana", "cherry"]
for fruit in fruits {
print "?? Fruit: " + fruit
}
print "?? Loop variable 'fruit' after loop: " + str(fruit) ?? Persists with last value
print "?? For loop with range():"
for i in range(3) { ?? 0, 1, 2
print "?? Range loop i: " + str(i)
}
for j in range(1, 4) { ?? 1, 2, 3
print "?? Range loop j: " + str(j)
}
for k in range(0, 10, 3) { ?? 0, 3, 6, 9
print "?? Range loop k (step 3): " + str(k)
}
?? == Break and Continue ==
?? Used to control loop execution.
print "?? Loop with break and continue:"
num = 0
while (num < 10) {
num = num + 1
if (num == 3) {
print "?? Skipping num=3 with continue"
continue ?? Skip rest of this iteration
}
if (num == 7) {
print "?? Breaking loop at num=7"
break ?? Exit the loop entirely
}
print "?? Current num in break/continue loop: " + str(num)
}
?? --- 5. Error Handling ---
?? == Try-Except Blocks ==
?? Handle potential runtime errors gracefully.
?? The current 'except' block catches any Mini error.
print "?? Testing try/except:"
try {
print "?? Attempting risky operation..."
?? result = 10 / 0 ?? This would cause an error
?? print "This won't print if error."
data = read_file("non_existent_file.txt") ?? This will error
} except {
print "?? An error was caught and handled!"
}
print "?? Program continues after try/except."
?? --- 6. Functions ---
?? == Standalone Functions ==
?? Define reusable blocks of code.
function add(a, b) {
return a + b ?? 'return' sends a value back
}
function say_hello(person_name) {
print "Hello, " + person_name + " from a Mini function!"
?? No explicit return, so it returns Null
}
sum_result = add(25, 17)
print "?? Result from add function: " + str(sum_result)
say_hello("Mini User")
?? --- 7. Classes and Object-Oriented Programming ---
?? == Class Definition ==
class Greeter {
?? Constructor method, called automatically when an instance is created
__init__(greeting_word) {
self.greeting = greeting_word ?? 'self' refers to the instance
print "Greeter instance created with greeting: " + self.greeting
}
?? Regular method
greet(name) {
return self.greeting + ", " + name + "!"
}
?? Custom string representation
__str__() {
return "<Greeter object with greeting: '" + self.greeting + "'>"
}
}
?? == Instantiation (Creating Objects) ==
english_greeter = Greeter("Hello") ?? __init__("Hello") is called
spanish_greeter = Greeter("Hola")
?? == Method Calls and Attribute Access ==
print english_greeter.greet("World")
print spanish_greeter.greet("Mundo")
english_greeter.greeting = "Hi" ?? Attributes can be modified
print english_greeter.greet("There")
print "?? String representation of english_greeter: " + str(english_greeter)
?? == Inheritance ==
class LoudGreeter extends Greeter {
?? Override __init__
__init__(greeting_word, volume) {
super(greeting_word) ?? Call parent's __init__
self.volume = volume
print "LoudGreeter instance created."
}
?? Override greet
greet(name) {
original_greeting = super.greet(name) ?? Not directly supported yet for general methods,
?? super() is mainly for __init__
?? For now, let's re-implement or call parent's method if possible
?? (Mini's super.method() is not fully implemented for general calls)
?? Let's just show overriding:
return self.greeting + ", " + name + "!!!! (Volume: " + str(self.volume) + ")"
}
?? Inherits __str__ from Greeter if not overridden
}
loud_g = LoudGreeter("HEY", 11)
print loud_g.greet("Everyone")
?? --- 8. Modules ---
?? (Interpreter Mode Only)
?? Modules allow you to organize code into separate files.
?? Assume we have a file 'myutils.mini' with:
?? ?? myutils.mini
?? pi = 3.14
?? function square(x) { return x*x; }
?? Import the whole module:
?? import "myutils.mini" ?? Creates a 'myutils' object
?? print myutils.pi
?? print myutils.square(4)
?? Import specific names:
?? from "myutils.mini" import pi, square
?? print pi
?? print square(5)
?? Import all names (use with caution):
?? from "myutils.mini" import *
?? print pi ?? Now pi is directly in scope
?? --- 9. Built-in Functions ---
?? Mini provides several useful built-in functions:
?? == General Utilities ==
print "?? type(10): " + type(10) ?? "number"
print "?? type('hi'): " + type("hi") ?? "string"
my_list_for_type = [1]
print "?? type(my_list_for_type): " + type(my_list_for_type) ?? "list"
print "?? str(123): " + str(123) ?? "123" (string)
print "?? len('hello'): " + str(len("hello")) ?? 5
print "?? len([1,2,3]): " + str(len([1,2,3])) ?? 3
print "?? number('42'): " + str(number("42")) ?? 42 (number)
print "?? number('3.14'): " + str(number("3.14")) ?? 3.14 (number)
?? == Type Checking ==
print "?? is_number(10.5): " + str(is_number(10.5))
print "?? is_string('text'): " + str(is_string("text"))
print "?? is_list([]): " + str(is_list([]))
print "?? is_null(Null): " + str(is_null(Null))
?? == Math ==
print "?? abs(-7): " + str(abs(-7)) ?? 7
print "?? sqrt(16): " + str(sqrt(16)) ?? 4.0
?? == List Manipulation ==
sample_list = [1, 2]
append(sample_list, 3)
print "?? append([1,2], 3): " + str(sample_list) ?? [1, 2, 3]
popped_val = pop(sample_list)
print "?? pop from [1,2,3]: " + str(popped_val) ?? 3
print "?? List after pop: " + str(sample_list) ?? [1, 2]
insert(sample_list, 0, "start")
print "?? insert([1,2], 0, 'start'): " + str(sample_list) ?? ["start", 1, 2]
remove(sample_list, 1)
print "?? remove(['start',1,2], 1): " + str(sample_list) ?? ["start", 2]
?? == Iteration ==
print "?? range(3): " + str(range(3)) ?? [0, 1, 2]
?? == Time ==
print "?? time(): (current timestamp) " + str(time())
?? == Input/Output (Filesystem - Interpreter Only) ==
?? write_file("minipy_test_files/temp_tutorial.txt", "Hello from Mini tutorial!")
?? file_content = read_file("minipy_test_files/temp_tutorial.txt")
?? print "?? Content from temp_tutorial.txt: " + file_content
?? == Dynamic Execution (Interpreter Only) ==
?? eval_code = "x_eval = 100; x_eval + 5"
?? eval_res = eval_string(eval_code)
?? print "?? Result of eval_string('" + eval_code + "'): " + str(eval_res) ?? 105
?? == Randomness ==
print "?? random(): (float 0-1) " + str(random())
print "?? random(5): (int 0-4) " + str(random(5))
print "?? random(10, 15): (int 10-15) " + str(random(10,15))
?? --- 10. Compiler and VM (Experimental Subset) ---
?? Mini also has an experimental compiler and Virtual Machine (VM).
?? To use it, you'd typically run your Mini script with a special flag.
?? Currently, the compiler supports a SUBSET of Mini's features:
?? - Numbers, basic arithmetic (+, -, *, /)
?? - Strings, Booleans, Null (as constants)
?? - Variable assignment and access (simple variables)
?? - print statement
?? - Comparison operators (==, !=, <, >, <=, >=)
?? - Unary 'not'
?? Features like classes, functions, loops, try/except, modules, lists (beyond literals),
?? and most built-ins are NOT YET supported by the compiler and will fall back to
?? the interpreter or cause a compiler error.
?? This concludes the basic Mini language tutorial!
?? Happy coding in Mini!
print "?? Mini Tutorial Complete."
Here is version 001.02 of the language, meaning 1 or 1st version, revision 02. There have been two versions of the language, the other being 001.01, which wasn’t really functional. But I at last, I got the computer to write a whole programming language successfully. I have been dreaming of doing this since AI came out, and now it’s happening!
What luck. It must be tied to the crash a few nights ago, I was thinking some really strange things, very strange things. But they made sense in a way, because well anyways here is the actual mini language, written in python: (below is version 001.03 while the above tutorial was written for 001.01)
# MiniPy: A Simple Dynamically-Typed Language Interpreter
import re
import os
import random
import time
import math
import threading
import copy
# --- Mini Error Types ---
E_TYPE_ERROR = "TypeError"; E_NAME_ERROR = "NameError"; E_INDEX_ERROR = "IndexError"
E_KEY_ERROR = "KeyError"; E_VALUE_ERROR = "ValueError"; E_ZERO_DIVISION_ERROR = "ZeroDivisionError"
E_IO_ERROR = "IOError"; E_ATTRIBUTE_ERROR = "AttributeError"; E_MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError"
E_IMPORT_ERROR = "ImportError"; E_SYNTAX_ERROR = "SyntaxError"
# --- Tokenizer (Lexer) ---
# ... (Tokenizer remains the same as before) ...
class Token:
def __init__(self, type, value): self.type = type; self.value = value
def __repr__(self): return f"Token({self.type}, {repr(self.value)})"
TOKEN_SPECIFICATION = [
('COMMENT', r'\?\?.*'), ('NUMBER', r'\d+(\.\d*)?'), ('ASSIGN', r'='),
('IMPORT', r'import'), ('FROM', r'from'), ('CLASS', r'class'), ('EXTENDS', r'extends'),
('SUPER', r'super'), ('SELF', r'self'), ('RETURN', r'return'), ('IF', r'if'),
('ELSE', r'else'), ('WHILE', r'while'), ('FOR', r'for'), ('IN', r'in'),
('TRY', r'try'), ('EXCEPT', r'except'), ('FUNCTION', r'function'), ('BREAK', r'break'),
('CONTINUE', r'continue'), ('TRUE', r'True'), ('FALSE', r'False'), ('NULL', r'Null'),
('PRINT', r'print'), ('AND', r'and'), ('OR', r'or'), ('NOT', r'not'),
('ID', r'[A-Za-z_][A-Za-z0-9_]*'), ('STRING', r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\''),
('EQ',r'=='),('NEQ',r'!='),('LTE',r'<='),('GTE',r'>='),('LT',r'<'),('GT',r'>'),
('PLUS',r'\+'),('MINUS',r'-'),('MUL',r'\*'),('DIV',r'/'), ('DOT',r'\.'),('LPAREN',r'\('),
('RPAREN',r'\)'),('LBRACE',r'\{'),('RBRACE',r'\}'),('LBRACKET',r'\['),('RBRACKET',r'\]'),
('COMMA',r','),('COLON',r':'), ('NEWLINE',r'\n'),('SKIP',r'[ \t]+'),('MISMATCH',r'.'),
]
TOKEN_REGEX = re.compile('|'.join('(?P<%s>%s)' % pair for pair in TOKEN_SPECIFICATION))
class LexerError(Exception): pass
def tokenize(code):
tokens = []; keywords_map = {'import':'IMPORT','from':'FROM','class':'CLASS','extends':'EXTENDS',
'super':'SUPER','self':'SELF','return':'RETURN','if':'IF','else':'ELSE','while':'WHILE','for':'FOR',
'in':'IN','try':'TRY','except':'EXCEPT','function':'FUNCTION','break':'BREAK','continue':'CONTINUE',
'True':'TRUE','False':'FALSE','Null':'NULL','print':'PRINT','and':'AND','or':'OR','not':'NOT'}
for mo in TOKEN_REGEX.finditer(code):
kind, value = mo.lastgroup, mo.group()
if kind == 'COMMENT': continue
elif kind == 'ID' and value in keywords_map:
kind = keywords_map[value]
if kind == 'TRUE': value = True
elif kind == 'FALSE': value = False
elif kind == 'NULL': value = None
elif kind == 'NUMBER': value = float(value) if '.' in value else int(value)
elif kind == 'STRING': value = value[1:-1].replace('\\"', '"').replace("\\'", "'")
elif kind in ('NEWLINE', 'SKIP'): continue
elif kind == 'MISMATCH': raise LexerError(f'Unexpected character: {value}')
tokens.append(Token(kind, value))
tokens.append(Token('EOF', None)); return tokens
# --- Abstract Syntax Tree (AST) Nodes ---
# ... (AST Node definitions remain the same as before) ...
class ASTNode: pass
class NumberNode(ASTNode):
def __init__(self, token): self.value = token.value
def __repr__(self): return f"NumberNode({self.value})"
class StringNode(ASTNode):
def __init__(self, token): self.value = token.value
def __repr__(self): return f"StringNode({repr(self.value)})"
class BooleanNode(ASTNode):
def __init__(self, token): self.value = token.value
def __repr__(self): return f"BooleanNode({self.value})"
class NullNode(ASTNode):
def __init__(self, token): self.value = token.value
def __repr__(self): return f"NullNode({self.value})"
class VariableNode(ASTNode):
def __init__(self, token): self.name = token.value
def __repr__(self): return f"VariableNode({self.name})"
class SelfNode(ASTNode):
def __init__(self, token): self.token = token
def __repr__(self): return "SelfNode"
class SuperNode(ASTNode):
def __init__(self, token): self.token = token
def __repr__(self): return "SuperNode"
class ListNode(ASTNode):
def __init__(self, elements): self.elements = elements
def __repr__(self): return f"ListNode({self.elements})"
class DictionaryNode(ASTNode):
def __init__(self, pairs): self.pairs = pairs
def __repr__(self): return f"DictionaryNode({self.pairs})"
class IndexAccessNode(ASTNode):
def __init__(self, collection_expr, index_or_key_expr):
self.collection_expr = collection_expr; self.index_or_key_expr = index_or_key_expr
def __repr__(self): return f"IndexAccessNode({self.collection_expr}, {self.index_or_key_expr})"
class MemberAccessNode(ASTNode):
def __init__(self, object_expr, member_token):
self.object_expr = object_expr; self.member_token = member_token
def __repr__(self): return f"MemberAccessNode({self.object_expr}, {self.member_token.value})"
class FunctionCallNode(ASTNode):
def __init__(self, callable_expr, arg_nodes):
self.callable_expr = callable_expr; self.arg_nodes = arg_nodes
def __repr__(self): return f"FunctionCallNode({self.callable_expr}, {self.arg_nodes})"
class ClassDefNode(ASTNode):
def __init__(self, name_token, parent_class_token, methods):
self.name_token = name_token; self.parent_class_token = parent_class_token; self.methods = methods
def __repr__(self):
parent_name = f" extends {self.parent_class_token.value}" if self.parent_class_token else ""
return f"ClassDefNode({self.name_token.value}{parent_name}, {len(self.methods)} methods)"
class MethodDefNode(ASTNode):
def __init__(self, name_token, params_tokens, body_node):
self.name_token = name_token; self.params_tokens = params_tokens; self.body_node = body_node
def __repr__(self): return f"MethodDefNode({self.name_token.value}, params={[p.value for p in self.params_tokens]})"
class FunctionDefNode(ASTNode):
def __init__(self, name_token, params_tokens, body_node):
self.name_token = name_token; self.params_tokens = params_tokens; self.body_node = body_node
def __repr__(self): return f"FunctionDefNode({self.name_token.value}, params={[p.value for p in self.params_tokens]})"
class ReturnNode(ASTNode):
def __init__(self, expr_node): self.expr_node = expr_node
def __repr__(self): return f"ReturnNode({self.expr_node})"
class BreakNode(ASTNode):
def __init__(self, token): self.token = token
def __repr__(self): return "BreakNode"
class ContinueNode(ASTNode):
def __init__(self, token): self.token = token
def __repr__(self): return "ContinueNode"
class UnaryOpNode(ASTNode):
def __init__(self, op_token, expr_node):
self.op_token = op_token; self.expr_node = expr_node
def __repr__(self): return f"UnaryOpNode({self.op_token.type}, {self.expr_node})"
class BinOpNode(ASTNode):
def __init__(self, left, op_token, right):
self.left = left; self.op_token = op_token; self.right = right
def __repr__(self): return f"BinOpNode({self.left}, {self.op_token.type}, {self.right})"
class AssignNode(ASTNode):
def __init__(self, target_node, value_node):
self.target_node = target_node; self.value_node = value_node
def __repr__(self): return f"AssignNode({self.target_node}, {self.value_node})"
class PrintNode(ASTNode):
def __init__(self, expr_node): self.expr_node = expr_node
def __repr__(self): return f"PrintNode({self.expr_node})"
class BlockNode(ASTNode):
def __init__(self, statements): self.statements = statements
def __repr__(self): return f"BlockNode({self.statements})"
class IfNode(ASTNode):
def __init__(self, condition, if_block, else_block=None):
self.condition = condition; self.if_block = if_block; self.else_block = else_block
def __repr__(self): return f"IfNode({self.condition}, {self.if_block}, else={self.else_block})"
class WhileNode(ASTNode):
def __init__(self, condition_node, body_node):
self.condition_node = condition_node; self.body_node = body_node
def __repr__(self): return f"WhileNode({self.condition_node}, {self.body_node})"
class ForInNode(ASTNode):
def __init__(self, var_token, iterable_node, body_node):
self.var_token = var_token; self.iterable_node = iterable_node; self.body_node = body_node
def __repr__(self): return f"ForInNode(var={self.var_token.value}, in={self.iterable_node}, body={self.body_node})"
class SpecificExceptClauseNode(ASTNode):
def __init__(self, error_type_token, block_node):
self.error_type_token = error_type_token; self.block_node = block_node
def __repr__(self): return f"SpecificExceptClauseNode(type={self.error_type_token.value}, block={self.block_node})"
class TryExceptNode(ASTNode):
def __init__(self, try_block, specific_except_clauses, generic_except_block):
self.try_block = try_block; self.specific_except_clauses = specific_except_clauses; self.generic_except_block = generic_except_block
def __repr__(self): return f"TryExceptNode(try={self.try_block}, specific_clauses={self.specific_except_clauses}, generic_except={self.generic_except_block})"
class ImportNode(ASTNode):
def __init__(self, filepath_token): self.filepath_token = filepath_token
def __repr__(self): return f"ImportNode('{self.filepath_token.value}')"
class ImportFromNode(ASTNode):
def __init__(self, filepath_token, names_tokens, import_all):
self.filepath_token = filepath_token; self.names_tokens = names_tokens; self.import_all = import_all
def __repr__(self):
names_str = "*" if self.import_all else ", ".join([t.value for t in self.names_tokens])
return f"ImportFromNode('{self.filepath_token.value}', [{names_str}])"
# --- Parser ---
# ... (Parser class remains the same as before) ...
class ParserError(Exception): pass
class Parser:
def __init__(self, tokens):
self.tokens = tokens; self.pos = 0
self.current_token = self.tokens[self.pos] if self.tokens else Token('EOF', None)
def _advance(self):
self.pos += 1; self.current_token = self.tokens[self.pos] if self.pos < len(self.tokens) else Token('EOF', None)
def _eat(self, token_type):
if self.current_token.type == token_type: self._advance()
else: raise ParserError(f"Expected {token_type} but got {self.current_token.type} ('{self.current_token.value}')")
def argument_list(self):
args = []
if self.current_token.type != 'RPAREN':
args.append(self.expression())
while self.current_token.type == 'COMMA': self._eat('COMMA'); args.append(self.expression())
return args
def dictionary_literal(self):
self._eat('LBRACE'); pairs = []
if self.current_token.type != 'RBRACE':
key_node = self.expression(); self._eat('COLON'); value_node = self.expression()
pairs.append((key_node, value_node))
while self.current_token.type == 'COMMA':
self._eat('COMMA'); key_node = self.expression(); self._eat('COLON'); value_node = self.expression()
pairs.append((key_node, value_node))
self._eat('RBRACE'); return DictionaryNode(pairs)
def atom(self):
token = self.current_token
if token.type == 'NUMBER': self._eat('NUMBER'); return NumberNode(token)
elif token.type == 'STRING': self._eat('STRING'); return StringNode(token)
elif token.type == 'TRUE': self._eat('TRUE'); return BooleanNode(token)
elif token.type == 'FALSE': self._eat('FALSE'); return BooleanNode(token)
elif token.type == 'NULL': self._eat('NULL'); return NullNode(token)
elif token.type == 'SELF': self._eat('SELF'); return SelfNode(token)
elif token.type == 'SUPER': self._eat('SUPER'); return SuperNode(token)
elif token.type == 'ID': id_token = token; self._eat('ID'); return VariableNode(id_token)
elif token.type == 'LBRACKET': return self.list_literal()
elif token.type == 'LBRACE': return self.dictionary_literal()
elif token.type == 'LPAREN': self._eat('LPAREN'); node = self.expression(); self._eat('RPAREN'); return node
else: raise ParserError(f"Invalid atom: Unexpected token {token.type} ('{token.value}')")
def list_literal(self):
self._eat('LBRACKET'); elements = []
if self.current_token.type != 'RBRACKET':
elements.append(self.expression())
while self.current_token.type == 'COMMA': self._eat('COMMA'); elements.append(self.expression())
self._eat('RBRACKET'); return ListNode(elements)
def postfix_expression(self):
node = self.atom()
while True:
if self.current_token.type == 'LPAREN': self._eat('LPAREN'); args = self.argument_list(); self._eat('RPAREN'); node = FunctionCallNode(node, args)
elif self.current_token.type == 'LBRACKET': self._eat('LBRACKET'); index_or_key_node = self.expression(); self._eat('RBRACKET'); node = IndexAccessNode(node, index_or_key_node)
elif self.current_token.type == 'DOT': self._eat('DOT'); member_token = self.current_token; self._eat('ID'); node = MemberAccessNode(node, member_token)
else: break
return node
def unary_expression(self):
if self.current_token.type == 'NOT': op_token = self.current_token; self._eat('NOT'); return UnaryOpNode(op_token, self.unary_expression())
return self.postfix_expression()
def multiplicative_expression(self):
node = self.unary_expression()
while self.current_token.type in ('MUL', 'DIV'): op_token = self.current_token; self._eat(op_token.type); node = BinOpNode(node, op_token, self.unary_expression())
return node
def additive_expression(self):
node = self.multiplicative_expression()
while self.current_token.type in ('PLUS', 'MINUS'): op_token = self.current_token; self._eat(op_token.type); node = BinOpNode(node, op_token, self.multiplicative_expression())
return node
def comparison_expression(self):
node = self.additive_expression()
while self.current_token.type in ('LT', 'GT', 'LTE', 'GTE'): op_token = self.current_token; self._eat(op_token.type); node = BinOpNode(node, op_token, self.additive_expression())
return node
def equality_expression(self):
node = self.comparison_expression()
while self.current_token.type in ('EQ', 'NEQ'): op_token = self.current_token; self._eat(op_token.type); node = BinOpNode(node, op_token, self.comparison_expression())
return node
def logical_and_expression(self):
node = self.equality_expression()
while self.current_token.type == 'AND': op_token = self.current_token; self._eat('AND'); node = BinOpNode(node, op_token, self.equality_expression())
return node
def logical_or_expression(self):
node = self.logical_and_expression()
while self.current_token.type == 'OR': op_token = self.current_token; self._eat('OR'); node = BinOpNode(node, op_token, self.logical_and_expression())
return node
def expression(self): return self.logical_or_expression()
def block(self):
self._eat('LBRACE'); statements = []
while self.current_token.type not in ('RBRACE', 'EOF'): stmt = self.statement();
if stmt: statements.append(stmt)
self._eat('RBRACE'); return BlockNode(statements)
def if_statement(self):
self._eat('IF'); self._eat('LPAREN'); condition_node = self.expression(); self._eat('RPAREN')
if_block_node = self.block(); else_block_node = None
if self.current_token.type == 'ELSE': self._eat('ELSE'); else_block_node = self.block()
return IfNode(condition_node, if_block_node, else_block_node)
def while_statement(self):
self._eat('WHILE'); self._eat('LPAREN'); condition_node = self.expression(); self._eat('RPAREN'); body_node = self.block()
return WhileNode(condition_node, body_node)
def for_statement(self):
self._eat('FOR'); var_token = self.current_token; self._eat('ID'); self._eat('IN'); iterable_node = self.expression(); body_node = self.block()
return ForInNode(var_token, iterable_node, body_node)
def return_statement(self):
self._eat('RETURN'); expr_node = None
can_start_expr = ['ID','NUMBER','STRING','TRUE','FALSE','NULL','LPAREN','LBRACKET','LBRACE','SELF','SUPER','NOT']
if self.current_token.type in can_start_expr: expr_node = self.expression()
return ReturnNode(expr_node)
def method_definition(self):
name_token=self.current_token;self._eat('ID');self._eat('LPAREN');params_tokens=[]
if self.current_token.type == 'ID':
params_tokens.append(self.current_token);self._eat('ID')
while self.current_token.type == 'COMMA': self._eat('COMMA');params_tokens.append(self.current_token);self._eat('ID')
self._eat('RPAREN');body_node=self.block();return MethodDefNode(name_token,params_tokens,body_node)
def class_definition(self):
self._eat('CLASS');name_token=self.current_token;self._eat('ID');parent_class_token=None
if self.current_token.type == 'EXTENDS': self._eat('EXTENDS');parent_class_token=self.current_token;self._eat('ID')
self._eat('LBRACE');methods=[]
while self.current_token.type != 'RBRACE' and self.current_token.type == 'ID': methods.append(self.method_definition())
self._eat('RBRACE');return ClassDefNode(name_token,parent_class_token,methods)
def try_except_statement(self):
self._eat('TRY'); try_block = self.block(); specific_except_clauses = []; generic_except_block = None
while self.current_token.type == 'EXCEPT':
self._eat('EXCEPT')
if self.current_token.type == 'LPAREN':
self._eat('LPAREN'); error_type_token = self.current_token; self._eat('ID'); self._eat('RPAREN'); block = self.block()
specific_except_clauses.append(SpecificExceptClauseNode(error_type_token, block))
else:
if generic_except_block is not None: raise ParserError("Generic 'except' block must be last.")
generic_except_block = self.block(); break
if not specific_except_clauses and not generic_except_block: raise ParserError("try must have at least one except.")
return TryExceptNode(try_block, specific_except_clauses, generic_except_block)
def function_definition(self):
self._eat('FUNCTION');name_token=self.current_token;self._eat('ID');self._eat('LPAREN');params_tokens=[]
if self.current_token.type == 'ID':
params_tokens.append(self.current_token);self._eat('ID')
while self.current_token.type == 'COMMA': self._eat('COMMA');params_tokens.append(self.current_token);self._eat('ID')
self._eat('RPAREN');body_node=self.block();return FunctionDefNode(name_token,params_tokens,body_node)
def import_statement(self):
self._eat('IMPORT');filepath_token=self.current_token
if filepath_token.type!='STRING':raise ParserError("Expected string filepath for import.")
self._eat('STRING');return ImportNode(filepath_token)
def import_from_statement(self):
self._eat('FROM');filepath_token=self.current_token
if filepath_token.type!='STRING':raise ParserError("Expected string filepath for from-import.")
self._eat('STRING');self._eat('IMPORT');names_tokens=[];import_all=False
if self.current_token.type == 'MUL': self._eat('MUL');import_all=True
elif self.current_token.type == 'ID':
names_tokens.append(self.current_token);self._eat('ID')
while self.current_token.type == 'COMMA':
self._eat('COMMA')
if self.current_token.type!='ID':raise ParserError("Expected ID after comma in from-import.")
names_tokens.append(self.current_token);self._eat('ID')
else:raise ParserError("Expected ID or '*' after 'import' in from-import.")
return ImportFromNode(filepath_token,names_tokens,import_all)
def statement(self):
token_type = self.current_token.type
if token_type=='PRINT':self._eat('PRINT');return PrintNode(self.expression())
elif token_type=='IF':return self.if_statement()
elif token_type=='WHILE':return self.while_statement()
elif token_type=='FOR':return self.for_statement()
elif token_type=='CLASS':return self.class_definition()
elif token_type=='FUNCTION':return self.function_definition()
elif token_type=='RETURN':return self.return_statement()
elif token_type=='TRY':return self.try_except_statement()
elif token_type=='BREAK':self._eat('BREAK');return BreakNode(self.current_token)
elif token_type=='CONTINUE':self._eat('CONTINUE');return ContinueNode(self.current_token)
elif token_type=='IMPORT':return self.import_statement()
elif token_type=='FROM':return self.import_from_statement()
elif token_type=='LBRACE':return self.block()
elif token_type=='EOF':return None
else:
expr_node=self.expression()
if self.current_token.type == 'ASSIGN':
self._eat('ASSIGN')
if not isinstance(expr_node,(VariableNode,IndexAccessNode,MemberAccessNode)):raise ParserError(f"Invalid assignment target: {type(expr_node).__name__}")
return AssignNode(expr_node,self.expression())
return expr_node
def program(self):
statements=[];
while self.current_token.type != 'EOF':stmt=self.statement();
if stmt:statements.append(stmt)
return statements
def parse(self):
if not self.tokens or self.current_token.type=='EOF':return[]
ast=self.program()
if self.current_token.type!='EOF':raise ParserError(f"Unexpected token {self.current_token.type} ('{self.current_token.value}') after statements.")
return ast
# --- Bytecode Opcodes ---
OP_LOAD_CONST = 0x01; OP_STORE_NAME = 0x02; OP_LOAD_NAME = 0x03
OP_BINARY_ADD = 0x04; OP_BINARY_SUBTRACT = 0x05; OP_PRINT_ITEM = 0x06
OP_POP_TOP = 0x07; OP_BINARY_MULTIPLY = 0x08; OP_BINARY_DIVIDE = 0x09
OP_COMPARE_EQ = 0x0A; OP_COMPARE_NEQ = 0x0B; OP_COMPARE_LT = 0x0C
OP_COMPARE_GT = 0x0D; OP_COMPARE_LTE = 0x0E; OP_COMPARE_GTE = 0x0F
OP_UNARY_NOT = 0x10; OP_JUMP_IF_FALSE = 0x11; OP_JUMP = 0x12
OP_MAKE_FUNCTION = 0x13 # New: Create a function object from a CodeObject
OP_CALL_FUNCTION = 0x14 # New: Arg: num_args
OP_RETURN_VALUE = 0x15 # New: Return from function call
OP_LOAD_LOCAL = 0x16 # New (alternative to LOAD_NAME for locals/params)
OP_STORE_LOCAL = 0x17 # New (alternative to STORE_NAME for locals/params)
class CompilerError(Exception): pass
class CodeObject:
def __init__(self, name="<module>", params=None): # params is a list of parameter name strings
self.name = name
self.instructions = []
self.constants = []
self.names = [] # For global names or names not covered by locals/params
self.locals = [] # For local variable names, including parameters
self.params = params if params else [] # Parameter names
def add_const(self, value):
if not isinstance(value, (int, float, str, bool, CodeObject)) and value is not None: # CodeObject can be a const
raise CompilerError(f"Cannot add type {type(value)} to constants table.")
if value not in self.constants: self.constants.append(value)
return self.constants.index(value)
def add_name(self, name): # For globals or fallback if not local/param
if name not in self.names: self.names.append(name)
return self.names.index(name)
def add_local(self, name): # For locals and parameters
if name not in self.locals: self.locals.append(name)
return self.locals.index(name)
def add_instruction(self, opcode, arg=None): self.instructions.append((opcode, arg))
def get_current_address(self): return len(self.instructions)
def patch_jump(self, idx, addr): op, _ = self.instructions[idx]; self.instructions[idx] = (op, addr)
def __repr__(self):
return (f"CodeObject(name='{self.name}', params={self.params}, instructions={len(self.instructions)} instrs, "
f"constants={len(self.constants)}, names={len(self.names)}, locals={len(self.locals)})")
class Compiler:
def __init__(self, parent_compiler=None):
self.current_code_object = None
self.loop_context_stack = []
self.parent_compiler = parent_compiler # To access outer scope's names/constants if needed for closures (future)
self.function_code_objects = [] # Store compiled function code objects here to add to main consts
def compile_program(self, ast_statements): # Renamed from compile for clarity
self.current_code_object = CodeObject(name="<main_program>")
for stmt_node in ast_statements:
self.visit(stmt_node)
if not isinstance(stmt_node, (AssignNode, PrintNode, ClassDefNode, FunctionDefNode,
IfNode, WhileNode, TryExceptNode, ReturnNode,
BreakNode, ContinueNode, ImportNode, ImportFromNode, ForInNode)):
if isinstance(stmt_node, (NumberNode, StringNode, BooleanNode, NullNode,
VariableNode, BinOpNode, UnaryOpNode, FunctionCallNode,
ListNode, DictionaryNode, IndexAccessNode, MemberAccessNode, SelfNode, SuperNode)):
self.current_code_object.add_instruction(OP_POP_TOP)
# Add compiled function code objects to the main code object's constants
for func_co in self.function_code_objects:
self.current_code_object.add_const(func_co)
return self.current_code_object
def compile_function_body(self, name, params_tokens, body_node):
# Create a new compiler context for the function
# For simplicity, the new CodeObject won't automatically inherit names/constants pools
# from the parent compiler in this version (no closures yet).
func_code_object = CodeObject(name=name, params=[p.value for p in params_tokens])
# Temporarily switch current_code_object
outer_co = self.current_code_object
self.current_code_object = func_code_object
# Parameters are treated as the first locals
for param_token in params_tokens:
self.current_code_object.add_local(param_token.value)
# STORE_LOCAL for params will be handled by VM during CALL_FUNCTION setup
self.visit(body_node) # Compile the function's block
# Ensure function implicitly returns Null if no explicit return
if not self.current_code_object.instructions or \
self.current_code_object.instructions[-1][0] != OP_RETURN_VALUE:
null_const_idx = self.current_code_object.add_const(None)
self.current_code_object.add_instruction(OP_LOAD_CONST, null_const_idx)
self.current_code_object.add_instruction(OP_RETURN_VALUE)
# Restore outer code object
compiled_func_co = self.current_code_object
self.current_code_object = outer_co
return compiled_func_co
def visit(self, node):
method_name = f'visit_{type(node).__name__}'; visitor = getattr(self, method_name, self.unsupported_node)
return visitor(node)
def unsupported_node(self,node):raise CompilerError(f"Compiler: Unsupported AST for compilation: {type(node).__name__}")
def visit_NumberNode(self,node):const_idx=self.current_code_object.add_const(node.value);self.current_code_object.add_instruction(OP_LOAD_CONST,const_idx)
def visit_StringNode(self,node):const_idx=self.current_code_object.add_const(node.value);self.current_code_object.add_instruction(OP_LOAD_CONST,const_idx)
def visit_BooleanNode(self,node):const_idx=self.current_code_object.add_const(node.value);self.current_code_object.add_instruction(OP_LOAD_CONST,const_idx)
def visit_NullNode(self,node):const_idx=self.current_code_object.add_const(None);self.current_code_object.add_instruction(OP_LOAD_CONST,const_idx)
def visit_VariableNode(self,node): # Could be local, param, or global
# Simple: try local/param first, then global name
# More complex: need proper scope analysis during compilation
var_name = node.name
if var_name in self.current_code_object.locals: # Check if it's a known local/param
local_idx = self.current_code_object.locals.index(var_name)
self.current_code_object.add_instruction(OP_LOAD_LOCAL, local_idx)
else: # Assume global if not in current function's locals/params
name_idx = self.current_code_object.add_name(var_name) # Add to global names if new
self.current_code_object.add_instruction(OP_LOAD_NAME, name_idx)
def visit_AssignNode(self,node):
if not isinstance(node.target_node,VariableNode):raise CompilerError("Compiler: Can only assign to simple vars.")
self.visit(node.value_node) # Value is on stack
var_name = node.target_node.name
# Determine if local/param or global store
# For now, if it's a parameter or already a local, it's local.
# Otherwise, treat as global. This is a simplification.
# True local variable declaration would be needed for better distinction.
if var_name in self.current_code_object.params or var_name in self.current_code_object.locals:
local_idx = self.current_code_object.add_local(var_name) # Ensure it's in locals
self.current_code_object.add_instruction(OP_STORE_LOCAL, local_idx)
else:
name_idx = self.current_code_object.add_name(var_name)
self.current_code_object.add_instruction(OP_STORE_NAME, name_idx)
def visit_UnaryOpNode(self,node):
self.visit(node.expr_node)
if node.op_token.type=='NOT':self.current_code_object.add_instruction(OP_UNARY_NOT)
else:raise CompilerError(f"Compiler: Unsupported unary op: {node.op_token.type}")
def visit_BinOpNode(self,node):
self.visit(node.left);self.visit(node.right)
op_map={'PLUS':OP_BINARY_ADD,'MINUS':OP_BINARY_SUBTRACT,'MUL':OP_BINARY_MULTIPLY,'DIV':OP_BINARY_DIVIDE,
'EQ':OP_COMPARE_EQ,'NEQ':OP_COMPARE_NEQ,'LT':OP_COMPARE_LT,'GT':OP_COMPARE_GT,
'LTE':OP_COMPARE_LTE,'GTE':OP_COMPARE_GTE}
if node.op_token.type in op_map:self.current_code_object.add_instruction(op_map[node.op_token.type])
elif node.op_token.type in ('AND','OR'): # AND/OR require jumps, complex for initial compiler
raise CompilerError(f"Compiler: Logical '{node.op_token.type}' not yet fully supported (require jumps).")
else:raise CompilerError(f"Compiler: Unsupported binary op: {node.op_token.type}")
def visit_PrintNode(self,node):self.visit(node.expr_node);self.current_code_object.add_instruction(OP_PRINT_ITEM)
def visit_BlockNode(self,node): # Compile statements in a block
for stmt in node.statements:
self.visit(stmt)
# Pop result of expression statements within blocks if they are not specific statement types
# This logic might still need refinement based on what expression statements are allowed.
if not isinstance(stmt, (AssignNode, PrintNode, IfNode, WhileNode, ReturnNode, BreakNode, ContinueNode)):
if isinstance(stmt, (NumberNode, StringNode, BooleanNode, NullNode, VariableNode, BinOpNode, UnaryOpNode, FunctionCallNode)):
self.current_code_object.add_instruction(OP_POP_TOP)
def visit_FunctionDefNode(self, node):
func_name = node.name_token.value
param_names = [p.value for p in node.params_tokens]
# Compile function body into its own CodeObject
# For simplicity, use a new Compiler instance or manage state carefully
# Let's compile it within the current compiler by switching context
func_co = self.compile_function_body(func_name, node.params_tokens, node.body_node)
# Add this function's code object as a constant in the current (outer) code object
# Or, if compiling the main program, store it in a separate list to be added later.
# Let's use self.function_code_objects for the main compiler instance.
# If this is a nested function compilation, this logic would be more complex.
# For now, assume this visit_FunctionDefNode is called by the top-level Compiler.
if self.parent_compiler is None: # Top-level compiler
self.function_code_objects.append(func_co)
const_idx = self.current_code_object.add_const(func_co) # Store CodeObject itself
else: # This would be for nested functions, more complex, not handled yet.
raise CompilerError("Nested function compilation not fully supported yet.")
self.current_code_object.add_instruction(OP_MAKE_FUNCTION, const_idx)
# Store the created function object in a variable
name_idx = self.current_code_object.add_name(func_name)
self.current_code_object.add_instruction(OP_STORE_NAME, name_idx)
def visit_ReturnNode(self, node):
if node.expr_node:
self.visit(node.expr_node) # Value to return is on stack
else: # Bare return -> return Null
null_const_idx = self.current_code_object.add_const(None)
self.current_code_object.add_instruction(OP_LOAD_CONST, null_const_idx)
self.current_code_object.add_instruction(OP_RETURN_VALUE)
def visit_FunctionCallNode(self, node):
# Compile args first, pushed onto stack right-to-left or left-to-right
# Python pushes left-to-right. Let's do that.
for arg_node in node.arg_nodes:
self.visit(arg_node)
# Then compile the callable expression (e.g., function name)
self.visit(node.callable_expr) # Callable object will be on top of stack
self.current_code_object.add_instruction(OP_CALL_FUNCTION, len(node.arg_nodes))
def visit_IfNode(self, node): # ... (no change) ...
self.visit(node.condition); jump_if_false_idx = self.current_code_object.get_current_address()
self.current_code_object.add_instruction(OP_JUMP_IF_FALSE, None)
self.visit(node.if_block)
if node.else_block:
jump_over_else_idx = self.current_code_object.get_current_address(); self.current_code_object.add_instruction(OP_JUMP, None)
else_start_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(jump_if_false_idx, else_start_addr)
self.visit(node.else_block); end_if_addr = self.current_code_object.get_current_address()
self.current_code_object.patch_jump(jump_over_else_idx, end_if_addr)
else: after_if_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(jump_if_false_idx, after_if_addr)
def visit_WhileNode(self, node): # ... (no change) ...
loop_start_addr = self.current_code_object.get_current_address(); break_patches, continue_patches = [], []
self.loop_context_stack.append({'break': break_patches, 'continue_target': loop_start_addr})
self.visit(node.condition_node); jump_if_false_idx = self.current_code_object.get_current_address()
self.current_code_object.add_instruction(OP_JUMP_IF_FALSE, None)
self.visit(node.body_node); self.current_code_object.add_instruction(OP_JUMP, loop_start_addr)
after_loop_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(jump_if_false_idx, after_loop_addr)
for break_idx in break_patches: self.current_code_object.patch_jump(break_idx, after_loop_addr)
self.loop_context_stack.pop()
def visit_BreakNode(self, node): # ... (no change) ...
if not self.loop_context_stack: raise CompilerError("'break' outside loop")
break_jump_idx = self.current_code_object.get_current_address(); self.current_code_object.add_instruction(OP_JUMP, None)
self.loop_context_stack[-1]['break'].append(break_jump_idx)
def visit_ContinueNode(self, node): # ... (no change) ...
if not self.loop_context_stack: raise CompilerError("'continue' outside loop")
continue_target = self.loop_context_stack[-1]['continue_target']; self.current_code_object.add_instruction(OP_JUMP, continue_target)
# --- Virtual Machine (VM) ---
class VirtualMachineError(Exception): pass
class Frame: # New: For call stack
def __init__(self, code_obj, prev_frame=None, base_sp=0):
self.code_obj = code_obj # CodeObject being executed
self.ip = 0 # Instruction pointer for this frame
self.prev_frame = prev_frame # Previous frame (caller)
self.locals = {} # Local variables and parameters for this frame
# self.operand_stack_base = base_sp # If operand stack is shared and sliced
def __repr__(self):
return f"<Frame for {self.code_obj.name} at IP {self.ip}>"
class MiniPyVMFunction: # New: Runtime representation of a compiled function
def __init__(self, name, code_obj): # code_obj is the CodeObject for the function body
self.name = name
self.code_obj = code_obj
# For closures, would store defining_environment here
def __repr__(self): return f"<VMFunction {self.name}>"
class VirtualMachine:
def __init__(self):
self.stack = [] # Operand stack
self.frames = [] # Call stack (list of Frame objects)
self.current_frame = None
self.globals = {} # Global environment (for STORE_NAME, LOAD_NAME fallback)
def push_frame(self, code_obj):
# base_sp = len(self.stack) # If using operand stack for locals
frame = Frame(code_obj, prev_frame=self.current_frame) #, base_sp=base_sp)
self.frames.append(frame)
self.current_frame = frame
# print(f"VM Pushed Frame: {frame}, Call Stack Depth: {len(self.frames)}")
def pop_frame(self):
if not self.frames:
raise VirtualMachineError("Cannot pop frame from empty call stack.")
frame = self.frames.pop()
self.current_frame = self.frames[-1] if self.frames else None
# print(f"VM Popped Frame: {frame}, Call Stack Depth: {len(self.frames)}")
return frame # Return popped frame if needed (e.g. for its operand_stack_base)
def run(self, top_level_code_obj):
self.globals = {} # Initialize global environment for this run
self.stack = []
self.frames = []
self.push_frame(top_level_code_obj) # Initial frame for the main script/module
# print(f"VM Running Top Level Code: {top_level_code_obj}")
while self.current_frame: # Loop as long as there are frames on call stack
code_obj = self.current_frame.code_obj
ip = self.current_frame.ip
if ip >= len(code_obj.instructions): # End of current frame's code
# Implicit return Null if we fall off end of function without OP_RETURN_VALUE
# This should ideally be handled by compiler adding explicit return.
# For now, if it's not the initial frame, pop it.
if self.current_frame.prev_frame: # If it's a function call frame
self.stack.append(None) # Function implicitly returns Null
self.pop_frame() # This also handles restoring IP of caller
# The pushed Null will be on caller's stack.
continue
else: # End of main program
break
opcode, arg = code_obj.instructions[ip]
self.current_frame.ip += 1
# print(f"VM: Frame={self.current_frame.code_obj.name}, IP={ip}, Op={opcode}, Arg={arg}, Stack={self.stack}, Locals={self.current_frame.locals if self.current_frame else {}}")
if opcode == OP_LOAD_CONST: self.stack.append(code_obj.constants[arg])
elif opcode == OP_STORE_NAME: # Stores in global environment for now
val = self.stack.pop()
self.globals[code_obj.names[arg]] = val
elif opcode == OP_LOAD_NAME: # Loads from global for now
name = code_obj.names[arg]
if name in self.current_frame.locals : # Try local first (includes params)
val = self.current_frame.locals[name]
elif name in self.globals:
val = self.globals[name]
else: raise VirtualMachineError(f"NameError: '{name}' not defined")
self.stack.append(val)
elif opcode == OP_STORE_LOCAL: # New
val = self.stack.pop()
local_name = self.current_frame.code_obj.locals[arg] # Get name from CodeObject.locals
self.current_frame.locals[local_name] = val
elif opcode == OP_LOAD_LOCAL: # New
local_name = self.current_frame.code_obj.locals[arg]
if local_name not in self.current_frame.locals:
raise VirtualMachineError(f"LocalVariableError: local '{local_name}' referenced before assignment")
self.stack.append(self.current_frame.locals[local_name])
elif opcode == OP_BINARY_ADD:
r, l = self.stack.pop(), self.stack.pop()
if isinstance(l, str) and isinstance(r, str): self.stack.append(l + r)
elif isinstance(l, (int, float)) and isinstance(r, (int, float)): self.stack.append(l + r)
else: raise VirtualMachineError(f"TypeError for +: '{type(l).__name__}' and '{type(r).__name__}'")
elif opcode == OP_BINARY_SUBTRACT: r,l=self.stack.pop(),self.stack.pop(); self.stack.append(l - r)
elif opcode == OP_BINARY_MULTIPLY: r,l=self.stack.pop(),self.stack.pop(); self.stack.append(l * r)
elif opcode == OP_BINARY_DIVIDE:
r,l=self.stack.pop(),self.stack.pop()
if not isinstance(r,(int,float)) or r==0 : raise VirtualMachineError("ZeroDivisionError or invalid divisor")
if not isinstance(l,(int,float)): raise VirtualMachineError("Invalid dividend")
self.stack.append(l/r)
elif opcode == OP_PRINT_ITEM:
val=self.stack.pop()
if val is None: print("Null")
elif isinstance(val,bool): print("True" if val else "False")
elif isinstance(val, MiniPyVMFunction): print(f"<VMFunction {val.name}>") # Print for function objects
else: print(val)
elif opcode == OP_POP_TOP: self.stack.pop()
elif opcode == OP_UNARY_NOT: self.stack.append(not bool(self.stack.pop()))
elif opcode == OP_COMPARE_EQ: r,l=self.stack.pop(),self.stack.pop(); self.stack.append(l == r)
# ... other comparison opcodes ...
elif opcode == OP_COMPARE_NEQ: r,l=self.stack.pop(),self.stack.pop(); self.stack.append(l != r)
elif opcode == OP_COMPARE_LT: r,l=self.stack.pop(),self.stack.pop(); self.stack.append(l < r)
elif opcode == OP_COMPARE_GT: r,l=self.stack.pop(),self.stack.pop(); self.stack.append(l > r)
elif opcode == OP_COMPARE_LTE: r,l=self.stack.pop(),self.stack.pop(); self.stack.append(l <= r)
elif opcode == OP_COMPARE_GTE: r,l=self.stack.pop(),self.stack.pop(); self.stack.append(l >= r)
elif opcode == OP_JUMP: self.current_frame.ip = arg # Jump is absolute within current code_obj
elif opcode == OP_JUMP_IF_FALSE:
condition = self.stack.pop()
if not bool(condition): self.current_frame.ip = arg
elif opcode == OP_MAKE_FUNCTION: # arg = index of CodeObject in constants
func_code_obj = code_obj.constants[arg] # The CodeObject for the function
# For closures, would capture current environment here
vm_func = MiniPyVMFunction(func_code_obj.name, func_code_obj)
self.stack.append(vm_func)
elif opcode == OP_CALL_FUNCTION: # arg = num_args
num_args = arg
args_on_stack = []
for _ in range(num_args): args_on_stack.insert(0, self.stack.pop()) # Pop in reverse order
func_obj = self.stack.pop() # Should be MiniPyVMFunction
if not isinstance(func_obj, MiniPyVMFunction):
raise VirtualMachineError(f"TypeError: '{type(func_obj).__name__}' object is not callable.")
# Parameter vs Argument count check
if len(args_on_stack) != len(func_obj.code_obj.params):
raise VirtualMachineError(f"TypeError: {func_obj.name}() takes {len(func_obj.code_obj.params)} arguments but {len(args_on_stack)} were given.")
# Create and push new frame
self.push_frame(func_obj.code_obj)
# Initialize parameters as locals in the new frame
for i, param_name in enumerate(func_obj.code_obj.params):
# param_name is a string. Add to frame's locals.
# self.current_frame.code_obj.add_local(param_name) # Ensure param is in locals list if not already
self.current_frame.locals[param_name] = args_on_stack[i]
elif opcode == OP_RETURN_VALUE:
return_value = self.stack.pop() if self.stack else None # Function might not have value on stack if just 'return;'
self.pop_frame() # Pop current function's frame
if self.current_frame: # If there's a calling frame
self.stack.append(return_value) # Push return value onto caller's stack
else: # Returning from top-level script
if self.stack: self.stack.pop() # Clear any leftover from main script
self.stack.append(return_value) # Final result of script
# print(f"VM Final return from top: {return_value}")
# Fall through to end of loop
else: raise VirtualMachineError(f"Unknown opcode: {opcode}")
# print(f"VM Finished. Final Stack: {self.stack}, Globals: {self.globals}")
return self.stack.pop() if self.stack else None
# --- Interpreter (Direct AST Execution) ---
# ... (Interpreter class definition and its methods remain the same as previous version) ...
class InterpreterError(Exception):
def __init__(self, message, error_type=None):
super().__init__(message); self.error_type = error_type if error_type else "Error"; self.message = message
def __str__(self): return f"{self.error_type}: {self.message}"
class ReturnSignal(Exception):
def __init__(self, value): self.value = value
class BreakSignal(Exception): pass
class ContinueSignal(Exception): pass
def _create_interpreter_error(message, error_type_str): return InterpreterError(message, error_type=error_type_str)
# ... (All built-in function definitions remain the same) ...
def builtin_len(args):
if len(args) != 1: raise _create_interpreter_error("len() takes 1 arg", E_TYPE_ERROR)
arg = args[0]
if isinstance(arg, (str,list,dict)): return len(arg)
raise _create_interpreter_error(f"object of type '{type(arg).__name__}' has no len()", E_TYPE_ERROR)
def builtin_type(args):
if len(args) != 1: raise _create_interpreter_error("type() takes 1 arg", E_TYPE_ERROR)
val = args[0]
if isinstance(val, (int,float)): return "number"
if isinstance(val, str): return "string";
if isinstance(val, bool): return "boolean";
if val is None: return "null"
if isinstance(val, list): return "list"
if isinstance(val, dict): return "dictionary"
if isinstance(val, MiniPyInstance): return f"instance:{val.klass.name}"
if isinstance(val, MiniPyClass): return "class"
if isinstance(val, BoundMethod): return "method"
if isinstance(val, MiniPyFunction): return "function" # Interpreter's function object
if isinstance(val, MiniPyVMFunction): return "vm_function" # VM's function object
if isinstance(val, MiniModuleNamespace): return "module"
if callable(val) and val in BUILTIN_FUNCTIONS_DEF.values(): return "builtin_function"
return "unknown"
def builtin_str(args, interpreter_instance):
if len(args) != 1: raise _create_interpreter_error("str() takes 1 arg", E_TYPE_ERROR)
val = args[0]
if isinstance(val, MiniPyInstance):
str_method_def = val.klass.find_method("__str__")
if str_method_def:
try:
if str_method_def.params_tokens:
raise _create_interpreter_error(f"{val.klass.name}.__str__() should take 0 arguments (besides self)", E_TYPE_ERROR)
str_val = interpreter_instance._call_method_or_function(str_method_def, val, val.klass, [], is_init=False, for_dunder_str=True)
if not isinstance(str_val, str):
raise _create_interpreter_error(f"__str__ method of class {val.klass.name} must return a string, not {type(str_val).__name__}", E_TYPE_ERROR)
return str_val
except ReturnSignal as rs_str:
if not isinstance(rs_str.value, str):
raise _create_interpreter_error(f"__str__ method of class {val.klass.name} must return a string, not {type(rs_str.value).__name__}", E_TYPE_ERROR)
return rs_str.value
if val is None: return "Null";
if isinstance(val, bool): return "True" if val else "False"
if isinstance(val, MiniModuleNamespace): return f"<module '{val.name}'>"
if isinstance(val, MiniPyVMFunction): return f"<VMFunction {val.name}>"
if isinstance(val, dict):
items_str = []
for k, v_val in val.items():
k_py_val = k
v_py_val = v_val
k_mini_str = builtin_str([k_py_val], interpreter_instance) if not isinstance(k_py_val, str) else repr(k_py_val)
v_mini_str = builtin_str([v_py_val], interpreter_instance)
items_str.append(f"{k_mini_str}: {v_mini_str}")
return "{" + ", ".join(items_str) + "}"
return str(val)
def builtin_read_file(args):
if len(args) != 1: raise _create_interpreter_error("read_file() takes 1 arg", E_TYPE_ERROR)
filepath = args[0];
if not isinstance(filepath, str): raise _create_interpreter_error("filepath must be str", E_TYPE_ERROR)
try:
with open(filepath, 'r', encoding='utf-8') as f: return f.read()
except FileNotFoundError: raise _create_interpreter_error(f"File not found '{filepath}'", E_IO_ERROR)
except Exception as e: raise _create_interpreter_error(f"Could not read file '{filepath}': {e}", E_IO_ERROR)
def builtin_write_file(args):
if len(args) != 2: raise _create_interpreter_error("write_file() takes 2 args", E_TYPE_ERROR)
filepath, content = args[0], args[1]
if not isinstance(filepath, str): raise _create_interpreter_error("filepath must be str", E_TYPE_ERROR)
if not isinstance(content, str): raise _create_interpreter_error("content must be str", E_TYPE_ERROR)
try:
with open(filepath, 'w', encoding='utf-8') as f: f.write(content)
return None
except Exception as e: raise _create_interpreter_error(f"Could not write to file '{filepath}': {e}", E_IO_ERROR)
def builtin_random(args):
num_args = len(args)
if num_args == 0: return random.random()
elif num_args == 1:
max_val = args[0]
if not isinstance(max_val, int): raise _create_interpreter_error("random(max) requires int", E_TYPE_ERROR)
if max_val <= 0: raise _create_interpreter_error("random(max) requires max > 0", E_VALUE_ERROR)
return random.randrange(max_val)
elif num_args == 2:
min_val, max_val = args[0], args[1]
if not (isinstance(min_val, int) and isinstance(max_val, int)): raise _create_interpreter_error("random(min,max) requires ints", E_TYPE_ERROR)
if min_val > max_val: raise _create_interpreter_error("random(min,max) requires min <= max", E_VALUE_ERROR)
return random.randint(min_val, max_val)
else: raise _create_interpreter_error(f"random() takes 0-2 args, got {num_args}", E_TYPE_ERROR)
def builtin_eval_string(args, interpreter_instance, current_mode_is_compiler):
if current_mode_is_compiler: raise _create_interpreter_error("eval_string() is not supported in compiled mode.", "Error")
if len(args) != 1: raise _create_interpreter_error("eval_string() takes 1 argument (Mini code string).", E_TYPE_ERROR)
code_string = args[0]
if not isinstance(code_string, str): raise _create_interpreter_error("arg to eval_string() must be a string.", E_TYPE_ERROR)
try:
eval_tokens = tokenize(code_string); eval_parser = Parser(eval_tokens)
eval_ast_statements = eval_parser.parse()
return interpreter_instance.interpret(eval_ast_statements, is_eval_call=True)
except (LexerError, ParserError) as e: raise _create_interpreter_error(f"Error in eval_string (lex/parse): {e}", E_SYNTAX_ERROR)
except InterpreterError as ie: raise ie
except ReturnSignal as rs: return rs.value
def builtin_time(args):
if len(args) != 0: raise _create_interpreter_error("time() takes 0 arguments.", E_TYPE_ERROR)
return time.time()
def builtin_input(args):
prompt = ""
if len(args) == 1:
prompt_arg = args[0]
if not isinstance(prompt_arg, str):
raise _create_interpreter_error("prompt for input() must be a string.", E_TYPE_ERROR)
prompt = prompt_arg
elif len(args) > 1:
raise _create_interpreter_error(f"input() takes 0 or 1 arguments, but {len(args)} were given.", E_TYPE_ERROR)
try: return input(prompt)
except EOFError: return Null
except Exception as e: raise _create_interpreter_error(f"Error during input(): {e}", E_IO_ERROR)
def builtin_number(args):
if len(args) != 1: raise _create_interpreter_error("number() takes exactly one argument.", E_TYPE_ERROR)
val = args[0]
if isinstance(val, (int, float)): return val
if isinstance(val, str):
try: return int(val)
except ValueError:
try: return float(val)
except ValueError: raise _create_interpreter_error(f"could not convert string to number: '{val}'", E_VALUE_ERROR)
raise _create_interpreter_error(f"number() argument must be a string or number, not {type(val).__name__}", E_TYPE_ERROR)
def builtin_is_number(args):
if len(args) != 1: raise _create_interpreter_error("is_number() takes 1 argument.", E_TYPE_ERROR)
return isinstance(args[0], (int, float))
def builtin_is_string(args):
if len(args) != 1: raise _create_interpreter_error("is_string() takes 1 argument.", E_TYPE_ERROR)
return isinstance(args[0], str)
def builtin_is_list(args):
if len(args) != 1: raise _create_interpreter_error("is_list() takes 1 argument.", E_TYPE_ERROR)
return isinstance(args[0], list)
def builtin_is_null(args):
if len(args) != 1: raise _create_interpreter_error("is_null() takes 1 argument.", E_TYPE_ERROR)
return args[0] is None
def builtin_abs(args):
if len(args) != 1: raise _create_interpreter_error("abs() takes 1 argument.", E_TYPE_ERROR)
val = args[0]
if not isinstance(val, (int, float)): raise _create_interpreter_error(f"abs() requires a number, not {type(val).__name__}.", E_TYPE_ERROR)
return abs(val)
def builtin_append(args):
if len(args) != 2: raise _create_interpreter_error("append() takes 2 arguments (list, item).", E_TYPE_ERROR)
target_list, item = args[0], args[1]
if not isinstance(target_list, list): raise _create_interpreter_error(f"append() requires a list as first argument, not {type(target_list).__name__}.", E_TYPE_ERROR)
target_list.append(item); return None
def builtin_pop(args):
if len(args) != 1: raise _create_interpreter_error("pop() takes 1 argument (list).", E_TYPE_ERROR)
target_list = args[0]
if not isinstance(target_list, list): raise _create_interpreter_error(f"pop() requires a list, not {type(target_list).__name__}.", E_TYPE_ERROR)
if not target_list: raise _create_interpreter_error("pop from empty list.", E_INDEX_ERROR)
return target_list.pop()
def builtin_range(args):
num_args = len(args); start, stop, step = 0, 0, 1
if num_args == 1: stop = args[0]
elif num_args == 2: start, stop = args[0], args[1]
elif num_args == 3: start, stop, step = args[0], args[1], args[2]
else: raise _create_interpreter_error(f"range() takes 1 to 3 arguments, but {num_args} were given.", E_TYPE_ERROR)
if not all(isinstance(x, int) for x in (start, stop, step)):
raise _create_interpreter_error("range() arguments must be integers.", E_TYPE_ERROR)
if step == 0: raise _create_interpreter_error("range() step argument cannot be zero.", E_VALUE_ERROR)
return list(range(start, stop, step))
def _thread_target_wrapper(interpreter_for_thread, func_to_call_obj, args_for_func, mini_thread_obj_ref):
try:
result = interpreter_for_thread._call_method_or_function(
func_to_call_obj.func_def_node, None, None, args_for_func, is_standalone_func=True )
mini_thread_obj_ref.result = result
except InterpreterError as e: mini_thread_obj_ref.error = e
except Exception as e: mini_thread_obj_ref.error = _create_interpreter_error(f"Python exception in thread: {e}", "ThreadError")
finally: mini_thread_obj_ref.is_done = True
class MiniPyThread:
def __init__(self, py_thread): self.py_thread = py_thread; self.result = None; self.error = None; self.is_done = False
def __repr__(self): return f"<MiniPyThread name='{self.py_thread.name}' alive={self.py_thread.is_alive()}>"
_active_mini_threads_global_ref = []
def builtin_start_thread(args, parent_interpreter_instance, current_mode_is_compiler):
if current_mode_is_compiler: raise _create_interpreter_error("start_thread() not in compiled mode.", "CompilerError")
if len(args) != 2: raise _create_interpreter_error("start_thread() takes 2 args: func_name (str), args_list (list).", E_TYPE_ERROR)
func_name_str, mini_args_list = args[0], args[1]
if not isinstance(func_name_str, str): raise _create_interpreter_error("First arg to start_thread() must be str.", E_TYPE_ERROR)
if not isinstance(mini_args_list, list): raise _create_interpreter_error("Second arg to start_thread() must be list.", E_TYPE_ERROR)
func_to_call = parent_interpreter_instance.environment.get(func_name_str)
if not isinstance(func_to_call, MiniPyFunction): raise _create_interpreter_error(f"'{func_name_str}' is not a defined Mini function.", E_NAME_ERROR)
# Create new interpreter for thread, with a shallow copy of parent's globals (user-defined part)
thread_interpreter = Interpreter(
is_module_execution=parent_interpreter_instance.is_module_execution,
current_script_path=parent_interpreter_instance.current_script_path,
initial_environment=copy.copy(parent_interpreter_instance.environment) # Shallow copy
)
# Ensure thread_interpreter's builtins are also there (in case copy didn't get them or they are instance methods)
for name, func in BUILTIN_FUNCTIONS_DEF.items():
if name not in thread_interpreter.environment: thread_interpreter.environment[name] = func
py_thread = threading.Thread(target=_thread_target_wrapper, args=(thread_interpreter, func_to_call, mini_args_list, None))
mini_thread_obj = MiniPyThread(py_thread)
py_thread._args = (thread_interpreter, func_to_call, mini_args_list, mini_thread_obj) # Update args with the handle
py_thread.daemon = True; py_thread.start(); _active_mini_threads_global_ref.append(mini_thread_obj)
return mini_thread_obj
def builtin_join_thread(args):
if len(args) < 1 or len(args) > 2: raise _create_interpreter_error("join_thread() takes 1 or 2 args: thread_obj, [timeout].", E_TYPE_ERROR)
thread_obj = args[0]
if not isinstance(thread_obj, MiniPyThread): raise _create_interpreter_error("First arg to join_thread() must be thread object.", E_TYPE_ERROR)
timeout = None
if len(args) == 2:
timeout_val = args[1]
if not isinstance(timeout_val, (int, float)): raise _create_interpreter_error("Timeout for join_thread() must be number.", E_TYPE_ERROR)
if timeout_val < 0: raise _create_interpreter_error("Timeout for join_thread() cannot be negative.", E_VALUE_ERROR)
timeout = timeout_val
thread_obj.py_thread.join(timeout=timeout)
if thread_obj.error: raise thread_obj.error
return thread_obj.result
class MiniLock:
def __init__(self): self._lock = threading.Lock(); self.acquired_by_thread_id = None # For debug/info
def acquire(self): # Mini's acquire will be blocking without timeout for simplicity
acquired = self._lock.acquire(blocking=True)
if acquired: self.acquired_by_thread_id = threading.get_ident()
return acquired # Should be True if blocking
def release(self):
try: self._lock.release(); self.acquired_by_thread_id = None
except RuntimeError as e: raise _create_interpreter_error(f"Cannot release unacquired or differently owned lock: {e}", "RuntimeError")
def __repr__(self): return f"<MiniLock acquired_by_thread={self.acquired_by_thread_id}>"
def builtin_Lock(args):
if len(args)!=0: raise _create_interpreter_error("Lock() takes 0 args.", E_TYPE_ERROR)
return MiniLock()
BUILTIN_FUNCTIONS_DEF = {
"len": builtin_len, "type": builtin_type, "str": builtin_str,
"read_file": builtin_read_file, "write_file": builtin_write_file,
"random": builtin_random, "eval_string": builtin_eval_string,
"time": builtin_time, "input": builtin_input, "number": builtin_number,
"is_number": builtin_is_number, "is_string": builtin_is_string,
"is_list": builtin_is_list, "is_null": builtin_is_null,
"abs": builtin_abs, "append": builtin_append, "pop": builtin_pop,
"range": builtin_range, "start_thread": builtin_start_thread,
"join_thread": builtin_join_thread, "Lock": builtin_Lock,
}
class Interpreter: # ... (Full Interpreter definition as before, with visit_ methods) ...
def __init__(self, is_module_execution=False, current_script_path=None, initial_environment=None):
self.environment = {}
if initial_environment is not None:
# Shallow copy the provided environment
# This means built-ins and user-defined functions/classes from parent are shared by reference initially
self.environment.update(initial_environment)
# Ensure all built-ins are present, potentially overwriting if initial_environment had them differently
# (though unlikely for BUILTIN_FUNCTIONS_DEF values themselves)
for name, func in BUILTIN_FUNCTIONS_DEF.items():
self.environment[name] = func
self.current_instance_for_self = None
self.current_method_defining_class = None
self.is_in_method_call = False
self.loop_depth = 0
self.is_module_execution = is_module_execution
self.current_script_path = current_script_path
self.is_compiler_mode = False
def _resolve_module_path(self, relative_path):
if os.path.isabs(relative_path): return relative_path
base_dir = os.getcwd()
if self.current_script_path: base_dir = os.path.dirname(self.current_script_path)
path = os.path.join(base_dir, relative_path)
if not os.path.splitext(path)[1]:
path_with_ext = path + ".mini"
if os.path.exists(path_with_ext): return os.path.abspath(path_with_ext)
return os.path.abspath(path)
def _load_module(self, filepath_str):
abs_filepath = self._resolve_module_path(filepath_str)
if abs_filepath in _LOADED_MODULES_CACHE: return _LOADED_MODULES_CACHE[abs_filepath]
try:
with open(abs_filepath, 'r', encoding='utf-8') as f: module_code = f.read()
except FileNotFoundError: raise _create_interpreter_error(f"No module named '{filepath_str}' (resolved to '{abs_filepath}')", E_MODULE_NOT_FOUND_ERROR)
except Exception as e: raise _create_interpreter_error(f"Could not read module '{filepath_str}': {e}", E_IO_ERROR)
module_interpreter = Interpreter(is_module_execution=True, current_script_path=abs_filepath) # Fresh environment
module_name = os.path.splitext(os.path.basename(abs_filepath))[0]
module_namespace_obj = MiniModuleNamespace(module_name, module_interpreter.environment)
_LOADED_MODULES_CACHE[abs_filepath] = module_namespace_obj
try:
module_tokens = tokenize(module_code); module_parser = Parser(module_tokens)
module_ast = module_parser.parse(); module_interpreter.interpret(module_ast)
except Exception as e:
if abs_filepath in _LOADED_MODULES_CACHE: del _LOADED_MODULES_CACHE[abs_filepath]
if isinstance(e, InterpreterError): raise
raise _create_interpreter_error(f"Error during module '{module_name}' execution: {e}", "ModuleExecutionError")
return module_namespace_obj
def visit_ImportNode(self, node):
if _CURRENTLY_USING_COMPILER: raise _create_interpreter_error("Modules/imports not supported in compiled mode yet.", "CompilerError")
filepath_str = node.filepath_token.value
module_obj = self._load_module(filepath_str)
module_name = module_obj.name
self.environment[module_name] = module_obj
return None
def visit_ImportFromNode(self, node):
if _CURRENTLY_USING_COMPILER: raise _create_interpreter_error("Modules/imports not supported in compiled mode yet.", "CompilerError")
filepath_str = node.filepath_token.value
module_obj = self._load_module(filepath_str)
if node.import_all:
for name, value in module_obj._environment.items():
if not name.startswith("__") and name not in BUILTIN_FUNCTIONS_DEF:
self.environment[name] = value
else:
for name_token in node.names_tokens:
name_to_import = name_token.value
if name_to_import in module_obj._environment:
self.environment[name_to_import] = module_obj._environment[name_to_import]
else:
raise _create_interpreter_error(f"cannot import name '{name_to_import}' from module '{module_obj.name}'", E_IMPORT_ERROR)
return None
def visit(self, node):
method_name = f'visit_{type(node).__name__}'; visitor = getattr(self, method_name, self.generic_visit)
return visitor(node)
def generic_visit(self, node): raise _create_interpreter_error(f"No visit method for {type(node).__name__}", "InternalError")
def visit_NumberNode(self, node): return node.value
def visit_StringNode(self, node): return node.value
def visit_BooleanNode(self, node): return node.value
def visit_NullNode(self, node): return node.value
def visit_ListNode(self, node): return [self.visit(elem_node) for elem_node in node.elements]
def visit_DictionaryNode(self, node):
if _CURRENTLY_USING_COMPILER: raise _create_interpreter_error("Dictionaries not supported in compiled mode yet.", "CompilerError")
the_dict = {}
for key_node, value_node in node.pairs:
key = self.visit(key_node)
if not isinstance(key, (int, float, str, bool)) and key is not None:
raise _create_interpreter_error(f"unhashable type: '{type(key).__name__}' for dictionary key", E_TYPE_ERROR)
value = self.visit(value_node); the_dict[key] = value
return the_dict
def visit_SelfNode(self, node):
if self.current_instance_for_self is None: raise _create_interpreter_error("'self' outside method.", E_NAME_ERROR)
return self.current_instance_for_self
def visit_SuperNode(self, node):
if self.current_instance_for_self is None or self.current_method_defining_class is None:
raise _create_interpreter_error("'super' used outside of an appropriate method context.", E_SYNTAX_ERROR)
return (self.current_instance_for_self, self.current_method_defining_class)
def visit_VariableNode(self, node):
var_name = node.name
val = self.environment.get(var_name)
if val is None and var_name not in self.environment:
raise _create_interpreter_error(f"name '{var_name}' is not defined", E_NAME_ERROR)
return val
def visit_IndexAccessNode(self, node):
collection_val = self.visit(node.collection_expr); key_or_index_val = self.visit(node.index_or_key_expr)
if isinstance(collection_val, list):
if not isinstance(key_or_index_val, int): raise _create_interpreter_error(f"List indices must be integers, not '{type(key_or_index_val).__name__}'", E_TYPE_ERROR)
try: return collection_val[key_or_index_val]
except IndexError: raise _create_interpreter_error(f"list index {key_or_index_val} out of range", E_INDEX_ERROR)
elif isinstance(collection_val, dict):
if not isinstance(key_or_index_val, (int, float, str, bool)) and key_or_index_val is not None:
raise _create_interpreter_error(f"unhashable type: '{type(key_or_index_val).__name__}' for dictionary key access", E_TYPE_ERROR)
try: return collection_val[key_or_index_val]
except KeyError: raise _create_interpreter_error(f"key {repr(key_or_index_val)} not found in dictionary.", E_KEY_ERROR)
else: raise _create_interpreter_error(f"'{type(collection_val).__name__}' object is not subscriptable", E_TYPE_ERROR)
def visit_MemberAccessNode(self, node):
object_val = self.visit(node.object_expr); member_name = node.member_token.value
if isinstance(object_val, MiniModuleNamespace):
try: return getattr(object_val, member_name)
except AttributeError: raise _create_interpreter_error(f"Module '{object_val.name}' has no attribute '{member_name}'", E_ATTRIBUTE_ERROR)
if isinstance(object_val, MiniPyInstance):
instance = object_val
if member_name in instance.attributes: return instance.attributes[member_name]
method_def = instance.klass.find_method(member_name)
if method_def:
current_klass = instance.klass; found_in_klass = instance.klass
while current_klass:
if member_name in current_klass.methods_map and current_klass.methods_map[member_name] == method_def:
found_in_klass = current_klass; break
current_klass = current_klass.parent_class
return BoundMethod(instance, method_def, found_in_klass or instance.klass)
raise _create_interpreter_error(f"'{instance.klass.name}' object has no attribute or method '{member_name}'", E_ATTRIBUTE_ERROR)
elif isinstance(object_val, tuple) and len(object_val) == 2 and isinstance(object_val[0], MiniPyInstance): # super.method
instance, class_where_super_is_called = object_val
parent_class = class_where_super_is_called.parent_class
if not parent_class: raise _create_interpreter_error(f"'{class_where_super_is_called.name}' has no parent for 'super.{member_name}'.", E_TYPE_ERROR)
method_def = parent_class.find_method(member_name)
if method_def:
found_in_klass_for_super_method = parent_class; temp_klass = parent_class
while temp_klass:
if member_name in temp_klass.methods_map and temp_klass.methods_map[member_name] == method_def:
found_in_klass_for_super_method = temp_klass; break
temp_klass = temp_klass.parent_class
return BoundMethod(instance, method_def, found_in_klass_for_super_method or parent_class)
raise _create_interpreter_error(f"'super' object (via {parent_class.name}) has no method '{member_name}'", E_ATTRIBUTE_ERROR)
elif isinstance(object_val, MiniLock):
if member_name == "acquire": return object_val.acquire
if member_name == "release": return object_val.release
raise _create_interpreter_error(f"'MiniLock' object has no attribute '{member_name}'", E_ATTRIBUTE_ERROR)
raise _create_interpreter_error(f"Member access requires an instance, module, Lock or 'super'. Got {type(object_val).__name__}", E_TYPE_ERROR)
def visit_UnaryOpNode(self, node):
op_type = node.op_token.type; value = self.visit(node.expr_node)
if op_type == 'NOT': return not bool(value)
raise _create_interpreter_error(f"Unsupported unary operator: {op_type}", E_TYPE_ERROR)
def visit_BinOpNode(self, node):
op_type = node.op_token.type
if op_type == 'AND': left_val = self.visit(node.left); return left_val if not bool(left_val) else self.visit(node.right)
elif op_type == 'OR': left_val = self.visit(node.left); return left_val if bool(left_val) else self.visit(node.right)
left_val = self.visit(node.left); right_val = self.visit(node.right)
try:
if op_type == 'PLUS':
if isinstance(left_val, list) and isinstance(right_val, list): return left_val + right_val
if isinstance(left_val, (int, float)) and isinstance(right_val, (int, float)): return left_val + right_val
if isinstance(left_val, str) and isinstance(right_val, str): return left_val + right_val
raise TypeError("Operands must be both numbers, both strings or both lists for +")
elif op_type == 'MINUS': return left_val - right_val; elif op_type == 'MUL': return left_val * right_val
elif op_type == 'DIV':
if not isinstance(right_val, (int, float)) or not isinstance(left_val, (int, float)):
raise TypeError("Operands for / must be numbers")
if right_val == 0: raise _create_interpreter_error("division by zero", E_ZERO_DIVISION_ERROR)
return left_val / right_val
elif op_type == 'EQ': return left_val == right_val; elif op_type == 'NEQ': return left_val != right_val
elif op_type == 'LT': return left_val < right_val; elif op_type == 'GT': return left_val > right_val
elif op_type == 'LTE': return left_val <= right_val; elif op_type == 'GTE': return left_val >= right_val
except TypeError as e:
type_l,type_r=type(left_val).__name__,type(right_val).__name__;
raise _create_interpreter_error(f"unsupported operand type(s) for {node.op_token.value}: '{type_l}' and '{type_r}' ({e})", E_TYPE_ERROR)
except Exception as e: raise _create_interpreter_error(f"RuntimeError for op {op_type}: {e}", "RuntimeError")
def _call_method_or_function(self, callable_object, instance_for_self, defining_class_for_method, args, is_init=False, for_dunder_str=False, is_standalone_func=False):
code_def_node = callable_object; expected_params = len(code_def_node.params_tokens); actual_args = len(args)
func_name_for_error = code_def_node.name_token.value
if is_standalone_func: target_name_for_error = func_name_for_error
elif instance_for_self:
class_name_for_error = defining_class_for_method.name if defining_class_for_method else instance_for_self.klass.name
target_name_for_error = f"{class_name_for_error}.{func_name_for_error}"
if is_init: target_name_for_error = f"{class_name_for_error}.__init__"
else: target_name_for_error = func_name_for_error
if for_dunder_str and actual_args != 0:
raise _create_interpreter_error(f"{target_name_for_error}() takes 0 positional arguments but {actual_args} were given", E_TYPE_ERROR)
elif not for_dunder_str and actual_args != expected_params :
raise _create_interpreter_error(f"{target_name_for_error}() takes {expected_params} positional arguments but {actual_args} were given", E_TYPE_ERROR)
prev_s, prev_iim, prev_mdefc = self.current_instance_for_self, self.is_in_method_call, self.current_method_defining_class
self.current_instance_for_self = instance_for_self if not is_standalone_func else None
self.is_in_method_call = True
self.current_method_defining_class = defining_class_for_method if not is_standalone_func else None
params_backup = {}
for i, param_token in enumerate(code_def_node.params_tokens):
param_name = param_token.value
if param_name in self.environment: params_backup[param_name] = self.environment[param_name]
self.environment[param_name] = args[i]
ret_val = None
try: self.visit(code_def_node.body_node)
except ReturnSignal as rs:
if is_init and rs.value is not None:
raise _create_interpreter_error(f"__init__ method of class {instance_for_self.klass.name} should not return a value", E_TYPE_ERROR)
if not is_init or is_standalone_func or for_dunder_str: ret_val = rs.value
finally:
self.current_instance_for_self, self.is_in_method_call, self.current_method_defining_class = prev_s, prev_iim, prev_mdefc
for i, param_token in enumerate(code_def_node.params_tokens):
param_name = param_token.value
if param_name in params_backup: self.environment[param_name] = params_backup[param_name]
else: del self.environment[param_name]
return ret_val
def visit_FunctionCallNode(self, node):
callable_target = self.visit(node.callable_expr); args = [self.visit(arg) for arg in node.arg_nodes]
if isinstance(callable_target, BoundMethod):
return self._call_method_or_function(callable_target.method_def_node, callable_target.instance, callable_target.defining_class, args)
elif isinstance(callable_target, MiniPyClass):
instance = MiniPyInstance(callable_target)
init_method_def = callable_target.find_method("__init__")
if init_method_def:
self._call_method_or_function(init_method_def, instance, callable_target, args, is_init=True)
elif args:
raise _create_interpreter_error(f"{callable_target.name}() takes no arguments if __init__ is not defined, but {len(args)} were given.", E_TYPE_ERROR)
return instance
elif isinstance(callable_target, tuple) and len(callable_target) == 2 and \
isinstance(callable_target[0], MiniPyInstance) and isinstance(callable_target[1], MiniPyClass): # super()
instance, class_where_super_is_called = callable_target
parent_class = class_where_super_is_called.parent_class
if not parent_class: raise _create_interpreter_error(f"'{class_where_super_is_called.name}' has no parent for 'super()'.", E_TYPE_ERROR)
parent_init_method_def = parent_class.find_method("__init__")
if not parent_init_method_def:
if args: raise _create_interpreter_error(f"{parent_class.name}.__init__() (via super) does not exist or takes no args, but {len(args)} given.", E_TYPE_ERROR)
return None
return self._call_method_or_function(parent_init_method_def, instance, parent_class, args, is_init=True)
elif isinstance(callable_target, MiniPyFunction):
return self._call_method_or_function(callable_target.func_def_node, None, None, args, is_standalone_func=True)
elif callable(callable_target) and callable_target in BUILTIN_FUNCTIONS_DEF.values(): # Built-in
try:
if callable_target in (builtin_str, builtin_eval_string, builtin_start_thread):
return callable_target(args, self, getattr(self, 'is_compiler_mode', False))
return callable_target(args)
except InterpreterError: raise
except Exception as e: raise _create_interpreter_error(f"Error in built-in function: {e}", "BuiltinError")
elif callable(callable_target): # For MiniLock methods like acquire(), release()
# Check if it's a bound method of MiniLock that was returned by MemberAccess
if hasattr(callable_target, '__self__') and isinstance(callable_target.__self__, MiniLock):
try: return callable_target(*args) # Call the Python method directly
except RuntimeError as re: raise _create_interpreter_error(str(re), "RuntimeError")
except TypeError as te: raise _create_interpreter_error(str(te), E_TYPE_ERROR) # e.g. wrong number of args to Python method
raise _create_interpreter_error(f"'{type(callable_target).__name__}' object is not callable or not a recognized function/class.", E_TYPE_ERROR)
def visit_ReturnNode(self, node):
if not self.is_in_method_call: raise _create_interpreter_error("'return' outside function or method", E_SYNTAX_ERROR)
val = self.visit(node.expr_node) if node.expr_node else None
raise ReturnSignal(val)
def visit_ClassDefNode(self, node):
class_name = node.name_token.value; parent_class = None
if node.parent_class_token:
parent_name = node.parent_class_token.value
parent_class_obj = self.environment.get(parent_name)
if not isinstance(parent_class_obj, MiniPyClass):
raise _create_interpreter_error(f"Parent class '{parent_name}' not found or not a class.", E_TYPE_ERROR)
parent_class = parent_class_obj
methods = {m.name_token.value: m for m in node.methods}
self.environment[class_name] = MiniPyClass(class_name, parent_class, methods)
return None
def visit_FunctionDefNode(self, node):
func_name = node.name_token.value
self.environment[func_name] = MiniPyFunction(func_name, node)
return None
def visit_AssignNode(self, node):
val_to_assign = self.visit(node.value_node); target = node.target_node
if isinstance(target, VariableNode): self.environment[target.name] = val_to_assign
elif isinstance(target, IndexAccessNode):
collection = self.visit(target.collection_expr); key_or_index = self.visit(target.index_or_key_expr)
if isinstance(collection, list):
if not isinstance(key_or_index, int): raise _create_interpreter_error(f"List indices must be int, not '{type(key_or_index).__name__}'", E_TYPE_ERROR)
try: collection[key_or_index] = val_to_assign
except IndexError: raise _create_interpreter_error(f"list assignment index {key_or_index} out of range", E_INDEX_ERROR)
elif isinstance(collection, dict):
if not isinstance(key_or_index, (int, float, str, bool)) and key_or_index is not None:
raise _create_interpreter_error(f"unhashable type: '{type(key_or_index).__name__}' for dictionary key assignment", E_TYPE_ERROR)
collection[key_or_index] = val_to_assign
else: raise _create_interpreter_error(f"'{type(collection).__name__}' object does not support item assignment.", E_TYPE_ERROR)
elif isinstance(target, MemberAccessNode):
obj = self.visit(target.object_expr)
if not isinstance(obj, MiniPyInstance): raise _create_interpreter_error(f"assign attributes to instances. Got {type(obj).__name__}", E_TYPE_ERROR)
obj.attributes[target.member_token.value] = val_to_assign
else: raise _create_interpreter_error("Invalid target for assignment.", "InternalError")
return val_to_assign
def visit_PrintNode(self, node):
val_to_print = self.visit(node.expr_node)
print(builtin_str([val_to_print], self))
return val_to_print
def visit_BlockNode(self, node):
last_val = None
for stmt in node.statements: last_val = self.visit(stmt)
return last_val
def visit_IfNode(self, node):
if bool(self.visit(node.condition)): return self.visit(node.if_block)
elif node.else_block: return self.visit(node.else_block)
return None
def visit_BreakNode(self, node):
if self.loop_depth == 0: raise _create_interpreter_error("'break' outside loop", E_SYNTAX_ERROR)
raise BreakSignal()
def visit_ContinueNode(self, node):
if self.loop_depth == 0: raise _create_interpreter_error("'continue' outside loop", E_SYNTAX_ERROR)
raise ContinueSignal()
def visit_ForInNode(self, node):
if _CURRENTLY_USING_COMPILER: raise _create_interpreter_error("For loops not supported in compiled mode yet.", "CompilerError")
iterable_val = self.visit(node.iterable_node)
if not isinstance(iterable_val, list):
raise _create_interpreter_error(f"'{type(iterable_val).__name__}' object is not iterable (expected list).", E_TYPE_ERROR)
var_name = node.var_token.value; last_val_in_loop = None
self.loop_depth += 1
try:
for item in iterable_val:
self.environment[var_name] = item
try: last_val_in_loop = self.visit(node.body_node)
except ContinueSignal: continue
except BreakSignal: break
finally: self.loop_depth -= 1
return last_val_in_loop
def visit_WhileNode(self, node):
last_val_in_loop = None; self.loop_depth += 1
try:
while bool(self.visit(node.condition_node)):
try: last_val_in_loop = self.visit(node.body_node)
except ContinueSignal: continue
except BreakSignal: break
finally: self.loop_depth -= 1
return last_val_in_loop
def visit_TryExceptNode(self, node):
try: return self.visit(node.try_block)
except (ReturnSignal, BreakSignal, ContinueSignal): raise
except InterpreterError as e:
for specific_clause in node.specific_except_clauses:
if e.error_type == specific_clause.error_type_token.value:
try: return self.visit(specific_clause.block_node)
except (ReturnSignal, BreakSignal, ContinueSignal): raise
except InterpreterError as e2: raise e2
if node.generic_except_block:
try: return self.visit(node.generic_except_block)
except (ReturnSignal, BreakSignal, ContinueSignal): raise
except InterpreterError as e3: raise e3
raise e
def interpret(self, ast_statements, is_eval_call=False):
last_val = None
if not ast_statements: return None
for stmt in ast_statements:
try: last_val = self.visit(stmt)
except ReturnSignal as rs:
if is_eval_call: return rs.value
if not self.is_module_execution:
print(_create_interpreter_error("'return' outside method/function (or top-level eval)", E_SYNTAX_ERROR)); return
else: raise rs
except BreakSignal:
if is_eval_call: raise _create_interpreter_error("'break' outside loop in eval_string", E_SYNTAX_ERROR)
print(_create_interpreter_error("'break' outside loop", E_SYNTAX_ERROR)); return
except ContinueSignal:
if is_eval_call: raise _create_interpreter_error("'continue' outside loop in eval_string", E_SYNTAX_ERROR)
print(_create_interpreter_error("'continue' outside loop", E_SYNTAX_ERROR)); return
except InterpreterError as e: print(e); return
except Exception as e: print(f"Internal Error: {e}"); import traceback; traceback.print_exc(); return
return last_val
# --- Main Execution ---
# ... (run_minipy and global variables remain the same as before) ...
_CURRENTLY_USING_COMPILER = False
_MAIN_SCRIPT_PATH = None
_LOADED_MODULES_CACHE = {}
_active_mini_threads_global_ref = []
def run_minipy(code_or_filepath, is_filepath=False, use_compiler=False):
global _CURRENTLY_USING_COMPILER, _MAIN_SCRIPT_PATH, _LOADED_MODULES_CACHE, _active_mini_threads_global_ref
is_primary_script_run = False; current_abs_path = None
if is_filepath: current_abs_path = os.path.abspath(code_or_filepath)
else: current_abs_path = os.getcwd()
if _MAIN_SCRIPT_PATH is None: is_primary_script_run = True; _MAIN_SCRIPT_PATH = current_abs_path
elif _MAIN_SCRIPT_PATH == current_abs_path and not hasattr(run_minipy, 'sub_run_active'): is_primary_script_run = True
if is_primary_script_run : _LOADED_MODULES_CACHE = {}; _active_mini_threads_global_ref = []
_CURRENTLY_USING_COMPILER = use_compiler
code_to_run = ""; script_path_for_this_run = None
if is_filepath:
script_path_for_this_run = os.path.abspath(code_or_filepath)
if _MAIN_SCRIPT_PATH is None: _MAIN_SCRIPT_PATH = script_path_for_this_run
try:
with open(script_path_for_this_run, 'r', encoding='utf-8') as f: code_to_run = f.read()
print(f"\nExecuting MiniPy file '{script_path_for_this_run}' (mode: {'compiler' if use_compiler else 'interpreter'}):\n---")
except Exception as e: print(f"Error reading file '{script_path_for_this_run}': {e}"); return
else:
code_to_run = code_or_filepath
script_path_for_this_run = _MAIN_SCRIPT_PATH if _MAIN_SCRIPT_PATH else os.getcwd()
print(f"\nExecuting MiniPy code string (mode: {'compiler' if use_compiler else 'interpreter'}):\n---\n{code_to_run.strip()}\n---")
test_dir = "minipy_test_files";
if not os.path.exists(test_dir): os.makedirs(test_dir)
try:
run_minipy.sub_run_active = True
tokens = tokenize(code_to_run)
parser = Parser(tokens)
ast_statements = parser.parse()
if use_compiler:
unsupported_for_compile = (ImportNode, ImportFromNode, ForInNode, ClassDefNode, FunctionDefNode, TryExceptNode, WhileNode, IfNode, SelfNode, SuperNode, ListNode, DictionaryNode, IndexAccessNode, MemberAccessNode, BreakNode, ContinueNode, ReturnNode)
def check_unsupported(node):
if isinstance(node, unsupported_for_compile): return True
for _, value in node.__dict__.items():
if isinstance(value, ASTNode) and check_unsupported(value): return True
if isinstance(value, list):
for item in value:
if isinstance(item, ASTNode) and check_unsupported(item): return True
return False
has_unsupported = any(check_unsupported(stmt) for stmt in ast_statements)
if not has_unsupported:
for stmt in ast_statements:
if isinstance(stmt, FunctionCallNode) and isinstance(stmt.callable_expr, VariableNode) and stmt.callable_expr.name in ["start_thread", "join_thread", "Lock", "eval_string"]: # Check for interpreter-only builtins
has_unsupported = True; break
if has_unsupported:
print("Compiler Warning: Code contains features not supported by compiler. Running as interpreter.")
use_compiler = False; _CURRENTLY_USING_COMPILER = False
if use_compiler:
print("Compiling..."); compiler = Compiler()
try:
code_obj = compiler.compile(ast_statements); print("Running VM...")
vm = VirtualMachine(); result = vm.run(code_obj)
except CompilerError as ce: print(f"Compiler Error: {ce}"); result = None
except VirtualMachineError as vme: print(f"VM Error: {vme}"); result = None
else:
interpreter = Interpreter(current_script_path=script_path_for_this_run)
interpreter.is_compiler_mode = False
result = interpreter.interpret(ast_statements)
print("--- Execution Finished ---")
except (LexerError, ParserError, InterpreterError) as e: print(f"Error: {e}"); print("--- Execution Halted ---")
except Exception as e: import traceback; print(f"Unexpected system error: {e}"); traceback.print_exc(); print("--- Execution Halted ---")
finally:
if is_primary_script_run: _MAIN_SCRIPT_PATH = None
if hasattr(run_minipy, 'sub_run_active'): delattr(run_minipy, 'sub_run_active')
if __name__ == '__main__':
code_typed_exceptions_advanced_test = """
?? --- Test Typed Exception Handling ---
function risky_divide(a, b) {
print "Attempting " + str(a) + " / " + str(b)
return a / b
}
function file_reader(path) {
print "Attempting to read: " + path
return read_file(path)
}
test_file_for_typed_error = "minipy_test_files/typed_error_test.txt"
write_file(test_file_for_typed_error, "content for typed error test")
values_to_test = [
[10, 2], ?? ok
[10, 0], ?? ZeroDivisionError
[10, "text"], ?? TypeError
[test_file_for_typed_error], ?? for file read
["minipy_test_files/no_such_file_typed.txt"] ?? for IOError
]
for val_set in values_to_test {
try {
if (len(val_set) == 2) {
res = risky_divide(val_set[0], val_set[1])
print "Division result: " + str(res)
} else if (len(val_set) == 1) {
file_content = file_reader(val_set[0])
print "File content length: " + str(len(file_content))
} else {
print "Unknown test set: " + str(val_set)
}
} except (ZeroDivisionError) {
print "HANDLED: Cannot divide by zero!"
} except (TypeError) {
print "HANDLED: Type mismatch in operation!"
} except (IOError) {
print "HANDLED: File operation failed!"
} except {
print "HANDLED: Some other unexpected Mini error occurred."
}
print "---"
}
print "?? Test error propagation if no matching type and no generic except"
?? try {
?? x = 10 / 0
?? } except (TypeError) {
?? print "This specific TypeError handler won't catch ZeroDivisionError"
?? }
?? print "This line should not print if error propagates (uncomment above to test)"
print "?? Test error in except block"
try {
x = 1 / 0
} except (ZeroDivisionError) {
print "Caught ZDE, now causing NameError in except block:"
print non_existent_var_in_except
} except {
print "Generic except after specific one with error (should not be reached if error propagates from specific)"
}
print "End of typed exception tests (if previous error didn't halt)."
"""
run_minipy(code_typed_exceptions_advanced_test, use_compiler=False)
so basically what that was, was about 1 days worth of work, if you work 12 hours in a day. Or 12 hours of work, and it yeilded a pretty sweet language called Mini. This is with using google’s Gemini 2.5 Pro in CANVAS mode. When you initially ask it to write a language, it says “that is an interesting thought experiment!” and doesn’t actually write the language. You have to word your response like this:
Write a basic programming language in python that has just the basics
Then, you have to ask it what it is capable of:
Give me a list of the top 10 improvements you might like to make for our language
Then you say this:
We'll call this "List-001" and it involves re-writing the 10 improvements to something you might be able to code (you code it) and then include substeps, and sub-substeps. Make sure that it is steps that can actually be coded!
Compare list-001 to the program we have now and see if everything looks good, making any necessary changes to the language and to our List as you go along
Take step 1-1 of List-001
Then you would write after thats done, take step 1-2 of List-001. This gives the AI it’s structure that it desires to be able to write in the code. You can’t just say, “Give me a language with as many features as the current CPython” because it can’t do that, and it can’t take these steps one at a time, you have to tell it “take step 1-1 of list-001” and when all the steps in list-001 are completed, oh I forgot to mention you have to actually go through each of the substeps for step-1, so step-1-1, step-1-2, and then step-1-3 and then step-2-1, step-2-2 and so on. This gives the AI something that it actually can do, while it is not smart enough yet to just outright write a whole programming language. This is strange because, it begs the question, why can’t you just say “Go through each of the 10 steps we made in list-001, and perform each sub-step and sub-sub-steps along the way”. It doesn’t do this, and the reason for it could be anything from Google doesn’t want to supply all that automatic computer power. Turning an AI on and running it on autopilot is the ultimate goal. To achieve this i’ve developed a master plan:
1. Polish the "Minipy" language until it becomes unreasonable to add to it. Eventually it will become so complex that adding an addition to it will be counter-productive. This is because the AI goes through the entire program to add just one new feature. Right now Minipy weighs in at 2k lines, and it's already kinda slow going process at that.
2. Coax the AI along to write a "MiniAi" that is a very simple thing. I haven't thought of what i'm going to write but, i'm thinking, "could you write an AI that can code? Could you write an AI that can code like, print statements?" once you have the program going in Gemini's Mind's Eye-- it can work on it easily from there, you just have to get the AI going on it. At first it is reluctant to do something this robust, with this much depth, however you want to put it. But it will go along with you if you first ask it to write a very simple artificial intelligence.
3. Once again, we ask Gemini for List-001 pertaining to MiniAI, 10 things that it would like to add to the AI. Then we have to type in each step, step 1-1, step 1-2, or however it plays out, Some things have less sub steps, but generally they have 2 - 5 substeps to complete one of the 10 steps in the List that it made for us.
4. How can we get a synergy between MiniPy and MiniAi? I am thinking to make changes to the language that would better suit AI development. The ultimate goal is an Artificial Intelligence that can code in like C++ or something, or in Python, but I can't just feed the AI that main, overwhelming goal. It doesn't go for it. But it goes for this substep system. Until google unlocks us to the full power of gemini, we are left entering one step at a time, you can't even write "do steps 1-1 and step 1-2 at the same time" doesn't work out, it writes one or doesn't write either. It always goes for one step at a time, as they say. Fortunately for us we have nothing better to do than to do stuff like this, so it kinda doesn't matter that gemini can't code automatically? I guess you could call it that.
So how do I know all of this? It’s because i’ve been working on programming languages for 10 years and playing with AI’s for 1 year. Now I am on the verge of having my very own AI, which will be able to code automatically. I will be able to feed MiniAI “give me 1,000 things you might like to add to MiniAI, and it will do all 1,000 of them, get it to do some testing, and then just run it, it will probably take it 24 hours to do that. After that comes the real fun.
So now we have an unlocked AI at our fingertips, MiniAi. Now I can get it to work on itself, work on the Mini language it’s written in, almost all automatically, I can ask it to scan the whole setup and see if it can find any ways we might be able to improve things.
This leads to an even greater goal: James Dean-type Data Creation. Those programs that I sometimes post, they create a type of data called james dean data and it is then placed onto people. Like everyone, and i’ve even made data for inanimate objects. Now I can point my MiniAi at the James Dean Data Creation and it can think for me. Thus, we have improvements daily, not monthly. It speeds the whole process up by like 30x, and on top of that I don’t even have to touch a line of code. It just goes through a 3 stage process:
Stage One: Scan the whole system, the Mini Language, and the Mini AI to see if we can make any improvements. Also during this phase we will scan our James Dean program (the program that actually produces the memory structures inside of the computers memory that we "put" on people. The james dean prograem, the ai and the language. 3 different things working synergistically. That's probably not even a word bro.
Stage Two: Implement all of these changes you've documented from the scan.
Stage Three: Run the james dean program, and get it to perform it's intended task: data creation at speeds of over 56k, this is like a cable modem, no a cellphone tower wired with fiber optics. It can perform these steps faster and faster.
I forgot to mention the AI’s name is actually the Performa AI. It has a rating system to it called the G System. Performa AI G1 stands for Performa Artificial Intelligence Generation 1. In generation one, you may have 300 different iterations before I branch out and create something even smarter USING performa, and that this new AI becomes Performa AI G2, Generation 2. The AI that Performa G2 will create eventually is Performa G3, and it goes on and on, a web-working of programming languages and artificial intelligences and data creation tools.
This is my master plan. I developed it today, and it changed because the process of me creating a programming language is taking much much to long. It will take me about one year to get all of this working, whereas when I was doing it by hand, it would take 20 years and even then you may not really have something going. The gemini tool is googles attempt at getting into the AI game, and they didn’t intend to lose to anyone. It is the greatest AI. I think it’s father or something is ChatGPT, and it uses basically similar code, it talks the same and everything. I haven’t tried chatgpt for making a language, only the “Grimoire” AI they provide that is “good at coding” and seems to always lose what it’s working on, bummer. But this will work, as i’ve already ran a test program that includes all of the features of Mini and it in fact does execute.
Here is the latest version of MiniPy before I go:
# MiniPy: A Simple Dynamically-Typed Language Interpreter
import re
import os
import random
import time
import math
import threading
import copy
# --- Mini Error Types ---
E_TYPE_ERROR = "TypeError"; E_NAME_ERROR = "NameError"; E_INDEX_ERROR = "IndexError"
E_KEY_ERROR = "KeyError"; E_VALUE_ERROR = "ValueError"; E_ZERO_DIVISION_ERROR = "ZeroDivisionError"
E_IO_ERROR = "IOError"; E_ATTRIBUTE_ERROR = "AttributeError"; E_MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError"
E_IMPORT_ERROR = "ImportError"; E_SYNTAX_ERROR = "SyntaxError"
E_COMPILER_ERROR = "CompilerError"; E_VM_ERROR = "VirtualMachineError"; E_THREAD_ERROR = "ThreadError"
E_BUILTIN_ERROR = "BuiltinError"; E_MODULE_EXECUTION_ERROR = "ModuleExecutionError"; E_RUNTIME_ERROR = "RuntimeError"
# --- Tokenizer (Lexer) ---
class Token:
def __init__(self, type, value): self.type = type; self.value = value
def __repr__(self): return f"Token({self.type}, {repr(self.value)})"
TOKEN_SPECIFICATION = [
('COMMENT', r'\?\?.*'), ('NUMBER', r'\d+(\.\d*)?'), ('ASSIGN', r'='),
('IMPORT', r'import'), ('FROM', r'from'), ('CLASS', r'class'), ('EXTENDS', r'extends'),
('SUPER', r'super'), ('SELF', r'self'), ('RETURN', r'return'), ('IF', r'if'),
('ELSE', r'else'), ('WHILE', r'while'), ('FOR', r'for'), ('IN', r'in'),
('TRY', r'try'), ('EXCEPT', r'except'), ('FUNCTION', r'function'), ('BREAK', r'break'),
('CONTINUE', r'continue'), ('TRUE', r'True'), ('FALSE', r'False'), ('NULL', r'Null'),
('PRINT', r'print'), ('AND', r'and'), ('OR', r'or'), ('NOT', r'not'),
('ID', r'[A-Za-z_][A-Za-z0-9_]*'), ('STRING', r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\''),
('EQ',r'=='),('NEQ',r'!='),('LTE',r'<='),('GTE',r'>='),('LT',r'<'),('GT',r'>'),
('PLUS',r'\+'),('MINUS',r'-'),('MUL',r'\*'),('DIV',r'/'), ('DOT',r'\.'),('LPAREN',r'\('),
('RPAREN',r'\)'),('LBRACE',r'\{'),('RBRACE',r'\}'),('LBRACKET',r'\['),('RBRACKET',r'\]'),
('COMMA',r','),('COLON',r':'), ('NEWLINE',r'\n'),('SKIP',r'[ \t]+'),('MISMATCH',r'.'),
]
TOKEN_REGEX = re.compile('|'.join('(?P<%s>%s)' % pair for pair in TOKEN_SPECIFICATION))
class LexerError(Exception): pass
def tokenize(code):
tokens = []; keywords_map = {'import':'IMPORT','from':'FROM','class':'CLASS','extends':'EXTENDS',
'super':'SUPER','self':'SELF','return':'RETURN','if':'IF','else':'ELSE','while':'WHILE','for':'FOR',
'in':'IN','try':'TRY','except':'EXCEPT','function':'FUNCTION','break':'BREAK','continue':'CONTINUE',
'True':'TRUE','False':'FALSE','Null':'NULL','print':'PRINT','and':'AND','or':'OR','not':'NOT'}
for mo in TOKEN_REGEX.finditer(code):
kind, value = mo.lastgroup, mo.group()
if kind == 'COMMENT': continue
elif kind == 'ID' and value in keywords_map:
kind = keywords_map[value]
if kind == 'TRUE': value = True
elif kind == 'FALSE': value = False
elif kind == 'NULL': value = None
elif kind == 'NUMBER': value = float(value) if '.' in value else int(value)
elif kind == 'STRING': value = value[1:-1].replace('\\"', '"').replace("\\'", "'")
elif kind in ('NEWLINE', 'SKIP'): continue
elif kind == 'MISMATCH': raise LexerError(f'Unexpected character: {value}')
tokens.append(Token(kind, value))
tokens.append(Token('EOF', None)); return tokens
# --- Abstract Syntax Tree (AST) Nodes ---
class ASTNode: pass
class NumberNode(ASTNode):
def __init__(self, token): self.value = token.value
def __repr__(self): return f"NumberNode({self.value})"
class StringNode(ASTNode):
def __init__(self, token): self.value = token.value
def __repr__(self): return f"StringNode({repr(self.value)})"
class BooleanNode(ASTNode):
def __init__(self, token): self.value = token.value
def __repr__(self): return f"BooleanNode({self.value})"
class NullNode(ASTNode):
def __init__(self, token): self.value = token.value
def __repr__(self): return f"NullNode({self.value})"
class VariableNode(ASTNode):
def __init__(self, token): self.name = token.value
def __repr__(self): return f"VariableNode({self.name})"
class SelfNode(ASTNode): # Not compiled yet
def __init__(self, token): self.token = token
def __repr__(self): return "SelfNode"
class SuperNode(ASTNode): # Not compiled yet
def __init__(self, token): self.token = token
def __repr__(self): return "SuperNode"
class ListNode(ASTNode):
def __init__(self, elements): self.elements = elements
def __repr__(self): return f"ListNode({self.elements})"
class DictionaryNode(ASTNode):
def __init__(self, pairs): self.pairs = pairs
def __repr__(self): return f"DictionaryNode({self.pairs})"
class IndexAccessNode(ASTNode):
def __init__(self, collection_expr, index_or_key_expr):
self.collection_expr = collection_expr; self.index_or_key_expr = index_or_key_expr
def __repr__(self): return f"IndexAccessNode({self.collection_expr}, {self.index_or_key_expr})"
class MemberAccessNode(ASTNode): # Not compiled yet
def __init__(self, object_expr, member_token):
self.object_expr = object_expr; self.member_token = member_token
def __repr__(self): return f"MemberAccessNode({self.object_expr}, {self.member_token.value})"
class FunctionCallNode(ASTNode): # Compiled for user-defined functions and print
def __init__(self, callable_expr, arg_nodes):
self.callable_expr = callable_expr; self.arg_nodes = arg_nodes
def __repr__(self): return f"FunctionCallNode({self.callable_expr}, {self.arg_nodes})"
class ClassDefNode(ASTNode): # Partially compiled (definition, no methods yet for VM)
def __init__(self, name_token, parent_class_token, methods):
self.name_token = name_token; self.parent_class_token = parent_class_token; self.methods = methods
def __repr__(self):
parent_name = f" extends {self.parent_class_token.value}" if self.parent_class_token else ""
return f"ClassDefNode({self.name_token.value}{parent_name}, {len(self.methods)} methods)"
class MethodDefNode(ASTNode): # Not compiled yet
def __init__(self, name_token, params_tokens, body_node):
self.name_token = name_token; self.params_tokens = params_tokens; self.body_node = body_node
def __repr__(self): return f"MethodDefNode({self.name_token.value}, params={[p.value for p in self.params_tokens]})"
class FunctionDefNode(ASTNode): # Compiled
def __init__(self, name_token, params_tokens, body_node):
self.name_token = name_token; self.params_tokens = params_tokens; self.body_node = body_node
def __repr__(self): return f"FunctionDefNode({self.name_token.value}, params={[p.value for p in self.params_tokens]})"
class ReturnNode(ASTNode): # Compiled
def __init__(self, expr_node): self.expr_node = expr_node
def __repr__(self): return f"ReturnNode({self.expr_node})"
class BreakNode(ASTNode): # Compiled
def __init__(self, token): self.token = token
def __repr__(self): return "BreakNode"
class ContinueNode(ASTNode): # Compiled
def __init__(self, token): self.token = token
def __repr__(self): return "ContinueNode"
class UnaryOpNode(ASTNode):
def __init__(self, op_token, expr_node):
self.op_token = op_token; self.expr_node = expr_node
def __repr__(self): return f"UnaryOpNode({self.op_token.type}, {self.expr_node})"
class BinOpNode(ASTNode):
def __init__(self, left, op_token, right):
self.left = left; self.op_token = op_token; self.right = right
def __repr__(self): return f"BinOpNode({self.left}, {self.op_token.type}, {self.right})"
class AssignNode(ASTNode):
def __init__(self, target_node, value_node):
self.target_node = target_node; self.value_node = value_node
def __repr__(self): return f"AssignNode({self.target_node}, {self.value_node})"
class PrintNode(ASTNode):
def __init__(self, expr_node): self.expr_node = expr_node
def __repr__(self): return f"PrintNode({self.expr_node})"
class BlockNode(ASTNode):
def __init__(self, statements): self.statements = statements
def __repr__(self): return f"BlockNode({self.statements})"
class IfNode(ASTNode):
def __init__(self, condition, if_block, else_block=None):
self.condition = condition; self.if_block = if_block; self.else_block = else_block
def __repr__(self): return f"IfNode({self.condition}, {self.if_block}, else={self.else_block})"
class WhileNode(ASTNode):
def __init__(self, condition_node, body_node):
self.condition_node = condition_node; self.body_node = body_node
def __repr__(self): return f"WhileNode({self.condition_node}, {self.body_node})"
class ForInNode(ASTNode):
def __init__(self, var_token, iterable_node, body_node):
self.var_token = var_token; self.iterable_node = iterable_node; self.body_node = body_node
def __repr__(self): return f"ForInNode(var={self.var_token.value}, in={self.iterable_node}, body={self.body_node})"
class SpecificExceptClauseNode(ASTNode): # Not compiled
def __init__(self, error_type_token, block_node):
self.error_type_token = error_type_token; self.block_node = block_node
def __repr__(self): return f"SpecificExceptClauseNode(type={self.error_type_token.value}, block={self.block_node})"
class TryExceptNode(ASTNode): # Not compiled
def __init__(self, try_block, specific_except_clauses, generic_except_block):
self.try_block = try_block; self.specific_except_clauses = specific_except_clauses; self.generic_except_block = generic_except_block
def __repr__(self): return f"TryExceptNode(try={self.try_block}, specific_clauses={self.specific_except_clauses}, generic_except={self.generic_except_block})"
class ImportNode(ASTNode): # Not compiled
def __init__(self, filepath_token): self.filepath_token = filepath_token
def __repr__(self): return f"ImportNode('{self.filepath_token.value}')"
class ImportFromNode(ASTNode): # Not compiled
def __init__(self, filepath_token, names_tokens, import_all):
self.filepath_token = filepath_token; self.names_tokens = names_tokens; self.import_all = import_all
def __repr__(self):
names_str = "*" if self.import_all else ", ".join([t.value for t in self.names_tokens])
return f"ImportFromNode('{self.filepath_token.value}', [{names_str}])"
# --- Parser ---
# ... (Parser class remains the same as before) ...
class ParserError(Exception): pass
class Parser:
def __init__(self, tokens):
self.tokens = tokens; self.pos = 0
self.current_token = self.tokens[self.pos] if self.tokens else Token('EOF', None)
def _advance(self):
self.pos += 1; self.current_token = self.tokens[self.pos] if self.pos < len(self.tokens) else Token('EOF', None)
def _eat(self, token_type):
if self.current_token.type == token_type: self._advance()
else: raise ParserError(f"Expected {token_type} but got {self.current_token.type} ('{self.current_token.value}')")
def argument_list(self):
args = []
if self.current_token.type != 'RPAREN':
args.append(self.expression())
while self.current_token.type == 'COMMA': self._eat('COMMA'); args.append(self.expression())
return args
def dictionary_literal(self):
self._eat('LBRACE'); pairs = []
if self.current_token.type != 'RBRACE':
key_node = self.expression(); self._eat('COLON'); value_node = self.expression()
pairs.append((key_node, value_node))
while self.current_token.type == 'COMMA':
self._eat('COMMA'); key_node = self.expression(); self._eat('COLON'); value_node = self.expression()
pairs.append((key_node, value_node))
self._eat('RBRACE'); return DictionaryNode(pairs)
def atom(self):
token = self.current_token
if token.type == 'NUMBER': self._eat('NUMBER'); return NumberNode(token)
elif token.type == 'STRING': self._eat('STRING'); return StringNode(token)
elif token.type == 'TRUE': self._eat('TRUE'); return BooleanNode(token)
elif token.type == 'FALSE': self._eat('FALSE'); return BooleanNode(token)
elif token.type == 'NULL': self._eat('NULL'); return NullNode(token)
elif token.type == 'SELF': self._eat('SELF'); return SelfNode(token)
elif token.type == 'SUPER': self._eat('SUPER'); return SuperNode(token)
elif token.type == 'ID': id_token = token; self._eat('ID'); return VariableNode(id_token)
elif token.type == 'LBRACKET': return self.list_literal()
elif token.type == 'LBRACE': return self.dictionary_literal()
elif token.type == 'LPAREN': self._eat('LPAREN'); node = self.expression(); self._eat('RPAREN'); return node
else: raise ParserError(f"Invalid atom: Unexpected token {token.type} ('{token.value}')")
def list_literal(self):
self._eat('LBRACKET'); elements = []
if self.current_token.type != 'RBRACKET':
elements.append(self.expression())
while self.current_token.type == 'COMMA': self._eat('COMMA'); elements.append(self.expression())
self._eat('RBRACKET'); return ListNode(elements)
def postfix_expression(self):
node = self.atom()
while True:
if self.current_token.type == 'LPAREN': self._eat('LPAREN'); args = self.argument_list(); self._eat('RPAREN'); node = FunctionCallNode(node, args)
elif self.current_token.type == 'LBRACKET': self._eat('LBRACKET'); index_or_key_node = self.expression(); self._eat('RBRACKET'); node = IndexAccessNode(node, index_or_key_node)
elif self.current_token.type == 'DOT': self._eat('DOT'); member_token = self.current_token; self._eat('ID'); node = MemberAccessNode(node, member_token)
else: break
return node
def unary_expression(self):
if self.current_token.type == 'NOT': op_token = self.current_token; self._eat('NOT'); return UnaryOpNode(op_token, self.unary_expression())
return self.postfix_expression()
def multiplicative_expression(self):
node = self.unary_expression()
while self.current_token.type in ('MUL', 'DIV'): op_token = self.current_token; self._eat(op_token.type); node = BinOpNode(node, op_token, self.unary_expression())
return node
def additive_expression(self):
node = self.multiplicative_expression()
while self.current_token.type in ('PLUS', 'MINUS'): op_token = self.current_token; self._eat(op_token.type); node = BinOpNode(node, op_token, self.multiplicative_expression())
return node
def comparison_expression(self):
node = self.additive_expression()
while self.current_token.type in ('LT', 'GT', 'LTE', 'GTE'): op_token = self.current_token; self._eat(op_token.type); node = BinOpNode(node, op_token, self.additive_expression())
return node
def equality_expression(self):
node = self.comparison_expression()
while self.current_token.type in ('EQ', 'NEQ'): op_token = self.current_token; self._eat(op_token.type); node = BinOpNode(node, op_token, self.comparison_expression())
return node
def logical_and_expression(self):
node = self.equality_expression()
while self.current_token.type == 'AND': op_token = self.current_token; self._eat('AND'); node = BinOpNode(node, op_token, self.equality_expression())
return node
def logical_or_expression(self):
node = self.logical_and_expression()
while self.current_token.type == 'OR': op_token = self.current_token; self._eat('OR'); node = BinOpNode(node, op_token, self.logical_and_expression())
return node
def expression(self): return self.logical_or_expression()
def block(self):
self._eat('LBRACE'); statements = []
while self.current_token.type not in ('RBRACE', 'EOF'): stmt = self.statement();
if stmt: statements.append(stmt)
self._eat('RBRACE'); return BlockNode(statements)
def if_statement(self):
self._eat('IF'); self._eat('LPAREN'); condition_node = self.expression(); self._eat('RPAREN')
if_block_node = self.block(); else_block_node = None
if self.current_token.type == 'ELSE': self._eat('ELSE'); else_block_node = self.block()
return IfNode(condition_node, if_block_node, else_block_node)
def while_statement(self):
self._eat('WHILE'); self._eat('LPAREN'); condition_node = self.expression(); self._eat('RPAREN'); body_node = self.block()
return WhileNode(condition_node, body_node)
def for_statement(self):
self._eat('FOR'); var_token = self.current_token; self._eat('ID'); self._eat('IN'); iterable_node = self.expression(); body_node = self.block()
return ForInNode(var_token, iterable_node, body_node)
def return_statement(self):
self._eat('RETURN'); expr_node = None
can_start_expr = ['ID','NUMBER','STRING','TRUE','FALSE','NULL','LPAREN','LBRACKET','LBRACE','SELF','SUPER','NOT']
if self.current_token.type in can_start_expr: expr_node = self.expression()
return ReturnNode(expr_node)
def method_definition(self):
name_token=self.current_token;self._eat('ID');self._eat('LPAREN');params_tokens=[]
if self.current_token.type == 'ID':
params_tokens.append(self.current_token);self._eat('ID')
while self.current_token.type == 'COMMA': self._eat('COMMA');params_tokens.append(self.current_token);self._eat('ID')
self._eat('RPAREN');body_node=self.block();return MethodDefNode(name_token,params_tokens,body_node)
def class_definition(self):
self._eat('CLASS');name_token=self.current_token;self._eat('ID');parent_class_token=None
if self.current_token.type == 'EXTENDS': self._eat('EXTENDS');parent_class_token=self.current_token;self._eat('ID')
self._eat('LBRACE');methods=[]
while self.current_token.type != 'RBRACE' and self.current_token.type == 'ID': methods.append(self.method_definition())
self._eat('RBRACE');return ClassDefNode(name_token,parent_class_token,methods)
def try_except_statement(self):
self._eat('TRY'); try_block = self.block(); specific_except_clauses = []; generic_except_block = None
while self.current_token.type == 'EXCEPT':
self._eat('EXCEPT')
if self.current_token.type == 'LPAREN':
self._eat('LPAREN'); error_type_token = self.current_token; self._eat('ID'); self._eat('RPAREN'); block = self.block()
specific_except_clauses.append(SpecificExceptClauseNode(error_type_token, block))
else:
if generic_except_block is not None: raise ParserError("Generic 'except' block must be last.")
generic_except_block = self.block(); break
if not specific_except_clauses and not generic_except_block: raise ParserError("try must have at least one except.")
return TryExceptNode(try_block, specific_except_clauses, generic_except_block)
def function_definition(self):
self._eat('FUNCTION');name_token=self.current_token;self._eat('ID');self._eat('LPAREN');params_tokens=[]
if self.current_token.type == 'ID':
params_tokens.append(self.current_token);self._eat('ID')
while self.current_token.type == 'COMMA': self._eat('COMMA');params_tokens.append(self.current_token);self._eat('ID')
self._eat('RPAREN');body_node=self.block();return FunctionDefNode(name_token,params_tokens,body_node)
def import_statement(self):
self._eat('IMPORT');filepath_token=self.current_token
if filepath_token.type!='STRING':raise ParserError("Expected string filepath for import.")
self._eat('STRING');return ImportNode(filepath_token)
def import_from_statement(self):
self._eat('FROM');filepath_token=self.current_token
if filepath_token.type!='STRING':raise ParserError("Expected string filepath for from-import.")
self._eat('STRING');self._eat('IMPORT');names_tokens=[];import_all=False
if self.current_token.type == 'MUL': self._eat('MUL');import_all=True
elif self.current_token.type == 'ID':
names_tokens.append(self.current_token);self._eat('ID')
while self.current_token.type == 'COMMA':
self._eat('COMMA')
if self.current_token.type!='ID':raise ParserError("Expected ID after comma in from-import.")
names_tokens.append(self.current_token);self._eat('ID')
else:raise ParserError("Expected ID or '*' after 'import' in from-import.")
return ImportFromNode(filepath_token,names_tokens,import_all)
def statement(self):
token_type = self.current_token.type
if token_type=='PRINT':self._eat('PRINT');return PrintNode(self.expression())
elif token_type=='IF':return self.if_statement()
elif token_type=='WHILE':return self.while_statement()
elif token_type=='FOR':return self.for_statement()
elif token_type=='CLASS':return self.class_definition()
elif token_type=='FUNCTION':return self.function_definition()
elif token_type=='RETURN':return self.return_statement()
elif token_type=='TRY':return self.try_except_statement()
elif token_type=='BREAK':self._eat('BREAK');return BreakNode(self.current_token)
elif token_type=='CONTINUE':self._eat('CONTINUE');return ContinueNode(self.current_token)
elif token_type=='IMPORT':return self.import_statement()
elif token_type=='FROM':return self.import_from_statement()
elif token_type=='LBRACE':return self.block()
elif token_type=='EOF':return None
else:
expr_node=self.expression()
if self.current_token.type == 'ASSIGN':
self._eat('ASSIGN')
if not isinstance(expr_node,(VariableNode,IndexAccessNode,MemberAccessNode)):raise ParserError(f"Invalid assignment target: {type(expr_node).__name__}")
return AssignNode(expr_node,self.expression())
return expr_node
def program(self):
statements=[];
while self.current_token.type != 'EOF':stmt=self.statement();
if stmt:statements.append(stmt)
return statements
def parse(self):
if not self.tokens or self.current_token.type=='EOF':return[]
ast=self.program()
if self.current_token.type!='EOF':raise ParserError(f"Unexpected token {self.current_token.type} ('{self.current_token.value}') after statements.")
return ast
# --- Bytecode Opcodes ---
OP_LOAD_CONST = 0x01; OP_STORE_NAME = 0x02; OP_LOAD_NAME = 0x03
OP_BINARY_ADD = 0x04; OP_BINARY_SUBTRACT = 0x05; OP_PRINT_ITEM = 0x06
OP_POP_TOP = 0x07; OP_BINARY_MULTIPLY = 0x08; OP_BINARY_DIVIDE = 0x09
OP_COMPARE_EQ = 0x0A; OP_COMPARE_NEQ = 0x0B; OP_COMPARE_LT = 0x0C
OP_COMPARE_GT = 0x0D; OP_COMPARE_LTE = 0x0E; OP_COMPARE_GTE = 0x0F
OP_UNARY_NOT = 0x10
OP_JUMP_IF_FALSE = 0x11
OP_JUMP = 0x12
OP_JUMP_IF_TRUE_SC = 0x13
OP_JUMP_IF_FALSE_SC= 0x14
OP_MAKE_FUNCTION = 0x15
OP_CALL_FUNCTION = 0x16
OP_RETURN_VALUE = 0x17
OP_LOAD_LOCAL = 0x18
OP_STORE_LOCAL = 0x19
OP_BUILD_LIST = 0x1A
OP_BUILD_DICT = 0x1B
OP_LOAD_SUBSCRIPT = 0x1C
OP_STORE_SUBSCRIPT = 0x1D
OP_GET_ITER = 0x1E
OP_FOR_ITER = 0x1F
OP_MAKE_CLASS = 0x20 # Arg: name_const_idx. Expects parent class (or Null) on stack.
OP_CREATE_INSTANCE = 0x21 # Pops class, pushes instance. (Not used yet, ClassName() handled by CALL_FUNCTION)
OP_LOAD_ATTR = 0x22 # Not used yet
OP_STORE_ATTR = 0x23 # Not used yet
class CompilerError(Exception): pass
class CodeObject: # ... (CodeObject unchanged) ...
def __init__(self, name="<module>", params=None): self.name=name;self.instructions=[];self.constants=[];self.names=[]; self.locals=[]; self.params=params if params else []
def add_const(self, value):
if isinstance(value, CodeObject) and value is self: raise CompilerError("Cannot add CodeObject to its own constants.")
if not isinstance(value,(int,float,str,bool,CodeObject)) and value is not None: raise CompilerError(f"Cannot add {type(value)} to constants")
if value not in self.constants: self.constants.append(value)
return self.constants.index(value)
def add_name(self, name):
if name not in self.names: self.names.append(name)
return self.names.index(name)
def add_local(self, name):
if name not in self.locals: self.locals.append(name)
return self.locals.index(name)
def add_instruction(self, opcode, arg=None): self.instructions.append((opcode, arg))
def get_current_address(self): return len(self.instructions)
def patch_jump(self, idx, addr): op, _ = self.instructions[idx]; self.instructions[idx] = (op, addr)
def __repr__(self): return f"CodeObject(name='{self.name}', params={self.params}, instructions={len(self.instructions)} instrs, constants={len(self.constants)}, names={len(self.names)}, locals={len(self.locals)})"
class Compiler: # Compiler updated for ClassDefNode
def __init__(self, parent_compiler=None, is_method=False): self.current_code_object=None; self.loop_context_stack=[]; self.parent_compiler=parent_compiler; self.function_code_objects=[]; self.is_method_compilation=is_method
def compile_program(self, ast_statements):
self.current_code_object=CodeObject(name="<main_program>")
for stmt_node in ast_statements:
self.visit(stmt_node)
if not isinstance(stmt_node, (AssignNode,PrintNode,ClassDefNode,FunctionDefNode,IfNode,WhileNode,ForInNode,TryExceptNode,ReturnNode,BreakNode,ContinueNode,ImportNode,ImportFromNode)):
if isinstance(stmt_node, (NumberNode,StringNode,BooleanNode,NullNode,VariableNode,BinOpNode,UnaryOpNode,FunctionCallNode,ListNode,DictionaryNode,IndexAccessNode,MemberAccessNode,SelfNode,SuperNode)):
self.current_code_object.add_instruction(OP_POP_TOP)
for func_co in self.function_code_objects: self.current_code_object.add_const(func_co)
return self.current_code_object
def compile_function_body(self, name, params_tokens, body_node, is_method_body=False):
func_compiler = Compiler(parent_compiler=self, is_method=is_method_body)
func_code_object=CodeObject(name=name, params=[p.value for p in params_tokens]); func_compiler.current_code_object=func_code_object
if is_method_body: func_compiler.current_code_object.add_local("self")
for param_token in params_tokens: func_compiler.current_code_object.add_local(param_token.value)
func_compiler.visit(body_node)
if not func_compiler.current_code_object.instructions or func_compiler.current_code_object.instructions[-1][0]!=OP_RETURN_VALUE:
null_const_idx=func_compiler.current_code_object.add_const(None); func_compiler.current_code_object.add_instruction(OP_LOAD_CONST,null_const_idx); func_compiler.current_code_object.add_instruction(OP_RETURN_VALUE)
return func_compiler.current_code_object
def visit(self, node): visitor=getattr(self,f'visit_{type(node).__name__}',self.unsupported_node); return visitor(node)
def unsupported_node(self,node):raise CompilerError(f"Compiler: Unsupported AST for compilation: {type(node).__name__}")
def visit_NumberNode(self,node):const_idx=self.current_code_object.add_const(node.value);self.current_code_object.add_instruction(OP_LOAD_CONST,const_idx)
def visit_StringNode(self,node):const_idx=self.current_code_object.add_const(node.value);self.current_code_object.add_instruction(OP_LOAD_CONST,const_idx)
def visit_BooleanNode(self,node):const_idx=self.current_code_object.add_const(node.value);self.current_code_object.add_instruction(OP_LOAD_CONST,const_idx)
def visit_NullNode(self,node):const_idx=self.current_code_object.add_const(None);self.current_code_object.add_instruction(OP_LOAD_CONST,const_idx)
def visit_ListNode(self, node):
for element_node in node.elements: self.visit(element_node)
self.current_code_object.add_instruction(OP_BUILD_LIST, len(node.elements))
def visit_DictionaryNode(self, node):
for key_node, value_node in node.pairs: self.visit(key_node); self.visit(value_node)
self.current_code_object.add_instruction(OP_BUILD_DICT, len(node.pairs))
def visit_IndexAccessNode(self, node):
self.visit(node.collection_expr); self.visit(node.index_or_key_expr)
self.current_code_object.add_instruction(OP_LOAD_SUBSCRIPT)
def visit_VariableNode(self,node):
var_name=node.name
if self.current_code_object.name != "<main_program>" and var_name in self.current_code_object.locals:
local_idx = self.current_code_object.locals.index(var_name); self.current_code_object.add_instruction(OP_LOAD_LOCAL, local_idx)
else: name_idx=self.current_code_object.add_name(var_name); self.current_code_object.add_instruction(OP_LOAD_NAME,name_idx)
def visit_AssignNode(self,node):
if isinstance(node.target_node, VariableNode):
self.visit(node.value_node); var_name = node.target_node.name
if self.current_code_object.name != "<main_program>" and (var_name in self.current_code_object.params or var_name not in self.current_code_object.names):
local_idx=self.current_code_object.add_local(var_name); self.current_code_object.add_instruction(OP_STORE_LOCAL,local_idx)
else: name_idx=self.current_code_object.add_name(var_name); self.current_code_object.add_instruction(OP_STORE_NAME,name_idx)
elif isinstance(node.target_node, IndexAccessNode):
self.visit(node.target_node.collection_expr); self.visit(node.target_node.index_or_key_expr); self.visit(node.value_node)
self.current_code_object.add_instruction(OP_STORE_SUBSCRIPT)
else: raise CompilerError(f"Compiler: Invalid assignment target type: {type(node.target_node).__name__}")
def visit_UnaryOpNode(self,node):
self.visit(node.expr_node)
if node.op_token.type=='NOT':self.current_code_object.add_instruction(OP_UNARY_NOT)
else:raise CompilerError(f"Compiler: Unsupported unary op: {node.op_token.type}")
def visit_BinOpNode(self, node):
op_type = node.op_token.type
if op_type == 'AND':
self.visit(node.left); jump_idx = self.current_code_object.get_current_address(); self.current_code_object.add_instruction(OP_JUMP_IF_FALSE_SC, None)
self.current_code_object.add_instruction(OP_POP_TOP); self.visit(node.right)
end_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(jump_idx, end_addr)
elif op_type == 'OR':
self.visit(node.left); jump_idx = self.current_code_object.get_current_address(); self.current_code_object.add_instruction(OP_JUMP_IF_TRUE_SC, None)
self.current_code_object.add_instruction(OP_POP_TOP); self.visit(node.right)
end_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(jump_idx, end_addr)
else:
self.visit(node.left); self.visit(node.right)
op_map = {'PLUS':OP_BINARY_ADD,'MINUS':OP_BINARY_SUBTRACT,'MUL':OP_BINARY_MULTIPLY,'DIV':OP_BINARY_DIVIDE,
'EQ':OP_COMPARE_EQ,'NEQ':OP_COMPARE_NEQ,'LT':OP_COMPARE_LT,'GT':OP_COMPARE_GT,
'LTE':OP_COMPARE_LTE,'GTE':OP_COMPARE_GTE}
if op_type in op_map: self.current_code_object.add_instruction(op_map[op_type])
else: raise CompilerError(f"Compiler: Unsupported binary op: {op_type}")
def visit_PrintNode(self,node):self.visit(node.expr_node);self.current_code_object.add_instruction(OP_PRINT_ITEM)
def visit_BlockNode(self,node):
for stmt in node.statements:
self.visit(stmt)
is_expr_statement = not isinstance(stmt,(AssignNode,PrintNode,IfNode,WhileNode,ForInNode,ReturnNode,BreakNode,ContinueNode, FunctionDefNode, ClassDefNode, ImportNode, ImportFromNode, TryExceptNode))
if is_expr_statement and isinstance(stmt,(NumberNode,StringNode,BooleanNode,NullNode,VariableNode,BinOpNode,UnaryOpNode,FunctionCallNode, ListNode, DictionaryNode, IndexAccessNode, MemberAccessNode, SelfNode, SuperNode)):
self.current_code_object.add_instruction(OP_POP_TOP)
def visit_FunctionDefNode(self,node):
func_name=node.name_token.value; func_co=self.compile_function_body(func_name,node.params_tokens,node.body_node, is_method_body=False)
if self.parent_compiler is None: self.function_code_objects.append(func_co); const_idx=self.current_code_object.add_const(func_co)
else: raise CompilerError("Nested function definition compilation not fully supported yet.")
self.current_code_object.add_instruction(OP_MAKE_FUNCTION,const_idx); name_idx=self.current_code_object.add_name(func_name)
self.current_code_object.add_instruction(OP_STORE_NAME,name_idx)
def visit_ReturnNode(self,node):
if node.expr_node: self.visit(node.expr_node)
else: null_const_idx=self.current_code_object.add_const(None); self.current_code_object.add_instruction(OP_LOAD_CONST,null_const_idx)
self.current_code_object.add_instruction(OP_RETURN_VALUE)
def visit_FunctionCallNode(self,node):
if isinstance(node.callable_expr, VariableNode) and node.callable_expr.name in BUILTIN_FUNCTIONS_DEF:
raise CompilerError(f"Compiler: Calling built-in function '{node.callable_expr.name}' not supported by VM yet (except print).")
for arg_node in node.arg_nodes: self.visit(arg_node)
self.visit(node.callable_expr); self.current_code_object.add_instruction(OP_CALL_FUNCTION,len(node.arg_nodes))
def visit_IfNode(self,node):
self.visit(node.condition); jump_if_false_idx = self.current_code_object.get_current_address(); self.current_code_object.add_instruction(OP_JUMP_IF_FALSE, None)
self.visit(node.if_block)
if node.else_block:
jump_over_else_idx = self.current_code_object.get_current_address(); self.current_code_object.add_instruction(OP_JUMP, None)
else_start_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(jump_if_false_idx, else_start_addr)
self.visit(node.else_block); end_if_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(jump_over_else_idx, end_if_addr)
else: after_if_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(jump_if_false_idx, after_if_addr)
def visit_WhileNode(self,node):
loop_start_addr=self.current_code_object.get_current_address(); break_patches = []
self.loop_context_stack.append({'break':break_patches,'continue_target':loop_start_addr})
self.visit(node.condition_node); jump_if_false_idx = self.current_code_object.get_current_address(); self.current_code_object.add_instruction(OP_JUMP_IF_FALSE, None)
self.visit(node.body_node); self.current_code_object.add_instruction(OP_JUMP, loop_start_addr)
after_loop_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(jump_if_false_idx, after_loop_addr)
for break_idx in break_patches: self.current_code_object.patch_jump(break_idx, after_loop_addr)
self.loop_context_stack.pop()
def visit_ForInNode(self, node):
self.visit(node.iterable_node); self.current_code_object.add_instruction(OP_GET_ITER)
loop_start_addr = self.current_code_object.get_current_address(); break_patches = []
self.loop_context_stack.append({'break': break_patches, 'continue_target': loop_start_addr})
for_iter_idx = self.current_code_object.get_current_address()
self.current_code_object.add_instruction(OP_FOR_ITER, None)
var_name = node.var_token.value
if self.current_code_object.name != "<main_program>" and (var_name in self.current_code_object.params or var_name in self.current_code_object.locals):
local_idx = self.current_code_object.add_local(var_name); self.current_code_object.add_instruction(OP_STORE_LOCAL, local_idx)
else: name_idx = self.current_code_object.add_name(var_name); self.current_code_object.add_instruction(OP_STORE_NAME, name_idx)
self.visit(node.body_node)
self.current_code_object.add_instruction(OP_JUMP, loop_start_addr)
after_loop_addr = self.current_code_object.get_current_address(); self.current_code_object.patch_jump(for_iter_idx, after_loop_addr)
for break_idx in break_patches: self.current_code_object.patch_jump(break_idx, after_loop_addr)
self.loop_context_stack.pop()
def visit_BreakNode(self,node):
if not self.loop_context_stack:raise CompilerError("'break' outside loop")
break_jump_idx = self.current_code_object.get_current_address(); self.current_code_object.add_instruction(OP_JUMP, None)
self.loop_context_stack[-1]['break'].append(break_jump_idx)
def visit_ContinueNode(self,node):
if not self.loop_context_stack:raise CompilerError("'continue' outside loop")
continue_target = self.loop_context_stack[-1]['continue_target']; self.current_code_object.add_instruction(OP_JUMP, continue_target)
def visit_ClassDefNode(self, node): # Basic compilation for class definition
class_name = node.name_token.value
class_name_const_idx = self.current_code_object.add_const(class_name)
# Handle parent class (simplified for now)
if node.parent_class_token:
# For the VM, the parent class object needs to be on the stack
# This assumes the parent class is already defined and loaded by name
parent_name_idx = self.current_code_object.add_name(node.parent_class_token.value)
self.current_code_object.add_instruction(OP_LOAD_NAME, parent_name_idx)
else:
# Push Null onto the stack if no parent
null_const_idx = self.current_code_object.add_const(None)
self.current_code_object.add_instruction(OP_LOAD_CONST, null_const_idx)
# Methods are not compiled into the class object in this step for the VM.
# OP_MAKE_CLASS will just use the name and parent.
# The arg to OP_MAKE_CLASS is the class name's index in constants.
self.current_code_object.add_instruction(OP_MAKE_CLASS, class_name_const_idx)
# Store the created class object in the environment
storage_name_idx = self.current_code_object.add_name(class_name)
self.current_code_object.add_instruction(OP_STORE_NAME, storage_name_idx)
class VirtualMachineError(Exception): pass
class Frame:
def __init__(self,code_obj,prev_frame=None):self.code_obj=code_obj;self.ip=0;self.prev_frame=prev_frame;self.locals={}
def __repr__(self):return f"<Frame for {self.code_obj.name} at IP {self.ip}>"
class MiniPyVMFunction:
def __init__(self,name,code_obj):self.name=name;self.code_obj=code_obj
def __repr__(self):return f"<VMFunction {self.name}>"
class MiniPyVMClassPlaceholder: # Placeholder for VM class objects
def __init__(self, name, parent=None, methods_map=None): # methods_map not used by VM yet
self.name = name
self.parent = parent # Another MiniPyVMClassPlaceholder or None
self.methods_map = methods_map if methods_map else {} # For future method storage
def __repr__(self): return f"<VMClass {self.name}>"
class MiniPyVMInstancePlaceholder: # Placeholder for VM instance objects
def __init__(self, klass_placeholder):
self.klass_placeholder = klass_placeholder # MiniPyVMClassPlaceholder
self.attributes = {} # Instance attributes stored here
def __repr__(self): return f"<VMInstance of {self.klass_placeholder.name}>"
class VirtualMachine: # VM updated for new opcodes
def __init__(self):self.stack=[];self.frames=[];self.current_frame=None;self.globals={}
def push_frame(self,code_obj):frame=Frame(code_obj,prev_frame=self.current_frame);self.frames.append(frame);self.current_frame=frame
def pop_frame(self):
if not self.frames:raise VirtualMachineError("Cannot pop frame from empty call stack.")
frame=self.frames.pop();self.current_frame=self.frames[-1]if self.frames else None;return frame
def run(self,top_level_code_obj):
self.globals={};self.stack=[];self.frames=[];self.push_frame(top_level_code_obj)
while self.current_frame:
code_obj=self.current_frame.code_obj;ip=self.current_frame.ip
if ip>=len(code_obj.instructions):
if self.current_frame.prev_frame:self.stack.append(None);self.pop_frame();continue
else:break
opcode,arg=code_obj.instructions[ip];self.current_frame.ip+=1
if opcode==OP_LOAD_CONST:self.stack.append(code_obj.constants[arg])
elif opcode==OP_STORE_NAME:val=self.stack.pop();self.globals[code_obj.names[arg]]=val
elif opcode==OP_LOAD_NAME:
name=code_obj.names[arg]
if self.current_frame.code_obj.name != "<main_program>" and name in self.current_frame.locals: val=self.current_frame.locals[name]
elif name in self.globals:val=self.globals[name]
else:raise VirtualMachineError(f"NameError: '{name}' not defined")
self.stack.append(val)
elif opcode==OP_STORE_LOCAL:val=self.stack.pop();self.current_frame.locals[self.current_frame.code_obj.locals[arg]]=val
elif opcode==OP_LOAD_LOCAL:
name=self.current_frame.code_obj.locals[arg]
if name not in self.current_frame.locals:raise VirtualMachineError(f"LocalVarError: '{name}' referenced before assignment in VM.")
self.stack.append(self.current_frame.locals[name])
elif opcode==OP_BINARY_ADD:
r,l=self.stack.pop(),self.stack.pop()
if isinstance(l,str)and isinstance(r,str):self.stack.append(l+r)
elif isinstance(l,(int,float))and isinstance(r,(int,float)):self.stack.append(l+r)
elif isinstance(l, list) and isinstance(r, list): self.stack.append(l + r)
else:raise VirtualMachineError(f"TypeError for +: '{type(l).__name__}' and '{type(r).__name__}'")
elif opcode==OP_BINARY_SUBTRACT:r,l=self.stack.pop(),self.stack.pop();self.stack.append(l-r)
elif opcode==OP_BINARY_MULTIPLY:r,l=self.stack.pop(),self.stack.pop();self.stack.append(l*r)
elif opcode==OP_BINARY_DIVIDE:
r,l=self.stack.pop(),self.stack.pop()
if not isinstance(r,(int,float))or r==0:raise VirtualMachineError("ZeroDivisionError or invalid divisor")
if not isinstance(l,(int,float)):raise VirtualMachineError("Invalid dividend")
self.stack.append(l/r)
elif opcode==OP_PRINT_ITEM:
val=self.stack.pop()
if val is None:print("Null")
elif isinstance(val,bool):print("True"if val else"False")
elif isinstance(val,MiniPyVMFunction):print(f"<VMFunction {val.name}>")
elif isinstance(val,MiniPyVMClassPlaceholder): print(f"<VMClass {val.name}>")
elif isinstance(val,MiniPyVMInstancePlaceholder): print(f"<VMInstance of {val.klass_placeholder.name}>")
elif isinstance(val, list): print(val)
elif isinstance(val, dict):
items_str = [f"{repr(k) if isinstance(k,str) else str(k)}: {repr(v) if isinstance(v,str) else str(v)}" for k,v in val.items()]
print("{" + ", ".join(items_str) + "}")
else:print(val)
elif opcode==OP_POP_TOP:self.stack.pop()
elif opcode==OP_UNARY_NOT:self.stack.append(not bool(self.stack.pop()))
elif opcode==OP_COMPARE_EQ:r,l=self.stack.pop(),self.stack.pop();self.stack.append(l==r)
elif opcode==OP_COMPARE_NEQ:r,l=self.stack.pop(),self.stack.pop();self.stack.append(l!=r)
elif opcode==OP_COMPARE_LT:r,l=self.stack.pop(),self.stack.pop();self.stack.append(l<r)
elif opcode==OP_COMPARE_GT:r,l=self.stack.pop(),self.stack.pop();self.stack.append(l>r)
elif opcode==OP_COMPARE_LTE:r,l=self.stack.pop(),self.stack.pop();self.stack.append(l<=r)
elif opcode==OP_COMPARE_GTE:r,l=self.stack.pop(),self.stack.pop();self.stack.append(l>=r)
elif opcode==OP_JUMP:self.current_frame.ip=arg
elif opcode==OP_JUMP_IF_FALSE:
condition=self.stack.pop();
if not bool(condition):self.current_frame.ip=arg
elif opcode==OP_JUMP_IF_FALSE_SC:
condition = self.stack[-1]
if not bool(condition): self.current_frame.ip = arg
elif opcode==OP_JUMP_IF_TRUE_SC:
condition = self.stack[-1]
if bool(condition): self.current_frame.ip = arg
elif opcode==OP_MAKE_FUNCTION:
func_co=code_obj.constants[arg]
if not isinstance(func_co, CodeObject): raise VirtualMachineError("OP_MAKE_FUNCTION expects a CodeObject constant.")
vm_func=MiniPyVMFunction(func_co.name,func_co)
self.stack.append(vm_func)
elif opcode==OP_CALL_FUNCTION: # Handles user functions and class instantiation
num_args=arg;args_on_stack=[]
for _ in range(num_args):args_on_stack.insert(0,self.stack.pop())
func_or_class_obj=self.stack.pop()
if isinstance(func_or_class_obj, MiniPyVMFunction):
if len(args_on_stack)!=len(func_or_class_obj.code_obj.params):raise VirtualMachineError(f"TypeError: {func_or_class_obj.name}() takes {len(func_or_class_obj.code_obj.params)} args but {len(args_on_stack)} were given.")
self.push_frame(func_or_class_obj.code_obj)
for i,param_name in enumerate(func_or_class_obj.code_obj.params):self.current_frame.locals[param_name]=args_on_stack[i]
elif isinstance(func_or_class_obj, MiniPyVMClassPlaceholder): # Instantiation
# For now, VM instantiation doesn't call __init__ or handle args for it.
if num_args > 0:
raise VirtualMachineError(f"TypeError: {func_or_class_obj.name}() takes 0 arguments for VM instantiation (subset), got {num_args}")
instance = MiniPyVMInstancePlaceholder(func_or_class_obj)
self.stack.append(instance)
else:
raise VirtualMachineError(f"TypeError: '{type(func_or_class_obj).__name__}' object is not callable by VM.")
elif opcode==OP_RETURN_VALUE:
return_value=self.stack.pop()if self.stack else None;self.pop_frame()
if self.current_frame:self.stack.append(return_value)
else:
if self.stack:self.stack.pop()
self.stack.append(return_value)
elif opcode == OP_BUILD_LIST:
count = arg; elements = []
for _ in range(count): elements.insert(0, self.stack.pop())
self.stack.append(elements)
elif opcode == OP_BUILD_DICT:
count = arg; the_dict = {}
temp_items = []
for _ in range(count * 2): temp_items.append(self.stack.pop())
for i in range(0, count * 2, 2):
value, key = temp_items[i], temp_items[i+1]
if not isinstance(key, (int, float, str, bool)) and key is not None:
raise VirtualMachineError(f"TypeError: unhashable type for dict key in VM: {type(key).__name__}")
the_dict[key] = value
self.stack.append(the_dict)
elif opcode == OP_LOAD_SUBSCRIPT:
key_or_idx = self.stack.pop(); collection = self.stack.pop()
if isinstance(collection, list):
if not isinstance(key_or_idx, int): raise VirtualMachineError(f"TypeError: list indices must be int, not {type(key_or_idx).__name__}")
try: self.stack.append(collection[key_or_idx])
except IndexError: raise VirtualMachineError(f"IndexError: list index {key_or_idx} out of range")
elif isinstance(collection, dict):
if not isinstance(key_or_idx, (int, float, str, bool)) and key_or_idx is not None:
raise VirtualMachineError(f"TypeError: unhashable type for dict key: {type(key_or_idx).__name__}")
try: self.stack.append(collection[key_or_idx])
except KeyError: raise VirtualMachineError(f"KeyError: {repr(key_or_idx)}")
else: raise VirtualMachineError(f"TypeError: '{type(collection).__name__}' is not subscriptable")
elif opcode == OP_STORE_SUBSCRIPT:
value_to_store = self.stack.pop(); key_or_idx = self.stack.pop(); collection = self.stack.pop()
if isinstance(collection, list):
if not isinstance(key_or_idx, int): raise VirtualMachineError(f"TypeError: list indices must be int for store, not {type(key_or_idx).__name__}")
try: collection[key_or_idx] = value_to_store
except IndexError: raise VirtualMachineError(f"IndexError: list assignment index {key_or_idx} out of range")
elif isinstance(collection, dict):
if not isinstance(key_or_idx, (int, float, str, bool)) and key_or_idx is not None:
raise VirtualMachineError(f"TypeError: unhashable type for dict key store: {type(key_or_idx).__name__}")
collection[key_or_idx] = value_to_store
else: raise VirtualMachineError(f"TypeError: '{type(collection).__name__}' does not support item assignment")
elif opcode == OP_GET_ITER:
iterable = self.stack.pop()
if not isinstance(iterable, list):
raise VirtualMachineError(f"TypeError: '{type(iterable).__name__}' object is not iterable (VM needs list).")
self.stack.append([iterable, 0])
elif opcode == OP_FOR_ITER:
iterator = self.stack[-1]
the_list, current_index = iterator[0], iterator[1]
if current_index < len(the_list):
self.stack.pop(); self.stack.append(the_list[current_index])
iterator[1] += 1; self.stack.append(iterator)
else:
self.stack.pop(); self.current_frame.ip = arg
elif opcode == OP_MAKE_CLASS: # New
parent_class_obj = self.stack.pop() # Parent or None
class_name = code_obj.constants[arg]
# Methods are not handled by this opcode in this simplified step
vm_class = MiniPyVMClassPlaceholder(class_name, parent=parent_class_obj, methods_map={})
self.stack.append(vm_class)
# OP_CREATE_INSTANCE is implicitly handled by OP_CALL_FUNCTION if func_or_class_obj is MiniPyVMClassPlaceholder
else:raise VirtualMachineError(f"Unknown opcode: {opcode}")
return self.stack.pop()if self.stack else None
# --- Interpreter (Direct AST Execution) ---
# ... (Interpreter class definition and its methods remain the same as previous version) ...
class InterpreterError(Exception):
def __init__(self, message, error_type=None):
super().__init__(message); self.error_type = error_type if error_type else "Error"; self.message = message
def __str__(self): return f"{self.error_type}: {self.message}"
class ReturnSignal(Exception):
def __init__(self, value): self.value = value
class BreakSignal(Exception): pass
class ContinueSignal(Exception): pass
def _create_interpreter_error(message, error_type_str): return InterpreterError(message, error_type=error_type_str)
# ... (All built-in function definitions remain the same) ...
def builtin_len(args):
if len(args) != 1: raise _create_interpreter_error("len() takes 1 arg", E_TYPE_ERROR)
arg = args[0]
if isinstance(arg, (str,list,dict)): return len(arg)
raise _create_interpreter_error(f"object of type '{type(arg).__name__}' has no len()", E_TYPE_ERROR)
def builtin_type(args):
if len(args) != 1: raise _create_interpreter_error("type() takes 1 arg", E_TYPE_ERROR)
val = args[0]
if isinstance(val, (int,float)): return "number"
if isinstance(val, str): return "string";
if isinstance(val, bool): return "boolean";
if val is None: return "null"
if isinstance(val, list): return "list"
if isinstance(val, dict): return "dictionary"
if isinstance(val, MiniPyInstance): return f"instance:{val.klass.name}" # Interpreter's instance
if isinstance(val, MiniPyClass): return "class" # Interpreter's class
if isinstance(val, MiniPyVMInstancePlaceholder): return f"vm_instance:{val.klass_placeholder.name}" # VM's instance
if isinstance(val, MiniPyVMClassPlaceholder): return "vm_class" # VM's class
if isinstance(val, BoundMethod): return "method"
if isinstance(val, MiniPyFunction): return "function"
if isinstance(val, MiniPyVMFunction): return "vm_function"
if isinstance(val, MiniModuleNamespace): return "module"
if callable(val) and val in BUILTIN_FUNCTIONS_DEF.values(): return "builtin_function"
return "unknown"
def builtin_str(args, interpreter_instance):
if len(args) != 1: raise _create_interpreter_error("str() takes 1 arg", E_TYPE_ERROR)
val = args[0]
if isinstance(val, MiniPyInstance): # Interpreter's instance
str_method_def = val.klass.find_method("__str__")
if str_method_def:
try:
if str_method_def.params_tokens:
raise _create_interpreter_error(f"{val.klass.name}.__str__() should take 0 arguments (besides self)", E_TYPE_ERROR)
str_val = interpreter_instance._call_method_or_function(str_method_def, val, val.klass, [], is_init=False, for_dunder_str=True)
if not isinstance(str_val, str):
raise _create_interpreter_error(f"__str__ method of class {val.klass.name} must return a string, not {type(str_val).__name__}", E_TYPE_ERROR)
return str_val
except ReturnSignal as rs_str:
if not isinstance(rs_str.value, str):
raise _create_interpreter_error(f"__str__ method of class {val.klass.name} must return a string, not {type(rs_str.value).__name__}", E_TYPE_ERROR)
return rs_str.value
return f"<instance of {val.klass.name}>"
if isinstance(val, MiniPyVMInstancePlaceholder): # VM's instance
return f"<VMInstance of {val.klass_placeholder.name}>" # Basic representation
if val is None: return "Null";
if isinstance(val, bool): return "True" if val else "False"
if isinstance(val, MiniModuleNamespace): return f"<module '{val.name}'>"
if isinstance(val, MiniPyVMFunction): return f"<VMFunction {val.name}>"
if isinstance(val, MiniPyVMClassPlaceholder): return f"<VMClass {val.name}>"
if isinstance(val, dict):
items_str = []
for k, v_val in val.items():
k_py_val = k
v_py_val = v_val
k_mini_str = builtin_str([k_py_val], interpreter_instance) if not isinstance(k_py_val, str) else repr(k_py_val)
v_mini_str = builtin_str([v_py_val], interpreter_instance)
items_str.append(f"{k_mini_str}: {v_mini_str}")
return "{" + ", ".join(items_str) + "}"
return str(val)
def builtin_read_file(args):
if len(args) != 1: raise _create_interpreter_error("read_file() takes 1 arg", E_TYPE_ERROR)
filepath = args[0];
if not isinstance(filepath, str): raise _create_interpreter_error("filepath must be str", E_TYPE_ERROR)
try:
with open(filepath, 'r', encoding='utf-8') as f: return f.read()
except FileNotFoundError: raise _create_interpreter_error(f"File not found '{filepath}'", E_IO_ERROR)
except Exception as e: raise _create_interpreter_error(f"Could not read file '{filepath}': {e}", E_IO_ERROR)
def builtin_write_file(args):
if len(args) != 2: raise _create_interpreter_error("write_file() takes 2 args", E_TYPE_ERROR)
filepath, content = args[0], args[1]
if not isinstance(filepath, str): raise _create_interpreter_error("filepath must be str", E_TYPE_ERROR)
if not isinstance(content, str): raise _create_interpreter_error("content must be str", E_TYPE_ERROR)
try:
with open(filepath, 'w', encoding='utf-8') as f: f.write(content)
return None
except Exception as e: raise _create_interpreter_error(f"Could not write to file '{filepath}': {e}", E_IO_ERROR)
def builtin_random(args):
num_args = len(args)
if num_args == 0: return random.random()
elif num_args == 1:
max_val = args[0]
if not isinstance(max_val, int): raise _create_interpreter_error("random(max) requires int", E_TYPE_ERROR)
if max_val <= 0: raise _create_interpreter_error("random(max) requires max > 0", E_VALUE_ERROR)
return random.randrange(max_val)
elif num_args == 2:
min_val, max_val = args[0], args[1]
if not (isinstance(min_val, int) and isinstance(max_val, int)): raise _create_interpreter_error("random(min,max) requires ints", E_TYPE_ERROR)
if min_val > max_val: raise _create_interpreter_error("random(min,max) requires min <= max", E_VALUE_ERROR)
return random.randint(min_val, max_val)
else: raise _create_interpreter_error(f"random() takes 0-2 args, got {num_args}", E_TYPE_ERROR)
def builtin_eval_string(args, interpreter_instance, current_mode_is_compiler):
if current_mode_is_compiler: raise _create_interpreter_error("eval_string() is not supported in compiled mode.", "Error")
if len(args) != 1: raise _create_interpreter_error("eval_string() takes 1 argument (Mini code string).", E_TYPE_ERROR)
code_string = args[0]
if not isinstance(code_string, str): raise _create_interpreter_error("arg to eval_string() must be a string.", E_TYPE_ERROR)
try:
eval_tokens = tokenize(code_string); eval_parser = Parser(eval_tokens)
eval_ast_statements = eval_parser.parse()
return interpreter_instance.interpret(eval_ast_statements, is_eval_call=True)
except (LexerError, ParserError) as e: raise _create_interpreter_error(f"Error in eval_string (lex/parse): {e}", E_SYNTAX_ERROR)
except InterpreterError as ie: raise ie
except ReturnSignal as rs: return rs.value
def builtin_time(args):
if len(args) != 0: raise _create_interpreter_error("time() takes 0 arguments.", E_TYPE_ERROR)
return time.time()
def builtin_input(args):
prompt = ""
if len(args) == 1:
prompt_arg = args[0]
if not isinstance(prompt_arg, str):
raise _create_interpreter_error("prompt for input() must be a string.", E_TYPE_ERROR)
prompt = prompt_arg
elif len(args) > 1:
raise _create_interpreter_error(f"input() takes 0 or 1 arguments, but {len(args)} were given.", E_TYPE_ERROR)
try: return input(prompt)
except EOFError: return Null
except Exception as e: raise _create_interpreter_error(f"Error during input(): {e}", E_IO_ERROR)
def builtin_number(args):
if len(args) != 1: raise _create_interpreter_error("number() takes exactly one argument.", E_TYPE_ERROR)
val = args[0]
if isinstance(val, (int, float)): return val
if isinstance(val, str):
try: return int(val)
except ValueError:
try: return float(val)
except ValueError: raise _create_interpreter_error(f"could not convert string to number: '{val}'", E_VALUE_ERROR)
raise _create_interpreter_error(f"number() argument must be a string or number, not {type(val).__name__}", E_TYPE_ERROR)
def builtin_is_number(args):
if len(args) != 1: raise _create_interpreter_error("is_number() takes 1 argument.", E_TYPE_ERROR)
return isinstance(args[0], (int, float))
def builtin_is_string(args):
if len(args) != 1: raise _create_interpreter_error("is_string() takes 1 argument.", E_TYPE_ERROR)
return isinstance(args[0], str)
def builtin_is_list(args):
if len(args) != 1: raise _create_interpreter_error("is_list() takes 1 argument.", E_TYPE_ERROR)
return isinstance(args[0], list)
def builtin_is_null(args):
if len(args) != 1: raise _create_interpreter_error("is_null() takes 1 argument.", E_TYPE_ERROR)
return args[0] is None
def builtin_abs(args):
if len(args) != 1: raise _create_interpreter_error("abs() takes 1 argument.", E_TYPE_ERROR)
val = args[0]
if not isinstance(val, (int, float)): raise _create_interpreter_error(f"abs() requires a number, not {type(val).__name__}.", E_TYPE_ERROR)
return abs(val)
def builtin_append(args):
if len(args) != 2: raise _create_interpreter_error("append() takes 2 arguments (list, item).", E_TYPE_ERROR)
target_list, item = args[0], args[1]
if not isinstance(target_list, list): raise _create_interpreter_error(f"append() requires a list as first argument, not {type(target_list).__name__}.", E_TYPE_ERROR)
target_list.append(item); return None
def builtin_pop(args):
if len(args) != 1: raise _create_interpreter_error("pop() takes 1 argument (list).", E_TYPE_ERROR)
target_list = args[0]
if not isinstance(target_list, list): raise _create_interpreter_error(f"pop() requires a list, not {type(target_list).__name__}.", E_TYPE_ERROR)
if not target_list: raise _create_interpreter_error("pop from empty list.", E_INDEX_ERROR)
return target_list.pop()
def builtin_range(args):
num_args = len(args); start, stop, step = 0, 0, 1
if num_args == 1: stop = args[0]
elif num_args == 2: start, stop = args[0], args[1]
elif num_args == 3: start, stop, step = args[0], args[1], args[2]
else: raise _create_interpreter_error(f"range() takes 1 to 3 arguments, but {num_args} were given.", E_TYPE_ERROR)
if not all(isinstance(x, int) for x in (start, stop, step)):
raise _create_interpreter_error("range() arguments must be integers.", E_TYPE_ERROR)
if step == 0: raise _create_interpreter_error("range() step argument cannot be zero.", E_VALUE_ERROR)
return list(range(start, stop, step))
def _thread_target_wrapper(interpreter_for_thread, func_to_call_obj, args_for_func, mini_thread_obj_ref):
try:
result = interpreter_for_thread._call_method_or_function(
func_to_call_obj.func_def_node, None, None, args_for_func, is_standalone_func=True )
mini_thread_obj_ref.result = result
except InterpreterError as e: mini_thread_obj_ref.error = e
except Exception as e: mini_thread_obj_ref.error = _create_interpreter_error(f"Python exception in thread: {e}", "ThreadError")
finally: mini_thread_obj_ref.is_done = True
class MiniPyThread:
def __init__(self, py_thread): self.py_thread = py_thread; self.result = None; self.error = None; self.is_done = False
def __repr__(self): return f"<MiniPyThread name='{self.py_thread.name}' alive={self.py_thread.is_alive()}>"
_active_mini_threads_global_ref = []
def builtin_start_thread(args, parent_interpreter_instance, current_mode_is_compiler):
if current_mode_is_compiler: raise _create_interpreter_error("start_thread() not in compiled mode.", "CompilerError")
if len(args) != 2: raise _create_interpreter_error("start_thread() takes 2 args: func_name (str), args_list (list).", E_TYPE_ERROR)
func_name_str, mini_args_list = args[0], args[1]
if not isinstance(func_name_str, str): raise _create_interpreter_error("First arg to start_thread() must be str.", E_TYPE_ERROR)
if not isinstance(mini_args_list, list): raise _create_interpreter_error("Second arg to start_thread() must be list.", E_TYPE_ERROR)
func_to_call = parent_interpreter_instance.environment.get(func_name_str)
if not isinstance(func_to_call, MiniPyFunction): raise _create_interpreter_error(f"'{func_name_str}' is not a defined Mini function.", E_NAME_ERROR)
thread_interpreter = Interpreter(is_module_execution=parent_interpreter_instance.is_module_execution,current_script_path=parent_interpreter_instance.current_script_path,initial_environment=copy.copy(parent_interpreter_instance.environment))
for name, func in BUILTIN_FUNCTIONS_DEF.items():
if name not in thread_interpreter.environment: thread_interpreter.environment[name] = func
py_thread = threading.Thread(target=_thread_target_wrapper, args=(thread_interpreter, func_to_call, mini_args_list, None))
mini_thread_obj = MiniPyThread(py_thread)
py_thread._args = (thread_interpreter, func_to_call, mini_args_list, mini_thread_obj)
py_thread.daemon = True; py_thread.start(); _active_mini_threads_global_ref.append(mini_thread_obj)
return mini_thread_obj
def builtin_join_thread(args):
if len(args) < 1 or len(args) > 2: raise _create_interpreter_error("join_thread() takes 1 or 2 args: thread_obj, [timeout].", E_TYPE_ERROR)
thread_obj = args[0]
if not isinstance(thread_obj, MiniPyThread): raise _create_interpreter_error("First arg to join_thread() must be thread object.", E_TYPE_ERROR)
timeout = None
if len(args) == 2:
timeout_val = args[1]
if not isinstance(timeout_val, (int, float)): raise _create_interpreter_error("Timeout for join_thread() must be number.", E_TYPE_ERROR)
if timeout_val < 0: raise _create_interpreter_error("Timeout for join_thread() cannot be negative.", E_VALUE_ERROR)
timeout = timeout_val
thread_obj.py_thread.join(timeout=timeout)
if thread_obj.error: raise thread_obj.error
return thread_obj.result
class MiniLock:
def __init__(self): self._lock = threading.Lock(); self.acquired_by_thread_id = None
def acquire(self): acquired = self._lock.acquire(blocking=True); self.acquired_by_thread_id = threading.get_ident() if acquired else None; return acquired
def release(self):
try: self._lock.release(); self.acquired_by_thread_id = None
except RuntimeError as e: raise _create_interpreter_error(f"Cannot release unacquired or differently owned lock: {e}", "RuntimeError")
def __repr__(self): return f"<MiniLock acquired_by_thread={self.acquired_by_thread_id}>"
def builtin_Lock(args):
if len(args)!=0: raise _create_interpreter_error("Lock() takes 0 args.", E_TYPE_ERROR)
return MiniLock()
BUILTIN_FUNCTIONS_DEF = {
"len": builtin_len, "type": builtin_type, "str": builtin_str,
"read_file": builtin_read_file, "write_file": builtin_write_file,
"random": builtin_random, "eval_string": builtin_eval_string,
"time": builtin_time, "input": builtin_input, "number": builtin_number,
"is_number": builtin_is_number, "is_string": builtin_is_string,
"is_list": builtin_is_list, "is_null": builtin_is_null,
"abs": builtin_abs, "append": builtin_append, "pop": builtin_pop,
"range": builtin_range, "start_thread": builtin_start_thread,
"join_thread": builtin_join_thread, "Lock": builtin_Lock,
}
class Interpreter: # ... (Full Interpreter definition with all visit methods) ...
def __init__(self, is_module_execution=False, current_script_path=None, initial_environment=None):
self.environment = {}
if initial_environment is not None: self.environment.update(initial_environment)
for name, func in BUILTIN_FUNCTIONS_DEF.items():
if name not in self.environment: self.environment[name] = func
self.current_instance_for_self = None; self.current_method_defining_class = None
self.is_in_method_call = False; self.loop_depth = 0
self.is_module_execution = is_module_execution; self.current_script_path = current_script_path
self.is_compiler_mode = False
def _resolve_module_path(self, relative_path):
if os.path.isabs(relative_path): return relative_path
base_dir = os.getcwd();
if self.current_script_path: base_dir = os.path.dirname(self.current_script_path)
path = os.path.join(base_dir, relative_path)
if not os.path.splitext(path)[1]:
path_with_ext = path + ".mini"
if os.path.exists(path_with_ext): return os.path.abspath(path_with_ext)
return os.path.abspath(path)
def _load_module(self, filepath_str):
abs_filepath = self._resolve_module_path(filepath_str)
if abs_filepath in _LOADED_MODULES_CACHE: return _LOADED_MODULES_CACHE[abs_filepath]
try:
with open(abs_filepath, 'r', encoding='utf-8') as f: module_code = f.read()
except FileNotFoundError: raise _create_interpreter_error(f"No module named '{filepath_str}' (resolved to '{abs_filepath}')", E_MODULE_NOT_FOUND_ERROR)
except Exception as e: raise _create_interpreter_error(f"Could not read module '{filepath_str}': {e}", E_IO_ERROR)
module_interpreter = Interpreter(is_module_execution=True, current_script_path=abs_filepath)
module_name = os.path.splitext(os.path.basename(abs_filepath))[0]
module_namespace_obj = MiniModuleNamespace(module_name, module_interpreter.environment)
_LOADED_MODULES_CACHE[abs_filepath] = module_namespace_obj
try:
module_tokens = tokenize(module_code); module_parser = Parser(module_tokens)
module_ast = module_parser.parse(); module_interpreter.interpret(module_ast)
except Exception as e:
if abs_filepath in _LOADED_MODULES_CACHE: del _LOADED_MODULES_CACHE[abs_filepath]
if isinstance(e, InterpreterError): raise
raise _create_interpreter_error(f"Error during module '{module_name}' execution: {e}", "ModuleExecutionError")
return module_namespace_obj
def visit_ImportNode(self, node):
if _CURRENTLY_USING_COMPILER: raise _create_interpreter_error("Modules/imports not supported in compiled mode yet.", "CompilerError")
filepath_str = node.filepath_token.value; module_obj = self._load_module(filepath_str)
self.environment[module_obj.name] = module_obj; return None
def visit_ImportFromNode(self, node):
if _CURRENTLY_USING_COMPILER: raise _create_interpreter_error("Modules/imports not supported in compiled mode yet.", "CompilerError")
filepath_str = node.filepath_token.value; module_obj = self._load_module(filepath_str)
if node.import_all:
for name, value in module_obj._environment.items():
if not name.startswith("__") and name not in BUILTIN_FUNCTIONS_DEF: self.environment[name] = value
else:
for name_token in node.names_tokens:
name_to_import = name_token.value
if name_to_import in module_obj._environment: self.environment[name_to_import] = module_obj._environment[name_to_import]
else: raise _create_interpreter_error(f"cannot import name '{name_to_import}' from module '{module_obj.name}'", E_IMPORT_ERROR)
return None
def visit(self, node): visitor = getattr(self,f'visit_{type(node).__name__}',self.generic_visit); return visitor(node)
def generic_visit(self, node): raise _create_interpreter_error(f"No visit method for {type(node).__name__}", "InternalError")
def visit_NumberNode(self, node): return node.value
def visit_StringNode(self, node): return node.value
def visit_BooleanNode(self, node): return node.value
def visit_NullNode(self, node): return node.value
def visit_ListNode(self, node): return [self.visit(elem) for elem in node.elements]
def visit_DictionaryNode(self, node):
if _CURRENTLY_USING_COMPILER: raise _create_interpreter_error("Dictionaries not compiled.", "CompilerError")
the_dict = {}
for key_node, value_node in node.pairs:
key = self.visit(key_node)
if not isinstance(key,(int,float,str,bool)) and key is not None: raise _create_interpreter_error(f"unhashable type: '{type(key).__name__}' for dict key", E_TYPE_ERROR)
the_dict[key] = self.visit(value_node)
return the_dict
def visit_SelfNode(self, node):
if self.current_instance_for_self is None: raise _create_interpreter_error("'self' outside method.", E_NAME_ERROR)
return self.current_instance_for_self
def visit_SuperNode(self, node):
if self.current_instance_for_self is None or self.current_method_defining_class is None: raise _create_interpreter_error("'super' outside method context.", E_SYNTAX_ERROR)
return (self.current_instance_for_self, self.current_method_defining_class)
def visit_VariableNode(self, node):
var_name = node.name; val = self.environment.get(var_name)
if val is None and var_name not in self.environment: raise _create_interpreter_error(f"name '{var_name}' not defined", E_NAME_ERROR)
return val
def visit_IndexAccessNode(self, node):
coll = self.visit(node.collection_expr); key_idx = self.visit(node.index_or_key_expr)
if isinstance(coll, list):
if not isinstance(key_idx, int): raise _create_interpreter_error(f"List indices must be int, not '{type(key_idx).__name__}'", E_TYPE_ERROR)
try: return coll[key_idx]
except IndexError: raise _create_interpreter_error(f"list index {key_idx} out of range", E_INDEX_ERROR)
elif isinstance(coll, dict):
if not isinstance(key_idx,(int,float,str,bool)) and key_idx is not None: raise _create_interpreter_error(f"unhashable type: '{type(key_idx).__name__}' for dict key", E_TYPE_ERROR)
try: return coll[key_idx]
except KeyError: raise _create_interpreter_error(f"key {repr(key_idx)} not found", E_KEY_ERROR)
else: raise _create_interpreter_error(f"'{type(coll).__name__}' not subscriptable", E_TYPE_ERROR)
def visit_MemberAccessNode(self, node):
obj_val = self.visit(node.object_expr); member_name = node.member_token.value
if isinstance(obj_val, MiniModuleNamespace):
try: return getattr(obj_val, member_name)
except AttributeError: raise _create_interpreter_error(f"Module '{obj_val.name}' has no attribute '{member_name}'", E_ATTRIBUTE_ERROR)
if isinstance(obj_val, MiniPyInstance):
if member_name in obj_val.attributes: return obj_val.attributes[member_name]
method_def = obj_val.klass.find_method(member_name)
if method_def:
curr_klass,found_klass=obj_val.klass,obj_val.klass
while curr_klass:
if member_name in curr_klass.methods_map and curr_klass.methods_map[member_name]==method_def: found_klass=curr_klass;break
curr_klass=curr_klass.parent_class
return BoundMethod(obj_val,method_def,found_klass or obj_val.klass)
raise _create_interpreter_error(f"'{obj_val.klass.name}' object has no attribute/method '{member_name}'", E_ATTRIBUTE_ERROR)
elif isinstance(obj_val,tuple) and len(obj_val)==2 and isinstance(obj_val[0],MiniPyInstance): # super.method
instance,class_super_called_in=obj_val;parent_class=class_super_called_in.parent_class
if not parent_class:raise _create_interpreter_error(f"'{class_super_called_in.name}' has no parent for 'super.{member_name}'.",E_TYPE_ERROR)
method_def=parent_class.find_method(member_name)
if method_def:
found_klass_super,tmp_klass=parent_class,parent_class
while tmp_klass:
if member_name in tmp_klass.methods_map and tmp_klass.methods_map[member_name]==method_def:found_klass_super=tmp_klass;break
tmp_klass=tmp_klass.parent_class
return BoundMethod(instance,method_def,found_klass_super or parent_class)
raise _create_interpreter_error(f"'super' (via {parent_class.name}) has no method '{member_name}'",E_ATTRIBUTE_ERROR)
elif isinstance(obj_val, MiniLock):
if member_name=="acquire":return obj_val.acquire
if member_name=="release":return obj_val.release
raise _create_interpreter_error(f"'MiniLock' has no attribute '{member_name}'",E_ATTRIBUTE_ERROR)
raise _create_interpreter_error(f"Member access needs instance, module, Lock or 'super'. Got {type(obj_val).__name__}",E_TYPE_ERROR)
def visit_UnaryOpNode(self, node): op=node.op_token.type;val=self.visit(node.expr_node); return not bool(val) if op=='NOT' else self._unknown_op(op)
def visit_BinOpNode(self, node):
op=node.op_token.type
if op=='AND':l=self.visit(node.left);return l if not bool(l)else self.visit(node.right)
if op=='OR':l=self.visit(node.left);return l if bool(l)else self.visit(node.right)
l,r=self.visit(node.left),self.visit(node.right)
try:
if op=='PLUS':
if isinstance(l,(list,str))and type(l)==type(r):return l+r
if isinstance(l,(int,float))and isinstance(r,(int,float)):return l+r
raise TypeError()
if op=='MINUS':return l-r;
if op=='MUL':return l*r;
if op=='DIV':
if not isinstance(r,(int,float))or not isinstance(l,(int,float)):raise TypeError()
if r==0:raise _create_interpreter_error("division by zero",E_ZERO_DIVISION_ERROR)
return l/r
if op=='EQ':return l==r; if op=='NEQ':return l!=r; if op=='LT':return l<r;
if op=='GT':return l>r; if op=='LTE':return l<=r; if op=='GTE':return l>=r
except TypeError:raise _create_interpreter_error(f"unsupported operand type(s) for {node.op_token.value}: '{type(l).__name__}' and '{type(r).__name__}'",E_TYPE_ERROR)
except Exception as e:raise _create_interpreter_error(f"RuntimeError for op {op}: {e}","RuntimeError")
def _call_method_or_function(self, callable_object, instance_for_self, defining_class_for_method, args, is_init=False, for_dunder_str=False, is_standalone_func=False):
code_def_node=callable_object;expected_params=len(code_def_node.params_tokens);actual_args=len(args)
func_name_err=code_def_node.name_token.value;target_name_err=func_name_err
if not is_standalone_func and instance_for_self:
class_name_err=defining_class_for_method.name if defining_class_for_method else instance_for_self.klass.name
target_name_err=f"{class_name_err}.{func_name_err}"
if is_init:target_name_err=f"{class_name_err}.__init__"
if for_dunder_str and actual_args!=0:raise _create_interpreter_error(f"{target_name_err}() takes 0 args but {actual_args} given",E_TYPE_ERROR)
elif not for_dunder_str and actual_args!=expected_params:raise _create_interpreter_error(f"{target_name_err}() takes {expected_params} args but {actual_args} given",E_TYPE_ERROR)
prev_s,prev_iim,prev_mdefc=self.current_instance_for_self,self.is_in_method_call,self.current_method_defining_class
self.current_instance_for_self=instance_for_self if not is_standalone_func else None
self.is_in_method_call=True;self.current_method_defining_class=defining_class_for_method if not is_standalone_func else None
params_backup={};
for i,param_token in enumerate(code_def_node.params_tokens):
name=param_token.value
if name in self.environment:params_backup[name]=self.environment[name]
self.environment[name]=args[i]
ret_val=None
try:self.visit(code_def_node.body_node)
except ReturnSignal as rs:
if is_init and rs.value is not None:raise _create_interpreter_error(f"__init__ of {instance_for_self.klass.name} should not return value",E_TYPE_ERROR)
if not is_init or is_standalone_func or for_dunder_str:ret_val=rs.value
finally:
self.current_instance_for_self,self.is_in_method_call,self.current_method_defining_class=prev_s,prev_iim,prev_mdefc
for i,param_token in enumerate(code_def_node.params_tokens):
name=param_token.value
if name in params_backup:self.environment[name]=params_backup[name]
else:del self.environment[name]
return ret_val
def visit_FunctionCallNode(self, node):
callable_target=self.visit(node.callable_expr);args=[self.visit(arg)for arg in node.arg_nodes]
if isinstance(callable_target,BoundMethod):return self._call_method_or_function(callable_target.method_def_node,callable_target.instance,callable_target.defining_class,args)
if isinstance(callable_target,MiniPyClass): # Instantiation
instance=MiniPyInstance(callable_target)
init_method=callable_target.find_method("__init__")
if init_method:self._call_method_or_function(init_method,instance,callable_target,args,is_init=True)
elif args:raise _create_interpreter_error(f"{callable_target.name}() takes no args if no __init__, but {len(args)} given.",E_TYPE_ERROR)
return instance
if isinstance(callable_target,tuple)and len(callable_target)==2 and isinstance(callable_target[0],MiniPyInstance): # super()
instance,class_super_called_in=callable_target;parent_class=class_super_called_in.parent_class
if not parent_class:raise _create_interpreter_error(f"'{class_super_called_in.name}' has no parent for 'super()'.",E_TYPE_ERROR)
parent_init=parent_class.find_method("__init__")
if not parent_init:
if args:raise _create_interpreter_error(f"{parent_class.name}.__init__ (via super) no args, but {len(args)} given.",E_TYPE_ERROR)
return None
return self._call_method_or_function(parent_init,instance,parent_class,args,is_init=True)
if isinstance(callable_target,MiniPyFunction):return self._call_method_or_function(callable_target.func_def_node,None,None,args,is_standalone_func=True)
if callable(callable_target)and callable_target in BUILTIN_FUNCTIONS_DEF.values():
try:
if callable_target in(builtin_str,builtin_eval_string,builtin_start_thread):return callable_target(args,self,getattr(self,'is_compiler_mode',False))
return callable_target(args)
except InterpreterError:raise
except Exception as e:raise _create_interpreter_error(f"Error in built-in: {e}","BuiltinError")
if callable(callable_target)and hasattr(callable_target,'__self__')and isinstance(callable_target.__self__,MiniLock):
try:return callable_target(*args) # Call Python method directly
except RuntimeError as re:raise _create_interpreter_error(str(re),"RuntimeError") # e.g. release unacquired lock
except TypeError as te:raise _create_interpreter_error(str(te),E_TYPE_ERROR) # e.g. wrong number of args to Python method
raise _create_interpreter_error(f"'{type(callable_target).__name__}' not callable or not a recognized function/class.",E_TYPE_ERROR)
def visit_ReturnNode(self, node):
if not self.is_in_method_call: raise _create_interpreter_error("'return' outside function/method", E_SYNTAX_ERROR)
val = self.visit(node.expr_node) if node.expr_node else None; raise ReturnSignal(val)
def visit_ClassDefNode(self, node):
name=node.name_token.value;parent=None
if node.parent_class_token:
p_name=node.parent_class_token.value;p_obj=self.environment.get(p_name)
if not isinstance(p_obj,MiniPyClass):raise _create_interpreter_error(f"Parent class '{p_name}' not found or not a class.",E_TYPE_ERROR)
parent=p_obj
methods={m.name_token.value:m for m in node.methods};self.environment[name]=MiniPyClass(name,parent,methods);return None
def visit_FunctionDefNode(self, node): name=node.name_token.value;self.environment[name]=MiniPyFunction(name,node);return None
def visit_AssignNode(self, node):
val=self.visit(node.value_node);target=node.target_node
if isinstance(target,VariableNode):self.environment[target.name]=val
elif isinstance(target,IndexAccessNode):
coll=self.visit(target.collection_expr);key_idx=self.visit(target.index_or_key_expr)
if isinstance(coll,list):
if not isinstance(key_idx,int):raise _create_interpreter_error(f"List indices must be int, not '{type(key_idx).__name__}'",E_TYPE_ERROR)
try:coll[key_idx]=val
except IndexError:raise _create_interpreter_error(f"list assignment index {key_idx} out of range",E_INDEX_ERROR)
elif isinstance(coll,dict):
if not isinstance(key_idx,(int,float,str,bool))and key_idx is not None:raise _create_interpreter_error(f"unhashable type: '{type(key_idx).__name__}' for dict key assign",E_TYPE_ERROR)
coll[key_idx]=val
else:raise _create_interpreter_error(f"'{type(coll).__name__}' object does not support item assignment.",E_TYPE_ERROR)
elif isinstance(target,MemberAccessNode):
obj=self.visit(target.object_expr)
if not isinstance(obj,MiniPyInstance):raise _create_interpreter_error(f"assign attributes to instances. Got {type(obj).__name__}",E_TYPE_ERROR)
obj.attributes[target.member_token.value]=val
else:raise _create_interpreter_error("Invalid target for assignment.","InternalError")
return val
def visit_PrintNode(self, node): val=self.visit(node.expr_node);print(builtin_str([val],self));return val
def visit_BlockNode(self, node): last_val=None;for stmt in node.statements:last_val=self.visit(stmt);return last_val
def visit_IfNode(self, node):
if bool(self.visit(node.condition)):return self.visit(node.if_block)
elif node.else_block:return self.visit(node.else_block)
return None
def visit_BreakNode(self, node):
if self.loop_depth==0:raise _create_interpreter_error("'break' outside loop",E_SYNTAX_ERROR)
raise BreakSignal()
def visit_ContinueNode(self, node):
if self.loop_depth==0:raise _create_interpreter_error("'continue' outside loop",E_SYNTAX_ERROR)
raise ContinueSignal()
def visit_ForInNode(self, node):
if _CURRENTLY_USING_COMPILER:raise _create_interpreter_error("For loops not compiled.", "CompilerError")
iterable=self.visit(node.iterable_node)
if not isinstance(iterable,list):raise _create_interpreter_error(f"'{type(iterable).__name__}' not iterable (expected list).",E_TYPE_ERROR)
var_name=node.var_token.value;last_val=None;self.loop_depth+=1
try:
for item in iterable:
self.environment[var_name]=item
try:last_val=self.visit(node.body_node)
except ContinueSignal:continue
except BreakSignal:break
finally:self.loop_depth-=1
return last_val
def visit_WhileNode(self, node):
last_val=None;self.loop_depth+=1
try:
while bool(self.visit(node.condition_node)):
try:last_val=self.visit(node.body_node)
except ContinueSignal:continue
except BreakSignal:break
finally:self.loop_depth-=1
return last_val
def visit_TryExceptNode(self, node):
try:return self.visit(node.try_block)
except(ReturnSignal,BreakSignal,ContinueSignal):raise
except InterpreterError as e:
for clause in node.specific_except_clauses:
if e.error_type==clause.error_type_token.value:
try:return self.visit(clause.block_node)
except(ReturnSignal,BreakSignal,ContinueSignal):raise
except InterpreterError as e2:raise e2
if node.generic_except_block:
try:return self.visit(node.generic_except_block)
except(ReturnSignal,BreakSignal,ContinueSignal):raise
except InterpreterError as e3:raise e3
raise e
def interpret(self, ast_statements, is_eval_call=False):
last_val=None
if not ast_statements:return None
for stmt in ast_statements:
try:last_val=self.visit(stmt)
except ReturnSignal as rs:
if is_eval_call:return rs.value
if not self.is_module_execution:print(_create_interpreter_error("'return' outside method/function",E_SYNTAX_ERROR));return
else:raise rs
except BreakSignal:
if is_eval_call:raise _create_interpreter_error("'break' outside loop in eval",E_SYNTAX_ERROR)
print(_create_interpreter_error("'break' outside loop",E_SYNTAX_ERROR));return
except ContinueSignal:
if is_eval_call:raise _create_interpreter_error("'continue' outside loop in eval",E_SYNTAX_ERROR)
print(_create_interpreter_error("'continue' outside loop",E_SYNTAX_ERROR));return
except InterpreterError as e:print(e);return
except Exception as e:print(f"Internal Error: {e}");import traceback;traceback.print_exc();return
return last_val
# --- Main Execution ---
_CURRENTLY_USING_COMPILER = False; _MAIN_SCRIPT_PATH = None; _LOADED_MODULES_CACHE = {}
_active_mini_threads_global_ref = []
def run_minipy(code_or_filepath, is_filepath=False, use_compiler=False):
global _CURRENTLY_USING_COMPILER,_MAIN_SCRIPT_PATH,_LOADED_MODULES_CACHE,_active_mini_threads_global_ref
is_primary,cur_abs_path=(False,None)
if is_filepath:cur_abs_path=os.path.abspath(code_or_filepath)
else:cur_abs_path=os.getcwd()
if _MAIN_SCRIPT_PATH is None:is_primary=True;_MAIN_SCRIPT_PATH=cur_abs_path
elif _MAIN_SCRIPT_PATH==cur_abs_path and not hasattr(run_minipy,'sub_run_active'):is_primary=True
if is_primary:_LOADED_MODULES_CACHE={};_active_mini_threads_global_ref=[]
_CURRENTLY_USING_COMPILER=use_compiler;code_to_run,script_path_for_run=("",None)
if is_filepath:
script_path_for_run=os.path.abspath(code_or_filepath)
if _MAIN_SCRIPT_PATH is None:_MAIN_SCRIPT_PATH=script_path_for_run
try:
with open(script_path_for_run,'r',encoding='utf-8')as f:code_to_run=f.read()
print(f"\nExecuting Mini file '{script_path_for_run}' ({'compiler' if use_compiler else 'interpreter'}):\n---")
except Exception as e:print(f"Error reading file '{script_path_for_run}': {e}");return
else:
code_to_run=code_or_filepath;script_path_for_run=_MAIN_SCRIPT_PATH if _MAIN_SCRIPT_PATH else os.getcwd()
print(f"\nExecuting Mini code string ({'compiler' if use_compiler else 'interpreter'}):\n---\n{code_to_run.strip()}\n---")
if not os.path.exists("minipy_test_files"):os.makedirs("minipy_test_files")
try:
run_minipy.sub_run_active=True;tokens=tokenize(code_to_run);parser=Parser(tokens);ast=parser.parse()
if use_compiler:
unsupported_nodes=(ImportNode,ImportFromNode,ClassDefNode,MethodDefNode,TryExceptNode,SelfNode,SuperNode,MemberAccessNode) # ForInNode, DictionaryNode, IndexAccessNode compiled
def check_unsupported(node):
if isinstance(node,unsupported_nodes):return True
for _,v in node.__dict__.items():
if isinstance(v,ASTNode)and check_unsupported(v):return True
if isinstance(v,list):
for i in v:
if isinstance(i,ASTNode)and check_unsupported(i):return True
return False
has_unsupported=any(check_unsupported(s)for s in ast)
if not has_unsupported:
for s in ast: # Check for interpreter-only builtins
if isinstance(s,FunctionCallNode)and isinstance(s.callable_expr,VariableNode)and s.callable_expr.name in["start_thread","join_thread","Lock","eval_string","range","is_number","is_string","is_list","is_null","abs","append","pop","string_upper","string_lower","string_startswith","string_find","string_split","string_strip","list_insert","list_remove","list_reverse","pow","floor","ceil","sin","cos","tan", "read_file", "write_file", "time", "input", "number", "list_sort", "string_join", "string_replace", "list_count"]:has_unsupported=True;break
if has_unsupported:print("Compiler Warning: Code has unsupported features. Using interpreter.");use_compiler=False;_CURRENTLY_USING_COMPILER=False
if use_compiler:
print("Compiling...");compiler=Compiler()
try:code_obj=compiler.compile_program(ast);print("Running VM...");vm=VirtualMachine();vm.run(code_obj)
except CompilerError as ce:print(f"Compiler Error: {ce}")
except VirtualMachineError as vme:print(f"VM Error: {vme}")
else:interp=Interpreter(current_script_path=script_path_for_run);interp.is_compiler_mode=False;interp.interpret(ast)
print("--- Execution Finished ---")
except(LexerError,ParserError,InterpreterError)as e:print(f"Error: {e}");print("--- Execution Halted ---")
except Exception as e:import traceback;print(f"Unexpected system error: {e}");traceback.print_exc();print("--- Execution Halted ---")
finally:
if is_primary:_MAIN_SCRIPT_PATH=None
if hasattr(run_minipy,'sub_run_active'):delattr(run_minipy,'sub_run_active')
if __name__ == '__main__':
code_compiler_functions_and_classes_stub = """
?? --- Test Compiler & VM with Function Defs and Basic Class Defs ---
function my_add(a, b) {
c = a + b
return c
}
function greet(name_str) {
msg = "Hello, " + name_str
print msg
return Null ?? Explicit return Null
}
sum_val = my_add(10, 22)
print "Sum from my_add (VM):"
print sum_val ?? Should be 32
greet_res = greet("VM User")
print "Greet result (VM - should be Null):"
print greet_res
?? Basic class definition (no methods compiled for VM yet)
class Point {
?? Methods would go here, but compiler ignores them for now
}
print "Type of Point (VM):"
print type(Point) ?? Should be vm_class or similar
?? Basic instantiation (no __init__ call by VM yet)
?? p = Point()
?? print "Instance p (VM):"
?? print p
?? print type(p)
?? Class with extends (parent must be defined first)
class ColorPoint extends Point {
}
print "Type of ColorPoint (VM):"
print type(ColorPoint)
print "?? VM Test for Funcs & Basic Classes Finished!"
"""
print("\n--- Running Function & Basic Class Def tests with COMPILER ---")
run_minipy(code_compiler_functions_and_classes_stub, use_compiler=True)
print("\n--- Running Function & Basic Class Def tests with INTERPRETER (for comparison) ---")
run_minipy(code_compiler_functions_and_classes_stub, use_compiler=False)
code_compiler_full_control_flow = """
?? --- Test Compiler & VM with Full Control Flow & Expressions ---
print "?? If/Else with various conditions (Compiler)"
val_true = True; val_false = False; num1 = 10; num2 = 5
str1 = "hello"; str2 = "world"
list1 = [1,2]; dict1 = {"a":1}
if (val_true and (num1 > num2)) { print "VM Cond 1: True"; } else { print "VM Cond 1: FAILED"; }
if (str1 == "hello" or list1[0] == 0) { print "VM Cond 2: True"; } else { print "VM Cond 2: FAILED"; }
if (not (num1 < num2) and (str1 + str2 == "helloworld")) { print "VM Cond 3: True"; }
if (dict1["a"] == list1[0]) { print "VM Cond 4: True"; }
print "?? While Loop with break/continue (Compiler)"
counter = 0; limit = 5; accumulator = 0
while (counter < limit and (accumulator < 10 or counter == 0) ) {
counter = counter + 1
if (counter == 2) { print " VM While: continue at 2"; accumulator = accumulator + 100; continue; }
accumulator = accumulator + counter
print " VM While: counter=" + str(counter) + ", acc=" + str(accumulator)
if (counter == 4) { print " VM While: break at 4"; break; }
}
print "VM After while: counter=" + str(counter) + ", acc=" + str(accumulator)
print "?? For loop over list literal (Compiler):"
for item_vm in [77, "eight", 99.0] { print "VM for item: "; print item_vm; }
print "VM for loop done."
"""
print("\n--- Running Solidified Control Flow & Expressions with COMPILER ---")
run_minipy(code_compiler_full_control_flow, use_compiler=True)
This is roughly “MiniPy 001.04” because it’s the forth iteration of it, the forth time i’ve saved the entire language. I have a numbering system that uses 001 so it’s version 1, revision 4. if it were version 2, revision 4, that would mean that there have been 100 revisions since the version 2 means 100 previous revisions. So this is only the very first 24 hours of this, more to come later, like tomorrow after i’ve slept after all of this madness. That was me refering to myself, sorry.
it weighs in at 1.4k lines and it is a fuckin tank. It even has some threading support. It’s not ready to create an AI in yet, but it’s only been 24 hours that i’ve had Gemini working on it. Things like this take humans decades, look at how long Bjarne Stroustrup has been working on C++, he doesn’t even have an AI, at least not that I know of, he doesn’t get into the program business, he’s in the programming business! C++ is the godfather of systems design, and I was even pondering writing Mini in C++ by hand, to get everything working just right, in a very fast language, the C++ system.
But this isn’t about C++ anymore, it’s about what is going to be most effective, and that is Gemini writing Python I am afraid. Now how might I add PCG random from C++ to my python program? I bet Gemini knows how to do that too.
Hi! Do you know if they make any plugins to help with SEO?
I’m trying to get my blog to rank for some targeted keywords but I’m
not seeing very good success. If you know of any please share.
Thanks!
Look at my web blog :: roulette online real money
Hello to every one, it’s actually a nice for me to pay a quick visit this web site, it consists of priceless Information.
I’m not sure the place you’re getting your info, but great topic.
I must spend a while studying more or figuring out more.
Thanks for fantastic information I was looking
for this information for my mission.
I want to to thank you for this very good read!! I absolutely loved every bit of it.
I have you book marked to look at new stuff you post…
It’s very simple to find out any topic on net as compared
to textbooks, as I found this paragraph at this website.
I visit every day a few blogs and websites to read content,
except this webpage offers quality based content.