1425 lines
61 KiB
Python
1425 lines
61 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
|
|
class Token:
|
|
def __init__(self, type_, value, line, column):
|
|
self.type = type_
|
|
self.value = value
|
|
self.line = line
|
|
self.column = column
|
|
|
|
def __str__(self):
|
|
return f"Token({self.type}, {repr(self.value)}, {self.line}, {self.column})"
|
|
|
|
class Lexer:
|
|
def __init__(self, source):
|
|
self.source = source
|
|
self.position = 0
|
|
self.line = 1
|
|
self.column = 1
|
|
|
|
def get_next_token(self):
|
|
while self.position < len(self.source):
|
|
char = self.source[self.position]
|
|
|
|
# Skip whitespace
|
|
if char.isspace():
|
|
if char == '\n':
|
|
self.line += 1
|
|
self.column = 1
|
|
else:
|
|
self.column += 1
|
|
self.position += 1
|
|
continue
|
|
|
|
# Skip comments (!! for single line, !* ... *! for multi-line)
|
|
if char == '!' and self.position + 1 < len(self.source):
|
|
# Check for single-line comment (!!)
|
|
if self.source[self.position + 1] == '!':
|
|
self.position += 2
|
|
self.column += 2
|
|
while self.position < len(self.source) and self.source[self.position] != '\n':
|
|
self.position += 1
|
|
self.column += 1
|
|
continue
|
|
# Check for multi-line comment (!* ... *!)
|
|
elif self.source[self.position + 1] == '*':
|
|
self.position += 2
|
|
self.column += 2
|
|
# Skip until closing *!
|
|
while self.position + 1 < len(self.source) and not (self.source[self.position] == '*' and self.source[self.position + 1] == '!'):
|
|
if self.source[self.position] == '\n':
|
|
self.line += 1
|
|
self.column = 1
|
|
else:
|
|
self.column += 1
|
|
self.position += 1
|
|
if self.position + 1 < len(self.source):
|
|
self.position += 2
|
|
self.column += 2
|
|
continue
|
|
# Include directive
|
|
elif char == '!' and self.position + 7 < len(self.source) and self.source[self.position:self.position+8] == '!include':
|
|
self.position += 8
|
|
self.column += 8
|
|
# Skip whitespace
|
|
while self.position < len(self.source) and self.source[self.position].isspace():
|
|
self.position += 1
|
|
self.column += 1
|
|
# Read module name in quotes
|
|
if self.position < len(self.source) and self.source[self.position] == "'":
|
|
self.position += 1
|
|
self.column += 1
|
|
start = self.position
|
|
while self.position < len(self.source) and self.source[self.position] != "'":
|
|
self.position += 1
|
|
self.column += 1
|
|
if self.position < len(self.source):
|
|
self.position += 1
|
|
self.column += 1
|
|
module = self.source[start:self.position-1]
|
|
return Token('INCLUDE_DIRECTIVE', module, self.line, self.column - len(module) - 2)
|
|
|
|
# String literal
|
|
if char == '`':
|
|
start = self.position
|
|
self.position += 1
|
|
self.column += 1
|
|
value = ''
|
|
while self.position < len(self.source) and self.source[self.position] != '`':
|
|
if self.source[self.position] == '\\':
|
|
# Handle escape sequences
|
|
self.position += 1
|
|
self.column += 1
|
|
if self.position < len(self.source):
|
|
if self.source[self.position] == 'n':
|
|
value += '\n'
|
|
elif self.source[self.position] == 't':
|
|
value += '\t'
|
|
elif self.source[self.position] == 'r':
|
|
value += '\r'
|
|
elif self.source[self.position] == '\\':
|
|
value += '\\'
|
|
elif self.source[self.position] == '`':
|
|
value += '`'
|
|
else:
|
|
# Unknown escape sequence, just add the character
|
|
value += self.source[self.position]
|
|
self.position += 1
|
|
self.column += 1
|
|
elif self.source[self.position] == '\n':
|
|
value += '\n'
|
|
self.line += 1
|
|
self.column = 1
|
|
self.position += 1
|
|
else:
|
|
value += self.source[self.position]
|
|
self.column += 1
|
|
self.position += 1
|
|
if self.position < len(self.source):
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('STRING', value, self.line, self.column - len(value) - 2)
|
|
|
|
# Negative number
|
|
if char == '-' and self.position + 1 < len(self.source) and self.source[self.position + 1].isdigit():
|
|
start = self.position
|
|
self.position += 1
|
|
self.column += 1
|
|
while self.position < len(self.source) and self.source[self.position].isdigit():
|
|
self.position += 1
|
|
self.column += 1
|
|
value = self.source[start:self.position]
|
|
return Token('INT', value, self.line, self.column - len(value))
|
|
# Number
|
|
elif char.isdigit():
|
|
start = self.position
|
|
while self.position < len(self.source) and self.source[self.position].isdigit():
|
|
self.position += 1
|
|
self.column += 1
|
|
value = self.source[start:self.position]
|
|
return Token('INT', value, self.line, self.column - len(value))
|
|
|
|
# Identifier or keyword
|
|
if char.isalpha() or char == '_':
|
|
start = self.position
|
|
while self.position < len(self.source) and (self.source[self.position].isalnum() or self.source[self.position] == '_'):
|
|
self.position += 1
|
|
self.column += 1
|
|
value = self.source[start:self.position]
|
|
if value == 'show':
|
|
return Token('KEYWORD_SHOW', value, self.line, self.column - len(value))
|
|
elif value == 'if':
|
|
return Token('KEYWORD_IF', value, self.line, self.column - len(value))
|
|
elif value == 'else':
|
|
return Token('KEYWORD_ELSE', value, self.line, self.column - len(value))
|
|
elif value == 'while':
|
|
return Token('KEYWORD_WHILE', value, self.line, self.column - len(value))
|
|
elif value == 'def':
|
|
return Token('KEYWORD_DEF', value, self.line, self.column - len(value))
|
|
elif value == 'class':
|
|
return Token('KEYWORD_CLASS', value, self.line, self.column - len(value))
|
|
elif value == 'return':
|
|
return Token('KEYWORD_RETURN', value, self.line, self.column - len(value))
|
|
elif value == 'import':
|
|
return Token('KEYWORD_IMPORT', value, self.line, self.column - len(value))
|
|
else:
|
|
return Token('IDENTIFIER', value, self.line, self.column - len(value))
|
|
|
|
# Operators and punctuation
|
|
if char == '+' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '+':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('PLUS_PLUS', '++', self.line, self.column - 2)
|
|
elif char == '-' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '-':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('MINUS_MINUS', '--', self.line, self.column - 2)
|
|
elif char == '+' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('PLUS_EQUALS', '+=', self.line, self.column - 2)
|
|
elif char == '-' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('MINUS_EQUALS', '-=', self.line, self.column - 2)
|
|
elif char == '*' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('MULTIPLY_EQUALS', '*=', self.line, self.column - 2)
|
|
elif char == '/' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('DIVIDE_EQUALS', '/=', self.line, self.column - 2)
|
|
elif char == '=' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('EQUALS', '==', self.line, self.column - 2)
|
|
elif char == '=':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('ASSIGN', '=', self.line, self.column - 1)
|
|
elif char == '!' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('NOT_EQUALS', '!=', self.line, self.column - 2)
|
|
elif char == '@':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('AT', '@', self.line, self.column - 1)
|
|
elif char == '.':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('DOT', '.', self.line, self.column - 1)
|
|
elif char == '<' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('LESS_EQUAL', '<=', self.line, self.column - 2)
|
|
elif char == '>' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('GREATER_EQUAL', '>=', self.line, self.column - 2)
|
|
elif char == '&' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '&':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('AND', '&&', self.line, self.column - 2)
|
|
elif char == '|' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '|':
|
|
self.position += 2
|
|
self.column += 2
|
|
return Token('OR', '||', self.line, self.column - 2)
|
|
elif char == '@':
|
|
# Check if it's an include directive
|
|
if self.position + 7 < len(self.source) and self.source[self.position+1:self.position+8] == 'include':
|
|
# It's an include directive
|
|
# Move past '@include'
|
|
self.position += 8
|
|
self.column += 8
|
|
# Skip whitespace after include
|
|
while self.position < len(self.source) and self.source[self.position].isspace():
|
|
self.position += 1
|
|
self.column += 1
|
|
# Get the module name
|
|
if self.position < len(self.source) and (self.source[self.position].isalpha() or self.source[self.position] == '_'):
|
|
start = self.position
|
|
while self.position < len(self.source) and (self.source[self.position].isalnum() or self.source[self.position] == '_'):
|
|
self.position += 1
|
|
module_name = self.source[start:self.position]
|
|
self.column += len(module_name)
|
|
return Token('INCLUDE_DIRECTIVE', module_name, self.line, self.column - len(module_name))
|
|
# Otherwise, it's an unknown token
|
|
self.position += 1
|
|
self.column += 1
|
|
elif char == '!':
|
|
# It's a NOT operator
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('NOT', '!', self.line, self.column - 1)
|
|
elif char == '+':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('PLUS', '+', self.line, self.column - 1)
|
|
elif char == '-':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('MINUS', '-', self.line, self.column - 1)
|
|
elif char == '*':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('MULTIPLY', '*', self.line, self.column - 1)
|
|
elif char == '/':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('DIVIDE', '/', self.line, self.column - 1)
|
|
elif char == '=':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('ASSIGN', '=', self.line, self.column - 1)
|
|
elif char == '<':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('LESS', '<', self.line, self.column - 1)
|
|
elif char == '>':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('GREATER', '>', self.line, self.column - 1)
|
|
elif char == '(':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('LPAREN', '(', self.line, self.column - 1)
|
|
elif char == ')':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('RPAREN', ')', self.line, self.column - 1)
|
|
elif char == ';':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('SEMICOLON', ';', self.line, self.column - 1)
|
|
elif char == ',':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('COMMA', ',', self.line, self.column - 1)
|
|
elif char == '{':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('LBRACE', '{', self.line, self.column - 1)
|
|
elif char == '}':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('RBRACE', '}', self.line, self.column - 1)
|
|
elif char == '[':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('LBRACKET', '[', self.line, self.column - 1)
|
|
elif char == ']':
|
|
self.position += 1
|
|
self.column += 1
|
|
return Token('RBRACKET', ']', self.line, self.column - 1)
|
|
|
|
# Unknown character
|
|
self.position += 1
|
|
self.column += 1
|
|
|
|
return Token('EOF', None, self.line, self.column)
|
|
|
|
class Parser:
|
|
def __init__(self, lexer):
|
|
self.lexer = lexer
|
|
self.current_token = self.lexer.get_next_token()
|
|
|
|
def error(self, message):
|
|
# For Windows PowerShell compatibility, use simple error messages without ANSI codes
|
|
error_msg = f"Syntax Error at line {self.current_token.line}, column {self.current_token.column}: {message}"
|
|
# Add more context information
|
|
if hasattr(self.lexer, 'source'):
|
|
# Get the line where the error occurred
|
|
lines = self.lexer.source.split('\n')
|
|
if self.current_token.line - 1 < len(lines):
|
|
error_line = lines[self.current_token.line - 1]
|
|
# Add the error line to the error message
|
|
error_msg += f"\nLine {self.current_token.line}: {error_line}"
|
|
# Add a pointer to the error position
|
|
error_msg += f"\n" + ' ' * (len(f"Line {self.current_token.line}: ") + self.current_token.column - 1) + "^"
|
|
# Add specific error messages for common errors
|
|
if "Expected SEMICOLON" in message:
|
|
error_msg += "\nHint: Missing semicolon at the end of the statement."
|
|
elif "Expected LPAREN" in message:
|
|
error_msg += "\nHint: Missing opening parenthesis '('."
|
|
elif "Expected RPAREN" in message:
|
|
error_msg += "\nHint: Missing closing parenthesis ')'."
|
|
elif "Expected LBRACE" in message:
|
|
error_msg += "\nHint: Missing opening brace '{'."
|
|
elif "Expected RBRACE" in message:
|
|
error_msg += "\nHint: Missing closing brace '}'."
|
|
raise Exception(error_msg)
|
|
|
|
def eat(self, token_type):
|
|
if self.current_token.type == token_type:
|
|
self.current_token = self.lexer.get_next_token()
|
|
else:
|
|
self.error(f"Expected {token_type}, got {self.current_token.type}")
|
|
|
|
def factor(self):
|
|
token = self.current_token
|
|
if token.type == 'INT':
|
|
self.eat('INT')
|
|
return {'type': 'literal', 'value': token.value}
|
|
elif token.type == 'STRING':
|
|
self.eat('STRING')
|
|
return {'type': 'literal', 'value': token.value}
|
|
elif token.type == 'IDENTIFIER':
|
|
# Check if it's a function call or array index
|
|
save_position = self.lexer.position
|
|
save_line = self.lexer.line
|
|
save_column = self.lexer.column
|
|
|
|
# Look ahead to see if it's a function call or array index
|
|
is_function_call = False
|
|
is_array_index = False
|
|
pos = save_position
|
|
while pos < len(self.lexer.source) and self.lexer.source[pos].isspace():
|
|
pos += 1
|
|
if pos < len(self.lexer.source):
|
|
if self.lexer.source[pos] == '(':
|
|
is_function_call = True
|
|
elif self.lexer.source[pos] == '[':
|
|
is_array_index = True
|
|
elif self.lexer.source[pos] == '.':
|
|
# Handle dot notation (e.g., math.abs)
|
|
is_function_call = True
|
|
|
|
# Restore the lexer position
|
|
self.lexer.position = save_position
|
|
self.lexer.line = save_line
|
|
self.lexer.column = save_column
|
|
|
|
if is_function_call:
|
|
# It's a function call (could be with dot notation)
|
|
name = self.current_token.value
|
|
self.eat('IDENTIFIER')
|
|
|
|
# Handle dot notation
|
|
while self.current_token.type == 'DOT':
|
|
self.eat('DOT')
|
|
name += '.' + self.current_token.value
|
|
self.eat('IDENTIFIER')
|
|
|
|
self.eat('LPAREN')
|
|
arguments = []
|
|
if self.current_token.type != 'RPAREN':
|
|
arguments.append(self.expression())
|
|
while self.current_token.type == 'COMMA':
|
|
self.eat('COMMA')
|
|
arguments.append(self.expression())
|
|
self.eat('RPAREN')
|
|
return {
|
|
'type': 'function_call',
|
|
'name': name,
|
|
'arguments': arguments
|
|
}
|
|
elif is_array_index:
|
|
# It's an array index
|
|
name = self.current_token.value
|
|
self.eat('IDENTIFIER')
|
|
self.eat('LBRACKET')
|
|
index = self.expression()
|
|
self.eat('RBRACKET')
|
|
return {
|
|
'type': 'array_index',
|
|
'name': name,
|
|
'index': index
|
|
}
|
|
else:
|
|
# It's a simple identifier
|
|
self.eat('IDENTIFIER')
|
|
return {'type': 'identifier', 'name': token.value}
|
|
elif token.type == 'LPAREN':
|
|
self.eat('LPAREN')
|
|
expr = self.expression()
|
|
self.eat('RPAREN')
|
|
return expr
|
|
else:
|
|
self.error(f"Unexpected token {token.type}")
|
|
|
|
def statement(self):
|
|
if self.current_token.type == 'INCLUDE_DIRECTIVE':
|
|
return self.include_directive()
|
|
elif self.current_token.type == 'KEYWORD_SHOW':
|
|
return self.function_call()
|
|
elif self.current_token.type == 'KEYWORD_IF':
|
|
return self.if_statement()
|
|
elif self.current_token.type == 'KEYWORD_WHILE':
|
|
return self.while_statement()
|
|
elif self.current_token.type == 'KEYWORD_DEF':
|
|
return self.def_statement()
|
|
elif self.current_token.type == 'KEYWORD_CLASS':
|
|
return self.class_statement()
|
|
elif self.current_token.type == 'KEYWORD_RETURN':
|
|
return self.return_statement()
|
|
elif self.current_token.type == 'KEYWORD_IMPORT':
|
|
return self.import_statement()
|
|
elif self.current_token.type == 'AT':
|
|
return self.import_statement()
|
|
elif self.current_token.type == 'IDENTIFIER':
|
|
# Check if it's a function call by looking ahead
|
|
# We need to save the current position first
|
|
save_position = self.lexer.position
|
|
save_line = self.lexer.line
|
|
save_column = self.lexer.column
|
|
|
|
# Look ahead to see if it's a function call
|
|
is_function_call = False
|
|
pos = save_position
|
|
while pos < len(self.lexer.source) and self.lexer.source[pos].isspace():
|
|
pos += 1
|
|
if pos < len(self.lexer.source) and self.lexer.source[pos] == '(':
|
|
is_function_call = True
|
|
|
|
# Restore the lexer position
|
|
self.lexer.position = save_position
|
|
self.lexer.line = save_line
|
|
self.lexer.column = save_column
|
|
|
|
if is_function_call:
|
|
# It's a function call (handled in factor)
|
|
# The function call is already parsed as part of the expression
|
|
# So we just need to parse the expression and add a semicolon
|
|
expr = self.expression()
|
|
self.eat('SEMICOLON')
|
|
return expr
|
|
else:
|
|
# It's an assignment
|
|
return self.assignment()
|
|
else:
|
|
self.error(f"Unexpected token {self.current_token.type}")
|
|
|
|
def term(self):
|
|
node = self.factor()
|
|
while self.current_token.type in ('MULTIPLY', 'DIVIDE'):
|
|
token = self.current_token
|
|
if token.type == 'MULTIPLY':
|
|
self.eat('MULTIPLY')
|
|
else:
|
|
self.eat('DIVIDE')
|
|
node = {
|
|
'type': 'binary_op',
|
|
'op': token.value,
|
|
'left': node,
|
|
'right': self.factor()
|
|
}
|
|
return node
|
|
|
|
def expression(self):
|
|
node = self.logical_or()
|
|
return node
|
|
|
|
def logical_or(self):
|
|
node = self.logical_and()
|
|
while self.current_token.type == 'OR':
|
|
token = self.current_token
|
|
self.eat('OR')
|
|
node = {
|
|
'type': 'binary_op',
|
|
'op': token.value,
|
|
'left': node,
|
|
'right': self.logical_and()
|
|
}
|
|
return node
|
|
|
|
def logical_and(self):
|
|
node = self.equality()
|
|
while self.current_token.type == 'AND':
|
|
token = self.current_token
|
|
self.eat('AND')
|
|
node = {
|
|
'type': 'binary_op',
|
|
'op': token.value,
|
|
'left': node,
|
|
'right': self.equality()
|
|
}
|
|
return node
|
|
|
|
def equality(self):
|
|
node = self.comparison()
|
|
while self.current_token.type in ('EQUALS', 'NOT_EQUALS'):
|
|
token = self.current_token
|
|
if token.type == 'EQUALS':
|
|
self.eat('EQUALS')
|
|
else:
|
|
self.eat('NOT_EQUALS')
|
|
node = {
|
|
'type': 'binary_op',
|
|
'op': token.value,
|
|
'left': node,
|
|
'right': self.comparison()
|
|
}
|
|
return node
|
|
|
|
def comparison(self):
|
|
node = self.term()
|
|
while self.current_token.type in ('PLUS', 'MINUS', 'LESS', 'LESS_EQUAL', 'GREATER', 'GREATER_EQUAL'):
|
|
token = self.current_token
|
|
if token.type == 'PLUS':
|
|
self.eat('PLUS')
|
|
elif token.type == 'MINUS':
|
|
self.eat('MINUS')
|
|
elif token.type == 'LESS':
|
|
self.eat('LESS')
|
|
elif token.type == 'LESS_EQUAL':
|
|
self.eat('LESS_EQUAL')
|
|
elif token.type == 'GREATER':
|
|
self.eat('GREATER')
|
|
else:
|
|
self.eat('GREATER_EQUAL')
|
|
node = {
|
|
'type': 'binary_op',
|
|
'op': token.value,
|
|
'left': node,
|
|
'right': self.term()
|
|
}
|
|
return node
|
|
|
|
def assignment(self):
|
|
token = self.current_token
|
|
self.eat('IDENTIFIER')
|
|
if self.current_token.type == 'ASSIGN':
|
|
self.eat('ASSIGN')
|
|
value = self.expression()
|
|
# Check if there's a semicolon, but don't require it
|
|
if self.current_token.type == 'SEMICOLON':
|
|
self.eat('SEMICOLON')
|
|
return {
|
|
'type': 'assignment',
|
|
'name': token.value,
|
|
'value': value
|
|
}
|
|
elif self.current_token.type in ('PLUS_EQUALS', 'MINUS_EQUALS', 'MULTIPLY_EQUALS', 'DIVIDE_EQUALS'):
|
|
op_token = self.current_token
|
|
if op_token.type == 'PLUS_EQUALS':
|
|
self.eat('PLUS_EQUALS')
|
|
elif op_token.type == 'MINUS_EQUALS':
|
|
self.eat('MINUS_EQUALS')
|
|
elif op_token.type == 'MULTIPLY_EQUALS':
|
|
self.eat('MULTIPLY_EQUALS')
|
|
else:
|
|
self.eat('DIVIDE_EQUALS')
|
|
value = self.expression()
|
|
# Check if there's a semicolon, but don't require it
|
|
if self.current_token.type == 'SEMICOLON':
|
|
self.eat('SEMICOLON')
|
|
# Create compound assignment as binary operation
|
|
compound_op = op_token.value[0] # Get the first character (e.g., '+' from '+=')
|
|
return {
|
|
'type': 'assignment',
|
|
'name': token.value,
|
|
'value': {
|
|
'type': 'binary_op',
|
|
'op': compound_op,
|
|
'left': {'type': 'identifier', 'name': token.value},
|
|
'right': value
|
|
}
|
|
}
|
|
else:
|
|
self.error(f"Expected assignment operator, got {self.current_token.type}")
|
|
|
|
def function_call(self):
|
|
token = self.current_token
|
|
self.eat('KEYWORD_SHOW')
|
|
self.eat('LPAREN')
|
|
arguments = []
|
|
if self.current_token.type != 'RPAREN':
|
|
arguments.append(self.expression())
|
|
while self.current_token.type == 'COMMA':
|
|
self.eat('COMMA')
|
|
arguments.append(self.expression())
|
|
self.eat('RPAREN')
|
|
# Check if there's a semicolon, but don't require it
|
|
if self.current_token.type == 'SEMICOLON':
|
|
self.eat('SEMICOLON')
|
|
return {
|
|
'type': 'function_call',
|
|
'name': token.value,
|
|
'arguments': arguments
|
|
}
|
|
|
|
def block(self):
|
|
self.eat('LBRACE')
|
|
statements = []
|
|
while self.current_token.type != 'RBRACE' and self.current_token.type != 'EOF':
|
|
statements.append(self.statement())
|
|
self.eat('RBRACE')
|
|
return {'type': 'block', 'statements': statements}
|
|
|
|
def if_statement(self):
|
|
self.eat('KEYWORD_IF')
|
|
self.eat('LPAREN')
|
|
condition = self.expression()
|
|
self.eat('RPAREN')
|
|
then_branch = self.block()
|
|
else_branch = None
|
|
|
|
if self.current_token.type == 'KEYWORD_ELSE':
|
|
self.eat('KEYWORD_ELSE')
|
|
if self.current_token.type == 'KEYWORD_IF':
|
|
else_branch = self.if_statement()
|
|
else:
|
|
else_branch = self.block()
|
|
|
|
return {
|
|
'type': 'if_statement',
|
|
'condition': condition,
|
|
'then_branch': then_branch,
|
|
'else_branch': else_branch
|
|
}
|
|
|
|
def while_statement(self):
|
|
self.eat('KEYWORD_WHILE')
|
|
self.eat('LPAREN')
|
|
condition = self.expression()
|
|
self.eat('RPAREN')
|
|
body = self.block()
|
|
return {
|
|
'type': 'while_statement',
|
|
'condition': condition,
|
|
'body': body
|
|
}
|
|
|
|
def return_statement(self):
|
|
self.eat('KEYWORD_RETURN')
|
|
value = self.expression()
|
|
# 检查是否需要分号(在函数体中可能不需要分号)
|
|
if self.current_token.type == 'SEMICOLON':
|
|
self.eat('SEMICOLON')
|
|
return {
|
|
'type': 'return_statement',
|
|
'value': value
|
|
}
|
|
|
|
def def_statement(self):
|
|
self.eat('KEYWORD_DEF')
|
|
name = self.current_token.value
|
|
self.eat('IDENTIFIER')
|
|
self.eat('LPAREN')
|
|
parameters = []
|
|
if self.current_token.type != 'RPAREN':
|
|
parameters.append(self.current_token.value)
|
|
self.eat('IDENTIFIER')
|
|
while self.current_token.type == 'COMMA':
|
|
self.eat('COMMA')
|
|
parameters.append(self.current_token.value)
|
|
self.eat('IDENTIFIER')
|
|
self.eat('RPAREN')
|
|
body = self.block()
|
|
return {
|
|
'type': 'def_statement',
|
|
'name': name,
|
|
'parameters': parameters,
|
|
'body': body
|
|
}
|
|
|
|
def import_statement(self):
|
|
# Handle both @library and import library syntax
|
|
if self.current_token.type == 'AT':
|
|
self.eat('AT')
|
|
elif self.current_token.type == 'KEYWORD_IMPORT':
|
|
self.eat('KEYWORD_IMPORT')
|
|
module = self.current_token.value
|
|
self.eat('IDENTIFIER')
|
|
# Check if there's a semicolon, but don't require it
|
|
if self.current_token.type == 'SEMICOLON':
|
|
self.eat('SEMICOLON')
|
|
return {
|
|
'type': 'import_statement',
|
|
'module': module
|
|
}
|
|
|
|
def include_directive(self):
|
|
module = self.current_token.value
|
|
self.eat('INCLUDE_DIRECTIVE')
|
|
# Check if there's a semicolon, but don't require it
|
|
if self.current_token.type == 'SEMICOLON':
|
|
self.eat('SEMICOLON')
|
|
return {
|
|
'type': 'include_directive',
|
|
'module': module
|
|
}
|
|
|
|
def class_statement(self):
|
|
self.eat('KEYWORD_CLASS')
|
|
name = self.current_token.value
|
|
self.eat('IDENTIFIER')
|
|
self.eat('LBRACE')
|
|
methods = []
|
|
while self.current_token.type != 'RBRACE' and self.current_token.type != 'EOF':
|
|
if self.current_token.type == 'KEYWORD_DEF':
|
|
methods.append(self.def_statement())
|
|
else:
|
|
self.error(f"Expected function definition in class, got {self.current_token.type}")
|
|
self.eat('RBRACE')
|
|
return {
|
|
'type': 'class_statement',
|
|
'name': name,
|
|
'methods': methods
|
|
}
|
|
|
|
def statement(self):
|
|
if self.current_token.type == 'INCLUDE_DIRECTIVE':
|
|
return self.include_directive()
|
|
elif self.current_token.type == 'KEYWORD_SHOW':
|
|
return self.function_call()
|
|
elif self.current_token.type == 'KEYWORD_IF':
|
|
return self.if_statement()
|
|
elif self.current_token.type == 'KEYWORD_WHILE':
|
|
return self.while_statement()
|
|
elif self.current_token.type == 'KEYWORD_DEF':
|
|
return self.def_statement()
|
|
elif self.current_token.type == 'KEYWORD_CLASS':
|
|
return self.class_statement()
|
|
elif self.current_token.type == 'KEYWORD_RETURN':
|
|
return self.return_statement()
|
|
elif self.current_token.type == 'KEYWORD_IMPORT':
|
|
return self.import_statement()
|
|
elif self.current_token.type == 'AT':
|
|
return self.import_statement()
|
|
elif self.current_token.type == 'IDENTIFIER':
|
|
# Check if it's a function call
|
|
if self.lexer.position + 1 < len(self.lexer.source) and self.lexer.source[self.lexer.position] == '(':
|
|
# It's a function call
|
|
name = self.current_token.value
|
|
self.eat('IDENTIFIER')
|
|
self.eat('LPAREN')
|
|
arguments = []
|
|
if self.current_token.type != 'RPAREN':
|
|
arguments.append(self.expression())
|
|
while self.current_token.type == 'COMMA':
|
|
self.eat('COMMA')
|
|
arguments.append(self.expression())
|
|
self.eat('RPAREN')
|
|
self.eat('SEMICOLON')
|
|
return {
|
|
'type': 'function_call',
|
|
'name': name,
|
|
'arguments': arguments
|
|
}
|
|
else:
|
|
# It's an assignment
|
|
return self.assignment()
|
|
else:
|
|
self.error(f"Unexpected token {self.current_token.type}")
|
|
|
|
def parse(self):
|
|
statements = []
|
|
while self.current_token.type != 'EOF':
|
|
statements.append(self.statement())
|
|
return {'type': 'block', 'statements': statements}
|
|
|
|
class Interpreter:
|
|
def __init__(self):
|
|
import math
|
|
import time
|
|
import random
|
|
import os
|
|
import subprocess
|
|
import hashlib
|
|
|
|
self.symbols = {}
|
|
self.functions = {
|
|
# 字符串和数组函数
|
|
'len': lambda s: str(len(eval(s)) if s.startswith('[') and s.endswith(']') else len(s)),
|
|
'upper': lambda s: s.upper(),
|
|
'lower': lambda s: s.lower(),
|
|
'substring': lambda s, start, end: s[int(start):int(end)],
|
|
# 数组函数
|
|
'arr': lambda *args: str(list(args)),
|
|
'push': lambda arr, item: str(eval(arr) + [item]),
|
|
'pop': lambda arr: str(eval(arr)[:-1]),
|
|
'indexOf': lambda arr, item: str(eval(arr).index(item) if item in eval(arr) else -1),
|
|
'join': lambda arr, sep: sep.join(eval(arr)),
|
|
'reverse': lambda arr: str(list(reversed(eval(arr)))),
|
|
'sort': lambda arr: str(sorted(eval(arr))),
|
|
'slice': lambda arr, start, end: str(eval(arr)[int(start):int(end)]),
|
|
# 数学库
|
|
'math.abs': lambda x: str(abs(float(x))),
|
|
'math.sqrt': lambda x: str(float(x) ** 0.5),
|
|
'math.pow': lambda x, y: str(float(x) ** float(y)),
|
|
'math.sin': lambda x: str(math.sin(math.radians(float(x)))),
|
|
'math.cos': lambda x: str(math.cos(math.radians(float(x)))),
|
|
'math.tan': lambda x: str(math.tan(math.radians(float(x)))),
|
|
'math.asin': lambda x: str(math.degrees(math.asin(float(x)))),
|
|
'math.acos': lambda x: str(math.degrees(math.acos(float(x)))),
|
|
'math.atan': lambda x: str(math.degrees(math.atan(float(x)))),
|
|
'math.pi': lambda: str(math.pi),
|
|
'math.e': lambda: str(math.e),
|
|
'math.max': lambda *args: str(max(float(arg) for arg in args)),
|
|
'math.min': lambda *args: str(min(float(arg) for arg in args)),
|
|
'math.random': lambda: str(random.random()),
|
|
'math.randint': lambda a, b: str(random.randint(int(a), int(b))),
|
|
# 日期时间库
|
|
'time.now': lambda: str(int(time.time())),
|
|
'time.sleep': lambda seconds: (time.sleep(float(seconds)), 'Done')[1],
|
|
'time.format': lambda timestamp, format_str: time.strftime(format_str, time.localtime(float(timestamp))),
|
|
# JSON库
|
|
'json.parse': lambda json_str: str(eval(json_str)),
|
|
'json.stringify': lambda obj: str(obj),
|
|
# 加密库
|
|
'crypto.md5': lambda s: hashlib.md5(s.encode()).hexdigest(),
|
|
'crypto.sha1': lambda s: hashlib.sha1(s.encode()).hexdigest(),
|
|
'crypto.sha256': lambda s: hashlib.sha256(s.encode()).hexdigest(),
|
|
# 字符串操作库
|
|
'string.split': lambda s, sep: str(list(s)) if sep == '' else str(s.split(sep)),
|
|
'string.upper': lambda s: s.upper(),
|
|
'string.lower': lambda s: s.lower(),
|
|
'string.trim': lambda s: s.strip(),
|
|
'string.replace': lambda s, old, new: s.replace(old, new),
|
|
'string.startsWith': lambda s, prefix: str(s.startswith(prefix)),
|
|
'string.endsWith': lambda s, suffix: str(s.endswith(suffix)),
|
|
'string.substring': lambda s, start, end: s[int(start):int(end)],
|
|
'string.indexOf': lambda s, substr: str(s.find(substr)),
|
|
'string.length': lambda s: str(len(s)),
|
|
'string.contains': lambda s, substr: str(substr in s),
|
|
'string.concat': lambda s1, s2: s1 + s2,
|
|
'string.repeat': lambda s, n: s * int(n),
|
|
'string.reverse': lambda s: s[::-1],
|
|
'string.includes': lambda s, substr: str(substr in s),
|
|
'string.lastIndexOf': lambda s, substr: str(s.rfind(substr)),
|
|
'string.padStart': lambda s, length, char: s.rjust(int(length), char),
|
|
'string.padEnd': lambda s, length, char: s.ljust(int(length), char),
|
|
# 数组操作库
|
|
'array.length': lambda arr: str(len(eval(arr))),
|
|
'array.push': lambda arr, item: str(eval(arr) + [item]),
|
|
'array.pop': lambda arr: str(eval(arr)[:-1]),
|
|
'array.shift': lambda arr: str(eval(arr)[1:]),
|
|
'array.unshift': lambda arr, item: str([item] + eval(arr)),
|
|
'array.indexOf': lambda arr, item: str(eval(arr).index(item) if item in eval(arr) else -1),
|
|
'array.lastIndexOf': lambda arr, item: str(len(eval(arr)) - 1 - eval(arr)[::-1].index(item) if item in eval(arr) else -1),
|
|
'array.includes': lambda arr, item: str(item in eval(arr)),
|
|
'array.join': lambda arr, sep: sep.join(eval(arr)),
|
|
'array.reverse': lambda arr: str(list(reversed(eval(arr)))),
|
|
'array.sort': lambda arr: str(sorted(eval(arr))),
|
|
'array.slice': lambda arr, start, end: str(eval(arr)[int(start):int(end)]),
|
|
# 工具库
|
|
'util.random': lambda: str(random.random()),
|
|
'util.randint': lambda a, b: str(random.randint(int(a), int(b))),
|
|
'util.round': lambda x, n=0: str(round(float(x), int(n))),
|
|
'util.abs': lambda x: str(abs(float(x))),
|
|
'util.min': lambda *args: str(min(float(arg) for arg in args)),
|
|
'util.max': lambda *args: str(max(float(arg) for arg in args)),
|
|
'util.sum': lambda *args: str(sum(float(arg) for arg in args)),
|
|
'util.average': lambda *args: str(sum(float(arg) for arg in args) / len(args)) if args else '0',
|
|
'util.isNumber': lambda x: str(x.replace('.', '', 1).replace('-', '', 1).isdigit()),
|
|
'util.isString': lambda x: str(isinstance(x, str)),
|
|
'util.isArray': lambda x: str(x.startswith('[') and x.endswith(']')),
|
|
'util.toNumber': lambda x: str(float(x)),
|
|
'util.toString': lambda x: str(x),
|
|
'util.typeof': lambda x: 'array' if x.startswith('[') and x.endswith(']') else 'number' if x.replace('.', '', 1).replace('-', '', 1).isdigit() else 'string' if isinstance(x, str) else 'unknown',
|
|
# 其他函数
|
|
'input': lambda prompt: input(prompt),
|
|
'int': lambda s: str(int(s)),
|
|
'str': lambda x: str(x),
|
|
# 网络请求函数
|
|
'http_get': self.http_get,
|
|
'http_post': self.http_post,
|
|
# 文件操作函数
|
|
'file_read': self.file_read,
|
|
'file_write': self.file_write,
|
|
'file_exists': self.file_exists,
|
|
# 系统函数
|
|
'system': self.system,
|
|
'get_env': self.get_env
|
|
}
|
|
self.classes = {}
|
|
self.return_value = None
|
|
self.modules = {}
|
|
|
|
# 模块引用
|
|
self.math = math
|
|
self.time = time
|
|
self.random = random
|
|
self.os = os
|
|
self.subprocess = subprocess
|
|
self.hashlib = hashlib
|
|
|
|
def error(self, message, line=0, column=0):
|
|
# For Windows PowerShell compatibility, use simple error messages without ANSI codes
|
|
error_msg = "Runtime Error"
|
|
if line > 0 and column > 0:
|
|
error_msg += f" at line {line}, column {column}"
|
|
error_msg += f": {message}"
|
|
raise Exception(error_msg)
|
|
|
|
def http_get(self, url):
|
|
"""Send HTTP GET request"""
|
|
try:
|
|
import urllib.request
|
|
import json
|
|
with urllib.request.urlopen(url) as response:
|
|
data = response.read().decode('utf-8')
|
|
return data
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def http_post(self, url, data):
|
|
"""Send HTTP POST request"""
|
|
try:
|
|
import urllib.request
|
|
import urllib.parse
|
|
import json
|
|
data = data.encode('utf-8')
|
|
req = urllib.request.Request(url, data=data, method='POST')
|
|
req.add_header('Content-Type', 'application/json')
|
|
with urllib.request.urlopen(req) as response:
|
|
response_data = response.read().decode('utf-8')
|
|
return response_data
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def file_read(self, filename):
|
|
"""Read file content"""
|
|
try:
|
|
with open(filename, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
return content
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def file_write(self, filename, content):
|
|
"""Write content to file"""
|
|
try:
|
|
with open(filename, 'w', encoding='utf-8') as f:
|
|
f.write(content)
|
|
return "Success"
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def file_exists(self, filename):
|
|
"""Check if file exists"""
|
|
try:
|
|
return str(1 if self.os.path.exists(filename) else 0)
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def time_now(self):
|
|
"""Get current time"""
|
|
try:
|
|
return str(self.time.time())
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def time_sleep(self, seconds):
|
|
"""Sleep for specified seconds"""
|
|
try:
|
|
self.time.sleep(float(seconds))
|
|
return "Success"
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def random_int(self, min_val, max_val):
|
|
"""Generate random integer between min and max"""
|
|
try:
|
|
return str(self.random.randint(int(min_val), int(max_val)))
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def random_float(self):
|
|
"""Generate random float between 0 and 1"""
|
|
try:
|
|
return str(self.random.random())
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def system(self, command):
|
|
"""Execute system command"""
|
|
try:
|
|
result = self.subprocess.run(command, shell=True, capture_output=True, text=True)
|
|
return result.stdout + result.stderr
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def get_env(self, var_name):
|
|
"""Get environment variable"""
|
|
try:
|
|
value = self.os.environ.get(var_name, "")
|
|
return value
|
|
except Exception as e:
|
|
return f"Error: {str(e)}"
|
|
|
|
def evaluate(self, node):
|
|
if node['type'] == 'literal':
|
|
return node['value']
|
|
elif node['type'] == 'identifier':
|
|
if node['name'] in self.symbols:
|
|
return self.symbols[node['name']]
|
|
else:
|
|
raise Exception(f"Undefined variable: {node['name']}")
|
|
elif node['type'] == 'array_index':
|
|
# Handle array indexing
|
|
array_value = self.symbols.get(node['name'])
|
|
if array_value is None:
|
|
raise Exception(f"Undefined variable: {node['name']}")
|
|
# Evaluate the index expression
|
|
index_value = self.evaluate(node['index'])
|
|
# Convert array string to actual list
|
|
try:
|
|
array = eval(array_value)
|
|
if isinstance(array, list):
|
|
index = int(index_value)
|
|
if 0 <= index < len(array):
|
|
return array[index]
|
|
else:
|
|
raise Exception(f"Array index out of range: {index}")
|
|
else:
|
|
raise Exception(f"Variable {node['name']} is not an array")
|
|
except Exception as e:
|
|
raise Exception(f"Error accessing array: {str(e)}")
|
|
elif node['type'] == 'binary_op':
|
|
left = self.evaluate(node['left'])
|
|
right = self.evaluate(node['right'])
|
|
|
|
# Check if both operands are numbers for arithmetic operations
|
|
if left.isdigit() and right.isdigit():
|
|
left_num = int(left)
|
|
right_num = int(right)
|
|
|
|
if node['op'] == '+':
|
|
return str(left_num + right_num)
|
|
elif node['op'] == '-':
|
|
return str(left_num - right_num)
|
|
elif node['op'] == '*':
|
|
return str(left_num * right_num)
|
|
elif node['op'] == '/':
|
|
if right_num == 0:
|
|
raise Exception("Division by zero")
|
|
return str(left_num // right_num)
|
|
elif node['op'] == '==':
|
|
return str(1 if left_num == right_num else 0)
|
|
elif node['op'] == '!=':
|
|
return str(1 if left_num != right_num else 0)
|
|
elif node['op'] == '<':
|
|
return str(1 if left_num < right_num else 0)
|
|
elif node['op'] == '<=':
|
|
return str(1 if left_num <= right_num else 0)
|
|
elif node['op'] == '>':
|
|
return str(1 if left_num > right_num else 0)
|
|
elif node['op'] == '>=':
|
|
return str(1 if left_num >= right_num else 0)
|
|
else:
|
|
# String operations
|
|
if node['op'] == '+':
|
|
return left + right
|
|
elif node['op'] == '==':
|
|
return str(1 if left == right else 0)
|
|
elif node['op'] == '!=':
|
|
return str(1 if left != right else 0)
|
|
else:
|
|
raise Exception("Unsupported operation for non-numeric values")
|
|
|
|
# Logical operations
|
|
if node['op'] == '&&':
|
|
return str(1 if (int(left) != 0 and int(right) != 0) else 0)
|
|
elif node['op'] == '||':
|
|
return str(1 if (int(left) != 0 or int(right) != 0) else 0)
|
|
elif node['type'] == 'function_call':
|
|
# Handle function calls in expressions
|
|
if node['name'] in self.symbols and callable(self.symbols[node['name']]):
|
|
# Check if it's a function from a module (like pylang or bflang)
|
|
func = self.symbols[node['name']]
|
|
args = [self.evaluate(arg) for arg in node['arguments']]
|
|
return func(*args)
|
|
elif node['name'] in self.functions:
|
|
# Check if it's a built-in function (lambda)
|
|
func = self.functions[node['name']]
|
|
if callable(func):
|
|
# Check if it's a library function that requires import
|
|
if '.' in node['name']:
|
|
# Extract library name from function name (e.g., math.abs -> math)
|
|
library_name = node['name'].split('.')[0]
|
|
# Check if the library is imported
|
|
if library_name not in self.modules:
|
|
self.error(f"Library '{library_name}' not imported. Use '@{library_name}' to import it.")
|
|
# Check if it's an HTTP function that requires http module
|
|
if (node['name'] == 'http_get' or node['name'] == 'http_post') and 'http' not in self.modules:
|
|
self.error("HTTP module not imported. Use '@http' to import it.")
|
|
# It's a built-in function
|
|
args = [self.evaluate(arg) for arg in node['arguments']]
|
|
return func(*args)
|
|
else:
|
|
# It's a user-defined function
|
|
# Create local symbol table
|
|
local_symbols = self.symbols.copy()
|
|
# Assign arguments to parameters
|
|
for i, param in enumerate(func['parameters']):
|
|
if i < len(node['arguments']):
|
|
local_symbols[param] = self.evaluate(node['arguments'][i])
|
|
else:
|
|
local_symbols[param] = '0' # Default value
|
|
# Save current symbol table and return value
|
|
old_symbols = self.symbols
|
|
old_return_value = self.return_value
|
|
# Set local symbol table
|
|
self.symbols = local_symbols
|
|
# Reset return value
|
|
self.return_value = None
|
|
# Execute function body
|
|
self.interpret(func['body'])
|
|
# Get return value
|
|
return_value = self.return_value
|
|
# Restore symbol table and return value
|
|
self.symbols = old_symbols
|
|
self.return_value = old_return_value
|
|
# Return the value (ensure it's a string)
|
|
if return_value is not None:
|
|
return str(return_value)
|
|
return return_value
|
|
else:
|
|
self.error(f"Unknown function: {node['name']}")
|
|
else:
|
|
raise Exception(f"Unknown node type: {node['type']}")
|
|
|
|
def interpret(self, node):
|
|
if isinstance(node, list):
|
|
for stmt in node:
|
|
result = self.interpret(stmt)
|
|
if result is not None:
|
|
return result
|
|
elif node['type'] == 'block':
|
|
for stmt in node['statements']:
|
|
result = self.interpret(stmt)
|
|
if result is not None:
|
|
return result
|
|
elif node['type'] == 'assignment':
|
|
value = self.evaluate(node['value'])
|
|
self.symbols[node['name']] = value
|
|
elif node['type'] == 'function_call':
|
|
if node['name'] == 'show':
|
|
for arg in node['arguments']:
|
|
value = self.evaluate(arg)
|
|
print(value, end=' ')
|
|
print()
|
|
elif node['name'] in self.symbols and callable(self.symbols[node['name']]):
|
|
# Check if it's a function from a module (like pylang or bflang)
|
|
func = self.symbols[node['name']]
|
|
args = [self.evaluate(arg) for arg in node['arguments']]
|
|
result = func(*args)
|
|
return result
|
|
elif node['name'] in self.functions:
|
|
# Check if it's a built-in function (lambda)
|
|
func = self.functions[node['name']]
|
|
if callable(func):
|
|
# Check if it's a library function that requires import
|
|
if '.' in node['name']:
|
|
# Extract library name from function name (e.g., math.abs -> math)
|
|
library_name = node['name'].split('.')[0]
|
|
# Check if the library is imported
|
|
if library_name not in self.modules:
|
|
self.error(f"Library '{library_name}' not imported. Use '@{library_name}' to import it.")
|
|
# Check if it's an HTTP function that requires http module
|
|
if (node['name'] == 'http_get' or node['name'] == 'http_post') and 'http' not in self.modules:
|
|
self.error("HTTP module not imported. Use '@http' to import it.")
|
|
# It's a built-in function
|
|
args = [self.evaluate(arg) for arg in node['arguments']]
|
|
result = func(*args)
|
|
return result
|
|
else:
|
|
# It's a user-defined function
|
|
# Create local symbol table
|
|
local_symbols = self.symbols.copy()
|
|
# Assign arguments to parameters
|
|
for i, param in enumerate(func['parameters']):
|
|
if i < len(node['arguments']):
|
|
local_symbols[param] = self.evaluate(node['arguments'][i])
|
|
else:
|
|
local_symbols[param] = '0' # Default value
|
|
# Save current symbol table and return value
|
|
old_symbols = self.symbols
|
|
old_return_value = self.return_value
|
|
# Set local symbol table
|
|
self.symbols = local_symbols
|
|
# Reset return value
|
|
self.return_value = None
|
|
# Execute function body
|
|
self.interpret(func['body'])
|
|
# Get return value
|
|
return_value = self.return_value
|
|
# Restore symbol table and return value
|
|
self.symbols = old_symbols
|
|
self.return_value = old_return_value
|
|
# Return the value (ensure it's a string)
|
|
if return_value is not None:
|
|
return str(return_value)
|
|
return return_value
|
|
elif node['name'] in self.functions and 'math.' in node['name']:
|
|
# Check if it's a math function
|
|
func = self.functions[node['name']]
|
|
if callable(func):
|
|
# It's a math function
|
|
args = [self.evaluate(arg) for arg in node['arguments']]
|
|
result = func(*args)
|
|
return result
|
|
else:
|
|
self.error(f"Unknown function: {node['name']}")
|
|
elif node['type'] == 'if_statement':
|
|
condition = self.evaluate(node['condition'])
|
|
if int(condition) != 0:
|
|
self.interpret(node['then_branch'])
|
|
elif node['else_branch']:
|
|
self.interpret(node['else_branch'])
|
|
elif node['type'] == 'while_statement':
|
|
while True:
|
|
condition = self.evaluate(node['condition'])
|
|
if int(condition) == 0:
|
|
break
|
|
self.interpret(node['body'])
|
|
# Check for return statement
|
|
if self.return_value is not None:
|
|
break
|
|
elif node['type'] == 'def_statement':
|
|
# Store function definition
|
|
self.functions[node['name']] = {
|
|
'parameters': node['parameters'],
|
|
'body': node['body']
|
|
}
|
|
elif node['type'] == 'class_statement':
|
|
# Store class definition
|
|
self.classes[node['name']] = {
|
|
'methods': {}
|
|
}
|
|
for method in node['methods']:
|
|
self.classes[node['name']]['methods'][method['name']] = {
|
|
'parameters': method['parameters'],
|
|
'body': method['body']
|
|
}
|
|
elif node['type'] == 'return_statement':
|
|
# Set return value
|
|
self.return_value = self.evaluate(node['value'])
|
|
elif node['type'] == 'import_statement':
|
|
# Handle import statement
|
|
module = node['module']
|
|
self.modules[module] = True
|
|
# Here you can add module-specific initialization
|
|
if module == 'http':
|
|
# HTTP module is already initialized in functions
|
|
pass
|
|
elif module == 'math':
|
|
# Add math functions to the current scope
|
|
pass
|
|
elif node['type'] == 'include_directive':
|
|
# Handle include directive
|
|
module = node['module']
|
|
self.modules[module] = True
|
|
|
|
# Check if it's a built-in module
|
|
if module == 'response':
|
|
# Enable HTTP request functions
|
|
pass
|
|
else:
|
|
# Try to load from includes folder
|
|
import os
|
|
include_path = os.path.join('includes', f'{module}.oraset')
|
|
if os.path.exists(include_path):
|
|
try:
|
|
with open(include_path, 'r', encoding='utf-8') as f:
|
|
source = f.read()
|
|
# Parse and interpret the included file
|
|
lexer = Lexer(source)
|
|
parser = Parser(lexer)
|
|
ast = parser.parse()
|
|
self.interpret(ast)
|
|
print(f"Successfully included module: {module}")
|
|
except Exception as e:
|
|
print(f"Error including module {module}: {e}")
|
|
else:
|
|
print(f"Warning: Module {module} not found in includes folder")
|
|
else:
|
|
raise Exception(f"Unknown node type: {node['type']}")
|
|
|
|
import base64
|
|
import zlib
|
|
|
|
def pack_file(input_file, output_file):
|
|
"""Pack .oas file into .osp file"""
|
|
try:
|
|
with open(input_file, 'r', encoding='utf-8') as f:
|
|
source = f.read()
|
|
|
|
# Compress the source code
|
|
compressed = zlib.compress(source.encode('utf-8'))
|
|
# Encode as base64
|
|
encoded = base64.b64encode(compressed)
|
|
|
|
with open(output_file, 'wb') as f:
|
|
f.write(encoded)
|
|
|
|
print(f"Successfully packed {input_file} into {output_file}")
|
|
return 0
|
|
except Exception as e:
|
|
print(f"Error packing file: {e}")
|
|
return 1
|
|
|
|
def unpack_file(input_file):
|
|
"""Unpack .osp file and return the source code"""
|
|
try:
|
|
with open(input_file, 'rb') as f:
|
|
encoded = f.read()
|
|
|
|
# Decode from base64
|
|
compressed = base64.b64decode(encoded)
|
|
# Decompress
|
|
source = zlib.decompress(compressed).decode('utf-8')
|
|
|
|
return source
|
|
except Exception as e:
|
|
print(f"Error unpacking file: {e}")
|
|
return None
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print(f"Usage:")
|
|
print(f" {sys.argv[0]} <filename.oas|.oraset> # Run .oas or .oraset file")
|
|
print(f" {sys.argv[0]} pack <filename.oas|.oraset> # Pack .oas or .oraset file into .osp")
|
|
print(f" {sys.argv[0]} -osp <filename.osp> # Run .osp file")
|
|
return 1
|
|
|
|
if sys.argv[1] == 'pack':
|
|
if len(sys.argv) != 3:
|
|
print(f"Usage: {sys.argv[0]} pack <filename.oas|.oraset>")
|
|
return 1
|
|
input_file = sys.argv[2]
|
|
output_file = input_file.replace('.oas', '.osp').replace('.oraset', '.osp')
|
|
return pack_file(input_file, output_file)
|
|
elif sys.argv[1] == '-osp':
|
|
if len(sys.argv) != 3:
|
|
print(f"Usage: {sys.argv[0]} -osp <filename.osp>")
|
|
return 1
|
|
input_file = sys.argv[2]
|
|
source = unpack_file(input_file)
|
|
if source is None:
|
|
return 1
|
|
else:
|
|
filename = sys.argv[1]
|
|
try:
|
|
with open(filename, 'r', encoding='utf-8') as f:
|
|
source = f.read()
|
|
except FileNotFoundError:
|
|
print(f"Error: Could not open file {filename}")
|
|
return 1
|
|
|
|
lexer = Lexer(source)
|
|
parser = Parser(lexer)
|
|
try:
|
|
ast = parser.parse()
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
return 1
|
|
|
|
interpreter = Interpreter()
|
|
try:
|
|
interpreter.interpret(ast)
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
return 1
|
|
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|