diff --git a/archive/classic_interpreter/core/computer/terminal/languages/php.py b/archive/classic_interpreter/core/computer/terminal/languages/php.py new file mode 100644 index 0000000000..77ec0731f3 --- /dev/null +++ b/archive/classic_interpreter/core/computer/terminal/languages/php.py @@ -0,0 +1,71 @@ +import os + +from .subprocess_language import SubprocessLanguage + + +class Php(SubprocessLanguage): + file_extension = "php" + name = "PHP" + aliases = ["php"] + + def __init__( + self, + ): + super().__init__() + self.close_stdin = True + self.start_cmd = ["php"] + + def preprocess_code(self, code): + """ + Add active line markers + Wrap in a try except (trap in shell) + Add end of execution marker + """ + lines = code.split("\n") + + if lines[0] == "": + # remove empty line at the start + lines.pop(0) + + if lines[-1] == "": + # remove empty line at the end + lines.pop(-1) + + if lines[-1] == "?>": + # remove close tag at the end + lines.pop(-1) + + r_code = "" + for i, line in enumerate(lines, 1): + if ( + os.environ.get("INTERPRETER_ACTIVE_LINE_DETECTION", "True").lower() + == "true" + ): + if -1 != line.find(""): + # Add commands that tell us what the active line is + r_code += f'echo "##active_line{i}##", PHP_EOL;\n' + r_code += f"{line}\n" + + # Add end command (we'll be listening for this so we know when it ends) + r_code += 'echo PHP_EOL, "##end_of_execution##", PHP_EOL;' + + return r_code + + def line_postprocessor(self, line): + return line + + def detect_active_line(self, line): + if "##active_line" in line: + return int(line.split("##active_line")[1].split("##")[0]) + return None + + def detect_end_of_execution(self, line): + return "##end_of_execution##" in line diff --git a/archive/classic_interpreter/core/computer/terminal/languages/subprocess_language.py b/archive/classic_interpreter/core/computer/terminal/languages/subprocess_language.py index dd422beb7f..af39313838 100644 --- a/archive/classic_interpreter/core/computer/terminal/languages/subprocess_language.py +++ b/archive/classic_interpreter/core/computer/terminal/languages/subprocess_language.py @@ -40,6 +40,7 @@ def terminate(self): self.process.terminate() self.process.stdin.close() self.process.stdout.close() + self.process.stderr.close() def start_process(self): if self.process: @@ -83,6 +84,7 @@ def run(self, code): yield { "type": "console", "format": "output", + "error": True, "content": traceback.format_exc(), } return @@ -95,7 +97,8 @@ def run(self, code): try: self.process.stdin.write(code + "\n") - self.process.stdin.flush() + # if execute PHP code with "flush()" it just hangs + self.process.stdin.close() break except: if retry_count != 0: @@ -105,6 +108,7 @@ def run(self, code): yield { "type": "console", "format": "output", + "error": True, "content": f"{traceback.format_exc()}\nRetrying... ({retry_count}/{max_retries})\nRestarting process.", } @@ -115,10 +119,12 @@ def run(self, code): yield { "type": "console", "format": "output", + "error": True, "content": "Maximum retries reached. Could not execute code.", } return + retry_count = 0 while True: if not self.output_queue.empty(): yield self.output_queue.get() @@ -136,6 +142,15 @@ def run(self, code): yield self.output_queue.get() time.sleep(0.2) break + retry_count += 1 + if retry_count > max_retries: + yield { + "type": "console", + "format": "output", + "error": True, + "content": "Maximum retries reached. Code is hang.", + } + return def handle_stream_output(self, stream, is_error_stream): try: diff --git a/archive/classic_interpreter/core/computer/terminal/terminal.py b/archive/classic_interpreter/core/computer/terminal/terminal.py index ba261a4d4d..bcfe38afb7 100644 --- a/archive/classic_interpreter/core/computer/terminal/terminal.py +++ b/archive/classic_interpreter/core/computer/terminal/terminal.py @@ -9,6 +9,7 @@ from .languages.html import HTML from .languages.java import Java from .languages.javascript import JavaScript +from .languages.php import Php from .languages.powershell import PowerShell from .languages.python import Python from .languages.r import R @@ -44,6 +45,7 @@ def __init__(self, computer): PowerShell, React, Java, + Php, ] self._active_languages = {} diff --git a/archive/classic_tests/core/computer/terminal/languages/test_php.py b/archive/classic_tests/core/computer/terminal/languages/test_php.py new file mode 100644 index 0000000000..ab85adbab6 --- /dev/null +++ b/archive/classic_tests/core/computer/terminal/languages/test_php.py @@ -0,0 +1,51 @@ +import shutil +import unittest + +from archive.classic_interpreter.core.computer.terminal.languages.php import Php + + +class TestPhp(unittest.TestCase): + def setUp(self): + if shutil.which("php") is None: + raise unittest.SkipTest("php not installed") + + self.php = Php() + + def tearDown(self): + self.php.terminate() + + def test_run(self): + for chunk in self.php.run("\n\n"): + if chunk["format"] == "active_line" or chunk["content"] == "\n": + pass + elif chunk["format"] == "output": + self.assertEqual("Hello World\n", chunk["content"]) + else: + self.fail("Wrong chunk format") + + def test_run_hang(self): + for chunk in self.php.run("\n\n"): + if chunk["format"] == "active_line" or chunk["content"] == "\n": + pass + elif "error" in chunk: + self.assertEqual( + "Maximum retries reached. Code is hang.", chunk["content"] + ) + elif chunk["format"] == "output": + self.assertEqual( + 'Parse error: syntax error, unexpected string content ";", ' + 'expecting "," or ";" in Standard input code on line 3\n', + chunk["content"], + ) + else: + self.fail("Wrong chunk format") + + +if __name__ == "__main__": + testing = TestPhp() + testing.setUp() + testing.test_run() + testing.tearDown() + testing.setUp() + testing.test_run_hang() + testing.tearDown() diff --git a/archive/classic_tests/core/computer/terminal/languages/test_shell.py b/archive/classic_tests/core/computer/terminal/languages/test_shell.py new file mode 100644 index 0000000000..53e3574006 --- /dev/null +++ b/archive/classic_tests/core/computer/terminal/languages/test_shell.py @@ -0,0 +1,43 @@ +import unittest + +from archive.classic_interpreter.core.computer.terminal.languages.shell import Shell + + +class TestShell(unittest.TestCase): + def setUp(self): + self.shell = Shell() + + def tearDown(self): + self.shell.terminate() + + def test_run(self): + for chunk in self.shell.run("echo 'Hello World'"): + if chunk["format"] == "active_line" or chunk["content"] == "\n": + pass + elif chunk["format"] == "output": + self.assertEqual("Hello World\n", chunk["content"]) + else: + self.fail("Wrong chunk format") + + def test_run_hang(self): + for chunk in self.shell.run("echo World'"): + if chunk["format"] == "active_line" or chunk["content"] == "\n": + pass + elif "error" in chunk: + self.assertEqual( + "Maximum retries reached. Code is hang.", chunk["content"] + ) + elif chunk["format"] == "output": + self.assertIn("unmatched", chunk["content"]) + else: + self.fail("Wrong chunk format") + + +if __name__ == "__main__": + testing = TestShell() + testing.setUp() + testing.test_run() + testing.tearDown() + testing.setUp() + testing.test_run_hang() + testing.tearDown()