lexical-parser (#15)
add: 词法分析器剩下数字、标识符的细节处理以及错误处理 Co-authored-by: duqoo <92306417+duqoo@users.noreply.github.com> Reviewed-on: PostGuard/Canon#15 Co-authored-by: Huaps <1183155719@qq.com> Co-committed-by: Huaps <1183155719@qq.com>
This commit is contained in:
parent
ccd1899739
commit
c4189fd1b2
11
Canon.Core/Enums/ErrorEnums.cs
Normal file
11
Canon.Core/Enums/ErrorEnums.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace Canon.Core.Enums;
|
||||||
|
|
||||||
|
public enum LexemeErrorType
|
||||||
|
{
|
||||||
|
IllegalNumberFormat,//数字格式不正确
|
||||||
|
UnknownCharacterOrString,//源代码包含无法识别的字符或字符串
|
||||||
|
UnclosedStringLiteral,//字符串字面量未闭合
|
||||||
|
UnclosedComment,//注释未闭合
|
||||||
|
InvalidEscapeSequence,//无效的转义字符
|
||||||
|
IllegalOperator,//非法的操作符
|
||||||
|
}
|
|
@ -93,7 +93,7 @@ public enum StateType
|
||||||
Word,
|
Word,
|
||||||
Digit,
|
Digit,
|
||||||
Delimiter,
|
Delimiter,
|
||||||
Other
|
Operator
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BasicIdType
|
public enum BasicIdType
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
namespace Canon.Core.Exceptions;
|
namespace Canon.Core.Exceptions;
|
||||||
|
using Enums;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 词法分析中引发的异常
|
/// 词法分析中引发的异常
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LexemeException : Exception
|
public class LexemeException : Exception
|
||||||
{
|
{
|
||||||
|
public LexemeErrorType ErrorType { get; }
|
||||||
|
public uint Line { get; }
|
||||||
|
public uint CharPosition { get; }
|
||||||
public LexemeException() { }
|
public LexemeException() { }
|
||||||
|
|
||||||
public LexemeException(string message) : base(message) { }
|
public LexemeException(string message) : base(message) { }
|
||||||
|
@ -11,15 +15,20 @@ public class LexemeException : Exception
|
||||||
public LexemeException(string message, Exception innerException) :
|
public LexemeException(string message, Exception innerException) :
|
||||||
base(message, innerException) { }
|
base(message, innerException) { }
|
||||||
|
|
||||||
|
/// <param name="errorType">错误类型</param>
|
||||||
/// <param name="line">单词的行号</param>
|
/// <param name="line">单词的行号</param>
|
||||||
/// <param name="charPosition">单词的列号</param>
|
/// <param name="charPosition">单词的列号</param>
|
||||||
/// <param name="message">错误信息</param>
|
/// <param name="message">错误信息</param>
|
||||||
public LexemeException(uint line, uint charPosition, string message) :
|
public LexemeException(LexemeErrorType errorType, uint line, uint charPosition, string message) :
|
||||||
base("line:" + line + ", charPosition:" + charPosition + " :" + message) { }
|
base("line:" + line + ", charPosition:" + charPosition + " :" + message)
|
||||||
|
{
|
||||||
public LexemeException(uint line, uint charPosition, Exception innerException) :
|
ErrorType = errorType;
|
||||||
base("line:" + line + ", charPosition:" + charPosition + " : ", innerException) { }
|
Line = line;
|
||||||
|
CharPosition = charPosition;
|
||||||
public LexemeException(uint line, uint charPosition, string message, Exception innerException) :
|
}
|
||||||
base("line:" + line + ", charPosition:" + charPosition + " :" + message, innerException) { }
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"LexemeException: ErrorType={ErrorType}, Line={Line}, CharPosition={CharPosition}, Message={Message}\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Canon.Core.Enums;
|
using Canon.Core.Enums;
|
||||||
|
using Canon.Core.Exceptions;
|
||||||
|
|
||||||
namespace Canon.Core.LexicalParser;
|
namespace Canon.Core.LexicalParser;
|
||||||
|
|
||||||
|
@ -19,25 +21,29 @@ public class Lexer(string source)
|
||||||
|
|
||||||
private readonly string[] _delimiter = [";", ",", ":", ".", "(", ")", "[", "]", "'", "\"", ".."];
|
private readonly string[] _delimiter = [";", ",", ":", ".", "(", ")", "[", "]", "'", "\"", ".."];
|
||||||
|
|
||||||
|
private readonly string[] _operator = ["=", "<>", "<", "<=", ">", ">=", "+", "-", "*", "/", ":="];
|
||||||
|
|
||||||
// 状态机
|
// 状态机
|
||||||
private StateType _state;
|
private StateType _state;
|
||||||
private char _ch;
|
private char _ch;
|
||||||
|
|
||||||
private LinkedList<char> _token = new LinkedList<char>();
|
private LinkedList<char> _token = new LinkedList<char>();
|
||||||
|
|
||||||
// bool save;
|
// bool save;
|
||||||
// int saved_state;
|
// int saved_state;
|
||||||
bool _finish;
|
bool _finish;
|
||||||
private bool eof;
|
|
||||||
|
|
||||||
//缓冲区
|
//缓冲区
|
||||||
private readonly char[] _buffer = new char[2048];
|
private readonly char[] _buffer = new char[2048];
|
||||||
|
|
||||||
// int start_pos;
|
// int start_pos;
|
||||||
private int _fwdPos;
|
private int _fwdPos;
|
||||||
|
|
||||||
// 计数器
|
// 计数器
|
||||||
private uint _line = 1;
|
private uint _line = 1;
|
||||||
private uint _chPos;
|
private uint _chPos;
|
||||||
private int _sourcePos;
|
|
||||||
private readonly Dictionary<SemanticTokenType, int> _tokenCount = new Dictionary<SemanticTokenType, int>
|
private readonly Dictionary<SemanticTokenType, int> _tokenCount = new Dictionary<SemanticTokenType, int>
|
||||||
{
|
{
|
||||||
{ SemanticTokenType.Keyword, 0 },
|
{ SemanticTokenType.Keyword, 0 },
|
||||||
|
@ -57,39 +63,76 @@ public class Lexer(string source)
|
||||||
// 缓冲区
|
// 缓冲区
|
||||||
// start_pos = 0;
|
// start_pos = 0;
|
||||||
_fwdPos = 0;
|
_fwdPos = 0;
|
||||||
FillLeftBuffer();
|
|
||||||
|
|
||||||
// 状态机
|
// 状态机
|
||||||
_finish = false;
|
_finish = false;
|
||||||
|
|
||||||
while (!_finish) {
|
while (!_finish)
|
||||||
|
{
|
||||||
GetChar();
|
GetChar();
|
||||||
GetNbc();
|
GetNbc();
|
||||||
|
if (_finish) break;
|
||||||
|
|
||||||
_token = new LinkedList<char>();
|
_token = new LinkedList<char>();
|
||||||
|
|
||||||
if (IsLetter()) {
|
if (IsLetter())
|
||||||
|
{
|
||||||
_state = StateType.Word;
|
_state = StateType.Word;
|
||||||
}
|
}
|
||||||
else if (IsDigit()) {
|
else if(_ch == '.')
|
||||||
|
{
|
||||||
|
char next = PeekNextChar();
|
||||||
|
if (next >= '0' && next <= '9')
|
||||||
|
{
|
||||||
_state = StateType.Digit;
|
_state = StateType.Digit;
|
||||||
}
|
}
|
||||||
else if (IsDelimiter()) {
|
|
||||||
_state = StateType.Delimiter;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_state = StateType.Other;
|
_state = StateType.Delimiter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IsDigit() || _ch == '$')
|
||||||
|
{
|
||||||
|
_state = StateType.Digit;
|
||||||
|
}
|
||||||
|
else if (IsDelimiter())
|
||||||
|
{
|
||||||
|
_state = StateType.Delimiter;
|
||||||
|
}
|
||||||
|
else if (_ch == '{')
|
||||||
|
{
|
||||||
|
GetChar();
|
||||||
|
while (_ch != '}')
|
||||||
|
{
|
||||||
|
GetChar();
|
||||||
|
if (_ch == '\n')
|
||||||
|
{
|
||||||
|
_line++;
|
||||||
|
_chPos = 0;
|
||||||
|
}
|
||||||
|
if (_finish)
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.UnclosedComment, _line, _chPos, "The comment is not closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_state = StateType.Operator;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (_state)
|
switch (_state)
|
||||||
{
|
{
|
||||||
case StateType.Word: {
|
case StateType.Word:
|
||||||
while (IsDigit() || IsLetter())
|
while (IsDigit() || IsLetter())
|
||||||
{
|
{
|
||||||
Cat();
|
Cat();
|
||||||
GetChar();
|
GetChar();
|
||||||
}
|
}
|
||||||
|
|
||||||
Retract();
|
Retract();
|
||||||
|
|
||||||
if (IsKeyword())
|
if (IsKeyword())
|
||||||
|
@ -98,81 +141,15 @@ public class Lexer(string source)
|
||||||
KeywordSemanticToken.GetKeywordTypeByKeyword(LinkedListToString(_token.First));
|
KeywordSemanticToken.GetKeywordTypeByKeyword(LinkedListToString(_token.First));
|
||||||
MakeToken(keywordType);
|
MakeToken(keywordType);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
MakeToken(SemanticTokenType.Identifier);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case StateType.Digit:
|
|
||||||
{
|
|
||||||
bool error = false;
|
|
||||||
bool tag = false; // 用于标记是否已经处理过科学记数法的指数部分
|
|
||||||
bool doubleDot = false;
|
|
||||||
NumberType numberType = NumberType.Integer;
|
|
||||||
|
|
||||||
while (IsDigit() || _ch == '.' || _ch == 'E' || _ch == '+' || _ch == '-' || _ch == 'e' || IsLetter()) {
|
|
||||||
if (_ch != '.')
|
|
||||||
{
|
|
||||||
Cat();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (_ch == '0' && !tag) {
|
|
||||||
GetChar();
|
|
||||||
if (_ch == 'x' || _ch == 'X') {
|
|
||||||
numberType = NumberType.Hex; // 标识十六进制
|
|
||||||
Cat();
|
|
||||||
while (IsHexDigit()) { // 假设IsHexDigit方法能够识别十六进制数字
|
|
||||||
Cat();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Retract(); // 如果不是'x'或'X',回退一个字符
|
|
||||||
}
|
|
||||||
else if (_ch == '.') {
|
|
||||||
GetChar();
|
|
||||||
if (_ch == '.') {
|
|
||||||
Retract(); // 回退到第一个'.'
|
|
||||||
Retract(); // 回退到'.'之前的数字
|
|
||||||
doubleDot = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Retract();
|
|
||||||
Cat();
|
|
||||||
numberType = NumberType.Real;
|
|
||||||
}
|
|
||||||
else if ((_ch == 'e' || _ch == 'E') && !tag) {
|
|
||||||
GetChar();
|
|
||||||
if (IsDigit() || _ch == '+' || _ch == '-') {
|
|
||||||
Cat();
|
|
||||||
tag = true; // 已处理指数部分
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
error = true; // 错误的科学记数法
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetChar();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!error) {
|
|
||||||
MakeToken(numberType);
|
|
||||||
if (doubleDot)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Retract();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Retract();
|
MakeToken(SemanticTokenType.Identifier);
|
||||||
PrintError(0,_token.First,_line);
|
|
||||||
_tokenCount[SemanticTokenType.Error]++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case StateType.Digit:
|
||||||
|
DealNumber();
|
||||||
|
break;
|
||||||
case StateType.Delimiter:
|
case StateType.Delimiter:
|
||||||
Cat();
|
Cat();
|
||||||
switch (_ch)
|
switch (_ch)
|
||||||
|
@ -186,22 +163,21 @@ public class Lexer(string source)
|
||||||
MakeToken(DelimiterType.DoubleDots);
|
MakeToken(DelimiterType.DoubleDots);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Retract();
|
Retract();
|
||||||
if (IsPeriod())
|
if (IsDot())
|
||||||
{
|
{
|
||||||
|
MakeToken(DelimiterType.Dot);
|
||||||
}else if (IsDot())
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
|
MakeToken(DelimiterType.Period);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '\'':
|
case '\'':
|
||||||
case '\"':
|
case '\"':
|
||||||
{
|
{
|
||||||
if(_ch == '\'') MakeToken(DelimiterType.SingleQuotation);
|
|
||||||
else if(_ch == '\"') MakeToken(DelimiterType.DoubleQuotation);
|
|
||||||
|
|
||||||
// 重置_token,准备收集字符串内容
|
// 重置_token,准备收集字符串内容
|
||||||
_token = new LinkedList<char>();
|
_token = new LinkedList<char>();
|
||||||
|
|
||||||
|
@ -210,29 +186,18 @@ public class Lexer(string source)
|
||||||
{
|
{
|
||||||
Cat(); // 收集字符
|
Cat(); // 收集字符
|
||||||
GetChar(); // 移动到下一个字符
|
GetChar(); // 移动到下一个字符
|
||||||
|
if (_ch == '\n' || _finish)
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.UnclosedStringLiteral, _line, _chPos, "The String is not closed.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在退出循环时,_ch为'或EOF,此时_token包含字符串内容
|
|
||||||
// 创建字符内容的token,注意这里使用SemanticTokenType.String表示字符串字面量
|
|
||||||
MakeToken(SemanticTokenType.Character); // 或其它适用于字符串字面量的SemanticTokenType
|
MakeToken(SemanticTokenType.Character); // 或其它适用于字符串字面量的SemanticTokenType
|
||||||
_token = new LinkedList<char>(); // 重置_token
|
_token = new LinkedList<char>(); // 重置_token
|
||||||
|
|
||||||
if (_ch == '\'' && _ch != '\n')
|
if (!(_ch == '\'' || _ch == '\"'))
|
||||||
{
|
{
|
||||||
// 识别并创建最后一个单引号的token
|
throw new LexemeException(LexemeErrorType.UnclosedStringLiteral, _line, _chPos, "The String is not closed.");
|
||||||
Cat();
|
|
||||||
MakeToken(DelimiterType.SingleQuotation);
|
|
||||||
}
|
|
||||||
else if (_ch == '\"')
|
|
||||||
{
|
|
||||||
Cat();
|
|
||||||
MakeToken(DelimiterType.DoubleQuotation);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 这里处理遇到EOF但没有闭合单引号的情况,例如:'字符串结尾没有单引号
|
|
||||||
// 可以添加错误处理代码
|
|
||||||
PrintError(0, _token.First, _line); // 假设这个方法用于打印错误
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -240,13 +205,56 @@ public class Lexer(string source)
|
||||||
MakeToken(DelimiterType.Comma);
|
MakeToken(DelimiterType.Comma);
|
||||||
break;
|
break;
|
||||||
case ':':
|
case ':':
|
||||||
|
char nextChar = PeekNextChar();
|
||||||
|
if (nextChar == '=')
|
||||||
|
{
|
||||||
|
GetChar();
|
||||||
|
Cat();
|
||||||
|
MakeToken(OperatorType.Assign);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
MakeToken(DelimiterType.Colon);
|
MakeToken(DelimiterType.Colon);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ';':
|
case ';':
|
||||||
MakeToken(DelimiterType.Semicolon);
|
MakeToken(DelimiterType.Semicolon);
|
||||||
break;
|
break;
|
||||||
case '(':
|
case '(':
|
||||||
|
char next = PeekNextChar();
|
||||||
|
if (next == '*')
|
||||||
|
{
|
||||||
|
GetChar();
|
||||||
|
bool commentClosed = false;
|
||||||
|
while (!commentClosed)
|
||||||
|
{
|
||||||
|
GetNbc();
|
||||||
|
GetChar();
|
||||||
|
while (_ch != '*')
|
||||||
|
{
|
||||||
|
GetNbc();
|
||||||
|
GetChar();
|
||||||
|
if (_finish)
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.UnclosedComment, _line, _chPos, "The comment is not closed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GetChar();
|
||||||
|
if (_finish)
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.UnclosedComment, _line, _chPos, "The comment is not closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ch == ')') commentClosed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
MakeToken(DelimiterType.LeftParenthesis);
|
MakeToken(DelimiterType.LeftParenthesis);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ')':
|
case ')':
|
||||||
MakeToken(DelimiterType.RightParenthesis);
|
MakeToken(DelimiterType.RightParenthesis);
|
||||||
|
@ -258,9 +266,9 @@ public class Lexer(string source)
|
||||||
MakeToken(DelimiterType.RightSquareBracket);
|
MakeToken(DelimiterType.RightSquareBracket);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case StateType.Other:
|
break;
|
||||||
|
case StateType.Operator:
|
||||||
DealOther();
|
DealOther();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -268,23 +276,181 @@ public class Lexer(string source)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
PrintResult();
|
|
||||||
return _tokens;
|
return _tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DealNumber()
|
||||||
|
{
|
||||||
|
// 十六进制
|
||||||
|
if (_ch == '$')
|
||||||
|
{
|
||||||
|
Cat();
|
||||||
|
|
||||||
|
GetChar();
|
||||||
|
while (!NumberShouldBreak())
|
||||||
|
{
|
||||||
|
// 假设IsHexDigit方法能够识别十六进制数字
|
||||||
|
if (IsHexDigit())
|
||||||
|
{
|
||||||
|
Cat();
|
||||||
|
GetChar();
|
||||||
|
}
|
||||||
|
else if(NumberShouldBreak())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.IllegalNumberFormat, _line, _chPos, "Illegal hex numbers!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MakeToken(NumberType.Hex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非十六进制
|
||||||
|
if(IsDigit() || _ch == '.')
|
||||||
|
{
|
||||||
|
while (!NumberShouldBreak())
|
||||||
|
{
|
||||||
|
// 含小数部分
|
||||||
|
if (_ch == '.')
|
||||||
|
{
|
||||||
|
// 检查是否是符号 “..”
|
||||||
|
char next = PeekNextChar();
|
||||||
|
if (next == '.')
|
||||||
|
{
|
||||||
|
Retract();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不是符号 “..”,进入小数点后的判断
|
||||||
|
Cat(); // 记录“.”
|
||||||
|
|
||||||
|
// “.”后不应为空,至少应该有一位小数
|
||||||
|
GetChar();
|
||||||
|
if (NumberShouldBreak())
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.IllegalNumberFormat, _line, _chPos, "Illegal numbers!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取小数点后的数字
|
||||||
|
while (!NumberShouldBreak())
|
||||||
|
{
|
||||||
|
if (IsDigit())
|
||||||
|
{
|
||||||
|
Cat();
|
||||||
|
GetChar();
|
||||||
|
}
|
||||||
|
else if (_ch == 'e' || _ch == 'E')
|
||||||
|
{
|
||||||
|
DealE();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(NumberShouldBreak())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.IllegalNumberFormat, _line, _chPos, "Illegal number.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MakeToken(NumberType.Real);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不含小数部分,含科学计数法
|
||||||
|
if (_ch == 'e' || _ch == 'E')
|
||||||
|
{
|
||||||
|
DealE();
|
||||||
|
MakeToken(NumberType.Real);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂时为整数
|
||||||
|
if (IsDigit())
|
||||||
|
{
|
||||||
|
Cat();
|
||||||
|
GetChar();
|
||||||
|
}
|
||||||
|
else if(NumberShouldBreak())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.IllegalNumberFormat, _line, _chPos, "Illegal number.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MakeToken(NumberType.Integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DealE()
|
||||||
|
{
|
||||||
|
Cat();
|
||||||
|
GetChar();
|
||||||
|
if (IsDigit() || _ch == '+' || _ch == '-')
|
||||||
|
{
|
||||||
|
Cat();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.IllegalNumberFormat, _line, _chPos, "Illegal number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取e后的数字
|
||||||
|
GetChar();
|
||||||
|
while (!NumberShouldBreak())
|
||||||
|
{
|
||||||
|
if (IsDigit())
|
||||||
|
{
|
||||||
|
Cat();
|
||||||
|
GetChar();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new LexemeException(LexemeErrorType.IllegalNumberFormat, _line, _chPos, "Illegal number.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NumberShouldBreak()
|
||||||
|
{
|
||||||
|
if (_ch == ' ' || _ch == '\n' || _ch == '\t' || _ch == '\r' || (IsDelimiter() && _ch!='.') || IsOperator() || _finish)
|
||||||
|
{
|
||||||
|
Retract();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsOperator()
|
||||||
|
{
|
||||||
|
foreach (var o in _operator)
|
||||||
|
{
|
||||||
|
if (o.Contains(_ch))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsDot()
|
private bool IsDot()
|
||||||
|
{
|
||||||
|
if (_tokens.Count != 0)
|
||||||
{
|
{
|
||||||
SemanticToken tokenBefore = _tokens.Last();
|
SemanticToken tokenBefore = _tokens.Last();
|
||||||
if (tokenBefore.TokenType == SemanticTokenType.Identifier) return true;
|
if (tokenBefore.TokenType == SemanticTokenType.Identifier) return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsPeriod()
|
|
||||||
{
|
|
||||||
SemanticToken tokenBefore = _tokens.Last();
|
|
||||||
if (tokenBefore.TokenType == SemanticTokenType.Keyword) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DealOther()
|
private void DealOther()
|
||||||
{
|
{
|
||||||
|
@ -348,28 +514,8 @@ public class Lexer(string source)
|
||||||
MakeToken(OperatorType.Greater);
|
MakeToken(OperatorType.Greater);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ':':
|
|
||||||
Cat();
|
|
||||||
GetChar();
|
|
||||||
if (_ch == '=')
|
|
||||||
{
|
|
||||||
// 识别 :=
|
|
||||||
Cat();
|
|
||||||
MakeToken(OperatorType.Assign);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 这里应该被识别为delimiter逻辑上
|
|
||||||
Cat();
|
|
||||||
PrintError(1, _token.First, _line);
|
|
||||||
_tokenCount[SemanticTokenType.Error]++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
Cat();
|
throw new LexemeException(LexemeErrorType.UnknownCharacterOrString, _line, _chPos, "Illegal lexeme.");
|
||||||
PrintError(1, _token.First, _line);
|
|
||||||
_tokenCount[SemanticTokenType.Error]++;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,14 +542,6 @@ public class Lexer(string source)
|
||||||
};
|
};
|
||||||
token = identifierSemanticToken;
|
token = identifierSemanticToken;
|
||||||
break;
|
break;
|
||||||
case SemanticTokenType.Error:
|
|
||||||
ErrorSemanticToken errorSemanticToken = new ErrorSemanticToken()
|
|
||||||
{
|
|
||||||
LinePos = _line, CharacterPos = _chPos, LiteralValue = LinkedListToString(_token.First),
|
|
||||||
};
|
|
||||||
token = errorSemanticToken;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException(nameof(tokenType), tokenType, null);
|
throw new ArgumentOutOfRangeException(nameof(tokenType), tokenType, null);
|
||||||
}
|
}
|
||||||
|
@ -449,6 +587,32 @@ public class Lexer(string source)
|
||||||
Console.WriteLine(LinkedListToString(_token.First));
|
Console.WriteLine(LinkedListToString(_token.First));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MakeToken(NumberType numberType)
|
||||||
|
{
|
||||||
|
string temp = LinkedListToString(_token.First);
|
||||||
|
string result;
|
||||||
|
if (numberType == NumberType.Hex)
|
||||||
|
{
|
||||||
|
result = string.Concat("0x", temp.AsSpan(1, temp.Length - 1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberSemanticToken numberSemanticToken = new NumberSemanticToken()
|
||||||
|
{
|
||||||
|
LinePos = _line,
|
||||||
|
CharacterPos = _chPos,
|
||||||
|
LiteralValue = result,
|
||||||
|
NumberType = numberType
|
||||||
|
};
|
||||||
|
_tokens.Add(numberSemanticToken);
|
||||||
|
_tokenCount[SemanticTokenType.Number]++;
|
||||||
|
Console.WriteLine($"<{SemanticTokenType.Number}> <{numberType}>");
|
||||||
|
Console.WriteLine(LinkedListToString(_token.First));
|
||||||
|
}
|
||||||
|
|
||||||
private void MakeToken(OperatorType operatorType)
|
private void MakeToken(OperatorType operatorType)
|
||||||
{
|
{
|
||||||
OperatorSemanticToken operatorSemanticToken = new OperatorSemanticToken()
|
OperatorSemanticToken operatorSemanticToken = new OperatorSemanticToken()
|
||||||
|
@ -464,88 +628,20 @@ public class Lexer(string source)
|
||||||
Console.WriteLine(LinkedListToString(_token.First));
|
Console.WriteLine(LinkedListToString(_token.First));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MakeToken(NumberType numberType)
|
// 读取字符操作
|
||||||
{
|
|
||||||
NumberSemanticToken numberSemanticToken = new NumberSemanticToken()
|
|
||||||
{
|
|
||||||
LinePos = _line,
|
|
||||||
CharacterPos = _chPos,
|
|
||||||
LiteralValue = LinkedListToString(_token.First),
|
|
||||||
NumberType = numberType
|
|
||||||
};
|
|
||||||
_tokens.Add(numberSemanticToken);
|
|
||||||
_tokenCount[SemanticTokenType.Number]++;
|
|
||||||
Console.WriteLine($"<{SemanticTokenType.Number}> <{numberType}>");
|
|
||||||
Console.WriteLine(LinkedListToString(_token.First));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 填充buffer操作
|
|
||||||
private void FillLeftBuffer() {
|
|
||||||
//cout << "fill left" << endl;
|
|
||||||
for (int i = 0; i < _buffer.Length / 2; i++) {
|
|
||||||
_buffer[i] = '$';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保source字符串足够长,避免超出范围
|
|
||||||
int lengthToCopy = Math.Min(_buffer.Length / 2 - 1, source.Length - _sourcePos);
|
|
||||||
|
|
||||||
// 使用Array.Copy方法
|
|
||||||
Array.Copy(source.ToCharArray(), _sourcePos, _buffer, 0, lengthToCopy);
|
|
||||||
|
|
||||||
_sourcePos += lengthToCopy;
|
|
||||||
|
|
||||||
if (_sourcePos == source.Length) {
|
|
||||||
eof = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FillRightBuffer() {
|
|
||||||
//cout << "fill right" << endl;
|
|
||||||
for (int i = _buffer.Length / 2; i < _buffer.Length; i++) {
|
|
||||||
_buffer[i] = '$';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保source字符串足够长,避免超出范围
|
|
||||||
int lengthToCopy = Math.Min(_buffer.Length / 2 - 1, source.Length - _sourcePos);
|
|
||||||
|
|
||||||
// 使用Array.Copy方法
|
|
||||||
Array.Copy(source.ToCharArray(), _sourcePos, _buffer, _buffer.Length / 2, lengthToCopy);
|
|
||||||
|
|
||||||
_sourcePos += lengthToCopy;
|
|
||||||
|
|
||||||
if (_sourcePos == source.Length) {
|
|
||||||
eof = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PrintBuffer() {
|
|
||||||
for (int i = 0; i < _buffer.Length; i++) {
|
|
||||||
Console.WriteLine($"[{i}] {_buffer[i]}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DealEof() {
|
|
||||||
if (eof) _finish = true;
|
|
||||||
else if (_fwdPos < _buffer.Length / 2) {
|
|
||||||
FillRightBuffer();
|
|
||||||
_fwdPos = _buffer.Length / 2;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
FillLeftBuffer();
|
|
||||||
// start_pos = 0;
|
|
||||||
_fwdPos = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取buffer操作
|
|
||||||
void GetChar() {
|
void GetChar() {
|
||||||
if (_fwdPos >= 0 && _fwdPos < _buffer.Length) _ch = _buffer[_fwdPos];
|
if (_fwdPos >= 0 && _fwdPos < source.Length)
|
||||||
|
{
|
||||||
|
_ch = source[_fwdPos];
|
||||||
_chPos++;
|
_chPos++;
|
||||||
if (_ch == '$') {
|
_fwdPos++;
|
||||||
DealEof();
|
}
|
||||||
if (_fwdPos >= 0 && _fwdPos < _buffer.Length) _ch = _buffer[_fwdPos];
|
else if (_fwdPos == source.Length)
|
||||||
|
{
|
||||||
|
_ch = '\0';
|
||||||
|
_chPos++;
|
||||||
|
_finish = true;
|
||||||
}
|
}
|
||||||
if (_fwdPos < _buffer.Length) _fwdPos++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetNbc() {
|
private void GetNbc() {
|
||||||
|
@ -622,24 +718,25 @@ public class Lexer(string source)
|
||||||
{
|
{
|
||||||
if (delimiter.Contains(_ch))
|
if (delimiter.Contains(_ch))
|
||||||
{
|
{
|
||||||
if (_ch != ':')
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetChar();
|
|
||||||
if (_ch == '=')
|
|
||||||
{
|
|
||||||
Retract();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private char PeekNextChar()
|
||||||
|
{
|
||||||
|
// 确认下一个位置是否仍在buffer的范围内
|
||||||
|
if (_fwdPos < source.Length)
|
||||||
|
{
|
||||||
|
return source[_fwdPos];
|
||||||
|
}
|
||||||
|
return '\0';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void PrintToken(SemanticTokenType type, LinkedListNode<char> token, uint line)
|
private void PrintToken(SemanticTokenType type, LinkedListNode<char> token, uint line)
|
||||||
{
|
{
|
||||||
string tokenString = LinkedListToString(token);
|
string tokenString = LinkedListToString(token);
|
||||||
|
|
|
@ -4,12 +4,10 @@ namespace Canon.Core.LexicalParser;
|
||||||
|
|
||||||
using Enums;
|
using Enums;
|
||||||
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 词法记号基类
|
/// 词法记号基类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class SemanticToken
|
public abstract class SemanticToken : IEquatable<SemanticToken>
|
||||||
{
|
{
|
||||||
public abstract SemanticTokenType TokenType { get; }
|
public abstract SemanticTokenType TokenType { get; }
|
||||||
|
|
||||||
|
@ -59,7 +57,34 @@ public abstract class SemanticToken
|
||||||
LinePos = 0, CharacterPos = 0, LiteralValue = string.Empty
|
LinePos = 0, CharacterPos = 0, LiteralValue = string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
public override string ToString() => LiteralValue;
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"LinePos: {LinePos}, CharacterPos: {CharacterPos}, LiteralValue: {LiteralValue}, TokenType: {TokenType}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(SemanticToken? other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return LinePos == other.LinePos &&
|
||||||
|
CharacterPos == other.CharacterPos &&
|
||||||
|
LiteralValue == other.LiteralValue &&
|
||||||
|
TokenType == other.TokenType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is SemanticToken semanticTokenObj && Equals(semanticTokenObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return LinePos.GetHashCode() ^
|
||||||
|
CharacterPos.GetHashCode() ^
|
||||||
|
LiteralValue.GetHashCode() ^
|
||||||
|
TokenType.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -118,6 +143,11 @@ public class DelimiterSemanticToken : SemanticToken
|
||||||
};
|
};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode() ^ this.DelimiterType.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -218,6 +248,11 @@ public class KeywordSemanticToken : SemanticToken
|
||||||
token = null;
|
token = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode() ^ this.KeywordType.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -229,12 +264,44 @@ public class OperatorSemanticToken : SemanticToken
|
||||||
|
|
||||||
public required OperatorType OperatorType { get; init; }
|
public required OperatorType OperatorType { get; init; }
|
||||||
|
|
||||||
|
public static readonly Dictionary<string, OperatorType> OperatorTypes = new Dictionary<string, OperatorType>
|
||||||
|
{
|
||||||
|
{ "=", OperatorType.Equal },
|
||||||
|
{ "<>", OperatorType.NotEqual },
|
||||||
|
{ "<", OperatorType.Less },
|
||||||
|
{ "<=", OperatorType.LessEqual },
|
||||||
|
{ ">", OperatorType.Greater },
|
||||||
|
{ ">=", OperatorType.GreaterEqual },
|
||||||
|
{ "+", OperatorType.Plus },
|
||||||
|
{ "-", OperatorType.Minus },
|
||||||
|
{ "*", OperatorType.Multiply },
|
||||||
|
{ "/", OperatorType.Divide },
|
||||||
|
{ ":=", OperatorType.Assign }
|
||||||
|
};
|
||||||
|
|
||||||
|
public static OperatorType GetOperatorTypeByOperator(string operatorSymbol)
|
||||||
|
{
|
||||||
|
if (OperatorTypes.TryGetValue(operatorSymbol, out var operatorType))
|
||||||
|
{
|
||||||
|
return operatorType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Unknown operator: {operatorSymbol}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static bool TryParse(uint linePos, uint characterPos, LinkedListNode<char> now,
|
public static bool TryParse(uint linePos, uint characterPos, LinkedListNode<char> now,
|
||||||
out OperatorSemanticToken? token)
|
out OperatorSemanticToken? token)
|
||||||
{
|
{
|
||||||
token = null;
|
token = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode() ^ this.OperatorType.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -245,65 +312,10 @@ public class NumberSemanticToken : SemanticToken
|
||||||
public override SemanticTokenType TokenType => SemanticTokenType.Number;
|
public override SemanticTokenType TokenType => SemanticTokenType.Number;
|
||||||
|
|
||||||
public required NumberType NumberType { get; init; }
|
public required NumberType NumberType { get; init; }
|
||||||
public double Value { get; private init; }
|
|
||||||
|
|
||||||
public static bool TryParse(uint linePos, uint characterPos, LinkedListNode<char> now,
|
public override int GetHashCode()
|
||||||
out NumberSemanticToken? token)
|
|
||||||
{
|
{
|
||||||
StringBuilder buffer = new();
|
return base.GetHashCode() ^ this.NumberType.GetHashCode();
|
||||||
|
|
||||||
bool hasDecimalPoint = false;
|
|
||||||
bool hasExponent = false;
|
|
||||||
bool hasMinusSign = false;
|
|
||||||
|
|
||||||
while (now != null && (char.IsDigit(now.Value) || now.Value == '.' || now.Value == 'e' || now.Value == 'E' || now.Value == '-' || now.Value == '+'))
|
|
||||||
{
|
|
||||||
if (now.Value == '.')
|
|
||||||
{
|
|
||||||
if (hasDecimalPoint)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
hasDecimalPoint = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now.Value == 'e' || now.Value == 'E')
|
|
||||||
{
|
|
||||||
if (hasExponent)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
hasExponent = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now.Value == '-' || now.Value == '+')
|
|
||||||
{
|
|
||||||
if (hasMinusSign)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
hasMinusSign = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Append(now.Value);
|
|
||||||
now = now.Next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (double.TryParse(buffer.ToString(), out double value))
|
|
||||||
{
|
|
||||||
token = new NumberSemanticToken
|
|
||||||
{
|
|
||||||
LinePos = linePos,
|
|
||||||
CharacterPos = characterPos,
|
|
||||||
LiteralValue = buffer.ToString(),
|
|
||||||
Value = value,
|
|
||||||
NumberType = hasDecimalPoint || hasExponent ? NumberType.Real : NumberType.Integer
|
|
||||||
};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
token = null;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
54
Canon.Tests/LexicalParserTests/CharacterTypeTests.cs
Normal file
54
Canon.Tests/LexicalParserTests/CharacterTypeTests.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using Canon.Core.Enums;
|
||||||
|
using Canon.Core.LexicalParser;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Canon.Core.Exceptions;
|
||||||
|
|
||||||
|
namespace Canon.Tests.LexicalParserTests
|
||||||
|
{
|
||||||
|
public class CharacterTypeTests
|
||||||
|
{
|
||||||
|
private readonly ITestOutputHelper _testOutputHelper;
|
||||||
|
|
||||||
|
public CharacterTypeTests(ITestOutputHelper testOutputHelper)
|
||||||
|
{
|
||||||
|
_testOutputHelper = testOutputHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("'a'", "a")]
|
||||||
|
[InlineData("'Hello, World!'", "Hello, World!")]
|
||||||
|
|
||||||
|
public void TestCharacterType(string input, string? expectedResult)
|
||||||
|
{
|
||||||
|
Lexer lexer = new(input);
|
||||||
|
if (expectedResult == null)
|
||||||
|
{
|
||||||
|
Assert.Throws<LexemeException>(() => lexer.Tokenize());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<SemanticToken> tokens = lexer.Tokenize();
|
||||||
|
_testOutputHelper.WriteLine(tokens[0].LiteralValue);
|
||||||
|
Assert.Single(tokens);
|
||||||
|
Assert.Equal(SemanticTokenType.Character, tokens[0].TokenType);
|
||||||
|
Assert.Equal(expectedResult, tokens[0].LiteralValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
//[InlineData("'\\x'", 1, 2, LexemeException.LexemeErrorType.InvalidEscapeSequence)]
|
||||||
|
[InlineData("'This is an unclosed string literal", 1, 36, LexemeErrorType.UnclosedStringLiteral)]
|
||||||
|
[InlineData("'This", 1, 6, LexemeErrorType.UnclosedStringLiteral)]
|
||||||
|
[InlineData("x @", 1, 3, LexemeErrorType.UnknownCharacterOrString)]
|
||||||
|
//[InlineData("\"x\'", 1, 3, LexemeException.LexemeErrorType.UnclosedStringLiteral)]
|
||||||
|
public void TestParseCharacterError(string input, uint expectedLine, uint expectedCharPosition, LexemeErrorType expectedErrorType)
|
||||||
|
{
|
||||||
|
Lexer lexer = new(input);
|
||||||
|
var ex = Assert.Throws<LexemeException>(() => lexer.Tokenize());
|
||||||
|
_testOutputHelper.WriteLine(ex.ToString());
|
||||||
|
Assert.Equal(expectedErrorType, ex.ErrorType);
|
||||||
|
Assert.Equal(expectedLine, ex.Line);
|
||||||
|
Assert.Equal(expectedCharPosition, ex.CharPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ public class DelimiterTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(",123", DelimiterType.Comma)]
|
[InlineData(",123", DelimiterType.Comma)]
|
||||||
[InlineData(".123", DelimiterType.Period)]
|
// [InlineData(".123", DelimiterType.Period)]
|
||||||
[InlineData(":123", DelimiterType.Colon)]
|
[InlineData(":123", DelimiterType.Colon)]
|
||||||
[InlineData(";123", DelimiterType.Semicolon)]
|
[InlineData(";123", DelimiterType.Semicolon)]
|
||||||
[InlineData("(123)", DelimiterType.LeftParenthesis)]
|
[InlineData("(123)", DelimiterType.LeftParenthesis)]
|
||||||
|
|
32
Canon.Tests/LexicalParserTests/ErrorSingleTests.cs
Normal file
32
Canon.Tests/LexicalParserTests/ErrorSingleTests.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using Canon.Core.LexicalParser;
|
||||||
|
using Canon.Core.Exceptions;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Canon.Core.Enums;
|
||||||
|
|
||||||
|
namespace Canon.Tests.LexicalParserTests
|
||||||
|
{
|
||||||
|
public class ErrorSingleTests
|
||||||
|
{
|
||||||
|
private readonly ITestOutputHelper _testOutputHelper;
|
||||||
|
public ErrorSingleTests(ITestOutputHelper testOutputHelper)
|
||||||
|
{
|
||||||
|
_testOutputHelper = testOutputHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("program main; var a: integer; begin a := 3#; end.", 1, 43, LexemeErrorType.IllegalNumberFormat)]
|
||||||
|
[InlineData("char c = 'abc;", 1, 15, LexemeErrorType.UnclosedStringLiteral)]
|
||||||
|
[InlineData("x := 10 @;", 1, 9, LexemeErrorType.UnknownCharacterOrString)]
|
||||||
|
[InlineData("identifier_with_special_chars@#",1, 30, LexemeErrorType.UnknownCharacterOrString)]
|
||||||
|
public void TestUnknownCharacterError(string pascalProgram, uint expectedLine, uint expectedCharPosition, LexemeErrorType expectedErrorType)
|
||||||
|
{
|
||||||
|
var lexer = new Lexer(pascalProgram);
|
||||||
|
|
||||||
|
var ex = Assert.Throws<LexemeException>(() => lexer.Tokenize());
|
||||||
|
_testOutputHelper.WriteLine(ex.ToString());
|
||||||
|
Assert.Equal(expectedErrorType, ex.ErrorType);
|
||||||
|
Assert.Equal(expectedLine, ex.Line);
|
||||||
|
Assert.Equal(expectedCharPosition, ex.CharPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
using Canon.Core.Enums;
|
using Canon.Core.Enums;
|
||||||
using Canon.Core.LexicalParser;
|
using Canon.Core.LexicalParser;
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Canon.Tests.LexicalParserTests
|
namespace Canon.Tests.LexicalParserTests
|
||||||
{
|
{
|
||||||
|
@ -10,20 +9,15 @@ namespace Canon.Tests.LexicalParserTests
|
||||||
[InlineData("identifier", true)]
|
[InlineData("identifier", true)]
|
||||||
[InlineData("_identifier", true)]
|
[InlineData("_identifier", true)]
|
||||||
[InlineData("identifier123", true)]
|
[InlineData("identifier123", true)]
|
||||||
[InlineData("123identifier", false)]
|
|
||||||
[InlineData("identifier_with_underscores", true)]
|
[InlineData("identifier_with_underscores", true)]
|
||||||
[InlineData("IdentifierWithCamelCase", true)]
|
[InlineData("IdentifierWithCamelCase", true)]
|
||||||
[InlineData("identifier-with-hyphen", false)]
|
[InlineData("andand", true)]
|
||||||
[InlineData("identifier with spaces", false)]
|
|
||||||
[InlineData("identifier_with_special_chars@#", false)]
|
|
||||||
[InlineData("", false)]
|
|
||||||
[InlineData(" ", false)]
|
|
||||||
[InlineData("andand", false)]
|
|
||||||
public void TestParseIdentifier(string input, bool expectedResult)
|
public void TestParseIdentifier(string input, bool expectedResult)
|
||||||
{
|
{
|
||||||
Lexer lexer = new(input);
|
Lexer lexer = new(input);
|
||||||
List<SemanticToken> tokens = lexer.Tokenize();
|
List<SemanticToken> tokens = lexer.Tokenize();
|
||||||
|
|
||||||
|
Assert.Single(tokens);
|
||||||
Assert.Equal(expectedResult, tokens.FirstOrDefault()?.TokenType == SemanticTokenType.Identifier);
|
Assert.Equal(expectedResult, tokens.FirstOrDefault()?.TokenType == SemanticTokenType.Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,6 +21,7 @@ public class KeywordTypeTests
|
||||||
[InlineData("for", KeywordType.For)]
|
[InlineData("for", KeywordType.For)]
|
||||||
[InlineData("to", KeywordType.To)]
|
[InlineData("to", KeywordType.To)]
|
||||||
[InlineData("do", KeywordType.Do)]
|
[InlineData("do", KeywordType.Do)]
|
||||||
|
[InlineData("DO", KeywordType.Do)]
|
||||||
public void SmokeTest(string input, KeywordType type)
|
public void SmokeTest(string input, KeywordType type)
|
||||||
{
|
{
|
||||||
Lexer lexer = new(input);
|
Lexer lexer = new(input);
|
||||||
|
|
312
Canon.Tests/LexicalParserTests/LexicalFileTests.cs
Normal file
312
Canon.Tests/LexicalParserTests/LexicalFileTests.cs
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Canon.Core.Enums;
|
||||||
|
using Canon.Core.Exceptions;
|
||||||
|
using Canon.Core.LexicalParser;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Canon.Tests.LexicalParserTests;
|
||||||
|
|
||||||
|
public class LexicalFileTests
|
||||||
|
{
|
||||||
|
private readonly ITestOutputHelper _testOutputHelper;
|
||||||
|
|
||||||
|
public LexicalFileTests(ITestOutputHelper testOutputHelper)
|
||||||
|
{
|
||||||
|
_testOutputHelper = testOutputHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: 基础的字符串匹配,因此变量名称不能被包含。手写一个存在包含情况的测试文件。
|
||||||
|
private static (int, int) FindNthPosition(string pascalProgram, string target, int occurrence)
|
||||||
|
{
|
||||||
|
int lineNumber = 0;
|
||||||
|
(int, int) nthPosition = (0, 0);
|
||||||
|
int foundCount = 0;
|
||||||
|
occurrence = occurrence + 1;
|
||||||
|
|
||||||
|
using (StringReader sr = new StringReader(pascalProgram))
|
||||||
|
{
|
||||||
|
string line;
|
||||||
|
while ((line = sr.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
lineNumber++;
|
||||||
|
int columnNumber = -1;
|
||||||
|
|
||||||
|
// line = Regex.Replace(line, "'[^']*'", "$");
|
||||||
|
|
||||||
|
while ((columnNumber = line.IndexOf(target, columnNumber + 1, StringComparison.Ordinal)) != -1)
|
||||||
|
{
|
||||||
|
foundCount++;
|
||||||
|
if (foundCount == occurrence)
|
||||||
|
{
|
||||||
|
nthPosition = (lineNumber, columnNumber + target.Length);
|
||||||
|
return nthPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nthPosition == (0, 0))
|
||||||
|
{
|
||||||
|
throw new Exception($"'{target}' not found in program.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return nthPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TestLexicalAnalysis(string pascalProgram, List<(string, SemanticTokenType, int)> stringLiterals)
|
||||||
|
{
|
||||||
|
var expectedTokens = new List<SemanticToken>();
|
||||||
|
|
||||||
|
foreach (var (literal, tokenType, skipCount) in stringLiterals)
|
||||||
|
{
|
||||||
|
var (line, column) = FindNthPosition(pascalProgram, literal, skipCount);
|
||||||
|
switch (tokenType)
|
||||||
|
{
|
||||||
|
case SemanticTokenType.Keyword:
|
||||||
|
expectedTokens.Add(new KeywordSemanticToken
|
||||||
|
{
|
||||||
|
LinePos = (uint)line,
|
||||||
|
CharacterPos = (uint)column,
|
||||||
|
LiteralValue = literal,
|
||||||
|
KeywordType = KeywordSemanticToken.GetKeywordTypeByKeyword(literal)
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SemanticTokenType.Identifier:
|
||||||
|
expectedTokens.Add(new IdentifierSemanticToken
|
||||||
|
{
|
||||||
|
LinePos = (uint)line, CharacterPos = (uint)column, LiteralValue = literal
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SemanticTokenType.Delimiter:
|
||||||
|
if (DelimiterSemanticToken.TryParse((uint)line, (uint)column, new LinkedListNode<char>(literal[0]),
|
||||||
|
out var delimiterToken))
|
||||||
|
{
|
||||||
|
if (delimiterToken != null)
|
||||||
|
{
|
||||||
|
expectedTokens.Add(delimiterToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SemanticTokenType.Operator:
|
||||||
|
expectedTokens.Add(new OperatorSemanticToken
|
||||||
|
{
|
||||||
|
LinePos = (uint)line,
|
||||||
|
CharacterPos = (uint)column,
|
||||||
|
LiteralValue = literal,
|
||||||
|
OperatorType = OperatorSemanticToken.GetOperatorTypeByOperator(literal)
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SemanticTokenType.Character:
|
||||||
|
expectedTokens.Add(new CharacterSemanticToken
|
||||||
|
{
|
||||||
|
LinePos = (uint)line, CharacterPos = (uint)column, LiteralValue = literal
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SemanticTokenType.Number:
|
||||||
|
expectedTokens.Add(new NumberSemanticToken
|
||||||
|
{
|
||||||
|
LinePos = (uint)line,
|
||||||
|
CharacterPos = (uint)column,
|
||||||
|
LiteralValue = literal,
|
||||||
|
NumberType = NumberType.Integer
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedTokens = expectedTokens.OrderBy(token => token.LinePos).ThenBy(token => token.CharacterPos).ToList();
|
||||||
|
expectedTokens = expectedTokens.Select(token =>
|
||||||
|
token is CharacterSemanticToken characterToken && characterToken.LiteralValue == "hello, world!"
|
||||||
|
? new CharacterSemanticToken
|
||||||
|
{
|
||||||
|
LinePos = characterToken.LinePos,
|
||||||
|
CharacterPos = characterToken.CharacterPos + 1,
|
||||||
|
LiteralValue = characterToken.LiteralValue
|
||||||
|
}
|
||||||
|
: token).ToList();
|
||||||
|
|
||||||
|
var lexer = new Lexer(pascalProgram);
|
||||||
|
var actualTokens = lexer.Tokenize();
|
||||||
|
for (int i = 0; i < expectedTokens.Count; i++)
|
||||||
|
{
|
||||||
|
_testOutputHelper.WriteLine($"Expect: {expectedTokens[i]}");
|
||||||
|
_testOutputHelper.WriteLine($"Actual: {actualTokens[i]}");
|
||||||
|
_testOutputHelper.WriteLine("----");
|
||||||
|
Assert.Equal(expectedTokens[i], actualTokens[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(expectedTokens, actualTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestLexicalAnalysisFirst()
|
||||||
|
{
|
||||||
|
string pascalProgram = """
|
||||||
|
program HelloWorld;
|
||||||
|
var
|
||||||
|
message: string;
|
||||||
|
begin
|
||||||
|
message := 'hello, world!';
|
||||||
|
writeln(message);
|
||||||
|
end.
|
||||||
|
""";
|
||||||
|
|
||||||
|
var stringLiterals = new List<(string, SemanticTokenType, int)>
|
||||||
|
{
|
||||||
|
("program", SemanticTokenType.Keyword, 0),
|
||||||
|
("HelloWorld", SemanticTokenType.Identifier, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 0),
|
||||||
|
("var", SemanticTokenType.Keyword, 0),
|
||||||
|
("message", SemanticTokenType.Identifier, 0),
|
||||||
|
(":", SemanticTokenType.Delimiter, 0),
|
||||||
|
("string", SemanticTokenType.Identifier, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 1),
|
||||||
|
("begin", SemanticTokenType.Keyword, 0),
|
||||||
|
("message", SemanticTokenType.Identifier, 1),
|
||||||
|
(":=", SemanticTokenType.Operator, 0),
|
||||||
|
("hello, world!", SemanticTokenType.Character, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 2),
|
||||||
|
("writeln", SemanticTokenType.Identifier, 0),
|
||||||
|
("(", SemanticTokenType.Delimiter, 0),
|
||||||
|
("message", SemanticTokenType.Identifier, 2),
|
||||||
|
(")", SemanticTokenType.Delimiter, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 3),
|
||||||
|
("end", SemanticTokenType.Keyword, 0),
|
||||||
|
(".", SemanticTokenType.Delimiter, 0)
|
||||||
|
};
|
||||||
|
TestLexicalAnalysis(pascalProgram, stringLiterals);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestLexicalAnalysisSecond()
|
||||||
|
{
|
||||||
|
string pascalProgram = """
|
||||||
|
program main;
|
||||||
|
var
|
||||||
|
ab: integer;
|
||||||
|
begin
|
||||||
|
ab := 3;
|
||||||
|
write(ab);
|
||||||
|
end.
|
||||||
|
""";
|
||||||
|
|
||||||
|
var stringLiterals = new List<(string, SemanticTokenType, int)>
|
||||||
|
{
|
||||||
|
("program", SemanticTokenType.Keyword, 0),
|
||||||
|
("main", SemanticTokenType.Identifier, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 0),
|
||||||
|
("var", SemanticTokenType.Keyword, 0),
|
||||||
|
("ab", SemanticTokenType.Identifier, 0),
|
||||||
|
(":", SemanticTokenType.Delimiter, 0),
|
||||||
|
("integer", SemanticTokenType.Keyword, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 1),
|
||||||
|
("begin", SemanticTokenType.Keyword, 0),
|
||||||
|
("ab", SemanticTokenType.Identifier, 1),
|
||||||
|
(":=", SemanticTokenType.Operator, 0),
|
||||||
|
("3", SemanticTokenType.Number, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 2),
|
||||||
|
("write", SemanticTokenType.Identifier, 0),
|
||||||
|
("(", SemanticTokenType.Delimiter, 0),
|
||||||
|
("ab", SemanticTokenType.Identifier, 2),
|
||||||
|
(")", SemanticTokenType.Delimiter, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 3),
|
||||||
|
("end", SemanticTokenType.Keyword, 0),
|
||||||
|
(".", SemanticTokenType.Delimiter, 0)
|
||||||
|
};
|
||||||
|
TestLexicalAnalysis(pascalProgram, stringLiterals);
|
||||||
|
}
|
||||||
|
|
||||||
|
//带注释的测试
|
||||||
|
[Fact]
|
||||||
|
public void TestLexicalAnalysisThird()
|
||||||
|
{
|
||||||
|
string pascalProgram = """
|
||||||
|
{test}
|
||||||
|
program main;
|
||||||
|
var
|
||||||
|
ab, ba: integer;
|
||||||
|
begin
|
||||||
|
ab := 3;
|
||||||
|
ba := 5;
|
||||||
|
ab := 5;
|
||||||
|
write(ab + ba);
|
||||||
|
end.
|
||||||
|
""";
|
||||||
|
|
||||||
|
var stringLiterals = new List<(string, SemanticTokenType, int)>
|
||||||
|
{
|
||||||
|
("program", SemanticTokenType.Keyword, 0),
|
||||||
|
("main", SemanticTokenType.Identifier, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 0),
|
||||||
|
("var", SemanticTokenType.Keyword, 0),
|
||||||
|
("ab", SemanticTokenType.Identifier, 0),
|
||||||
|
(",", SemanticTokenType.Delimiter, 0),
|
||||||
|
("ba", SemanticTokenType.Identifier, 0),
|
||||||
|
(":", SemanticTokenType.Delimiter, 0),
|
||||||
|
("integer", SemanticTokenType.Keyword, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 1),
|
||||||
|
("begin", SemanticTokenType.Keyword, 0),
|
||||||
|
("ab", SemanticTokenType.Identifier, 1),
|
||||||
|
(":=", SemanticTokenType.Operator, 0),
|
||||||
|
("3", SemanticTokenType.Number, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 2),
|
||||||
|
("ba", SemanticTokenType.Identifier, 1),
|
||||||
|
(":=", SemanticTokenType.Operator, 1),
|
||||||
|
("5", SemanticTokenType.Number, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 3),
|
||||||
|
("ab", SemanticTokenType.Identifier, 2),
|
||||||
|
(":=", SemanticTokenType.Operator, 2),
|
||||||
|
("5", SemanticTokenType.Number, 1),
|
||||||
|
(";", SemanticTokenType.Delimiter, 4),
|
||||||
|
("write", SemanticTokenType.Identifier, 0),
|
||||||
|
("(", SemanticTokenType.Delimiter, 0),
|
||||||
|
("ab", SemanticTokenType.Identifier, 3),
|
||||||
|
("+", SemanticTokenType.Operator, 0),
|
||||||
|
("ba", SemanticTokenType.Identifier, 2),
|
||||||
|
(")", SemanticTokenType.Delimiter, 0),
|
||||||
|
(";", SemanticTokenType.Delimiter, 5),
|
||||||
|
("end", SemanticTokenType.Keyword, 0),
|
||||||
|
(".", SemanticTokenType.Delimiter, 0)
|
||||||
|
};
|
||||||
|
TestLexicalAnalysis(pascalProgram, stringLiterals);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnclosedCommentFirst()
|
||||||
|
{
|
||||||
|
string pascalProgram = """
|
||||||
|
(* This is an example of an unclosed comment
|
||||||
|
program CommentError;
|
||||||
|
var
|
||||||
|
x: integer;
|
||||||
|
begin
|
||||||
|
x := 42;
|
||||||
|
end.
|
||||||
|
""";
|
||||||
|
var lexer = new Lexer(pascalProgram);
|
||||||
|
var ex = Assert.Throws<LexemeException>(() => lexer.Tokenize());
|
||||||
|
//打印exception信息
|
||||||
|
_testOutputHelper.WriteLine(ex.ToString());
|
||||||
|
Assert.Equal(LexemeErrorType.UnclosedComment, ex.ErrorType);
|
||||||
|
Assert.Equal((uint)7, ex.Line);
|
||||||
|
Assert.Equal((uint)5, ex.CharPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnclosedCommentSecond()
|
||||||
|
{
|
||||||
|
string pascalProgram = """
|
||||||
|
{
|
||||||
|
This is a block comment that does not close.
|
||||||
|
|
||||||
|
program CommentNotClosed;
|
||||||
|
""";
|
||||||
|
var lexer = new Lexer(pascalProgram);
|
||||||
|
var ex = Assert.Throws<LexemeException>(() => lexer.Tokenize());
|
||||||
|
_testOutputHelper.WriteLine(ex.ToString());
|
||||||
|
Assert.Equal(LexemeErrorType.UnclosedComment, ex.ErrorType);
|
||||||
|
Assert.Equal((uint)4, ex.Line);
|
||||||
|
Assert.Equal((uint)26, ex.CharPosition);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,46 +1,58 @@
|
||||||
using Canon.Core.Enums;
|
using Canon.Core.Enums;
|
||||||
using Canon.Core.LexicalParser;
|
using Canon.Core.LexicalParser;
|
||||||
|
using Canon.Core.Exceptions;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Canon.Tests.LexicalParserTests
|
namespace Canon.Tests.LexicalParserTests
|
||||||
{
|
{
|
||||||
|
|
||||||
public class NumberTests
|
public class NumberTests
|
||||||
{
|
{
|
||||||
|
private readonly ITestOutputHelper _testOutputHelper;
|
||||||
|
public NumberTests(ITestOutputHelper testOutputHelper)
|
||||||
|
{
|
||||||
|
_testOutputHelper = testOutputHelper;
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("123", 123, NumberType.Integer)]
|
[InlineData("123", "123", NumberType.Integer)]
|
||||||
[InlineData("0", 0, NumberType.Integer)]
|
[InlineData("0", "0", NumberType.Integer)]
|
||||||
[InlineData("-123", -123, NumberType.Integer)]
|
[InlineData("1.23", "1.23", NumberType.Real)]
|
||||||
[InlineData("1.23", 1.23, NumberType.Real)]
|
[InlineData("0.0", "0.0", NumberType.Real)]
|
||||||
[InlineData("-1.23", -1.23, NumberType.Real)]
|
[InlineData("1e7", "1e7", NumberType.Real)]
|
||||||
[InlineData("0.0", 0.0, NumberType.Real)]
|
[InlineData("1E7", "1E7", NumberType.Real)]
|
||||||
[InlineData("1e7", 1e7, NumberType.Real)]
|
[InlineData("1.23e-7", "1.23e-7", NumberType.Real)]
|
||||||
[InlineData("1E7", 1E7, NumberType.Real)]
|
[InlineData("1.23E-7", "1.23E-7", NumberType.Real)]
|
||||||
[InlineData("1.23e-7", 1.23e-7, NumberType.Real)]
|
[InlineData("1234567890", "1234567890", NumberType.Integer)]
|
||||||
[InlineData("1.23E-7", 1.23E-7, NumberType.Real)]
|
[InlineData("1234567890.1234567890", "1234567890.1234567890", NumberType.Real)]
|
||||||
[InlineData("1234567890", 1234567890, NumberType.Integer)]
|
[InlineData("1e-7", "1e-7", NumberType.Real)]
|
||||||
[InlineData("1234567890.1234567890", 1234567890.1234567890, NumberType.Real)]
|
[InlineData("1E-7", "1E-7", NumberType.Real)]
|
||||||
[InlineData("-1234567890", -1234567890, NumberType.Integer)]
|
[InlineData(".67",".67", NumberType.Real)]
|
||||||
[InlineData("-1234567890.1234567890", -1234567890.1234567890, NumberType.Real)]
|
[InlineData("$123", "0x123", NumberType.Hex)]
|
||||||
[InlineData("1e-7", 1e-7, NumberType.Real)]
|
public void TestParseNumber(string input, string expected, NumberType expectedNumberType)
|
||||||
[InlineData("1E-7", 1E-7, NumberType.Real)]
|
|
||||||
[InlineData("1E", 0, NumberType.Real, false)]
|
|
||||||
[InlineData("abc", 0, NumberType.Integer, false)]
|
|
||||||
[InlineData("123abc", 123, NumberType.Integer, true)]
|
|
||||||
public void TestParseNumber(string input, double expected, NumberType expectedNumberType,
|
|
||||||
bool expectedResult = true)
|
|
||||||
{
|
{
|
||||||
Lexer lexer = new(input);
|
Lexer lexer = new(input);
|
||||||
List<SemanticToken> tokens = lexer.Tokenize();
|
List<SemanticToken> tokens = lexer.Tokenize();
|
||||||
|
|
||||||
SemanticToken token = tokens[0];
|
SemanticToken token = tokens[0];
|
||||||
if (!expectedResult)
|
|
||||||
{
|
|
||||||
Assert.NotEqual(SemanticTokenType.Keyword, token.TokenType);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Assert.Equal(SemanticTokenType.Number, token.TokenType);
|
Assert.Equal(SemanticTokenType.Number, token.TokenType);
|
||||||
NumberSemanticToken numberSemanticToken = (NumberSemanticToken)token;
|
NumberSemanticToken numberSemanticToken = (NumberSemanticToken)token;
|
||||||
Assert.Equal(expectedNumberType, numberSemanticToken.NumberType);
|
Assert.Equal(expectedNumberType, numberSemanticToken.NumberType);
|
||||||
Assert.Equal(expected, numberSemanticToken.Value);
|
Assert.Equal(expected, numberSemanticToken.LiteralValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("1E", 1, 3, LexemeErrorType.IllegalNumberFormat)]
|
||||||
|
[InlineData("123abc", 1, 4, LexemeErrorType.IllegalNumberFormat)]
|
||||||
|
[InlineData("123.45.67", 1, 7, LexemeErrorType.IllegalNumberFormat)]
|
||||||
|
[InlineData("123identifier", 1, 4, LexemeErrorType.IllegalNumberFormat)]
|
||||||
|
public void TestParseNumberError(string input, uint expectedLine, uint expectedCharPosition, LexemeErrorType expectedErrorType)
|
||||||
|
{
|
||||||
|
Lexer lexer = new(input);
|
||||||
|
var ex = Assert.Throws<LexemeException>(() => lexer.Tokenize());
|
||||||
|
_testOutputHelper.WriteLine(ex.ToString());
|
||||||
|
Assert.Equal(expectedErrorType, ex.ErrorType);
|
||||||
|
Assert.Equal(expectedLine, ex.Line);
|
||||||
|
Assert.Equal(expectedCharPosition, ex.CharPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,38 +6,33 @@ namespace Canon.Tests.LexicalParserTests;
|
||||||
public class OperatorTypeTests
|
public class OperatorTypeTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("+ 123", OperatorType.Plus)]
|
[InlineData("+ 123", OperatorType.Plus, true)]
|
||||||
[InlineData("+123", OperatorType.Plus)]
|
[InlineData("+123", OperatorType.Plus, true)]
|
||||||
[InlineData("-123", OperatorType.Minus)]
|
[InlineData("-123", OperatorType.Minus, true)]
|
||||||
[InlineData("*123", OperatorType.Multiply)]
|
[InlineData("*123", OperatorType.Multiply, true)]
|
||||||
[InlineData("/123", OperatorType.Divide)]
|
[InlineData("/123", OperatorType.Divide, true)]
|
||||||
[InlineData("=123", OperatorType.Equal)]
|
[InlineData("=123", OperatorType.Equal, true)]
|
||||||
[InlineData("<123", OperatorType.Less)]
|
[InlineData("<123", OperatorType.Less, true)]
|
||||||
[InlineData(">123", OperatorType.Greater)]
|
[InlineData(">123", OperatorType.Greater, true)]
|
||||||
[InlineData("<=123", OperatorType.LessEqual)]
|
[InlineData("<=123", OperatorType.LessEqual, true)]
|
||||||
[InlineData(">=123", OperatorType.GreaterEqual)]
|
[InlineData(">=123", OperatorType.GreaterEqual, true)]
|
||||||
[InlineData("<>123", OperatorType.NotEqual)]
|
[InlineData("<>123", OperatorType.NotEqual, true)]
|
||||||
[InlineData(":=123", OperatorType.Assign)]
|
[InlineData(":=123", OperatorType.Assign, true)]
|
||||||
public void ParseTest(string input, OperatorType result)
|
[InlineData("1 + 123", OperatorType.Plus, false)]
|
||||||
|
[InlineData("m +123", OperatorType.Plus, false)]
|
||||||
|
public void ParseTest(string input, OperatorType result, bool expectedResult)
|
||||||
{
|
{
|
||||||
Lexer lexer = new(input);
|
Lexer lexer = new(input);
|
||||||
List<SemanticToken> tokens = lexer.Tokenize();
|
List<SemanticToken> tokens = lexer.Tokenize();
|
||||||
|
|
||||||
SemanticToken token = tokens[0];
|
SemanticToken token = tokens[0];
|
||||||
|
if (!expectedResult)
|
||||||
|
{
|
||||||
|
Assert.NotEqual(SemanticTokenType.Operator, token.TokenType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
Assert.Equal(SemanticTokenType.Operator, token.TokenType);
|
Assert.Equal(SemanticTokenType.Operator, token.TokenType);
|
||||||
OperatorSemanticToken operatorSemanticToken = (OperatorSemanticToken)token;
|
OperatorSemanticToken operatorSemanticToken = (OperatorSemanticToken)token;
|
||||||
Assert.Equal(result, operatorSemanticToken.OperatorType);
|
Assert.Equal(result, operatorSemanticToken.OperatorType);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("1 + 123")]
|
|
||||||
[InlineData("m +123")]
|
|
||||||
public void ParseFailedTest(string input)
|
|
||||||
{
|
|
||||||
Lexer lexer = new(input);
|
|
||||||
List<SemanticToken> tokens = lexer.Tokenize();
|
|
||||||
|
|
||||||
SemanticToken token = tokens[0];
|
|
||||||
Assert.NotEqual(SemanticTokenType.Operator, token.TokenType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user