feat(代码运行): 添加多语言代码执行引擎

- 实现 sandbox 引擎支持 JavaScript/TypeScript 执行
- 实现 pyodide 引擎支持 Python 浏览器端执行
- 实现 remote 引擎支持 Java/Go/C/C++/Rust 等远程执行
- 添加语言配置和入口点检测逻辑
- 支持执行状态回调和加载进度显示
This commit is contained in:
gaoziman 2025-12-21 03:20:16 +08:00
parent 2e5120dc72
commit 192cd175da
5 changed files with 1111 additions and 0 deletions

View File

@ -0,0 +1,265 @@
/**
* Python Pyodide
* 使 Pyodide (Python WebAssembly) Python
*/
import type { IExecutionEngine, ExecutionResult } from '../types';
// Pyodide 类型定义
interface PyodideInterface {
runPython(code: string): unknown;
runPythonAsync(code: string): Promise<unknown>;
loadPackage(packages: string | string[]): Promise<void>;
loadPackagesFromImports(code: string): Promise<void>;
globals: Map<string, unknown>;
}
// 加载状态回调
export interface PyodideLoadingCallback {
onLoadingStart?: () => void;
onLoadingProgress?: (message: string, progress?: number) => void;
onLoadingComplete?: () => void;
onLoadingError?: (error: string) => void;
}
// 执行超时时间(毫秒)
const EXECUTION_TIMEOUT = 30000;
// 最大输出长度
const MAX_OUTPUT_LENGTH = 50000;
// Pyodide CDN URL
const PYODIDE_CDN = 'https://cdn.jsdelivr.net/pyodide/v0.24.1/full/';
class PyodideEngine implements IExecutionEngine {
private pyodide: PyodideInterface | null = null;
private loading = false;
private loadPromise: Promise<PyodideInterface> | null = null;
private loadingCallbacks: PyodideLoadingCallback | null = null;
supports(language: string): boolean {
const lang = language.toLowerCase();
return ['python', 'py'].includes(lang);
}
// 设置加载回调
setLoadingCallbacks(callbacks: PyodideLoadingCallback | null): void {
this.loadingCallbacks = callbacks;
}
// 检查是否已加载
isLoaded(): boolean {
return this.pyodide !== null;
}
// 检查是否正在加载
isLoading(): boolean {
return this.loading;
}
// 预加载 Pyodide可选提前加载以减少首次执行延迟
async preload(): Promise<void> {
if (!this.pyodide && !this.loading) {
await this.loadPyodide();
}
}
async execute(code: string, language: string): Promise<ExecutionResult> {
const startTime = performance.now();
try {
// 确保 Pyodide 已加载
if (!this.pyodide) {
await this.loadPyodide();
}
if (!this.pyodide) {
throw new Error('Pyodide 加载失败');
}
// 尝试加载代码中 import 的包
try {
await this.pyodide.loadPackagesFromImports(code);
} catch {
// 忽略包加载错误,继续执行
}
// 执行代码并捕获输出
const output = await this.executeWithTimeout(code);
const executionTime = Math.round(performance.now() - startTime);
return {
success: true,
output: output.slice(0, MAX_OUTPUT_LENGTH),
executionTime,
engine: 'pyodide',
};
} catch (error) {
const executionTime = Math.round(performance.now() - startTime);
return {
success: false,
output: '',
error: this.formatPythonError(error),
executionTime,
engine: 'pyodide',
};
}
}
private async loadPyodide(): Promise<PyodideInterface> {
if (this.pyodide) {
return this.pyodide;
}
if (this.loadPromise) {
return this.loadPromise;
}
this.loading = true;
this.loadingCallbacks?.onLoadingStart?.();
this.loadingCallbacks?.onLoadingProgress?.('正在加载 Python 运行时...', 0);
this.loadPromise = new Promise<PyodideInterface>(async (resolve, reject) => {
try {
// 动态加载 Pyodide 脚本
if (typeof window !== 'undefined' && !(window as unknown as Record<string, unknown>).loadPyodide) {
this.loadingCallbacks?.onLoadingProgress?.('正在下载 Pyodide...', 20);
await this.loadScript(`${PYODIDE_CDN}pyodide.js`);
}
this.loadingCallbacks?.onLoadingProgress?.('正在初始化 Python 环境...', 50);
// 初始化 Pyodide
const loadPyodide = (window as unknown as { loadPyodide: (config: { indexURL: string }) => Promise<PyodideInterface> }).loadPyodide;
const pyodide = await loadPyodide({
indexURL: PYODIDE_CDN,
});
this.loadingCallbacks?.onLoadingProgress?.('Python 环境准备就绪', 100);
// 设置标准输出重定向
await pyodide.runPythonAsync(`
import sys
from io import StringIO
class OutputCapture:
def __init__(self):
self.outputs = []
def write(self, text):
if text and text.strip():
self.outputs.append(str(text))
def flush(self):
pass
def get_output(self):
return ''.join(self.outputs)
def clear(self):
self.outputs = []
__output_capture__ = OutputCapture()
sys.stdout = __output_capture__
sys.stderr = __output_capture__
`);
this.pyodide = pyodide;
this.loading = false;
this.loadingCallbacks?.onLoadingComplete?.();
resolve(pyodide);
} catch (error) {
this.loading = false;
this.loadPromise = null;
const errorMsg = error instanceof Error ? error.message : String(error);
this.loadingCallbacks?.onLoadingError?.(errorMsg);
reject(new Error(`Pyodide 加载失败: ${errorMsg}`));
}
});
return this.loadPromise;
}
private loadScript(src: string): Promise<void> {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
document.head.appendChild(script);
});
}
private async executeWithTimeout(code: string): Promise<string> {
return new Promise(async (resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error(`执行超时(${EXECUTION_TIMEOUT / 1000}秒)`));
}, EXECUTION_TIMEOUT);
try {
if (!this.pyodide) {
throw new Error('Pyodide 未加载');
}
// 清空之前的输出
await this.pyodide.runPythonAsync('__output_capture__.clear()');
// 执行用户代码
const result = await this.pyodide.runPythonAsync(code);
// 获取捕获的输出
const capturedOutput = await this.pyodide.runPythonAsync('__output_capture__.get_output()');
clearTimeout(timeoutId);
// 组合输出
let output = String(capturedOutput || '');
// 如果代码有返回值且不是 None添加到输出
if (result !== undefined && result !== null && String(result) !== 'None') {
if (output) {
output += '\n';
}
output += String(result);
}
resolve(output);
} catch (error) {
clearTimeout(timeoutId);
reject(error);
}
});
}
private formatPythonError(error: unknown): string {
if (error instanceof Error) {
let message = error.message;
// 清理 Pyodide 错误信息
message = message
.replace(/PythonError:\s*/g, '')
.replace(/Traceback \(most recent call last\):\s*/g, 'Traceback:\n')
.replace(/File "<exec>", /g, '')
.trim();
return message;
}
return String(error);
}
// 加载额外的 Python 包
async loadPackages(packages: string[]): Promise<void> {
if (!this.pyodide) {
await this.loadPyodide();
}
if (this.pyodide) {
await this.pyodide.loadPackage(packages);
}
}
}
// 导出单例
export const pyodideEngine = new PyodideEngine();

