#!/usr/bin/python

#
# Copyright (c) 2008 Michael Eddington
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#

# Authors:
#   Frank Laub (frank.laub@gmail.com)
#   Michael Eddington (mike@phed.org)

# $Id$

from __future__ import print_function

import re
import pprint

from . import spark

def _private():
	class Token(object):
		def __init__(self, type, value=None):
			self.type = type
			self.value = value
		def __lt__(self, o):
			return self.type < o
		def __gt__(self, o):
			return self.type > o
		def __le__(self, o):
			return self.type <= o
		def __ge__(self, o):
			return self.type >= o
		def __eq__(self, o):
			return self.type == o
		def __ne__(self, o):
			return self.type != o
		def __repr__(self):
			return self.value or self.type

	class AST(object):
		def __init__(self, type):
			self.type = type
			self._kids = []
		def __getitem__(self, i):
			return self._kids[i]
		def __setitem__(self, i, k):
			self._kids[i] = k
		def __len__(self):
			return len(self._kids)
		def __lt__(self, o):
			return self.type < o
		def __gt__(self, o):
			return self.type > o
		def __le__(self, o):
			return self.type <= o
		def __ge__(self, o):
			return self.type >= o
		def __eq__(self, o):
			return self.type == o
		def __ne__(self, o):
			return self.type != o

	class GdbMiScannerBase(spark.GenericScanner):
		def tokenize(self, input):
			self.rv = []
			spark.GenericScanner.tokenize(self, input)
			return self.rv

		def t_nl(self, s):
			r'\n|\r\n'
			self.rv.append(Token('nl'))

		def t_whitespace(self, s):
			r'[ \t\f\v]+'
			pass

		def t_symbol(self, s):
			r',|\{|\}|\[|\]|\='
			self.rv.append(Token(s, s))

		def t_result_type(self, s):
			r'\*|\+|\^'
			self.rv.append(Token('result_type', s))

		def t_stream_type(self, s):
			r'\@|\&|\~'
			self.rv.append(Token('stream_type', s))

		def t_string(self, s):
			r'[\w-]+'
			self.rv.append(Token('string', s))

		def t_c_string(self, s):
			r'\".*?(?<![\\\\])\"'
			inner = self.__unescape(s[1:len(s)-1])
			self.rv.append(Token('c_string', inner))

		def t_default(self, s):
			r'( . | \n )+'
			raise Exception("Specification error: unmatched input for '%s'" % s)

		def __unescape(self, s):
			s = re.sub(r'\\r', r'\r', s)
			s = re.sub(r'\\n', r'\n', s)
			s = re.sub(r'\\t', r'\t', s)
			return re.sub(r'\\(.)', r'\1', s)


	class GdbMiScanner(GdbMiScannerBase):
		def t_token(self, s):
			r'\d+'
			self.rv.append(Token('token', s))

	class GdbMiParser(spark.GenericASTBuilder):
		def __init__(self):
			spark.GenericASTBuilder.__init__(self, AST, 'output')

		def p_output(self, args):
			'''
				output ::= record_list
				record_list ::= generic_record
				record_list ::= generic_record record_list
				generic_record ::= result_record
				generic_record ::= stream_record
				result_record ::= result_header result_list nl
				result_record ::= result_header nl
				result_header ::= token result_type class
				result_header ::= result_type class
				result_header ::= token = class
				result_header ::= = class
				stream_record ::= stream_type c_string nl
				result_list ::= , result result_list
				result_list ::= , result
				result_list ::= , tuple
				result ::= variable = value
				class ::= string
				variable ::= string
				value ::= const
				value ::= tuple
				value ::= list
				value_list ::= , value
				value_list ::= , value value_list
				const ::= c_string
				tuple ::= { }
				tuple ::= { result }
				tuple ::= { result result_list }
				list ::= [ ]
				list ::= [ value ]
				list ::= [ value value_list ]
				list ::= [ result ]
				list ::= [ result result_list ]
				list ::= { value }
				list ::= { value value_list }
			'''
			pass

		def terminal(self, token):
			#  Homogeneous AST.
			rv = AST(token.type)
			rv.value = token.value
			return rv

		def nonterminal(self, type, args):
			#  Flatten AST a bit by not making nodes if there's only one child.
			exclude = [
				'record_list'
			]
			if len(args) == 1 and type not in exclude:
				return args[0]
			return spark.GenericASTBuilder.nonterminal(self, type, args)

		def error(self, token, i=0, tokens=None):
			if i > 2:
				print('%s %s %s %s' % (tokens[i-3], tokens[i-2], tokens[i-1], tokens[i]))
			raise Exception("Syntax error at or near %d:'%s' token" % (i, token))

	class GdbMiInterpreter(spark.GenericASTTraversal):
		def __init__(self, ast):
			spark.GenericASTTraversal.__init__(self, ast)
			self.postorder()

		def __translate_type(self, type):
			table = {
				'^': 'result',
				'=': 'notify',
				'+': 'status',
				'*': 'exec',
				'~': 'console',
				'@': 'target',
				'&': 'log'
			}
			return table[type]

		def n_result(self, node):
			# result ::= variable = value
			node.value = { node[0].value: node[2].value }
			#print 'result: %s' % node.value

		def n_tuple(self, node):
			if len(node) == 2:
				# tuple ::= {}
				node.value = {}
			elif len(node) == 3:
				# tuple ::= { result }
				node.value = node[1].value
			elif len(node) == 4:
				# tuple ::= { result result_list }
				node.value = node[1].value
				for result in node[2].value:
					for n, v in list(result.items()):
						if n in node.value:
							#print '**********list conversion: [%s] %s -> %s' % (n, node.value[n], v)
							old = node.value[n]
							if not isinstance(old, list):
								node.value[n] = [ node.value[n] ]
							node.value[n].append(v)
						else:
							node.value[n] = v
			else:
				raise Exception('Invalid tuple')
			#print 'tuple: %s' % node.value

		def n_list(self, node):
			if len(node) == 2:
				# list ::= []
				node.value = []
			elif len(node) == 3:
				# list ::= [ value ]
				node.value = [ node[1].value ]
			elif len(node) == 4:
				# list ::= [ value value_list ]
				node.value = [ node[1].value ] + node[2].value
				#list ::= [ result ]
				#list ::= [ result result_list ]
				#list ::= { value }
				#list ::= { value value_list }
			#print 'list %s' % node.value

		def n_value_list(self, node):
			if len(node) == 2:
				#value_list ::= , value
				node.value = [ node[1].value ]
			elif len(node) == 3:
				#value_list ::= , value value_list
				node.value = [ node[1].value ] + node[2].value

		def n_result_list(self, node):
			if len(node) == 2:
				# result_list ::= , result
				node.value = [ node[1].value ]
			else:
				# result_list ::= , result result_list
				node.value = [ node[1].value ] + node[2].value
			#print 'result_list: %s' % node.value

		def n_result_record(self, node):
			node.value = node[0].value
			if len(node) == 3:
				# result_record ::= result_header result_list nl
				node.value['results'] = node[1].value
			elif len(node) == 2:
				# result_record ::= result_header nl
				pass
			#print 'result_record: %s' % (node.value)

		def n_result_header(self, node):
			if len(node) == 3:
				# result_header ::= token result_type class
				node.value = {
					'token': node[0].value,
					'type': self.__translate_type(node[1].value),
					'class_': node[2].value,
					'record_type': 'result'
				}
			elif len(node) == 2:
				# result_header ::= result_type class
				node.value = {
					'token': None,
					'type': self.__translate_type(node[0].value),
					'class_': node[1].value,
					'record_type': 'result'
				}

		def n_stream_record(self, node):
			# stream_record ::= stream_type c_string nl
			node.value = {
				'type': self.__translate_type(node[0].value),
				'value': node[1].value,
				'record_type': 'stream'
			}
			#print 'stream_record: %s' % node.value

		def n_record_list(self, node):
			if len(node) == 1:
				# record_list ::= generic_record
				node.value = [ node[0].value ]
			elif len(node) == 2:
				# record_list ::= generic_record record_list
				node.value = [ node[0].value ] + node[1].value
			#print 'record_list: %s' % node.value

		#def default(self, node):
			#print 'default: ' + node.type

	class GdbDynamicObject(object):
		def __init__(self, dict_):
			self.graft(dict_)

		def __repr__(self):
			return pprint.pformat(self.__dict__)

		def __bool__(self):
			return len(self.__dict__) > 0

		def __getitem__(self, i):
			if i == 0 and len(self.__dict__) > 0:
				return self
			else:
				raise IndexError

		def __getattr__(self, name):
			if name.startswith('__'):
				raise AttributeError
			return None

		def graft(self, dict_):
			for name, value in list(dict_.items()):
				name = name.replace('-', '_')
				if isinstance(value, dict):
					value = GdbDynamicObject(value)
				elif isinstance(value, list):
					x = value
					value = []
					for item in x:
						if isinstance(item, dict):
							item = GdbDynamicObject(item)
						value.append(item)
				setattr(self, name, value)

	class GdbMiRecord:
		def __init__(self, record):
			self.result = None
			for name, value in list(record[0].items()):
				name = name.replace('-', '_')
				if name == 'results':
					for result in value:
						if not self.result:
							self.result = GdbDynamicObject(result)
						else:
							# graft this result to self.results
							self.result.graft(result)
				else:
					setattr(self, name, value)

		def __repr__(self):
			return pprint.pformat(self.__dict__)

	return (GdbMiScanner(), GdbMiParser(), GdbMiInterpreter, GdbMiRecord)


