Files
Viper_VRTE/argparse.vp
T

447 lines
17 KiB
Plaintext

import t
import string
import memhub
import stdio
import hashtable
# ============================================================
# argparse.vp — Command line argument parsing library, inspired by Python argparse
#
# Usage:
# argparse._mbuddy = mb
# parser = argparse.ArgumentParser("myprog", "A test", pool=mb)
# parser.add_argument("--input", "-i", arg_type=argparse.STRING,
# required=True, help="input file")
# parser.add_argument("--count", "-c", arg_type=argparse.INT,
# default=0, help="repeat count")
# parser.add_argument("--verbose", "-v", action=argparse.STORE_TRUE,
# help="verbose mode")
# parser.add_argument("output", arg_type=argparse.STRING,
# help="output path")
# args = parser.parse_args(argc, argv)
# if args is not None:
# inp: str = args.get_str("input")
# cnt: int = args.get_int("count")
# verb: bool = args.get_bool("verbose")
# out: str = args.get_str("output")
# argparse.release(args)
# ============================================================
# Type constants
define STRING = 0
define INT = 1
define BOOL = 2
# Action constants
define STORE = 0
define STORE_TRUE = 1
define STORE_FALSE = 2
define COUNT = 3
# Limits
define MAX_ARGS = 32
# Module-level membuddy pointer (assigned inside main by user)
_mbuddy: memhub.MemBuddy*
# ============================================================
# Argument — Argument definition structure
# ============================================================
class Argument:
name: str # "--input" or positional "output"
short_name: str # "-i" or null
help_text: str # Help description text
arg_type: int # STRING, INT, BOOL
action: int # STORE, STORE_TRUE, STORE_FALSE, COUNT
default_int: int # Default value for integer types
default_str: str # Default value for string types
required: bool # Whether this argument is mandatory
is_positional: bool # True for positional parameters
dest: str # Lookup key ("--input" → "input")
# ============================================================
# ParsedArgs — Parsing result container
# ============================================================
class ParsedArgs:
_ht: hashtable.HashTable*
__mbuddy__: memhub.MemBuddy*
def __new__(self, mb: memhub.MemBuddy*) -> t.CPtr:
return mb.alloc(16)
def __init__(self, mb: memhub.MemBuddy*):
self.__mbuddy__ = mb
self._ht = hashtable.HashTable(mb)
def get_str(self, name: str) -> str:
"""Fetch string value. Returns None if key not found."""
if self._ht.__contains__(name) == 0:
return None
r: str = self._ht[name]
return r
def get_int(self, name: str) -> int:
"""Fetch integer value. Returns 0 if key not found."""
if self._ht.__contains__(name) == 0:
return 0
ptr: t.CPtr = self._ht[name]
return *(int*(ptr))
def get_bool(self, name: str) -> bool:
"""Fetch boolean value. Returns false if key not found."""
if self._ht.__contains__(name) == 0:
return False
ptr: rawptr = self._ht[name]
return *(int*(ptr)) != 0
def has(self, name: str) -> bool:
"""Check whether specified key exists."""
return self._ht.__contains__(name) != 0
def _set_str(self, name: str, val: str):
self._ht.set_str(name, val)
def _set_int(self, name: str, val: int):
self._ht.set_int(name, val)
def _set_bool(self, name: str, val: bool):
if val:
self._ht.set_int(name, 1)
else:
self._ht.set_int(name, 0)
# ============================================================
# ArgumentParser — Command line argument parser
# ============================================================
class ArgumentParser:
_prog: str
_description: str
_args: Argument*
_arg_count: int
__mbuddy__: memhub.MemBuddy*
def __new__(self, prog: str = "", description: str = "",
pool: memhub.MemBuddy* = None) -> rawptr:
mb: memhub.MemBuddy* = pool
if mb is None:
mb = _mbuddy
ret: rawptr = mb.alloc(40)
return ret
def __init__(self, prog: str = "", description: str = "",
pool: memhub.MemBuddy* = None):
mb: memhub.MemBuddy* = pool
if mb is None:
mb = _mbuddy
self.__mbuddy__ = mb
self._prog = prog
self._description = description
self._arg_count = 0
self._args = mb.alloc(MAX_ARGS * Argument.__sizeof__())
def add_argument(self, name: str = "", short: str = None,
arg_type: int = 0, default: int = 0,
default_str: str = None, required: bool = False,
action: int = 0, help: str = None):
"""Register a command line argument.
Args:
name: Argument name ("--input" or positional "output")
short: Short option ("-i"), optional
arg_type: Data type (STRING/INT/BOOL)
default: Default integer value
default_str: Default string value
required: Mandatory flag
action: Action mode (STORE/STORE_TRUE/STORE_FALSE/COUNT)
help: Help description text
"""
if self._arg_count >= MAX_ARGS:
return
idx: int = self._arg_count
arg: Argument* = t.CPtr(uint64(self._args) + idx * Argument.__sizeof__())
arg.name = name
arg.short_name = short
arg.help_text = help
arg.arg_type = arg_type
arg.action = action
arg.default_int = default
arg.default_str = default_str
arg.required = required
# Determine positional flag
if name is not None and name[0] != '-':
arg.is_positional = True
arg.dest = name
else:
arg.is_positional = False
if name is not None and name[0] == '-' and name[1] == '-':
arg.dest = name + 2
else:
arg.dest = name + 1
self._arg_count = idx + 1
def _get_arg(self, idx: int) -> Argument*:
return t.CPtr(uint64(self._args) + idx * Argument.__sizeof__())
def _find_long(self, token: str) -> int:
"""Exact match for long option name"""
for i in range(int(self._arg_count)):
arg: Argument* = self._get_arg(i)
if string.strcmp(arg.name, token) == 0:
return i
return -1
def _find_long_prefix(self, token: str) -> int:
"""Match prefix for --name=value syntax"""
for i in range(int(self._arg_count)):
arg: Argument* = self._get_arg(i)
name_len: int = int(string.strlen(arg.name)) # 显式 cast:避免 64→32 隐式窄化
if string.strncmp(token, arg.name, name_len) == 0:
if token[name_len] == '=':
return i
return -1
def _find_short(self, token: str) -> int:
for i in range(int(self._arg_count)):
arg: Argument* = self._get_arg(i)
if arg.short_name is not None:
if string.strcmp(arg.short_name, token) == 0:
return i
return -1
def _find_positional(self, pos_idx: int) -> int:
count: int = 0
for i in range(int(self._arg_count)):
arg: Argument* = self._get_arg(i)
if arg.is_positional:
if count == pos_idx:
return i
count += 1
return -1
def _store_value(self, args: ParsedArgs*,
arg: Argument*, value: str):
if arg.arg_type == INT:
args._set_int(arg.dest, string.atoi(value))
elif arg.arg_type == STRING:
args._set_str(arg.dest, value)
elif arg.arg_type == BOOL:
v: int = string.atoi(value)
args._set_bool(arg.dest, v != 0)
def _apply_action(self, args: ParsedArgs*, arg: Argument*):
if arg.action == STORE_TRUE:
args._set_bool(arg.dest, True)
elif arg.action == STORE_FALSE:
args._set_bool(arg.dest, False)
elif arg.action == COUNT:
cur: int = args.get_int(arg.dest)
args._set_int(arg.dest, cur + 1)
def parse_args(self, argc: int, argv: str*) -> ParsedArgs*:
"""Parse command line arguments.
Args:
argc: Argument count
argv: Argument array (char**, argv[0] = program name)
Returns:
Pointer to ParsedArgs; returns None on failure
"""
if self.__mbuddy__ is None:
return None
args: ParsedArgs* = ParsedArgs(self.__mbuddy__)
if args is None:
return None
# Initialize default values
i: int = 0
while i < self._arg_count:
arg: Argument* = self._get_arg(i)
if arg.action == STORE:
if arg.arg_type == INT:
args._set_int(arg.dest, arg.default_int)
elif arg.arg_type == STRING:
# A NULL default means "no default": skip the insert. Otherwise the key exists
# with a NULL value, has() reports the argument as present, and the required
# check at the end of this function can never fire for a string argument.
if arg.default_str is not None:
args._set_str(arg.dest, arg.default_str)
elif arg.action == STORE_TRUE:
args._set_bool(arg.dest, False)
elif arg.action == STORE_FALSE:
args._set_bool(arg.dest, True)
elif arg.action == COUNT:
args._set_int(arg.dest, 0)
i += 1
# Traverse argv
idx: int = 1
pos_idx: int = 0
token: str = None
found: int = 0
arg: Argument* = None
value: str = None
# argv is char** (`str | t.CPtr`); argv[idx] directly gives the char* at that position.
# Do NOT write `str*(uint64(argv) + idx*8)`: the right-hand side would be i8**, and TPC's assignment path dereferences a "pointer to pointer" rvalue one extra time (an extra load) -> a wild pointer.
while idx < argc:
token = argv[idx]
if token is None:
break
# "--" terminator: all subsequent tokens become positional arguments
if token[0] == '-' and token[1] == '-' and token[2] == 0:
idx += 1
while idx < argc:
token = argv[idx]
found = self._find_positional(pos_idx)
if found < 0:
stdio.printf("error: unexpected argument '%s'\n", token)
release(args)
return None
arg = self._get_arg(found)
self._store_value(args, arg, token)
pos_idx += 1
idx += 1
break
# Long option --name or --name=value
if token[0] == '-' and token[1] == '-':
has_eq: bool = False
p: str = token
while p[0] != 0:
if p[0] == '=':
has_eq = True
break
p += 1
if has_eq:
# --name=value syntax
found = self._find_long_prefix(token)
if found < 0:
stdio.printf("error: unrecognized argument '%s'\n", token)
release(args)
return None
arg = self._get_arg(found)
arg_name_len: int = int(string.strlen(arg.name)) # 显式 cast:避免 64→32 隐式窄化
value = token + arg_name_len + 1
self._store_value(args, arg, value)
idx += 1
else:
# --name with value in next token or flag action
found = self._find_long(token)
if found < 0:
stdio.printf("error: unrecognized argument '%s'\n", token)
release(args)
return None
arg = self._get_arg(found)
if arg.action == STORE:
if idx + 1 >= argc:
stdio.printf("error: %s requires a value\n", arg.name)
release(args)
return None
value = argv[idx + 1]
self._store_value(args, arg, value)
idx += 2
else:
self._apply_action(args, arg)
idx += 1
# Short option -x
elif token[0] == '-':
found = self._find_short(token)
if found < 0:
stdio.printf("error: unrecognized argument '%s'\n", token)
release(args)
return None
arg = self._get_arg(found)
if arg.action == STORE:
if idx + 1 >= argc:
stdio.printf("error: %s requires a value\n", arg.name)
release(args)
return None
value = argv[idx + 1]
self._store_value(args, arg, value)
idx += 2
else:
self._apply_action(args, arg)
idx += 1
# Positional argument
else:
found = self._find_positional(pos_idx)
if found < 0:
stdio.printf("error: unexpected argument '%s'\n", token)
release(args)
return None
arg = self._get_arg(found)
self._store_value(args, arg, token)
pos_idx += 1
idx += 1
# Validate required arguments
for i in range(int(self._arg_count)):
arg = self._get_arg(i)
if arg.required:
if not args.has(arg.dest):
stdio.printf("error: missing required argument '%s'\n", arg.name)
release(args)
return None
return args
def print_help(self):
"""Print complete help message"""
stdio.printf("usage: %s [options]\n", self._prog)
if self._description is not None and self._description[0] != 0:
stdio.printf("\n%s\n", self._description)
stdio.printf("\npositional arguments:\n")
i: int = 0
while i < self._arg_count:
arg: Argument* = self._get_arg(i)
if arg.is_positional:
ht: str = arg.help_text
if ht is None:
ht = ""
stdio.printf(" %-20s %s\n", arg.name, ht)
i += 1
stdio.printf("\noptional arguments:\n")
for i in range(int(self._arg_count)):
arg = self._get_arg(i)
if not arg.is_positional:
ht: str = arg.help_text
if ht is None:
ht = ""
if arg.short_name is not None:
stdio.printf(" %s, %-18s %s\n", arg.short_name, arg.name, ht)
else:
stdio.printf(" %-22s %s\n", arg.name, ht)
def print_usage(self):
"""Print brief usage summary"""
stdio.printf("usage: %s [options]\n", self._prog)
# ============================================================
# release — Release ParsedArgs and all internal resources
# ============================================================
def release(args: ParsedArgs*):
"""Free ParsedArgs object and its inner HashTable resources."""
if args is None: return
if args.__mbuddy__ is None: return
if args._ht is not None:
# Split the chained attribute access with a temp variable to avoid cross-module type inference failure for args._ht.__slots__.
# Note: the type annotation must be hashtable.HashTable (module-prefixed), otherwise the compiler cannot resolve HashTable and ht.__slots__ access fails.
ht: hashtable.HashTable* = args._ht
if ht.__slots__ is not None:
args.__mbuddy__.free(ht.__slots__)
args.__mbuddy__.free(ht)
args.__mbuddy__.free(args)