1# ************************************************************* 2# 3# Licensed to the Apache Software Foundation (ASF) under one 4# or more contributor license agreements. See the NOTICE file 5# distributed with this work for additional information 6# regarding copyright ownership. The ASF licenses this file 7# to you under the Apache License, Version 2.0 (the 8# "License"); you may not use this file except in compliance 9# with the License. You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, 14# software distributed under the License is distributed on an 15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16# KIND, either express or implied. See the License for the 17# specific language governing permissions and limitations 18# under the License. 19# 20# ************************************************************* 21 22import sys 23from globals import * 24import srclexer 25 26# simple name translation map 27postTransMap = {"ok-button": "okbutton", 28 "cancel-button": "cancelbutton", 29 "help-button": "helpbutton"} 30 31def transName (name): 32 """Translate a mixed-casing name to dash-separated name. 33 34Translate a mixed-casing name (e.g. MyLongName) to a dash-separated name 35(e.g. my-long-name). 36""" 37 def isUpper (c): 38 return c >= 'A' and c <= 'Z' 39 40 newname = '' 41 parts = [] 42 buf = '' 43 for c in name: 44 if isUpper(c) and len(buf) > 1: 45 parts.append(buf) 46 buf = c 47 else: 48 buf += c 49 50 if len(buf) > 0: 51 parts.append(buf) 52 53 first = True 54 for part in parts: 55 if first: 56 first = False 57 else: 58 newname += '-' 59 newname += part.lower() 60 61 # special-case mapping ... 62 if 0: #postTransMap.has_key(newname): 63 newname = postTransMap[newname] 64 65 return newname 66 67 68def transValue (value): 69 """Translate certain values. 70 71Examples of translated values include TRUE -> true, FALSE -> false. 72""" 73 if value.lower() in ["true", "false"]: 74 value = value.lower() 75 return value 76 77 78def renameAttribute (name, elemName): 79 80 # TODO: all manner of evil special cases ... 81 if elemName == 'metric-field' and name == 'spin-size': 82 return 'step-size' 83 84 return name 85 86 87class Statement(object): 88 """Container to hold information for a single statement. 89 90Each statement consists of the left-hand-side token(s), and right-hand-side 91tokens, separated by a '=' token. This class stores the information on the 92left-hand-side tokens. 93""" 94 def __init__ (self): 95 self.leftTokens = [] 96 self.leftScope = None 97 98 99class MacroExpander(object): 100 def __init__ (self, tokens, defines): 101 self.tokens = tokens 102 self.defines = defines 103 104 def expand (self): 105 self.pos = 0 106 while self.pos < len(self.tokens): 107 self.expandToken() 108 109 def expandToken (self): 110 token = self.tokens[self.pos] 111 if token not in self.defines: 112 self.pos += 1 113 return 114 115 macro = self.defines[token] 116 nvars = len(list(macro.vars.keys())) 117 if nvars == 0: 118 # Simple expansion 119 self.tokens[self.pos:self.pos+1] = macro.tokens 120 return 121 else: 122 # Expansion with arguments. 123 values, lastPos = self.parseValues() 124 newtokens = [] 125 for mtoken in macro.tokens: 126 if mtoken in macro.vars: 127 # variable 128 pos = macro.vars[mtoken] 129 valtokens = values[pos] 130 for valtoken in valtokens: 131 newtokens.append(valtoken) 132 else: 133 # not a variable 134 newtokens.append(mtoken) 135 136 self.tokens[self.pos:self.pos+lastPos+1] = newtokens 137 138 139 def parseValues (self): 140 """Parse tokens to get macro function variable values. 141 142Be aware that there is an implicit quotes around the text between the open 143paren, the comma(s), and the close paren. For instance, if a macro is defined 144as FOO(a, b) and is used as FOO(one two three, and four), then the 'a' must be 145replaced with 'one two three', and the 'b' replaced with 'and four'. In other 146words, whitespace does not end a token. 147 148""" 149 values = [] 150 i = 1 151 scope = 0 152 value = [] 153 while True: 154 try: 155 tk = self.tokens[self.pos+i] 156 except IndexError: 157 progress ("error parsing values (%d)\n"%i) 158 for j in range(0, i): 159 print(self.tokens[self.pos+j], end=' ') 160 print('') 161 srclexer.dumpTokens(self.tokens) 162 srclexer.dumpTokens(self.newtokens) 163 print("tokens expanded so far:") 164 for tk in self.expandedTokens: 165 print("-"*20) 166 print(tk) 167 srclexer.dumpTokens(self.defines[tk].tokens) 168 sys.exit(1) 169 if tk == '(': 170 value = [] 171 scope += 1 172 elif tk == ',': 173 values.append(value) 174 value = [] 175 elif tk == ')': 176 scope -= 1 177 values.append(value) 178 value = [] 179 if scope == 0: 180 break 181 else: 182 raise ParseError ('') 183 else: 184 value.append(tk) 185 i += 1 186 187 return values, i 188 189 def getTokens (self): 190 return self.tokens 191 192 193class SrcParser(object): 194 195 def __init__ (self, tokens, defines = None): 196 self.tokens = tokens 197 self.defines = defines 198 self.debug = False 199 self.onlyExpandMacros = False 200 201 def init (self): 202 self.elementStack = [RootNode()] 203 self.stmtData = Statement() 204 self.tokenBuf = [] 205 self.leftTokens = [] 206 207 # Expand defined macros. 208 if self.debug: 209 progress ("-"*68+"\n") 210 for key in list(self.defines.keys()): 211 progress ("define: %s\n"%key) 212 213 self.expandMacro() 214 self.tokenSize = len(self.tokens) 215 216 def expandMacro (self): 217 macroExp = MacroExpander(self.tokens, self.defines) 218 macroExp.expand() 219 self.tokens = macroExp.getTokens() 220 if self.onlyExpandMacros: 221 srclexer.dumpTokens(self.tokens) 222 sys.exit(0) 223 224 def parse (self): 225 """Parse it! 226 227This is the main loop for the parser. This is where it all begins and ends. 228""" 229 self.init() 230 231 i = 0 232 while i < self.tokenSize: 233 tk = self.tokens[i] 234 if tk == '{': 235 i = self.openBrace(i) 236 elif tk == '}': 237 i = self.closeBrace(i) 238 elif tk == ';': 239 i = self.semiColon(i) 240 elif tk == '=': 241 i = self.assignment(i) 242 else: 243 self.tokenBuf.append(tk) 244 245 i += 1 246 247 return self.elementStack[0] 248 249 #------------------------------------------------------------------------- 250 # Token Handlers 251 252 """ 253Each token handler takes the current token position and returns the position 254of the last token processed. For the most part, the current token position 255and the last processed token are one and the same, in which case the handler 256can simply return the position value it receives without incrementing it. 257 258If you need to read ahead to process more tokens than just the current token, 259make sure that the new token position points to the last token that has been 260processed, not the next token that has not yet been processed. This is 261because the main loop increments the token position when it returns from the 262handler. 263""" 264 265 # assignment token '=' 266 def assignment (self, i): 267 self.leftTokens = self.tokenBuf[:] 268 if self.stmtData.leftScope == None: 269 # Keep track of lhs data in case of compound statement. 270 self.stmtData.leftTokens = self.tokenBuf[:] 271 self.stmtData.leftScope = len(self.elementStack) - 1 272 273 self.tokenBuf = [] 274 return i 275 276 # open brace token '{' 277 def openBrace (self, i): 278 bufSize = len(self.tokenBuf) 279 leftSize = len(self.leftTokens) 280 obj = None 281 if bufSize == 0 and leftSize > 0: 282 # Name = { ... 283 obj = Element(self.leftTokens[0]) 284 285 elif bufSize > 0 and leftSize == 0: 286 # Type Name { ... 287 wgtType = self.tokenBuf[0] 288 wgtRID = None 289 if bufSize >= 2: 290 wgtRID = self.tokenBuf[1] 291 obj = Element(wgtType, wgtRID) 292 293 else: 294 # LeftName = Name { ... 295 obj = Element(self.leftTokens[0]) 296 obj.setAttr("type", self.tokenBuf[0]) 297 298 obj.name = transName(obj.name) 299 300 if obj.name == 'string-list': 301 i = self.parseStringList(i) 302 elif obj.name == 'filter-list': 303 i = self.parseFilterList(i, obj) 304 else: 305 self.elementStack[-1].appendChild(obj) 306 self.elementStack.append(obj) 307 308 self.tokenBuf = [] 309 self.leftTokens = [] 310 311 return i 312 313 # close brace token '}' 314 def closeBrace (self, i): 315 if len(self.tokenBuf) > 0: 316 if self.debug: 317 print(self.tokenBuf) 318 raise ParseError ('') 319 self.elementStack.pop() 320 return i 321 322 # semi colon token ';' 323 def semiColon (self, i): 324 stackSize = len(self.elementStack) 325 scope = stackSize - 1 326 if len(self.tokenBuf) == 0: 327 pass 328 elif scope == 0: 329 # We are not supposed to have any statment in global scope. 330 # Just ignore this statement. 331 pass 332 else: 333 # Statement within a scope. Import it as an attribute for the 334 # current element. 335 elem = self.elementStack[-1] 336 337 name = "none" 338 if len(self.leftTokens) > 0: 339 # Use the leftmost token as the name for now. If we need to 340 # do more complex parsing of lhs, add more code here. 341 name = self.leftTokens[0] 342 name = transName(name) 343 344 if name == 'pos': 345 i = self.parsePosAttr(i) 346 elif name == 'size': 347 i = self.parseSizeAttr(i) 348 elif len (self.tokenBuf) == 1: 349 # Simple value 350 value = transValue(self.tokenBuf[0]) 351 name = renameAttribute(name, elem.name) 352 elem.setAttr(name, value) 353 354 if not self.stmtData.leftScope == None and self.stmtData.leftScope < scope: 355 # This is a nested scope within a statement. Do nothing for now. 356 pass 357 358 if self.stmtData.leftScope == scope: 359 # end of (nested) statement. 360 self.stmtData.leftScope = None 361 362 self.tokenBuf = [] 363 self.leftTokens = [] 364 365 return i 366 367 def parseStringList (self, i): 368 369 i += 1 370 while i < self.tokenSize: 371 tk = self.tokens[i] 372 if tk == '}': 373 break 374 i += 1 375 376 return i 377 378 def parseFilterList (self, i, obj): 379 self.elementStack[-1].appendChild(obj) 380 self.elementStack.append(obj) 381 382 return i 383 384 def parsePosAttr (self, i): 385 386 # MAP_APPFONT ( 6 , 5 ) 387 elem = self.elementStack[-1] 388 x, y = self.parseMapAppfont(self.tokenBuf) 389 elem.setAttr("x", x) 390 elem.setAttr("y", y) 391 392 return i 393 394 def parseSizeAttr (self, i): 395 396 # MAP_APPFONT ( 6 , 5 ) 397 elem = self.elementStack[-1] 398 width, height = self.parseMapAppfont(self.tokenBuf) 399 elem.setAttr("width", width) 400 elem.setAttr("height", height) 401 402 return i 403 404 def parseMapAppfont (self, tokens): 405 values = [] 406 scope = 0 407 val = '' 408 for tk in tokens: 409 if tk == '(': 410 scope += 1 411 if scope == 1: 412 val = '' 413 else: 414 val += tk 415 elif tk == ')': 416 scope -= 1 417 if scope == 0: 418 if len(val) == 0: 419 raise ParseError ('') 420 values.append(val) 421 break 422 else: 423 val += tk 424 elif tk == ',': 425 if len(val) == 0: 426 raise ParseError ('') 427 values.append(val) 428 val = '' 429 elif scope > 0: 430 val += tk 431 432 if len(values) != 2: 433 raise ParseError ('') 434 435 return eval(values[0]), eval(values[1]) 436