Skip to content

Commit 3e3b493

Browse files
Add support for interactive submissions
1 parent db22c5b commit 3e3b493

File tree

6 files changed

+208
-4
lines changed

6 files changed

+208
-4
lines changed

examples/interactive.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import judge0
2+
3+
user_solution = judge0.Submission(
4+
source_code="""
5+
lower_bound, upper_bound = (int(x) for x in input().strip().split())
6+
while True:
7+
my_guess = (lower_bound + upper_bound) // 2
8+
print(my_guess)
9+
10+
command = input().strip()
11+
if command == "lower":
12+
lower_bound, upper_bound = lower_bound, my_guess
13+
elif command == "higher":
14+
lower_bound, upper_bound = my_guess, upper_bound
15+
else:
16+
break
17+
"""
18+
)
19+
20+
interactive_producer = judge0.Submission(
21+
source_code="""
22+
#include <stdio.h>
23+
24+
int main(int argc, char **argv) {
25+
int lower_bound, upper_bound, number_to_guess, max_tries;
26+
scanf("%d %d %d %d", &lower_bound, &upper_bound, &number_to_guess, &max_tries);
27+
28+
FILE *user_solution_stdin = fopen(argv[1], "w");
29+
FILE *user_solution_stdout = fopen(argv[2], "r");
30+
31+
fprintf(user_solution_stdin, "%d %d\\n", lower_bound, upper_bound);
32+
fflush(user_solution_stdin);
33+
34+
int user_guess;
35+
for (int i = 0; i < max_tries; i++) {
36+
fscanf(user_solution_stdout, "%d", &user_guess);
37+
if (user_guess > number_to_guess) {
38+
fprintf(user_solution_stdin, "lower\\n");
39+
fflush(user_solution_stdin);
40+
} else if (user_guess < number_to_guess) {
41+
fprintf(user_solution_stdin, "higher\\n");
42+
fflush(user_solution_stdin);
43+
} else {
44+
fprintf(user_solution_stdin, "correct\\n");
45+
fflush(user_solution_stdin);
46+
47+
printf("User successfully guessed the number.\\n");
48+
49+
return 0;
50+
}
51+
}
52+
53+
fprintf(user_solution_stdin, "failed\\n");
54+
fflush(user_solution_stdin);
55+
56+
printf("User failed to guess the number within %d guesses.\\n", max_tries);
57+
58+
return 0;
59+
}
60+
""",
61+
language=judge0.C,
62+
)
63+
64+
result = judge0.run(
65+
submissions=user_solution,
66+
interactive_producer=interactive_producer,
67+
test_cases=judge0.TestCase(input="0 100 42 7", expected_output="User successfully guessed the number.\n"),
68+
)
69+
70+
print(f"Submission status: {result.status}")
71+
print(f"Producer stdout: {result.stdout}")
72+
print(f'User stdout:\n{result.post_execution_filesystem.find("user.stdout")}')
73+
print(f'User stdin:\n{result.post_execution_filesystem.find("user.stdin")}')

src/judge0/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,10 @@ def _get_implicit_client(flavor: Flavor) -> Client:
101101
CE = Flavor.CE
102102
EXTRA_CE = Flavor.EXTRA_CE
103103

104-
PYTHON = LanguageAlias.PYTHON
104+
C = LanguageAlias.C
105105
CPP = LanguageAlias.CPP
106-
JAVA = LanguageAlias.JAVA
107-
CPP_GCC = LanguageAlias.CPP_GCC
108106
CPP_CLANG = LanguageAlias.CPP_CLANG
107+
CPP_GCC = LanguageAlias.CPP_GCC
108+
JAVA = LanguageAlias.JAVA
109+
PYTHON = LanguageAlias.PYTHON
109110
PYTHON_FOR_ML = LanguageAlias.PYTHON_FOR_ML

src/judge0/api.py

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from typing import Optional, Union
22

