Princeaka commited on
Commit
8a9eed1
·
verified ·
1 Parent(s): b24bb61

Update coder.py

Browse files
Files changed (1) hide show
  1. coder.py +315 -221
coder.py CHANGED
@@ -1,49 +1,75 @@
 
1
  # coder.py — offline multi-language sandbox runner and preview manager
2
- import os, sys, subprocess, tempfile, traceback, threading, shlex, time, sqlite3, shutil, psutil, http.server, socketserver
 
 
 
 
 
 
 
 
 
 
 
 
3
  from typing import Optional, Dict, Any
 
4
 
5
  class Coder:
6
- def __init__(self, base_dir="sandbox"):
7
  self.base_dir = base_dir
 
 
 
8
  self.reset_sandbox()
9
- self._preview_proc = None
10
- self._preview_log = []
11
  self._preview_lock = threading.Lock()
12
- self._preview_port = None
13
 
14
  # ---- sandbox management ----
15
  def reset_sandbox(self):
 
16
  if os.path.exists(self.base_dir):
17
  shutil.rmtree(self.base_dir, ignore_errors=True)
18
  os.makedirs(self.base_dir, exist_ok=True)
19
- self.db_path = os.path.join(self.base_dir, "/tmp/sandbox")
20
  conn = sqlite3.connect(self.db_path)
21
  conn.execute("CREATE TABLE IF NOT EXISTS data(id INTEGER PRIMARY KEY, key TEXT, value TEXT)")
22
  conn.commit()
23
  conn.close()
24
 
25
- def _sandbox_path(self, filename):
26
  return os.path.join(self.base_dir, filename)
27
 
28
  # ---- core code runner ----
29
- def run_code(self, code: str, lang: str = "python", timeout: int = 15) -> Dict[str, Any]:
 
 
 
 
 
 
30
  lang = lang.lower().strip()
 
 
31
  exec_map = {
32
  "python": [sys.executable],
33
  "javascript": ["node"],
34
  "node": ["node"],
35
- "java": ["javac"],
36
  "c": ["gcc"],
37
  "c++": ["g++"],
38
  "php": ["php"],
39
  "ruby": ["ruby"],
40
- "go": ["go", "run"],
41
  "rust": ["rustc"],
42
  "bash": ["bash"],
43
  "typescript": ["ts-node"],
44
  "kotlin": ["kotlinc"],
45
  "lua": ["lua"],
46
- "sql": ["sqlite3", self.db_path],
47
  "html": ["cat"],
48
  "css": ["cat"],
49
  }
@@ -52,45 +78,123 @@ class Coder:
52
  return {"error": f"Language '{lang}' not supported."}
53
 
54
  try:
55
- suffix = {
56
  "python": ".py", "javascript": ".js", "node": ".js", "java": ".java",
57
  "c": ".c", "c++": ".cpp", "php": ".php", "ruby": ".rb", "go": ".go",
58
  "rust": ".rs", "bash": ".sh", "typescript": ".ts", "kotlin": ".kt",
59
  "lua": ".lua", "sql": ".sql", "html": ".html", "css": ".css"
60
- }[lang]
 
61
 
 
62
  with tempfile.NamedTemporaryFile(mode="w", suffix=suffix, dir=self.base_dir, delete=False) as tmp:
63
  tmp.write(code)
64
  path = tmp.name
65
 
66
- if lang in ["java"]:
67
- subprocess.run(["javac", path], cwd=self.base_dir, timeout=timeout, capture_output=True)
68
- run_cmd = ["java", os.path.splitext(os.path.basename(path))[0]]
69
- elif lang in ["c", "c++", "go", "rust"]:
70
- out_exe = path + ".out"
71
- compile_cmd = exec_map[lang] + [path, "-o", out_exe] if lang in ["c", "c++"] else exec_map[lang] + [path]
72
- subprocess.run(compile_cmd, cwd=self.base_dir, timeout=timeout, capture_output=True)
73
- run_cmd = [out_exe] if os.path.exists(out_exe) else compile_cmd
74
- elif lang == "sql":
75
- run_cmd = exec_map[lang] + [path]
76
- elif lang in ["html", "css"]:
77
  run_cmd = ["type" if os.name == "nt" else "cat", path]
78
- else:
 
 
79
  run_cmd = exec_map[lang] + [path]
80
-
81
- result = subprocess.run(
82
- run_cmd, capture_output=True, text=True, timeout=timeout, cwd=self.base_dir
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  )
84
 
85
- return {
86
- "stdout": result.stdout.strip(),
87
- "stderr": result.stderr.strip(),
88
- "returncode": result.returncode,
89
- "path": path
90
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- except subprocess.TimeoutExpired:
93
- return {"error": "Execution timeout."}
94
  except Exception:
95
  return {"error": traceback.format_exc()}
96
 
@@ -99,12 +203,12 @@ class Coder:
99
  """Deep code analysis with comprehensive error detection"""
100
  import re
101
  import ast
102
-
103
  errors = []
104
  warnings = []
105
  solutions = []
106
- code_issues = []
107
-
108
  # Static analysis before execution
109
  if lang == "python":
110
  # Check for syntax errors
@@ -119,19 +223,18 @@ class Coder:
119
  "text": e.text.strip() if e.text else ""
120
  })
121
  solutions.append(f"Fix syntax error at line {e.lineno}, column {e.offset}: {e.msg}")
122
-
123
- # Check for common issues
124
  lines = code.split("\n")
125
  for i, line in enumerate(lines, 1):
126
  line_stripped = line.strip()
