8 Corresponds to multiple arguments

Introduction

Added Simple Function Definitions and Calls in a previous article. At that time, the function argument corresponded to only one. This time it supports no arguments or two or more arguments.

What you want to do with multiple arguments

Make sure you want to do it easily. For example, if you have a program like the one below, the ʻadd3 ()function is defined and we aim to output6` to the standard output.

v = 0
function add3(a1, a2, a3) {
  v = a1 + a2 + a3
}
add3(1,2,3)
println(v)

It's still the previous article that it doesn't correspond to the return value of a function or the scope of a variable.

How to implement

We will consider how to implement it in the order of lexical analysis (Lexer), parser (Parser), and interpreter (Interpreter).

How to implement in lexical analysis (Lexer)

Since there is no function to analyze commas and , in the lexical analysis of the implementation so far, we will implement it.

How to implement parsing

We'll make some changes to the implementation of parsing function definitions and function calls that we made in the previous article. Both only consider the case with one argument, so Modify the parsing implementation to take into account no arguments or multiple arguments.

How to implement the Interpreter

This is also a class that represents the function added in the previous article, Use it to make changes to the part that defines and calls the function. All of them only consider the case with one argument, so Change to take into account no arguments or multiple arguments.

Try to implement in Java

Move on to implementation. About lexical analysis (Lexer), parser (Parser), interpreter (Interpreter) Let's take a look at the changes and additions in order.

Lexer.java

An implementation of Lexer.java. First, add the function to analyze commas and ,.

Lexer.java


    private boolean isSymbolStart(char c) {
        return c == ',';
    }

    private Token symbol() throws Exception {
        Token t = new Token();
        t.kind = "symbol";
        t.value = Character.toString(next());
        return t;
    }

I will add the calling part of comma analysis. The implementation of Lexer.java is complete.

Lexer.java


    public Token nextToken() throws Exception {
        skipSpace();
        if (isEOT()) {
            return null;
        } else if (isSignStart(c())) {
            return sign();
        } else if (isDigitStart(c())) {
            return digit();
        } else if (isIdentStart(c())) {
            return ident();
        } else if (isParenStart(c())) {
            return paren();
        } else if (isCurlyStart(c())) {
            return curly();
        } else if (isSymbolStart(c())) {
            return symbol();
        } else {
            throw new Exception("Not a character for tokens");
        }
    }

Parser.java

Changed the method func () to parse the function definition. Before explaining the func () method change, because the Token class change is relevant I will explain how to change the Token class. The analysis result of the function definition is summarized in the argument token. In the previous article, I added a field variable called param, which represents a formal argument, to the Token class. This time, in order to support multiple arguments, the param field is abolished and a field variable called params of type List <Token> is added to the Token class. Next is a description of changing the func () method. The main changes are <-Update. The parsing there is as follows: (After the token, after

And analyze.

Parser.java


    private Token func(Token token) throws Exception {
        token.kind = "func";
        token.ident = ident();
        consume("(");
        token.params = new ArrayList<Token>();
        if (!token().value.equals(")")) {           // <-- Update
            token.params.add(ident());
            while (!token().value.equals(")")) {
                consume(",");
                token.params.add(ident());
            }
        }
        consume(")");
        consume("{");
        token.block = block();
        consume("}");
        return token;
    }

An implementation of parsing the part that makes the function call. The main change is <-Update, and you can see that the implementation method is almost the same as the parsing of the function definition earlier.

Parser.java


    private Token bind(Token left, Token operator) throws Exception {
        if (binaryKinds.contains(operator.kind)) {
            operator.left = left;
            int leftDegree = degree(operator);
            if (rightAssocs.contains(operator.value)) {
                leftDegree -= 1;
            }
            operator.right = expression(leftDegree);
            return operator;
        } else if (operator.kind.equals("paren") && operator.value.equals("(")) {
            operator.left = left;
            operator.params = new ArrayList<Token>();
            if (!token().value.equals(")")) {           // <-- Update
                operator.params.add(expression(0));
                while (!token().value.equals(")")) {
                    consume(",");
                    operator.params.add(expression(0));
                }
            }
            consume(")");
            return operator;
        } else {
            throw new Exception("The token cannot place there.");
        }
    }

Interpreter.java

An implementation of Interpreter.java.

A modification of the abstract class of the class that represents the function. ʻInvoke () method argument according to multiple arguments, Changed from ʻObject arg to List <Object> args.

Interpreter.java


    public static abstract class Func {
        public String name;

        abstract public Object invoke(List<Object> args) throws Exception;
    }

I changed the ʻinvoke ()method of thePrintln` class to match the change of the abstract class.

