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