mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1487 lines
44 KiB
1487 lines
44 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under MIT X11 license (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Globalization; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Text; |
|
using System.Xml; |
|
|
|
namespace ICSharpCode.NRefactory.VB.Parser |
|
{ |
|
public class VBLexer : IDisposable |
|
{ |
|
bool lineEnd = true; |
|
bool isAtLineBegin = false; // TODO: handle line begin, if neccessarry |
|
bool misreadExclamationMarkAsTypeCharacter; |
|
bool encounteredLineContinuation; |
|
|
|
ExpressionFinder ef; |
|
|
|
bool inXmlMode; |
|
|
|
Stack<XmlModeInfo> xmlModeStack = new Stack<XmlModeInfo>(); |
|
|
|
public VBLexer(TextReader reader) |
|
{ |
|
this.reader = new LATextReader(reader); |
|
ef = new ExpressionFinder(); |
|
} |
|
|
|
public VBLexer(TextReader reader, VBLexerMemento state) : this(reader) |
|
{ |
|
SetInitialLocation(new TextLocation(state.Line, state.Column)); |
|
lastToken = new Token(state.PrevTokenKind, 0, 0); |
|
ef = new ExpressionFinder(state.ExpressionFinder); |
|
lineEnd = state.LineEnd; |
|
isAtLineBegin = state.IsAtLineBegin; |
|
encounteredLineContinuation = state.EncounteredLineContinuation; |
|
misreadExclamationMarkAsTypeCharacter = state.MisreadExclamationMarkAsTypeCharacter; |
|
xmlModeStack = new Stack<XmlModeInfo>(state.XmlModeInfoStack.Select(i => (XmlModeInfo)i.Clone()).Reverse()); |
|
inXmlMode = state.InXmlMode; |
|
} |
|
|
|
Token NextInternal() |
|
{ |
|
if (misreadExclamationMarkAsTypeCharacter) { |
|
misreadExclamationMarkAsTypeCharacter = false; |
|
return new Token(Tokens.ExclamationMark, Col - 1, Line); |
|
} |
|
|
|
unchecked { |
|
while (true) { |
|
TextLocation startLocation = new TextLocation(Line, Col); |
|
int nextChar = ReaderRead(); |
|
if (nextChar == -1) |
|
return new Token(Tokens.EOF, Col, Line, string.Empty); |
|
char ch = (char)nextChar; |
|
#region XML mode |
|
CheckXMLState(startLocation); |
|
if (inXmlMode && xmlModeStack.Peek().level <= 0 && !xmlModeStack.Peek().isDocumentStart && !xmlModeStack.Peek().inXmlTag) { |
|
XmlModeInfo info = xmlModeStack.Peek(); |
|
int peek = nextChar; |
|
while (true) { |
|
int step = -1; |
|
while (peek != -1 && XmlConvert.IsWhitespaceChar((char)peek)) { |
|
step++; |
|
peek = ReaderPeek(step); |
|
} |
|
|
|
if (peek == '<' && (ReaderPeek(step + 1) == '!' || ReaderPeek(step + 1) == '?')) { |
|
char lastCh = '\0'; |
|
for (int i = 0; i < step + 2; i++) |
|
lastCh = (char)ReaderRead(); |
|
|
|
if (lastCh == '!') |
|
return ReadXmlCommentOrCData(Col - 2, Line); |
|
else |
|
return ReadXmlProcessingInstruction(Col - 2, Line); |
|
} |
|
|
|
break; |
|
} |
|
inXmlMode = false; |
|
xmlModeStack.Pop(); |
|
} |
|
if (inXmlMode) { |
|
XmlModeInfo info = xmlModeStack.Peek(); |
|
int x = Col - 1; |
|
int y = Line; |
|
switch (ch) { |
|
case '<': |
|
if (ReaderPeek() == '/') { |
|
ReaderRead(); |
|
info.inXmlCloseTag = true; |
|
return new Token(Tokens.XmlOpenEndTag, new TextLocation(y, x), new TextLocation(Line, Col)); |
|
} |
|
if (ReaderPeek() == '%' && ReaderPeek(1) == '=') { |
|
inXmlMode = false; |
|
ReaderRead(); ReaderRead(); |
|
return new Token(Tokens.XmlStartInlineVB, new TextLocation(y, x), new TextLocation(Line, Col)); |
|
} |
|
if (ReaderPeek() == '?') { |
|
ReaderRead(); |
|
Token t = ReadXmlProcessingInstruction(x, y); |
|
return t; |
|
} |
|
if (ReaderPeek() == '!') { |
|
ReaderRead(); |
|
Token token = ReadXmlCommentOrCData(x, y); |
|
return token; |
|
} |
|
info.level++; |
|
info.isDocumentStart = false; |
|
info.inXmlTag = true; |
|
return new Token(Tokens.XmlOpenTag, x, y); |
|
case '/': |
|
if (ReaderPeek() == '>') { |
|
ReaderRead(); |
|
info.inXmlTag = false; |
|
info.level--; |
|
return new Token(Tokens.XmlCloseTagEmptyElement, new TextLocation(y, x), new TextLocation(Line, Col)); |
|
} |
|
break; |
|
case '>': |
|
if (info.inXmlCloseTag) |
|
info.level--; |
|
info.inXmlTag = info.inXmlCloseTag = false; |
|
return new Token(Tokens.XmlCloseTag, x, y); |
|
case '=': |
|
return new Token(Tokens.Assign, x, y); |
|
case '\'': |
|
case '"': |
|
string s = ReadXmlString(ch); |
|
return new Token(Tokens.LiteralString, x, y, ch + s + ch, s); |
|
default: |
|
if (info.inXmlCloseTag || info.inXmlTag) { |
|
if (XmlConvert.IsWhitespaceChar(ch)) |
|
continue; |
|
return new Token(Tokens.Identifier, x, y, ReadXmlIdent(ch)); |
|
} else { |
|
string content = ReadXmlContent(ch); |
|
return new Token(Tokens.XmlContent, startLocation, new TextLocation(Line, Col), content, null); |
|
} |
|
} |
|
#endregion |
|
} else { |
|
#region Standard Mode |
|
if (Char.IsWhiteSpace(ch)) { |
|
if (HandleLineEnd(ch)) { |
|
if (lineEnd) { |
|
// second line end before getting to a token |
|
// -> here was a blank line |
|
// specialTracker.AddEndOfLine(startLocation); |
|
} else { |
|
lineEnd = true; |
|
return new Token(Tokens.EOL, startLocation, new TextLocation(Line, Col), null, null); |
|
} |
|
} |
|
continue; |
|
} |
|
if (ch == '_') { |
|
if (ReaderPeek() == -1) { |
|
errors.Error(Line, Col, String.Format("No EOF expected after _")); |
|
return new Token(Tokens.EOF, Col, Line, string.Empty); |
|
} |
|
if (!Char.IsWhiteSpace((char)ReaderPeek())) { |
|
int x = Col - 1; |
|
int y = Line; |
|
string s = ReadIdent('_'); |
|
lineEnd = false; |
|
return new Token(Tokens.Identifier, x, y, s); |
|
} |
|
encounteredLineContinuation = true; |
|
ch = (char)ReaderRead(); |
|
|
|
bool oldLineEnd = lineEnd; |
|
lineEnd = false; |
|
while (Char.IsWhiteSpace(ch)) { |
|
if (HandleLineEnd(ch)) { |
|
lineEnd = true; |
|
break; |
|
} |
|
if (ReaderPeek() != -1) { |
|
ch = (char)ReaderRead(); |
|
} else { |
|
errors.Error(Line, Col, String.Format("No EOF expected after _")); |
|
return new Token(Tokens.EOF, Col, Line, string.Empty); |
|
} |
|
} |
|
if (!lineEnd) { |
|
errors.Error(Line, Col, String.Format("NewLine expected")); |
|
} |
|
lineEnd = oldLineEnd; |
|
continue; |
|
} |
|
|
|
if (ch == '#') { |
|
while (Char.IsWhiteSpace((char)ReaderPeek())) { |
|
ReaderRead(); |
|
} |
|
if (Char.IsDigit((char)ReaderPeek())) { |
|
int x = Col - 1; |
|
int y = Line; |
|
string s = ReadDate(); |
|
DateTime time = new DateTime(1, 1, 1, 0, 0, 0); |
|
try { |
|
time = DateTime.Parse(s, System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault); |
|
} catch (Exception e) { |
|
errors.Error(Line, Col, String.Format("Invalid date time {0}", e)); |
|
} |
|
return new Token(Tokens.LiteralDate, x, y, s, time); |
|
} else { |
|
ReadPreprocessorDirective(); |
|
continue; |
|
} |
|
} |
|
|
|
if (ch == '[') { // Identifier |
|
lineEnd = false; |
|
if (ReaderPeek() == -1) { |
|
errors.Error(Line, Col, String.Format("Identifier expected")); |
|
} |
|
ch = (char)ReaderRead(); |
|
if (ch == ']' || Char.IsWhiteSpace(ch)) { |
|
errors.Error(Line, Col, String.Format("Identifier expected")); |
|
} |
|
int x = Col - 1; |
|
int y = Line; |
|
string s = ReadIdent(ch); |
|
if (ReaderPeek() == -1) { |
|
errors.Error(Line, Col, String.Format("']' expected")); |
|
} |
|
ch = (char)ReaderRead(); |
|
if (!(ch == ']')) { |
|
errors.Error(Line, Col, String.Format("']' expected")); |
|
} |
|
return new Token(Tokens.Identifier, x, y, s); |
|
} |
|
if (Char.IsLetter(ch)) { |
|
int x = Col - 1; |
|
int y = Line; |
|
char typeCharacter; |
|
string s = ReadIdent(ch, out typeCharacter); |
|
if (typeCharacter == '\0') { |
|
int keyWordToken = Keywords.GetToken(s); |
|
if (keyWordToken >= 0) { |
|
// handle 'REM' comments |
|
if (keyWordToken == Tokens.Rem) { |
|
ReadComment(); |
|
if (!lineEnd) { |
|
lineEnd = true; |
|
return new Token(Tokens.EOL, Col, Line, "\n"); |
|
} |
|
continue; |
|
} |
|
|
|
lineEnd = false; |
|
return new Token(keyWordToken, x, y, s); |
|
} |
|
} |
|
|
|
lineEnd = false; |
|
return new Token(Tokens.Identifier, x, y, s); |
|
|
|
} |
|
if (Char.IsDigit(ch)) { |
|
lineEnd = false; |
|
return ReadDigit(ch, Col - 1); |
|
} |
|
if (ch == '&') { |
|
lineEnd = false; |
|
if (ReaderPeek() == -1) { |
|
return ReadOperator('&'); |
|
} |
|
ch = (char)ReaderPeek(); |
|
if (Char.ToUpper(ch, CultureInfo.InvariantCulture) == 'H' || Char.ToUpper(ch, CultureInfo.InvariantCulture) == 'O') { |
|
return ReadDigit('&', Col - 1); |
|
} |
|
return ReadOperator('&'); |
|
} |
|
if (ch == '\'' || ch == '\u2018' || ch == '\u2019') { |
|
int x = Col - 1; |
|
int y = Line; |
|
ReadComment(); |
|
if (!lineEnd) { |
|
lineEnd = true; |
|
return new Token(Tokens.EOL, x, y, "\n"); |
|
} |
|
continue; |
|
} |
|
if (ch == '"') { |
|
lineEnd = false; |
|
int x = Col - 1; |
|
int y = Line; |
|
string s = ReadString(); |
|
if (ReaderPeek() != -1 && (ReaderPeek() == 'C' || ReaderPeek() == 'c')) { |
|
ReaderRead(); |
|
if (s.Length != 1) { |
|
errors.Error(Line, Col, String.Format("Chars can only have Length 1 ")); |
|
} |
|
if (s.Length == 0) { |
|
s = "\0"; |
|
} |
|
return new Token(Tokens.LiteralCharacter, x, y, '"' + s + "\"C", s[0]); |
|
} |
|
return new Token(Tokens.LiteralString, x, y, '"' + s + '"', s); |
|
} |
|
if (ch == '%' && ReaderPeek() == '>') { |
|
int x = Col - 1; |
|
int y = Line; |
|
inXmlMode = true; |
|
ReaderRead(); |
|
return new Token(Tokens.XmlEndInlineVB, new TextLocation(y, x), new TextLocation(Line, Col)); |
|
} |
|
#endregion |
|
if (ch == '<' && (ef.NextTokenIsPotentialStartOfExpression || ef.NextTokenIsStartOfImportsOrAccessExpression)) { |
|
xmlModeStack.Push(new XmlModeInfo(ef.NextTokenIsStartOfImportsOrAccessExpression)); |
|
XmlModeInfo info = xmlModeStack.Peek(); |
|
int x = Col - 1; |
|
int y = Line; |
|
inXmlMode = true; |
|
if (ReaderPeek() == '/') { |
|
ReaderRead(); |
|
info.inXmlCloseTag = true; |
|
return new Token(Tokens.XmlOpenEndTag, new TextLocation(y, x), new TextLocation(Line, Col)); |
|
} |
|
// should we allow <%= at start of an expression? not valid with vbc ... |
|
if (ReaderPeek() == '%' && ReaderPeek(1) == '=') { |
|
inXmlMode = false; |
|
ReaderRead(); ReaderRead(); |
|
return new Token(Tokens.XmlStartInlineVB, new TextLocation(y, x), new TextLocation(Line, Col)); |
|
} |
|
if (ReaderPeek() == '!') { |
|
ReaderRead(); |
|
Token t = ReadXmlCommentOrCData(x, y); |
|
return t; |
|
} |
|
if (ReaderPeek() == '?') { |
|
ReaderRead(); |
|
Token t = ReadXmlProcessingInstruction(x, y); |
|
info.isDocumentStart = t.val.Trim().StartsWith("xml", StringComparison.OrdinalIgnoreCase); |
|
return t; |
|
} |
|
info.inXmlTag = true; |
|
info.level++; |
|
return new Token(Tokens.XmlOpenTag, x, y); |
|
} |
|
Token token = ReadOperator(ch); |
|
if (token != null) { |
|
lineEnd = false; |
|
return token; |
|
} |
|
} |
|
|
|
errors.Error(Line, Col, String.Format("Unknown char({0}) which can't be read", ch)); |
|
} |
|
} |
|
} |
|
|
|
void CheckXMLState(TextLocation startLocation) |
|
{ |
|
if (inXmlMode && !xmlModeStack.Any()) |
|
throw new InvalidOperationException("invalid XML stack state at " + startLocation); |
|
} |
|
|
|
Token prevToken; |
|
|
|
Token Next() |
|
{ |
|
Token t = NextInternal(); |
|
if (t.kind == Tokens.EOL) { |
|
Debug.Assert(t.next == null); // NextInternal() must return only 1 token |
|
t.next = NextInternal(); |
|
Debug.Assert(t.next.next == null); |
|
if (SkipEOL(prevToken.kind, t.next.kind)) { |
|
t = t.next; |
|
} |
|
} else |
|
encounteredLineContinuation = false; |
|
// inform EF only once we're sure it's really a token |
|
// this means we inform it about EOL tokens "1 token too late", but that's not a problem because |
|
// XML literals cannot start immediately after an EOL token |
|
ef.InformToken(t); |
|
if (t.next != null) { |
|
// Next() isn't called again when it returns 2 tokens, so we need to process both tokens |
|
ef.InformToken(t.next); |
|
prevToken = t.next; |
|
} else { |
|
prevToken = t; |
|
} |
|
ef.Advance(); |
|
Debug.Assert(t != null); |
|
return t; |
|
} |
|
|
|
/// <remarks>see VB language specification 10; pg. 6</remarks> |
|
bool SkipEOL(int prevTokenKind, int nextTokenKind) |
|
{ |
|
// exception directly after _ |
|
if (encounteredLineContinuation) { |
|
return encounteredLineContinuation = false; |
|
} |
|
|
|
// 1st rule |
|
// after a comma (,), open parenthesis ((), open curly brace ({), or open embedded expression (<%=) |
|
if (new[] { Tokens.Comma, Tokens.OpenParenthesis, Tokens.OpenCurlyBrace, Tokens.XmlStartInlineVB } |
|
.Contains(prevTokenKind)) |
|
return true; |
|
|
|
// 2nd rule |
|
// after a member qualifier (. or .@ or ...), provided that something is being qualified (i.e. is not |
|
// using an implicit With context) |
|
if (new[] { Tokens.Dot, Tokens.DotAt, Tokens.TripleDot }.Contains(prevTokenKind) |
|
&& !ef.WasQualifierTokenAtStart) |
|
return true; |
|
|
|
// 3rd rule |
|
// before a close parenthesis ()), close curly brace (}), or close embedded expression (%>) |
|
if (new[] { Tokens.CloseParenthesis, Tokens.CloseCurlyBrace, Tokens.XmlEndInlineVB } |
|
.Contains(nextTokenKind)) |
|
return true; |
|
|
|
// 4th rule |
|
// after a less-than (<) in an attribute context |
|
if (prevTokenKind == Tokens.LessThan && ef.InContext(Context.Attribute)) |
|
return true; |
|
|
|
// 5th rule |
|
// before a greater-than (>) in an attribute context |
|
if (nextTokenKind == Tokens.GreaterThan && ef.InContext(Context.Attribute)) |
|
return true; |
|
|
|
// 6th rule |
|
// after a greater-than (>) in a non-file-level attribute context |
|
if (ef.WasNormalAttribute && prevTokenKind == Tokens.GreaterThan) |
|
return true; |
|
|
|
// 7th rule |
|
// before and after query operators (Where, Order, Select, etc.) |
|
var queryOperators = new int[] { Tokens.From, Tokens.Aggregate, Tokens.Select, Tokens.Distinct, |
|
Tokens.Where, Tokens.Order, Tokens.By, Tokens.Ascending, Tokens.Descending, Tokens.Take, |
|
Tokens.Skip, Tokens.Let, Tokens.Group, Tokens.Into, Tokens.On, Tokens.While, Tokens.Join }; |
|
if (ef.InContext(Context.Query)) { |
|
// Ascending, Descending, Distinct are special |
|
// fixes http://community.sharpdevelop.net/forums/p/12068/32893.aspx#32893 |
|
var specialQueryOperators = new int[] { Tokens.Ascending, Tokens.Descending, Tokens.Distinct }; |
|
if (specialQueryOperators.Contains(prevTokenKind) && !queryOperators.Contains(nextTokenKind)) |
|
return false; |
|
|
|
if ((queryOperators.Contains(prevTokenKind) || queryOperators.Contains(nextTokenKind))) |
|
return true; |
|
} |
|
|
|
// 8th rule |
|
// after binary operators (+, -, /, *, etc.) in an expression context |
|
if (new[] { Tokens.Plus, Tokens.Minus, Tokens.Div, Tokens.DivInteger, Tokens.Times, Tokens.Mod, Tokens.Power, |
|
Tokens.Assign, Tokens.NotEqual, Tokens.LessThan, Tokens.LessEqual, Tokens.GreaterThan, Tokens.GreaterEqual, |
|
Tokens.Like, Tokens.ConcatString, Tokens.AndAlso, Tokens.OrElse, Tokens.And, Tokens.Or, Tokens.Xor, |
|
Tokens.ShiftLeft, Tokens.ShiftRight }.Contains(prevTokenKind) && ef.CurrentBlock.context == Context.Expression) |
|
return true; |
|
|
|
// 9th rule |
|
// after assignment operators (=, :=, +=, -=, etc.) in any context. |
|
if (new[] { Tokens.Assign, Tokens.ColonAssign, Tokens.ConcatStringAssign, Tokens.DivAssign, |
|
Tokens.DivIntegerAssign, Tokens.MinusAssign, Tokens.PlusAssign, Tokens.PowerAssign, |
|
Tokens.ShiftLeftAssign, Tokens.ShiftRightAssign, Tokens.TimesAssign }.Contains(prevTokenKind)) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Reads the next token. |
|
/// </summary> |
|
/// <returns>A <see cref="Token"/> object.</returns> |
|
public Token NextToken() |
|
{ |
|
if (curToken == null) { // first call of NextToken() |
|
curToken = Next(); |
|
//Console.WriteLine("Tok:" + Tokens.GetTokenString(curToken.kind) + " --- " + curToken.val); |
|
return curToken; |
|
} |
|
|
|
lastToken = curToken; |
|
|
|
if (curToken.next == null) { |
|
curToken.next = Next(); |
|
} |
|
|
|
curToken = curToken.next; |
|
|
|
if (curToken.kind == Tokens.EOF && !(lastToken.kind == Tokens.EOL)) { // be sure that before EOF there is an EOL token |
|
curToken = new Token(Tokens.EOL, curToken.col, curToken.line, string.Empty); |
|
curToken.next = new Token(Tokens.EOF, curToken.col, curToken.line, string.Empty); |
|
} |
|
//Console.WriteLine("Tok:" + Tokens.GetTokenString(curToken.kind) + " --- " + curToken.val); |
|
return curToken; |
|
} |
|
|
|
#region VB Readers |
|
string ReadIdent(char ch) |
|
{ |
|
char typeCharacter; |
|
return ReadIdent(ch, out typeCharacter); |
|
} |
|
|
|
string ReadIdent(char ch, out char typeCharacter) |
|
{ |
|
typeCharacter = '\0'; |
|
|
|
if (ef.ReadXmlIdentifier) { |
|
ef.ReadXmlIdentifier = false; |
|
return ReadXmlIdent(ch); |
|
} |
|
|
|
sb.Length = 0; |
|
sb.Append(ch); |
|
int peek; |
|
while ((peek = ReaderPeek()) != -1 && (Char.IsLetterOrDigit(ch = (char)peek) || ch == '_')) { |
|
ReaderRead(); |
|
sb.Append(ch.ToString()); |
|
} |
|
if (peek == -1) { |
|
return sb.ToString(); |
|
} |
|
|
|
if ("%&@!#$".IndexOf((char)peek) != -1) { |
|
typeCharacter = (char)peek; |
|
ReaderRead(); |
|
if (typeCharacter == '!') { |
|
peek = ReaderPeek(); |
|
if (peek != -1 && (peek == '_' || peek == '[' || char.IsLetter((char)peek))) { |
|
misreadExclamationMarkAsTypeCharacter = true; |
|
} |
|
} |
|
} |
|
return sb.ToString(); |
|
} |
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1818:DoNotConcatenateStringsInsideLoops")] |
|
Token ReadDigit(char ch, int x) |
|
{ |
|
sb.Length = 0; |
|
sb.Append(ch); |
|
|
|
int y = Line; |
|
string digit = ""; |
|
if (ch != '&') { |
|
digit += ch; |
|
} |
|
|
|
bool isHex = false; |
|
bool isOct = false; |
|
bool isSingle = false; |
|
bool isDouble = false; |
|
bool isDecimal = false; |
|
|
|
if (ReaderPeek() == -1) { |
|
if (ch == '&') { |
|
errors.Error(Line, Col, String.Format("digit expected")); |
|
} |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString() ,ch - '0'); |
|
} |
|
if (ch == '.') { |
|
if (Char.IsDigit((char)ReaderPeek())) { |
|
isDouble = true; // double is default |
|
if (isHex || isOct) { |
|
errors.Error(Line, Col, String.Format("No hexadecimal or oktadecimal floating point values allowed")); |
|
} |
|
while (ReaderPeek() != -1 && Char.IsDigit((char)ReaderPeek())){ // read decimal digits beyond the dot |
|
digit += (char)ReaderRead(); |
|
} |
|
} |
|
} else if (ch == '&' && PeekUpperChar() == 'H') { |
|
const string hex = "0123456789ABCDEF"; |
|
sb.Append((char)ReaderRead()); // skip 'H' |
|
while (ReaderPeek() != -1 && hex.IndexOf(PeekUpperChar()) != -1) { |
|
ch = (char)ReaderRead(); |
|
sb.Append(ch); |
|
digit += Char.ToUpper(ch, CultureInfo.InvariantCulture); |
|
} |
|
isHex = true; |
|
} else if (ReaderPeek() != -1 && ch == '&' && PeekUpperChar() == 'O') { |
|
const string okt = "01234567"; |
|
sb.Append((char)ReaderRead()); // skip 'O' |
|
while (ReaderPeek() != -1 && okt.IndexOf(PeekUpperChar()) != -1) { |
|
ch = (char)ReaderRead(); |
|
sb.Append(ch); |
|
digit += Char.ToUpper(ch, CultureInfo.InvariantCulture); |
|
} |
|
isOct = true; |
|
} else { |
|
while (ReaderPeek() != -1 && Char.IsDigit((char)ReaderPeek())) { |
|
ch = (char)ReaderRead();; |
|
digit += ch; |
|
sb.Append(ch); |
|
} |
|
} |
|
|
|
if (digit.Length == 0) { |
|
errors.Error(Line, Col, String.Format("digit expected")); |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), 0); |
|
} |
|
|
|
if (ReaderPeek() != -1 && "%&SILU".IndexOf(PeekUpperChar()) != -1 || isHex || isOct) { |
|
bool unsigned = false; |
|
if (ReaderPeek() != -1) { |
|
ch = (char)ReaderPeek(); |
|
sb.Append(ch); |
|
ch = Char.ToUpper(ch, CultureInfo.InvariantCulture); |
|
unsigned = ch == 'U'; |
|
if (unsigned) { |
|
ReaderRead(); // read the U |
|
ch = (char)ReaderPeek(); |
|
sb.Append(ch); |
|
ch = Char.ToUpper(ch, CultureInfo.InvariantCulture); |
|
if (ch != 'I' && ch != 'L' && ch != 'S') { |
|
errors.Error(Line, Col, "Invalid type character: U" + ch); |
|
} |
|
} |
|
} |
|
try { |
|
if (isOct) { |
|
ReaderRead(); |
|
ulong number = 0L; |
|
for (int i = 0; i < digit.Length; ++i) { |
|
number = number * 8 + digit[i] - '0'; |
|
} |
|
if (ch == 'S') { |
|
if (unsigned) |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), (ushort)number); |
|
else |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), (short)number); |
|
} else if (ch == '%' || ch == 'I') { |
|
if (unsigned) |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), (uint)number); |
|
else |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), (int)number); |
|
} else if (ch == '&' || ch == 'L') { |
|
if (unsigned) |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), (ulong)number); |
|
else |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), (long)number); |
|
} else { |
|
if (number > uint.MaxValue) { |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), unchecked((long)number)); |
|
} else { |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), unchecked((int)number)); |
|
} |
|
} |
|
} |
|
if (ch == 'S') { |
|
ReaderRead(); |
|
if (unsigned) |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), UInt16.Parse(digit, isHex ? NumberStyles.HexNumber : NumberStyles.Number)); |
|
else |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), Int16.Parse(digit, isHex ? NumberStyles.HexNumber : NumberStyles.Number)); |
|
} else if (ch == '%' || ch == 'I') { |
|
ReaderRead(); |
|
if (unsigned) |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), UInt32.Parse(digit, isHex ? NumberStyles.HexNumber : NumberStyles.Number)); |
|
else |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), Int32.Parse(digit, isHex ? NumberStyles.HexNumber : NumberStyles.Number)); |
|
} else if (ch == '&' || ch == 'L') { |
|
ReaderRead(); |
|
if (unsigned) |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), UInt64.Parse(digit, isHex ? NumberStyles.HexNumber : NumberStyles.Number)); |
|
else |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), Int64.Parse(digit, isHex ? NumberStyles.HexNumber : NumberStyles.Number)); |
|
} else if (isHex) { |
|
ulong number = UInt64.Parse(digit, NumberStyles.HexNumber); |
|
if (number > uint.MaxValue) { |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), unchecked((long)number)); |
|
} else { |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), unchecked((int)number)); |
|
} |
|
} |
|
} catch (OverflowException ex) { |
|
errors.Error(Line, Col, ex.Message); |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), 0); |
|
} catch (FormatException) { |
|
errors.Error(Line, Col, String.Format("{0} is not a parseable number", digit)); |
|
return new Token(Tokens.LiteralInteger, x, y, sb.ToString(), 0); |
|
} |
|
} |
|
Token nextToken = null; // if we accidently read a 'dot' |
|
if (!isDouble && ReaderPeek() == '.') { // read floating point number |
|
ReaderRead(); |
|
if (ReaderPeek() != -1 && Char.IsDigit((char)ReaderPeek())) { |
|
isDouble = true; // double is default |
|
if (isHex || isOct) { |
|
errors.Error(Line, Col, String.Format("No hexadecimal or oktadecimal floating point values allowed")); |
|
} |
|
digit += '.'; |
|
while (ReaderPeek() != -1 && Char.IsDigit((char)ReaderPeek())){ // read decimal digits beyond the dot |
|
digit += (char)ReaderRead(); |
|
} |
|
} else { |
|
nextToken = new Token(Tokens.Dot, Col - 1, Line); |
|
} |
|
} |
|
|
|
if (ReaderPeek() != -1 && PeekUpperChar() == 'E') { // read exponent |
|
isDouble = true; |
|
digit += (char)ReaderRead(); |
|
if (ReaderPeek() != -1 && (ReaderPeek() == '-' || ReaderPeek() == '+')) { |
|
digit += (char)ReaderRead(); |
|
} |
|
while (ReaderPeek() != -1 && Char.IsDigit((char)ReaderPeek())) { // read exponent value |
|
digit += (char)ReaderRead(); |
|
} |
|
} |
|
|
|
if (ReaderPeek() != -1) { |
|
switch (PeekUpperChar()) { |
|
case 'R': |
|
case '#': |
|
ReaderRead(); |
|
isDouble = true; |
|
break; |
|
case 'D': |
|
case '@': |
|
ReaderRead(); |
|
isDecimal = true; |
|
break; |
|
case 'F': |
|
case '!': |
|
ReaderRead(); |
|
isSingle = true; |
|
break; |
|
} |
|
} |
|
|
|
try { |
|
if (isSingle) { |
|
return new Token(Tokens.LiteralSingle, x, y, sb.ToString(), Single.Parse(digit, CultureInfo.InvariantCulture)); |
|
} |
|
if (isDecimal) { |
|
return new Token(Tokens.LiteralDecimal, x, y, sb.ToString(), Decimal.Parse(digit, NumberStyles.Currency | NumberStyles.AllowExponent, CultureInfo.InvariantCulture)); |
|
} |
|
if (isDouble) { |
|
return new Token(Tokens.LiteralDouble, x, y, sb.ToString(), Double.Parse(digit, CultureInfo.InvariantCulture)); |
|
} |
|
} catch (FormatException) { |
|
errors.Error(Line, Col, String.Format("{0} is not a parseable number", digit)); |
|
if (isSingle) |
|
return new Token(Tokens.LiteralSingle, x, y, sb.ToString(), 0f); |
|
if (isDecimal) |
|
return new Token(Tokens.LiteralDecimal, x, y, sb.ToString(), 0m); |
|
if (isDouble) |
|
return new Token(Tokens.LiteralDouble, x, y, sb.ToString(), 0.0); |
|
} |
|
Token token; |
|
try { |
|
token = new Token(Tokens.LiteralInteger, x, y, sb.ToString(), Int32.Parse(digit, isHex ? NumberStyles.HexNumber : NumberStyles.Number)); |
|
} catch (Exception) { |
|
try { |
|
token = new Token(Tokens.LiteralInteger, x, y, sb.ToString(), Int64.Parse(digit, isHex ? NumberStyles.HexNumber : NumberStyles.Number)); |
|
} catch (FormatException) { |
|
errors.Error(Line, Col, String.Format("{0} is not a parseable number", digit)); |
|
// fallback, when nothing helps :) |
|
token = new Token(Tokens.LiteralInteger, x, y, sb.ToString(), 0); |
|
} catch (OverflowException) { |
|
errors.Error(Line, Col, String.Format("{0} is too long for a integer literal", digit)); |
|
// fallback, when nothing helps :) |
|
token = new Token(Tokens.LiteralInteger, x, y, sb.ToString(), 0); |
|
} |
|
} |
|
token.next = nextToken; |
|
return token; |
|
} |
|
|
|
void ReadPreprocessorDirective() |
|
{ |
|
TextLocation start = new TextLocation(Line, Col - 1); |
|
string directive = ReadIdent('#'); |
|
// TODO : expression parser for PP directives |
|
// needed for proper conversion to e. g. C# |
|
string argument = ReadToEndOfLine(); |
|
// this.specialTracker.AddPreprocessingDirective(new PreprocessingDirective(directive, argument.Trim(), start, new AstLocation(start.Line, start.Column + directive.Length + argument.Length))); |
|
} |
|
|
|
string ReadDate() |
|
{ |
|
char ch = '\0'; |
|
sb.Length = 0; |
|
int nextChar; |
|
while ((nextChar = ReaderRead()) != -1) { |
|
ch = (char)nextChar; |
|
if (ch == '#') { |
|
break; |
|
} else if (ch == '\n') { |
|
errors.Error(Line, Col, String.Format("No return allowed inside Date literal")); |
|
} else { |
|
sb.Append(ch); |
|
} |
|
} |
|
if (ch != '#') { |
|
errors.Error(Line, Col, String.Format("End of File reached before Date literal terminated")); |
|
} |
|
return sb.ToString(); |
|
} |
|
|
|
string ReadString() |
|
{ |
|
char ch = '\0'; |
|
sb.Length = 0; |
|
int nextChar; |
|
while ((nextChar = ReaderRead()) != -1) { |
|
ch = (char)nextChar; |
|
if (ch == '"') { |
|
if (ReaderPeek() != -1 && ReaderPeek() == '"') { |
|
sb.Append('"'); |
|
ReaderRead(); |
|
} else { |
|
break; |
|
} |
|
} else if (ch == '\n') { |
|
errors.Error(Line, Col, String.Format("No return allowed inside String literal")); |
|
} else { |
|
sb.Append(ch); |
|
} |
|
} |
|
if (ch != '"') { |
|
errors.Error(Line, Col, String.Format("End of File reached before String terminated ")); |
|
} |
|
return sb.ToString(); |
|
} |
|
|
|
void ReadComment() |
|
{ |
|
TextLocation startPos = new TextLocation(Line, Col); |
|
sb.Length = 0; |
|
StringBuilder curWord = specialCommentHash != null ? new StringBuilder() : null; |
|
int missingApostrophes = 2; // no. of ' missing until it is a documentation comment |
|
int nextChar; |
|
while ((nextChar = ReaderRead()) != -1) { |
|
char ch = (char)nextChar; |
|
|
|
if (HandleLineEnd(ch)) { |
|
break; |
|
} |
|
|
|
sb.Append(ch); |
|
|
|
if (missingApostrophes > 0) { |
|
if (ch == '\'' || ch == '\u2018' || ch == '\u2019') { |
|
if (--missingApostrophes == 0) { |
|
// specialTracker.StartComment(CommentType.Documentation, isAtLineBegin, startPos); |
|
sb.Length = 0; |
|
} |
|
} else { |
|
// specialTracker.StartComment(CommentType.SingleLine, isAtLineBegin, startPos); |
|
missingApostrophes = 0; |
|
} |
|
} |
|
|
|
if (specialCommentHash != null) { |
|
if (Char.IsLetter(ch)) { |
|
curWord.Append(ch); |
|
} else { |
|
string tag = curWord.ToString(); |
|
curWord.Length = 0; |
|
if (specialCommentHash.ContainsKey(tag)) { |
|
TextLocation p = new TextLocation(Line, Col); |
|
string comment = ch + ReadToEndOfLine(); |
|
// this.TagComments.Add(new TagComment(tag, comment, isAtLineBegin, p, new Location(Col, Line))); |
|
sb.Append(comment); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
// if (missingApostrophes > 0) { |
|
// specialTracker.StartComment(CommentType.SingleLine, isAtLineBegin, startPos); |
|
// } |
|
// specialTracker.AddString(sb.ToString()); |
|
// specialTracker.FinishComment(new Location(Col, Line)); |
|
} |
|
|
|
Token ReadOperator(char ch) |
|
{ |
|
int x = Col - 1; |
|
int y = Line; |
|
switch(ch) { |
|
case '+': |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.PlusAssign, x, y); |
|
default: |
|
break; |
|
} |
|
return new Token(Tokens.Plus, x, y); |
|
case '-': |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.MinusAssign, x, y); |
|
default: |
|
break; |
|
} |
|
return new Token(Tokens.Minus, x, y); |
|
case '*': |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.TimesAssign, x, y); |
|
default: |
|
break; |
|
} |
|
return new Token(Tokens.Times, x, y, "*"); |
|
case '/': |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.DivAssign, x, y); |
|
default: |
|
break; |
|
} |
|
return new Token(Tokens.Div, x, y); |
|
case '\\': |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.DivIntegerAssign, x, y); |
|
default: |
|
break; |
|
} |
|
return new Token(Tokens.DivInteger, x, y); |
|
case '&': |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.ConcatStringAssign, x, y); |
|
default: |
|
break; |
|
} |
|
return new Token(Tokens.ConcatString, x, y); |
|
case '^': |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.PowerAssign, x, y); |
|
default: |
|
break; |
|
} |
|
return new Token(Tokens.Power, x, y); |
|
case ':': |
|
if (ReaderPeek() == '=') { |
|
ReaderRead(); |
|
return new Token(Tokens.ColonAssign, x, y); |
|
} |
|
return new Token(Tokens.Colon, x, y); |
|
case '=': |
|
return new Token(Tokens.Assign, x, y); |
|
case '<': |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.LessEqual, x, y); |
|
case '>': |
|
ReaderRead(); |
|
return new Token(Tokens.NotEqual, x, y); |
|
case '<': |
|
ReaderRead(); |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.ShiftLeftAssign, x, y); |
|
default: |
|
break; |
|
} |
|
return new Token(Tokens.ShiftLeft, x, y); |
|
} |
|
return new Token(Tokens.LessThan, x, y); |
|
case '>': |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.GreaterEqual, x, y); |
|
case '>': |
|
ReaderRead(); |
|
if (ReaderPeek() != -1) { |
|
switch (ReaderPeek()) { |
|
case '=': |
|
ReaderRead(); |
|
return new Token(Tokens.ShiftRightAssign, x, y); |
|
default: |
|
break; |
|
} |
|
} |
|
return new Token(Tokens.ShiftRight, x, y); |
|
} |
|
return new Token(Tokens.GreaterThan, x, y); |
|
case ',': |
|
return new Token(Tokens.Comma, x, y); |
|
case '.': |
|
// Prevent OverflowException when Peek returns -1 |
|
int tmp = ReaderPeek(); int tmp2 = ReaderPeek(1); |
|
if (tmp > 0) { |
|
if (char.IsDigit((char)tmp)) |
|
return ReadDigit('.', Col); |
|
else if ((char)tmp == '@') { |
|
ReaderRead(); |
|
return new Token(Tokens.DotAt, x, y); |
|
} else if ((char)tmp == '.' && tmp2 > 0 && (char)tmp2 == '.') { |
|
ReaderRead(); ReaderRead(); |
|
return new Token(Tokens.TripleDot, x, y); |
|
} |
|
} |
|
return new Token(Tokens.Dot, x, y); |
|
case '(': |
|
return new Token(Tokens.OpenParenthesis, x, y); |
|
case ')': |
|
return new Token(Tokens.CloseParenthesis, x, y); |
|
case '{': |
|
return new Token(Tokens.OpenCurlyBrace, x, y); |
|
case '}': |
|
return new Token(Tokens.CloseCurlyBrace, x, y); |
|
case '?': |
|
return new Token(Tokens.QuestionMark, x, y); |
|
case '!': |
|
return new Token(Tokens.ExclamationMark, x, y); |
|
} |
|
return null; |
|
} |
|
#endregion |
|
|
|
#region XML Readers |
|
Token ReadXmlProcessingInstruction(int x, int y) |
|
{ |
|
sb.Length = 0; |
|
int nextChar = -1; |
|
|
|
while (ReaderPeek() != '?' || ReaderPeek(1) != '>') { |
|
nextChar = ReaderRead(); |
|
if (nextChar == -1) |
|
break; |
|
sb.Append((char)nextChar); |
|
} |
|
|
|
ReaderSkip("?>".Length); |
|
|
|
return new Token(Tokens.XmlProcessingInstruction, new TextLocation(y, x), new TextLocation(Line, Col), sb.ToString(), null); |
|
} |
|
|
|
Token ReadXmlCommentOrCData(int x, int y) |
|
{ |
|
sb.Length = 0; |
|
int nextChar = -1; |
|
|
|
if (string.CompareOrdinal(ReaderPeekString("--".Length), "--") == 0) { |
|
ReaderSkip("--".Length); |
|
while ((nextChar = ReaderRead()) != -1) { |
|
sb.Append((char)nextChar); |
|
if (string.CompareOrdinal(ReaderPeekString("-->".Length), "-->") == 0) { |
|
ReaderSkip("-->".Length); |
|
return new Token(Tokens.XmlComment, new TextLocation(y, x), new TextLocation(Line, Col), sb.ToString(), null); |
|
} |
|
} |
|
} |
|
|
|
if (string.CompareOrdinal(ReaderPeekString("[CDATA[".Length), "[CDATA[") == 0) { |
|
ReaderSkip("[CDATA[".Length); |
|
while ((nextChar = ReaderRead()) != -1) { |
|
sb.Append((char)nextChar); |
|
if (string.CompareOrdinal(ReaderPeekString("]]>".Length), "]]>") == 0) { |
|
ReaderSkip("]]>".Length); |
|
return new Token(Tokens.XmlCData, new TextLocation(y, x), new TextLocation(Line, Col), sb.ToString(), null); |
|
} |
|
} |
|
} |
|
|
|
return new Token(Tokens.XmlComment, new TextLocation(y, x), new TextLocation(Line, Col), sb.ToString(), null); |
|
} |
|
|
|
string ReadXmlContent(char ch) |
|
{ |
|
sb.Length = 0; |
|
while (true) { |
|
sb.Append(ch); |
|
int next = ReaderPeek(); |
|
|
|
if (next == -1 || next == '<') |
|
break; |
|
ch = (char)ReaderRead(); |
|
} |
|
|
|
return sb.ToString(); |
|
} |
|
|
|
string ReadXmlString(char terminator) |
|
{ |
|
char ch = '\0'; |
|
sb.Length = 0; |
|
int nextChar; |
|
while ((nextChar = ReaderRead()) != -1) { |
|
ch = (char)nextChar; |
|
if (ch == terminator) { |
|
break; |
|
} else if (ch == '\n') { |
|
errors.Error(Line, Col, String.Format("No return allowed inside String literal")); |
|
} else { |
|
sb.Append(ch); |
|
} |
|
} |
|
if (ch != terminator) { |
|
errors.Error(Line, Col, String.Format("End of File reached before String terminated ")); |
|
} |
|
return sb.ToString(); |
|
} |
|
|
|
string ReadXmlIdent(char ch) |
|
{ |
|
sb.Length = 0; |
|
sb.Append(ch); |
|
|
|
int peek; |
|
|
|
while ((peek = ReaderPeek()) != -1 && (peek == ':' || XmlConvert.IsNCNameChar((char)peek))) { |
|
sb.Append((char)ReaderRead()); |
|
} |
|
|
|
return sb.ToString(); |
|
} |
|
#endregion |
|
|
|
char PeekUpperChar() |
|
{ |
|
return Char.ToUpper((char)ReaderPeek(), CultureInfo.InvariantCulture); |
|
} |
|
|
|
/// <summary> |
|
/// Skips to the end of the current code block. |
|
/// For this, the lexer must have read the next token AFTER the token opening the |
|
/// block (so that Lexer.Token is the block-opening token, not Lexer.LookAhead). |
|
/// After the call, Lexer.LookAhead will be the block-closing token. |
|
/// </summary> |
|
public void SkipCurrentBlock(int targetToken) |
|
{ |
|
int lastKind = -1; |
|
int kind = lastToken.kind; |
|
while (kind != Tokens.EOF && |
|
!(lastKind == Tokens.End && kind == targetToken)) |
|
{ |
|
lastKind = kind; |
|
NextToken(); |
|
kind = lastToken.kind; |
|
} |
|
} |
|
|
|
public void SetInitialContext(SnippetType type) |
|
{ |
|
ef.SetContext(type); |
|
} |
|
|
|
public VBLexerMemento Export() |
|
{ |
|
return new VBLexerMemento() { |
|
Column = Col, |
|
Line = Line, |
|
EncounteredLineContinuation = encounteredLineContinuation, |
|
ExpressionFinder = ef.Export(), |
|
InXmlMode = inXmlMode, |
|
IsAtLineBegin = isAtLineBegin, |
|
LineEnd = lineEnd, |
|
PrevTokenKind = prevToken.kind, |
|
MisreadExclamationMarkAsTypeCharacter = misreadExclamationMarkAsTypeCharacter, |
|
XmlModeInfoStack = new Stack<XmlModeInfo>(xmlModeStack.Select(i => (XmlModeInfo)i.Clone()).Reverse()) |
|
}; |
|
} |
|
|
|
LATextReader reader; |
|
int col = 1; |
|
int line = 1; |
|
|
|
protected Errors errors = new Errors(); |
|
|
|
protected Token lastToken = null; |
|
protected Token curToken = null; |
|
protected Token peekToken = null; |
|
|
|
string[] specialCommentTags = null; |
|
protected Hashtable specialCommentHash = null; |
|
// List<TagComment> tagComments = new List<TagComment>(); |
|
protected StringBuilder sb = new StringBuilder(); |
|
// protected SpecialTracker specialTracker = new SpecialTracker(); |
|
|
|
// used for the original value of strings (with escape sequences). |
|
protected StringBuilder originalValue = new StringBuilder(); |
|
|
|
public bool SkipAllComments { get; set; } |
|
public bool EvaluateConditionalCompilation { get; set; } |
|
public virtual IDictionary<string, object> ConditionalCompilationSymbols { |
|
get { throw new NotSupportedException(); } |
|
} |
|
|
|
protected static IEnumerable<string> GetSymbols (string symbols) |
|
{ |
|
if (!string.IsNullOrEmpty(symbols)) { |
|
foreach (string symbol in symbols.Split (';', ' ', '\t')) { |
|
string s = symbol.Trim (); |
|
if (s.Length == 0) |
|
continue; |
|
yield return s; |
|
} |
|
} |
|
} |
|
|
|
public void SetConditionalCompilationSymbols (string symbols) |
|
{ |
|
throw new NotSupportedException (); |
|
} |
|
|
|
protected int Line { |
|
get { |
|
return line; |
|
} |
|
} |
|
protected int Col { |
|
get { |
|
return col; |
|
} |
|
} |
|
|
|
protected bool recordRead = false; |
|
protected StringBuilder recordedText = new StringBuilder (); |
|
|
|
protected int ReaderRead() |
|
{ |
|
int val = reader.Read(); |
|
if (recordRead && val >= 0) |
|
recordedText.Append ((char)val); |
|
if ((val == '\r' && reader.Peek() != '\n') || val == '\n') { |
|
++line; |
|
col = 1; |
|
LineBreak(); |
|
} else if (val >= 0) { |
|
col++; |
|
} |
|
return val; |
|
} |
|
|
|
protected int ReaderPeek() |
|
{ |
|
return reader.Peek(); |
|
} |
|
|
|
protected int ReaderPeek(int step) |
|
{ |
|
return reader.Peek(step); |
|
} |
|
|
|
protected void ReaderSkip(int steps) |
|
{ |
|
for (int i = 0; i < steps; i++) { |
|
ReaderRead(); |
|
} |
|
} |
|
|
|
protected string ReaderPeekString(int length) |
|
{ |
|
StringBuilder builder = new StringBuilder(); |
|
|
|
for (int i = 0; i < length; i++) { |
|
int peek = ReaderPeek(i); |
|
if (peek != -1) |
|
builder.Append((char)peek); |
|
} |
|
|
|
return builder.ToString(); |
|
} |
|
|
|
public void SetInitialLocation(TextLocation location) |
|
{ |
|
if (lastToken != null || curToken != null || peekToken != null) |
|
throw new InvalidOperationException(); |
|
this.line = location.Line; |
|
this.col = location.Column; |
|
} |
|
|
|
public Errors Errors { |
|
get { |
|
return errors; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Returns the comments that had been read and containing tag key words. |
|
/// </summary> |
|
// public List<TagComment> TagComments { |
|
// get { |
|
// return tagComments; |
|
// } |
|
// } |
|
|
|
// public SpecialTracker SpecialTracker { |
|
// get { |
|
// return specialTracker; |
|
// } |
|
// } |
|
|
|
/// <summary> |
|
/// Special comment tags are tags like TODO, HACK or UNDONE which are read by the lexer and stored in <see cref="TagComments"/>. |
|
/// </summary> |
|
public string[] SpecialCommentTags { |
|
get { |
|
return specialCommentTags; |
|
} |
|
set { |
|
specialCommentTags = value; |
|
specialCommentHash = null; |
|
if (specialCommentTags != null && specialCommentTags.Length > 0) { |
|
specialCommentHash = new Hashtable(); |
|
foreach (string str in specialCommentTags) { |
|
specialCommentHash.Add(str, null); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// The current Token. <seealso cref="ICSharpCode.NRefactory.VB.Parser.Token"/> |
|
/// </summary> |
|
public Token Token { |
|
get { |
|
// Console.WriteLine("Call to Token"); |
|
return lastToken; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// The next Token (The <see cref="Token"/> after <see cref="NextToken"/> call) . <seealso cref="ICSharpCode.NRefactory.VB.Parser.Token"/> |
|
/// </summary> |
|
public Token LookAhead { |
|
get { |
|
// Console.WriteLine("Call to LookAhead"); |
|
return curToken; |
|
} |
|
} |
|
|
|
#region System.IDisposable interface implementation |
|
public virtual void Dispose() |
|
{ |
|
reader.Close(); |
|
reader = null; |
|
errors = null; |
|
lastToken = curToken = peekToken = null; |
|
specialCommentHash = null; |
|
sb = originalValue = null; |
|
} |
|
#endregion |
|
|
|
/// <summary> |
|
/// Must be called before a peek operation. |
|
/// </summary> |
|
public void StartPeek() |
|
{ |
|
peekToken = curToken; |
|
} |
|
|
|
/// <summary> |
|
/// Gives back the next token. A second call to Peek() gives the next token after the last call for Peek() and so on. |
|
/// </summary> |
|
/// <returns>An <see cref="Token"/> object.</returns> |
|
public Token Peek() |
|
{ |
|
// Console.WriteLine("Call to Peek"); |
|
if (peekToken.next == null) { |
|
peekToken.next = Next(); |
|
} |
|
peekToken = peekToken.next; |
|
return peekToken; |
|
} |
|
|
|
protected static bool IsIdentifierPart(int ch) |
|
{ |
|
if (ch == 95) return true; // 95 = '_' |
|
if (ch == -1) return false; |
|
return char.IsLetterOrDigit((char)ch); // accept unicode letters |
|
} |
|
|
|
protected static bool IsHex(char digit) |
|
{ |
|
return Char.IsDigit(digit) || ('A' <= digit && digit <= 'F') || ('a' <= digit && digit <= 'f'); |
|
} |
|
|
|
protected int GetHexNumber(char digit) |
|
{ |
|
if (Char.IsDigit(digit)) { |
|
return digit - '0'; |
|
} |
|
if ('A' <= digit && digit <= 'F') { |
|
return digit - 'A' + 0xA; |
|
} |
|
if ('a' <= digit && digit <= 'f') { |
|
return digit - 'a' + 0xA; |
|
} |
|
errors.Error(line, col, String.Format("Invalid hex number '" + digit + "'")); |
|
return 0; |
|
} |
|
protected TextLocation lastLineEnd = new TextLocation(1, 1); |
|
protected TextLocation curLineEnd = new TextLocation(1, 1); |
|
protected void LineBreak () |
|
{ |
|
lastLineEnd = curLineEnd; |
|
curLineEnd = new TextLocation (line, col - 1); |
|
} |
|
protected bool HandleLineEnd(char ch) |
|
{ |
|
// Handle MS-DOS or MacOS line ends. |
|
if (ch == '\r') { |
|
if (reader.Peek() == '\n') { // MS-DOS line end '\r\n' |
|
ReaderRead(); // LineBreak (); called by ReaderRead (); |
|
return true; |
|
} else { // assume MacOS line end which is '\r' |
|
LineBreak (); |
|
return true; |
|
} |
|
} |
|
if (ch == '\n') { |
|
LineBreak (); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
protected void SkipToEndOfLine() |
|
{ |
|
int nextChar; |
|
while ((nextChar = reader.Read()) != -1) { |
|
if (nextChar == '\r') { |
|
if (reader.Peek() == '\n') |
|
reader.Read(); |
|
nextChar = '\n'; |
|
} |
|
if (nextChar == '\n') { |
|
++line; |
|
col = 1; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
protected string ReadToEndOfLine() |
|
{ |
|
sb.Length = 0; |
|
int nextChar; |
|
while ((nextChar = reader.Read()) != -1) { |
|
char ch = (char)nextChar; |
|
|
|
if (nextChar == '\r') { |
|
if (reader.Peek() == '\n') |
|
reader.Read(); |
|
nextChar = '\n'; |
|
} |
|
// Return read string, if EOL is reached |
|
if (nextChar == '\n') { |
|
++line; |
|
col = 1; |
|
return sb.ToString(); |
|
} |
|
|
|
sb.Append(ch); |
|
} |
|
|
|
// Got EOF before EOL |
|
string retStr = sb.ToString(); |
|
col += retStr.Length; |
|
return retStr; |
|
} |
|
|
|
public event EventHandler<SavepointEventArgs> SavepointReached; |
|
|
|
protected virtual void OnSavepointReached(SavepointEventArgs e) |
|
{ |
|
if (SavepointReached != null) { |
|
SavepointReached(this, e); |
|
} |
|
} |
|
} |
|
}
|
|
|