共计 17807 个字符,预计需要花费 45 分钟才能阅读完成。
在了解词法分析之前,先看看对单词的定义:
/// <summary>
/// A structure for the result of word parsing.
/// </summary>
public class Word
{
public int AbsoluteLine;
public int AbsoluteStartPos;
public int AbsoluteEndPos;
public int Offset;
public int Column;
public string Text;
public bool IsAvailable;
public Context.LocationInfo Location;
}
注意为了方便计,实现中并未将所有词语都标记为单词,比如操作符。仅有关键字、变量名和函数名被标记为单词。
为了更好的分析源代码,创建了一个 SourceCode 类,以进行词法分析工作:
public class SourceCode
{
// 各种标记
public static char[] MultiLineCommentsStartMark = new char[] {'/', '*'};
public static char[] HexMark = new char[] {'x', 'X'};
public static char[] ExponentialMark = new char[] {'e', 'E'};
public static char[] FloatMark = new char[] {'f', 'F'};
public static char[] PlusMinusMark = new char[] {'-', '+'};
public string Text = "";
public int Line = 0;
public int ColumnOfCurrentLine = 0;
public int PosOffset = 0;
public int LineOffset = 0;
public int Column;
public Word LastWord;
public SourceCode() {}
public SourceCode(string txt);
public void LoadFromFile(string path);
public bool Eof;
// 当前位置在整个代码中的绝对位置
public int AbsolutePos;
// 当前行在整个代码中的绝对行
public int AbsoluteLine;
public Context.LocationInfo Location;
public char CurrentChar;
// 重置位置索引(局部位置索引)public void ResetPos();
// 检测下一个字符是否与指定的字符匹配
public bool TestNextChar(char c);
// 检测下一个字符是否在指定的字符集中
public bool TestNextChar(char[] chars);
// 获取下一个字符,默认会跳过前面的空格
public void NextChar(bool skipSpace = true);
// 获取从当前位置开始的剩余代码
public string Tail;
public static bool IsDigit(char ch);
public static bool IsLetter(char ch);
public static bool IsSpace(char ch);
public static bool IsOperator(char ch);
public static bool IsBracket(char ch);
public void SkipSpace();
// 以 ';' 为分隔符,划分代码片断
public List<SourceCode> SplitStatement();
// 以 ',' 为分隔符,划分多个变量定义片断
public List<SourceCode> SplitMultiDeclaration();
// 划分参数
public List<SourceCode> SplitParameter();
// 获取一个单词
public static Word GetWord(SourceCode src);
// 获取括号内的代码
public static SourceCode GetBracketCode(char leftBracket, char rightBracket, SourceCode src);
}
更复杂的分析,与语法分析一道,整合到 Parser 类中。
Parser 类用于对 C 代码进行语法分析并构造语法树。
/// <summary>
/// Parser class parses the simple C source code to build
/// the syntax serial.
/// </summary>
public class Parser
{
public enum ParsingErrorType
{
Warning,
UnknownError,
UndefinedVariable,
UnexpectedKeyword,
TokenExpected,
SyntaxError,
TypeError,
FormatError,
FunctionBodyUndefined
};
public class ParsingEventArg
{public Context Context;}
public class ParsingWarningEventArg
{
public Context Context;
public Context.LocationInfo Location;
public string Description;
}
public class ParsingErrorEventArg
{
public Context Context;
public Context.LocationInfo Location;
public ParsingErrorType Error;
public string Description;
public bool Continue;
}
public event EventHandler<ParsingEventArg> OnParsing;
public event EventHandler<ParsingWarningEventArg> OnParsingWarning;
public event EventHandler<ParsingErrorEventArg> OnParsingError;
///////////////////////////////
// Private member variables
///////////////////////////////
private SourceCode m_sourceCode = null;
private Word m_lastWord = null;
private Expression.ExpressionNode m_lastExpNode = null;
private Dictionary<char, char> m_escapeCharDict = new Dictionary<char, char>();
private int m_errorCount = 0;
private int m_warningCount = 0;
public String Source
{get { return m_sourceCode.Text;}
}
public bool HasError
{get { return m_errorCount > 0;}
}
public bool HasWarning
{get { return m_warningCount > 0;}
}
public int ErrorCount
{get { return m_errorCount;}
}
public int WarningCount
{get { return m_warningCount;}
}
public int MaxError = 0;
public int MaxWarning = 65535;
public Context Parse(SourceCode src)
{
m_sourceCode = src;
Context ctx = new Context();
if (Parse(ctx, src))
{
// 语法分析完成后,校验是否有未使用的变量或函数,以及对申明而未定义函数的使用
if (ValidateContextReference(ctx))
return ctx;
}
return null;
}
...
语法分析的主要方法定义如下:
private bool Parse(Context ctx, SourceCode src)
{
bool res = true;
ctx.Location.FirstLine = src.AbsoluteLine;
ctx.Location.FirstPos = src.AbsolutePos;
foreach (SourceCode stmt in src.SplitStatement()) // 逐语句进行处理
{
try
{
// 检测 do...while 的 while 部分
if (ctx.Children.Count > 0 && ctx.Children.Last() is ControlFlow.DoWhileLoop)
{if ((ctx.Children.Last() as ControlFlow.DoWhileLoop).Condition == null)
{Word wordOfWhile = GetWord(stmt);
if (wordOfWhile.Text != "while")
{if (!NotifyError(ctx, wordOfWhile.Location, ParsingErrorType.SyntaxError, "\"while\"is expected."))
return false;
}
else
{res = ParseControl_While(ctx, stmt, new Context.LocationInfo()
{
FirstLine = wordOfWhile.AbsoluteLine,
FirstPos = wordOfWhile.AbsoluteStartPos
});
if (!res)
return false;
else
continue;
}
}
}
if (stmt.Text.EndsWith(";")) // 语句
{res = ParseStatement(ctx, stmt);
}
else
{if (stmt.Text.EndsWith("}")) // 函数体或块
{if (stmt.Text.StartsWith("{")) // 块
{SourceCode blockSrc = new SourceCode()
{
LineOffset = stmt.AbsoluteLine,
PosOffset = stmt.AbsolutePos,
Text = stmt.Text.Substring(1, stmt.Text.Length - 2)
};
Block block = new Block()
{Name = Context.GetAnonymousName("block"),
Location = new Context.LocationInfo()
{
FirstLine = stmt.AbsoluteLine,
FirstPos = stmt.AbsolutePos
}
};
ctx.AddChild(block);
res = Parse(block, blockSrc);
block.Location.LastLine = stmt.AbsoluteLine;
block.Location.LastPos = stmt.AbsolutePos;
}
else
{ // 函数
// 过滤控制结构
Word wordOfControlFlow = GetWord(stmt);
if (Context.IsControlFlow(wordOfControlFlow.Text))
{res = ParseControlFlow(ctx, stmt, wordOfControlFlow);
}
else
{stmt.ResetPos();
res = ParseFunction(ctx, stmt, wordOfControlFlow.Location);
}
}
}
}
}
catch (ParseException pe)
{if (!NotifyError(ctx, ctx.Location, ParsingErrorType.SyntaxError, pe.Message))
return false;
}
if (!res)
return false;
} // for
ctx.Location.LastLine = src.AbsoluteLine;
ctx.Location.LastPos = src.AbsolutePos;
return true;
}
语句处理,分三种情况:申明、控制结构及表达式。
private bool ParseStatement(Context ctx, SourceCode src)
{Word firstWord = GetWord(src);
if (Context.IsDataType(firstWord.Text)) // 以类型打头
{
// 变量或函数申明
return ParseDeclare(ctx, src, firstWord);
}
else
if (Context.IsControlFlow(firstWord.Text)) // 控制结构
{
//Control
return ParseControlFlow(ctx, src, firstWord);
}
else
{
// 表达式
src.ResetPos();
return ParseExpression(ctx, src, firstWord.Location);
}
}
函数解析的实现如下:
private bool ParseFunction(Context ctx, SourceCode src, Context.LocationInfo loc)
{
// 定位函数体
while (!src.Eof && src.CurrentChar != '{')
src.NextChar();
// 头部位置信息
Context.LocationInfo headerLoc = loc;
headerLoc.LastPos = src.AbsolutePos - 1;
// 函数头部
SourceCode funcHeader = new SourceCode()
{
PosOffset = loc.FirstPos,
LineOffset = loc.FirstLine,
Text = src.Text.Substring(0, src.Column)
};
// 解析头部
// 如成功, 一个 FunctionDefine 将被添加到当前 Context 的尾
if (!ParseStatement(ctx, funcHeader))
return false;
src.NextChar(); // skip '{'
// 函数体
SourceCode bodyStmt = new SourceCode()
{
PosOffset = src.AbsolutePos,
LineOffset = src.AbsoluteLine,
Text = src.Text.Substring(src.Column, src.Text.Length - src.Column - 1)
};
// 函数对象
Function.FunctionDefine funcDef = ctx.Children.Last() as Function.FunctionDefine;
funcDef.AddChild(new Block()
{Name = Context.GetAnonymousName("block")
});
// 递归解析函数体
if (Parse(funcDef.Body, bodyStmt))
{
funcDef.Location = headerLoc;
return true;
}
return false;
}
控制结构的解析如下:private bool ParseControlFlow(Context ctx, SourceCode src, Word wordOfControlFlow)
{
bool res = false;
switch (wordOfControlFlow.Text)
{case "if": res = ParseControl_If(ctx, src, wordOfControlFlow.Location); break;
case "else": res = ParseControl_Else(ctx, src, wordOfControlFlow.Location); break;
case "for": res = ParseControl_For(ctx, src, wordOfControlFlow.Location); break;
case "do": res = ParseControl_DoWhile(ctx, src, wordOfControlFlow.Location); break;
case "while": res = ParseControl_While(ctx, src, wordOfControlFlow.Location); break;
case "switch": res = ParseControl_Switch(ctx, src, wordOfControlFlow.Location); break;
case "continue": res = ParseControl_Continue(ctx, src, wordOfControlFlow.Location); break;
case "break": res = ParseControl_Break(ctx, src, wordOfControlFlow.Location); break;
case "return": res = ParseControl_Return(ctx, src, wordOfControlFlow.Location); break;
default:
{
// Unsupported control flow.
if (!NotifyError(ctx, wordOfControlFlow.Location, ParsingErrorType.SyntaxError, "Unsupported keyword."))
return false;
}
break;
} // switch
if (res)
NotifyParsing(ctx.Children.Last());
return res;
}
以 if/else 为例,说明控制结构的解析过程。
先看 if 部分:
private bool ParseControl_If(Context ctx, SourceCode src, Context.LocationInfo loc)
{src.SkipSpace(); // get '('
if (src.CurrentChar != '(')
if (!NotifyError(ctx, GetLocation(loc.FirstLine, loc.FirstPos, src.AbsoluteLine, src.AbsolutePos), ParsingErrorType.SyntaxError, "'(' is expected."))
return false;
// 获取判断表达式
SourceCode condition = GetParenthesisCode(src);
ControlFlow.IfThen stxIf = new ControlFlow.IfThen()
{Location = new Context.LocationInfo()
{
FirstLine = loc.FirstLine,
FirstPos = loc.FirstPos
}
};
ctx.AddChild(stxIf);
// 解析判断表达式
if (!ParseExpression(stxIf, condition, ref stxIf.Condition))
return false;
// 尝试解析 then 部分代码
src.SkipSpace();
bool res = false;
Block ThenBlock = new Block();
stxIf.AddChild(ThenBlock);
if (src.CurrentChar == '{') // 块?{SourceCode code = GetBlockCode(src);
res = Parse(ThenBlock, code);
stxIf.Location.LastLine = src.AbsoluteLine;
stxIf.Location.LastPos = src.AbsolutePos;
}
else
{ // 单个语句?SourceCode stmt = new SourceCode()
{
PosOffset = src.AbsolutePos,
LineOffset = src.AbsoluteLine,
Text = src.Text.Substring(src.Column)
};
res = Parse(ThenBlock, stmt);
stxIf.Location.LastLine = stmt.AbsoluteLine;
stxIf.Location.LastPos = stmt.AbsolutePos;
}
// else 另案处理
return res;
}
再看 else 部分:
private bool ParseControl_Else(Context ctx, SourceCode src, Context.LocationInfo loc)
{
// else 不能单独出现,前面必须有个 if。这种处理方式也解决了 else 与 if 的就近匹配问题。if (!(ctx.Children.Last() is ControlFlow.IfThen))
if (!NotifyError(ctx, loc, ParsingErrorType.SyntaxError, "\"else\"should not appear here."))
return false;
// 上一个语法可能是多重 if/then
Context lastStx = ctx.Children.Last();
while (lastStx.Children.Count > 2) // Children 数大于 2,表示具有 else 部分,则尝试取得最后一个 if/then
{lastStx = lastStx.Children.Last();
}
// 再次检测 if/then
if (!(lastStx is ControlFlow.IfThen))
if (!NotifyError(ctx, loc, ParsingErrorType.SyntaxError, "Can't find matched \"if\"."))
return false;
ControlFlow.IfThen stxIf = lastStx as ControlFlow.IfThen;
src.SkipSpace();
bool res = false;
Block elseBlock = new Block();
stxIf.AddChild(elseBlock);
// Block
if (src.CurrentChar == '{')
{SourceCode code = GetBlockCode(src);
res = Parse(elseBlock, code);
lastStx.Location.LastLine = src.AbsoluteLine;
lastStx.Location.LastPos = src.AbsolutePos;
}
else
{ // Statement
SourceCode stmt = new SourceCode()
{
PosOffset = src.AbsolutePos,
LineOffset = src.AbsoluteLine,
Text = src.Text.Substring(src.Column)
};
res = Parse(elseBlock, stmt);
lastStx.Location.LastLine = stmt.AbsoluteLine;
lastStx.Location.LastPos = stmt.AbsolutePos;
}
return res;
}
for 循环的解析:
private bool ParseControl_For(Context ctx, SourceCode src, Context.LocationInfo loc)
{src.SkipSpace();
if (src.CurrentChar != '(')
if (!NotifyError(ctx, GetLocation(loc.FirstLine, loc.FirstPos, src.AbsoluteLine, src.AbsolutePos), ParsingErrorType.SyntaxError, "'(' is expected."))
return false;
// 括号内的代码
SourceCode stmt = GetParenthesisCode(src);
List<SourceCode> stmtList = stmt.SplitStatement();
// 三部分,必须的
if (stmtList.Count != 3)
if (!NotifyError(ctx, GetLocation(loc.FirstLine, loc.FirstPos, src.AbsoluteLine, src.AbsolutePos), ParsingErrorType.SyntaxError, "Syntax error."))
return false;
ControlFlow.ForLoop stxFor = new ControlFlow.ForLoop()
{Location = new Context.LocationInfo()
{
FirstLine = loc.FirstLine,
FirstPos = loc.FirstPos
}
};
ctx.AddChild(stxFor);
// 初始化
Context stxInit = new Context();
stxFor.AddChild(stxInit);
if (!ParseStatement(stxInit, stmtList[0]))
return false;
// 条件判断
if (!ParseExpression(stxInit, stmtList[1], ref stxFor.Condition))
return false;
// 迭代器
if (!ParseExpression(stxInit, stmtList[2], ref stxFor.Iterator))
return false;
src.SkipSpace();
// 循环体
if (src.CurrentChar == '{')
{stmt = GetBlockCode(src);
}
else
{stmt = new SourceCode()
{
PosOffset = src.AbsolutePos,
LineOffset = src.AbsoluteLine,
Text = src.Text.Substring(src.Column)
};
}
Block block = new Block();
stxFor.AddChild(block);
bool res = Parse(block, stmt);
stxFor.Location.LastLine = stmt.AbsoluteLine;
stxFor.Location.LastPos = stmt.AbsolutePos;
return res;
}
复杂的是 switch:
private bool ParseControl_Switch(Context ctx, SourceCode src, Context.LocationInfo loc)
{
// Check condition
src.SkipSpace();
if (src.CurrentChar != '(')
if (!NotifyError(ctx, GetLocation(loc.FirstLine, loc.FirstPos, src.AbsoluteLine, src.AbsolutePos), ParsingErrorType.SyntaxError, "Expecte a'('."))
return false;
ControlFlow.Switch stxSwitch = new ControlFlow.Switch()
{Location = new Context.LocationInfo()
{
FirstLine = loc.FirstLine,
FirstPos = loc.FirstPos
}
};
ctx.AddChild(stxSwitch);
// Parse condition expression
if (!ParseExpression(stxSwitch, GetParenthesisCode(src), ref stxSwitch.Condition))
{ctx.Children.RemoveAt(ctx.Children.Count - 1);
return false;
}
// Add body
stxSwitch.AddChild(new Block());
ControlFlow.Case stxDefault = new ControlFlow.Case(); // default part
stxSwitch.Body.AddChild(stxDefault);
// Check '{'
src.SkipSpace();
if (src.CurrentChar != '{')
if (!NotifyError(ctx, GetLocation(loc.FirstLine, loc.FirstPos, src.AbsoluteLine, src.AbsolutePos), ParsingErrorType.SyntaxError, "Expecte a'{'."))
return false;
// Parse body
SourceCode switchBodyStmt = GetBlockCode(src);
Dictionary<object, bool> caseValDict = new Dictionary<object, bool>();
while (!switchBodyStmt.Eof)
{switchBodyStmt.SkipSpace();
Word word = GetWord(switchBodyStmt);
switch(word.Text)
{
case "case":
{ControlFlow.Case stxCase = new ControlFlow.Case()
{Location = new Context.LocationInfo()
{
FirstLine = word.AbsoluteLine,
FirstPos = word.AbsoluteStartPos
}
};
switchBodyStmt.SkipSpace();
if (switchBodyStmt.CurrentChar == '\'') // char
{Expression.Operand.Value charVal = GetCharValue(stxSwitch, switchBodyStmt);
if (charVal == null)
if (!NotifyError(ctx, GetLocation(word.AbsoluteLine, word.AbsoluteStartPos, switchBodyStmt.AbsoluteLine, switchBodyStmt.AbsolutePos), ParsingErrorType.SyntaxError, "Expecte a expression."))
return false;
stxCase.Value = charVal;
}
else
{if (SourceCode.IsDigit(switchBodyStmt.CurrentChar)) // number
{stxCase.Value = GetNumberValue(ctx, switchBodyStmt);
if ((stxCase.Value.GetTypeInfo(ctx).Type & PrimitiveDataType.BaseTypeMask) == PrimitiveDataType.FloatType)
if (!NotifyError(ctx, GetLocation(word.AbsoluteLine, word.AbsoluteStartPos, switchBodyStmt.AbsoluteLine, switchBodyStmt.AbsolutePos), ParsingErrorType.SyntaxError, "Expression must be an integral constant value."))
return false;
}
else
if (!NotifyError(ctx, GetLocation(word.AbsoluteLine, word.AbsoluteStartPos, switchBodyStmt.AbsoluteLine, switchBodyStmt.AbsolutePos), ParsingErrorType.SyntaxError, "Expression must have a constant value."))
return false;
}
if (caseValDict.ContainsKey(stxCase.Value.AsInt))
{if (!NotifyError(ctx, GetLocation(word.AbsoluteLine, word.AbsoluteStartPos, switchBodyStmt.AbsoluteLine, switchBodyStmt.AbsolutePos), ParsingErrorType.SyntaxError, "Case label value has appeared in this switch."))
return false;
}
else
caseValDict.Add(stxCase.Value.AsInt, true);
stxSwitch.Body.AddChild(stxCase);
// Parse case body
switchBodyStmt.SkipSpace();
if (switchBodyStmt.CurrentChar != ':')
if (!NotifyError(ctx, GetLocation(word.AbsoluteLine, word.AbsoluteStartPos, switchBodyStmt.AbsoluteLine, switchBodyStmt.AbsolutePos), ParsingErrorType.SyntaxError, "identifier is undefined."))
return false;
switchBodyStmt.NextChar(); // skip ':'
stxCase.Location.LastLine = switchBodyStmt.AbsoluteLine;
stxCase.Location.LastPos = switchBodyStmt.AbsolutePos;
NotifyParsing(stxCase);
stxCase.AddChild(new Block());
if (!Parse(stxCase.Body, GetCaseBodyCode(ctx, switchBodyStmt)))
return false;
}
break;
case "default":
{if (stxDefault.Children.Count > 0)
if (!NotifyError(ctx, GetLocation(word.AbsoluteLine, word.AbsoluteStartPos, switchBodyStmt.AbsoluteLine, switchBodyStmt.AbsolutePos), ParsingErrorType.SyntaxError, "default label has already appeared in this switch."))
return false;
// Check ':'
switchBodyStmt.SkipSpace();
if (switchBodyStmt.CurrentChar != ':')
if (!NotifyError(ctx, GetLocation(word.AbsoluteLine, word.AbsoluteStartPos, switchBodyStmt.AbsoluteLine, switchBodyStmt.AbsolutePos), ParsingErrorType.SyntaxError, "':' is expected."))
return false;
switchBodyStmt.NextChar(); // skip ':'
stxDefault.AddChild(new Block()
{Location = new Context.LocationInfo()
{
FirstLine = word.AbsoluteLine,
FirstPos = word.AbsoluteStartPos,
LastLine = src.AbsoluteLine,
LastPos = src.AbsolutePos
}
});
if (!Parse(stxDefault.Body, GetCaseBodyCode(ctx, switchBodyStmt)))
return false;
}
break;
default:
if (!NotifyError(ctx, GetLocation(word.AbsoluteLine, word.AbsoluteStartPos, switchBodyStmt.AbsoluteLine, switchBodyStmt.AbsolutePos), ParsingErrorType.SyntaxError, "\"case\"is expected but get \"" + word.Text + "\"."))
return false;
break;
}
} // while
stxSwitch.Body.RemoveChild(stxDefault);
stxSwitch.Body.AddChild(stxDefault);
stxSwitch.Location.LastLine = src.AbsoluteLine;
stxSwitch.Location.LastPos = src.AbsolutePos;
return true;
}
表达式解析分为两步,第一步先生成 Statement 对象,再以此对象为基础进行解析。
第一步:
private bool ParseExpression(Context ctx, SourceCode src, Context.LocationInfo loc)
{Statement stmtStx = new Statement()
{Name = Context.GetAnonymousName("statement"),
Location = loc
};
if (!ParseExpression(ctx, src, ref stmtStx.TargetExpression))
return false;
ctx.AddChild(stmtStx);
stmtStx.Location.LastLine = src.AbsoluteLine;
stmtStx.Location.LastPos = src.AbsolutePos;
NotifyParsing(stmtStx);
return true;
}
第二步:
private bool ParseExpression(Context ctx, SourceCode src, ref Expression.ExpressionNode expTree)
{
bool res = true;
while (!src.Eof && res)
{src.SkipSpace();
switch (src.CurrentChar)
{
case ',':
{src.NextChar(); // skip ','
Statement stxExp = new Statement();
if (ctx.Parent != null)
ctx.Parent.AddChild(stxExp);
else
ctx.AddChild(stxExp);
res = ParseExpression(ctx, src, ref stxExp.TargetExpression);
}
break;
case ';': src.NextChar(); break; // End of statement
case '=': res = ParseExpression_Equal(ctx, src, ref expTree); break;
case '+': res = ParseExpression_Plus(ctx, src, ref expTree); break;
case '-': res = ParseExpression_Minus(ctx, src, ref expTree); break;
case '*': res = ParseExpression_Mul(ctx, src, ref expTree); break;
case '/': res = ParseExpression_Div(ctx, src, ref expTree); break;
case '%': res = ParseExpression_Mod(ctx, src, ref expTree); break;
case '&': res = ParseExpression_And(ctx, src, ref expTree); break;
case '|': res = ParseExpression_Or(ctx, src, ref expTree); break;
case '^': res = ParseExpression_Xor(ctx, src, ref expTree); break;
case '!': res = ParseExpression_Not(ctx, src, ref expTree); break;
case '~': res = ParseExpression_BitwiseNot(ctx, src, ref expTree); break;
case '<': res = ParseExpression_Less(ctx, src, ref expTree); break;
case '>': res = ParseExpression_Greater(ctx, src, ref expTree); break;
case '(': res = ParseExpression_Parentheses(ctx, src, ref expTree); break;
case '\'': res = ParseExpression_CharValue(ctx, src, ref expTree); break;
case '"':
{
// const string
res = ParseExpression_ConstStringValue(ctx, src, ref expTree);
//if (!FireParsingFailedEvent(ctx, src, ParsingErrorType.SyntaxError, "String is not supported."))
// return false;
}
break;
default:
{
Expression.ExpressionNode lastNode = m_lastExpNode;
if (SourceCode.IsDigit(src.CurrentChar))
{res = ParseExpression_NumberValue(ctx, src, ref expTree);
}
else
if (SourceCode.IsLetter(src.CurrentChar))
{res = ParseExpression_Var(ctx, src, ref expTree);
}
else
if (!NotifyError(ctx, src.Location, ParsingErrorType.SyntaxError, "Syntax error."))
return false;
if (!ValidateOperator(ctx, src, lastNode))
return false;
}
break;
} // switch
} // while !Eof
return res;
} // func ParseExpression
正文完