Skip to content

Commit

Permalink
Implement sys.exit(code)
Browse files Browse the repository at this point in the history
  • Loading branch information
jayvdb committed Jun 28, 2021
1 parent b3b92e7 commit 706d26a
Show file tree
Hide file tree
Showing 19 changed files with 148 additions and 11 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ name = "sys_argv"
test = true
path = "tests/expected/sys_argv.rs"

[[bin]]
name = "sys_exit"
test = true
path = "tests/expected/sys_exit.rs"

[[bin]]
name = "fib_with_argparse"
test = true
Expand Down
1 change: 1 addition & 0 deletions doc/langspec.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ py2many supports a subset of python and mixes it with concepts from other static
* asyncio (rust only)
* imports
* asserts, prints
* standalone scripts using `if __name__ == "__main__":`, `sys.argv` and `sys.exit(code)`

# Not Supported Features

Expand Down
6 changes: 4 additions & 2 deletions pycpp/plugins.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import io
import os
import ast
import functools
import io
import math
import os
import random
import sys
import time

from tempfile import NamedTemporaryFile
Expand Down Expand Up @@ -191,4 +192,5 @@ def visit_asyncio_run(node, vargs) -> str:
False,
),
os.unlink: (lambda self, node, vargs: f"std::fs::remove_file({vargs[0]})", True),
sys.exit: (lambda self, node, vargs: f"exit({vargs[0]})", True),
}
6 changes: 6 additions & 0 deletions pydart/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import ast
import functools
import sys
import textwrap

from tempfile import NamedTemporaryFile
Expand Down Expand Up @@ -124,6 +125,10 @@ def visit_min_max(self, node, vargs, is_max: bool) -> str:
vargs_str = ", ".join(vargs)
return f"{min_max}({vargs_str})"

def visit_exit(self, node, vargs) -> str:
self._usings.add("dart:io")
return f"exit({vargs[0]})"


# small one liners are inlined here as lambdas
SMALL_DISPATCH_MAP = {
Expand Down Expand Up @@ -169,4 +174,5 @@ def visit_min_max(self, node, vargs, is_max: bool) -> str:
io.TextIOWrapper.read: (DartTranspilerPlugins.visit_textio_read, True),
io.TextIOWrapper.read: (DartTranspilerPlugins.visit_textio_write, True),
os.unlink: (lambda self, node, vargs: f"std::fs::remove_file({vargs[0]})", True),
sys.exit: (DartTranspilerPlugins.visit_exit, True),
}
10 changes: 8 additions & 2 deletions pygo/plugins.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import io
import os
import ast
import functools
import io
import os
import sys
import textwrap

from tempfile import NamedTemporaryFile
Expand Down Expand Up @@ -122,6 +123,10 @@ def visit_floor(self, node, vargs) -> str:
self._usings.add('"math"')
return f"math.Floor({vargs[0]})"

def visit_exit(self, node, vargs) -> str:
self._usings.add('"os"')
return f"os.Exit({vargs[0]})"


# small one liners are inlined here as lambdas
SMALL_DISPATCH_MAP = {
Expand Down Expand Up @@ -166,4 +171,5 @@ def visit_floor(self, node, vargs) -> str:
io.TextIOWrapper.read: (GoTranspilerPlugins.visit_textio_read, True),
io.TextIOWrapper.read: (GoTranspilerPlugins.visit_textio_write, True),
os.unlink: (lambda self, node, vargs: f"std::fs::remove_file({vargs[0]})", True),
sys.exit: (GoTranspilerPlugins.visit_exit, True),
}
2 changes: 2 additions & 0 deletions pyjl/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import ast
import functools
import sys
import textwrap

from tempfile import NamedTemporaryFile
Expand Down Expand Up @@ -181,4 +182,5 @@ def visit_asyncio_run(node, vargs) -> str:
io.TextIOWrapper.read: (JuiliaTranspilerPlugins.visit_textio_read, True),
io.TextIOWrapper.read: (JuiliaTranspilerPlugins.visit_textio_write, True),
os.unlink: (lambda self, node, vargs: f"std::fs::remove_file({vargs[0]})", True),
sys.exit: (lambda self, node, vargs: f"quit({vargs[0]})", True),
}
5 changes: 5 additions & 0 deletions pykt/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ast
import functools
import re
import sys
import textwrap

