================ @@ -1255,6 +1258,676 @@ class CommandObjectScriptingObject : public CommandObjectRaw { CompletionType m_completion_type = eNoCompletion; }; + +/// This command implements a lldb parsed scripted command. The command +/// provides a definition of the options and arguments, and a option value +/// setting callback, and then the command's execution function gets passed +/// just the parsed arguments. +/// Note, implementing a command in Python using these base interfaces is a bit +/// of a pain, but it is much easier to export this low level interface, and +/// then make it nicer on the Python side, than to try to do that in a +/// script language neutral way. +/// So I've also added a base class in Python that provides a table-driven +/// way of defining the options and arguments, which automatically fills the +/// option values, making them available as properties in Python. +/// +class CommandObjectScriptingObjectParsed : public CommandObjectParsed { +private: + class CommandOptions : public Options { + public: + CommandOptions(CommandInterpreter &interpreter, + StructuredData::GenericSP cmd_obj_sp) : m_interpreter(interpreter), + m_cmd_obj_sp(cmd_obj_sp) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + ScriptInterpreter *scripter = + m_interpreter.GetDebugger().GetScriptInterpreter(); + if (!scripter) { + error.SetErrorString("No script interpreter for SetOptionValue."); + return error; + } + if (!m_cmd_obj_sp) { + error.SetErrorString("SetOptionValue called with empty cmd_obj."); + return error; + } + if (!m_options_definition_up) { + error.SetErrorString("SetOptionValue called before options definitions " + "were created."); + return error; + } + // Pass the long option, since you aren't actually required to have a + // short_option, and for those options the index or short option character + // aren't meaningful on the python side. + const char * long_option = + m_options_definition_up.get()[option_idx].long_option; + bool success = scripter->SetOptionValueForCommandObject(m_cmd_obj_sp, + execution_context, long_option, option_arg); + if (!success) + error.SetErrorStringWithFormatv("Error setting option: {0} to {1}", + long_option, option_arg); + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + ScriptInterpreter *scripter = + m_interpreter.GetDebugger().GetScriptInterpreter(); + if (!scripter) { + return; + } + if (!m_cmd_obj_sp) { + return; + } + scripter->OptionParsingStartedForCommandObject(m_cmd_obj_sp); + }; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + if (!m_options_definition_up) + return {}; + return llvm::ArrayRef(m_options_definition_up.get(), m_num_options); + } + + static bool ParseUsageMaskFromArray(StructuredData::ObjectSP obj_sp, + size_t counter, uint32_t &usage_mask, Status &error) { + // If the usage entry is not provided, we use LLDB_OPT_SET_ALL. + // If the usage mask is a UINT, the option belongs to that group. + // If the usage mask is a vector of UINT's, the option belongs to all the + // groups listed. + // If a subelement of the vector is a vector of two ints, then the option + // belongs to the inclusive range from the first to the second element. + if (!obj_sp) { + usage_mask = LLDB_OPT_SET_ALL; + return true; + } + + usage_mask = 0; + + StructuredData::UnsignedInteger *uint_val = + obj_sp->GetAsUnsignedInteger(); + if (uint_val) { + // If this is an integer, then this specifies a single group: + uint32_t value = uint_val->GetValue(); + if (value == 0) { + error.SetErrorStringWithFormatv( + "0 is not a valid group for option {0}", counter); + return false; + } + usage_mask = (1 << (value - 1)); + return true; + } + // Otherwise it has to be an array: + StructuredData::Array *array_val = obj_sp->GetAsArray(); + if (!array_val) { + error.SetErrorStringWithFormatv( + "required field is not a array for option {0}", counter); + return false; + } + // This is the array ForEach for accumulating a group usage mask from + // an array of string descriptions of groups. + auto groups_accumulator + = [counter, &usage_mask, &error] + (StructuredData::Object *obj) -> bool { + StructuredData::UnsignedInteger *int_val = obj->GetAsUnsignedInteger(); + if (int_val) { + uint32_t value = int_val->GetValue(); + if (value == 0) { + error.SetErrorStringWithFormatv( + "0 is not a valid group for element {0}", counter); + return false; + } + usage_mask |= (1 << (value - 1)); + return true; + } + StructuredData::Array *arr_val = obj->GetAsArray(); + if (!arr_val) { + error.SetErrorStringWithFormatv( + "Group element not an int or array of integers for element {0}", + counter); + return false; + } + size_t num_range_elem = arr_val->GetSize(); + if (num_range_elem != 2) { + error.SetErrorStringWithFormatv( + "Subranges of a group not a start and a stop for element {0}", + counter); + return false; + } + int_val = arr_val->GetItemAtIndex(0)->GetAsUnsignedInteger(); + if (!int_val) { + error.SetErrorStringWithFormatv("Start element of a subrange of a " + "group not unsigned int for element {0}", counter); + return false; + } + uint32_t start = int_val->GetValue(); + int_val = arr_val->GetItemAtIndex(1)->GetAsUnsignedInteger(); + if (!int_val) { + error.SetErrorStringWithFormatv("End element of a subrange of a group" + " not unsigned int for element {0}", counter); + return false; + } + uint32_t end = int_val->GetValue(); + if (start == 0 || end == 0 || start > end) { + error.SetErrorStringWithFormatv("Invalid subrange of a group: {0} - " + "{1} for element {2}", start, end, counter); + return false; + } + for (uint32_t i = start; i <= end; i++) { + usage_mask |= (1 << (i - 1)); + } + return true; + }; + return array_val->ForEach(groups_accumulator); + } + + + Status SetOptionsFromArray(StructuredData::Array &options, Status &error) { + m_num_options = options.GetSize(); + m_options_definition_up.reset(new OptionDefinition[m_num_options]); + // We need to hand out pointers to contents of these vectors; we reserve + // as much as we'll need up front so they don't get freed on resize... + m_usage_container.reserve(m_num_options); + m_enum_storage.reserve(m_num_options); + m_enum_vector.reserve(m_num_options); + + size_t counter = 0; + size_t short_opt_counter = 0; + // This is the Array::ForEach function for adding option elements: + auto add_element = [this, &error, &counter, &short_opt_counter] + (StructuredData::Object *object) -> bool { + StructuredData::Dictionary *opt_dict = object->GetAsDictionary(); + if (!opt_dict) { + error.SetErrorString("Object in options array is not a dictionary"); + return false; + } + OptionDefinition &option_def = m_options_definition_up.get()[counter]; + + // We aren't exposing the validator yet, set it to null + option_def.validator = nullptr; + // We don't require usage masks, so set it to one group by default: + option_def.usage_mask = 1; + + // Now set the fields of the OptionDefinition Array from the dictionary: + // + // Note that I don't check for unknown fields in the option dictionaries + // so a scriptor can add extra elements that are helpful when they go to + // do "set_option_value" + + // Usage Mask: + StructuredData::ObjectSP obj_sp = opt_dict->GetValueForKey("groups"); + if (obj_sp) { + ParseUsageMaskFromArray(obj_sp, counter, option_def.usage_mask, + error); + if (error.Fail()) + return false; + } + + // Required: + option_def.required = false; + obj_sp = opt_dict->GetValueForKey("required"); + if (obj_sp) { + StructuredData::Boolean *boolean_val = obj_sp->GetAsBoolean(); + if (!boolean_val) { + error.SetErrorStringWithFormatv("'required' field is not a boolean " + "for option {0}", counter); + return false; + } + option_def.required = boolean_val->GetValue(); + } + + // Short Option: + int short_option; + obj_sp = opt_dict->GetValueForKey("short_option"); + if (obj_sp) { + // The value is a string, so pull the + llvm::StringRef short_str = obj_sp->GetStringValue(); + if (short_str.empty()) { + error.SetErrorStringWithFormatv("short_option field empty for " + "option {0}", counter); + return false; + } else if (short_str.size() != 1) { + error.SetErrorStringWithFormatv("short_option field has extra " + "characters for option {0}", counter); + return false; + } + short_option = (int) short_str[0]; + } else { + // If the short option is not provided, then we need a unique value + // less than the lowest printable ASCII character. + short_option = short_opt_counter++; + } + option_def.short_option = short_option; + + // Long Option: + std::string long_option; + obj_sp = opt_dict->GetValueForKey("long_option"); + if (!obj_sp) { + error.SetErrorStringWithFormatv("required long_option missing from " + "option {0}", counter); + return false; + } + llvm::StringRef long_stref = obj_sp->GetStringValue(); + if (long_stref.empty()) { + error.SetErrorStringWithFormatv("empty long_option for option {0}", + counter); + return false; + } + auto inserted = g_string_storer.insert(long_stref.str()); + option_def.long_option = ((*(inserted.first)).data()); + + // Value Type: + obj_sp = opt_dict->GetValueForKey("value_type"); + if (obj_sp) { + StructuredData::UnsignedInteger *uint_val + = obj_sp->GetAsUnsignedInteger(); + if (!uint_val) { + error.SetErrorStringWithFormatv("Value type must be an unsigned " + "integer"); + return false; + } + uint64_t val_type = uint_val->GetValue(); + if (val_type >= eArgTypeLastArg) { + error.SetErrorStringWithFormatv("Value type {0} beyond the " + "CommandArgumentType bounds", val_type); + return false; + } + option_def.argument_type = (CommandArgumentType) val_type; + option_def.option_has_arg = true; + } else { + option_def.argument_type = eArgTypeNone; + option_def.option_has_arg = false; + } + + // Completion Type: + obj_sp = opt_dict->GetValueForKey("completion_type"); + if (obj_sp) { + StructuredData::UnsignedInteger *uint_val = obj_sp->GetAsUnsignedInteger(); + if (!uint_val) { + error.SetErrorStringWithFormatv("Completion type must be an " + "unsigned integer for option {0}", counter); + return false; + } + uint64_t completion_type = uint_val->GetValue(); + if (completion_type > eCustomCompletion) { + error.SetErrorStringWithFormatv("Completion type for option {0} " + "beyond the CompletionType bounds", completion_type); + return false; + } + option_def.completion_type = (CommandArgumentType) completion_type; + } else + option_def.completion_type = eNoCompletion; + + // Usage Text: + std::string usage_text; + obj_sp = opt_dict->GetValueForKey("usage"); + if (!obj_sp) { + error.SetErrorStringWithFormatv("required usage missing from option " + "{0}", counter); + return false; + } + long_stref = obj_sp->GetStringValue(); + if (long_stref.empty()) { + error.SetErrorStringWithFormatv("empty usage text for option {0}", + counter); + return false; + } + m_usage_container[counter] = long_stref.str().c_str(); + option_def.usage_text = m_usage_container[counter].data(); + + // Enum Values: + + obj_sp = opt_dict->GetValueForKey("enum_values"); + if (obj_sp) { + StructuredData::Array *array = obj_sp->GetAsArray(); + if (!array) { + error.SetErrorStringWithFormatv("enum values must be an array for " + "option {0}", counter); + return false; + } + size_t num_elem = array->GetSize(); + size_t enum_ctr = 0; + m_enum_storage[counter] = std::vector<EnumValueStorer>(num_elem); + std::vector<EnumValueStorer> &curr_elem = m_enum_storage[counter]; + + // This is the Array::ForEach function for adding enum elements: + // Since there are only two fields to specify the enum, use a simple + // two element array with value first, usage second. + // counter is only used for reporting so I pass it by value here. + auto add_enum = [&enum_ctr, &curr_elem, counter, &error] + (StructuredData::Object *object) -> bool { + StructuredData::Array *enum_arr = object->GetAsArray(); + if (!enum_arr) { + error.SetErrorStringWithFormatv("Enum values for option {0} not " + "an array", counter); + return false; + } + size_t num_enum_elements = enum_arr->GetSize(); + if (num_enum_elements != 2) { + error.SetErrorStringWithFormatv("Wrong number of elements: {0} " + "for enum {1} in option {2}", + num_enum_elements, enum_ctr, counter); + return false; + } + // Enum Value: + StructuredData::ObjectSP obj_sp = enum_arr->GetItemAtIndex(0); + llvm::StringRef val_stref = obj_sp->GetStringValue(); + std::string value_cstr_str = val_stref.str().c_str(); + + // Enum Usage: + obj_sp = enum_arr->GetItemAtIndex(1); + if (!obj_sp) { + error.SetErrorStringWithFormatv("No usage for enum {0} in option " + "{1}", enum_ctr, counter); + return false; + } + llvm::StringRef usage_stref = obj_sp->GetStringValue(); + std::string usage_cstr_str = usage_stref.str().c_str(); + curr_elem[enum_ctr] = EnumValueStorer(value_cstr_str, + usage_cstr_str, enum_ctr); + + enum_ctr++; + return true; + }; // end of add_enum + + array->ForEach(add_enum); + if (!error.Success()) + return false; + // We have to have a vector of elements to set in the options, make + // that here: + for (auto &elem : curr_elem) + m_enum_vector[counter].emplace_back(elem.element); + + option_def.enum_values = llvm::ArrayRef(m_enum_vector[counter]); + } + counter++; + return true; + }; // end of add_element + + options.ForEach(add_element); + return error; + } + + private: + struct EnumValueStorer { + EnumValueStorer() { + element.string_value = "value not set"; + element.usage = "usage not set"; + element.value = 0; + } + + EnumValueStorer(std::string &in_str_val, std::string &in_usage, + size_t in_value) : value(in_str_val), usage(in_usage) { ---------------- jimingham wrote:
Why? https://github.com/llvm/llvm-project/pull/70734 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits