예제 #1
0
파일: Parser.cs 프로젝트: cyrsis/C-SandBox
        //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).
        }