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.

organization.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. #!/usr/bin/env python3
  2. '''
  3. KLL Data Organization
  4. '''
  5. # Copyright (C) 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 copy
  21. import re
  22. ### Decorators ###
  23. ## Print Decorator Variables
  24. ERROR = '\033[5;1;31mERROR\033[0m:'
  25. WARNING = '\033[5;1;33mWARNING\033[0m:'
  26. ansi_escape = re.compile(r'\x1b[^m]*m')
  27. ### Classes ###
  28. class Data:
  29. '''
  30. Base class for KLL datastructures
  31. '''
  32. # Debug output formatters
  33. debug_output = {
  34. 'add' : "\t\033[1;42;37m++\033[0m\033[1mADD KEY\033[1;42;37m++\033[0m \033[1m<==\033[0m {0}",
  35. 'app' : "\t\033[1;45;37m**\033[0m\033[1mAPP KEY\033[1;45;37m**\033[0m \033[1m<==\033[0m {0}",
  36. 'mod' : "\t\033[1;44;37m##\033[0m\033[1mMOD KEY\033[1;44;37m##\033[0m \033[1m<==\033[0m {0}",
  37. 'rem' : "\t\033[1;41;37m--\033[0m\033[1mREM KEY\033[1;41;37m--\033[0m \033[1m<==\033[0m {0}",
  38. 'drp' : "\t\033[1;43;37m@@\033[0m\033[1mDRP KEY\033[1;43;37m@@\033[0m \033[1m<==\033[0m {0}",
  39. 'dup' : "\t\033[1;46;37m!!\033[0m\033[1mDUP KEY\033[1;46;37m!!\033[0m \033[1m<==\033[0m {0}",
  40. }
  41. def __init__( self, parent ):
  42. '''
  43. Initialize datastructure
  44. @param parent: Parent organization, used to query data from other datastructures
  45. '''
  46. self.data = {}
  47. self.parent = parent
  48. def add_expression( self, expression, debug ):
  49. '''
  50. Add expression to data structure
  51. May have multiple keys to add for a given expression
  52. @param expression: KLL Expression (fully tokenized and parsed)
  53. @param debug: Enable debug output
  54. '''
  55. # Lookup unique keys for expression
  56. keys = expression.unique_keys()
  57. # Add/Modify expressions in datastructure
  58. for key, uniq_expr in keys:
  59. # Check which operation we are trying to do, add or modify
  60. if debug[0]:
  61. if key in self.data.keys():
  62. output = self.debug_output['mod'].format( key )
  63. else:
  64. output = self.debug_output['add'].format( key )
  65. print( debug[1] and output or ansi_escape.sub( '', output ) )
  66. self.data[ key ] = uniq_expr
  67. def merge( self, merge_in, debug ):
  68. '''
  69. Merge in the given datastructure to this datastructure
  70. This datastructure serves as the base.
  71. @param merge_in: Data structure from another organization to merge into this one
  72. @param debug: Enable debug out
  73. '''
  74. # The default case is just to add the expression in directly
  75. for key, kll_expression in merge_in.data.items():
  76. # Display key:expression being merged in
  77. if debug[0]:
  78. output = merge_in.elem_str( key, True )
  79. print( debug[1] and output or ansi_escape.sub( '', output ), end="" )
  80. self.add_expression( kll_expression, debug )
  81. def reduction( self ):
  82. '''
  83. Simplifies datastructure
  84. Most of the datastructures don't have a reduction. Just do nothing in this case.
  85. '''
  86. pass
  87. def elem_str( self, key, single=False ):
  88. '''
  89. Debug output for a single element
  90. @param key: Index to datastructure
  91. @param single: Setting to True will bold the key
  92. '''
  93. if single:
  94. return "\033[1;33m{0: <20}\033[0m \033[1;36;41m>\033[0m {1}\n".format( key, self.data[ key ] )
  95. else:
  96. return "{0: <20} \033[1;36;41m>\033[0m {1}\n".format( key, self.data[ key ] )
  97. def __repr__( self ):
  98. output = ""
  99. # Display sorted list of keys, along with the internal value
  100. for key in sorted( self.data ):
  101. output += self.elem_str( key )
  102. return output
  103. class MappingData( Data ):
  104. '''
  105. KLL datastructure for data mapping
  106. ScanCode trigger -> result
  107. USBCode trigger -> result
  108. Animation trigger -> result
  109. '''
  110. def add_expression( self, expression, debug ):
  111. '''
  112. Add expression to data structure
  113. May have multiple keys to add for a given expression
  114. Map expressions insert into the datastructure according to their operator.
  115. +Operators+
  116. : Add/Modify
  117. :+ Append
  118. :- Remove
  119. :: Lazy Add/Modify
  120. i: Add/Modify
  121. i:+ Append
  122. i:- Remove
  123. i:: Lazy Add/Modify
  124. The i or isolation operators are stored separately from the main ones.
  125. Each key is pre-pended with an i
  126. The :: or lazy operators act just like : operators, except that they will be ignore if the evaluation
  127. merge cannot resolve a ScanCode.
  128. @param expression: KLL Expression (fully tokenized and parsed)
  129. @param debug: Enable debug output
  130. '''
  131. # Lookup unique keys for expression
  132. keys = expression.unique_keys()
  133. # Add/Modify expressions in datastructure
  134. for key, uniq_expr in keys:
  135. # Determine which the expression operator
  136. operator = expression.operator
  137. # Except for the : operator, all others have delayed action
  138. # Meaning, they change behaviour depending on how Contexts are merged
  139. # This means we can't simplify yet
  140. # In addition, :+ and :- are stackable, which means each key has a list of expressions
  141. # We append the operator to differentiate between the different types of delayed operations
  142. key = "{0}{1}".format( operator, key )
  143. # Determine if key exists already
  144. exists = key in self.data.keys()
  145. # Add/Modify
  146. if operator in [':', '::', 'i:', 'i::']:
  147. debug_tag = exists and 'mod' or 'add'
  148. # Append/Remove
  149. else:
  150. # Check to make sure we haven't already appended expression
  151. # Use the string representation to do the comparison (general purpose)
  152. if exists and "{0}".format( uniq_expr ) in [ "{0}".format( elem ) for elem in self.data[ key ] ]:
  153. debug_tag = 'dup'
  154. # Append
  155. elif operator in [':+', 'i:+']:
  156. debug_tag = 'app'
  157. # Remove
  158. else:
  159. debug_tag = 'rem'
  160. # Debug output
  161. if debug[0]:
  162. output = self.debug_output[ debug_tag ].format( key )
  163. print( debug[1] and output or ansi_escape.sub( '', output ) )
  164. # Don't append if a duplicate
  165. if debug_tag == 'dup':
  166. continue
  167. # Append, rather than replace
  168. if operator in [':+', ':-', 'i:+', 'i:-']:
  169. if exists:
  170. self.data[ key ].append( uniq_expr )
  171. # Create initial list
  172. else:
  173. self.data[ key ] = [ uniq_expr ]
  174. else:
  175. self.data[ key ] = [ uniq_expr ]
  176. def set_interconnect_id( self, interconnect_id, triggers ):
  177. '''
  178. Traverses the sequence of combo of identifiers to set the interconnect_id
  179. '''
  180. for sequence in triggers:
  181. for combo in sequence:
  182. for identifier in combo:
  183. identifier.interconnect_id = interconnect_id
  184. def merge( self, merge_in, debug ):
  185. '''
  186. Merge in the given datastructure to this datastructure
  187. This datastructure serves as the base.
  188. Map expressions merge differently than insertions.
  189. +Operators+
  190. : Add/Modify - Replace
  191. :+ Append - Add
  192. :- Remove - Remove
  193. :: Lazy Add/Modify - Replace if found, otherwise drop
  194. i: Add/Modify - Replace
  195. i:+ Append - Add
  196. i:- Remove - Remove
  197. i:: Lazy Add/Modify - Replace if found, otherwise drop
  198. @param merge_in: Data structure from another organization to merge into this one
  199. @param debug: Enable debug out
  200. '''
  201. # Check what the current interconnectId is
  202. # If not set, we set to 0 (default)
  203. # We use this to calculate the scancode during the DataAnalysisStage
  204. interconnect_id = 0
  205. if 'interconnectId' in self.parent.variable_data.data.keys():
  206. interconnect_id = self.parent.variable_data.data['interconnectId']
  207. # Sort different types of keys
  208. cur_keys = merge_in.data.keys()
  209. # Lazy Set ::
  210. lazy_keys = [ key for key in cur_keys if key[0:2] == '::' or key[0:3] == 'i::' ]
  211. cur_keys = list( set( cur_keys ) - set( lazy_keys ) )
  212. # Append :+
  213. append_keys = [ key for key in cur_keys if key[0:2] == ':+' or key[0:3] == 'i:+' ]
  214. cur_keys = list( set( cur_keys ) - set( append_keys ) )
  215. # Remove :-
  216. remove_keys = [ key for key in cur_keys if key[0:2] == ':-' or key[0:3] == 'i:-' ]
  217. cur_keys = list( set( cur_keys ) - set( remove_keys ) )
  218. # Set :
  219. # Everything left is just a set
  220. set_keys = cur_keys
  221. # First process the :: (or lazy) operators
  222. # We need to read into this datastructure and apply those first
  223. # Otherwise we may get undesired behaviour
  224. for key in lazy_keys:
  225. # Display key:expression being merged in
  226. if debug[0]:
  227. output = merge_in.elem_str( key, True )
  228. print( debug[1] and output or ansi_escape.sub( '', output ), end="" )
  229. # Construct target key
  230. target_key = key[0] == 'i' and "i{0}".format( key[2:] ) or key[1:]
  231. # If target key exists, replace
  232. if target_key in self.data.keys():
  233. debug_tag = 'mod'
  234. else:
  235. debug_tag = 'drp'
  236. # Debug output
  237. if debug[0]:
  238. output = self.debug_output[ debug_tag ].format( key )
  239. print( debug[1] and output or ansi_escape.sub( '', output ) )
  240. # Only replace
  241. if debug_tag == 'mod':
  242. self.data[ target_key ] = merge_in.data[ key ]
  243. # Then apply : assignment operators
  244. for key in set_keys:
  245. # Display key:expression being merged in
  246. if debug[0]:
  247. output = merge_in.elem_str( key, True )
  248. print( debug[1] and output or ansi_escape.sub( '', output ), end="" )
  249. # Construct target key
  250. target_key = key
  251. # Indicate if add or modify
  252. if target_key in self.data.keys():
  253. debug_tag = 'mod'
  254. else:
  255. debug_tag = 'add'
  256. # Debug output
  257. if debug[0]:
  258. output = self.debug_output[ debug_tag ].format( key )
  259. print( debug[1] and output or ansi_escape.sub( '', output ) )
  260. # Set into new datastructure regardless
  261. self.data[ target_key ] = merge_in.data[ key ]
  262. # Only the : is used to set ScanCodes
  263. # We need to set the interconnect_id just in case the base context has it set
  264. # and in turn influence the new context as well
  265. # This must be done during the merge
  266. for elem in self.data[ target_key ]:
  267. if elem.type == 'ScanCode':
  268. self.set_interconnect_id( interconnect_id, elem.triggers )
  269. # Now apply append operations
  270. for key in append_keys:
  271. # Display key:expression being merged in
  272. if debug[0]:
  273. output = merge_in.elem_str( key, True )
  274. print( debug[1] and output or ansi_escape.sub( '', output ), end="" )
  275. # Construct target key
  276. target_key = key[0] == 'i' and "i:{0}".format( key[3:] ) or ":{0}".format( key[2:] )
  277. # Alwyays appending
  278. debug_tag = 'app'
  279. # Debug output
  280. if debug[0]:
  281. output = self.debug_output[ debug_tag ].format( key )
  282. print( debug[1] and output or ansi_escape.sub( '', output ) )
  283. # Extend list if it exists
  284. if target_key in self.data.keys():
  285. self.data[ target_key ].extend( merge_in.data[ key ] )
  286. else:
  287. self.data[ target_key ] = merge_in.data[ key ]
  288. # Finally apply removal operations to this datastructure
  289. # If the target removal doesn't exist, ignore silently (show debug message)
  290. for key in remove_keys:
  291. # Display key:expression being merged in
  292. if debug[0]:
  293. output = merge_in.elem_str( key, True )
  294. print( debug[1] and output or ansi_escape.sub( '', output ), end="" )
  295. # Construct target key
  296. target_key = key[0] == 'i' and "i:{0}".format( key[3:] ) or ":{0}".format( key[2:] )
  297. # Drop right away if target datastructure doesn't have target key
  298. if target_key not in self.data.keys():
  299. debug_tag = 'drp'
  300. # Debug output
  301. if debug[0]:
  302. output = self.debug_output[ debug_tag ].format( key )
  303. print( debug[1] and output or ansi_escape.sub( '', output ) )
  304. continue
  305. # Compare expressions to be removed with the current set
  306. # Use strings to compare
  307. remove_expressions = [ "{0}".format( expr ) for expr in merge_in.data[ key ] ]
  308. current_expressions = [ ( "{0}".format( expr ), expr ) for expr in self.data[ target_key ] ]
  309. for string, expr in current_expressions:
  310. debug_tag = 'drp'
  311. # Check if an expression matches
  312. if string in remove_expressions:
  313. debug_tag = 'rem'
  314. # Debug output
  315. if debug[0]:
  316. output = self.debug_output[ debug_tag ].format( key )
  317. print( debug[1] and output or ansi_escape.sub( '', output ) )
  318. # Remove if found
  319. if debug_tag == 'rem':
  320. self.data[ target_key ] = [ value for value in self.data.values() if value != expr ]
  321. def reduction( self ):
  322. '''
  323. Simplifies datastructure
  324. Used to replace all trigger HIDCode(USBCode)s with ScanCodes
  325. NOTE: Make sure to create a new MergeContext before calling this as you lose data and prior context
  326. '''
  327. scan_code_lookup = {}
  328. # Build dictionary of single ScanCodes first
  329. for key, expr in self.data.items():
  330. if expr[0].elems()[0] == 1 and expr[0].triggers[0][0][0].type == 'ScanCode':
  331. scan_code_lookup[ key ] = expr
  332. # Using this dictionary, replace all the trigger USB codes
  333. new_data = copy.copy( scan_code_lookup )
  334. # 1) Single USB Codes trigger results will replace the original ScanCode result
  335. # 2)
  336. #TODO
  337. #print("YAY")
  338. #print( scan_code_lookup )
  339. class AnimationData( Data ):
  340. '''
  341. KLL datastructure for Animation configuration
  342. Animation -> modifiers
  343. '''
  344. class AnimationFrameData( Data ):
  345. '''
  346. KLL datastructure for Animation Frame configuration
  347. Animation -> Pixel Settings
  348. '''
  349. class CapabilityData( Data ):
  350. '''
  351. KLL datastructure for Capability mapping
  352. Capability -> C Function/Identifier
  353. '''
  354. class DefineData( Data ):
  355. '''
  356. KLL datastructure for Define mapping
  357. Variable -> C Define/Identifier
  358. '''
  359. class PixelChannelData( Data ):
  360. '''
  361. KLL datastructure for Pixel Channel mapping
  362. Pixel -> Channels
  363. '''
  364. class PixelPositionData( Data ):
  365. '''
  366. KLL datastructure for Pixel Position mapping
  367. Pixel -> Physical Location
  368. '''
  369. class ScanCodePositionData( Data ):
  370. '''
  371. KLL datastructure for ScanCode Position mapping
  372. ScanCode -> Physical Location
  373. '''
  374. class VariableData( Data ):
  375. '''
  376. KLL datastructure for Variables and Arrays
  377. Variable -> Data
  378. Array -> Data
  379. '''
  380. class Organization:
  381. '''
  382. Container class for KLL datastructures
  383. The purpose of these datastructures is to symbolically store at first, and slowly solve/deduplicate expressions.
  384. Since the order in which the merges occurs matters, this involves a number of intermediate steps.
  385. '''
  386. def __init__( self ):
  387. '''
  388. Intialize data structure
  389. '''
  390. # Setup each of the internal sub-datastructures
  391. self.animation_data = AnimationData( self )
  392. self.animation_frame_data = AnimationFrameData( self )
  393. self.capability_data = CapabilityData( self )
  394. self.define_data = DefineData( self )
  395. self.mapping_data = MappingData( self )
  396. self.pixel_channel_data = PixelChannelData( self )
  397. self.pixel_position_data = PixelPositionData( self )
  398. self.scan_code_position_data = ScanCodePositionData( self )
  399. self.variable_data = VariableData( self )
  400. # Expression to Datastructure mapping
  401. self.data_mapping = {
  402. 'AssignmentExpression' : {
  403. 'Array' : self.variable_data,
  404. 'Variable' : self.variable_data,
  405. },
  406. 'DataAssociationExpression' : {
  407. 'Animation' : self.animation_data,
  408. 'AnimationFrame' : self.animation_frame_data,
  409. 'PixelPosition' : self.pixel_position_data,
  410. 'ScanCodePosition' : self.scan_code_position_data,
  411. },
  412. 'MapExpression' : {
  413. 'ScanCode' : self.mapping_data,
  414. 'USBCode' : self.mapping_data,
  415. 'Animation' : self.mapping_data,
  416. 'PixelChannel' : self.pixel_channel_data,
  417. },
  418. 'NameAssociationExpression' : {
  419. 'Capability' : self.capability_data,
  420. 'Define' : self.define_data,
  421. },
  422. }
  423. def stores( self ):
  424. '''
  425. Returns list of sub-datastructures
  426. '''
  427. return [
  428. self.animation_data,
  429. self.animation_frame_data,
  430. self.capability_data,
  431. self.define_data,
  432. self.mapping_data,
  433. self.pixel_channel_data,
  434. self.pixel_position_data,
  435. self.scan_code_position_data,
  436. self.variable_data,
  437. ]
  438. def add_expression( self, expression, debug ):
  439. '''
  440. Add expression to datastructure
  441. Will automatically determine which type of expression and place in the relevant store
  442. @param expression: KLL Expression (fully tokenized and parsed)
  443. @param debug: Enable debug output
  444. '''
  445. # Determine type of of Expression
  446. expression_type = expression.__class__.__name__
  447. # Determine Expression Subtype
  448. expression_subtype = expression.type
  449. # Locate datastructure
  450. data = self.data_mapping[ expression_type ][ expression_subtype ]
  451. # Debug output
  452. if debug[0]:
  453. output = "\t\033[4m{0}\033[0m".format( data.__class__.__name__ )
  454. print( debug[1] and output or ansi_escape.sub( '', output ) )
  455. # Add expression to determined datastructure
  456. data.add_expression( expression, debug )
  457. def merge( self, merge_in, debug ):
  458. '''
  459. Merge in the given organization to this organization
  460. This organization serves as the base.
  461. @param merge_in: Organization to merge into this one
  462. @param debug: Enable debug out
  463. '''
  464. # Merge each of the sub-datastructures
  465. for this, that in zip( self.stores(), merge_in.stores() ):
  466. this.merge( that, debug )
  467. def reduction( self ):
  468. '''
  469. Simplifies datastructure
  470. NOTE: This will remove data, therefore, context is lost
  471. '''
  472. for store in self.stores():
  473. store.reduction()
  474. def __repr__( self ):
  475. return "{0}".format( self.stores() )