protected override void ProcessNext() { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_ASSIGN)) { ProcessAssignment(NamespaceType.BulletUpdate); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_RANGE)) { ProcessRange(); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_CHILDREN)) { throw new NotImplementedException("This hasn't been implemented yet, sorry!"); } else if (DmlTokens.IsTimeCommand(CurrentToken)) { ProcessTimeCommand(); } else if (DmlTokens.IsName(CurrentToken) || DmlTokens.IsBuiltin(CurrentToken)) { ProcessExpression(GetUntil(DmlTokens.EOL)); } else if (DmlTokens.IsBehaviour(CurrentToken)) { ProcessBehaviour(); } else { throw DmlSyntaxError.InvalidTokenForContext(CurrentToken, "bullet update"); } }
/// <summary> /// Reverse the parser by the given number of steps. /// </summary> /// <param name="steps">The number of tokens backwards to reverse.</param> /// <param name="strict"> /// Whether or not to throw an error if we could not /// reverse the desired number of steps. True by default. /// </param> /// <param name="exception"> /// The exception to throw if strict is true and we could /// not reverse the desired number of steps. Null by default. /// </param> public void Reverse(int steps = 1, bool strict = true, Exception exception = null) { // `current` will only ever be null if we advanced past the last token. if (current == null) { current = _tokens.Last; steps--; } while (0 < steps-- && current.Previous != null) { current = current.Previous; // Newline tokens don't count towards the number of steps. if (DmlTokens.IsMatch(CurrentToken, DmlTokens.NEWLINE) && current.Previous != null) { current = current.Previous; currentLine--; } } // The value of `steps` will be zero unless we couldn't reverse anymore. if (steps != -1 && strict) { if (exception == null) { throw new ParserError("Cannot reverse the desired amount of steps."); } throw exception; } }
/// <summary> /// Parse the next token. /// </summary> protected void ParseNext() { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.NEWLINE)) { Advance(notNull: false); } else if (skipNext > 0) { skipNext--; Advance(notNull: false); } // Just kill the parsing if either of the above calls to Advance // caused the current token to push past the end. if (CurrentToken == null) { return; } if (expecting.Count > 0) { string nextExpecting = expecting.Pop(); if (!DmlTokens.IsMatch(CurrentToken, nextExpecting)) { throw DmlSyntaxError.UnexpectedToken(nextExpecting); } } ProcessNext(); Advance(notNull: false, strict: false); // By setting `notNull` to `false`, we allow `current` to be set to `null`, // which is what causes `Done` to return `true` causing the while loop to // end. }
protected override void ProcessNext() { if (DmlTokens.IsTimeCommand(CurrentToken)) { ProcessTimeCommand(); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_ASSIGN)) { ProcessAssignment(NamespaceType.Timeline); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_RANGE)) { ProcessRange(); } else if (DmlTokens.IsMatch(CurrentToken, "Spawn")) { if (!inTimeStamp) { throw DmlSyntaxError.InvalidSpawnPlacement(); } ProcessBehaviour(); } else { throw DmlSyntaxError.InvalidTokenForContext(CurrentToken, "timeline"); } }
/// <summary> /// Advance the parser by the given number of steps. /// /// Note: This will also automatically ignore newline tokens (incrementing the newline counter) /// and will check for any expecting tokens. /// </summary> /// <param name="steps">The number of tokens forwards to advance.</param> /// <param name="notNull"> /// Whether or not to allow CurrentToken to advance /// past the last token and become null. /// </param> /// <param name="strict"> /// Whether or not to throw an error if we could not /// advance the desired number of steps. True by default. /// </param> /// <param name="exception"> /// The exception to throw if strict is true and we could /// not advance the desired number of steps. Null by default. /// </param> public void Advance(int steps = 1, bool notNull = true, bool strict = true, Exception exception = null) { // If `notNull` is `true`, then we disallow the setting of `current` as `null`. while (0 < steps-- && HasNext(notNull)) { current = current.Next; if (DmlTokens.IsMatch(CurrentToken, DmlTokens.NEWLINE) && HasNext(notNull)) { current = current.Next; currentLine++; } if (expecting.Count > 0) { string nextExpecting = expecting.Pop(); if (!DmlTokens.IsMatch(CurrentToken, nextExpecting)) { throw DmlSyntaxError.UnexpectedToken(nextExpecting); } } } // As with `Reverse`, the value of `steps` will be -1 unless we couldn't advance anymore. if (steps != -1 && strict) { if (exception == null) { throw new ParserError("Cannot advance the desired amount of steps."); } throw exception; } }
/// <summary> /// Parse a function call using the given loading instruction. /// </summary> /// <param name="load"></param> private void ParseFuncCall(Instruction load) { int parenthDepth = 1; DmlSyntaxError badCall = new DmlSyntaxError( "Invalid syntax; mismatched brackets for function call."); var subExpressions = new List <List <string> >(); var currentExpression = new List <string>(); while (true) { Advance(exception: badCall); if (DmlTokens.IsMatch(CurrentToken, DmlTokens.LPAR)) { parenthDepth++; } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.RPAR)) { parenthDepth--; } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.COMMA) && parenthDepth == 1) { subExpressions.Add(currentExpression); currentExpression = new List <string>(); continue; } if (parenthDepth == 0) { if (currentExpression.Count > 0) { subExpressions.Add(currentExpression); } break; } currentExpression.Add(CurrentToken); } ExpressionBuilder eb; foreach (var subExpression in subExpressions) { eb = new ExpressionBuilder(subExpression.ToArray(), currentLine); eb.Parse(); foreach (Instruction instruction in (Instruction[])(eb.GetResult())) { instructions.Add(instruction); } } instructions.Add(load); instructions.Add(new CallFunction(subExpressions.Count)); }
protected override void ProcessNext() { try { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_ASSIGN)) { ProcessAssignment(NamespaceType.Global); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_RANGE)) { ProcessRange(); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.UQNAME) || DmlTokens.IsBuiltin(CurrentToken)) { ProcessExpression(GetUntil(DmlTokens.EOL)); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_BULLET)) { SetExpecting(DmlTokens.AT, DmlTokens.UQNAME, DmlTokens.LANGLE); Advance(2, exception: DmlSyntaxError.BadBulletDeclaration()); string bulletName = CurrentToken; Advance(exception: DmlSyntaxError.BadBulletDeclaration()); BulletBuilder bulletBuilder = new BulletBuilder(bulletName, GetNamespaceBlock(), currentLine); bulletBuilder.Parse(); Bullets[bulletName] = (DmlBulletFactory)bulletBuilder.GetResult(); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_TIMELINE)) { if (globalTimeline != null) { throw DmlSyntaxError.DuplicateTimeline(); } SetExpecting(DmlTokens.LANGLE); Advance(exception: DmlSyntaxError.BlockMissingDelimiters("Timeline")); TimelineBuilder timelineBuilder = new TimelineBuilder(GetNamespaceBlock(), currentLine); timelineBuilder.Parse(); globalTimeline = (DmlTimeline)timelineBuilder.GetResult(); } } catch (DmlSyntaxError exception) { throw new DmlSyntaxError(currentLine, exception); } }
/// <summary> /// Parse an "inline-able" range statement. /// /// An "inline-able" statement is one where we can infer the values of /// the start, end, and step as compile-time constants, and then inline /// the statements inside in the range block. /// /// This can make certain simple range statements faster since it requires /// less operations than a jump statement and a label instruction (which is /// the procedure for range statements that aren't simple). /// </summary> private void InlineRange(string loopVar, double start, double end, double step) { // Make a copy of the current instructions and clear the original instructions. // This will just make our life easier when we get to copying the instructions in // the range block into the previous instructions explicitly. List <Instruction> TempInstructions = new List <Instruction>(Instructions); Instructions = new List <Instruction>(); int angleDepth = 0; while (!Done) { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.LANGLE)) { angleDepth++; } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.RANGLE)) { if (angleDepth == 0) { break; } angleDepth--; } ParseNext(); } double i = start; while (i <= end) { foreach (Instruction instruction in Instructions) { if (instruction is LoadLocal && ((LoadLocal)instruction).name == loopVar) { TempInstructions.Add(new LoadConstant(new DmlObject(DmlType.Number, i))); } else { TempInstructions.Add(instruction); } } i += step; } // Reset the actual instructions to the temporary instructions now that we're done // copying over the range block instructions. Instructions = TempInstructions; }
/// <summary> /// Get the tokens within a namespace block. /// </summary> /// <param name="reposition"> /// Whether or not to reposition the parser after grabbing the tokens. /// </param> /// <param name="exception"> /// The exception to throw if the end of the tokens is reached before /// the namespace is closed. /// </param> /// <returns>The tokens inside the namespace (not including the delimiters).</returns> protected string[] GetNamespaceBlock(bool reposition = false, Exception exception = null) { LinkedListNode <string> oldToken = current; if (CurrentToken != DmlTokens.LANGLE) { current = oldToken; throw new ParserError("Initial token of namespace must be DmlTokens.NS_START."); } int namespaceDepth = 1; var newTokens = new List <string>(); Advance(); while (true) { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.LANGLE)) { namespaceDepth++; } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.RANGLE)) { namespaceDepth--; if (namespaceDepth == 0) { break; } } // FIXME: Aren't we double checking if current.Next == null? (Once here and once in Advance) if (current.Next == null) { if (exception == null) { throw new DmlSyntaxError("Invalid syntax; mismatched namespace delimiters detected."); } throw exception; } newTokens.Add(CurrentToken); Advance(exception: exception); } if (reposition) { current = oldToken; } return(newTokens.ToArray()); }
protected override void ProcessNext() { // Check for an assignment statement. if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_ASSIGN)) { throw DmlSyntaxError.BadAssignmentNamespace(); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_INIT)) { if (Init != null) { throw DmlSyntaxError.DuplicateNamespaceInBullet("Init"); } SetExpecting(DmlTokens.LANGLE); Advance(exception: DmlSyntaxError.BlockMissingDelimiters("Init")); string[] tokens = GetNamespaceBlock(); BulletInitBuilder initBuilder = new BulletInitBuilder(tokens, currentLine); initBuilder.Parse(); var initBlock = (Instruction[])initBuilder.GetResult(); Init = new CodeBlock(initBlock); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_UPDATE)) { if (Update != null) { throw DmlSyntaxError.DuplicateNamespaceInBullet("Update"); } SetExpecting(DmlTokens.LANGLE); Advance(exception: DmlSyntaxError.BlockMissingDelimiters("Update")); string[] tokens = GetNamespaceBlock(); BulletUpdateBuilder updateBuilder = new BulletUpdateBuilder(tokens, currentLine); updateBuilder.Parse(); var updateBlock = (Instruction[])updateBuilder.GetResult(); Update = new CodeBlock(updateBlock); } else { throw DmlSyntaxError.InvalidTokenForContext(CurrentToken, "bullet"); } }
protected override void ProcessNext() { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_ASSIGN)) { ProcessAssignment(NamespaceType.BulletInit); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_RANGE)) { ProcessRange(); } else if (DmlTokens.IsName(CurrentToken) || DmlTokens.IsBuiltin(CurrentToken)) { ProcessExpression(GetUntil(DmlTokens.EOL)); } else { throw DmlSyntaxError.InvalidTokenForContext(CurrentToken, "bullet init"); } }
/// <summary> /// Return a list of all the tokens until a given token (not including the terminal token). /// This will either reposition the parser at the initial token's position, or position the /// parser at the terminal character depending on whether or not `reposition` is `true` or /// `false`. /// /// If no terminal character is found then an error is thrown. /// /// No matter whether `ignoreBracketDepth` is `true` or `false`, this function will NOT /// check for mismatched brackets. That is the job of the DmlSyntaxChecker. /// </summary> /// <param name="token">The token to read until.</param> /// <param name="ignoreBracketDepth"> /// Whether or not to ignore bracket depth. If false (which it is by default), /// then the parser will continue taking tokens until one the end token is encountered /// outside brackets. /// </param> /// <param name="reposition"> /// Whether or not to reposition the parser after grabbing the tokens. /// </param> /// <param name="exception"> /// The exception to throw if the end of the tokens is reached before /// the given end token is encountered. /// </param> protected string[] GetUntil( string token, bool ignoreBracketDepth = false, bool reposition = false, Exception exception = null) { LinkedListNode <string> oldToken = current; int bracketDepth = 0; var tokens = new List <string>(); while (true) { if (DmlTokens.IsMatch(CurrentToken, token) && (!ignoreBracketDepth && bracketDepth == 0)) { break; } if (!ignoreBracketDepth) { if (DmlTokens.IsLeftBracket(CurrentToken)) { bracketDepth++; } else if (DmlTokens.IsRightBracket(CurrentToken)) { bracketDepth--; } } tokens.Add(CurrentToken); Advance(exception: exception); } if (reposition) { current = oldToken; } return(tokens.ToArray()); }
protected virtual void ProcessTimeCommand() { DmlSyntaxError badTimeCommand = DmlSyntaxError.BadTimeCommandSyntax(); ProcessExpression(GetUntil(DmlTokens.LANGLE)); string jumpName = String.Format("#{0:X6}", Globals.randGen.Next(0x1000000)); Instructions.Add(new JumpIfFalse(jumpName)); Advance(exception: badTimeCommand); int angleDepth = 0; while (!Done) { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.LANGLE)) { angleDepth++; } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.RANGLE)) { if (angleDepth == 0) { break; } angleDepth--; } ParseNext(); } if (angleDepth != 0) // We want it to break forcefully, otherwise there would be invalid syntax. { throw DmlSyntaxError.MismatchedNamespaceDelimiters(); } Instructions.Add(new Label(jumpName)); }
/// <summary> /// Run a second pass through the tokens to clean them up. /// </summary> private static string[] SecondPass(string[] tokens) { List <string> output = new List <string>(); int length = tokens.Length; string currentToken; bool seenOperator = false; for (int i = 0; i < length; i++) { currentToken = tokens[i]; // Check for comments and ignore them. if (DmlTokens.IsMatch(currentToken, DmlTokens.HASH)) { while (i < length && !DmlTokens.IsMatch(currentToken, DmlTokens.NEWLINE)) { currentToken = tokens[++i]; } output.Add(currentToken); } // Check for unary operators. else if (DmlTokens.IsOperator(currentToken) && !DmlTokens.IsMatch(currentToken, DmlTokens.RPAR)) { if (!seenOperator) { seenOperator = true; output.Add(currentToken); } else { switch (currentToken) { case DmlTokens.MINUS: output.Add(DmlTokens.UNARY_NEG); break; case DmlTokens.TILDE: output.Add(DmlTokens.UNARY_ABS); break; default: output.Add(currentToken); break; } } } // Check for decimals. This folds "123", ".", "567", into "123.567" else if (i < length - 2 && // At least 2 more tokens to parse. DmlTokens.IsNumber(currentToken) && DmlTokens.IsMatch(tokens[i + 1], DmlTokens.DOT) && DmlTokens.IsNumber(tokens[i + 2])) { output.Add(currentToken + DmlTokens.DOT + tokens[i + 2]); i += 2; } else { seenOperator = false; output.Add(currentToken); } } return(output.ToArray()); }
protected override void ProcessTimeCommand() { if (inTimeStamp) { throw DmlSyntaxError.BadTimeCommandPlacement(); } inTimeStamp = true; DmlSyntaxError badTimeCommand = DmlSyntaxError.BadTimeCommandSyntax(); double time, start = -1, end = -1; string timeCommand = CurrentToken; switch (timeCommand) { case "At": case "Before": case "After": SetExpecting(DmlTokens.LPAR, DmlTokens.NUMBER, DmlTokens.RPAR, DmlTokens.LANGLE); Advance(2, exception: badTimeCommand); time = Double.Parse(CurrentToken); Advance(2, exception: badTimeCommand); if (timeCommand == "At") { currentTimestamp = new TimestampAt(time); } else if (timeCommand == "Before") { currentTimestamp = new TimestampBefore(time); } else { currentTimestamp = new TimestampAfter(time); } break; case "AtIntervals": case "DuringIntervals": SetExpecting(DmlTokens.LPAR, DmlTokens.NUMBER); Advance(2, exception: badTimeCommand); time = Double.Parse(CurrentToken); if (DmlTokens.IsMatch(NextToken, DmlTokens.COMMA)) { SetExpecting(DmlTokens.COMMA, DmlTokens.NUMBER); Advance(2, exception: badTimeCommand); start = Double.Parse(CurrentToken); SetExpecting(DmlTokens.COMMA, DmlTokens.NUMBER); Advance(2, exception: badTimeCommand); end = Double.Parse(CurrentToken); } SetExpecting(DmlTokens.RPAR, DmlTokens.LANGLE); Advance(2, exception: badTimeCommand); if (timeCommand == "AtIntervals") { if (start == -1) { currentTimestamp = new TimestampAtIntervals(time); } else { currentTimestamp = new TimestampAtIntervals(time, start, end); } } else { if (start == -1) { currentTimestamp = new TimestampDuringIntervals(time); } else { currentTimestamp = new TimestampDuringIntervals(time, start, end); } } break; case "From": SetExpecting( DmlTokens.LPAR, DmlTokens.NUMBER, DmlTokens.COMMA, DmlTokens.NUMBER, DmlTokens.RPAR, DmlTokens.LANGLE ); Advance(2, exception: badTimeCommand); start = Double.Parse(CurrentToken); Advance(2, exception: badTimeCommand); end = Double.Parse(CurrentToken); Advance(2, exception: badTimeCommand); currentTimestamp = new TimestampFrom(start, end); break; } Advance(exception: badTimeCommand); int angleDepth = 0; while (!Done) { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.LANGLE)) { angleDepth++; } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.RANGLE)) { if (angleDepth == 0) { break; } angleDepth--; } ParseNext(); } currentTimestamp.SetCode(new CodeBlock(Instructions.ToArray())); Instructions = new List <Instruction>(); timeline.AddTimestamp(currentTimestamp); inTimeStamp = false; }
/// <summary> /// Process a generic `Assign` statement. /// </summary> /// <param name="nsType"></param> protected void ProcessAssignment(NamespaceType nsType) { DmlSyntaxError badAssign = DmlSyntaxError.BadAssignmentStatement(); Advance(exception: badAssign); Instruction assigner; // The instruction that performs the assignment. // Check for a global identifier. if (DmlTokens.IsMatch(CurrentToken, DmlTokens.AT)) { Advance(exception: badAssign); if (DmlTokens.IsMatch(CurrentToken, DmlTokens.UQNAME)) { assigner = new AssignGlobal(CurrentToken); } else { throw DmlSyntaxError.BadGlobalName(CurrentToken); } } // Check for an instance bound indentifier. else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.DOLLAR)) { switch (nsType) { case NamespaceType.BulletInit: case NamespaceType.BulletUpdate: Advance(exception: badAssign); if (DmlTokens.IsName(CurrentToken)) { switch (CurrentToken) { // Assign directly to the bullet direction. case DmlTokens.INTRINSIC_DIRECTION: assigner = AssignIntrinsicBulletProperty.Direction; break; // Assign directly to the bullet speed. case DmlTokens.INTRINSIC_SPEED: assigner = AssignIntrinsicBulletProperty.Speed; break; // Assign directly to the bullet colour. case DmlTokens.INTRINSIC_COLOUR: assigner = AssignIntrinsicBulletProperty.Colour; break; case DmlTokens.INTRINSIC_SPRITE: assigner = AssignIntrinsicBulletProperty.Sprite; break; default: assigner = new AssignBulletBound(CurrentToken); break; } } else { throw DmlSyntaxError.BadVariableName(CurrentToken); } break; default: throw DmlSyntaxError.BadAssignmentNamespace(); } } // Check for an ordinary local name. else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.UQNAME)) { assigner = new AssignLocal(CurrentToken); } else { throw DmlSyntaxError.BadVariableName(CurrentToken); } // Read the expression. Advance(exception: badAssign); string[] expression_tokens = GetUntil(DmlTokens.EOL); ProcessExpression(expression_tokens); Instructions.Add(assigner); }
/// <summary> /// Process a generic `Range` statement. /// </summary> /// <param name="nsType"></param> protected void ProcessRange() { DmlSyntaxError badRange = DmlSyntaxError.BadRangeStatement(); Advance(exception: badRange); // Parse the loop variable. if (!DmlTokens.IsName(CurrentToken)) { throw badRange; } string loopVariable = CurrentToken; Advance(exception: badRange); // Parse the range start. List <String> startTokens; try { startTokens = new List <String>(GetUntil(DmlTokens.ELLIPSES)); } catch (ParserError) { throw badRange; } ExpressionBuilder startExpressionBuilder = new ExpressionBuilder(startTokens.ToArray(), currentLine); startExpressionBuilder.Parse(); var startInstructions = (Instruction[])startExpressionBuilder.GetResult(); Advance(exception: badRange); // Parse the range end and range step. List <String> endTokens; try { endTokens = new List <String>(GetUntil(DmlTokens.LANGLE)); } catch (ParserError) { throw badRange; } // Check if the range operator is extended to include a step. int stepIndex = endTokens.IndexOf(DmlTokens.TRANSOP); List <String> stepTokens; if (stepIndex == endTokens.Count - 1) { throw badRange; } else if (stepIndex != -1) { stepTokens = new List <string>(endTokens.Skip(stepIndex + 1)); endTokens = new List <string>(endTokens.Take(stepIndex)); } else { // Default step size is 1. stepTokens = new List <String>() { "1" } }; ExpressionBuilder endExpressionBuilder = new ExpressionBuilder(endTokens.ToArray(), currentLine); ExpressionBuilder stepExpressionBuilder = new ExpressionBuilder(stepTokens.ToArray(), currentLine); endExpressionBuilder.Parse(); stepExpressionBuilder.Parse(); var endInstructions = (Instruction[])endExpressionBuilder.GetResult(); var stepInstructions = (Instruction[])stepExpressionBuilder.GetResult(); /* * if (DmlParser.IsSimpleConstant(startInstructions) && * DmlParser.IsSimpleConstant(endInstructions) && * DmlParser.IsSimpleConstant(stepInstructions) * ) * { * double start, end, step; * try * { * start = (double)(new CodeBlock(startInstructions).Evaluate(null, null).Value); * end = (double)(new CodeBlock(endInstructions).Evaluate(null, null).Value); * step = (double)(new CodeBlock(stepInstructions).Evaluate(null, null).Value); * InlineRange(loopVariable, start, end, step); * } * catch (InvalidCastException) * { * // InlineRange can only handle double values. * ComplexRange(loopVariable, startInstructions, endInstructions, stepInstructions); * } * } * else */ // FIXME: // So there's a problem with InlineRange. If there are jump statements inside the code within // the Range block, the labels and the jumps will get repeated, so all jumps lead to the same // spot, which leads to infinite recursion. // Not sure why but sometimes the current token is set to the left namespace delimiter, // and sometimes its set to the first token after the delimiter. It doesn't seem to be // dependent on the presense of the step operator \> either. For now this works. if (DmlTokens.IsMatch(CurrentToken, DmlTokens.LANGLE)) { Advance(); } ComplexRange(loopVariable, startInstructions, endInstructions, stepInstructions); } }
/// <summary> /// Parse a complex range statement. /// </summary> private void ComplexRange( string loopVariable, Instruction[] startInstructions, Instruction[] endInstructions, Instruction[] stepInstructions) { // Generate a random name for the loop label. string loopName = String.Format("#{0:X6}", Globals.randGen.Next(0x1000000)); // These names are just "private" names that the executer uses but the user // can never use (as it will always evaluate to a comment in the Tokenizer). string endName = "#" + loopName + "_end"; string stepName = "#" + loopName + "_step"; // Add the instructions for the start, end, and step respectively. foreach (Instruction instruction in startInstructions) { Instructions.Add(instruction); } Instructions.Add(new AssignLocal(loopVariable)); foreach (Instruction instruction in endInstructions) { Instructions.Add(instruction); } Instructions.Add(new AssignLocal(endName)); foreach (Instruction instruction in stepInstructions) { Instructions.Add(instruction); } Instructions.Add(new AssignLocal(stepName)); // Add a label to mark the start of the loop. Instructions.Add(new Label(loopName)); int angleDepth = 0; // Begin processing the interior of the loop. while (!Done) { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.LANGLE)) { angleDepth++; } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.RANGLE)) { if (angleDepth == 0) { break; } angleDepth--; } ParseNext(); } // Increment the loop variable by the step value. Instructions.Add(new LoadLocal(loopVariable)); Instructions.Add(new LoadLocal(stepName)); Instructions.Add(BinaryAdd.Instance); Instructions.Add(new AssignLocal(loopVariable)); // Jump to the start of the loop if the loop variable is still less than or equal to the end value. Instructions.Add(new LoadLocal(loopVariable)); Instructions.Add(new LoadLocal(endName)); Instructions.Add(new JumpIfLessThanOrEqual(loopName)); }
protected override void ProcessNext() { if (DmlTokens.IsMatch(CurrentToken, DmlTokens.AT) || DmlTokens.IsMatch(CurrentToken, DmlTokens.DOLLAR) || DmlTokens.IsNonKeywordName(CurrentToken) || DmlTokens.IsBuiltin(CurrentToken)) { Instruction load; string badName = "Invalid syntax; invalid name \"{0}\"."; if (DmlTokens.IsMatch(CurrentToken, DmlTokens.AT)) { Advance(exception: new DmlSyntaxError("Invalid syntax: global identifier `@` must be followed by a name.")); if (!DmlTokens.IsName(CurrentToken)) { throw new DmlSyntaxError(String.Format(badName, CurrentToken)); } load = new LoadGlobal(CurrentToken); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.DOLLAR)) { Advance(exception: new DmlSyntaxError("Invalid syntax: identifier `$` must be followed by a name.")); if (DmlTokens.IsName(CurrentToken)) { switch (CurrentToken) { case DmlTokens.INTRINSIC_DIRECTION: load = LoadIntrinsicBulletProperty.Direction; break; case DmlTokens.INTRINSIC_SPEED: load = LoadIntrinsicBulletProperty.Speed; break; case DmlTokens.INTRINSIC_COLOUR: load = LoadIntrinsicBulletProperty.Colour; break; case DmlTokens.INTRINSIC_ORIGIN: load = LoadIntrinsicBulletProperty.Origin; break; case DmlTokens.INTRINSIC_POSITION: load = LoadIntrinsicBulletProperty.Position; break; case DmlTokens.INTRINSIC_VELOCITY: load = LoadIntrinsicBulletProperty.Velocity; break; case DmlTokens.INTRINSIC_TIME: load = LoadIntrinsicBulletProperty.Time; break; default: load = new LoadInstanceBound(CurrentToken); break; } } else { throw new DmlSyntaxError(String.Format(badName, CurrentToken)); } } else if (DmlTokens.IsName(CurrentToken)) { load = new LoadLocal(CurrentToken); } else { load = new LoadConstant(BuiltinsDict.Builtins[CurrentToken]); } bool isCall = false; // Check if this is a function call. // We actually want to catch the ParserError thrown by Advance() in this case // because it's very possible the expression could end with a name. if (NextToken != null) { Advance(); if (DmlTokens.IsMatch(CurrentToken, DmlTokens.LPAR)) { isCall = true; ParseFuncCall(load); } else { Reverse(); } } if (!isCall) { instructions.Add(load); } } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_TRUE)) { instructions.Add(new LoadConstant(new DmlObject(DmlType.Bool, true))); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_FALSE)) { instructions.Add(new LoadConstant(new DmlObject(DmlType.Bool, false))); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_NULL)) { instructions.Add(new LoadConstant(new DmlObject(DmlType.Null, null))); } // Check for a left parenthesis. else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.LPAR)) { // We know this will never be the start of a function call because // that case would be caught by the above if block. operators.Push(CurrentToken); } // Check for a right parenthesis. else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.RPAR)) { // By the shunting-yard algorithm, we pop every operator until we find a left // parenthesis. If we don't find one, then there are mismatched parentheses. if (operators.Count == 0 || !operators.Contains(DmlTokens.LPAR)) { throw DmlSyntaxError.MismatchedParentheses(); } string top; while (operators.Count > 0) { top = operators.First(); if (DmlTokens.IsMatch(top, DmlTokens.LPAR)) { operators.Pop(); break; } instructions.Add(OPERATOR_INSTRUCTIONS[operators.Pop()]); } } // Check if the next token is a valid mathematical operator. else if (IsOperator(CurrentToken)) { if (operators.Count == 0) { operators.Push(CurrentToken); } // If there are prior operators, pop them and add them to the instructions according to their // associativity and precedence. else { string top; while (operators.Count > 0) { top = operators.First(); if ((IsLeftAssociative(top) && OPERATOR_PRECEDENCE[top] >= OPERATOR_PRECEDENCE[CurrentToken]) || (IsRightAssociative(top) && OPERATOR_PRECEDENCE[top] > OPERATOR_PRECEDENCE[CurrentToken])) { instructions.Add(OPERATOR_INSTRUCTIONS[operators.Pop()]); } else { break; } } operators.Push(CurrentToken); } } // Check for a number. else if (DmlTokens.IsNumber(CurrentToken)) { instructions.Add(new LoadConstant(new DmlObject(DmlType.Number, Double.Parse(CurrentToken)))); } // Check for True. else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_TRUE)) { instructions.Add(new LoadConstant(new DmlObject(DmlType.Bool, true))); } // Check for False. else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_FALSE)) { instructions.Add(new LoadConstant(new DmlObject(DmlType.Bool, false))); } // Check for Null. else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_NULL)) { instructions.Add(new LoadConstant(new DmlObject(DmlType.Null, null))); } else if (DmlTokens.IsString(CurrentToken)) { instructions.Add(new LoadConstant(new DmlObject(DmlType.String, CurrentToken))); } // Check for Lambda. else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.KW_LAMBDA)) { throw new NotImplementedException("This hasn't been implemented yet, sorry!"); } else { throw DmlSyntaxError.InvalidTokenForContext(CurrentToken, "expression"); } }
protected void ProcessBehaviour() { DmlSyntaxError badBehaviour = DmlSyntaxError.BadBehaviourSyntax(); string behaviourName = CurrentToken; SetExpecting(DmlTokens.VBAR); Advance(2, exception: badBehaviour); if (DmlTokens.IsMatch(CurrentToken, DmlTokens.EOL)) { // The behaviour is empty. Instructions.Add(BehavioursDict.InitializeBehaviour(behaviourName, new string[] { })); return; } var paramExpressions = new List <List <string> >(); var parameters = new List <string>(); bool insideExpression = false; int parenthDepth = 0; while (!Done) { if (!insideExpression && DmlTokens.IsMatch(CurrentToken, DmlTokens.PERCENT)) { Advance(exception: badBehaviour); if (!DmlTokens.IsNonKeywordName(CurrentToken)) { throw DmlSyntaxError.BadVariableName(CurrentToken); } parameters.Add(CurrentToken); paramExpressions.Add(new List <string>()); insideExpression = true; } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.COMMA) && parenthDepth == 0) { insideExpression = false; SetExpecting(DmlTokens.PERCENT); } else if (DmlTokens.IsMatch(CurrentToken, DmlTokens.EOL) && parenthDepth == 0) { break; } else { if (DmlTokens.IsLeftBracket(CurrentToken)) { if (!insideExpression) { throw badBehaviour; } parenthDepth++; } else if (DmlTokens.IsRightBracket(CurrentToken)) { if (!insideExpression) { throw badBehaviour; } parenthDepth--; } paramExpressions.Last().Add(CurrentToken); } Advance(); } if (parenthDepth != 0) // We want it to break forcefully, otherwise there would be invalid syntax. { throw badBehaviour; } foreach (var expression in paramExpressions) { ProcessExpression(expression.ToArray()); } Instructions.Add(BehavioursDict.InitializeBehaviour(behaviourName, parameters.ToArray())); }