Merge pull request #1718 from KLayout/wip

Wip
This commit is contained in:
Matthias Köfferlein 2024-05-31 22:48:24 +02:00 committed by GitHub
commit a494892529
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 664 additions and 135 deletions

View File

@ -197,10 +197,13 @@ GenericReaderOptions::add_options (tl::CommandLineOptions &cmd)
"--" + m_long_prefix + "blend-mode=mode", &m_cell_conflict_resolution, "Specifies how cell conflicts are resolved when using file concatenation",
"When concatenating files with '+', the reader will handle cells with identical names according to this mode:\n"
"\n"
"* 0: joins everything (unsafe)\n"
"* 0: joins everything (usually unsafe)\n"
"* 1: overwrite\n"
"* 2: skip new cell\n"
"* 3: rename cell (safe, default)"
"* 3: rename cell (safe, default)\n"
"\n"
"Mode 0 is a safe solution for the 'same hierarchy, different layers' case. Mode 3 is a safe solution for "
"joining multiple files into one and combining the hierarchy tree of all files as distinct separate trees.\n"
)
;
}

View File

@ -164,7 +164,7 @@ CommonReaderBase::rename_cell (db::Layout &layout, size_t id, const std::string
// Both cells already exist and are not identical: merge ID-declared cell into the name-declared one
layout.force_update ();
merge_cell (layout, iname->second.second, iid->second.second, true);
merge_cell (layout, iname->second.second, iid->second.second, true, false);
iid->second.second = iname->second.second;
}
@ -239,18 +239,44 @@ CommonReaderBase::cell_for_instance (db::Layout &layout, const std::string &cn)
}
void
CommonReaderBase::merge_cell (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index, bool with_meta) const
CommonReaderBase::merge_cell (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index, bool with_meta, bool no_duplicate_instances) const
{
const db::Cell &src_cell = layout.cell (src_cell_index);
db::Cell &target_cell = layout.cell (target_cell_index);
target_cell.set_ghost_cell (src_cell.is_ghost_cell () && target_cell.is_ghost_cell ());
// copy over the instances
for (db::Cell::const_iterator i = src_cell.begin (); ! i.at_end (); ++i) {
// NOTE: cell indexed may be invalid because we delete subcells without update()
if (layout.is_valid_cell_index (i->cell_index ())) {
target_cell.insert (*i);
if (no_duplicate_instances) {
// avoid generating duplicates
std::set<db::Instance, db::InstanceCompareFunction> current;
for (db::Cell::const_iterator i = target_cell.begin (); ! i.at_end (); ++i) {
current.insert (*i);
}
// copy over the instances
// NOTE: need to do that in a two-step fashion as inserting instances may invalidate
// the existing ones.
std::vector<bool> selected;
for (db::Cell::const_iterator i = src_cell.begin (); ! i.at_end (); ++i) {
// NOTE: cell indexed may be invalid because we delete subcells without update()
selected.push_back (layout.is_valid_cell_index (i->cell_index ()) && current.find (*i) == current.end ());
}
auto s = selected.begin ();
for (db::Cell::const_iterator i = src_cell.begin (); ! i.at_end (); ++i, ++s) {
if (*s) {
target_cell.insert (*i);
}
}
} else {
for (db::Cell::const_iterator i = src_cell.begin (); ! i.at_end (); ++i) {
// NOTE: cell indexed may be invalid because we delete subcells without update()
if (layout.is_valid_cell_index (i->cell_index ())) {
target_cell.insert (*i);
}
}
}
merge_cell_without_instances (layout, target_cell_index, src_cell_index, with_meta);
@ -385,7 +411,7 @@ CommonReaderBase::finish (db::Layout &layout)
layout.cell (ci_org).clear_shapes ();
merge_cell (layout, ci_org, ci_new, true);
merge_cell (layout, ci_org, ci_new, true, false);
} else if (m_cc_resolution == SkipNewCell && ! layout.cell (ci_org).is_ghost_cell ()) {
@ -397,7 +423,7 @@ CommonReaderBase::finish (db::Layout &layout)
} else {
merge_cell (layout, ci_org, ci_new, m_cc_resolution != SkipNewCell);
merge_cell (layout, ci_org, ci_new, m_cc_resolution != SkipNewCell, m_cc_resolution == AddToCell);
}

View File

@ -242,7 +242,7 @@ protected:
/**
* @brief Merge (and delete) the src_cell into target_cell
*/
void merge_cell (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index, bool with_meta) const;
void merge_cell (db::Layout &layout, db::cell_index_type target_cell_index, db::cell_index_type src_cell_index, bool with_meta, bool no_duplicate_instances) const;
/**
* @brief Merge (and delete) the src_cell into target_cell without instances

View File

@ -1977,7 +1977,20 @@ OverlappingInstanceIteratorTraits::instance_from_stable_iter (const Iter &iter)
// box tree iterators deliver pointers, not iterators. Use instance_from_pointer to do this conversion.
return mp_insts->instance_from_pointer (&*iter);
}
/**
* @brief A compare function for db::Instance that uses "less" for value compare
*
* In contrast, "operator<" will compare the instance reference, not value.
*/
struct InstanceCompareFunction
{
bool operator() (const db::Instance &a, const db::Instance &b) const
{
return a.less (b);
}
};
}
#endif

View File

@ -98,6 +98,24 @@ static std::vector<std::string> l2n_layer_names (const db::LayoutToNetlist *l2n)
return ln;
}
static std::vector<unsigned int> l2n_layer_indexes (const db::LayoutToNetlist *l2n)
{
std::vector<unsigned int> li;
for (db::LayoutToNetlist::layer_iterator l = l2n->begin_layers (); l != l2n->end_layers (); ++l) {
li.push_back (l->first);
}
return li;
}
static db::LayerProperties l2n_layer_info (const db::LayoutToNetlist *l2n, unsigned int layer)
{
if (! l2n->internal_layout () || ! l2n->internal_layout ()->is_valid_layer (layer)) {
return db::LayerProperties ();
} else {
return l2n->internal_layout ()->get_properties (layer);
}
}
static db::Region antenna_check3 (db::LayoutToNetlist *l2n, const db::Region &poly, double poly_area_factor, double poly_perimeter_factor, const db::Region &metal, double metal_area_factor, double metal_perimeter_factor, double ratio, const std::vector<tl::Variant> &diodes, db::Texts *texts)
{
std::vector<std::pair<const db::Region *, double> > diode_pairs;
@ -331,7 +349,22 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
"This method has been generalized in version 0.27.\n"
) +
gsi::method_ext ("layer_names", &l2n_layer_names,
"@brief Returns a list of names of the layer kept inside the LayoutToNetlist object."
"@brief Returns a list of names of the layers kept inside the LayoutToNetlist object."
) +
gsi::method_ext ("layer_indexes", &l2n_layer_indexes,
"@brief Returns a list of indexes of the layers kept inside the LayoutToNetlist object.\n"
"You can use \\layer_name to get the name from a layer index. You can use \\layer_info to get "
"the \\LayerInfo object attached to a layer - if the layer is an original layer.\n"
"\n"
"This method has been introduced in version 0.29.2.\n"
) +
gsi::method_ext ("layer_info", &l2n_layer_info, gsi::arg ("index"),
"@brief Returns the LayerInfo object attached to a layer (by index).\n"
"If the layer is an original layer and not a derived one, this method will return the "
"stream layer information where the original layer was taken from. Otherwise an empty \\LayerInfo object "
"is returned.\n"
"\n"
"This method has been introduced in version 0.29.2.\n"
) +
gsi::factory ("layer_by_name", &db::LayoutToNetlist::layer_by_name, gsi::arg ("name"),
"@brief Gets a layer object for the given name.\n"
@ -599,8 +632,10 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
) +
gsi::method_ext ("internal_layout", &l2n_internal_layout,
"@brief Gets the internal layout\n"
"Usually it should not be required to obtain the internal layout. If you need to do so, make sure not to modify the layout as\n"
"the functionality of the netlist extractor depends on it."
"The internal layout is where the LayoutToNetlist database stores the shapes for the nets. "
"Usually you do not need to access this object - you must use \\build_net or \\shapes_of_net to "
"retrieve the per-net shape information. If you access the internal layout, make sure you do not "
"modify it."
) +
gsi::method_ext ("internal_top_cell", &l2n_internal_top_cell,
"@brief Gets the internal top cell\n"
@ -664,8 +699,13 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
"This method puts the shapes of a net into the given target cell using a variety of options\n"
"to represent the net name and the hierarchy of the net.\n"
"\n"
"If the netname_prop name is not nil, a property with the given name is created and assigned\n"
"the net name.\n"
"If 'netname_prop' is not nil, a property with the given name is created and attached to shapes. The value "
"of the property is the net name.\n"
"\n"
"'lmap' defines which layers are to be produced. It is map, where the keys are layer indexes in the "
"target layout and the values are Region objects indicating the layer where shapes are to be taken from. "
"Use \\layer_by_name or \\layer_by_index to get the Region object corresponding to a layer stored inside "
"the LayoutToNetlist database.\n"
"\n"
"Net hierarchy is covered in three ways:\n"
"@ul\n"
@ -696,6 +736,14 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
"If no mapping is provided for a specific circuit cell, the nets are copied into the next mapped parent as "
"many times as the circuit cell appears there (circuit flattening).\n"
"\n"
"If 'netname_prop' is not nil, a property with the given name is created and attached to shapes. The value "
"of the property is the net name.\n"
"\n"
"'lmap' defines which layers are to be produced. It is map, where the keys are layer indexes in the "
"target layout and the values are Region objects indicating the layer where shapes are to be taken from. "
"Use \\layer_by_name or \\layer_by_index to get the Region object corresponding to a layer stored inside "
"the LayoutToNetlist database.\n"
"\n"
"The method has three net annotation modes:\n"
"@ul\n"
" @li No annotation (net_cell_name_prefix == nil and netname_prop == nil): the shapes will be put\n"
@ -910,6 +958,25 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
"hierarchical data and an existing DeepShapeStore object, use the "
"'LayoutToNetlist(dss)' constructor.\n"
"\n"
"Once the extraction is done, you can persist the \\LayoutToNetlist object "
"using \\write and restore it using \\read. You can use the query API (see below) to "
"analyze the LayoutToNetlist database.\n"
"\n"
"The query API of the \\LayoutToNetlist object consists of the following parts:\n"
"\n"
"@ul\n"
"@li Net shape retrieval: \\build_all_nets, \\build_nets, \\build_net and \\shapes_per_net @/li\n"
"@li Layers: \\layer_by_index, \\layer_by_name, \\layer_indexes, \\layer_names, \\layer_info, \\layer_name @/li\n"
"@li Log entries: \\each_log_entry @/li\n"
"@li Probing (get net from position): \\probe_net @/li\n"
"@li Netlist: \\netlist @/li\n"
"@li Internal shape storage: \\internal_layout, \\internal_top_cell @/li\n"
"@li Helper functions: \\cell_mapping_into, \\const_cell_mapping_into @/li\n"
"@/ul\n"
"\n"
"The \\LayoutToNetlist object is also the entry point for connectivity-aware DRC checks, "
"such as antenna checks.\n"
"\n"
"This class has been introduced in version 0.26."
);

