gerbonara/gerber/am_read.py

256 wiersze
6.7 KiB
Python

#! /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)