// 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>(); }
// Compiles a method definition inside a class body. Returns the symbol in the // method table for the new method. private int Method(ClassCompiler classCompiler, bool isConstructor, SignatureFn signatureFn) { // Build the method signature. Signature signature = new Signature(); SignatureFromToken(signature); classCompiler.MethodName = signature.Name; classCompiler.MethodLength = signature.Length; Compiler methodCompiler = new Compiler(_parser, this, false); // Compile the method signature. signatureFn(methodCompiler, signature); // Include the full signature in debug messages in stack traces. Consume(TokenType.LeftBrace, "Expect '{' to begin method body."); methodCompiler.FinishBody(isConstructor); methodCompiler.EndCompiler(); return SignatureSymbol(signature); }
// Compiles a class definition. Assumes the "class" token has already been // consumed. private void ClassDefinition() { // Create a variable to store the class in. int slot = DeclareNamedVariable(); bool isModule = _scopeDepth == -1; // Make a string constant for the name. int nameConstant = AddConstant(Obj.MakeString(_parser.Source.Substring(_parser.Previous.Start, _parser.Previous.Length))); EmitConstant(nameConstant); // Load the superclass (if there is one). if (Match(TokenType.Is)) { ParsePrecedence(false, Precedence.Call); } else { // Create the empty class. Emit(Instruction.Null); } // Store a placeholder for the number of fields argument. We don't know // the value until we've compiled all the methods to see which fields are // used. int numFieldsInstruction = EmitByteArg(Instruction.Class, 255); // Store it in its name. DefineVariable(slot); // Push a local variable scope. Static fields in a class body are hoisted out // into local variables declared in this scope. Methods that use them will // have upvalues referencing them. PushScope(); ClassCompiler classCompiler = new ClassCompiler(); // Set up a symbol table for the class's fields. We'll initially compile // them to slots starting at zero. When the method is bound to the class, the // bytecode will be adjusted by [BindMethod] to take inherited fields // into account. List<string> fields = new List<string>(); classCompiler.Fields = fields; _enclosingClass = classCompiler; // Compile the method definitions. Consume(TokenType.LeftBrace, "Expect '{' after class declaration."); MatchLine(); while (!Match(TokenType.RightBrace)) { Instruction instruction = Instruction.MethodInstance; bool isConstructor = false; classCompiler.IsStaticMethod = false; if (Match(TokenType.Static)) { instruction = Instruction.MethodStatic; classCompiler.IsStaticMethod = true; } else if (Peek() == TokenType.New) { // If the method name is "new", it's a constructor. isConstructor = true; } SignatureFn signature = _rules[(int)_parser.Current.Type].Method; NextToken(); if (signature == null) { Error("Expect method definition."); break; } int methodSymbol = Method(classCompiler, isConstructor, signature); // Load the class. We have to do this for each method because we can't // keep the class on top of the stack. If there are static fields, they // will be locals above the initial variable slot for the class on the // stack. To skip past those, we just load the class each time right before // defining a method. if (isModule) { EmitShortArg(Instruction.LoadModuleVar, slot); } else { LoadLocal(slot); } // Define the method. EmitShortArg(instruction, methodSymbol); // Don't require a newline after the last definition. if (Match(TokenType.RightBrace)) break; ConsumeLine("Expect newline after definition in class."); } // Update the class with the number of fields. _bytecode[numFieldsInstruction] = (byte)fields.Count; _enclosingClass = null; PopScope(); }