19 Corresponds to object creation

Introduction

I want to load a Java class and call a constructor to handle object creation. This article is a continuation of "Corresponding to JSON-like object definition".

What you want to do with object creation support

Confirm what you want to do with object creation support. For example, there is the following program. Introduce a new loadClass function as the default global function. The loadClass function is a string as an argument, and if you pass a Java fully qualified class name, it will load that class. (The first line) The loaded one is assigned to the variable. If you add new to that variable and call it like a function, it will instantiate. (2nd line)

var dateClass = loadClass("java.util.Date")
var date = new dateClass()
println(date.toString())

How to implement

We will consider how to implement it in the order of parser and interpreter. There are no changes to lexical analysis (Lexer).

How to implement parsing

The newly added syntax is new, so let's think about how to parse new. new comes first syntactically, so just like any other token-determined syntax, Implement the process to parse the syntax of new to thelead ()method. Since the syntax element that follows new has the same syntax as the function call, The process of parsing the syntax of new verifies that the next element is a function call.

How to implement the Interpreter

The interpreter implements the newly introduced loadClass function and the execution of the new syntax.

Implement the loadClass function in the same way as the existing default global function println function.

The implementation of new syntax execution is the syntax that comes after new Treat it like a function call and make it instantiate.

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.

Implements parsing of the new syntax. Of the lead () method that implements the syntax determined by the first token Added a call to the new_ () method that parses new to // Add.

Parser.java


    private Token lead(Token token) throws Exception {
        if (token.kind.equals("ident") && token.value.equals("function")) {
            return func(token);
        } else if (token.kind.equals("ident") && token.value.equals("return")) {
            token.kind = "ret";
            if (!token().kind.equals("eob")) {
                token.left = expression(0);
            }
            return token;
        } else if (token.kind.equals("ident") && token.value.equals("if")) {
            return if_(token);
        } else if (token.kind.equals("ident") && token.value.equals("while")) {
            return while_(token);
        } else if (token.kind.equals("ident") && token.value.equals("break")) {
            token.kind = "brk";
            return token;
        } else if (token.kind.equals("ident") && token.value.equals("var")) {
            return var(token);
            // Add
        } else if (token.kind.equals("ident") && token.value.equals("new")) {
            return new_(token);
        } else if (factorKinds.contains(token.kind)) {
            return token;
        } else if (unaryOperators.contains(token.value)) {
            token.kind = "unary";
            token.left = expression(70);
            return token;
        } else if (token.kind.equals("paren") && token.value.equals("(")) {
            Token expr = expression(0);
            consume(")");
            return expr;
        } else if (token.kind.equals("bracket") && token.value.equals("[")) {
            return newArray(token);
        } else if (token.kind.equals("curly") && token.value.equals("{")) {
            return newMap(token);
        } else {
            throw new Exception("The token cannot place there.");
        }
    }

Added new_ () method to perform new parsing. The token argument is passed a new token. The new token type, kind, which is the key to interpreter processing, is set to new. The left of the new token assigns the return value of the ʻexpression` method. Make sure that the return value is a parsing of the function call.

Parser.java


    private Token new_(Token token) throws Exception {
        token.kind = "new";
        token.left = expression(0);
        if (!(token.left.value.equals("(") && token.left.kind.equals("paren"))) {
            throw new Exception("No constructor invocation.");
        }
        return token;
    }

That's all for changing Parser.java.

Interpreter.java

An implementation of Interpreter.java.

Added a class that is the substance of the loadClass function. The argument of the ʻinvoke method is the argument of the loadClassfunction call. Assuming that the first argument is a string of fully qualified class names Load the class using theClass.forName` method.

Interpreter.java


    public static class LoadClass extends Func {
        public LoadClass() {
            name = "loadClass";
        }

        @Override
        public Object invoke(List<Object> args) throws Exception {
            return Class.forName((String) args.get(0));
        }
    }

ʻInit () method change. A method that initializes the interpreter. Instantiate the LoadClass class added to // Addand So that it can be called as the default global function Add tofunctions` in global scope.

Interpreter.java


    public Interpreter init(List<Token> body) {
        global = new Scope();
        local = global;
        // Add
        Func loadClass = new LoadClass();
        global.functions.put(loadClass.name, loadClass);
        Func f = new Println();
        global.functions.put(f.name, f);
        this.body = body;
        return this;
    }

ʻExpression () method change. It is a process that branches according to the meaning (kind) of the token that represents the expression. Added new_ ()method call for object creation under// Add`.

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("blank")) {
            return blank(expr);
            // Add
        } else if (expr.kind.equals("new")) {
            return new_(expr);
        } else if (expr.kind.equals("newMap")) {
            return newMap(expr);
        } else if (expr.kind.equals("newArray")) {
            return newArray(expr);
        } else if (expr.kind.equals("bracket")) {
            return accessArrayOrMap(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);
        } else if (expr.kind.equals("dot")) {
            return dot(expr);
        } else {
            throw new Exception("Expression error");
        }
    }

Added new_ () method for object creation. This method calls the constructor and returns the created object.

The new token is passed to thenew_ ()method argument ʻexpr. The left of the newtoken is assigned a token that has the function call syntax. Theleft of the newtoken, which is the function call syntax, and theleftare the part corresponding to the function name. Theparams of the leftof thenew` token is the argument to the function call syntax.

The part corresponding to the function name (ʻexpr.left.left`) is a class in the constructor call, so At the beginning of the process, that part is resolved into a class.

To make a constructor call, you need to get information about the constructor. Constructor information can be obtained from the information that represents the class. The c variable resolved to a class is the information for that class.

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

In the new_ method, in order to select one constructor information that you want to call Narrow down using the type information of the argument of the constructor call.

If the corresponding constructor 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 Object new_(Token expr) throws Exception {
        
        // "("Resolve the left side of to a class and get a list of constructors for that class
        Class<?> c = class_(expression(expr.left.left));
        Constructor<?>[] ctors = c.getConstructors();
        
        // "("Make the constructor argument on the right side of the list resolved to a value
        List<Object> args = new ArrayList<Object>();
        for (Token arg : expr.left.params) {
            args.add(value(expression(arg)));
        }

        //Make a list of argument types from arguments
        List<Class<?>> aClasses = argClasses(args);

        //The list of argument types is limited to constructors whose signatures are assignable.
        List<Constructor<?>> byAssignables = ctorsByAssignable(ctors, aClasses);

        //As a result of narrowing down, if there is no corresponding constructor, an error will occur
        if (byAssignables.size() == 0) {
            throw new Exception("No constructor error");
        }

        Constructor<?> ctor;
        if (byAssignables.size() == 1) {

            //As a result of narrowing down, if there is only one applicable constructor,
            //That is the constructor to call
            ctor = byAssignables.get(0);

        } else {

            //As a result of narrowing down, if there are two or more applicable constructors, narrow down further.
            //A list of constructors narrowed down by assignable signatures,
            //Narrow down to only constructors that have a signature that exactly matches the list of argument types.
            List<Constructor<?>> byAbsolutes = ctorsByAbsolute(byAssignables, aClasses);

            //As a result of narrowing down, if the corresponding constructor does not become one, an error occurs
            if (byAbsolutes.size() != 1) {
                throw new Exception("No constructor error");
            }

            //As a result of narrowing down, if there is only one applicable constructor,
            //That is the constructor to call
            ctor = byAbsolutes.get(0);

        }

        //Make a constructor call using a single constructor
        Object val = ctor.newInstance(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 static List<Constructor<?>> ctorsByAssignable(Constructor<?>[] ctors, List<Class<?>> aClasses) {
        List<Constructor<?>> candidates = new ArrayList<Constructor<?>>();

        int aSize = aClasses.size();
        for (Constructor<?> ctor : ctors) {
            Class<?>[] pTypes = ctor.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(ctor);
            }
        }
        return candidates;
    }

    public static List<Constructor<?>> ctorsByAbsolute(List<Constructor<?>> candidates, List<Class<?>> aClasses) {
        List<Constructor<?>> screened = new ArrayList<Constructor<?>>();
        int aSize = aClasses.size();
        for (int i = 0; i < aSize; ++i) {
            Class<?> ac = aClasses.get(i);
            if (ac == null) {
                return screened;
            }
        }
        for (Constructor<?> ctor : candidates) {
            Class<?>[] pTypes = ctor.getParameterTypes();
            if (aSize != pTypes.length) {
                continue;
            }
            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(ctor);
            }
        }
        return screened;
    }

Added a class_ () method that guarantees that the value of the argument is a class. Ueno Used at the beginning of the new () method.

Interpreter.java


    public Class<?> class_(Object value) throws Exception {
        if (value instanceof Class<?>) {
            return (Class<?>) value;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return class_(v.value);
        }
        throw new Exception("right value error");
    }

