diff options
author | jaseg <git@jaseg.de> | 2021-06-06 13:25:45 +0200 |
---|---|---|
committer | jaseg <git@jaseg.de> | 2021-06-06 13:25:45 +0200 |
commit | 5a5ba2b709f01b2100cd767a25a41737541ad53c (patch) | |
tree | 6362ca960945e08d4a77b7f059e971e6099217c9 /gerber/am_read.py | |
parent | 8bad573131e4c91782425d81a141dd656b622d7b (diff) | |
parent | 72257258edf16cbda691483ef1fa722192ac0d38 (diff) | |
download | gerbonara-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.py | 255 |
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) |