127
-
128
- # Indentation issues
129
- if line and line[0] in (' ', '\t'):
130
- if '\t' in line and ' ' in line:
131
- warnings.append(f"Line {i}: Mixed tabs and spaces")
132
-
133
- # Missing colons
134
- if re.match(r'^\s*(if|elif|else|for|while|def|class|try|except|finally|with)\s+.*[^:]$', line):
135
  errors.append({
136
  "type": "MissingColon",
137
  "line": i,
@@ -139,128 +242,113 @@ class Coder:
139
  "text": line_stripped
140
  })
141
  solutions.append(f"Add ':' at end of line {i}")
142
-
143
- # Undefined variables (basic check)
144
- if '=' in line and not line_stripped.startswith('#'):
145
- # Check for common typos
146
- if 'pirnt' in line:
147
- warnings.append(f"Line {i}: Possible typo 'pirnt' (should be 'print')")
148
- if 'retrun' in line:
149
- warnings.append(f"Line {i}: Possible typo 'retrun' (should be 'return')")
150
- if 'improt' in line:
151
- warnings.append(f"Line {i}: Possible typo 'improt' (should be 'import')")
152
-
153
- # Unclosed brackets/parentheses
154
- open_parens = line.count('(') - line.count(')')
155
- open_brackets = line.count('[') - line.count(']')
156
- open_braces = line.count('{') - line.count('}')
157
-
158
- if open_parens > 0:
159
  warnings.append(f"Line {i}: Unclosed parenthesis '('")
160
- if open_brackets > 0:
161
  warnings.append(f"Line {i}: Unclosed bracket '['")
162
- if open_braces > 0:
163
  warnings.append(f"Line {i}: Unclosed brace '{{'")
164
-
165
- # Check for bare except
166
- if re.match(r'^\s*except\s*:', line):
167
  warnings.append(f"Line {i}: Bare except clause (specify exception type)")
168
-
169
- # Check for mutable default arguments
170
- if 'def ' in line and '=[]' in line.replace(' ', ''):
171
  warnings.append(f"Line {i}: Mutable default argument (use None instead)")
172
-
173
- # Check for comparison with None
174
  if '== None' in line or '!= None' in line:
175
  warnings.append(f"Line {i}: Use 'is None' or 'is not None' instead of '==' or '!='")
176
-
177
- elif lang in ["javascript", "node"]:
178
  lines = code.split("\n")
179
  for i, line in enumerate(lines, 1):
180
  line_stripped = line.strip()
181
-
182
- # Missing semicolons
183
  if line_stripped and not line_stripped.endswith((';', '{', '}', ',')) and not line_stripped.startswith('//'):
184
- warnings.append(f"Line {i}: Missing semicolon")
185
-
186
- # var instead of let/const
187
  if line_stripped.startswith('var '):
188
  warnings.append(f"Line {i}: Use 'let' or 'const' instead of 'var'")
189
-
190
- # == instead of ===
191
- if '==' in line and '===' not in line and '!=' in line and '!==' not in line:
192
- warnings.append(f"Line {i}: Use '===' or '!==' for strict equality")
193
-
194
- # Execute code and capture runtime errors
195
- result = self.run_code(code, lang)
196
-
197
- if result.get("stderr"):
198
- stderr = result["stderr"]
199
- for line in stderr.split("\n"):
200
- if line.strip():
201
- # Extract line numbers and error types
202
- match = re.search(r'line (\d+)', line, re.IGNORECASE)
203
- line_num = match.group(1) if match else "unknown"
204
-
205
- # Categorize error
206
- error_type = "RuntimeError"
207
- if "NameError" in line:
208
- error_type = "NameError"
209
- solutions.append(f"Variable not defined at line {line_num}. Check spelling or define the variable.")
210
- elif "TypeError" in line:
211
- error_type = "TypeError"
212
- solutions.append(f"Type mismatch at line {line_num}. Check data types.")
213
- elif "ValueError" in line:
214
- error_type = "ValueError"
215
- solutions.append(f"Invalid value at line {line_num}. Check input values.")
216
- elif "IndexError" in line:
217
- error_type = "IndexError"
218
- solutions.append(f"Index out of range at line {line_num}. Check array/list bounds.")
219
- elif "KeyError" in line:
220
- error_type = "KeyError"
221
- solutions.append(f"Key not found at line {line_num}. Check dictionary keys.")
222
- elif "AttributeError" in line:
223
- error_type = "AttributeError"
224
- solutions.append(f"Attribute not found at line {line_num}. Check object attributes.")
225
- elif "ImportError" in line or "ModuleNotFoundError" in line:
226
- error_type = "ImportError"
227
- solutions.append(f"Module not found at line {line_num}. Install required package.")
228
-
229
- errors.append({
230
- "type": error_type,
231
- "line": line_num,
232
- "message": line.strip()
233
- })
234
-
235
- elif "error" in result:
236
  errors.append({
237
  "type": "ExecutionError",
238
- "message": result["error"]
239
  })
240
  solutions.append("Check code syntax and dependencies")
241
-
242
  # Code quality checks
243
  if not errors:
244
  code_lines = [l for l in code.split("\n") if l.strip() and not l.strip().startswith('#')]
245
-
246
- # Check code length
247
  if len(code_lines) > 100:
248
  warnings.append("Code is quite long. Consider breaking into smaller functions.")
249
-
250
- # Check for print statements (debugging leftovers)
251
  print_count = sum(1 for l in code_lines if 'print(' in l)
252
  if print_count > 5:
253
  warnings.append(f"Found {print_count} print statements. Consider using logging instead.")
254
-
255
  # Build comprehensive result
256
  result["errors"] = errors
257
  result["warnings"] = warnings
258
  result["solutions"] = solutions
259
  result["error_count"] = len(errors)
260
  result["warning_count"] = len(warnings)
261
-
262
  if errors:
263
- result["analysis"] = f"❌ Found {len(errors)} error(s) and {len(warnings)} warning(s). Code will not execute correctly."
264
  result["severity"] = "critical"
265
  elif warnings:
266
  result["analysis"] = f"⚠️ Found {len(warnings)} warning(s). Code may have issues or bad practices."
@@ -268,64 +356,59 @@ class Coder:
268
  else:
269
  result["analysis"] = "✅ No errors or warnings detected. Code looks good!"
270
  result["severity"] = "success"
271
-
272
  return result
273
-
274
  # ---- Code Fix Feature ----
275
  def fix_code(self, code: str, lang: str = "python") -> Dict[str, Any]:
276
- """Automatically fix common code issues"""
277
  import re
278
-
279
  fixed_code = code
280
  fixes_applied = []
281
-
282
  if lang == "python":
283
- # Fix common Python issues
284
- # Add missing imports
285
- if "print(" in code and "import" not in code:
286
- pass # print is builtin
287
-
288
- # Fix indentation
289
  lines = code.split("\n")
290
- fixed_lines = []
291
- for line in lines:
292
- # Remove trailing whitespace
293
- fixed_lines.append(line.rstrip())
294
  fixed_code = "\n".join(fixed_lines)
295
-
296
- # Add missing colons
297
- fixed_code = re.sub(r'(if|while|for|def|class)\s+([^:]+)$', r'\1 \2:', fixed_code, flags=re.MULTILINE)
298
-
299
- if fixed_code != code:
300
- fixes_applied.append("Fixed indentation and syntax")
301
-
302
- elif lang in ["javascript", "node"]:
303
- # Fix common JS issues
304
- # Add missing semicolons
305
  lines = fixed_code.split("\n")
306
  fixed_lines = []
307
  for line in lines:
308
- line = line.rstrip()
309
- if line and not line.endswith((";", "{", "}", ",")):
310
- if not line.strip().startswith("//"):
311
- line += ";"
312
- fixed_lines.append(line)
313
  fixed_code = "\n".join(fixed_lines)
314
-
315
- if fixed_code != code:
316
- fixes_applied.append("Added missing semicolons")
317
-
318
- # Test the fixed code
319
  test_result = self.run_code(fixed_code, lang)
320
-
 
 
 
 
 
 
 
321
  return {
322
  "original_code": code,
323
  "fixed_code": fixed_code,
324
  "fixes_applied": fixes_applied,
325
  "test_result": test_result,
326
- "success": test_result.get("returncode") == 0 if "returncode" in test_result else "error" not in test_result
327
  }
328
-
329
  # ---- Code Generation ----
330
  def generate_code(self, request: str, lang: str = "python") -> Dict[str, Any]:
331
  """Generate code from natural language request"""
@@ -361,7 +444,7 @@ class ChatBot:
361
  "bye": ["Goodbye!", "See you!", "Bye!"],
362
  "default": ["I'm not sure I understand.", "Can you rephrase?"]
363
  }
364
-
365
  def respond(self, message):
366
  message = message.lower()
367
  for key in self.responses:
@@ -400,21 +483,19 @@ print(bot.respond("hello"))
400
  '''
401
  }
402
  }
403
-
404
- # Simple keyword matching for templates
405
  request_lower = request.lower()
406
  generated_code = ""
407
  template_used = None
408
-
409
  if lang in templates:
410
  for key, template in templates[lang].items():
411
  if key in request_lower or any(word in request_lower for word in key.split("_")):
412
  generated_code = template
413
  template_used = key
414
  break
415
-
416
  if not generated_code:
417
- # Generate basic structure
418
  if lang == "python":
419
  generated_code = f'''# Generated code for: {request}
420
 
@@ -437,7 +518,9 @@ if __name__ == "__main__":
437
  </body>
438
  </html>
