AntlrCompiler.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. using System;
  2. using System.Text;
  3. using System.Collections.Generic;
  4. using Antlr4.Runtime.Misc;
  5. using Antlr4.Runtime;
  6. using Antlr4.Runtime.Tree;
  7. namespace Yarn
  8. {
  9. public class AntlrCompiler : YarnSpinnerParserBaseListener
  10. {
  11. internal struct CompileFlags
  12. {
  13. // should we emit code that turns (VAR_SHUFFLE_OPTIONS) off
  14. // after the next RunOptions bytecode?
  15. public bool DisableShuffleOptionsAfterNextSet;
  16. }
  17. internal CompileFlags flags;
  18. private int labelCount = 0;
  19. internal Program program { get; private set; }
  20. internal Node currentNode = null;
  21. internal Library library;
  22. // this is used to determine if we are to do any parsing
  23. // or just dump the entire node body as raw text
  24. internal bool rawTextNode = false;
  25. internal AntlrCompiler(Library library)
  26. {
  27. program = new Program();
  28. this.library = library;
  29. }
  30. // Generates a unique label name to use
  31. internal string RegisterLabel(string commentary = null)
  32. {
  33. return "L" + labelCount++ + commentary;
  34. }
  35. // creates the relevant instruction and adds it to the stack
  36. void Emit(Node node, ByteCode code, object operandA = null, object operandB = null)
  37. {
  38. var instruction = new Instruction();
  39. instruction.operation = code;
  40. instruction.operandA = operandA;
  41. instruction.operandB = operandB;
  42. node.instructions.Add(instruction);
  43. if (code == ByteCode.Label)
  44. {
  45. // Add this label to the label table
  46. node.labels.Add((string)instruction.operandA, node.instructions.Count - 1);
  47. }
  48. }
  49. // exactly same as above but defaults to using currentNode
  50. // creates the relevant instruction and adds it to the stack
  51. internal void Emit(ByteCode code, object operandA = null, object operandB = null)
  52. {
  53. this.Emit(this.currentNode, code, operandA, operandB);
  54. }
  55. // returns the lineID for this statement if it has one
  56. // otherwise returns null
  57. // takes in a hashtag block which it handles here
  58. // may need to be changed as future hashtags get support
  59. internal string GetLineID(YarnSpinnerParser.Hashtag_blockContext context)
  60. {
  61. // if there are any hashtags
  62. if (context != null)
  63. {
  64. foreach (var hashtag in context.hashtag())
  65. {
  66. string tagText = hashtag.GetText().Trim('#');
  67. if (tagText.StartsWith("line:"))
  68. {
  69. return tagText;
  70. }
  71. }
  72. }
  73. return null;
  74. }
  75. // this replaces the CompileNode from the old compiler
  76. // will start walking the parse tree
  77. // emitting byte code as it goes along
  78. // this will all get stored into our program var
  79. // needs a tree to walk, this comes from the ANTLR Parser/Lexer steps
  80. internal void Compile(IParseTree tree)
  81. {
  82. ParseTreeWalker walker = new ParseTreeWalker();
  83. walker.Walk(this, tree);
  84. }
  85. // we have found a new node
  86. // set up the currentNode var ready to hold it and otherwise continue
  87. public override void EnterNode(YarnSpinnerParser.NodeContext context)
  88. {
  89. if (currentNode != null)
  90. {
  91. string newNode = context.header().header_title().TITLE_TEXT().GetText().Trim();
  92. string message = string.Format("Discovered a new node {0} while {1} is still being parsed", newNode, currentNode.name);
  93. throw new Yarn.ParseException(message);
  94. }
  95. currentNode = new Node();
  96. rawTextNode = false;
  97. }
  98. // have left the current node
  99. // store it into the program
  100. // wipe the var and make it ready to go again
  101. public override void ExitNode(YarnSpinnerParser.NodeContext context)
  102. {
  103. program.nodes[currentNode.name] = currentNode;
  104. currentNode = null;
  105. rawTextNode = false;
  106. }
  107. // quick check to make sure we have the required number of headers
  108. // basically only allowed one of each tags, position, colourID
  109. // don't need to check for title because without it'll be a parse error
  110. public override void EnterHeader(YarnSpinnerParser.HeaderContext context)
  111. {
  112. if (context.header_tag().Length > 1)
  113. {
  114. string message = string.Format("Too many header tags defined inside {0}", context.header_title().TITLE_TEXT().GetText().Trim());
  115. throw new Yarn.ParseException(message);
  116. }
  117. }
  118. // all we need to do is store the title as the name of the node
  119. public override void EnterHeader_title(YarnSpinnerParser.Header_titleContext context)
  120. {
  121. currentNode.name = context.TITLE_TEXT().GetText().Trim();
  122. }
  123. // parsing the header tags
  124. // will not enter if there aren't any
  125. public override void EnterHeader_tag(YarnSpinnerParser.Header_tagContext context)
  126. {
  127. var tags = new List<string>(context.TAG_TEXT().GetText().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
  128. // if the node is to be parsed as raw text
  129. // ie just straight passthrough
  130. // this is for things like if you want to store books in YarnSpinner
  131. if (tags.Contains("rawText"))
  132. {
  133. rawTextNode = true;
  134. }
  135. // storing the tags in the node
  136. currentNode.tags = tags;
  137. }
  138. // have finished with the header
  139. // so about to enter the node body and all its statements
  140. // do the initial setup required before compiling that body statements
  141. // eg emit a new startlabel
  142. public override void ExitHeader(YarnSpinnerParser.HeaderContext context)
  143. {
  144. // if this is flagged as a regular node
  145. if (!rawTextNode)
  146. {
  147. Emit(currentNode, ByteCode.Label, RegisterLabel());
  148. }
  149. }
  150. // have entered the body
  151. // the header should have finished being parsed and currentNode ready
  152. // all we do is set up a body visitor and tell it to run through all the statements
  153. // it handles everything from that point onwards
  154. public override void EnterBody(YarnSpinnerParser.BodyContext context)
  155. {
  156. // if it is a regular node
  157. if (!rawTextNode)
  158. {
  159. BodyVisitor visitor = new BodyVisitor(this);
  160. foreach (var statement in context.statement())
  161. {
  162. visitor.Visit(statement);
  163. }
  164. }
  165. // we are a rawText node
  166. // turn the body into text
  167. // save that into the node
  168. // perform no compilation
  169. // TODO: oh glob! there has to be a better way
  170. else
  171. {
  172. // moving in by 4 from the end to cut off the ---/=== delimiters
  173. // and their associated /n's
  174. int start = context.Start.StartIndex + 4;
  175. int end = context.Stop.StopIndex - 4;
  176. string body = context.Start.InputStream.GetText(new Interval(start, end));
  177. currentNode.sourceTextStringID = program.RegisterString(body, currentNode.name, "line:" + currentNode.name, context.Start.Line, true);
  178. }
  179. }
  180. // exiting the body of the node, time for last minute work
  181. // before moving onto the next node
  182. // Does this node end after emitting AddOptions codes
  183. // without calling ShowOptions?
  184. public override void ExitBody(YarnSpinnerParser.BodyContext context)
  185. {
  186. // if it is a regular node
  187. if (!rawTextNode)
  188. {
  189. // Note: this only works when we know that we don't have
  190. // AddOptions and then Jump up back into the code to run them.
  191. // TODO: A better solution would be for the parser to flag
  192. // whether a node has Options at the end.
  193. var hasRemainingOptions = false;
  194. foreach (var instruction in currentNode.instructions)
  195. {
  196. if (instruction.operation == ByteCode.AddOption)
  197. {
  198. hasRemainingOptions = true;
  199. }
  200. if (instruction.operation == ByteCode.ShowOptions)
  201. {
  202. hasRemainingOptions = false;
  203. }
  204. }
  205. // If this compiled node has no lingering options to show at the end of the node, then stop at the end
  206. if (hasRemainingOptions == false)
  207. {
  208. Emit(currentNode, ByteCode.Stop);
  209. }
  210. else
  211. {
  212. // Otherwise, show the accumulated nodes and then jump to the selected node
  213. Emit(currentNode, ByteCode.ShowOptions);
  214. if (flags.DisableShuffleOptionsAfterNextSet == true)
  215. {
  216. Emit(currentNode, ByteCode.PushBool, false);
  217. Emit(currentNode, ByteCode.StoreVariable, VirtualMachine.SpecialVariables.ShuffleOptions);
  218. Emit(currentNode, ByteCode.Pop);
  219. flags.DisableShuffleOptionsAfterNextSet = false;
  220. }
  221. Emit(currentNode, ByteCode.RunNode);
  222. }
  223. }
  224. }
  225. }
  226. // the visitor for the body of the node
  227. // does not really return ints, just has to return something
  228. // might be worth later investigating returning Instructions
  229. public class BodyVisitor : YarnSpinnerParserBaseVisitor<int>
  230. {
  231. internal AntlrCompiler compiler;
  232. public BodyVisitor(AntlrCompiler compiler)
  233. {
  234. this.compiler = compiler;
  235. this.loadOperators();
  236. }
  237. // a regular ol' line of text
  238. public override int VisitLine_statement(YarnSpinnerParser.Line_statementContext context)
  239. {
  240. // grabbing the line of text and stripping off any "'s if they had them
  241. string lineText = context.text().GetText().Trim('"');
  242. // getting the lineID from the hashtags if it has one
  243. string lineID = compiler.GetLineID(context.hashtag_block());
  244. // technically this only gets the line the statement started on
  245. int lineNumber = context.Start.Line;
  246. // TODO: why is this called num?
  247. string num = compiler.program.RegisterString(lineText, compiler.currentNode.name, lineID, lineNumber, true);
  248. compiler.Emit(ByteCode.RunLine, num);
  249. return 0;
  250. }
  251. // an option statement
  252. // [[ OPTION_TEXT | OPTION_LINK]] or [[OPTION_TEXT]]
  253. public override int VisitOption_statement(YarnSpinnerParser.Option_statementContext context)
  254. {
  255. // if it is a split style option
  256. if (context.OPTION_LINK() != null)
  257. {
  258. string destination = context.OPTION_LINK().GetText();
  259. string label = context.OPTION_TEXT().GetText();
  260. int lineNumber = context.Start.Line;
  261. // getting the lineID from the hashtags if it has one
  262. string lineID = compiler.GetLineID(context.hashtag_block());
  263. string stringID = compiler.program.RegisterString(label, compiler.currentNode.name, lineID, lineNumber, true);
  264. compiler.Emit(ByteCode.AddOption, stringID, destination);
  265. }
  266. else
  267. {
  268. string destination = context.OPTION_TEXT().GetText();
  269. compiler.Emit(ByteCode.RunNode, destination);
  270. }
  271. return 0;
  272. }
  273. // for setting variables, has two forms
  274. // << SET variable TO/= expression >>
  275. // << SET expression >>
  276. // the second form does need to match the structure:
  277. // variable (+= -= *= /= %=) expression
  278. public override int VisitSet_statement(YarnSpinnerParser.Set_statementContext context)
  279. {
  280. // if it is the first form
  281. // a regular << SET $varName TO expression >>
  282. if (context.variable() != null)
  283. {
  284. // add the expression (whatever it resolves to)
  285. Visit(context.expression());
  286. // now store the variable and clean up the stack
  287. string variableName = context.variable().GetText();
  288. compiler.Emit(ByteCode.StoreVariable, variableName);
  289. compiler.Emit(ByteCode.Pop);
  290. }
  291. // it is the second form
  292. else
  293. {
  294. // checking the expression is of the correct form
  295. var expression = context.expression();
  296. // TODO: is there really no more elegant way of doing this?!
  297. if (expression is YarnSpinnerParser.ExpMultDivModEqualsContext ||
  298. expression is YarnSpinnerParser.ExpPlusMinusEqualsContext)
  299. {
  300. // run the expression, it handles it from here
  301. Visit(expression);
  302. }
  303. else
  304. {
  305. // throw an error
  306. throw Yarn.ParseException.Make(context,"Invalid expression inside assignment statement");
  307. }
  308. }
  309. return 0;
  310. }
  311. // semi-free form text that gets passed along to the game
  312. // for things like <<turn fred left>> or <<unlockAchievement FacePlant>>
  313. public override int VisitAction_statement(YarnSpinnerParser.Action_statementContext context)
  314. {
  315. char[] trimming = { '<', '>' };
  316. string action = context.GetText().Trim(trimming);
  317. // TODO: look into replacing this as it seems a bit odd
  318. switch (action)
  319. {
  320. case "stop":
  321. compiler.Emit(ByteCode.Stop);
  322. break;
  323. case "shuffleNextOptions":
  324. compiler.Emit(ByteCode.PushBool, true);
  325. compiler.Emit(ByteCode.StoreVariable, VirtualMachine.SpecialVariables.ShuffleOptions);
  326. compiler.Emit(ByteCode.Pop);
  327. compiler.flags.DisableShuffleOptionsAfterNextSet = true;
  328. break;
  329. default:
  330. compiler.Emit(ByteCode.RunCommand, action);
  331. break;
  332. }
  333. return 0;
  334. }
  335. // solo function statements
  336. // this is such a weird thing...
  337. // COMMAND_FUNC expression, expression ) >>
  338. // COMMAND_FUNC = << ID (
  339. public override int VisitFunction_statement(YarnSpinnerParser.Function_statementContext context)
  340. {
  341. char[] lTrim = { '<' };
  342. char[] rTrim = { '(' };
  343. string functionName = context.GetChild(0).GetText().TrimStart(lTrim).TrimEnd(rTrim);
  344. var output = this.HandleFunction(functionName, context.expression());
  345. // failed to handle the function
  346. if (output == false)
  347. {
  348. Yarn.ParseException.Make(context, "Invalid number of parameters for " + functionName);
  349. }
  350. return 0;
  351. }
  352. // emits the required tokens for the function call
  353. // returns a bool indicating if the function was valid
  354. private bool HandleFunction(string functionName, YarnSpinnerParser.ExpressionContext[] parameters)
  355. {
  356. // this will throw an exception if it doesn't exist
  357. FunctionInfo functionInfo = compiler.library.GetFunction(functionName);
  358. // if the function is not variadic we need to check it has the right number of params
  359. if (functionInfo.paramCount != -1)
  360. {
  361. if (parameters.Length != functionInfo.paramCount)
  362. {
  363. // invalid function, return false
  364. return false;
  365. }
  366. }
  367. // generate the instructions for all of the parameters
  368. foreach (var parameter in parameters)
  369. {
  370. Visit(parameter);
  371. }
  372. // if the function is variadic we push the parameter number onto the stack
  373. // variadic functions are those with paramCount of -1
  374. if (functionInfo.paramCount == -1)
  375. {
  376. compiler.Emit(ByteCode.PushNumber, parameters.Length);
  377. }
  378. // then call the function itself
  379. compiler.Emit(ByteCode.CallFunc, functionName);
  380. // everything went fine, return true
  381. return true;
  382. }
  383. // handles emiting the correct instructions for the function
  384. public override int VisitFunction(YarnSpinnerParser.FunctionContext context)
  385. {
  386. string functionName = context.FUNC_ID().GetText();
  387. this.HandleFunction(functionName, context.expression());
  388. return 0;
  389. }
  390. // if statement
  391. // ifclause (elseifclause)* (elseclause)? <<endif>>
  392. public override int VisitIf_statement(YarnSpinnerParser.If_statementContext context)
  393. {
  394. // label to give us a jump point for when the if finishes
  395. string endOfIfStatementLabel = compiler.RegisterLabel("endif");
  396. // handle the if
  397. var ifClause = context.if_clause();
  398. generateClause(endOfIfStatementLabel, ifClause.statement(), ifClause.expression());
  399. // all elseifs
  400. foreach (var elseIfClause in context.else_if_clause())
  401. {
  402. generateClause(endOfIfStatementLabel, elseIfClause.statement(), elseIfClause.expression());
  403. }
  404. // the else, if there is one
  405. var elseClause = context.else_clause();
  406. if (elseClause != null)
  407. {
  408. generateClause(endOfIfStatementLabel, elseClause.statement(), null);
  409. }
  410. compiler.Emit(ByteCode.Label, endOfIfStatementLabel);
  411. return 0;
  412. }
  413. internal void generateClause(string jumpLabel, YarnSpinnerParser.StatementContext[] children, YarnSpinnerParser.ExpressionContext expression)
  414. {
  415. string endOfClauseLabel = compiler.RegisterLabel("skipclause");
  416. // handling the expression (if it has one)
  417. // will only be called on ifs and elseifs
  418. if (expression != null)
  419. {
  420. Visit(expression);
  421. compiler.Emit(ByteCode.JumpIfFalse, endOfClauseLabel);
  422. }
  423. // running through all of the children statements
  424. foreach (var child in children)
  425. {
  426. Visit(child);
  427. }
  428. compiler.Emit(ByteCode.JumpTo, jumpLabel);
  429. if (expression != null)
  430. {
  431. compiler.Emit(ByteCode.Label, endOfClauseLabel);
  432. compiler.Emit(ByteCode.Pop);
  433. }
  434. }
  435. // tiny helper to return the text of a short cut
  436. // making it a separate method call because I am positive shortcuts will change
  437. private string ShortcutText(YarnSpinnerParser.Shortcut_textContext context)
  438. {
  439. return context.SHORTCUT_TEXT().GetText().Trim();
  440. }
  441. // for the shortcut options
  442. // (-> line of text <<if expression>> indent statements dedent)+
  443. public override int VisitShortcut_statement(YarnSpinnerParser.Shortcut_statementContext context)
  444. {
  445. string endOfGroupLabel = compiler.RegisterLabel("group_end");
  446. var labels = new List<string>();
  447. int optionCount = 0;
  448. foreach (var shortcut in context.shortcut())
  449. {
  450. string optionDestinationLabel = compiler.RegisterLabel("option_" + (optionCount + 1));
  451. labels.Add(optionDestinationLabel);
  452. string endOfClauseLabel = null;
  453. if (shortcut.shortcut_conditional() != null)
  454. {
  455. endOfClauseLabel = compiler.RegisterLabel("conditional_" + optionCount);
  456. Visit(shortcut.shortcut_conditional().expression());
  457. compiler.Emit(ByteCode.JumpIfFalse, endOfClauseLabel);
  458. }
  459. // getting the lineID from the hashtags if it has one
  460. string lineID = compiler.GetLineID(shortcut.hashtag_block());
  461. string shortcutLine = ShortcutText(shortcut.shortcut_text());
  462. string labelStringID = compiler.program.RegisterString(shortcutLine, compiler.currentNode.name, lineID, shortcut.Start.Line, true);
  463. compiler.Emit(ByteCode.AddOption, labelStringID, optionDestinationLabel);
  464. if (shortcut.shortcut_conditional() != null)
  465. {
  466. compiler.Emit(ByteCode.Label, endOfClauseLabel);
  467. compiler.Emit(ByteCode.Pop);
  468. }
  469. optionCount++;
  470. }
  471. compiler.Emit(ByteCode.ShowOptions);
  472. // TODO: investigate a cleaner way because this is odd...
  473. if (compiler.flags.DisableShuffleOptionsAfterNextSet == true)
  474. {
  475. compiler.Emit(ByteCode.PushBool, false);
  476. compiler.Emit(ByteCode.StoreVariable, VirtualMachine.SpecialVariables.ShuffleOptions);
  477. compiler.Emit(ByteCode.Pop);
  478. compiler.flags.DisableShuffleOptionsAfterNextSet = false;
  479. }
  480. compiler.Emit(ByteCode.Jump);
  481. optionCount = 0;
  482. foreach (var shortcut in context.shortcut())
  483. {
  484. compiler.Emit(ByteCode.Label, labels[optionCount]);
  485. // running through all the children statements of the shortcut
  486. foreach (var child in shortcut.statement())
  487. {
  488. Visit(child);
  489. }
  490. compiler.Emit(ByteCode.JumpTo, endOfGroupLabel);
  491. optionCount++;
  492. }
  493. compiler.Emit(ByteCode.Label, endOfGroupLabel);
  494. compiler.Emit(ByteCode.Pop);
  495. return 0;
  496. }
  497. // the calls for the various operations and expressions
  498. // first the special cases (), unary -, !, and if it is just a value by itself
  499. #region specialCaseCalls
  500. // (expression)
  501. public override int VisitExpParens(YarnSpinnerParser.ExpParensContext context)
  502. {
  503. return Visit(context.expression());
  504. }
  505. // -expression
  506. public override int VisitExpNegative(YarnSpinnerParser.ExpNegativeContext context)
  507. {
  508. int expression = Visit(context.expression());
  509. // TODO: temp operator call
  510. compiler.Emit(ByteCode.CallFunc, TokenType.UnaryMinus.ToString());
  511. return 0;
  512. }
  513. // (not NOT !)expression
  514. public override int VisitExpNot(YarnSpinnerParser.ExpNotContext context)
  515. {
  516. Visit(context.expression());
  517. // TODO: temp operator call
  518. compiler.Emit(ByteCode.CallFunc, TokenType.Not.ToString());
  519. return 0;
  520. }
  521. // variable
  522. public override int VisitExpValue(YarnSpinnerParser.ExpValueContext context)
  523. {
  524. return Visit(context.value());
  525. }
  526. #endregion
  527. // left OPERATOR right style expressions
  528. // the most common form of expressions
  529. // for things like 1 + 3
  530. #region lValueOperatorrValueCalls
  531. internal void genericExpVisitor(YarnSpinnerParser.ExpressionContext left, YarnSpinnerParser.ExpressionContext right, int op)
  532. {
  533. Visit(left);
  534. Visit(right);
  535. // TODO: temp operator call
  536. compiler.Emit(ByteCode.CallFunc, tokens[op].ToString());
  537. }
  538. // * / %
  539. public override int VisitExpMultDivMod(YarnSpinnerParser.ExpMultDivModContext context)
  540. {
  541. genericExpVisitor(context.expression(0), context.expression(1), context.op.Type);
  542. return 0;
  543. }
  544. // + -
  545. public override int VisitExpAddSub(YarnSpinnerParser.ExpAddSubContext context)
  546. {
  547. genericExpVisitor(context.expression(0), context.expression(1), context.op.Type);
  548. return 0;
  549. }
  550. // < <= > >=
  551. public override int VisitExpComparison(YarnSpinnerParser.ExpComparisonContext context)
  552. {
  553. genericExpVisitor(context.expression(0), context.expression(1), context.op.Type);
  554. return 0;
  555. }
  556. // == !=
  557. public override int VisitExpEquality(YarnSpinnerParser.ExpEqualityContext context)
  558. {
  559. genericExpVisitor(context.expression(0), context.expression(1), context.op.Type);
  560. return 0;
  561. }
  562. // and && or || xor ^
  563. public override int VisitExpAndOrXor(YarnSpinnerParser.ExpAndOrXorContext context)
  564. {
  565. genericExpVisitor(context.expression(0), context.expression(1), context.op.Type);
  566. return 0;
  567. }
  568. #endregion
  569. // operatorEquals style operators, eg +=
  570. // these two should only be called during a SET operation
  571. // eg << set $var += 1 >>
  572. // the left expression has to be a variable
  573. // the right value can be anything
  574. #region operatorEqualsCalls
  575. // generic helper for these types of expressions
  576. internal void opEquals(string varName, YarnSpinnerParser.ExpressionContext expression, int op)
  577. {
  578. // Get the current value of the variable
  579. compiler.Emit(ByteCode.PushVariable, varName);
  580. // run the expression
  581. Visit(expression);
  582. // Stack now contains [currentValue, expressionValue]
  583. // now we evaluate the operator
  584. // op will match to one of + - / * %
  585. compiler.Emit(ByteCode.CallFunc, tokens[op].ToString());
  586. // Stack now has the destination value
  587. // now store the variable and clean up the stack
  588. compiler.Emit(ByteCode.StoreVariable, varName);
  589. compiler.Emit(ByteCode.Pop);
  590. }
  591. // *= /= %=
  592. public override int VisitExpMultDivModEquals(YarnSpinnerParser.ExpMultDivModEqualsContext context)
  593. {
  594. // call the helper to deal with this
  595. opEquals(context.variable().GetText(), context.expression(), context.op.Type);
  596. return 0;
  597. }
  598. // += -=
  599. public override int VisitExpPlusMinusEquals(YarnSpinnerParser.ExpPlusMinusEqualsContext context)
  600. {
  601. // call the helper to deal with this
  602. opEquals(context.variable().GetText(), context.expression(), context.op.Type);
  603. return 0;
  604. }
  605. #endregion
  606. // the calls for the various value types
  607. // this is a wee bit messy but is easy to extend, easy to read
  608. // and requires minimal checking as ANTLR has already done all that
  609. // does have code duplication though
  610. #region valueCalls
  611. public override int VisitValueVar(YarnSpinnerParser.ValueVarContext context)
  612. {
  613. return Visit(context.variable());
  614. }
  615. public override int VisitValueNumber(YarnSpinnerParser.ValueNumberContext context)
  616. {
  617. float number = float.Parse(context.BODY_NUMBER().GetText());
  618. compiler.Emit(ByteCode.PushNumber, number);
  619. return 0;
  620. }
  621. public override int VisitValueTrue(YarnSpinnerParser.ValueTrueContext context)
  622. {
  623. compiler.Emit(ByteCode.PushBool, true);
  624. return 0;
  625. }
  626. public override int VisitValueFalse(YarnSpinnerParser.ValueFalseContext context)
  627. {
  628. compiler.Emit(ByteCode.PushBool, false);
  629. return 0;
  630. }
  631. public override int VisitVariable(YarnSpinnerParser.VariableContext context)
  632. {
  633. string variableName = context.VAR_ID().GetText();
  634. compiler.Emit(ByteCode.PushVariable, variableName);
  635. return 0;
  636. }
  637. public override int VisitValueString(YarnSpinnerParser.ValueStringContext context)
  638. {
  639. // stripping the " off the front and back
  640. // actually is this what we want?
  641. string stringVal = context.COMMAND_STRING().GetText().Trim('"');
  642. int lineNumber = context.Start.Line;
  643. string id = compiler.program.RegisterString(stringVal, compiler.currentNode.name, null, lineNumber, false);
  644. compiler.Emit(ByteCode.PushString, id);
  645. return 0;
  646. }
  647. // all we need do is visit the function itself, it will handle everything
  648. public override int VisitValueFunc(YarnSpinnerParser.ValueFuncContext context)
  649. {
  650. Visit(context.function());
  651. return 0;
  652. }
  653. // null value
  654. public override int VisitValueNull(YarnSpinnerParser.ValueNullContext context)
  655. {
  656. compiler.Emit(ByteCode.PushNull);
  657. return 0;
  658. }
  659. #endregion
  660. // TODO: figure out a better way to do operators
  661. Dictionary<int, TokenType> tokens = new Dictionary<int, TokenType>();
  662. private void loadOperators()
  663. {
  664. // operators for the standard expressions
  665. tokens[YarnSpinnerLexer.OPERATOR_LOGICAL_LESS_THAN_EQUALS] = TokenType.LessThanOrEqualTo;
  666. tokens[YarnSpinnerLexer.OPERATOR_LOGICAL_GREATER_THAN_EQUALS] = TokenType.GreaterThanOrEqualTo;
  667. tokens[YarnSpinnerLexer.OPERATOR_LOGICAL_LESS] = TokenType.LessThan;
  668. tokens[YarnSpinnerLexer.OPERATOR_LOGICAL_GREATER] = TokenType.GreaterThan;
  669. tokens[YarnSpinnerLexer.OPERATOR_LOGICAL_EQUALS] = TokenType.EqualTo;
  670. tokens[YarnSpinnerLexer.OPERATOR_LOGICAL_NOT_EQUALS] = TokenType.NotEqualTo;
  671. tokens[YarnSpinnerLexer.OPERATOR_LOGICAL_AND] = TokenType.And;
  672. tokens[YarnSpinnerLexer.OPERATOR_LOGICAL_OR] = TokenType.Or;
  673. tokens[YarnSpinnerLexer.OPERATOR_LOGICAL_XOR] = TokenType.Xor;
  674. tokens[YarnSpinnerLexer.OPERATOR_MATHS_ADDITION] = TokenType.Add;
  675. tokens[YarnSpinnerLexer.OPERATOR_MATHS_SUBTRACTION] = TokenType.Minus;
  676. tokens[YarnSpinnerLexer.OPERATOR_MATHS_MULTIPLICATION] = TokenType.Multiply;
  677. tokens[YarnSpinnerLexer.OPERATOR_MATHS_DIVISION] = TokenType.Divide;
  678. tokens[YarnSpinnerLexer.OPERATOR_MATHS_MODULUS] = TokenType.Modulo;
  679. // operators for the set expressions
  680. // these map directly to the operator if they didn't have the =
  681. tokens[YarnSpinnerLexer.OPERATOR_MATHS_ADDITION_EQUALS] = TokenType.Add;
  682. tokens[YarnSpinnerLexer.OPERATOR_MATHS_SUBTRACTION_EQUALS] = TokenType.Minus;
  683. tokens[YarnSpinnerLexer.OPERATOR_MATHS_MULTIPLICATION_EQUALS] = TokenType.Multiply;
  684. tokens[YarnSpinnerLexer.OPERATOR_MATHS_DIVISION_EQUALS] = TokenType.Divide;
  685. tokens[YarnSpinnerLexer.OPERATOR_MATHS_MODULUS_EQUALS] = TokenType.Modulo;
  686. }
  687. }
  688. public class Graph
  689. {
  690. public ArrayList<String> nodes = new ArrayList<String>();
  691. public MultiMap<String, String> edges = new MultiMap<String, String>();
  692. public string graphName = "G";
  693. public void edge(String source, String target)
  694. {
  695. edges.Map(source, target);
  696. }
  697. public String toDot()
  698. {
  699. StringBuilder buf = new StringBuilder();
  700. buf.AppendFormat("digraph {0} ",graphName);
  701. buf.Append("{\n");
  702. buf.Append(" ");
  703. foreach (String node in nodes)
  704. { // print all nodes first
  705. buf.Append(node);
  706. buf.Append("; ");
  707. }
  708. buf.Append("\n");
  709. foreach (String src in edges.Keys)
  710. {
  711. IList<string> output;
  712. if (edges.TryGetValue(src, out output))
  713. {
  714. foreach (String trg in output)
  715. {
  716. buf.Append(" ");
  717. buf.Append(src);
  718. buf.Append(" -> ");
  719. buf.Append(trg);
  720. buf.Append(";\n");
  721. }
  722. }
  723. }
  724. buf.Append("}\n");
  725. return buf.ToString();
  726. }
  727. }
  728. public class GraphListener:YarnSpinnerParserBaseListener
  729. {
  730. String currentNode = null;
  731. public Graph graph = new Graph();
  732. String yarnName = "G";
  733. public override void EnterHeader_title(YarnSpinnerParser.Header_titleContext context)
  734. {
  735. currentNode = context.HEADER_TITLE().GetText();
  736. graph.nodes.Add(currentNode);
  737. }
  738. public override void ExitOption_statement(YarnSpinnerParser.Option_statementContext context)
  739. {
  740. var link = context.OPTION_LINK();
  741. if (link != null)
  742. {
  743. graph.edge(currentNode,link.GetText());
  744. }
  745. else
  746. {
  747. graph.edge(currentNode, context.OPTION_TEXT().GetText());
  748. }
  749. }
  750. }
  751. }