LuaCompiler.cs 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287
  1. using Lua.Internal;
  2. using Lua.CodeAnalysis.Syntax;
  3. using Lua.CodeAnalysis.Syntax.Nodes;
  4. using Lua.Runtime;
  5. namespace Lua.CodeAnalysis.Compilation;
  6. public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bool>
  7. {
  8. public static readonly LuaCompiler Default = new();
  9. public Chunk Compile(string source, string? chunkName = null)
  10. {
  11. return Compile(LuaSyntaxTree.Parse(source, chunkName), chunkName);
  12. }
  13. /// <summary>
  14. /// Returns a compiled chunk of the syntax tree.
  15. /// </summary>
  16. public Chunk Compile(LuaSyntaxTree syntaxTree, string? chunkName = null)
  17. {
  18. using var context = FunctionCompilationContext.Create(null);
  19. context.HasVariableArguments = true;
  20. context.LineDefined = syntaxTree.Position.Line;
  21. context.LastLineDefined = syntaxTree.Position.Line;
  22. // set global enviroment upvalue
  23. context.AddUpValue(new()
  24. {
  25. Name = "_ENV".AsMemory(), Id = 0, Index = -1, IsInRegister = false,
  26. });
  27. context.ChunkName = chunkName;
  28. syntaxTree.Accept(this, context.Scope);
  29. return context.ToChunk();
  30. }
  31. // Syntax Tree
  32. public bool VisitSyntaxTree(LuaSyntaxTree node, ScopeCompilationContext context)
  33. {
  34. foreach (var childNode in node.Nodes)
  35. {
  36. childNode.Accept(this, context);
  37. }
  38. return true;
  39. }
  40. // Literals
  41. public bool VisitNilLiteralNode(NilLiteralNode node, ScopeCompilationContext context)
  42. {
  43. context.PushInstruction(Instruction.LoadNil(context.StackPosition, 1), node.Position, true);
  44. return true;
  45. }
  46. public bool VisitBooleanLiteralNode(BooleanLiteralNode node, ScopeCompilationContext context)
  47. {
  48. context.PushInstruction(Instruction.LoadBool(context.StackPosition, (ushort)(node.Value ? 1 : 0), 0), node.Position, true);
  49. return true;
  50. }
  51. public bool VisitNumericLiteralNode(NumericLiteralNode node, ScopeCompilationContext context)
  52. {
  53. var index = context.Function.GetConstantIndex(node.Value);
  54. context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.Position, true);
  55. return true;
  56. }
  57. public bool VisitStringLiteralNode(StringLiteralNode node, ScopeCompilationContext context)
  58. {
  59. string? str;
  60. if (node.IsShortLiteral)
  61. {
  62. if (!StringHelper.TryFromStringLiteral(node.Text.Span, out str))
  63. {
  64. throw new LuaParseException(context.Function.ChunkName, node.Position, $"invalid escape sequence near '{node.Text}'");
  65. }
  66. }
  67. else
  68. {
  69. str = node.Text.ToString();
  70. }
  71. var index = context.Function.GetConstantIndex(str);
  72. context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.Position, true);
  73. return true;
  74. }
  75. // identifier
  76. public bool VisitIdentifierNode(IdentifierNode node, ScopeCompilationContext context)
  77. {
  78. GetOrLoadIdentifier(node.Name, context, node.Position, false);
  79. return true;
  80. }
  81. // vararg
  82. public bool VisitVariableArgumentsExpressionNode(VariableArgumentsExpressionNode node, ScopeCompilationContext context)
  83. {
  84. CompileVariableArgumentsExpression(node, context, 1);
  85. return true;
  86. }
  87. void CompileVariableArgumentsExpression(VariableArgumentsExpressionNode node, ScopeCompilationContext context, int resultCount)
  88. {
  89. context.PushInstruction(Instruction.VarArg(context.StackPosition, (ushort)(resultCount == -1 ? 0 : resultCount + 1)), node.Position, true);
  90. }
  91. // Unary/Binary expression
  92. public bool VisitUnaryExpressionNode(UnaryExpressionNode node, ScopeCompilationContext context)
  93. {
  94. var b = context.StackPosition;
  95. node.Node.Accept(this, context);
  96. switch (node.Operator)
  97. {
  98. case UnaryOperator.Negate:
  99. context.PushInstruction(Instruction.Unm(b, b), node.Position);
  100. break;
  101. case UnaryOperator.Not:
  102. context.PushInstruction(Instruction.Not(b, b), node.Position);
  103. break;
  104. case UnaryOperator.Length:
  105. context.PushInstruction(Instruction.Len(b, b), node.Position);
  106. break;
  107. }
  108. return true;
  109. }
  110. public bool VisitBinaryExpressionNode(BinaryExpressionNode node, ScopeCompilationContext context)
  111. {
  112. var r = context.StackPosition;
  113. if (node.OperatorType is BinaryOperator.And or BinaryOperator.Or)
  114. {
  115. byte a;
  116. if (node.LeftNode is IdentifierNode leftIdentifier)
  117. {
  118. a = GetOrLoadIdentifier(leftIdentifier.Name, context, leftIdentifier.Position, true);
  119. }
  120. else
  121. {
  122. node.LeftNode.Accept(this, context);
  123. a = context.StackTopPosition;
  124. }
  125. context.PushInstruction(Instruction.Test(a, 0), node.Position);
  126. if (node.OperatorType is BinaryOperator.Or)
  127. {
  128. context.PushInstruction(Instruction.Jmp(0, 2), node.Position);
  129. context.PushInstruction(Instruction.Move(r, a), node.Position);
  130. }
  131. var testJmpIndex = context.Function.Instructions.Length;
  132. context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
  133. context.StackPosition = r;
  134. node.RightNode.Accept(this, context);
  135. context.Function.Instructions[testJmpIndex].SBx = context.Function.Instructions.Length - testJmpIndex - 1;
  136. }
  137. else
  138. {
  139. var b = (ushort)GetRKIndex(node.LeftNode, context);
  140. var c = (ushort)GetRKIndex(node.RightNode, context);
  141. switch (node.OperatorType)
  142. {
  143. case BinaryOperator.Addition:
  144. context.PushInstruction(Instruction.Add(r, b, c), node.Position);
  145. break;
  146. case BinaryOperator.Subtraction:
  147. context.PushInstruction(Instruction.Sub(r, b, c), node.Position);
  148. break;
  149. case BinaryOperator.Multiplication:
  150. context.PushInstruction(Instruction.Mul(r, b, c), node.Position);
  151. break;
  152. case BinaryOperator.Division:
  153. context.PushInstruction(Instruction.Div(r, b, c), node.Position);
  154. break;
  155. case BinaryOperator.Modulo:
  156. context.PushInstruction(Instruction.Mod(r, b, c), node.Position);
  157. break;
  158. case BinaryOperator.Exponentiation:
  159. context.PushInstruction(Instruction.Pow(r, b, c), node.Position);
  160. break;
  161. case BinaryOperator.Equality:
  162. context.PushInstruction(Instruction.Eq(1, b, c), node.Position);
  163. context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
  164. context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
  165. break;
  166. case BinaryOperator.Inequality:
  167. context.PushInstruction(Instruction.Eq(0, b, c), node.Position);
  168. context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
  169. context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
  170. break;
  171. case BinaryOperator.GreaterThan:
  172. context.PushInstruction(Instruction.Lt(1, c, b), node.Position);
  173. context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
  174. context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
  175. break;
  176. case BinaryOperator.GreaterThanOrEqual:
  177. context.PushInstruction(Instruction.Le(1, c, b), node.Position);
  178. context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
  179. context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
  180. break;
  181. case BinaryOperator.LessThan:
  182. context.PushInstruction(Instruction.Lt(1, b, c), node.Position);
  183. context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
  184. context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
  185. break;
  186. case BinaryOperator.LessThanOrEqual:
  187. context.PushInstruction(Instruction.Le(1, b, c), node.Position);
  188. context.PushInstruction(Instruction.LoadBool(r, 1, 1), node.Position);
  189. context.PushInstruction(Instruction.LoadBool(r, 0, 0), node.Position);
  190. break;
  191. case BinaryOperator.Concat:
  192. context.PushInstruction(Instruction.Concat(r, b, c), node.Position);
  193. break;
  194. }
  195. context.StackPosition = (byte)(r + 1);
  196. }
  197. return true;
  198. }
  199. public bool VisitGroupedExpressionNode(GroupedExpressionNode node, ScopeCompilationContext context)
  200. {
  201. return node.Expression.Accept(this, context);
  202. }
  203. // table
  204. public bool VisitTableConstructorExpressionNode(TableConstructorExpressionNode node, ScopeCompilationContext context)
  205. {
  206. var tableRegisterIndex = context.StackPosition;
  207. var newTableInstructionIndex = context.Function.Instructions.Length;
  208. context.PushInstruction(Instruction.NewTable(tableRegisterIndex, 0, 0), node.Position, true);
  209. var currentArrayChunkSize = 0;
  210. ushort hashMapSize = 0;
  211. ushort arrayBlock = 1;
  212. ListTableConstructorField? lastField = null;
  213. if (node.Fields.LastOrDefault() is ListTableConstructorField t)
  214. {
  215. lastField = t;
  216. }
  217. foreach (var group in node.Fields.GroupConsecutiveBy(x => x.GetType()))
  218. {
  219. foreach (var field in group)
  220. {
  221. var p = context.StackPosition;
  222. switch (field)
  223. {
  224. case ListTableConstructorField listItem:
  225. context.StackPosition = (byte)(p + currentArrayChunkSize - 50 * (arrayBlock - 1));
  226. // For the last element, we need to take into account variable arguments and multiple return values.
  227. if (listItem == lastField)
  228. {
  229. bool isFixedItems = true;
  230. switch (listItem.Expression)
  231. {
  232. case CallFunctionExpressionNode call:
  233. CompileCallFunctionExpression(call, context, false, -1);
  234. isFixedItems = false;
  235. break;
  236. case CallTableMethodExpressionNode method:
  237. CompileTableMethod(method, context, false, -1);
  238. break;
  239. case VariableArgumentsExpressionNode varArg:
  240. CompileVariableArgumentsExpression(varArg, context, -1);
  241. isFixedItems = false;
  242. break;
  243. default:
  244. listItem.Expression.Accept(this, context);
  245. break;
  246. }
  247. context.PushInstruction(Instruction.SetList(tableRegisterIndex, (ushort)(isFixedItems ? context.StackTopPosition - tableRegisterIndex : 0), arrayBlock), listItem.Position);
  248. currentArrayChunkSize = 0;
  249. }
  250. else
  251. {
  252. listItem.Expression.Accept(this, context);
  253. currentArrayChunkSize++;
  254. if (currentArrayChunkSize == 50)
  255. {
  256. context.PushInstruction(Instruction.SetList(tableRegisterIndex, 50, arrayBlock), listItem.Position);
  257. currentArrayChunkSize = 0;
  258. arrayBlock++;
  259. }
  260. }
  261. break;
  262. case RecordTableConstructorField recordItem:
  263. recordItem.ValueExpression.Accept(this, context);
  264. var keyConstIndex = context.Function.GetConstantIndex(recordItem.Key) + 256;
  265. context.PushInstruction(Instruction.SetTable(tableRegisterIndex, (ushort)keyConstIndex, p), recordItem.Position);
  266. hashMapSize++;
  267. break;
  268. case GeneralTableConstructorField generalItem:
  269. var keyIndex = context.StackPosition;
  270. generalItem.KeyExpression.Accept(this, context);
  271. var valueIndex = context.StackPosition;
  272. generalItem.ValueExpression.Accept(this, context);
  273. context.PushInstruction(Instruction.SetTable(tableRegisterIndex, keyIndex, valueIndex), generalItem.Position);
  274. hashMapSize++;
  275. break;
  276. default:
  277. throw new NotSupportedException();
  278. }
  279. context.StackPosition = p;
  280. }
  281. if (currentArrayChunkSize > 0)
  282. {
  283. context.PushInstruction(Instruction.SetList(tableRegisterIndex, (ushort)currentArrayChunkSize, arrayBlock), node.Position);
  284. currentArrayChunkSize = 0;
  285. arrayBlock = 1;
  286. }
  287. }
  288. context.Function.Instructions[newTableInstructionIndex].B = (ushort)(currentArrayChunkSize + (arrayBlock - 1) * 50);
  289. context.Function.Instructions[newTableInstructionIndex].C = hashMapSize;
  290. return true;
  291. }
  292. public bool VisitTableIndexerAccessExpressionNode(TableIndexerAccessExpressionNode node, ScopeCompilationContext context)
  293. {
  294. // load table
  295. var tablePosition = context.StackPosition;
  296. node.TableNode.Accept(this, context);
  297. // load key
  298. var keyPosition = (ushort)GetRKIndex(node.KeyNode, context);
  299. // push interuction
  300. context.PushInstruction(Instruction.GetTable(tablePosition, tablePosition, keyPosition), node.Position);
  301. context.StackPosition = (byte)(tablePosition + 1);
  302. return true;
  303. }
  304. public bool VisitTableMemberAccessExpressionNode(TableMemberAccessExpressionNode node, ScopeCompilationContext context)
  305. {
  306. // load table
  307. var tablePosition = context.StackPosition;
  308. node.TableNode.Accept(this, context);
  309. // load key
  310. var keyIndex = context.Function.GetConstantIndex(node.MemberName) + 256;
  311. // push interuction
  312. context.PushInstruction(Instruction.GetTable(tablePosition, tablePosition, (ushort)keyIndex), node.Position);
  313. context.StackPosition = (byte)(tablePosition + 1);
  314. return true;
  315. }
  316. public bool VisitCallTableMethodExpressionNode(CallTableMethodExpressionNode node, ScopeCompilationContext context)
  317. {
  318. CompileTableMethod(node, context, false, 1);
  319. return true;
  320. }
  321. public bool VisitCallTableMethodStatementNode(CallTableMethodStatementNode node, ScopeCompilationContext context)
  322. {
  323. CompileTableMethod(node.Expression, context, false, 0);
  324. return true;
  325. }
  326. void CompileTableMethod(CallTableMethodExpressionNode node, ScopeCompilationContext context, bool isTailCall, int resultCount)
  327. {
  328. // load table
  329. var tablePosition = context.StackPosition;
  330. node.TableNode.Accept(this, context);
  331. // load key
  332. var keyIndex = context.Function.GetConstantIndex(node.MethodName) + 256;
  333. // get closure
  334. context.PushInstruction(Instruction.Self(tablePosition, tablePosition, (ushort)keyIndex), node.Position);
  335. context.StackPosition = (byte)(tablePosition + 2);
  336. // load arguments
  337. var b = node.ArgumentNodes.Length + 2;
  338. if (node.ArgumentNodes.Length > 0 && !IsFixedNumberOfReturnValues(node.ArgumentNodes[^1]))
  339. {
  340. b = 0;
  341. }
  342. CompileExpressionList(node, node.ArgumentNodes, b - 2, context);
  343. // push call interuction
  344. if (isTailCall)
  345. {
  346. context.PushInstruction(Instruction.TailCall(tablePosition, (ushort)b, 0), node.Position);
  347. context.StackPosition = tablePosition;
  348. }
  349. else
  350. {
  351. context.PushInstruction(Instruction.Call(tablePosition, (ushort)b, (ushort)(resultCount < 0 ? 0 : resultCount + 1)), node.Position);
  352. context.StackPosition = (byte)(tablePosition + resultCount);
  353. }
  354. }
  355. // return
  356. public bool VisitReturnStatementNode(ReturnStatementNode node, ScopeCompilationContext context)
  357. {
  358. ushort b;
  359. // tail call
  360. if (node.Nodes.Length == 1)
  361. {
  362. var lastNode = node.Nodes[^1];
  363. if (lastNode is CallFunctionExpressionNode call)
  364. {
  365. CompileCallFunctionExpression(call, context, true, -1);
  366. return true;
  367. }
  368. else if (lastNode is CallTableMethodExpressionNode callMethod)
  369. {
  370. CompileTableMethod(callMethod, context, true, -1);
  371. return true;
  372. }
  373. }
  374. b = node.Nodes.Length > 0 && !IsFixedNumberOfReturnValues(node.Nodes[^1])
  375. ? (ushort)0
  376. : (ushort)(node.Nodes.Length + 1);
  377. var a = context.StackPosition;
  378. CompileExpressionList(node, node.Nodes, b - 1, context);
  379. context.PushInstruction(Instruction.Return(a, b), node.Position);
  380. return true;
  381. }
  382. // assignment
  383. public bool VisitLocalAssignmentStatementNode(LocalAssignmentStatementNode node, ScopeCompilationContext context)
  384. {
  385. var startPosition = context.StackPosition;
  386. CompileExpressionList(node, node.RightNodes, node.LeftNodes.Length, context);
  387. for (int i = 0; i < node.Identifiers.Length; i++)
  388. {
  389. context.StackPosition = (byte)(startPosition + i + 1);
  390. var identifier = node.Identifiers[i];
  391. if (context.TryGetLocalVariableInThisScope(identifier.Name, out var variable))
  392. {
  393. // assign local variable
  394. context.PushInstruction(Instruction.Move(variable.RegisterIndex, (ushort)(context.StackPosition - 1)), node.Position, true);
  395. }
  396. else
  397. {
  398. // register local variable
  399. context.AddLocalVariable(identifier.Name, new() { RegisterIndex = (byte)(context.StackPosition - 1), StartPc = context.Function.Instructions.Length, });
  400. }
  401. }
  402. return true;
  403. }
  404. public bool VisitAssignmentStatementNode(AssignmentStatementNode node, ScopeCompilationContext context)
  405. {
  406. var startPosition = context.StackPosition;
  407. CompileExpressionList(node, node.RightNodes, node.LeftNodes.Length, context);
  408. for (int i = 0; i < node.LeftNodes.Length; i++)
  409. {
  410. context.StackPosition = (byte)(startPosition + i + 1);
  411. var leftNode = node.LeftNodes[i];
  412. switch (leftNode)
  413. {
  414. case IdentifierNode identifier:
  415. {
  416. if (context.TryGetLocalVariable(identifier.Name, out var variable))
  417. {
  418. // assign local variable
  419. context.PushInstruction(Instruction.Move(variable.RegisterIndex, (ushort)(context.StackPosition - 1)), node.Position, true);
  420. }
  421. else if (context.Function.TryGetUpValue(identifier.Name, out var upValue))
  422. {
  423. // assign upvalue
  424. context.PushInstruction(Instruction.SetUpVal((byte)(context.StackPosition - 1), (ushort)upValue.Id), node.Position);
  425. }
  426. else if (context.TryGetLocalVariable("_ENV".AsMemory(), out variable))
  427. {
  428. // assign env element
  429. var index = context.Function.GetConstantIndex(identifier.Name.ToString()) + 256;
  430. context.PushInstruction(Instruction.SetTable(variable.RegisterIndex, (ushort)index, (ushort)(context.StackPosition - 1)), node.Position);
  431. }
  432. else
  433. {
  434. // assign global variable
  435. var index = context.Function.GetConstantIndex(identifier.Name.ToString()) + 256;
  436. context.PushInstruction(Instruction.SetTabUp(0, (ushort)index, (ushort)(context.StackPosition - 1)), node.Position);
  437. }
  438. }
  439. break;
  440. case TableIndexerAccessExpressionNode tableIndexer:
  441. {
  442. var valueIndex = context.StackPosition - 1;
  443. tableIndexer.TableNode.Accept(this, context);
  444. var tableIndex = context.StackPosition - 1;
  445. tableIndexer.KeyNode.Accept(this, context);
  446. var keyIndex = context.StackPosition - 1;
  447. context.PushInstruction(Instruction.SetTable((byte)tableIndex, (ushort)keyIndex, (ushort)valueIndex), node.Position);
  448. }
  449. break;
  450. case TableMemberAccessExpressionNode tableMember:
  451. {
  452. var valueIndex = context.StackPosition - 1;
  453. tableMember.TableNode.Accept(this, context);
  454. var tableIndex = context.StackPosition - 1;
  455. var keyIndex = context.Function.GetConstantIndex(tableMember.MemberName) + 256;
  456. context.PushInstruction(Instruction.SetTable((byte)tableIndex, (ushort)keyIndex, (ushort)valueIndex), node.Position);
  457. }
  458. break;
  459. default:
  460. throw new LuaParseException(default, default, "An error occurred while parsing the code"); // TODO: add message
  461. }
  462. }
  463. context.StackPosition = startPosition;
  464. return true;
  465. }
  466. // function call
  467. public bool VisitCallFunctionStatementNode(CallFunctionStatementNode node, ScopeCompilationContext context)
  468. {
  469. CompileCallFunctionExpression(node.Expression, context, false, 0);
  470. return true;
  471. }
  472. public bool VisitCallFunctionExpressionNode(CallFunctionExpressionNode node, ScopeCompilationContext context)
  473. {
  474. CompileCallFunctionExpression(node, context, false, 1);
  475. return true;
  476. }
  477. void CompileCallFunctionExpression(CallFunctionExpressionNode node, ScopeCompilationContext context, bool isTailCall, int resultCount)
  478. {
  479. // get closure
  480. var r = context.StackPosition;
  481. node.FunctionNode.Accept(this, context);
  482. // load arguments
  483. var b = node.ArgumentNodes.Length + 1;
  484. if (node.ArgumentNodes.Length > 0 && !IsFixedNumberOfReturnValues(node.ArgumentNodes[^1]))
  485. {
  486. b = 0;
  487. }
  488. CompileExpressionList(node, node.ArgumentNodes, b - 1, context);
  489. // push call interuction
  490. if (isTailCall)
  491. {
  492. context.PushInstruction(Instruction.TailCall(r, (ushort)b, 0), node.Position);
  493. context.StackPosition = r;
  494. }
  495. else
  496. {
  497. context.PushInstruction(Instruction.Call(r, (ushort)b, (ushort)(resultCount == -1 ? 0 : resultCount + 1)), node.Position);
  498. context.StackPosition = (byte)(r + resultCount);
  499. }
  500. }
  501. // function declaration
  502. public bool VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, ScopeCompilationContext context)
  503. {
  504. var funcIndex = CompileFunctionProto(ReadOnlyMemory<char>.Empty, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.EndPosition.Line);
  505. // push closure instruction
  506. context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true);
  507. return true;
  508. }
  509. public bool VisitLocalFunctionDeclarationStatementNode(LocalFunctionDeclarationStatementNode node, ScopeCompilationContext context)
  510. {
  511. // assign local variable
  512. context.AddLocalVariable(node.Name, new() { RegisterIndex = context.StackPosition, StartPc = context.Function.Instructions.Length, });
  513. // compile function
  514. var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.EndPosition.Line);
  515. // push closure instruction
  516. context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true);
  517. return true;
  518. }
  519. public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, ScopeCompilationContext context)
  520. {
  521. var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.EndPosition.Line);
  522. // add closure
  523. var index = context.Function.GetConstantIndex(node.Name.ToString());
  524. // push closure instruction
  525. context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true);
  526. if (context.TryGetLocalVariableInThisScope(node.Name, out var variable))
  527. {
  528. // assign local variable
  529. context.PushInstruction(Instruction.Move(variable.RegisterIndex, (ushort)(context.StackPosition - 1)), node.Position, true);
  530. }
  531. else
  532. {
  533. // assign global variable
  534. context.PushInstruction(Instruction.SetTabUp(0, (ushort)(index + 256), (ushort)(context.StackPosition - 1)), node.Position);
  535. }
  536. return true;
  537. }
  538. public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, ScopeCompilationContext context)
  539. {
  540. var funcIdentifier = node.MemberPath[^1];
  541. var funcIndex = CompileFunctionProto(funcIdentifier.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length + 1, node.HasVariableArguments, node.HasSelfParameter, node.LineDefined, node.EndPosition.Line);
  542. // add closure
  543. var index = context.Function.GetConstantIndex(funcIdentifier.Name.ToString());
  544. var r = context.StackPosition;
  545. // assign global variable
  546. var first = node.MemberPath[0];
  547. var tableIndex = GetOrLoadIdentifier(first.Name, context, first.Position, true);
  548. for (int i = 1; i < node.MemberPath.Length - 1; i++)
  549. {
  550. var member = node.MemberPath[i];
  551. var constant = context.Function.GetConstantIndex(member.Name.ToString());
  552. context.PushInstruction(Instruction.GetTable(context.StackPosition, tableIndex, (ushort)(constant + 256)), member.Position, true);
  553. tableIndex = context.StackTopPosition;
  554. }
  555. // push closure instruction
  556. var closureIndex = context.StackPosition;
  557. context.PushInstruction(Instruction.Closure(closureIndex, funcIndex), node.EndPosition, true);
  558. // set table
  559. context.PushInstruction(Instruction.SetTable(tableIndex, (ushort)(index + 256), closureIndex), funcIdentifier.Position);
  560. context.StackPosition = r;
  561. return true;
  562. }
  563. int CompileFunctionProto(ReadOnlyMemory<char> functionName, ScopeCompilationContext context, IdentifierNode[] parameters, SyntaxNode[] statements, int parameterCount, bool hasVarArg, bool hasSelfParameter, int lineDefined, int lastLineDefined)
  564. {
  565. using var funcContext = context.CreateChildFunction();
  566. funcContext.ChunkName = functionName.ToString();
  567. funcContext.ParameterCount = parameterCount;
  568. funcContext.HasVariableArguments = hasVarArg;
  569. funcContext.LineDefined = lineDefined;
  570. funcContext.LastLineDefined = lastLineDefined;
  571. if (hasSelfParameter)
  572. {
  573. funcContext.Scope.AddLocalVariable("self".AsMemory(), new() { RegisterIndex = 0, StartPc = 0, });
  574. funcContext.Scope.StackPosition++;
  575. }
  576. // add arguments
  577. for (int i = 0; i < parameters.Length; i++)
  578. {
  579. var parameter = parameters[i];
  580. funcContext.Scope.AddLocalVariable(parameter.Name, new() { RegisterIndex = (byte)(i + (hasSelfParameter ? 1 : 0)), StartPc = 0, });
  581. funcContext.Scope.StackPosition++;
  582. }
  583. foreach (var statement in statements)
  584. {
  585. statement.Accept(this, funcContext.Scope);
  586. }
  587. // compile function
  588. var chunk = funcContext.ToChunk();
  589. int index;
  590. if (functionName.Length == 0)
  591. {
  592. // anonymous function
  593. context.Function.AddFunctionProto(chunk, out index);
  594. }
  595. else
  596. {
  597. context.Function.AddOrSetFunctionProto(functionName, chunk, out index);
  598. }
  599. return index;
  600. }
  601. // control statements
  602. public bool VisitDoStatementNode(DoStatementNode node, ScopeCompilationContext context)
  603. {
  604. using var scopeContext = context.CreateChildScope();
  605. foreach (var childNode in node.StatementNodes)
  606. {
  607. childNode.Accept(this, scopeContext);
  608. }
  609. scopeContext.TryPushCloseUpValue(scopeContext.StackTopPosition, node.Position);
  610. return true;
  611. }
  612. public bool VisitBreakStatementNode(BreakStatementNode node, ScopeCompilationContext context)
  613. {
  614. context.Function.AddUnresolvedBreak(new() { Index = context.Function.Instructions.Length }, node.Position);
  615. context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
  616. return true;
  617. }
  618. public bool VisitIfStatementNode(IfStatementNode node, ScopeCompilationContext context)
  619. {
  620. using var endJumpIndexList = new PooledList<int>(8);
  621. var hasElse = node.ElseNodes.Length > 0;
  622. var stackPositionToClose = (byte)(context.StackPosition + 1);
  623. // if
  624. using (var scopeContext = context.CreateChildScope())
  625. {
  626. CompileConditionNode(node.IfNode.ConditionNode, scopeContext, true, node.IfNode.Position);
  627. var ifPosition = scopeContext.Function.Instructions.Length;
  628. scopeContext.PushInstruction(Instruction.Jmp(0, 0), node.IfNode.Position);
  629. foreach (var childNode in node.IfNode.ThenNodes)
  630. {
  631. childNode.Accept(this, scopeContext);
  632. }
  633. stackPositionToClose = scopeContext.HasCapturedLocalVariables ? stackPositionToClose : (byte)0;
  634. if (hasElse)
  635. {
  636. endJumpIndexList.Add(scopeContext.Function.Instructions.Length);
  637. scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), node.IfNode.ThenNodes[^1].Position, true);
  638. }
  639. else
  640. {
  641. scopeContext.TryPushCloseUpValue(stackPositionToClose, node.Position);
  642. }
  643. scopeContext.Function.Instructions[ifPosition].SBx = scopeContext.Function.Instructions.Length - 1 - ifPosition;
  644. }
  645. // elseif
  646. foreach (var elseIf in node.ElseIfNodes)
  647. {
  648. using var scopeContext = context.CreateChildScope();
  649. CompileConditionNode(elseIf.ConditionNode, scopeContext, true);
  650. var elseifPosition = scopeContext.Function.Instructions.Length;
  651. scopeContext.PushInstruction(Instruction.Jmp(0, 0), elseIf.Position);
  652. foreach (var childNode in elseIf.ThenNodes)
  653. {
  654. childNode.Accept(this, scopeContext);
  655. }
  656. stackPositionToClose = scopeContext.HasCapturedLocalVariables ? stackPositionToClose : (byte)0;
  657. // skip if node doesn't have else statements
  658. if (hasElse)
  659. {
  660. endJumpIndexList.Add(scopeContext.Function.Instructions.Length);
  661. scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), elseIf.Position);
  662. }
  663. else
  664. {
  665. scopeContext.TryPushCloseUpValue(stackPositionToClose, elseIf.Position);
  666. }
  667. scopeContext.Function.Instructions[elseifPosition].SBx = scopeContext.Function.Instructions.Length - 1 - elseifPosition;
  668. }
  669. // else nodes
  670. using (var scopeContext = context.CreateChildScope())
  671. {
  672. foreach (var childNode in node.ElseNodes)
  673. {
  674. childNode.Accept(this, scopeContext);
  675. }
  676. scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, node.Position);
  677. }
  678. // set JMP sBx
  679. foreach (var index in endJumpIndexList.AsSpan())
  680. {
  681. context.Function.Instructions[index].SBx = context.Function.Instructions.Length - 1 - index;
  682. }
  683. return true;
  684. }
  685. public bool VisitRepeatStatementNode(RepeatStatementNode node, ScopeCompilationContext context)
  686. {
  687. var startIndex = context.Function.Instructions.Length;
  688. context.Function.LoopLevel++;
  689. using var scopeContext = context.CreateChildScope();
  690. var stackPosition = scopeContext.StackPosition;
  691. foreach (var childNode in node.Nodes)
  692. {
  693. childNode.Accept(this, scopeContext);
  694. }
  695. CompileConditionNode(node.ConditionNode, scopeContext, true);
  696. var a = scopeContext.HasCapturedLocalVariables ? (byte)(stackPosition + 1) : (byte)0;
  697. var untilPosition = node.ConditionNode.Position;
  698. scopeContext.PushInstruction(Instruction.Jmp(a, startIndex - scopeContext.Function.Instructions.Length - 1), untilPosition);
  699. scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, untilPosition);
  700. context.Function.LoopLevel--;
  701. // resolve break statements inside repeat block
  702. context.Function.ResolveAllBreaks(a, context.Function.Instructions.Length - 1, scopeContext);
  703. return true;
  704. }
  705. public bool VisitWhileStatementNode(WhileStatementNode node, ScopeCompilationContext context)
  706. {
  707. var conditionIndex = context.Function.Instructions.Length;
  708. context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
  709. context.Function.LoopLevel++;
  710. using var scopeContext = context.CreateChildScope();
  711. var stackPosition = scopeContext.StackPosition;
  712. foreach (var childNode in node.Nodes)
  713. {
  714. childNode.Accept(this, scopeContext);
  715. }
  716. context.Function.LoopLevel--;
  717. // set JMP sBx
  718. scopeContext.Function.Instructions[conditionIndex].SBx = scopeContext.Function.Instructions.Length - 1 - conditionIndex;
  719. CompileConditionNode(node.ConditionNode, scopeContext, false);
  720. var a = scopeContext.HasCapturedLocalVariables ? (byte)(1 + stackPosition) : (byte)0;
  721. scopeContext.PushInstruction(Instruction.Jmp(a, conditionIndex - context.Function.Instructions.Length), node.Position);
  722. scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, node.Position);
  723. // resolve break statements inside while block
  724. context.Function.ResolveAllBreaks(scopeContext.StackPosition, context.Function.Instructions.Length - 1, scopeContext);
  725. return true;
  726. }
  727. public bool VisitNumericForStatementNode(NumericForStatementNode node, ScopeCompilationContext context)
  728. {
  729. var startPosition = context.StackPosition;
  730. node.InitNode.Accept(this, context);
  731. node.LimitNode.Accept(this, context);
  732. if (node.StepNode != null)
  733. {
  734. node.StepNode.Accept(this, context);
  735. }
  736. else
  737. {
  738. var index = context.Function.GetConstantIndex(1);
  739. context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.DoPosition, true);
  740. }
  741. var prepIndex = context.Function.Instructions.Length;
  742. context.PushInstruction(Instruction.ForPrep(startPosition, 0), node.DoPosition, true);
  743. // compile statements
  744. context.Function.LoopLevel++;
  745. using var scopeContext = context.CreateChildScope();
  746. {
  747. scopeContext.AddLocalVariable("(for index)".AsMemory(), new() { RegisterIndex = startPosition, StartPc = context.Function.Instructions.Length, });
  748. scopeContext.AddLocalVariable("(for limit)".AsMemory(), new() { RegisterIndex = (byte)(startPosition + 1), StartPc = context.Function.Instructions.Length, });
  749. scopeContext.AddLocalVariable("(for step)".AsMemory(), new() { RegisterIndex = (byte)(startPosition + 2), StartPc = context.Function.Instructions.Length, });
  750. // add local variable
  751. scopeContext.AddLocalVariable(node.VariableName, new() { RegisterIndex = (byte)(startPosition + 3), StartPc = context.Function.Instructions.Length, });
  752. foreach (var childNode in node.StatementNodes)
  753. {
  754. childNode.Accept(this, scopeContext);
  755. }
  756. scopeContext.TryPushCloseUpValue((byte)(startPosition + 1), node.Position);
  757. }
  758. context.Function.LoopLevel--;
  759. // set ForPrep
  760. context.Function.Instructions[prepIndex].SBx = context.Function.Instructions.Length - prepIndex - 1;
  761. // push ForLoop
  762. context.PushInstruction(Instruction.ForLoop(startPosition, prepIndex - context.Function.Instructions.Length), node.Position);
  763. context.Function.ResolveAllBreaks((byte)(startPosition + 1), context.Function.Instructions.Length - 1, scopeContext);
  764. context.StackPosition = startPosition;
  765. return true;
  766. }
  767. public bool VisitGenericForStatementNode(GenericForStatementNode node, ScopeCompilationContext context)
  768. {
  769. // get iterator
  770. var startPosition = context.StackPosition;
  771. CompileExpressionList(node, node.ExpressionNodes, 3, context);
  772. // jump to TFORCALL
  773. var startJumpIndex = context.Function.Instructions.Length;
  774. context.PushInstruction(Instruction.Jmp(0, 0), node.DoPosition);
  775. // compile statements
  776. context.Function.LoopLevel++;
  777. using var scopeContext = context.CreateChildScope();
  778. {
  779. scopeContext.StackPosition = (byte)(startPosition + 3 + node.Names.Length);
  780. scopeContext.AddLocalVariable("(for generator)".AsMemory(), new() { RegisterIndex = (byte)(startPosition), StartPc = context.Function.Instructions.Length, });
  781. scopeContext.AddLocalVariable("(for state)".AsMemory(), new() { RegisterIndex = (byte)(startPosition + 1), StartPc = context.Function.Instructions.Length, });
  782. scopeContext.AddLocalVariable("(for control)".AsMemory(), new() { RegisterIndex = (byte)(startPosition + 2), StartPc = context.Function.Instructions.Length, });
  783. // add local variables
  784. for (int i = 0; i < node.Names.Length; i++)
  785. {
  786. var name = node.Names[i];
  787. scopeContext.AddLocalVariable(name.Name, new() { RegisterIndex = (byte)(startPosition + 3 + i), StartPc = context.Function.Instructions.Length, });
  788. }
  789. foreach (var childNode in node.StatementNodes)
  790. {
  791. childNode.Accept(this, scopeContext);
  792. }
  793. scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, node.Position);
  794. }
  795. context.Function.LoopLevel--;
  796. // set jump
  797. context.Function.Instructions[startJumpIndex].SBx = context.Function.Instructions.Length - startJumpIndex - 1;
  798. // push OP_TFORCALL and OP_TFORLOOP
  799. context.PushInstruction(Instruction.TForCall(startPosition, (ushort)node.Names.Length), node.Position);
  800. context.PushInstruction(Instruction.TForLoop((byte)(startPosition + 2), startJumpIndex - context.Function.Instructions.Length), node.Position);
  801. context.Function.ResolveAllBreaks((byte)(startPosition + 1), context.Function.Instructions.Length - 1, scopeContext);
  802. context.StackPosition = startPosition;
  803. return true;
  804. }
  805. public bool VisitLabelStatementNode(LabelStatementNode node, ScopeCompilationContext context)
  806. {
  807. var desc = new LabelDescription() { Name = node.Name, Index = context.Function.Instructions.Length, RegisterIndex = context.StackPosition };
  808. context.AddLabel(desc);
  809. context.Function.ResolveGoto(desc);
  810. return true;
  811. }
  812. public bool VisitGotoStatementNode(GotoStatementNode node, ScopeCompilationContext context)
  813. {
  814. if (context.TryGetLabel(node.Name, out var description))
  815. {
  816. context.PushInstruction(Instruction.Jmp(description.RegisterIndex, description.Index - context.Function.Instructions.Length - 1), node.Position);
  817. }
  818. else
  819. {
  820. context.Function.AddUnresolvedGoto(new() { Name = node.Name, JumpInstructionIndex = context.Function.Instructions.Length });
  821. // add uninitialized jmp instruction
  822. context.PushInstruction(Instruction.Jmp(0, 0), node.Position);
  823. }
  824. return true;
  825. }
  826. static byte GetOrLoadIdentifier(ReadOnlyMemory<char> name, ScopeCompilationContext context, SourcePosition sourcePosition, bool dontLoadLocalVariable)
  827. {
  828. var p = context.StackPosition;
  829. if (context.TryGetLocalVariable(name, out var variable))
  830. {
  831. if (dontLoadLocalVariable)
  832. {
  833. return variable.RegisterIndex;
  834. }
  835. else if (p == variable.RegisterIndex)
  836. {
  837. context.StackPosition++;
  838. return p;
  839. }
  840. else
  841. {
  842. context.PushInstruction(Instruction.Move(p, variable.RegisterIndex), sourcePosition, true);
  843. return p;
  844. }
  845. }
  846. else if (context.Function.TryGetUpValue(name, out var upValue))
  847. {
  848. context.PushInstruction(Instruction.GetUpVal(p, (ushort)upValue.Id), sourcePosition, true);
  849. return p;
  850. }
  851. else if (context.TryGetLocalVariable("_ENV".AsMemory(), out variable))
  852. {
  853. var keyStringIndex = context.Function.GetConstantIndex(name.ToString()) + 256;
  854. context.PushInstruction(Instruction.GetTable(p, variable.RegisterIndex, (ushort)keyStringIndex), sourcePosition, true);
  855. return p;
  856. }
  857. else
  858. {
  859. context.Function.TryGetUpValue("_ENV".AsMemory(), out upValue);
  860. var index = context.Function.GetConstantIndex(name.ToString()) + 256;
  861. context.PushInstruction(Instruction.GetTabUp(p, (ushort)upValue.Id, (ushort)index), sourcePosition, true);
  862. return p;
  863. }
  864. }
  865. uint GetRKIndex(ExpressionNode node, ScopeCompilationContext context)
  866. {
  867. if (node is IdentifierNode identifier)
  868. {
  869. return GetOrLoadIdentifier(identifier.Name, context, identifier.Position, true);
  870. }
  871. else if (TryGetConstant(node, context, out var constant))
  872. {
  873. return context.Function.GetConstantIndex(constant) + 256;
  874. }
  875. else
  876. {
  877. node.Accept(this, context);
  878. return context.StackTopPosition;
  879. }
  880. }
  881. static bool TryGetConstant(ExpressionNode node, ScopeCompilationContext context, out LuaValue value)
  882. {
  883. switch (node)
  884. {
  885. case NilLiteralNode:
  886. value = LuaValue.Nil;
  887. return true;
  888. case BooleanLiteralNode booleanLiteral:
  889. value = booleanLiteral.Value;
  890. return true;
  891. case NumericLiteralNode numericLiteral:
  892. value = numericLiteral.Value;
  893. return true;
  894. case StringLiteralNode stringLiteral:
  895. if (stringLiteral.IsShortLiteral)
  896. {
  897. if (!StringHelper.TryFromStringLiteral(stringLiteral.Text.Span, out var str))
  898. {
  899. throw new LuaParseException(context.Function.ChunkName, stringLiteral.Position, $"invalid escape sequence near '{stringLiteral.Text}'");
  900. }
  901. value = str;
  902. }
  903. else
  904. {
  905. value = stringLiteral.Text.ToString();
  906. }
  907. return true;
  908. case UnaryExpressionNode unaryExpression:
  909. if (TryGetConstant(unaryExpression.Node, context, out var unaryNodeValue))
  910. {
  911. switch (unaryExpression.Operator)
  912. {
  913. case UnaryOperator.Negate:
  914. if (unaryNodeValue.TryRead<double>(out var d1))
  915. {
  916. value = -d1;
  917. return true;
  918. }
  919. break;
  920. case UnaryOperator.Not:
  921. if (unaryNodeValue.TryRead<bool>(out var b))
  922. {
  923. value = !b;
  924. return true;
  925. }
  926. break;
  927. }
  928. }
  929. break;
  930. case BinaryExpressionNode binaryExpression:
  931. if (TryGetConstant(binaryExpression.LeftNode, context, out var leftValue) &&
  932. TryGetConstant(binaryExpression.RightNode, context, out var rightValue))
  933. {
  934. switch (binaryExpression.OperatorType)
  935. {
  936. case BinaryOperator.Addition:
  937. {
  938. if (leftValue.TryRead<double>(out var d1) && rightValue.TryRead<double>(out var d2))
  939. {
  940. value = d1 + d2;
  941. return true;
  942. }
  943. }
  944. break;
  945. case BinaryOperator.Subtraction:
  946. {
  947. if (leftValue.TryRead<double>(out var d1) && rightValue.TryRead<double>(out var d2))
  948. {
  949. value = d1 - d2;
  950. return true;
  951. }
  952. }
  953. break;
  954. case BinaryOperator.Multiplication:
  955. {
  956. if (leftValue.TryRead<double>(out var d1) && rightValue.TryRead<double>(out var d2))
  957. {
  958. value = d1 * d2;
  959. return true;
  960. }
  961. }
  962. break;
  963. case BinaryOperator.Division:
  964. {
  965. if (leftValue.TryRead<double>(out var d1) && rightValue.TryRead<double>(out var d2) && d2 != 0)
  966. {
  967. value = d1 / d2;
  968. return true;
  969. }
  970. }
  971. break;
  972. }
  973. }
  974. break;
  975. }
  976. value = default;
  977. return false;
  978. }
  979. static bool IsFixedNumberOfReturnValues(ExpressionNode node)
  980. {
  981. return node is not (CallFunctionExpressionNode or CallTableMethodExpressionNode or VariableArgumentsExpressionNode);
  982. }
  983. /// <summary>
  984. /// Compiles a conditional boolean branch: if true (or false), the next instruction added is skipped.
  985. /// </summary>
  986. /// <param name="node">Condition node</param>
  987. /// <param name="context">Context</param>
  988. /// <param name="falseIsSkip">If true, generates an instruction sequence that skips the next instruction if the condition is false.</param>
  989. /// <param name="testPosition">Position of the test instruction</param>
  990. void CompileConditionNode(ExpressionNode node, ScopeCompilationContext context, bool falseIsSkip, SourcePosition? testPosition = null)
  991. {
  992. if (node is BinaryExpressionNode binaryExpression)
  993. {
  994. switch (binaryExpression.OperatorType)
  995. {
  996. case BinaryOperator.Equality:
  997. {
  998. var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
  999. var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
  1000. context.PushInstruction(Instruction.Eq(falseIsSkip ? (byte)0 : (byte)1, b, c), node.Position);
  1001. return;
  1002. }
  1003. case BinaryOperator.Inequality:
  1004. {
  1005. var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
  1006. var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
  1007. context.PushInstruction(Instruction.Eq(falseIsSkip ? (byte)1 : (byte)0, b, c), node.Position);
  1008. return;
  1009. }
  1010. case BinaryOperator.LessThan:
  1011. {
  1012. var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
  1013. var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
  1014. context.PushInstruction(Instruction.Lt(falseIsSkip ? (byte)0 : (byte)1, b, c), node.Position);
  1015. return;
  1016. }
  1017. case BinaryOperator.LessThanOrEqual:
  1018. {
  1019. var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
  1020. var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
  1021. context.PushInstruction(Instruction.Le(falseIsSkip ? (byte)0 : (byte)1, b, c), node.Position);
  1022. return;
  1023. }
  1024. case BinaryOperator.GreaterThan:
  1025. {
  1026. var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
  1027. var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
  1028. context.PushInstruction(Instruction.Lt(falseIsSkip ? (byte)0 : (byte)1, c, b), node.Position);
  1029. return;
  1030. }
  1031. case BinaryOperator.GreaterThanOrEqual:
  1032. {
  1033. var b = (ushort)GetRKIndex(binaryExpression.LeftNode, context);
  1034. var c = (ushort)GetRKIndex(binaryExpression.RightNode, context);
  1035. context.PushInstruction(Instruction.Le(falseIsSkip ? (byte)0 : (byte)1, c, b), node.Position);
  1036. return;
  1037. }
  1038. }
  1039. }
  1040. node.Accept(this, context);
  1041. context.PushInstruction(Instruction.Test((byte)(context.StackPosition - 1), falseIsSkip ? (byte)0 : (byte)1), testPosition ?? node.Position);
  1042. }
  1043. void CompileExpressionList(SyntaxNode rootNode, ExpressionNode[] expressions, int minimumCount, ScopeCompilationContext context)
  1044. {
  1045. var isLastFunction = false;
  1046. for (int i = 0; i < expressions.Length; i++)
  1047. {
  1048. var expression = expressions[i];
  1049. var isLast = i == expressions.Length - 1;
  1050. var resultCount = isLast ? (minimumCount == -1 ? -1 : minimumCount - i) : 1;
  1051. if (expression is CallFunctionExpressionNode call)
  1052. {
  1053. CompileCallFunctionExpression(call, context, false, resultCount);
  1054. isLastFunction = isLast;
  1055. }
  1056. else if (expression is CallTableMethodExpressionNode method)
  1057. {
  1058. CompileTableMethod(method, context, false, resultCount);
  1059. isLastFunction = isLast;
  1060. }
  1061. else if (expression is VariableArgumentsExpressionNode varArg)
  1062. {
  1063. CompileVariableArgumentsExpression(varArg, context, resultCount);
  1064. isLastFunction = isLast;
  1065. }
  1066. else if (TryGetConstant(expression, context, out var constant))
  1067. {
  1068. var index = context.Function.GetConstantIndex(constant);
  1069. context.PushInstruction(Instruction.LoadK(context.StackPosition, index), expression.Position, true);
  1070. isLastFunction = false;
  1071. }
  1072. else
  1073. {
  1074. expression.Accept(this, context);
  1075. isLastFunction = false;
  1076. }
  1077. }
  1078. // fill space with nil
  1079. var varCount = minimumCount - expressions.Length;
  1080. if (varCount > 0 && !isLastFunction)
  1081. {
  1082. context.PushInstruction(Instruction.LoadNil(context.StackPosition, (ushort)varCount), rootNode.Position);
  1083. context.StackPosition = (byte)(context.StackPosition + varCount);
  1084. }
  1085. }
  1086. }