16 Corresponds to method invocation

Introduction

I would like to support an array of basic data structures. This article is Supports strings. When it corresponds to a character string, I want to be able to handle a substring of the character string. Here, I would like to be able to call a method so that a substring can be extracted from the string.

What you want to do with method call support

Confirm what you want to do with method call support. For example, there is the following program. After assigning a string to the variable hw, we aim to be able to call the instance methodsubstring ()of the variable hw. The println () method call at the end of the program aims to output a substring of the variable hw, Hello.

var hw = "Hello world!"
var h = hw.substring(0, 5)
println(h)

What you don't do is not support static method calls.

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 lexical analysis (Lexer)

Since there is no function to parse ., add it.

How to implement parsing

The . token will be added by adding the function of Lexer. Add the ability to parse it. The . token can be parsed like a binary operator, so change it that way.

How to implement the Interpreter

We will implement the instance method call so that it can be treated as if it were an already implemented function call. In a function call, the token to the left of the token that triggers the function call is There was only one token for the function or variable name. The method call (the token to the left of thetoken is The.token and its left and right tokens are also involved in the call. Therefore, the.` token and the tokens on the left and right of it can be treated as one piece of information. Introduce a class that puts them together. We will make it possible to handle the collected classes in the same way as function calls.

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.

Add a lexical analysis function for ..

Add the ʻisDotStart ()method. Detects.`.

Lexer.java


    private boolean isDotStart(char c) {
        return c == '.';
    }

Add the dot () method. Parse . into a token. The kind that represents . should be dot.

Lexer.java


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

Modify the nextToken () method. Add the call to the added method to // Add. Now you can decompose . into tokens.

Lexer.java


    public Token nextToken() throws Exception {
        skipSpace();
        if (isEOT()) {
            return null;
        } else if (isSignStart(c())) {
            return sign();
            // Add
        } else if (isDotStart(c())) {
            return dot();
        } else if (isDigitStart(c())) {
            return digit();
        } else if (isStringStart(c())) {
            return string();
        } 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");
        }
    }

That's all for changing Lexer.java.

Parser.java

An implementation of Parser.java.

Add a definition of how it works for the meaning of the token.

Added the degree of ordering of . to // Update 1. . Is better than arithmetic operators like+and* Because it strongly connects the left and right tokens, It is 80, which is larger than the degree of + and *.

Added " dot " to // Update 2. The syntax of . is equivalent to the binary operator, Since parsing can be done in the same way as binary operators, So that it is subject to parsing of binary operators Added " dot " to binary Kinds.

Parser.java


    public Parser() {
        degrees = new HashMap<>();
        // Update 1
        degrees.put(".", 80);
        degrees.put("(", 80);
        degrees.put("*", 60);
        degrees.put("/", 60);
        degrees.put("+", 50);
        degrees.put("-", 50);
        degrees.put("==", 40);
        degrees.put("!=", 40);
        degrees.put("<", 40);
        degrees.put("<=", 40);
        degrees.put(">", 40);
        degrees.put(">=", 40);
        degrees.put("&&", 30);
        degrees.put("||", 30);
        degrees.put("=", 10);
        factorKinds = Arrays.asList(new String[] { "digit", "ident", "string" });
        // Update 2
        binaryKinds = Arrays.asList(new String[] { "sign", "dot" });
        rightAssocs = Arrays.asList(new String[] { "=" });
        unaryOperators = Arrays.asList(new String[] { "+", "-", "!" });
        reserved = Arrays.asList(new String[] { "function", "return", "if", "else", "while", "break", "var" });
    }

The parsing process itself does not make any changes or additions. This is because it is subject to parsing of existing binary operators in addition to the definition. That's all for changing Parser.java.

Interpreter.java

An implementation of Interpreter.java.

