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 <noreply@anthropic.com>
This commit is contained in:
Petru Conduraru
2026-07-12 07:45:21 +03:00
committed by Dion Moult
parent 0b7e25a3ef
commit 5c11946470
3 changed files with 24 additions and 1 deletions
@@ -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.
@@ -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
@@ -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")