Je veux charger une classe Java et appeler un constructeur pour gérer la création d'objets. Cet article est une suite de "Correspondant aux définitions d'objets de type JSON".
Confirmez ce que vous voulez faire avec la prise en charge de la création d'objets.
Par exemple, il existe le programme suivant.
Introduit la fonction loadClass
comme fonction globale par défaut.
La fonction loadClass
est une chaîne comme argument, et si vous passez un nom de classe Java complet, cette classe sera chargée. (La première ligne)
Celui chargé est affecté à la variable.
Si vous ajoutez new
à cette variable et que vous l'appelez comme une fonction, cela créera une instance. (2e ligne)
var dateClass = loadClass("java.util.Date")
var date = new dateClass()
println(date.toString())
Nous examinerons comment l'implémenter dans l'ordre de l'analyse syntaxique (Parser) et de l'interpréteur (Interpreter). Il n'y a pas de changement dans l'analyse des phrases (Lexer).
La syntaxe nouvellement ajoutée est «new», alors réfléchissons à la façon d'analyser «new».
new
vient en premier dans la syntaxe, donc comme tout autre jeton dans la syntaxe.
Implémentez le processus d'analyse de la syntaxe de la méthode new
dans la méthode lead ()
.
Puisque l'élément de la syntaxe qui vient après new
a la même syntaxe que l'appel de fonction,
Le processus d'analyse de la syntaxe de «nouveau» vérifie que l'élément suivant est un appel de fonction.
L'interpréteur implémente la fonction nouvellement introduite «loadClass» et l'exécution de la «nouvelle» syntaxe.
Implémentez la fonction loadClass
de la même manière que la fonction globale par défaut existante println
.
L'implémentation de l'exécution de la syntaxe new
est la syntaxe qui vient après new
Traitez-le comme un appel de fonction et faites-le instancier.
Passez à la mise en œuvre. À propos de l'analyseur syntaxique (Parser) et de l'interpréteur (Interpreter) Jetons un coup d'œil aux modifications et aux ajouts dans l'ordre.
Parser.java
Une implémentation de Parser.java.
Implémente l'analyse syntaxique new
.
De la méthode lead ()
qui implémente la syntaxe déterminée par le premier jeton
Ajout d'un appel à la méthode new_ ()
qui analyse 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.");
}
}
Ajout de la méthode new_ ()
pour effectuer l'analyse syntaxique new
.
L'argument «token» reçoit un «nouveau» token.
Le type de jeton new
, kind
, qui est la clé du traitement de l'interpréteur, est défini sur new
.
La «gauche» du «nouveau» jeton attribue la valeur de retour de la méthode «expression».
Assurez-vous que la valeur de retour est une analyse syntaxique de l'appel de fonction.
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;
}
C'est tout pour changer Parser.java.
Interpreter.java
Une implémentation d'Interpreter.java.
Ajout d'une classe qui est la substance de la fonction loadClass
.
L'argument de la méthode ʻinvoke correspond à l'argument de l'appel de fonction
loadClass. En supposant que le premier argument est une chaîne de noms de classe pleinement qualifiés Chargez la classe à l'aide de la méthode
Class.forName`.
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));
}
}
Changement de méthode ʻInit () . Une méthode qui initialise l'interpréteur. Instanciez la classe
LoadClass ajoutée à
// Addet Pour qu'elle puisse être appelée comme fonction globale par défaut Ajouter aux
fonctions` dans la portée globale.
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;
}
Modification de la méthode ʻExpression () . C'est un processus qui se ramifie en fonction de la signification (sorte) du jeton qui représente l'expression. Ajout de l'appel de méthode
new_ ()pour la création d'objets sous
// 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");
}
}
Ajout de la méthode new_ ()
pour la création d'objets.
Cette méthode appelle le constructeur et retourne l'objet créé.
Le jeton new
est passé à l'argument de méthode new_ ()
ʻexpr. La «gauche» du «nouveau» jeton se voit attribuer un jeton qui a la syntaxe d'appel de fonction. La «gauche» du «nouveau» jeton, qui est la syntaxe de l'appel de fonction, et la «gauche» sont la partie correspondant au nom de la fonction. Le
params de la
gauche du jeton
new` est l'argument de la syntaxe d'appel de fonction.
La partie correspondant au nom de la fonction (ʻexpr.left.left`) est une classe dans l'appel du constructeur, donc Au début du processus, cette partie est résolue en classe.
Pour effectuer un appel de constructeur, vous devez obtenir des informations qui représentent le constructeur.
Les informations sur le constructeur peuvent être obtenues à partir des informations qui représentent la classe.
La variable c
résolue en classe est l'information pour cette classe.
Vous pouvez obtenir les informations du constructeur à partir des informations de classe, Parmi les multiples constructeurs définis dans la classe Vous devez sélectionner une information sur le constructeur que vous souhaitez appeler.
Dans la méthode new_
, afin de sélectionner une information de constructeur que vous voulez appeler
Filtrez en utilisant les informations de type de l'argument de l'appel du constructeur.
Si les informations de constructeur correspondantes sont introuvables ou réduites à une, une erreur se produit.
La méthode de réduction est très simple et est différente de l'implémentation dans le compilateur Java. J'ai fait un commentaire avec une petite explication détaillée.
Interpreter.java
public Object new_(Token expr) throws Exception {
// "("Résolvez le côté gauche de dans une classe et obtenez une liste de constructeurs pour cette classe
Class<?> c = class_(expression(expr.left.left));
Constructor<?>[] ctors = c.getConstructors();
// "("Rendre l'argument constructeur sur le côté droit de la liste résolu en valeur
List<Object> args = new ArrayList<Object>();
for (Token arg : expr.left.params) {
args.add(value(expression(arg)));
}
//Faire une liste des types d'arguments à partir des arguments
List<Class<?>> aClasses = argClasses(args);
//La liste des types d'arguments est limitée aux constructeurs dont les signatures peuvent être attribuées.
List<Constructor<?>> byAssignables = ctorsByAssignable(ctors, aClasses);
//En raison de la réduction, une erreur se produit s'il n'y a pas de constructeur correspondant
if (byAssignables.size() == 0) {
throw new Exception("No constructor error");
}
Constructor<?> ctor;
if (byAssignables.size() == 1) {
//En raison de la réduction, s'il n'y a qu'un seul constructeur applicable,
//C'est le constructeur à appeler
ctor = byAssignables.get(0);
} else {
//En raison de la réduction, s'il y a deux ou plusieurs constructeurs applicables, rétrécissez davantage.
//Une liste de constructeurs restreinte par des signatures assignables,
//Limitez-vous aux seuls constructeurs qui ont une signature qui correspond exactement à la liste des types d'arguments.
List<Constructor<?>> byAbsolutes = ctorsByAbsolute(byAssignables, aClasses);
//En raison de la réduction, une erreur se produira si le nombre de constructeurs applicables n'est pas un.
if (byAbsolutes.size() != 1) {
throw new Exception("No constructor error");
}
//En raison de la réduction, s'il n'y a qu'un seul constructeur applicable,
//C'est le constructeur à appeler
ctor = byAbsolutes.get(0);
}
//Faire un appel au constructeur en utilisant un seul constructeur
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;
}
Ajout d'une méthode class_ ()
qui garantit que la valeur de l'argument est une classe.
Ueno Utilisé au début de la méthode new ()
.
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");
}
Changement de la méthode value ()
de la méthode qui garantit que l'argument est une valeur.
Jusqu'à présent, la politique de mise en œuvre consistait à vérifier si le type de l'argument «valeur» était valide en tant que valeur.
Maintenant que vous pouvez créer des objets, vous ne pouvez plus examiner tous les types de valeur pour déterminer s'ils sont valides.
Par conséquent, j'ai ajouté void_
à la variable de champ statique, ce qui indique qu'elle est inappropriée en tant que valeur.
Si l'argument «valeur» est «void_», il est considéré comme inapproprié en tant que valeur.
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;
}
Correspondant à l'ajout de void_
, qui indique que la valeur est inappropriée,
Changé pour retourner void_
au lieu de renvoyer null
pour ceux qui n'ont pas renvoyé de valeur jusqu'à présent.
Modifié pour renvoyer void_
là où il y a // Update
dans chaque méthode ci-dessous.
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_;
}
}
Le programme ci-dessous utilisant l'implémentation ci-dessus
var dateClass = loadClass("java.util.Date")
var date = new dateClass()
println(date.toString())
Pour générer un objet Date
et afficher la date et l'heure d'exécution.
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 et heure d'exécution)
}
C'est tout pour la mise en œuvre. Merci beaucoup.
La source complète est disponible ici.
Calc https://github.com/quwahara/Calc/tree/article-19-class-loading/Calc/src/main/java
Il y a un article de suite.
** Correspond aux appels de méthode statiques ** http://qiita.com/quwahara/items/a8ef47f78b1479a117de
Recommended Posts