Changed the value () method of the method that guarantees that the argument is a value. Until now, the implementation policy has been to check whether the type of the argument value is valid as a value. Now that you can create objects, you can no longer examine all the value types to determine if they are valid. Therefore, I added void_ to the static field variable, which indicates that it is inappropriate as a value. If the argument value is void_, it is considered inappropriate as a value.

Interpreter.java



    Scope global;
    Scope local;
    List<Token> body;
    // Add
    public static Object void_ = new Object();

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

Corresponding to the addition of void_, a value that indicates inappropriate as a value, Changed to return void_ instead of returning null for those that did not return a value until now. Changed to return void_ where there is // Update in each method below.

Interpreter.java


    public Object body(List<Token> body, boolean[] ret, boolean[] brk) throws Exception {
        for (Token exprs : body) {
            if (exprs.kind.equals("if")) {
                Object val = if_(exprs, ret, brk);
                if (ret != null && ret[0]) {
                    return val;
                }
            } else if (exprs.kind.equals("ret")) {
                if (ret == null) {
                    throw new Exception("Can not return");
                }
                ret[0] = true;
                if (exprs.left == null) {
                    // Update
                    return void_;
                } else {
                    return expression(exprs.left);
                }
            } else if (exprs.kind.equals("while")) {
                Object val = while_(exprs, ret);
                if (ret != null && ret[0]) {
                    return val;
                }
            } else if (exprs.kind.equals("brk")) {
                if (brk == null) {
                    throw new Exception("Can not break");
                }
                brk[0] = true;
                // Update
                return void_;
            } else if (exprs.kind.equals("var")) {
                var(exprs);
            } else {
                expression(exprs);
            }
        }
        // Update
        return void_;
    }

    public Object ret(Token token) throws Exception {
        if (token.left == null) {
            // Update
            return void_;
        }
        return expression(token.left);
    }

    public Object if_(Token token, boolean[] ret, boolean[] brk) throws Exception {
        List<Token> block;
        if (isTrue(token.left)) {
            block = token.block;
        } else {
            block = token.blockOfElse;
        }
        if (block != null) {
            return body(block, ret, brk);
        } else {
            // Update
            return void_;
        }
    }

    public Object while_(Token token, boolean[] ret) throws Exception {
        boolean[] brk = new boolean[1];
        Object val;
        while (isTrue(token.left)) {
            val = body(token.block, ret, brk);
            if (ret != null && ret[0]) {
                return val;
            }
            if (brk[0]) {
                // Update
                return void_;
            }
        }
        // Update
        return void_;
    }

    public Object var(Token token) throws Exception {
        for (Token item : token.block) {
            String name;
            Token expr;
            if (item.kind.equals("ident")) {
                name = item.value;
                expr = null;
            } else if (item.kind.equals("sign") && item.value.equals("=")) {
                name = item.left.value;
                expr = item;
            } else {
                throw new Exception("var error");
            }
            if (!local.variables.containsKey(name)) {
                newVariable(name);
            }
            if (expr != null) {
                expression(expr);
            }
        }
        // Update
        return void_;
    }

    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);
            // Update
            return void_;
        }
    }

The program below using the above implementation

var dateClass = loadClass("java.util.Date")
var date = new dateClass()
println(date.toString())

To generate a Date object and output the execution date and time.

Interpreter.java


    public static void main(String[] args) throws Exception {
        String text = "";
        text += "var dateClass = loadClass(\"java.util.Date\")";
        text += "var date = new dateClass()";
        text += "println(date.toString())";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> Sat Jun 17 18:29:13 JST 2017 (Date and time of execution)
    }

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-19-class-loading/Calc/src/main/java

There is a continuation article.

** Corresponds to static method calls ** http://qiita.com/quwahara/items/a8ef47f78b1479a117de

Recommended Posts

19 Corresponds to object creation
18 Corresponds to JSON-like object definitions
Corresponds to 17 arrays
Corresponds to Scope
Corresponds to 15 strings
8 Corresponds to multiple arguments
Road to REPL (?) Creation (3)
10 Corresponds to if statement
14 Corresponds to function expressions
5 Corresponds to prioritizing parentheses
Road to REPL (?) Creation (1)
16 Corresponds to method invocation
Road to REPL (?) Creation (2)
12 Corresponds to the while statement
Convert Serializable Object to byte []
20 Corresponds to static method invocation
11 Corresponds to comparison and logical operators
Convert ruby object to JSON format
[Docker] Operation up to container creation # 2
to_ ○
Try to organize your own object orientation
The road to Web service creation (Part 2)
Mapping json to an already instantiated Object
Introduction to kotlin for iOS developers ⑥-Kotlin creation