42Cummer commited on
Commit
14a97bc
·
verified ·
1 Parent(s): 870b155

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +481 -20
  2. compiler.py +40 -6
app.py CHANGED
@@ -1,11 +1,13 @@
1
  import subprocess
2
  import sys
3
  import os
 
 
4
  from pathlib import Path
5
 
6
  from fastapi import FastAPI, HTTPException, Request # type: ignore
7
  from fastapi.middleware.cors import CORSMiddleware # type: ignore
8
- from pydantic import BaseModel # type: ignore
9
  from slowapi import Limiter, _rate_limit_exceeded_handler # type: ignore
10
  from slowapi.util import get_remote_address # type: ignore
11
  from slowapi.errors import RateLimitExceeded # type: ignore
@@ -63,6 +65,313 @@ class PerformanceRequest(BaseModel):
63
  inputs: list[int] = []
64
  password: str # Secret password for authentication (required)
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  @app.get("/")
67
  @limiter.limit("20/minute")
68
  async def root(request: Request):
@@ -87,38 +396,136 @@ async def run_eraslang(request: Request, body: PerformanceRequest):
87
  4. Returns results or errors
88
  """
89
 
90
- # --- Step 0: Password Check (Always Required) ---
91
  if not body.password:
92
- raise HTTPException(
93
- status_code=401,
94
- detail="Unauthorized. Password is required."
95
- )
 
 
 
 
 
96
 
97
  if body.password != API_SECRET:
98
- raise HTTPException(
99
- status_code=401,
100
- detail="Unauthorized. Invalid password."
101
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  # --- Step 1: Transpilation ---
104
  try:
105
  # We ensure the compiler adds # ERAS_LINE_X comments for our worker
106
  python_source = compiler.transpile(body.code)
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  # If inputs are provided, inject an input provider function
109
- if body.inputs:
110
- import re
 
 
 
 
 
 
 
 
 
 
 
 
111
  # Replace all int(input()) calls with our custom input function
112
  python_source = re.sub(
113
  r'int\s*\(\s*input\s*\(\s*\)\s*\)',
114
  'int(_eras_input())',
115
  python_source
116
  )
 
117
  # Prepend the input provider code
118
  input_provider = f"""
119
  # Input provider for prefilled inputs
120
  _input_index = 0
121
- _input_values = {body.inputs}
122
 
123
  def _eras_input():
124
  global _input_index
@@ -131,18 +538,32 @@ def _eras_input():
131
 
132
  """
133
  python_source = input_provider + python_source
 
134
  except Exception as e:
135
- # Catch BadBloodSyntaxErrors from the compiler
 
136
  return {
137
  "status": "transpilation_error",
138
  "output": "",
139
- "error": str(e)
140
  }
141
 
142
  # --- Step 2: Subprocess Execution ---
143
  try:
144
  # We call worker.py and pipe the python_source into its stdin
145
  worker_path = os.path.join(os.path.dirname(__file__), "worker.py")
 
 
 
 
 
 
 
 
 
 
 
 
146
  process = subprocess.Popen(
147
  [sys.executable, worker_path],
148
  stdin=subprocess.PIPE,
@@ -159,10 +580,16 @@ def _eras_input():
159
  )
160
  except subprocess.TimeoutExpired:
161
  process.kill()
 
162
  return {
163
  "status": "timeout",
164
- "output": "Performance Timed Out. Did you get stuck in an 'Is it Over Now?' loop?",
165
- "error": "Execution exceeded 10 second limit."
 
 
 
 
 
166
  }
167
 
168
  # --- Step 3: Handle Results ---
@@ -174,14 +601,48 @@ def _eras_input():
174
  }
175
  else:
176
  # The worker.py prints thematic errors to stdout on failure
 
 
 
 
177
  return {
178
  "status": "runtime_error",
179
- "output": stdout_data,
180
- "error": stderr_data.strip()
181
  }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  except Exception as e:
184
- raise HTTPException(status_code=500, detail=f"System Error: {str(e)}")
 
 
 
 
 
 
 
 
 
 
185
 
186
  if __name__ == "__main__":
187
  import uvicorn # type: ignore
 
1
  import subprocess
2
  import sys
3
  import os
4
+ import re
5
+ import traceback
6
  from pathlib import Path
7
 
8
  from fastapi import FastAPI, HTTPException, Request # type: ignore
9
  from fastapi.middleware.cors import CORSMiddleware # type: ignore
10
+ from pydantic import BaseModel, ValidationError # type: ignore
11
  from slowapi import Limiter, _rate_limit_exceeded_handler # type: ignore
12
  from slowapi.util import get_remote_address # type: ignore
13
  from slowapi.errors import RateLimitExceeded # type: ignore
 
65
  inputs: list[int] = []
66
  password: str # Secret password for authentication (required)
67
 
68
+ def parse_error_message(error_output: str) -> dict:
69
+ """
70
+ Parse error messages from worker.py to extract structured information.
71
+ Returns a dictionary with error details.
72
+ """
73
+ error_info = {
74
+ "message": error_output.strip(),
75
+ "type": "unknown",
76
+ "line": None,
77
+ "variable": None,
78
+ "suggestion": None
79
+ }
80
+
81
+ # Parse EXILE ERROR (ZeroDivisionError)
82
+ if "EXILE ERROR" in error_output:
83
+ error_info["type"] = "division_by_zero"
84
+ error_info["message"] = "You tried to divide by zero. You're on your own, kid."
85
+ error_info["suggestion"] = "Check your math operations - make sure you're not dividing by zero."
86
+
87
+ # Parse VAULT ERROR (NameError)
88
+ elif "VAULT ERROR" in error_output:
89
+ error_info["type"] = "undefined_variable"
90
+ # Extract variable name and line number
91
+ var_match = re.search(r"'(.+?)' was never declared at Line (\d+)", error_output)
92
+ if var_match:
93
+ error_info["variable"] = var_match.group(1)
94
+ error_info["line"] = var_match.group(2)
95
+ error_info["message"] = f"Variable '{error_info['variable']}' was never declared at Line {error_info['line']}. You left a Blank Space."
96
+ error_info["suggestion"] = f"Use 'BLANK SPACE {error_info['variable']}' to declare the variable before using it."
97
+ else:
98
+ error_info["message"] = error_output.strip()
99
+
100
+ # Parse BAD BLOOD (SyntaxError)
101
+ elif "BAD BLOOD" in error_output:
102
+ error_info["type"] = "syntax_error"
103
+ line_match = re.search(r"Line (\d+)", error_output)
104
+ if line_match:
105
+ error_info["line"] = line_match.group(1)
106
+ error_info["message"] = f"The bridge is broken. Syntax error near Line {error_info['line']}."
107
+ else:
108
+ error_info["message"] = error_output.strip()
109
+ error_info["suggestion"] = "Check your ErasLang syntax - make sure all keywords are spelled correctly and properly formatted."
110
+
111
+ # Parse RUNTIME ERROR
112
+ elif "RUNTIME ERROR" in error_output:
113
+ error_info["type"] = "runtime_error"
114
+ line_match = re.search(r"Line (\d+):", error_output)
115
+ if line_match:
116
+ error_info["line"] = line_match.group(1)
117
+ # Extract the actual error message
118
+ msg_match = re.search(r"RUNTIME ERROR at Line \d+: (.+)", error_output)
119
+ if msg_match:
120
+ error_info["message"] = msg_match.group(1).strip()
121
+ else:
122
+ error_info["message"] = error_output.strip()
123
+ error_info["suggestion"] = "Check your code logic - there may be an issue with how variables are being used."
124
+
125
+ # Parse ValueError (not enough inputs)
126
+ elif "Not enough inputs provided" in error_output:
127
+ error_info["type"] = "insufficient_inputs"
128
+ error_info["message"] = "Not enough inputs provided. Expected more QUESTION...? calls."
129
+ error_info["suggestion"] = "Make sure you provide enough input values in the inputs array for all QUESTION...? calls in your code."
130
+
131
+ # Default: use the error output as-is
132
+ else:
133
+ error_info["message"] = error_output.strip()
134
+ if not error_info["message"]:
135
+ error_info["message"] = "An unknown error occurred during execution."
136
+ error_info["suggestion"] = "Please check your code and try again."
137
+
138
+ return error_info
139
+
140
+ def format_transpilation_error(error: Exception) -> dict:
141
+ """
142
+ Format transpilation errors with helpful context.
143
+ """
144
+ error_info = {
145
+ "message": str(error),
146
+ "type": "transpilation_error",
147
+ "line": None,
148
+ "suggestion": None
149
+ }
150
+
151
+ error_str = str(error)
152
+
153
+ # Try to extract line number from error message
154
+ line_match = re.search(r'line (\d+)', error_str, re.IGNORECASE)
155
+ if line_match:
156
+ error_info["line"] = line_match.group(1)
157
+
158
+ # Provide specific suggestions based on error type
159
+ if "not defined" in error_str.lower() or "name" in error_str.lower():
160
+ error_info["suggestion"] = "Make sure all variables are declared with 'BLANK SPACE' before use."
161
+ elif "syntax" in error_str.lower():
162
+ error_info["suggestion"] = "Check your ErasLang syntax. Make sure all keywords are spelled correctly."
163
+ elif "unexpected" in error_str.lower():
164
+ error_info["suggestion"] = "There's an unexpected token in your code. Check for typos or missing keywords."
165
+ else:
166
+ error_info["suggestion"] = "Review your ErasLang code for syntax errors or unrecognized keywords."
167
+
168
+ return error_info
169
+
170
+ def validate_code_structure(code: str) -> dict:
171
+ """
172
+ Validate ErasLang code structure before transpilation.
173
+ Returns None if valid, or error dict if invalid.
174
+ """
175
+ lines = code.splitlines()
176
+ non_empty_lines = [line.strip() for line in lines if line.strip() and not line.strip().startswith("DEAR JOHN")]
177
+
178
+ if not non_empty_lines:
179
+ return {
180
+ "message": "Code contains no valid ErasLang instructions. Only comments or empty lines found.",
181
+ "type": "empty_code",
182
+ "suggestion": "Add ErasLang code starting with 'ARE YOU READY FOR IT?' and ending with 'LONG LIVE'."
183
+ }
184
+
185
+ # Check for BEGIN_MAIN
186
+ has_begin_main = any("ARE YOU READY FOR IT?" in line for line in lines)
187
+ if not has_begin_main:
188
+ return {
189
+ "message": "Code is missing 'ARE YOU READY FOR IT?' - every ErasLang program must start with this.",
190
+ "type": "missing_begin_main",
191
+ "suggestion": "Start your code with 'ARE YOU READY FOR IT?' to begin the main function."
192
+ }
193
+
194
+ # Check for END_MAIN
195
+ has_end_main = any("LONG LIVE" in line for line in lines)
196
+ if not has_end_main:
197
+ return {
198
+ "message": "Code is missing 'LONG LIVE' - every ErasLang program must end with this.",
199
+ "type": "missing_end_main",
200
+ "suggestion": "End your code with 'LONG LIVE' to terminate the program."
201
+ }
202
+
203
+ # Check for unmatched blocks
204
+ begin_main_count = sum(1 for line in lines if "ARE YOU READY FOR IT?" in line)
205
+ end_main_count = sum(1 for line in lines if "LONG LIVE" in line)
206
+
207
+ if begin_main_count > end_main_count:
208
+ return {
209
+ "message": f"Found {begin_main_count} 'ARE YOU READY FOR IT?' but only {end_main_count} 'LONG LIVE'. Blocks are not properly closed.",
210
+ "type": "unmatched_blocks",
211
+ "suggestion": "Make sure every 'ARE YOU READY FOR IT?' has a matching 'LONG LIVE'."
212
+ }
213
+
214
+ if end_main_count > begin_main_count:
215
+ return {
216
+ "message": f"Found {end_main_count} 'LONG LIVE' but only {begin_main_count} 'ARE YOU READY FOR IT?'. Too many closing statements.",
217
+ "type": "unmatched_blocks",
218
+ "suggestion": "Make sure you have the correct number of 'LONG LIVE' statements matching your 'ARE YOU READY FOR IT?' statements."
219
+ }
220
+
221
+ # Check for unmatched IF blocks
222
+ if_count = sum(1 for line in lines if "I KNEW YOU WERE TROUBLE" in line)
223
+ end_if_count = sum(1 for line in lines if line.strip() == "EXILE")
224
+
225
+ if if_count != end_if_count:
226
+ return {
227
+ "message": f"Found {if_count} 'I KNEW YOU WERE TROUBLE' but {end_if_count} 'EXILE'. IF blocks are not properly closed.",
228
+ "type": "unmatched_if_blocks",
229
+ "suggestion": "Make sure every 'I KNEW YOU WERE TROUBLE' has a matching 'EXILE' to close the IF block."
230
+ }
231
+
232
+ # Check for unmatched WHILE blocks
233
+ while_count = sum(1 for line in lines if "IS IT OVER NOW?" in line)
234
+ end_while_count = sum(1 for line in lines if "OUT OF THE WOODS" in line)
235
+
236
+ if while_count != end_while_count:
237
+ return {
238
+ "message": f"Found {while_count} 'IS IT OVER NOW?' but {end_while_count} 'OUT OF THE WOODS'. WHILE loops are not properly closed.",
239
+ "type": "unmatched_while_blocks",
240
+ "suggestion": "Make sure every 'IS IT OVER NOW?' has a matching 'OUT OF THE WOODS' to close the WHILE loop."
241
+ }
242
+
243
+ # Check for unmatched MATH blocks
244
+ begin_math_count = sum(1 for line in lines if "SHAKE IT OFF" in line)
245
+ end_math_count = sum(1 for line in lines if "CALL IT WHAT YOU WANT" in line)
246
+
247
+ if begin_math_count != end_math_count:
248
+ return {
249
+ "message": f"Found {begin_math_count} 'SHAKE IT OFF' but {end_math_count} 'CALL IT WHAT YOU WANT'. Math blocks are not properly closed.",
250
+ "type": "unmatched_math_blocks",
251
+ "suggestion": "Make sure every 'SHAKE IT OFF' has a matching 'CALL IT WHAT YOU WANT' to close the math block."
252
+ }
253
+
254
+ # Check for unmatched FUNCTION blocks
255
+ begin_func_count = sum(1 for line in lines if "FROM THE VAULT" in line)
256
+ end_func_count = sum(1 for line in lines if "CLEAN" in line)
257
+
258
+ if begin_func_count != end_func_count:
259
+ return {
260
+ "message": f"Found {begin_func_count} 'FROM THE VAULT' but {end_func_count} 'CLEAN'. Function blocks are not properly closed.",
261
+ "type": "unmatched_function_blocks",
262
+ "suggestion": "Make sure every 'FROM THE VAULT' has a matching 'CLEAN' to close the function definition."
263
+ }
264
+
265
+ return None # Code structure is valid
266
+
267
+ def count_input_calls(code: str) -> int:
268
+ """
269
+ Count the number of QUESTION...? calls in the code.
270
+ """
271
+ # Count standalone QUESTION...? (INPUT token)
272
+ standalone_count = sum(1 for line in code.splitlines() if line.strip() == "QUESTION...?")
273
+
274
+ # Count QUESTION...? in "YOU BELONG WITH ME QUESTION...?"
275
+ inline_count = sum(1 for line in code.splitlines() if "YOU BELONG WITH ME QUESTION...?" in line)
276
+
277
+ return standalone_count + inline_count
278
+
279
+ def validate_inputs(code: str, inputs: list[int]) -> dict:
280
+ """
281
+ Validate that the inputs array matches the number of QUESTION...? calls.
282
+ Returns None if valid, or error dict if invalid.
283
+ """
284
+ input_calls = count_input_calls(code)
285
+
286
+ if input_calls == 0:
287
+ # No inputs needed, but if inputs are provided, warn
288
+ if inputs and len(inputs) > 0:
289
+ return {
290
+ "message": f"Your code has no QUESTION...? calls, but you provided {len(inputs)} input value(s). These will be ignored.",
291
+ "type": "unused_inputs",
292
+ "suggestion": "Remove the inputs array or add QUESTION...? calls to your code if you need user input."
293
+ }
294
+ return None # No inputs needed, none provided - valid
295
+
296
+ # Code requires inputs
297
+ if not inputs or len(inputs) == 0:
298
+ return {
299
+ "message": f"Your code has {input_calls} QUESTION...? call(s) but no input values were provided.",
300
+ "type": "missing_inputs",
301
+ "suggestion": f"Provide {input_calls} integer value(s) in the inputs array to match your QUESTION...? calls."
302
+ }
303
+
304
+ if len(inputs) < input_calls:
305
+ return {
306
+ "message": f"Your code has {input_calls} QUESTION...? call(s) but only {len(inputs)} input value(s) were provided.",
307
+ "type": "insufficient_inputs",
308
+ "suggestion": f"Provide {input_calls} integer value(s) in the inputs array. You need {input_calls - len(inputs)} more value(s)."
309
+ }
310
+
311
+ if len(inputs) > input_calls:
312
+ return {
313
+ "message": f"Your code has {input_calls} QUESTION...? call(s) but {len(inputs)} input value(s) were provided. Extra inputs will be ignored.",
314
+ "type": "excess_inputs",
315
+ "suggestion": f"Provide exactly {input_calls} integer value(s) in the inputs array, or remove the extra {len(inputs) - input_calls} value(s)."
316
+ }
317
+
318
+ return None # Inputs match perfectly
319
+
320
+ def validate_syntax(code: str) -> dict:
321
+ """
322
+ Perform basic syntax validation before transpilation.
323
+ Returns None if valid, or error dict if issues found.
324
+ """
325
+ lines = code.splitlines()
326
+
327
+ # Check for common syntax issues
328
+ for i, line in enumerate(lines, 1):
329
+ stripped = line.strip()
330
+ if not stripped or stripped.startswith("DEAR JOHN"):
331
+ continue
332
+
333
+ # Check for invalid variable names in BLANK SPACE
334
+ if stripped.startswith("BLANK SPACE"):
335
+ var_name = stripped.replace("BLANK SPACE", "").strip()
336
+ if not var_name:
337
+ return {
338
+ "message": f"Line {i}: 'BLANK SPACE' requires a variable name.",
339
+ "type": "syntax_error",
340
+ "line": str(i),
341
+ "suggestion": "Use 'BLANK SPACE [variable_name]' to declare a variable. Example: 'BLANK SPACE myVar'"
342
+ }
343
+ # Check for invalid characters in variable name
344
+ if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_name):
345
+ return {
346
+ "message": f"Line {i}: Invalid variable name '{var_name}'. Variable names must start with a letter or underscore and contain only letters, numbers, and underscores.",
347
+ "type": "syntax_error",
348
+ "line": str(i),
349
+ "suggestion": f"Use a valid variable name like 'myVar', 'x', or '_temp'. Invalid: '{var_name}'"
350
+ }
351
+
352
+ # Check for invalid usage of QUESTION...? without BLANK SPACE
353
+ if stripped == "QUESTION...?":
354
+ # This is valid as standalone, but check context
355
+ # Look backwards for BLANK SPACE
356
+ found_declaration = False
357
+ for j in range(i - 1, max(0, i - 10), -1):
358
+ prev_line = lines[j].strip()
359
+ if prev_line.startswith("BLANK SPACE"):
360
+ found_declaration = True
361
+ break
362
+ if prev_line and not prev_line.startswith("DEAR JOHN"):
363
+ break
364
+
365
+ if not found_declaration:
366
+ return {
367
+ "message": f"Line {i}: 'QUESTION...?' is used without a preceding variable declaration.",
368
+ "type": "syntax_error",
369
+ "line": str(i),
370
+ "suggestion": "Declare a variable with 'BLANK SPACE [name]' before using 'QUESTION...?', or use 'YOU BELONG WITH ME QUESTION...?' to assign input to the last declared variable."
371
+ }
372
+
373
+ return None # Syntax appears valid
374
+
375
  @app.get("/")
376
  @limiter.limit("20/minute")
377
  async def root(request: Request):
 
396
  4. Returns results or errors
397
  """
