From 5c11946470fb4d2126b2f7b3d38e931ba5143062 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sun, 12 Jul 2026 07:45:21 +0300 Subject: [PATCH] Support block comments in selector filter syntax (#5023) The filter_elements selector grammar had no way to comment out part of a query, so users had to delete and retype text to temporarily toggle a facet. Add a /* ... */ block comment terminal that is ignored by the lexer, and tolerate a trailing "+" so that commenting out the final operand (e.g. "IfcWall + /* IfcSlab */") parses cleanly. Comments may span multiple lines; a /* sequence inside a quoted string is not treated as a comment. Only the filter grammar is affected, not get_element or format which use "/" for regex and division. Adds a regression test and documents the syntax. Generated with the assistance of an AI coding tool. Co-Authored-By: Claude Opus 4.8 --- .../docs/ifcopenshell-python/selector_syntax.rst | 5 +++++ .../ifcopenshell/util/selector.py | 4 +++- .../test/util/test_selector.py | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst index f8098c83af..f03f01d3d4 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst @@ -89,6 +89,11 @@ The filters are chained and apply from left to right. filter[, filter]* +Any part of a query may be commented out using a ``/* ... */`` block comment. +This lets you temporarily disable part of a query without deleting the text, for +example ``IfcWall + /* IfcSlab, material=concrete */`` selects only walls while +keeping the slab criteria on hand. Block comments may span multiple lines. + Below is the table of filters to choose from. Most of these filters will filter previously added elements in your filter group based on their criteria. diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index abaa4e4119..79b2e5ebac 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -40,7 +40,7 @@ import ifcopenshell.util.system import ifcopenshell.util.unit filter_elements_grammar = lark.Lark("""start: filter_group - filter_group: facet_list ("+" facet_list)* + filter_group: facet_list ("+" facet_list)* "+"? facet_list: facet ("," facet)* facet: instance | entity | attribute | type | material | query | classification | location | property | group | parent @@ -108,8 +108,10 @@ filter_elements_grammar = lark.Lark("""start: filter_group CR : /\\r/ LF : /\\n/ NEWLINE: (CR? LF)+ + COMMENT: "/*" /.*?/s "*/" %ignore WS // Disregard spaces in text + %ignore COMMENT // Allow /* ... */ block comments to toggle parts of a query """) get_element_grammar = lark.Lark("""start: keys diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index b8302e0f4a..3319e0df7a 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -351,6 +351,22 @@ class TestFilterElements(test.bootstrap.IFC4): assert subject.filter_elements(self.file, "IfcWall, Name=Foo + IfcSlab") == {element, element2} assert subject.filter_elements(self.file, "IfcWall, Name=Foo + IfcSlab, Name=Bar") == {element, element2} + def test_block_comments_are_ignored(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + element.Name = "Foo" + element2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSlab") + element2.Name = "Bar" + # A /* ... */ block comment lets a query be toggled off without deleting the text. + assert subject.filter_elements(self.file, "IfcWall /* + IfcSlab */") == {element} + assert subject.filter_elements(self.file, "IfcWall + /* IfcSlab */") == {element} + assert subject.filter_elements(self.file, "/* IfcWall + */ IfcSlab") == {element2} + assert subject.filter_elements(self.file, "IfcWall /* commented */ + IfcSlab") == {element, element2} + # Comments may span multiple lines. + assert subject.filter_elements(self.file, "IfcWall + /* multi\nline\ncomment */ IfcSlab") == {element, element2} + # A /* sequence inside a quoted string is not treated as a comment. + element.Name = "a/*b" + assert subject.filter_elements(self.file, 'IfcWall, Name="a/*b"') == {element} + def test_using_elements_argument(self): wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") slab = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSlab")