brintos

brintos / llvm-project-archived public Read only

0
0
Text · 17.8 KiB · c1baad3 Raw
464 lines · python
1"""2This module implements a couple of utility classes to make writing3lldb parsed commands more Pythonic.4The way to use it is to make a class for your command that inherits from ParsedCommandBase.5That will make an LLDBOptionValueParser which you will use for your6option definition, and to fetch option values for the current invocation7of your command.  For concision, I'll call this the `OVParser`.  8Access to the `OVParser` is through:9 10ParsedCommandBase.get_parser()11 12Next, implement setup_command_definition() in your new command class, and call:13 14  self.get_parser().add_option()15 16to add all your options.  The order doesn't matter for options, lldb will sort them17alphabetically for you when it prints help.18 19Similarly you can define the arguments with:20 21  self.get_parser().add_argument()22 23At present, lldb doesn't do as much work as it should verifying arguments, it24only checks that commands that take no arguments don't get passed arguments.25 26Then implement the execute function for your command as:27 28    def __call__(self, debugger, args_list, exe_ctx, result):29 30The arguments will be a list of strings.  31 32You can access the option values using the 'dest' string you passed in when defining the option.33And if you need to know whether a given option was set by the user or not, you can34use the was_set API.  35 36So for instance, if you have an option whose "dest" is "my_option", then:37 38    self.get_parser().my_option39 40will fetch the value, and:41 42    self.get_parser().was_set("my_option")43 44will return True if the user set this option, and False if it was left at its default45value.46 47Custom Completions:48 49You can also implement custom completers for your custom command, either for the50arguments to your command or to the option values in your command.  If you use enum51values or if your option/argument uses is one of the types we have completers for,52you should not need to do this.  But if you have your own completeable types, or if53you want completion of one option to be conditioned by other options on the command54line, you can use this interface to take over the completion.  55 56You can choose to add a completion for the option values defined for your command,57or for the arguments, separately.  For the option values, define:58 59def handle_option_argument_completion(self, long_option, cursor_pos):60 61The line to be completed will be parsed up to the option containint the cursor position, 62and the values will be set in the OptionValue parser object.  long_option will be63the option name containing the cursor, and cursor_pos will be the position of the cursor64in that option's value.  You can call the `OVParser` method: `dest_for_option(long_option)` 65to get the value for that option.  The other options that came before the cursor in the command66line will also be set in the `OVParser` when the completion handler is called.67 68For argument values, define:69 70def handle_argument_completion(self, args, arg_pos, cursor_pos):71 72Again, the command line will be parsed up to the cursor position, and all the options73before the cursor pose will be set in the `OVParser`.  args is a python list of the74arguments, arg_pos is the index of the argument with the cursor, and cursor_pos is75the position of the cursor in the argument.76 77In both cases, the return value determines the completion.78 79Return False to mean "Not Handled" - in which case lldb will fall back on the80standard completion machinery.81 82Return True to mean "Handled with no completions".83 84If there is a single unique completion, return a Python dictionary with two elements:85 86return {"completion" : "completed_value", "mode" : <"partial", "complete">}87 88If the mode is "partial", then the completion is to a common base, if it is "complete"89then the argument is considered done - mostly meaning lldb will put a space after the90completion string.  "complete" is the default if no "mode" is specified.91 92If there are multiple completion options, then return:93 94return {"values" : ["option1", "option2"]}95 96Optionally, you can return a parallel array of "descriptions" which the completer will 97print alongside the options:98 99return {"values" : ["option1", "option2"], "descriptions" : ["the first option", "the second option"]}100 101The cmdtemplate example currently uses the parsed command infrastructure:102 103llvm-project/lldb/examples/python/cmdtemplate.py104 105There are also a few example commands in the lldb testsuite at:106 107llvm-project/lldb/test/API/commands/command/script/add/test_commands.py108"""109import inspect110import lldb111import sys112from abc import abstractmethod113 114# Some methods to translate common value types.  Should return a115# tuple of the value and an error value (True => error) if the116# type can't be converted.  These are called internally when the117# command line is parsed into the 'dest' properties, you should118# not need to call them directly.119# FIXME: Need a way to push the conversion error string back to lldb.120def to_bool(in_value):121    error = True122    value = False123    if type(in_value) != str or len(in_value) == 0:124        return (value, error)125 126    low_in = in_value.lower()127    if low_in in ["y", "yes", "t", "true", "1"]:128        value = True129        error = False130        131    if not value and low_in in ["n", "no", "f", "false", "0"]:132        value = False133        error = False134 135    return (value, error)136 137def to_int(in_value):138    #FIXME: Not doing errors yet...139    return (int(in_value), False)140 141def to_unsigned(in_value):142    # FIXME: find an unsigned converter...143    # And handle errors.144    return (int(in_value), False)145 146translators = {147    lldb.eArgTypeBoolean : to_bool,148    lldb.eArgTypeBreakpointID : to_unsigned,149    lldb.eArgTypeByteSize : to_unsigned,150    lldb.eArgTypeCount : to_unsigned,151    lldb.eArgTypeFrameIndex : to_unsigned,152    lldb.eArgTypeIndex : to_unsigned,153    lldb.eArgTypeLineNum : to_unsigned,154    lldb.eArgTypeNumLines : to_unsigned,155    lldb.eArgTypeNumberPerLine : to_unsigned,156    lldb.eArgTypeOffset : to_int,157    lldb.eArgTypeThreadIndex : to_unsigned,158    lldb.eArgTypeUnsignedInteger : to_unsigned,159    lldb.eArgTypeWatchpointID : to_unsigned,160    lldb.eArgTypeColumnNum : to_unsigned,161    lldb.eArgTypeRecognizerID : to_unsigned,162    lldb.eArgTypeTargetID : to_unsigned,163    lldb.eArgTypeStopHookID : to_unsigned164}165 166def translate_value(value_type, value):167    try:168        return translators[value_type](value)169    except KeyError:170        # If we don't have a translator, return the string value.171        return (value, False)172 173class LLDBOptionValueParser:174    """175    This class holds the option definitions for the command, and when176    the command is run, you can ask the parser for the current values.  """177 178    def __init__(self):179        # This is a dictionary of dictionaries.  The key is the long option180        # name, and the value is the rest of the definition.181        self.options_dict = {}182        self.args_array = []183 184 185    # FIXME: would this be better done on the C++ side?186    # The common completers are missing some useful ones.187    # For instance there really should be a common Type completer188    # And an "lldb command name" completer.189    completion_table = {190        lldb.eArgTypeAddressOrExpression : lldb.eVariablePathCompletion,191        lldb.eArgTypeArchitecture : lldb.eArchitectureCompletion,192        lldb.eArgTypeBreakpointID : lldb.eBreakpointCompletion,193        lldb.eArgTypeBreakpointIDRange : lldb.eBreakpointCompletion,194        lldb.eArgTypeBreakpointName : lldb.eBreakpointNameCompletion,195        lldb.eArgTypeClassName : lldb.eSymbolCompletion,196        lldb.eArgTypeDirectoryName : lldb.eDiskDirectoryCompletion,197        lldb.eArgTypeExpression : lldb.eVariablePathCompletion,198        lldb.eArgTypeExpressionPath : lldb.eVariablePathCompletion,199        lldb.eArgTypeFilename : lldb.eDiskFileCompletion,200        lldb.eArgTypeFrameIndex : lldb.eFrameIndexCompletion,201        lldb.eArgTypeFunctionName : lldb.eSymbolCompletion,202        lldb.eArgTypeFunctionOrSymbol : lldb.eSymbolCompletion,203        lldb.eArgTypeLanguage : lldb.eTypeLanguageCompletion,204        lldb.eArgTypePath : lldb.eDiskFileCompletion,205        lldb.eArgTypePid : lldb.eProcessIDCompletion,206        lldb.eArgTypeProcessName : lldb.eProcessNameCompletion,207        lldb.eArgTypeRegisterName : lldb.eRegisterCompletion,208        lldb.eArgTypeRunArgs : lldb.eDiskFileCompletion,209        lldb.eArgTypeShlibName : lldb.eModuleCompletion,210        lldb.eArgTypeSourceFile : lldb.eSourceFileCompletion,211        lldb.eArgTypeSymbol : lldb.eSymbolCompletion,212        lldb.eArgTypeThreadIndex : lldb.eThreadIndexCompletion,213        lldb.eArgTypeVarName : lldb.eVariablePathCompletion,214        lldb.eArgTypePlatform : lldb.ePlatformPluginCompletion,215        lldb.eArgTypeWatchpointID : lldb.eWatchpointIDCompletion,216        lldb.eArgTypeWatchpointIDRange : lldb.eWatchpointIDCompletion,217        lldb.eArgTypeModuleUUID : lldb.eModuleUUIDCompletion,218        lldb.eArgTypeStopHookID : lldb.eStopHookIDCompletion219    }220 221    @classmethod222    def determine_completion(cls, arg_type):223        return cls.completion_table.get(arg_type, lldb.eNoCompletion)224 225    def add_argument_set(self, arguments):226        self.args_array.append(arguments)227 228    def get_option_element(self, long_name):229        return self.options_dict.get(long_name, None)230 231    def is_enum_opt(self, opt_name):232        elem = self.get_option_element(opt_name)233        if not elem:234            return False235        return "enum_values" in elem236 237    def option_parsing_started(self):238        """ This makes the ivars for all the "dest" values in the array and gives them239            their default values.  You should not have to call this by hand, though if240            you have some option that needs to do some work when a new command invocation241            starts, you can override this to handle your special option.  """242        for key, elem in self.options_dict.items():243            elem['_value_set'] = False244            # If there's no value_type, then there can't be a dest.245            if not "value_type" in elem:246                continue247 248            try:249                object.__setattr__(self, elem["dest"], elem["default"])250            except AttributeError:251                # It isn't an error not to have a "dest" variable name, you'll252                # just have to manage this option's value on your own.253                continue254            except KeyError:255                continue256 257    def set_enum_value(self, enum_values, input):258        """ This sets the value for an enum option, you should not have to call this259        by hand.  """260        candidates = []261        for candidate in enum_values:262            # The enum_values are a two element list of value & help string.263            value = candidate[0]264            if value.startswith(input):265                candidates.append(value)266 267        if len(candidates) == 1:268            return (candidates[0], False)269        else:270            return (input, True)271        272    def set_option_value(self, exe_ctx, opt_name, opt_value):273        """ This sets a single option value.  This will handle most option274        value types, but if you have an option that has some complex behavior,275        you can override this to implement that behavior, and then pass the276        rest of the options to the base class implementation. """277        elem = self.get_option_element(opt_name)278        if not elem:279            return False280 281        # If there's no value_type in element, then it has no value, so just mark282        # it set and return:283        if not "value_type" in elem:284            elem["_value_set"] = True285            return True286 287        if "enum_values" in elem:288            (value, error) = self.set_enum_value(elem["enum_values"], opt_value)289        else:290            (value, error)  = translate_value(elem["value_type"], opt_value)291 292        if error:293            return False294        295        object.__setattr__(self, elem["dest"], value)296        elem["_value_set"] = True297        return True298 299    def was_set(self, opt_name):300        """Call this in the __call__ method of your command to determine301        whether this option was set on the command line.  It is sometimes302        useful to know whether an option has the default value because the303        user set it explicitly (was_set -> True) or not.304        You can also call this in a handle_completion method, but it will305        currently only report true values for the options mentioned306        BEFORE the cursor point in the command line.307        """308 309        elem = self.get_option_element(opt_name)310        if not elem:311            return False312        try:313            return elem["_value_set"]314        except AttributeError:315            return False316 317    def dest_for_option(self, opt_name):318        """This will return the value of the dest variable you defined for opt_name.319        Mostly useful for handle_completion where you get passed the long option.320        """321        elem = self.get_option_element(opt_name)322        if not elem:323            return None324        value = self.__dict__[elem["dest"]]325        return value326 327    def add_option(328        self,329        short_option,330        long_option,331        help,332        default=None,333        dest=None,334        required=False,335        groups=None,336        value_type=None,337        completion_type=None,338        enum_values=None,339    ):340        """341        short_option: one character, must be unique, not required342        long_option: no spaces, must be unique, required343        help: a usage string for this option, will print in the command help344        default: the initial value for this option (if it has a value)345        dest: the name of the property that gives you access to the value for346                 this value.  Defaults to the long option if not provided.347        required: if true, this option must be provided or the command will error out348        groups: Which "option groups" does this option belong to.  This can either be349                a simple list (e.g. [1, 3, 4, 5]) or you can specify ranges by sublists:350                so [1, [3,5]] is the same as [1, 3, 4, 5].351        value_type: one of the lldb.eArgType enum values.  Some of the common arg352                    types also have default completers, which will be applied automatically.353        completion_type: currently these are values form the lldb.CompletionType enum.  If354                         you need custom completions, implement handle_option_argument_completion.355        enum_values: An array of duples: ["element_name", "element_help"].  If provided,356                     only one of the enum elements is allowed.  The value will be the357                     element_name for the chosen enum element as a string.358        """359        if not dest:360            dest = long_option361 362        if not completion_type:363            completion_type = self.determine_completion(value_type)364 365        dict = {366            "short_option": short_option,367            "required": required,368            "help": help,369        }370 371        if enum_values:372            if not value_type:373                print("I am setting value type for an enum value")374                value_type = lldb.eArgTypeNone375            else:376                print(f"An enum value had a type: {value_type}")377            dict["enum_values"] = enum_values378 379        if value_type:380            dict["value_type"] = value_type381            dict["completion_type"] = completion_type382            dict["dest"] = dest383            dict["default"] = default384 385        if groups:386            dict["groups"] = groups387 388        self.options_dict[long_option] = dict389 390    def make_argument_element(self, arg_type, repeat = "optional", groups = None):391        element = {"arg_type" : arg_type, "repeat" : repeat}392        if groups:393            element["groups"] = groups394        return element395 396class ParsedCommand:397    def __init__(self, debugger, unused):398        self.debugger = debugger399        self.ov_parser = LLDBOptionValueParser()400        self.setup_command_definition()401        402    def get_options_definition(self):403        return self.get_parser().options_dict404 405    def get_flags(self):406        return 0407 408    def get_args_definition(self):409        return self.get_parser().args_array410 411    # The base class will handle calling these methods412    # when appropriate.413    414    def option_parsing_started(self):415        self.get_parser().option_parsing_started()416 417    def set_option_value(self, exe_ctx, opt_name, opt_value):418        return self.get_parser().set_option_value(exe_ctx, opt_name, opt_value)419 420    def get_parser(self):421        """Returns the option value parser for this command.422        When defining the command, use the parser to add423        argument and option definitions to the command.424        When you are in the command callback, the parser425        gives you access to the options passes to this426        invocation"""427 428        return self.ov_parser429 430    # These are the two "pure virtual" methods:431    @abstractmethod432    def __call__(self, debugger, args_array, exe_ctx, result):433        """This is the command callback.  The option values are434        provided by the 'dest' properties on the parser.435    436        args_array: This is the list of arguments provided.437        exe_ctx: Gives the SBExecutionContext on which the438                 command should operate.439        result:  Any results of the command should be440                 written into this SBCommandReturnObject.441        """442        raise NotImplementedError()443 444    @abstractmethod445    def setup_command_definition(self):446        """This will be called when your command is added to447        the command interpreter.  Here is where you add your448        options and argument definitions for the command."""449        raise NotImplementedError()450 451    @staticmethod452    def do_register_cmd(cls, debugger, module_name):453        """ Add any commands contained in this module to LLDB """454        command = "command script add -o -p -c %s.%s %s" % (455            module_name,456            cls.__name__,457            cls.program,458        )459        debugger.HandleCommand(command)460        print(461            'The "{0}" command has been installed, type "help {0}"'462            'for detailed help.'.format(cls.program)463        )464