1 # Copyright (c) 2013 ARM Limited
4 # The license below extends only to copyright in the software and shall
5 # not be construed as granting a license to any other intellectual
6 # property including but not limited to intellectual property relating
7 # to a hardware implementation of the functionality of the software
8 # licensed hereunder. You may use the software subject to the license
9 # terms below provided that you ensure that this notice is replicated
10 # unmodified and in its entirety in all distributions of the software,
11 # modified or unmodified, in source code or in binary form.
13 # Redistribution and use in source and binary forms, with or without
14 # modification, are permitted provided that the following conditions are
15 # met: redistributions of source code must retain the above copyright
16 # notice, this list of conditions and the following disclaimer;
17 # redistributions in binary form must reproduce the above copyright
18 # notice, this list of conditions and the following disclaimer in the
19 # documentation and/or other materials provided with the distribution;
20 # neither the name of the copyright holders nor the names of its
21 # contributors may be used to endorse or promote products derived from
22 # this software without specific prior written permission.
24 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 from colours
import unknownColour
39 from point
import Point
42 from time
import time
as wall_time
47 all_ids
= set(id_parts
)
50 class BlobDataSelect(object):
51 """Represents which data is displayed for Ided object"""
54 self
.ids
= set(all_ids
)
56 def __and__(self
, rhs
):
57 """And for filtering"""
58 ret
= BlobDataSelect()
59 ret
.ids
= self
.ids
.intersection(rhs
.ids
)
62 class BlobVisualData(object):
63 """Super class for block data colouring"""
64 def to_striped_block(self
, select
):
65 """Return an array of colours to use for a striped block"""
69 """Get an instruction Id (if any) from this data"""
73 """Get a line Id (if any) from this data"""
77 return self
.__class
__.__name
__ + '().from_string(' + \
83 class Id(BlobVisualData
):
84 """A line or instruction id"""
89 self
.predictionSeqNum
= 0
95 return [self
.threadId
, self
.streamSeqNum
, self
.predictionSeqNum
,
96 self
.lineSeqNum
, self
.fetchSeqNum
, self
.execSeqNum
]
98 def __cmp__(self
, right
):
99 return cmp(self
.as_list(), right
.as_list())
101 def from_string(self
, string
):
102 m
= re
.match('^(F;)?(\d+)/(\d+)\.(\d+)/(\d+)(/(\d+)(\.(\d+))?)?',
105 def seqnum_from_string(string
):
112 print 'Invalid Id string', string
116 if elems
[0] is not None:
121 self
.threadId
= seqnum_from_string(elems
[1])
122 self
.streamSeqNum
= seqnum_from_string(elems
[2])
123 self
.predictionSeqNum
= seqnum_from_string(elems
[3])
124 self
.lineSeqNum
= seqnum_from_string(elems
[4])
125 self
.fetchSeqNum
= seqnum_from_string(elems
[6])
126 self
.execSeqNum
= seqnum_from_string(elems
[8])
130 if self
.fetchSeqNum
!= 0:
139 """Returns the usual id T/S.P/L/F.E string"""
141 str(self
.threadId
) + '/' +
142 str(self
.streamSeqNum
) + '.' +
143 str(self
.predictionSeqNum
) + '/' +
144 str(self
.lineSeqNum
) + '/' +
145 str(self
.fetchSeqNum
) + '.' +
146 str(self
.execSeqNum
))
148 def to_striped_block(self
, select
):
152 ret
.append(colours
.faultColour
)
154 if 'T' in select
.ids
:
155 ret
.append(colours
.number_to_colour(self
.threadId
))
156 if 'S' in select
.ids
:
157 ret
.append(colours
.number_to_colour(self
.streamSeqNum
))
158 if 'P' in select
.ids
:
159 ret
.append(colours
.number_to_colour(self
.predictionSeqNum
))
160 if 'L' in select
.ids
:
161 ret
.append(colours
.number_to_colour(self
.lineSeqNum
))
162 if self
.fetchSeqNum
!= 0 and 'F' in select
.ids
:
163 ret
.append(colours
.number_to_colour(self
.fetchSeqNum
))
164 if self
.execSeqNum
!= 0 and 'E' in select
.ids
:
165 ret
.append(colours
.number_to_colour(self
.execSeqNum
))
168 ret
= [colours
.unknownColour
]
171 ret
.append(colours
.faultColour
)
175 class Branch(BlobVisualData
):
176 """Branch data new stream and prediction sequence numbers, a branch
177 reason and a new PC"""
179 self
.newStreamSeqNum
= 0
180 self
.newPredictionSeqNum
= 0
182 self
.reason
= "NoBranch"
185 def from_string(self
, string
):
186 m
= re
.match('^(\w+);(\d+)\.(\d+);([0-9a-fA-Fx]+);(.*)$', string
)
189 self
.reason
, newStreamSeqNum
, newPredictionSeqNum
, \
190 newPC
, id = m
.groups()
192 self
.newStreamSeqNum
= int(newStreamSeqNum
)
193 self
.newPredictionSeqNum
= int(newPredictionSeqNum
)
194 self
.newPC
= int(newPC
, 0)
195 self
.id = special_view_decoder(Id
)(id)
196 # self.branch = special_view_decoder(Branch)(branch)
198 print "Bad Branch data:", string
201 def to_striped_block(self
, select
):
202 return [colours
.number_to_colour(self
.newStreamSeqNum
),
203 colours
.number_to_colour(self
.newPredictionSeqNum
),
204 colours
.number_to_colour(self
.newPC
)]
206 class Counts(BlobVisualData
):
207 """Treat the input data as just a /-separated list of count values (or
208 just a single value)"""
212 def from_string(self
, string
):
213 self
.counts
= map(int, re
.split('/', string
))
216 def to_striped_block(self
, select
):
217 return map(colours
.number_to_colour
, self
.counts
)
219 class Colour(BlobVisualData
):
220 """A fixed colour block, used for special colour decoding"""
221 def __init__(self
, colour
):
224 def to_striped_block(self
, select
):
227 class DcacheAccess(BlobVisualData
):
228 """Data cache accesses [RW];id"""
233 def from_string(self
, string
):
234 self
.direc
, id = re
.match('^([RW]);([^;]*);.*$', string
).groups()
235 self
.id.from_string(id)
241 def to_striped_block(self
, select
):
242 if self
.direc
== 'R':
243 direc_colour
= colours
.readColour
244 elif self
.direc
== 'R':
245 direc_colour
= colours
.writeColour
247 direc_colour
= colours
.errorColour
248 return [direc_colour
] + self
.id.to_striped_block(select
)
250 class ColourPattern(object):
251 """Super class for decoders that make 2D grids rather than just single
256 def to_striped_block(self
, select
):
257 return [[[colours
.errorColour
]]]
259 def special_view_decoder(class_
):
260 """Generate a decode function that checks for special character
261 arguments first (and generates a fixed colour) before building a
262 BlobVisualData of the given class"""
264 if symbol
in special_state_colours
:
265 return Colour(special_state_colours
[symbol
])
267 return class_().from_string(symbol
)
270 class TwoDColours(ColourPattern
):
271 """A 2D grid pattern decoder"""
272 def __init__(self
, blockss
):
273 self
.blockss
= blockss
276 def decoder(class_
, elemClass
, dataName
):
277 """Factory for making decoders for particular block types"""
279 if dataName
not in pairs
:
280 print 'TwoDColours: no event data called:', \
281 dataName
, 'in:', pairs
282 return class_([[Colour(colours
.errorColour
)]])
284 parsed
= parse
.list_parser(pairs
[dataName
])
285 return class_(parse
.map2(special_view_decoder(elemClass
), \
290 def indexed_decoder(class_
, elemClass
, dataName
, picPairs
):
291 """Factory for making decoders for particular block types but
292 where the list elements are pairs of (index, data) and
293 strip and stripelems counts are picked up from the pair
294 data on the decoder's picture file. This gives a 2D layout
295 of the values with index 0 at strip=0, elem=0 and index 1
296 at strip=0, elem=1"""
298 if dataName
not in pairs
:
299 print 'TwoDColours: no event data called:', \
300 dataName
, 'in:', pairs
301 return class_([[Colour(colours
.errorColour
)]])
303 strips
= int(picPairs
['strips'])
304 strip_elems
= int(picPairs
['stripelems'])
306 raw_iv_pairs
= pairs
[dataName
]
308 parsed
= parse
.parse_indexed_list(raw_iv_pairs
)
310 array
= [[Colour(colours
.emptySlotColour
)
311 for i
in xrange(0, strip_elems
)]
312 for j
in xrange(0, strips
)]
314 for index
, value
in parsed
:
316 array
[index
% strips
][index
/ strips
] = \
317 special_view_decoder(elemClass
)(value
)
319 print "Element out of range strips: %d," \
320 " stripelems %d, index: %d" % (strips
,
323 # return class_(array)
328 """Get a flat list of all elements"""
330 for blocks
in self
.blockss
:
334 def to_striped_block(self
, select
):
335 return parse
.map2(lambda d
: d
.to_striped_block(select
), self
.blockss
)
337 class FrameColours(ColourPattern
):
338 """Decode to a 2D grid which has a single occupied row from the event
339 data and some blank rows forming a frame with the occupied row as a
340 'title' coloured stripe"""
341 def __init__(self
, block
, numBlankSlots
):
342 self
.numBlankSlots
= numBlankSlots
346 def decoder(class_
, elemClass
, numBlankSlots
, dataName
):
347 """Factory for element type"""
349 if dataName
not in pairs
:
350 print 'FrameColours: no event data called:', dataName
, \
352 return class_([Colour(colours
.errorColour
)])
354 parsed
= parse
.list_parser(pairs
[dataName
])
355 return class_(special_view_decoder(elemClass
)
356 (parsed
[0][0]), numBlankSlots
)
362 def to_striped_block(self
, select
):
363 return ([[self
.block
.to_striped_block(select
)]] +
364 (self
.numBlankSlots
* [[[colours
.backgroundColour
]]]))
366 special_state_colours
= {
367 'U': colours
.unknownColour
,
368 'B': colours
.blockedColour
,
369 '-': colours
.bubbleColour
,
370 '': colours
.emptySlotColour
,
371 'E': colours
.emptySlotColour
,
372 'R': colours
.reservedSlotColour
,
373 'X': colours
.errorColour
,
374 'F': colours
.faultColour
,
375 'r': colours
.readColour
,
376 'w': colours
.writeColour
379 special_state_names
= {
392 special_state_chars
= special_state_colours
.keys()
394 # The complete set of available block data types
395 decoder_element_classes
= {
399 'dcache': DcacheAccess
,
403 indexed_decoder_element_classes
= {
404 'indexedCounts' : Counts
407 def find_colour_decoder(stripSpace
, decoderName
, dataName
, picPairs
):
408 """Make a colour decoder from some picture file blob attributes"""
409 if decoderName
== 'frame':
410 return FrameColours
.decoder(Counts
, stripSpace
, dataName
)
411 elif decoderName
in decoder_element_classes
:
412 return TwoDColours
.decoder(decoder_element_classes
[decoderName
],
414 elif decoderName
in indexed_decoder_element_classes
:
415 return TwoDColours
.indexed_decoder(
416 indexed_decoder_element_classes
[decoderName
], dataName
, picPairs
)
420 class IdedObj(object):
421 """An object identified by an Id carrying paired data.
422 The super class for Inst and Line"""
424 def __init__(self
, id, pairs
={}):
428 def __cmp__(self
, right
):
429 return cmp(self
.id, right
.id)
431 def table_line(self
):
432 """Represent the object as a list of table row data"""
435 # FIXME, add a table column titles?
438 return ' '.join(self
.table_line())
441 """A non-fault instruction"""
442 def __init__(self
, id, disassembly
, addr
, pairs
={}):
443 super(Inst
,self
).__init
__(id, pairs
)
444 if 'nextAddr' in pairs
:
445 self
.nextAddr
= int(pairs
['nextAddr'], 0)
446 del pairs
['nextAddr']
449 self
.disassembly
= disassembly
452 def table_line(self
):
453 if self
.nextAddr
is not None:
454 addrStr
= '0x%x->0x%x' % (self
.addr
, self
.nextAddr
)
456 addrStr
= '0x%x' % self
.addr
457 ret
= [addrStr
, self
.disassembly
]
458 for name
, value
in self
.pairs
.iteritems():
459 ret
.append("%s=%s" % (name
, str(value
)))
462 class InstFault(IdedObj
):
463 """A fault instruction"""
464 def __init__(self
, id, fault
, addr
, pairs
={}):
465 super(InstFault
,self
).__init
__(id, pairs
)
469 def table_line(self
):
470 ret
= ["0x%x" % self
.addr
, self
.fault
]
471 for name
, value
in self
.pairs
:
472 ret
.append("%s=%s", name
, str(value
))
477 def __init__(self
, id, vaddr
, paddr
, size
, pairs
={}):
478 super(Line
,self
).__init
__(id, pairs
)
483 def table_line(self
):
484 ret
= ["0x%x/0x%x" % (self
.vaddr
, self
.paddr
), "%d" % self
.size
]
485 for name
, value
in self
.pairs
:
486 ret
.append("%s=%s", name
, str(value
))
489 class LineFault(IdedObj
):
490 """A faulting line"""
491 def __init__(self
, id, fault
, vaddr
, pairs
={}):
492 super(LineFault
,self
).__init
__(id, pairs
)
496 def table_line(self
):
497 ret
= ["0x%x" % self
.vaddr
, self
.fault
]
498 for name
, value
in self
.pairs
:
499 ret
.append("%s=%s", name
, str(value
))
502 class BlobEvent(object):
503 """Time event for a single blob"""
504 def __init__(self
, unit
, time
, pairs
= {}):
508 # dict of picChar (blob name) to visual data
510 # Miscellaneous unparsed MinorTrace line data
512 # Non-MinorTrace debug printout for this unit at this time
515 def find_ided_objects(self
, model
, picChar
, includeInstLines
):
516 """Find instructions/lines mentioned in the blob's event
519 if picChar
in self
.visuals
:
520 blocks
= self
.visuals
[picChar
].elems()
522 instId
= data
.get_inst()
523 lineId
= data
.get_line()
524 if instId
is not None:
525 inst
= model
.find_inst(instId
)
526 line
= model
.find_line(instId
)
529 if includeInstLines
and line
is not None:
531 elif lineId
is not None:
532 line
= model
.find_line(lineId
)
535 map(find_inst
, blocks
)
538 class BlobModel(object):
539 """Model bringing together blob definitions and parsed events"""
540 def __init__(self
, unitNamePrefix
=''):
542 self
.unitNameToBlobs
= {}
545 self
.picSize
= Point(20,10)
547 self
.unitNamePrefix
= unitNamePrefix
549 def clear_events(self
):
550 """Drop all events and times"""
557 for unit
, events
in self
.unitEvents
.iteritems():
558 self
.unitEvents
[unit
] = []
560 def add_blob(self
, blob
):
561 """Add a parsed blob to the model"""
562 self
.blobs
.append(blob
)
563 if blob
.unit
not in self
.unitNameToBlobs
:
564 self
.unitNameToBlobs
[blob
.unit
] = []
566 self
.unitNameToBlobs
[blob
.unit
].append(blob
)
568 def add_inst(self
, inst
):
569 """Add a MinorInst instruction definition to the model"""
570 # Is this a non micro-op instruction. Microops (usually) get their
571 # fetchSeqNum == 0 varient stored first
572 macroop_key
= (inst
.id.fetchSeqNum
, 0)
573 full_key
= (inst
.id.fetchSeqNum
, inst
.id.execSeqNum
)
575 if inst
.id.execSeqNum
!= 0 and macroop_key
not in self
.insts
:
576 self
.insts
[macroop_key
] = inst
578 self
.insts
[full_key
] = inst
580 def find_inst(self
, id):
581 """Find an instruction either as a microop or macroop"""
582 macroop_key
= (id.fetchSeqNum
, 0)
583 full_key
= (id.fetchSeqNum
, id.execSeqNum
)
585 if full_key
in self
.insts
:
586 return self
.insts
[full_key
]
587 elif macroop_key
in self
.insts
:
588 return self
.insts
[macroop_key
]
592 def add_line(self
, line
):
593 """Add a MinorLine line to the model"""
594 self
.lines
[line
.id.lineSeqNum
] = line
596 def add_unit_event(self
, event
):
597 """Add a single event to the model. This must be an event at a
598 time >= the current maximum time"""
599 if event
.unit
in self
.unitEvents
:
600 events
= self
.unitEvents
[event
.unit
]
601 if len(events
) > 0 and events
[len(events
)-1].time
> event
.time
:
602 print "Bad event ordering"
605 self
.lastTime
= max(self
.lastTime
, event
.time
)
607 def extract_times(self
):
608 """Extract a list of all the times from the seen events. Call after
609 reading events to give a safe index list to use for time indices"""
611 for unitEvents
in self
.unitEvents
.itervalues():
612 for event
in unitEvents
:
613 times
[event
.time
] = 1
614 self
.times
= times
.keys()
617 def find_line(self
, id):
618 """Find a line by id"""
620 return self
.lines
.get(key
, None)
622 def find_event_bisection(self
, unit
, time
, events
,
623 lower_index
, upper_index
):
624 """Find an event by binary search on time indices"""
625 while lower_index
<= upper_index
:
626 pivot
= (upper_index
+ lower_index
) / 2
627 pivotEvent
= events
[pivot
]
628 event_equal
= (pivotEvent
.time
== time
or
629 (pivotEvent
.time
< time
and
630 (pivot
== len(events
) - 1 or
631 events
[pivot
+ 1].time
> time
)))
635 elif time
> pivotEvent
.time
:
636 if pivot
== upper_index
:
639 lower_index
= pivot
+ 1
640 elif time
< pivotEvent
.time
:
641 if pivot
== lower_index
:
644 upper_index
= pivot
- 1
649 def find_unit_event_by_time(self
, unit
, time
):
650 """Find the last event for the given unit at time <= time"""
651 if unit
in self
.unitEvents
:
652 events
= self
.unitEvents
[unit
]
653 ret
= self
.find_event_bisection(unit
, time
, events
,
660 def find_time_index(self
, time
):
661 """Find a time index close to the given time (where
662 times[return] <= time and times[return+1] > time"""
664 lastIndex
= len(self
.times
) - 1
665 while ret
< lastIndex
and self
.times
[ret
+ 1] <= time
:
669 def add_minor_inst(self
, rest
):
670 """Parse and add a MinorInst line to the model"""
671 pairs
= parse
.parse_pairs(rest
)
672 other_pairs
= dict(pairs
)
674 id = Id().from_string(pairs
['id'])
675 del other_pairs
['id']
677 addr
= int(pairs
['addr'], 0)
678 del other_pairs
['addr']
680 if 'inst' in other_pairs
:
681 del other_pairs
['inst']
683 # Collapse unnecessary spaces in disassembly
684 disassembly
= re
.sub(' *', ' ',
685 re
.sub('^ *', '', pairs
['inst']))
687 inst
= Inst(id, disassembly
, addr
, other_pairs
)
689 elif 'fault' in other_pairs
:
690 del other_pairs
['fault']
692 inst
= InstFault(id, pairs
['fault'], addr
, other_pairs
)
696 def add_minor_line(self
, rest
):
697 """Parse and add a MinorLine line to the model"""
698 pairs
= parse
.parse_pairs(rest
)
699 other_pairs
= dict(pairs
)
701 id = Id().from_string(pairs
['id'])
702 del other_pairs
['id']
704 vaddr
= int(pairs
['vaddr'], 0)
705 del other_pairs
['vaddr']
707 if 'paddr' in other_pairs
:
708 del other_pairs
['paddr']
709 del other_pairs
['size']
710 paddr
= int(pairs
['paddr'], 0)
711 size
= int(pairs
['size'], 0)
713 self
.add_line(Line(id,
714 vaddr
, paddr
, size
, other_pairs
))
715 elif 'fault' in other_pairs
:
716 del other_pairs
['fault']
718 self
.add_line(LineFault(id, pairs
['fault'], vaddr
, other_pairs
))
720 def load_events(self
, file, startTime
=0, endTime
=None):
721 """Load an event file and add everything to this model"""
722 def update_comments(comments
, time
):
723 # Add a list of comments to an existing event, if there is one at
724 # the given time, or create a new, correctly-timed, event from
725 # the last event and attach the comments to that
726 for commentUnit
, commentRest
in comments
:
727 event
= self
.find_unit_event_by_time(commentUnit
, time
)
728 # Find an event to which this comment can be attached
730 # No older event, make a new empty one
731 event
= BlobEvent(commentUnit
, time
, {})
732 self
.add_unit_event(event
)
733 elif event
.time
!= time
:
734 # Copy the old event and make a new one with the right
736 newEvent
= BlobEvent(commentUnit
, time
, event
.pairs
)
737 newEvent
.visuals
= dict(event
.visuals
)
739 self
.add_unit_event(event
)
740 event
.comments
.append(commentRest
)
744 # A negative time will *always* be different from an event time
748 minor_trace_line_count
= 0
751 default_colour
= [[colours
.unknownColour
]]
752 next_progress_print_event_count
= 1000
754 if not os
.access(file, os
.R_OK
):
755 print 'Can\'t open file', file
758 print 'Opening file', file
762 start_wall_time
= wall_time()
764 # Skip leading events
765 still_skipping
= True
767 while l
and still_skipping
:
768 match
= re
.match('^\s*(\d+):', l
)
769 if match
is not None:
770 event_time
= match
.groups()
771 if int(event_time
[0]) >= startTime
:
772 still_skipping
= False
778 match_line_re
= re
.compile(
779 '^\s*(\d+):\s*([\w\.]+):\s*(Minor\w+:)?\s*(.*)$')
781 # Parse each line of the events file, accumulating comments to be
782 # attached to MinorTrace events when the time changes
783 reached_end_time
= False
784 while not reached_end_time
and l
:
785 match
= match_line_re
.match(l
)
786 if match
is not None:
787 event_time
, unit
, line_type
, rest
= match
.groups()
788 event_time
= int(event_time
)
790 unit
= re
.sub('^' + self
.unitNamePrefix
+ '\.?(.*)$',
793 # When the time changes, resolve comments
794 if event_time
!= time
:
795 if self
.numEvents
> next_progress_print_event_count
:
796 print ('Parsed to time: %d' % event_time
)
797 next_progress_print_event_count
= (
798 self
.numEvents
+ 1000)
799 update_comments(comments
, time
)
803 if line_type
is None:
804 # Treat this line as just a 'comment'
805 comments
.append((unit
, rest
))
806 elif line_type
== 'MinorTrace:':
807 minor_trace_line_count
+= 1
809 # Only insert this event if it's not the same as
810 # the last event we saw for this unit
811 if last_time_lines
.get(unit
, None) != rest
:
812 event
= BlobEvent(unit
, event_time
, {})
813 pairs
= parse
.parse_pairs(rest
)
816 # Try to decode the colour data for this event
817 blobs
= self
.unitNameToBlobs
.get(unit
, [])
819 if blob
.visualDecoder
is not None:
820 event
.visuals
[blob
.picChar
] = (
821 blob
.visualDecoder(pairs
))
823 self
.add_unit_event(event
)
824 last_time_lines
[unit
] = rest
825 elif line_type
== 'MinorInst:':
826 self
.add_minor_inst(rest
)
827 elif line_type
== 'MinorLine:':
828 self
.add_minor_line(rest
)
830 if endTime
is not None and time
> endTime
:
831 reached_end_time
= True
835 update_comments(comments
, time
)
839 end_wall_time
= wall_time()
841 print 'Total events:', minor_trace_line_count
, 'unique events:', \
843 print 'Time to parse:', end_wall_time
- start_wall_time
845 def add_blob_picture(self
, offset
, pic
, nameDict
):
846 """Add a parsed ASCII-art pipeline markup to the model"""
849 pic_width
= max(pic_width
, len(line
))
850 pic_height
= len(pic
)
852 # Number of horizontal characters per 'pixel'. Should be 2
855 # Clean up pic_width to a multiple of charsPerPixel
856 pic_width
= (pic_width
+ charsPerPixel
- 1) // 2
858 self
.picSize
= Point(pic_width
, pic_height
)
861 """Return the char pair at the given point.
862 Returns None for characters off the picture"""
863 x
, y
= point
.to_pair()
865 if y
>= len(pic
) or x
>= len(pic
[y
]):
868 return pic
[y
][x
:x
+ charsPerPixel
]
870 def clear_pic_at(point
):
871 """Clear the chars at point so we don't trip over them again"""
873 x
= point
.x
* charsPerPixel
874 pic
[point
.y
] = line
[0:x
] + (' ' * charsPerPixel
) + \
875 line
[x
+ charsPerPixel
:]
877 def skip_same_char(start
, increment
):
878 """Skip characters which match pic_at(start)"""
881 while pic_at(hunt
) == char
:
885 def find_size(start
):
886 """Find the size of a rectangle with top left hand corner at
887 start consisting of (at least) a -. shaped corner describing
888 the top right corner of a rectangle of the same char"""
890 hunt_x
= skip_same_char(start
, Point(1,0))
891 hunt_y
= skip_same_char(start
, Point(0,1))
892 off_bottom_right
= (hunt_x
* Point(1,0)) + (hunt_y
* Point(0,1))
893 return off_bottom_right
- start
895 def point_return(point
):
896 """Carriage return, line feed"""
897 return Point(0, point
.y
+ 1)
899 def find_arrow(start
):
900 """Find a simple 1-char wide arrow"""
902 def body(endChar
, contChar
, direc
):
904 arrow_point
+= Point(0, 1)
906 while pic_at(arrow_point
) == contChar
:
907 clear_pic_at(arrow_point
)
908 arrow_point
+= Point(0, 1)
910 if pic_at(arrow_point
) == endChar
:
911 clear_pic_at(arrow_point
)
912 self
.add_blob(blobs
.Arrow('_', start
+ offset
,
914 size
= (Point(1, 1) + arrow_point
- start
)))
916 print 'Bad arrow', start
920 body('-/', ' :', 'right')
922 body('\\-', ': ', 'left')
924 blank_chars
= [' ', ' :', ': ']
926 # Traverse the picture left to right, top to bottom to find blobs
929 while pic_at(point
) is not None:
930 while pic_at(point
) is not None:
933 self
.add_blob(blobs
.Arrow('_', point
+ offset
,
936 self
.add_blob(blobs
.Arrow('_', point
+ offset
,
938 elif char
== '-\\' or char
== '/-':
940 elif char
in blank_chars
:
943 if char
not in seen_dict
:
944 size
= find_size(point
)
945 topLeft
= point
+ offset
946 if char
not in nameDict
:
948 self
.add_blob(blobs
.Block(char
,
949 nameDict
.get(char
, '_'),
950 topLeft
, size
= size
))
952 # Named blobs, set visual info.
953 blob
= nameDict
[char
]
955 blob
.topLeft
= topLeft
957 seen_dict
[char
] = True
958 point
= skip_same_char(point
, Point(1,0))
959 point
= point_return(point
)
961 def load_picture(self
, filename
):
962 """Load a picture file into the model"""
963 def parse_blob_description(char
, unit
, macros
, pairsList
):
964 # Parse the name value pairs in a blob-describing line
965 def expand_macros(pairs
, newPairs
):
966 # Recursively expand macros
967 for name
, value
in newPairs
:
969 expand_macros(pairs
, macros
[name
])
974 pairs
= expand_macros({}, pairsList
)
978 typ
= pairs
.get('type', 'block')
979 colour
= colours
.name_to_colour(pairs
.get('colour', 'black'))
982 ret
= blobs
.Key(char
, unit
, Point(0,0), colour
)
984 ret
= blobs
.Block(char
, unit
, Point(0,0), colour
)
986 print "Bad picture blog type:", typ
988 if 'hideId' in pairs
:
989 hide
= pairs
['hideId']
990 ret
.dataSelect
.ids
-= set(hide
)
993 ret
.displayName
= pairs
.get('name', unit
)
994 ret
.nameLoc
= pairs
.get('nameLoc', 'top')
995 ret
.shape
= pairs
.get('shape', 'box')
996 ret
.stripDir
= pairs
.get('stripDir', 'horiz')
997 ret
.stripOrd
= pairs
.get('stripOrd', 'LR')
998 ret
.blankStrips
= int(pairs
.get('blankStrips', '0'))
999 ret
.shorten
= int(pairs
.get('shorten', '0'))
1001 if 'decoder' in pairs
:
1002 decoderName
= pairs
['decoder']
1003 dataElement
= pairs
.get('dataElement', decoderName
)
1005 decoder
= find_colour_decoder(ret
.blankStrips
,
1006 decoderName
, dataElement
, pairs
)
1007 if decoder
is not None:
1008 ret
.visualDecoder
= decoder
1010 print 'Bad visualDecoder requested:', decoderName
1012 if 'border' in pairs
:
1013 border
= pairs
['border']
1014 if border
== 'thin':
1016 elif border
== 'mid':
1021 ret
.colours
= pairs
.get('colours', ret
.colours
)
1025 def line_is_comment(line
):
1026 """Returns true if a line starts with #, returns False
1027 for lines which are None"""
1028 return line
is not None \
1029 and re
.match('^\s*#', line
) is not None
1032 """Get a line from file f extending that line if it ends in
1033 '\' and dropping lines that start with '#'s"""
1036 # Discard comment lines
1037 while line_is_comment(ret
):
1041 extend_match
= re
.match('^(.*)\\\\$', ret
)
1043 while extend_match
is not None:
1044 new_line
= f
.readline()
1046 if new_line
is not None and not line_is_comment(new_line
):
1047 line_wo_backslash
, = extend_match
.groups()
1048 ret
= line_wo_backslash
+ new_line
1049 extend_match
= re
.match('^(.*)\\\\$', ret
)
1055 # Macros are recursively expanded into name=value pairs
1058 if not os
.access(filename
, os
.R_OK
):
1059 print 'Can\'t open file', filename
1062 print 'Opening file', filename
1069 self
.unitEvents
= {}
1072 # Actually parse the file
1075 l
= parse
.remove_trailing_ws(l
)
1076 l
= re
.sub('#.*', '', l
)
1078 if re
.match("^\s*$", l
) is not None:
1085 picture
.append(re
.sub('\s*$', '', l
))
1087 line_match
= re
.match(
1088 '^([a-zA-Z0-9][a-zA-Z0-9]):\s+([\w.]+)\s*(.*)', l
)
1089 macro_match
= re
.match('macro\s+(\w+):(.*)', l
)
1091 if macro_match
is not None:
1092 name
, defn
= macro_match
.groups()
1093 macros
[name
] = parse
.parse_pairs_list(defn
)
1094 elif line_match
is not None:
1095 char
, unit
, pairs
= line_match
.groups()
1096 blob
= parse_blob_description(char
, unit
, macros
,
1097 parse
.parse_pairs_list(pairs
))
1098 blob_char_dict
[char
] = blob
1099 # Setup the events structure
1100 self
.unitEvents
[unit
] = []
1102 print 'Problem with Blob line:', l
1107 self
.add_blob_picture(Point(0,1), picture
, blob_char_dict
)