Skip to content

Commit

Permalink
Add partial macro support for Parser2 (#113)
Browse files Browse the repository at this point in the history
### Macros

-   ✅ Parameters.
-   ❌ Primitives in definition:
    -   ✅ Code 1, Circle
    -   ❌ Code 2, Vector line
    -   ✅ Code 4, Outline
    -   ✅ Code 5, Polygon
    -   ❌ Code 6, Moire
    -   ✅ Code 7, Thermal
    -   ✅ Code 20, Vector line
    -   ✅ Code 21, Center Line
    -   ❌ Code 22, Lower Left Line
-   ❌ Primitives in aperture instance:
    -   ✅ Code 1, Circle
    -   ❌ Code 2, Vector line
    -   ✅ Code 4, Outline
    -   ✅ Code 5, Polygon
    -   ❌ Code 6, Moire
    -   ❌ Code 7, Thermal
    -   ✅ Code 20, Vector line
    -   ✅ Code 21, Center Line
    -   ❌ Code 22, Lower Left Line
-   ❌ Rotation around macro origin:
    -   ❌ Code 1, Circle
    -   ❌ Code 2, Vector line
    -   ❌ Code 4, Outline
    -   ❌ Code 5, Polygon
    -   ❌ Code 6, Moire
    -   ❌ Code 7, Thermal
    -   ❌ Code 20, Vector line
    -   ❌ Code 21, Center Line
    -   ❌ Code 22, Lower Left Line
-   ✅ Expressions.
    -   ✅ Constants.
    -   ✅ Variables.
    -   ✅ Addition.
    -   ✅ Subtraction.
    -   ✅ Multiplication.
    -   ✅ Division.
    -   ✅ Unary + operator.
    -   ✅ Negation.
-   ✅ Variable definitions.

Resolves: #115
  • Loading branch information
Argmaster authored Jan 17, 2024
2 parents c9b6fbd + 1af9440 commit a71dec0
Show file tree
Hide file tree
Showing 65 changed files with 3,478 additions and 1,052 deletions.
57 changes: 39 additions & 18 deletions docs/gerber/feature_support/parser2.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ of hook based interface.

| Symbol | Count |
| ------ | ----- |
|| 92 |
|| 116 |
| 🚧 | 0 |
| 🚫 | 4 |
|| 47 |
| 👽 | 34 |
|| 45 |
| 👽 | 39 |
| 👾 | 0 |
| total | 185 |
| total | 204 |

## Supported Gerber X3 features

Expand Down Expand Up @@ -123,26 +123,47 @@ of hook based interface.

### Macros

- ❌ Parameters.
- ❌ Primitives:
- ❌ Code 0, Comment
- ❌ Code 1, Circle
- ❌ Code 20, Vector line
- ❌ Code 21, Center Line
- ❌ Code 4, Outline
- ❌ Code 5, Polygon
- ✅ Parameters.
- 👽 Primitives in definition:
- ✅ Code 1, Circle
- ❌ Code 2, Vector line
- ✅ Code 4, Outline
- ✅ Code 5, Polygon
- ❌ Code 6, Moire
- ✅ Code 7, Thermal
- ✅ Code 20, Vector line
- ✅ Code 21, Center Line
- ❌ Code 22, Lower Left Line
- 👽 Primitives in aperture instance:
- ✅ Code 1, Circle
- ❌ Code 2, Vector line
- ✅ Code 4, Outline
- ✅ Code 5, Polygon
- ❌ Code 6, Moire
- ❌ Code 7, Thermal
- ✅ Code 20, Vector line
- ✅ Code 21, Center Line
- ❌ Code 22, Lower Left Line
- ❌ Rotation around macro origin:
- ❌ Code 0, Comment
- ❌ Code 1, Circle
- ❌ Code 20, Vector line
- ❌ Code 21, Center Line
- ❌ Code 2, Vector line
- ❌ Code 4, Outline
- ❌ Code 5, Polygon
- ❌ Code 6, Moire
- ❌ Code 7, Thermal
- ❌ Constants.
- ❌ Variables.
- ❌ Variable definitions.
- ❌ Code 20, Vector line
- ❌ Code 21, Center Line
- ❌ Code 22, Lower Left Line
- ✅ Expressions.
- ✅ Constants.
- ✅ Variables.
- ✅ Addition.
- ✅ Subtraction.
- ✅ Multiplication.
- ✅ Division.
- ✅ Unary + operator.
- ✅ Negation.
- ✅ Variable definitions.

### Aperture blocks

Expand Down
6 changes: 6 additions & 0 deletions src/pygerber/gerberx3/parser2/apertures2/circle2.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ class Circle2(Aperture2):

diameter: Offset
hole_diameter: Optional[Offset]


class NoCircle2(Circle2):
"""Dummy aperture representing case when aperture is not needed but has to be
given to denote width of draw line command.
"""
6 changes: 2 additions & 4 deletions src/pygerber/gerberx3/parser2/apertures2/macro2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

from __future__ import annotations

from pygerber.common.frozen_general_model import FrozenGeneralModel
from pygerber.gerberx3.parser2.apertures2.aperture2 import Aperture2
from pygerber.gerberx3.parser2.command_buffer2 import ReadonlyCommandBuffer2


class Macro2(Aperture2):
"""Parser level abstraction of aperture info for macro aperture."""


class MacroExpressionTree(FrozenGeneralModel):
"""Object representing content of macro."""
command_buffer: ReadonlyCommandBuffer2
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@
from pygerber.gerberx3.parser2.apertures2.aperture2 import Aperture2
from pygerber.gerberx3.parser2.attributes2 import ObjectAttributes
from pygerber.gerberx3.parser2.commands2.command2 import Command2
from pygerber.gerberx3.tokenizer.aperture_id import ApertureID


class ApertureDrawCommand2(Command2):
"""Parser level abstraction of draw operation for Gerber AST parser, version 2."""

attributes: ObjectAttributes = Field(default_factory=ObjectAttributes)
aperture_id: ApertureID

def get_aperture(self) -> Aperture2:
"""Get aperture."""
return self.state.get_aperture(self.aperture_id)
aperture: Aperture2

def __str__(self) -> str:
return f"{self.__class__.__qualname__}()"
7 changes: 3 additions & 4 deletions src/pygerber/gerberx3/parser2/commands2/command2.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from pygerber.common.frozen_general_model import FrozenGeneralModel
from pygerber.gerberx3.math.bounding_box import BoundingBox
from pygerber.gerberx3.math.vector_2d import Vector2D
from pygerber.gerberx3.parser2.state2 import Command2State2Proxy
from pygerber.gerberx3.state_enums import Mirroring, Polarity
from pygerber.gerberx3.parser2.state2 import ApertureTransform
from pygerber.gerberx3.state_enums import Mirroring

if TYPE_CHECKING:
from typing_extensions import Self
Expand All @@ -17,8 +17,7 @@
class Command2(FrozenGeneralModel):
"""Parser level abstraction of draw operation for Gerber AST parser, version 2."""

polarity: Polarity
state: Command2State2Proxy
transform: ApertureTransform

def get_bounding_box(self) -> BoundingBox:
"""Get bounding box of draw operation."""
Expand Down
2 changes: 1 addition & 1 deletion src/pygerber/gerberx3/parser2/commands2/region2.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Region2(BufferCommand2):
def command_to_json(self) -> str:
"""Dump draw operation."""
return f"""{{ "cls": "{self.__module__}.{self.__class__.__qualname__}", "dict": {{
"polarity": "{self.polarity.value}",
"polarity": "{self.transform.polarity.value}",
"aperture_attributes": {self.aperture_attributes.model_dump_json()},
"command_buffer": {self.command_buffer.model_dump_json()},
"command_buffer": {
Expand Down
87 changes: 83 additions & 4 deletions src/pygerber/gerberx3/parser2/context2.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Gerber AST parser, version 2, parsing context."""
from __future__ import annotations

from typing import TYPE_CHECKING, Any, NoReturn, Optional
from dataclasses import dataclass
from typing import TYPE_CHECKING, NoReturn, Optional, Type

from pydantic import Field

Expand All @@ -17,11 +18,24 @@
ApertureNotDefined2Error,
ExitParsingProcess2Interrupt,
MacroNotDefinedError,
MacroNotInitializedError,
ReferencedNotInitializedBlockBufferError,
RegionNotInitializedError,
SkipTokenInterrupt,
StepAndRepeatNotInitializedError,
)
from pygerber.gerberx3.parser2.ihooks import IHooks
from pygerber.gerberx3.parser2.macro2.expressions2.binary2 import (
Addition2,
Division2,
Multiplication2,
Subtraction2,
)
from pygerber.gerberx3.parser2.macro2.expressions2.constant2 import Constant2
from pygerber.gerberx3.parser2.macro2.expressions2.unary2 import Negation2, Positive2
from pygerber.gerberx3.parser2.macro2.expressions2.variable_name import VariableName2
from pygerber.gerberx3.parser2.macro2.macro2 import ApertureMacro2
from pygerber.gerberx3.parser2.macro2.statement_buffer2 import StatementBuffer2
from pygerber.gerberx3.parser2.parser2hooks import Parser2Hooks
from pygerber.gerberx3.parser2.state2 import State2
from pygerber.gerberx3.state_enums import AxisCorrespondence
Expand All @@ -33,7 +47,6 @@
from pygerber.gerberx3.math.vector_2d import Vector2D
from pygerber.gerberx3.parser2.apertures2.aperture2 import Aperture2
from pygerber.gerberx3.parser2.commands2.command2 import Command2
from pygerber.gerberx3.parser2.ihooks import IHooks
from pygerber.gerberx3.state_enums import DrawMode, Mirroring, Polarity, Unit
from pygerber.gerberx3.tokenizer.tokens.bases.token import Token
from pygerber.gerberx3.tokenizer.tokens.fs_coordinate_format import CoordinateParser
Expand All @@ -58,6 +71,9 @@ def __init__(self, options: Parser2ContextOptions | None = None) -> None:
self.block_command_buffer_stack: list[CommandBuffer2] = []
self.step_and_repeat_command_buffer: Optional[CommandBuffer2] = None
self.state_before_step_and_repeat: Optional[State2] = None
self.macro_statement_buffer: Optional[StatementBuffer2] = None
self.macro_eval_buffer: Optional[CommandBuffer2] = None
self.macro_variable_buffer: dict[str, Decimal] = {}
self.hooks: IHooks = (
Parser2Hooks() if self.options.hooks is None else self.options.hooks
)
Expand All @@ -70,6 +86,12 @@ def __init__(self, options: Parser2ContextOptions | None = None) -> None:
self.aperture_attributes = ApertureAttributes()
self.object_attributes = ObjectAttributes()

self.macro_expressions = (
Parser2ContextMacroExpressionFactories()
if self.options.custom_macro_expression_factories is None
else self.options.custom_macro_expression_factories
)

def push_block_command_buffer(self) -> None:
"""Add new command buffer for block aperture draw commands."""
self.block_command_buffer_stack.append(
Expand Down Expand Up @@ -144,6 +166,42 @@ def reset_state_to_pre_step_and_repeat(self) -> None:
"""Set state to state before step and repeat."""
self.set_state(self.get_state_before_step_and_repeat())

def get_macro_statement_buffer(self) -> StatementBuffer2:
"""Return macro statement buffer."""
if self.macro_statement_buffer is None:
raise MacroNotInitializedError(self.current_token)
return self.macro_statement_buffer

def set_macro_statement_buffer(self) -> None:
"""Add new command buffer for block aperture draw commands."""
self.macro_statement_buffer = (
StatementBuffer2()
if self.options.initial_macro_statement_buffer is None
else self.options.initial_macro_statement_buffer
)

def unset_macro_statement_buffer(self) -> None:
"""Unset step and repeat command buffer."""
self.macro_statement_buffer = None

def get_macro_eval_buffer(self) -> CommandBuffer2:
"""Return macro evaluation buffer."""
if self.macro_eval_buffer is None:
raise MacroNotInitializedError(self.current_token)
return self.macro_eval_buffer

def set_macro_eval_buffer(self) -> None:
"""Add new command buffer for block aperture draw commands."""
self.macro_eval_buffer = (
CommandBuffer2()
if self.options.initial_macro_eval_buffer is None
else self.options.initial_macro_eval_buffer
)

def unset_macro_eval_buffer(self) -> None:
"""Unset step and repeat command buffer."""
self.macro_eval_buffer = None

def skip_token(self) -> NoReturn:
"""Skip this token."""
raise SkipTokenInterrupt
Expand Down Expand Up @@ -393,14 +451,14 @@ def set_aperture(self, __key: ApertureID, __value: Aperture2) -> None:
"""Set the apertures property value."""
return self.set_state(self.get_state().set_aperture(__key, __value))

def get_macro(self, __key: str) -> Any:
def get_macro(self, __key: str) -> ApertureMacro2:
"""Get macro property value."""
try:
return self.get_state().get_macro(__key)
except KeyError as e:
raise MacroNotDefinedError(self.current_token) from e

def set_macro(self, __key: str, __value: str) -> None:
def set_macro(self, __key: str, __value: ApertureMacro2) -> None:
"""Set the macro property value."""
return self.set_state(self.get_state().set_macro(__key, __value))

Expand Down Expand Up @@ -473,11 +531,32 @@ def clear_object_attributes(self) -> None:
self.object_attributes = ObjectAttributes()


@dataclass
class Parser2ContextMacroExpressionFactories:
"""Collection of factories for all macro expressions."""

constant: Type[Constant2] = Constant2
variable_name: Type[VariableName2] = VariableName2
addition: Type[Addition2] = Addition2
subtraction: Type[Subtraction2] = Subtraction2
multiplication: Type[Multiplication2] = Multiplication2
division: Type[Division2] = Division2
negation: Type[Negation2] = Negation2
positive: Type[Positive2] = Positive2


class Parser2ContextOptions(FrozenGeneralModel):
"""Options for Parser2Context."""

initial_state: Optional[State2] = Field(default=None)
initial_main_command_buffer: Optional[CommandBuffer2] = Field(default=None)
initial_region_command_buffer: Optional[CommandBuffer2] = Field(default=None)
initial_block_command_buffer: Optional[CommandBuffer2] = Field(default=None)
initial_macro_statement_buffer: Optional[StatementBuffer2] = Field(default=None)
initial_macro_eval_buffer: Optional[CommandBuffer2] = Field(default=None)
custom_macro_expression_factories: Optional[
Parser2ContextMacroExpressionFactories
] = Field(
default=None,
)
hooks: Optional[IHooks] = Field(default=None)
6 changes: 6 additions & 0 deletions src/pygerber/gerberx3/parser2/errors2.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ class StepAndRepeatNotInitializedError(Parser2Error):
"""Raised when step and repeat block is closed without being correctly opened."""


class MacroNotInitializedError(Parser2Error):
"""Raised when macro statement buffer is requested without being correctly
initialized.
"""


class StandardAttributeError(Parser2Error):
"""Raised when parser encounters an error while processing a standard attribute."""

Expand Down
Loading

0 comments on commit a71dec0

Please sign in to comment.