]> git.donarmstrong.com Git - kiibohd-kll.git/blob - kll_lib/containers.py
a6c205ad96a6997b549fbc0e67b89a2734e009a2
[kiibohd-kll.git] / kll_lib / containers.py
1 #!/usr/bin/env python3
2 # KLL Compiler Containers
3 #
4 # Copyright (C) 2014 by Jacob Alexander
5 #
6 # This file is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This file is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this file.  If not, see <http://www.gnu.org/licenses/>.
18
19 ### Imports ###
20
21
22
23 ### Decorators ###
24
25  ## Print Decorator Variables
26 ERROR = '\033[5;1;31mERROR\033[0m:'
27
28
29
30 ### Parsing ###
31
32  ## Containers
33 class Capabilities:
34         # Container for capabilities dictionary and convenience functions
35         def __init__( self ):
36                 self.capabilities = dict()
37
38         def __getitem__( self, name ):
39                 return self.capabilities[ name ]
40
41         def __setitem__( self, name, contents ):
42                 self.capabilities[ name ] = contents
43
44         def __repr__( self ):
45                 return "Capabilities => {0}\nIndexed Capabilities => {1}".format( self.capabilities, sorted( self.capabilities, key = self.capabilities.get ) )
46
47
48         # Total bytes needed to store arguments
49         def totalArgBytes( self, name ):
50                 totalBytes = 0
51
52                 # Iterate over the arguments, summing the total bytes
53                 for arg in self.capabilities[ name ][ 1 ]:
54                         totalBytes += int( arg[ 1 ] )
55
56                 return totalBytes
57
58         # Name of the capability function
59         def funcName( self, name ):
60                 return self.capabilities[ name ][ 0 ]
61
62
63         # Only valid while dictionary keys are not added/removed
64         def getIndex( self, name ):
65                 return sorted( self.capabilities, key = self.capabilities.get ).index( name )
66
67         def getName( self, index ):
68                 return sorted( self.capabilities, key = self.capabilities.get )[ index ]
69
70         def keys( self ):
71                 return sorted( self.capabilities, key = self.capabilities.get )
72
73
74 class Macros:
75         # Container for Trigger Macro : Result Macro correlation
76         # Layer selection for generating TriggerLists
77         #
78         # Only convert USB Code list once all the ResultMacros have been accumulated (does a macro reduction; not reversible)
79         # Two staged list for ResultMacros:
80         #  1) USB Code/Non-converted (may contain capabilities)
81         #  2) Capabilities
82         def __init__( self ):
83                 # Default layer (0)
84                 self.layer = 0
85
86                 # Macro Storage
87                 self.macros = [ dict() ]
88
89                 # Correlated Macro Data
90                 self.resultsIndex = dict()
91                 self.triggersIndex = dict()
92                 self.resultsIndexSorted = []
93                 self.triggersIndexSorted = []
94                 self.triggerList = []
95                 self.maxScanCode = []
96
97                 # USBCode Assignment Cache
98                 self.assignmentCache = []
99
100         def __repr__( self ):
101                 return "{0}".format( self.macros )
102
103         def addLayer( self ):
104                 # Increment layer count, and append another macros dictionary
105                 self.layer += 1
106                 self.macros.append( dict() )
107
108         # Use for ScanCode trigger macros
109         def appendScanCode( self, trigger, result ):
110                 if not trigger in self.macros[ self.layer ]:
111                         self.replaceScanCode( trigger, result )
112                 else:
113                         self.macros[ self.layer ][ trigger ].append( result )
114
115         # Remove the given trigger/result pair
116         def removeScanCode( self, trigger, result ):
117                 # Remove all instances of the given trigger/result pair
118                 while result in self.macros[ self.layer ][ trigger ]:
119                         self.macros[ self.layer ][ trigger ].remove( result )
120
121         # Replaces the given trigger with the given result
122         # If multiple results for a given trigger, clear, then add
123         def replaceScanCode( self, trigger, result ):
124                 self.macros[ self.layer ][ trigger ] = [ result ]
125
126         # Return a list of ScanCode triggers with the given USB Code trigger
127         def lookupUSBCodes( self, usbCode ):
128                 scanCodeList = []
129
130                 # Scan current layer for USB Codes
131                 for macro in self.macros[ self.layer ].keys():
132                         if usbCode in self.macros[ self.layer ][ macro ]:
133                                 scanCodeList.append( macro )
134
135                 return scanCodeList
136
137         # Cache USBCode Assignment
138         def cacheAssignment( self, operator, scanCode, result ):
139                 self.assignmentCache.append( [ operator, scanCode, result ] )
140
141         # Assign cached USBCode Assignments
142         def replayCachedAssignments( self ):
143                 # Iterate over each item in the assignment cache
144                 for item in self.assignmentCache:
145                         # Check operator, and choose the specified assignment action
146                         # Append Case
147                         if item[0] == ":+":
148                                 self.appendScanCode( item[1], item[2] )
149
150                         # Remove Case
151                         elif item[0] == ":-":
152                                 self.removeScanCode( item[1], item[2] )
153
154                         # Replace Case
155                         elif item[0] == ":":
156                                 self.replaceScanCode( item[1], item[2] )
157
158                 # Clear assignment cache
159                 self.assignmentCache = []
160
161         # Generate/Correlate Layers
162         def generate( self ):
163                 self.generateIndices()
164                 self.sortIndexLists()
165                 self.generateTriggerLists()
166
167         # Generates Index of Results and Triggers
168         def generateIndices( self ):
169                 # Iterate over every trigger result, and add to the resultsIndex and triggersIndex
170                 for layer in range( 0, len( self.macros ) ):
171                         for trigger in self.macros[ layer ].keys():
172                                 # Each trigger has a list of results
173                                 for result in self.macros[ layer ][ trigger ]:
174                                         # Only add, with an index, if result hasn't been added yet
175                                         if not result in self.resultsIndex:
176                                                 self.resultsIndex[ result ] = len( self.resultsIndex )
177
178                                         # Then add a trigger for each result, if trigger hasn't been added yet
179                                         triggerItem = tuple( [ trigger, self.resultsIndex[ result ] ] )
180                                         if not triggerItem in self.triggersIndex:
181                                                 self.triggersIndex[ triggerItem ] = len( self.triggersIndex )
182
183         # Sort Index Lists using the indices rather than triggers/results
184         def sortIndexLists( self ):
185                 self.resultsIndexSorted = [ None ] * len( self.resultsIndex )
186                 # Iterate over the resultsIndex and sort by index
187                 for result in self.resultsIndex.keys():
188                         self.resultsIndexSorted[ self.resultsIndex[ result ] ] = result
189
190                 self.triggersIndexSorted = [ None ] * len( self.triggersIndex )
191                 # Iterate over the triggersIndex and sort by index
192                 for trigger in self.triggersIndex.keys():
193                         self.triggersIndexSorted[ self.triggersIndex[ trigger ] ] = trigger
194
195         # Generates Trigger Lists per layer using index lists
196         def generateTriggerLists( self ):
197                 for layer in range( 0, len( self.macros ) ):
198                         # Set max scancode to 0xFF (255)
199                         # But keep track of the actual max scancode and reduce the list size
200                         self.triggerList.append( [ [] ] * 0xFF )
201                         self.maxScanCode.append( 0x00 )
202
203                         # Iterate through trigger macros to locate necessary ScanCodes and corresponding triggerIndex
204                         for trigger in self.macros[ layer ].keys():
205                                 for variant in range( 0, len( self.macros[ layer ][ trigger ] ) ):
206                                         # Identify result index
207                                         resultIndex = self.resultsIndex[ self.macros[ layer ][ trigger ][ variant ] ]
208
209                                         # Identify trigger index
210                                         triggerIndex = self.triggersIndex[ tuple( [ trigger, resultIndex ] ) ]
211
212                                         # Iterate over the trigger to locate the ScanCodes
213                                         for sequence in trigger:
214                                                 for combo in sequence:
215                                                         # Append triggerIndex for each found scanCode of the Trigger List
216                                                         # Do not re-add if triggerIndex is already in the Trigger List
217                                                         if not triggerIndex in self.triggerList[ layer ][ combo ]:
218                                                                 # Append is working strangely with list pre-initialization
219                                                                 # Doing a 0 check replacement instead -HaaTa
220                                                                 if len( self.triggerList[ layer ][ combo ] ) == 0:
221                                                                         self.triggerList[ layer ][ combo ] = [ triggerIndex ]
222                                                                 else:
223                                                                         self.triggerList[ layer ][ combo ].append( triggerIndex )
224
225                                                         # Look for max Scan Code
226                                                         if combo > self.maxScanCode[ layer ]:
227                                                                 self.maxScanCode[ layer ] = combo
228
229                         # Shrink triggerList to actual max size
230                         self.triggerList[ layer ] = self.triggerList[ layer ][ : self.maxScanCode[ layer ] + 1 ]
231
232                 # Determine overall maxScanCode
233                 self.overallMaxScanCode = 0x00
234                 for maxVal in self.maxScanCode:
235                         if maxVal > self.overallMaxScanCode:
236                                 self.overallMaxScanCode = maxVal
237