View File

@ -998,6 +998,10 @@ TEST(6)
// Note: iterating and replace does not work in non-editable mode
if (db::default_editable_mode ()) {
std::vector<db::Instance> insts;
for (db::Cell::const_iterator i = cc.begin (); ! i.at_end (); ++i) {
insts.push_back (*i);
}
for (db::Cell::const_iterator i = cc.begin (); ! i.at_end (); ++i) {
cc.replace_prop_id (*i, i->prop_id () + 1);
}

View File

@ -1020,7 +1020,6 @@ void init_layout2 (db::Layout &g)
tl::InputStream stream (tl::testdata () + "/gds/issue-1671.gds");
db::Reader reader (stream);
reader.read (g, db::LoadLayoutOptions ());
return; // @@@
g.insert_layer (0, db::LayerProperties ("l0"));
g.insert_layer (1, db::LayerProperties ("l1"));

View File

@ -445,7 +445,9 @@ struct NoAdaptorTag { };
template <class X, class Adapted>
struct adaptor_type_info
{
static const std::type_info *type_info ()
typedef Adapted final_type;
static const std::type_info *type_info ()
{
return &typeid (Adapted);
}
@ -476,6 +478,8 @@ struct adaptor_type_info
template <class X>
struct adaptor_type_info<X, NoAdaptorTag>
{
typedef X final_type;
static const std::type_info *type_info ()
{
return 0;
@ -517,6 +521,8 @@ class GSI_PUBLIC_TEMPLATE Class
: public ClassBase
{
public:
typedef typename adaptor_type_info<X, Adapted>::final_type final_type;
Class (const std::string &module, const std::string &name, const Methods &mm, const std::string &doc = std::string (), bool do_register = true)
: ClassBase (doc, mm, do_register)
{
@ -681,9 +687,9 @@ public:
}
private:
gsi::VariantUserClass<X> m_var_cls;
gsi::VariantUserClass<X> m_var_cls_c;
gsi::VariantUserClass<X> m_var_cls_cls;
gsi::VariantUserClass<final_type> m_var_cls;
gsi::VariantUserClass<final_type> m_var_cls_c;
gsi::VariantUserClass<final_type> m_var_cls_cls;
std::unique_ptr<SubClassTesterBase> m_subclass_tester;
};

View File

@ -721,19 +721,49 @@ num_args (const gsi::MethodBase *m)
return int (m->end_arguments () - m->begin_arguments ());
}
std::set<std::string>
invalid_kwnames (const gsi::MethodBase *meth, const std::map<std::string, tl::Variant> *kwargs)
{
std::set<std::string> valid_names;
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a) {
valid_names.insert (a->spec ()->name ());
}
std::set<std::string> invalid_names;
for (auto i = kwargs->begin (); i != kwargs->end (); ++i) {
if (valid_names.find (i->first) == valid_names.end ()) {
invalid_names.insert (i->first);
}
}
return invalid_names;
}
static bool
compatible_with_args (const gsi::MethodBase *m, int argc, const std::map<std::string, tl::Variant> *kwargs)
compatible_with_args (const gsi::MethodBase *m, int argc, const std::map<std::string, tl::Variant> *kwargs, std::string *why_not = 0)
{
int nargs = num_args (m);
int nkwargs = kwargs ? int (kwargs->size ()) : 0;
if (argc >= nargs) {
if (argc > nargs) {
if (why_not) {
*why_not = tl::sprintf (tl::to_string (tr ("%d argument(s) expected, but %d given")), nargs, argc);
}
return false;
} else if (argc == nargs) {
// no more arguments to consider
return argc == nargs && (! kwargs || kwargs->empty ());
if (nkwargs > 0) {
if (why_not) {
*why_not = tl::to_string (tr ("all arguments given, but additional keyword arguments specified"));
}
return false;
} else {
return true;
}
}
if (kwargs) {
int nkwargs = int (kwargs->size ());
int kwargs_taken = 0;
while (argc < nargs) {
@ -741,6 +771,9 @@ compatible_with_args (const gsi::MethodBase *m, int argc, const std::map<std::st
auto i = kwargs->find (atype.spec ()->name ());
if (i == kwargs->end ()) {
if (! atype.spec ()->has_default ()) {
if (why_not) {
*why_not = tl::sprintf (tl::to_string (tr ("no argument specified for '%s' (neither positional or keyword)")), atype.spec ()->name ());
}
return false;
}
} else {
@ -750,13 +783,33 @@ compatible_with_args (const gsi::MethodBase *m, int argc, const std::map<std::st
}
// matches if all keyword arguments are taken
return kwargs_taken == nkwargs;
if (kwargs_taken != nkwargs) {
if (why_not) {
std::set<std::string> invalid_names = invalid_kwnames (m, kwargs);
if (invalid_names.size () > 1) {
std::string names_str = tl::join (invalid_names.begin (), invalid_names.end (), ", ");
*why_not = tl::to_string (tr ("unknown keyword parameters: ")) + names_str;
} else if (invalid_names.size () == 1) {
*why_not = tl::to_string (tr ("unknown keyword parameter: ")) + *invalid_names.begin ();
}
}
return false;
} else {
return true;
}
} else {
while (argc < nargs) {
const gsi::ArgType &atype = m->begin_arguments () [argc];
if (! atype.spec ()->has_default ()) {
if (why_not) {
if (argc < nargs - 1 && ! m->begin_arguments () [argc + 1].spec ()->has_default ()) {
*why_not = tl::sprintf (tl::to_string (tr ("no value given for argument #%d and following")), argc + 1);
} else {
*why_not = tl::sprintf (tl::to_string (tr ("no value given for argument #%d")), argc + 1);
}
}
return false;
}
++argc;
@ -771,8 +824,11 @@ static std::string
describe_overload (const gsi::MethodBase *m, int argc, const std::map<std::string, tl::Variant> *kwargs)
{
std::string res = m->to_string ();
if (compatible_with_args (m, argc, kwargs)) {
std::string why_not;
if (compatible_with_args (m, argc, kwargs, &why_not)) {
res += " " + tl::to_string (tr ("[match candidate]"));
} else if (! why_not.empty ()) {
res += " [" + why_not + "]";
}
return res;
}
@ -980,8 +1036,10 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context
// leave it to the consumer to establish the default values (that is faster)
break;
}
tl::Variant def_value = a->spec ()->default_value ();
gsi::push_arg (arglist, *a, def_value, &heap);
const tl::Variant &def_value = a->spec ()->default_value ();
// NOTE: this const_cast means we need to take care that we do not use default values on "out" parameters.
// Otherwise there is a chance we will modify the default value.
gsi::push_arg (arglist, *a, const_cast<tl::Variant &> (def_value), &heap);
} else {
throw tl::Exception (tl::to_string ("No argument provided (positional or keyword) and no default value available"));
}
@ -1004,19 +1062,7 @@ VariantUserClassImpl::execute_gsi (const tl::ExpressionParserContext & /*context
if (kwargs_taken != nkwargs) {
// check if there are any left-over keyword parameters with unknown names
std::set<std::string> valid_names;
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a) {
valid_names.insert (a->spec ()->name ());
}
std::set<std::string> invalid_names;
for (auto i = kwargs->begin (); i != kwargs->end (); ++i) {
if (valid_names.find (i->first) == valid_names.end ()) {
invalid_names.insert (i->first);
}
}
std::set<std::string> invalid_names = invalid_kwnames (meth, kwargs);
if (invalid_names.size () > 1) {
std::string names_str = tl::join (invalid_names.begin (), invalid_names.end (), ", ");
throw tl::Exception (tl::to_string (tr ("Unknown keyword parameters: ")) + names_str);

View File

@ -1715,9 +1715,15 @@ gsi::EnumIn<B3, B3::E> enum_in_b3 ("", "E",
gsi::enum_const ("E3C", B3::E3C)
);
// 3 base classes
static std::string d4 (BB *, int a, std::string b, double c, B3::E d, tl::Variant e)
{
return tl::sprintf ("%d,%s,%.12g,%d,%s", a, b, c, int (d), e.to_string ());
}
// 3 base classes and enums
static gsi::Class<BB> decl_bb (decl_b1, "", "BB",
gsi::method ("d3", &BB::d3)
gsi::method ("d3", &BB::d3) +
gsi::method_ext ("d4", &d4, gsi::arg ("a"), gsi::arg ("b"), gsi::arg ("c"), gsi::arg ("d", B3::E3A, "E3A"), gsi::arg ("e", tl::Variant (), "nil"), "")
);
gsi::ClassExt<BB> b2_in_bb (decl_b2);
gsi::ClassExt<BB> b3_in_bb (decl_b3);

View File

@ -772,3 +772,60 @@ TEST(14)
EXPECT_EQ (ex.msg ().find ("No overload with matching arguments. Variants are:"), 0);
}
}
TEST(15)
{
// Keyword arguments, enums and errors
tl::Eval e;
tl::Variant v;
try {
v = e.parse("var bb = BB.new; bb.d4()").execute();
EXPECT_EQ (true, false);
} catch (tl::Exception &ex) {
EXPECT_EQ (ex.msg (), "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [no value given for argument #1 and following]\n at position 19 (...d4())");
}
try {
v = e.parse("var bb = BB.new; bb.d4(1, 'a')").execute();
EXPECT_EQ (true, false);
} catch (tl::Exception &ex) {
EXPECT_EQ (ex.msg (), "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [no value given for argument #3]\n at position 19 (...d4(1, 'a'))");
}
try {
v = e.parse("var bb = BB.new; bb.d4(1, 'a', 2.0, xxx=17)").execute();
EXPECT_EQ (true, false);
} catch (tl::Exception &ex) {
EXPECT_EQ (ex.msg (), "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [unknown keyword parameter: xxx]\n at position 19 (...d4(1, 'a', 2.0, xxx..)");
}
try {
v = e.parse("var bb = BB.new; bb.d4(a=1, b='a', c=2.0, xxx=17)").execute();
EXPECT_EQ (true, false);
} catch (tl::Exception &ex) {
EXPECT_EQ (ex.msg (), "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [unknown keyword parameter: xxx]\n at position 19 (...d4(a=1, b='a', c=2...)");
}
v = e.parse("var bb = BB.new; bb.d4(1, 'a', 2.0)").execute();
EXPECT_EQ (v.to_string (), "1,a,2,100,nil");
v = e.parse("var bb = BB.new; bb.d4(1, 'a', 2.0, e=42)").execute();
EXPECT_EQ (v.to_string (), "1,a,2,100,42");
v = e.parse("var bb = BB.new; bb.d4(1, 'a', c=2.0, e=42)").execute();
EXPECT_EQ (v.to_string (), "1,a,2,100,42");
v = e.parse("var bb = BB.new; bb.d4(c=2.0, a=1, b='a', e=42)").execute();
EXPECT_EQ (v.to_string (), "1,a,2,100,42");
v = e.parse("var bb = BB.new; bb.d4(1, 'a', 2.0, d=BB.E.E3B)").execute();
EXPECT_EQ (v.to_string (), "1,a,2,101,nil");
v = e.parse("var bb = BB.new; bb.d4(1, 'a', d=BB.E.E3B, c=2.0)").execute();
EXPECT_EQ (v.to_string (), "1,a,2,101,nil");
v = e.parse("var bb = BB.new; bb.d4(1, 'a', 2.0, BB.E.E3B, 42)").execute();
EXPECT_EQ (v.to_string (), "1,a,2,101,42");
}

View File

@ -278,7 +278,8 @@ MacroEditorDialog::MacroEditorDialog (lay::Dispatcher *pr, lym::MacroCollection
m_edit_trace_index (-1),
m_add_edit_trace_enabled (true),
dm_refresh_file_watcher (this, &MacroEditorDialog::do_refresh_file_watcher),
dm_update_ui_to_run_mode (this, &MacroEditorDialog::do_update_ui_to_run_mode)
dm_update_ui_to_run_mode (this, &MacroEditorDialog::do_update_ui_to_run_mode),
dm_current_tab_changed (this, &MacroEditorDialog::do_current_tab_changed)
{
// Makes this dialog receive events while progress bars are on - this way we can set breakpoints
// during execution of a macro even if anything lengthy is running.
@ -1759,13 +1760,12 @@ MacroEditorDialog::macro_deleted (lym::Macro *macro)
std::map <lym::Macro *, MacroEditorPage *>::iterator page = m_tab_widgets.find (macro);
if (page != m_tab_widgets.end ()) {
// disable the macro on the page - we'll ask for updates when the file
// watcher becomes active. So long, the macro is "zombie".
page->second->connect_macro (0);
m_tab_widgets.erase (page);
int index = tabWidget->indexOf (page->second);
if (index >= 0) {
tab_close_requested (index);
}
}
refresh_file_watcher ();
update_ui_to_run_mode ();
}
@ -1792,12 +1792,10 @@ MacroEditorDialog::macro_changed (lym::Macro *macro)
}
}
void
MacroEditorDialog::current_tab_changed (int index)
void
MacroEditorDialog::do_current_tab_changed ()
{
add_edit_trace (false);
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->widget (index));
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
if (page) {
int tab_index = 0;
for (std::vector<lay::MacroEditorTree *>::const_iterator mt = m_macro_trees.begin (); mt != m_macro_trees.end (); ++mt, ++tab_index) {
@ -1807,9 +1805,19 @@ MacroEditorDialog::current_tab_changed (int index)
}
}
}
}
void
MacroEditorDialog::current_tab_changed (int index)
{
// select the current macro - done in a delayed fashion so there is
// no interacting during erase of macros
dm_current_tab_changed ();
add_edit_trace (false);
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->widget (index));
replaceFrame->setEnabled (page && page->macro () && !page->macro ()->is_readonly ());
apply_search ();
do_update_ui_to_run_mode ();
@ -2500,6 +2508,7 @@ BEGIN_PROTECTED
if (m->is_readonly ()) {
throw tl::Exception ("Can't delete this macro - it is readonly");
}
if (collection) {
if (QMessageBox::question (this, QObject::tr ("Delete Macro File"),
@ -2512,12 +2521,11 @@ BEGIN_PROTECTED
throw tl::Exception ("Can't delete this macro");
}
ct->set_current (collection);
collection->erase (m);
}
ct->set_current (collection);
}
refresh_file_watcher ();

View File

@ -271,6 +271,7 @@ private:
void close_many (int which_relative_to_current);
void ensure_writeable_collection_selected ();
void update_console_text ();
void do_current_tab_changed ();
void start_exec (gsi::Interpreter *interpreter);
void end_exec (gsi::Interpreter *interpreter);
size_t id_for_path (gsi::Interpreter *interpreter, const std::string &path);
@ -360,6 +361,7 @@ private:
std::vector<QString> m_changed_files, m_removed_files;
tl::DeferredMethod<MacroEditorDialog> dm_refresh_file_watcher;
tl::DeferredMethod<MacroEditorDialog> dm_update_ui_to_run_mode;
tl::DeferredMethod<MacroEditorDialog> dm_current_tab_changed;
QMenu *mp_tabs_menu;
};

View File

@ -123,7 +123,9 @@ MacroTreeModel::MacroTreeModel (QObject *parent, lay::MacroEditorDialog *dialog,
: QAbstractItemModel (parent), mp_dialog (dialog), mp_parent (dialog), mp_root (root), m_category (cat)
{
connect (root, SIGNAL (macro_changed (lym::Macro *)), this, SLOT (macro_changed ()));
connect (root, SIGNAL (macro_about_to_be_deleted (lym::Macro *)), this, SLOT (macro_about_to_be_deleted (lym::Macro *)));
connect (root, SIGNAL (macro_deleted (lym::Macro *)), this, SLOT (macro_deleted (lym::Macro *)));
connect (root, SIGNAL (macro_collection_about_to_be_deleted (lym::MacroCollection *)), this, SLOT (macro_collection_about_to_be_deleted (lym::MacroCollection *)));
connect (root, SIGNAL (macro_collection_deleted (lym::MacroCollection *)), this, SLOT (macro_collection_deleted (lym::MacroCollection *)));
connect (root, SIGNAL (macro_collection_changed (lym::MacroCollection *)), this, SLOT (macro_collection_changed ()));
connect (root, SIGNAL (about_to_change ()), this, SLOT (about_to_change ()));
@ -133,7 +135,9 @@ MacroTreeModel::MacroTreeModel (QWidget *parent, lym::MacroCollection *root, con
: QAbstractItemModel (parent), mp_dialog (0), mp_parent (parent), mp_root (root), m_category (cat)
{
connect (root, SIGNAL (macro_changed (lym::Macro *)), this, SLOT (macro_changed ()));
connect (root, SIGNAL (macro_about_to_be_deleted (lym::Macro *)), this, SLOT (macro_about_to_be_deleted (lym::Macro *)));
connect (root, SIGNAL (macro_deleted (lym::Macro *)), this, SLOT (macro_deleted (lym::Macro *)));
connect (root, SIGNAL (macro_collection_about_to_be_deleted (lym::MacroCollection *)), this, SLOT (macro_collection_about_to_be_deleted (lym::MacroCollection *)));
connect (root, SIGNAL (macro_collection_deleted (lym::MacroCollection *)), this, SLOT (macro_collection_deleted (lym::MacroCollection *)));
connect (root, SIGNAL (macro_collection_changed (lym::MacroCollection *)), this, SLOT (macro_collection_changed ()));
connect (root, SIGNAL (about_to_change ()), this, SLOT (about_to_change ()));
@ -144,11 +148,27 @@ Qt::DropActions MacroTreeModel::supportedDropActions() const
return Qt::MoveAction;
}
void MacroTreeModel::macro_about_to_be_deleted (lym::Macro *macro)
{
QModelIndex index = index_for (macro);
if (index.isValid ()) {
changePersistentIndex (index, QModelIndex ());
}
}
void MacroTreeModel::macro_deleted (lym::Macro *)
{
// .. nothing yet ..
}
void MacroTreeModel::macro_collection_about_to_be_deleted (lym::MacroCollection *mc)
{
QModelIndex index = index_for (mc);
if (index.isValid ()) {
changePersistentIndex (index, QModelIndex ());
}
}
void MacroTreeModel::macro_collection_deleted (lym::MacroCollection *)
{
// .. nothing yet ..

View File

@ -69,7 +69,9 @@ signals:
private slots:
void macro_changed ();
void macro_about_to_be_deleted (lym::Macro *macro);
void macro_deleted (lym::Macro *macro);
void macro_collection_about_to_be_deleted (lym::MacroCollection *mc);
void macro_collection_deleted (lym::MacroCollection *mc);
void macro_collection_changed ();
void about_to_change ();

View File

@ -124,6 +124,14 @@ void MacroCollection::on_macro_collection_changed (MacroCollection *mc)
}
}
void MacroCollection::on_child_about_to_be_deleted (MacroCollection *mc)
{
#if defined(HAVE_QT)
emit child_about_to_be_deleted (mc);
#endif
on_macro_collection_about_to_be_deleted (mc);
}
void MacroCollection::on_child_deleted (MacroCollection *mc)
{
#if defined(HAVE_QT)
@ -132,6 +140,17 @@ void MacroCollection::on_child_deleted (MacroCollection *mc)
on_macro_collection_deleted (mc);
}
void MacroCollection::on_macro_collection_about_to_be_deleted (MacroCollection *mc)
{
if (mp_parent) {
mp_parent->on_macro_collection_about_to_be_deleted (mc);
} else {
#if defined(HAVE_QT)
emit macro_collection_about_to_be_deleted (mc);
#endif
}
}
void MacroCollection::on_macro_collection_deleted (MacroCollection *mc)
{
if (mp_parent) {
@ -143,6 +162,14 @@ void MacroCollection::on_macro_collection_deleted (MacroCollection *mc)
}
}
void MacroCollection::on_macro_about_to_be_deleted_here (Macro *macro)
{
#if defined(HAVE_QT)
emit macro_about_to_be_deleted_here (macro);
#endif
on_macro_about_to_be_deleted (macro);
}
void MacroCollection::on_macro_deleted_here (Macro *macro)
{
#if defined(HAVE_QT)
@ -151,6 +178,17 @@ void MacroCollection::on_macro_deleted_here (Macro *macro)
on_macro_deleted (macro);
}
void MacroCollection::on_macro_about_to_be_deleted (Macro *macro)
{
if (mp_parent) {
mp_parent->on_macro_about_to_be_deleted (macro);
} else {
#if defined(HAVE_QT)
emit macro_about_to_be_deleted (macro);
#endif
}
}
void MacroCollection::on_macro_deleted (Macro *macro)
{
if (mp_parent) {
@ -539,8 +577,9 @@ void MacroCollection::erase (lym::Macro *mp)
for (iterator m = m_macros.begin (); m != m_macros.end (); ++m) {
if (m->second == mp) {
begin_changes ();
on_macro_deleted_here (mp);
on_macro_about_to_be_deleted_here (mp);
m_macros.erase (m);
on_macro_deleted_here (mp);
delete mp;
on_changed ();
return;
@ -553,8 +592,9 @@ void MacroCollection::erase (lym::MacroCollection *mp)
for (child_iterator f = m_folders.begin (); f != m_folders.end (); ++f) {
if (f->second == mp) {
begin_changes ();
on_child_deleted (mp);
on_child_about_to_be_deleted (mp);
m_folders.erase (f);
on_child_deleted (mp);
delete mp;
on_changed ();
return;
@ -608,11 +648,25 @@ bool MacroCollection::rename (const std::string &n)
return false;
} else {
m_path = n;
if (parent ()) {
parent ()->folder_renamed (this);
}
on_changed ();
return true;
}
}
void MacroCollection::folder_renamed (MacroCollection *mc)
{
for (auto f = m_folders.begin (); f != m_folders.end (); ++f) {
if (f->second == mc) {
m_folders.erase (f);
m_folders.insert (std::make_pair (mc->name (), mc));
return;
}
}
}
lym::MacroCollection *MacroCollection::create_folder (const char *prefix, bool mkdir)
{
std::string name;

View File

@ -463,21 +463,41 @@ signals:
*/
void changed ();
/**
* @brief This signal is sent by collection when a child collection is about to be deleted in this collection
*/
void child_about_to_be_deleted (lym::MacroCollection *);
/**
* @brief This signal is sent by collection when a child collection is deleted in this collection
*/
void child_deleted (lym::MacroCollection *);
/**
* @brief This signal is sent by the root object when a macro collection is about to be deleted
*/
void macro_collection_about_to_be_deleted (lym::MacroCollection *);
/**
* @brief This signal is sent by the root object when a macro collection is deleted
*/
void macro_collection_deleted (lym::MacroCollection *);
/**
* @brief This signal is sent by collection when a macro is about to be deleted in this collection
*/
void macro_about_to_be_deleted_here (lym::Macro *);
/**
* @brief This signal is sent by collection when a macro is deleted in this collection
*/
void macro_deleted_here (lym::Macro *);
/**
* @brief This signal is sent by the root object when a macro is about to be deleted
*/
void macro_about_to_be_deleted (lym::Macro *);
/**
* @brief This signal is sent by the root object when a macro is deleted
*/
@ -522,9 +542,13 @@ private:
int m_virtual_mode;
bool m_readonly;
void on_child_about_to_be_deleted (MacroCollection *mc);
void on_child_deleted (MacroCollection *mc);
void on_macro_collection_about_to_be_deleted (MacroCollection *mc);
void on_macro_collection_deleted (MacroCollection *mc);
void on_macro_about_to_be_deleted_here (Macro *macro);
void on_macro_deleted_here (Macro *macro);
void on_macro_about_to_be_deleted (Macro *macro);
void on_macro_deleted (Macro *macro);
void on_macro_changed (Macro *macro);
void on_macro_collection_changed (MacroCollection *mc);
@ -535,6 +559,7 @@ private:
void create_entry (const std::string &path);
void rename_macro (Macro *macro, const std::string &new_name);
void folder_renamed (MacroCollection *mc);
void begin_changes ();

View File

@ -47,6 +47,36 @@ static const char *s_bounding_box_name = "S_BOUNDING_BOX";
// ---------------------------------------------------------------------------------
/**
* @brief Compare operator for points, distinct x clustered (with same y)
*/
struct vector_cmp_x
{
bool operator() (const db::Vector &a, const db::Vector &b) const
{
if (a.y () != b.y ()) {
return a.y () < b.y ();
} else {
return a.x () < b.x ();
}
}
};
/**
* @brief Compare operator for points, distinct y clustered (with same x)
*/
struct vector_cmp_y
{
bool operator() (const db::Vector &a, const db::Vector &b) const
{
if (a.x () != b.x ()) {
return a.x () < b.x ();
} else {
return a.y () < b.y ();
}
}
};
/**
* @brief Determines whether a property shall be produced as S_GDS_PROPERTY
*/
@ -224,6 +254,7 @@ template <class Tag> void create_repetition_by_type (const db::Shape &array_shap
*pw++ = *p - po;
}
pts.erase (pw, pts.end ());
std::sort (pts.begin (), pts.end (), vector_cmp_x ());
db::IrregularRepetition *rep_base = new db::IrregularRepetition ();
rep_base->points ().swap (pts);
@ -269,36 +300,6 @@ void create_repetition (const db::Shape &array, db::Repetition &rep)
}
}
/**
* @brief Compare operator for points, distinct x clustered (with same y)
*/
struct vector_cmp_x
{
bool operator() (const db::Vector &a, const db::Vector &b) const
{
if (a.y () != b.y ()) {
return a.y () < b.y ();
} else {
return a.x () < b.x ();
}
}
};
/**
* @brief Compare operator for points, distinct y clustered (with same x)
*/
struct vector_cmp_y
{
bool operator() (const db::Vector &a, const db::Vector &b) const
{
if (a.x () != b.x ()) {
return a.x () < b.x ();
} else {
return a.y () < b.y ();
}
}
};
/**
* @brief Compare operator for points/abstract repetition pair with configurable point compare operator
*/
@ -2030,6 +2031,7 @@ OASISWriter::write (const db::CellInstArray &inst, db::properties_id_type prop_i
*pw++ = *p - po;
}
pts.erase (pw, pts.end ());
std::sort (pts.begin (), pts.end (), vector_cmp_x ());
db::IrregularRepetition *rep_base = new db::IrregularRepetition ();
rep_base->points ().swap (pts);

View File

@ -193,19 +193,53 @@ num_args (const gsi::MethodBase *m)
return int (m->end_arguments () - m->begin_arguments ());
}
std::set<std::string>
invalid_kwnames (const gsi::MethodBase *meth, PyObject *kwargs)
{
pya::PythonRef keys (PyDict_Keys (kwargs));
std::set<std::string> valid_names;
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a) {
valid_names.insert (a->spec ()->name ());
}
std::set<std::string> invalid_names;
for (int i = int (PyList_Size (keys.get ())); i > 0; ) {
--i;
std::string k = python2c<std::string> (PyList_GetItem (keys.get (), i));
if (valid_names.find (k) == valid_names.end ()) {
invalid_names.insert (k);
}
}
return invalid_names;
}
static bool
compatible_with_args (const gsi::MethodBase *m, int argc, PyObject *kwargs)
compatible_with_args (const gsi::MethodBase *m, int argc, PyObject *kwargs, std::string *why_not = 0)
{
int nargs = num_args (m);
int nkwargs = kwargs == NULL ? 0 : int (PyDict_Size (kwargs));
if (argc >= nargs) {
if (argc > nargs) {
if (why_not) {
*why_not = tl::sprintf (tl::to_string (tr ("%d argument(s) expected, but %d given")), nargs, argc);
}
return false;
} else if (argc == nargs) {
// no more arguments to consider
return argc == nargs && (kwargs == NULL || PyDict_Size (kwargs) == 0);
if (nkwargs > 0) {
if (why_not) {
*why_not = tl::to_string (tr ("all arguments given, but additional keyword arguments specified"));
}
return false;
} else {
return true;
}
}
if (kwargs != NULL) {
int nkwargs = int (PyDict_Size (kwargs));
int kwargs_taken = 0;
while (argc < nargs) {
@ -213,6 +247,9 @@ compatible_with_args (const gsi::MethodBase *m, int argc, PyObject *kwargs)
pya::PythonPtr py_arg = PyDict_GetItemString (kwargs, atype.spec ()->name ().c_str ());
if (! py_arg) {
if (! atype.spec ()->has_default ()) {
if (why_not) {
*why_not = tl::sprintf (tl::to_string (tr ("no argument specified for '%s' (neither positional or keyword)")), atype.spec ()->name ());
}
return false;
}
} else {
@ -221,14 +258,33 @@ compatible_with_args (const gsi::MethodBase *m, int argc, PyObject *kwargs)
++argc;
}
// matches if all keyword arguments are taken
return kwargs_taken == nkwargs;
if (kwargs_taken != nkwargs) {
if (why_not) {
std::set<std::string> invalid_names = invalid_kwnames (m, kwargs);
if (invalid_names.size () > 1) {
std::string names_str = tl::join (invalid_names.begin (), invalid_names.end (), ", ");
*why_not = tl::to_string (tr ("unknown keyword parameters: ")) + names_str;
} else if (invalid_names.size () == 1) {
*why_not = tl::to_string (tr ("unknown keyword parameter: ")) + *invalid_names.begin ();
}
}
return false;
} else {
return true;
}
} else {
while (argc < nargs) {
const gsi::ArgType &atype = m->begin_arguments () [argc];
if (! atype.spec ()->has_default ()) {
if (why_not) {
if (argc < nargs - 1 && ! m->begin_arguments () [argc + 1].spec ()->has_default ()) {
*why_not = tl::sprintf (tl::to_string (tr ("no value given for argument #%d and following")), argc + 1);
} else {
*why_not = tl::sprintf (tl::to_string (tr ("no value given for argument #%d")), argc + 1);
}
}
return false;
}
++argc;
@ -243,8 +299,11 @@ static std::string
describe_overload (const gsi::MethodBase *m, int argc, PyObject *kwargs)
{
std::string res = m->to_string ();
if (compatible_with_args (m, argc, kwargs)) {
std::string why_not;
if (compatible_with_args (m, argc, kwargs, &why_not)) {
res += " " + tl::to_string (tr ("[match candidate]"));
} else if (! why_not.empty ()) {
res += " [" + why_not + "]";
}
return res;
}
@ -705,8 +764,10 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args
// leave it to the consumer to establish the default values (that is faster)
break;
}
tl::Variant def_value = a->spec ()->default_value ();
gsi::push_arg (arglist, *a, def_value, &heap);
const tl::Variant &def_value = a->spec ()->default_value ();
// NOTE: this const_cast means we need to take care that we do not use default values on "out" parameters.
// Otherwise there is a chance we will modify the default value.
gsi::push_arg (arglist, *a, const_cast<tl::Variant &> (def_value), &heap);
} else {
throw tl::Exception (tl::to_string (tr ("No argument provided (positional or keyword) and no default value available")));
}
@ -721,23 +782,7 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, PyObject *args
if (kwargs_taken != nkwargs) {
// check if there are any left-over keyword parameters with unknown names
pya::PythonRef keys (PyDict_Keys (kwargs));
std::set<std::string> valid_names;
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a) {
valid_names.insert (a->spec ()->name ());
}
std::set<std::string> invalid_names;
for (int i = int (PyList_Size (keys.get ())); i > 0; ) {
--i;
std::string k = python2c<std::string> (PyList_GetItem (keys.get (), i));
if (valid_names.find (k) == valid_names.end ()) {
invalid_names.insert (k);
}
}
std::set<std::string> invalid_names = invalid_kwnames (meth, kwargs);
if (invalid_names.size () > 1) {
std::string names_str = tl::join (invalid_names.begin (), invalid_names.end (), ", ");
throw tl::Exception (tl::to_string (tr ("Unknown keyword parameters: ")) + names_str);

View File

@ -182,6 +182,27 @@ get_kwarg (const gsi::ArgType &atype, VALUE kwargs)
}
}
static int get_kwargs_keys (VALUE key, VALUE, VALUE arg)
{
std::set<std::string> *names = reinterpret_cast<std::set<std::string> *> (arg);
names->insert (ruby2c<std::string> (rba_safe_obj_as_string (key)));
return ST_CONTINUE;
}
static std::set<std::string>
invalid_kwnames (const gsi::MethodBase *meth, VALUE kwargs)
{
std::set<std::string> invalid_names;
rb_hash_foreach (kwargs, (int (*)(...)) &get_kwargs_keys, (VALUE) &invalid_names);
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments (); ++a) {
invalid_names.erase (a->spec ()->name ());
}
return invalid_names;
}
// -------------------------------------------------------------------
// The lookup table for the method overload resolution
@ -325,19 +346,32 @@ public:
}
private:
static bool
compatible_with_args (const gsi::MethodBase *m, int argc, VALUE kwargs)
compatible_with_args (const gsi::MethodBase *m, int argc, VALUE kwargs, std::string *why_not = 0)
{
int nargs = num_args (m);
int nkwargs = kwargs == Qnil ? 0 : RHASH_SIZE (kwargs);
if (argc >= nargs) {
if (argc > nargs) {
if (why_not) {
*why_not = tl::sprintf (tl::to_string (tr ("%d argument(s) expected, but %d given")), nargs, argc);
}
return false;
} else if (argc == nargs) {
// no more arguments to consider
return argc == nargs && (kwargs == Qnil || RHASH_SIZE (kwargs) == 0);
if (nkwargs > 0) {
if (why_not) {
*why_not = tl::to_string (tr ("all arguments given, but additional keyword arguments specified"));
}
return false;
} else {
return true;
}
}
if (kwargs != Qnil) {
int nkwargs = int (RHASH_SIZE (kwargs));
int kwargs_taken = 0;
while (argc < nargs) {
@ -345,6 +379,9 @@ private:
VALUE rb_arg = rb_hash_lookup2 (kwargs, ID2SYM (rb_intern (atype.spec ()->name ().c_str ())), Qnil);
if (rb_arg == Qnil) {
if (! atype.spec ()->has_default ()) {
if (why_not) {
*why_not = tl::sprintf (tl::to_string (tr ("no argument specified for '%s' (neither positional or keyword)")), atype.spec ()->name ());
}
return false;
}
} else {
@ -353,14 +390,33 @@ private:
++argc;
}
// matches if all keyword arguments are taken
return kwargs_taken == nkwargs;
if (kwargs_taken != nkwargs) {
if (why_not) {
std::set<std::string> invalid_names = invalid_kwnames (m, kwargs);
if (invalid_names.size () > 1) {
std::string names_str = tl::join (invalid_names.begin (), invalid_names.end (), ", ");
*why_not = tl::to_string (tr ("unknown keyword parameters: ")) + names_str;
} else if (invalid_names.size () == 1) {
*why_not = tl::to_string (tr ("unknown keyword parameter: ")) + *invalid_names.begin ();
}
}
return false;
} else {
return true;
}
} else {
while (argc < nargs) {
const gsi::ArgType &atype = m->begin_arguments () [argc];
if (! atype.spec ()->has_default ()) {
if (why_not) {
if (argc < nargs - 1 && ! m->begin_arguments () [argc + 1].spec ()->has_default ()) {
*why_not = tl::sprintf (tl::to_string (tr ("no value given for argument #%d and following")), argc + 1);
} else {
*why_not = tl::sprintf (tl::to_string (tr ("no value given for argument #%d")), argc + 1);
}
}
return false;
}
++argc;
@ -375,8 +431,11 @@ private:
describe_overload (const gsi::MethodBase *m, int argc, VALUE kwargs)
{
std::string res = m->to_string ();
if (compatible_with_args (m, argc, kwargs)) {
std::string why_not;
if (compatible_with_args (m, argc, kwargs, &why_not)) {
res += " " + tl::to_string (tr ("[match candidate]"));
} else if (! why_not.empty ()) {
res += " [" + why_not + "]";
}
return res;
}
@ -1040,14 +1099,6 @@ static gsi::ArgType create_void_type ()
static gsi::ArgType s_void_type = create_void_type ();
static int get_kwargs_keys (VALUE key, VALUE, VALUE arg)
{
std::set<std::string> *names = reinterpret_cast<std::set<std::string> *> (arg);
names->insert (ruby2c<std::string> (key));
return ST_CONTINUE;
}
void
push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, VALUE *argv, int argc, VALUE kwargs, tl::Heap &heap)
{
@ -1066,8 +1117,10 @@ push_args (gsi::SerialArgs &arglist, const gsi::MethodBase *meth, VALUE *argv, i
// leave it to the consumer to establish the default values (that is faster)
break;
}
tl::Variant def_value = a->spec ()->default_value ();
gsi::push_arg (arglist, *a, def_value, &heap);
const tl::Variant &def_value = a->spec ()->default_value ();
// NOTE: this const_cast means we need to take care that we do not use default values on "out" parameters.
// Otherwise there is a chance we will modify the default value.
gsi::push_arg (arglist, *a, const_cast<tl::Variant &> (def_value), &heap);
} else {
throw tl::Exception (tl::to_string (tr ("No argument provided (positional or keyword) and no default value available")));
}

Binary file not shown.

View File

@ -3236,6 +3236,48 @@ class BasicTest(unittest.TestCase):
self.assertEqual(pc.x, 3)
self.assertEqual(pdc.x, 4)
# keyword arguments, enums and error messages
def test_92(self):
bb = pya.BB()
m = ""
try:
bb.d4()
except Exception as ex:
m = str(ex)
self.assertEqual(m, "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [no value given for argument #1 and following]\n in BB.d4")
m = ""
try:
bb.d4(1, "a")
except Exception as ex:
m = str(ex)
self.assertEqual(m, "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [no value given for argument #3]\n in BB.d4")
m = ""
try:
bb.d4(1, "a", 2.0, xxx=17)
except Exception as ex:
m = str(ex)
self.assertEqual(m, "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [unknown keyword parameter: xxx]\n in BB.d4")
m = ""
try:
bb.d4(a=1, b="a", c=2.0, xxx=17)
except Exception as ex:
m = str(ex)
self.assertEqual(m, "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [unknown keyword parameter: xxx]\n in BB.d4")
self.assertEqual(bb.d4(1, "a", 2.0), "1,a,2,100,nil")
self.assertEqual(bb.d4(1, "a", 2.0, e=42), "1,a,2,100,42")
self.assertEqual(bb.d4(1, "a", c=2.0, e=42), "1,a,2,100,42")
self.assertEqual(bb.d4(c=2.0, a=1, b="a", e=42), "1,a,2,100,42")
self.assertEqual(bb.d4(1, "a", 2.0, d=pya.BB.E.E3B), "1,a,2,101,nil")
self.assertEqual(bb.d4(1, "a", d=pya.BB.E.E3B, c=2.5), "1,a,2.5,101,nil")
self.assertEqual(bb.d4(1, "a", 2.0, pya.BB.E.E3B, 42), "1,a,2,101,42")
# run unit tests
if __name__ == '__main__':
suite = unittest.TestSuite()

View File

@ -3154,4 +3154,51 @@ class Basic_TestClass < TestBase
end
# keyword arguments, enums and error messages
def test_81
bb = RBA::BB::new
m = ""
begin
bb.d4()
rescue => ex
m = ex.to_s
end
assert_equal(m, "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [no value given for argument #1 and following]\n in BB::d4")
m = ""
begin
bb.d4(1, "a")
rescue => ex
m = ex.to_s
end
assert_equal(m, "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [no value given for argument #3]\n in BB::d4")
m = ""
begin
bb.d4(1, "a", 2.0, xxx: 17)
rescue => ex
m = ex.to_s
end
assert_equal(m, "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [unknown keyword parameter: xxx]\n in BB::d4")
m = ""
begin
bb.d4(a: 1, b: "a", c: 2.0, xxx: 17)
rescue => ex
m = ex.to_s
end
assert_equal(m, "Can't match arguments. Variants are:\n string d4(int a, string b, double c, B3::E d = E3A, variant e = nil) [unknown keyword parameter: xxx]\n in BB::d4")
assert_equal(bb.d4(1, "a", 2.0), "1,a,2,100,nil")
assert_equal(bb.d4(1, "a", 2.0, e: 42), "1,a,2,100,42")
assert_equal(bb.d4(1, "a", c: 2.0, e: 42), "1,a,2,100,42")
assert_equal(bb.d4(c: 2.0, a: 1, b: "a", e: 42), "1,a,2,100,42")
assert_equal(bb.d4(1, "a", 2.0, d: RBA::BB::E::E3B), "1,a,2,101,nil")
assert_equal(bb.d4(1, "a", d: RBA::BB::E::E3B, c: 2.5), "1,a,2.5,101,nil")
assert_equal(bb.d4(1, "a", 2.0, RBA::BB::E::E3B, 42), "1,a,2,101,42")
end
end

View File

@ -551,6 +551,8 @@ END
assert_equal(File.open(tmp, "r").read, File.open(input, "r").read)
assert_equal(l2n.layer_names.join(","), "poly,poly_lbl,diff_cont,poly_cont,metal1,metal1_lbl,via1,metal2,metal2_lbl,psd,nsd")
assert_equal(l2n.layer_indexes.join(","), "1,2,3,4,5,6,7,8,9,10,11")
assert_equal(l2n.layer_indexes.collect { |li| l2n.layer_info(li).to_s }.join(","), "3/0,3/1,4/0,5/0,6/0,6/1,7/0,8/0,8/1,,")
assert_equal(l2n.layer_name(l2n.layer_by_name("metal1")), "metal1")
assert_equal(l2n.layer_name(l2n.layer_by_index(l2n.layer_of(l2n.layer_by_name("metal1")))), "metal1")