398
 
399
+ # --- Step 0: Input Validation ---
400
  if not body.password:
401
+ return {
402
+ "status": "authentication_error",
403
+ "output": "",
404
+ "error": {
405
+ "message": "Password is required for authentication.",
406
+ "type": "missing_password",
407
+ "suggestion": "Please provide a valid password in your request."
408
+ }
409
+ }
410
 
411
  if body.password != API_SECRET:
412
+ return {
413
+ "status": "authentication_error",
414
+ "output": "",
415
+ "error": {
416
+ "message": "Invalid password. Access denied.",
417
+ "type": "invalid_password",
418
+ "suggestion": "Please check your password and try again."
419
+ }
420
+ }
421
+
422
+ # Validate code is not empty
423
+ if not body.code or not body.code.strip():
424
+ return {
425
+ "status": "validation_error",
426
+ "output": "",
427
+ "error": {
428
+ "message": "Code cannot be empty. Please provide ErasLang code to execute.",
429
+ "type": "empty_code",
430
+ "suggestion": "Write some ErasLang code starting with 'ARE YOU READY FOR IT?' and ending with 'LONG LIVE'."
431
+ }
432
+ }
433
+
434
+ # --- Step 0.5: Pre-transpilation Validation ---
435
+ # Validate code structure
436
+ structure_error = validate_code_structure(body.code)
437
+ if structure_error:
438
+ return {
439
+ "status": "validation_error",
440
+ "output": "",
441
+ "error": structure_error
442
+ }
443
+
444
+ # Validate syntax
445
+ syntax_error = validate_syntax(body.code)
446
+ if syntax_error:
447
+ return {
448
+ "status": "validation_error",
449
+ "output": "",
450
+ "error": syntax_error
451
+ }
452
+
453
+ # Validate inputs match QUESTION...? calls
454
+ input_error = validate_inputs(body.code, body.inputs)
455
+ if input_error:
456
+ # For unused/excess inputs, we can still proceed but warn
457
+ # For missing/insufficient inputs, we should error
458
+ if input_error["type"] in ["missing_inputs", "insufficient_inputs"]:
459
+ return {
460
+ "status": "validation_error",
461
+ "output": "",
462
+ "error": input_error
463
+ }
464
+ # For unused/excess, we'll proceed but the error will be in the response
465
+ # (Actually, let's proceed for now and handle at runtime)
466
 
