Je voudrais prendre en charge un tableau de structures de données de base. Cet article est Correspondant aux chaînes de caractères. Lorsqu'elle correspond à une chaîne de caractères, je souhaite pouvoir gérer une sous-chaîne de la chaîne de caractères. Ici, je voudrais pouvoir appeler une méthode pour qu'une sous-chaîne puisse être extraite de la chaîne.
Confirmez ce que vous voulez faire avec la prise en charge des appels de méthode.
Par exemple, il existe le programme suivant.
Après avoir assigné une chaîne à la variable hw
, nous cherchons à pouvoir appeler la méthode d'instance substring () ʻ de la variable
hw. L'appel de méthode
println ()à la fin du programme vise à afficher une sous-chaîne de la variable
hw,
Hello`.
var hw = "Hello world!"
var h = hw.substring(0, 5)
println(h)
Ce que vous ne faites pas, c'est de ne pas prendre en charge les appels de méthode statiques.
Nous examinerons comment l'implémenter dans l'ordre de l'analyse des phrases (Lexer), de l'analyse syntaxique (Parser) et de l'interpréteur (Interpreter).
Puisqu'il n'y a pas de fonction à analyser .
, ajoutez-la.
Le jeton .
sera ajouté en ajoutant la fonction de Lexer.
Ajoutez la possibilité de l'analyser.
Le jeton .
peut être analysé comme un opérateur binaire, changez-le en conséquence.
Nous implémenterons les appels de méthode d'instance afin qu'ils puissent être traités de la même manière que les appels de fonction qui ont déjà été implémentés.
Dans un appel de fonction, le "à gauche du" jeton est le déclencheur de l'appel de fonction.
Il n'y avait qu'un seul jeton pour le nom de la fonction ou de la variable.
Le (à gauche du
jeton est le jeton dans l'appel de méthode est
Le jeton «.» Et ses jetons gauche et droit sont également impliqués dans l'appel.
Par conséquent, le jeton «.» Et les jetons à gauche et à droite de celui-ci peuvent être traités comme une seule information.
Présentez une classe qui les rassemble.
Nous allons permettre de gérer les classes groupées de la même manière que les appels de fonction.
Passez à la mise en œuvre. À propos de l'analyse de phrases (Lexer), de l'analyse syntaxique (Parser), de l'interpréteur (Interpreter) Jetons un coup d'œil aux modifications et aux ajouts dans l'ordre.
Lexer.java
Une implémentation de Lexer.java.
Ajoutez la fonction d'analyse de phrase de .
.
ʻAjoutez la méthode isDotStart () `. Détecte «.».
Lexer.java
private boolean isDotStart(char c) {
return c == '.';
}
Ajoutez la méthode dot ()
.
Analyser .
en un jeton.
Le «genre» qui représente «.» Devrait être «point».
Lexer.java
private Token dot() throws Exception {
Token t = new Token();
t.kind = "dot";
t.value = Character.toString(next());
return t;
}
Modifiez la méthode nextToken ()
.
Ajoutez l'appel à la méthode ajoutée à // Add
.
Vous pouvez maintenant décomposer «.» En jetons.
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");
}
}
C'est tout pour changer Lexer.java.
Parser.java
Une implémentation de Parser.java.
Ajoutez une définition de la façon dont cela fonctionne pour la signification du jeton.
Ajout du degré de classement de .
à // Update 1
.
.
Est meilleur que les opérateurs arithmétiques comme + ʻet
`
Parce qu'il relie fortement les jetons gauche et droit,
Il est de 80, ce qui est plus grand que le degré de «+» et «».
Ajout de «point» à «// Update 2»
La syntaxe de .
équivaut à l'opérateur binaire,
Comme l'analyse syntaxique peut être effectuée de la même manière que les opérateurs binaires,
A traiter pour l'analyse syntaxique des opérateurs binaires
Ajout de «point» à «types binaires».
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" });
}
Le processus d'analyse syntaxique lui-même n'apporte aucune modification ni aucun ajout. En effet, il est soumis à une analyse syntaxique des opérateurs binaires existants en plus de la définition. C'est tout pour changer Parser.java.
Interpreter.java
Une implémentation d'Interpreter.java.
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 d'une branche sous
// Add pour que le jeton
dot représente
.`.
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");
}
}
Ajout de la méthode dot ()
.
Appelé sur la branche ajoutée ci-dessus.
La méthode dot ()
est une classe qui associe les côtés gauche et droit de .
Renvoie une instance de «Dotted».
Le côté gauche de .
assigne la valeur de retour de la méthode value ()
, qui garantit qu'il s'agit d'une valeur.
Ceci permet de garantir que le côté gauche de «.» Est toujours garanti comme instance de l'appel de méthode.
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;
}
Ceci est une modification de la méthode func ()
.
La méthode func ()
garantit que l'argument value
est appelable comme une fonction.
Le traitement lorsque le «pointillé» ajouté ci-dessus est passé par l'argument «valeur» a été ajouté sous «// Ajouter».
Si l'argument est «Dotted», il renvoie une instance de la classe «MethodFunc».
La classe MethodFunc
hérite de la classe abstraite appelableFunc
et
Les informations requises pour l'appel de méthode sont stockées dans la variable de champ.
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");
}
}
Ajout de la classe MethodFunc
.
Il est retourné par la méthode func ()
.
Faites un appel de méthode d'instance avec la méthode ʻinvoke` de cette classe.
Pour effectuer un appel de méthode d'instance, vous devez obtenir les informations qui représentent la méthode.
Les informations de méthode peuvent être obtenues à partir des informations qui représentent la classe.
La variable de champ class_
est l'information pour cette classe.
Vous pouvez obtenir les informations de méthode à partir des informations de classe, Parmi les multiples méthodes définies dans la classe Vous devez sélectionner une information pour la méthode que vous souhaitez appeler.
La méthode ʻInvoke est utilisée pour sélectionner les informations de la méthode que vous voulez appeler. Utilisez le nom de la méthode et les informations de type de l'argument de la méthode ʻinvoke
pour affiner.
Si les informations de méthode correspondantes ne peuvent pas être trouvées 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 static class MethodFunc extends Func {
//Représente le type de cible d'appel de méthode
public Class<?> class_;
//Représente l'instance pour laquelle la méthode est appelée
public Object target;
@Override
public Object invoke(List<Object> args) throws Exception {
//Faire une liste des types d'arguments à partir des arguments
List<Class<?>> aClasses = argClasses(args);
//Une liste d'informations de méthode que le type à appeler la méthode a
//Ne listez que ceux qui portent le même nom que ce MethodFunc
List<Method> mByName = methodByName(class_.getMethods(), name);
//Une liste d'informations sur les méthodes classées par nom,
//La liste des types d'arguments est limitée à ceux avec des signatures assignables.
List<Method> mByAssignable = methodByAssignable(mByName, aClasses);
//En raison de la réduction, une erreur se produit s'il n'y a pas d'informations de méthode correspondantes
if (mByAssignable.size() == 0) {
throw new Exception("MethodFunc.invoke error");
}
Method method;
if (mByAssignable.size() == 1) {
//En raison de la réduction, s'il n'existe qu'une seule information de méthode applicable
//Ce sont les informations de méthode à appeler
method = mByAssignable.get(0);
} else {
//En raison de la réduction, s'il y a deux ou plusieurs informations de méthode applicables, affinez davantage.
//Une liste d'informations sur les méthodes réduites par des signatures assignables,
//Réduisez la liste des types d'arguments à ceux dont les signatures correspondent exactement.
List<Method> mByAbsolute = methodByAbsolute(mByAssignable, aClasses);
//En raison de la réduction, une erreur se produira si les informations de méthode correspondantes ne le deviennent pas.
if (mByAbsolute.size() != 1) {
throw new Exception("MethodFunc.invoke error");
}
//En raison de la réduction, s'il n'existe qu'une seule information de méthode applicable
//Ce sont les informations de méthode à appeler
method = mByAbsolute.get(0);
}
//Effectuer un appel de méthode en utilisant une seule information de méthode
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;
}
Le programme ci-dessous utilisant l'implémentation ci-dessus
var hw = "Hello world!"
var h = hw.substring(0, 5)
println(h)
Et
L'appel de méthode println ()
à la fin du programme imprime Hello
.
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
}
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-16-method-call/Calc/src/main/java
Il y a un article de suite.
** Correspond au tableau ** http://qiita.com/quwahara/items/b4f821a797a146c8d873
Recommended Posts