LuaCompiler.cs 54 KB

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