467
  # --- Step 1: Transpilation ---
468
  try:
469
  # We ensure the compiler adds # ERAS_LINE_X comments for our worker
470
  python_source = compiler.transpile(body.code)
471
 
472
+ # Validate that transpilation produced valid code
473
+ if not python_source or not python_source.strip():
474
+ return {
475
+ "status": "transpilation_error",
476
+ "output": "",
477
+ "error": {
478
+ "message": "Transpilation produced empty code. Your ErasLang code may be missing required elements.",
479
+ "type": "empty_transpilation",
480
+ "suggestion": "Make sure your code includes 'ARE YOU READY FOR IT?' to start and 'LONG LIVE' to end."
481
+ }
482
+ }
483
+
484
+ # Validate Python syntax of transpiled code
485
+ try:
486
+ compile(python_source, '<string>', 'exec')
487
+ except SyntaxError as syn_err:
488
+ return {
489
+ "status": "transpilation_error",
490
+ "output": "",
491
+ "error": {
492
+ "message": f"Transpiled code has a syntax error at line {syn_err.lineno}: {syn_err.msg}",
493
+ "type": "transpilation_syntax_error",
494
+ "line": str(syn_err.lineno) if syn_err.lineno else None,
495
+ "suggestion": "This usually indicates an issue with your ErasLang code structure. Check for unmatched blocks or invalid syntax."
496
+ }
497
+ }
498
+
499
+ # Count how many input() calls we'll need to replace
500
+ input_call_count = len(re.findall(r'int\s*\(\s*input\s*\(\s*\)\s*\)', python_source))
501
+
502
  # If inputs are provided, inject an input provider function
