From e8c10266bad77c95d429acef95321953124986b6 Mon Sep 17 00:00:00 2001 From: "JGZSunShineMod1.0.3" Date: Fri, 24 Apr 2026 23:49:03 +0800 Subject: [PATCH] Add Oraset for Termux with build workflow --- termux/oraset.py | 1424 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1424 insertions(+) create mode 100644 termux/oraset.py diff --git a/termux/oraset.py b/termux/oraset.py new file mode 100644 index 0000000..f769852 --- /dev/null +++ b/termux/oraset.py @@ -0,0 +1,1424 @@ +#!/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]} # Run .oas or .oraset file") + print(f" {sys.argv[0]} pack # Pack .oas or .oraset file into .osp") + print(f" {sys.argv[0]} -osp # Run .osp file") + return 1 + + if sys.argv[1] == 'pack': + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} pack ") + 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 ") + 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())