summaryrefslogtreecommitdiff
path: root/gerber/am_read.py
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-06-06 13:25:45 +0200
committerjaseg <git@jaseg.de>2021-06-06 13:25:45 +0200
commit5a5ba2b709f01b2100cd767a25a41737541ad53c (patch)
tree6362ca960945e08d4a77b7f059e971e6099217c9 /gerber/am_read.py
parent8bad573131e4c91782425d81a141dd656b622d7b (diff)
parent72257258edf16cbda691483ef1fa722192ac0d38 (diff)
downloadgerbonara-5a5ba2b709f01b2100cd767a25a41737541ad53c.tar.gz
gerbonara-5a5ba2b709f01b2100cd767a25a41737541ad53c.tar.bz2
gerbonara-5a5ba2b709f01b2100cd767a25a41737541ad53c.zip
Graft pcb-tools upstream onto gerbonara tree
Diffstat (limited to 'gerber/am_read.py')
-rw-r--r--gerber/am_read.py255
1 files changed, 255 insertions, 0 deletions
diff --git a/gerber/am_read.py b/gerber/am_read.py
new file mode 100644
index 0000000..4aff00b
--- /dev/null
+++ b/gerber/am_read.py
@@ -0,0 +1,255 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
+# copyright 2014 Paulo Henrique Silva <ph.silva@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+""" This module provides RS-274-X AM macro modifiers parsing.
+"""
+
+from .am_eval import OpCode, eval_macro
+
+import string
+
+
+class Token:
+ ADD = "+"
+ SUB = "-"
+ # compatibility as many gerber writes do use non compliant X
+ MULT = ("x", "X")
+ DIV = "/"
+ OPERATORS = (ADD, SUB, MULT[0], MULT[1], DIV)
+ LEFT_PARENS = "("
+ RIGHT_PARENS = ")"
+ EQUALS = "="
+ EOF = "EOF"
+
+
+def token_to_opcode(token):
+ if token == Token.ADD:
+ return OpCode.ADD
+ elif token == Token.SUB:
+ return OpCode.SUB
+ elif token in Token.MULT:
+ return OpCode.MUL
+ elif token == Token.DIV:
+ return OpCode.DIV
+ else:
+ return None
+
+
+def precedence(token):
+ if token == Token.ADD or token == Token.SUB:
+ return 1
+ elif token in Token.MULT or token == Token.DIV:
+ return 2
+ else:
+ return 0
+
+
+def is_op(token):
+ return token in Token.OPERATORS
+
+
+class Scanner:
+
+ def __init__(self, s):
+ self.buff = s
+ self.n = 0
+
+ def eof(self):
+ return self.n == len(self.buff)
+
+ def peek(self):
+ if not self.eof():
+ return self.buff[self.n]
+
+ return Token.EOF
+
+ def ungetc(self):
+ if self.n > 0:
+ self.n -= 1
+
+ def getc(self):
+ if self.eof():
+ return ""
+
+ c = self.buff[self.n]
+ self.n += 1
+ return c
+
+ def readint(self):
+ n = ""
+ while not self.eof() and (self.peek() in string.digits):
+ n += self.getc()
+ return int(n)
+
+ def readfloat(self):
+ n = ""
+ while not self.eof() and (self.peek() in string.digits or self.peek() == "."):
+ n += self.getc()
+ # weird case where zero is ommited inthe last modifider, like in ',0.'
+ if n == ".":
+ return 0
+ return float(n)
+
+ def readstr(self, end="*"):
+ s = ""
+ while not self.eof() and self.peek() != end:
+ s += self.getc()
+ return s.strip()
+
+
+def print_instructions(instructions):
+ for opcode, argument in instructions:
+ print("%s %s" % (OpCode.str(opcode),
+ str(argument) if argument is not None else ""))
+
+
+def read_macro(macro):
+ instructions = []
+
+ for block in macro.split("*"):
+
+ is_primitive = False
+ is_equation = False
+
+ found_equation_left_side = False
+ found_primitive_code = False
+
+ equation_left_side = 0
+ primitive_code = 0
+
+ unary_minus_allowed = False
+ unary_minus = False
+
+ if Token.EQUALS in block:
+ is_equation = True
+ else:
+ is_primitive = True
+
+ scanner = Scanner(block)
+
+ # inlined here for compactness and convenience
+ op_stack = []
+
+ def pop():
+ return op_stack.pop()
+
+ def push(op):
+ op_stack.append(op)
+
+ def top():
+ return op_stack[-1]
+
+ def empty():
+ return len(op_stack) == 0
+
+ while not scanner.eof():
+
+ c = scanner.getc()
+
+ if c == ",":
+ found_primitive_code = True
+
+ # add all instructions on the stack to finish last modifier
+ while not empty():
+ instructions.append((token_to_opcode(pop()), None))
+
+ unary_minus_allowed = True
+
+ elif c in Token.OPERATORS:
+ if c == Token.SUB and unary_minus_allowed:
+ unary_minus = True
+ unary_minus_allowed = False
+ continue
+
+ while not empty() and is_op(top()) and precedence(top()) >= precedence(c):
+ instructions.append((token_to_opcode(pop()), None))
+
+ push(c)
+
+ elif c == Token.LEFT_PARENS:
+ push(c)
+
+ elif c == Token.RIGHT_PARENS:
+ while not empty() and top() != Token.LEFT_PARENS:
+ instructions.append((token_to_opcode(pop()), None))
+
+ if empty():
+ raise ValueError("unbalanced parentheses")
+
+ # discard "("
+ pop()
+
+ elif c.startswith("$"):
+ n = scanner.readint()
+
+ if is_equation and not found_equation_left_side:
+ equation_left_side = n
+ else:
+ instructions.append((OpCode.LOAD, n))
+
+ elif c == Token.EQUALS:
+ found_equation_left_side = True
+
+ elif c == "0":
+ if is_primitive and not found_primitive_code:
+ instructions.append((OpCode.PUSH, scanner.readstr("*")))
+ found_primitive_code = True
+ else:
+ # decimal or integer disambiguation
+ if scanner.peek() not in '.' or scanner.peek() == Token.EOF:
+ instructions.append((OpCode.PUSH, 0))
+
+ elif c in "123456789.":
+ scanner.ungetc()
+
+ if is_primitive and not found_primitive_code:
+ primitive_code = scanner.readint()
+ else:
+ n = scanner.readfloat()
+ if unary_minus:
+ unary_minus = False
+ n *= -1
+
+ instructions.append((OpCode.PUSH, n))
+ else:
+ # whitespace or unknown char
+ pass
+
+ # add all instructions on the stack to finish last modifier (if any)
+ while not empty():
+ instructions.append((token_to_opcode(pop()), None))
+
+ # at end, we either have a primitive or a equation
+ if is_primitive and found_primitive_code:
+ instructions.append((OpCode.PRIM, primitive_code))
+
+ if is_equation:
+ instructions.append((OpCode.STORE, equation_left_side))
+
+ return instructions
+
+if __name__ == '__main__':
+ import sys
+
+ instructions = read_macro(sys.argv[1])
+
+ print("insructions:")
+ print_instructions(instructions)
+
+ print("eval:")
+ for primitive in eval_macro(instructions):
+ print(primitive)