503
+ if body.inputs or input_call_count > 0:
504
+ # Validate inputs match input calls
505
+ if input_call_count > 0:
506
+ if not body.inputs or len(body.inputs) < input_call_count:
507
+ return {
508
+ "status": "validation_error",
509
+ "output": "",
510
+ "error": {
511
+ "message": f"Your code requires {input_call_count} input value(s) but only {len(body.inputs) if body.inputs else 0} were provided.",
512
+ "type": "insufficient_inputs",
513
+ "suggestion": f"Provide {input_call_count} integer value(s) in the inputs array. You need {input_call_count - (len(body.inputs) if body.inputs else 0)} more value(s)."
514
+ }
515
+ }
516
+
517
  # Replace all int(input()) calls with our custom input function
518
  python_source = re.sub(
519
  r'int\s*\(\s*input\s*\(\s*\)\s*\)',
520
  'int(_eras_input())',
521
  python_source
522
  )
523
+
524
  # Prepend the input provider code
525
  input_provider = f"""
526
  # Input provider for prefilled inputs
527
  _input_index = 0
528
+ _input_values = {body.inputs if body.inputs else []}
529
 
530
  def _eras_input():
531
  global _input_index
 
538
 
539
  """
540
  python_source = input_provider + python_source
541
+
542
  except Exception as e:
