Compiler.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. using System;
  2. using System.Collections.Generic;
  3. using Newtonsoft.Json;
  4. namespace Yarn
  5. {
  6. internal class Compiler
  7. {
  8. struct CompileFlags {
  9. // should we emit code that turns (VAR_SHUFFLE_OPTIONS) off
  10. // after the next RunOptions bytecode?
  11. public bool DisableShuffleOptionsAfterNextSet;
  12. }
  13. CompileFlags flags;
  14. internal Program program { get; private set; }
  15. internal Compiler (string programName)
  16. {
  17. program = new Program ();
  18. }
  19. internal void CompileNode(Parser.Node node) {
  20. if (program.nodes.ContainsKey(node.name)) {
  21. throw new ArgumentException ("Duplicate node name " + node.name);
  22. }
  23. var compiledNode = new Node();
  24. compiledNode.name = node.name;
  25. compiledNode.tags = node.nodeTags;
  26. // Register the entire text of this node if we have it
  27. if (node.source != null)
  28. {
  29. // Dump the entire contents of this node into the string table
  30. // instead of compiling its contents.
  31. // the line number is 0 because the string starts at the start of the node
  32. compiledNode.sourceTextStringID = program.RegisterString(node.source, node.name, "line:"+node.name, 0, true);
  33. } else {
  34. // Compile the node.
  35. var startLabel = RegisterLabel();
  36. Emit(compiledNode, ByteCode.Label, startLabel);
  37. foreach (var statement in node.statements)
  38. {
  39. GenerateCode(compiledNode, statement);
  40. }
  41. // Does this node end after emitting AddOptions codes
  42. // without calling ShowOptions?
  43. // Note: this only works when we know that we don't have
  44. // AddOptions and then Jump up back into the code to run them.
  45. // TODO: A better solution would be for the parser to flag
  46. // whether a node has Options at the end.
  47. var hasRemainingOptions = false;
  48. foreach (var instruction in compiledNode.instructions)
  49. {
  50. if (instruction.operation == ByteCode.AddOption)
  51. {
  52. hasRemainingOptions = true;
  53. }
  54. if (instruction.operation == ByteCode.ShowOptions)
  55. {
  56. hasRemainingOptions = false;
  57. }
  58. }
  59. // If this compiled node has no lingering options to show at the end of the node, then stop at the end
  60. if (hasRemainingOptions == false)
  61. {
  62. Emit(compiledNode, ByteCode.Stop);
  63. }
  64. else {
  65. // Otherwise, show the accumulated nodes and then jump to the selected node
  66. Emit(compiledNode, ByteCode.ShowOptions);
  67. if (flags.DisableShuffleOptionsAfterNextSet == true)
  68. {
  69. Emit(compiledNode, ByteCode.PushBool, false);
  70. Emit(compiledNode, ByteCode.StoreVariable, VirtualMachine.SpecialVariables.ShuffleOptions);
  71. Emit(compiledNode, ByteCode.Pop);
  72. flags.DisableShuffleOptionsAfterNextSet = false;
  73. }
  74. Emit(compiledNode, ByteCode.RunNode);
  75. }
  76. }
  77. program.nodes[compiledNode.name] = compiledNode;
  78. }
  79. private int labelCount = 0;
  80. // Generates a unique label name to use
  81. string RegisterLabel(string commentary = null) {
  82. return "L" + labelCount++ + commentary;
  83. }
  84. void Emit(Node node, ByteCode code, object operandA = null, object operandB = null) {
  85. var instruction = new Instruction();
  86. instruction.operation = code;
  87. instruction.operandA = operandA;
  88. instruction.operandB = operandB;
  89. node.instructions.Add (instruction);
  90. if (code == ByteCode.Label) {
  91. // Add this label to the label table
  92. node.labels.Add ((string)instruction.operandA, node.instructions.Count - 1);
  93. }
  94. }
  95. // Statements
  96. void GenerateCode(Node node, Parser.Statement statement) {
  97. switch (statement.type) {
  98. case Parser.Statement.Type.CustomCommand:
  99. GenerateCode (node, statement.customCommand);
  100. break;
  101. case Parser.Statement.Type.ShortcutOptionGroup:
  102. GenerateCode (node, statement.shortcutOptionGroup);
  103. break;
  104. case Parser.Statement.Type.Block:
  105. // Blocks are just groups of statements
  106. foreach (var blockStatement in statement.block.statements) {
  107. GenerateCode(node, blockStatement);
  108. }
  109. break;
  110. case Parser.Statement.Type.IfStatement:
  111. GenerateCode (node, statement.ifStatement);
  112. break;
  113. case Parser.Statement.Type.OptionStatement:
  114. GenerateCode (node, statement.optionStatement);
  115. break;
  116. case Parser.Statement.Type.AssignmentStatement:
  117. GenerateCode (node, statement.assignmentStatement);
  118. break;
  119. case Parser.Statement.Type.Line:
  120. GenerateCode (node, statement, statement.line);
  121. break;
  122. default:
  123. throw new ArgumentOutOfRangeException ();
  124. }
  125. }
  126. void GenerateCode(Node node, Parser.CustomCommand statement) {
  127. // If this command is an evaluable expression, evaluate it
  128. if (statement.expression != null) {
  129. GenerateCode (node, statement.expression);
  130. } else {
  131. switch (statement.clientCommand) {
  132. case "stop":
  133. Emit (node, ByteCode.Stop);
  134. break;
  135. case "shuffleNextOptions":
  136. // Emit code that sets "VAR_SHUFFLE_OPTIONS" to true
  137. Emit (node, ByteCode.PushBool, true);
  138. Emit (node, ByteCode.StoreVariable, VirtualMachine.SpecialVariables.ShuffleOptions);
  139. Emit (node, ByteCode.Pop);
  140. flags.DisableShuffleOptionsAfterNextSet = true;
  141. break;
  142. default:
  143. Emit (node, ByteCode.RunCommand, statement.clientCommand);
  144. break;
  145. }
  146. }
  147. }
  148. string GetLineIDFromNodeTags(Parser.ParseNode node) {
  149. // TODO: This will use only the first #line: tag, ignoring all others
  150. foreach (var tag in node.tags)
  151. {
  152. if (tag.StartsWith("line:"))
  153. {
  154. return tag;
  155. }
  156. }
  157. return null;
  158. }
  159. void GenerateCode(Node node, Parser.Statement parseNode, string line) {
  160. // Does this line have a "#line:LINENUM" tag? Use it
  161. string lineID = GetLineIDFromNodeTags(parseNode);
  162. var parts = new List<String>();
  163. var currentPart = "";
  164. foreach (var c in line)
  165. {
  166. if (c == '$')
  167. {
  168. if (currentPart.Length > 0) parts.Add(currentPart);
  169. currentPart = "$";
  170. }
  171. else
  172. {
  173. if (currentPart.Length > 0 && currentPart[0] == '$' && ",.?!: ".Contains(new string(c,1)))
  174. {
  175. parts.Add(currentPart);
  176. currentPart = new string(c, 1);
  177. }
  178. else
  179. {
  180. currentPart += c;
  181. }
  182. }
  183. }
  184. if (currentPart.Length > 0) parts.Add(currentPart);
  185. if (parts.Count == 1)
  186. {
  187. var num = program.RegisterString(line, node.name, lineID, parseNode.lineNumber, true);
  188. Emit(node, ByteCode.RunLine, num);
  189. }
  190. else
  191. {
  192. EmitLinePart(node, parseNode, parts[0], lineID);
  193. for (var i = 1; i < parts.Count; ++i)
  194. {
  195. EmitLinePart(node, parseNode, parts[i], lineID);
  196. node.instructions.Add(new Instruction
  197. {
  198. operation = ByteCode.Concat,
  199. operandA = null,
  200. operandB = null
  201. });
  202. }
  203. node.instructions.Add(new Instruction
  204. {
  205. operation = ByteCode.RunLineFromStack,
  206. operandA = null,
  207. operandB = null
  208. });
  209. }
  210. }
  211. private void EmitLinePart(Node node, Parser.Statement parseNode, String part, String lineID)
  212. {
  213. if (part[0] == '$')
  214. {
  215. node.instructions.Add(new Instruction
  216. {
  217. operation = ByteCode.PushVariable,
  218. operandA = part,
  219. operandB = null
  220. });
  221. }
  222. else
  223. {
  224. var num = program.RegisterString(part, node.name, lineID, parseNode.lineNumber, true);
  225. node.instructions.Add(new Instruction
  226. {
  227. operation = ByteCode.PushString,
  228. operandA = num,
  229. operandB = null
  230. });
  231. }
  232. }
  233. void GenerateCode(Node node, Parser.ShortcutOptionGroup statement) {
  234. var endOfGroupLabel = RegisterLabel ("group_end");
  235. var labels = new List<string> ();
  236. int optionCount = 0;
  237. foreach (var shortcutOption in statement.options) {
  238. var optionDestinationLabel = RegisterLabel ("option_" + (optionCount+1));
  239. labels.Add (optionDestinationLabel);
  240. string endOfClauseLabel = null;
  241. if (shortcutOption.condition != null) {
  242. endOfClauseLabel = RegisterLabel ("conditional_"+optionCount);
  243. GenerateCode (node, shortcutOption.condition);
  244. Emit (node, ByteCode.JumpIfFalse, endOfClauseLabel);
  245. }
  246. var labelLineID = GetLineIDFromNodeTags(shortcutOption);
  247. var labelStringID = program.RegisterString (shortcutOption.label, node.name, labelLineID, shortcutOption.lineNumber, true);
  248. Emit (node, ByteCode.AddOption, labelStringID, optionDestinationLabel);
  249. if (shortcutOption.condition != null) {
  250. Emit (node, ByteCode.Label, endOfClauseLabel);
  251. Emit (node, ByteCode.Pop);
  252. }
  253. optionCount++;
  254. }
  255. Emit (node, ByteCode.ShowOptions);
  256. if (flags.DisableShuffleOptionsAfterNextSet == true) {
  257. Emit (node, ByteCode.PushBool, false);
  258. Emit (node, ByteCode.StoreVariable, VirtualMachine.SpecialVariables.ShuffleOptions);
  259. Emit (node, ByteCode.Pop);
  260. flags.DisableShuffleOptionsAfterNextSet = false;
  261. }
  262. Emit (node, ByteCode.Jump);
  263. optionCount = 0;
  264. foreach (var shortcutOption in statement.options) {
  265. Emit (node, ByteCode.Label, labels [optionCount]);
  266. if (shortcutOption.optionNode != null)
  267. GenerateCode (node, shortcutOption.optionNode.statements);
  268. Emit (node, ByteCode.JumpTo, endOfGroupLabel);
  269. optionCount++;
  270. }
  271. // reached the end of the option group
  272. Emit (node, ByteCode.Label, endOfGroupLabel);
  273. // clean up after the jump
  274. Emit (node, ByteCode.Pop);
  275. }
  276. void GenerateCode(Node node, IEnumerable<Yarn.Parser.Statement> statementList) {
  277. if (statementList == null)
  278. return;
  279. foreach (var statement in statementList) {
  280. GenerateCode (node, statement);
  281. }
  282. }
  283. void GenerateCode(Node node, Parser.IfStatement statement) {
  284. // We'll jump to this label at the end of every clause
  285. var endOfIfStatementLabel = RegisterLabel ("endif");
  286. foreach (var clause in statement.clauses) {
  287. var endOfClauseLabel = RegisterLabel ("skipclause");
  288. if (clause.expression != null) {
  289. GenerateCode (node, clause.expression);
  290. Emit (node, ByteCode.JumpIfFalse, endOfClauseLabel);
  291. }
  292. GenerateCode (node, clause.statements);
  293. Emit (node, ByteCode.JumpTo, endOfIfStatementLabel);
  294. if (clause.expression != null) {
  295. Emit (node, ByteCode.Label, endOfClauseLabel);
  296. }
  297. // Clean up the stack by popping the expression that was tested earlier
  298. if (clause.expression != null) {
  299. Emit (node, ByteCode.Pop);
  300. }
  301. }
  302. Emit (node, ByteCode.Label, endOfIfStatementLabel);
  303. }
  304. void GenerateCode(Node node, Parser.OptionStatement statement) {
  305. var destination = statement.destination;
  306. if (statement.label == null) {
  307. // this is a jump to another node
  308. Emit(node, ByteCode.RunNode, destination);
  309. } else {
  310. var lineID = GetLineIDFromNodeTags(statement.parent);
  311. var stringID = program.RegisterString (statement.label, node.name, lineID, statement.lineNumber, true);
  312. Emit (node, ByteCode.AddOption, stringID, destination);
  313. }
  314. }
  315. void GenerateCode(Node node, Parser.AssignmentStatement statement) {
  316. // Is it a straight assignment?
  317. if (statement.operation == TokenType.EqualToOrAssign) {
  318. // Evaluate the expression, which will result in a value
  319. // on the stack
  320. GenerateCode (node, statement.valueExpression);
  321. // Stack now contains [destinationValue]
  322. } else {
  323. // It's a combined operation-plus-assignment
  324. // Get the current value of the variable
  325. Emit(node, ByteCode.PushVariable, statement.destinationVariableName);
  326. // Evaluate the expression, which will result in a value
  327. // on the stack
  328. GenerateCode (node, statement.valueExpression);
  329. // Stack now contains [currentValue, expressionValue]
  330. switch (statement.operation) {
  331. case TokenType.AddAssign:
  332. Emit (node, ByteCode.CallFunc, TokenType.Add.ToString ());
  333. break;
  334. case TokenType.MinusAssign:
  335. Emit (node, ByteCode.CallFunc, TokenType.Minus.ToString ());
  336. break;
  337. case TokenType.MultiplyAssign:
  338. Emit (node, ByteCode.CallFunc, TokenType.Multiply.ToString ());
  339. break;
  340. case TokenType.DivideAssign:
  341. Emit (node, ByteCode.CallFunc, TokenType.Divide.ToString ());
  342. break;
  343. default:
  344. throw new ArgumentOutOfRangeException ();
  345. }
  346. // Stack now contains [destinationValue]
  347. }
  348. // Store the top of the stack in the variable
  349. Emit(node, ByteCode.StoreVariable, statement.destinationVariableName);
  350. // Clean up the stack
  351. Emit (node, ByteCode.Pop);
  352. }
  353. void GenerateCode(Node node, Parser.Expression expression) {
  354. // Expressions are either plain values, or function calls
  355. switch (expression.type) {
  356. case Parser.Expression.Type.Value:
  357. // Plain value? Emit that
  358. GenerateCode (node, expression.value);
  359. break;
  360. case Parser.Expression.Type.FunctionCall:
  361. // Evaluate all parameter expressions (which will
  362. // push them to the stack)
  363. foreach (var parameter in expression.parameters) {
  364. GenerateCode (node, parameter);
  365. }
  366. // If this function has a variable number of parameters, put
  367. // the number of parameters that were passed onto the stack
  368. if (expression.function.paramCount == -1) {
  369. Emit (node, ByteCode.PushNumber, expression.parameters.Count);
  370. }
  371. // And then call the function
  372. Emit (node, ByteCode.CallFunc, expression.function.name);
  373. break;
  374. }
  375. }
  376. void GenerateCode(Node node, Parser.ValueNode value) {
  377. // Push a value onto the stack
  378. switch (value.value.type) {
  379. case Value.Type.Number:
  380. Emit (node, ByteCode.PushNumber, value.value.AsNumber);
  381. break;
  382. case Value.Type.String:
  383. // TODO: we use 'null' as the line ID here because strings used in expressions
  384. // don't have a #line: tag we can use
  385. var id = program.RegisterString (value.value.AsString, node.name, null, value.lineNumber, false);
  386. Emit (node, ByteCode.PushString, id);
  387. break;
  388. case Value.Type.Bool:
  389. Emit (node, ByteCode.PushBool, value.value.AsBool);
  390. break;
  391. case Value.Type.Variable:
  392. Emit (node, ByteCode.PushVariable, value.value.AsString);
  393. break;
  394. case Value.Type.Null:
  395. Emit (node, ByteCode.PushNull);
  396. break;
  397. default:
  398. throw new ArgumentOutOfRangeException (); // Since the language can't actually produce an object, this is okay.
  399. }
  400. }
  401. }
  402. }