KLL Compiler
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
Dieses Repo ist archiviert. Du kannst Dateien sehen und es klonen, kannst aber nicht pushen oder Issues/Pull-Requests öffnen.

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