View File

@ -0,0 +1,252 @@
/**
*
* Piston API JavaGoC/C++Rust
*/
import type { IExecutionEngine, ExecutionResult, LanguageConfig } from '../types';
import { RUNNABLE_LANGUAGES } from '../types';
// 最大输出长度
const MAX_OUTPUT_LENGTH = 50000;
// Piston API 响应类型
interface PistonResponse {
language: string;
version: string;
run: {
stdout: string;
stderr: string;
output: string;
code: number;
signal: string | null;
};
compile?: {
stdout: string;
stderr: string;
output: string;
code: number;
signal: string | null;
};
}
class RemoteEngine implements IExecutionEngine {
private abortController: AbortController | null = null;
supports(language: string): boolean {
const config = this.getConfig(language);
return config !== null && config.engine === 'remote';
}
private getConfig(language: string): LanguageConfig | null {
return RUNNABLE_LANGUAGES[language.toLowerCase()] || null;
}
async execute(code: string, language: string): Promise<ExecutionResult> {
const startTime = performance.now();
const config = this.getConfig(language);
if (!config || !config.pistonLanguage) {
return {
success: false,
output: '',
error: `不支持的语言: ${language}`,
executionTime: 0,
engine: 'remote',
};
}
try {
this.abortController = new AbortController();
// 确定文件名
const fileName = this.getFileName(language, config);
// 对于 Java将非 ASCII 字符转换为 Unicode 转义序列
const processedCode = this.preprocessCode(code, language);
// 调用后端代理 API
const response = await fetch('/api/code/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
language: config.pistonLanguage,
version: config.pistonVersion || '*',
files: [
{
name: fileName,
content: processedCode,
},
],
}),
signal: this.abortController.signal,
});
const executionTime = Math.round(performance.now() - startTime);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: '执行失败' }));
return {
success: false,
output: '',
error: errorData.error || `HTTP ${response.status}`,
executionTime,
engine: 'remote',
};
}
const result: PistonResponse = await response.json();
// 处理编译错误
if (result.compile && result.compile.code !== 0) {
return {
success: false,
output: '',
error: this.formatOutput(result.compile.stderr || result.compile.output, '编译错误'),
executionTime,
engine: 'remote',
};
}
// 处理运行结果
const hasError = result.run.code !== 0 || result.run.signal !== null;
const output = this.formatOutput(result.run.stdout || result.run.output);
const errorOutput = result.run.stderr ? this.formatOutput(result.run.stderr) : undefined;
return {
success: !hasError || (output.length > 0 && !errorOutput),
output: output.slice(0, MAX_OUTPUT_LENGTH),
error: hasError ? errorOutput || `退出代码: ${result.run.code}` : undefined,
executionTime,
engine: 'remote',
};
} catch (error) {
const executionTime = Math.round(performance.now() - startTime);
if (error instanceof Error && error.name === 'AbortError') {
return {
success: false,
output: '',
error: '执行已取消',
executionTime,
engine: 'remote',
};
}
return {
success: false,
output: '',
error: error instanceof Error ? error.message : '执行失败',
executionTime,
engine: 'remote',
};
}
}
stop(): void {
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
}
private getFileName(language: string, config: LanguageConfig): string {
const lang = language.toLowerCase();
const ext = config.fileExtension || lang;
// 特殊处理 Java需要类名匹配文件名
if (lang === 'java') {
return 'Main.java';
}
return `main.${ext}`;
}
/**
* ASCII Unicode
* Piston API UTF-8
*/
private preprocessCode(code: string, language: string): string {
const lang = language.toLowerCase();
// 只对 Java 进行处理Java 字符串支持 \uXXXX 转义)
if (lang === 'java') {
return this.escapeNonAsciiForJava(code);
}
return code;
}
/**
* Java ASCII Unicode
* UTF-8
*/
private escapeNonAsciiForJava(code: string): string {
// 首先处理字符串中的非 ASCII 字符
// 处理双引号字符串
let result = code.replace(/"([^"\\]|\\.)*"/g, (match) => {
return this.escapeStringContent(match, '"');
});
// 处理单引号字符char
result = result.replace(/'([^'\\]|\\.)*'/g, (match) => {
return this.escapeStringContent(match, "'");
});
// 注入 UTF-8 输出流设置到 main 方法开头
// 匹配 public static void main 方法
const mainMethodRegex = /(public\s+static\s+void\s+main\s*\([^)]*\)\s*\{)/;
const utf8Setup = `$1
try { System.setOut(new java.io.PrintStream(System.out, true, "UTF-8")); } catch (Exception e) {}`;
result = result.replace(mainMethodRegex, utf8Setup);
return result;
}
/**
* ASCII
*/
private escapeStringContent(str: string, quote: string): string {
const content = str.slice(1, -1); // 移除引号
let escaped = '';
for (let i = 0; i < content.length; i++) {
const char = content[i];
const code = char.charCodeAt(0);
// 处理转义序列(保留原样)
if (char === '\\' && i + 1 < content.length) {
escaped += char + content[i + 1];
i++;
continue;
}
// 非 ASCII 字符转换为 \uXXXX
if (code > 127) {
escaped += '\\u' + code.toString(16).padStart(4, '0');
} else {
escaped += char;
}
}
return quote + escaped + quote;
}
private formatOutput(output: string, prefix?: string): string {
let result = output.trim();
// 移除 ANSI 颜色代码
result = result.replace(/\x1b\[[0-9;]*m/g, '');
if (prefix && result) {
return `${prefix}:\n${result}`;
}
return result;
}
}
// 导出单例
export const remoteEngine = new RemoteEngine();

