.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.
 
 
 
 

1111 lines
25 KiB

// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable disable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace ICSharpCode.ILSpyX.Search
{
class LATextReader : TextReader
{
List<int> buffer;
TextReader reader;
public LATextReader(TextReader reader)
{
this.buffer = new List<int>();
this.reader = reader;
}
public override int Peek()
{
return Peek(0);
}
public override int Read()
{
int c = Peek();
buffer.RemoveAt(0);
return c;
}
public int Peek(int step)
{
while (step >= buffer.Count)
{
buffer.Add(reader.Read());
}
if (step < 0)
return -1;
return buffer[step];
}
protected override void Dispose(bool disposing)
{
if (disposing)
reader.Dispose();
base.Dispose(disposing);
}
}
enum TokenKind : byte
{
EOF,
Literal,
Identifier,
Symbol,
}
enum LiteralFormat : byte
{
None,
DecimalNumber,
HexadecimalNumber,
OctalNumber,
StringLiteral,
VerbatimStringLiteral,
CharLiteral
}
class Literal
{
internal readonly TokenKind tokenKind;
internal readonly LiteralFormat literalFormat;
internal readonly object literalValue;
internal readonly string val;
internal Literal next;
public TokenKind TokenKind {
get { return tokenKind; }
}
public LiteralFormat LiteralFormat {
get { return literalFormat; }
}
public object LiteralValue {
get { return literalValue; }
}
public string Value {
get { return val; }
}
public Literal(string val, TokenKind tokenKind)
{
this.val = val;
this.tokenKind = tokenKind;
}
public Literal(string val, object literalValue, LiteralFormat literalFormat)
{
this.val = val;
this.literalValue = literalValue;
this.literalFormat = literalFormat;
this.tokenKind = TokenKind.Literal;
}
}
internal abstract class AbstractLexer : IDisposable
{
LATextReader reader;
int col = 1;
int line = 1;
protected Literal lastToken = null;
protected Literal curToken = null;
protected Literal peekToken = null;
protected StringBuilder sb = new StringBuilder();
// used for the original value of strings (with escape sequences).
protected StringBuilder originalValue = new StringBuilder();
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();
}
/// <summary>
/// The current Token. <seealso cref="ICSharpCode.NRefactory.Parser.Token"/>
/// </summary>
public Literal Token {
get {
return lastToken;
}
}
/// <summary>
/// The next Token (The <see cref="Token"/> after <see cref="NextToken"/> call) . <seealso cref="ICSharpCode.NRefactory.Parser.Token"/>
/// </summary>
public Literal LookAhead {
get {
return curToken;
}
}
/// <summary>
/// Constructor for the abstract lexer class.
/// </summary>
protected AbstractLexer(TextReader reader)
{
this.reader = new LATextReader(reader);
}
#region System.IDisposable interface implementation
public virtual void Dispose()
{
reader.Close();
reader = null;
lastToken = curToken = peekToken = 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 Literal Peek()
{
// Debug.WriteLine("Call to Peek");
if (peekToken.next == null)
{
peekToken.next = Next();
}
peekToken = peekToken.next;
return peekToken;
}
/// <summary>
/// Reads the next token and gives it back.
/// </summary>
/// <returns>An <see cref="Token"/> object.</returns>
public virtual Literal NextToken()
{
if (curToken == null)
{
curToken = Next();
// Debug.WriteLine(ICSharpCode.NRefactory.Parser.CSharp.Tokens.GetTokenString(curToken.kind) + " -- " + curToken.val + "(" + curToken.kind + ")");
return curToken;
}
lastToken = curToken;
if (curToken.next == null)
{
curToken.next = Next();
}
curToken = curToken.next;
// Debug.WriteLine(ICSharpCode.NRefactory.Parser.CSharp.Tokens.GetTokenString(curToken.kind) + " -- " + curToken.val + "(" + curToken.kind + ")");
return curToken;
}
protected abstract Literal Next();
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;
}
return 0;
}
protected void LineBreak()
{
}
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;
}
}
internal sealed class Lexer : AbstractLexer
{
public Lexer(TextReader reader) : base(reader)
{
}
protected override Literal Next()
{
char ch;
while (true)
{
int nextChar = ReaderRead();
if (nextChar == -1)
break;
Literal token = null;
switch (nextChar)
{
case ' ':
case '\t':
continue;
case '\r':
case '\n':
HandleLineEnd((char)nextChar);
continue;
case '"':
token = ReadString();
break;
case '\'':
token = ReadChar();
break;
case '@':
int next = ReaderRead();
if (next == -1)
{
Error(Line, Col, String.Format("EOF after @"));
continue;
}
else
{
int x = Col - 1;
int y = Line;
ch = (char)next;
if (ch == '"')
{
token = ReadVerbatimString();
}
else if (Char.IsLetterOrDigit(ch) || ch == '_')
{
string s = ReadIdent(ch, out _);
return new Literal(s, TokenKind.Identifier);
}
else
{
HandleLineEnd(ch);
Error(y, x, String.Format("Unexpected char in Lexer.Next() : {0}", ch));
continue;
}
}
break;
default: // non-ws chars are handled here
ch = (char)nextChar;
if (Char.IsLetter(ch) || ch == '_' || ch == '\\')
{
int x = Col - 1; // Col was incremented above, but we want the start of the identifier
int y = Line;
string s = ReadIdent(ch, out _);
return new Literal(s, TokenKind.Identifier);
}
else if (Char.IsDigit(ch))
{
token = ReadDigit(ch, Col - 1);
}
break;
}
// try error recovery (token = null -> continue with next char)
if (token != null)
{
return token;
}
}
return new Literal(null, TokenKind.EOF);
}
// The C# compiler has a fixed size length therefore we'll use a fixed size char array for identifiers
// it's also faster than using a string builder.
const int MAX_IDENTIFIER_LENGTH = 512;
char[] identBuffer = new char[MAX_IDENTIFIER_LENGTH];
string ReadIdent(char ch, out bool canBeKeyword)
{
int peek;
int curPos = 0;
canBeKeyword = true;
while (true)
{
if (ch == '\\')
{
peek = ReaderPeek();
if (peek != 'u' && peek != 'U')
{
Error(Line, Col, "Identifiers can only contain unicode escape sequences");
}
canBeKeyword = false;
string surrogatePair;
ReadEscapeSequence(out ch, out surrogatePair);
if (surrogatePair != null)
{
if (!char.IsLetterOrDigit(surrogatePair, 0))
{
Error(Line, Col, "Unicode escape sequences in identifiers cannot be used to represent characters that are invalid in identifiers");
}
for (int i = 0; i < surrogatePair.Length - 1; i++)
{
if (curPos < MAX_IDENTIFIER_LENGTH)
{
identBuffer[curPos++] = surrogatePair[i];
}
}
ch = surrogatePair[surrogatePair.Length - 1];
}
else
{
if (!IsIdentifierPart(ch))
{
Error(Line, Col, "Unicode escape sequences in identifiers cannot be used to represent characters that are invalid in identifiers");
}
}
}
if (curPos < MAX_IDENTIFIER_LENGTH)
{
if (ch != '\0') // only add character, if it is valid
// prevents \ from being added
identBuffer[curPos++] = ch;
}
else
{
Error(Line, Col, String.Format("Identifier too long"));
while (IsIdentifierPart(ReaderPeek()))
{
ReaderRead();
}
break;
}
peek = ReaderPeek();
if (IsIdentifierPart(peek) || peek == '\\')
{
ch = (char)ReaderRead();
}
else
{
break;
}
}
return new String(identBuffer, 0, curPos);
}
Literal ReadDigit(char ch, int x)
{
unchecked
{ // prevent exception when ReaderPeek() = -1 is cast to char
int y = Line;
sb.Length = 0;
sb.Append(ch);
string prefix = null;
string suffix = null;
bool ishex = false;
bool isunsigned = false;
bool islong = false;
bool isfloat = false;
bool isdouble = false;
bool isdecimal = false;
char peek = (char)ReaderPeek();
if (ch == '.')
{
isdouble = true;
while (Char.IsDigit((char)ReaderPeek()))
{ // read decimal digits beyond the dot
sb.Append((char)ReaderRead());
}
peek = (char)ReaderPeek();
}
else if (ch == '0' && (peek == 'x' || peek == 'X'))
{
ReaderRead(); // skip 'x'
sb.Length = 0; // Remove '0' from 0x prefix from the stringvalue
while (IsHex((char)ReaderPeek()))
{
sb.Append((char)ReaderRead());
}
if (sb.Length == 0)
{
sb.Append('0'); // dummy value to prevent exception
Error(y, x, "Invalid hexadecimal integer literal");
}
ishex = true;
prefix = "0x";
peek = (char)ReaderPeek();
}
else
{
while (Char.IsDigit((char)ReaderPeek()))
{
sb.Append((char)ReaderRead());
}
peek = (char)ReaderPeek();
}
Literal nextToken = null; // if we accidently read a 'dot'
if (peek == '.')
{ // read floating point number
ReaderRead();
peek = (char)ReaderPeek();
if (!Char.IsDigit(peek))
{
nextToken = new Literal(".", TokenKind.Symbol);
peek = '.';
}
else
{
isdouble = true; // double is default
if (ishex)
{
Error(y, x, "No hexadecimal floating point values allowed");
}
sb.Append('.');
while (Char.IsDigit((char)ReaderPeek()))
{ // read decimal digits beyond the dot
sb.Append((char)ReaderRead());
}
peek = (char)ReaderPeek();
}
}
if (peek == 'e' || peek == 'E')
{ // read exponent
isdouble = true;
sb.Append((char)ReaderRead());
peek = (char)ReaderPeek();
if (peek == '-' || peek == '+')
{
sb.Append((char)ReaderRead());
}
while (Char.IsDigit((char)ReaderPeek()))
{ // read exponent value
sb.Append((char)ReaderRead());
}
isunsigned = true;
peek = (char)ReaderPeek();
}
if (peek == 'f' || peek == 'F')
{ // float value
ReaderRead();
suffix = "f";
isfloat = true;
}
else if (peek == 'd' || peek == 'D')
{ // double type suffix (obsolete, double is default)
ReaderRead();
suffix = "d";
isdouble = true;
}
else if (peek == 'm' || peek == 'M')
{ // decimal value
ReaderRead();
suffix = "m";
isdecimal = true;
}
else if (!isdouble)
{
if (peek == 'u' || peek == 'U')
{
ReaderRead();
suffix = "u";
isunsigned = true;
peek = (char)ReaderPeek();
}
if (peek == 'l' || peek == 'L')
{
ReaderRead();
peek = (char)ReaderPeek();
islong = true;
if (!isunsigned && (peek == 'u' || peek == 'U'))
{
ReaderRead();
suffix = "Lu";
isunsigned = true;
}
else
{
suffix = isunsigned ? "uL" : "L";
}
}
}
string digit = sb.ToString();
string stringValue = prefix + digit + suffix;
if (isfloat)
{
float num;
if (float.TryParse(digit, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
return new Literal(stringValue, num, LiteralFormat.DecimalNumber);
}
else
{
Error(y, x, String.Format("Can't parse float {0}", digit));
return new Literal(stringValue, 0f, LiteralFormat.DecimalNumber);
}
}
if (isdecimal)
{
decimal num;
if (decimal.TryParse(digit, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
return new Literal(stringValue, num, LiteralFormat.DecimalNumber);
}
else
{
Error(y, x, String.Format("Can't parse decimal {0}", digit));
return new Literal(stringValue, 0m, LiteralFormat.DecimalNumber);
}
}
if (isdouble)
{
double num;
if (double.TryParse(digit, NumberStyles.Any, CultureInfo.InvariantCulture, out num))
{
return new Literal(stringValue, num, LiteralFormat.DecimalNumber);
}
else
{
Error(y, x, String.Format("Can't parse double {0}", digit));
return new Literal(stringValue, 0d, LiteralFormat.DecimalNumber);
}
}
// Try to determine a parsable value using ranges.
ulong result;
if (ishex)
{
if (!ulong.TryParse(digit, NumberStyles.HexNumber, null, out result))
{
Error(y, x, String.Format("Can't parse hexadecimal constant {0}", digit));
return new Literal(stringValue.ToString(), 0, LiteralFormat.HexadecimalNumber);
}
}
else
{
if (!ulong.TryParse(digit, NumberStyles.Integer, null, out result))
{
Error(y, x, String.Format("Can't parse integral constant {0}", digit));
return new Literal(stringValue.ToString(), 0, LiteralFormat.DecimalNumber);
}
}
if (result > long.MaxValue)
{
islong = true;
isunsigned = true;
}
else if (result > uint.MaxValue)
{
islong = true;
}
else if (islong == false && result > int.MaxValue)
{
isunsigned = true;
}
Literal token;
LiteralFormat literalFormat = ishex ? LiteralFormat.HexadecimalNumber : LiteralFormat.DecimalNumber;
if (islong)
{
if (isunsigned)
{
ulong num;
if (ulong.TryParse(digit, ishex ? NumberStyles.HexNumber : NumberStyles.Number, CultureInfo.InvariantCulture, out num))
{
token = new Literal(stringValue, num, literalFormat);
}
else
{
Error(y, x, String.Format("Can't parse unsigned long {0}", digit));
token = new Literal(stringValue, 0UL, literalFormat);
}
}
else
{
long num;
if (long.TryParse(digit, ishex ? NumberStyles.HexNumber : NumberStyles.Number, CultureInfo.InvariantCulture, out num))
{
token = new Literal(stringValue, num, literalFormat);
}
else
{
Error(y, x, String.Format("Can't parse long {0}", digit));
token = new Literal(stringValue, 0L, literalFormat);
}
}
}
else
{
if (isunsigned)
{
uint num;
if (uint.TryParse(digit, ishex ? NumberStyles.HexNumber : NumberStyles.Number, CultureInfo.InvariantCulture, out num))
{
token = new Literal(stringValue, num, literalFormat);
}
else
{
Error(y, x, String.Format("Can't parse unsigned int {0}", digit));
token = new Literal(stringValue, (uint)0, literalFormat);
}
}
else
{
int num;
if (int.TryParse(digit, ishex ? NumberStyles.HexNumber : NumberStyles.Number, CultureInfo.InvariantCulture, out num))
{
token = new Literal(stringValue, num, literalFormat);
}
else
{
Error(y, x, String.Format("Can't parse int {0}", digit));
token = new Literal(stringValue, 0, literalFormat);
}
}
}
token.next = nextToken;
return token;
}
}
Literal ReadString()
{
int x = Col - 1;
int y = Line;
sb.Length = 0;
originalValue.Length = 0;
originalValue.Append('"');
bool doneNormally = false;
int nextChar;
while ((nextChar = ReaderRead()) != -1)
{
char ch = (char)nextChar;
if (ch == '"')
{
doneNormally = true;
originalValue.Append('"');
break;
}
if (ch == '\\')
{
originalValue.Append('\\');
string surrogatePair;
originalValue.Append(ReadEscapeSequence(out ch, out surrogatePair));
if (surrogatePair != null)
{
sb.Append(surrogatePair);
}
else
{
sb.Append(ch);
}
}
else if (HandleLineEnd(ch))
{
// call HandleLineEnd to ensure line numbers are still correct after the error
Error(y, x, "No new line is allowed inside a string literal");
break;
}
else
{
originalValue.Append(ch);
sb.Append(ch);
}
}
if (!doneNormally)
{
Error(y, x, "End of file reached inside string literal");
}
return new Literal(originalValue.ToString(), sb.ToString(), LiteralFormat.StringLiteral);
}
Literal ReadVerbatimString()
{
sb.Length = 0;
originalValue.Length = 0;
originalValue.Append("@\"");
int nextChar;
while ((nextChar = ReaderRead()) != -1)
{
char ch = (char)nextChar;
if (ch == '"')
{
if (ReaderPeek() != '"')
{
originalValue.Append('"');
break;
}
originalValue.Append("\"\"");
sb.Append('"');
ReaderRead();
}
else if (HandleLineEnd(ch))
{
sb.Append("\r\n");
originalValue.Append("\r\n");
}
else
{
sb.Append(ch);
originalValue.Append(ch);
}
}
if (nextChar == -1)
{
Error(Line, Col, "End of file reached inside verbatim string literal");
}
return new Literal(originalValue.ToString(), sb.ToString(), LiteralFormat.VerbatimStringLiteral);
}
readonly char[] escapeSequenceBuffer = new char[12];
/// <summary>
/// reads an escape sequence
/// </summary>
/// <param name="ch">The character represented by the escape sequence,
/// or '\0' if there was an error or the escape sequence represents a character that
/// can be represented only be a suggorate pair</param>
/// <param name="surrogatePair">Null, except when the character represented
/// by the escape sequence can only be represented by a surrogate pair (then the string
/// contains the surrogate pair)</param>
/// <returns>The escape sequence</returns>
string ReadEscapeSequence(out char ch, out string surrogatePair)
{
surrogatePair = null;
int nextChar = ReaderRead();
if (nextChar == -1)
{
Error(Line, Col, "End of file reached inside escape sequence");
ch = '\0';
return String.Empty;
}
int number;
char c = (char)nextChar;
int curPos = 1;
escapeSequenceBuffer[0] = c;
switch (c)
{
case '\'':
ch = '\'';
break;
case '\"':
ch = '\"';
break;
case '\\':
ch = '\\';
break;
case '0':
ch = '\0';
break;
case 'a':
ch = '\a';
break;
case 'b':
ch = '\b';
break;
case 'f':
ch = '\f';
break;
case 'n':
ch = '\n';
break;
case 'r':
ch = '\r';
break;
case 't':
ch = '\t';
break;
case 'v':
ch = '\v';
break;
case 'u':
case 'x':
// 16 bit unicode character
c = (char)ReaderRead();
number = GetHexNumber(c);
escapeSequenceBuffer[curPos++] = c;
if (number < 0)
{
Error(Line, Col - 1, String.Format("Invalid char in literal : {0}", c));
}
for (int i = 0; i < 3; ++i)
{
if (IsHex((char)ReaderPeek()))
{
c = (char)ReaderRead();
int idx = GetHexNumber(c);
escapeSequenceBuffer[curPos++] = c;
number = 16 * number + idx;
}
else
{
break;
}
}
ch = (char)number;
break;
case 'U':
// 32 bit unicode character
number = 0;
for (int i = 0; i < 8; ++i)
{
if (IsHex((char)ReaderPeek()))
{
c = (char)ReaderRead();
int idx = GetHexNumber(c);
escapeSequenceBuffer[curPos++] = c;
number = 16 * number + idx;
}
else
{
Error(Line, Col - 1, String.Format("Invalid char in literal : {0}", (char)ReaderPeek()));
break;
}
}
if (number > 0xffff)
{
ch = '\0';
surrogatePair = char.ConvertFromUtf32(number);
}
else
{
ch = (char)number;
}
break;
default:
Error(Line, Col, String.Format("Unexpected escape sequence : {0}", c));
ch = '\0';
break;
}
return new String(escapeSequenceBuffer, 0, curPos);
}
Literal ReadChar()
{
int x = Col - 1;
int y = Line;
int nextChar = ReaderRead();
if (nextChar == -1 || HandleLineEnd((char)nextChar))
{
return null;
}
char ch = (char)nextChar;
char chValue = ch;
string escapeSequence = String.Empty;
if (ch == '\\')
{
string surrogatePair;
escapeSequence = ReadEscapeSequence(out chValue, out surrogatePair);
if (surrogatePair != null)
{
Error(y, x, "The unicode character must be represented by a surrogate pair and does not fit into a System.Char");
}
}
unchecked
{
if ((char)ReaderRead() != '\'')
{
Error(y, x, "Char not terminated");
}
}
return new Literal("'" + ch + escapeSequence + "'", chValue, LiteralFormat.CharLiteral);
}
void Error(int y, int x, string message)
{
}
}
}