Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor[tool]: refactor venom and fang CLI parsing #4398

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
89 changes: 64 additions & 25 deletions vyper/cli/venom_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,46 +20,85 @@


def _parse_args(argv: list[str]):
usage = (

Check warning on line 23 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L23

Added line #L23 was not covered by tests
f"venom [-h] [--version] [--evm-version {{{','.join(evm.EVM_VERSIONS)}}}] "
"[--stdin | input_file]"
)
parser = argparse.ArgumentParser(
description="Venom EVM IR parser & compiler", formatter_class=argparse.RawTextHelpFormatter
description="Venom EVM IR parser & compiler",
usage=usage,
formatter_class=argparse.RawTextHelpFormatter,
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(

Check warning on line 33 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L32-L33

Added lines #L32 - L33 were not covered by tests
"input_file",
nargs="?",
help="path to the Venom source file (required if --stdin is not used)",
)
group.add_argument(

Check warning on line 38 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L38

Added line #L38 was not covered by tests
"--stdin", action="store_true", help="read the Venom source code from standard input"
)

parser.add_argument(

Check warning on line 42 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L42

Added line #L42 was not covered by tests
"--version",
action="version",
version=vyper.__long_version__,
help="display the version of the Vyper compiler",
)
parser.add_argument("input_file", help="Venom sourcefile", nargs="?")
parser.add_argument("--version", action="version", version=vyper.__long_version__)
parser.add_argument(
"--evm-version",
help=f"Select desired EVM version (default {evm.DEFAULT_EVM_VERSION})",
help=f"select the desired EVM version (default {evm.DEFAULT_EVM_VERSION})",
choices=list(evm.EVM_VERSIONS),
dest="evm_version",
)
parser.add_argument(
"--stdin", action="store_true", help="whether to pull venom input from stdin"
)

args = parser.parse_args(argv)

if args.evm_version is not None:
if args.evm_version:
set_global_settings(Settings(evm_version=args.evm_version))

venom_source = None

Check warning on line 60 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L60

Added line #L60 was not covered by tests

if args.stdin:
if not sys.stdin.isatty():
venom_source = sys.stdin.read()
else:
# No input provided
print("Error: --stdin flag used but no input provided")
sys.exit(1)
venom_source = read_from_stdin()

Check warning on line 63 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L63

Added line #L63 was not covered by tests
elif args.input_file:
venom_source = read_from_file(args.input_file)

Check warning on line 65 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L65

Added line #L65 was not covered by tests

assert venom_source is not None
process_venom_source(venom_source)

Check warning on line 68 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L67-L68

Added lines #L67 - L68 were not covered by tests


def read_from_stdin():

Check warning on line 71 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L71

Added line #L71 was not covered by tests

Check notice

Code scanning / CodeQL

Explicit returns mixed with implicit (fall through) returns Note

Mixing implicit and explicit returns may indicate an error as implicit returns always return None.
if not sys.stdin.isatty():
return sys.stdin.read()

Check warning on line 73 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L73

Added line #L73 was not covered by tests
else:
if args.input_file is None:
print("Error: No input file provided, either use --stdin or provide a path")
sys.exit(1)
with open(args.input_file, "r") as f:
venom_source = f.read()
print("Error: --stdin flag used but no input provided.")
sys.exit(1)

Check warning on line 76 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L75-L76

Added lines #L75 - L76 were not covered by tests


def read_from_file(input_file: str):
try:
with open(input_file, "r") as f:
return f.read()
except FileNotFoundError:
print(f"Error: File '{input_file}' not found.")
sys.exit(1)
except IOError as e:
print(f"Error: Unable to read file '{input_file}': {e}")
sys.exit(1)

Check warning on line 88 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L79-L88

Added lines #L79 - L88 were not covered by tests


ctx = parse_venom(venom_source)
run_passes_on(ctx, OptimizationLevel.default())
asm = generate_assembly_experimental(ctx)
bytecode = generate_bytecode(asm, compiler_metadata=None)
print(f"0x{bytecode.hex()}")
def process_venom_source(source: str):
try:
ctx = parse_venom(source)
run_passes_on(ctx, OptimizationLevel.default())
asm = generate_assembly_experimental(ctx)
bytecode = generate_bytecode(asm, compiler_metadata=None)
print(f"0x{bytecode.hex()}")
except Exception as e:
print(f"Error: Compilation failed: {e}.")
sys.exit(1)

Check warning on line 100 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L91-L100

Added lines #L91 - L100 were not covered by tests


if __name__ == "__main__":
_parse_args(sys.argv[1:])
_parse_cli_args()

Check warning on line 104 in vyper/cli/venom_main.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/venom_main.py#L104

Added line #L104 was not covered by tests
62 changes: 48 additions & 14 deletions vyper/cli/vyper_ir.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import argparse
import os

Check warning on line 3 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L3

Added line #L3 was not covered by tests
import sys

import vyper
Expand All @@ -9,37 +10,67 @@


def _parse_cli_args():
return _parse_args(sys.argv[1:])
args = _parse_args(sys.argv[1:])
output_formats = validate_output_formats(args.format)
compiler_data = compile_to_ir(args.input_file, output_formats, args.show_gas_estimates)

Check warning on line 15 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L13-L15

Added lines #L13 - L15 were not covered by tests

for key in ("ir", "opt_ir", "asm", "bytecode"):
if key in compiler_data:
print(compiler_data[key])

Check warning on line 19 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L19

Added line #L19 was not covered by tests


def _parse_args(argv):
parser = argparse.ArgumentParser(description="Vyper IR IR compiler")
parser.add_argument("input_file", help="Vyper sourcecode to compile")
parser = argparse.ArgumentParser(description="Legacy Vyper IR compiler")
parser.add_argument("input_file", help="path to the Vyper IR source file (e.g., foo.lll)")

Check warning on line 24 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L23-L24

Added lines #L23 - L24 were not covered by tests
parser.add_argument(
"--version", action="version", version=f"{vyper.__version__}+commit{vyper.__commit__}"
"--version",
action="version",
version=vyper.__long_version__,
help="display the version of the Vyper compiler",
)
parser.add_argument(
"-f",
help="Format to print csv list of ir,opt_ir,asm,bytecode",
help=(
"comma-separated list of output formats to generate; "
"valid options: ir, opt_ir, asm, bytecode (default: bytecode)"
),
default="bytecode",
dest="format",
)
parser.add_argument(
"--show-gas-estimates", help="Show gas estimates in ir output mode.", action="store_true"
"--show-gas-estimates",
help="include gas estimates in IR output (only applicable to 'ir' format)",
action="store_true",
)
return parser.parse_args(argv)

Check warning on line 45 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L45

Added line #L45 was not covered by tests

args = parser.parse_args(argv)
output_formats = set(dict.fromkeys(args.format.split(",")))
compiler_data = compile_to_ir(args.input_file, output_formats, args.show_gas_estimates)

for key in ("ir", "opt_ir", "asm", "bytecode"):
if key in compiler_data:
print(compiler_data[key])
def validate_output_formats(format_str):
valid_formats = {"ir", "opt_ir", "asm", "bytecode"}
formats = set(format_str.split(","))
invalid_formats = formats - valid_formats

Check warning on line 51 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L48-L51

Added lines #L48 - L51 were not covered by tests
if invalid_formats:
print(f"Error: Invalid output formats: {', '.join(invalid_formats)}")
print(f"Valid options are: {', '.join(valid_formats)}")
sys.exit(1)
return formats

Check warning on line 56 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L53-L56

Added lines #L53 - L56 were not covered by tests


def compile_to_ir(input_file, output_formats, show_gas_estimates=False):
with open(input_file) as fh:
s_expressions = parse_s_exp(fh.read())
if not os.path.exists(input_file):
print(f"Error: File '{input_file}' does not exist.")
sys.exit(1)

Check warning on line 62 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L61-L62

Added lines #L61 - L62 were not covered by tests

try:
with open(input_file, "r") as fh:
s_expressions = parse_s_exp(fh.read())
except Exception as e:
print(f"Error: Unable to read or parse file '{input_file}': {e}")
sys.exit(1)

Check warning on line 69 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L64-L69

Added lines #L64 - L69 were not covered by tests

if show_gas_estimates and "ir" not in output_formats:
print("Warning: --show-gas-estimates has no effect without 'ir' format.")
show_gas_estimates = False

Check warning on line 73 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L72-L73

Added lines #L72 - L73 were not covered by tests

if show_gas_estimates:
IRnode.repr_show_gas = True
Expand All @@ -50,6 +81,9 @@
if "ir" in output_formats:
compiler_data["ir"] = ir

if "opt_ir" in output_formats:
compiler_data["opt_ir"] = ir

Check warning on line 85 in vyper/cli/vyper_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/cli/vyper_ir.py#L85

Added line #L85 was not covered by tests

asm = compile_ir.compile_to_assembly(ir)
if "asm" in output_formats:
compiler_data["asm"] = asm
Expand Down