Standard attributes as comments are birefly mentioned in The Gerber Layer Format Specification 2024.05 in section 4.1 Comment (G04), dedicated description is available in section 5.1.1 Comment attributes.
There are at least two ways to approach how this can be handled from perspective of AST node classes:
- create separate collection of classes mrroring
src/pygerber/gerberx3/ast/nodes/attribute but dedicated for comment attributes.
- add is_comment member to each attribute base class (TA, TD, TF, TO)
First approach requires a lot of code duplication, will require separate set of on_<attr-name> callbacks on AstVisitor and in general will unnecessarily complicate implementation, in my humble opition, since comment attributes are bound to behave like the X3 standalone counterparts.
Therefore I would advise choosing second approach. This will require adding is_comment=False for all existing construction calls for attributes in Gerber grammar.
src/pygerber/gerberx3/ast/nodes/attribute/TA.py
class TA(Node):
"""Represents TA Gerber extended command."""
is_standalone: bool
@property
def attribute_name(self) -> str:
"""Get attribute name."""
raise NotImplementedError
Afterwards, separate grammar definition for all the attributes will be needed, as % signs must be a part of standalone attributes while comment attributes must not contain them.
src/pygerber/gerberx3/parser/pyparsing/grammar.py:582
@pp.cached_property
def _ta_user_name(self) -> pp.ParserElement:
return (
self._extended_command(
self._ta
+ self.user_name
+ pp.ZeroOrMore(
self.comma
+ self.field.set_results_name("fields", list_all_matches=True)
)
)
.set_name("TA<UserName>")
.set_parse_action(self.make_unpack_callback(TA_UserName, is_standalone=True))
)
G04 with pp.MatchFirst for comment attributes must be placed before general G04.
src/pygerber/gerberx3/parser/pyparsing/grammar.py:1174
@pp.cached_property
def g_codes(self) -> pp.ParserElement:
"""Create a parser element capable of parsing G-codes."""
g04_comment = self._command(
pp.Regex(r"G0*4") + pp.Opt(self.string),
).set_name("G04")
if self.optimization & Optimization.DISCARD_COMMENTS:
g04_comment = pp.Suppress(g04_comment)
else:
g04_comment = g04_comment.set_parse_action(self.make_unpack_callback(G04))
def _standalone(cls: Type[Node]) -> pp.ParserElement:
code = int(cls.__qualname__.lstrip("G"))
return (
self._command(pp.Regex(f"G0*{code}"))
.set_name(cls.__qualname__)
.set_parse_action(self.make_unpack_callback(cls, is_standalone=True))
)
def _non_standalone(cls: Type[Node]) -> pp.ParserElement:
# We have to account for legacy cases like `G70D02*`, see
# `G.is_standalone` docstring for more information.
code = int(cls.__qualname__.lstrip("G"))
return (
(
pp.Regex(f"G0*{code}")
+ pp.FollowedBy(pp.one_of(["D", "X", "Y", "I", "J"]))
)
.set_name(cls.__qualname__)
.set_parse_action(self.make_unpack_callback(cls, is_standalone=False))
) + self.d_codes_non_standalone
return pp.MatchFirst(
[
# ----------------------------------------------------------
# COMMENT ATTRIBUTES SHOULD BE INSERTED HERE
# ----------------------------------------------------------
g04_comment,
*(
_standalone(cast(Type[Node], cls))
for cls in reversed(
(
G01,
G02,
G03,
G36,
G37,
G54,
G55,
G70,
G71,
G74,
G75,
G90,
G91,
)
)
),
*(
_non_standalone(cast(Type[Node], cls))
for cls in reversed(
(
G01,
G02,
G03,
G36,
G37,
G54,
G55,
G70,
G71,
G74,
G75,
G90,
G91,
)
)
),
]
)
Comment attributes syntax should be defined similarily to standalone attributes:
src/pygerber/gerberx3/parser/pyparsing/grammar.py:558
@pp.cached_property
def attribute(self) -> pp.ParserElement:
"""Create a parser element capable of parsing attributes."""
return pp.MatchFirst(
[
self.ta(),
self.td(),
self.tf(),
self.to(),
]
)
src/pygerber/gerberx3/parser/pyparsing/grammar.py:570
def ta(self) -> pp.ParserElement:
"""Create a parser element capable of parsing TA attributes."""
return pp.MatchFirst(
[
self._ta_user_name,
self._ta_aper_function,
self._ta_drill_tolerance,
self._ta_flash_text,
]
)
But with _comment suffix. eg.
def ta_comment(self) -> pp.ParserElement:
"""Create a parser element capable of parsing TA comment attributes."""
return pp.MatchFirst(
[
self._ta_user_name_comment,
self._ta_aper_function_comment,
self._ta_drill_tolerance_comment,
self._ta_flash_text_comment,
]
)
Keep in mind that you cannot use self._extended_command as it includes % at the beginning and the end. I suppose example comment attribute definition could look like this:
@pp.cached_property
def _ta_user_name_comment(self) -> pp.ParserElement:
return (
self._comment_attribute(
self._ta
+ self.user_name
+ pp.ZeroOrMore(
self.comma
+ self.field.set_results_name("fields", list_all_matches=True)
)
)
.set_name("TA<UserName>")
.set_parse_action(self.make_unpack_callback(TA_UserName, is_standalone=False))
)
Where self._comment_attribute has to be implemented and could look similarily to self._extended_command, lets say:
def _comment_attribute(self, inner: pp.ParserElement) -> pp.ParserElement:
return Literal("G04") + Literal("#@!") + inner + self._asterisk
Notice that G04 is a separate literal to allow undefined amount of whitespace characters between G04 and #@!. Make sure that I didn't mess up the special sequence, I am not sure that it is #@!
All of this will inevitebly require changes in test suite to account for new is_standalone member of attribute node classes.
It would also be good to prepare test assets similar to test/assets/gerberx3/tokens/attribute for comment attributes. Gerber assets created should automatically get included in test suite thanks to how test/gerberx3/test_parser/test_pyparsing/test_parser.py:test_pyparsing_parser_grammar() works.
Standard attributes as comments are birefly mentioned in The Gerber Layer Format Specification 2024.05 in section 4.1 Comment (G04), dedicated description is available in section 5.1.1 Comment attributes.
There are at least two ways to approach how this can be handled from perspective of AST node classes:
src/pygerber/gerberx3/ast/nodes/attributebut dedicated for comment attributes.First approach requires a lot of code duplication, will require separate set of
on_<attr-name>callbacks on AstVisitor and in general will unnecessarily complicate implementation, in my humble opition, since comment attributes are bound to behave like the X3 standalone counterparts.Therefore I would advise choosing second approach. This will require adding
is_comment=Falsefor all existing construction calls for attributes in Gerber grammar.src/pygerber/gerberx3/ast/nodes/attribute/TA.pyAfterwards, separate grammar definition for all the attributes will be needed, as
%signs must be a part of standalone attributes while comment attributes must not contain them.src/pygerber/gerberx3/parser/pyparsing/grammar.py:582G04 with
pp.MatchFirstfor comment attributes must be placed before general G04.src/pygerber/gerberx3/parser/pyparsing/grammar.py:1174Comment attributes syntax should be defined similarily to standalone attributes:
src/pygerber/gerberx3/parser/pyparsing/grammar.py:558src/pygerber/gerberx3/parser/pyparsing/grammar.py:570But with
_commentsuffix. eg.Keep in mind that you cannot use
self._extended_commandas it includes%at the beginning and the end. I suppose example comment attribute definition could look like this:Where
self._comment_attributehas to be implemented and could look similarily toself._extended_command, lets say:Notice that G04 is a separate literal to allow undefined amount of whitespace characters between
G04and#@!. Make sure that I didn't mess up the special sequence, I am not sure that it is#@!All of this will inevitebly require changes in test suite to account for new
is_standalonemember of attribute node classes.It would also be good to prepare test assets similar to
test/assets/gerberx3/tokens/attributefor comment attributes. Gerber assets created should automatically get included in test suite thanks to howtest/gerberx3/test_parser/test_pyparsing/test_parser.py:test_pyparsing_parser_grammar()works.