]> git.donarmstrong.com Git - kiibohd-kll.git/blob - kll_lib/containers.py
Add USB Code support for combination and sequence triggers
[kiibohd-kll.git] / kll_lib / containers.py
1 #!/usr/bin/env python3
2 # KLL Compiler Containers
3 #
4 # Copyright (C) 2014-2015 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 import copy
22
23
24
25 ### Decorators ###
26
27  ## Print Decorator Variables
28 ERROR = '\033[5;1;31mERROR\033[0m:'
29
30
31
32 ### Parsing ###
33
34
35  ## Containers
36
37 class Capabilities:
38         # Container for capabilities dictionary and convenience functions
39         def __init__( self ):
40                 self.capabilities = dict()
41
42         def __getitem__( self, name ):
43                 return self.capabilities[ name ]
44
45         def __setitem__( self, name, contents ):
46                 self.capabilities[ name ] = contents
47
48         def __repr__( self ):
49                 return "Capabilities => {0}\nIndexed Capabilities => {1}".format( self.capabilities, sorted( self.capabilities, key = self.capabilities.get ) )
50
51
52         # Total bytes needed to store arguments
53         def totalArgBytes( self, name ):
54                 totalBytes = 0
55
56                 # Iterate over the arguments, summing the total bytes
57                 for arg in self.capabilities[ name ][ 1 ]:
58                         totalBytes += int( arg[ 1 ] )
59
60                 return totalBytes
61
62         # Name of the capability function
63         def funcName( self, name ):
64                 return self.capabilities[ name ][ 0 ]
65
66
67         # Only valid while dictionary keys are not added/removed
68         def getIndex( self, name ):
69                 return sorted( self.capabilities, key = self.capabilities.get ).index( name )
70
71         def getName( self, index ):
72                 return sorted( self.capabilities, key = self.capabilities.get )[ index ]
73
74         def keys( self ):
75                 return sorted( self.capabilities, key = self.capabilities.get )
76
77
78 class Macros:
79         # Container for Trigger Macro : Result Macro correlation
80         # Layer selection for generating TriggerLists
81         #
82         # Only convert USB Code list once all the ResultMacros have been accumulated (does a macro reduction; not reversible)
83         # Two staged list for ResultMacros:
84         #  1) USB Code/Non-converted (may contain capabilities)
85         #  2) Capabilities
86         def __init__( self ):
87                 # Default layer (0)
88                 self.layer = 0
89
90                 # Macro Storage
91                 self.macros = [ dict() ]
92
93                 # Base Layout Storage
94                 self.baseLayout = None
95                 self.layerLayoutMarkers = []
96
97                 # Correlated Macro Data
98                 self.resultsIndex = dict()
99                 self.triggersIndex = dict()
100                 self.resultsIndexSorted = []
101                 self.triggersIndexSorted = []
102                 self.triggerList = []
103                 self.maxScanCode = []
104                 self.firstScanCode = []
105
106                 # USBCode Assignment Cache
107                 self.assignmentCache = []
108
109         def __repr__( self ):
110                 return "{0}".format( self.macros )
111
112         def completeBaseLayout( self ):
113                 # Copy base layout for later use when creating partial layers and add marker
114                 self.baseLayout = copy.deepcopy( self.macros[ 0 ] )
115                 self.layerLayoutMarkers.append( copy.deepcopy( self.baseLayout ) ) # Not used for default layer, just simplifies coding
116
117         def removeUnmarked( self ):
118                 # Remove all of the unmarked mappings from the partial layer
119                 for trigger in self.layerLayoutMarkers[ self.layer ].keys():
120                         del self.macros[ self.layer ][ trigger ]
121
122         def addLayer( self ):
123                 # Increment layer count, and append another macros dictionary
124                 self.layer += 1
125                 self.macros.append( copy.deepcopy( self.baseLayout ) )
126
127                 # Add a layout marker for each layer
128                 self.layerLayoutMarkers.append( copy.deepcopy( self.baseLayout ) )
129
130         # Use for ScanCode trigger macros
131         def appendScanCode( self, trigger, result ):
132                 if not trigger in self.macros[ self.layer ]:
133                         self.replaceScanCode( trigger, result )
134                 else:
135                         self.macros[ self.layer ][ trigger ].append( result )
136
137         # Remove the given trigger/result pair
138         def removeScanCode( self, trigger, result ):
139                 # Remove all instances of the given trigger/result pair
140                 while result in self.macros[ self.layer ][ trigger ]:
141                         self.macros[ self.layer ][ trigger ].remove( result )
142
143         # Replaces the given trigger with the given result
144         # If multiple results for a given trigger, clear, then add
145         def replaceScanCode( self, trigger, result ):
146                 self.macros[ self.layer ][ trigger ] = [ result ]
147
148                 # Mark layer scan code, so it won't be removed later
149                 # Also check to see if it hasn't already been removed before
150                 if not self.baseLayout is None and trigger in self.layerLayoutMarkers[ self.layer ]:
151                         del self.layerLayoutMarkers[ self.layer ][ trigger ]
152
153         # Return a list of ScanCode triggers with the given USB Code trigger
154         def lookupUSBCodes( self, usbCode ):
155                 scanCodeList = []
156
157                 # Scan current layer for USB Codes
158                 for macro in self.macros[ self.layer ].keys():
159                         if usbCode in self.macros[ self.layer ][ macro ]:
160                                 scanCodeList.append( macro )
161
162                 if len(scanCodeList) == 0:
163                         if len(usbCode) > 1 or len(usbCode[0]) > 1:
164                                 for combo in usbCode:
165                                         comboCodes = list()
166                                         for key in combo:
167                                                 scanCode = self.lookupUSBCodes(((key,),))
168                                                 comboCodes.append(scanCode[0][0][0])
169                                         scanCodeList.append(tuple(code for code in comboCodes))
170                                 scanCodeList = [tuple(scanCodeList)]
171
172                 return scanCodeList
173
174         # Cache USBCode Assignment
175         def cacheAssignment( self, operator, scanCode, result ):
176                 self.assignmentCache.append( [ operator, scanCode, result ] )
177
178         # Assign cached USBCode Assignments
179         def replayCachedAssignments( self ):
180                 # Iterate over each item in the assignment cache
181                 for item in self.assignmentCache:
182                         # Check operator, and choose the specified assignment action
183                         # Append Case
184                         if item[0] == ":+":
185                                 self.appendScanCode( item[1], item[2] )
186
187                         # Remove Case
188                         elif item[0] == ":-":
189                                 self.removeScanCode( item[1], item[2] )
190
191                         # Replace Case
192                         elif item[0] == ":":
193                                 self.replaceScanCode( item[1], item[2] )
194
195                 # Clear assignment cache
196                 self.assignmentCache = []
197
198         # Generate/Correlate Layers
199         def generate( self ):
200                 self.generateIndices()
201                 self.sortIndexLists()
202                 self.generateTriggerLists()
203
204         # Generates Index of Results and Triggers
205         def generateIndices( self ):
206                 # Iterate over every trigger result, and add to the resultsIndex and triggersIndex
207                 for layer in range( 0, len( self.macros ) ):
208                         for trigger in self.macros[ layer ].keys():
209                                 # Each trigger has a list of results
210                                 for result in self.macros[ layer ][ trigger ]:
211                                         # Only add, with an index, if result hasn't been added yet
212                                         if not result in self.resultsIndex:
213                                                 self.resultsIndex[ result ] = len( self.resultsIndex )
214
215                                         # Then add a trigger for each result, if trigger hasn't been added yet
216                                         triggerItem = tuple( [ trigger, self.resultsIndex[ result ] ] )
217                                         if not triggerItem in self.triggersIndex:
218                                                 self.triggersIndex[ triggerItem ] = len( self.triggersIndex )
219
220         # Sort Index Lists using the indices rather than triggers/results
221         def sortIndexLists( self ):
222                 self.resultsIndexSorted = [ None ] * len( self.resultsIndex )
223                 # Iterate over the resultsIndex and sort by index
224                 for result in self.resultsIndex.keys():
225                         self.resultsIndexSorted[ self.resultsIndex[ result ] ] = result
226
227                 self.triggersIndexSorted = [ None ] * len( self.triggersIndex )
228                 # Iterate over the triggersIndex and sort by index
229                 for trigger in self.triggersIndex.keys():
230                         self.triggersIndexSorted[ self.triggersIndex[ trigger ] ] = trigger
231
232         # Generates Trigger Lists per layer using index lists
233         def generateTriggerLists( self ):
234                 for layer in range( 0, len( self.macros ) ):
235                         # Set max scancode to 0xFF (255)
236                         # But keep track of the actual max scancode and reduce the list size
237                         self.triggerList.append( [ [] ] * 0xFF )
238                         self.maxScanCode.append( 0x00 )
239
240                         # Iterate through trigger macros to locate necessary ScanCodes and corresponding triggerIndex
241                         for trigger in self.macros[ layer ].keys():
242                                 for variant in range( 0, len( self.macros[ layer ][ trigger ] ) ):
243                                         # Identify result index
244                                         resultIndex = self.resultsIndex[ self.macros[ layer ][ trigger ][ variant ] ]
245
246                                         # Identify trigger index
247                                         triggerIndex = self.triggersIndex[ tuple( [ trigger, resultIndex ] ) ]
248
249                                         # Iterate over the trigger to locate the ScanCodes
250                                         for sequence in trigger:
251                                                 for combo in sequence:
252                                                         # Append triggerIndex for each found scanCode of the Trigger List
253                                                         # Do not re-add if triggerIndex is already in the Trigger List
254                                                         if not triggerIndex in self.triggerList[ layer ][ combo ]:
255                                                                 # Append is working strangely with list pre-initialization
256                                                                 # Doing a 0 check replacement instead -HaaTa
257                                                                 if len( self.triggerList[ layer ][ combo ] ) == 0:
258                                                                         self.triggerList[ layer ][ combo ] = [ triggerIndex ]
259                                                                 else:
260                                                                         self.triggerList[ layer ][ combo ].append( triggerIndex )
261
262                                                         # Look for max Scan Code
263                                                         if combo > self.maxScanCode[ layer ]:
264                                                                 self.maxScanCode[ layer ] = combo
265
266                         # Shrink triggerList to actual max size
267                         self.triggerList[ layer ] = self.triggerList[ layer ][ : self.maxScanCode[ layer ] + 1 ]
268
269                         # Calculate first scan code for layer, useful for uC implementations trying to save RAM
270                         firstScanCode = 0
271                         for triggerList in range( 0, len( self.triggerList[ layer ] ) ):
272                                 firstScanCode = triggerList
273
274                                 # Break if triggerList has items
275                                 if len( self.triggerList[ layer ][ triggerList ] ) > 0:
276                                         break;
277                         self.firstScanCode.append( firstScanCode )
278
279                 # Determine overall maxScanCode
280                 self.overallMaxScanCode = 0x00
281                 for maxVal in self.maxScanCode:
282                         if maxVal > self.overallMaxScanCode:
283                                 self.overallMaxScanCode = maxVal
284
285
286 class Variables:
287         # Container for variables
288         # Stores three sets of variables, the overall combined set, per layer, and per file
289         def __init__( self ):
290                 # Dictionaries of variables
291                 self.baseLayout       = dict()
292                 self.fileVariables    = dict()
293                 self.layerVariables   = [ dict() ]
294                 self.overallVariables = dict()
295                 self.defines          = dict()
296
297                 self.currentFile = ""
298                 self.currentLayer = 0
299                 self.baseLayoutEnabled = True
300
301         def baseLayoutFinished( self ):
302                 self.baseLayoutEnabled = False
303
304         def setCurrentFile( self, name ):
305                 # Store using filename and current layer
306                 self.currentFile = name
307                 self.fileVariables[ name ] = dict()
308
309                 # If still processing BaseLayout
310                 if self.baseLayoutEnabled:
311                         if '*LayerFiles' in self.baseLayout.keys():
312                                 self.baseLayout['*LayerFiles'] += [ name ]
313                         else:
314                                 self.baseLayout['*LayerFiles'] = [ name ]
315                 # Set for the current layer
316                 else:
317                         if '*LayerFiles' in self.layerVariables[ self.currentLayer ].keys():
318                                 self.layerVariables[ self.currentLayer ]['*LayerFiles'] += [ name ]
319                         else:
320                                 self.layerVariables[ self.currentLayer ]['*LayerFiles'] = [ name ]
321
322         def incrementLayer( self ):
323                 # Store using layer index
324                 self.currentLayer += 1
325                 self.layerVariables.append( dict() )
326
327         def assignVariable( self, key, value ):
328                 # Overall set of variables
329                 self.overallVariables[ key ] = value
330
331                 # The Name variable is a special accumulation case
332                 if key == 'Name':
333                         # BaseLayout still being processed
334                         if self.baseLayoutEnabled:
335                                 if '*NameStack' in self.baseLayout.keys():
336                                         self.baseLayout['*NameStack'] += [ value ]
337                                 else:
338                                         self.baseLayout['*NameStack'] = [ value ]
339                         # Layers
340                         else:
341                                 if '*NameStack' in self.layerVariables[ self.currentLayer ].keys():
342                                         self.layerVariables[ self.currentLayer ]['*NameStack'] += [ value ]
343                                 else:
344                                         self.layerVariables[ self.currentLayer ]['*NameStack'] = [ value ]
345
346                 # If still processing BaseLayout
347                 if self.baseLayoutEnabled:
348                         self.baseLayout[ key ] = value
349                 # Set for the current layer
350                 else:
351                         self.layerVariables[ self.currentLayer ][ key ] = value
352
353                 # File context variables
354                 self.fileVariables[ self.currentFile ][ key ] = value
355