]> git.donarmstrong.com Git - kiibohd-kll.git/blobdiff - kll_lib/containers.py
Adding support for "Soft Replace" kll 0.3c
[kiibohd-kll.git] / kll_lib / containers.py
index b3a22185c688e141a6562255af657183720885d4..814cd5cc044de8c4ef4b66fe22c0229130471598 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 # KLL Compiler Containers
 #
-# Copyright (C) 2014 by Jacob Alexander
+# Copyright (C) 2014-2015 by Jacob Alexander
 #
 # This file is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -18,6 +18,8 @@
 
 ### Imports ###
 
+import copy
+
 
 
 ### Decorators ###
@@ -29,7 +31,70 @@ ERROR = '\033[5;1;31mERROR\033[0m:'
 
 ### Parsing ###
 
+
  ## Containers
+
+class ScanCode:
+       # Container for ScanCodes
+       #
+       # scancode        - Non-interconnect adjusted scan code
+       # interconnect_id - Unique id for the interconnect node
+       def __init__( self, scancode, interconnect_id ):
+               self.scancode = scancode
+               self.interconnect_id = interconnect_id
+
+       def __eq__( self, other ):
+               return self.dict() == other.dict()
+
+       def __repr__( self ):
+               return repr( self.dict() )
+
+       def dict( self ):
+               return {
+                       'ScanCode' : self.scancode,
+                       'Id'       : self.interconnect_id,
+               }
+
+       # Calculate the actual scancode using the offset list
+       def offset( self, offsetList ):
+               if self.interconnect_id > 0:
+                       return self.scancode + offsetList[ self.interconnect_id - 1 ]
+               else:
+                       return self.scancode
+
+
+class ScanCodeStore:
+       # Unique lookup for ScanCodes
+       def __init__( self ):
+               self.scancodes = []
+
+       def __getitem__( self, name ):
+               # First check if this is a ScanCode object
+               if isinstance( name, ScanCode ):
+                       # Do a reverse lookup
+                       for idx, scancode in enumerate( self.scancodes ):
+                               if scancode == name:
+                                       return idx
+
+                       # Could not find scancode
+                       return None
+
+               # Return scancode using unique id
+               return self.scancodes[ name ]
+
+       # Attempt add ScanCode to list, return unique id
+       def append( self, new_scancode ):
+               # Iterate through list to make sure this is a unique ScanCode
+               for idx, scancode in enumerate( self.scancodes ):
+                       if new_scancode == scancode:
+                               return idx
+
+               # Unique entry, add to the list
+               self.scancodes.append( new_scancode )
+
+               return len( self.scancodes ) - 1
+
+
 class Capabilities:
        # Container for capabilities dictionary and convenience functions
        def __init__( self ):
@@ -50,14 +115,14 @@ class Capabilities:
                totalBytes = 0
 
                # Iterate over the arguments, summing the total bytes
-               for arg in self.capabilities[ name ][1]:
-                       totalBytes += int( arg[1] )
+               for arg in self.capabilities[ name ][ 1 ]:
+                       totalBytes += int( arg[ 1 ] )
 
                return totalBytes
 
        # Name of the capability function
        def funcName( self, name ):
-               return self.capabilities[ name ][0]
+               return self.capabilities[ name ][ 0 ]
 
 
        # Only valid while dictionary keys are not added/removed
@@ -83,32 +148,316 @@ class Macros:
                # Default layer (0)
                self.layer = 0
 
+               # Unique ScanCode Hash Id Lookup
+               self.scanCodeStore = ScanCodeStore()
+
                # Macro Storage
-               self.macros = [ [] ]
+               self.macros = [ dict() ]
+
+               # Base Layout Storage
+               self.baseLayout = None
+               self.layerLayoutMarkers = []
+
+               # Correlated Macro Data
+               self.resultsIndex = dict()
+               self.triggersIndex = dict()
+               self.resultsIndexSorted = []
+               self.triggersIndexSorted = []
+               self.triggerList = []
+               self.maxScanCode = []
+               self.firstScanCode = []
+               self.interconnectOffset = []
+
+               # USBCode Assignment Cache
+               self.assignmentCache = []
+
+       def __repr__( self ):
+               return "{0}".format( self.macros )
+
+       def completeBaseLayout( self ):
+               # Copy base layout for later use when creating partial layers and add marker
+               self.baseLayout = copy.deepcopy( self.macros[ 0 ] )
+               self.layerLayoutMarkers.append( copy.deepcopy( self.baseLayout ) ) # Not used for default layer, just simplifies coding
 
