#!/usr/bin/env python3 ''' KLL Compiler Stage Definitions ''' # 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 . ### Imports ### from multiprocessing.dummy import Pool as ThreadPool import io import os import re import sys import common.context as context import common.expression as expression import common.file as file import emitters.emitters as emitters from funcparserlib.lexer import make_tokenizer, Token, LexerError from funcparserlib.parser import many, oneplus, maybe, skip, NoParseError, Parser_debug ### Decorators ### ## Print Decorator Variables ERROR = '\033[5;1;31mERROR\033[0m:' WARNING = '\033[5;1;33mWARNING\033[0m:' ansi_escape = re.compile(r'\x1b[^m]*m') ### Classes ### class ControlStage: ''' Top-level Stage Controls the order in which each stage is processed ''' def __init__( self ): ''' Initialize stage objects and control variables ''' # Initialized in process order # NOTE: Only unique classes in this list, otherwise stage() will get confused self.stages = [ CompilerConfigurationStage( self ), FileImportStage( self ), PreprocessorStage( self ), OperationClassificationStage( self ), OperationSpecificsStage( self ), OperationOrganizationStage( self ), DataOrganizationStage( self ), DataFinalizationStage( self ), DataAnalysisStage( self ), CodeGenerationStage( self ), #ReportGenerationStage( self ), ] self.git_rev = None self.git_changes = None self.version = None def stage( self, context_str ): ''' Returns the stage object of the associated string name of the class @param context_str: String name of the class of the stage e.g. CompilerConfigurationStage ''' return [ stage for stage in self.stages if type( stage ).__name__ is context_str ][0] def command_line_args( self, args ): ''' Capture commmand line arguments for each processing stage @param args: Name space of processed arguments ''' for stage in self.stages: stage.command_line_args( args ) def command_line_flags( self, parser ): ''' Prepare group parser for each processing stage @param parser: argparse setup object ''' for stage in self.stages: stage.command_line_flags( parser ) def process( self ): ''' Main processing section Initializes each stage in order. Each stage must complete before the next one begins. ''' # Run report even if stage doesn't complete run_report = False for stage in self.stages: stage.process() # Make sure stage has successfully completed if stage.status() != 'Completed': print( "{0} Invalid stage status '{1}' for '{2}'.".format( ERROR, stage.status(), stage.__class__.__name__, ) ) run_report = True break # Only need to explicitly run reports if there was a stage problem # Otherwise reports are run automatically if run_report: # TODO sys.exit( 1 ) class Stage: ''' Base Stage Class ''' def __init__( self, control ): ''' Stage initialization @param control: ControlStage object, used to access data from other stages ''' self.control = control self.color = False self._status = 'Queued' def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' print( "{0} '{1}' '{2}' has not been implemented yet" .format( WARNING, self.command_line_args.__name__, type( self ).__name__ ) ) def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' print( "{0} '{1}' '{2}' has not been implemented yet" .format( WARNING, self.command_line_flags.__name__, type( self ).__name__ ) ) def process( self ): ''' Main procesing section ''' self._status = 'Running' print( "{0} '{1}' '{2}' has not been implemented yet" .format( WARNING, self.process.__name__, type( self ).__name__ ) ) self._status = 'Completed' def status( self ): ''' Returns the current status of the Stage Values: Queued - Not yet run Running - Currently running Completed - Successfully completed Incomplete - Unsuccessfully completed ''' return self._status class CompilerConfigurationStage( Stage ): ''' Compiler Configuration Stage * Does initial setup of KLL compiler. * Handles any global configuration that must be done before parsing can begin ''' def __init__( self, control ): ''' Initialize compiler configuration variables ''' super().__init__( control ) self.color = "auto" self.jobs = os.cpu_count() self.pool = None # Build list of emitters self.emitters = emitters.Emitters( control ) self.emitter = self.emitters.emitter_default() def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' self.emitter = args.emitter self.color = args.color self.jobs = args.jobs # Validate color argument before processing if self.color not in ['auto', 'always', 'never' ]: print( "Invalid color option '{0}'".format( self.color ) ) sys.exit( 2 ) # TODO Detect whether colorization should be used self.color = self.color in ['auto', 'always'] def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group group = parser.add_argument_group('\033[1mCompiler Configuration\033[0m') # Optional Arguments group.add_argument( '--emitter', type=str, default=self.emitter, help="Specify target emitter for the KLL compiler.\n" "\033[1mDefault\033[0m: {0}\n" "\033[1mOptions\033[0m: {1}".format( self.emitter, self.emitters.emitter_list() ) ) group.add_argument( '--color', type=str, default=self.color, help="Specify debug colorizer mode.\n" "\033[1mDefault\033[0m: {0}\n" "\033[1mOptions\033[0m: auto, always, never (auto attempts to detect support)".format( self.color ) ) group.add_argument( '--jobs', type=int, default=self.jobs, help="Specify max number of threads to use.\n" "\033[1mDefault\033[0m: {0}".format( self.jobs ) ) def process( self ): ''' Compiler Configuration Processing ''' self._status = 'Running' # Initialize thread pool self.pool = ThreadPool( self.jobs ) self._status = 'Completed' class FileImportStage( Stage ): ''' FIle Import Stage * Loads text of all files into memory * Does initial sorting of KLL Contexts based upon command line arguments ''' def __init__( self, control ): ''' Initialize file storage datastructures and variables ''' super().__init__( control ) # These lists are order sensitive self.generic_files = [] self.config_files = [] self.base_files = [] self.default_files = [] # This is a list of lists, each sub list is another layer in order from 1 to max self.partial_files = [] # List of all files contained in KLLFile objects self.kll_files = [] def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' self.generic_files = args.generic self.config_files = args.config self.base_files = args.base self.default_files = args.default self.partial_files = args.partial def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group group = parser.add_argument_group('\033[1mFile Context Configuration\033[0m') # Positional Arguments group.add_argument( 'generic', type=str, nargs='*', default=self.generic_files, help="Auto-detect context of .kll files, defaults to a base map configuration." ) # Optional Arguments group.add_argument( '--config', type=str, nargs='+', default=self.config_files, help="Specify base configuration .kll files, earliest priority" ) group.add_argument( '--base', type=str, nargs='+', default=self.base_files, help="Specify base map configuration, applied after config .kll files.\n" "The base map is applied prior to all default and partial maps and is used as the basis for them." ) group.add_argument( '--default', type=str, nargs='+', default=self.default_files, help="Specify .kll files to layer on top of the default map to create a combined map.\n" "Also known as layer 0." ) group.add_argument( '--partial', type=str, nargs='+', action='append', default=self.partial_files, help="Specify .kll files to generate partial map, multiple files per flag.\n" "Each -p defines another partial map.\n" "Base .kll files (that define the scan code maps) must be defined for each partial map." ) def init_kllfile( self, path, file_context ): ''' Prepares a KLLFile object with the given context @path: Path to the KLL file @file_context: Type of file context, e.g. DefaultMapContext ''' return file.KLLFile( path, file_context ) def process( self ): ''' Process each of the files, sorting them by command line argument context order ''' self._status = 'Running' # Determine colorization setting self.color = self.control.stage('CompilerConfigurationStage').color # Process each type of file # Iterates over each file in the context list and creates a KLLFile object with a context and path self.kll_files += map( lambda path: self.init_kllfile( path, context.GenericContext() ), self.generic_files ) self.kll_files += map( lambda path: self.init_kllfile( path, context.ConfigurationContext() ), self.config_files ) self.kll_files += map( lambda path: self.init_kllfile( path, context.BaseMapContext() ), self.base_files ) self.kll_files += map( lambda path: self.init_kllfile( path, context.DefaultMapContext() ), self.default_files ) # Partial Maps require a third parameter which specifies which layer it's in for layer, files in enumerate( self.partial_files ): self.kll_files += map( lambda path: self.init_kllfile( path, context.PartialMapContext( layer ) ), files ) # Validate that all the file paths exist, exit if any of the checks fail if False in [ path.check() for path in self.kll_files ]: self._status = 'Incomplete' return # Now that we have a full list of files and their given context, we can now read the files into memory # Uses the thread pool to speed up processing # Make sure processing was successful before continuing pool = self.control.stage('CompilerConfigurationStage').pool if False in pool.map( lambda kll_file: kll_file.read(), self.kll_files ): self._status = 'Incomplete' return self._status = 'Completed' class PreprocessorStage( Stage ): ''' Preprocessor Stage * Does initial split and decision of contexts * Handles Preprocessor part of KLL ''' def __init__( self, control ): ''' Initialize preprocessor configuration variables ''' super().__init__( control ) def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group #group = parser.add_argument_group('\033[1mPreprocessor Configuration\033[0m') def seed_context( self, kll_file ): ''' Build list of context TODO Update later for proper preprocessor Adds data from KLFile into the Context ''' kll_file.context.initial_context( kll_file.lines, kll_file.data, kll_file ) def process( self ): ''' Preprocessor Execution ''' self._status = 'Running' # Determine colorization setting self.color = self.control.stage('CompilerConfigurationStage').color # Acquire thread pool pool = self.control.stage('CompilerConfigurationStage').pool # TODO # Once the KLL Spec has preprocessor commands, there may be a risk of infinite/circular dependencies # Please add a non-invasive way to avoid/warn/stop in this case -HaaTa # First, since initial contexts have been populated, populate details # TODO # This step will change once preprocessor commands have been added # Simply, this just takes the imported file data (KLLFile) and puts it in the context container kll_files = self.control.stage('FileImportStage').kll_files if False in pool.map( self.seed_context, kll_files ): self._status = 'Incomplete' return # Next, tokenize and parser the preprocessor KLL commands. # NOTE: This may result in having to create more KLL Contexts and tokenize/parse again numerous times over # TODO self._status = 'Completed' class OperationClassificationStage( Stage ): ''' Operation Classification Stage * Sorts operations by type based on operator * Tokenizes only operator pivots and left/right arguments * Further tokenization and parsing occurs at a later stage ''' def __init__( self, control ): ''' Initialize operation classification stage ''' super().__init__( control ) self.tokenized_data = [] self.contexts = [] def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group #group = parser.add_argument_group('\033[1mOperation Classification Configuration\033[0m') def merge_tokens( self, token_list, token_type ): ''' Merge list of tokens into a single token @param token_list: List of tokens @param token_type: String name of token type ''' # Initial token parameters ret_token = Token( token_type, '' ) # Set start/end positions of token ret_token.start = token_list[0].start ret_token.end = token_list[-1].end # Build token value for token in token_list: ret_token.value += token.value return ret_token def tokenize( self, kll_context ): ''' Tokenize a single string @param kll_context: KLL Context containing file data ''' ret = True # Basic Tokens Spec spec = [ ( 'Comment', ( r' *#.*', ) ), ( 'Space', ( r'[ \t]+', ) ), ( 'NewLine', ( r'[\r\n]+', ) ), # Tokens that will be grouped together after tokenization # Ignored at this stage # This is required to isolate the Operator tags ( 'Misc', ( r'r?[xyz]:[0-9]+(.[0-9]+)?', ) ), # Position context ( 'Misc', ( r'\([^\)]*\)', ) ), # Parenthesis context ( 'Misc', ( r'\[[^\]]*\]', ) ), # Square bracket context ( 'Misc', ( r'"[^"]*"', ) ), # Double quote context ( 'Misc', ( r"'[^']*'", ) ), # Single quote context ( 'Operator', ( r'=>|<=|i:\+|i:-|i::|i:|:\+|:-|::|:|=', ) ), ( 'EndOfLine', ( r';', ) ), # Everything else to be ignored at this stage ( 'Misc', ( r'.', ) ), # Everything else ] # Tokens to filter out of the token stream #useless = [ 'Space', 'Comment' ] useless = [ 'Comment', 'NewLine' ] # Build tokenizer that appends unknown characters to Misc Token groups # NOTE: This is technically slower processing wise, but allows for multi-stage tokenization # Which in turn allows for parsing and tokenization rules to be simplified tokenizer = make_tokenizer( spec ) # Tokenize and filter out useless tokens try: tokens = [ x for x in tokenizer( kll_context.data ) if x.type not in useless ] except LexerError as err: print( err ) print( "{0} {1}:tokenize -> {2}:{3}".format( ERROR, self.__class__.__name__, kll_context.parent.path, err.place[0], ) ) # Merge Misc tokens delimited by Operator and EndOfLine tokens kll_context.classification_token_data = [] new_token = [] last_operator = None for token in tokens: # Check for delimiter, append new_token if ready if token.type in ['EndOfLine', 'Operator']: # Determine the token type token_type = 'LOperatorData' if token.type is 'EndOfLine': token_type = 'ROperatorData' # If this is a 'misplaced' operator, set as Misc if token_type == last_operator: token.type = 'Misc' new_token.append( token ) continue if len( new_token ) > 0: # Build new token kll_context.classification_token_data.append( self.merge_tokens( new_token, token_type ) ) new_token = [] kll_context.classification_token_data.append( token ) last_operator = token_type # Collect Misc tokens elif token.type in ['Misc', 'Space']: new_token.append( token ) # Invalid token for this stage else: print( "{0} Invalid token '{1}' for '{2}'".format( ERROR, token, type( self ).__name__, ) ) ret = False return ret def sort( self, kll_context ): ''' Sorts tokenized data into expressions LOperatorData + Operator + ROperatorData + EndOfLine @param kll_context: KLL Context, contains tokenized data ''' ret = True def validate_token( token, token_type ): ''' Validate token @param token: Given token to validate @param token_type: Token type to validate against @return True if the token is correct ''' ret = token.type is token_type # Error message if not ret: print( "Expected: '{0}' got '{1}':{2} '{3}'".format( token_type, token.type, token._pos_str(), token.value, ) ) return ret tokens = kll_context.classification_token_data for index in range( 0, len( tokens ), 4 ): # Make sure enough tokens exist if index + 3 >= len( tokens ): print( "Not enough tokens left: {0}".format( tokens[index:] ) ) print( "Expected: LOperatorData, Operator, ROperatorData, EndOfLine" ) print( "{0} {1}:sort -> {2}:{3}".format( ERROR, self.__class__.__name__, kll_context.parent.path, tokens[-1].start[0], ) ) ret = False break # Validate the tokens are what was expected ret = validate_token( tokens[index], 'LOperatorData' ) and ret ret = validate_token( tokens[index + 1], 'Operator' ) and ret ret = validate_token( tokens[index + 2], 'ROperatorData' ) and ret ret = validate_token( tokens[index + 3], 'EndOfLine' ) and ret # Append expression kll_context.expressions.append( expression.Expression( tokens[index], tokens[index + 1], tokens[index + 2], kll_context ) ) return ret def process( self ): ''' Compiler Configuration Processing ''' self._status = 'Running' # Determine colorization setting self.color = self.control.stage('CompilerConfigurationStage').color # Acquire thread pool pool = self.control.stage('CompilerConfigurationStage').pool # Get list of KLLFiles kll_files = self.control.stage('FileImportStage').kll_files # Build list of contexts self.contexts = [ kll_file.context for kll_file in kll_files ] # Tokenize operators # TODO # Once preprocessor includes are implemented use a second kll_files list # This way processing doesn't have to be recursive for a few stages -HaaTa if False in pool.map( self.tokenize, self.contexts ): self._status = 'Incomplete' return # Sort elements into expressions # LOperatorData + Operator + ROperatorData + EndOfLine if False in pool.map( self.sort, self.contexts ): self._status = 'Incomplete' return self._status = 'Completed' class OperationSpecificsStage( Stage ): ''' Operation Specifics Stage * For each sorted operation, tokenize and parse the left/right arguments * Data is stored with the operation, but no context is given to the data beyond the argument types ''' def __init__( self, control ): ''' Initialize operation specifics stage ''' super().__init__( control ) self.parser_debug = False self.parser_token_debug = False self.token_debug = False def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' self.parser_debug = args.parser_debug self.parser_token_debug = args.parser_token_debug self.token_debug = args.token_debug # Auto-set parser_debug if parser_token_debug is set if self.parser_token_debug: self.parser_debug = True def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group group = parser.add_argument_group('\033[1mOperation Specifics Configuration\033[0m') # Optional Arguments group.add_argument( '--parser-debug', action='store_true', default=self.parser_debug, help="Enable parser debug output.\n", ) group.add_argument( '--parser-token-debug', action='store_true', default=self.parser_token_debug, help="Enable parser-stage token debug output.\n", ) group.add_argument( '--token-debug', action='store_true', default=self.token_debug, help="Enable tokenization debug output.\n", ) ## Tokenizers ## def tokenize_base( self, kll_expression, lspec, rspec ): ''' Base tokenization logic for this stage @param kll_expression: KLL expression to tokenize @param lspec: Regex tokenization spec for the left parameter @param rspec: Regex tokenization spec for the right parameter @return False if a LexerError was detected ''' # Build tokenizers for lparam and rparam ltokenizer = make_tokenizer( lspec ) rtokenizer = make_tokenizer( rspec ) # Tokenize lparam and rparam # Ignore the generators, not useful in this case (i.e. use list()) err_pos = [] # Error positions try: kll_expression.lparam_sub_tokens = list( ltokenizer( kll_expression.lparam_token.value ) ) except LexerError as err: # Determine place in constructed expression err_pos.append( err.place[1] ) print( type( err ).__name__, err ) try: kll_expression.rparam_sub_tokens = list( rtokenizer( kll_expression.rparam_token.value ) ) except LexerError as err: # Determine place in constructed expression err_pos.append( err.place[1] + kll_expression.rparam_start() ) print( type( err ).__name__, err ) # Display more information if any errors were detected if len( err_pos ) > 0: print( kll_expression.point_chars( err_pos ) ) return False return True def tokenize_name_association( self, kll_expression ): ''' Tokenize lparam and rparam in name association expressions => ; ''' # Define tokenization regex lspec = [ ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ), ( 'Space', ( r'[ \t]+', ) ), ] rspec = [ ( 'Space', ( r'[ \t]+', ) ), ( 'Parenthesis', ( r'\(|\)', ) ), ( 'Operator', ( r':', ) ), ( 'Comma', ( r',', ) ), ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ), ( 'Number', ( r'-?(0x[0-9a-fA-F]+)|(0|([1-9][0-9]*))', ) ), ] # Tokenize, expression stores the result, status is returned return self.tokenize_base( kll_expression, lspec, rspec ) def tokenize_data_association( self, kll_expression ): ''' Tokenize lparam and rparam in data association expressions <= ; ''' # Define tokenization regex lspec = [ ( 'Space', ( r'[ \t]+', ) ), ( 'ScanCode', ( r'S((0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'ScanCodeStart', ( r'S\[', ) ), ( 'Pixel', ( r'P((0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'PixelStart', ( r'P\[', ) ), ( 'Animation', ( r'A"[^"]+"', ) ), ( 'AnimationStart', ( r'A\[', ) ), ( 'CodeBegin', ( r'\[', ) ), ( 'CodeEnd', ( r'\]', ) ), ( 'Position', ( r'r?[xyz]:[0-9]+(.[0-9]+)?', ) ), ( 'Comma', ( r',', ) ), ( 'Dash', ( r'-', ) ), ( 'Number', ( r'-?(0x[0-9a-fA-F]+)|(0|([1-9][0-9]*))', ) ), ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ), ] rspec = [ ( 'Space', ( r'[ \t]+', ) ), ( 'Pixel', ( r'P((0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'PixelStart', ( r'P\[', ) ), ( 'PixelLayer', ( r'PL((0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'PixelLayerStart', ( r'PL\[', ) ), ( 'Animation', ( r'A"[^"]+"', ) ), ( 'AnimationStart', ( r'A\[', ) ), ( 'CodeBegin', ( r'\[', ) ), ( 'CodeEnd', ( r'\]', ) ), ( 'Position', ( r'r?[xyz]:[0-9]+(.[0-9]+)?', ) ), ( 'PixelOperator', ( r'(\+:|-:|>>|<<)', ) ), ( 'Operator', ( r':', ) ), ( 'Comma', ( r',', ) ), ( 'Dash', ( r'-', ) ), ( 'Plus', ( r'\+', ) ), ( 'Parenthesis', ( r'\(|\)', ) ), ( 'Number', ( r'-?(0x[0-9a-fA-F]+)|(0|([1-9][0-9]*))', ) ), ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ), ] # Tokenize, expression stores the result, status is returned return self.tokenize_base( kll_expression, lspec, rspec ) def tokenize_assignment( self, kll_expression ): ''' Tokenize lparam and rparam in assignment expressions = ; ''' # Define tokenization regex lspec = [ ( 'Space', ( r'[ \t]+', ) ), ( 'Number', ( r'-?(0x[0-9a-fA-F]+)|(0|([1-9][0-9]*))', ) ), ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ), ( 'CodeBegin', ( r'\[', ) ), ( 'CodeEnd', ( r'\]', ) ), ] rspec = [ ( 'Space', ( r'[ \t]+', ) ), ( 'String', ( r'"[^"]*"', ) ), ( 'SequenceString', ( r"'[^']*'", ) ), ( 'Number', ( r'-?(0x[0-9a-fA-F]+)|(0|([1-9][0-9]*))', ) ), ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ), ( 'VariableContents', ( r'''[^"' ;:=>()]+''', ) ), ] # Tokenize, expression stores the result, status is returned return self.tokenize_base( kll_expression, lspec, rspec ) def tokenize_mapping( self, kll_expression ): ''' Tokenize lparam and rparam in mapping expressions : ; # Set mapping :+ ; # Mappping append :- ; # Mapping removal :: ; # Replace mapping (does nothing if nothing to replace) Isolated versions of mappings When expressions are evalutated during runtime, any non-isolated mapping expressions are cancelled i: ; i:+ ; i:- ; i:: ; ''' # Define tokenization regex lspec = [ ( 'Space', ( r'[ \t]+', ) ), ( 'USBCode', ( r'U(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'USBCodeStart', ( r'U\[', ) ), ( 'ConsCode', ( r'CONS(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'ConsCodeStart', ( r'CONS\[', ) ), ( 'SysCode', ( r'SYS(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'SysCodeStart', ( r'SYS\[', ) ), ( 'ScanCode', ( r'S((0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'ScanCodeStart', ( r'S\[', ) ), ( 'IndCode', ( r'I(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'IndicatorStart', ( r'I\[', ) ), ( 'Pixel', ( r'P((0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'PixelStart', ( r'P\[', ) ), ( 'Animation', ( r'A"[^"]+"', ) ), ( 'AnimationStart', ( r'A\[', ) ), ( 'CodeBegin', ( r'\[', ) ), ( 'CodeEnd', ( r'\]', ) ), ( 'String', ( r'"[^"]*"', ) ), ( 'SequenceString', ( r"'[^']*'", ) ), ( 'Operator', ( r':', ) ), ( 'Comma', ( r',', ) ), ( 'Dash', ( r'-', ) ), ( 'Plus', ( r'\+', ) ), ( 'Parenthesis', ( r'\(|\)', ) ), ( 'Timing', ( r'[0-9]+(.[0-9]+)?((s)|(ms)|(us)|(ns))', ) ), ( 'Number', ( r'-?(0x[0-9a-fA-F]+)|(0|([1-9][0-9]*))', ) ), ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ), ] rspec = [ ( 'Space', ( r'[ \t]+', ) ), ( 'USBCode', ( r'U(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'USBCodeStart', ( r'U\[', ) ), ( 'ConsCode', ( r'CONS(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'ConsCodeStart', ( r'CONS\[', ) ), ( 'SysCode', ( r'SYS(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'SysCodeStart', ( r'SYS\[', ) ), ( 'ScanCode', ( r'S((0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'ScanCodeStart', ( r'S\[', ) ), ( 'Pixel', ( r'P((0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'PixelStart', ( r'P\[', ) ), ( 'PixelLayer', ( r'PL((0x[0-9a-fA-F]+)|([0-9]+))', ) ), ( 'PixelLayerStart', ( r'PL\[', ) ), ( 'Animation', ( r'A"[^"]+"', ) ), ( 'AnimationStart', ( r'A\[', ) ), ( 'CodeBegin', ( r'\[', ) ), ( 'CodeEnd', ( r'\]', ) ), ( 'String', ( r'"[^"]*"', ) ), ( 'SequenceString', ( r"'[^']*'", ) ), ( 'None', ( r'None', ) ), ( 'Operator', ( r':', ) ), ( 'Comma', ( r',', ) ), ( 'Dash', ( r'-', ) ), ( 'Plus', ( r'\+', ) ), ( 'Parenthesis', ( r'\(|\)', ) ), ( 'Timing', ( r'[0-9]+(.[0-9]+)?((s)|(ms)|(us)|(ns))', ) ), ( 'Number', ( r'-?(0x[0-9a-fA-F]+)|(0|([1-9][0-9]*))', ) ), ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ), ] # Tokenize, expression stores the result, status is returned return self.tokenize_base( kll_expression, lspec, rspec ) ## Parsers ## def parse_base( self, kll_expression, parse_expression, quiet ): ''' Base parsing logic @param kll_expression: Expression being parsed, contains tokens @param parse_expression: Parse tree expression that understands the group of tokens @param quiet: Reduces verbosity, used when re-running an errored command in debug mode @return: False if parsing wasn't successful ''' ret = True try: # Since the expressions have already been pre-organized, we only expect a single expression at a time ret = parse_expression.parse( kll_expression.final_tokens() ) # Parse intepretation error, more info is provided by the specific parse intepreter if not ret and not quiet: print( kll_expression.final_tokens() ) except NoParseError as err: if not quiet: print( kll_expression.final_tokens() ) print( err ) ret = False return ret def parse_name_association( self, kll_expression, quiet=False ): ''' Parse name association expressions => ; ''' # Import parse elements/lambda functions from common.parse import ( comma, name, number, operator, parenthesis, unarg, Make, ) # Name Association # => ; capability_arguments = name + skip( operator(':') ) + number + skip( maybe( comma ) ) >> unarg( Make.capArg ) capability_expression = name + skip( operator('=>') ) + name + skip( parenthesis('(') ) + many( capability_arguments ) + skip( parenthesis(')') ) >> unarg( kll_expression.capability ) # Name Association # => ; define_expression = name + skip( operator('=>') ) + name >> unarg( kll_expression.define ) # Top-level Parser expr = ( capability_expression | define_expression ) return self.parse_base( kll_expression, expr, quiet ) def parse_data_association( self, kll_expression, quiet=False ): ''' Parse data association expressions <= ; ''' from common.parse import ( animation_def, animation_elem, animation_flattened, animation_modlist, comma, flatten, operator, pixel_elem, pixel_expanded, pixelmod_elem, position_list, triggerCode_outerList, unarg, ) # Data Association # <= ; # <= ; animation_expression = ( animation_elem | animation_def ) + skip( operator('<=') ) + animation_modlist >> unarg( kll_expression.animation ) animationFrame_expression = animation_flattened + skip( operator('<=') ) + many( pixelmod_elem + skip( maybe( comma ) ) ) >> unarg( kll_expression.animationFrame ) # Data Association # <= ; pixelPosition_expression = ( pixel_expanded | pixel_elem ) + skip( operator('<=') ) + position_list >> unarg( kll_expression.pixelPosition ) # Data Association # <= ; scanCodePosition_expression = ( triggerCode_outerList >> flatten >> flatten ) + skip( operator('<=') ) + position_list >> unarg( kll_expression.scanCodePosition ) # Top-level Parser expr = ( animation_expression | animationFrame_expression | pixelPosition_expression | scanCodePosition_expression ) return self.parse_base( kll_expression, expr, quiet ) def parse_assignment( self, kll_expression, quiet=False ): ''' Parse assignment expressions = ; ''' # Import parse elements/lambda functions from common.parse import ( code_begin, code_end, comma, content, dash, name, number, operator, string, unarg, unseqString, ) # Assignment # = ; variable_contents = name | content | string | number | comma | dash | unseqString variable_expression = name + skip( operator('=') ) + oneplus( variable_contents ) >> unarg( kll_expression.variable ) # Array Assignment # [] = ; # [] = ; array_expression = name + skip( code_begin ) + maybe( number ) + skip( code_end ) + skip( operator('=') ) + oneplus( variable_contents ) >> unarg( kll_expression.array ) # Top-level Parser expr = ( array_expression | variable_expression ) return self.parse_base( kll_expression, expr, quiet ) def parse_mapping( self, kll_expression, quiet=False ): ''' Parse mapping expressions : ; # Set mapping :+ ; # Mappping append :- ; # Mapping removal :: ; # Replace mapping (does nothing if nothing to replace) Isolated versions of mappings When expressions are evalutated during runtime, any non-isolated mapping expressions are cancelled i: ; i:+ ; i:- ; i:: ; ''' # Import parse elements/lambda functions from common.parse import ( animation_expanded, none, operator, pixelchan_elem, resultCode_outerList, scanCode_single, triggerCode_outerList, triggerUSBCode_outerList, unarg, ) # Mapping # : ; operatorTriggerResult = operator(':') | operator(':+') | operator(':-') | operator('::') | operator('i:') | operator('i:+') | operator('i:-') | operator('i::') scanCode_expression = triggerCode_outerList + operatorTriggerResult + resultCode_outerList >> unarg( kll_expression.scanCode ) usbCode_expression = triggerUSBCode_outerList + operatorTriggerResult + resultCode_outerList >> unarg( kll_expression.usbCode ) animation_trigger = animation_expanded + operatorTriggerResult + resultCode_outerList >> unarg( kll_expression.animationTrigger ) # Data Association # : ; pixelChan_expression = pixelchan_elem + skip( operator(':') ) + ( scanCode_single | none ) >> unarg( kll_expression.pixelChannels ) # Top-level Parser expr = ( scanCode_expression | usbCode_expression | pixelChan_expression | animation_trigger ) return self.parse_base( kll_expression, expr, quiet ) ## Processing ## def tokenize( self, kll_context ): ''' Tokenizes contents of both LOperatorData and ROperatorData LOperatorData and ROperatorData have different contexts, so tokenization can be simplified a bit @param context: KLL Context containing file data ''' ret = True # Tokenizer map, each takes an expression argument tokenizers = { # Name association '=>' : self.tokenize_name_association, # Data association '<=' : self.tokenize_data_association, # Assignment '=' : self.tokenize_assignment, # Mapping # All : based operators have the same structure # The only difference is the application context (handled in a later stage) ':' : self.tokenize_mapping, } # Tokenize left and right parameters of the expression for kll_expression in kll_context.expressions: # Determine which parser to use token = kll_expression.operator_type() # If there was a problem tokenizing, display exprersion info if not tokenizers[ token ]( kll_expression ): ret = False print( "{0} {1}:tokenize -> {2}:{3}".format( ERROR, self.__class__.__name__, kll_context.parent.path, kll_expression.lparam_token.start[0], ) ) # Debug Output # Displays each parsed expression on a single line # Includes : if self.token_debug: # Uncolorize if requested output = "\033[1m{0}\033[0m:\033[1;33m{1}\033[0m:\033[1;32m{2}\033[0m\033[1;36;41m>\033[0m {3}".format( os.path.basename( kll_context.parent.path ), kll_expression.lparam_token.start[0], kll_expression.__class__.__name__, kll_expression.final_tokens(), ) print( self.color and output or ansi_escape.sub( '', output ) ) return ret def parse( self, kll_context ): ''' Parse the fully tokenized expressions @param kll_context: KLL Context which has the fully tokenized expression list ''' ret = True # Parser map of functions, each takes an expression argument parsers = { # Name association '=>' : self.parse_name_association, # Data association '<=' : self.parse_data_association, # Assignment '=' : self.parse_assignment, # Mapping # All : based operators have the same structure # The only difference is the application context (handled in a later stage) ':' : self.parse_mapping, } # Parse each expression to extract the data from it for kll_expression in kll_context.expressions: token = kll_expression.operator_type() # Assume failed, unless proven otherwise cur_ret = False # In some situations we don't want a parser trace, but only disable when we know parser_debug_ignore = False # If there was a problem parsing, display expression info # Catch any TypeErrors due to incorrect parsing rules try: cur_ret = parsers[ token ]( kll_expression ) # Unexpected token (user grammar error), sometimes might be a bug except NoParseError as err: import traceback traceback.print_tb( err.__traceback__ ) print( type( err ).__name__, err ) print( "Bad kll expression, usually a syntax error." ) # Invalid parsing rules, definitely a bug except TypeError as err: import traceback traceback.print_tb( err.__traceback__ ) print( type( err ).__name__, err ) print( "Bad parsing rule, this is a bug!" ) # Lookup error, invalid lookup except KeyError as err: import traceback print( "".join( traceback.format_tb( err.__traceback__ )[-1:] ), end='' ) print( "Invalid dictionary lookup, check syntax." ) parser_debug_ignore = True # Parsing failed, show more error info if not cur_ret: ret = False # We don't always want a full trace of the parser if not parser_debug_ignore: # StringIO stream from funcparserlib parser.py # Command failed, run again, this time with verbose logging enabled # Helps debug erroneous parsing expressions parser_log = io.StringIO() # This part is not thread-safe # You must run with --jobs 1 to get 100% valid output Parser_debug( True, parser_log ) try: parsers[ token ]( kll_expression, True ) except: pass Parser_debug( False ) # Display print( parser_log.getvalue() ) # Cleanup StringIO parser_log.close() print( "{0} {1}:parse -> {2}:{3}".format( ERROR, self.__class__.__name__, kll_context.parent.path, kll_expression.lparam_token.start[0], ) ) # Debug Output # Displays each parsed expression on a single line # Includes : if self.parser_debug: # Uncolorize if requested output = "\033[1m{0}\033[0m:\033[1;33m{1}\033[0m:\033[1;32m{2}\033[0m:\033[1;35m{3}\033[1;36;41m>\033[0m {4}".format( os.path.basename( kll_context.parent.path ), kll_expression.lparam_token.start[0], kll_expression.__class__.__name__, kll_expression.type, kll_expression ) print( self.color and output or ansi_escape.sub( '', output ) ) if self.parser_token_debug: # Uncolorize if requested output = "\t\033[1;4mTokens\033[0m\033[1;36m:\033[0m {0}".format( [ ( t.type, t.value ) for t in kll_expression.final_tokens() ] ) print( self.color and output or ansi_escape.sub( '', output ) ) return ret def process( self ): ''' Compiler Configuration Processing ''' self._status = 'Running' # Determine colorization setting self.color = self.control.stage('CompilerConfigurationStage').color # Acquire thread pool pool = self.control.stage('CompilerConfigurationStage').pool # Get list of KLL contexts contexts = self.control.stage('OperationClassificationStage').contexts # Tokenize operators if False in pool.map( self.tokenize, contexts ): self._status = 'Incomplete' return # Parse operators if False in pool.map( self.parse, contexts ): self._status = 'Incomplete' return self._status = 'Completed' class OperationOrganizationStage( Stage ): ''' Operation Organization Stage * Using the type of each operation, apply the KLL Context to each operation * This results in various datastructures being populated based upon the context and type of operation * Each Context instance (distinct Context of the same type), remain separate ''' def __init__( self, control ): ''' Initialize configuration variables ''' super().__init__( control ) self.operation_organization_debug = False self.operation_organization_display = False def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' self.operation_organization_debug = args.operation_organization_debug self.operation_organization_display = args.operation_organization_display def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group group = parser.add_argument_group('\033[1mOperation Organization Configuration\033[0m') # Optional Arguments group.add_argument( '--operation-organization-debug', action='store_true', default=self.operation_organization_debug, help="Enable operation organization debug output.\n", ) group.add_argument( '--operation-organization-display', action='store_true', default=self.operation_organization_display, help="Show datastructure of each context after filling.\n", ) def organize( self, kll_context ): ''' Organize each set of expressions on a context level The full layout organization occurs over multiple stages, this is the first one ''' # Add each of the expressions to the organization data structure try: for kll_expression in kll_context.expressions: # Debug output if self.operation_organization_debug: # Uncolorize if requested output = "\033[1m{0}\033[0m:\033[1;33m{1}\033[0m:\033[1;32m{2}\033[0m:\033[1;35m{3}\033[1;36;41m>\033[0m {4}".format( os.path.basename( kll_context.parent.path ), kll_expression.lparam_token.start[0], kll_expression.__class__.__name__, kll_expression.type, kll_expression ) print( self.color and output or ansi_escape.sub( '', output ) ) # Add expression kll_context.organization.add_expression( kll_expression, ( self.operation_organization_debug, self.color ) ) except Exception as err: import traceback traceback.print_tb( err.__traceback__ ) print( type( err ).__name__, err ) print( "Could not add/modify kll expression in context datastructure." ) return False return True def process( self ): ''' Operation Organization Stage Processing ''' self._status = 'Running' # Determine colorization setting self.color = self.control.stage('CompilerConfigurationStage').color # Acquire thread pool pool = self.control.stage('CompilerConfigurationStage').pool # Get list of KLL contexts contexts = self.control.stage('OperationClassificationStage').contexts # Add expressions from contexts to context datastructures if False in pool.map( self.organize, contexts ): self._status = 'Incomplete' return # Show result of filling datastructure if self.operation_organization_display: for kll_context in contexts: # Uncolorize if requested output = "\033[1m{0}\033[0m:\033[1;33m{1}\033[0m".format( os.path.basename( kll_context.parent.path ), kll_context.__class__.__name__ ) print( self.color and output or ansi_escape.sub( '', output ) ) # Display Table for store in kll_context.organization.stores(): # Uncolorize if requested output = "\t\033[1;4;32m{0}\033[0m".format( store.__class__.__name__ ) print( self.color and output or ansi_escape.sub( '', output ) ) print( self.color and store or ansi_escape.sub( '', store ), end="" ) self._status = 'Completed' class DataOrganizationStage( Stage ): ''' Data Organization Stage * Using the constructed Context datastructures, merge contexts of the same type together * Precedence/priority is defined by the order each Context was included on the command line * May include datastructure data optimizations ''' def __init__( self, control ): ''' Initialize configuration variables ''' super().__init__( control ) self.data_organization_debug = False self.data_organization_display = False self.contexts = None def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' self.data_organization_debug = args.data_organization_debug self.data_organization_display = args.data_organization_display def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group group = parser.add_argument_group('\033[1mData Organization Configuration\033[0m') # Optional Arguments group.add_argument( '--data-organization-debug', action='store_true', default=self.data_organization_debug, help="Show debug info from data organization stage.\n", ) group.add_argument( '--data-organization-display', action='store_true', default=self.data_organization_display, help="Show datastructure of each context after merging.\n", ) def sort_contexts( self, contexts ): ''' Returns a dictionary of list of sorted 'like' contexts This is used to group the contexts that need merging ''' lists = {} for kll_context in contexts: name = kll_context.__class__.__name__ # PartialMapContext's are sorted by name *and* layer number if name == "PartialMapContext": name = "{0}{1}".format( name, kll_context.layer ) # Add new list if no elements yet if name not in lists.keys(): lists[ name ] = [ kll_context ] else: lists[ name ].append( kll_context ) return lists def organize( self, kll_context ): ''' Symbolically merge all like Contexts The full layout organization occurs over multiple stages, this is the second stage ''' # Lookup context name context_name = "{0}".format( kll_context[0].__class__.__name__ ) # PartialMapContext's are sorted by name *and* layer number if context_name == "PartialMapContext": context_name = "{0}{1}".format( context_name, kll_context[0].layer ) # Initialize merge context as the first one self.contexts[ context_name ] = context.MergeContext( kll_context[0] ) # Indicate when a context is skipped as there is only one if self.data_organization_debug: if len( kll_context ) < 2: output = "\033[1;33mSkipping\033[0m\033[1m:\033[1;32m{0}\033[0m".format( context_name ) print( self.color and output or ansi_escape.sub( '', output ) ) return True # The incoming list is ordered # Merge in each of the contexts symbolically for next_context in kll_context[1:]: try: self.contexts[ context_name ].merge( next_context, ( self.data_organization_debug, self.color ) ) except Exception as err: import traceback traceback.print_tb( err.__traceback__ ) print( type( err ).__name__, err ) print( "Could not merge '{0}' into '{1}' context.".format( os.path.basename( next_context.parent.path ), context_name ) ) return False return True def process( self ): ''' Data Organization Stage Processing ''' self._status = 'Running' # Determine colorization setting self.color = self.control.stage('CompilerConfigurationStage').color # Acquire thread pool pool = self.control.stage('CompilerConfigurationStage').pool # Get list of KLL contexts contexts = self.control.stage('OperationClassificationStage').contexts # Get sorted list of KLL contexts sorted_contexts = self.sort_contexts( contexts ) self.contexts = {} # Add expressions from contexts to context datastructures if False in pool.map( self.organize, sorted_contexts.values() ): self._status = 'Incomplete' return # Show result of filling datastructure if self.data_organization_display: for key, kll_context in self.contexts.items(): # Uncolorize if requested output = "\033[1;33m{0}\033[0m:\033[1m{1}\033[0m".format( key, kll_context.paths(), ) print( self.color and output or ansi_escape.sub( '', output ) ) # Display Table for store in kll_context.organization.stores(): # Uncolorize if requested output = "\t\033[1;4;32m{0}\033[0m".format( store.__class__.__name__ ) print( self.color and output or ansi_escape.sub( '', output ) ) print( self.color and store or ansi_escape.sub( '', store ), end="" ) self._status = 'Completed' class DataFinalizationStage( Stage ): ''' Data Finalization Stage * Using the merged Context datastructures, apply the Configuration and BaseMap contexts to the higher level DefaultMap and PartialMap Contexts * First BaseMap is applied on top of Configuration * Next, DefaultMap is applied on top of (Configuration+BaseMap) as well as the PartialMaps * May include datastructure data optimizations ''' def __init__( self, control ): ''' Initialize configuration variables ''' super().__init__( control ) self.data_finalization_debug = False self.data_finalization_display = False self.base_context = None self.default_context = None self.partial_contexts = None self.full_context = None self.context_list = None self.layer_contexts = None def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' self.data_finalization_debug = args.data_finalization_debug self.data_finalization_display = args.data_finalization_display def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group group = parser.add_argument_group('\033[1mData Organization Configuration\033[0m') # Optional Arguments group.add_argument( '--data-finalization-debug', action='store_true', default=self.data_finalization_debug, help="Show debug info from data finalization stage.\n", ) group.add_argument( '--data-finalization-display', action='store_true', default=self.data_finalization_display, help="Show datastructure of each context after merging.\n", ) def process( self ): ''' Data Organization Stage Processing ''' self._status = 'Running' # Determine colorization setting self.color = self.control.stage('CompilerConfigurationStage').color # Get context silos contexts = self.control.stage('DataOrganizationStage').contexts self._status = 'Incomplete' # Context list self.context_list = [] # Depending on the calling order, we may need to use a GenericContext or ConfigurationContext as the base # Default to ConfigurationContext first if 'ConfigurationContext' in contexts.keys(): self.base_context = context.MergeContext( contexts['ConfigurationContext'] ) # If we still have GenericContexts around, merge them on top of the ConfigurationContext if 'GenericContext' in contexts.keys(): self.base_context.merge( contexts['GenericContext'], ( self.data_finalization_debug, self.color ) ) # Otherwise, just use a GenericContext elif 'GenericContext' in contexts.keys(): self.base_context = context.MergeContext( contexts['GenericContext'] ) # Fail otherwise, you *must* have a GenericContext or ConfigurationContext else: print( "{0} Missing a 'GenericContext' and/or 'ConfigurationContext'.".format( ERROR ) ) self._status = 'Incomplete' return # Next use the BaseMapContext and overlay on ConfigurationContext # This serves as the basis for the next two merges if 'BaseMapContext' in contexts.keys(): self.base_context.merge( contexts['BaseMapContext'], ( self.data_finalization_debug, self.color ) ) self.context_list.append( ( 'BaseMapContext', self.base_context ) ) # Then use the DefaultMapContext as the default keyboard mapping self.default_context = context.MergeContext( self.base_context ) if 'DefaultMapContext' in contexts.keys(): self.default_context.merge( contexts['DefaultMapContext'], ( self.data_finalization_debug, self.color ) ) self.context_list.append( ( 'DefaultMapContext', self.default_context ) ) # For convenience build a fully merged dataset # This is usually only required for variables self.full_context = context.MergeContext( self.default_context ) # Finally setup each of the PartialMapContext groups # Build list of PartialMapContexts and sort by layer before iterating over self.partial_contexts = [] partial_context_list = [ ( item[1].layer, item[1] ) for item in contexts.items() if 'PartialMapContext' in item[0] ] for layer, partial in sorted( partial_context_list, key=lambda x: x[0] ): self.partial_contexts.append( context.MergeContext( self.base_context ) ) self.partial_contexts[ layer ].merge( partial, ( self.data_finalization_debug, self.color ) ) self.context_list.append( ( 'PartialMapContext{0}'.format( layer ), self.default_context ) ) # Add each partial to the full_context as well self.full_context.merge( partial, ( self.data_finalization_debug, self.color ) ) # Build layer context list # Each index of the list corresponds to the keyboard layer self.layer_contexts = [ self.default_context ] self.layer_contexts.extend( self.partial_contexts ) # Show result of filling datastructure if self.data_finalization_display: for key, kll_context in self.context_list: # Uncolorize if requested output = "*\033[1;33m{0}\033[0m:\033[1m{1}\033[0m".format( key, kll_context.paths(), ) print( self.color and output or ansi_escape.sub( '', output ) ) # Display Table for store in kll_context.organization.stores(): # Uncolorize if requested output = "\t\033[1;4;32m{0}\033[0m".format( store.__class__.__name__ ) print( self.color and output or ansi_escape.sub( '', output ) ) print( self.color and store or ansi_escape.sub( '', store ), end="" ) self._status = 'Completed' class DataAnalysisStage( Stage ): ''' Data Analysis Stage * Using the completed Context datastructures, do additional analysis that may be required for Code Generation ''' def __init__( self, control ): ''' Initialize configuration variables ''' super().__init__( control ) self.layer_contexts = None self.full_context = None def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group #group = parser.add_argument_group('\033[1mData Analysis Configuration\033[0m') def reduction( self ): ''' Builds a new reduced_contexts list For each of the layers, evaluate triggers into ScanCodes (USBCode to ScanCodes) (all other triggers don't require reductions) ''' self.reduced_contexts = [] for layer in self.layer_contexts: reduced = context.MergeContext( layer ) reduced.reduction() self.reduced_contexts.append( reduced ) def generate_mapping_indices( self ): ''' For each trigger:result pair generate a unique index The triggers and results are first sorted alphabetically ''' # Build list of map expressions expressions = [] # Gather list of expressions for layer in self.layer_contexts: expressions.extend( layer.organization.mapping_data.data.items() ) # Sort expressions by trigger, there may be *duplicate* triggers however don't reduce yet # we need the result mappings as well trigger_sorted = sorted( expressions, key=lambda x: x[1][0].trigger_str() ) trigger_filtered = [ elem for elem in trigger_sorted if not elem[1][0].type == 'USBCode' ] #print( trigger_filtered ) # Sort expressions by result, there may be *duplicate* results however don't reduce yet # we need the result mappings as well result_sorted = sorted( expressions, key=lambda x: x[1][0].result_str() ) #print( result_sorted ) # Build List of Triggers and sort by string contents # XXX Only scan codes right now # This will need to expand to a #TODO # Build List of Results and sort by string contents # TODO def sort_map_index_lists( self ): ''' ''' def generate_map_offset_table( self ): ''' ''' def generate_trigger_lists( self ): ''' ''' def analyze( self ): ''' Analyze the set of configured contexts TODO: Perhaps use emitters or something like it for this code? -HaaTa ''' # Reduce Contexts # Convert all trigger USBCodes to ScanCodes self.reduction() # Generate Indices # Assigns a sequential index (starting from 0) for each map expresssion self.generate_mapping_indices() # Sort Index Lists # Using indices sort Trigger and Results macros self.sort_map_index_lists() # Generate Offset Table # This is needed for interconnect devices self.generate_map_offset_table() # Generate Trigger Lists self.generate_trigger_lists() def process( self ): ''' Data Analysis Stage Processing ''' self._status = 'Running' # Determine colorization setting self.color = self.control.stage('CompilerConfigurationStage').color # Acquire list of contexts self.layer_contexts = self.control.stage('DataFinalizationStage').layer_contexts self.full_context = self.control.stage('DataFinalizationStage').full_context # Analyze set of contexts self.analyze() self._status = 'Completed' class CodeGenerationStage( Stage ): ''' Code Generation Stage * Generates code for the given firmware backend * Backend is selected in the Compiler Configuration Stage * Uses the specified emitter to generate the code ''' def __init__( self, control ): ''' Initialize configuration variables ''' super().__init__( control ) def command_line_args( self, args ): ''' Group parser for command line arguments @param args: Name space of processed arguments ''' self.control.stage('CompilerConfigurationStage').emitters.command_line_args( args ) def command_line_flags( self, parser ): ''' Group parser for command line options @param parser: argparse setup object ''' # Create new option group #group = parser.add_argument_group('\033[1mCode Generation Configuration\033[0m') # Create options groups for each of the Emitters self.control.stage('CompilerConfigurationStage').emitters.command_line_flags( parser ) def process( self ): ''' Data Organization Stage Processing ''' self._status = 'Running' # Determine colorization setting self.color = self.control.stage('CompilerConfigurationStage').color # Get Emitter object self.emitter = self.control.stage('CompilerConfigurationStage').emitters.emitter( self.control.stage('CompilerConfigurationStage').emitter ) # Call Emitter self.emitter.process() # Generate Outputs using Emitter self.emitter.output() self._status = 'Completed' class ReportGenerationStage( Stage ): ''' Report Generation Stage * Using the datastructures and analyzed data, generate a compiler report * TODO '''