View File

@ -0,0 +1,224 @@
/**
* JavaScript/TypeScript
* iframe
*/
import type { IExecutionEngine, ExecutionResult } from '../types';
// 执行超时时间(毫秒)
const EXECUTION_TIMEOUT = 10000;
// 最大输出长度
const MAX_OUTPUT_LENGTH = 50000;
export class SandboxEngine implements IExecutionEngine {
private iframe: HTMLIFrameElement | null = null;
private abortController: AbortController | null = null;
supports(language: string): boolean {
const lang = language.toLowerCase();
return ['javascript', 'js', 'typescript', 'ts'].includes(lang);
}
async execute(code: string, language: string): Promise<ExecutionResult> {
const startTime = performance.now();
const lang = language.toLowerCase();
try {
// TypeScript 需要先编译
let executableCode = code;
if (lang === 'typescript' || lang === 'ts') {
executableCode = this.transpileTypeScript(code);
}
// 在沙箱中执行
const output = await this.executeInSandbox(executableCode);
const executionTime = Math.round(performance.now() - startTime);
return {
success: true,
output: output.slice(0, MAX_OUTPUT_LENGTH),
executionTime,
engine: 'sandbox',
};
} catch (error) {
const executionTime = Math.round(performance.now() - startTime);
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
executionTime,
engine: 'sandbox',
};
}
}
stop(): void {
if (this.abortController) {
this.abortController.abort();
}
this.cleanup();
}
private transpileTypeScript(code: string): string {
// 简单的 TypeScript 转换(移除类型注解)
// 实际项目中可以使用 @babel/standalone 或 typescript
return code
// 移除类型注解
.replace(/:\s*\w+(\[\])?(\s*[=,)])/g, '$2')
// 移除接口定义
.replace(/interface\s+\w+\s*\{[^}]*\}/g, '')
// 移除类型别名
.replace(/type\s+\w+\s*=\s*[^;]+;/g, '')
// 移除泛型
.replace(/<[^>]+>/g, '')
// 移除 as 类型断言
.replace(/\s+as\s+\w+/g, '')
// 移除 ! 非空断言
.replace(/!\./g, '.')
// 移除可选链前的类型
.replace(/\?\./g, '?.');
}
private executeInSandbox(code: string): Promise<string> {
return new Promise((resolve, reject) => {
this.abortController = new AbortController();
const outputs: string[] = [];
// 创建隔离的 iframe
this.iframe = document.createElement('iframe');
this.iframe.style.display = 'none';
this.iframe.sandbox.add('allow-scripts');
document.body.appendChild(this.iframe);
const iframeWindow = this.iframe.contentWindow;
if (!iframeWindow) {
this.cleanup();
reject(new Error('无法创建执行沙箱'));
return;
}
// 设置超时
const timeoutId = setTimeout(() => {
this.cleanup();
reject(new Error(`执行超时(${EXECUTION_TIMEOUT / 1000}秒)`));
}, EXECUTION_TIMEOUT);
// 监听消息
const messageHandler = (event: MessageEvent) => {
if (event.source !== iframeWindow) return;
const { type, data } = event.data;
if (type === 'console') {
outputs.push(data);
} else if (type === 'done') {
clearTimeout(timeoutId);
window.removeEventListener('message', messageHandler);
this.cleanup();
resolve(outputs.join('\n'));
} else if (type === 'error') {
clearTimeout(timeoutId);
window.removeEventListener('message', messageHandler);
this.cleanup();
reject(new Error(data));
}
};
window.addEventListener('message', messageHandler);
// 中止处理
this.abortController.signal.addEventListener('abort', () => {
clearTimeout(timeoutId);
window.removeEventListener('message', messageHandler);
this.cleanup();
reject(new Error('执行已取消'));
});
// 在 iframe 中执行代码
const sandboxCode = `
(function() {
const outputs = [];
// 重写 console 方法
const originalConsole = console;
const customConsole = {
log: (...args) => {
const msg = args.map(a => {
if (typeof a === 'object') {
try {
return JSON.stringify(a, null, 2);
} catch {
return String(a);
}
}
return String(a);
}).join(' ');
parent.postMessage({ type: 'console', data: msg }, '*');
},
error: (...args) => customConsole.log('[Error]', ...args),
warn: (...args) => customConsole.log('[Warn]', ...args),
info: (...args) => customConsole.log(...args),
debug: (...args) => customConsole.log('[Debug]', ...args),
table: (data) => customConsole.log(JSON.stringify(data, null, 2)),
clear: () => {},
dir: (obj) => customConsole.log(obj),
time: () => {},
timeEnd: () => {},
group: () => {},
groupEnd: () => {},
};
// 替换全局 console
window.console = customConsole;
// 限制危险 API
delete window.fetch;
delete window.XMLHttpRequest;
delete window.WebSocket;
delete window.localStorage;
delete window.sessionStorage;
delete window.indexedDB;
delete window.open;
delete window.close;
delete window.alert;
delete window.confirm;
delete window.prompt;
try {
// 执行用户代码
const result = eval(${JSON.stringify(code)});
// 如果有返回值且不是 undefined输出它
if (result !== undefined) {
customConsole.log(result);
}
parent.postMessage({ type: 'done' }, '*');
} catch (error) {
parent.postMessage({ type: 'error', data: error.message || String(error) }, '*');
}
})();
`;
// 写入并执行
const iframeDoc = this.iframe.contentDocument;
if (iframeDoc) {
iframeDoc.open();
iframeDoc.write(`<script>${sandboxCode}</script>`);
iframeDoc.close();
}
});
}
private cleanup(): void {
if (this.iframe && this.iframe.parentNode) {
this.iframe.parentNode.removeChild(this.iframe);
}
this.iframe = null;
this.abortController = null;
}
}
// 导出单例
export const sandboxEngine = new SandboxEngine();

