KLL Compiler
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

kll.py 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. #!/usr/bin/env python3
  2. # KLL Compiler
  3. # Keyboard Layout Langauge
  4. #
  5. # Copyright (C) 2014-2016 by Jacob Alexander
  6. #
  7. # This file is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This file is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this file. If not, see <http://www.gnu.org/licenses/>.
  19. ### Imports ###
  20. import argparse
  21. import importlib
  22. import io
  23. import os
  24. import re
  25. import sys
  26. import token
  27. from pprint import pformat
  28. from re import VERBOSE
  29. from tokenize import generate_tokens
  30. from kll_lib.containers import *
  31. from kll_lib.hid_dict import *
  32. from funcparserlib.lexer import make_tokenizer, Token, LexerError
  33. from funcparserlib.parser import (some, a, many, oneplus, skip, finished, maybe, skip, forward_decl, NoParseError)
  34. ### Decorators ###
  35. ## Print Decorator Variables
  36. ERROR = '\033[5;1;31mERROR\033[0m:'
  37. ## Python Text Formatting Fixer...
  38. ## Because the creators of Python are averse to proper capitalization.
  39. textFormatter_lookup = {
  40. "usage: " : "Usage: ",
  41. "optional arguments" : "Optional Arguments",
  42. }
  43. def textFormatter_gettext( s ):
  44. return textFormatter_lookup.get( s, s )
  45. argparse._ = textFormatter_gettext
  46. ### Argument Parsing ###
  47. def checkFileExists( filename ):
  48. if not os.path.isfile( filename ):
  49. print ( "{0} {1} does not exist...".format( ERROR, filename ) )
  50. sys.exit( 1 )
  51. def processCommandLineArgs():
  52. # Setup argument processor
  53. pArgs = argparse.ArgumentParser(
  54. usage="%(prog)s [options] <file1>...",
  55. description="Generates .h file state tables and pointer indices from KLL .kll files.",
  56. epilog="Example: {0} mykeyboard.kll -d colemak.kll -p hhkbpro2.kll -p symbols.kll".format( os.path.basename( sys.argv[0] ) ),
  57. formatter_class=argparse.RawTextHelpFormatter,
  58. add_help=False,
  59. )
  60. # Positional Arguments
  61. pArgs.add_argument( 'files', type=str, nargs='+',
  62. help=argparse.SUPPRESS ) # Suppressed help output, because Python output is verbosely ugly
  63. # Optional Arguments
  64. pArgs.add_argument( '-b', '--backend', type=str, default="kiibohd",
  65. help="Specify target backend for the KLL compiler.\n"
  66. "Default: kiibohd\n"
  67. "Options: kiibohd" )
  68. pArgs.add_argument( '-d', '--default', type=str, nargs='+',
  69. help="Specify .kll files to layer on top of the default map to create a combined map." )
  70. pArgs.add_argument( '-p', '--partial', type=str, nargs='+', action='append',
  71. help="Specify .kll files to generate partial map, multiple files per flag.\n"
  72. "Each -p defines another partial map.\n"
  73. "Base .kll files (that define the scan code maps) must be defined for each partial map." )
  74. pArgs.add_argument( '-t', '--templates', type=str, nargs='+',
  75. help="Specify template used to generate the keymap.\n"
  76. "Default: <backend specific>" )
  77. pArgs.add_argument( '-o', '--outputs', type=str, nargs='+',
  78. help="Specify output file. Writes to current working directory by default.\n"
  79. "Default: <backend specific>" )
  80. pArgs.add_argument( '-h', '--help', action="help",
  81. help="This message." )
  82. # Process Arguments
  83. args = pArgs.parse_args()
  84. # Parameters
  85. baseFiles = args.files
  86. defaultFiles = args.default
  87. partialFileSets = args.partial
  88. if defaultFiles is None:
  89. defaultFiles = []
  90. if partialFileSets is None:
  91. partialFileSets = [[]]
  92. # Check file existance
  93. for filename in baseFiles:
  94. checkFileExists( filename )
  95. for filename in defaultFiles:
  96. checkFileExists( filename )
  97. for partial in partialFileSets:
  98. for filename in partial:
  99. checkFileExists( filename )
  100. return (baseFiles, defaultFiles, partialFileSets, args.backend, args.templates, args.outputs)
  101. ### Tokenizer ###
  102. def tokenize( string ):
  103. """str -> Sequence(Token)"""
  104. # Basic Tokens Spec
  105. specs = [
  106. ( 'Comment', ( r' *#.*', ) ),
  107. ( 'Space', ( r'[ \t\r\n]+', ) ),
  108. ( 'USBCode', ( r'U(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ),
  109. ( 'USBCodeStart', ( r'U\[', ) ),
  110. ( 'ConsCode', ( r'CONS(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ),
  111. ( 'ConsCodeStart', ( r'CONS\[', ) ),
  112. ( 'SysCode', ( r'SYS(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ),
  113. ( 'SysCodeStart', ( r'SYS\[', ) ),
  114. ( 'ScanCode', ( r'S((0x[0-9a-fA-F]+)|([0-9]+))', ) ),
  115. ( 'ScanCodeStart', ( r'S\[', ) ),
  116. ( 'Indicator', ( r'I(("[^"]+")|(0x[0-9a-fA-F]+)|([0-9]+))', ) ),
  117. ( 'IndicatorStart', ( r'I\[', ) ),
  118. ( 'Pixel', ( r'P"[^"]+"', ) ),
  119. ( 'PixelStart', ( r'P\[', ) ),
  120. ( 'PixelLayer', ( r'PL"[^"]+"', ) ),
  121. ( 'PixelLayerStart', ( r'PL\[', ) ),
  122. ( 'Animation', ( r'A"[^"]+"', ) ),
  123. ( 'AnimationStart', ( r'A\[', ) ),
  124. ( 'CodeBegin', ( r'\[', ) ),
  125. ( 'CodeEnd', ( r'\]', ) ),
  126. ( 'Position', ( r'r?[xyz]:[0-9]+(.[0-9]+)?', ) ),
  127. ( 'String', ( r'"[^"]*"', ) ),
  128. ( 'SequenceString', ( r"'[^']*'", ) ),
  129. ( 'PixelOperator', ( r'(\+:|-:|>>|<<)', ) ),
  130. ( 'Operator', ( r'=>|<=|:\+|:-|::|:|=', ) ),
  131. ( 'Comma', ( r',', ) ),
  132. ( 'Dash', ( r'-', ) ),
  133. ( 'Plus', ( r'\+', ) ),
  134. ( 'Parenthesis', ( r'\(|\)', ) ),
  135. ( 'None', ( r'None', ) ),
  136. ( 'Number', ( r'-?(0x[0-9a-fA-F]+)|(0|([1-9][0-9]*))', VERBOSE ) ),
  137. ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ),
  138. ( 'VariableContents', ( r'''[^"' ;:=>()]+''', ) ),
  139. ( 'EndOfLine', ( r';', ) ),
  140. ]
  141. # Tokens to filter out of the token stream
  142. useless = ['Space', 'Comment']
  143. tokens = make_tokenizer( specs )
  144. return [x for x in tokens( string ) if x.type not in useless]
  145. ### Parsing ###
  146. ## Map Arrays
  147. macros_map = Macros()
  148. variables_dict = Variables()
  149. capabilities_dict = Capabilities()
  150. ## Parsing Functions
  151. class Make:
  152. def scanCode( token ):
  153. scanCode = int( token[1:], 0 )
  154. # Check size, to make sure it's valid
  155. # XXX Add better check that takes symbolic names into account (i.e. U"Latch5")
  156. #if scanCode > 0xFF:
  157. # print ( "{0} ScanCode value {1} is larger than 255".format( ERROR, scanCode ) )
  158. # raise
  159. return scanCode
  160. def hidCode( type, token ):
  161. # If first character is a U, strip
  162. if token[0] == "U":
  163. token = token[1:]
  164. # CONS specifier
  165. elif 'CONS' in token:
  166. token = token[4:]
  167. # SYS specifier
  168. elif 'SYS' in token:
  169. token = token[3:]
  170. # If using string representation of USB Code, do lookup, case-insensitive
  171. if '"' in token:
  172. try:
  173. hidCode = kll_hid_lookup_dictionary[ type ][ token[1:-1].upper() ][1]
  174. except LookupError as err:
  175. print ( "{0} {1} is an invalid USB HID Code Lookup...".format( ERROR, err ) )
  176. raise
  177. else:
  178. # Already tokenized
  179. if type == 'USBCode' and token[0] == 'USB' or type == 'SysCode' and token[0] == 'SYS' or type == 'ConsCode' and token[0] == 'CONS':
  180. hidCode = token[1]
  181. # Convert
  182. else:
  183. hidCode = int( token, 0 )
  184. # Check size if a USB Code, to make sure it's valid
  185. # XXX Add better check that takes symbolic names into account (i.e. U"Latch5")
  186. #if type == 'USBCode' and hidCode > 0xFF:
  187. # print ( "{0} USBCode value {1} is larger than 255".format( ERROR, hidCode ) )
  188. # raise
  189. # Return a tuple, identifying which type it is
  190. if type == 'USBCode':
  191. return Make.usbCode_number( hidCode )
  192. elif type == 'ConsCode':
  193. return Make.consCode_number( hidCode )
  194. elif type == 'SysCode':
  195. return Make.sysCode_number( hidCode )
  196. print ( "{0} Unknown HID Specifier '{1}'".format( ERROR, type ) )
  197. raise
  198. def usbCode( token ):
  199. return Make.hidCode( 'USBCode', token )
  200. def consCode( token ):
  201. return Make.hidCode( 'ConsCode', token )
  202. def sysCode( token ):
  203. return Make.hidCode( 'SysCode', token )
  204. def animation( token ):
  205. # TODO
  206. print( token )
  207. return "NULL"
  208. def animationCapability( token ):
  209. # TODO
  210. print( token )
  211. return "DIS"
  212. def pixelCapability( token ):
  213. # TODO
  214. print( token )
  215. return "DAT"
  216. def pixel( token ):
  217. # TODO
  218. print( token )
  219. return "PNULL"
  220. def pixelLayer( token ):
  221. # TODO
  222. print( token )
  223. return "PLNULL"
  224. def pixelchans( token ):
  225. # Create dictionary list
  226. channel_widths = []
  227. for elem in token:
  228. channel_widths.append( {
  229. 'chan' : elem[0],
  230. 'width' : elem[1],
  231. } )
  232. print(channel_widths)
  233. return channel_widths
  234. def pixelchan_elem( token ):
  235. channel_config = {
  236. 'pixels' : token[0],
  237. 'chans' : token[1],
  238. }
  239. return channel_config
  240. def pixelmods( token ):
  241. # TODO
  242. print( token )
  243. return "PMOD"
  244. def pixellayer( token ):
  245. # TODO
  246. print( token )
  247. return "PL"
  248. def position( token ):
  249. return token.split(':')
  250. def hidCode_number( type, token ):
  251. lookup = {
  252. 'ConsCode' : 'CONS',
  253. 'SysCode' : 'SYS',
  254. 'USBCode' : 'USB',
  255. }
  256. return ( lookup[ type ], token )
  257. def usbCode_number( token ):
  258. return Make.hidCode_number( 'USBCode', token )
  259. def consCode_number( token ):
  260. return Make.hidCode_number( 'ConsCode', token )
  261. def sysCode_number( token ):
  262. return Make.hidCode_number( 'SysCode', token )
  263. # Replace key-word with None specifier (which indicates a noneOut capability)
  264. def none( token ):
  265. return [[[('NONE', 0)]]]
  266. def seqString( token ):
  267. # Shifted Characters, and amount to move by to get non-shifted version
  268. # US ANSI
  269. shiftCharacters = (
  270. ( "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x20 ),
  271. ( "+", 0x12 ),
  272. ( "&(", 0x11 ),
  273. ( "!#$%", 0x10 ),
  274. ( "*", 0x0E ),
  275. ( ")", 0x07 ),
  276. ( '"', 0x05 ),
  277. ( ":", 0x01 ),
  278. ( "@", -0x0E ),
  279. ( "<>?", -0x10 ),
  280. ( "~", -0x1E ),
  281. ( "{}|", -0x20 ),
  282. ( "^", -0x28 ),
  283. ( "_", -0x32 ),
  284. )
  285. listOfLists = []
  286. shiftKey = kll_hid_lookup_dictionary['USBCode']["SHIFT"]
  287. # Creates a list of USB codes from the string: sequence (list) of combos (lists)
  288. for char in token[1:-1]:
  289. processedChar = char
  290. # Whether or not to create a combo for this sequence with a shift
  291. shiftCombo = False
  292. # Depending on the ASCII character, convert to single character or Shift + character
  293. for pair in shiftCharacters:
  294. if char in pair[0]:
  295. shiftCombo = True
  296. processedChar = chr( ord( char ) + pair[1] )
  297. break
  298. # Do KLL HID Lookup on non-shifted character
  299. # NOTE: Case-insensitive, which is why the shift must be pre-computed
  300. usbCode = kll_hid_lookup_dictionary['USBCode'][ processedChar.upper() ]
  301. # Create Combo for this character, add shift key if shifted
  302. charCombo = []
  303. if shiftCombo:
  304. charCombo = [ [ shiftKey ] ]
  305. charCombo.append( [ usbCode ] )
  306. # Add to list of lists
  307. listOfLists.append( charCombo )
  308. return listOfLists
  309. def string( token ):
  310. return token[1:-1]
  311. def unseqString( token ):
  312. return token[1:-1]
  313. def number( token ):
  314. return int( token, 0 )
  315. # Range can go from high to low or low to high
  316. def scanCode_range( rangeVals ):
  317. start = rangeVals[0]
  318. end = rangeVals[1]
  319. # Swap start, end if start is greater than end
  320. if start > end:
  321. start, end = end, start
  322. # Iterate from start to end, and generate the range
  323. return list( range( start, end + 1 ) )
  324. # Range can go from high to low or low to high
  325. # Warn on 0-9 for USBCodes (as this does not do what one would expect) TODO
  326. # Lookup USB HID tags and convert to a number
  327. def hidCode_range( type, rangeVals ):
  328. # Check if already integers
  329. if isinstance( rangeVals[0], int ):
  330. start = rangeVals[0]
  331. else:
  332. start = Make.hidCode( type, rangeVals[0] )[1]
  333. if isinstance( rangeVals[1], int ):
  334. end = rangeVals[1]
  335. else:
  336. end = Make.hidCode( type, rangeVals[1] )[1]
  337. # Swap start, end if start is greater than end
  338. if start > end:
  339. start, end = end, start
  340. # Iterate from start to end, and generate the range
  341. listRange = list( range( start, end + 1 ) )
  342. # Convert each item in the list to a tuple
  343. for item in range( len( listRange ) ):
  344. listRange[ item ] = Make.hidCode_number( type, listRange[ item ] )
  345. return listRange
  346. def usbCode_range( rangeVals ):
  347. return Make.hidCode_range( 'USBCode', rangeVals )
  348. def sysCode_range( rangeVals ):
  349. return Make.hidCode_range( 'SysCode', rangeVals )
  350. def consCode_range( rangeVals ):
  351. return Make.hidCode_range( 'ConsCode', rangeVals )
  352. def range( rangeVals ):
  353. # TODO
  354. print (rangeVals)
  355. return ""
  356. ## Base Rules
  357. const = lambda x: lambda _: x
  358. unarg = lambda f: lambda x: f(*x)
  359. flatten = lambda list: sum( list, [] )
  360. tokenValue = lambda x: x.value
  361. tokenType = lambda t: some( lambda x: x.type == t ) >> tokenValue
  362. operator = lambda s: a( Token( 'Operator', s ) ) >> tokenValue
  363. parenthesis = lambda s: a( Token( 'Parenthesis', s ) ) >> tokenValue
  364. bracket = lambda s: a( Token( 'Bracket', s ) ) >> tokenValue
  365. eol = a( Token( 'EndOfLine', ';' ) )
  366. def listElem( item ):
  367. return [ item ]
  368. def listToTuple( items ):
  369. return tuple( items )
  370. # Flatten only the top layer (list of lists of ...)
  371. def oneLayerFlatten( items ):
  372. mainList = []
  373. for sublist in items:
  374. for item in sublist:
  375. mainList.append( item )
  376. return mainList
  377. # Capability arguments may need to be expanded (e.g. 1 16 bit argument needs to be 2 8 bit arguments for the state machine)
  378. def capArgExpander( items ):
  379. newArgs = []
  380. # For each defined argument in the capability definition
  381. for arg in range( 0, len( capabilities_dict[ items[0] ][1] ) ):
  382. argLen = capabilities_dict[ items[0] ][1][ arg ][1]
  383. num = items[1][ arg ]
  384. byteForm = num.to_bytes( argLen, byteorder='little' ) # XXX Yes, little endian from how the uC structs work
  385. # For each sub-argument, split into byte-sized chunks
  386. for byte in range( 0, argLen ):
  387. newArgs.append( byteForm[ byte ] )
  388. return tuple( [ items[0], tuple( newArgs ) ] )
  389. # Expand ranges of values in the 3rd dimension of the list, to a list of 2nd lists
  390. # i.e. [ sequence, [ combo, [ range ] ] ] --> [ [ sequence, [ combo ] ], <option 2>, <option 3> ]
  391. def optionExpansion( sequences ):
  392. expandedSequences = []
  393. # Total number of combinations of the sequence of combos that needs to be generated
  394. totalCombinations = 1
  395. # List of leaf lists, with number of leaves
  396. maxLeafList = []
  397. # Traverse to the leaf nodes, and count the items in each leaf list
  398. for sequence in sequences:
  399. for combo in sequence:
  400. rangeLen = len( combo )
  401. totalCombinations *= rangeLen
  402. maxLeafList.append( rangeLen )
  403. # Counter list to keep track of which combination is being generated
  404. curLeafList = [0] * len( maxLeafList )
  405. # Generate a list of permuations of the sequence of combos
  406. for count in range( 0, totalCombinations ):
  407. expandedSequences.append( [] ) # Prepare list for adding the new combination
  408. position = 0
  409. # Traverse sequence of combos to generate permuation
  410. for sequence in sequences:
  411. expandedSequences[ -1 ].append( [] )
  412. for combo in sequence:
  413. expandedSequences[ -1 ][ -1 ].append( combo[ curLeafList[ position ] ] )
  414. position += 1
  415. # Increment combination tracker
  416. for leaf in range( 0, len( curLeafList ) ):
  417. curLeafList[ leaf ] += 1
  418. # Reset this position, increment next position (if it exists), then stop
  419. if curLeafList[ leaf ] >= maxLeafList[ leaf ]:
  420. curLeafList[ leaf ] = 0
  421. if leaf + 1 < len( curLeafList ):
  422. curLeafList[ leaf + 1 ] += 1
  423. return expandedSequences
  424. # Converts USB Codes into Capabilities
  425. # These are tuples (<type>, <integer>)
  426. def hidCodeToCapability( items ):
  427. # Items already converted to variants using optionExpansion
  428. for variant in range( 0, len( items ) ):
  429. # Sequence of Combos
  430. for sequence in range( 0, len( items[ variant ] ) ):
  431. for combo in range( 0, len( items[ variant ][ sequence ] ) ):
  432. if items[ variant ][ sequence ][ combo ][0] in backend.requiredCapabilities.keys():
  433. try:
  434. # Use backend capability name and a single argument
  435. items[ variant ][ sequence ][ combo ] = tuple(
  436. [ backend.capabilityLookup( items[ variant ][ sequence ][ combo ][0] ),
  437. tuple( [ hid_lookup_dictionary[ items[ variant ][ sequence ][ combo ] ] ] ) ]
  438. )
  439. except KeyError:
  440. print ( "{0} {1} is an invalid HID lookup value".format( ERROR, items[ variant ][ sequence ][ combo ] ) )
  441. sys.exit( 1 )
  442. return items
  443. # Convert tuple of tuples to list of lists
  444. def listit( t ):
  445. return list( map( listit, t ) ) if isinstance( t, ( list, tuple ) ) else t
  446. # Convert list of lists to tuple of tuples
  447. def tupleit( t ):
  448. return tuple( map( tupleit, t ) ) if isinstance( t, ( tuple, list ) ) else t
  449. ## Evaluation Rules
  450. class Eval:
  451. def scanCode( triggers, operator, results ):
  452. # Convert to lists of lists of lists to tuples of tuples of tuples
  453. # Tuples are non-mutable, and can be used has index items
  454. triggers = tuple( tuple( tuple( sequence ) for sequence in variant ) for variant in triggers )
  455. results = tuple( tuple( tuple( sequence ) for sequence in variant ) for variant in results )
  456. # Lookup interconnect id (Current file scope)
  457. # Default to 0 if not specified
  458. if 'ConnectId' not in variables_dict.overallVariables.keys():
  459. id_num = 0
  460. else:
  461. id_num = int( variables_dict.overallVariables['ConnectId'] )
  462. # Iterate over all combinations of triggers and results
  463. for sequence in triggers:
  464. # Convert tuple of tuples to list of lists so each element can be modified
  465. trigger = listit( sequence )
  466. # Create ScanCode entries for trigger
  467. for seq_index, combo in enumerate( sequence ):
  468. for com_index, scancode in enumerate( combo ):
  469. trigger[ seq_index ][ com_index ] = macros_map.scanCodeStore.append( ScanCode( scancode, id_num ) )
  470. # Convert back to a tuple of tuples
  471. trigger = tupleit( trigger )
  472. for result in results:
  473. # Append Case
  474. if operator == ":+":
  475. macros_map.appendScanCode( trigger, result )
  476. # Remove Case
  477. elif operator == ":-":
  478. macros_map.removeScanCode( trigger, result )
  479. # Replace Case
  480. # Soft Replace Case is the same for Scan Codes
  481. elif operator == ":" or operator == "::":
  482. macros_map.replaceScanCode( trigger, result )
  483. def usbCode( triggers, operator, results ):
  484. # Convert to lists of lists of lists to tuples of tuples of tuples
  485. # Tuples are non-mutable, and can be used has index items
  486. triggers = tuple( tuple( tuple( sequence ) for sequence in variant ) for variant in triggers )
  487. results = tuple( tuple( tuple( sequence ) for sequence in variant ) for variant in results )
  488. # Iterate over all combinations of triggers and results
  489. for trigger in triggers:
  490. scanCodes = macros_map.lookupUSBCodes( trigger )
  491. for scanCode in scanCodes:
  492. for result in results:
  493. # Soft Replace needs additional checking to see if replacement is necessary
  494. if operator == "::" and not macros_map.softReplaceCheck( scanCode ):
  495. continue
  496. # Cache assignment until file finishes processing
  497. macros_map.cacheAssignment( operator, scanCode, result )
  498. def variable( name, content ):
  499. # Content might be a concatenation of multiple data types, convert everything into a single string
  500. assigned_content = ""
  501. for item in content:
  502. assigned_content += str( item )
  503. variables_dict.assignVariable( name, assigned_content )
  504. def capability( name, function, args ):
  505. capabilities_dict[ name ] = [ function, args ]
  506. def define( name, cdefine_name ):
  507. variables_dict.defines[ name ] = cdefine_name
  508. def scanCodePosition( scanCode, positions ):
  509. print (scanCode)
  510. # Re-organize position lists into a dictionary first
  511. pos_dict = dict()
  512. for pos in positions:
  513. pos_dict[ pos[0] ] = pos[1]
  514. print (pos_dict)
  515. # TODO Create datastructure for positions
  516. def pixelPosition( pixel, position ):
  517. # TODO
  518. print (pixel, position)
  519. def pixelChannels( channel, scanCode ):
  520. # TODO
  521. print (channel, scanCode )
  522. def animation( animation, modifiers ):
  523. # TODO
  524. print( animation, modifiers )
  525. def animationFrame( animation, frame, modifiers ):
  526. # TODO
  527. print( frame, modifiers )
  528. def animationTrigger( animation, operator, results ):
  529. # TODO
  530. print ( animation, operator, results )
  531. def animationTriggerFrame( animation, frame, operator, results ):
  532. # TODO
  533. print ( animation, frame, operator, results )
  534. def array( name, index, content ):
  535. # TODO
  536. print (name, index, content)
  537. class Map:
  538. scanCode = unarg( Eval.scanCode )
  539. usbCode = unarg( Eval.usbCode )
  540. animationTrigger = unarg( Eval.animationTrigger )
  541. animationTriggerFrame = unarg( Eval.animationTriggerFrame )
  542. class Set:
  543. animation = unarg( Eval.animation )
  544. animationFrame = unarg( Eval.animationFrame )
  545. array = unarg( Eval.array )
  546. capability = unarg( Eval.capability )
  547. define = unarg( Eval.define )
  548. pixelChannels = unarg( Eval.pixelChannels )
  549. pixelPosition = unarg( Eval.pixelPosition )
  550. scanCodePosition = unarg( Eval.scanCodePosition )
  551. variable = unarg( Eval.variable )
  552. ## Sub Rules
  553. usbCode = tokenType('USBCode') >> Make.usbCode
  554. scanCode = tokenType('ScanCode') >> Make.scanCode
  555. consCode = tokenType('ConsCode') >> Make.consCode
  556. sysCode = tokenType('SysCode') >> Make.sysCode
  557. animation = tokenType('Animation') >> Make.animation
  558. pixel = tokenType('Pixel') >> Make.pixel
  559. pixelLayer = tokenType('PixelLayer') >> Make.pixelLayer
  560. none = tokenType('None') >> Make.none
  561. position = tokenType('Position') >> Make.position
  562. name = tokenType('Name')
  563. number = tokenType('Number') >> Make.number
  564. comma = tokenType('Comma')
  565. dash = tokenType('Dash')
  566. plus = tokenType('Plus')
  567. content = tokenType('VariableContents')
  568. string = tokenType('String') >> Make.string
  569. unString = tokenType('String') # When the double quotes are still needed for internal processing
  570. seqString = tokenType('SequenceString') >> Make.seqString
  571. unseqString = tokenType('SequenceString') >> Make.unseqString # For use with variables
  572. pixelOperator = tokenType('PixelOperator')
  573. # Code variants
  574. code_begin = tokenType('CodeBegin')
  575. code_end = tokenType('CodeEnd')
  576. # Scan Codes
  577. scanCode_start = tokenType('ScanCodeStart')
  578. scanCode_range = number + skip( dash ) + number >> Make.scanCode_range
  579. scanCode_listElem = number >> listElem
  580. scanCode_innerList = oneplus( ( scanCode_range | scanCode_listElem ) + skip( maybe( comma ) ) ) >> flatten
  581. scanCode_expanded = skip( scanCode_start ) + scanCode_innerList + skip( code_end )
  582. scanCode_elem = scanCode >> listElem
  583. scanCode_combo = oneplus( ( scanCode_expanded | scanCode_elem ) + skip( maybe( plus ) ) )
  584. scanCode_sequence = oneplus( scanCode_combo + skip( maybe( comma ) ) )
  585. # USB Codes
  586. usbCode_start = tokenType('USBCodeStart')
  587. usbCode_number = number >> Make.usbCode_number
  588. usbCode_range = ( usbCode_number | unString ) + skip( dash ) + ( number | unString ) >> Make.usbCode_range
  589. usbCode_listElemTag = unString >> Make.usbCode
  590. usbCode_listElem = ( usbCode_number | usbCode_listElemTag ) >> listElem
  591. usbCode_innerList = oneplus( ( usbCode_range | usbCode_listElem ) + skip( maybe( comma ) ) ) >> flatten
  592. usbCode_expanded = skip( usbCode_start ) + usbCode_innerList + skip( code_end )
  593. usbCode_elem = usbCode >> listElem
  594. usbCode_combo = oneplus( ( usbCode_expanded | usbCode_elem ) + skip( maybe( plus ) ) ) >> listElem
  595. usbCode_sequence = oneplus( ( usbCode_combo | seqString ) + skip( maybe( comma ) ) ) >> oneLayerFlatten
  596. # Cons Codes
  597. consCode_start = tokenType('ConsCodeStart')
  598. consCode_number = number >> Make.consCode_number
  599. consCode_range = ( consCode_number | unString ) + skip( dash ) + ( number | unString ) >> Make.consCode_range
  600. consCode_listElemTag = unString >> Make.consCode
  601. consCode_listElem = ( consCode_number | consCode_listElemTag ) >> listElem
  602. consCode_innerList = oneplus( ( consCode_range | consCode_listElem ) + skip( maybe( comma ) ) ) >> flatten
  603. consCode_expanded = skip( consCode_start ) + consCode_innerList + skip( code_end )
  604. consCode_elem = consCode >> listElem
  605. # Sys Codes
  606. sysCode_start = tokenType('SysCodeStart')
  607. sysCode_number = number >> Make.sysCode_number
  608. sysCode_range = ( sysCode_number | unString ) + skip( dash ) + ( number | unString ) >> Make.sysCode_range
  609. sysCode_listElemTag = unString >> Make.sysCode
  610. sysCode_listElem = ( sysCode_number | sysCode_listElemTag ) >> listElem
  611. sysCode_innerList = oneplus( ( sysCode_range | sysCode_listElem ) + skip( maybe( comma ) ) ) >> flatten
  612. sysCode_expanded = skip( sysCode_start ) + sysCode_innerList + skip( code_end )
  613. sysCode_elem = sysCode >> listElem
  614. # HID Codes
  615. hidCode_elem = usbCode_expanded | usbCode_elem | sysCode_expanded | sysCode_elem | consCode_expanded | consCode_elem
  616. # Pixels
  617. pixel_start = tokenType('PixelStart')
  618. pixel_number = number
  619. pixel_range = ( pixel_number ) + skip( dash ) + ( number ) >> Make.range
  620. pixel_listElem = pixel_number >> listElem
  621. pixel_innerList = many( ( pixel_range | pixel_listElem ) + skip( maybe( comma ) ) )
  622. pixel_expanded = skip( pixel_start ) + pixel_innerList + skip( code_end )
  623. pixel_elem = pixel >> listElem
  624. # Pixel Layer
  625. pixellayer_start = tokenType('PixelLayerStart')
  626. pixellayer_number = number
  627. pixellayer_expanded = skip( pixellayer_start ) + pixellayer_number + skip( code_end )
  628. pixellayer_elem = pixelLayer >> listElem
  629. # Pixel Channels
  630. pixelchan_chans = many( number + skip( operator(':') ) + number + skip( maybe( comma ) ) ) >> Make.pixelchans
  631. pixelchan_elem = ( pixel_expanded | pixel_elem ) + skip( parenthesis('(') ) + pixelchan_chans + skip( parenthesis(')') ) >> Make.pixelchan_elem
  632. # Pixel Mods
  633. pixelmod_mods = many( maybe( pixelOperator | plus | dash ) + number + skip( maybe( comma ) ) ) >> Make.pixelmods
  634. pixelmod_layer = ( pixellayer_expanded | pixellayer_elem ) >> Make.pixellayer
  635. pixelmod_elem = ( pixel_expanded | pixel_elem | pixelmod_layer ) + skip( parenthesis('(') ) + pixelmod_mods + skip( parenthesis(')') )
  636. # Pixel Capability
  637. pixel_capability = pixelmod_elem >> Make.pixelCapability
  638. # Animations
  639. animation_start = tokenType('AnimationStart')
  640. animation_name = name
  641. animation_frame_range = ( number ) + skip( dash ) + ( number ) >> Make.range
  642. animation_name_frame = many( ( number | animation_frame_range ) + skip( maybe( comma ) ) )
  643. animation_def = skip( animation_start ) + animation_name + skip( code_end )
  644. animation_expanded = skip( animation_start ) + animation_name + skip( comma ) + animation_name_frame + skip( code_end )
  645. animation_elem = animation >> listElem
  646. # Animation Modifier
  647. animation_modifier = many( ( name | number ) + maybe( skip( operator(':') ) + number ) + skip( maybe( comma ) ) )
  648. # Animation Capability
  649. animation_capability = ( animation_def | animation_elem ) + maybe( skip( parenthesis('(') + animation_modifier + skip( parenthesis(')') ) ) ) >> Make.animationCapability
  650. # Capabilities
  651. capFunc_arguments = many( number + skip( maybe( comma ) ) ) >> listToTuple
  652. capFunc_elem = name + skip( parenthesis('(') ) + capFunc_arguments + skip( parenthesis(')') ) >> capArgExpander >> listElem
  653. capFunc_combo = oneplus( ( hidCode_elem | capFunc_elem | animation_capability | pixel_capability ) + skip( maybe( plus ) ) ) >> listElem
  654. capFunc_sequence = oneplus( ( capFunc_combo | seqString ) + skip( maybe( comma ) ) ) >> oneLayerFlatten
  655. # Trigger / Result Codes
  656. triggerCode_outerList = scanCode_sequence >> optionExpansion
  657. triggerUSBCode_outerList = usbCode_sequence >> optionExpansion >> hidCodeToCapability
  658. resultCode_outerList = ( ( capFunc_sequence >> optionExpansion ) | none ) >> hidCodeToCapability
  659. # Positions
  660. position_list = oneplus( position + skip( maybe( comma ) ) )
  661. ## Main Rules
  662. #| Assignment
  663. #| <variable> = <variable contents>;
  664. variable_contents = name | content | string | number | comma | dash | unseqString
  665. variable_expression = name + skip( operator('=') ) + oneplus( variable_contents ) + skip( eol ) >> Set.variable
  666. #| Array Assignment
  667. #| <variable>[] = <space> <separated> <list>;
  668. #| <variable>[<index>] = <index element>;
  669. array_expression = name + skip( code_begin ) + maybe( number ) + skip( code_end ) + skip( operator('=') ) + oneplus( variable_contents ) + skip( eol ) >> Set.array
  670. #| Name Association
  671. #| <capability name> => <c function>;
  672. capability_arguments = name + skip( operator(':') ) + number + skip( maybe( comma ) )
  673. capability_expression = name + skip( operator('=>') ) + name + skip( parenthesis('(') ) + many( capability_arguments ) + skip( parenthesis(')') ) + skip( eol ) >> Set.capability
  674. #| Name Association
  675. #| <define name> => <c define>;
  676. define_expression = name + skip( operator('=>') ) + name + skip( eol ) >> Set.define
  677. #| Data Association
  678. #| <animation> <= <modifiers>;
  679. #| <animation frame> <= <modifiers>;
  680. animation_expression = ( animation_elem | animation_def ) + skip( operator('<=') ) + animation_modifier + skip( eol ) >> Set.animation
  681. animationFrame_expression = animation_expanded + skip( operator('<=') ) + many( pixelmod_elem + skip( maybe( comma ) ) ) + skip( eol ) >> Set.animationFrame
  682. #| Data Association
  683. #| <pixel> <= <position>;
  684. pixelPosition_expression = ( pixel_expanded | pixel_elem ) + skip( operator('<=') ) + position_list + skip( eol ) >> Set.pixelPosition
  685. #| Data Association
  686. #| <pixel chan> <= <scanCode>;
  687. pixelChan_expression = pixelchan_elem + skip( operator(':') ) + ( scanCode_expanded | scanCode_elem | none ) + skip( eol ) >> Set.pixelChannels
  688. #| Data Association
  689. #| <scancode> <= <position>;
  690. scanCodePosition_expression = triggerCode_outerList + skip( operator('<=') ) + position_list + skip( eol ) >> Set.scanCodePosition
  691. #| Mapping
  692. #| <trigger> : <result>;
  693. operatorTriggerResult = operator(':') | operator(':+') | operator(':-') | operator('::')
  694. scanCode_expression = triggerCode_outerList + operatorTriggerResult + resultCode_outerList + skip( eol ) >> Map.scanCode
  695. usbCode_expression = triggerUSBCode_outerList + operatorTriggerResult + resultCode_outerList + skip( eol ) >> Map.usbCode
  696. animation_trigger = ( animation_elem | animation_def ) + operatorTriggerResult + resultCode_outerList + skip( eol ) >> Map.animationTrigger
  697. animation_triggerFrame = animation_expanded + operatorTriggerResult + resultCode_outerList + skip( eol ) >> Map.animationTriggerFrame
  698. def parse( tokenSequence ):
  699. """Sequence(Token) -> object"""
  700. # Top-level Parser
  701. expression = (
  702. scanCode_expression |
  703. scanCodePosition_expression |
  704. usbCode_expression |
  705. animation_trigger |
  706. animation_triggerFrame |
  707. pixelPosition_expression |
  708. pixelChan_expression |
  709. animation_expression |
  710. animationFrame_expression |
  711. array_expression |
  712. variable_expression |
  713. capability_expression |
  714. define_expression
  715. )
  716. kll_text = many( expression )
  717. kll_file = maybe( kll_text ) + skip( finished )
  718. return kll_file.parse( tokenSequence )
  719. def processKLLFile( filename ):
  720. with open( filename ) as file:
  721. data = file.read()
  722. try:
  723. tokenSequence = tokenize( data )
  724. except LexerError as err:
  725. print ( "{0} Tokenization error in '{1}' - {2}".format( ERROR, filename, err ) )
  726. sys.exit( 1 )
  727. #print ( pformat( tokenSequence ) ) # Display tokenization
  728. try:
  729. tree = parse( tokenSequence )
  730. except (NoParseError, KeyError) as err:
  731. print ( "{0} Parsing error in '{1}' - {2}".format( ERROR, filename, err ) )
  732. sys.exit( 1 )
  733. ### Misc Utility Functions ###
  734. def gitRevision( kllPath ):
  735. import subprocess
  736. # Change the path to where kll.py is
  737. origPath = os.getcwd()
  738. os.chdir( kllPath )
  739. # Just in case git can't be found
  740. try:
  741. # Get hash of the latest git commit
  742. revision = subprocess.check_output( ['git', 'rev-parse', 'HEAD'] ).decode()[:-1]
  743. # Get list of files that have changed since the commit
  744. changed = subprocess.check_output( ['git', 'diff-index', '--name-only', 'HEAD', '--'] ).decode().splitlines()
  745. except:
  746. revision = "<no git>"
  747. changed = []
  748. # Change back to the old working directory
  749. os.chdir( origPath )
  750. return revision, changed
  751. ### Main Entry Point ###
  752. if __name__ == '__main__':
  753. (baseFiles, defaultFiles, partialFileSets, backend_name, templates, outputs) = processCommandLineArgs()
  754. # Look up git information on the compiler
  755. gitRev, gitChanges = gitRevision( os.path.dirname( os.path.realpath( __file__ ) ) )
  756. # Load backend module
  757. global backend
  758. backend_import = importlib.import_module( "backends.{0}".format( backend_name ) )
  759. backend = backend_import.Backend( templates )
  760. # Process base layout files
  761. for filename in baseFiles:
  762. variables_dict.setCurrentFile( filename )
  763. processKLLFile( filename )
  764. macros_map.completeBaseLayout() # Indicates to macros_map that the base layout is complete
  765. variables_dict.baseLayoutFinished()
  766. # Default combined layer
  767. for filename in defaultFiles:
  768. variables_dict.setCurrentFile( filename )
  769. processKLLFile( filename )
  770. # Apply assignment cache, see 5.1.2 USB Codes for why this is necessary
  771. macros_map.replayCachedAssignments()
  772. # Iterate through additional layers
  773. for partial in partialFileSets:
  774. # Increment layer for each -p option
  775. macros_map.addLayer()
  776. variables_dict.incrementLayer() # DefaultLayer is layer 0
  777. # Iterate and process each of the file in the layer
  778. for filename in partial:
  779. variables_dict.setCurrentFile( filename )
  780. processKLLFile( filename )
  781. # Apply assignment cache, see 5.1.2 USB Codes for why this is necessary
  782. macros_map.replayCachedAssignments()
  783. # Remove un-marked keys to complete the partial layer
  784. macros_map.removeUnmarked()
  785. # Do macro correlation and transformation
  786. macros_map.generate()
  787. # Process needed templating variables using backend
  788. backend.process(
  789. capabilities_dict,
  790. macros_map,
  791. variables_dict,
  792. gitRev,
  793. gitChanges
  794. )
  795. # Generate output file using template and backend
  796. backend.generate( outputs )
  797. # Successful Execution
  798. sys.exit( 0 )