class session(object):
        def __init__(self):
                (self.the_scanner,
                 self.the_parser,
                 self.the_interpreter,
                 self.the_output) = _private()

        def scan(self, input):
	        return self.the_scanner.tokenize(input)

        def parse(self, tokens):
	        return self.the_parser.parse(tokens)

        def process(self, input):
	        tokens = self.scan(input)
	        ast = self.parse(tokens)
	        self.the_interpreter(ast)
	        return self.the_output(ast.value)

#
# Not updated with the session class
#
if __name__ == '__main__':
	def main():
		def print_tokens(tokens):
			print()
			for token in tokens:
				if token.value:
					print(token.type + ': ' + token.value)
				else:
					print(token.type)

		def run_test(test):
			lines = test.splitlines()
			for line in lines:
				tokens = scan(line + '\n')
				#print_tokens(tokens)

				ast = parse(tokens)
				__the_interpreter(ast)
				output = __the_output(ast.value)
				print(output)

		x = '"No symbol table is loaded.  Use the \\"file\\" command."'
		m = re.match('\".*?(?<![\\\\])\"', x)
		z = x[m.start():m.end()]

		test1 = '22^done,time={wallclock="0.05395",user="0.02996",system="0.02222",start="1210321030.972724",end="1210321031.026675"}\n'
		test2 = '''~"[Switching to process 3832 local thread 0x3607]\\n"
=shlibs-updated
^running
'''

		test3 = '''=shlibs-added,shlib-info={num="2",name="qi",kind="-",dyld-addr="0x1000",reason="exec",requested-state="Y",state="Y",path="/Users/franklaub/bin/qi",description="/Users/franklaub/bin/qi",loaded_addr="",slide="0x0",prefix="",dsym-objpath="/Users/franklaub/bin/qi.dSYM/Contents/Resources/DWARF/qi"},time={now="1210290757.432413"}
=shlibs-added,shlib-info={num="3",name="libgcc_s.1.dylib",kind="-",dyld-addr="0x9230a000",reason="dyld",requested-state="Y",state="Y",path="/usr/lib/libgcc_s.1.dylib",description="/usr/lib/libgcc_s.1.dylib",loaded_addr="0x9230a000",slide="-0x6dcf6000",prefix=""},time={now="1210290757.432771"}
=shlibs-added,shlib-info={num="4",name="libSystem.B.dylib",kind="-",dyld-addr="0x950aa000",reason="dyld",requested-state="Y",state="Y",path="/usr/lib/libSystem.B.dylib",description="/usr/lib/libSystem.B.dylib",loaded_addr="0x950aa000",slide="-0x6af56000",prefix="",commpage-objpath="/usr/lib/libSystem.B.dylib[LC_SEGMENT.__DATA.__commpage]"},time={now="1210290757.433091"}
=shlibs-added,shlib-info={num="5",name="libmathCommon.A.dylib",kind="-",dyld-addr="0x95018000",reason="dyld",requested-state="Y",state="Y",path="/usr/lib/system/libmathCommon.A.dylib",description="/usr/lib/system/libmathCommon.A.dylib",loaded_addr="0x95018000",slide="-0x6afe8000",prefix=""},time={now="1210290757.433390"}
*stopped,time={wallclock="1.02740",user="0.00379",system="0.00791",start="1210290756.408774",end="1210290757.436179"},reason="breakpoint-hit",commands="no",times="1",bkptno="1",thread-id="1"
'''

		test4 = '''=shlibs-added,shlib-info={num="2",name="qi",kind="-",dyld-addr="0x1000",reason="exec",requested-state="Y",state="Y",path="/Users/franklaub/bin/qi",description="/Users/franklaub/bin/qi",loaded_addr="",slide="0x0",prefix="",dsym-objpath="/Users/franklaub/bin/qi.dSYM/Contents/Resources/DWARF/qi"},time={now="1210290757.432413"}
^running
'''
		test5 = '''=class,variable={frame={x="2"},frame={x="2"}, regs={"1","2","3"}}
^running
'''
		test6 = '10^done,stack-args={frame={level="0",args={}}},time={wallclock="0.00006",user="0.00004",system="0.00002",start="1210530442.460765",end="1210530442.460825"}\n'

		run_test(test1)
		run_test(test2)
		run_test(test3)
		run_test(test4)
		run_test(test5)
		run_test(test6)

	main()