543
+ # Format transpilation errors with helpful context
544
+ error_info = format_transpilation_error(e)
545
  return {
546
  "status": "transpilation_error",
547
  "output": "",
548
+ "error": error_info
549
  }
550
 
551
  # --- Step 2: Subprocess Execution ---
552
  try:
553
  # We call worker.py and pipe the python_source into its stdin
554
  worker_path = os.path.join(os.path.dirname(__file__), "worker.py")
555
+
556
+ if not os.path.exists(worker_path):
557
+ return {
558
+ "status": "system_error",
559
+ "output": "",
560
+ "error": {
561
+ "message": "Worker script not found. System configuration error.",
562
+ "type": "missing_worker",
563
+ "suggestion": "Please contact support - this is a system configuration issue."
564
+ }
565
+ }
566
+
567
  process = subprocess.Popen(
568
  [sys.executable, worker_path],
569
  stdin=subprocess.PIPE,
 
580
  )
581
  except subprocess.TimeoutExpired:
582
  process.kill()
583
+ process.wait() # Ensure process is terminated
584
  return {
585
  "status": "timeout",
586
+ "output": "",
587
+ "error": {
588
+ "message": "Performance Timed Out. Did you get stuck in an 'Is it Over Now?' loop?",
589
+ "type": "execution_timeout",
590
+ "line": None,
591
+ "suggestion": "Your code took longer than 10 seconds to execute. Check for infinite loops or very long-running operations. Make sure your 'IS IT OVER NOW?' loops have proper exit conditions."
592
+ }
593
  }