439
  '''
440
-
 
 
441
  return {
442
  "request": request,
443
  "language": lang,
@@ -448,6 +531,8 @@ if __name__ == "__main__":
448
 
449
  # ---- Interactive shell (terminal) ----
450
  def open_shell(self, cmd: str = "bash"):
 
 
451
  proc = subprocess.Popen(
452
  cmd,
453
  shell=True,
@@ -457,8 +542,7 @@ if __name__ == "__main__":
457
  stderr=subprocess.STDOUT,
458
  text=True,
459
  bufsize=1,
460
- universal_newlines=True,
461
- executable="powershell.exe" if os.name == "nt" else "/bin/bash",
462
  )
463
  return proc
464
 
@@ -468,12 +552,11 @@ if __name__ == "__main__":
468
  return {"error": "Preview already running.", "port": self._preview_port}
469
 
470
  detected = lang.lower()
471
-
472
  # Create index.html if it doesn't exist for HTML previews
473
- if detected in ["html", "css", "javascript"]:
474
  index_path = os.path.join(self.base_dir, "index.html")
475
  if not os.path.exists(index_path):
476
- # Create a default index.html
477
  default_html = """<!DOCTYPE html>
478
  <html lang="en">
479
  <head>
@@ -490,14 +573,13 @@ if __name__ == "__main__":
490
  <p>Your code preview will appear here.</p>
491
  </body>
492
  </html>"""
493
- with open(index_path, 'w') as f:
494
  f.write(default_html)
495
-
496
  cmd = f"{sys.executable} -m http.server {port}"
497
- elif detected in ["node", "javascript"]:
498
- cmd = f"node server.js --port {port}" if os.path.exists(os.path.join(self.base_dir, "server.js")) else f"node app.js --port {port}"
499
- elif detected in ["python"]:
500
- if any(x in os.listdir(self.base_dir) for x in ["main.py", "app.py"]):
501
  cmd = f"uvicorn app:app --host 0.0.0.0 --port {port} --reload"
502
  else:
503
  cmd = f"{sys.executable} -m http.server {port}"
@@ -507,23 +589,28 @@ if __name__ == "__main__":
507
  with self._preview_lock:
508
  self._preview_log.clear()
509
  self._preview_port = port
510
- self._preview_proc = subprocess.Popen(
511
- shlex.split(cmd),
512
- cwd=self.base_dir,
513
- stdout=subprocess.PIPE,
514
- stderr=subprocess.STDOUT,
515
- stdin=subprocess.DEVNULL,
516
- text=True,
517
- )
 
 
 
 
 
 
518
  threading.Thread(target=self._stream_preview_output, daemon=True).start()
519
-
520
- # Wait a moment for server to start
521
  time.sleep(1)
522
-
523
- # Return comprehensive preview info
524
  return {
525
- "status": "started",
526
- "cmd": cmd,
527
  "port": port,
528
  "preview_url": f"http://localhost:{port}",
529
  "iframe_html": f'<iframe src="http://localhost:{port}" width="100%" height="600px" frameborder="0" sandbox="allow-scripts allow-same-origin"></iframe>',
@@ -533,16 +620,18 @@ if __name__ == "__main__":
533
 
534
  def _stream_preview_output(self):
535
  proc = self._preview_proc
536
- if not proc:
537
  return
538
- for line in proc.stdout:
 
 
 
 
 
 
539
  with self._preview_lock:
540
- self._preview_log.append(line.rstrip())
541
- if len(self._preview_log) > 2000:
542
- self._preview_log = self._preview_log[-1000:]
543
- with self._preview_lock:
544
- self._preview_proc = None
545
- self._preview_port = None
546
 
547
  def stop_preview(self) -> Dict[str, Any]:
548
  with self._preview_lock:
@@ -564,7 +653,7 @@ if __name__ == "__main__":
564
  return {
565
  "running": self._preview_proc is not None,
566
  "port": self._preview_port,
567
- "log_tail": self._preview_log[-200:],
568
  }
569
 
570
  # ---- SQLite sandbox ----
@@ -572,7 +661,7 @@ if __name__ == "__main__":
572
  try:
573
  conn = sqlite3.connect(self.db_path)
574
  cur = conn.cursor()
575
- cur.execute(query)
576
  if query.strip().lower().startswith("select"):
577
  rows = cur.fetchall()
578
  result = {"rows": rows, "count": len(rows)}
@@ -636,10 +725,15 @@ if __name__ == "__main__":
636
  except Exception as e:
637
  return {"error": str(e)}
638
 
639
- def cleanup(self):
640
  try:
641
  shutil.rmtree(self.base_dir, ignore_errors=True)
642
  os.makedirs(self.base_dir, exist_ok=True)
 
 
 
 
 
643
  return {"status": "cleaned"}
644
  except Exception as e:
645
- return {"error": str(e)}
 
1
+ ```python
2
  # coder.py — offline multi-language sandbox runner and preview manager
3
+ # Corrected and cleaned-up version
4
+ import os
5
+ import sys
6
+ import subprocess
7
+ import tempfile
8
+ import signal
9
+ import platform
10
+ import traceback
11
+ import threading
12
+ import shlex
13
+ import time
14
+ import sqlite3
15
+ import shutil
16
  from typing import Optional, Dict, Any
17
+ import psutil
18
 
19
  class Coder:
20
+ def __init__(self, base_dir: str = "sandbox"):
21
  self.base_dir = base_dir
22
+ os.makedirs(self.base_dir, exist_ok=True)
23
+ # initialize DB path inside base_dir
24
+ self.db_path = os.path.join(self.base_dir, "sandbox.db")
25
  self.reset_sandbox()
26
+ self._preview_proc: Optional[subprocess.Popen] = None
27
+ self._preview_log: list[str] = []
28
  self._preview_lock = threading.Lock()
29
+ self._preview_port: Optional[int] = None
30
 
31
  # ---- sandbox management ----
32
  def reset_sandbox(self):
33
+ # Wipe and recreate sandbox, ensure DB file is created inside base_dir
34
  if os.path.exists(self.base_dir):
35
  shutil.rmtree(self.base_dir, ignore_errors=True)
36
  os.makedirs(self.base_dir, exist_ok=True)
37
+ self.db_path = os.path.join(self.base_dir, "sandbox.db")
38
  conn = sqlite3.connect(self.db_path)
39
  conn.execute("CREATE TABLE IF NOT EXISTS data(id INTEGER PRIMARY KEY, key TEXT, value TEXT)")
40
  conn.commit()
41
  conn.close()
42
 
43
+ def _sandbox_path(self, filename: str) -> str:
44
  return os.path.join(self.base_dir, filename)
45
 
46
  # ---- core code runner ----
47
+ def run_code(self, code: str, lang: str = "python", timeout: int = 15, cpu_limit: float = 50.0, mem_limit_mb: int = 200) -> Dict[str, Any]:
48
+ """
49
+ Execute code in sandbox with resource limits.
50
+ - cpu_limit: CPU percent limit (not enforced except via RLIMIT_CPU)
51
+ - mem_limit_mb: Memory limit in MB (attempted via RLIMIT_AS on Unix)
52
+ Returns a dictionary with stdout, stderr, returncode, and path (temp file used).
53
+ """
54
  lang = lang.lower().strip()
55
+
56
+ # map languages to their runner/compiler commands (base)
57
  exec_map = {
58
  "python": [sys.executable],
59
  "javascript": ["node"],
60
  "node": ["node"],
61
+ "java": ["javac"], # compile step uses javac; runtime uses java
62
  "c": ["gcc"],
63
  "c++": ["g++"],
64
  "php": ["php"],
65
  "ruby": ["ruby"],
66
+ "go": ["go"],
67
  "rust": ["rustc"],
68
  "bash": ["bash"],
69
  "typescript": ["ts-node"],
70
  "kotlin": ["kotlinc"],
71
  "lua": ["lua"],
72
+ "sql": ["sqlite3"],
73
  "html": ["cat"],
74
  "css": ["cat"],
75
  }
 
78
  return {"error": f"Language '{lang}' not supported."}
79
 
80
  try:
81
+ suffix_map = {
82
  "python": ".py", "javascript": ".js", "node": ".js", "java": ".java",
83
  "c": ".c", "c++": ".cpp", "php": ".php", "ruby": ".rb", "go": ".go",
84
  "rust": ".rs", "bash": ".sh", "typescript": ".ts", "kotlin": ".kt",
85
  "lua": ".lua", "sql": ".sql", "html": ".html", "css": ".css"
86
+ }
87
+ suffix = suffix_map.get(lang, ".txt")
88
 
89
+ # write code to a file inside the sandbox
90
  with tempfile.NamedTemporaryFile(mode="w", suffix=suffix, dir=self.base_dir, delete=False) as tmp:
91
  tmp.write(code)
92
  path = tmp.name
93
 
94
+ # decide run command
95
+ run_cmd: list[str] = []
96
+ # SQL: use sqlite3 <db> < <file> => we'll run sqlite3 with the file as input
97
+ if lang == "sql":
98
+ run_cmd = ["sqlite3", self.db_path]
99
+ # we'll provide the SQL file to stdin when running
100
+ stdin_for_run = open(path, "r", encoding="utf-8")
101
+ elif lang in ("html", "css"):
 
 
 
102
  run_cmd = ["type" if os.name == "nt" else "cat", path]
103
+ stdin_for_run = None
104
+ elif lang in ("python", "php", "ruby", "bash", "lua", "typescript"):
105
+ # these accept a file to run
106
  run_cmd = exec_map[lang] + [path]
107
+ stdin_for_run = None
108
+ elif lang in ("javascript", "node"):
109
+ run_cmd = exec_map[lang] + [path]
110
+ stdin_for_run = None
111
+ elif lang == "go":
112
+ # Use 'go run file.go' (no separate compile artifact)
113
+ run_cmd = ["go", "run", path]
114
+ stdin_for_run = None
115
+ elif lang == "java":
116
+ # compile with javac then run with java -cp base_dir ClassName
117
+ compile_result = subprocess.run(["javac", path], cwd=self.base_dir, capture_output=True, text=True, timeout=timeout)
118
+ if compile_result.returncode != 0:
119
+ return {"stdout": compile_result.stdout, "stderr": compile_result.stderr, "returncode": compile_result.returncode, "compile_error": True, "path": path}
120
+ class_name = os.path.splitext(os.path.basename(path))[0]
121
+ run_cmd = ["java", "-cp", self.base_dir, class_name]
122
+ stdin_for_run = None
123
+ elif lang in ("c", "c++", "rust"):
124
+ out_exe = path + ".out"
125
+ if lang == "c":
126
+ compiler = "gcc"
127
+ compile_cmd = [compiler, path, "-o", out_exe]
128
+ elif lang == "c++":
129
+ compiler = "g++"
130
+ compile_cmd = [compiler, path, "-o", out_exe]
131
+ else: # rust
132
+ compiler = "rustc"
133
+ compile_cmd = [compiler, path, "-o", out_exe]
134
+ compile_result = subprocess.run(compile_cmd, cwd=self.base_dir, capture_output=True, text=True, timeout=timeout)
135
+ if compile_result.returncode != 0:
136
+ return {"stdout": compile_result.stdout, "stderr": compile_result.stderr, "returncode": compile_result.returncode, "compile_error": True, "path": path}
137
+ run_cmd = [out_exe]
138
+ stdin_for_run = None
139
+ else:
140
+ # Fallback
141
+ run_cmd = exec_map.get(lang, [sys.executable]) + [path]
142
+ stdin_for_run = None
143
+
144
+ # ---- Resource limiting ----
145
+ preexec_fn = None
146
+ if platform.system() != "Windows":
147
+ try:
148
+ import resource
149
+ def set_limits():
150
+ # CPU time limit (seconds)
151
+ resource.setrlimit(resource.RLIMIT_CPU, (timeout, timeout+1))
152
+ # Virtual memory limit
153
+ mem_bytes = mem_limit_mb * 1024 * 1024
154
+ resource.setrlimit(resource.RLIMIT_AS, (mem_bytes, mem_bytes))
155
+ preexec_fn = set_limits
156
+ except Exception:
157
+ # resource module may not be available on some platforms
158
+ preexec_fn = None
159
+
160
+ # start process
161
+ proc = subprocess.Popen(
162
+ run_cmd,
163
+ cwd=self.base_dir,
164
+ stdout=subprocess.PIPE,
165
+ stderr=subprocess.PIPE,
166
+ stdin=stdin_for_run or subprocess.DEVNULL,
167
+ text=True,
168
+ preexec_fn=preexec_fn
169
  )
170
 
171
+ start_time = time.time()
172
+ # Poll loop with timeouts and Windows memory enforcement
173
+ while proc.poll() is None:
174
+ time.sleep(0.1)
175
+ # Windows: enforce memory limit via psutil
176
+ if platform.system() == "Windows":
177
+ try:
178
+ p = psutil.Process(proc.pid)
179
+ if p.memory_info().rss > mem_limit_mb * 1024 * 1024:
180
+ p.kill()
181
+ return {"error": "Memory limit exceeded."}
182
+ except Exception:
183
+ pass
184
+ if time.time() - start_time > timeout:
185
+ proc.kill()
186
+ return {"error": "Execution timeout."}
187
+
188
+ stdout, stderr = proc.communicate()
189
+ # close any opened stdin file for sqlite
190
+ if lang == "sql" and 'stdin_for_run' in locals() and stdin_for_run:
191
+ try:
192
+ stdin_for_run.close()
193
+ except Exception:
194
+ pass
195
+
196
+ return {"stdout": stdout.strip(), "stderr": stderr.strip(), "returncode": proc.returncode, "path": path}
197
 
 
 
198
  except Exception:
199
  return {"error": traceback.format_exc()}
200
 
 
203
  """Deep code analysis with comprehensive error detection"""
204
  import re
205
  import ast
206
+
207
  errors = []
208
  warnings = []
209
  solutions = []
210
+ result = {}
211
+
212
  # Static analysis before execution
213
  if lang == "python":
214
  # Check for syntax errors
 
223
  "text": e.text.strip() if e.text else ""
224
  })
225
  solutions.append(f"Fix syntax error at line {e.lineno}, column {e.offset}: {e.msg}")
226
+
227
+ # Basic line checks
228
  lines = code.split("\n")
229
  for i, line in enumerate(lines, 1):
230
  line_stripped = line.strip()
231
+
232
+ # Mixed tabs and spaces
233
+ if line and '\t' in line and ' ' in line:
234
+ warnings.append(f"Line {i}: Mixed tabs and spaces")
235
+
236
+ # Missing colons for common constructs
237
+ if re.match(r'^\s*(if|elif|else|for|while|def|class|try|except|finally|with)\b.*[^:]\s*$', line):
 
238
  errors.append({
239
  "type": "MissingColon",
240
  "line": i,
 
242
  "text": line_stripped
243
  })
244
  solutions.append(f"Add ':' at end of line {i}")
245
+
246
+ # Simple typo checks
247
+ if 'pirnt' in line:
248
+ warnings.append(f"Line {i}: Possible typo 'pirnt' (should be 'print')")
249
+ if 'retrun' in line:
250
+ warnings.append(f"Line {i}: Possible typo 'retrun' (should be 'return')")
251
+ if 'improt' in line:
252
+ warnings.append(f"Line {i}: Possible typo 'improt' (should be 'import')")
253
+
254
+ # Unclosed brackets in the line (very basic)
255
+ if line.count('(') > line.count(')'):
 
 
 
 
 
 
256
  warnings.append(f"Line {i}: Unclosed parenthesis '('")
257
+ if line.count('[') > line.count(']'):
258
  warnings.append(f"Line {i}: Unclosed bracket '['")
259
+ if line.count('{') > line.count('}'):
260
  warnings.append(f"Line {i}: Unclosed brace '{{'")
261
+
262
+ # Bare except check
263
+ if re.match(r'^\s*except\s*:\s*$', line):
264
  warnings.append(f"Line {i}: Bare except clause (specify exception type)")
265
+
266
+ # Mutable default args
267
+ if re.search(r'def\s+\w+\(.*=\s*\[\s*\]', line):
268
  warnings.append(f"Line {i}: Mutable default argument (use None instead)")
269
+
270
+ # Comparison with None using == or !=
271
  if '== None' in line or '!= None' in line:
272
  warnings.append(f"Line {i}: Use 'is None' or 'is not None' instead of '==' or '!='")
273
+
274
+ elif lang in ("javascript", "node"):
275
  lines = code.split("\n")
276
  for i, line in enumerate(lines, 1):
277
  line_stripped = line.strip()
 
 
278
  if line_stripped and not line_stripped.endswith((';', '{', '}', ',')) and not line_stripped.startswith('//'):
279
+ warnings.append(f"Line {i}: Possible missing semicolon")
 
 
280
  if line_stripped.startswith('var '):
281
  warnings.append(f"Line {i}: Use 'let' or 'const' instead of 'var'")
282
+ if '==' in line and '===' not in line:
283
+ warnings.append(f"Line {i}: Consider using '===' for strict equality")
284
+
285
+ # Execute code and capture runtime errors (only if no critical static syntax error found)
286
+ run_result = None
287
+ if not any(e.get("type") == "SyntaxError" for e in errors):
288
+ run_result = self.run_code(code, lang)
289
+
290
+ if run_result:
291
+ result.update(run_result)
292
+ stderr = run_result.get("stderr", "")
293
+ if stderr:
294
+ for line in stderr.splitlines():
295
+ if line.strip():
296
+ match = re.search(r'line\s+(\d+)', line, re.IGNORECASE)
297
+ line_num = match.group(1) if match else "unknown"
298
+ error_type = "RuntimeError"
299
+ if "NameError" in line:
300
+ error_type = "NameError"
301
+ solutions.append(f"Variable not defined at line {line_num}. Check spelling or define the variable.")
302
+ elif "TypeError" in line:
303
+ error_type = "TypeError"
304
+ solutions.append(f"Type mismatch at line {line_num}. Check data types.")
305
+ elif "ValueError" in line:
306
+ error_type = "ValueError"
307
+ solutions.append(f"Invalid value at line {line_num}. Check input values.")
308
+ elif "IndexError" in line:
309
+ error_type = "IndexError"
310
+ solutions.append(f"Index out of range at line {line_num}. Check array/list bounds.")
311
+ elif "KeyError" in line:
312
+ error_type = "KeyError"
313
+ solutions.append(f"Key not found at line {line_num}. Check dictionary keys.")
314
+ elif "AttributeError" in line:
315
+ error_type = "AttributeError"
316
+ solutions.append(f"Attribute not found at line {line_num}. Check object attributes.")
317
+ elif "ImportError" in line or "ModuleNotFoundError" in line:
318
+ error_type = "ImportError"
319
+ solutions.append(f"Module not found at line {line_num}. Install required package.")
320
+
321
+ errors.append({
322
+ "type": error_type,
323
+ "line": line_num,
324
+ "message": line.strip()
325
+ })
326
+
327
+ elif "error" in (run_result or {}):
 
328
  errors.append({
329
  "type": "ExecutionError",
330
+ "message": run_result.get("error")
331
  })
332
  solutions.append("Check code syntax and dependencies")
333
+
334
  # Code quality checks
335
  if not errors:
336
  code_lines = [l for l in code.split("\n") if l.strip() and not l.strip().startswith('#')]
 
 
337
  if len(code_lines) > 100:
338
  warnings.append("Code is quite long. Consider breaking into smaller functions.")
 
 
339
  print_count = sum(1 for l in code_lines if 'print(' in l)
340
  if print_count > 5:
341
  warnings.append(f"Found {print_count} print statements. Consider using logging instead.")
342
+
343
  # Build comprehensive result
344
  result["errors"] = errors
345
  result["warnings"] = warnings
346
  result["solutions"] = solutions
347
  result["error_count"] = len(errors)
348
  result["warning_count"] = len(warnings)
349
+
350
  if errors:
351
+ result["analysis"] = f"❌ Found {len(errors)} error(s) and {len(warnings)} warning(s). Code may not execute correctly."
352
  result["severity"] = "critical"
353
  elif warnings:
354
  result["analysis"] = f"⚠️ Found {len(warnings)} warning(s). Code may have issues or bad practices."
 
356
  else:
357
  result["analysis"] = "✅ No errors or warnings detected. Code looks good!"
358
  result["severity"] = "success"
359
+
360
  return result
361
+
362
  # ---- Code Fix Feature ----
363
  def fix_code(self, code: str, lang: str = "python") -> Dict[str, Any]:
364
+ """Automatically fix common code issues (basic, heuristic fixes)."""
365
  import re
366
+
367
  fixed_code = code
368
  fixes_applied = []
369
+
370
  if lang == "python":
371
+ # Remove trailing whitespace on lines
 
 
 
 
 
372
  lines = code.split("\n")
373
+ fixed_lines = [ln.rstrip() for ln in lines]
 
 
 
374
  fixed_code = "\n".join(fixed_lines)
375
+
376
+ # Add missing colons for common constructs (heuristic)
377
+ new_code = re.sub(r'(^\s*(if|while|for|def|class)\b[^\n:]*)(\n)', lambda m: (m.group(1) + ':' + m.group(3)) if not m.group(1).rstrip().endswith(':') else m.group(0), fixed_code, flags=re.M)
378
+ if new_code != fixed_code:
379
+ fixed_code = new_code
380
+ fixes_applied.append("Added missing colons in control/function/class statements")
381
+
382
+ elif lang in ("javascript", "node"):
383
+ # Add missing semicolons in a very naive way
 
384
  lines = fixed_code.split("\n")
385
  fixed_lines = []
386
  for line in lines:
387
+ stripped = line.rstrip()
388
+ if stripped and not stripped.endswith((';', '{', '}', ',')) and not stripped.strip().startswith('//'):
389
+ stripped += ';'
390
+ fixed_lines.append(stripped)
 
391
  fixed_code = "\n".join(fixed_lines)
392
+ fixes_applied.append("Added missing semicolons (heuristic)")
393
+
394
+ # Test the fixed code (best-effort)
 
 
395
  test_result = self.run_code(fixed_code, lang)
396
+ success = False
397
+ if "returncode" in test_result:
398
+ success = test_result.get("returncode") == 0
399
+ elif "error" in test_result:
400
+ success = False
401
+ else:
402
+ success = True
403
+
404
  return {
405
  "original_code": code,
406
  "fixed_code": fixed_code,
407
  "fixes_applied": fixes_applied,
408
  "test_result": test_result,
409
+ "success": success
410
  }
411
+
412
  # ---- Code Generation ----
413
  def generate_code(self, request: str, lang: str = "python") -> Dict[str, Any]:
414
  """Generate code from natural language request"""
 
444
  "bye": ["Goodbye!", "See you!", "Bye!"],
445
  "default": ["I'm not sure I understand.", "Can you rephrase?"]
446
  }
447
+
448
  def respond(self, message):
449
  message = message.lower()
450
  for key in self.responses:
 
483
  '''