Interpreter.java


    public static class Println extends Func {
        public Println() {
            name = "println";
        }

        @Override
        public Object invoke(List<Object> args) throws Exception {
            Object arg = args.size() > 0 ? args.get(0) : null;
            System.out.println(arg);
            return null;
        }
    }

Similarly, I changed the ʻinvoke ()method of theDynamicFuncclass to match the change of the abstract class. To change. Also, the field variableparam has been abolished so that multiple arguments can be expressed, and paramsof typeList ` has been introduced.

Interpreter.java


    public static class DynamicFunc extends Func {

        public Interpreter context;
        public List<Token> params;
        public List<Token> block;

        @Override
        public Object invoke(List<Object> args) throws Exception {
            for (int i = 0; i < params.size(); ++i) {
                Token param = params.get(i);
                Variable v = context.variable(context.ident(param));
                if (i < args.size()) {
                    v.value = context.value(args.get(i));
                } else {
                    v.value = null;
                }
            }
            context.body(block);
            return null;
        }
    }

It is a change of the function definition part. The main changes are <-Add and <-Update. It is repeated in the for statement to process multiple arguments.

Interpreter.java


    public Object func(Token token) throws Exception {
        String name = token.ident.value;
        if (functions.containsKey(name)) {
            throw new Exception("Name was used");
        }
        if (variables.containsKey(name)) {
            throw new Exception("Name was used");
        }
        List<String> paramCheckList = new ArrayList<String>(); // <-- Add
        for (Token p : token.params) {
            String param = p.value;
            if (paramCheckList.contains(param)) {
                throw new Exception("Parameter name was used");
            }
            paramCheckList.add(param);
        }
        DynamicFunc func = new DynamicFunc();
        func.context = this;
        func.name = name;
        func.params = token.params; // <-- Update
        func.block = token.block;
        functions.put(name, func);
        return null;
    }

The program below using the above implementation

v = 0
function add3(a1, a2, a3) {
  v = a1 + a2 + a3
}
add3(1,2,3)
println(v)

To print the value 6 assigned to the variable v to the standard output.

Interpreter.java


    public static void main(String[] args) throws Exception {
        String text = "";
        text += "v = 0";
        text += "function add3(a1, a2, a3) {";
        text += "  v = a1 + a2 + a3";
        text += "}";
        text += "add3(1,2,3)";
        text += "println(v)";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> 6
    }

That's all for the implementation. Thank you very much.

in conclusion

The full source is available here.

Calc https://github.com/quwahara/Calc/tree/article-8-multiple-arguments-r2/Calc/src/main/java

There is a continuation article.

** Corresponds to the return value ** http://qiita.com/quwahara/items/1db9a5b880fd36dcfd3c

Recommended Posts

8 Corresponds to multiple arguments
Corresponds to 17 arrays
Corresponds to Scope
Corresponds to 15 strings
10 Corresponds to if statement
14 Corresponds to function expressions
5 Corresponds to prioritizing parentheses
19 Corresponds to object creation
16 Corresponds to method invocation
9 Corresponds to the return value
12 Corresponds to the while statement
18 Corresponds to JSON-like object definitions
20 Corresponds to static method invocation
11 Corresponds to comparison and logical operators
Operation to connect multiple Streams @Java
To display multiple lines with UILabel
[Swift] How to replace multiple strings
to_ ○
Note to have multiple values in HashMap
Summary of how to write annotation arguments
How to micro-benchmark while changing VM arguments
How to switch between multiple Java versions
How to execute multiple commands in docker-compose.yml