594
 
595
  # --- Step 3: Handle Results ---
 
601
  }
602
  else:
603
  # The worker.py prints thematic errors to stdout on failure
604
+ # Parse the error message for better structure
605
+ error_output = stdout_data if stdout_data.strip() else stderr_data
606
+ error_info = parse_error_message(error_output)
607
+
608
  return {
609
  "status": "runtime_error",
610
+ "output": stdout_data if process.returncode != 0 else "",
611
+ "error": error_info
612
  }
613
 
614
+ except FileNotFoundError:
615
+ return {
616
+ "status": "system_error",
617
+ "output": "",
618
+ "error": {
619
+ "message": "Python interpreter not found. System configuration error.",
620
+ "type": "missing_python",
621
+ "suggestion": "Please contact support - this is a system configuration issue."
622
+ }
623
+ }
624
+ except PermissionError:
625
+ return {
626
+ "status": "system_error",
627
+ "output": "",
628
+ "error": {
629
+ "message": "Permission denied when trying to execute code. System configuration error.",
630
+ "type": "permission_error",
631
+ "suggestion": "Please contact support - this is a system configuration issue."
632
+ }
633
+ }
634
  except Exception as e:
635
+ # Log the full error for debugging (in production, use proper logging)
636
+ error_trace = traceback.format_exc()
637
+ return {
638
+ "status": "system_error",
639
+ "output": "",
640
+ "error": {
641
+ "message": f"An unexpected system error occurred: {str(e)}",
642
+ "type": "unexpected_error",
643
+ "suggestion": "Please try again. If the problem persists, contact support with details about what you were trying to do."
644
+ }
645
+ }
646
 
647
  if __name__ == "__main__":
648
  import uvicorn # type: ignore
compiler.py CHANGED
@@ -34,6 +34,8 @@ class ErasCompiler:
34
  indent_level = 0
35
  math_target = None
36
  last_var = None
 
 
37
 
38
  for line in lines:
39
  raw = line # Save original line for counting
@@ -52,6 +54,18 @@ class ErasCompiler:
52
  if not token:
53
  continue
54
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  # Replace comparison operators in payload
56
  payload = payload.replace("STAY STAY STAY", "==")
