Wiki

root/cobra/trunk/Source/Parser.cobra

Revision 2425, 6.3 KB (checked in by Chuck.Esterbrook, 21 months ago)

Show column info for parser errors.
ticket:212
credit:hopscc

  • Property svn:eol-style set to native
Line 
1class ParserException inherits SourceException
2
3    var _token as IToken?
4    var _fileName as String
5    var _lineNum as int
6
7    cue init(token as IToken, message as String)
8        base.init(message)
9        _token = token
10        _fileName = _token.fileName
11        _lineNum = _token.lineNum
12
13    cue init(fileName as String, message as String)
14        base.init(message)
15        _fileName = fileName
16        _lineNum = 1
17
18    cue init(message as String) is protected
19        # for use by def clone...
20        base.init(message)
21        _fileName = ''
22
23    def consoleString as String is override
24        if _token
25            type = if(.isError, 'error', 'warning')
26            msg = '[_token.fileName]([_token.lineNum],[_token.colNum]): [type]: [.message]' 
27            return msg
28        return base.consoleString
29
30    get hasSourceSite as bool is override
31        return true
32
33    get fileName from var is override
34
35    get lineNum from var is override
36
37    def cloneWithMessage(message as String) as ParserException
38        pe = ParserException(message)
39        pe._token = _token
40        pe._fileName = _fileName
41        pe._lineNum = _lineNum
42        return pe
43
44
45interface IWarningRecorder
46
47    def warning(we as CobraWarning)
48
49
50interface IErrorRecorder
51
52    def recordError(error as SourceException)
53
54
55class Parser
56    is abstract
57
58    var _fileName as String?
59    var _willShowTokens = false
60    var _verbosity = 0
61
62    var _tokens as List<of IToken>?
63    var _nextTokenIndex as int
64
65    pro verbosity from var
66
67    ##
68    ## Tokens
69    ##
70
71    def grab as IToken?
72        """
73        Returns the next token or nil if there are none left.
74        """
75        if _nextTokenIndex >= _tokens.count, return nil
76        token = _tokens[_nextTokenIndex]
77        _nextTokenIndex += 1
78        if _willShowTokens, print 'grab    --> [token]'
79        return token
80
81    def ungrab
82        """
83        Undoes the last .grab call. Often called "push" in parser examples.
84        """
85        require _nextTokenIndex > 0
86        _nextTokenIndex -= 1
87        if _willShowTokens, print 'undo'
88
89    def peek as IToken?
90        return .peek(0)
91
92    def peek(offset as int) as IToken?
93        """
94        Returns a token without changing the current token, or nil if none left past the range.
95        An offset of 0 looks one ahead, 1 looks two ahead, etc.
96        """
97        i = _nextTokenIndex + offset
98        if i < _tokens.count, token = _tokens[i] to ?
99        else, token = nil
100        if _willShowTokens, print 'peek([offset]) --> [token]'
101        return token
102
103    def replace(token as IToken)
104        """
105        Replaces the current token in the token stream with the given argument.
106        """
107        require .peek
108        ensure .peek == token
109        _tokens[_nextTokenIndex] = token
110       
111    def last as IToken?
112        return .last(0)
113
114    def last(n as int) as IToken?
115        """ Returns the last token returned by .grab or nil if .grab was never invoked. """
116        require n >= 0
117        if _nextTokenIndex > n, token = _tokens[_nextTokenIndex-n-1] to ?
118        else, token = nil
119        if _willShowTokens, print 'last([n]) --> [token]'
120        return token
121
122    def lastN(n as int) as List<of NumberedToken>  # CC: use List<of Pair<of int, IToken>>
123        """
124        Returns a list of NumberedTokens.
125        """
126/# TODO - figure out a how to test this
127        test
128            p = CobraParser()
129            s = 'namespace class Foo def'
130            p._preParseSource('(no filename)', s)
131            p.grab
132            p.grab
133            p.grab
134            t = p.lastN(2)
135            assert t[0].token.which=='CLASS'
136            assert t[1].token.which=='ID'
137            assert t.count==2
138#/
139        require
140            n >= 0
141        body
142            tokens = List<of NumberedToken>()
143            if _nextTokenIndex > 0
144                while true
145                    n -= 1
146                    if n == -1, break
147                    i = _nextTokenIndex - n - 1
148                    if i >= 0 and i < _tokens.count
149                        tokens.add(NumberedToken(i, _tokens[i]))
150            return tokens
151
152    def expect(whatTypes as vari String) as IToken
153        """
154        Gets a token and complains if its type does not match whatType(s).
155        Returns the token.
156        """
157        t = .grab
158        if _willShowTokens
159            print 'expect([whatTypes]) --> [t]'
160        if t is nil
161            .throwError('Expecting [List<of String>(whatTypes).join(" or ")], but source ended suddenly.')  # CC: shouldn't need to wrap a 'vari String' in a list to use extensions on IEnumerable
162        if t.which not in whatTypes
163            .throwError('Expecting [List<of String>(whatTypes).join(" or ")], but got [t] instead.')
164        return t to !
165
166    def optional(whatTypes as vari String) as IToken?
167        """
168        Gets a token, but only if it matches whatTypes.
169        Does not complain or consume a token if there is no match.
170        """
171        t = .peek
172        if _willShowTokens, print 'optional([whatTypes]) --> [t]'
173        if t is nil, return nil
174        else if t.which in whatTypes, return .grab
175        else, return nil
176
177    def oneOrMore(which as String)
178        """
179        Consumes the expected token and any other additional contiguous ones.
180        Returns nothing.
181        Example:  .oneOrMore('EOL')
182        """
183        .expect(which)
184        while .peek and .peek.which == which, .grab
185
186    def zeroOrMore(which as String)
187        """
188        Consumes the token (if present) and any other additional contiguous ones.
189        Returns nothing.
190        Example:  .zeroOrMore('EOL')
191        """
192        while .peek and .peek.which == which, .grab
193
194
195    ##
196    ## Errors and warnings
197    ##
198
199    def recordError(msg as String) as ParserException
200        return .recordError(.last, msg)
201
202    def recordError(token as IToken?, msg as String) as ParserException
203        err = _makeError(token, msg)
204        _errorRecorder.recordError(err)
205        return err
206       
207    def throwError(msg as String)
208        .throwError(.last, msg)
209
210    def throwError(token as IToken?, msg as String)
211        throw _makeError(token, msg# will get recorded in .parseSource if the exception actually "makes it out" (some parser logic catches errors)
212
213    def _makeError(token as IToken?, msg as String) as ParserException
214        if _verbosity>=2
215            print 'PARSER ERROR: [msg]'
216            print 'Last tokens:'
217            print '    ...'
218            for pair in .lastN(9)
219                s = '    [pair.i]. [pair.token]'
220                s = s.padRight(25) + 'line [pair.token.lineNum]'
221                print s
222            # assert false
223        token ?= .last
224        if token is nil
225            return ParserException(_fileName, msg)
226        else
227            return ParserException(token, msg)
228
229    def _warning(msg as String)
230        _warning(.last, msg)
231
232    def _warning(token as IToken?, msg as String)
233        _warningRecorder.warning(CobraWarning(_fileName, token, msg))
234
235    # TODO: do this with a callback/delegate instead of this bullshit Java-style interface technique
236    var _warningRecorder as IWarningRecorder?
237    var _errorRecorder as IErrorRecorder?
238
239    pro warningRecorder from var
240    pro errorRecorder from var
241
242
243class NumberedToken
244    """
245    In support of Parser.lastN.
246    # TODO: Replace this with Pair<T1, T2>
247    """
248
249    cue init(i as int, token as IToken)
250        base.init
251        _i, _token = i, token
252
253    get i from var as int
254    get token from var as IToken
255
256    def toString as String is override
257        return '([_i], [_token])'
Note: See TracBrowser for help on using the browser.