from tempfile import NamedTemporaryFile
Expand Down Expand Up @@ -174,4 +175,8 @@ def visit_floor(self, node, vargs) -> str:
io.TextIOWrapper.read: (KotlinTranspilerPlugins.visit_textio_read, True),
io.TextIOWrapper.read: (KotlinTranspilerPlugins.visit_textio_write, True),
os.unlink: (lambda self, node, vargs: f"std::fs::remove_file({vargs[0]})", True),
sys.exit: (
lambda self, node, vargs: f"kotlin.system.exitProcess({vargs[0]})",
True,
),
}
2 changes: 2 additions & 0 deletions pynim/plugins.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import io
import os
import random
import sys
import time

from tempfile import NamedTemporaryFile
Expand Down Expand Up @@ -87,6 +88,7 @@ def visit_print(self, node, vargs: List[str]) -> str:
io.TextIOWrapper.read: (NimTranspilerPlugins.visit_textio_read, True),
io.TextIOWrapper.read: (NimTranspilerPlugins.visit_textio_write, True),
os.unlink: (lambda self, node, vargs: f"std::fs::remove_file({vargs[0]})", True),
sys.exit: (lambda self, node, vargs: f"quit({vargs[0]})", True),
}

FUNC_USINGS_MAP = {
Expand Down
6 changes: 6 additions & 0 deletions pyrs/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import math
import time
import random
import sys
import textwrap

from tempfile import NamedTemporaryFile
Expand Down Expand Up @@ -126,6 +127,10 @@ def visit_print(self, node, vargs: List[str]) -> str:
placeholders.append("{}")
return 'println!("{0}",{1});'.format(" ".join(placeholders), ", ".join(vargs))

def visit_exit(self, node, vargs) -> str:
self._allows.add("unreachable_code")
return f"std::process::exit({vargs[0]})"

def visit_min_max(self, node, vargs, is_max: bool) -> str:
self._usings.add("std::cmp")
min_max = "max" if is_max else "min"
Expand Down Expand Up @@ -214,6 +219,7 @@ def visit_asyncio_run(node, vargs) -> str:
),
random.random: (lambda self, node, vargs: "pylib::random::random()", False),
os.unlink: (lambda self, node, vargs: f"std::fs::remove_file({vargs[0]})", True),
sys.exit: (RustTranspilerPlugins.visit_exit, True),
}

