UBS-Server / routes /interpreter.py
m-abdur2024's picture
Upload 40 files
0d3476b verified
raw
history blame contribute delete
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})