View File

@ -0,0 +1,96 @@
/**
*
*
*/
import { sandboxEngine } from './engines/sandbox';
import { pyodideEngine, type PyodideLoadingCallback } from './engines/pyodide';
import { remoteEngine } from './engines/remote';
import type { ExecutionResult, IExecutionEngine, LanguageConfig } from './types';
import { RUNNABLE_LANGUAGES, isRunnableLanguage, getLanguageConfig, isCodeExecutable } from './types';
// 所有引擎
const engines: IExecutionEngine[] = [
sandboxEngine,
pyodideEngine,
remoteEngine,
];
/**
*
*/
export async function executeCode(
code: string,
language: string
): Promise<ExecutionResult> {
const normalizedLang = language.toLowerCase();
// 查找支持该语言的引擎
const engine = engines.find((e) => e.supports(normalizedLang));
if (!engine) {
return {
success: false,
output: '',
error: `不支持的语言: ${language}`,
executionTime: 0,
engine: 'sandbox',
};
}
return engine.execute(code, normalizedLang);
}
/**
*
*/
export function stopExecution(): void {
engines.forEach((engine) => {
if (engine.stop) {
engine.stop();
}
});
}
/**
* Pyodide
*/
export function setPyodideLoadingCallbacks(callbacks: PyodideLoadingCallback | null): void {
pyodideEngine.setLoadingCallbacks(callbacks);
}
/**
* Pyodide
*/
export async function preloadPyodide(): Promise<void> {
await pyodideEngine.preload();
}
/**
* Pyodide
*/
export function isPyodideLoaded(): boolean {
return pyodideEngine.isLoaded();
}
/**
* Pyodide
*/
export function isPyodideLoading(): boolean {
return pyodideEngine.isLoading();
}
// 导出类型和工具函数
export {
isRunnableLanguage,
getLanguageConfig,
isCodeExecutable,
RUNNABLE_LANGUAGES,
};
export type {
ExecutionResult,
LanguageConfig,
IExecutionEngine,
PyodideLoadingCallback,
};

