14 Corresponds to function expressions

Introduction

In the previous article, Supports Scope. Now it corresponds to the function expression. I will try to make a closure by making use of the Scope that was supported in the previous article.

What you want to do with function expressions

Confirm what you want to do with function expression support. For example, there is the following program. The inner function expression returned by the outer function expression is assigned to the variable counter. Every time you call counter as a function The value of the variable c defined in the outer function expression is added. The first println (counter ()) prints 1, The next println (counter ()) will output 2.

counter = (function() {
  var c = 0
  return function() {
    c = c + 1
    return c
  }
})()
println(counter())
println(counter())

How to implement

We will consider how to implement it in the order of parser and interpreter.

How to implement parsing

Parsing a function expression is different from parsing a function definition with or without a function name. The difference is reflected in the parsing of the function definition.

How to implement the Interpreter

Introduces an object that represents a function expression. The object can be treated as a value, and it also supports function calls. Until now, only ʻInteger` was treated as a value, Function expressions must also be treated as values.

Try to implement in Java

Move on to implementation. About parser and interpreter Let's take a look at the changes and additions in order.

Parser.java

An implementation of Parser.java.

A modification of the func () method that parses the function definition. The changed part is the first if statement. The original implementation expected the function token to be followed by the ʻidenttoken, which is the function name. In the case of a function expression, thefunction token is followed by the (token. The if statement is judged after thefunction token is (if it is a `token, the function expression is parsed, If not, I changed it to parse the function definition.

Parser.java


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

Interpreter.java

An implementation of Interpreter.java.

A modification of the Variable class that represents a variable. Until now, the value has only dealt with ʻInteger. Since we are introducing a function expression, we must be able to treat the function expression as a value as well. To be able to handle the type of the field variable value, Changed from ʻInteger to ʻObject`.

Interpreter.java


    public static class Variable {
        public String name;
        public Object value;

        @Override
        public String toString() {
            return name + " " + value;
        }
    }

ʻExpression () method change. It is a process that branches according to the meaning (kind) of the token that represents the expression. Added a branch under // Add for the token fexpr`, which represents a function expression.

Interpreter.java


    public Object expression(Token expr) throws Exception {
        if (expr.kind.equals("digit")) {
            return digit(expr);
        } else if (expr.kind.equals("ident")) {
            return ident(expr);
        } else if (expr.kind.equals("func")) {
            return func(expr);
            // Add
        } else if (expr.kind.equals("fexpr")) {
            return fexpr(expr);
        } else if (expr.kind.equals("paren")) {
            return invoke(expr);
        } else if (expr.kind.equals("sign") && expr.value.equals("=")) {
            return assign(expr);
        } else if (expr.kind.equals("unary")) {
            return unaryCalc(expr);
        } else if (expr.kind.equals("sign")) {
            return calc(expr);
        } else {
            throw new Exception("Expression error");
        }
    }

Added the fexpr () method, which is called on the branch added by the ʻexpression method above. Creates an object that represents a function expression. To be able to keep the parent scope of a function expression, given that it is used in closures Clone and keep the current ʻInterpreter in func.context.

Interpreter.java


    public Object fexpr(Token token) throws Exception {
        List<String> paramCheckList = new ArrayList<String>();
        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 = new Interpreter();
        func.context.global = global;
        func.context.local = local;
        func.context.body = body;
        func.params = token.params;
        func.block = token.block;
        return func;
    }

A modification of the func () method that creates an instance that represents a function definition. Synchronized with the fexpr () method cloning the current ʻInterpreter, I also changed it to clone under // Update`.

Interpreter.java


    public Object func(Token token) throws Exception {
        String name = token.ident.value;
        if (local.functions.containsKey(name)) {
            throw new Exception("Name was used");
        }
        if (local.variables.containsKey(name)) {
            throw new Exception("Name was used");
        }
        List<String> paramCheckList = new ArrayList<String>();
        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();
        // Update
        func.context = new Interpreter();
        func.context.global = global;
        func.context.local = local;
        func.context.body = body;
        func.name = name;
        func.params = token.params;
        func.block = token.block;
        local.functions.put(name, func);
        return null;
    }