ʻ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 dotto represent.`.

Interpreter.java


    public Object expression(Token expr) throws Exception {
        if (expr.kind.equals("digit")) {
            return digit(expr);
        } else if (expr.kind.equals("string")) {
            return string(expr);
        } else if (expr.kind.equals("ident")) {
            return ident(expr);
        } else if (expr.kind.equals("func")) {
            return func(expr);
        } 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);
            // Add
        } else if (expr.kind.equals("dot")) {
            return dot(expr);
        } else {
            throw new Exception("Expression error");
        }
    }

Added the dot () method. Called on the branch added above. The dot () method is a class that pairs the left and right sides of . Returns an instance of Dotted. The left side of . assigns the return value of thevalue ()method, which guarantees that it is a value. This is to ensure that the left side of . is always an instance of the method call.

Interpreter.java


    public Object dot(Token token) throws Exception {
        Dotted d = new Dotted();
        d.left = value(expression(token.left));
        d.right = token.right;
        return d;
    }

    public static class Dotted {
        public Object left;
        public Token right;
    }

This is a modification of the func () method. The func () method guarantees that the argument value is callable like a function. Added the processing when the Dotted added above is passed by the argument value under // Add. If the argument is Dotted, it returns an instance of the MethodFunc class. The MethodFunc class inherits from the callable abstract classFunc and The information required for method call is stored in the field variable.

Interpreter.java


    public Func func(Object value) throws Exception {
        if (value instanceof Func) {
            return (Func) value;
            // Add
        } else if (value instanceof Dotted) {
            Dotted d = (Dotted) value;
            MethodFunc mf = new MethodFunc();
            mf.name = d.right.value;
            mf.class_ = d.left.getClass();
            mf.target = d.left;
            return mf;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return func(v.value);
        } else {
            throw new Exception("Not a function");
        }
    }

Added the MethodFunc class. It is returned by the func () method. Make an instance method call with the ʻinvoke` method of this class.

To make an instance method call, you need to get the information that represents the method. Method information can be obtained from the information that represents the class. The class_ field variable is the information for that class.

You can get the method information from the class information, Of the multiple methods defined in the class You need to select one piece of information about the method you want to call.

In the ʻinvoke method, in order to select the information of the method you want to call, Use the method name and the type information of the argument of the ʻinvoke method to narrow down.

If the corresponding method information cannot be found or narrowed down to one, an error occurs.

The method of narrowing down is very simple and is different from the implementation in the Java compiler. I made a comment with a little detailed explanation.

