private void WhileStatement() { Loop l = new Loop(); StartLoop(l); // Compile the condition. Consume(TokenType.LeftParen, "Expect '(' after 'while'."); Expression(); Consume(TokenType.RightParen, "Expect ')' after while condition."); TestExitLoop(); LoopBody(); EndLoop(); }
// Marks the beginning of a loop. Keeps track of the current instruction so we // know what to loop back to at the end of the body. private void StartLoop(Loop l) { l.Enclosing = _loop; l.Start = _bytecode.Count - 1 - 1; l.ScopeDepth = _scopeDepth; _loop = l; }
private void ForStatement() { // A for statement like: // // for (i in sequence.expression) { // System.write(i) // } // // Is compiled to bytecode almost as if the source looked like this: // // { // var seq_ = sequence.expression // var iter_ // while (iter_ = seq_.iterate(iter_)) { // var i = seq_.iteratorValue(iter_) // System.write(i) // } // } // // It's not exactly this, because the synthetic variables `seq_` and `iter_` // actually get names that aren't valid identfiers, but that's the basic // idea. // // The important parts are: // - The sequence expression is only evaluated once. // - The .iterate() method is used to advance the iterator and determine if // it should exit the loop. // - The .iteratorValue() method is used to get the value at the current // iterator position. // Create a scope for the hidden local variables used for the iterator. PushScope(); Consume(TokenType.LeftParen, "Expect '(' after 'for'."); Consume(TokenType.Name, "Expect for loop variable name."); // Remember the name of the loop variable. string name = _parser.Source.Substring(_parser.Previous.Start, _parser.Previous.Length); int length = _parser.Previous.Length; Consume(TokenType.In, "Expect 'in' after loop variable."); IgnoreNewlines(); // Evaluate the sequence expression and store it in a hidden local variable. // The space in the variable name ensures it won't collide with a user-defined // variable. Expression(); int seqSlot = DefineLocal("seq ", 4); // Create another hidden local for the iterator object. null_(this, false); int iterSlot = DefineLocal("iter ", 5); Consume(TokenType.RightParen, "Expect ')' after loop expression."); Loop l = new Loop(); StartLoop(l); // Advance the iterator by calling the ".iterate" method on the sequence. LoadLocal(seqSlot); LoadLocal(iterSlot); CallMethod(1, "iterate(_)"); // Store the iterator back in its local for the next iteration. EmitByteArg(Instruction.StoreLocal, iterSlot); // TODO: We can probably get this working with a bit less stack juggling. TestExitLoop(); // Get the current value in the sequence by calling ".iteratorValue". LoadLocal(seqSlot); LoadLocal(iterSlot); CallMethod(1, "iteratorValue(_)"); // Bind the loop variable in its own scope. This ensures we get a fresh // variable each iteration so that closures for it don't all see the same one. PushScope(); DefineLocal(name, length); LoopBody(); // Loop variable. PopScope(); EndLoop(); // Hidden variables. PopScope(); }
// Initializes [compiler]. public Compiler(Parser parser, Compiler parent, bool isFunction) { _parser = parser; _parent = parent; // Initialize this to null before allocating in case a GC gets triggered in // the middle of initializing the compiler. _constants = new List<Obj>(); _numUpValues = 0; _numParams = 0; _loop = null; _enclosingClass = null; parser.Vm.Compiler = this; if (parent == null) { _numLocals = 0; // Compiling top-level code, so the initial scope is module-level. _scopeDepth = -1; } else { // Declare a fake local variable for the receiver so that it's slot in the // stack is taken. For methods, we call this "this", so that we can resolve // references to that like a normal variable. For functions, they have no // explicit "this". So we pick a bogus name. That way references to "this" // inside a function will try to walk up the parent chain to find a method // enclosing the function whose "this" we can close over. _numLocals = 1; if (isFunction) { _locals[0].Name = null; _locals[0].Length = 0; } else { _locals[0].Name = "this"; _locals[0].Length = 4; } _locals[0].Depth = -1; _locals[0].IsUpvalue = false; // The initial scope for function or method is a local scope. _scopeDepth = 0; } _bytecode = new List<byte>(); }
// Ends the current innermost loop. Patches up all jumps and breaks now that // we know where the end of the loop is. private void EndLoop() { int loopOffset = _bytecode.Count - 1 - _loop.Start + 2; // TODO: Check for overflow. EmitShortArg(Instruction.Loop, loopOffset); PatchJump(_loop.ExitJump); // Find any break placeholder instructions (which will be Instruction.END in the // bytecode) and replace them with real jumps. int i = _loop.Body; while (i < _bytecode.Count - 1) { if (_bytecode[i] == (byte)Instruction.End) { _bytecode[i] = (byte)Instruction.Jump; PatchJump(i + 1); i += 3; } else { // Skip this instruction and its arguments. i += 1 + GetNumArguments(_bytecode.ToArray(), _constants, i); } } _loop = _loop.Enclosing; }