Parser.cs 38 KB

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