Parser.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Runtime.CompilerServices;
  3. using Lua.Internal;
  4. using Lua.CodeAnalysis.Syntax.Nodes;
  5. using System.Globalization;
  6. namespace Lua.CodeAnalysis.Syntax;
  7. public ref struct Parser
  8. {
  9. public string? ChunkName { get; init; }
  10. PooledList<SyntaxToken> tokens;
  11. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  12. public void Add(SyntaxToken token) => tokens.Add(token);
  13. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  14. public void Dispose()
  15. {
  16. tokens.Dispose();
  17. }
  18. public LuaSyntaxTree Parse()
  19. {
  20. using var root = new PooledList<SyntaxNode>(64);
  21. var enumerator = new SyntaxTokenEnumerator(tokens.AsSpan());
  22. while (enumerator.MoveNext())
  23. {
  24. if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
  25. var node = ParseStatement(ref enumerator);
  26. root.Add(node);
  27. }
  28. var tree = new LuaSyntaxTree(root.AsSpan().ToArray());
  29. Dispose();
  30. return tree;
  31. }
  32. StatementNode ParseStatement(ref SyntaxTokenEnumerator enumerator)
  33. {
  34. switch (enumerator.Current.Type)
  35. {
  36. case SyntaxTokenType.LParen:
  37. case SyntaxTokenType.Identifier:
  38. {
  39. var firstExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
  40. switch (firstExpression)
  41. {
  42. case CallFunctionExpressionNode callFunctionExpression:
  43. return new CallFunctionStatementNode(callFunctionExpression);
  44. case CallTableMethodExpressionNode callTableMethodExpression:
  45. return new CallTableMethodStatementNode(callTableMethodExpression);
  46. default:
  47. if (enumerator.GetNext(true).Type is SyntaxTokenType.Comma or SyntaxTokenType.Assignment)
  48. {
  49. // skip ','
  50. MoveNextWithValidation(ref enumerator);
  51. enumerator.SkipEoL();
  52. return ParseAssignmentStatement(firstExpression, ref enumerator);
  53. }
  54. break;
  55. }
  56. }
  57. break;
  58. case SyntaxTokenType.Return:
  59. return ParseReturnStatement(ref enumerator);
  60. case SyntaxTokenType.Do:
  61. return ParseDoStatement(ref enumerator);
  62. case SyntaxTokenType.Goto:
  63. return ParseGotoStatement(ref enumerator);
  64. case SyntaxTokenType.Label:
  65. return new LabelStatementNode(enumerator.Current.Text, enumerator.Current.Position);
  66. case SyntaxTokenType.If:
  67. return ParseIfStatement(ref enumerator);
  68. case SyntaxTokenType.While:
  69. return ParseWhileStatement(ref enumerator);
  70. case SyntaxTokenType.Repeat:
  71. return ParseRepeatStatement(ref enumerator);
  72. case SyntaxTokenType.For:
  73. {
  74. // skip 'for' keyword
  75. var forToken = enumerator.Current;
  76. MoveNextWithValidation(ref enumerator);
  77. enumerator.SkipEoL();
  78. if (enumerator.GetNext(true).Type is SyntaxTokenType.Assignment)
  79. {
  80. return ParseNumericForStatement(ref enumerator, forToken);
  81. }
  82. else
  83. {
  84. return ParseGenericForStatement(ref enumerator, forToken);
  85. }
  86. }
  87. case SyntaxTokenType.Break:
  88. return new BreakStatementNode(enumerator.Current.Position);
  89. case SyntaxTokenType.Local:
  90. {
  91. // skip 'local' keyword
  92. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Local, out var localToken);
  93. // local function
  94. if (enumerator.Current.Type is SyntaxTokenType.Function)
  95. {
  96. // skip 'function' keyword
  97. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
  98. enumerator.SkipEoL();
  99. return ParseLocalFunctionDeclarationStatement(ref enumerator, functionToken);
  100. }
  101. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  102. var nextType = enumerator.GetNext().Type;
  103. if (nextType is SyntaxTokenType.Comma or SyntaxTokenType.Assignment)
  104. {
  105. return ParseLocalAssignmentStatement(ref enumerator, localToken);
  106. }
  107. else if (nextType is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon)
  108. {
  109. return new LocalAssignmentStatementNode([new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position)], [], localToken.Position);
  110. }
  111. }
  112. break;
  113. case SyntaxTokenType.Function:
  114. {
  115. // skip 'function' keyword
  116. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
  117. enumerator.SkipEoL();
  118. if (enumerator.GetNext(true).Type is SyntaxTokenType.Dot or SyntaxTokenType.Colon)
  119. {
  120. return ParseTableMethodDeclarationStatement(ref enumerator, functionToken);
  121. }
  122. else
  123. {
  124. return ParseFunctionDeclarationStatement(ref enumerator, functionToken);
  125. }
  126. }
  127. }
  128. LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current);
  129. return default!;
  130. }
  131. ReturnStatementNode ParseReturnStatement(ref SyntaxTokenEnumerator enumerator)
  132. {
  133. // skip 'return' keyword
  134. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Return, out var returnToken);
  135. // parse parameters
  136. var expressions = ParseExpressionList(ref enumerator);
  137. return new ReturnStatementNode(expressions, returnToken.Position);
  138. }
  139. DoStatementNode ParseDoStatement(ref SyntaxTokenEnumerator enumerator)
  140. {
  141. // check 'do' keyword
  142. CheckCurrent(ref enumerator, SyntaxTokenType.Do);
  143. var doToken = enumerator.Current;
  144. // parse statements
  145. var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
  146. return new DoStatementNode(statements, doToken.Position);
  147. }
  148. GotoStatementNode ParseGotoStatement(ref SyntaxTokenEnumerator enumerator)
  149. {
  150. // skip 'goto' keyword
  151. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Goto, out var gotoToken);
  152. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  153. return new GotoStatementNode(enumerator.Current.Text, gotoToken.Position);
  154. }
  155. AssignmentStatementNode ParseAssignmentStatement(ExpressionNode firstExpression, ref SyntaxTokenEnumerator enumerator)
  156. {
  157. // parse leftNodes
  158. using var leftNodes = new PooledList<SyntaxNode>(8);
  159. leftNodes.Add(firstExpression);
  160. while (enumerator.Current.Type == SyntaxTokenType.Comma)
  161. {
  162. // skip ','
  163. MoveNextWithValidation(ref enumerator);
  164. enumerator.SkipEoL();
  165. // parse identifier
  166. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  167. leftNodes.Add(ParseExpression(ref enumerator, OperatorPrecedence.NonOperator));
  168. MoveNextWithValidation(ref enumerator);
  169. enumerator.SkipEoL();
  170. }
  171. // skip '='
  172. if (enumerator.Current.Type is not SyntaxTokenType.Assignment)
  173. {
  174. enumerator.MovePrevious();
  175. return new AssignmentStatementNode(leftNodes.AsSpan().ToArray(), [], firstExpression.Position);
  176. }
  177. MoveNextWithValidation(ref enumerator);
  178. // parse expressions
  179. var expressions = ParseExpressionList(ref enumerator);
  180. return new AssignmentStatementNode(leftNodes.AsSpan().ToArray(), expressions, firstExpression.Position);
  181. }
  182. LocalAssignmentStatementNode ParseLocalAssignmentStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken localToken)
  183. {
  184. // parse identifiers
  185. var identifiers = ParseIdentifierList(ref enumerator);
  186. // skip '='
  187. if (enumerator.Current.Type is not SyntaxTokenType.Assignment)
  188. {
  189. enumerator.MovePrevious();
  190. return new LocalAssignmentStatementNode(identifiers, [], localToken.Position);
  191. }
  192. MoveNextWithValidation(ref enumerator);
  193. // parse expressions
  194. var expressions = ParseExpressionList(ref enumerator);
  195. return new LocalAssignmentStatementNode(identifiers, expressions, localToken.Position);
  196. }
  197. IfStatementNode ParseIfStatement(ref SyntaxTokenEnumerator enumerator)
  198. {
  199. // skip 'if' keyword
  200. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.If, out var ifToken);
  201. enumerator.SkipEoL();
  202. // parse condition
  203. var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
  204. MoveNextWithValidation(ref enumerator);
  205. enumerator.SkipEoL();
  206. // skip 'then' keyword
  207. CheckCurrent(ref enumerator, SyntaxTokenType.Then);
  208. using var builder = new PooledList<StatementNode>(64);
  209. using var elseIfBuilder = new PooledList<IfStatementNode.ConditionAndThenNodes>(64);
  210. IfStatementNode.ConditionAndThenNodes ifNodes = default!;
  211. StatementNode[] elseNodes = [];
  212. // if = 0, elseif = 1, else = 2
  213. var state = 0;
  214. // parse statements
  215. while (true)
  216. {
  217. if (!enumerator.MoveNext())
  218. {
  219. LuaParseException.ExpectedToken(ChunkName, enumerator.Current.Position, SyntaxTokenType.End);
  220. }
  221. var tokenType = enumerator.Current.Type;
  222. if (tokenType is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon)
  223. {
  224. continue;
  225. }
  226. if (tokenType is SyntaxTokenType.ElseIf or SyntaxTokenType.Else or SyntaxTokenType.End)
  227. {
  228. switch (state)
  229. {
  230. case 0:
  231. ifNodes = new()
  232. {
  233. ConditionNode = condition,
  234. ThenNodes = builder.AsSpan().ToArray(),
  235. };
  236. builder.Clear();
  237. break;
  238. case 1:
  239. elseIfBuilder.Add(new()
  240. {
  241. ConditionNode = condition,
  242. ThenNodes = builder.AsSpan().ToArray(),
  243. });
  244. builder.Clear();
  245. break;
  246. case 2:
  247. elseNodes = builder.AsSpan().ToArray();
  248. break;
  249. }
  250. if (tokenType is SyntaxTokenType.ElseIf)
  251. {
  252. // skip 'elseif' keywords
  253. MoveNextWithValidation(ref enumerator);
  254. enumerator.SkipEoL();
  255. // parse condition
  256. condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
  257. MoveNextWithValidation(ref enumerator);
  258. enumerator.SkipEoL();
  259. // check 'then' keyword
  260. CheckCurrent(ref enumerator, SyntaxTokenType.Then);
  261. // set elseif state
  262. state = 1;
  263. continue;
  264. }
  265. else if (tokenType is SyntaxTokenType.Else)
  266. {
  267. // set else state
  268. state = 2;
  269. continue;
  270. }
  271. else if (tokenType is SyntaxTokenType.End)
  272. {
  273. goto RETURN;
  274. }
  275. }
  276. var node = ParseStatement(ref enumerator);
  277. builder.Add(node);
  278. }
  279. RETURN:
  280. return new IfStatementNode(ifNodes, elseIfBuilder.AsSpan().ToArray(), elseNodes, ifToken.Position);
  281. }
  282. WhileStatementNode ParseWhileStatement(ref SyntaxTokenEnumerator enumerator)
  283. {
  284. // skip 'while' keyword
  285. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.While, out var whileToken);
  286. enumerator.SkipEoL();
  287. // parse condition
  288. var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
  289. MoveNextWithValidation(ref enumerator);
  290. enumerator.SkipEoL();
  291. // skip 'do' keyword
  292. CheckCurrent(ref enumerator, SyntaxTokenType.Do);
  293. // parse statements
  294. var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
  295. return new WhileStatementNode(condition, statements, whileToken.Position);
  296. }
  297. RepeatStatementNode ParseRepeatStatement(ref SyntaxTokenEnumerator enumerator)
  298. {
  299. // skip 'repeat' keyword
  300. CheckCurrent(ref enumerator, SyntaxTokenType.Repeat);
  301. var repeatToken = enumerator.Current;
  302. // parse statements
  303. var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until, out _);
  304. // skip 'until keyword'
  305. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Until, out _);
  306. enumerator.SkipEoL();
  307. // parse condition
  308. var condition = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
  309. return new RepeatStatementNode(condition, statements, repeatToken.Position);
  310. }
  311. NumericForStatementNode ParseNumericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
  312. {
  313. // parse variable name
  314. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  315. var varName = enumerator.Current.Text;
  316. MoveNextWithValidation(ref enumerator);
  317. enumerator.SkipEoL();
  318. // skip '='
  319. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Assignment, out _);
  320. enumerator.SkipEoL();
  321. // parse initial value
  322. var initialValueNode = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
  323. MoveNextWithValidation(ref enumerator);
  324. enumerator.SkipEoL();
  325. // skip ','
  326. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Comma, out _);
  327. enumerator.SkipEoL();
  328. // parse limit
  329. var limitNode = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
  330. MoveNextWithValidation(ref enumerator);
  331. enumerator.SkipEoL();
  332. // parse stepNode
  333. ExpressionNode? stepNode = null;
  334. if (enumerator.Current.Type is SyntaxTokenType.Comma)
  335. {
  336. // skip ','
  337. enumerator.MoveNext();
  338. // parse step
  339. stepNode = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
  340. MoveNextWithValidation(ref enumerator);
  341. enumerator.SkipEoL();
  342. }
  343. // skip 'do' keyword
  344. CheckCurrent(ref enumerator, SyntaxTokenType.Do);
  345. var doToken = enumerator.Current;
  346. // parse statements
  347. var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _);
  348. return new NumericForStatementNode(varName, initialValueNode, limitNode, stepNode, statements, forToken.Position, doToken.Position);
  349. }
  350. GenericForStatementNode ParseGenericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken)
  351. {
  352. var identifiers = ParseIdentifierList(ref enumerator);
  353. enumerator.SkipEoL();
  354. // skip 'in' keyword
  355. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.In, out _);
  356. enumerator.SkipEoL();
  357. var iteratorToken = enumerator.Current;
  358. var expressions = ParseExpressionList(ref enumerator);
  359. MoveNextWithValidation(ref enumerator);
  360. enumerator.SkipEoL();
  361. // skip 'do' keyword
  362. CheckCurrent(ref enumerator, SyntaxTokenType.Do);
  363. var doToken = enumerator.Current;
  364. // parse statements
  365. var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken);
  366. return new GenericForStatementNode(identifiers, expressions, statements, iteratorToken.Position, doToken.Position, endToken.Position);
  367. }
  368. FunctionDeclarationStatementNode ParseFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
  369. {
  370. var (Name, Identifiers, Statements, HasVariableArgments, LineDefined, LastLineDefined) = ParseFunctionDeclarationCore(ref enumerator, false);
  371. return new FunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, LastLineDefined);
  372. }
  373. LocalFunctionDeclarationStatementNode ParseLocalFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
  374. {
  375. var (Name, Identifiers, Statements, HasVariableArgments, LineDefined, LastLineDefined) = ParseFunctionDeclarationCore(ref enumerator, false);
  376. return new LocalFunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, LastLineDefined);
  377. }
  378. (ReadOnlyMemory<char> Name, IdentifierNode[] Identifiers, StatementNode[] Statements, bool HasVariableArgments, int LineDefined, int LastLineDefined) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous)
  379. {
  380. ReadOnlyMemory<char> name;
  381. if (isAnonymous)
  382. {
  383. name = ReadOnlyMemory<char>.Empty;
  384. }
  385. else
  386. {
  387. // parse function name
  388. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  389. name = enumerator.Current.Text;
  390. MoveNextWithValidation(ref enumerator);
  391. enumerator.SkipEoL();
  392. }
  393. // skip '('
  394. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out var leftParenToken);
  395. enumerator.SkipEoL();
  396. // parse parameters
  397. var identifiers = enumerator.Current.Type is SyntaxTokenType.Identifier
  398. ? ParseIdentifierList(ref enumerator)
  399. : [];
  400. // check variable arguments
  401. var hasVarArg = enumerator.Current.Type is SyntaxTokenType.VarArg;
  402. if (hasVarArg) enumerator.MoveNext();
  403. // skip ')'
  404. CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
  405. // parse statements
  406. var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken);
  407. return (name, identifiers, statements, hasVarArg, leftParenToken.Position.Line, endToken.Position.Line);
  408. }
  409. TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken)
  410. {
  411. using var names = new PooledList<IdentifierNode>(32);
  412. var hasSelfParameter = false;
  413. SyntaxToken leftParenToken;
  414. while (true)
  415. {
  416. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  417. names.Add(new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position));
  418. MoveNextWithValidation(ref enumerator);
  419. enumerator.SkipEoL();
  420. if (enumerator.Current.Type is SyntaxTokenType.Dot or SyntaxTokenType.Colon)
  421. {
  422. if (hasSelfParameter)
  423. {
  424. LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current);
  425. }
  426. hasSelfParameter = enumerator.Current.Type is SyntaxTokenType.Colon;
  427. MoveNextWithValidation(ref enumerator);
  428. enumerator.SkipEoL();
  429. }
  430. else if (enumerator.Current.Type is SyntaxTokenType.LParen)
  431. {
  432. leftParenToken = enumerator.Current;
  433. // skip '('
  434. MoveNextWithValidation(ref enumerator);
  435. enumerator.SkipEoL();
  436. break;
  437. }
  438. }
  439. // parse parameters
  440. var identifiers = enumerator.Current.Type is SyntaxTokenType.Identifier
  441. ? ParseIdentifierList(ref enumerator)
  442. : [];
  443. // check variable arguments
  444. var hasVarArg = enumerator.Current.Type is SyntaxTokenType.VarArg;
  445. if (hasVarArg) enumerator.MoveNext();
  446. // skip ')'
  447. CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
  448. // parse statements
  449. var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken);
  450. return new TableMethodDeclarationStatementNode(names.AsSpan().ToArray(), identifiers, statements, hasVarArg, hasSelfParameter, functionToken.Position, leftParenToken.Position.Line, endToken.Position.Line);
  451. }
  452. bool TryParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, [NotNullWhen(true)] out ExpressionNode? result)
  453. {
  454. result = enumerator.Current.Type switch
  455. {
  456. SyntaxTokenType.Identifier => enumerator.GetNext(true).Type switch
  457. {
  458. SyntaxTokenType.LParen or SyntaxTokenType.String or SyntaxTokenType.RawString => ParseCallFunctionExpression(ref enumerator, null),
  459. SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon => ParseTableAccessExpression(ref enumerator, null),
  460. _ => new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position),
  461. },
  462. SyntaxTokenType.Number => new NumericLiteralNode(ConvertTextToNumber(enumerator.Current.Text.Span), enumerator.Current.Position),
  463. SyntaxTokenType.String => new StringLiteralNode(enumerator.Current.Text, true, enumerator.Current.Position),
  464. SyntaxTokenType.RawString => new StringLiteralNode(enumerator.Current.Text, false, enumerator.Current.Position),
  465. SyntaxTokenType.True => new BooleanLiteralNode(true, enumerator.Current.Position),
  466. SyntaxTokenType.False => new BooleanLiteralNode(false, enumerator.Current.Position),
  467. SyntaxTokenType.Nil => new NilLiteralNode(enumerator.Current.Position),
  468. SyntaxTokenType.VarArg => new VariableArgumentsExpressionNode(enumerator.Current.Position),
  469. SyntaxTokenType.Subtraction => ParseMinusNumber(ref enumerator),
  470. SyntaxTokenType.Not or SyntaxTokenType.Length => ParseUnaryExpression(ref enumerator, enumerator.Current),
  471. SyntaxTokenType.LParen => ParseGroupedExpression(ref enumerator),
  472. SyntaxTokenType.LCurly => ParseTableConstructorExpression(ref enumerator),
  473. SyntaxTokenType.Function => ParseFunctionDeclarationExpression(ref enumerator),
  474. _ => null,
  475. };
  476. if (result == null) return false;
  477. // nested table access & function call
  478. RECURSIVE:
  479. enumerator.SkipEoL();
  480. var nextType = enumerator.GetNext().Type;
  481. if (nextType is SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon)
  482. {
  483. MoveNextWithValidation(ref enumerator);
  484. result = ParseTableAccessExpression(ref enumerator, result);
  485. goto RECURSIVE;
  486. }
  487. else if (nextType is SyntaxTokenType.LParen or SyntaxTokenType.String or SyntaxTokenType.RawString or SyntaxTokenType.LCurly)
  488. {
  489. MoveNextWithValidation(ref enumerator);
  490. result = ParseCallFunctionExpression(ref enumerator, result);
  491. goto RECURSIVE;
  492. }
  493. // binary expression
  494. while (true)
  495. {
  496. var opPrecedence = GetPrecedence(enumerator.GetNext(true).Type);
  497. if (precedence >= opPrecedence) break;
  498. MoveNextWithValidation(ref enumerator);
  499. enumerator.SkipEoL();
  500. result = ParseBinaryExpression(ref enumerator, opPrecedence, result);
  501. enumerator.SkipEoL();
  502. }
  503. return true;
  504. }
  505. ExpressionNode ParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence)
  506. {
  507. if (!TryParseExpression(ref enumerator, precedence, out var result))
  508. {
  509. throw new LuaParseException(ChunkName, enumerator.Current.Position, $"Unexpected token <{enumerator.Current.Type}>");
  510. }
  511. return result;
  512. }
  513. ExpressionNode ParseMinusNumber(ref SyntaxTokenEnumerator enumerator)
  514. {
  515. var token = enumerator.Current;
  516. if (enumerator.GetNext(true).Type is SyntaxTokenType.Number)
  517. {
  518. enumerator.MoveNext();
  519. enumerator.SkipEoL();
  520. return new NumericLiteralNode(-ConvertTextToNumber(enumerator.Current.Text.Span), token.Position);
  521. }
  522. else
  523. {
  524. return ParseUnaryExpression(ref enumerator, token);
  525. }
  526. }
  527. UnaryExpressionNode ParseUnaryExpression(ref SyntaxTokenEnumerator enumerator, SyntaxToken operatorToken)
  528. {
  529. var operatorType = enumerator.Current.Type switch
  530. {
  531. SyntaxTokenType.Subtraction => UnaryOperator.Negate,
  532. SyntaxTokenType.Not => UnaryOperator.Not,
  533. SyntaxTokenType.Length => UnaryOperator.Length,
  534. _ => throw new LuaParseException(ChunkName, operatorToken.Position, $"unexpected symbol near '{enumerator.Current.Text}'"),
  535. };
  536. MoveNextWithValidation(ref enumerator);
  537. var right = ParseExpression(ref enumerator, OperatorPrecedence.Unary);
  538. return new UnaryExpressionNode(operatorType, right, operatorToken.Position);
  539. }
  540. BinaryExpressionNode ParseBinaryExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, ExpressionNode left)
  541. {
  542. var operatorToken = enumerator.Current;
  543. var operatorType = operatorToken.Type switch
  544. {
  545. SyntaxTokenType.Addition => BinaryOperator.Addition,
  546. SyntaxTokenType.Subtraction => BinaryOperator.Subtraction,
  547. SyntaxTokenType.Multiplication => BinaryOperator.Multiplication,
  548. SyntaxTokenType.Division => BinaryOperator.Division,
  549. SyntaxTokenType.Modulo => BinaryOperator.Modulo,
  550. SyntaxTokenType.Exponentiation => BinaryOperator.Exponentiation,
  551. SyntaxTokenType.Equality => BinaryOperator.Equality,
  552. SyntaxTokenType.Inequality => BinaryOperator.Inequality,
  553. SyntaxTokenType.LessThan => BinaryOperator.LessThan,
  554. SyntaxTokenType.LessThanOrEqual => BinaryOperator.LessThanOrEqual,
  555. SyntaxTokenType.GreaterThan => BinaryOperator.GreaterThan,
  556. SyntaxTokenType.GreaterThanOrEqual => BinaryOperator.GreaterThanOrEqual,
  557. SyntaxTokenType.And => BinaryOperator.And,
  558. SyntaxTokenType.Or => BinaryOperator.Or,
  559. SyntaxTokenType.Concat => BinaryOperator.Concat,
  560. _ => throw new LuaParseException(ChunkName, enumerator.Current.Position, $"unexpected symbol near '{enumerator.Current.Text}'"),
  561. };
  562. enumerator.SkipEoL();
  563. MoveNextWithValidation(ref enumerator);
  564. enumerator.SkipEoL();
  565. var right = ParseExpression(ref enumerator, precedence);
  566. return new BinaryExpressionNode(operatorType, left, right, operatorToken.Position);
  567. }
  568. TableConstructorExpressionNode ParseTableConstructorExpression(ref SyntaxTokenEnumerator enumerator)
  569. {
  570. CheckCurrent(ref enumerator, SyntaxTokenType.LCurly);
  571. var startToken = enumerator.Current;
  572. using var items = new PooledList<TableConstructorField>(16);
  573. while (enumerator.MoveNext())
  574. {
  575. var currentToken = enumerator.Current;
  576. switch (currentToken.Type)
  577. {
  578. case SyntaxTokenType.RCurly:
  579. goto RETURN;
  580. case SyntaxTokenType.EndOfLine:
  581. case SyntaxTokenType.SemiColon:
  582. case SyntaxTokenType.Comma:
  583. continue;
  584. case SyntaxTokenType.LSquare:
  585. // general style ([key] = value)
  586. enumerator.MoveNext();
  587. var keyExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
  588. enumerator.MoveNext();
  589. // skip '] ='
  590. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.RSquare, out _);
  591. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Assignment, out _);
  592. var valueExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
  593. items.Add(new GeneralTableConstructorField(keyExpression, valueExpression, currentToken.Position));
  594. break;
  595. case SyntaxTokenType.Identifier when enumerator.GetNext(true).Type is SyntaxTokenType.Assignment:
  596. // record style (key = value)
  597. var name = enumerator.Current.Text;
  598. // skip key and '='
  599. enumerator.MoveNext();
  600. enumerator.MoveNext();
  601. var expression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
  602. items.Add(new RecordTableConstructorField(name.ToString(), expression, currentToken.Position));
  603. break;
  604. default:
  605. // list style
  606. items.Add(new ListTableConstructorField(ParseExpression(ref enumerator, OperatorPrecedence.NonOperator), currentToken.Position));
  607. break;
  608. }
  609. }
  610. RETURN:
  611. return new TableConstructorExpressionNode(items.AsSpan().ToArray(), startToken.Position);
  612. }
  613. ExpressionNode ParseTableAccessExpression(ref SyntaxTokenEnumerator enumerator, ExpressionNode? parentTable)
  614. {
  615. IdentifierNode? identifier = null;
  616. if (parentTable == null)
  617. {
  618. // parse identifier
  619. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  620. identifier = new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position);
  621. MoveNextWithValidation(ref enumerator);
  622. enumerator.SkipEoL();
  623. }
  624. ExpressionNode result;
  625. var current = enumerator.Current;
  626. if (current.Type is SyntaxTokenType.LSquare)
  627. {
  628. // indexer access -- table[key]
  629. // skip '['
  630. MoveNextWithValidation(ref enumerator);
  631. enumerator.SkipEoL();
  632. // parse key expression
  633. var keyExpression = ParseExpression(ref enumerator, OperatorPrecedence.NonOperator);
  634. MoveNextWithValidation(ref enumerator);
  635. enumerator.SkipEoL();
  636. // check ']'
  637. CheckCurrent(ref enumerator, SyntaxTokenType.RSquare);
  638. result = new TableIndexerAccessExpressionNode(identifier ?? parentTable!, keyExpression, current.Position);
  639. }
  640. else if (current.Type is SyntaxTokenType.Dot)
  641. {
  642. // member access -- table.key
  643. // skip '.'
  644. MoveNextWithValidation(ref enumerator);
  645. enumerator.SkipEoL();
  646. // parse identifier
  647. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  648. var key = enumerator.Current.Text.ToString();
  649. result = new TableMemberAccessExpressionNode(identifier ?? parentTable!, key, current.Position);
  650. }
  651. else if (current.Type is SyntaxTokenType.Colon)
  652. {
  653. // self method call -- table:method(arg0, arg1, ...)
  654. // skip ':'
  655. MoveNextWithValidation(ref enumerator);
  656. enumerator.SkipEoL();
  657. // parse identifier
  658. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  659. var methodName = enumerator.Current.Text;
  660. MoveNextWithValidation(ref enumerator);
  661. enumerator.SkipEoL();
  662. // parse arguments
  663. var arguments = ParseCallFunctionArguments(ref enumerator);
  664. result = new CallTableMethodExpressionNode(identifier ?? parentTable!, methodName.ToString(), arguments, current.Position);
  665. }
  666. else
  667. {
  668. LuaParseException.SyntaxError(ChunkName, current.Position, current);
  669. return null!; // dummy
  670. }
  671. return result;
  672. }
  673. GroupedExpressionNode ParseGroupedExpression(ref SyntaxTokenEnumerator enumerator)
  674. {
  675. // skip '('
  676. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out var lParen);
  677. enumerator.SkipEoL();
  678. var expression = ParseExpression(ref enumerator, GetPrecedence(enumerator.Current.Type));
  679. MoveNextWithValidation(ref enumerator);
  680. // check ')'
  681. CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
  682. return new GroupedExpressionNode(expression, lParen.Position);
  683. }
  684. ExpressionNode ParseCallFunctionExpression(ref SyntaxTokenEnumerator enumerator, ExpressionNode? function)
  685. {
  686. // parse name
  687. if (function == null)
  688. {
  689. CheckCurrent(ref enumerator, SyntaxTokenType.Identifier);
  690. function = new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position);
  691. enumerator.MoveNext();
  692. enumerator.SkipEoL();
  693. }
  694. // parse parameters
  695. var parameters = ParseCallFunctionArguments(ref enumerator);
  696. return new CallFunctionExpressionNode(function, parameters);
  697. }
  698. FunctionDeclarationExpressionNode ParseFunctionDeclarationExpression(ref SyntaxTokenEnumerator enumerator)
  699. {
  700. // skip 'function' keyword
  701. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken);
  702. enumerator.SkipEoL();
  703. var (_, Identifiers, Statements, HasVariableArgments, LineDefined, LastLineDefined) = ParseFunctionDeclarationCore(ref enumerator, true);
  704. return new FunctionDeclarationExpressionNode(Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, LastLineDefined);
  705. }
  706. ExpressionNode[] ParseCallFunctionArguments(ref SyntaxTokenEnumerator enumerator)
  707. {
  708. if (enumerator.Current.Type is SyntaxTokenType.String)
  709. {
  710. return [new StringLiteralNode(enumerator.Current.Text, true, enumerator.Current.Position)];
  711. }
  712. else if (enumerator.Current.Type is SyntaxTokenType.RawString)
  713. {
  714. return [new StringLiteralNode(enumerator.Current.Text, false, enumerator.Current.Position)];
  715. }
  716. else if (enumerator.Current.Type is SyntaxTokenType.LCurly)
  717. {
  718. return [ParseTableConstructorExpression(ref enumerator)];
  719. }
  720. // check and skip '('
  721. CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out _);
  722. ExpressionNode[] arguments;
  723. if (enumerator.Current.Type is SyntaxTokenType.RParen)
  724. {
  725. // parameterless
  726. arguments = [];
  727. }
  728. else
  729. {
  730. // parse arguments
  731. arguments = ParseExpressionList(ref enumerator);
  732. enumerator.SkipEoL();
  733. MoveNextWithValidation(ref enumerator);
  734. enumerator.SkipEoL();
  735. // check ')'
  736. CheckCurrent(ref enumerator, SyntaxTokenType.RParen);
  737. }
  738. return arguments;
  739. }
  740. ExpressionNode[] ParseExpressionList(ref SyntaxTokenEnumerator enumerator)
  741. {
  742. using var builder = new PooledList<ExpressionNode>(8);
  743. while (true)
  744. {
  745. enumerator.SkipEoL();
  746. if (!TryParseExpression(ref enumerator, OperatorPrecedence.NonOperator, out var expression))
  747. {
  748. enumerator.MovePrevious();
  749. break;
  750. }
  751. builder.Add(expression);
  752. enumerator.SkipEoL();
  753. if (enumerator.GetNext().Type != SyntaxTokenType.Comma) break;
  754. MoveNextWithValidation(ref enumerator);
  755. enumerator.SkipEoL();
  756. if (!enumerator.MoveNext()) break;
  757. }
  758. return builder.AsSpan().ToArray();
  759. }
  760. IdentifierNode[] ParseIdentifierList(ref SyntaxTokenEnumerator enumerator)
  761. {
  762. using var buffer = new PooledList<IdentifierNode>(8);
  763. while (true)
  764. {
  765. if (enumerator.Current.Type != SyntaxTokenType.Identifier) break;
  766. var identifier = new IdentifierNode(enumerator.Current.Text, enumerator.Current.Position);
  767. buffer.Add(identifier);
  768. MoveNextWithValidation(ref enumerator);
  769. enumerator.SkipEoL();
  770. if (enumerator.Current.Type != SyntaxTokenType.Comma) break;
  771. MoveNextWithValidation(ref enumerator);
  772. enumerator.SkipEoL();
  773. }
  774. return buffer.AsSpan().ToArray();
  775. }
  776. StatementNode[] ParseStatementList(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType endTokenType, out SyntaxToken endToken)
  777. {
  778. using var statements = new PooledList<StatementNode>(64);
  779. // parse statements
  780. while (enumerator.MoveNext())
  781. {
  782. if (enumerator.Current.Type == endTokenType) break;
  783. if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue;
  784. var node = ParseStatement(ref enumerator);
  785. statements.Add(node);
  786. }
  787. endToken = enumerator.Current;
  788. return statements.AsSpan().ToArray();
  789. }
  790. void CheckCurrentAndSkip(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType expectedToken, out SyntaxToken token)
  791. {
  792. CheckCurrent(ref enumerator, expectedToken);
  793. token = enumerator.Current;
  794. MoveNextWithValidation(ref enumerator);
  795. }
  796. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  797. void CheckCurrent(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType expectedToken)
  798. {
  799. if (enumerator.Current.Type != expectedToken)
  800. {
  801. LuaParseException.ExpectedToken(ChunkName, enumerator.Current.Position, expectedToken);
  802. }
  803. }
  804. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  805. void MoveNextWithValidation(ref SyntaxTokenEnumerator enumerator)
  806. {
  807. if (!enumerator.MoveNext()) LuaParseException.SyntaxError(ChunkName, enumerator.Current.Position, enumerator.Current);
  808. }
  809. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  810. static OperatorPrecedence GetPrecedence(SyntaxTokenType type)
  811. {
  812. return type switch
  813. {
  814. SyntaxTokenType.Addition or SyntaxTokenType.Subtraction => OperatorPrecedence.Addition,
  815. SyntaxTokenType.Multiplication or SyntaxTokenType.Division or SyntaxTokenType.Modulo => OperatorPrecedence.Multiplication,
  816. SyntaxTokenType.Equality or SyntaxTokenType.Inequality or SyntaxTokenType.LessThan or SyntaxTokenType.LessThanOrEqual or SyntaxTokenType.GreaterThan or SyntaxTokenType.GreaterThanOrEqual => OperatorPrecedence.Relational,
  817. SyntaxTokenType.Concat => OperatorPrecedence.Concat,
  818. SyntaxTokenType.Exponentiation => OperatorPrecedence.Exponentiation,
  819. SyntaxTokenType.And => OperatorPrecedence.And,
  820. SyntaxTokenType.Or => OperatorPrecedence.Or,
  821. _ => OperatorPrecedence.NonOperator,
  822. };
  823. }
  824. static double ConvertTextToNumber(ReadOnlySpan<char> text)
  825. {
  826. if (text.Length > 2 && text[0] is '0' && text[1] is 'x' or 'X')
  827. {
  828. return HexConverter.ToDouble(text);
  829. }
  830. else
  831. {
  832. return double.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);
  833. }
  834. }
  835. }