diff --git a/.gitignore b/.gitignore index f87a4ba..b89fa2d 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,10 @@ __pycache__/ ########### *.attr +# Misc # +######## +generatedKeymap.h +generatedPixelmap.c +kll_defs.h +tests/generated + diff --git a/.travis.yml b/.travis.yml index d14752a..82a17f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,7 @@ env: # Basic KLL Tests - DIR=tests SCRIPT=sanity.bash - DIR=tests SCRIPT=syntax.bash + - DIR=tests SCRIPT=assignment.bash # Exclusions matrix: diff --git a/common/emitter.py b/common/emitter.py index 37bfcf4..9ee830c 100644 --- a/common/emitter.py +++ b/common/emitter.py @@ -106,6 +106,31 @@ class Emitter: ) +class FileEmitter: + ''' + KLL File Emitter Class + + Base class for any emitter that wants to output a file. + Generally, it is recommended to use the TextEmitter as templates are more readable. + ''' + def __init__( self ): + ''' + FileEmitter Initialization + ''' + self.output_files = [] + + def generate( self, output_path ): + ''' + Generate output file + + @param contents: String contents of file + @param output_path: Path to output file + ''' + for name, contents in self.output_files: + with open( "{0}/{1}".format( output_path, name ), 'w' ) as outputFile: + outputFile.write( contents ) + + class TextEmitter: ''' KLL Text Emitter Class diff --git a/common/expression.py b/common/expression.py index 02da8e8..472cabc 100644 --- a/common/expression.py +++ b/common/expression.py @@ -189,6 +189,38 @@ class AssignmentExpression( Expression ): 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 @@ -211,7 +243,17 @@ class AssignmentExpression( Expression ): if self.type == 'Variable': return "{0} = {1};".format( self.name, self.value ) elif self.type == 'Array': - return "{0}[{1}] = {2};".format( self.name, self.pos, self.value ) + # 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" diff --git a/common/organization.py b/common/organization.py index 8d44441..d160128 100644 --- a/common/organization.py +++ b/common/organization.py @@ -497,6 +497,44 @@ class VariableData( Data ): Variable -> Data Array -> Data ''' + def add_expression( self, expression, debug ): + ''' + Add expression to data structure + + May have multiple keys to add for a given expression + + In the case of indexed variables, only replaced the specified index + + @param expression: KLL Expression (fully tokenized and parsed) + @param debug: Enable debug output + ''' + # Lookup unique keys for expression + keys = expression.unique_keys() + + # Add/Modify expressions in datastructure + for key, uniq_expr in keys: + # Check which operation we are trying to do, add or modify + if debug[0]: + if key in self.data.keys(): + output = self.debug_output['mod'].format( key ) + else: + output = self.debug_output['add'].format( key ) + print( debug[1] and output or ansi_escape.sub( '', output ) ) + + # Check to see if we need to cap-off the array (a position parameter is given) + if uniq_expr.type == 'Array' and uniq_expr.pos is not None: + # Modify existing array + if key in self.data.keys(): + self.data[ key ].merge_array( uniq_expr ) + + # Add new array + else: + uniq_expr.merge_array() + self.data[ key ] = uniq_expr + + # Otherwise just add/replace expression + else: + self.data[ key ] = uniq_expr class Organization: diff --git a/emitters/emitters.py b/emitters/emitters.py index 014c2d5..c982222 100644 --- a/emitters/emitters.py +++ b/emitters/emitters.py @@ -21,6 +21,7 @@ KLL Emitters Container Classes ### Imports ### import emitters.kiibohd.kiibohd as kiibohd +import emitters.kll.kll as kll import emitters.none.none as none @@ -60,6 +61,7 @@ class Emitters: # Dictionary of Emitters self.emitters = { 'kiibohd' : kiibohd.Kiibohd( control ), + 'kll' : kll.KLL( control ), 'none' : none.Drop( control ) } diff --git a/emitters/kiibohd/kiibohd.py b/emitters/kiibohd/kiibohd.py index 44f1bc5..63078b6 100644 --- a/emitters/kiibohd/kiibohd.py +++ b/emitters/kiibohd/kiibohd.py @@ -253,9 +253,9 @@ class Kiibohd( Emitter, TextEmitter ): self.fill_dict['CapabilitiesList'] = "const Capability CapabilitiesList[] = {\n" self.fill_dict['CapabilitiesIndices'] = "typedef enum CapabilityIndex {\n" - # Keys are pre-sorted + # Sorted by C Function name capabilities = full_context.query( 'NameAssociationExpression', 'Capability' ) - for dkey, dvalue in sorted( capabilities.data.items() ): + for dkey, dvalue in sorted( capabilities.data.items(), key=lambda x: x[1].association.name ): funcName = dvalue.association.name argByteWidth = dvalue.association.total_arg_bytes() @@ -266,6 +266,48 @@ class Kiibohd( Emitter, TextEmitter ): self.fill_dict['CapabilitiesList'] += "};" self.fill_dict['CapabilitiesIndices'] += "} CapabilityIndex;" + + + ## TODO MOVE ## + ## Pixel Buffer Setup ## + self.fill_dict['PixelBufferSetup'] = "PixelBuf PixelBuffers[] = {\n" + + # Lookup number of buffers + bufsize = len( variables.data[ defines.data['Pixel_Buffer_Size'].name ].value ) + for index in range( bufsize ): + # TODO + self.fill_dict['PixelBufferSetup'] += "\tPixelBufElem( {0}, {1}, {2}, {3} ),\n".format( + 0, + #variables.data[ defines.data['Pixel_Buffer_Length'].name ].value[ index ], + variables.data[ defines.data['Pixel_Buffer_Width'].name ].value[ index ], + variables.data[ defines.data['Pixel_Buffer_Size'].name ].value[ index ], + #variables.data[ defines.data['Pixel_Buffer_Buffer'].name ].value[ index ], + 0, + ) + self.fill_dict['PixelBufferSetup'] += "};" + print( self.fill_dict['PixelBufferSetup'] ) + + + ## Pixel Mapping ## + self.fill_dict['PixelMapping'] = "" + # TODO + + + ## Pixel Display Mapping ## + self.fill_dict['PixelDisplayMapping'] = "const uint8_t Pixel_DisplayMapping[] = {\n" + # TODO + self.fill_dict['PixelDisplayMapping'] += "};" + + + ## ScanCode to Pixel Mapping ## + self.fill_dict['ScanCodeToPixelMapping'] = "const uint8_t Pixel_ScanCodeToPixel[] = {\n" + # TODO + self.fill_dict['ScanCodeToPixelMapping'] = "};" + + + ## Animations ## + self.fill_dict['Animations'] = "" + ## TODO MOVE END ## return diff --git a/emitters/kll/__init__.py b/emitters/kll/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/emitters/kll/kll.py b/emitters/kll/kll.py new file mode 100644 index 0000000..89ea856 --- /dev/null +++ b/emitters/kll/kll.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +''' +Re-Emits KLL files after processing. May do simplification. +''' + +# 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 ### + +import os + +from common.emitter import Emitter, FileEmitter + + +### Decorators ### + +## Print Decorator Variables +ERROR = '\033[5;1;31mERROR\033[0m:' +WARNING = '\033[5;1;33mWARNING\033[0m:' + + + +### Classes ### + +class KLL( Emitter, FileEmitter ): + ''' + Re-Emits KLL files, may simplify and re-order expressions. + ''' + + def __init__( self, control ): + ''' + Emitter initialization + + @param control: ControlStage object, used to access data from other stages + ''' + Emitter.__init__( self, control ) + FileEmitter.__init__( self ) + + # Defaults + self.target_dir = "generated" + + def command_line_args( self, args ): + ''' + Group parser for command line arguments + + @param args: Name space of processed arguments + ''' + self.target_dir = args.target_dir + + 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[1mKLL Emitter Configuration\033[0m') + + group.add_argument( '--target-dir', type=str, default=self.target_dir, + help="Target directory for generated files.\n" + "\033[1mDefault\033[0m: {0}\n".format( self.target_dir ) + ) + + def output( self ): + ''' + Final Stage of Emitter + + Generate KLL files + ''' + # Make sure output directory exists + os.makedirs( self.target_dir, exist_ok=True ) + + # Output list of files to disk + self.generate( self.target_dir ) + + def reconstitute_store( self, stores, name, debug=False ): + ''' + Takes a list of organization stores and re-constitutes them into a kll file + + @param stores: List of organization stores + @param name: Filename to call list of stores + @param debug: Debug mode (adds a comment to every line with the store key) + + @return: kll file contents + ''' + output = "" + + for store in stores: + for key, value in sorted( store.data.items(), key=lambda x: x[0] ): + if debug: + output += "{0} # {1}\n".format( value, key ) + else: + output += "{0}\n".format( value ) + + self.output_files.append( (name, output) ) + + def process( self ): + ''' + Emitter Processing + + Takes KLL datastructures and Analysis results then outputs them individually as kll files + ''' + # Acquire Datastructures + early_contexts = self.control.stage('DataOrganizationStage').contexts + base_context = self.control.stage('DataFinalizationStage').base_context + default_context = self.control.stage('DataFinalizationStage').default_context + partial_contexts = self.control.stage('DataFinalizationStage').partial_contexts + full_context = self.control.stage('DataFinalizationStage').full_context + + # Re-constitute KLL files using contexts of various stages + for key, context in early_contexts.items(): + self.reconstitute_store( context.organization.stores(), "{0}.kll".format( key ) ) + + self.reconstitute_store( base_context.organization.stores(), "base.kll" ) + + self.reconstitute_store( default_context.organization.stores(), "default.kll" ) + + for index, partial in enumerate( partial_contexts ): + self.reconstitute_store( partial.organization.stores(), "partial-{0}.kll".format( index ) ) + + self.reconstitute_store( full_context.organization.stores(), "final.kll" ) + diff --git a/examples/assignment.kll b/examples/assignment.kll index 5db85d6..9313061 100644 --- a/examples/assignment.kll +++ b/examples/assignment.kll @@ -1,9 +1,9 @@ -Variable = 1; -Array[] = a b c "b c" 3; -Index[5] = "this text" thing; # Single element -Index[6] = moar; +myVariable = 1; +myArray[] = a b c "b c" 3; +myIndex[5] = "this text" thing; # Single element +myIndex[6] = moar; -Variable => Variable_define; -Array => Array_define; -Index => Index_define; +myVariable => Variable_define; +myArray => Array_define; +myIndex => Index_define; diff --git a/templates/kiibohdPixelmap.c b/templates/kiibohdPixelmap.c index 849ffa4..c2c26cb 100644 --- a/templates/kiibohdPixelmap.c +++ b/templates/kiibohdPixelmap.c @@ -53,6 +53,7 @@ PixelBuf Pixel_Buffers[] = { // Pixel Mapping +<|PixelMapping|> //#define Pixel_TotalPixels 128 // TODO Generate const PixelElement Pixel_Mapping[] = { // Function Row (1-16) @@ -219,6 +220,7 @@ const PixelElement Pixel_Mapping[] = { // - 0.5 key spacing between the columns, in the case where multiple leds should be in the column, one is shifted to the right //#define Pixel_DisplayMapping_Cols 38 //#define Pixel_DisplayMapping_Rows 6 +<|PixelDisplayMapping|> const uint8_t Pixel_DisplayMapping[] = { 97, 1, 0, 98, 0, 2, 99, 3, 0, 4,100, 5, 0,101, 6,102, 7, 0, 8,103, 9,104, 0, 10,105, 11, 0, 12,106, 13, 0,107, 14,108, 15, 0, 16,109, 128, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 0, 30, 0, 0, 0, 31, 0, 32, 0, 33,110, @@ -269,6 +271,8 @@ const uint8_t testani_frame2[] = { #define RGB_White 255,255,255 #define RGB_Black 0,0,0 +#define RGB_MD_Blue 0x00,0xAE,0xDA + const uint8_t rainbow_inter_frame0[] = { Pixel_ModRGBCol(0, Set, RGB_Green), @@ -313,16 +317,32 @@ const uint8_t *clear_pixels_frames[] = { }; +const uint8_t md_blue_frame0[] = { + Pixel_ModRGBCol(0, Set, RGB_MD_Blue), + Pixel_ModRGBCol(37, Set, RGB_MD_Blue), + 0, +}; + + +const uint8_t *md_blue_frames[] = { + md_blue_frame0, + 0, +}; + + // Index of animations // uint8_t *Pixel_Animations[] = { _frames, ... } +<|Animations|> const uint8_t **Pixel_Animations[] = { testani_frames, rainbow_inter_frames, clear_pixels_frames, + md_blue_frames, }; // ScanCode to Pixel Mapping +<|ScanCodeToPixelMapping|> const uint8_t Pixel_ScanCodeToPixel[] = { // Function Row (1-16) 1, diff --git a/tests/assignment.bash b/tests/assignment.bash new file mode 100755 index 0000000..01feea2 --- /dev/null +++ b/tests/assignment.bash @@ -0,0 +1,39 @@ +#!/bin/bash +# Use example .kll files to check basic kll processing +# Does a diff comparison with a pre-generated file for validation +# Jacob Alexander 2016 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Common functions +source ${SCRIPT_DIR}/common.bash + +# Start in kll top-level directory +cd ${SCRIPT_DIR}/.. + +# Cleanup previously generated files +rm -rf ${SCRIPT_DIR}/generated + + +# Args used for each of the tests +ARGS="--emitter kll --target-dir ${SCRIPT_DIR}/generated" +FAIL_ARGS="--emitter kll --target-dir ${SCRIPT_DIR}/generated --token-debug --parser-token-debug --operation-organization-display --data-organization-display --data-finalization-display" + +# Files to check syntax on +FILES=( + examples/assignment.kll +) + + +## Tests + + +cmds "./kll" "${ARGS}" "${FAIL_ARGS}" ${FILES[@]} +cmd diff --color=always ${SCRIPT_DIR}/cmp_assignment/final.kll ${SCRIPT_DIR}/generated/final.kll + + +## Tests complete + + +result +exit $? + diff --git a/tests/cmp_assignment/final.kll b/tests/cmp_assignment/final.kll new file mode 100644 index 0000000..b17ee18 --- /dev/null +++ b/tests/cmp_assignment/final.kll @@ -0,0 +1,6 @@ +myArray <= Array_define; +myIndex <= Index_define; +myVariable <= Variable_define; +myArray[] = "a" "b" "c" "b c" "3"; +myIndex[] = "" "" "" "" "" "this textthing" "moar"; +myVariable = 1;