-       def setLayer( self, layer ):
-               self.layer = layer
+       def removeUnmarked( self ):
+               # Remove all of the unmarked mappings from the partial layer
+               for trigger in self.layerLayoutMarkers[ self.layer ].keys():
+                       del self.macros[ self.layer ][ trigger ]
+
+       def addLayer( self ):
+               # Increment layer count, and append another macros dictionary
+               self.layer += 1
+               self.macros.append( copy.deepcopy( self.baseLayout ) )
+
+               # Add a layout marker for each layer
+               self.layerLayoutMarkers.append( copy.deepcopy( self.baseLayout ) )
 
        # Use for ScanCode trigger macros
        def appendScanCode( self, trigger, result ):
-               self.macros[ self.layer ][ trigger ] = result
+               if not trigger in self.macros[ self.layer ]:
+                       self.replaceScanCode( trigger, result )
+               else:
+                       self.macros[ self.layer ][ trigger ].append( result )
+
+       # Remove the given trigger/result pair
+       def removeScanCode( self, trigger, result ):
+               # Remove all instances of the given trigger/result pair
+               while result in self.macros[ self.layer ][ trigger ]:
+                       self.macros[ self.layer ][ trigger ].remove( result )
+
+       # Replaces the given trigger with the given result
+       # If multiple results for a given trigger, clear, then add
+       def replaceScanCode( self, trigger, result ):
+               self.macros[ self.layer ][ trigger ] = [ result ]
+
+               # Mark layer scan code, so it won't be removed later
+               # Also check to see if it hasn't already been removed before
+               if not self.baseLayout is None and trigger in self.layerLayoutMarkers[ self.layer ]:
+                       del self.layerLayoutMarkers[ self.layer ][ trigger ]
 
-       # Use for USBCode trigger macros
-       # An extra lookup is required
-       def appendUSBCode( self, trigger, result ):
-               noSuccess = True
+       # Return a list of ScanCode triggers with the given USB Code trigger
+       def lookupUSBCodes( self, usbCode ):
+               scanCodeList = []
 
+               # Scan current layer for USB Codes
                for macro in self.macros[ self.layer ].keys():
-                       # USB Code Found
-                       if trigger == self.macros[ self.layer ][ macro ]:
-                               print ( "USBCode - Replacing '{0}' with '{1}' -> '{2}'".format( trigger, macro, result ) )
-                               self.macros[ self.layer ][ macro ] = result
-                               noSuccess = False
-
-               # Only show warning if no replacements were done
-               if noSuccess:
-                       print ( "Warning: '{1}' USB Code not found in layer {1}".format( trigger, self.layer ) )
+                       if usbCode in self.macros[ self.layer ][ macro ]:
+                               scanCodeList.append( macro )
+
+               if len(scanCodeList) == 0:
+                       if len(usbCode) > 1 or len(usbCode[0]) > 1:
+                               for combo in usbCode:
+                                       comboCodes = list()
+                                       for key in combo:
+                                               scanCode = self.lookupUSBCodes(((key,),))
+                                               comboCodes.append(scanCode[0][0][0])
+                                       scanCodeList.append(tuple(code for code in comboCodes))
+                               scanCodeList = [tuple(scanCodeList)]
+
+               return scanCodeList
+
+       # Check whether we should do soft replacement
+       def softReplaceCheck( self, scanCode ):
+               # First check if not the default layer
+               if self.layer == 0:
+                       return True
+
+               # Check if current layer is set the same as the BaseMap
+               if not self.baseLayout is None and scanCode in self.layerLayoutMarkers[ self.layer ]:
                        return False
 
+               # Otherwise, allow replacement
                return True
 
