Newer
Older
TheVengeance-Project-IADE-Unity2D / Assets / Ink / InkLibs / InkCompiler / InkParser / InkParser_Expressions.cs
  1. using System;
  2. using Ink.Parsed;
  3. using System.Collections.Generic;
  4. namespace Ink
  5. {
  6. public partial class InkParser
  7. {
  8. protected class InfixOperator
  9. {
  10. public string type;
  11. public int precedence;
  12. public bool requireWhitespace;
  13. public InfixOperator(string type, int precedence, bool requireWhitespace) {
  14. this.type = type;
  15. this.precedence = precedence;
  16. this.requireWhitespace = requireWhitespace;
  17. }
  18. public override string ToString ()
  19. {
  20. return type;
  21. }
  22. }
  23. protected Parsed.Object TempDeclarationOrAssignment()
  24. {
  25. Whitespace ();
  26. bool isNewDeclaration = ParseTempKeyword();
  27. Whitespace ();
  28. Identifier varIdentifier = null;
  29. if (isNewDeclaration) {
  30. varIdentifier = (Identifier)Expect (IdentifierWithMetadata, "variable name");
  31. } else {
  32. varIdentifier = Parse(IdentifierWithMetadata);
  33. }
  34. if (varIdentifier == null) {
  35. return null;
  36. }
  37. Whitespace();
  38. // += -=
  39. bool isIncrement = ParseString ("+") != null;
  40. bool isDecrement = ParseString ("-") != null;
  41. if (isIncrement && isDecrement) Error ("Unexpected sequence '+-'");
  42. if (ParseString ("=") == null) {
  43. // Definitely in an assignment expression?
  44. if (isNewDeclaration) Error ("Expected '='");
  45. return null;
  46. }
  47. Expression assignedExpression = (Expression)Expect (Expression, "value expression to be assigned");
  48. if (isIncrement || isDecrement) {
  49. var result = new IncDecExpression (varIdentifier, assignedExpression, isIncrement);
  50. return result;
  51. } else {
  52. var result = new VariableAssignment (varIdentifier, assignedExpression);
  53. result.isNewTemporaryDeclaration = isNewDeclaration;
  54. return result;
  55. }
  56. }
  57. protected void DisallowIncrement (Parsed.Object expr)
  58. {
  59. if (expr is Parsed.IncDecExpression)
  60. Error ("Can't use increment/decrement here. It can only be used on a ~ line");
  61. }
  62. protected bool ParseTempKeyword()
  63. {
  64. var ruleId = BeginRule ();
  65. if (Parse (Identifier) == "temp") {
  66. SucceedRule (ruleId);
  67. return true;
  68. } else {
  69. FailRule (ruleId);
  70. return false;
  71. }
  72. }
  73. protected Parsed.Return ReturnStatement()
  74. {
  75. Whitespace ();
  76. var returnOrDone = Parse(Identifier);
  77. if (returnOrDone != "return") {
  78. return null;
  79. }
  80. Whitespace ();
  81. var expr = Parse(Expression);
  82. var returnObj = new Return (expr);
  83. return returnObj;
  84. }
  85. protected Expression Expression() {
  86. return Expression(minimumPrecedence:0);
  87. }
  88. // Pratt Parser
  89. // aka "Top down operator precedence parser"
  90. // http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
  91. // Algorithm overview:
  92. // The two types of precedence are handled in two different ways:
  93. // ((((a . b) . c) . d) . e) #1
  94. // (a . (b . (c . (d . e)))) #2
  95. // Where #1 is automatically handled by successive loops within the main 'while' in this function,
  96. // so long as continuing operators have lower (or equal) precedence (e.g. imagine some series of "*"s then "+" above.
  97. // ...and #2 is handled by recursion of the right hand term in the binary expression parser.
  98. // (see link for advice on how to extend for postfix and mixfix operators)
  99. protected Expression Expression(int minimumPrecedence)
  100. {
  101. Whitespace ();
  102. // First parse a unary expression e.g. "-a" or parethensised "(1 + 2)"
  103. var expr = ExpressionUnary ();
  104. if (expr == null) {
  105. return null;
  106. }
  107. Whitespace ();
  108. // Attempt to parse (possibly multiple) continuing infix expressions (e.g. 1 + 2 + 3)
  109. while(true) {
  110. var ruleId = BeginRule ();
  111. // Operator
  112. var infixOp = ParseInfixOperator ();
  113. if (infixOp != null && infixOp.precedence > minimumPrecedence) {
  114. // Expect right hand side of operator
  115. var expectationMessage = string.Format("right side of '{0}' expression", infixOp.type);
  116. var multiaryExpr = Expect (() => ExpressionInfixRight (left: expr, op: infixOp), expectationMessage);
  117. if (multiaryExpr == null) {
  118. // Fail for operator and right-hand side of multiary expression
  119. FailRule (ruleId);
  120. return null;
  121. }
  122. expr = SucceedRule(ruleId, multiaryExpr) as Parsed.Expression;
  123. continue;
  124. }
  125. FailRule (ruleId);
  126. break;
  127. }
  128. Whitespace ();
  129. return expr;
  130. }
  131. protected Expression ExpressionUnary()
  132. {
  133. // Divert target is a special case - it can't have any other operators
  134. // applied to it, and we also want to check for it first so that we don't
  135. // confuse "->" for subtraction.
  136. var divertTarget = Parse (ExpressionDivertTarget);
  137. if (divertTarget != null) {
  138. return divertTarget;
  139. }
  140. var prefixOp = (string) OneOf (String ("-"), String ("!"));
  141. // Don't parse like the string rules above, in case its actually
  142. // a variable that simply starts with "not", e.g. "notable".
  143. // This rule uses the Identifier rule, which will scan as much text
  144. // as possible before returning.
  145. if (prefixOp == null) {
  146. prefixOp = Parse(ExpressionNot);
  147. }
  148. Whitespace ();
  149. // - Since we allow numbers at the start of variable names, variable names are checked before literals
  150. // - Function calls before variable names in case we see parentheses
  151. var expr = OneOf (ExpressionList, ExpressionParen, ExpressionFunctionCall, ExpressionVariableName, ExpressionLiteral) as Expression;
  152. // Only recurse immediately if we have one of the (usually optional) unary ops
  153. if (expr == null && prefixOp != null) {
  154. expr = ExpressionUnary ();
  155. }
  156. if (expr == null)
  157. return null;
  158. if (prefixOp != null) {
  159. expr = UnaryExpression.WithInner(expr, prefixOp);
  160. }
  161. Whitespace ();
  162. var postfixOp = (string) OneOf (String ("++"), String ("--"));
  163. if (postfixOp != null) {
  164. bool isInc = postfixOp == "++";
  165. if (!(expr is VariableReference)) {
  166. Error ("can only increment and decrement variables, but saw '" + expr + "'");
  167. // Drop down and succeed without the increment after reporting error
  168. } else {
  169. // TODO: Language Server - (Identifier combined into one vs. list of Identifiers)
  170. var varRef = (VariableReference)expr;
  171. expr = new IncDecExpression(varRef.identifier, isInc);
  172. }
  173. }
  174. return expr;
  175. }
  176. protected string ExpressionNot()
  177. {
  178. var id = Identifier ();
  179. if (id == "not") {
  180. return id;
  181. }
  182. return null;
  183. }
  184. protected Expression ExpressionLiteral()
  185. {
  186. return (Expression) OneOf (ExpressionFloat, ExpressionInt, ExpressionBool, ExpressionString);
  187. }
  188. protected Expression ExpressionDivertTarget()
  189. {
  190. Whitespace ();
  191. var divert = Parse(SingleDivert);
  192. if (divert == null)
  193. return null;
  194. if (divert.isThread)
  195. return null;
  196. Whitespace ();
  197. return new DivertTarget (divert);
  198. }
  199. protected Number ExpressionInt()
  200. {
  201. int? intOrNull = ParseInt ();
  202. if (intOrNull == null) {
  203. return null;
  204. } else {
  205. return new Number (intOrNull.Value);
  206. }
  207. }
  208. protected Number ExpressionFloat()
  209. {
  210. float? floatOrNull = ParseFloat ();
  211. if (floatOrNull == null) {
  212. return null;
  213. } else {
  214. return new Number (floatOrNull.Value);
  215. }
  216. }
  217. protected StringExpression ExpressionString()
  218. {
  219. var openQuote = ParseString ("\"");
  220. if (openQuote == null)
  221. return null;
  222. // Set custom parser state flag so that within the text parser,
  223. // it knows to treat the quote character (") as an end character
  224. parsingStringExpression = true;
  225. List<Parsed.Object> textAndLogic = Parse (MixedTextAndLogic);
  226. Expect (String ("\""), "close quote for string expression");
  227. parsingStringExpression = false;
  228. if (textAndLogic == null) {
  229. textAndLogic = new List<Ink.Parsed.Object> ();
  230. textAndLogic.Add (new Parsed.Text (""));
  231. }
  232. else if (textAndLogic.Exists (c => c is Divert))
  233. Error ("String expressions cannot contain diverts (->)");
  234. return new StringExpression (textAndLogic);
  235. }
  236. protected Number ExpressionBool()
  237. {
  238. var id = Parse(Identifier);
  239. if (id == "true") {
  240. return new Number (true);
  241. } else if (id == "false") {
  242. return new Number (false);
  243. }
  244. return null;
  245. }
  246. protected Expression ExpressionFunctionCall()
  247. {
  248. var iden = Parse(IdentifierWithMetadata);
  249. if (iden == null)
  250. return null;
  251. Whitespace ();
  252. var arguments = Parse(ExpressionFunctionCallArguments);
  253. if (arguments == null) {
  254. return null;
  255. }
  256. return new FunctionCall(iden, arguments);
  257. }
  258. protected List<Expression> ExpressionFunctionCallArguments()
  259. {
  260. if (ParseString ("(") == null)
  261. return null;
  262. // "Exclude" requires the rule to succeed, but causes actual comma string to be excluded from the list of results
  263. ParseRule commas = Exclude (String (","));
  264. var arguments = Interleave<Expression>(Expression, commas);
  265. if (arguments == null) {
  266. arguments = new List<Expression> ();
  267. }
  268. Whitespace ();
  269. Expect (String (")"), "closing ')' for function call");
  270. return arguments;
  271. }
  272. protected Expression ExpressionVariableName()
  273. {
  274. List<Identifier> path = Interleave<Identifier> (IdentifierWithMetadata, Exclude (Spaced (String ("."))));
  275. if (path == null || Story.IsReservedKeyword (path[0].name) )
  276. return null;
  277. return new VariableReference (path);
  278. }
  279. protected Expression ExpressionParen()
  280. {
  281. if (ParseString ("(") == null)
  282. return null;
  283. var innerExpr = Parse(Expression);
  284. if (innerExpr == null)
  285. return null;
  286. Whitespace ();
  287. Expect (String(")"), "closing parenthesis ')' for expression");
  288. return innerExpr;
  289. }
  290. protected Expression ExpressionInfixRight(Parsed.Expression left, InfixOperator op)
  291. {
  292. Whitespace ();
  293. var right = Parse(() => Expression (op.precedence));
  294. if (right) {
  295. // We assume that the character we use for the operator's type is the same
  296. // as that used internally by e.g. Runtime.Expression.Add, Runtime.Expression.Multiply etc
  297. var expr = new BinaryExpression (left, right, op.type);
  298. return expr;
  299. }
  300. return null;
  301. }
  302. private InfixOperator ParseInfixOperator()
  303. {
  304. foreach (var op in _binaryOperators) {
  305. int ruleId = BeginRule ();
  306. if (ParseString (op.type) != null) {
  307. if (op.requireWhitespace) {
  308. if (Whitespace () == null) {
  309. FailRule (ruleId);
  310. continue;
  311. }
  312. }
  313. return (InfixOperator) SucceedRule(ruleId, op);
  314. }
  315. FailRule (ruleId);
  316. }
  317. return null;
  318. }
  319. protected Parsed.List ExpressionList ()
  320. {
  321. Whitespace ();
  322. if (ParseString ("(") == null)
  323. return null;
  324. Whitespace ();
  325. // When list has:
  326. // - 0 elements (null list) - this is okay, it's an empty list: "()"
  327. // - 1 element - it could be confused for a single non-list related
  328. // identifier expression in brackets, but this is a useless thing
  329. // to do, so we reserve that syntax for a list with one item.
  330. // - 2 or more elements - normal!
  331. List<Identifier> memberNames = SeparatedList (ListMember, Spaced (String (",")));
  332. Whitespace ();
  333. // May have failed to parse the inner list - the parentheses may
  334. // be for a normal expression
  335. if (ParseString (")") == null)
  336. return null;
  337. return new List (memberNames);
  338. }
  339. protected Identifier ListMember ()
  340. {
  341. Whitespace ();
  342. Identifier identifier = Parse (IdentifierWithMetadata);
  343. if (identifier == null)
  344. return null;
  345. var dot = ParseString (".");
  346. if (dot != null) {
  347. Identifier identifier2 = Expect (IdentifierWithMetadata, "element name within the set " + identifier) as Identifier;
  348. identifier.name = identifier.name + "." + identifier2?.name;
  349. }
  350. Whitespace ();
  351. return identifier;
  352. }
  353. void RegisterExpressionOperators()
  354. {
  355. _maxBinaryOpLength = 0;
  356. _binaryOperators = new List<InfixOperator> ();
  357. // These will be tried in order, so we need "<=" before "<"
  358. // for correctness
  359. RegisterBinaryOperator ("&&", precedence:1);
  360. RegisterBinaryOperator ("||", precedence:1);
  361. RegisterBinaryOperator ("and", precedence:1, requireWhitespace: true);
  362. RegisterBinaryOperator ("or", precedence:1, requireWhitespace: true);
  363. RegisterBinaryOperator ("==", precedence:2);
  364. RegisterBinaryOperator (">=", precedence:2);
  365. RegisterBinaryOperator ("<=", precedence:2);
  366. RegisterBinaryOperator ("<", precedence:2);
  367. RegisterBinaryOperator (">", precedence:2);
  368. RegisterBinaryOperator ("!=", precedence:2);
  369. // (apples, oranges) + cabbages has (oranges, cabbages) == true
  370. RegisterBinaryOperator ("?", precedence: 3);
  371. RegisterBinaryOperator ("has", precedence: 3, requireWhitespace:true);
  372. RegisterBinaryOperator ("!?", precedence: 3);
  373. RegisterBinaryOperator ("hasnt", precedence: 3, requireWhitespace: true);
  374. RegisterBinaryOperator ("^", precedence: 3);
  375. RegisterBinaryOperator ("+", precedence:4);
  376. RegisterBinaryOperator ("-", precedence:5);
  377. RegisterBinaryOperator ("*", precedence:6);
  378. RegisterBinaryOperator ("/", precedence:7);
  379. RegisterBinaryOperator ("%", precedence:8);
  380. RegisterBinaryOperator ("mod", precedence:8, requireWhitespace:true);
  381. }
  382. void RegisterBinaryOperator(string op, int precedence, bool requireWhitespace = false)
  383. {
  384. _binaryOperators.Add(new InfixOperator (op, precedence, requireWhitespace));
  385. _maxBinaryOpLength = Math.Max (_maxBinaryOpLength, op.Length);
  386. }
  387. List<InfixOperator> _binaryOperators;
  388. int _maxBinaryOpLength;
  389. }
  390. }