Newer
Older
TheVengeance-Project-IADE-Unity2D / Assets / Ink / InkLibs / InkCompiler / InkParser / InkParser_Logic.cs
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Ink.Parsed;
  4. namespace Ink
  5. {
  6. public partial class InkParser
  7. {
  8. protected Parsed.Object LogicLine()
  9. {
  10. Whitespace ();
  11. if (ParseString ("~") == null) {
  12. return null;
  13. }
  14. Whitespace ();
  15. // Some example lines we need to be able to distinguish between:
  16. // ~ temp x = 5 -- var decl + assign
  17. // ~ temp x -- var decl
  18. // ~ x = 5 -- var assign
  19. // ~ x -- expr (not var decl or assign)
  20. // ~ f() -- expr
  21. // We don't treat variable decl/assign as an expression since we don't want an assignment
  22. // to have a return value, or to be used in compound expressions.
  23. ParseRule afterTilda = () => OneOf (ReturnStatement, TempDeclarationOrAssignment, Expression);
  24. var result = Expect(afterTilda, "expression after '~'", recoveryRule: SkipToNextLine) as Parsed.Object;
  25. // Prevent further errors, already reported expected expression and have skipped to next line.
  26. if (result == null) return new ContentList();
  27. // Parse all expressions, but tell the writer off if they did something useless like:
  28. // ~ 5 + 4
  29. // And even:
  30. // ~ false && myFunction()
  31. // ...since it's bad practice, and won't do what they expect if
  32. // they're expecting C's lazy evaluation.
  33. if (result is Expression && !(result is FunctionCall || result is IncDecExpression) ) {
  34. // TODO: Remove this specific error message when it has expired in usefulness
  35. var varRef = result as VariableReference;
  36. if (varRef && varRef.name == "include") {
  37. Error ("'~ include' is no longer the correct syntax - please use 'INCLUDE your_filename.ink', without the tilda, and in block capitals.");
  38. }
  39. else {
  40. Error ("Logic following a '~' can't be that type of expression. It can only be something like:\n\t~ return\n\t~ var x = blah\n\t~ x++\n\t~ myFunction()");
  41. }
  42. }
  43. // Line is pure function call? e.g.
  44. // ~ f()
  45. // Add extra pop to make sure we tidy up after ourselves.
  46. // We no longer need anything on the evaluation stack.
  47. var funCall = result as FunctionCall;
  48. if (funCall) funCall.shouldPopReturnedValue = true;
  49. // If the expression contains a function call, then it could produce a text side effect,
  50. // in which case it needs a newline on the end. e.g.
  51. // ~ printMyName()
  52. // ~ x = 1 + returnAValueAndAlsoPrintStuff()
  53. // If no text gets printed, then the extra newline will have to be culled later.
  54. // Multiple newlines on the output will be removed, so there will be no "leak" for
  55. // long running calculations. It's disappointingly messy though :-/
  56. if (result.Find<FunctionCall>() != null ) {
  57. result = new ContentList (result, new Parsed.Text ("\n"));
  58. }
  59. Expect(EndOfLine, "end of line", recoveryRule: SkipToNextLine);
  60. return result as Parsed.Object;
  61. }
  62. protected Parsed.Object VariableDeclaration()
  63. {
  64. Whitespace ();
  65. var id = Parse (Identifier);
  66. if (id != "VAR")
  67. return null;
  68. Whitespace ();
  69. var varName = Expect (IdentifierWithMetadata, "variable name") as Identifier;
  70. Whitespace ();
  71. Expect (String ("="), "the '=' for an assignment of a value, e.g. '= 5' (initial values are mandatory)");
  72. Whitespace ();
  73. var definition = Expect (Expression, "initial value for ");
  74. var expr = definition as Parsed.Expression;
  75. if (expr) {
  76. if (!(expr is Number || expr is StringExpression || expr is DivertTarget || expr is VariableReference || expr is List)) {
  77. Error ("initial value for a variable must be a number, constant, list or divert target");
  78. }
  79. if (Parse (ListElementDefinitionSeparator) != null)
  80. Error ("Unexpected ','. If you're trying to declare a new list, use the LIST keyword, not VAR");
  81. // Ensure string expressions are simple
  82. else if (expr is StringExpression) {
  83. var strExpr = expr as StringExpression;
  84. if (!strExpr.isSingleString)
  85. Error ("Constant strings cannot contain any logic.");
  86. }
  87. var result = new VariableAssignment (varName, expr);
  88. result.isGlobalDeclaration = true;
  89. return result;
  90. }
  91. return null;
  92. }
  93. protected Parsed.VariableAssignment ListDeclaration ()
  94. {
  95. Whitespace ();
  96. var id = Parse (Identifier);
  97. if (id != "LIST")
  98. return null;
  99. Whitespace ();
  100. var varName = Expect (IdentifierWithMetadata, "list name") as Identifier;
  101. Whitespace ();
  102. Expect (String ("="), "the '=' for an assignment of the list definition");
  103. Whitespace ();
  104. var definition = Expect (ListDefinition, "list item names") as ListDefinition;
  105. if (definition) {
  106. definition.identifier = varName;
  107. return new VariableAssignment (varName, definition);
  108. }
  109. return null;
  110. }
  111. protected Parsed.ListDefinition ListDefinition ()
  112. {
  113. AnyWhitespace ();
  114. var allElements = SeparatedList (ListElementDefinition, ListElementDefinitionSeparator);
  115. if (allElements == null)
  116. return null;
  117. return new ListDefinition (allElements);
  118. }
  119. protected string ListElementDefinitionSeparator ()
  120. {
  121. AnyWhitespace ();
  122. if (ParseString (",") == null) return null;
  123. AnyWhitespace ();
  124. return ",";
  125. }
  126. protected Parsed.ListElementDefinition ListElementDefinition ()
  127. {
  128. var inInitialList = ParseString ("(") != null;
  129. var needsToCloseParen = inInitialList;
  130. Whitespace ();
  131. var name = Parse (IdentifierWithMetadata);
  132. if (name == null)
  133. return null;
  134. Whitespace ();
  135. if (inInitialList) {
  136. if (ParseString (")") != null) {
  137. needsToCloseParen = false;
  138. Whitespace ();
  139. }
  140. }
  141. int? elementValue = null;
  142. if (ParseString ("=") != null) {
  143. Whitespace ();
  144. var elementValueNum = Expect (ExpressionInt, "value to be assigned to list item") as Number;
  145. if (elementValueNum != null) {
  146. elementValue = (int) elementValueNum.value;
  147. }
  148. if (needsToCloseParen) {
  149. Whitespace ();
  150. if (ParseString (")") != null)
  151. needsToCloseParen = false;
  152. }
  153. }
  154. if (needsToCloseParen)
  155. Error("Expected closing ')'");
  156. return new ListElementDefinition (name, inInitialList, elementValue);
  157. }
  158. protected Parsed.Object ConstDeclaration()
  159. {
  160. Whitespace ();
  161. var id = Parse (Identifier);
  162. if (id != "CONST")
  163. return null;
  164. Whitespace ();
  165. var varName = Expect (IdentifierWithMetadata, "constant name") as Identifier;
  166. Whitespace ();
  167. Expect (String ("="), "the '=' for an assignment of a value, e.g. '= 5' (initial values are mandatory)");
  168. Whitespace ();
  169. var expr = Expect (Expression, "initial value for ") as Parsed.Expression;
  170. if (!(expr is Number || expr is DivertTarget || expr is StringExpression)) {
  171. Error ("initial value for a constant must be a number or divert target");
  172. }
  173. // Ensure string expressions are simple
  174. else if (expr is StringExpression) {
  175. var strExpr = expr as StringExpression;
  176. if (!strExpr.isSingleString)
  177. Error ("Constant strings cannot contain any logic.");
  178. }
  179. var result = new ConstantDeclaration (varName, expr);
  180. return result;
  181. }
  182. protected Parsed.Object InlineLogicOrGlueOrStartTag()
  183. {
  184. return (Parsed.Object) OneOf (InlineLogic, Glue, StartTag);
  185. }
  186. protected Parsed.Glue Glue()
  187. {
  188. // Don't want to parse whitespace, since it might be important
  189. // surrounding the glue.
  190. var glueStr = ParseString("<>");
  191. if (glueStr != null) {
  192. return new Parsed.Glue (new Runtime.Glue ());
  193. } else {
  194. return null;
  195. }
  196. }
  197. protected Parsed.Object InlineLogic()
  198. {
  199. if ( ParseString ("{") == null) {
  200. return null;
  201. }
  202. var wasParsingString = parsingStringExpression;
  203. var wasTagActive = tagActive;
  204. Whitespace ();
  205. var logic = (Parsed.Object) Expect(InnerLogic, "some kind of logic, conditional or sequence within braces: { ... }");
  206. if (logic == null) {
  207. parsingStringExpression = wasParsingString;
  208. return null;
  209. }
  210. DisallowIncrement (logic);
  211. ContentList contentList = logic as ContentList;
  212. if (!contentList) {
  213. contentList = new ContentList (logic);
  214. }
  215. Whitespace ();
  216. Expect (String("}"), "closing brace '}' for inline logic");
  217. // Allow nested strings and logic
  218. parsingStringExpression = wasParsingString;
  219. // Difference between:
  220. //
  221. // 1) A thing # {image}.jpg
  222. // 2) A {red #red|blue #blue} sequence.
  223. //
  224. // When logic ends in (1) we still want tag to continue.
  225. // When logic ends in (2) we want to auto-end the tag.
  226. // Side note: we simply disallow tags within strings.
  227. if( !wasTagActive ) EndTagIfNecessary(contentList);
  228. return contentList;
  229. }
  230. protected Parsed.Object InnerLogic()
  231. {
  232. Whitespace ();
  233. // Explicitly try the combinations of inner logic
  234. // that could potentially have conflicts first.
  235. // Explicit sequence annotation?
  236. SequenceType? explicitSeqType = (SequenceType?) ParseObject(SequenceTypeAnnotation);
  237. if (explicitSeqType != null) {
  238. var contentLists = (List<ContentList>) Expect(InnerSequenceObjects, "sequence elements (for cycle/stoping etc)");
  239. if (contentLists == null)
  240. return null;
  241. return new Sequence (contentLists, (SequenceType) explicitSeqType);
  242. }
  243. // Conditional with expression?
  244. var initialQueryExpression = Parse(ConditionExpression);
  245. if (initialQueryExpression) {
  246. var conditional = (Conditional) Expect(() => InnerConditionalContent (initialQueryExpression), "conditional content following query");
  247. return conditional;
  248. }
  249. // Now try to evaluate each of the "full" rules in turn
  250. ParseRule[] rules = {
  251. // Conditional still necessary, since you can have a multi-line conditional
  252. // without an initial query expression:
  253. // {
  254. // - true: this is true
  255. // - false: this is false
  256. // }
  257. InnerConditionalContent,
  258. InnerSequence,
  259. InnerExpression,
  260. };
  261. bool wasTagActiveAtStartOfScope = tagActive;
  262. // Adapted from "OneOf" structuring rule except that in
  263. // order for the rule to succeed, it has to maximally
  264. // cover the entire string within the { }. Used to
  265. // differentiate between:
  266. // {myVar} -- Expression (try first)
  267. // {my content is jolly} -- sequence with single element
  268. foreach (ParseRule rule in rules) {
  269. int ruleId = BeginRule ();
  270. Parsed.Object result = ParseObject(rule) as Parsed.Object;
  271. if (result) {
  272. // Not yet at end?
  273. if (Peek (Spaced (String ("}"))) == null)
  274. FailRule (ruleId);
  275. // Full parse of content within braces
  276. else {
  277. return (Parsed.Object) SucceedRule (ruleId, result);
  278. }
  279. } else {
  280. FailRule (ruleId);
  281. }
  282. }
  283. return null;
  284. }
  285. protected Parsed.Object InnerExpression()
  286. {
  287. var expr = Parse(Expression);
  288. if (expr) {
  289. expr.outputWhenComplete = true;
  290. }
  291. return expr;
  292. }
  293. protected Identifier IdentifierWithMetadata()
  294. {
  295. var id = Identifier();
  296. if( id == null ) return null;
  297. // InkParser.RuleDidSucceed will add DebugMetadata
  298. return new Identifier { name = id, debugMetadata = null };
  299. }
  300. // Note: we allow identifiers that start with a number,
  301. // but not if they *only* comprise numbers
  302. protected string Identifier()
  303. {
  304. // Parse remaining characters (if any)
  305. var name = ParseCharactersFromCharSet (identifierCharSet);
  306. if (name == null)
  307. return null;
  308. // Reject if it's just a number
  309. bool isNumberCharsOnly = true;
  310. foreach (var c in name) {
  311. if ( !(c >= '0' && c <= '9') ) {
  312. isNumberCharsOnly = false;
  313. break;
  314. }
  315. }
  316. if (isNumberCharsOnly) {
  317. return null;
  318. }
  319. return name;
  320. }
  321. CharacterSet identifierCharSet {
  322. get {
  323. if (_identifierCharSet == null) {
  324. (_identifierCharSet = new CharacterSet ())
  325. .AddRange ('A', 'Z')
  326. .AddRange ('a', 'z')
  327. .AddRange ('0', '9')
  328. .Add ('_');
  329. // Enable non-ASCII characters for story identifiers.
  330. ExtendIdentifierCharacterRanges (_identifierCharSet);
  331. }
  332. return _identifierCharSet;
  333. }
  334. }
  335. private CharacterSet _identifierCharSet;
  336. }
  337. }