Commits

Aarni Koskela  committed bcb24c6

better

  • Participants
  • Parent commits aeaadb4

Comments (0)

Files changed (2)

File BlockParser.py

+from sre_constants import BRANCH, SUBPATTERN
+import sre_compile
+import sre_parse
+
+class ReScanner:
+	def __init__(self, lexicon, flags=0):
+		
+		self.lexicon = lexicon
+		# combine phrases into a compound pattern
+		p = []
+		s = sre_parse.Pattern()
+		s.flags = flags
+		for phrase, action in lexicon:
+			p.append(sre_parse.SubPattern(s, [
+				(SUBPATTERN, (len(p)+1, sre_parse.parse(phrase, flags))),
+				]))
+		s.groups = len(p)+1
+		p = sre_parse.SubPattern(s, [(BRANCH, (None, p))])
+		self.scanner = sre_compile.compile(p)
+	def scan(self, string):
+		result = []
+		append = result.append
+		match = self.scanner.scanner(string).match
+		i = 0
+		self.pos=None
+		while 1:
+			m = match()
+			if not m:
+				break
+			j = m.end()
+			if i == j:
+				break
+			self.pos=m.start(0)
+			action = self.lexicon[m.lastindex-1][1]
+			if hasattr(action, '__call__'):
+				self.match = m
+				action = action(self, m.group())
+			if action is not None:
+				append(action)
+			i = j
+		return result, string[i:]
+
+class Block(object):
+	def __init__(self, name):
+		self.name=name
+		self.children=[]
+		self.assoc={}
+		
+	def append(self, x):
+		self.children.append(x)
+		
+	def pop(self):
+		return self.children.pop()
+		
+	def __setitem__(self, k, v):
+		self.assoc[k]=v
+		
+	def __getitem__(self, k):
+		if k in self.assoc:
+			return self.assoc[k]
+		else:
+			for kid in self.children:
+				if kid.name == k:
+					return kid
+		raise KeyError
+		
+	def get(self, k, default = None):
+		try:
+			return self[k]
+		except KeyError:
+			return default
+		
+	@classmethod
+	def xrepr(self, obj, endComma=True):
+		if isinstance(obj, (list, tuple)):
+			oq=[]
+			for lo in obj:
+				oq.append(self.xrepr(lo))
+			return u"( "+" , ".join(oq)+" )"
+		elif isinstance(obj, (float, int)):
+			return unicode(repr(obj))
+		elif isinstance(obj, basestring):
+			if obj == "":
+				return repr("")
+			if " " in obj or "\n" in obj:
+				return repr(obj)+(";" if endComma else "")
+			return obj
+		elif obj is None:
+			return ""
+		return str(type(obj))+"::"+repr(obj)
+		
+	def __repr__(self, indent=0):
+		name=str(self.name) if self.name else "; "
+		indtab="\t"*indent
+		indtab2="\t"*(1+indent)
+		s=indtab+name
+		if self.children or self.assoc:
+			smallBlock=(not self.assoc and len(self.children) == 1 and not isinstance(self.children[0], Block))
+			if smallBlock:
+				s+=" { %s }"%self.xrepr(self.children[0])
+			else:
+				s+=" {\n"
+				if self.assoc:
+					for k, v in sorted(self.assoc.iteritems()):
+						s+=indtab2+"%s => "%self.xrepr(k, False)
+						if isinstance(v, Block):
+							s+="\n"
+							s+=v.__repr__(indent+1)
+							s+=";\n"
+						else:
+							s+=self.xrepr(v)+"\n"
+				
+				for kid in self.children:
+					if isinstance(kid, Block):
+						s+=kid.__repr__(indent+1)
+					else:
+						s+=indtab2+self.xrepr(kid)+"\n"
+				s+=indtab+"}"
+		else:
+			s+=" { }"
+		s+="\n"
+		return s
+	
+	def __hash__(self):
+		cq=[self.name]
+		for k in sorted(self.assoc.iterkeys()):
+			cq.append(repr(k))
+			cq.append(repr(self.assoc[k]))
+		
+		for k in self.children:
+			if isinstance(k, Block):
+				cq.append("^"+str(hash(k)))
+			else:
+				cq.append(str(hash(repr(k))))
+		ch="|".join(cq)
+		return hash(ch)
+		
+	def __eq__(self, other):
+		return hash(self)==hash(other)
+		
+	def toXML(self):
+		import xml.etree.cElementTree as etree
+		e=etree.Element(self.name, self.assoc.copy())
+		for i,k in enumerate(self.children):
+			if isinstance(k, Block):
+				e.append(k.toXML())
+			else:
+				if i==0:
+					e.text=k
+				else:
+					e[-1].tail=k
+		return e
+	
+	@classmethod
+	def fromXML(cls, e):
+		b=Block(e.tag)
+		b.assoc=e.attrib.copy()
+		kids=e.getchildren()
+		if kids:
+			for c in kids:
+				sb=cls.fromXML(c)
+				b.append(sb)
+				if c.tail and c.tail.strip():
+					b.append(c.tail)
+		else:
+			b.append(e.text)
+		return b
+
+def offset2rowcol(s, offset):
+	lastnl=s.rfind("\n", 0, offset)
+	if lastnl<0: lastnl=0
+	return (s.count("\n", 0, offset), offset-lastnl)
+
+class ParseError(Exception):
+	pass
+
+class BlockParser:
+	defaultIdentRegexp=r"[-!+#&$:a-zA-Z_][\w.()\[\]]*"
+	def __init__(self, mergeIdents=False, newLineBreak=False, identRegexp=None):
+		self.top=[]
+		self.stack=[self.top]
+		self.ident=None
+		self.assoc=None
+		self.mergeIdents=mergeIdents
+		if not identRegexp:
+			identRegexp=self.defaultIdentRegexp
+		H=self.handle
+		lexicon=[
+			(r"#.*\n",											lambda s,t:H("COMMENT")),
+			(identRegexp,										lambda s,t:H("IDENT", t)),
+			(r"(?P<q>[\"'])(?P<v>.+?)((?<!\\)(?P=q))",			lambda s,t:H("IDENT", t.replace(r'\"','"').replace(r"\'","'").strip(t[0]))),
+			(r"\{",												lambda s,t:H("BLOCKSTART")),
+			(r"\}",												lambda s,t:H("BLOCKEND")),
+			(r"\(",												lambda s,t:H("TUPLESTART")),
+			(r"\)",												lambda s,t:H("TUPLEEND")),
+			(r"-?[0-9]+(\.[0-9]+)?",							lambda s,t:H("NUMBER", t)),
+			(r"[;,]",											lambda s,t:H("COMMA")),
+			(r"=\>",											lambda s,t:H("ASSOC")),
+		]
+		if newLineBreak:
+			lexicon.append((r"\n", lambda s,t:("COMMA", None)))
+		lexicon.append((r"\s+", None))
+		self.scanner = ReScanner(lexicon)
+		
+	def handle(self, token, data=None):
+		self.handleToken(token, data, self.scanner.pos)
+		return None# (token, data)
+		
+	def choke(self, message):
+		if self.pos:
+			row, col=offset2rowcol(self.input, self.pos)
+			row+=1
+			col+=1
+			ctx=self.input[self.pos-5:self.pos+5].replace("\n","").strip()
+		else:
+			row, col, ctx="???"
+		raise ParseError("Gack! %s at position %s (row %s, column %s), context `%s'"%(message, self.pos, row, col, ctx))
+
+	def handle_IDENT(self, data, stack):
+		self.ident=data
+		
+	def handle_NUMBER(self, data, stack):
+		self.ident=float(data)
+
+	def handle_ASSOC(self, data, stack):
+		if self.assoc:
+			self.choke("Invalid association.")
+		if not isinstance(stack[-1], Block):
+			self.choke("Pity the fool who puts assocs in tuples")
+		self.assoc=self.ident
+		self.ident=None
+		
+	def handle_BLOCKSTART(self, data, stack, block=True):
+		if block:
+			b=Block(self.ident)
+		else:
+			b=[]
+		stack[-1].append(b)
+		stack.append(b)
+		self.ident=None
+
+	def handle_TUPLESTART(self, data, stack):
+		return self.handle_BLOCKSTART(data, stack, False)
+
+	def handle_BLOCKEND(self, data, stack):
+		if not isinstance(stack[-1], Block):
+			self.choke("Ending block where no block has gone before")
+		if self.assoc:
+			self.choke("Aren't you forgetting to close an assoc before closing a block")
+		if len(stack)==1:
+			self.choke("Closing too many blocks.")
+		stack.pop()
+		
+	def handle_TUPLEEND(self, data, stack):
+		if not isinstance(stack[-1], list):
+			self.choke("Ending tuple where no tuple has gone before")
+		stack.pop()
+		
+	def handle_COMMA(self, data, stack):
+		pass
+	
+	def handle_COMMENT(self, data, stack):
+		pass
+
+	
+	def handleToken(self, token, data, pos=0):
+		self.pos=pos
+		stack=self.stack
+		if self.mergeIdents and token=="IDENT" and self.ident is not None:
+			self.ident=self.ident+" "+data
+			return
+		# Handle pushing stuff into the block.
+		if self.ident is not None and token != "BLOCKSTART" and token != "ASSOC":
+			if self.assoc:
+				stack[-1][self.assoc]=self.ident
+				self.assoc=None
+			else:
+				stack[-1].append(self.ident)
+			self.ident=None
+
+		# Then do actions.
+		handler=getattr(self, "handle_%s"%token, None)
+		if handler:
+			handler(data, stack)
+		else:
+			self.choke("I haven't got a clue how to handle a %s token"%token)
+		
+	def parse(self, inp):
+		self.input=inp
+		unused, remainder = self.scanner.scan(inp)
+		if len(self.stack)!=1:
+			self.choke("Oh snap, forgot to close a block")
+		return self.top
 		self.changedFiles = changedFiles
 		
 		if newFiles or delFiles or changedFiles:
-			print self.name
+			cline = "[%8s] %s " % (time.strftime("%H:%M:%S"), self.name)
+			print cline.ljust(80, "-")
 			if newFiles:
 				print "  +: %s" % " ".join(sorted(newFiles))
 			if delFiles:
 				print "     (Dry run)"
 			else:
 				self.trigger()
+				print
 			
 			if delFiles:
 				for f in delFiles:
 	dryFirst = bool(options.get("dry-first"))
 	dryRun = bool(options.get("dry-run"))
 	
+	print "%d group definitions." % len(groups)
+	
 	if dryFirst:
 		for group in groups:
 			group.check(dryrun = True)