Spaces:
Running
Running
File size: 14,000 Bytes
0d3476b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 |
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})
|