protected Parsed.Object LogicLine() { Whitespace (); if (ParseString ("~") == null) { return null; } Whitespace (); // Some example lines we need to be able to distinguish between: // ~ temp x = 5 -- var decl + assign // ~ temp x -- var decl // ~ x = 5 -- var assign // ~ x -- expr (not var decl or assign) // ~ f() -- expr // We don't treat variable decl/assign as an expression since we don't want an assignment // to have a return value, or to be used in compound expressions. ParseRule afterTilda = () => OneOf (ReturnStatement, TempDeclarationOrAssignment, Expression); var result = Expect(afterTilda, "expression after '~'", recoveryRule: SkipToNextLine) as Parsed.Object; // Parse all expressions, but tell the writer off if they did something useless like: // ~ 5 + 4 // And even: // ~ false && myFunction() // ...since it's bad practice, and won't do what they expect if // they're expecting C's lazy evaluation. if (result is Expression && !(result is FunctionCall || result is IncDecExpression) ) { // TODO: Remove this specific error message when it has expired in usefulness var varRef = result as VariableReference; if (varRef && varRef.name == "include") { Error ("'~ include' is no longer the correct syntax - please use 'INCLUDE your_filename.ink', without the tilda, and in block capitals."); } else { 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()"); } } // A function call on its own line could result in a text side effect, in which case // it needs a newline on the end. e.g. // ~ printMyName() // If no text gets printed, then the extra newline will have to be culled later. if (result is FunctionCall) { // Add extra pop to make sure we tidy up after ourselves - we no longer need anything on the evaluation stack. var funCall = result as FunctionCall; funCall.shouldPopReturnedValue = true; result = new ContentList (funCall, new Parsed.Text ("\n")); } Expect(EndOfLine, "end of line", recoveryRule: SkipToNextLine); return result as Parsed.Object; }
public Choice(ContentList startContent, ContentList choiceOnlyContent, ContentList innerContent, Divert divert) { this.startContent = startContent; this.choiceOnlyContent = choiceOnlyContent; this.innerContent = innerContent; this.indentationDepth = 1; if (startContent) AddContent (this.startContent); if (choiceOnlyContent) AddContent (this.choiceOnlyContent); if( innerContent ) AddContent (this.innerContent); this.onceOnly = true; // default if (divert) { terminatingDivert = divert; AddContent (terminatingDivert); } }
public Choice(ContentList startContent, ContentList choiceOnlyContent, ContentList innerContent) { this.startContent = startContent; this.choiceOnlyContent = choiceOnlyContent; this.innerContent = innerContent; this.indentationDepth = 1; if (startContent) { AddContent(this.startContent); } if (choiceOnlyContent) { AddContent(this.choiceOnlyContent); } if (innerContent) { AddContent(this.innerContent); } this.onceOnly = true; // default }
protected Parsed.Object InlineLogic() { if ( ParseString ("{") == null) { return null; } Whitespace (); var logic = (Parsed.Object) Expect(InnerLogic, "some kind of logic, conditional or sequence within braces: { ... }"); if (logic == null) return null; ContentList contentList = logic as ContentList; if (!contentList) { contentList = new ContentList (logic); } // Create left-glue. Like normal glue, except it only absorbs newlines to // the left, ensuring that the logic is inline, but without having the side effect // of possibly absorbing desired newlines that come after. var rightGlue = new Parsed.Wrap<Runtime.Glue>(new Runtime.Glue (Runtime.GlueType.Right)); var leftGlue = new Parsed.Wrap<Runtime.Glue>(new Runtime.Glue (Runtime.GlueType.Left)); contentList.InsertContent (0, rightGlue); contentList.AddContent (leftGlue); Whitespace (); Expect (String("}"), "closing brace '}' for inline logic"); return contentList; }
protected Choice Choice() { bool onceOnlyChoice = true; var bullets = Interleave <string>(OptionalExclude(Whitespace), String("*") ); if (bullets == null) { bullets = Interleave <string>(OptionalExclude(Whitespace), String("+") ); if (bullets == null) { return null; } onceOnlyChoice = false; } // Optional name for the choice string optionalName = Parse(BracketedName); Whitespace (); // Optional condition for whether the choice should be shown to the player Expression conditionExpr = Parse(ChoiceCondition); Whitespace (); // Ordinarily we avoid parser state variables like these, since // nesting would require us to store them in a stack. But since you should // never be able to nest choices within choice content, it's fine here. Debug.Assert(_parsingChoice == false, "Already parsing a choice - shouldn't have nested choices"); _parsingChoice = true; ContentList startContent = null; var startTextAndLogic = Parse (MixedTextAndLogic); if (startTextAndLogic != null) startContent = new ContentList (startTextAndLogic); ContentList optionOnlyContent = null; ContentList innerContent = null; // Check for a the weave style format: // * "Hello[."]," he said. bool hasWeaveStyleInlineBrackets = ParseString("[") != null; if (hasWeaveStyleInlineBrackets) { var optionOnlyTextAndLogic = Parse (MixedTextAndLogic); if (optionOnlyTextAndLogic != null) optionOnlyContent = new ContentList (optionOnlyTextAndLogic); Expect (String("]"), "closing ']' for weave-style option"); var innerTextAndLogic = Parse (MixedTextAndLogic); if( innerTextAndLogic != null ) innerContent = new ContentList (innerTextAndLogic); } _parsingChoice = false; // Trim if( innerContent ) TrimChoiceContent (ref innerContent); else if( optionOnlyContent ) TrimChoiceContent (ref optionOnlyContent); else TrimChoiceContent (ref startContent); if (innerContent != null) { innerContent.AddContent (new Text ("\n")); } bool isDefaultChoice = startContent == null && optionOnlyContent == null; Whitespace (); var divert = Parse(SingleDivert); Whitespace (); // Completely empty choice? if (!startContent && !optionOnlyContent && !innerContent && !divert) { Warning ("Choice is completely empty. Interpretting as a default fallback choice. Add a divert arrow to remove this warning: * ->"); } var tags = Parse (Tags); if (tags != null) { if (hasWeaveStyleInlineBrackets) { if (!innerContent) innerContent = new ContentList(); innerContent.AddContent(tags); } else { startContent.AddContent (tags); } } var choice = new Choice (startContent, optionOnlyContent, innerContent, divert); choice.name = optionalName; choice.indentationDepth = bullets.Count; choice.hasWeaveStyleInlineBrackets = hasWeaveStyleInlineBrackets; choice.condition = conditionExpr; choice.onceOnly = onceOnlyChoice; choice.isInvisibleDefault = isDefaultChoice; return choice; }
void TrimChoiceContent(ref ContentList content) { if (content != null) { content.TrimTrailingWhitespace (); if (content.content.Count == 0) { content = null; } } }