FUNC_USINGS_MAP = {
Expand Down
6 changes: 5 additions & 1 deletion pyrs/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def __init__(self, extension: bool = False, no_prologue: bool = False):
self._func_dispatch_table = FUNC_DISPATCH_TABLE
self._func_usings_map = FUNC_USINGS_MAP
self._attr_dispatch_table = ATTR_DISPATCH_TABLE
self._allows = set()

def usings(self):
if self._extension:
Expand Down Expand Up @@ -143,6 +144,7 @@ def usings(self):
if not self._no_prologue
else ""
)
lint_ignores += "\n".join(f"#![allow({allow})]" for allow in self._allows)
cargo_toml = (
f"""
//! ```cargo
Expand All @@ -155,7 +157,7 @@ def usings(self):
if not self._no_prologue
else ""
)
return f"{cargo_toml}\n{lint_ignores}\n{externs}\n{uses}\n"
return f"{cargo_toml}\n{lint_ignores}\n\n{externs}\n{uses}\n"

def features(self):
if self._features:
Expand Down Expand Up @@ -373,6 +375,8 @@ def visit_Call(self, node):
node_result_type = getattr(node, "result_type", False)
node_func_result_type = getattr(node.func, "result_type", False)
if ret is not None:
if ret.startswith("std::process::exit("):
node_result_type = False
unwrap = "?" if node_result_type or node_func_result_type else ""
return f"{ret}{unwrap}"

Expand Down
5 changes: 5 additions & 0 deletions tests/cases/sys_exit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sys

if __name__ == "__main__":
print("OK")
sys.exit(1)
11 changes: 11 additions & 0 deletions tests/expected/sys_exit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <iostream> // NOLINT(build/include_order)

#include "pycpp/runtime/builtins.h" // NOLINT(build/include_order)
#include "pycpp/runtime/sys.h" // NOLINT(build/include_order)

int main(int argc, char** argv) {
pycpp::sys::argv = std::vector<std::string>(argv, argv + argc);
std::cout << std::string{"OK"};
std::cout << std::endl;
exit(1);
}
8 changes: 8 additions & 0 deletions tests/expected/sys_exit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @dart=2.9
import 'dart:io';
import 'package:sprintf/sprintf.dart';

main(List<String> argv) {
print(sprintf("%s", ["OK"]));
exit(1);
}
11 changes: 11 additions & 0 deletions tests/expected/sys_exit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"fmt"
"os"
)

func main() {
fmt.Printf("%v\n", "OK")
os.Exit(1)
}
7 changes: 7 additions & 0 deletions tests/expected/sys_exit.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

function main()
println(join(["OK"], " "))
exit(sys, 1)
end

main()
5 changes: 5 additions & 0 deletions tests/expected/sys_exit.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

fun main(argv: Array<String>) {
println("OK")
kotlin.system.exitProcess(1)
}
6 changes: 6 additions & 0 deletions tests/expected/sys_exit.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

proc main() =
echo "OK"
quit(1)

main()
34 changes: 34 additions & 0 deletions tests/expected/sys_exit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

//! ```cargo
//! [package]
//! edition = "2018"
//! [dependencies]
//! anyhow = "*"
//! ```
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::double_parens)] // https://github.com/adsharma/py2many/issues/17
#![allow(clippy::map_identity)]
#![allow(clippy::needless_return)]
#![allow(clippy::print_literal)]
#![allow(clippy::ptr_arg)]
#![allow(clippy::redundant_static_lifetimes)] // https://github.com/adsharma/py2many/issues/266
#![allow(clippy::unnecessary_cast)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::useless_vec)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(unused_imports)]
#![allow(unused_mut)]
#![allow(unused_parens)]
#![allow(unreachable_code)]

extern crate anyhow;
use anyhow::Result;

pub fn main() -> Result<()> {
println!("{}", "OK");
std::process::exit(1);
Ok(())
}
23 changes: 17 additions & 6 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
CASE_ARGS = {
"sys_argv": ("arg1",),
}
CASE_EXPECTED_EXITCODE = {
"sys_exit": 1,
}

EXTENSION_TEST_CASES = [
item.stem
Expand Down Expand Up @@ -98,9 +101,11 @@ def get_exe_filename(case, ext):


@lru_cache()
def get_python_case_output(case_filename, main_args):
def get_python_case_output(case_filename, main_args, exit_code):
proc = run([sys.executable, str(case_filename), *main_args], capture_output=True)
if proc.returncode:
if exit_code:
assert proc.returncode == exit_code
elif proc.returncode:
raise RuntimeError(f"Invalid {case_filename}:\n{proc.stdout}{proc.stderr}")
return proc.stdout

Expand Down Expand Up @@ -151,7 +156,10 @@ def test_generated(self, case, lang):
self.assertTrue(is_script)

main_args = CASE_ARGS.get(case, tuple())
expected_output = get_python_case_output(case_filename, main_args)
expected_exit_code = CASE_EXPECTED_EXITCODE.get(case, 0)
expected_output = get_python_case_output(
case_filename, main_args, expected_exit_code
)
self.assertTrue(expected_output, "Test cases must print something")
expected_output = expected_output.splitlines()

Expand Down Expand Up @@ -213,10 +221,10 @@ def test_generated(self, case, lang):

stdout = proc.stdout

if proc.returncode and expect_failure:
if expect_failure and expected_exit_code != proc.returncode:
raise unittest.SkipTest(f"Execution of {case}{ext} failed")
assert (
not proc.returncode
expected_exit_code == proc.returncode
), f"Execution of {case}{ext} failed:\n{stdout}{proc.stderr}"

if self.UPDATE_EXPECTED or not os.path.exists(expected_filename):
Expand All @@ -225,7 +233,10 @@ def test_generated(self, case, lang):
elif exe.exists() and os.access(exe, os.X_OK):
cmd = [exe, *main_args]
print(f"Running {cmd} ...")
stdout = run(cmd, env=env, capture_output=True, check=True).stdout
proc = run(cmd, env=env, capture_output=True)
assert expected_exit_code == proc.returncode

stdout = proc.stdout
else:
raise RuntimeError(f"Compiled output {exe} not detected")

Expand Down

0 comments on commit 706d26a

Please sign in to comment.