- Added kll file emitter * Re-constitutes KLL files from various stages of the compilation process - Using kll file emitter added a basic assignment expression unit test * Added unit test to travis - Fixed array assignment merging when using position operators * Required a special case merge - Update output templates for kiibohddev
@@ -65,3 +65,10 @@ __pycache__/ | |||
########### | |||
*.attr | |||
# Misc # | |||
######## | |||
generatedKeymap.h | |||
generatedPixelmap.c | |||
kll_defs.h | |||
tests/generated | |||
@@ -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: |
@@ -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 |
@@ -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" | |||
@@ -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: |
@@ -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 ) | |||
} | |||
@@ -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 | |||
@@ -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 <http://www.gnu.org/licenses/>. | |||
### 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" ) | |||
@@ -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; | |||
@@ -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[] = { <animation>_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, |
@@ -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 $? | |||
@@ -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; |