42Cummer commited on
Commit
02215ec
·
verified ·
1 Parent(s): 8e05cf9

Upload 8 files

Browse files
Files changed (8) hide show
  1. Dockerfile +21 -0
  2. README.md +27 -12
  3. __init__.py +1 -0
  4. app.py +135 -0
  5. compiler.py +140 -0
  6. exceptions.py +22 -0
  7. requirements.txt +5 -0
  8. worker.py +76 -0
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11.9-slim
2
+
3
+ # Set the working directory in the container
4
+ WORKDIR /app
5
+
6
+ # Install system dependencies (optional, for resource limiting)
7
+ RUN apt-get update && apt-get install -y procps && rm -rf /var/lib/apt/lists/*
8
+
9
+ # Copy requirements first to leverage Docker cache
10
+ COPY requirements.txt .
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy the rest of your ErasLang engine
14
+ COPY . .
15
+
16
+ # Hugging Face Spaces runs on port 7860 by default
17
+ ENV PORT=7860
18
+ EXPOSE 7860
19
+
20
+ # Start the FastAPI server
21
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,12 +1,27 @@
1
- ---
2
- title: ErasLangBackend
3
- emoji: 📉
4
- colorFrom: green
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- license: cc-by-nc-nd-4.0
9
- short_description: Compiler for the ErasLang programming languagee
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ErasLang API
2
+
3
+ FastAPI backend for the ErasLang programming language.
4
+
5
+ ## Endpoints
6
+
7
+ - `GET /` - Health check
8
+ - `POST /perform` - Execute ErasLang code
9
+
10
+ ## Request Format
11
+
12
+ ```json
13
+ {
14
+ "code": "ARE YOU READY FOR IT?\n BLANK SPACE x\n YOU BELONG WITH ME 13\n SPEAK NOW x\nLONG LIVE",
15
+ "inputs": [1989]
16
+ }
17
+ ```
18
+
19
+ ## Response Format
20
+
21
+ ```json
22
+ {
23
+ "status": "success",
24
+ "output": "13\n",
25
+ "error": null
26
+ }
27
+ ```
__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # ErasLang package
app.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import sys
3
+ import os
4
+
5
+ from fastapi import FastAPI, HTTPException, Request # type: ignore
6
+ from fastapi.middleware.cors import CORSMiddleware # type: ignore
7
+ from pydantic import BaseModel # type: ignore
8
+ from slowapi import Limiter, _rate_limit_exceeded_handler # type: ignore
9
+ from slowapi.util import get_remote_address # type: ignore
10
+ from slowapi.errors import RateLimitExceeded # type: ignore
11
+ from compiler import ErasCompiler # type: ignore
12
+
13
+ # Initialize FastAPI and Rate Limiter
14
+ app = FastAPI(title="ErasLang IDE Backend")
15
+ limiter = Limiter(key_func=get_remote_address)
16
+ app.state.limiter = limiter
17
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
18
+
19
+ # SWE Best Practice: Enable CORS so your frontend can communicate with this API
20
+ app.add_middleware(
21
+ CORSMiddleware,
22
+ allow_origins=["*"], # In production, replace with your frontend URL
23
+ allow_credentials=True,
24
+ allow_methods=["*"],
25
+ allow_headers=["*"],
26
+ )
27
+
28
+ compiler = ErasCompiler()
29
+
30
+ class PerformanceRequest(BaseModel):
31
+ code: str
32
+ inputs: list[int] = []
33
+
34
+ @app.get("/")
35
+ @limiter.limit("20/minute")
36
+ async def root(request: Request):
37
+ return {"message": "ErasLang Stage is Live. Ready for the Performance?"}
38
+
39
+ @app.post("/perform")
40
+ @limiter.limit("10/minute")
41
+ async def run_eraslang(request: Request, body: PerformanceRequest):
42
+ """
43
+ Main execution endpoint.
44
+ 1. Transpiles ErasLang -> Python
45
+ 2. Spawns worker.py subprocess
46
+ 3. Returns results or errors
47
+ """
48
+
49
+ # --- Step 1: Transpilation ---
50
+ try:
51
+ # We ensure the compiler adds # ERAS_LINE_X comments for our worker
52
+ python_source = compiler.transpile(body.code)
53
+
54
+ # If inputs are provided, inject an input provider function
55
+ if body.inputs:
56
+ import re
57
+ # Replace all int(input()) calls with our custom input function
58
+ python_source = re.sub(
59
+ r'int\s*\(\s*input\s*\(\s*\)\s*\)',
60
+ 'int(_eras_input())',
61
+ python_source
62
+ )
63
+ # Prepend the input provider code
64
+ input_provider = f"""
65
+ # Input provider for prefilled inputs
66
+ _input_index = 0
67
+ _input_values = {body.inputs}
68
+
69
+ def _eras_input():
70
+ global _input_index
71
+ if _input_index < len(_input_values):
72
+ value = _input_values[_input_index]
73
+ _input_index += 1
74
+ return str(value)
75
+ else:
76
+ raise ValueError("Not enough inputs provided. Expected more TELL ME WHY calls.")
77
+
78
+ """
79
+ python_source = input_provider + python_source
80
+ except Exception as e:
81
+ # Catch BadBloodSyntaxErrors from the compiler
82
+ return {
83
+ "status": "transpilation_error",
84
+ "output": "",
85
+ "error": str(e)
86
+ }
87
+
88
+ # --- Step 2: Subprocess Execution ---
89
+ try:
90
+ # We call worker.py and pipe the python_source into its stdin
91
+ worker_path = os.path.join(os.path.dirname(__file__), "worker.py")
92
+ process = subprocess.Popen(
93
+ [sys.executable, worker_path],
94
+ stdin=subprocess.PIPE,
95
+ stdout=subprocess.PIPE,
96
+ stderr=subprocess.PIPE,
97
+ text=True
98
+ )
99
+
100
+ # We set a strict timeout (e.g., 10 seconds) to prevent infinite loops
101
+ try:
102
+ stdout_data, stderr_data = process.communicate(
103
+ input=python_source,
104
+ timeout=10
105
+ )
106
+ except subprocess.TimeoutExpired:
107
+ process.kill()
108
+ return {
109
+ "status": "timeout",
110
+ "output": "Performance Timed Out. Did you get stuck in an 'Is it Over Now?' loop?",
111
+ "error": "Execution exceeded 10 second limit."
112
+ }
113
+
114
+ # --- Step 3: Handle Results ---
115
+ if process.returncode == 0:
116
+ return {
117
+ "status": "success",
118
+ "output": stdout_data,
119
+ "error": None
120
+ }
121
+ else:
122
+ # The worker.py prints thematic errors to stdout on failure
123
+ return {
124
+ "status": "runtime_error",
125
+ "output": stdout_data,
126
+ "error": stderr_data.strip()
127
+ }
128
+
129
+ except Exception as e:
130
+ raise HTTPException(status_code=500, detail=f"System Error: {str(e)}")
131
+
132
+ if __name__ == "__main__":
133
+ import uvicorn # type: ignore
134
+ # Start the server on port 8000
135
+ uvicorn.run(app, host="0.0.0.0", port=8000)
compiler.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class ErasCompiler:
2
+ def __init__(self):
3
+ self.KEYWORDS = {
4
+ "ARE YOU READY FOR IT?": "BEGIN_MAIN",
5
+ "LONG LIVE": "END_MAIN",
6
+ "BLANK SPACE": "DECLARE_VAR",
7
+ "YOU BELONG WITH ME": "ASSIGN_VAL",
8
+ "SHAKE IT OFF": "BEGIN_MATH",
9
+ "MINE": "ADD",
10
+ "BACK TO DECEMBER": "SUBTRACT",
11
+ "ENOUGH TALK": "END_MATH",
12
+ "SPEAK NOW": "PRINT",
13
+ "I KNEW YOU WERE TROUBLE": "IF",
14
+ "YOU'RE ON YOUR OWN, KID": "ELSE",
15
+ "EXILE": "END_IF",
16
+ "STAY STAY STAY": "==",
17
+ "LET'S GO BATTLE": ">",
18
+ "FROM THE VAULT": "BEGIN_FUNC",
19
+ "I'LL NAME MINE": "FUNC_ARG",
20
+ "CLEAN": "END_FUNC",
21
+ "I'LL BE BACK": "RETURN",
22
+ "ME!": "AND",
23
+ "THE 1": "OR",
24
+ "LOOK WHAT YOU MADE ME DO": "NOT",
25
+ "TELL ME WHY": "INPUT",
26
+ "WE ARE NEVER EVER GETTING BACK TOGETHER": "BREAK",
27
+ "IS IT OVER NOW?": "WHILE",
28
+ "OUT OF THE WOODS": "END_WHILE",
29
+ }
30
+
31
+ def transpile(self, source_code: str) -> str:
32
+ lines = source_code.splitlines()
33
+ python_output = ["enchanted = 1\nexile = 0\nthirteen = 13\n"]
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
40
+ line = line.strip()
41
+ if not line or line.startswith("DEAR JOHN"):
42
+ continue
43
+
44
+ token = None
45
+ payload = ""
46
+ for lyric, t in self.KEYWORDS.items():
47
+ if line.startswith(lyric):
48
+ token = t
49
+ payload = line.replace(lyric, "").strip()
50
+ break
51
+
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", ">")
58
+ # Replace input keyword in payload - this handles "YOU BELONG WITH ME TELL ME WHY"
59
+ payload = payload.replace("TELL ME WHY", "int(input())")
60
+ # Remove PLAY keyword (used for function calls)
61
+ payload = payload.replace("PLAY ", "")
62
+
63
+ current_indent = " " * indent_level
64
+
65
+ # Core Logic Mapping
66
+ if token == "BEGIN_MAIN":
67
+ python_output.append("def main():\n")
68
+ indent_level += 1
69
+ elif token == "END_MAIN":
70
+ python_output.append("\nmain()\n") # Simplified for API execution
71
+ elif token == "DECLARE_VAR":
72
+ last_var = payload
73
+ python_output.append(f"{current_indent}{payload} = 0\n")
74
+ elif token == "ASSIGN_VAL":
75
+ # payload already has "int(input())" if it contained "TELL ME WHY"
76
+ val = payload
77
+ if math_target:
78
+ python_output.append(f"{current_indent}_acc = {val}\n")
79
+ else:
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")
88
+ elif token == "SUBTRACT":
89
+ python_output.append(f"{current_indent}_acc -= {payload}\n")
90
+ elif token == "AND":
91
+ python_output.append(f"{current_indent}_acc = int(bool(_acc) and bool({payload}))\n")
92
+ elif token == "OR":
93
+ python_output.append(f"{current_indent}_acc = int(bool(_acc) or bool({payload}))\n")
94
+ elif token == "NOT":
95
+ # Count how many times Taylor told you to look
96
+ flip_count = raw.count("LOOK WHAT YOU MADE ME DO")
97
+
98
+ # We start with the value (either the payload variable or the current accumulator)
99
+ target = payload if payload else "_acc"
100
+
101
+ # Use modulo 2 instead of spamming nots
102
+ if flip_count % 2 == 1:
103
+ python_output.append(f"{current_indent}_acc = int(not bool({target}))\n")
104
+ else:
105
+ python_output.append(f"{current_indent}_acc = int(bool({target}))\n")
106
+ elif token == "==":
107
+ python_output.append(f"{current_indent}_acc = int(_acc == {payload})\n")
108
+ elif token == "WHILE":
109
+ python_output.append(f"{current_indent}while {payload}:\n")
110
+ indent_level += 1
111
+ elif token == "END_WHILE":
112
+ indent_level -= 1
113
+ elif token == "IF":
114
+ python_output.append(f"{current_indent}if {payload}:\n")
115
+ indent_level += 1
116
+ elif token == "ELSE":
117
+ indent_level -= 1
118
+ python_output.append(f"{' ' * indent_level}else:\n")
119
+ indent_level += 1
120
+ elif token == "END_IF":
121
+ indent_level -= 1
122
+ elif token == "PRINT":
123
+ python_output.append(f"{current_indent}print({payload})\n")
124
+ elif token == "BREAK":
125
+ python_output.append(f"{current_indent}break\n")
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")
139
+
140
+ return "".join(python_output)
exceptions.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # eras_exceptions.py
2
+
3
+ class ErasLangError(Exception):
4
+ """Base class for all ErasLang errors."""
5
+ def __init__(self, message, line_no=None):
6
+ self.line_no = line_no
7
+ self.message = f"🎶 Performance Issue: {message}"
8
+ if line_no:
9
+ self.message += f" (at line {line_no})"
10
+ super().__init__(self.message)
11
+
12
+ class BadBloodSyntaxError(ErasLangError):
13
+ """Raised when the compiler finds lyrics it doesn't recognize."""
14
+ pass
15
+
16
+ class ExileRuntimeError(ErasLangError):
17
+ """Raised when the logic fails during execution (e.g., div by zero)."""
18
+ pass
19
+
20
+ class VaultAccessError(ErasLangError):
21
+ """Raised when calling a function that wasn't found in the vault."""
22
+ pass
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi==0.128.0
2
+ uvicorn==0.40.0
3
+ pydantic==2.12.5
4
+ httpx==0.28.1
5
+ slowapi==0.1.9
worker.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import traceback
3
+ import re
4
+ import resource
5
+
6
+ def limit_resources():
7
+ """
8
+ Prevents malicious or accidental resource exhaustion.
9
+ """
10
+ try:
11
+ # Limit CPU time to 5 seconds
12
+ resource.setrlimit(resource.RLIMIT_CPU, (5, 5))
13
+ except (ValueError, OSError):
14
+ # Resource limits may not be available on all platforms
15
+ pass
16
+ try:
17
+ # Limit memory usage to 128MB
18
+ resource.setrlimit(resource.RLIMIT_AS, (128 * 1024 * 1024, 128 * 1024 * 1024))
19
+ except (ValueError, OSError):
20
+ # Resource limits may not be available on all platforms
21
+ pass
22
+
23
+ def translate_line_number(error_msg):
24
+ """
25
+ Search the traceback for the # ERAS_LINE_X comment
26
+ to map Python errors back to the original ErasLang line.
27
+ """
28
+ match = re.search(r"# ERAS_LINE_(\d+)", error_msg)
29
+ return match.group(1) if match else "Unknown"
30
+
31
+ def perform_execution():
32
+ # 1. Read the transpiled Python code from the standard input pipe
33
+ try:
34
+ code = sys.stdin.read()
35
+ except EOFError:
36
+ return
37
+
38
+ # 2. Set up the "Stage" (Global Constants)
39
+ globals_dict = {
40
+ "enchanted": 1,
41
+ "exile": 0,
42
+ "thirteen": 13,
43
+ "__name__": "__main__", # Ensure __name__ is set for if __name__ checks
44
+ "__builtins__": __builtins__ # Keep basic functions like print()
45
+ }
46
+
47
+ try:
48
+ # 3. Apply SWE Guardrails
49
+ limit_resources()
50
+
51
+ # 4. Execute the performance
52
+ exec(code, globals_dict)
53
+
54
+ # 5. Catch and Translate Errors to ErasLang Terminology
55
+ except ZeroDivisionError:
56
+ print("EXILE ERROR: You tried to divide by zero. You're on your own, kid.")
57
+ sys.exit(1)
58
+ except NameError as e:
59
+ full_error = traceback.format_exc()
60
+ line = translate_line_number(full_error)
61
+ # Extract the missing variable name from the error message
62
+ var_match = re.search(r"name '(.+)' is not defined", str(e))
63
+ var_name = var_match.group(1) if var_match else "something"
64
+ print(f"VAULT ERROR: '{var_name}' was never declared at Line {line}. You left a Blank Space.")
65
+ sys.exit(1)
66
+ except SyntaxError as e:
67
+ print(f"BAD BLOOD: The bridge is broken. Syntax error near Line {e.lineno}.")
68
+ sys.exit(1)
69
+ except Exception as e:
70
+ full_error = traceback.format_exc()
71
+ line = translate_line_number(full_error)
72
+ print(f"RUNTIME ERROR at Line {line}: {str(e)}")
73
+ sys.exit(1)
74
+
75
+ if __name__ == "__main__":
76
+ perform_execution()