484
  }
485
  }
486
+
 
487
  request_lower = request.lower()
488
  generated_code = ""
489
  template_used = None
490
+
491
  if lang in templates:
492
  for key, template in templates[lang].items():
493
  if key in request_lower or any(word in request_lower for word in key.split("_")):
494
  generated_code = template
495
  template_used = key
496
  break
497
+
498
  if not generated_code:
 
499
  if lang == "python":
500
  generated_code = f'''# Generated code for: {request}
501
 
 
518
  </body>
519
  </html>
520
  '''
521
+ else:
522
+ generated_code = f"# Generated code for: {request}\n# Language: {lang}\n"
523
+
524
  return {
525
  "request": request,
526
  "language": lang,
 
531
 
532
  # ---- Interactive shell (terminal) ----
533
  def open_shell(self, cmd: str = "bash"):
534
+ # Return a subprocess.Popen for an interactive shell in the sandbox dir
535
+ executable = "powershell.exe" if os.name == "nt" else "/bin/bash"
536
  proc = subprocess.Popen(
537
  cmd,
538
  shell=True,
 
542
  stderr=subprocess.STDOUT,
543
  text=True,
544
  bufsize=1,
545
+ executable=executable,
 
546
  )
547
  return proc
548
 
 
552
  return {"error": "Preview already running.", "port": self._preview_port}
553
 
554
  detected = lang.lower()
555
+
556
  # Create index.html if it doesn't exist for HTML previews
557
+ if detected in ("html", "css", "javascript", "node"):
558
  index_path = os.path.join(self.base_dir, "index.html")
559
  if not os.path.exists(index_path):
 
560
  default_html = """<!DOCTYPE html>
561
  <html lang="en">
562
  <head>
 
573
  <p>Your code preview will appear here.</p>
574
  </body>
575
  </html>"""
576
+ with open(index_path, 'w', encoding='utf-8') as f:
577
  f.write(default_html)
578
+
579
  cmd = f"{sys.executable} -m http.server {port}"
580
+ elif detected == "python":
581
+ if any(x in os.listdir(self.base_dir) for x in ("main.py", "app.py")):
582
+ # if app.py/main.py exists, try to run a uvicorn server (best-effort)
 
583
  cmd = f"uvicorn app:app --host 0.0.0.0 --port {port} --reload"
584
  else:
585
  cmd = f"{sys.executable} -m http.server {port}"
 
589
  with self._preview_lock:
590
  self._preview_log.clear()
591
  self._preview_port = port
592
+ try:
593
+ self._preview_proc = subprocess.Popen(
594
+ shlex.split(cmd),
595
+ cwd=self.base_dir,
596
+ stdout=subprocess.PIPE,
597
+ stderr=subprocess.STDOUT,
598
+ stdin=subprocess.DEVNULL,
599
+ text=True,
600
+ )
601
+ except Exception as e:
602
+ self._preview_proc = None
603
+ self._preview_port = None
604
+ return {"error": str(e), "cmd": cmd}
605
+
606
  threading.Thread(target=self._stream_preview_output, daemon=True).start()
607
+
608
+ # Give a short moment for the server to (attempt to) start
609
  time.sleep(1)
610
+
 
611
  return {
612
+ "status": "started",
613
+ "cmd": cmd,
614
  "port": port,
615
  "preview_url": f"http://localhost:{port}",
616
  "iframe_html": f'<iframe src="http://localhost:{port}" width="100%" height="600px" frameborder="0" sandbox="allow-scripts allow-same-origin"></iframe>',
 
620
 
621
  def _stream_preview_output(self):
622
  proc = self._preview_proc
623
+ if not proc or not proc.stdout:
624
  return
625
+ try:
626
+ for line in proc.stdout:
627
+ with self._preview_lock:
628
+ self._preview_log.append(line.rstrip())
629
+ if len(self._preview_log) > 2000:
630
+ self._preview_log = self._preview_log[-1000:]
631
+ finally:
632
  with self._preview_lock:
633
+ self._preview_proc = None
634
+ self._preview_port = None
 
 
 
 
635
 
636
  def stop_preview(self) -> Dict[str, Any]:
637
  with self._preview_lock:
 
653
  return {
654
  "running": self._preview_proc is not None,
655
  "port": self._preview_port,
656
+ "log_tail": list(self._preview_log[-200:]),
657
  }
658
 
659
  # ---- SQLite sandbox ----
 
661
  try:
662
  conn = sqlite3.connect(self.db_path)
663
  cur = conn.cursor()
664
+ cur.executescript(query) if (";" in query and not query.strip().lower().startswith("select")) else cur.execute(query)
665
  if query.strip().lower().startswith("select"):
666
  rows = cur.fetchall()
667
  result = {"rows": rows, "count": len(rows)}
 
725
  except Exception as e:
726
  return {"error": str(e)}
727
 
728
+ def cleanup(self) -> Dict[str, Any]:
729
  try:
730
  shutil.rmtree(self.base_dir, ignore_errors=True)
731
  os.makedirs(self.base_dir, exist_ok=True)
732
+ # recreate db
733
+ conn = sqlite3.connect(self.db_path)
734
+ conn.execute("CREATE TABLE IF NOT EXISTS data(id INTEGER PRIMARY KEY, key TEXT, value TEXT)")
735
+ conn.commit()
736
+ conn.close()
737
  return {"status": "cleaned"}
738
  except Exception as e:
739
+ return {"error": str(e)}