View File

@ -0,0 +1,274 @@
/**
*
*/
// 执行引擎类型
export type EngineType = 'sandbox' | 'pyodide' | 'remote';
// 执行状态
export type ExecutionStatus = 'idle' | 'running' | 'success' | 'error';
// 执行结果
export interface ExecutionResult {
success: boolean;
output: string; // 标准输出
error?: string; // 错误输出
executionTime: number; // 执行时间(ms)
engine: EngineType; // 使用的引擎
}
// 语言配置
export interface LanguageConfig {
engine: EngineType;
label: string;
pistonLanguage?: string; // Piston API 的语言标识
pistonVersion?: string; // Piston API 的版本
fileExtension?: string; // 文件扩展名
}
// 支持的可执行语言
export const RUNNABLE_LANGUAGES: Record<string, LanguageConfig> = {
// 前端沙箱执行
javascript: {
engine: 'sandbox',
label: 'JavaScript',
fileExtension: 'js',
},
js: {
engine: 'sandbox',
label: 'JavaScript',
fileExtension: 'js',
},
typescript: {
engine: 'sandbox',
label: 'TypeScript',
fileExtension: 'ts',
},
ts: {
engine: 'sandbox',
label: 'TypeScript',
fileExtension: 'ts',
},
// Pyodide 执行
python: {
engine: 'pyodide',
label: 'Python',
fileExtension: 'py',
},
py: {
engine: 'pyodide',
label: 'Python',
fileExtension: 'py',
},
// 远程 API 执行
java: {
engine: 'remote',
label: 'Java',
pistonLanguage: 'java',
pistonVersion: '15.0.2',
fileExtension: 'java',
},
go: {
engine: 'remote',
label: 'Go',
pistonLanguage: 'go',
pistonVersion: '1.16.2',
fileExtension: 'go',
},
golang: {
engine: 'remote',
label: 'Go',
pistonLanguage: 'go',
pistonVersion: '1.16.2',
fileExtension: 'go',
},
c: {
engine: 'remote',
label: 'C',
pistonLanguage: 'c',
pistonVersion: '10.2.0',
fileExtension: 'c',
},
cpp: {
engine: 'remote',
label: 'C++',
pistonLanguage: 'c++',
pistonVersion: '10.2.0',
fileExtension: 'cpp',
},
'c++': {
engine: 'remote',
label: 'C++',
pistonLanguage: 'c++',
pistonVersion: '10.2.0',
fileExtension: 'cpp',
},
rust: {
engine: 'remote',
label: 'Rust',
pistonLanguage: 'rust',
pistonVersion: '1.68.2',
fileExtension: 'rs',
},
rs: {
engine: 'remote',
label: 'Rust',
pistonLanguage: 'rust',
pistonVersion: '1.68.2',
fileExtension: 'rs',
},
ruby: {
engine: 'remote',
label: 'Ruby',
pistonLanguage: 'ruby',
pistonVersion: '3.0.1',
fileExtension: 'rb',
},
rb: {
engine: 'remote',
label: 'Ruby',
pistonLanguage: 'ruby',
pistonVersion: '3.0.1',
fileExtension: 'rb',
},
php: {
engine: 'remote',
label: 'PHP',
pistonLanguage: 'php',
pistonVersion: '8.2.3',
fileExtension: 'php',
},
csharp: {
engine: 'remote',
label: 'C#',
pistonLanguage: 'csharp',
pistonVersion: '6.12.0',
fileExtension: 'cs',
},
cs: {
engine: 'remote',
label: 'C#',
pistonLanguage: 'csharp',
pistonVersion: '6.12.0',
fileExtension: 'cs',
},
swift: {
engine: 'remote',
label: 'Swift',
pistonLanguage: 'swift',
pistonVersion: '5.3.3',
fileExtension: 'swift',
},
kotlin: {
engine: 'remote',
label: 'Kotlin',
pistonLanguage: 'kotlin',
pistonVersion: '1.8.20',
fileExtension: 'kt',
},
kt: {
engine: 'remote',
label: 'Kotlin',
pistonLanguage: 'kotlin',
pistonVersion: '1.8.20',
fileExtension: 'kt',
},
};
// 检查语言是否可执行
export function isRunnableLanguage(language: string): boolean {
return language.toLowerCase() in RUNNABLE_LANGUAGES;
}
// 获取语言配置
export function getLanguageConfig(language: string): LanguageConfig | null {
return RUNNABLE_LANGUAGES[language.toLowerCase()] || null;
}
/**
*
*
*/
export function isCodeExecutable(code: string, language: string): boolean {
const lang = language.toLowerCase();
const trimmedCode = code.trim();
// 如果代码为空,不可执行
if (!trimmedCode) {
return false;
}
switch (lang) {
case 'java':
// Java 需要 class 定义和 main 方法
return /\bclass\s+\w+/.test(code) &&
/public\s+static\s+void\s+main\s*\(\s*String\s*\[\s*\]\s*\w*\s*\)/.test(code);
case 'go':
case 'golang':
// Go 需要 package main 和 func main()
return /package\s+main/.test(code) &&
/func\s+main\s*\(\s*\)/.test(code);
case 'c':
// C 需要 main 函数
return /\bint\s+main\s*\(/.test(code) || /\bvoid\s+main\s*\(/.test(code);
case 'cpp':
case 'c++':
// C++ 需要 main 函数
return /\bint\s+main\s*\(/.test(code) || /\bvoid\s+main\s*\(/.test(code);
case 'rust':
case 'rs':
// Rust 需要 fn main()
return /fn\s+main\s*\(\s*\)/.test(code);
case 'csharp':
case 'cs':
// C# 需要 Main 方法或者是顶级语句(简单判断:有 class 或直接有语句)
return /\bclass\s+\w+/.test(code) &&
/\bstatic\s+void\s+Main\s*\(/.test(code) ||
// 顶级语句:没有 class 但有实际代码
(!/\bclass\s+\w+/.test(code) && /\w+\s*[;(]/.test(code));
case 'kotlin':
case 'kt':
// Kotlin 需要 fun main()
return /fun\s+main\s*\(/.test(code);
case 'swift':
// Swift 可以直接执行顶级代码,只要有语句即可
return trimmedCode.length > 0;
case 'javascript':
case 'js':
case 'typescript':
case 'ts':
case 'python':
case 'py':
case 'ruby':
case 'rb':
case 'php':
// 这些语言可以直接执行任何代码
return true;
default:
// 默认检查是否有代码
return trimmedCode.length > 0;
}
}
// 执行引擎接口
export interface IExecutionEngine {
// 检查是否支持该语言
supports(language: string): boolean;
// 执行代码
execute(code: string, language: string): Promise<ExecutionResult>;
// 停止执行(可选)
stop?(): void;
}