@doccident/doccident 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +195 -3
- package/README.md +333 -7
- package/bin/cmd.js +12 -1
- package/dist/doctest.js +189 -7
- package/dist/languages/basic.js +51 -0
- package/dist/languages/c.js +123 -12
- package/dist/languages/cobol.js +8 -3
- package/dist/languages/csharp.js +77 -0
- package/dist/languages/fortran.js +119 -10
- package/dist/languages/go.js +124 -10
- package/dist/languages/java.js +92 -0
- package/dist/languages/javascript.js +31 -4
- package/dist/languages/pascal.js +88 -0
- package/dist/languages/perl.js +36 -0
- package/dist/languages/python.js +20 -3
- package/dist/languages/r.js +37 -0
- package/dist/languages/rust.js +58 -10
- package/dist/languages/shell.js +12 -3
- package/dist/parse-code-snippets-from-markdown.js +102 -3
- package/dist/reporter.js +39 -0
- package/package.json +6 -6
|
@@ -5,36 +5,140 @@ const child_process_1 = require("child_process");
|
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
const os_1 = require("os");
|
|
8
|
-
const fortranHandler = (code, _snippet,
|
|
8
|
+
const fortranHandler = (code, _snippet, config, sandbox, isSharedSandbox) => {
|
|
9
9
|
let success = false;
|
|
10
10
|
let stack = "";
|
|
11
|
-
|
|
12
|
-
// Auto-wrap if 'program' is missing
|
|
11
|
+
const context = sandbox;
|
|
13
12
|
let fortranCode = code;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
// Handle shared state
|
|
14
|
+
if (isSharedSandbox) {
|
|
15
|
+
if (!context._fortranCode) {
|
|
16
|
+
context._fortranCode = "";
|
|
17
|
+
}
|
|
18
|
+
context._fortranCode += code + "\n";
|
|
19
|
+
fortranCode = context._fortranCode;
|
|
20
|
+
}
|
|
21
|
+
let finalSource = "";
|
|
22
|
+
// Parse modules vs program
|
|
23
|
+
// We want to lift modules to the top, before the program block
|
|
24
|
+
if (!isSharedSandbox) {
|
|
25
|
+
// Legacy/Single snippet mode
|
|
26
|
+
if (!fortranCode.toLowerCase().includes('program ')) {
|
|
27
|
+
finalSource = `program main
|
|
28
|
+
${fortranCode}
|
|
29
|
+
end program main`;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
finalSource = fortranCode;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Shared state mode
|
|
37
|
+
// We need to separate modules, subroutines/functions (external), and the main program body.
|
|
38
|
+
// However, standard Fortran requires:
|
|
39
|
+
// MODULES
|
|
40
|
+
// PROGRAM
|
|
41
|
+
//
|
|
42
|
+
// If the user provides snippets that are just code (print *, ...), they belong in PROGRAM.
|
|
43
|
+
// If they provide MODULE ..., it belongs at top level.
|
|
44
|
+
// If they provide SUBROUTINE ... inside a module or program?
|
|
45
|
+
//
|
|
46
|
+
// Strategy:
|
|
47
|
+
// 1. Extract MODULE blocks.
|
|
48
|
+
// 2. Extract SUBROUTINE/FUNCTION blocks that are top-level? Or assume they are contained?
|
|
49
|
+
// Actually, simple subroutines can be outside/after program if using CONTAINS?
|
|
50
|
+
// No, simplest structure is:
|
|
51
|
+
// MODULES...
|
|
52
|
+
// PROGRAM main
|
|
53
|
+
// USE modules...
|
|
54
|
+
// IMPLICIT NONE
|
|
55
|
+
// ... statements ...
|
|
56
|
+
// END PROGRAM main
|
|
57
|
+
// Let's implement a simple parser that looks for MODULE ... END MODULE blocks.
|
|
58
|
+
const lines = fortranCode.split('\n');
|
|
59
|
+
const modules = [];
|
|
60
|
+
const programBody = [];
|
|
61
|
+
let inModule = false;
|
|
62
|
+
let moduleBuffer = [];
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
const trimmed = line.trim().toLowerCase();
|
|
65
|
+
if (trimmed.startsWith('module ') && !trimmed.startsWith('module procedure') && !inModule) {
|
|
66
|
+
inModule = true;
|
|
67
|
+
moduleBuffer.push(line);
|
|
68
|
+
}
|
|
69
|
+
else if (inModule) {
|
|
70
|
+
moduleBuffer.push(line);
|
|
71
|
+
if (trimmed.startsWith('end module')) {
|
|
72
|
+
inModule = false;
|
|
73
|
+
modules.push(moduleBuffer.join('\n'));
|
|
74
|
+
moduleBuffer = [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// If it's a "use" statement, it should be at the top of program?
|
|
79
|
+
// Or "implicit none"?
|
|
80
|
+
// We'll just dump everything else into the program body for now.
|
|
81
|
+
// NOTE: If users provide a full PROGRAM block in shared mode, this breaks.
|
|
82
|
+
// We assume shared mode = snippets building up a program.
|
|
83
|
+
if (!trimmed.startsWith('program ') && !trimmed.startsWith('end program')) {
|
|
84
|
+
programBody.push(line);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// If we have open module buffer (unclosed), dump it to body? Or fail?
|
|
89
|
+
// Or assume it's just code.
|
|
90
|
+
if (inModule) {
|
|
91
|
+
// Fallback
|
|
92
|
+
programBody.push(...moduleBuffer);
|
|
93
|
+
}
|
|
94
|
+
// Detect dependencies (USE statements)
|
|
95
|
+
// Move USE statements to the top of the program body
|
|
96
|
+
const useStatements = [];
|
|
97
|
+
const otherStatements = [];
|
|
98
|
+
for (const line of programBody) {
|
|
99
|
+
const trimmed = line.trim().toLowerCase();
|
|
100
|
+
if (trimmed.startsWith('use ')) {
|
|
101
|
+
useStatements.push(line);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
otherStatements.push(line);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
finalSource = `${modules.join('\n\n')}
|
|
108
|
+
|
|
109
|
+
program main
|
|
110
|
+
${useStatements.join('\n')}
|
|
111
|
+
implicit none
|
|
112
|
+
${otherStatements.join('\n')}
|
|
113
|
+
end program main`;
|
|
18
114
|
}
|
|
19
115
|
const uniqueId = `${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
20
116
|
const tempSourceFile = (0, path_1.join)((0, os_1.tmpdir)(), `doccident_fortran_${uniqueId}.f90`);
|
|
21
117
|
const tempExeFile = (0, path_1.join)((0, os_1.tmpdir)(), `doccident_fortran_${uniqueId}`);
|
|
118
|
+
const timeout = config.timeout || 30000;
|
|
22
119
|
try {
|
|
23
|
-
(0, fs_1.writeFileSync)(tempSourceFile,
|
|
120
|
+
(0, fs_1.writeFileSync)(tempSourceFile, finalSource);
|
|
24
121
|
// Compile with gfortran
|
|
25
|
-
const compileResult = (0, child_process_1.spawnSync)('gfortran', [tempSourceFile, '-o', tempExeFile], { encoding: 'utf-8' });
|
|
122
|
+
const compileResult = (0, child_process_1.spawnSync)('gfortran', [tempSourceFile, '-o', tempExeFile], { encoding: 'utf-8', timeout });
|
|
26
123
|
if (compileResult.status !== 0) {
|
|
27
124
|
stack = compileResult.stderr || "Fortran compilation failed";
|
|
125
|
+
if (isSharedSandbox) {
|
|
126
|
+
stack += `\n\nGenerated Source:\n${finalSource}`;
|
|
127
|
+
}
|
|
28
128
|
}
|
|
29
129
|
else {
|
|
30
130
|
// Run
|
|
31
|
-
const runResult = (0, child_process_1.spawnSync)(tempExeFile, [], { encoding: 'utf-8' });
|
|
131
|
+
const runResult = (0, child_process_1.spawnSync)(tempExeFile, [], { encoding: 'utf-8', timeout });
|
|
132
|
+
if (runResult.error && runResult.error.code === 'ETIMEDOUT') {
|
|
133
|
+
return { success: false, stack: `Execution timed out after ${timeout}ms` };
|
|
134
|
+
}
|
|
32
135
|
if (runResult.status === 0) {
|
|
33
136
|
success = true;
|
|
34
137
|
}
|
|
35
138
|
else {
|
|
36
139
|
stack = runResult.stderr || "Fortran execution failed with non-zero exit code";
|
|
37
140
|
}
|
|
141
|
+
return { success, stack, output: runResult.stdout };
|
|
38
142
|
}
|
|
39
143
|
}
|
|
40
144
|
catch (e) {
|
|
@@ -46,6 +150,11 @@ const fortranHandler = (code, _snippet, _config, _sandbox, _isSharedSandbox) =>
|
|
|
46
150
|
(0, fs_1.unlinkSync)(tempSourceFile);
|
|
47
151
|
if ((0, fs_1.existsSync)(tempExeFile))
|
|
48
152
|
(0, fs_1.unlinkSync)(tempExeFile);
|
|
153
|
+
// Cleanup .mod files generated by modules
|
|
154
|
+
// They are usually in the cwd (tmpdir)
|
|
155
|
+
// We can try to clean them up if we know the module names, or just ignore for now as tmpdir is cleaned by OS eventually?
|
|
156
|
+
// Actually, we are running in tmpdir, so .mod files will pollute it?
|
|
157
|
+
// Better to run in a specific subdir like other handlers to avoid collisions.
|
|
49
158
|
}
|
|
50
159
|
catch {
|
|
51
160
|
// Ignore cleanup error
|
package/dist/languages/go.js
CHANGED
|
@@ -5,29 +5,143 @@ const child_process_1 = require("child_process");
|
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
const os_1 = require("os");
|
|
8
|
-
|
|
8
|
+
// Helper to parse Go code into imports, top-level decls, and main body
|
|
9
|
+
function parseGoCode(fullCode) {
|
|
10
|
+
const lines = fullCode.split('\n');
|
|
11
|
+
const imports = [];
|
|
12
|
+
let topLevel = "";
|
|
13
|
+
let mainBody = "";
|
|
14
|
+
let state = 'NORMAL';
|
|
15
|
+
let braceCount = 0;
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
// Skip package main lines in shared snippets
|
|
19
|
+
if (trimmed.startsWith('package main'))
|
|
20
|
+
continue;
|
|
21
|
+
if (state === 'NORMAL') {
|
|
22
|
+
if (trimmed.startsWith('import')) {
|
|
23
|
+
if (trimmed.includes('(')) {
|
|
24
|
+
state = 'IN_IMPORT_BLOCK';
|
|
25
|
+
imports.push(line);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
imports.push(line);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (trimmed.startsWith('func ') ||
|
|
32
|
+
trimmed.startsWith('type ') ||
|
|
33
|
+
trimmed.startsWith('const ') ||
|
|
34
|
+
trimmed.startsWith('var ')) {
|
|
35
|
+
// Heuristic: Top level declaration
|
|
36
|
+
topLevel += line + "\n";
|
|
37
|
+
// Count braces
|
|
38
|
+
const open = (line.match(/\{/g) || []).length;
|
|
39
|
+
const close = (line.match(/\}/g) || []).length;
|
|
40
|
+
braceCount = open - close;
|
|
41
|
+
if (braceCount > 0) {
|
|
42
|
+
state = 'IN_BRACE_BLOCK';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
mainBody += line + "\n";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (state === 'IN_IMPORT_BLOCK') {
|
|
50
|
+
imports.push(line);
|
|
51
|
+
if (trimmed.includes(')')) {
|
|
52
|
+
state = 'NORMAL';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (state === 'IN_BRACE_BLOCK') {
|
|
56
|
+
topLevel += line + "\n";
|
|
57
|
+
const open = (line.match(/\{/g) || []).length;
|
|
58
|
+
const close = (line.match(/\}/g) || []).length;
|
|
59
|
+
braceCount += open - close;
|
|
60
|
+
if (braceCount <= 0) {
|
|
61
|
+
state = 'NORMAL';
|
|
62
|
+
braceCount = 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { imports, topLevel, mainBody };
|
|
67
|
+
}
|
|
68
|
+
const goHandler = (code, _snippet, config, sandbox, isSharedSandbox) => {
|
|
9
69
|
let success = false;
|
|
10
70
|
let stack = "";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
71
|
+
const context = sandbox;
|
|
72
|
+
let fullCode = code;
|
|
73
|
+
// Handle shared state
|
|
74
|
+
if (isSharedSandbox) {
|
|
75
|
+
if (!context._goCode) {
|
|
76
|
+
context._goCode = "";
|
|
77
|
+
}
|
|
78
|
+
context._goCode += code + "\n";
|
|
79
|
+
fullCode = context._goCode;
|
|
80
|
+
}
|
|
81
|
+
let finalSource = "";
|
|
82
|
+
// If explicit package main is present, use as is (unless shared?)
|
|
83
|
+
if (fullCode.includes('package main') && !isSharedSandbox) {
|
|
84
|
+
finalSource = fullCode;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const { imports, topLevel, mainBody } = parseGoCode(fullCode);
|
|
88
|
+
// Auto-add fmt if used but not imported
|
|
89
|
+
const importsStr = imports.join('\n');
|
|
90
|
+
let autoImports = "";
|
|
91
|
+
if (!importsStr.includes('"fmt"') && (topLevel.includes('fmt.') || mainBody.includes('fmt.'))) {
|
|
92
|
+
autoImports = 'import "fmt"';
|
|
93
|
+
}
|
|
94
|
+
finalSource = `package main
|
|
95
|
+
${autoImports}
|
|
96
|
+
${importsStr}
|
|
97
|
+
|
|
98
|
+
${topLevel}
|
|
99
|
+
|
|
17
100
|
func main() {
|
|
18
|
-
${
|
|
101
|
+
${mainBody}
|
|
19
102
|
}`;
|
|
20
103
|
}
|
|
21
104
|
const tempFile = (0, path_1.join)((0, os_1.tmpdir)(), `doccident_go_${Date.now()}_${Math.random().toString(36).substring(7)}.go`);
|
|
105
|
+
const timeout = config.timeout || 30000;
|
|
22
106
|
try {
|
|
23
|
-
(0, fs_1.writeFileSync)(tempFile,
|
|
24
|
-
|
|
107
|
+
(0, fs_1.writeFileSync)(tempFile, finalSource);
|
|
108
|
+
let result = (0, child_process_1.spawnSync)('go', ['run', tempFile], { encoding: 'utf-8', timeout });
|
|
109
|
+
// Retry logic for unused imports
|
|
110
|
+
if (result.status !== 0 && result.stderr && result.stderr.includes("imported and not used")) {
|
|
111
|
+
const lines = finalSource.split('\n');
|
|
112
|
+
const errorLines = result.stderr.split('\n');
|
|
113
|
+
let modified = false;
|
|
114
|
+
errorLines.forEach(err => {
|
|
115
|
+
const match = err.match(/:(\d+):\d+: "(.+)" imported and not used/);
|
|
116
|
+
if (match) {
|
|
117
|
+
const lineNum = parseInt(match[1], 10);
|
|
118
|
+
// Comment out the unused import line (using 1-based index from error)
|
|
119
|
+
if (lines[lineNum - 1]) {
|
|
120
|
+
lines[lineNum - 1] = "// " + lines[lineNum - 1];
|
|
121
|
+
modified = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
if (modified) {
|
|
126
|
+
const retriedSource = lines.join('\n');
|
|
127
|
+
(0, fs_1.writeFileSync)(tempFile, retriedSource);
|
|
128
|
+
result = (0, child_process_1.spawnSync)('go', ['run', tempFile], { encoding: 'utf-8', timeout });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
132
|
+
return { success: false, stack: `Execution timed out after ${timeout}ms` };
|
|
133
|
+
}
|
|
25
134
|
if (result.status === 0) {
|
|
26
135
|
success = true;
|
|
27
136
|
}
|
|
28
137
|
else {
|
|
29
138
|
stack = result.stderr || "Go execution failed with non-zero exit code";
|
|
139
|
+
// Debug output for shared state issues
|
|
140
|
+
if (isSharedSandbox) {
|
|
141
|
+
stack += `\n\nGenerated Source:\n${finalSource}`;
|
|
142
|
+
}
|
|
30
143
|
}
|
|
144
|
+
return { success, stack, output: result.stdout };
|
|
31
145
|
}
|
|
32
146
|
catch (e) {
|
|
33
147
|
stack = e.message || "Failed to execute go run";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.javaHandler = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const javaHandler = (code, _snippet, config, _sandbox, _isSharedSandbox) => {
|
|
9
|
+
let success = false;
|
|
10
|
+
let stack = "";
|
|
11
|
+
// Java execution logic
|
|
12
|
+
// Auto-wrap in class Main and main method if not present
|
|
13
|
+
let javaCode = code;
|
|
14
|
+
let className = "Main";
|
|
15
|
+
// Simple heuristic to detect if class is provided or just snippet
|
|
16
|
+
if (!javaCode.includes("class ")) {
|
|
17
|
+
javaCode = `public class ${className} {
|
|
18
|
+
public static void main(String[] args) {
|
|
19
|
+
${javaCode}
|
|
20
|
+
}
|
|
21
|
+
}`;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Try to extract class name if provided
|
|
25
|
+
const classMatch = javaCode.match(/class\s+(\w+)/);
|
|
26
|
+
if (classMatch) {
|
|
27
|
+
className = classMatch[1];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// Fallback or error? For now assume user knows what they are doing if they write "class"
|
|
31
|
+
// but maybe we should ensure file name matches public class
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const uniqueId = `${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
35
|
+
// Create a unique directory for this execution to avoid class name collisions
|
|
36
|
+
// if running parallel or multiple Main classes
|
|
37
|
+
const tempDir = (0, path_1.join)((0, os_1.tmpdir)(), `doccident_java_${uniqueId}`);
|
|
38
|
+
const timeout = config.timeout || 30000;
|
|
39
|
+
// We need to make the directory
|
|
40
|
+
try {
|
|
41
|
+
if (!(0, fs_1.existsSync)(tempDir)) {
|
|
42
|
+
(0, fs_1.mkdirSync)(tempDir);
|
|
43
|
+
}
|
|
44
|
+
const tempSourceFile = (0, path_1.join)(tempDir, `${className}.java`);
|
|
45
|
+
(0, fs_1.writeFileSync)(tempSourceFile, javaCode);
|
|
46
|
+
// Compile with javac
|
|
47
|
+
const compileResult = (0, child_process_1.spawnSync)('javac', [tempSourceFile], { encoding: 'utf-8', cwd: tempDir, timeout });
|
|
48
|
+
if (compileResult.status !== 0) {
|
|
49
|
+
stack = compileResult.stderr || "Java compilation failed";
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// Run
|
|
53
|
+
const runResult = (0, child_process_1.spawnSync)('java', ['-cp', tempDir, className], { encoding: 'utf-8', cwd: tempDir, timeout });
|
|
54
|
+
if (runResult.error && runResult.error.code === 'ETIMEDOUT') {
|
|
55
|
+
return { success: false, stack: `Execution timed out after ${timeout}ms` };
|
|
56
|
+
}
|
|
57
|
+
if (runResult.status === 0) {
|
|
58
|
+
success = true;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
stack = runResult.stderr || "Java execution failed with non-zero exit code";
|
|
62
|
+
}
|
|
63
|
+
return { success, stack, output: runResult.stdout };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
stack = e.message || "Failed to execute javac or java";
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
try {
|
|
71
|
+
// Cleanup: remove file and directory
|
|
72
|
+
// This is a bit manual, but fs.rmSync is available in node 14+
|
|
73
|
+
if (fs_1.rmSync) {
|
|
74
|
+
(0, fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Fallback for older nodes if necessary, but we are on node 22 in CI
|
|
78
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(tempDir, `${className}.java`)))
|
|
79
|
+
(0, fs_1.unlinkSync)((0, path_1.join)(tempDir, `${className}.java`));
|
|
80
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(tempDir, `${className}.class`)))
|
|
81
|
+
(0, fs_1.unlinkSync)((0, path_1.join)(tempDir, `${className}.class`));
|
|
82
|
+
if ((0, fs_1.existsSync)(tempDir))
|
|
83
|
+
(0, fs_1.rmdirSync)(tempDir);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Ignore cleanup error
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { success, stack };
|
|
91
|
+
};
|
|
92
|
+
exports.javaHandler = javaHandler;
|
|
@@ -3,9 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.javascriptHandler = void 0;
|
|
4
4
|
const vm_1 = require("vm");
|
|
5
5
|
const esbuild_1 = require("esbuild");
|
|
6
|
-
const javascriptHandler = (code, _snippet,
|
|
6
|
+
const javascriptHandler = (code, _snippet, config, sandbox, _isSharedSandbox) => {
|
|
7
7
|
let success = false;
|
|
8
8
|
let stack = "";
|
|
9
|
+
let output = "";
|
|
10
|
+
// Capture console.log
|
|
11
|
+
const originalLog = sandbox.console.log;
|
|
12
|
+
sandbox.console.log = (...args) => {
|
|
13
|
+
output += args.map(String).join(' ') + '\n';
|
|
14
|
+
if (originalLog)
|
|
15
|
+
originalLog(...args);
|
|
16
|
+
};
|
|
9
17
|
try {
|
|
10
18
|
const result = (0, esbuild_1.transformSync)(code, {
|
|
11
19
|
loader: 'ts',
|
|
@@ -13,12 +21,31 @@ const javascriptHandler = (code, _snippet, _config, sandbox, _isSharedSandbox) =
|
|
|
13
21
|
target: 'node12'
|
|
14
22
|
});
|
|
15
23
|
const compiledCode = result.code || "";
|
|
16
|
-
|
|
24
|
+
const timeout = config.timeout || 30000;
|
|
25
|
+
(0, vm_1.runInNewContext)(compiledCode, sandbox, { timeout });
|
|
17
26
|
success = true;
|
|
18
27
|
}
|
|
19
28
|
catch (e) {
|
|
20
|
-
|
|
29
|
+
if (e.code === 'ERR_SCRIPT_EXECUTION_TIMEOUT') {
|
|
30
|
+
stack = `Execution timed out after ${config.timeout || 30000}ms`;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
stack = e.stack || "";
|
|
34
|
+
}
|
|
21
35
|
}
|
|
22
|
-
|
|
36
|
+
finally {
|
|
37
|
+
// Restore console.log? Not strictly necessary as sandbox is recreated or dedicated
|
|
38
|
+
// But if shared sandbox, we might want to keep accumulating output?
|
|
39
|
+
// No, output capture is per-snippet usually.
|
|
40
|
+
// But if shared sandbox is reused, `output` variable here is local.
|
|
41
|
+
// We override sandbox.console.log every time.
|
|
42
|
+
// Ideally we should restore it to avoid nesting if we re-use the sandbox object in a way that stacks?
|
|
43
|
+
// But `makeTestSandbox` creates a fresh object or we reuse the same one.
|
|
44
|
+
// If we reuse, `sandbox.console.log` is overwritten.
|
|
45
|
+
// We should probably save the original log from the *very first* time?
|
|
46
|
+
// Actually, `makeTestSandbox` sets `console.log: () => null` or `console`.
|
|
47
|
+
// So we are wrapping that.
|
|
48
|
+
}
|
|
49
|
+
return { success, stack, output: output.trimEnd() }; // Trim trailing newline for easier comparison
|
|
23
50
|
};
|
|
24
51
|
exports.javascriptHandler = javascriptHandler;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pascalHandler = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const pascalHandler = (code, _snippet, config, _sandbox, _isSharedSandbox) => {
|
|
9
|
+
let success = false;
|
|
10
|
+
let stack = "";
|
|
11
|
+
// Pascal execution logic
|
|
12
|
+
// Auto-wrap in program block if not present
|
|
13
|
+
let pascalCode = code;
|
|
14
|
+
let programName = "TestProgram";
|
|
15
|
+
// Simple heuristic to detect if program is provided
|
|
16
|
+
if (!pascalCode.toLowerCase().includes("program ")) {
|
|
17
|
+
pascalCode = `program ${programName};
|
|
18
|
+
begin
|
|
19
|
+
${pascalCode}
|
|
20
|
+
end.`;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
// Try to extract program name if provided
|
|
24
|
+
const match = pascalCode.match(/program\s+(\w+);/i);
|
|
25
|
+
if (match) {
|
|
26
|
+
programName = match[1];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const uniqueId = `${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
30
|
+
const tempDir = (0, path_1.join)((0, os_1.tmpdir)(), `doccident_pascal_${uniqueId}`);
|
|
31
|
+
const timeout = config.timeout || 30000;
|
|
32
|
+
try {
|
|
33
|
+
if (!(0, fs_1.existsSync)(tempDir)) {
|
|
34
|
+
(0, fs_1.mkdirSync)(tempDir);
|
|
35
|
+
}
|
|
36
|
+
const tempSourceFile = (0, path_1.join)(tempDir, `${programName}.pas`);
|
|
37
|
+
const tempExeFile = (0, path_1.join)(tempDir, programName); // fpc creates executable with program name by default (or we specify -o)
|
|
38
|
+
(0, fs_1.writeFileSync)(tempSourceFile, pascalCode);
|
|
39
|
+
// Compile with fpc
|
|
40
|
+
// -o specifies output file name
|
|
41
|
+
const compileResult = (0, child_process_1.spawnSync)('fpc', [`-o${tempExeFile}`, tempSourceFile], { encoding: 'utf-8', cwd: tempDir, timeout });
|
|
42
|
+
if (compileResult.status !== 0) {
|
|
43
|
+
// Filter out the banner to just show the error if possible, or show all stderr/stdout
|
|
44
|
+
// fpc outputs errors to stdout often
|
|
45
|
+
stack = compileResult.stdout + "\n" + compileResult.stderr || "Pascal compilation failed";
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Run
|
|
49
|
+
const runResult = (0, child_process_1.spawnSync)(tempExeFile, [], { encoding: 'utf-8', cwd: tempDir, timeout });
|
|
50
|
+
if (runResult.error && runResult.error.code === 'ETIMEDOUT') {
|
|
51
|
+
return { success: false, stack: `Execution timed out after ${timeout}ms` };
|
|
52
|
+
}
|
|
53
|
+
if (runResult.status === 0) {
|
|
54
|
+
success = true;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
stack = runResult.stderr || "Pascal execution failed with non-zero exit code";
|
|
58
|
+
}
|
|
59
|
+
return { success, stack, output: runResult.stdout };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
stack = e.message || "Failed to execute fpc";
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
try {
|
|
67
|
+
if (fs_1.rmSync) {
|
|
68
|
+
(0, fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Cleanup fallback
|
|
72
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(tempDir, `${programName}.pas`)))
|
|
73
|
+
(0, fs_1.unlinkSync)((0, path_1.join)(tempDir, `${programName}.pas`));
|
|
74
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(tempDir, `${programName}.o`)))
|
|
75
|
+
(0, fs_1.unlinkSync)((0, path_1.join)(tempDir, `${programName}.o`));
|
|
76
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(tempDir, programName)))
|
|
77
|
+
(0, fs_1.unlinkSync)((0, path_1.join)(tempDir, programName));
|
|
78
|
+
if ((0, fs_1.existsSync)(tempDir))
|
|
79
|
+
(0, fs_1.rmdirSync)(tempDir);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore cleanup error
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { success, stack };
|
|
87
|
+
};
|
|
88
|
+
exports.pascalHandler = pascalHandler;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.perlHandler = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const perlHandler = (code, _snippet, config, sandbox, isSharedSandbox) => {
|
|
6
|
+
let success = false;
|
|
7
|
+
let stack = "";
|
|
8
|
+
const context = sandbox;
|
|
9
|
+
// If sharing code, we need to accumulate previous perl snippets
|
|
10
|
+
if (isSharedSandbox) {
|
|
11
|
+
if (!context._perlContext) {
|
|
12
|
+
context._perlContext = "";
|
|
13
|
+
}
|
|
14
|
+
context._perlContext += code + "\n";
|
|
15
|
+
code = context._perlContext;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const timeout = config.timeout || 30000;
|
|
19
|
+
const result = (0, child_process_1.spawnSync)('perl', [], { input: code, encoding: 'utf-8', timeout });
|
|
20
|
+
if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
21
|
+
return { success: false, stack: `Execution timed out after ${timeout}ms` };
|
|
22
|
+
}
|
|
23
|
+
if (result.status === 0) {
|
|
24
|
+
success = true;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
stack = result.stderr || "Perl execution failed with non-zero exit code";
|
|
28
|
+
}
|
|
29
|
+
return { success, stack, output: result.stdout };
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
stack = e.message || "Failed to spawn perl";
|
|
33
|
+
}
|
|
34
|
+
return { success, stack };
|
|
35
|
+
};
|
|
36
|
+
exports.perlHandler = perlHandler;
|
package/dist/languages/python.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.pythonHandler = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
|
-
const pythonHandler = (code,
|
|
5
|
+
const pythonHandler = (code, snippet, config, sandbox, isSharedSandbox) => {
|
|
6
6
|
let success = false;
|
|
7
7
|
let stack = "";
|
|
8
8
|
const context = sandbox;
|
|
@@ -15,17 +15,34 @@ const pythonHandler = (code, _snippet, _config, sandbox, isSharedSandbox) => {
|
|
|
15
15
|
code = context._pythonContext;
|
|
16
16
|
}
|
|
17
17
|
try {
|
|
18
|
-
const
|
|
18
|
+
const args = snippet.args || [];
|
|
19
|
+
const env = snippet.env ? { ...process.env, ...snippet.env } : undefined;
|
|
20
|
+
const timeout = config.timeout || 30000;
|
|
21
|
+
// For python, args usually go to the script or interpreter?
|
|
22
|
+
// If we want to pass args to the script, we might need to invoke differently if using stdin.
|
|
23
|
+
// `python3 - arg1 arg2` reads from stdin.
|
|
24
|
+
// Let's prepend '-' to args if not empty, so python knows to read script from stdin before args.
|
|
25
|
+
// But spawnSync args array includes 'python3' arguments.
|
|
26
|
+
// If user supplies `-v`, it should be `python3 -v`.
|
|
27
|
+
// If user supplies script args, they come after.
|
|
28
|
+
// This is tricky. Let's assume user provides INTERPRETER args for now, as typical.
|
|
29
|
+
// Or if they start with `-`, they are interpreter args.
|
|
30
|
+
const spawnArgs = [...args];
|
|
31
|
+
const result = (0, child_process_1.spawnSync)('python3', spawnArgs, { input: code, encoding: 'utf-8', env, timeout });
|
|
32
|
+
if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
33
|
+
return { success: false, stack: `Execution timed out after ${timeout}ms` };
|
|
34
|
+
}
|
|
19
35
|
if (result.status === 0) {
|
|
20
36
|
success = true;
|
|
21
37
|
}
|
|
22
38
|
else {
|
|
23
39
|
stack = result.stderr || "Python execution failed with non-zero exit code";
|
|
24
40
|
}
|
|
41
|
+
return { success, stack, output: result.stdout };
|
|
25
42
|
}
|
|
26
43
|
catch (e) {
|
|
27
44
|
stack = e.message || "Failed to spawn python3";
|
|
45
|
+
return { success, stack };
|
|
28
46
|
}
|
|
29
|
-
return { success, stack };
|
|
30
47
|
};
|
|
31
48
|
exports.pythonHandler = pythonHandler;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rHandler = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const rHandler = (code, _snippet, config, sandbox, isSharedSandbox) => {
|
|
6
|
+
let success = false;
|
|
7
|
+
let stack = "";
|
|
8
|
+
const context = sandbox;
|
|
9
|
+
// If sharing code, we need to accumulate previous R snippets
|
|
10
|
+
if (isSharedSandbox) {
|
|
11
|
+
if (!context._rContext) {
|
|
12
|
+
context._rContext = "";
|
|
13
|
+
}
|
|
14
|
+
context._rContext += code + "\n";
|
|
15
|
+
code = context._rContext;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
// Rscript - runs R code from stdin
|
|
19
|
+
const timeout = config.timeout || 30000;
|
|
20
|
+
const result = (0, child_process_1.spawnSync)('Rscript', ['-'], { input: code, encoding: 'utf-8', timeout });
|
|
21
|
+
if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
22
|
+
return { success: false, stack: `Execution timed out after ${timeout}ms` };
|
|
23
|
+
}
|
|
24
|
+
if (result.status === 0) {
|
|
25
|
+
success = true;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
stack = result.stderr || "R execution failed with non-zero exit code";
|
|
29
|
+
}
|
|
30
|
+
return { success, stack, output: result.stdout };
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
stack = e.message || "Failed to spawn Rscript";
|
|
34
|
+
return { success, stack };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
exports.rHandler = rHandler;
|