internal OptionStatement(ParseNode parent, Parser p) : base(parent, p) { // The meaning of the string(s) we have changes // depending on whether we have one or two, so // keep them both and decide their meaning once // we know more string firstString; string secondString; // Parse "[[LABEL" p.ExpectSymbol(TokenType.OptionStart); firstString = p.ExpectSymbol(TokenType.Text).value as String; // If there's a | in there, get the string that comes after it if (p.NextSymbolIs(TokenType.OptionDelimit)) { p.ExpectSymbol(TokenType.OptionDelimit); secondString = p.ExpectSymbol(TokenType.Text, TokenType.Identifier).value as String; // Two strings mean that the first is the label, and the second // is the name of the node that we should head to if this option // is selected label = firstString; destination = secondString; } else { // One string means we don't have a label label = null; destination = firstString; } // Parse the closing ]] p.ExpectSymbol(TokenType.OptionEnd); }
private Yarn.Value EvaluateExpression(Parser.Expression expression) { if (expression == null) return Yarn.Value.NULL; switch (expression.type) { case Parser.Expression.Type.Value: // just a regular value? return it return EvaluateValue (expression.value.value); case Parser.Expression.Type.FunctionCall: // get the function var func = expression.function; // evaluate all parameters var evaluatedParameters = new List<Value> (); foreach (var param in expression.parameters) { var expr = EvaluateExpression (param); evaluatedParameters.Add (expr); } var result = func.InvokeWithArray (evaluatedParameters.ToArray ()); return result; } throw new NotImplementedException ("Unimplemented expression type " + expression.type.ToString ()); }
private IEnumerable<Dialogue.RunnerResult> RunShortcutOptionGroup(Parser.ShortcutOptionGroup shortcutOptionGroup) { var optionsToDisplay = new List<Parser.ShortcutOption> (); // Determine which options to present foreach (var option in shortcutOptionGroup.options) { var include = true; if (option.condition != null) { include = EvaluateExpression(option.condition).AsBool != false; } if (include) { optionsToDisplay.Add(option); } } if (optionsToDisplay.Count > 0) { // Give this list to our client var optionStrings = new List<string> (); foreach (var option in optionsToDisplay) { optionStrings.Add(option.label); } Parser.ShortcutOption selectedOption = null; yield return new Dialogue.OptionSetResult (optionStrings, delegate(int selectedOptionIndex) { selectedOption = optionsToDisplay[selectedOptionIndex]; }); if (selectedOption == null) { dialogue.LogErrorMessage ("The OptionChooser I provided was not called before the " + "next line was run! Stopping dialogue."); yield break; } if (selectedOption.optionNode != null) { foreach (var command in RunStatements(selectedOption.optionNode.statements)) { yield return command; } } } }
void GenerateCode(Node node, Parser.OptionStatement statement) { var destination = statement.destination; if (statement.label == null) { // this is a jump to another node Emit(node, ByteCode.RunNode, destination); } else { var stringID = program.RegisterString (statement.label, node.name); Emit (node, ByteCode.AddOption, stringID, destination); } }
void GenerateCode(Node node, Parser.Expression expression) { // Expressions are either plain values, or function calls switch (expression.type) { case Parser.Expression.Type.Value: // Plain value? Emit that GenerateCode (node, expression.value); break; case Parser.Expression.Type.FunctionCall: // Evaluate all parameter expressions (which will // push them to the stack) foreach (var parameter in expression.parameters) { GenerateCode (node, parameter); } // If this function has a variable number of parameters, put // the number of parameters that were passed onto the stack if (expression.function.paramCount == -1) { Emit (node, ByteCode.PushNumber, expression.parameters.Count); } // And then call the function Emit (node, ByteCode.CallFunc, expression.function.name); break; } }
// Statements void GenerateCode(Node node, Parser.Statement statement) { switch (statement.type) { case Parser.Statement.Type.CustomCommand: GenerateCode (node, statement.customCommand); break; case Parser.Statement.Type.ShortcutOptionGroup: GenerateCode (node, statement.shortcutOptionGroup); break; case Parser.Statement.Type.Block: // Blocks are just groups of statements foreach (var blockStatement in statement.block.statements) { GenerateCode(node, blockStatement); } break; case Parser.Statement.Type.IfStatement: GenerateCode (node, statement.ifStatement); break; case Parser.Statement.Type.OptionStatement: GenerateCode (node, statement.optionStatement); break; case Parser.Statement.Type.AssignmentStatement: GenerateCode (node, statement.assignmentStatement); break; case Parser.Statement.Type.Line: GenerateCode (node, statement.line); break; default: throw new ArgumentOutOfRangeException (); } }
void GenerateCode(Node node, Parser.ShortcutOptionGroup statement) { var endOfGroupLabel = RegisterLabel ("group_end"); var labels = new List<string> (); int optionCount = 0; foreach (var shortcutOption in statement.options) { var optionDestinationLabel = RegisterLabel ("option_" + (optionCount+1)); labels.Add (optionDestinationLabel); string endOfClauseLabel = null; if (shortcutOption.condition != null) { endOfClauseLabel = RegisterLabel ("conditional_"+optionCount); GenerateCode (node, shortcutOption.condition); Emit (node, ByteCode.JumpIfFalse, endOfClauseLabel); } var labelStringID = program.RegisterString (shortcutOption.label, node.name); Emit (node, ByteCode.AddOption, labelStringID, optionDestinationLabel); if (shortcutOption.condition != null) { Emit (node, ByteCode.Label, endOfClauseLabel); Emit (node, ByteCode.Pop); } optionCount++; } Emit (node, ByteCode.ShowOptions); if (flags.DisableShuffleOptionsAfterNextSet == true) { Emit (node, ByteCode.PushBool, false); Emit (node, ByteCode.StoreVariable, VirtualMachine.SpecialVariables.ShuffleOptions); Emit (node, ByteCode.Pop); flags.DisableShuffleOptionsAfterNextSet = false; } Emit (node, ByteCode.Jump); optionCount = 0; foreach (var shortcutOption in statement.options) { Emit (node, ByteCode.Label, labels [optionCount]); if (shortcutOption.optionNode != null) GenerateCode (node, shortcutOption.optionNode.statements); Emit (node, ByteCode.JumpTo, endOfGroupLabel); optionCount++; } // reached the end of the option group Emit (node, ByteCode.Label, endOfGroupLabel); // clean up after the jump Emit (node, ByteCode.Pop); }
internal Statement(ParseNode parent, Parser p) : base(parent, p) { if (Block.CanParse(p)) { type = Type.Block; block = new Block(this, p); return; } else if (IfStatement.CanParse(p)) { type = Type.IfStatement; ifStatement = new IfStatement(this, p); return; } else if (OptionStatement.CanParse(p)) { type = Type.OptionStatement; optionStatement = new OptionStatement(this, p); return; } else if (AssignmentStatement.CanParse(p)) { type = Type.AssignmentStatement; assignmentStatement = new AssignmentStatement(this, p); return; } else if (ShortcutOptionGroup.CanParse(p)) { type = Type.ShortcutOptionGroup; shortcutOptionGroup = new ShortcutOptionGroup(this, p); return; } else if (CustomCommand.CanParse(p)) { type = Type.CustomCommand; customCommand = new CustomCommand(this, p); return; } else if (p.NextSymbolIs(TokenType.Text)) { line = p.ExpectSymbol(TokenType.Text).value as string; type = Type.Line; } else { throw ParseException.Make(p.tokens.Peek(), "Expected a statement here but got " + p.tokens.Peek().ToString() +" instead (was there an unbalanced if statement earlier?)"); } }
// Read a number or a variable name from the parser internal ValueNode(ParseNode parent, Parser p) : base(parent, p) { Token t = p.ExpectSymbol(TokenType.Number, TokenType.Variable, TokenType.String); UseToken(t); }
internal ShortcutOptionGroup(ParseNode parent, Parser p) : base(parent, p) { // keep parsing options until we can't, but expect at least one (otherwise it's // not actually a list of options) int shortcutIndex = 1; // give each option a number so it can name itself do { _options.Add(new ShortcutOption(shortcutIndex++, this, p)); } while (p.NextSymbolIs(TokenType.ShortcutOption)); }
internal static bool CanParse(Parser p) { return p.NextSymbolIs (TokenType.ShortcutOption); }
internal ShortcutOption(int optionIndex, ParseNode parent, Parser p) : base(parent, p) { p.ExpectSymbol(TokenType.ShortcutOption); label = p.ExpectSymbol(TokenType.Text).value as string; // Parse the conditional ("<<if $foo>>") if it's there if (p.NextSymbolsAre(TokenType.BeginCommand, TokenType.If)) { p.ExpectSymbol(TokenType.BeginCommand); p.ExpectSymbol(TokenType.If); condition = Expression.Parse(this, p); p.ExpectSymbol(TokenType.EndCommand); } // Parse the statements belonging to this option if it has any if (p.NextSymbolIs(TokenType.Indent)) { p.ExpectSymbol(TokenType.Indent); optionNode = new Node(NodeParent().name + "." + optionIndex, this, p); p.ExpectSymbol(TokenType.Dedent); } }
// ParseNodes do their parsing by consuming tokens from the Parser. // You parse tokens into a ParseNode by using its constructor. internal ParseNode(ParseNode parent, Parser p) { this.parent = parent; }
internal static bool CanParse(Parser p) { return p.NextSymbolIs (TokenType.OptionStart); }
internal static bool CanParse(Parser p) { return p.NextSymbolsAre (TokenType.BeginCommand, TokenType.If); }
internal AssignmentStatement(ParseNode parent, Parser p) : base(parent, p) { p.ExpectSymbol(TokenType.BeginCommand); p.ExpectSymbol(TokenType.Set); destinationVariableName = p.ExpectSymbol(TokenType.Variable).value as string; operation = p.ExpectSymbol(validOperators).type; valueExpression = Expression.Parse(this, p); p.ExpectSymbol(TokenType.EndCommand); }
internal void CompileNode(Parser.Node node) { if (program.nodes.ContainsKey(node.name)) { throw new ArgumentException ("Duplicate node name " + node.name); } var compiledNode = new Node(); compiledNode.name = node.name; var startLabel = RegisterLabel (); Emit (compiledNode, ByteCode.Label, startLabel); foreach (var statement in node.statements) { GenerateCode (compiledNode, statement); } // Does this node end after emitting AddOptions codes // without calling ShowOptions? // Note: this only works when we know that we don't have // AddOptions and then Jump up back into the code to run them. // TODO: A better solution would be for the parser to flag // whether a node has Options at the end. var hasRemainingOptions = false; foreach (var instruction in compiledNode.instructions) { if (instruction.operation == ByteCode.AddOption) { hasRemainingOptions = true; } if (instruction.operation == ByteCode.ShowOptions) { hasRemainingOptions = false; } } // If this compiled node has no lingering options to show at the end of the node, then stop at the end if (hasRemainingOptions == false) { Emit (compiledNode, ByteCode.Stop); } else { // Otherwise, show the accumulated nodes and then jump to the selected node Emit (compiledNode, ByteCode.ShowOptions); if (flags.DisableShuffleOptionsAfterNextSet == true) { Emit (compiledNode, ByteCode.PushBool, false); Emit (compiledNode, ByteCode.StoreVariable, VirtualMachine.SpecialVariables.ShuffleOptions); Emit (compiledNode, ByteCode.Pop); flags.DisableShuffleOptionsAfterNextSet = false; } Emit (compiledNode, ByteCode.RunNode); } if (node.source != null) { compiledNode.sourceTextStringID = program.RegisterString (node.source, node.name); } program.nodes [compiledNode.name] = compiledNode; }
internal Block(ParseNode parent, Parser p) : base(parent, p) { // Read the indent token p.ExpectSymbol(TokenType.Indent); // Keep reading statements until we hit a dedent while (p.NextSymbolIs(TokenType.Dedent) == false) { // fun fact! because Blocks are a type of Statement, // we get nested block parsing for free! \:D/ _statements.Add(new Statement(this, p)); } // Tidy up by reading the dedent p.ExpectSymbol(TokenType.Dedent); }
void GenerateCode(Node node, Parser.CustomCommand statement) { // If this command is an evaluable expression, evaluate it if (statement.expression != null) { GenerateCode (node, statement.expression); } else { switch (statement.clientCommand) { case "stop": Emit (node, ByteCode.Stop); break; case "shuffleNextOptions": // Emit code that sets "VAR_SHUFFLE_OPTIONS" to true Emit (node, ByteCode.PushBool, true); Emit (node, ByteCode.StoreVariable, VirtualMachine.SpecialVariables.ShuffleOptions); Emit (node, ByteCode.Pop); flags.DisableShuffleOptionsAfterNextSet = true; break; default: Emit (node, ByteCode.RunCommand, statement.clientCommand); break; } } }
internal static bool CanParse(Parser p) { return p.NextSymbolIs (TokenType.Indent); }
void GenerateCode(Node node, Parser.IfStatement statement) { // We'll jump to this label at the end of every clause var endOfIfStatementLabel = RegisterLabel ("endif"); foreach (var clause in statement.clauses) { var endOfClauseLabel = RegisterLabel ("skipclause"); if (clause.expression != null) { GenerateCode (node, clause.expression); Emit (node, ByteCode.JumpIfFalse, endOfClauseLabel); } GenerateCode (node, clause.statements); Emit (node, ByteCode.JumpTo, endOfIfStatementLabel); if (clause.expression != null) { Emit (node, ByteCode.Label, endOfClauseLabel); } // Clean up the stack by popping the expression that was tested earlier if (clause.expression != null) { Emit (node, ByteCode.Pop); } } Emit (node, ByteCode.Label, endOfIfStatementLabel); }
internal CustomCommand(ParseNode parent, Parser p) : base(parent, p) { p.ExpectSymbol(TokenType.BeginCommand); // Custom commands can have ANY token in them. Read them all until we hit the // end command token. var commandTokens = new List<Token>(); do { commandTokens.Add(p.ExpectSymbol()); } while (p.NextSymbolIs(TokenType.EndCommand) == false); p.ExpectSymbol(TokenType.EndCommand); // If the first token is an identifier and the second is // a left paren, it may be a function call expression; // evaluate it as such if (commandTokens.Count > 1 && commandTokens[0].type == TokenType.Identifier && commandTokens[1].type == TokenType.LeftParen) { var parser = new Parser(commandTokens, p.library); var expression = Expression.Parse(this, parser); type = Type.Expression; this.expression = expression; } else { // Otherwise, evaluate it as a command type = Type.ClientCommand; this.clientCommand = commandTokens[0].value; } }
void GenerateCode(Node node, Parser.AssignmentStatement statement) { // Is it a straight assignment? if (statement.operation == TokenType.EqualToOrAssign) { // Evaluate the expression, which will result in a value // on the stack GenerateCode (node, statement.valueExpression); // Stack now contains [destinationValue] } else { // It's a combined operation-plus-assignment // Get the current value of the variable Emit(node, ByteCode.PushVariable, statement.destinationVariableName); // Evaluate the expression, which will result in a value // on the stack GenerateCode (node, statement.valueExpression); // Stack now contains [currentValue, expressionValue] switch (statement.operation) { case TokenType.AddAssign: Emit (node, ByteCode.CallFunc, TokenType.Add.ToString ()); break; case TokenType.MinusAssign: Emit (node, ByteCode.CallFunc, TokenType.Minus.ToString ()); break; case TokenType.MultiplyAssign: Emit (node, ByteCode.CallFunc, TokenType.Multiply.ToString ()); break; case TokenType.DivideAssign: Emit (node, ByteCode.CallFunc, TokenType.Divide.ToString ()); break; default: throw new ArgumentOutOfRangeException (); } // Stack now contains [destinationValue] } // Store the top of the stack in the variable Emit(node, ByteCode.StoreVariable, statement.destinationVariableName); // Clean up the stack Emit (node, ByteCode.Pop); }
internal static bool CanParse(Parser p) { return p.NextSymbolsAre (TokenType.BeginCommand, TokenType.Text) || p.NextSymbolsAre (TokenType.BeginCommand, TokenType.Identifier); }
void GenerateCode(Node node, Parser.ValueNode value) { // Push a value onto the stack switch (value.value.type) { case Value.Type.Number: Emit (node, ByteCode.PushNumber, value.value.numberValue); break; case Value.Type.String: var id = program.RegisterString (value.value.stringValue, node.name); Emit (node, ByteCode.PushString, id); break; case Value.Type.Bool: Emit (node, ByteCode.PushBool, value.value.boolValue); break; case Value.Type.Variable: Emit (node, ByteCode.PushVariable, value.value.variableName); break; case Value.Type.Null: Emit (node, ByteCode.PushNull); break; default: throw new ArgumentOutOfRangeException (); } }
internal static Expression Parse(ParseNode parent, Parser p) { // Applies Djikstra's "shunting-yard" algorithm to convert the // stream of infix expressions into postfix notation; we then // build a tree of expressions from the result // https://en.wikipedia.org/wiki/Shunting-yard_algorithm Queue<Token> _expressionRPN = new Queue<Token> (); var operatorStack = new Stack<Token>(); // used for keeping count of parameters for each function var functionStack = new Stack<Token> (); var allValidTokenTypes = new List<TokenType>(Operator.operatorTypes); allValidTokenTypes.Add(TokenType.Number); allValidTokenTypes.Add(TokenType.Variable); allValidTokenTypes.Add(TokenType.String); allValidTokenTypes.Add(TokenType.LeftParen); allValidTokenTypes.Add(TokenType.RightParen); allValidTokenTypes.Add(TokenType.Identifier); allValidTokenTypes.Add(TokenType.Comma); allValidTokenTypes.Add(TokenType.True); allValidTokenTypes.Add(TokenType.False); allValidTokenTypes.Add(TokenType.Null); Token lastToken = null; // Read all the contents of the expression while (p.tokens.Count > 0 && p.NextSymbolIs(allValidTokenTypes.ToArray())) { Token nextToken = p.ExpectSymbol(allValidTokenTypes.ToArray()); if (nextToken.type == TokenType.Number || nextToken.type == TokenType.Variable || nextToken.type == TokenType.String || nextToken.type == TokenType.True || nextToken.type == TokenType.False || nextToken.type == TokenType.Null) { // Primitive values go straight onto the output _expressionRPN.Enqueue (nextToken); } else if (nextToken.type == TokenType.Identifier) { operatorStack.Push (nextToken); functionStack.Push (nextToken); // next token must be a left paren, so process that immediately nextToken = p.ExpectSymbol (TokenType.LeftParen); // enter that sub-expression operatorStack.Push (nextToken); } else if (nextToken.type == TokenType.Comma) { // Resolve this sub-expression before moving on to the // next parameter try { // pop operators until we reach a left paren while (operatorStack.Peek().type != TokenType.LeftParen) { _expressionRPN.Enqueue(operatorStack.Pop()); } } catch (InvalidOperationException) { // we reached the end of the stack prematurely // this means unbalanced parens! throw ParseException.Make(nextToken, "Error parsing expression: " + "unbalanced parentheses"); } // We expect the top of the stack to now contain the left paren that // began the list of parameters if (operatorStack.Peek().type != TokenType.LeftParen) { throw ParseException.Make (operatorStack.Peek (), "Expression parser got " + "confused dealing with a function"); } // The next token is not allowed to be a right-paren or a comma // (that is, you can't say "foo(2,,)") if (p.NextSymbolIs(TokenType.RightParen, TokenType.Comma)) { throw ParseException.Make (p.tokens.Peek(), "Expected expression"); } // Find the closest function on the stack // and increment the number of parameters functionStack.Peek().parameterCount++; } else if (Operator.IsOperator(nextToken.type)) { // This is an operator // If this is a Minus, we need to determine if it's a // unary minus or a binary minus. // Unary minus looks like this: "-1" // Binary minus looks like this: "2 - 3" // Things get complex when we say stuff like "1 + -1". // But it's easier when we realise that a minus // is ONLY unary when the last token was a left paren, // an operator, or it's the first token. if (nextToken.type == TokenType.Minus) { if (lastToken == null || lastToken.type == TokenType.LeftParen || Operator.IsOperator(lastToken.type)) { // This is actually a unary minus. nextToken.type = TokenType.UnaryMinus; } } // We cannot assign values inside an expression. That is, // saying "$foo = 2" in an express does not assign $foo to 2 // and then evaluate to 2. Instead, Yarn defines this // to mean "$foo == 2" if (nextToken.type == TokenType.EqualToOrAssign) { nextToken.type = TokenType.EqualTo; } // O1 = this operator // O2 = the token at the top of the stack // While O2 is an operator, and EITHER: 1. O1 is left-associative and // has precedence <= O2, or 2. O1 is right-associative and // has precedence > O2: while (ShouldApplyPrecedence(nextToken.type, operatorStack)) { var o = operatorStack.Pop(); _expressionRPN.Enqueue(o); } operatorStack.Push(nextToken); } else if (nextToken.type == TokenType.LeftParen) { // Record that we have entered a paren-delimited // subexpression operatorStack.Push(nextToken); } else if (nextToken.type == TokenType.RightParen) { // We're leaving a subexpression; time to resolve the // order of operations that we saw in between the parens. try { // pop operators until we reach a left paren while (operatorStack.Peek().type != TokenType.LeftParen) { _expressionRPN.Enqueue(operatorStack.Pop()); } // pop the left paren operatorStack.Pop(); } catch (InvalidOperationException) { // we reached the end of the stack prematurely // this means unbalanced parens! throw ParseException.Make(nextToken, "Error parsing expression: unbalanced parentheses"); } if (operatorStack.Peek().type == TokenType.Identifier) { // This whole paren-delimited subexpression is actually // a function call // If the last token was a left-paren, then this // was a function with no parameters; otherwise, we // have an additional parameter (on top of the ones we counted // while encountering commas) if (lastToken.type != TokenType.LeftParen) { functionStack.Peek ().parameterCount++; } _expressionRPN.Enqueue(operatorStack.Pop()); functionStack.Pop (); } } // Record this as the last token we saw; we'll use // this to figure out if minuses are unary or not lastToken = nextToken; } // No more tokens; pop all operators onto the output queue while (operatorStack.Count > 0) { _expressionRPN.Enqueue(operatorStack.Pop()); } // If the output queue is empty, then this is not an expression if (_expressionRPN.Count == 0) { throw new ParseException ("Error parsing expression: no expression found!"); } // We've now got this in more easily parsed RPN form; // time to build the expression tree. Token firstToken = _expressionRPN.Peek(); var evaluationStack = new Stack<Expression>(); while (_expressionRPN.Count > 0) { var next = _expressionRPN.Dequeue(); if (Operator.IsOperator(next.type)) { // This is an operation var info = Operator.InfoForOperator(next.type); if (evaluationStack.Count < info.arguments) { throw ParseException.Make(next, "Error parsing expression: not enough " + "arguments for operator "+next.type.ToString()); } var parameters = new List<Expression> (); for (int i = 0; i < info.arguments; i++) { parameters.Add (evaluationStack.Pop ()); } parameters.Reverse (); var operatorFunc = p.library.GetFunction (next.type.ToString()); var expr = new Expression (parent, operatorFunc, parameters); evaluationStack.Push(expr); } else if (next.type == TokenType.Identifier) { // This is a function call var info = p.library.GetFunction(next.value as String); // Ensure that this call has the right number of params if (info.IsParameterCountCorrect(next.parameterCount) == false) { string error = string.Format("Error parsing expression: " + "Unsupported number of parameters for function {0} (expected {1}, got {2})", next.value as String, info.paramCount, next.parameterCount ); throw ParseException.Make(next, error); } var parameterList = new List<Expression> (); for (int i = 0; i < next.parameterCount; i++) { parameterList.Add (evaluationStack.Pop()); } parameterList.Reverse (); var expr = new Expression (parent, info, parameterList); evaluationStack.Push (expr); } else { // This is a raw value var v = new ValueNode(parent, next); Expression expr = new Expression(parent, v); evaluationStack.Push(expr); } } // We should now have a single expression in this stack, which is the root // of the expression's tree. If we have more than one, then we have a problem. if (evaluationStack.Count != 1) { throw ParseException.Make(firstToken, "Error parsing expression " + "(stack did not reduce correctly)"); } // Return it return evaluationStack.Pop (); }
// Assigns a value to a variable. private void RunAssignmentStatement(Parser.AssignmentStatement assignment) { // The place where we're stickin' this value. var variableName = assignment.destinationVariableName; // The value that's going into this variable. var computedValue = EvaluateExpression (assignment.valueExpression); // The current value of this variable. Value originalValue = dialogue.continuity.GetValue (variableName); // What shall we do with it? Value finalValue = Value.NULL; switch (assignment.operation) { case TokenType.EqualToOrAssign: finalValue = computedValue; break; case TokenType.AddAssign: finalValue = originalValue + computedValue; break; case TokenType.MinusAssign: finalValue = originalValue - computedValue; break; case TokenType.MultiplyAssign: finalValue = originalValue * computedValue; break; case TokenType.DivideAssign: finalValue = originalValue / computedValue; break; } dialogue.LogDebugMessage(string.Format("Set {0} to {1}", variableName, finalValue)); dialogue.continuity.SetValue (variableName, finalValue); }
internal IfStatement(ParseNode parent, Parser p) : base(parent, p) { // All if statements begin with "<<if EXPRESSION>>", so parse that Clause primaryClause = new Clause(); p.ExpectSymbol(TokenType.BeginCommand); p.ExpectSymbol(TokenType.If); primaryClause.expression = Expression.Parse(this, p); p.ExpectSymbol(TokenType.EndCommand); // Read the statements for this clause until we hit an <<endif or <<else // (which could be an "<<else>>" or an "<<else if" var statements = new List<Statement>(); while (p.NextSymbolsAre(TokenType.BeginCommand, TokenType.EndIf) == false && p.NextSymbolsAre(TokenType.BeginCommand, TokenType.Else) == false && p.NextSymbolsAre(TokenType.BeginCommand, TokenType.ElseIf) == false) { statements.Add(new Statement(this, p)); // Ignore any dedents while (p.NextSymbolIs(TokenType.Dedent)) { p.ExpectSymbol(TokenType.Dedent); } } primaryClause.statements = statements; clauses.Add(primaryClause); // Handle as many <<elseif clauses as we find while (p.NextSymbolsAre(TokenType.BeginCommand, TokenType.ElseIf)) { var elseIfClause = new Clause(); // Parse the syntax for this clause's condition p.ExpectSymbol(TokenType.BeginCommand); p.ExpectSymbol(TokenType.ElseIf); elseIfClause.expression = Expression.Parse(this, p); p.ExpectSymbol(TokenType.EndCommand); // Read statements until we hit an <<endif, <<else or another <<elseif var clauseStatements = new List<Statement>(); while (p.NextSymbolsAre(TokenType.BeginCommand, TokenType.EndIf) == false && p.NextSymbolsAre(TokenType.BeginCommand, TokenType.Else) == false && p.NextSymbolsAre(TokenType.BeginCommand, TokenType.ElseIf) == false) { clauseStatements.Add(new Statement(this, p)); // Ignore any dedents while (p.NextSymbolIs(TokenType.Dedent)) { p.ExpectSymbol(TokenType.Dedent); } } elseIfClause.statements = clauseStatements; clauses.Add(elseIfClause); } // Handle <<else>> if we have it if (p.NextSymbolsAre(TokenType.BeginCommand, TokenType.Else, TokenType.EndCommand)) { // parse the syntax (no expression this time, just "<<else>>" p.ExpectSymbol(TokenType.BeginCommand); p.ExpectSymbol(TokenType.Else); p.ExpectSymbol(TokenType.EndCommand); // and parse statements until we hit "<<endif" var elseClause = new Clause(); var clauseStatements = new List<Statement>(); while (p.NextSymbolsAre(TokenType.BeginCommand, TokenType.EndIf) == false) { clauseStatements.Add(new Statement(this, p)); } elseClause.statements = clauseStatements; this.clauses.Add(elseClause); // Ignore any dedents while (p.NextSymbolIs(TokenType.Dedent)) { p.ExpectSymbol(TokenType.Dedent); } } // Finish up by reading the <<endif>> p.ExpectSymbol(TokenType.BeginCommand); p.ExpectSymbol(TokenType.EndIf); p.ExpectSymbol(TokenType.EndCommand); }
// Run a single statement. private IEnumerable<Dialogue.RunnerResult> RunStatement(Parser.Statement statement) { switch (statement.type) { case Parser.Statement.Type.Block: // Blocks just contain statements, so run them! foreach (var command in RunStatements (statement.block.statements)) { yield return command; } break; case Parser.Statement.Type.Line: // Lines get forwarded to the client for display yield return new Dialogue.LineResult(statement.line); break; case Parser.Statement.Type.IfStatement: // Evaluate each clause in the statement, and run its statements if appropriate foreach (var clause in statement.ifStatement.clauses) { // if this clause's expression doesn't evaluate to 0, run it; alternatively, // if this clause has no expression (ie it's the 'else' clause) then also run it if (clause.expression == null || EvaluateExpression(clause.expression).AsBool != false) { foreach (var command in RunStatements (clause.statements)) { yield return command; } // don't continue on to the other clauses break; } } break; case Parser.Statement.Type.OptionStatement: // If we encounter an option, record it so that we can present it later currentOptions.Add (statement.optionStatement); break; case Parser.Statement.Type.AssignmentStatement: // Evaluate the expression and assign it to a variable RunAssignmentStatement (statement.assignmentStatement); break; case Parser.Statement.Type.ShortcutOptionGroup: // Evaluate and present the options, then run the stuff that came after the options foreach (var command in RunShortcutOptionGroup (statement.shortcutOptionGroup)) { yield return command; } break; case Parser.Statement.Type.CustomCommand: // Deal with a custom command - it's either an expression or a client command // If it's an expression, evaluate it // If it's a client command, yield it to the client switch (statement.customCommand.type) { case Parser.CustomCommand.Type.Expression: EvaluateExpression (statement.customCommand.expression); break; case Parser.CustomCommand.Type.ClientCommand: yield return new Dialogue.CommandResult (statement.customCommand.clientCommand); break; } break; default: // Just in case we added a new type of statement and didn't implement it here throw new NotImplementedException ("YarnRunner: Unimplemented statement type " + statement.type); } }
internal Operator(ParseNode parent, Parser p) : base(parent, p) { operatorType = p.ExpectSymbol(Operator.operatorTypes).type; }