UBS-Server / routes /interpreter.py
m-abdur2024's picture
Upload 40 files
0d3476b verified
raw
history blame
No virus
14 kB
import json
import logging
import re
from flask import Flask, request, jsonify
from routes import app
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Global Symbol table and Output collector
symbols = {}
output = []
# Regular expressions for tokenizing
TOKEN_REGEX = re.compile(r'\s*(?:(\()|(\))|(".*?")|([^()\s]+))')
# Exception class for interpreter errors
class InterpreterError(Exception):
def __init__(self, message, line):
self.message = message
self.line = line
# Helper functions for type checking and conversion
def is_number(value):
return isinstance(value, int) or isinstance(value, float)
def convert_str(value):
if isinstance(value, str):
return value
elif is_number(value):
if isinstance(value, float):
# Format to remove trailing zeros, keeping up to 4 decimal places
return f"{value:.4f}".rstrip('0').rstrip('.') if '.' in f"{value:.4f}" else f"{value:.4f}"
elif isinstance(value, int):
return str(value)
elif isinstance(value, bool):
return "true" if value else "false"
elif value is None:
return "null"
else:
raise ValueError("Unsupported type for str conversion")
# Function implementations
def func_puts(args, line):
if len(args) != 1:
raise InterpreterError("puts expects exactly one argument", line)
arg = args[0]
if not isinstance(arg, str):
raise InterpreterError("puts expects a string argument", line)
logger.info(f"puts: Appending '{arg}' to output")
output.append(arg)
return None
def func_set(args, line):
if len(args) != 2:
raise InterpreterError("set expects exactly two arguments", line)
var_name, value = args
if not isinstance(var_name, str):
raise InterpreterError("set expects the first argument to be a string (variable name)", line)
# Variable names must be alphabetic
if not re.fullmatch(r'[A-Za-z]+', var_name):
raise InterpreterError("Invalid variable name format", line)
if var_name in symbols:
raise InterpreterError("Cannot reassign to an existing constant", line)
symbols[var_name] = value
logger.info(f"set: '{var_name}' set to '{value}'")
return None
def func_concat(args, line):
if len(args) != 2:
raise InterpreterError("concat expects exactly two arguments", line)
a, b = args
if not isinstance(a, str) or not isinstance(b, str):
raise InterpreterError("concat expects string arguments", line)
result = a + b
logger.info(f"concat: '{a}' + '{b}' = '{result}'")
return result
def func_substring(args, line):
if len(args) != 3:
raise InterpreterError("substring expects exactly three arguments", line)
source, start, end = args
if not isinstance(source, str):
raise InterpreterError("substring expects the first argument to be a string", line)
if not is_number(start) or not is_number(end):
raise InterpreterError("substring expects start and end indices to be numbers", line)
start = int(start)
end = int(end)
if start < 0 or end < 0 or start > len(source) or end > len(source) or start > end:
raise InterpreterError("substring indices out of bounds", line)
result = source[start:end]
logger.info(f"substring: '{source}'[{start}:{end}] = '{result}'")
return result
def func_lowercase(args, line):
if len(args) != 1:
raise InterpreterError("lowercase expects exactly one argument", line)
a = args[0]
if not isinstance(a, str):
raise InterpreterError("lowercase expects a string argument", line)
result = a.lower()
logger.info(f"lowercase: '{a}' -> '{result}'")
return result
def func_uppercase(args, line):
if len(args) != 1:
raise InterpreterError("uppercase expects exactly one argument", line)
a = args[0]
if not isinstance(a, str):
raise InterpreterError("uppercase expects a string argument", line)
result = a.upper()
logger.info(f"uppercase: '{a}' -> '{result}'")
return result
def func_replace(args, line):
if len(args) != 3:
raise InterpreterError("replace expects exactly three arguments", line)
source, target, replacement = args
if not isinstance(source, str) or not isinstance(target, str) or not isinstance(replacement, str):
raise InterpreterError("replace expects string arguments", line)
result = source.replace(target, replacement)
logger.info(f"replace: '{source}' with '{target}' replaced by '{replacement}' -> '{result}'")
return result
def func_add(args, line):
if len(args) < 2:
raise InterpreterError("add expects at least two arguments", line)
total = 0
for arg in args:
if not is_number(arg):
raise InterpreterError("add expects numeric arguments", line)
total += arg
total = round(total, 4) if isinstance(total, float) else total
logger.info(f"add: sum({args}) = {total}")
return total
def func_subtract(args, line):
if len(args) != 2:
raise InterpreterError("subtract expects exactly two arguments", line)
a, b = args
if not is_number(a) or not is_number(b):
raise InterpreterError("subtract expects numeric arguments", line)
result = a - b
result = round(result, 4) if isinstance(result, float) else result
logger.info(f"subtract: {a} - {b} = {result}")
return result
def func_multiply(args, line):
if len(args) < 2:
raise InterpreterError("multiply expects at least two arguments", line)
result = 1
for arg in args:
if not is_number(arg):
raise InterpreterError("multiply expects numeric arguments", line)
result *= arg
result = round(result, 4) if isinstance(result, float) else result
logger.info(f"multiply: product({args}) = {result}")
return result
def func_divide(args, line):
if len(args) != 2:
raise InterpreterError("divide expects exactly two arguments", line)
dividend, divisor = args
if not is_number(dividend) or not is_number(divisor):
raise InterpreterError("divide expects numeric arguments", line)
if divisor == 0:
raise InterpreterError("Division by zero", line)
if isinstance(dividend, int) and isinstance(divisor, int):
result = dividend // divisor
else:
result = round(dividend / divisor, 4)
logger.info(f"divide: {dividend} / {divisor} = {result}")
return result
def func_abs(args, line):
if len(args) != 1:
raise InterpreterError("abs expects exactly one argument", line)
a = args[0]
if not is_number(a):
raise InterpreterError("abs expects a numeric argument", line)
result = abs(a)
logger.info(f"abs: abs({a}) = {result}")
return result
def func_max(args, line):
if len(args) < 1:
raise InterpreterError("max expects at least one argument", line)
if not all(is_number(arg) for arg in args):
raise InterpreterError("max expects numeric arguments", line)
result = max(args)
logger.info(f"max: max({args}) = {result}")
return result
def func_min(args, line):
if len(args) < 1:
raise InterpreterError("min expects at least one argument", line)
if not all(is_number(arg) for arg in args):
raise InterpreterError("min expects numeric arguments", line)
result = min(args)
logger.info(f"min: min({args}) = {result}")
return result
def func_gt(args, line):
if len(args) != 2:
raise InterpreterError("gt expects exactly two arguments", line)
a, b = args
if not is_number(a) or not is_number(b):
raise InterpreterError("gt expects numeric arguments", line)
result = a > b
logger.info(f"gt: {a} > {b} = {result}")
return result
def func_lt(args, line):
if len(args) != 2:
raise InterpreterError("lt expects exactly two arguments", line)
a, b = args
if not is_number(a) or not is_number(b):
raise InterpreterError("lt expects numeric arguments", line)
result = a < b
logger.info(f"lt: {a} < {b} = {result}")
return result
def func_equal(args, line):
if len(args) != 2:
raise InterpreterError("equal expects exactly two arguments", line)
a, b = args
result = a == b
logger.info(f"equal: {a} == {b} = {result}")
return result
def func_not_equal(args, line):
if len(args) != 2:
raise InterpreterError("not_equal expects exactly two arguments", line)
a, b = args
result = a != b
logger.info(f"not_equal: {a} != {b} = {result}")
return result
def func_str(args, line):
if len(args) != 1:
raise InterpreterError("str expects exactly one argument", line)
a = args[0]
try:
result = convert_str(a)
logger.info(f"str: {a} -> '{result}'")
return result
except ValueError:
raise InterpreterError("str cannot convert the given type", line)
# Mapping of function names to their implementations
FUNCTIONS = {
"puts": func_puts,
"set": func_set,
"concat": func_concat,
"substring": func_substring,
"lowercase": func_lowercase,
"uppercase": func_uppercase,
"replace": func_replace,
"add": func_add,
"subtract": func_subtract,
"multiply": func_multiply,
"divide": func_divide,
"abs": func_abs,
"max": func_max,
"min": func_min,
"gt": func_gt,
"lt": func_lt,
"equal": func_equal,
"not_equal": func_not_equal,
"str": func_str,
}
# Parser and evaluator
def parse_expression(expression, line):
tokens = tokenize(expression)
if not tokens:
raise InterpreterError("Empty expression", line)
return parse_tokens(tokens, line)
def tokenize(expression):
tokens = TOKEN_REGEX.findall(expression)
tokens = [tok for group in tokens for tok in group if tok]
logger.debug(f"Tokenized expression: {tokens}")
return tokens
def parse_tokens(tokens, line):
if not tokens:
raise InterpreterError("Unexpected end of expression", line)
token = tokens.pop(0)
if token == '(':
lst = []
while tokens and tokens[0] != ')':
lst.append(parse_tokens(tokens, line))
if not tokens:
raise InterpreterError("Expected ')'", line)
tokens.pop(0) # Remove ')'
logger.debug(f"Parsed list: {lst}")
return lst
elif token == ')':
raise InterpreterError("Unexpected ')'", line)
else:
return atom(token, line)
def atom(token, line):
if token.startswith('"') and token.endswith('"'):
value = token[1:-1]
logger.debug(f"Atom: String '{value}'")
return value
elif token == "true":
logger.debug("Atom: Boolean True")
return True
elif token == "false":
logger.debug("Atom: Boolean False")
return False
elif token == "null":
logger.debug("Atom: Null")
return None
else:
# Try to parse as integer or float
try:
if '.' in token:
value = float(token)
logger.debug(f"Atom: Float {value}")
return value
else:
value = int(token)
logger.debug(f"Atom: Integer {value}")
return value
except ValueError:
# It's a variable name
if token in symbols:
value = symbols[token]
logger.debug(f"Atom: Variable '{token}' with value '{value}'")
return value
else:
logger.debug(f"Atom: Undefined variable or function '{token}'")
return token # Could be a function name
def evaluate(ast, line):
if isinstance(ast, list):
if not ast:
raise InterpreterError("Empty expression", line)
func_name = ast[0]
if not isinstance(func_name, str):
raise InterpreterError("Function name must be a string", line)
if func_name not in FUNCTIONS:
raise InterpreterError(f"Undefined function '{func_name}'", line)
func = FUNCTIONS[func_name]
args = []
for arg in ast[1:]:
args.append(evaluate(arg, line))
logger.debug(f"Evaluating function '{func_name}' with arguments {args}")
return func(args, line)
else:
logger.debug(f"Returning atom: {ast}")
return ast
@app.route('/lisp-parser', methods=['POST'])
def lisp_parser():
global symbols, output
symbols = {}
output = []
data = request.get_json()
expressions = data.get("expressions", [])
logger.info(f"Received expressions: {expressions}")
for idx, expr in enumerate(expressions):
line = idx + 1
logger.info(f"Processing line {line}: {expr}")
try:
ast = parse_expression(expr, line)
logger.debug(f"AST for line {line}: {ast}")
result = evaluate(ast, line)
logger.debug(f"Result for line {line}: {result}")
# If the result is a boolean or number and not part of 'puts', it's ignored
except InterpreterError as e:
error_message = f"ERROR at line {e.line}"
logger.error(error_message)
output.append(error_message)
break
except Exception as e:
error_message = f"ERROR at line {line}"
logger.error(f"{error_message}: {str(e)}")
output.append(error_message)
break
logger.info(f"Final output: {output}")
return jsonify({"output": output})