+       # Cache USBCode Assignment
+       def cacheAssignment( self, operator, scanCode, result ):
+               self.assignmentCache.append( [ operator, scanCode, result ] )
+
+       # Assign cached USBCode Assignments
+       def replayCachedAssignments( self ):
+               # Iterate over each item in the assignment cache
+               for item in self.assignmentCache:
+                       # Check operator, and choose the specified assignment action
+                       # Append Case
+                       if item[0] == ":+":
+                               self.appendScanCode( item[1], item[2] )
+
+                       # Remove Case
+                       elif item[0] == ":-":
+                               self.removeScanCode( item[1], item[2] )
+
+                       # Replace Case
+                       elif item[0] == ":" or item[0] == "::":
+                               self.replaceScanCode( item[1], item[2] )
+
+               # Clear assignment cache
+               self.assignmentCache = []
+
+       # Generate/Correlate Layers
+       def generate( self ):
+               self.generateIndices()
+               self.sortIndexLists()
+               self.generateOffsetTable()
+               self.generateTriggerLists()
+
+       # Generates Index of Results and Triggers
+       def generateIndices( self ):
+               # Iterate over every trigger result, and add to the resultsIndex and triggersIndex
+               for layer in range( 0, len( self.macros ) ):
+                       for trigger in self.macros[ layer ].keys():
+                               # Each trigger has a list of results
+                               for result in self.macros[ layer ][ trigger ]:
+                                       # Only add, with an index, if result hasn't been added yet
+                                       if not result in self.resultsIndex:
+                                               self.resultsIndex[ result ] = len( self.resultsIndex )
+
+                                       # Then add a trigger for each result, if trigger hasn't been added yet
+                                       triggerItem = tuple( [ trigger, self.resultsIndex[ result ] ] )
+                                       if not triggerItem in self.triggersIndex:
+                                               self.triggersIndex[ triggerItem ] = len( self.triggersIndex )
+
+       # Sort Index Lists using the indices rather than triggers/results
+       def sortIndexLists( self ):
+               self.resultsIndexSorted = [ None ] * len( self.resultsIndex )
+               # Iterate over the resultsIndex and sort by index
+               for result in self.resultsIndex.keys():
+                       self.resultsIndexSorted[ self.resultsIndex[ result ] ] = result
+
+               self.triggersIndexSorted = [ None ] * len( self.triggersIndex )
+               # Iterate over the triggersIndex and sort by index
+               for trigger in self.triggersIndex.keys():
+                       self.triggersIndexSorted[ self.triggersIndex[ trigger ] ] = trigger
+
+       # Generates list of offsets for each of the interconnect ids
+       def generateOffsetTable( self ):
+               idMaxScanCode = [ 0 ]
+
+               # Iterate over each layer to get list of max scancodes associated with each interconnect id
+               for layer in range( 0, len( self.macros ) ):
+                       # Iterate through each trigger/sequence in the layer
+                       for sequence in self.macros[ layer ].keys():
+                               # Iterate over the trigger to locate the ScanCodes
+                               for combo in sequence:
+                                       # Iterate over each scancode id in the combo
+                                       for scancode_id in combo:
+                                               # Lookup ScanCode
+                                               scancode_obj = self.scanCodeStore[ scancode_id ]
+
+                                               # Extend list if not large enough
+                                               if scancode_obj.interconnect_id >= len( idMaxScanCode ):
+                                                       idMaxScanCode.extend( [ 0 ] * ( scancode_obj.interconnect_id - len( idMaxScanCode ) + 1 ) )
+
+                                               # Determine if the max seen id for this interconnect id
+                                               if scancode_obj.scancode > idMaxScanCode[ scancode_obj.interconnect_id ]:
+                                                       idMaxScanCode[ scancode_obj.interconnect_id ] = scancode_obj.scancode
+
+               # Generate interconnect offsets
+               self.interconnectOffset = [ idMaxScanCode[0] + 1 ]
+               for index in range( 1, len( idMaxScanCode ) ):
+                       self.interconnectOffset.append( self.interconnectOffset[ index - 1 ] + idMaxScanCode[ index ] )
+
+       # Generates Trigger Lists per layer using index lists
+       def generateTriggerLists( self ):
+               for layer in range( 0, len( self.macros ) ):
+                       # Set max scancode to 0xFF (255)
+                       # But keep track of the actual max scancode and reduce the list size
+                       self.triggerList.append( [ [] ] * 0xFF )
+                       self.maxScanCode.append( 0x00 )
+
+                       # Iterate through trigger macros to locate necessary ScanCodes and corresponding triggerIndex
+                       for trigger in self.macros[ layer ].keys():
+                               for variant in range( 0, len( self.macros[ layer ][ trigger ] ) ):
+                                       # Identify result index
+                                       resultIndex = self.resultsIndex[ self.macros[ layer ][ trigger ][ variant ] ]
+
+                                       # Identify trigger index
+                                       triggerIndex = self.triggersIndex[ tuple( [ trigger, resultIndex ] ) ]
+
+                                       # Iterate over the trigger to locate the ScanCodes
+                                       for sequence in trigger:
+                                               for combo_id in sequence:
+                                                       combo = self.scanCodeStore[ combo_id ].offset( self.interconnectOffset )
+                                                       # Append triggerIndex for each found scanCode of the Trigger List
+                                                       # Do not re-add if triggerIndex is already in the Trigger List
+                                                       if not triggerIndex in self.triggerList[ layer ][ combo ]:
+                                                               # Append is working strangely with list pre-initialization
+                                                               # Doing a 0 check replacement instead -HaaTa
+                                                               if len( self.triggerList[ layer ][ combo ] ) == 0:
+                                                                       self.triggerList[ layer ][ combo ] = [ triggerIndex ]
+                                                               else:
+                                                                       self.triggerList[ layer ][ combo ].append( triggerIndex )
+
+                                                       # Look for max Scan Code
+                                                       if combo > self.maxScanCode[ layer ]:
+                                                               self.maxScanCode[ layer ] = combo
+
+                       # Shrink triggerList to actual max size
+                       self.triggerList[ layer ] = self.triggerList[ layer ][ : self.maxScanCode[ layer ] + 1 ]
+
+                       # Calculate first scan code for layer, useful for uC implementations trying to save RAM
+                       firstScanCode = 0
+                       for triggerList in range( 0, len( self.triggerList[ layer ] ) ):
+                               firstScanCode = triggerList
+
+                               # Break if triggerList has items
+                               if len( self.triggerList[ layer ][ triggerList ] ) > 0:
+                                       break;
+                       self.firstScanCode.append( firstScanCode )
+
+               # Determine overall maxScanCode
+               self.overallMaxScanCode = 0x00
+               for maxVal in self.maxScanCode:
+                       if maxVal > self.overallMaxScanCode:
+                               self.overallMaxScanCode = maxVal
+
+
+class Variables:
+       # Container for variables
+       # Stores three sets of variables, the overall combined set, per layer, and per file
+       def __init__( self ):
+               # Dictionaries of variables
+               self.baseLayout       = dict()
+               self.fileVariables    = dict()
+               self.layerVariables   = [ dict() ]
+               self.overallVariables = dict()
+               self.defines          = dict()
+
+               self.currentFile = ""
+               self.currentLayer = 0
+               self.baseLayoutEnabled = True
+
+       def baseLayoutFinished( self ):
+               self.baseLayoutEnabled = False
+
+       def setCurrentFile( self, name ):
+               # Store using filename and current layer
+               self.currentFile = name
+               self.fileVariables[ name ] = dict()
+
+               # If still processing BaseLayout
+               if self.baseLayoutEnabled:
+                       if '*LayerFiles' in self.baseLayout.keys():
+                               self.baseLayout['*LayerFiles'] += [ name ]
+                       else:
+                               self.baseLayout['*LayerFiles'] = [ name ]
+               # Set for the current layer
+               else:
+                       if '*LayerFiles' in self.layerVariables[ self.currentLayer ].keys():
+                               self.layerVariables[ self.currentLayer ]['*LayerFiles'] += [ name ]
+                       else:
+                               self.layerVariables[ self.currentLayer ]['*LayerFiles'] = [ name ]
+
+       def incrementLayer( self ):
+               # Store using layer index
+               self.currentLayer += 1
+               self.layerVariables.append( dict() )
+
+       def assignVariable( self, key, value ):
+               # Overall set of variables
+               self.overallVariables[ key ] = value
+
+               # The Name variable is a special accumulation case
+               if key == 'Name':
+                       # BaseLayout still being processed
+                       if self.baseLayoutEnabled:
+                               if '*NameStack' in self.baseLayout.keys():
+                                       self.baseLayout['*NameStack'] += [ value ]
+                               else:
+                                       self.baseLayout['*NameStack'] = [ value ]
+                       # Layers
+                       else:
+                               if '*NameStack' in self.layerVariables[ self.currentLayer ].keys():
+                                       self.layerVariables[ self.currentLayer ]['*NameStack'] += [ value ]
+                               else:
+                                       self.layerVariables[ self.currentLayer ]['*NameStack'] = [ value ]
+
+               # If still processing BaseLayout
+               if self.baseLayoutEnabled:
+                       self.baseLayout[ key ] = value
+               # Set for the current layer
+               else:
+                       self.layerVariables[ self.currentLayer ][ key ] = value
+
+               # File context variables
+               self.fileVariables[ self.currentFile ][ key ] = value
+