aboutsummaryrefslogtreecommitdiff
path: root/lldb/examples/python/templates/parsed_cmd.py
blob: 13d6eae405c08ddcb50590e4e4fd6f1649d54f10 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
"""
This module implements a couple of utility classes to make writing
lldb parsed commands more Pythonic.
The way to use it is to make a class for your command that inherits from ParsedCommandBase.
That will make an LLDBOptionValueParser which you will use for your
option definition, and to fetch option values for the current invocation
of your command.  For concision, I'll call this the `OVParser`.  
Access to the `OVParser` is through:

ParsedCommandBase.get_parser()

Next, implement setup_command_definition() in your new command class, and call:

  self.get_parser().add_option()

to add all your options.  The order doesn't matter for options, lldb will sort them
alphabetically for you when it prints help.

Similarly you can define the arguments with:

  self.get_parser().add_argument()

At present, lldb doesn't do as much work as it should verifying arguments, it
only checks that commands that take no arguments don't get passed arguments.

Then implement the execute function for your command as:

    def __call__(self, debugger, args_list, exe_ctx, result):

The arguments will be a list of strings.  

You can access the option values using the 'dest' string you passed in when defining the option.
And if you need to know whether a given option was set by the user or not, you can
use the was_set API.  

So for instance, if you have an option whose "dest" is "my_option", then:

    self.get_parser().my_option

will fetch the value, and:

    self.get_parser().was_set("my_option")

will return True if the user set this option, and False if it was left at its default
value.

Custom Completions:

You can also implement custom completers for your custom command, either for the
arguments to your command or to the option values in your command.  If you use enum
values or if your option/argument uses is one of the types we have completers for,
you should not need to do this.  But if you have your own completeable types, or if
you want completion of one option to be conditioned by other options on the command
line, you can use this interface to take over the completion.  

You can choose to add a completion for the option values defined for your command,
or for the arguments, separately.  For the option values, define:

def handle_option_argument_completion(self, long_option, cursor_pos):

The line to be completed will be parsed up to the option containint the cursor position, 
and the values will be set in the OptionValue parser object.  long_option will be
the option name containing the cursor, and cursor_pos will be the position of the cursor
in that option's value.  You can call the `OVParser` method: `dest_for_option(long_option)` 
to get the value for that option.  The other options that came before the cursor in the command
line will also be set in the `OVParser` when the completion handler is called.

For argument values, define:

def handle_argument_completion(self, args, arg_pos, cursor_pos):

Again, the command line will be parsed up to the cursor position, and all the options
before the cursor pose will be set in the `OVParser`.  args is a python list of the
arguments, arg_pos is the index of the argument with the cursor, and cursor_pos is
the position of the cursor in the argument.

In both cases, the return value determines the completion.

Return False to mean "Not Handled" - in which case lldb will fall back on the
standard completion machinery.

Return True to mean "Handled with no completions".

If there is a single unique completion, return a Python dictionary with two elements:

return {"completion" : "completed_value", "mode" : <"partial", "complete">}

If the mode is "partial", then the completion is to a common base, if it is "complete"
then the argument is considered done - mostly meaning lldb will put a space after the
completion string.  "complete" is the default if no "mode" is specified.

If there are multiple completion options, then return:

return {"values" : ["option1", "option2"]}

Optionally, you can return a parallel array of "descriptions" which the completer will 
print alongside the options:

return {"values" : ["option1", "option2"], "descriptions" : ["the first option", "the second option"]}

The cmdtemplate example currently uses the parsed command infrastructure:

llvm-project/lldb/examples/python/cmdtemplate.py

There are also a few example commands in the lldb testsuite at:

llvm-project/lldb/test/API/commands/command/script/add/test_commands.py
"""
import inspect
import lldb
import sys
from abc import abstractmethod

# Some methods to translate common value types.  Should return a
# tuple of the value and an error value (True => error) if the
# type can't be converted.  These are called internally when the
# command line is parsed into the 'dest' properties, you should
# not need to call them directly.
# FIXME: Need a way to push the conversion error string back to lldb.
def to_bool(in_value):
    error = True
    value = False
    if type(in_value) != str or len(in_value) == 0:
        return (value, error)

    low_in = in_value.lower()
    if low_in in ["y", "yes", "t", "true", "1"]:
        value = True
        error = False
        
    if not value and low_in in ["n", "no", "f", "false", "0"]:
        value = False
        error = False

    return (value, error)

def to_int(in_value):
    #FIXME: Not doing errors yet...
    return (int(in_value), False)

def to_unsigned(in_value):
    # FIXME: find an unsigned converter...
    # And handle errors.
    return (int(in_value), False)

