#!/usr/bin/env python
"""
Footprint PCB layouting program
Copyright (C) 2012 Danny Milosavljevic
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
from __future__ import division

# see <http://www.gedasymbols.org/footprints/> for the files.

NOFLAG = 0
PINFLAG = 1
VIAFLAG = 2
HOLEFLAG = 8
DISPLAYNAMEFLAG = 0x20
SQUAREFLAG = 0x0100
USETHERMALFLAG = 0x0400
OCTAGONFLAG = 0x0800
import re

def sIdent(scanner, token):
	return intern(token)
def sFloat(scanner, token):
	return float(token)
def sInt(scanner, token):
	return int(token)
def sInt16(scanner, token):
	assert(token.startswith("0x"))
	return int(token[2:], 16)
def sParen(scanner, token):
	return intern(token)
def sString(scanner, token):
	return str(token)
scanner = re.Scanner([
	(r"[a-zA-Z_]\w*", sIdent),
	(r"[+-]*\d+\.\d*", sFloat),
	(r"0x\d+", sInt16),
	(r"[+-]*\d+", sInt),
	(r"\"[^\"]*\"", sString),
	#(r"=|\+|-|\*|/", sOperator),
	(r"\(", sParen),
	(r"\)", sParen),
	(r"\[", sParen),
	(r"\]", sParen),
	(r"\r", None),
	(r"\n", None),
	(r"\t", None),
	(r" ", None),
	(r"#[^\n]*\n", None),
])
Element = "Element"
OpenParen = "("
OpenBracket = "["
CloseParen = ")"
CloseBracket = "]"
ElementLine = "ElementLine"
Pin = "Pin"
List = "$List$"
Pad = "Pad"
Attribute = "Attribute"
ElementArc = "ElementArc"
Mark = "Mark"

def elementP(item):
	return item is Element
def padP(item):
	return item is Pad
def elementLineP(item):
	return item is ElementLine
def elementArcP(item):
	return item is ElementArc
def pinP(item):
	return item is Pin
def attributeP(item):
	return item is Attribute
def markP(item):
	return item is Mark
def getListItem(list, index):
	assert(list[0] is List)
	return list[1 + index]
def getListItems(list, *indices):
	assert(list[0] is List)
	return [list[1 + index] for index in indices]
def getListSize(list):
	assert(list[0] is List)
	return len(list) - 1
def tokenize(f):
	data = f.read()
	result, rest = scanner.scan(data)
	assert(len(rest.strip()) == 0)
	class Stream(object):
		def __init__(self, result):
			self.input = None
			self.result = result
		def consume(self):
			oldInput = self.input
			#print("OI", oldInput)
			if len(self.result) > 0:
				self.input = self.result[0]
				self.result = self.result[1:]
			else:
				self.input = None
			return(oldInput)
	return(Stream(result))
def translateElementCoordinatesToRelative(items):
	#assert(False) # FIXME
	return(items)
def load(f):
	stream = tokenize(f)
	stream.consume()
	def parseListBody(closer):
		while stream.input is not None and stream.input is not closer:
			e = parseInnerElement()
			if closer is CloseBracket and (isinstance(e, int) or isinstance(e, float)):
                                # the file format is strange like that...
			        e /= 100
			yield e
	def parseAtom():
		return(stream.consume())
	def parseInnerElement():
		if(stream.input is Pin or stream.input is ElementLine or stream.input is Pad or stream.input is Attribute or stream.input is ElementArc or stream.input is Mark):
			operator = stream.input
			stream.consume()
			operands = parseList()
			return [operator, operands]
                elif stream.input is CloseParen or stream.input is OpenParen or stream.input is CloseBracket or stream.input is OpenBracket:
                        assert(False)
		else:
			return(parseAtom())
	def parseListB():
		assert(stream.input is OpenParen or stream.input is OpenBracket)
		closer = CloseParen if stream.input is OpenParen else CloseBracket
		stream.consume()
		#print("expect", closer)
		result = [e for e in parseListBody(closer)]
		#print("... now")
		assert(stream.input is closer)
		stream.consume()
		return ([List] + result), closer
	def parseList():
		return parseListB()[0]
	def parseElement():
		assert(stream.input is Element)
		stream.consume()
		elementArgs1, closer = parseListB()
		elementArgs2 = parseList()
		if closer is CloseParen: # absolute coordinates are evil
			elementArgs2 = translateElementCoordinatesToRelative(elementArgs2)
		return([Element, elementArgs1, elementArgs2])
	return(parseElement())

if __name__ == "__main__":
	import StringIO
	s = StringIO.StringIO(
	"""Element(0x000000 "" "R11" "" 1800 1050 56 -138 0 100 0x00000000)
(
	Pin(0 0 60 30 60 28 "" "1" 0x00000001)
	Pin(0 -300 60 30 60 28 "" "2" 0x00000001)
	ElementLine (0 -50 0 -90 10)
	ElementLine (40 -90 -40 -90 10)
	ElementLine (-40 -90 -40 -210 10)
	ElementLine (-40 -210 40 -210 10)
	ElementLine (40 -210 40 -90 10)
	ElementLine (0 -210 0 -250 10)
	)""")
	result = load(s)
	#import pprint
	#pprint.pprint(result)
	assert(result ==
	[Element, [List, 0, '""', '"R11"', '""', 1800, 1050, 56, -138, 0, 100, 0],
	          [List, [Pin, [List, 0, 0, 60, 30, 60, 28, '""', '"1"', 1]], 
	                 [Pin, [List, 0, -300, 60, 30, 60, 28, '""', '"2"', 1]], 
	                 [ElementLine, [List, 0, -50, 0, -90, 10]], 
	                 [ElementLine, [List, 40, -90, -40, -90, 10]], 
	                 [ElementLine, [List, -40, -90, -40, -210, 10]], 
	                 [ElementLine, [List, -40, -210, 40, -210, 10]], 
	                 [ElementLine, [List, 40, -210, 40, -90, 10]], 
	                 [ElementLine, [List, 0, -210, 0, -250, 10]]]])
	#print(result)

	"""
Element (element_flags,  description,  pcb-name,  value,  
mark_x,  mark_y,  text_x,  text_y,
        text_direction,  text_scale,  text_flags)
(
individual graphical components, such as Pad, Pin, 
or ElementLine.
)
	"""
	load(StringIO.StringIO("""Element["" "" "BOX_MGF44061" "" 39370 787401 0 0 0 100 ""]
(
	Pad[90551 -19685 90551 -11811 1000 2000 3000 "" "1" "edge2"]
	ElementLine [0 0 1901574 0 1000]
	ElementLine [1901574 0 1901574 -7874 1000]
	ElementLine [1901574 -7874 0 -7874 1000]
	ElementLine [0 -7874 0 0 1000]
	ElementLine [86614 -7874 86614 -240158 1000]
	ElementLine [86614 -240158 1814960 -240158 1000]
	ElementLine [1814960 -240158 1814960 -7874 1000]
	ElementLine [82677 -7874 82677 -236221 1000]
	ElementLine [82677 -236221 90551 -244095 1000]
	ElementLine [90551 -244095 1811023 -244095 1000]
	ElementLine [1811023 -244095 1818897 -236221 1000]
	ElementLine [1818897 -236221 1818897 -7874 1000]

	)
"""))
