2014-09-02 17:03:50 +00:00
#!/usr/bin/env python3
# KLL Compiler Containers
#
2015-02-16 21:29:26 +00:00
# Copyright (C) 2014-2015 by Jacob Alexander
2014-09-02 17:03:50 +00:00
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
### Imports ###
2014-09-10 00:49:46 +00:00
import copy
2014-09-02 17:03:50 +00:00
### Decorators ###
## Print Decorator Variables
ERROR = ' \033 [5;1;31mERROR \033 [0m: '
### Parsing ###
2014-09-17 00:01:40 +00:00
2014-09-02 17:03:50 +00:00
## Containers
2014-09-17 00:01:40 +00:00
2014-09-02 17:03:50 +00:00
class Capabilities :
# Container for capabilities dictionary and convenience functions
def __init__ ( self ) :
self . capabilities = dict ( )
def __getitem__ ( self , name ) :
return self . capabilities [ name ]
def __setitem__ ( self , name , contents ) :
self . capabilities [ name ] = contents
def __repr__ ( self ) :
return " Capabilities => {0} \n Indexed Capabilities => {1} " . format ( self . capabilities , sorted ( self . capabilities , key = self . capabilities . get ) )
# Total bytes needed to store arguments
def totalArgBytes ( self , name ) :
totalBytes = 0
# Iterate over the arguments, summing the total bytes
2014-09-07 03:56:46 +00:00
for arg in self . capabilities [ name ] [ 1 ] :
totalBytes + = int ( arg [ 1 ] )
2014-09-02 17:03:50 +00:00
return totalBytes
# Name of the capability function
def funcName ( self , name ) :
2014-09-07 03:56:46 +00:00
return self . capabilities [ name ] [ 0 ]
2014-09-02 17:03:50 +00:00
# Only valid while dictionary keys are not added/removed
def getIndex ( self , name ) :
return sorted ( self . capabilities , key = self . capabilities . get ) . index ( name )
def getName ( self , index ) :
return sorted ( self . capabilities , key = self . capabilities . get ) [ index ]
def keys ( self ) :
return sorted ( self . capabilities , key = self . capabilities . get )
class Macros :
# Container for Trigger Macro : Result Macro correlation
# Layer selection for generating TriggerLists
#
# Only convert USB Code list once all the ResultMacros have been accumulated (does a macro reduction; not reversible)
# Two staged list for ResultMacros:
# 1) USB Code/Non-converted (may contain capabilities)
# 2) Capabilities
def __init__ ( self ) :
# Default layer (0)
self . layer = 0
# Macro Storage
2014-09-06 19:35:22 +00:00
self . macros = [ dict ( ) ]
2014-09-10 00:49:46 +00:00
# Base Layout Storage
self . baseLayout = None
self . layerLayoutMarkers = [ ]
2014-09-07 03:56:46 +00:00
# Correlated Macro Data
self . resultsIndex = dict ( )
self . triggersIndex = dict ( )
self . resultsIndexSorted = [ ]
self . triggersIndexSorted = [ ]
self . triggerList = [ ]
self . maxScanCode = [ ]
2014-09-17 00:01:40 +00:00
self . firstScanCode = [ ]
2014-09-07 03:56:46 +00:00
2014-09-08 04:32:36 +00:00
# USBCode Assignment Cache
self . assignmentCache = [ ]
2014-09-06 19:35:22 +00:00
def __repr__ ( self ) :
return " {0} " . format ( self . macros )
2014-09-02 17:03:50 +00:00
2014-09-10 00:49:46 +00:00
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 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 ]
2014-09-08 06:22:07 +00:00
def addLayer ( self ) :
# Increment layer count, and append another macros dictionary
self . layer + = 1
2014-09-10 00:49:46 +00:00
self . macros . append ( copy . deepcopy ( self . baseLayout ) )
# Add a layout marker for each layer
self . layerLayoutMarkers . append ( copy . deepcopy ( self . baseLayout ) )
2014-09-02 17:03:50 +00:00
# Use for ScanCode trigger macros
def appendScanCode ( self , trigger , result ) :
2014-09-06 19:35:22 +00:00
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 ]
2014-09-10 00:49:46 +00:00
# Mark layer scan code, so it won't be removed later
2014-09-17 01:14:06 +00:00
# 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 ] :
2014-09-10 00:49:46 +00:00
del self . layerLayoutMarkers [ self . layer ] [ trigger ]
2014-09-06 19:35:22 +00:00
# Return a list of ScanCode triggers with the given USB Code trigger
def lookupUSBCodes ( self , usbCode ) :
scanCodeList = [ ]
# Scan current layer for USB Codes
2014-09-02 17:03:50 +00:00
for macro in self . macros [ self . layer ] . keys ( ) :
2014-09-08 04:32:36 +00:00
if usbCode in self . macros [ self . layer ] [ macro ] :
2014-09-06 19:35:22 +00:00
scanCodeList . append ( macro )
2015-02-28 04:26:01 +00:00
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 ) ]
2014-09-06 19:35:22 +00:00
return scanCodeList
2014-09-02 17:03:50 +00:00
2014-09-08 04:32:36 +00:00
# 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 ] == " : " :
self . replaceScanCode ( item [ 1 ] , item [ 2 ] )
# Clear assignment cache
self . assignmentCache = [ ]
2014-09-07 03:56:46 +00:00
# Generate/Correlate Layers
def generate ( self ) :
self . generateIndices ( )
self . sortIndexLists ( )
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 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 )
2014-09-09 06:51:44 +00:00
# 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 in sequence :
# 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
2014-09-07 03:56:46 +00:00
# Shrink triggerList to actual max size
self . triggerList [ layer ] = self . triggerList [ layer ] [ : self . maxScanCode [ layer ] + 1 ]
2014-09-17 00:01:40 +00:00
# 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 )
2014-09-07 03:56:46 +00:00
# Determine overall maxScanCode
self . overallMaxScanCode = 0x00
for maxVal in self . maxScanCode :
if maxVal > self . overallMaxScanCode :
self . overallMaxScanCode = maxVal
2014-09-17 00:01:40 +00:00
class Variables :
# Container for variables
# Stores three sets of variables, the overall combined set, per layer, and per file
def __init__ ( self ) :
2014-11-20 21:52:58 +00:00
# Dictionaries of variables
self . baseLayout = dict ( )
self . fileVariables = dict ( )
self . layerVariables = [ dict ( ) ]
self . overallVariables = dict ( )
self . defines = dict ( )
2014-09-17 00:01:40 +00:00
2014-11-20 21:52:58 +00:00
self . currentFile = " "
self . currentLayer = 0
self . baseLayoutEnabled = True
def baseLayoutFinished ( self ) :
self . baseLayoutEnabled = False
2014-09-17 00:01:40 +00:00
def setCurrentFile ( self , name ) :
# Store using filename and current layer
2014-11-20 21:52:58 +00:00
self . currentFile = name
self . fileVariables [ name ] = dict ( )
# If still processing BaseLayout
if self . baseLayoutEnabled :
2015-02-16 21:29:26 +00:00
if ' *LayerFiles ' in self . baseLayout . keys ( ) :
self . baseLayout [ ' *LayerFiles ' ] + = [ name ]
else :
self . baseLayout [ ' *LayerFiles ' ] = [ name ]
2014-11-20 21:52:58 +00:00
# Set for the current layer
else :
2015-02-16 21:29:26 +00:00
if ' *LayerFiles ' in self . layerVariables [ self . currentLayer ] . keys ( ) :
self . layerVariables [ self . currentLayer ] [ ' *LayerFiles ' ] + = [ name ]
else :
self . layerVariables [ self . currentLayer ] [ ' *LayerFiles ' ] = [ name ]
2014-09-17 00:01:40 +00:00
2014-11-20 21:52:58 +00:00
def incrementLayer ( self ) :
2014-09-17 00:01:40 +00:00
# Store using layer index
2014-11-20 21:52:58 +00:00
self . currentLayer + = 1
self . layerVariables . append ( dict ( ) )
2014-09-17 00:01:40 +00:00
def assignVariable ( self , key , value ) :
2014-11-20 21:52:58 +00:00
# Overall set of variables
self . overallVariables [ key ] = value
2015-02-16 21:29:26 +00:00
# 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 ]
2014-11-20 21:52:58 +00:00
# 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
2014-09-17 00:01:40 +00:00