translators = {
    lldb.eArgTypeBoolean : to_bool,
    lldb.eArgTypeBreakpointID : to_unsigned,
    lldb.eArgTypeByteSize : to_unsigned,
    lldb.eArgTypeCount : to_unsigned,
    lldb.eArgTypeFrameIndex : to_unsigned,
    lldb.eArgTypeIndex : to_unsigned,
    lldb.eArgTypeLineNum : to_unsigned,
    lldb.eArgTypeNumLines : to_unsigned,
    lldb.eArgTypeNumberPerLine : to_unsigned,
    lldb.eArgTypeOffset : to_int,
    lldb.eArgTypeThreadIndex : to_unsigned,
    lldb.eArgTypeUnsignedInteger : to_unsigned,
    lldb.eArgTypeWatchpointID : to_unsigned,
    lldb.eArgTypeColumnNum : to_unsigned,
    lldb.eArgTypeRecognizerID : to_unsigned,
    lldb.eArgTypeTargetID : to_unsigned,
    lldb.eArgTypeStopHookID : to_unsigned
}

def translate_value(value_type, value):
    try:
        return translators[value_type](value)
    except KeyError:
        # If we don't have a translator, return the string value.
        return (value, False)

class LLDBOptionValueParser:
    """
    This class holds the option definitions for the command, and when
    the command is run, you can ask the parser for the current values.  """

    def __init__(self):
        # This is a dictionary of dictionaries.  The key is the long option
        # name, and the value is the rest of the definition.
        self.options_dict = {}
        self.args_array = []


    # FIXME: would this be better done on the C++ side?
    # The common completers are missing some useful ones.
    # For instance there really should be a common Type completer
    # And an "lldb command name" completer.
    completion_table = {
        lldb.eArgTypeAddressOrExpression : lldb.eVariablePathCompletion,
        lldb.eArgTypeArchitecture : lldb.eArchitectureCompletion,
        lldb.eArgTypeBreakpointID : lldb.eBreakpointCompletion,
        lldb.eArgTypeBreakpointIDRange : lldb.eBreakpointCompletion,
        lldb.eArgTypeBreakpointName : lldb.eBreakpointNameCompletion,
        lldb.eArgTypeClassName : lldb.eSymbolCompletion,
        lldb.eArgTypeDirectoryName : lldb.eDiskDirectoryCompletion,
        lldb.eArgTypeExpression : lldb.eVariablePathCompletion,
        lldb.eArgTypeExpressionPath : lldb.eVariablePathCompletion,
        lldb.eArgTypeFilename : lldb.eDiskFileCompletion,
        lldb.eArgTypeFrameIndex : lldb.eFrameIndexCompletion,
        lldb.eArgTypeFunctionName : lldb.eSymbolCompletion,
        lldb.eArgTypeFunctionOrSymbol : lldb.eSymbolCompletion,
        lldb.eArgTypeLanguage : lldb.eTypeLanguageCompletion,
        lldb.eArgTypePath : lldb.eDiskFileCompletion,
        lldb.eArgTypePid : lldb.eProcessIDCompletion,
        lldb.eArgTypeProcessName : lldb.eProcessNameCompletion,
        lldb.eArgTypeRegisterName : lldb.eRegisterCompletion,
        lldb.eArgTypeRunArgs : lldb.eDiskFileCompletion,
        lldb.eArgTypeShlibName : lldb.eModuleCompletion,
        lldb.eArgTypeSourceFile : lldb.eSourceFileCompletion,
        lldb.eArgTypeSymbol : lldb.eSymbolCompletion,
        lldb.eArgTypeThreadIndex : lldb.eThreadIndexCompletion,
        lldb.eArgTypeVarName : lldb.eVariablePathCompletion,
        lldb.eArgTypePlatform : lldb.ePlatformPluginCompletion,
        lldb.eArgTypeWatchpointID : lldb.eWatchpointIDCompletion,
        lldb.eArgTypeWatchpointIDRange : lldb.eWatchpointIDCompletion,
        lldb.eArgTypeModuleUUID : lldb.eModuleUUIDCompletion,
        lldb.eArgTypeStopHookID : lldb.eStopHookIDCompletion
    }

    @classmethod
    def determine_completion(cls, arg_type):
        return cls.completion_table.get(arg_type, lldb.eNoCompletion)

    def add_argument_set(self, arguments):
        self.args_array.append(arguments)

    def get_option_element(self, long_name):
        return self.options_dict.get(long_name, None)

    def is_enum_opt(self, opt_name):
        elem = self.get_option_element(opt_name)
        if not elem:
            return False
        return "enum_values" in elem

    def option_parsing_started(self):
        """ This makes the ivars for all the "dest" values in the array and gives them
            their default values.  You should not have to call this by hand, though if
            you have some option that needs to do some work when a new command invocation
            starts, you can override this to handle your special option.  """
        for key, elem in self.options_dict.items():
            elem['_value_set'] = False
            try:
                object.__setattr__(self, elem["dest"], elem["default"])
            except AttributeError:
                # It isn't an error not to have a "dest" variable name, you'll
                # just have to manage this option's value on your own.
                continue

    def set_enum_value(self, enum_values, input):
        """ This sets the value for an enum option, you should not have to call this
        by hand.  """
        candidates = []
        for candidate in enum_values:
            # The enum_values are a two element list of value & help string.
            value = candidate[0]
            if value.startswith(input):
                candidates.append(value)

        if len(candidates) == 1:
            return (candidates[0], False)
        else:
            return (input, True)
        
    def set_option_value(self, exe_ctx, opt_name, opt_value):
        """ This sets a single option value.  This will handle most option
        value types, but if you have an option that has some complex behavior,
        you can override this to implement that behavior, and then pass the
        rest of the options to the base class implementation. """
        elem = self.get_option_element(opt_name)
        if not elem:
            return False
        
        if "enum_values" in elem:
            (value, error) = self.set_enum_value(elem["enum_values"], opt_value)
        else:
            (value, error)  = translate_value(elem["value_type"], opt_value)

        if error:
            return False
        
        object.__setattr__(self, elem["dest"], value)
        elem["_value_set"] = True
        return True

    def was_set(self, opt_name):
        """Call this in the __call__ method of your command to determine
        whether this option was set on the command line.  It is sometimes
        useful to know whether an option has the default value because the
        user set it explicitly (was_set -> True) or not.
        You can also call this in a handle_completion method, but it will
        currently only report true values for the options mentioned
        BEFORE the cursor point in the command line.
        """

        elem = self.get_option_element(opt_name)
        if not elem:
            return False
        try:
            return elem["_value_set"]
        except AttributeError:
            return False

    def dest_for_option(self, opt_name):
        """This will return the value of the dest variable you defined for opt_name.
        Mostly useful for handle_completion where you get passed the long option.
        """
        elem = self.get_option_element(opt_name)
        if not elem:
            return None
        value = self.__dict__[elem["dest"]]
        return value

    def add_option(self, short_option, long_option, help, default,
                   dest = None, required=False, groups = None,
                   value_type=lldb.eArgTypeNone, completion_type=None,
                   enum_values=None):
        """
        short_option: one character, must be unique, not required
        long_option: no spaces, must be unique, required
        help: a usage string for this option, will print in the command help
        default: the initial value for this option (if it has a value)
        dest: the name of the property that gives you access to the value for
                 this value.  Defaults to the long option if not provided.
        required: if true, this option must be provided or the command will error out
        groups: Which "option groups" does this option belong to.  This can either be
                a simple list (e.g. [1, 3, 4, 5]) or you can specify ranges by sublists:
                so [1, [3,5]] is the same as [1, 3, 4, 5].
        value_type: one of the lldb.eArgType enum values.  Some of the common arg
                    types also have default completers, which will be applied automatically.
        completion_type: currently these are values form the lldb.CompletionType enum.  If
                         you need custom completions, implement handle_option_argument_completion.
        enum_values: An array of duples: ["element_name", "element_help"].  If provided,
                     only one of the enum elements is allowed.  The value will be the
                     element_name for the chosen enum element as a string.
        """
        if not dest:
            dest = long_option

        if not completion_type:
            completion_type = self.determine_completion(value_type)
            
        dict = {"short_option" : short_option,
                "required" : required,
                "help" : help,
                "value_type" : value_type,
                "completion_type" : completion_type,
                "dest" : dest,
                "default" : default}

        if enum_values:
            dict["enum_values"] = enum_values
        if groups:
            dict["groups"] = groups

        self.options_dict[long_option] = dict

    def make_argument_element(self, arg_type, repeat = "optional", groups = None):
        element = {"arg_type" : arg_type, "repeat" : repeat}
        if groups:
            element["groups"] = groups
        return element

