// Compiles an (optional) argument list and then calls it. private void MethodCall(Instruction instruction, Signature signature) { Signature called = new Signature { Type = SignatureType.Getter, Arity = 0, Name = signature.Name, Length = signature.Length }; // Parse the argument list, if any. if (Match(TokenType.LeftParen)) { called.Type = SignatureType.Method; // Allow empty an argument list. if (Peek() != TokenType.RightParen) { FinishArgumentList(called); } Consume(TokenType.RightParen, "Expect ')' after arguments."); } // Parse the block argument, if any. if (Match(TokenType.LeftBrace)) { // Include the block argument in the arity. called.Type = SignatureType.Method; called.Arity++; Compiler fnCompiler = new Compiler(_parser, this, true); // Make a dummy signature to track the arity. Signature fnSignature = new Signature { Arity = 0 }; // Parse the parameter list, if any. if (Match(TokenType.Pipe)) { fnCompiler.FinishParameterList(fnSignature); Consume(TokenType.Pipe, "Expect '|' after function parameters."); } fnCompiler._numParams = fnSignature.Arity; fnCompiler.FinishBody(false); String blockName = SignatureToString(called) + " block argument"; fnCompiler.EndCompiler(blockName); } // TODO: Allow Grace-style mixfix methods? // If this is a super() call for an initializer, make sure we got an actual // argument list. if (signature.Type == SignatureType.Initializer) { if (called.Type != SignatureType.Method) { Error("A superclass constructor must have an argument list."); } called.Type = SignatureType.Initializer; } CallSignature(instruction, called); }
// Compiles a method definition inside a class body. Returns the symbol in the // method table for the new method. private bool Method(ClassCompiler classCompiler, int classSlot) { bool isForeign = Match(TokenType.Foreign); classCompiler.IsStaticMethod = Match(TokenType.Static); SignatureFn signatureFn = GetRule(_parser.Current.Type).Method; NextToken(); if (signatureFn == null) { Error("Expect method definition."); return false; } // Build the method signature. Signature signature = SignatureFromToken(new Signature(), SignatureType.Getter); classCompiler.Signature = signature; Compiler methodCompiler = new Compiler(_parser, this, false); signatureFn(methodCompiler, signature); if (classCompiler.IsStaticMethod && signature.Type == SignatureType.Initializer) { Error("A constructor cannot be static."); } String fullSignature = SignatureToString(signature); if (isForeign) { int constant = AddConstant(Obj.MakeString(fullSignature)); EmitShortArg(Instruction.CONSTANT, constant); } else { Consume(TokenType.LeftBrace, "Expect '{' to begin method body."); methodCompiler.FinishBody(signature.Type == SignatureType.Initializer); methodCompiler.EndCompiler(fullSignature); } // Define the method. For a constructor, this defines the instance // initializer method. int methodSymbol = SignatureSymbol(signature); DefineMethod(classSlot, classCompiler.IsStaticMethod, methodSymbol); if (signature.Type == SignatureType.Initializer) { signature.Type = SignatureType.Method; int constructorSymbol = SignatureSymbol(signature); CreateConstructor(signature, methodSymbol); DefineMethod(classSlot, true, constructorSymbol); } return true; }
public static ObjFn Compile(WrenVM vm, ObjModule module, string sourcePath, string source, bool printErrors) { Parser parser = new Parser { vm = vm, Module = module, SourcePath = sourcePath, Source = source, TokenStart = 0, CurrentChar = 0, CurrentLine = 1, Current = { Type = TokenType.Error, Start = 0, Length = 0, Line = 0 }, SkipNewlines = true, PrintErrors = printErrors, HasError = false, Raw = "" }; Compiler compiler = new Compiler(parser, null, true); // Read the first token. compiler.NextToken(); compiler.IgnoreNewlines(); while (!compiler.Match(TokenType.Eof)) { compiler.Definition(); // If there is no newline, it must be the end of the block on the same line. if (!compiler.MatchLine()) { compiler.Consume(TokenType.Eof, "Expect end of file."); break; } } compiler.Emit(Instruction.NULL); compiler.Emit(Instruction.RETURN); // See if there are any implicitly declared module-level variables that never // got an explicit definition. // TODO: It would be nice if the error was on the line where it was used. for (int i = 0; i < parser.Module.Variables.Count; i++) { ModuleVariable t = parser.Module.Variables[i]; if (t.Container == Obj.Undefined) { compiler.Error(string.Format("Variable '{0}' is used but not defined.", t.Name)); } } return compiler.EndCompiler("(script)"); }
// Compiles a method signature for an operator that can either be unary or // infix (i.e. "-"). private static void MixedSignature(Compiler c, Signature signature) { signature.Type = SignatureType.Getter; // If there is a parameter, it's an infix operator, otherwise it's unary. if (c.Match(TokenType.LeftParen)) { // Add the RHS parameter. signature.Type = SignatureType.Method; signature.Arity = 1; // Parse the parameter name. c.DeclareNamedVariable(); c.Consume(TokenType.RightParen, "Expect ')' after parameter name."); } }
// Compiles a method signature for a named method or setter. private static void NamedSignature(Compiler c, Signature signature) { signature.Type = SignatureType.Getter; // If it's a setter, it can't also have a parameter list. if (c.MaybeSetter(signature)) return; // Regular named method with an optional parameter list. c.ParameterList(signature); }
private static void Conditional(Compiler c, bool allowAssignment) { // Ignore newline after '?'. c.IgnoreNewlines(); // Jump to the else branch if the condition is false. int ifJump = c.EmitJump(Instruction.JUMP_IF); // Compile the then branch. c.ParsePrecedence(allowAssignment, Precedence.Ternary); c.Consume(TokenType.Colon, "Expect ':' after then branch of conditional operator."); c.IgnoreNewlines(); // Jump over the else branch when the if branch is taken. int elseJump = c.EmitJump(Instruction.JUMP); // Compile the else branch. c.PatchJump(ifJump); c.ParsePrecedence(allowAssignment, Precedence.Assignment); // Patch the jump over the else. c.PatchJump(elseJump); }
// Compiles a method signature for an infix operator. static void InfixSignature(Compiler c, Signature signature) { // Add the RHS parameter. signature.Type = SignatureType.Method; signature.Arity = 1; // Parse the parameter name. c.Consume(TokenType.LeftParen, "Expect '(' after operator name."); c.DeclareNamedVariable(); c.Consume(TokenType.RightParen, "Expect ')' after parameter name."); }
private static void Field(Compiler c, bool allowAssignment) { // Initialize it with a fake value so we can keep parsing and minimize the // number of cascaded errors. int field = 255; ClassCompiler enclosingClass = c.GetEnclosingClass(); if (enclosingClass == null) { c.Error("Cannot reference a field outside of a class definition."); } else if (enclosingClass.IsStaticMethod) { c.Error("Cannot use an instance field in a static method."); } else { // Look up the field, or implicitly define it. string fieldName = c._parser.Source.Substring(c._parser.Previous.Start, c._parser.Previous.Length); field = enclosingClass.Fields.IndexOf(fieldName); if (field < 0) { enclosingClass.Fields.Add(fieldName); field = enclosingClass.Fields.IndexOf(fieldName); } if (field >= MaxFields) { c.Error(string.Format("A class can only have {0} fields.", MaxFields)); } } // If there's an "=" after a field name, it's an assignment. bool isLoad = true; if (c.Match(TokenType.Eq)) { if (!allowAssignment) c.Error("Invalid assignment."); // Compile the right-hand side. c.Expression(); isLoad = false; } // If we're directly inside a method, use a more optimal instruction. if (c._parent != null && c._parent._enclosingClass == enclosingClass) { c.EmitByteArg(isLoad ? Instruction.LOAD_FIELD_THIS : Instruction.STORE_FIELD_THIS, field); } else { c.LoadThis(); c.EmitByteArg(isLoad ? Instruction.LOAD_FIELD : Instruction.STORE_FIELD, field); } }
private static void StaticField(Compiler c, bool allowAssignment) { Instruction loadInstruction = Instruction.LOAD_LOCAL; int index = 255; Compiler classCompiler = c.GetEnclosingClassCompiler(); if (classCompiler == null) { c.Error("Cannot use a static field outside of a class definition."); } else { // Look up the name in the scope chain. Token token = c._parser.Previous; // If this is the first time we've seen this static field, implicitly // define it as a variable in the scope surrounding the class definition. if (classCompiler.ResolveLocal(c._parser.Source.Substring(token.Start, token.Length), token.Length) == -1) { int symbol = classCompiler.DeclareVariable(null); // Implicitly initialize it to null. classCompiler.Emit(Instruction.NULL); classCompiler.DefineVariable(symbol); } // It definitely exists now, so resolve it properly. This is different from // the above resolveLocal() call because we may have already closed over it // as an upvalue. index = c.ResolveName(c._parser.Source.Substring(token.Start, token.Length), token.Length, out loadInstruction); } c.Variable(allowAssignment, index, loadInstruction); }
// Unary operators like `-foo`. private static void UnaryOp(Compiler c, bool allowAssignment) { GrammarRule rule = c.GetRule(c._parser.Previous.Type); c.IgnoreNewlines(); // Compile the argument. c.ParsePrecedence(false, Precedence.Unary + 1); // Call the operator method on the left-hand side. c.CallMethod(0, rule.Function); }
private static void Boolean(Compiler c, bool allowAssignment) { c.Emit(c._parser.Previous.Type == TokenType.False ? Instruction.FALSE : Instruction.TRUE); }
// A map literal. private static void Map(Compiler c, bool allowAssignment) { // Load the Map class. c.LoadCoreVariable("Map"); // Instantiate a new map. c.CallMethod(0, "new()"); // Compile the map elements. Each one is compiled to just invoke the // subscript setter on the map. if (c.Peek() != TokenType.RightBrace) { do { c.IgnoreNewlines(); if (c.Peek() == TokenType.RightBrace) break; // Push a copy of the map since the subscript call will consume it. c.Emit(Instruction.DUP); // The key. c.ParsePrecedence(false, Precedence.Primary); c.Consume(TokenType.Colon, "Expect ':' after map key."); c.IgnoreNewlines(); // The value. c.Expression(); c.CallMethod(2, "[_]=(_)"); // Discard the result of the setter call. c.Emit(Instruction.POP); } while (c.Match(TokenType.Comma)); } // Allow newlines before the closing '}'. c.IgnoreNewlines(); c.Consume(TokenType.RightBrace, "Expect '}' after map entries."); }
// A list literal. private static void List(Compiler c, bool allowAssignment) { // Load the List class. c.LoadCoreVariable("List"); // Instantiate a new list. c.CallMethod(0, "new()"); // Compile the list elements. Each one compiles to a ".add()" call. if (c.Peek() != TokenType.RightBracket) { do { c.IgnoreNewlines(); if (c.Peek() == TokenType.RightBracket) break; // Push a copy of the list since the add() call will consume it. c.Emit(Instruction.DUP); // The element. c.Expression(); c.CallMethod(1, "add(_)"); // Discard the result of the add() call. c.Emit(Instruction.POP); } while (c.Match(TokenType.Comma)); } // Allow newlines before the closing ']'. c.IgnoreNewlines(); c.Consume(TokenType.RightBracket, "Expect ']' after list elements."); }
// A parenthesized expression. private static void Grouping(Compiler c, bool allowAssignment) { c.Expression(); c.Consume(TokenType.RightParen, "Expect ')' after expression."); }
private static void Call(Compiler c, bool allowAssignment) { c.IgnoreNewlines(); c.Consume(TokenType.Name, "Expect method name after '.'."); c.NamedCall(allowAssignment, Instruction.CALL_0); }
// Compiles a variable name or method call with an implicit receiver. private static void Name(Compiler c, bool allowAssignment) { // Look for the name in the scope chain up to the nearest enclosing method. Token token = c._parser.Previous; Instruction loadInstruction; string varName = c._parser.Source.Substring(token.Start, token.Length); int index = c.ResolveNonmodule(varName, token.Length, out loadInstruction); if (index != -1) { c.Variable(allowAssignment, index, loadInstruction); return; } // TODO: The fact that we return above here if the variable is known and parse // an optional argument list below if not means that the grammar is not // context-free. A line of code in a method like "someName(foo)" is a parse // error if "someName" is a defined variable in the surrounding scope and not // if it isn't. Fix this. One option is to have "someName(foo)" always // resolve to a self-call if there is an argument list, but that makes // getters a little confusing. // If we're inside a method and the name is lowercase, treat it as a method // on this. if (IsLocalName(varName) && c.GetEnclosingClass() != null) { c.LoadThis(); c.NamedCall(allowAssignment, Instruction.CALL_0); return; } // Otherwise, look for a module-level variable with the name. int module = c._parser.Module.Variables.FindIndex(v => v.Name == varName); if (module == -1) { if (IsLocalName(varName)) { c.Error("Undefined variable."); return; } // If it's a nonlocal name, implicitly define a module-level variable in // the hopes that we get a real definition later. module = c._parser.vm.DeclareVariable(c._parser.Module, varName); if (module == -2) { c.Error("Too many module variables defined."); } } c.Variable(allowAssignment, module, Instruction.LOAD_MODULE_VAR); }
static void or_(Compiler c, bool allowAssignment) { c.IgnoreNewlines(); // Skip the right argument if the left is true. int jump = c.EmitJump(Instruction.OR); c.ParsePrecedence(false, Precedence.LogicalOr); c.PatchJump(jump); }
private static void null_(Compiler c, bool allowAssignment) { c.Emit(Instruction.NULL); }
private static void InfixOp(Compiler c, bool allowAssignment) { GrammarRule rule = c.GetRule(c._parser.Previous.Type); // An infix operator cannot end an expression. c.IgnoreNewlines(); // Compile the right-hand side. c.ParsePrecedence(false, rule.Precedence + 1); // Call the operator method on the left-hand side. Signature signature = new Signature { Type = SignatureType.Method, Arity = 1, Name = rule.Function, Length = rule.Function.Length }; c.CallSignature(Instruction.CALL_0, signature); }
private static void Number(Compiler c, bool allowAssignment) { int constant = c.AddConstant(new Obj(c._parser.Number)); // Compile the code to load the constant. c.EmitShortArg(Instruction.CONSTANT, constant); }
// Compiles a method signature for an unary operator (i.e. "!"). private static void UnarySignature(Compiler c, Signature signature) { // Do nothing. The name is already complete. signature.Type = SignatureType.Getter; }
private static void string_(Compiler c, bool allowAssignment) { int constant = c.StringConstant(); // Compile the code to load the constant. c.EmitShortArg(Instruction.CONSTANT, constant); }
// Compiles a method signature for a subscript operator. private static void SubscriptSignature(Compiler c, Signature signature) { signature.Type = SignatureType.Subscript; // The signature currently has "[" as its name since that was the token that // matched it. Clear that out. signature.Length = 0; signature.Name = ""; // Parse the parameters inside the subscript. c.FinishParameterList(signature); c.Consume(TokenType.RightBracket, "Expect ']' after parameters."); c.MaybeSetter(signature); }
private static void super_(Compiler c, bool allowAssignment) { ClassCompiler enclosingClass = c.GetEnclosingClass(); if (enclosingClass == null) { c.Error("Cannot use 'super' outside of a method."); } c.LoadThis(); // TODO: Super operator calls. // See if it's a named super call, or an unnamed one. if (c.Match(TokenType.Dot)) { // Compile the superclass call. c.Consume(TokenType.Name, "Expect method name after 'super.'."); c.NamedCall(allowAssignment, Instruction.SUPER_0); } else if (enclosingClass != null) { // No explicit name, so use the name of the enclosing method. Make sure we // check that enclosingClass isn't null first. We've already reported the // error, but we don't want to crash here. c.MethodCall(Instruction.SUPER_0, enclosingClass.Signature); } }
// Compiles a method signature for a constructor. private static void ConstructorSignature(Compiler c, Signature signature) { c.Consume(TokenType.Name, "Expect constructor name after 'construct'."); // Capture the name. signature = c.SignatureFromToken(signature, SignatureType.Initializer); if (c.Match(TokenType.Eq)) { c.Error("A constructor cannot be a setter."); } if (!c.Match(TokenType.LeftParen)) { c.Error("A constructor cannot be a getter."); return; } if (c.Match(TokenType.RightParen)) return; c.FinishParameterList(signature); c.Consume(TokenType.RightParen, "Expect ')' after parameters."); }
private static void this_(Compiler c, bool allowAssignment) { if (c.GetEnclosingClass() == null) { c.Error("Cannot use 'this' outside of a method."); return; } c.LoadThis(); }
private void CreateConstructor(Signature signature, int initializerSymbol) { Compiler methodCompiler = new Compiler(_parser, this, false); methodCompiler.Emit(_enclosingClass.IsForeign ? Instruction.FOREIGN_CONSTRUCT : Instruction.CONSTRUCT); methodCompiler.EmitShortArg(Instruction.CALL_0 + signature.Arity, initializerSymbol); methodCompiler.Emit(Instruction.RETURN); methodCompiler.EndCompiler(""); }
// Subscript or "array indexing" operator like `foo[bar]`. private static void Subscript(Compiler c, bool allowAssignment) { Signature signature = new Signature { Name = "", Length = 0, Type = SignatureType.Subscript, Arity = 0 }; // Parse the argument list. c.FinishArgumentList(signature); c.Consume(TokenType.RightBracket, "Expect ']' after arguments."); if (c.Match(TokenType.Eq)) { if (!allowAssignment) c.Error("Invalid assignment."); signature.Type = SignatureType.SubscriptSetter; // Compile the assigned value. c.ValidateNumParameters(++signature.Arity); c.Expression(); } c.CallSignature(Instruction.CALL_0, signature); }
// 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>(); _debugSourceLines = new int[16000]; }
// Compiles a method definition inside a class body. Returns the symbol in the // method table for the new method. private bool Method(ClassCompiler classCompiler, int classSlot, out bool hasConstructor) { hasConstructor = false; bool isForeign = Match(TokenType.TOKEN_FOREIGN); classCompiler.isStaticMethod = Match(TokenType.TOKEN_STATIC); SignatureFn signatureFn = GetRule(parser.current.type).method; NextToken(); if (signatureFn == null) { Error("Expect method definition."); return false; } // Build the method signature. Signature signature = SignatureFromToken(SignatureType.SIG_GETTER); classCompiler.signature = signature; Compiler methodCompiler = new Compiler(parser, this, false); signatureFn(methodCompiler, signature); if (classCompiler.isStaticMethod && signature.Type == SignatureType.SIG_INITIALIZER) { Error("A constructor cannot be static."); } String fullSignature = SignatureToString(signature); if (isForeign) { int constant = AddConstant(new Value(fullSignature)); EmitShortArg(Instruction.CONSTANT, constant); } else { Consume(TokenType.TOKEN_LEFT_BRACE, "Expect '{' to begin method body."); methodCompiler.FinishBody(signature.Type == SignatureType.SIG_INITIALIZER); methodCompiler.EndCompiler(fullSignature); } // Define the method. For a constructor, this defines the instance // initializer method. int methodSymbol = SignatureSymbol(signature); DefineMethod(classSlot, classCompiler.isStaticMethod, methodSymbol); if (signature.Type == SignatureType.SIG_INITIALIZER) { signature.Type = SignatureType.SIG_METHOD; int constructorSymbol = SignatureSymbol(signature); CreateConstructor(signature, methodSymbol); DefineMethod(classSlot, true, constructorSymbol); hasConstructor = true; } return true; }