//sub-call ::= <sub-name> '(' <expr-list> ')' | // (<class-name> | <var-name>) '.' <sub-name> '(' <expr-list> ')' //expr-list ::= (<expression> (',' <expression>)*)? private void ParseSubCall(Token firstPart = null) { //NOTE: Because the parser may inadvertently swallow the first part //of the subroutine call expression (i.e. the subroutine name, //class name, or variable name), there's an option for this method //to accept it explicitly instead of reading it from the tokenizer. // //After this method completes, the code generator should have //available to it the result of the subroutine's invocation (e.g. //on the stack or in a predefined register). If the subroutine //call is not part of an expression (i.e. it's part of a 'do' //statement), then this result should be discarded via an explicit //call to the code generator. if (firstPart == null) { firstPart = NextToken(); } if (firstPart.Type != TokenType.Ident) { ThrowCompilationException("Expected an identifier, got: " + firstPart.Value); } string classNameOfSubroutine = null; string subroutineName = null; bool isInstanceMethod = false; Symbol classOrVarName = GetClosestSymbol(firstPart.Value); if (classOrVarName == null) { if (LookAheadToken.Value == "(") { //If this is the case, this is a direct method call within //the current class. The current subroutine has to be //non-static (i.e. a method or a constructor) for us to //dispatch a method call. Because we're a one-pass compiler, //we can't verify that the method actually exists. if (_currentSub.Kind != SubroutineKind.Constructor && _currentSub.Kind != SubroutineKind.Method) { ThrowCompilationException("Method calls are allowed only within a method or a constructor"); } classNameOfSubroutine = _currentClassName; subroutineName = firstPart.Value; isInstanceMethod = true; } else if (LookAheadToken.Value == ".") { //This is a function (or constructor) call on a class name. //We don't know about all class names, so we have to assume //that this class exists. The assembler will tell us if //we're wrong. Alternatively, we could implement a multi- //pass compiler that goes over only declarations, or require //all subroutines and classes to be declared before they //can be called. //NOTE: If we want to do inlining as an optimization, we //must perform two passes (or else we don't know what's in //the subroutine we're placing inline :-)). Match(new Token(TokenType.Symbol, ".")); Token subName = NextToken(); if (subName.Type != TokenType.Ident) { ThrowCompilationException("Expected an identifier, got: " + subName.Value); } classNameOfSubroutine = firstPart.Value; subroutineName = subName.Value; isInstanceMethod = false; } else { ThrowCompilationException("Unexpected: " + LookAheadToken.Value); } } else { //This is a method call on a variable. We must be able to tell //the class name by looking at the variable's type. Note that //built-in types that we recognize as keywords don't have any //methods, so this is an error we can flag easily. classNameOfSubroutine = classOrVarName.Type; if (Syntax.IsKeyword(classNameOfSubroutine)) { ThrowCompilationException("Can't call methods on built-in types"); } Match(new Token(TokenType.Symbol, ".")); Token subName = NextToken(); if (subName.Type != TokenType.Ident) { ThrowCompilationException("Expected an identifier, got: " + subName.Value); } subroutineName = subName.Value; isInstanceMethod = true; } Match(new Token(TokenType.Symbol, "(")); //If this is an instance method, we need to supply 'this' as the //first parameter to be extracted on the other side. if (isInstanceMethod) { if (classNameOfSubroutine == _currentClassName) { //'this' is actually our very own instance, as we're calling //an instance method on ourselves. _codeGenerator.This(); } else { //'this' is actually the variable on which we're making //the call, so we need that pushed. _codeGenerator.VariableRead(firstPart, false); } } ParseExpressionList(); Match(new Token(TokenType.Symbol, ")")); _codeGenerator.Call(classNameOfSubroutine, subroutineName); //TODO: Detect the situation where a void subroutine is called //inside an expression where a value has to be provided. This is //impossible without having the subroutine's declaration in front //of us. To alleviate this, we can have all subroutines, even void //ones, return some arbitrary value (e.g. 0). }