using System; using System.Collections.Generic; using System.Linq; using Ink.Parsed; namespace Ink { public partial class InkParser { protected enum StatementLevel { InnerBlock, Stitch, Knot, Top } protected List<Parsed.Object> StatementsAtLevel(StatementLevel level) { // Check for error: Should not be allowed gather dashes within an inner block if (level == StatementLevel.InnerBlock) { object badGatherDashCount = Parse(GatherDashes); if (badGatherDashCount != null) { Error ("You can't use a gather (the dashes) within the { curly braces } context. For multi-line sequences and conditions, you should only use one dash."); } } return Interleave<Parsed.Object>( Optional (MultilineWhitespace), () => StatementAtLevel (level), untilTerminator: () => StatementsBreakForLevel(level)); } protected object StatementAtLevel(StatementLevel level) { ParseRule[] rulesAtLevel = _statementRulesAtLevel[(int)level]; var statement = OneOf (rulesAtLevel); // For some statements, allow them to parse, but create errors, since // writers may think they can use the statement, so it's useful to have // the error message. if (level == StatementLevel.Top) { if( statement is Return ) Error ("should not have return statement outside of a knot"); } return statement; } protected object StatementsBreakForLevel(StatementLevel level) { Whitespace (); ParseRule[] breakRules = _statementBreakRulesAtLevel[(int)level]; var breakRuleResult = OneOf (breakRules); if (breakRuleResult == null) return null; return breakRuleResult; } void GenerateStatementLevelRules() { var levels = Enum.GetValues (typeof(StatementLevel)).Cast<StatementLevel> ().ToList(); _statementRulesAtLevel = new ParseRule[levels.Count][]; _statementBreakRulesAtLevel = new ParseRule[levels.Count][]; foreach (var level in levels) { List<ParseRule> rulesAtLevel = new List<ParseRule> (); List<ParseRule> breakingRules = new List<ParseRule> (); // Diverts can go anywhere rulesAtLevel.Add(Line(MultiDivert)); // Knots can only be parsed at Top/Global scope if (level >= StatementLevel.Top) rulesAtLevel.Add (KnotDefinition); rulesAtLevel.Add(Line(Choice)); rulesAtLevel.Add(Line(AuthorWarning)); // Gather lines would be confused with multi-line block separators, like // within a multi-line if statement if (level > StatementLevel.InnerBlock) { rulesAtLevel.Add (Gather); } // Stitches (and gathers) can (currently) only go in Knots and top level if (level >= StatementLevel.Knot) { rulesAtLevel.Add (StitchDefinition); } // Global variable declarations can go anywhere rulesAtLevel.Add(Line(ListDeclaration)); rulesAtLevel.Add(Line(VariableDeclaration)); rulesAtLevel.Add(Line(ConstDeclaration)); rulesAtLevel.Add(Line(ExternalDeclaration)); // Global include can go anywhere rulesAtLevel.Add(Line(IncludeStatement)); // Normal logic / text can go anywhere rulesAtLevel.Add(LogicLine); rulesAtLevel.Add(LineOfMixedTextAndLogic); // -------- // Breaking rules // Break current knot with a new knot if (level <= StatementLevel.Knot) { breakingRules.Add (KnotDeclaration); } // Break current stitch with a new stitch if (level <= StatementLevel.Stitch) { breakingRules.Add (StitchDeclaration); } // Breaking an inner block (like a multi-line condition statement) if (level <= StatementLevel.InnerBlock) { breakingRules.Add (ParseDashNotArrow); breakingRules.Add (String ("}")); } _statementRulesAtLevel [(int)level] = rulesAtLevel.ToArray (); _statementBreakRulesAtLevel [(int)level] = breakingRules.ToArray (); } } protected object SkipToNextLine() { ParseUntilCharactersFromString ("\n\r"); ParseNewline (); return ParseSuccess; } // Modifier to turn a rule into one that expects a newline on the end. // e.g. anywhere you can use "MixedTextAndLogic" as a rule, you can use // "Line(MixedTextAndLogic)" to specify that it expects a newline afterwards. protected ParseRule Line(ParseRule inlineRule) { return () => { object result = ParseObject(inlineRule); if (result == null) { return null; } Expect(EndOfLine, "end of line", recoveryRule: SkipToNextLine); return result; }; } ParseRule[][] _statementRulesAtLevel; ParseRule[][] _statementBreakRulesAtLevel; } }