| # ************************************************************* |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| # ************************************************************* |
| |
| import sys |
| from globals import * |
| import srclexer |
| |
| # simple name translation map |
| postTransMap = {"ok-button": "okbutton", |
| "cancel-button": "cancelbutton", |
| "help-button": "helpbutton"} |
| |
| def transName (name): |
| """Translate a mixed-casing name to dash-separated name. |
| |
| Translate a mixed-casing name (e.g. MyLongName) to a dash-separated name |
| (e.g. my-long-name). |
| """ |
| def isUpper (c): |
| return c >= 'A' and c <= 'Z' |
| |
| newname = '' |
| parts = [] |
| buf = '' |
| for c in name: |
| if isUpper(c) and len(buf) > 1: |
| parts.append(buf) |
| buf = c |
| else: |
| buf += c |
| |
| if len(buf) > 0: |
| parts.append(buf) |
| |
| first = True |
| for part in parts: |
| if first: |
| first = False |
| else: |
| newname += '-' |
| newname += part.lower() |
| |
| # special-case mapping ... |
| if 0: #postTransMap.has_key(newname): |
| newname = postTransMap[newname] |
| |
| return newname |
| |
| |
| def transValue (value): |
| """Translate certain values. |
| |
| Examples of translated values include TRUE -> true, FALSE -> false. |
| """ |
| if value.lower() in ["true", "false"]: |
| value = value.lower() |
| return value |
| |
| |
| def renameAttribute (name, elemName): |
| |
| # TODO: all manner of evil special cases ... |
| if elemName == 'metric-field' and name == 'spin-size': |
| return 'step-size' |
| |
| return name |
| |
| |
| class Statement(object): |
| """Container to hold information for a single statement. |
| |
| Each statement consists of the left-hand-side token(s), and right-hand-side |
| tokens, separated by a '=' token. This class stores the information on the |
| left-hand-side tokens. |
| """ |
| def __init__ (self): |
| self.leftTokens = [] |
| self.leftScope = None |
| |
| |
| class MacroExpander(object): |
| def __init__ (self, tokens, defines): |
| self.tokens = tokens |
| self.defines = defines |
| |
| def expand (self): |
| self.pos = 0 |
| while self.pos < len(self.tokens): |
| self.expandToken() |
| |
| def expandToken (self): |
| token = self.tokens[self.pos] |
| if token not in self.defines: |
| self.pos += 1 |
| return |
| |
| macro = self.defines[token] |
| nvars = len(list(macro.vars.keys())) |
| if nvars == 0: |
| # Simple expansion |
| self.tokens[self.pos:self.pos+1] = macro.tokens |
| return |
| else: |
| # Expansion with arguments. |
| values, lastPos = self.parseValues() |
| newtokens = [] |
| for mtoken in macro.tokens: |
| if mtoken in macro.vars: |
| # variable |
| pos = macro.vars[mtoken] |
| valtokens = values[pos] |
| for valtoken in valtokens: |
| newtokens.append(valtoken) |
| else: |
| # not a variable |
| newtokens.append(mtoken) |
| |
| self.tokens[self.pos:self.pos+lastPos+1] = newtokens |
| |
| |
| def parseValues (self): |
| """Parse tokens to get macro function variable values. |
| |
| Be aware that there is an implicit quotes around the text between the open |
| paren, the comma(s), and the close paren. For instance, if a macro is defined |
| as FOO(a, b) and is used as FOO(one two three, and four), then the 'a' must be |
| replaced with 'one two three', and the 'b' replaced with 'and four'. In other |
| words, whitespace does not end a token. |
| |
| """ |
| values = [] |
| i = 1 |
| scope = 0 |
| value = [] |
| while True: |
| try: |
| tk = self.tokens[self.pos+i] |
| except IndexError: |
| progress ("error parsing values (%d)\n"%i) |
| for j in range(0, i): |
| print(self.tokens[self.pos+j], end=' ') |
| print('') |
| srclexer.dumpTokens(self.tokens) |
| srclexer.dumpTokens(self.newtokens) |
| print("tokens expanded so far:") |
| for tk in self.expandedTokens: |
| print("-"*20) |
| print(tk) |
| srclexer.dumpTokens(self.defines[tk].tokens) |
| sys.exit(1) |
| if tk == '(': |
| value = [] |
| scope += 1 |
| elif tk == ',': |
| values.append(value) |
| value = [] |
| elif tk == ')': |
| scope -= 1 |
| values.append(value) |
| value = [] |
| if scope == 0: |
| break |
| else: |
| raise ParseError ('') |
| else: |
| value.append(tk) |
| i += 1 |
| |
| return values, i |
| |
| def getTokens (self): |
| return self.tokens |
| |
| |
| class SrcParser(object): |
| |
| def __init__ (self, tokens, defines = None): |
| self.tokens = tokens |
| self.defines = defines |
| self.debug = False |
| self.onlyExpandMacros = False |
| |
| def init (self): |
| self.elementStack = [RootNode()] |
| self.stmtData = Statement() |
| self.tokenBuf = [] |
| self.leftTokens = [] |
| |
| # Expand defined macros. |
| if self.debug: |
| progress ("-"*68+"\n") |
| for key in list(self.defines.keys()): |
| progress ("define: %s\n"%key) |
| |
| self.expandMacro() |
| self.tokenSize = len(self.tokens) |
| |
| def expandMacro (self): |
| macroExp = MacroExpander(self.tokens, self.defines) |
| macroExp.expand() |
| self.tokens = macroExp.getTokens() |
| if self.onlyExpandMacros: |
| srclexer.dumpTokens(self.tokens) |
| sys.exit(0) |
| |
| def parse (self): |
| """Parse it! |
| |
| This is the main loop for the parser. This is where it all begins and ends. |
| """ |
| self.init() |
| |
| i = 0 |
| while i < self.tokenSize: |
| tk = self.tokens[i] |
| if tk == '{': |
| i = self.openBrace(i) |
| elif tk == '}': |
| i = self.closeBrace(i) |
| elif tk == ';': |
| i = self.semiColon(i) |
| elif tk == '=': |
| i = self.assignment(i) |
| else: |
| self.tokenBuf.append(tk) |
| |
| i += 1 |
| |
| return self.elementStack[0] |
| |
| #------------------------------------------------------------------------- |
| # Token Handlers |
| |
| """ |
| Each token handler takes the current token position and returns the position |
| of the last token processed. For the most part, the current token position |
| and the last processed token are one and the same, in which case the handler |
| can simply return the position value it receives without incrementing it. |
| |
| If you need to read ahead to process more tokens than just the current token, |
| make sure that the new token position points to the last token that has been |
| processed, not the next token that has not yet been processed. This is |
| because the main loop increments the token position when it returns from the |
| handler. |
| """ |
| |
| # assignment token '=' |
| def assignment (self, i): |
| self.leftTokens = self.tokenBuf[:] |
| if self.stmtData.leftScope == None: |
| # Keep track of lhs data in case of compound statement. |
| self.stmtData.leftTokens = self.tokenBuf[:] |
| self.stmtData.leftScope = len(self.elementStack) - 1 |
| |
| self.tokenBuf = [] |
| return i |
| |
| # open brace token '{' |
| def openBrace (self, i): |
| bufSize = len(self.tokenBuf) |
| leftSize = len(self.leftTokens) |
| obj = None |
| if bufSize == 0 and leftSize > 0: |
| # Name = { ... |
| obj = Element(self.leftTokens[0]) |
| |
| elif bufSize > 0 and leftSize == 0: |
| # Type Name { ... |
| wgtType = self.tokenBuf[0] |
| wgtRID = None |
| if bufSize >= 2: |
| wgtRID = self.tokenBuf[1] |
| obj = Element(wgtType, wgtRID) |
| |
| else: |
| # LeftName = Name { ... |
| obj = Element(self.leftTokens[0]) |
| obj.setAttr("type", self.tokenBuf[0]) |
| |
| obj.name = transName(obj.name) |
| |
| if obj.name == 'string-list': |
| i = self.parseStringList(i) |
| elif obj.name == 'filter-list': |
| i = self.parseFilterList(i, obj) |
| else: |
| self.elementStack[-1].appendChild(obj) |
| self.elementStack.append(obj) |
| |
| self.tokenBuf = [] |
| self.leftTokens = [] |
| |
| return i |
| |
| # close brace token '}' |
| def closeBrace (self, i): |
| if len(self.tokenBuf) > 0: |
| if self.debug: |
| print(self.tokenBuf) |
| raise ParseError ('') |
| self.elementStack.pop() |
| return i |
| |
| # semi colon token ';' |
| def semiColon (self, i): |
| stackSize = len(self.elementStack) |
| scope = stackSize - 1 |
| if len(self.tokenBuf) == 0: |
| pass |
| elif scope == 0: |
| # We are not supposed to have any statment in global scope. |
| # Just ignore this statement. |
| pass |
| else: |
| # Statement within a scope. Import it as an attribute for the |
| # current element. |
| elem = self.elementStack[-1] |
| |
| name = "none" |
| if len(self.leftTokens) > 0: |
| # Use the leftmost token as the name for now. If we need to |
| # do more complex parsing of lhs, add more code here. |
| name = self.leftTokens[0] |
| name = transName(name) |
| |
| if name == 'pos': |
| i = self.parsePosAttr(i) |
| elif name == 'size': |
| i = self.parseSizeAttr(i) |
| elif len (self.tokenBuf) == 1: |
| # Simple value |
| value = transValue(self.tokenBuf[0]) |
| name = renameAttribute(name, elem.name) |
| elem.setAttr(name, value) |
| |
| if not self.stmtData.leftScope == None and self.stmtData.leftScope < scope: |
| # This is a nested scope within a statement. Do nothing for now. |
| pass |
| |
| if self.stmtData.leftScope == scope: |
| # end of (nested) statement. |
| self.stmtData.leftScope = None |
| |
| self.tokenBuf = [] |
| self.leftTokens = [] |
| |
| return i |
| |
| def parseStringList (self, i): |
| |
| i += 1 |
| while i < self.tokenSize: |
| tk = self.tokens[i] |
| if tk == '}': |
| break |
| i += 1 |
| |
| return i |
| |
| def parseFilterList (self, i, obj): |
| self.elementStack[-1].appendChild(obj) |
| self.elementStack.append(obj) |
| |
| return i |
| |
| def parsePosAttr (self, i): |
| |
| # MAP_APPFONT ( 6 , 5 ) |
| elem = self.elementStack[-1] |
| x, y = self.parseMapAppfont(self.tokenBuf) |
| elem.setAttr("x", x) |
| elem.setAttr("y", y) |
| |
| return i |
| |
| def parseSizeAttr (self, i): |
| |
| # MAP_APPFONT ( 6 , 5 ) |
| elem = self.elementStack[-1] |
| width, height = self.parseMapAppfont(self.tokenBuf) |
| elem.setAttr("width", width) |
| elem.setAttr("height", height) |
| |
| return i |
| |
| def parseMapAppfont (self, tokens): |
| values = [] |
| scope = 0 |
| val = '' |
| for tk in tokens: |
| if tk == '(': |
| scope += 1 |
| if scope == 1: |
| val = '' |
| else: |
| val += tk |
| elif tk == ')': |
| scope -= 1 |
| if scope == 0: |
| if len(val) == 0: |
| raise ParseError ('') |
| values.append(val) |
| break |
| else: |
| val += tk |
| elif tk == ',': |
| if len(val) == 0: |
| raise ParseError ('') |
| values.append(val) |
| val = '' |
| elif scope > 0: |
| val += tk |
| |
| if len(values) != 2: |
| raise ParseError ('') |
| |
| return eval(values[0]), eval(values[1]) |