class ParsedCommand:
    def __init__(self, debugger, unused):
        self.debugger = debugger
        self.ov_parser = LLDBOptionValueParser()
        self.setup_command_definition()
        
    def get_options_definition(self):
        return self.get_parser().options_dict

    def get_flags(self):
        return 0

    def get_args_definition(self):
        return self.get_parser().args_array

    # The base class will handle calling these methods
    # when appropriate.
    
    def option_parsing_started(self):
        self.get_parser().option_parsing_started()

    def set_option_value(self, exe_ctx, opt_name, opt_value):
        return self.get_parser().set_option_value(exe_ctx, opt_name, opt_value)

    def get_parser(self):
        """Returns the option value parser for this command.
        When defining the command, use the parser to add
        argument and option definitions to the command.
        When you are in the command callback, the parser
        gives you access to the options passes to this
        invocation"""

        return self.ov_parser

    # These are the two "pure virtual" methods:
    @abstractmethod
    def __call__(self, debugger, args_array, exe_ctx, result):
        """This is the command callback.  The option values are
        provided by the 'dest' properties on the parser.
    
        args_array: This is the list of arguments provided.
        exe_ctx: Gives the SBExecutionContext on which the
                 command should operate.
        result:  Any results of the command should be
                 written into this SBCommandReturnObject.
        """
        raise NotImplementedError()

    @abstractmethod
    def setup_command_definition(self):
        """This will be called when your command is added to
        the command interpreter.  Here is where you add your
        options and argument definitions for the command."""
        raise NotImplementedError()

    @staticmethod
    def do_register_cmd(cls, debugger, module_name):
        """ Add any commands contained in this module to LLDB """
        command = "command script add -o -p -c %s.%s %s" % (
            module_name,
            cls.__name__,
            cls.program,
        )
        debugger.HandleCommand(command)
        print(
            'The "{0}" command has been installed, type "help {0}"'
            'for detailed help.'.format(cls.program)
        )