3-
from .base_types import Flavor, TestCase
3+
from .base_types import Flavor, TestCase, LanguageAlias
44
from .clients import Client
55
from .retry import RegularPeriodRetry, RetryMechanism
66
from .submission import Submission
77

8+
from .filesystem import Filesystem, File
9+
import textwrap
10+
811

912
def resolve_client(
1013
client: Optional[Union[Client, Flavor]] = None,
@@ -133,13 +136,111 @@ def create_submissions_from_test_cases(
133136
return all_submissions
134137

135138

139+
def safe_format(f, arg):
140+
try:
141+
return (f or "") % (arg or "")
142+
except TypeError:
143+
return f or ""
144+
145+
146+
def create_fs(client, submission):
147+
fs = Filesystem(submission.additional_files)
148+
language_id = client.get_language_id(submission.language)
149+
if language_id != LanguageAlias.MULTI_FILE: # Multi-file program
150+
language = client.get_language(language_id)
151+
fs.files.append(File(language["source_file"], submission.source_code))
152+
fs.files.append(
153+
File(
154+
"compile.sh",
155+
safe_format(language["compile_cmd"], submission.compiler_options),
156+
)
157+
)
158+
fs.files.append(
159+
File("run.sh", f'{language["run_cmd"]} {submission.command_line_arguments}')
160+
)
161+
return fs
162+
163+
164+
def create_submission_with_interactive_producer(
165+
client, submission, interactive_producer
166+
):
167+
interactive_producer.command_line_arguments = "/box/stdin.fifo /box/stdout.fifo"
168+
169+
consumer_fs = create_fs(client, submission)
170+
producer_fs = create_fs(client, interactive_producer)
171+
172+
final_fs = Filesystem()
173+
for f in consumer_fs.files:
174+
final_fs.files.append(File(f"./consumer/{f.name}", f.content))
175+
176+
for f in producer_fs.files:
177+
final_fs.files.append(File(f"./producer/{f.name}", f.content))
178+
179+
final_fs.files.append(
180+
File(
181+
"compile.sh",
182+
textwrap.dedent(
183+
"""
184+
cd /box/consumer && bash compile.sh
185+
cd /box/producer && bash compile.sh
186+
"""
187+
),
188+
)
189+
)
190+
191+
final_fs.files.append(
192+
File(
193+
"run.sh",
194+
textwrap.dedent(
195+
"""
196+
mkfifo /box/stdin.fifo /box/stdout.fifo
197+
198+
cd /box/consumer
199+
tee >(bash run.sh 2> /box/user.stderr | tee /box/stdout.fifo /box/user.stdout &> /dev/null) /box/user.stdin < /box/stdin.fifo &> /dev/null &
200+
201+
cd /box/producer
202+
bash run.sh < /dev/stdin &
203+
PRODUCER_PID=$!
204+
205+
wait $PRODUCER_PID
206+
207+
rm -f /box/stdin.fifo /box/stdout.fifo
208+
"""
209+
),
210+
)
211+
)
212+
213+
return Submission(
214+
source_code="",
215+
additional_files=final_fs,
216+
language=LanguageAlias.MULTI_FILE,
217+
)
218+
219+
220+
def create_submissions_with_interactive_producer(
221+
client, submissions, interactive_producer
222+
):
223+
if isinstance(submissions, Submission):
224+
return create_submission_with_interactive_producer(
225+
client, submissions, interactive_producer
226+
)
227+
else:
228+
return [
229+
create_submission_with_interactive_producer(
230+
client, submission, interactive_producer
231+
)
232+
for submission in submissions
233+
]
234+
235+
136236
def _execute(
137237
*,
138238
client: Optional[Union[Client, Flavor]] = None,
139239
submissions: Optional[Union[Submission, list[Submission]]] = None,
140240
source_code: Optional[str] = None,
141241
wait_for_result: bool = False,
142242
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
243+
interactive_producer: Optional[Submission] = None,
143244
**kwargs,
144245
) -> Union[Submission, list[Submission]]:
145246
if submissions is not None and source_code is not None:
@@ -164,6 +265,11 @@ def _execute(
164265

165266
client = resolve_client(client, submissions=submissions)
166267

268+
if interactive_producer is not None:
269+
submissions = create_submissions_with_interactive_producer(
270+
client, submissions, interactive_producer
271+
)
272+
167273
all_submissions = create_submissions_from_test_cases(submissions, test_cases)
168274

169275
# We differentiate between creating a single submission and multiple
@@ -189,6 +295,7 @@ def async_execute(
189295
submissions: Optional[Union[Submission, list[Submission]]] = None,
190296
source_code: Optional[str] = None,
191297
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
298+
interactive_producer: Optional[Submission] = None,
192299
**kwargs,
193300
) -> Union[Submission, list[Submission]]:
194301
return _execute(
@@ -197,6 +304,7 @@ def async_execute(
197304
source_code=source_code,
198305
wait_for_result=False,
199306
test_cases=test_cases,
307+
interactive_producer=interactive_producer,
200308
**kwargs,
201309
)
202310

@@ -207,6 +315,7 @@ def sync_execute(
207315
submissions: Optional[Union[Submission, list[Submission]]] = None,
208316
source_code: Optional[str] = None,
209317
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
318+
interactive_producer: Optional[Submission] = None,
210319
**kwargs,
211320
) -> Union[Submission, list[Submission]]:
212321
return _execute(
@@ -215,6 +324,7 @@ def sync_execute(
215324
source_code=source_code,
216325
wait_for_result=True,
217326
test_cases=test_cases,
327+
interactive_producer=interactive_producer,
218328
**kwargs,
219329
)
220330

src/judge0/base_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class LanguageAlias(IntEnum):
3232
CPP_GCC = 3
3333
CPP_CLANG = 4
3434
PYTHON_FOR_ML = 5
35+
C = 6
36+
MULTI_FILE = 89
3537

3638

3739
class Flavor(IntEnum):

src/judge0/data.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,34 @@
77
LanguageAlias.JAVA: 62,
88
LanguageAlias.CPP_GCC: 54,
99
LanguageAlias.CPP_CLANG: 76,
10+
LanguageAlias.C: 50,
11+
LanguageAlias.MULTI_FILE: 89,
1012
},
1113
"1.13.1-extra": {
1214
LanguageAlias.PYTHON: 10,
1315
LanguageAlias.CPP: 2,
1416
LanguageAlias.JAVA: 4,
1517
LanguageAlias.CPP_CLANG: 2,
1618
LanguageAlias.PYTHON_FOR_ML: 10,
19+
LanguageAlias.C: 1,
20+
LanguageAlias.MULTI_FILE: 89,
1721
},
1822
"1.14.0": {
1923
LanguageAlias.PYTHON: 100,
2024
LanguageAlias.CPP: 105,
2125
LanguageAlias.JAVA: 91,
2226
LanguageAlias.CPP_GCC: 105,
2327
LanguageAlias.CPP_CLANG: 76,
28+
LanguageAlias.C: 103,
29+
LanguageAlias.MULTI_FILE: 89,
2430
},
2531
"1.14.0-extra": {
2632
LanguageAlias.PYTHON: 25,
2733
LanguageAlias.CPP: 2,
2834
LanguageAlias.JAVA: 4,
2935
LanguageAlias.CPP_CLANG: 2,
3036
LanguageAlias.PYTHON_FOR_ML: 25,
37+
LanguageAlias.C: 1,
38+
LanguageAlias.MULTI_FILE: 89,
3139
},
3240
}

src/judge0/filesystem.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ def encode(self) -> bytes:
5656
zip_file.writestr(file.name, file.content)
5757
return zip_buffer.getvalue()
5858

59+
def find(self, name: str) -> Optional[File]:
60+
if name.startswith("./"):
61+
name = name[2:]
62+
63+
for file in self.files:
64+
if file.name == name:
65+
return file
66+
67+
return None
68+
5969
def __str__(self) -> str:
6070
return b64encode(self.encode()).decode()
6171

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy