123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672 |
- #!/usr/bin/env python3
- '''
- KLL Expression Container
- '''
-
- # Copyright (C) 2016 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
- # 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 ###
-
- import copy
-
- from common.id import CapId
-
-
-
- ### Decorators ###
-
- ## Print Decorator Variables
- ERROR = '\033[5;1;31mERROR\033[0m:'
- WARNING = '\033[5;1;33mWARNING\033[0m:'
-
-
-
- ### Classes ###
-
- class Expression:
- '''
- Container class for KLL expressions
- '''
- def __init__( self, lparam, operator, rparam, context ):
- '''
- Initialize expression container
-
- @param lparam: LOperatorData token
- @param operator: Operator token
- @param rparam: ROperatorData token
- @param context: Parent context of expression
- '''
- # First stage/init
- self.lparam_token = lparam
- self.operator_token = operator
- self.rparam_token = rparam
- self.context = context # TODO, set multiple contexts for later stages
-
- # Second stage
- self.lparam_sub_tokens = []
- self.rparam_sub_tokens = []
-
- # Mutate class into the desired type
- self.__class__ = {
- '=>' : NameAssociationExpression,
- '<=' : DataAssociationExpression,
- '=' : AssignmentExpression,
- ':' : MapExpression,
- }[ self.operator_type() ]
-
- def operator_type( self ):
- '''
- Determine which base operator this operator is of
-
- All : (map) expressions are tokenized/parsed the same way
-
- @return Base string representation of the operator
- '''
- if ':' in self.operator_token.value:
- return ':'
-
- return self.operator_token.value
-
-
- def final_tokens( self, no_filter=False ):
- '''
- Return the final list of tokens, must complete the second stage first
-
- @param no_filter: If true, do not filter out Space tokens
-
- @return Finalized list of tokens
- '''
- ret = self.lparam_sub_tokens + [ self.operator_token ] + self.rparam_sub_tokens
-
- if not no_filter:
- ret = [ x for x in ret if x.type != 'Space' ]
- return ret
-
- def regen_str( self ):
- '''
- Re-construct the string based off the original set of tokens
-
- <lparam><operator><rparam>;
- '''
- return "{0}{1}{2};".format(
- self.lparam_token.value,
- self.operator_token.value,
- self.rparam_token.value,
- )
-
- def point_chars( self, pos_list ):
- '''
- Using the regenerated string, point to a given list of characters
- Used to indicate where a possible issue/syntax error is
-
- @param pos_list: List of character indices
-
- i.e.
- > U"A" : : U"1";
- > ^
- '''
- out = "\t{0}\n\t".format( self.regen_str() )
-
- # Place a ^ character at the given locations
- curpos = 1
- for pos in sorted( pos_list ):
- # Pad spaces, then add a ^
- out += ' ' * (pos - curpos)
- out += '^'
- curpos += pos
-
- return out
-
- def rparam_start( self ):
- '''
- Starting positing char of rparam_token in a regen_str
- '''
- return len( self.lparam_token.value ) + len( self.operator_token.value )
-
- def __repr__( self ):
- # Build string representation based off of what has been set
- # lparam, operator and rparam are always set
- out = "Expression: {0}{1}{2}".format(
- self.lparam_token.value,
- self.operator_token.value,
- self.rparam_token.value,
- )
-
- # TODO - Add more depending on what has been set
- return out
-
- def unique_keys( self ):
- '''
- Generates a list of unique identifiers for the expression that is mergeable
- with other functional equivalent expressions.
-
- This method should never get called directly as a generic Expression
- '''
- return [ ('UNKNOWN KEY', 'UNKNOWN EXPRESSION') ]
-
-
- class AssignmentExpression( Expression ):
- '''
- Container class for assignment KLL expressions
- '''
- type = None
- name = None
- pos = None
- value = None
-
- ## Setters ##
- def array( self, name, pos, value ):
- '''
- Assign array assignment parameters to expression
-
- @param name: Name of variable
- @param pos: Array position of the value (if None, overwrite the entire array)
- @param value: Value of the array, if pos is specified, this is the value of an element
-
- @return: True if parsing was successful
- '''
- self.type = 'Array'
- self.name = name
- self.pos = pos
- self.value = value
-
- # If pos is not none, flatten
- if pos is not None:
- self.value = "".join( str( x ) for x in self.value )
-
- return True
-
- def merge_array( self, new_expression=None ):
- '''
- Merge arrays, used for position assignments
- Merges unconditionally, make sure this is what you want to do first
-
- If no additional array is specified, just "cap-off" array.
- This does a proper array expansion into a python list.
-
- @param new_expression: AssignmentExpression type array, ignore if None
- '''
- # First, check if base expression needs to be capped
- if self.pos is not None:
- # Generate a new string array
- new_value = [""] * self.pos
-
- # Append the old contents to the list
- new_value.append( self.value )
- self.value = new_value
-
- # Clear pos, to indicate that array has been capped
- self.pos = None
-
- # Next, if a new_expression has been specified, merge in
- if new_expression is not None and new_expression.pos is not None:
- # Check if we need to extend the list
- new_size = new_expression.pos + 1 - len( self.value )
- if new_size > 0:
- self.value.extend( [""] * new_size )
-
- # Assign value to array
- self.value[ new_expression.pos ] = new_expression.value
-
- def variable( self, name, value ):
- '''
- Assign variable assignment parameters to expression
-
- @param name: Name of variable
- @param value: Value of variable
-
- @return: True if parsing was successful
- '''
- self.type = 'Variable'
- self.name = name
- self.value = value
-
- # Flatten value, often a list of various token types
- self.value = "".join( str( x ) for x in self.value )
-
- return True
-
- def __repr__( self ):
- if self.type == 'Variable':
- return "{0} = {1};".format( self.name, self.value )
- elif self.type == 'Array':
- # Output KLL style array, double quoted elements, space-separated
- if isinstance( self.value, list ):
- output = "{0}[] =".format( self.name )
- for value in self.value:
- output += ' "{0}"'.format( value )
- output += ";"
- return output
-
- # Single array assignment
- else:
- return "{0}[{1}] = {2};".format( self.name, self.pos, self.value )
-
- return "ASSIGNMENT UNKNOWN"
-
- def unique_keys( self ):
- '''
- Generates a list of unique identifiers for the expression that is mergeable
- with other functional equivalent expressions.
- '''
- return [ ( self.name, self ) ]
-
-
- class NameAssociationExpression( Expression ):
- '''
- Container class for name association KLL expressions
- '''
- type = None
- name = None
- association = None
-
- ## Setters ##
- def capability( self, name, association, parameters ):
- '''
- Assign a capability C function name association
-
- @param name: Name of capability
- @param association: Name of capability in target backend output
-
- @return: True if parsing was successful
- '''
- self.type = 'Capability'
- self.name = name
- self.association = CapId( association, 'Definition', parameters )
-
- return True
-
- def define( self, name, association ):
- '''
- Assign a define C define name association
-
- @param name: Name of variable
- @param association: Name of association in target backend output
-
- @return: True if parsing was successful
- '''
- self.type = 'Define'
- self.name = name
- self.association = association
-
- return True
-
- def __repr__( self ):
- return "{0} <= {1};".format( self.name, self.association )
-
- def unique_keys( self ):
- '''
- Generates a list of unique identifiers for the expression that is mergeable
- with other functional equivalent expressions.
- '''
- return [ ( self.name, self ) ]
-
-
- class DataAssociationExpression( Expression ):
- '''
- Container class for data association KLL expressions
- '''
- type = None
- association = None
- value = None
-
- ## Setters ##
- def animation( self, animations, animation_modifiers ):
- '''
- Animation definition and configuration
-
- @return: True if parsing was successful
- '''
- self.type = 'Animation'
- self.association = animations
- self.value = animation_modifiers
-
- return True
-
- def animationFrame( self, animation_frames, pixel_modifiers ):
- '''
- Pixel composition of an Animation Frame
-
- @return: True if parsing was successful
- '''
-
- self.type = 'AnimationFrame'
- self.association = animation_frames
- self.value = pixel_modifiers
-
- return True
-
- def pixelPosition( self, pixels, position ):
- '''
- Pixel Positioning
-
- @return: True if parsing was successful
- '''
- for pixel in pixels:
- pixel.setPosition( position )
-
- self.type = 'PixelPosition'
- self.association = pixels
-
- return True
-
- def scanCodePosition( self, scancodes, position ):
- '''
- Scan Code to Position Mapping
-
- Note: Accepts lists of scan codes
- Alone this isn't useful, but you can assign rows and columns using ranges instead of individually
-
- @return: True if parsing was successful
- '''
- for scancode in scancodes:
- scancode.setPosition( position )
-
- self.type = 'ScanCodePosition'
- self.association = scancodes
-
- return True
-
- def __repr__( self ):
- if self.type in ['PixelPosition', 'ScanCodePosition']:
- output = ""
- for index, association in enumerate( self.association ):
- if index > 0:
- output += "; "
- output += "{0}".format( association )
- return "{0};".format( output )
- return "{0} <= {1};".format( self.association, self.value )
-
- def unique_keys( self ):
- '''
- Generates a list of unique identifiers for the expression that is mergeable
- with other functional equivalent expressions.
- '''
- keys = []
-
- # Positions require a bit more introspection to get the unique keys
- if self.type in ['PixelPosition', 'ScanCodePosition']:
- for index, key in enumerate( self.association ):
- uniq_expr = self
-
- # If there is more than one key, copy the expression
- # and remove the non-related variants
- if len( self.association ) > 1:
- uniq_expr = copy.copy( self )
-
- # Isolate variant by index
- uniq_expr.association = [ uniq_expr.association[ index ] ]
-
- keys.append( ( "{0}".format( key.unique_key() ), uniq_expr ) )
-
- # AnimationFrames are already list of keys
- # TODO Reorder frame assignments to dedup function equivalent mappings
- elif self.type in ['AnimationFrame']:
- for index, key in enumerate( self.association ):
- uniq_expr = self
-
- # If there is more than one key, copy the expression
- # and remove the non-related variants
- if len( self.association ) > 1:
- uniq_expr = copy.copy( self )
-
- # Isolate variant by index
- uniq_expr.association = [ uniq_expr.association[ index ] ]
-
- keys.append( ( "{0}".format( key ), uniq_expr ) )
-
- # Otherwise treat as a single element
- else:
- keys = [ ( "{0}".format( self.association ), self ) ]
-
- # Remove any duplicate keys
- # TODO Stat? Might be at neat report about how many duplicates were squashed
- keys = list( set( keys ) )
-
- return keys
-
-
- class MapExpression( Expression ):
- '''
- Container class for KLL map expressions
- '''
- type = None
- triggers = None
- operator = None
- results = None
- animation = None
- animation_frame = None
- pixels = None
- position = None
-
- ## Setters ##
- def scanCode( self, triggers, operator, results ):
- '''
- Scan Code mapping
-
- @param triggers: Sequence of combos of ranges of namedtuples
- @param operator: Type of map operation
- @param results: Sequence of combos of ranges of namedtuples
-
- @return: True if parsing was successful
- '''
- self.type = 'ScanCode'
- self.triggers = triggers
- self.operator = operator
- self.results = results
-
- return True
-
- def usbCode( self, triggers, operator, results ):
- '''
- USB Code mapping
-
- @param triggers: Sequence of combos of ranges of namedtuples
- @param operator: Type of map operation
- @param results: Sequence of combos of ranges of namedtuples
-
- @return: True if parsing was successful
- '''
- self.type = 'USBCode'
- self.triggers = triggers
- self.operator = operator
- self.results = results
-
- return True
-
- def animationTrigger( self, animation, operator, results ):
- '''
- Animation Trigger mapping
-
- @param animation: Animation trigger of result
- @param operator: Type of map operation
- @param results: Sequence of combos of ranges of namedtuples
-
- @return: True if parsing was successful
- '''
- self.type = 'Animation'
- self.animation = animation
- self.triggers = animation
- self.operator = operator
- self.results = results
-
- return True
-
- def pixelChannels( self, pixelmap, trigger ):
- '''
- Pixel Channel Composition
-
- @return: True if parsing was successful
- '''
- self.type = 'PixelChannel'
- self.pixel = pixelmap
- self.position = trigger
-
- return True
-
- def sequencesOfCombosOfIds( self, expression_param ):
- '''
- Prettified Sequence of Combos of Identifiers
-
- @param expression_param: Trigger or Result parameter of an expression
-
- Scan Code Example
- [[[S10, S16], [S42]], [[S11, S16], [S42]]] -> (S10 + S16, S42)|(S11 + S16, S42)
- '''
- output = ""
-
- # Sometimes during error cases, might be None
- if expression_param is None:
- return output
-
- # Iterate over each trigger/result variants (expanded from ranges), each one is a sequence
- for index, sequence in enumerate( expression_param ):
- if index > 0:
- output += "|"
- output += "("
-
- # Iterate over each combo (element of the sequence)
- for index, combo in enumerate( sequence ):
- if index > 0:
- output += ", "
-
- # Iterate over each trigger identifier
- for index, identifier in enumerate( combo ):
- if index > 0:
- output += " + "
- output += "{0}".format( identifier )
-
- output += ")"
-
- return output
-
- def elems( self ):
- '''
- Return number of trigger and result elements
-
- Useful for determining if this is a trigger macro (2+)
- Should always return at least (1,1) unless it's an invalid calculation
-
- @return: ( triggers, results )
- '''
- elems = [ 0, 0 ]
-
- # XXX Needed?
- if self.type == 'PixelChannel':
- return tuple( elems )
-
- # Iterate over each trigger variant (expanded from ranges), each one is a sequence
- for sequence in self.triggers:
- # Iterate over each combo (element of the sequence)
- for combo in sequence:
- # Just measure the size of the combo
- elems[0] += len( combo )
-
- # Iterate over each result variant (expanded from ranges), each one is a sequence
- for sequence in self.results:
- # Iterate over each combo (element of the sequence)
- for combo in sequence:
- # Just measure the size of the combo
- elems[1] += len( combo )
-
- return tuple( elems )
-
- def trigger_str( self ):
- '''
- String version of the trigger
- Used for sorting
- '''
- # Pixel Channel Mapping doesn't follow the same pattern
- if self.type == 'PixelChannel':
- return "{0}".format( self.pixel )
-
- return "{0}".format(
- self.sequencesOfCombosOfIds( self.triggers ),
- )
-
- def result_str( self ):
- '''
- String version of the result
- Used for sorting
- '''
- # Pixel Channel Mapping doesn't follow the same pattern
- if self.type == 'PixelChannel':
- return "{0}".format( self.position )
-
- return "{0}".format(
- self.sequencesOfCombosOfIds( self.results ),
- )
-
- def __repr__( self ):
- # Pixel Channel Mapping doesn't follow the same pattern
- if self.type == 'PixelChannel':
- return "{0} : {1};".format( self.pixel, self.position )
-
- return "{0} {1} {2};".format(
- self.sequencesOfCombosOfIds( self.triggers ),
- self.operator,
- self.sequencesOfCombosOfIds( self.results ),
- )
-
- def unique_keys( self ):
- '''
- Generates a list of unique identifiers for the expression that is mergeable
- with other functional equivalent expressions.
-
- TODO: This function should re-order combinations to generate the key.
- The final generated combo will be in the original order.
- '''
- keys = []
-
- # Pixel Channel only has key per mapping
- if self.type == 'PixelChannel':
- keys = [ ( "{0}".format( self.pixel ), self ) ]
-
- # Split up each of the keys
- else:
- # Iterate over each trigger/result variants (expanded from ranges), each one is a sequence
- for index, sequence in enumerate( self.triggers ):
- key = ""
- uniq_expr = self
-
- # If there is more than one key, copy the expression
- # and remove the non-related variants
- if len( self.triggers ) > 1:
- uniq_expr = copy.copy( self )
-
- # Isolate variant by index
- uniq_expr.triggers = [ uniq_expr.triggers[ index ] ]
-
- # Iterate over each combo (element of the sequence)
- for index, combo in enumerate( sequence ):
- if index > 0:
- key += ", "
-
- # Iterate over each trigger identifier
- for index, identifier in enumerate( combo ):
- if index > 0:
- key += " + "
- key += "{0}".format( identifier )
-
- # Add key to list
- keys.append( ( key, uniq_expr ) )
-
- # Remove any duplicate keys
- # TODO Stat? Might be at neat report about how many duplicates were squashed
- keys = list( set( keys ) )
-
- return keys
|