Added Simple Function Definitions and Calls in a previous article. At that time, the function argument corresponded to only one. This time it supports no arguments or two or more arguments.
Make sure you want to do it easily.
For example, if you have a program like the one below, the ʻadd3 ()function is defined and we aim to output6` to the standard output.
v = 0
function add3(a1, a2, a3) {
v = a1 + a2 + a3
}
add3(1,2,3)
println(v)
It's still the previous article that it doesn't correspond to the return value of a function or the scope of a variable.
We will consider how to implement it in the order of lexical analysis (Lexer), parser (Parser), and interpreter (Interpreter).
Since there is no function to analyze commas and , in the lexical analysis of the implementation so far, we will implement it.
We'll make some changes to the implementation of parsing function definitions and function calls that we made in the previous article. Both only consider the case with one argument, so Modify the parsing implementation to take into account no arguments or multiple arguments.
This is also a class that represents the function added in the previous article, Use it to make changes to the part that defines and calls the function. All of them only consider the case with one argument, so Change to take into account no arguments or multiple arguments.
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.
First, add the function to analyze commas and ,.
Lexer.java
private boolean isSymbolStart(char c) {
return c == ',';
}
private Token symbol() throws Exception {
Token t = new Token();
t.kind = "symbol";
t.value = Character.toString(next());
return t;
}
I will add the calling part of comma analysis. The implementation of Lexer.java is complete.
Lexer.java
public Token nextToken() throws Exception {
skipSpace();
if (isEOT()) {
return null;
} else if (isSignStart(c())) {
return sign();
} else if (isDigitStart(c())) {
return digit();
} 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");
}
}
Parser.java
Changed the method func () to parse the function definition.
Before explaining the func () method change, because the Token class change is relevant
I will explain how to change the Token class.
The analysis result of the function definition is summarized in the argument token.
In the previous article, I added a field variable called param, which represents a formal argument, to the Token class.
This time, in order to support multiple arguments, the param field is abolished and a field variable called params of type List <Token> is added to the Token class.
Next is a description of changing the func () method.
The main changes are <-Update.
The parsing there is as follows:
(After the token, after
) token comes soon, there are no arguments) token right away, first you have one or more arguments) token does not come, two or more arguments will continue.And analyze.
Parser.java
private Token func(Token token) throws Exception {
token.kind = "func";
token.ident = ident();
consume("(");
token.params = new ArrayList<Token>();
if (!token().value.equals(")")) { // <-- Update
token.params.add(ident());
while (!token().value.equals(")")) {
consume(",");
token.params.add(ident());
}
}
consume(")");
consume("{");
token.block = block();
consume("}");
return token;
}
An implementation of parsing the part that makes the function call.
The main change is <-Update, and you can see that the implementation method is almost the same as the parsing of the function definition earlier.
Parser.java
private Token bind(Token left, Token operator) throws Exception {
if (binaryKinds.contains(operator.kind)) {
operator.left = left;
int leftDegree = degree(operator);
if (rightAssocs.contains(operator.value)) {
leftDegree -= 1;
}
operator.right = expression(leftDegree);
return operator;
} else if (operator.kind.equals("paren") && operator.value.equals("(")) {
operator.left = left;
operator.params = new ArrayList<Token>();
if (!token().value.equals(")")) { // <-- Update
operator.params.add(expression(0));
while (!token().value.equals(")")) {
consume(",");
operator.params.add(expression(0));
}
}
consume(")");
return operator;
} else {
throw new Exception("The token cannot place there.");
}
}
Interpreter.java
An implementation of Interpreter.java.
A modification of the abstract class of the class that represents the function.
ʻInvoke () method argument according to multiple arguments, Changed from ʻObject arg to List <Object> args.
Interpreter.java
public static abstract class Func {
public String name;
abstract public Object invoke(List<Object> args) throws Exception;
}
I changed the ʻinvoke ()method of thePrintln` class to match the change of the abstract class.
Interpreter.java
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);
return null;
}
}
Similarly, I changed the ʻinvoke ()method of theDynamicFuncclass to match the change of the abstract class. To change. Also, the field variableparam has been abolished so that multiple arguments can be expressed, and paramsof typeList
Interpreter.java
public static class DynamicFunc extends Func {
public Interpreter context;
public List<Token> params;
public List<Token> block;
@Override
public Object invoke(List<Object> args) throws Exception {
for (int i = 0; i < params.size(); ++i) {
Token param = params.get(i);
Variable v = context.variable(context.ident(param));
if (i < args.size()) {
v.value = context.value(args.get(i));
} else {
v.value = null;
}
}
context.body(block);
return null;
}
}
It is a change of the function definition part.
The main changes are <-Add and <-Update.
It is repeated in the for statement to process multiple arguments.
Interpreter.java
public Object func(Token token) throws Exception {
String name = token.ident.value;
if (functions.containsKey(name)) {
throw new Exception("Name was used");
}
if (variables.containsKey(name)) {
throw new Exception("Name was used");
}
List<String> paramCheckList = new ArrayList<String>(); // <-- Add
for (Token p : token.params) {
String param = p.value;
if (paramCheckList.contains(param)) {
throw new Exception("Parameter name was used");
}
paramCheckList.add(param);
}
DynamicFunc func = new DynamicFunc();
func.context = this;
func.name = name;
func.params = token.params; // <-- Update
func.block = token.block;
functions.put(name, func);
return null;
}
The program below using the above implementation
v = 0
function add3(a1, a2, a3) {
v = a1 + a2 + a3
}
add3(1,2,3)
println(v)
To print the value 6 assigned to the variable v to the standard output.
Interpreter.java
public static void main(String[] args) throws Exception {
String text = "";
text += "v = 0";
text += "function add3(a1, a2, a3) {";
text += " v = a1 + a2 + a3";
text += "}";
text += "add3(1,2,3)";
text += "println(v)";
List<Token> tokens = new Lexer().init(text).tokenize();
List<Token> blk = new Parser().init(tokens).block();
new Interpreter().init(blk).run();
// --> 6
}
That's all for the implementation. Thank you very much.
The full source is available here.
Calc https://github.com/quwahara/Calc/tree/article-8-multiple-arguments-r2/Calc/src/main/java
There is a continuation article.
** Corresponds to the return value ** http://qiita.com/quwahara/items/1db9a5b880fd36dcfd3c
Recommended Posts