Interpreter.java


    public static class MethodFunc extends Func {

        //Represents the type of method call target
        public Class<?> class_;
        //Represents the instance for which the method is called
        public Object target;

        @Override
        public Object invoke(List<Object> args) throws Exception {
            
            //Make a list of argument types from arguments
            List<Class<?>> aClasses = argClasses(args);
            
            //A list of method information that the type to call the method has
            //List only those with the same name as this MethodFunc
            List<Method> mByName = methodByName(class_.getMethods(), name);
            
            //A list of method information narrowed down by name,
            //The list of argument types is limited to those with assignable signatures.
            List<Method> mByAssignable = methodByAssignable(mByName, aClasses);
            
            //As a result of narrowing down, an error occurs if there is no corresponding method information
            if (mByAssignable.size() == 0) {
                throw new Exception("MethodFunc.invoke error");
            }
            
            Method method;
            if (mByAssignable.size() == 1) {
                
                //As a result of narrowing down, if there is only one applicable method information,
                //That is the method information to be called
                method = mByAssignable.get(0);
            
            } else {
                
                //As a result of narrowing down, if there are two or more applicable method information, further narrow down.
                //A list of method information narrowed down by assignable signatures,
                //Narrow down the list of argument types to those with exact matching signatures.
                List<Method> mByAbsolute = methodByAbsolute(mByAssignable, aClasses);
                
                //As a result of narrowing down, an error will occur if the corresponding method information does not become one.
                if (mByAbsolute.size() != 1) {
                    throw new Exception("MethodFunc.invoke error");
                }
                
                //As a result of narrowing down, if there is only one applicable method information,
                //That is the method information to be called
                method = mByAbsolute.get(0);
                
            }
            
            //Make a method call using only one method information
            Object val = method.invoke(target, args.toArray());
            return val;
        }

        public List<Class<?>> argClasses(List<Object> args) {
            List<Class<?>> classes = new ArrayList<Class<?>>();
            int psize = args.size();
            for (int i = 0; i < psize; ++i) {
                Object a = args.get(i);
                if (a != null) {
                    classes.add(a.getClass());
                } else {
                    classes.add(null);
                }
            }
            return classes;
        }

        public List<Method> methodByName(Method[] methods, String name) {
            List<Method> ms = new ArrayList<Method>();
            for (Method m : methods) {
                if (m.getName().equals(name)) {
                    ms.add(m);
                }
            }
            return ms;
        }

        public List<Method> methodByAssignable(List<Method> methods, List<Class<?>> aClasses) {
            List<Method> candidates = new ArrayList<Method>();

            int aSize = aClasses.size();
            for (Method m : methods) {
                Class<?>[] pTypes = m.getParameterTypes();

                if (pTypes.length != aSize) {
                    continue;
                }

                Boolean allAssignable = true;
                for (int i = 0; i < aSize; ++i) {
                    Class<?> c = pTypes[i];
                    Class<?> cc = toBoxClass(c);
                    Class<?> ac = aClasses.get(i);
                    if (ac != null) {
                        Class<?> acc = toBoxClass(ac);
                        allAssignable &= cc.isAssignableFrom(acc);
                    }
                    if (!allAssignable) {
                        break;
                    }
                }
                if (allAssignable) {
                    candidates.add(m);
                }
            }
            return candidates;
        }

        public List<Method> methodByAbsolute(List<Method> candidates, List<Class<?>> aClasses) {
            List<Method> screened = new ArrayList<Method>();
            int aSize = aClasses.size();
            for (int i = 0; i < aSize; ++i) {
                Class<?> ac = aClasses.get(i);
                if (ac == null) {
                    return screened;
                }
            }
            for (Method m : candidates) {
                Class<?>[] pTypes = m.getParameterTypes();
                Boolean allEquals = true;
                for (int i = 0; i < aSize; ++i) {
                    Class<?> c = pTypes[i];
                    Class<?> ac = aClasses.get(i);
                    allEquals &= c == ac;
                    if (!allEquals) {
                        break;
                    }
                }
                if (allEquals) {
                    screened.add(m);
                }
            }
            return screened;
        }
    }

    public static Class<?> toBoxClass(Class<?> c) {
        Class<?> bc;
        if (c == boolean.class) {
            bc = Boolean.class;
        } else if (c == char.class) {
            bc = Character.class;
        } else if (c == byte.class) {
            bc = Byte.class;
        } else if (c == short.class) {
            bc = Short.class;
        } else if (c == int.class) {
            bc = Integer.class;
        } else if (c == long.class) {
            bc = Long.class;
        } else if (c == float.class) {
            bc = Float.class;
        } else if (c == double.class) {
            bc = Double.class;
        } else {
            bc = c;
        }
        return bc;
    }

The program below using the above implementation

var hw = "Hello world!"
var h = hw.substring(0, 5)
println(h)

And Print Hello with theprintln ()method call at the end of the program.

Interpreter.java


    public static void main(String[] args) throws Exception {
        String text = "";
        text += "var hw = \"Hello world!\"";
        text += "var h = hw.substring(0, 5)";
        text += "println(h)";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> Hello
    }

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-16-method-call/Calc/src/main/java

There is a continuation article.

** Corresponds to the array ** http://qiita.com/quwahara/items/b4f821a797a146c8d873

Recommended Posts

16 Corresponds to method invocation
20 Corresponds to static method invocation
Method to search
Corresponds to 17 arrays
Corresponds to Scope
8 Corresponds to multiple arguments
10 Corresponds to if statement
14 Corresponds to function expressions
5 Corresponds to prioritizing parentheses
19 Corresponds to object creation
9 Corresponds to the return value
12 Corresponds to the while statement
18 Corresponds to JSON-like object definitions
Easy to use array.map (&: method)
How to use the link_to method
Item 43: Prefer method references to lambdas
Method
How to use the include? method
11 Corresponds to comparison and logical operators
[Ruby] Method to count specific characters
[Java] How to use join method
Introduction to algorithms with java-Shakutori method
Resilience4j TimeLimiter times out method invocation
Introduction to Design Patterns (Factory Method)
[Ruby] How to use any? Method
Try to extract java public method
How to use Ruby inject method
to_ ○
[Java] How to compare with equals method
How to use submit method (Java Silver)
[Ruby] From the basics to the inject method
[Rails] How to use the map method
[Java] How to use the toString () method
How to have params in link_to method
[SwiftUI] Implementation method equivalent to changing UIWindow.rootViewController
What to do when undefined method ʻuser_signed_in?'