diff --git a/vyper/cli/venom_main.py b/vyper/cli/venom_main.py index 3114246e04..5ba5c61afb 100755 --- a/vyper/cli/venom_main.py +++ b/vyper/cli/venom_main.py @@ -20,46 +20,85 @@ def _parse_cli_args(): def _parse_args(argv: list[str]): + usage = ( + 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( + "input_file", + nargs="?", + help="path to the Venom source file (required if --stdin is not used)", + ) + group.add_argument( + "--stdin", action="store_true", help="read the Venom source code from standard input" + ) + + parser.add_argument( + "--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 + 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() + elif args.input_file: + venom_source = read_from_file(args.input_file) + + assert venom_source is not None + process_venom_source(venom_source) + + +def read_from_stdin(): + if not sys.stdin.isatty(): + return sys.stdin.read() 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) + + +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) + - 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) if __name__ == "__main__": - _parse_args(sys.argv[1:]) + _parse_cli_args() diff --git a/vyper/cli/vyper_ir.py b/vyper/cli/vyper_ir.py index 1f90badcaa..5f44499fc1 100755 --- a/vyper/cli/vyper_ir.py +++ b/vyper/cli/vyper_ir.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import argparse +import os import sys import vyper @@ -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) + + for key in ("ir", "opt_ir", "asm", "bytecode"): + if key in compiler_data: + print(compiler_data[key]) 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)") 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) - 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 + 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 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) + + 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) + + 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 if show_gas_estimates: IRnode.repr_show_gas = True @@ -50,6 +81,9 @@ def compile_to_ir(input_file, output_formats, show_gas_estimates=False): if "ir" in output_formats: compiler_data["ir"] = ir + if "opt_ir" in output_formats: + compiler_data["opt_ir"] = ir + asm = compile_ir.compile_to_assembly(ir) if "asm" in output_formats: compiler_data["asm"] = asm