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