.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
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

// 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);
}
}
}
}