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.

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