class ParserException inherits SourceException var _token as IToken? var _fileName as String var _lineNum as int cue init(token as IToken, message as String) base.init(message) _token = token _fileName = _token.fileName _lineNum = _token.lineNum cue init(fileName as String, message as String) base.init(message) _fileName = fileName _lineNum = 1 cue init(message as String) is protected # for use by def clone... base.init(message) _fileName = '' def consoleString as String is override if _token type = if(.isError, 'error', 'warning') msg = '[_token.fileName]([_token.lineNum],[_token.colNum]): [type]: [.message]' return msg return base.consoleString get hasSourceSite as bool is override return true get fileName from var is override get lineNum from var is override def cloneWithMessage(message as String) as ParserException pe = ParserException(message) pe._token = _token pe._fileName = _fileName pe._lineNum = _lineNum return pe interface IWarningRecorder def warning(we as CobraWarning) interface IErrorRecorder def recordError(error as SourceException) class Parser is abstract var _fileName as String? var _willShowTokens = false var _verbosity = 0 var _tokens as List? var _nextTokenIndex as int pro verbosity from var ## ## Tokens ## def grab as IToken? """ Returns the next token or nil if there are none left. """ if _nextTokenIndex >= _tokens.count, return nil token = _tokens[_nextTokenIndex] _nextTokenIndex += 1 if _willShowTokens, print 'grab --> [token]' return token def ungrab """ Undoes the last .grab call. Often called "push" in parser examples. """ require _nextTokenIndex > 0 _nextTokenIndex -= 1 if _willShowTokens, print 'undo' def peek as IToken? return .peek(0) def peek(offset as int) as IToken? """ Returns a token without changing the current token, or nil if none left past the range. An offset of 0 looks one ahead, 1 looks two ahead, etc. """ i = _nextTokenIndex + offset if i < _tokens.count, token = _tokens[i] to ? else, token = nil if _willShowTokens, print 'peek([offset]) --> [token]' return token def replace(token as IToken) """ Replaces the current token in the token stream with the given argument. """ require .peek ensure .peek == token _tokens[_nextTokenIndex] = token def last as IToken? return .last(0) def last(n as int) as IToken? """ Returns the last token returned by .grab or nil if .grab was never invoked. """ require n >= 0 if _nextTokenIndex > n, token = _tokens[_nextTokenIndex-n-1] to ? else, token = nil if _willShowTokens, print 'last([n]) --> [token]' return token def lastN(n as int) as List # CC: use List> """ Returns a list of NumberedTokens. """ /# TODO - figure out a how to test this test p = CobraParser() s = 'namespace class Foo def' p._preParseSource('(no filename)', s) p.grab p.grab p.grab t = p.lastN(2) assert t[0].token.which=='CLASS' assert t[1].token.which=='ID' assert t.count==2 #/ require n >= 0 body tokens = List() if _nextTokenIndex > 0 while true n -= 1 if n == -1, break i = _nextTokenIndex - n - 1 if i >= 0 and i < _tokens.count tokens.add(NumberedToken(i, _tokens[i])) return tokens def expect(whatTypes as vari String) as IToken """ Gets a token and complains if its type does not match whatType(s). Returns the token. """ t = .grab if _willShowTokens print 'expect([whatTypes]) --> [t]' if t is nil .throwError('Expecting [List(whatTypes).join(" or ")], but source ended suddenly.') # CC: shouldn't need to wrap a 'vari String' in a list to use extensions on IEnumerable if t.which not in whatTypes .throwError('Expecting [List(whatTypes).join(" or ")], but got [t] instead.') return t to ! def optional(whatTypes as vari String) as IToken? """ Gets a token, but only if it matches whatTypes. Does not complain or consume a token if there is no match. """ t = .peek if _willShowTokens, print 'optional([whatTypes]) --> [t]' if t is nil, return nil else if t.which in whatTypes, return .grab else, return nil def oneOrMore(which as String) """ Consumes the expected token and any other additional contiguous ones. Returns nothing. Example: .oneOrMore('EOL') """ .expect(which) while .peek and .peek.which == which, .grab def zeroOrMore(which as String) """ Consumes the token (if present) and any other additional contiguous ones. Returns nothing. Example: .zeroOrMore('EOL') """ while .peek and .peek.which == which, .grab ## ## Errors and warnings ## def recordError(msg as String) as ParserException return .recordError(.last, msg) def recordError(token as IToken?, msg as String) as ParserException err = _makeError(token, msg) _errorRecorder.recordError(err) return err def throwError(msg as String) .throwError(.last, msg) def throwError(token as IToken?, msg as String) throw _makeError(token, msg) # will get recorded in .parseSource if the exception actually "makes it out" (some parser logic catches errors) def _makeError(token as IToken?, msg as String) as ParserException if _verbosity>=2 print 'PARSER ERROR: [msg]' print 'Last tokens:' print ' ...' for pair in .lastN(9) s = ' [pair.i]. [pair.token]' s = s.padRight(25) + 'line [pair.token.lineNum]' print s # assert false token ?= .last if token is nil return ParserException(_fileName, msg) else return ParserException(token, msg) def _warning(msg as String) _warning(.last, msg) def _warning(token as IToken?, msg as String) _warningRecorder.warning(CobraWarning(_fileName, token, msg)) # TODO: do this with a callback/delegate instead of this bullshit Java-style interface technique var _warningRecorder as IWarningRecorder? var _errorRecorder as IErrorRecorder? pro warningRecorder from var pro errorRecorder from var class NumberedToken """ In support of Parser.lastN. # TODO: Replace this with Pair """ cue init(i as int, token as IToken) base.init _i, _token = i, token get i from var as int get token from var as IToken def toString as String is override return '([_i], [_token])'