private static parseFunctionCall()

in src/language/expressions/TLE.ts [1086:1216]


    private static parseFunctionCall(tokenizer: Tokenizer, errors: Issue[]): FunctionCallValue {
        assert(tokenizer);
        assert(tokenizer.current, "tokenizer must have a current token.");
        // tslint:disable-next-line:no-non-null-assertion // Asserted
        assert.deepEqual(TokenType.Literal, tokenizer.current!.getType(), "tokenizer's current token must be a literal.");
        assert(errors);

        let namespaceToken: Token | undefined;
        let nameToken: Token | undefined;
        let periodToken: Token | undefined;

        // tslint:disable-next-line:no-non-null-assertion // Asserted
        let firstToken: Token = tokenizer.current!;
        tokenizer.next();

        // Check for <namespace>.<functionname>
        // tslint:disable-next-line: strict-boolean-expressions
        if (tokenizer.current && tokenizer.current.getType() === TokenType.Period) {
            // It's a user-defined function because it has a namespace before the function name
            periodToken = tokenizer.current;
            namespaceToken = firstToken;
            tokenizer.next();

            // Get the function name following the period
            if (tokenizer.hasCurrent() && tokenizer.current.getType() === TokenType.Literal) {
                nameToken = tokenizer.current;
                tokenizer.next();
            } else {
                errors.push(new Issue(periodToken.span, "Expected user-defined function name.", IssueKind.tleSyntax));
            }
        } else {
            nameToken = firstToken;
        }

        let leftParenthesisToken: Token | undefined;
        let rightParenthesisToken: Token | undefined;
        let commaTokens: Token[] = [];
        let argumentExpressions: (Value | undefined)[] = [];

        // tslint:disable-next-line: strict-boolean-expressions
        if (tokenizer.current) {
            // tslint:disable-next-line: strict-boolean-expressions // False positive
            while (tokenizer.current) {
                if (tokenizer.current.getType() === TokenType.LeftParenthesis) {
                    leftParenthesisToken = tokenizer.current;
                    tokenizer.next();
                    break;
                } else if (tokenizer.current.getType() === TokenType.RightSquareBracket) {
                    // tslint:disable-next-line: strict-boolean-expressions
                    errors.push(new Issue(getFullNameSpan(), "Missing function argument list.", IssueKind.tleSyntax));
                    break;
                } else {
                    errors.push(new Issue(tokenizer.current.span, "Expected the end of the string.", IssueKind.tleSyntax));
                    tokenizer.next();
                }
            }
        } else {
            errors.push(new Issue(getFullNameSpan(), "Missing function argument list.", IssueKind.tleSyntax));
        }

        if (tokenizer.hasCurrent()) {
            let expectingArgument: boolean = true;

            // tslint:disable-next-line: strict-boolean-expressions
            while (tokenizer.current) {
                if (tokenizer.current.getType() === TokenType.RightParenthesis || tokenizer.current.getType() === TokenType.RightSquareBracket) {
                    break;
                } else if (expectingArgument) {
                    let expression = Parser.parseExpression(tokenizer, errors);
                    if (!expression && tokenizer.hasCurrent() && tokenizer.current.getType() === TokenType.Comma) {
                        errors.push(new Issue(tokenizer.current.span, "Expected a constant string, function, or property expression.", IssueKind.tleSyntax));
                    }
                    argumentExpressions.push(expression);
                    expectingArgument = false;
                } else if (tokenizer.current.getType() === TokenType.Comma) {
                    expectingArgument = true;
                    commaTokens.push(tokenizer.current);
                    tokenizer.next();
                } else {
                    errors.push(new Issue(tokenizer.current.span, "Expected a comma (',').", IssueKind.tleSyntax));
                    tokenizer.next();
                }
            }

            if (Parser.isMissingArgument(expectingArgument, leftParenthesisToken, argumentExpressions.length, tokenizer)) {
                argumentExpressions.push(undefined);
                let errorSpan: Span;
                // tslint:disable-next-line: strict-boolean-expressions
                if (tokenizer.current) {
                    errorSpan = tokenizer.current.span;
                } else {
                    assert(0 < commaTokens.length);

                    errorSpan = commaTokens[commaTokens.length - 1].span;
                }
                errors.push(new Issue(errorSpan, "Expected a constant string, function, or property expression.", IssueKind.tleSyntax));
            }
        } else if (!!leftParenthesisToken) {
            errors.push(new Issue(leftParenthesisToken.span, "Expected a right parenthesis (')').", IssueKind.tleSyntax));
        }

        // tslint:disable-next-line: strict-boolean-expressions
        if (tokenizer.current) {
            switch (tokenizer.current.getType()) {
                case TokenType.RightParenthesis:
                    rightParenthesisToken = tokenizer.current;
                    tokenizer.next();
                    break;

                case TokenType.RightSquareBracket:
                    if (!!leftParenthesisToken) {
                        errors.push(new Issue(tokenizer.current.span, "Expected a right parenthesis (')').", IssueKind.tleSyntax));
                    }
                    break;
            }
        }

        assert(namespaceToken || nameToken, "Should have had a namespace or a name");
        return new FunctionCallValue(namespaceToken, periodToken, nameToken, leftParenthesisToken, commaTokens, argumentExpressions, rightParenthesisToken);

        function getFullNameSpan(): Span {
            if (!nameToken) {
                assert(namespaceToken);
                // tslint:disable-next-line: no-non-null-assertion
                return namespaceToken!.span;
            } else {
                // tslint:disable-next-line: strict-boolean-expressions
                return nameToken.span.union(namespaceToken && namespaceToken.span);
            }
        }
    }