Changes to the value () method and addition of the ʻinteger ()` method.

The value () method returns the value if the argument is the value itself, such as 1. If it is a variable, it returns the value that the variable holds. Until now, we have only dealt with ʻIntegeras a value. The corresponding function expression is also the value returned by this method, so it corresponds. Change the return type of thevalue () method from ʻInteger to ʻObject. Now you can return both ʻInteger and function expressions with the value () method.

The reason for adding the ʻinteger () method is This is because the return value of the value () method has been changed from ʻInteger to ʻObject. Until now, the return value of the value () method was guaranteed as ʻInteger, The change from ʻInteger to ʻObject no longer guarantees it. Instead, the ʻinteger () method guarantees that the value is ʻInteger.

To influence these changes and ensure that the value is ʻInteger Thevalue () method was called, Even with the ʻunaryCalc () method and the calc () method I am changing to call the ʻinteger () `method instead.

Interpreter.java


    public Object value(Object value) throws Exception {
        if (value instanceof Integer) {
            return value;
        } else if (value instanceof Func) {
            return value;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return value(v.value);
        }
        throw new Exception("right value error");
    }

    public Integer integer(Object value) throws Exception {
        if (value instanceof Integer) {
            return (Integer) value;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return integer(v.value);
        }
        throw new Exception("right value error");
    }

This is a modification of the func () method. A method that guarantees that the argument is a function. If the argument is of type Variable to ʻelse if`, Added processing to ensure that the value is a function.

Interpreter.java


    public Func func(Object value) throws Exception {
        if (value instanceof Func) {
            return (Func) value;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return func(v.value);
        } else {
            throw new Exception("Not a function");
        }
    }

ʻIsTrue method change. The ʻisTrue method is the process of resolving a value to a boolean value. Until now, the value was only ʻInteger, but since functions are now treated as values, When the value was a function, I simply resolved it to true`.

Interpreter.java


    public boolean isTrue(Object value) throws Exception {
        if (value instanceof Integer) {
            return 0 != ((Integer) value);
        } else if (value instanceof Func) {
            return true;
        } else {
            return false;
        }
    }

The program below using the above implementation

counter = (function() {
  var c = 0
  return function() {
    c = c + 1
    return c
  }
})()
println(counter())
println(counter())

And The first println (counter ()) prints 1, The next println (counter ()) will output 2.

Interpreter.java


    public static void main(String[] args) throws Exception {
        String text = "";
        text += "counter = (function() {";
        text += "  var c = 0";
        text += "  return function() {";
        text += "    c = c + 1";
        text += "    return c";
        text += "  }";
        text += "})()";
        text += "println(counter())";
        text += "println(counter())";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> 1
        // --> 2
    }

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-14-function-expression-r3/Calc/src/main/java

There is a continuation article.

** Corresponds to a string ** http://qiita.com/quwahara/items/ddcbc8c37b9d442fe0f2

Recommended Posts

14 Corresponds to function expressions
Corresponds to 17 arrays
Corresponds to Scope
Corresponds to 15 strings
8 Corresponds to multiple arguments
10 Corresponds to if statement
5 Corresponds to prioritizing parentheses
19 Corresponds to object creation
16 Corresponds to method invocation
9 Corresponds to the return value
Java to play with Function
How to add ActionText function
12 Corresponds to the while statement
[Java] Introduction to lambda expressions
11 Corresponds to comparison and logical operators
[Introduction to Java] About lambda expressions
Function is very easy to use
Try to implement iOS14 Widget function
How to add the delete function
Things to note in conditional expressions
How to use Java lambda expressions
to_ ○
[Java] How to use the hasNext function
I tried to summarize Java lambda expressions
Try to implement login function with Spring-Boot
[Swift] How to implement the countdown function
How to implement TextInputLayout with validation function
[Processing × Java] How to use the function
Easy to trip with Java regular expressions