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 36KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076
  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. ( 'Timing', ( r'[0-9]+(.[0-9]+)?((s)|(ms)|(us))', ) ),
  137. ( 'Number', ( r'-?(0x[0-9a-fA-F]+)|(0|([1-9][0-9]*))', VERBOSE ) ),
  138. ( 'Name', ( r'[A-Za-z_][A-Za-z_0-9]*', ) ),
  139. ( 'VariableContents', ( r'''[^"' ;:=>()]+''', ) ),
  140. ( 'EndOfLine', ( r';', ) ),
  141. ]
  142. # Tokens to filter out of the token stream
  143. useless = ['Space', 'Comment']
  144. tokens = make_tokenizer( specs )
  145. return [x for x in tokens( string ) if x.type not in useless]
  146. ### Parsing ###
  147. ## Map Arrays
  148. macros_map = Macros()
  149. variables_dict = Variables()
  150. capabilities_dict = Capabilities()
  151. ## Parsing Functions
  152. class Make:
  153. def scanCode( token ):
  154. scanCode = int( token[1:], 0 )
  155. # Check size, to make sure it's valid
  156. # XXX Add better check that takes symbolic names into account (i.e. U"Latch5")
  157. #if scanCode > 0xFF:
  158. # print ( "{0} ScanCode value {1} is larger than 255".format( ERROR, scanCode ) )
  159. # raise
  160. return scanCode
  161. def hidCode( type, token ):
  162. # If first character is a U or I, strip
  163. if token[0] == "U" or token[0] == "I":
  164. token = token[1:]
  165. # CONS specifier
  166. elif 'CONS' in token:
  167. token = token[4:]
  168. # SYS specifier
  169. elif 'SYS' in token:
  170. token = token[3:]
  171. # If using string representation of USB Code, do lookup, case-insensitive
  172. if '"' in token:
  173. try:
  174. hidCode = kll_hid_lookup_dictionary[ type ][ token[1:-1].upper() ][1]
  175. except LookupError as err:
  176. print ( "{0} {1} is an invalid USB HID Code Lookup...".format( ERROR, err ) )
  177. raise
  178. else:
  179. # Already tokenized
  180. if type == 'USBCode' and token[0] == 'USB' or type == 'SysCode' and token[0] == 'SYS' or type == 'ConsCode' and token[0] == 'CONS':
  181. hidCode = token[1]
  182. # Convert
  183. else:
  184. hidCode = int( token, 0 )
  185. # Check size if a USB Code, to make sure it's valid
  186. # XXX Add better check that takes symbolic names into account (i.e. U"Latch5")
  187. #if type == 'USBCode' and hidCode > 0xFF:
  188. # print ( "{0} USBCode value {1} is larger than 255".format( ERROR, hidCode ) )
  189. # raise
  190. # Return a tuple, identifying which type it is
  191. if type == 'USBCode':
  192. return Make.usbCode_number( hidCode )
  193. elif type == 'ConsCode':
  194. return Make.consCode_number( hidCode )
  195. elif type == 'SysCode':
  196. return Make.sysCode_number( hidCode )
  197. elif type == 'IndCode':
  198. return Make.indCode_number( hidCode )
  199. print ( "{0} Unknown HID Specifier '{1}'".format( ERROR, type ) )
  200. raise
  201. def usbCode( token ):
  202. return Make.hidCode( 'USBCode', token )
  203. def consCode( token ):
  204. return Make.hidCode( 'ConsCode', token )
  205. def sysCode( token ):
  206. return Make.hidCode( 'SysCode', token )
  207. def indCode( token ):
  208. return Make.hidCode( 'IndCode', token )
  209. def animation( token ):
  210. # TODO
  211. print( token )
  212. return "NULL"
  213. def animationCapability( token ):
  214. # TODO
  215. print( token )
  216. return "DIS"
  217. def pixelCapability( token ):
  218. # TODO
  219. print( token )
  220. return "DAT"
  221. def pixel( token ):
  222. # TODO
  223. print( token )
  224. return "PNULL"
  225. def pixelLayer( token ):
  226. # TODO
  227. print( token )
  228. return "PLNULL"
  229. def pixelchans( token ):
  230. # Create dictionary list
  231. channel_widths = []
  232. for elem in token:
  233. channel_widths.append( {
  234. 'chan' : elem[0],
  235. 'width' : elem[1],
  236. } )
  237. print(channel_widths)
  238. return channel_widths
  239. def pixelchan_elem( token ):
  240. channel_config = {
  241. 'pixels' : token[0],
  242. 'chans' : token[1],
  243. }
  244. return channel_config
  245. def pixelmods( token ):
  246. # TODO
  247. print( token )
  248. return "PMOD"
  249. def pixellayer( token ):
  250. # TODO
  251. print( token )
  252. return "PL"
  253. def position( token ):
  254. return token.split(':')
  255. def hidCode_number( type, token ):
  256. lookup = {
  257. 'ConsCode' : 'CONS',
  258. 'SysCode' : 'SYS',
  259. 'USBCode' : 'USB',
  260. 'IndCode' : 'LED',
  261. }
  262. return ( lookup[ type ], token )
  263. def usbCode_number( token ):
  264. return Make.hidCode_number( 'USBCode', token )
  265. def consCode_number( token ):
  266. return Make.hidCode_number( 'ConsCode', token )
  267. def sysCode_number( token ):
  268. return Make.hidCode_number( 'SysCode', token )
  269. def indCode_number( token ):
  270. return Make.hidCode_number( 'IndCode', token )
  271. # Replace key-word with None specifier (which indicates a noneOut capability)
  272. def none( token ):
  273. return [[[('NONE', 0)]]]
  274. def seqString( token ):
  275. # Shifted Characters, and amount to move by to get non-shifted version
  276. # US ANSI
  277. shiftCharacters = (
  278. ( "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x20 ),
  279. ( "+", 0x12 ),
  280. ( "&(", 0x11 ),
  281. ( "!#$%", 0x10 ),
  282. ( "*", 0x0E ),
  283. ( ")", 0x07 ),
  284. ( '"', 0x05 ),
  285. ( ":", 0x01 ),
  286. ( "@", -0x0E ),
  287. ( "<>?", -0x10 ),
  288. ( "~", -0x1E ),
  289. ( "{}|", -0x20 ),
  290. ( "^", -0x28 ),
  291. ( "_", -0x32 ),
  292. )
  293. listOfLists = []
  294. shiftKey = kll_hid_lookup_dictionary['USBCode']["SHIFT"]
  295. # Creates a list of USB codes from the string: sequence (list) of combos (lists)
  296. for char in token[1:-1]:
  297. processedChar = char
  298. # Whether or not to create a combo for this sequence with a shift
  299. shiftCombo = False
  300. # Depending on the ASCII character, convert to single character or Shift + character
  301. for pair in shiftCharacters:
  302. if char in pair[0]:
  303. shiftCombo = True
  304. processedChar = chr( ord( char ) + pair[1] )
  305. break
  306. # Do KLL HID Lookup on non-shifted character
  307. # NOTE: Case-insensitive, which is why the shift must be pre-computed
  308. usbCode = kll_hid_lookup_dictionary['USBCode'][ processedChar.upper() ]
  309. # Create Combo for this character, add shift key if shifted
  310. charCombo = []
  311. if shiftCombo:
  312. charCombo = [ [ shiftKey ] ]
  313. charCombo.append( [ usbCode ] )
  314. # Add to list of lists
  315. listOfLists.append( charCombo )
  316. return listOfLists
  317. def string( token ):
  318. return token[1:-1]
  319. def unseqString( token ):
  320. return token[1:-1]
  321. def number( token ):
  322. return int( token, 0 )
  323. def timing( token ):
  324. # Find ms, us, or s
  325. if 'ms' in token:
  326. unit = 'ms'
  327. num = token.split('m')[0]
  328. print (token.split('m'))
  329. elif 'us' in token:
  330. unit = 'us'
  331. num = token.split('u')[0]
  332. elif 's' in token:
  333. unit = 's'
  334. num = token.split('s')[0]
  335. else:
  336. print ( "{0} cannot find timing unit in token '{1}'".format( ERROR, token ) )
  337. return "NULL"
  338. print ( num, unit )
  339. ret = {
  340. 'time' : float( num ),
  341. 'unit' : unit,
  342. }
  343. return ret
  344. def specifierState( values ):
  345. # TODO
  346. print ( values )
  347. return "SPECSTATE"
  348. def specifierAnalog( value ):
  349. # TODO
  350. print( value )
  351. return "SPECANALOG"
  352. def specifierUnroll( value ):
  353. # TODO
  354. print( value )
  355. return [ value[0] ]
  356. # Range can go from high to low or low to high
  357. def scanCode_range( rangeVals ):
  358. start = rangeVals[0]
  359. end = rangeVals[1]
  360. # Swap start, end if start is greater than end
  361. if start > end:
  362. start, end = end, start
  363. # Iterate from start to end, and generate the range
  364. return list( range( start, end + 1 ) )
  365. # Range can go from high to low or low to high
  366. # Warn on 0-9 for USBCodes (as this does not do what one would expect) TODO
  367. # Lookup USB HID tags and convert to a number
  368. def hidCode_range( type, rangeVals ):
  369. # Check if already integers
  370. if isinstance( rangeVals[0], int ):
  371. start = rangeVals[0]
  372. else:
  373. start = Make.hidCode( type, rangeVals[0] )[1]
  374. if isinstance( rangeVals[1], int ):
  375. end = rangeVals[1]
  376. else:
  377. end = Make.hidCode( type, rangeVals[1] )[1]
  378. # Swap start, end if start is greater than end
  379. if start > end:
  380. start, end = end, start
  381. # Iterate from start to end, and generate the range
  382. listRange = list( range( start, end + 1 ) )
  383. # Convert each item in the list to a tuple
  384. for item in range( len( listRange ) ):
  385. listRange[ item ] = Make.hidCode_number( type, listRange[ item ] )
  386. return listRange
  387. def usbCode_range( rangeVals ):
  388. return Make.hidCode_range( 'USBCode', rangeVals )
  389. def sysCode_range( rangeVals ):
  390. return Make.hidCode_range( 'SysCode', rangeVals )
  391. def consCode_range( rangeVals ):
  392. return Make.hidCode_range( 'ConsCode', rangeVals )
  393. def indCode_range( rangeVals ):
  394. return Make.hidCode_range( 'IndCode', rangeVals )
  395. def range( rangeVals ):
  396. # TODO
  397. print (rangeVals)
  398. return ""
  399. ## Base Rules
  400. const = lambda x: lambda _: x
  401. unarg = lambda f: lambda x: f(*x)
  402. flatten = lambda list: sum( list, [] )
  403. tokenValue = lambda x: x.value
  404. tokenType = lambda t: some( lambda x: x.type == t ) >> tokenValue
  405. operator = lambda s: a( Token( 'Operator', s ) ) >> tokenValue
  406. parenthesis = lambda s: a( Token( 'Parenthesis', s ) ) >> tokenValue
  407. bracket = lambda s: a( Token( 'Bracket', s ) ) >> tokenValue
  408. eol = a( Token( 'EndOfLine', ';' ) )
  409. def listElem( item ):
  410. return [ item ]
  411. def listToTuple( items ):
  412. return tuple( items )
  413. # Flatten only the top layer (list of lists of ...)
  414. def oneLayerFlatten( items ):
  415. mainList = []
  416. for sublist in items:
  417. for item in sublist:
  418. mainList.append( item )
  419. return mainList
  420. # Capability arguments may need to be expanded (e.g. 1 16 bit argument needs to be 2 8 bit arguments for the state machine)
  421. def capArgExpander( items ):
  422. newArgs = []
  423. # For each defined argument in the capability definition
  424. for arg in range( 0, len( capabilities_dict[ items[0] ][1] ) ):
  425. argLen = capabilities_dict[ items[0] ][1][ arg ][1]
  426. num = items[1][ arg ]
  427. byteForm = num.to_bytes( argLen, byteorder='little' ) # XXX Yes, little endian from how the uC structs work
  428. # For each sub-argument, split into byte-sized chunks
  429. for byte in range( 0, argLen ):
  430. newArgs.append( byteForm[ byte ] )
  431. return tuple( [ items[0], tuple( newArgs ) ] )
  432. # Expand ranges of values in the 3rd dimension of the list, to a list of 2nd lists
  433. # i.e. [ sequence, [ combo, [ range ] ] ] --> [ [ sequence, [ combo ] ], <option 2>, <option 3> ]
  434. def optionExpansion( sequences ):
  435. expandedSequences = []
  436. # Total number of combinations of the sequence of combos that needs to be generated
  437. totalCombinations = 1
  438. # List of leaf lists, with number of leaves
  439. maxLeafList = []
  440. # Traverse to the leaf nodes, and count the items in each leaf list
  441. for sequence in sequences:
  442. for combo in sequence:
  443. rangeLen = len( combo )
  444. totalCombinations *= rangeLen
  445. maxLeafList.append( rangeLen )
  446. # Counter list to keep track of which combination is being generated
  447. curLeafList = [0] * len( maxLeafList )
  448. # Generate a list of permuations of the sequence of combos
  449. for count in range( 0, totalCombinations ):
  450. expandedSequences.append( [] ) # Prepare list for adding the new combination
  451. position = 0
  452. # Traverse sequence of combos to generate permuation
  453. for sequence in sequences:
  454. expandedSequences[ -1 ].append( [] )
  455. for combo in sequence:
  456. expandedSequences[ -1 ][ -1 ].append( combo[ curLeafList[ position ] ] )
  457. position += 1
  458. # Increment combination tracker
  459. for leaf in range( 0, len( curLeafList ) ):
  460. curLeafList[ leaf ] += 1
  461. # Reset this position, increment next position (if it exists), then stop
  462. if curLeafList[ leaf ] >= maxLeafList[ leaf ]:
  463. curLeafList[ leaf ] = 0
  464. if leaf + 1 < len( curLeafList ):
  465. curLeafList[ leaf + 1 ] += 1
  466. return expandedSequences
  467. # Converts USB Codes into Capabilities
  468. # These are tuples (<type>, <integer>)
  469. def hidCodeToCapability( items ):
  470. # Items already converted to variants using optionExpansion
  471. for variant in range( 0, len( items ) ):
  472. # Sequence of Combos
  473. for sequence in range( 0, len( items[ variant ] ) ):
  474. for combo in range( 0, len( items[ variant ][ sequence ] ) ):
  475. if items[ variant ][ sequence ][ combo ][0] in backend.requiredCapabilities.keys():
  476. try:
  477. # Use backend capability name and a single argument
  478. items[ variant ][ sequence ][ combo ] = tuple(
  479. [ backend.capabilityLookup( items[ variant ][ sequence ][ combo ][0] ),
  480. tuple( [ hid_lookup_dictionary[ items[ variant ][ sequence ][ combo ] ] ] ) ]
  481. )
  482. except KeyError:
  483. print ( "{0} {1} is an invalid HID lookup value".format( ERROR, items[ variant ][ sequence ][ combo ] ) )
  484. sys.exit( 1 )
  485. return items
  486. # Convert tuple of tuples to list of lists
  487. def listit( t ):
  488. return list( map( listit, t ) ) if isinstance( t, ( list, tuple ) ) else t
  489. # Convert list of lists to tuple of tuples
  490. def tupleit( t ):
  491. return tuple( map( tupleit, t ) ) if isinstance( t, ( tuple, list ) ) else t
  492. ## Evaluation Rules
  493. class Eval:
  494. def scanCode( triggers, operator, results ):
  495. print ( triggers, operator, results )
  496. # Convert to lists of lists of lists to tuples of tuples of tuples
  497. # Tuples are non-mutable, and can be used has index items
  498. triggers = tuple( tuple( tuple( sequence ) for sequence in variant ) for variant in triggers )
  499. results = tuple( tuple( tuple( sequence ) for sequence in variant ) for variant in results )
  500. # Lookup interconnect id (Current file scope)
  501. # Default to 0 if not specified
  502. if 'ConnectId' not in variables_dict.overallVariables.keys():
  503. id_num = 0
  504. else:
  505. id_num = int( variables_dict.overallVariables['ConnectId'] )
  506. # Iterate over all combinations of triggers and results
  507. for sequence in triggers:
  508. # Convert tuple of tuples to list of lists so each element can be modified
  509. trigger = listit( sequence )
  510. # Create ScanCode entries for trigger
  511. for seq_index, combo in enumerate( sequence ):
  512. for com_index, scancode in enumerate( combo ):
  513. trigger[ seq_index ][ com_index ] = macros_map.scanCodeStore.append( ScanCode( scancode, id_num ) )
  514. # Convert back to a tuple of tuples
  515. trigger = tupleit( trigger )
  516. for result in results:
  517. # Append Case
  518. if operator == ":+":
  519. macros_map.appendScanCode( trigger, result )
  520. # Remove Case
  521. elif operator == ":-":
  522. macros_map.removeScanCode( trigger, result )
  523. # Replace Case
  524. # Soft Replace Case is the same for Scan Codes
  525. elif operator == ":" or operator == "::":
  526. macros_map.replaceScanCode( trigger, result )
  527. def usbCode( triggers, operator, results ):
  528. # TODO
  529. return
  530. # Convert to lists of lists of lists to tuples of tuples of tuples
  531. # Tuples are non-mutable, and can be used has index items
  532. triggers = tuple( tuple( tuple( sequence ) for sequence in variant ) for variant in triggers )
  533. results = tuple( tuple( tuple( sequence ) for sequence in variant ) for variant in results )
  534. # Iterate over all combinations of triggers and results
  535. for trigger in triggers:
  536. scanCodes = macros_map.lookupUSBCodes( trigger )
  537. for scanCode in scanCodes:
  538. for result in results:
  539. # Soft Replace needs additional checking to see if replacement is necessary
  540. if operator == "::" and not macros_map.softReplaceCheck( scanCode ):
  541. continue
  542. # Cache assignment until file finishes processing
  543. macros_map.cacheAssignment( operator, scanCode, result )
  544. def variable( name, content ):
  545. # Content might be a concatenation of multiple data types, convert everything into a single string
  546. assigned_content = ""
  547. for item in content:
  548. assigned_content += str( item )
  549. variables_dict.assignVariable( name, assigned_content )
  550. def capability( name, function, args ):
  551. capabilities_dict[ name ] = [ function, args ]
  552. def define( name, cdefine_name ):
  553. variables_dict.defines[ name ] = cdefine_name
  554. def scanCodePosition( scanCode, positions ):
  555. print (scanCode)
  556. # Re-organize position lists into a dictionary first
  557. pos_dict = dict()
  558. for pos in positions:
  559. pos_dict[ pos[0] ] = pos[1]
  560. print (pos_dict)
  561. # TODO Create datastructure for positions
  562. def pixelPosition( pixel, position ):
  563. # TODO
  564. print (pixel, position)
  565. def pixelChannels( channel, scanCode ):
  566. # TODO
  567. print (channel, scanCode )
  568. def animation( animation, modifiers ):
  569. # TODO
  570. print( animation, modifiers )
  571. def animationFrame( animation, frame, modifiers ):
  572. # TODO
  573. print( frame, modifiers )
  574. def animationTrigger( animation, operator, results ):
  575. # TODO
  576. print ( animation, operator, results )
  577. def animationTriggerFrame( animation, frame, operator, results ):
  578. # TODO
  579. print ( animation, frame, operator, results )
  580. def array( name, index, content ):
  581. # TODO
  582. print (name, index, content)
  583. class Map:
  584. scanCode = unarg( Eval.scanCode )
  585. usbCode = unarg( Eval.usbCode )
  586. animationTrigger = unarg( Eval.animationTrigger )
  587. animationTriggerFrame = unarg( Eval.animationTriggerFrame )
  588. class Set:
  589. animation = unarg( Eval.animation )
  590. animationFrame = unarg( Eval.animationFrame )
  591. array = unarg( Eval.array )
  592. capability = unarg( Eval.capability )
  593. define = unarg( Eval.define )
  594. pixelChannels = unarg( Eval.pixelChannels )
  595. pixelPosition = unarg( Eval.pixelPosition )
  596. scanCodePosition = unarg( Eval.scanCodePosition )
  597. variable = unarg( Eval.variable )
  598. ## Sub Rules
  599. usbCode = tokenType('USBCode') >> Make.usbCode
  600. scanCode = tokenType('ScanCode') >> Make.scanCode
  601. consCode = tokenType('ConsCode') >> Make.consCode
  602. sysCode = tokenType('SysCode') >> Make.sysCode
  603. indCode = tokenType('Indicator') >> Make.indCode
  604. animation = tokenType('Animation') >> Make.animation
  605. pixel = tokenType('Pixel') >> Make.pixel
  606. pixelLayer = tokenType('PixelLayer') >> Make.pixelLayer
  607. none = tokenType('None') >> Make.none
  608. position = tokenType('Position') >> Make.position
  609. name = tokenType('Name')
  610. number = tokenType('Number') >> Make.number
  611. timing = tokenType('Timing') >> Make.timing
  612. comma = tokenType('Comma')
  613. dash = tokenType('Dash')
  614. plus = tokenType('Plus')
  615. content = tokenType('VariableContents')
  616. string = tokenType('String') >> Make.string
  617. unString = tokenType('String') # When the double quotes are still needed for internal processing
  618. seqString = tokenType('SequenceString') >> Make.seqString
  619. unseqString = tokenType('SequenceString') >> Make.unseqString # For use with variables
  620. pixelOperator = tokenType('PixelOperator')
  621. # Code variants
  622. code_begin = tokenType('CodeBegin')
  623. code_end = tokenType('CodeEnd')
  624. # Specifier
  625. specifier_state = ( name + skip( operator(':') ) + timing ) | ( name + skip( operator(':') ) + timing ) | timing | name >> Make.specifierState
  626. specifier_analog = number >> Make.specifierAnalog
  627. specifier_list = skip( parenthesis('(') ) + many( ( specifier_state | specifier_analog ) + skip( maybe( comma ) ) ) + skip( parenthesis(')') )
  628. # Scan Codes
  629. scanCode_start = tokenType('ScanCodeStart')
  630. scanCode_range = number + skip( dash ) + number >> Make.scanCode_range
  631. scanCode_listElem = number >> listElem
  632. scanCode_specifier = ( scanCode_range | scanCode_listElem ) + maybe( specifier_list ) >> Make.specifierUnroll >> flatten
  633. scanCode_innerList = many( scanCode_specifier + skip( maybe( comma ) ) ) >> flatten
  634. scanCode_expanded = skip( scanCode_start ) + scanCode_innerList + skip( code_end )
  635. scanCode_elem = scanCode + maybe( specifier_list ) >> Make.specifierUnroll >> listElem
  636. scanCode_combo = oneplus( ( scanCode_expanded | scanCode_elem ) + skip( maybe( plus ) ) )
  637. scanCode_sequence = oneplus( scanCode_combo + skip( maybe( comma ) ) )
  638. # Cons Codes
  639. consCode_start = tokenType('ConsCodeStart')
  640. consCode_number = number >> Make.consCode_number
  641. consCode_range = ( consCode_number | unString ) + skip( dash ) + ( number | unString ) >> Make.consCode_range
  642. consCode_listElemTag = unString >> Make.consCode
  643. consCode_listElem = ( consCode_number | consCode_listElemTag ) >> listElem
  644. consCode_specifier = ( consCode_range | consCode_listElem ) + maybe( specifier_list ) >> Make.specifierUnroll >> flatten
  645. consCode_innerList = oneplus( consCode_specifier + skip( maybe( comma ) ) ) >> flatten
  646. consCode_expanded = skip( consCode_start ) + consCode_innerList + skip( code_end )
  647. consCode_elem = consCode + maybe( specifier_list ) >> Make.specifierUnroll >> listElem
  648. # Sys Codes
  649. sysCode_start = tokenType('SysCodeStart')
  650. sysCode_number = number >> Make.sysCode_number
  651. sysCode_range = ( sysCode_number | unString ) + skip( dash ) + ( number | unString ) >> Make.sysCode_range
  652. sysCode_listElemTag = unString >> Make.sysCode
  653. sysCode_listElem = ( sysCode_number | sysCode_listElemTag ) >> listElem
  654. sysCode_specifier = ( sysCode_range | sysCode_listElem ) + maybe( specifier_list ) >> Make.specifierUnroll >> flatten
  655. sysCode_innerList = oneplus( sysCode_specifier + skip( maybe( comma ) ) ) >> flatten
  656. sysCode_expanded = skip( sysCode_start ) + sysCode_innerList + skip( code_end )
  657. sysCode_elem = sysCode + maybe( specifier_list ) >> Make.specifierUnroll >> listElem
  658. # Indicator Codes
  659. indCode_start = tokenType('IndicatorStart')
  660. indCode_number = number >> Make.indCode_number
  661. indCode_range = ( indCode_number | unString ) + skip( dash ) + ( number | unString ) >> Make.indCode_range
  662. indCode_listElemTag = unString >> Make.indCode
  663. indCode_listElem = ( indCode_number | indCode_listElemTag ) >> listElem
  664. indCode_specifier = ( indCode_range | indCode_listElem ) + maybe( specifier_list ) >> Make.specifierUnroll >> flatten
  665. indCode_innerList = oneplus( indCode_specifier + skip( maybe( comma ) ) ) >> flatten
  666. indCode_expanded = skip( indCode_start ) + indCode_innerList + skip( code_end )
  667. indCode_elem = indCode + maybe( specifier_list ) >> Make.specifierUnroll >> listElem
  668. # USB Codes
  669. usbCode_start = tokenType('USBCodeStart')
  670. usbCode_number = number >> Make.usbCode_number
  671. usbCode_range = ( usbCode_number | unString ) + skip( dash ) + ( number | unString ) >> Make.usbCode_range
  672. usbCode_listElemTag = unString >> Make.usbCode
  673. usbCode_listElem = ( usbCode_number | usbCode_listElemTag ) >> listElem
  674. usbCode_specifier = ( usbCode_range | usbCode_listElem ) + maybe( specifier_list ) >> Make.specifierUnroll >> flatten
  675. usbCode_innerList = oneplus( usbCode_specifier + skip( maybe( comma ) ) ) >> flatten
  676. usbCode_expanded = skip( usbCode_start ) + usbCode_innerList + skip( code_end )
  677. usbCode_elem = usbCode + maybe( specifier_list ) >> Make.specifierUnroll >> listElem
  678. # HID Codes
  679. hidCode_elem = usbCode_expanded | usbCode_elem | sysCode_expanded | sysCode_elem | consCode_expanded | consCode_elem | indCode_expanded | indCode_elem
  680. usbCode_combo = oneplus( hidCode_elem + skip( maybe( plus ) ) ) >> listElem
  681. usbCode_sequence = oneplus( ( usbCode_combo | seqString ) + skip( maybe( comma ) ) ) >> oneLayerFlatten
  682. # Pixels
  683. pixel_start = tokenType('PixelStart')
  684. pixel_number = number
  685. pixel_range = ( pixel_number ) + skip( dash ) + ( number ) >> Make.range
  686. pixel_listElem = pixel_number >> listElem
  687. pixel_innerList = many( ( pixel_range | pixel_listElem ) + skip( maybe( comma ) ) )
  688. pixel_expanded = skip( pixel_start ) + pixel_innerList + skip( code_end )
  689. pixel_elem = pixel >> listElem
  690. # Pixel Layer
  691. pixellayer_start = tokenType('PixelLayerStart')
  692. pixellayer_number = number
  693. pixellayer_expanded = skip( pixellayer_start ) + pixellayer_number + skip( code_end )
  694. pixellayer_elem = pixelLayer >> listElem
  695. # Pixel Channels
  696. pixelchan_chans = many( number + skip( operator(':') ) + number + skip( maybe( comma ) ) ) >> Make.pixelchans
  697. pixelchan_elem = ( pixel_expanded | pixel_elem ) + skip( parenthesis('(') ) + pixelchan_chans + skip( parenthesis(')') ) >> Make.pixelchan_elem
  698. # Pixel Mods
  699. pixelmod_mods = many( maybe( pixelOperator | plus | dash ) + number + skip( maybe( comma ) ) ) >> Make.pixelmods
  700. pixelmod_layer = ( pixellayer_expanded | pixellayer_elem ) >> Make.pixellayer
  701. pixelmod_elem = ( pixel_expanded | pixel_elem | pixelmod_layer ) + skip( parenthesis('(') ) + pixelmod_mods + skip( parenthesis(')') )
  702. # Pixel Capability
  703. pixel_capability = pixelmod_elem >> Make.pixelCapability
  704. # Animations
  705. animation_start = tokenType('AnimationStart')
  706. animation_name = name
  707. animation_frame_range = ( number ) + skip( dash ) + ( number ) >> Make.range
  708. animation_name_frame = many( ( number | animation_frame_range ) + skip( maybe( comma ) ) )
  709. animation_def = skip( animation_start ) + animation_name + skip( code_end )
  710. animation_expanded = skip( animation_start ) + animation_name + skip( comma ) + animation_name_frame + skip( code_end )
  711. animation_elem = animation >> listElem
  712. # Animation Modifier
  713. animation_modifier = many( ( name | number ) + maybe( skip( operator(':') ) + number ) + skip( maybe( comma ) ) )
  714. # Animation Capability
  715. animation_capability = ( animation_def | animation_elem ) + maybe( skip( parenthesis('(') + animation_modifier + skip( parenthesis(')') ) ) ) >> Make.animationCapability
  716. # Capabilities
  717. capFunc_arguments = many( number + skip( maybe( comma ) ) ) >> listToTuple
  718. capFunc_elem = name + skip( parenthesis('(') ) + capFunc_arguments + skip( parenthesis(')') ) >> capArgExpander >> listElem
  719. capFunc_combo = oneplus( ( hidCode_elem | capFunc_elem | animation_capability | pixel_capability ) + skip( maybe( plus ) ) ) >> listElem
  720. capFunc_sequence = oneplus( ( capFunc_combo | seqString ) + skip( maybe( comma ) ) ) >> oneLayerFlatten
  721. # Trigger / Result Codes
  722. triggerCode_outerList = scanCode_sequence >> optionExpansion
  723. triggerUSBCode_outerList = usbCode_sequence >> optionExpansion >> hidCodeToCapability
  724. resultCode_outerList = ( ( capFunc_sequence >> optionExpansion ) | none ) >> hidCodeToCapability
  725. # Positions
  726. position_list = oneplus( position + skip( maybe( comma ) ) )
  727. ## Main Rules
  728. #| Assignment
  729. #| <variable> = <variable contents>;
  730. variable_contents = name | content | string | number | comma | dash | unseqString
  731. variable_expression = name + skip( operator('=') ) + oneplus( variable_contents ) + skip( eol ) >> Set.variable
  732. #| Array Assignment
  733. #| <variable>[] = <space> <separated> <list>;
  734. #| <variable>[<index>] = <index element>;
  735. array_expression = name + skip( code_begin ) + maybe( number ) + skip( code_end ) + skip( operator('=') ) + oneplus( variable_contents ) + skip( eol ) >> Set.array
  736. #| Name Association
  737. #| <capability name> => <c function>;
  738. capability_arguments = name + skip( operator(':') ) + number + skip( maybe( comma ) )
  739. capability_expression = name + skip( operator('=>') ) + name + skip( parenthesis('(') ) + many( capability_arguments ) + skip( parenthesis(')') ) + skip( eol ) >> Set.capability
  740. #| Name Association
  741. #| <define name> => <c define>;
  742. define_expression = name + skip( operator('=>') ) + name + skip( eol ) >> Set.define
  743. #| Data Association
  744. #| <animation> <= <modifiers>;
  745. #| <animation frame> <= <modifiers>;
  746. animation_expression = ( animation_elem | animation_def ) + skip( operator('<=') ) + animation_modifier + skip( eol ) >> Set.animation
  747. animationFrame_expression = animation_expanded + skip( operator('<=') ) + many( pixelmod_elem + skip( maybe( comma ) ) ) + skip( eol ) >> Set.animationFrame
  748. #| Data Association
  749. #| <pixel> <= <position>;
  750. pixelPosition_expression = ( pixel_expanded | pixel_elem ) + skip( operator('<=') ) + position_list + skip( eol ) >> Set.pixelPosition
  751. #| Data Association
  752. #| <pixel chan> <= <scanCode>;
  753. pixelChan_expression = pixelchan_elem + skip( operator(':') ) + ( scanCode_expanded | scanCode_elem | none ) + skip( eol ) >> Set.pixelChannels
  754. #| Data Association
  755. #| <scancode> <= <position>;
  756. scanCodePosition_expression = triggerCode_outerList + skip( operator('<=') ) + position_list + skip( eol ) >> Set.scanCodePosition
  757. #| Mapping
  758. #| <trigger> : <result>;
  759. operatorTriggerResult = operator(':') | operator(':+') | operator(':-') | operator('::')
  760. scanCode_expression = triggerCode_outerList + operatorTriggerResult + resultCode_outerList + skip( eol ) >> Map.scanCode
  761. usbCode_expression = triggerUSBCode_outerList + operatorTriggerResult + resultCode_outerList + skip( eol ) >> Map.usbCode
  762. animation_trigger = ( animation_elem | animation_def ) + operatorTriggerResult + resultCode_outerList + skip( eol ) >> Map.animationTrigger
  763. animation_triggerFrame = animation_expanded + operatorTriggerResult + resultCode_outerList + skip( eol ) >> Map.animationTriggerFrame
  764. def parse( tokenSequence ):
  765. """Sequence(Token) -> object"""
  766. # Top-level Parser
  767. expression = (
  768. scanCode_expression |
  769. scanCodePosition_expression |
  770. usbCode_expression |
  771. animation_trigger |
  772. animation_triggerFrame |
  773. pixelPosition_expression |
  774. pixelChan_expression |
  775. animation_expression |
  776. animationFrame_expression |
  777. array_expression |
  778. variable_expression |
  779. capability_expression |
  780. define_expression
  781. )
  782. kll_text = many( expression )
  783. kll_file = maybe( kll_text ) + skip( finished )
  784. return kll_file.parse( tokenSequence )
  785. def processKLLFile( filename ):
  786. with open( filename ) as file:
  787. data = file.read()
  788. try:
  789. tokenSequence = tokenize( data )
  790. except LexerError as err:
  791. print ( "{0} Tokenization error in '{1}' - {2}".format( ERROR, filename, err ) )
  792. sys.exit( 1 )
  793. #print ( pformat( tokenSequence ) ) # Display tokenization
  794. try:
  795. tree = parse( tokenSequence )
  796. except (NoParseError, KeyError) as err:
  797. print ( "{0} Parsing error in '{1}' - {2}".format( ERROR, filename, err ) )
  798. sys.exit( 1 )
  799. ### Misc Utility Functions ###
  800. def gitRevision( kllPath ):
  801. import subprocess
  802. # Change the path to where kll.py is
  803. origPath = os.getcwd()
  804. os.chdir( kllPath )
  805. # Just in case git can't be found
  806. try:
  807. # Get hash of the latest git commit
  808. revision = subprocess.check_output( ['git', 'rev-parse', 'HEAD'] ).decode()[:-1]
  809. # Get list of files that have changed since the commit
  810. changed = subprocess.check_output( ['git', 'diff-index', '--name-only', 'HEAD', '--'] ).decode().splitlines()
  811. except:
  812. revision = "<no git>"
  813. changed = []
  814. # Change back to the old working directory
  815. os.chdir( origPath )
  816. return revision, changed
  817. ### Main Entry Point ###
  818. if __name__ == '__main__':
  819. (baseFiles, defaultFiles, partialFileSets, backend_name, templates, outputs) = processCommandLineArgs()
  820. # Look up git information on the compiler
  821. gitRev, gitChanges = gitRevision( os.path.dirname( os.path.realpath( __file__ ) ) )
  822. # Load backend module
  823. global backend
  824. backend_import = importlib.import_module( "backends.{0}".format( backend_name ) )
  825. backend = backend_import.Backend( templates )
  826. # Process base layout files
  827. for filename in baseFiles:
  828. variables_dict.setCurrentFile( filename )
  829. processKLLFile( filename )
  830. macros_map.completeBaseLayout() # Indicates to macros_map that the base layout is complete
  831. variables_dict.baseLayoutFinished()
  832. # Default combined layer
  833. for filename in defaultFiles:
  834. variables_dict.setCurrentFile( filename )
  835. processKLLFile( filename )
  836. # Apply assignment cache, see 5.1.2 USB Codes for why this is necessary
  837. macros_map.replayCachedAssignments()
  838. # Iterate through additional layers
  839. for partial in partialFileSets:
  840. # Increment layer for each -p option
  841. macros_map.addLayer()
  842. variables_dict.incrementLayer() # DefaultLayer is layer 0
  843. # Iterate and process each of the file in the layer
  844. for filename in partial:
  845. variables_dict.setCurrentFile( filename )
  846. processKLLFile( filename )
  847. # Apply assignment cache, see 5.1.2 USB Codes for why this is necessary
  848. macros_map.replayCachedAssignments()
  849. # Remove un-marked keys to complete the partial layer
  850. macros_map.removeUnmarked()
  851. # Do macro correlation and transformation
  852. macros_map.generate()
  853. # Process needed templating variables using backend
  854. backend.process(
  855. capabilities_dict,
  856. macros_map,
  857. variables_dict,
  858. gitRev,
  859. gitChanges
  860. )
  861. # Generate output file using template and backend
  862. backend.generate( outputs )
  863. # Successful Execution
  864. sys.exit( 0 )