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 output
6` 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 the
Println` 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 the
DynamicFuncclass to match the change of the abstract class. To change. Also, the field variable
param has been abolished so that multiple arguments can be expressed, and
paramsof type
List
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