#! /usr/bin/env python

# Copyright (c) 2008, Joerg Wunsch
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in
#   the documentation and/or other materials provided with the
#   distribution.
# * Neither the name of the copyright holders nor the names of
#   contributors may be used to endorse or promote products derived
#   from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# The ATmega128 simulations return an error code string in "external
# memory" at address 0x2000 upon failure.  If runtest.sh is run with
# option -s, it will abort the simulation, and leave the file
# core_avr_dump.core where this script can read the error code string
# from.  (The simulations on smaller AVRs don't generate this string
# in order to not bloat their code beyond the available ROM size by
# including sprintf().)

# If an argument is given to the script, it is used as the name of the
# simulavr core dump file to read.  Otherwise, the simulavr default
# name "core_avr_dump.core" is used.

# $Id: readcore.py,v 1.1.2.2 2008/03/20 21:42:29 joerg_wunsch Exp $

# Enum implementation, from Python recipe:
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/413486
# Author: Zoran Isailovski
def Enum(*names):
   ##assert names, "Empty enums are not supported" # <- Don't like empty enums? Uncomment!

   class EnumClass(object):
      __slots__ = names
      def __iter__(self):        return iter(constants)
      def __len__(self):         return len(constants)
      def __getitem__(self, i):  return constants[i]
      def __repr__(self):        return 'Enum' + str(names)
      def __str__(self):         return 'enum ' + str(constants)

   class EnumValue(object):
      __slots__ = ('__value')
      def __init__(self, value): self.__value = value
      Value = property(lambda self: self.__value)
      EnumType = property(lambda self: EnumType)
      def __hash__(self):        return hash(self.__value)
      def __cmp__(self, other):
         # C fans might want to remove the following assertion
         # to make all enums comparable by ordinal value {;))
         assert self.EnumType is other.EnumType, "Only values from the same enum are comparable"
         return cmp(self.__value, other.__value)
      def __invert__(self):      return constants[maximum - self.__value]
      def __nonzero__(self):     return bool(self.__value)
      def __repr__(self):        return str(names[self.__value])

   maximum = len(names) - 1
   constants = [None] * len(names)
   for i, each in enumerate(names):
      val = EnumValue(i)
      setattr(EnumClass, each, val)
      constants[i] = val
   constants = tuple(constants)
   EnumType = EnumClass()
   return EnumType
# end Enum recipe

import re, sys

# Start of CPU register dump
regmagic = re.compile('^General Purpose Register Dump')

# Location of exit code is r24/r25
r24magic = re.compile('r24=(..) +r25=(..)')

# Start of external SRAM dump
srammagic = re.compile('^External SRAM Memory Dump:')

# Start of error code string at address 0x2000
startaddr = re.compile('^2000 :')

# Pattern to detect repeated lines
repline = re.compile('-- last line repeats --')

# Turn one line from the memory dump into an ASCII string.
# Stops processing upon encountering a NUL character.
# Returns a tuple consisting of the string and a condition
# code that is 1 when processing has been terminated by
# detecting NUL, 0 when reaching end of line without seeing
# NUL.
def asciiize(s):
    rv = ''
    a = s.split()
    for iascii in a[2:]:
        i = int(iascii, 16)
        if i == 0:
            return (rv, 1)
        if i == 10 or (i >= 32 and i < 127):
            rv += chr(i)
        else:
            # Non-printable character, not supposed to happen
            rv += '?'
    return (rv, 0)

# Calculate exitcode from r24/r25 hex values
def exitcode(r24, r25):
    i24 = int(r24, 16)
    i25 = int(r25, 16)
    return i25 * 256 + i24


# Start of main
try:
   corename = sys.argv[1]
except IndexError:
   corename = 'core_avr_dump.core'

core = open(corename)

# Our result string
s = ''

# Exit code
ec = -1

# Parser state.
pstateClass = Enum('Done', 'StartAddr', 'SRAMfound', 'GotExitCode',
                   'FoundCPUregs', 'Starting')
pstate = pstateClass.Starting

oline = ''

while pstate > pstateClass.Done:
    l = core.readline()
    if l == '':
        # EOF encountered
        break

    if pstate == pstateClass.Starting:
        if regmagic.match(l):
            pstate = pstateClass.FoundCPUregs
        continue
    elif pstate == pstateClass.FoundCPUregs:
        matchobj = r24magic.match(l)
        if matchobj != None:
            ec = exitcode(matchobj.group(1), matchobj.group(2))
            pstate = pstateClass.GotExitCode
        continue
    elif pstate == pstateClass.GotExitCode:
        if srammagic.match(l):
            pstate = pstateClass.SRAMfound
        continue
    elif pstate == pstateClass.SRAMfound or pstate == pstateClass.StartAddr:
        if repline.match(l):
            l = oline
        if pstate == pstateClass.SRAMfound:
            if startaddr.match(l):
                pstate = pstateClass.StartAddr
            else:
                continue
        (part, condcode) = asciiize(l)
        s += part
        if condcode == 1:
            pstate = pstateClass.Done
    oline = l

core.close()

print("Exit code: %d" % ec)

if s != '':
    print("Message string:")
    print(s)
else:
    print("No message string found.")