57
  payload = payload.replace("LET'S GO BATTLE", ">")
@@ -80,8 +94,13 @@ class ErasCompiler:
80
  python_output.append(f"{current_indent}{last_var} = {val}\n")
81
  elif token == "BEGIN_MATH":
82
  math_target = payload
 
 
83
  elif token == "END_MATH":
84
- python_output.append(f"{current_indent}{math_target} = _acc\n")
 
 
 
85
  math_target = None
86
  elif token == "ADD":
87
  python_output.append(f"{current_indent}_acc += {payload}\n")
@@ -126,13 +145,28 @@ class ErasCompiler:
126
  elif token == "INPUT":
127
  python_output.append(f"{current_indent}{last_var} = int(input())\n")
128
  elif token == "BEGIN_FUNC":
129
- python_output.append(f"def {payload}")
 
 
130
  elif token == "FUNC_ARG":
131
- python_output[-1] += f"({payload}):\n"
132
- indent_level += 1
 
 
 
 
 
133
  elif token == "END_FUNC":
134
- # Functions in Python don't need an explicit 'end' keyword,
135
- # so we just manage the indentation.
 
 
 
 
 
 
 
 
136
  indent_level -= 1
137
  elif token == "RETURN":
138
  python_output.append(f"{current_indent}return {payload}\n")
 
34
  indent_level = 0
35
  math_target = None
36
  last_var = None
37
+ in_function_def = False # Track if we're building a function definition
38
+ func_arg_count = 0 # Count function arguments
39
 
40
  for line in lines:
41
  raw = line # Save original line for counting
 
54
  if not token:
55
  continue
56
 
57
+ # If we're in a function definition and encounter a non-FUNC_ARG/END_FUNC token,
58
+ # close the function definition first
59
+ if in_function_def and token not in ["FUNC_ARG", "END_FUNC"]:
60
+ if python_output and not python_output[-1].endswith(":\n"):
61
+ if python_output[-1].endswith("("):
62
+ python_output[-1] += "):\n"
63
+ else:
64
+ python_output[-1] += "):\n"
65
+ indent_level += 1 # Increment for function body
66
+ in_function_def = False
67
+ func_arg_count = 0
68
+
69
  # Replace comparison operators in payload
70
  payload = payload.replace("STAY STAY STAY", "==")
71
  payload = payload.replace("LET'S GO BATTLE", ">")
 
94
  python_output.append(f"{current_indent}{last_var} = {val}\n")
95
  elif token == "BEGIN_MATH":
96
  math_target = payload
97
+ # Initialize accumulator with the target variable
98
+ python_output.append(f"{current_indent}_acc = {payload}\n")
99
  elif token == "END_MATH":
100
+ # Use payload if provided, otherwise use math_target
101
+ target_var = payload if payload else math_target
102
+ if target_var:
103
+ python_output.append(f"{current_indent}{target_var} = _acc\n")
104
  math_target = None
105
  elif token == "ADD":
106
  python_output.append(f"{current_indent}_acc += {payload}\n")
 
145
  elif token == "INPUT":
146
  python_output.append(f"{current_indent}{last_var} = int(input())\n")
147
  elif token == "BEGIN_FUNC":
148
+ python_output.append(f"{current_indent}def {payload}(")
149
+ in_function_def = True
150
+ func_arg_count = 0
151
  elif token == "FUNC_ARG":
152
+ func_arg_count += 1
153
+ # Check if this is the first argument (last line ends with "(")
154
+ if python_output and python_output[-1].endswith("("):
155
+ python_output[-1] += payload
156
+ else:
157
+ # Add comma and argument
158
+ python_output[-1] += f", {payload}"
159
  elif token == "END_FUNC":
160
+ # Close the function definition if it's still open
161
+ if in_function_def and python_output and not python_output[-1].endswith(":\n"):
162
+ if python_output[-1].endswith("("):
163
+ # No arguments, close with ):
164
+ python_output[-1] += "):\n"
165
+ else:
166
+ # Has arguments, close with ):
167
+ python_output[-1] += "):\n"
168
+ in_function_def = False
169
+ func_arg_count = 0
170
  indent_level -= 1
171
  elif token == "RETURN":
172
  python_output.append(f"{current_indent}return {payload}\n")