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".
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())
We will consider how to implement it in the order of parser and interpreter. There are no changes to lexical analysis (Lexer).
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.
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.
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 the
Class.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 to
functions` 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. The
left of the
newtoken, which is the function call syntax, and the
leftare the part corresponding to the function name. The
params of the
leftof the
new` 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.
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