Spaces:
Running
Running
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 | |
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}) | |