In the previous article, Supports Scope. Now it corresponds to the function expression. I will try to make a closure by making use of the Scope that was supported in the previous article.
Confirm what you want to do with function expression support.
For example, there is the following program.
The inner function expression returned by the outer function expression is assigned to the variable counter
.
Every time you call counter
as a function
The value of the variable c
defined in the outer function expression is added.
The first println (counter ())
prints 1
,
The next println (counter ())
will output 2
.
counter = (function() {
var c = 0
return function() {
c = c + 1
return c
}
})()
println(counter())
println(counter())
We will consider how to implement it in the order of parser and interpreter.
Parsing a function expression is different from parsing a function definition with or without a function name. The difference is reflected in the parsing of the function definition.
Introduces an object that represents a function expression. The object can be treated as a value, and it also supports function calls. Until now, only ʻInteger` was treated as a value, Function expressions must also be treated as values.
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.
A modification of the func ()
method that parses the function definition.
The changed part is the first if statement.
The original implementation expected the function
token to be followed by the ʻidenttoken, which is the function name. In the case of a function expression, the
function token is followed by the
(token. The if statement is judged after the
function token is
(if it is a `token, the function expression is parsed,
If not, I changed it to parse the function definition.
Parser.java
private Token func(Token token) throws Exception {
if (token().value.equals("(")) {
token.kind = "fexpr";
} else {
token.kind = "func";
token.ident = ident();
}
consume("(");
token.params = new ArrayList<Token>();
if (!token().value.equals(")")) {
token.params.add(ident());
while (!token().value.equals(")")) {
consume(",");
token.params.add(ident());
}
}
consume(")");
token.block = body();
return token;
}
Interpreter.java
An implementation of Interpreter.java.
A modification of the Variable
class that represents a variable.
Until now, the value has only dealt with ʻInteger. Since we are introducing a function expression, we must be able to treat the function expression as a value as well. To be able to handle the type of the field variable
value, Changed from ʻInteger
to ʻObject`.
Interpreter.java
public static class Variable {
public String name;
public Object value;
@Override
public String toString() {
return name + " " + value;
}
}
ʻExpression () method change. It is a process that branches according to the meaning (kind) of the token that represents the expression. Added a branch under
// Add for the token
fexpr`, which represents a function expression.
Interpreter.java
public Object expression(Token expr) throws Exception {
if (expr.kind.equals("digit")) {
return digit(expr);
} else if (expr.kind.equals("ident")) {
return ident(expr);
} else if (expr.kind.equals("func")) {
return func(expr);
// Add
} 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 {
throw new Exception("Expression error");
}
}
Added the fexpr ()
method, which is called on the branch added by the ʻexpression method above. Creates an object that represents a function expression. To be able to keep the parent scope of a function expression, given that it is used in closures Clone and keep the current ʻInterpreter
in func.context
.
Interpreter.java
public Object fexpr(Token token) throws Exception {
List<String> paramCheckList = new ArrayList<String>();
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 = new Interpreter();
func.context.global = global;
func.context.local = local;
func.context.body = body;
func.params = token.params;
func.block = token.block;
return func;
}
A modification of the func ()
method that creates an instance that represents a function definition.
Synchronized with the fexpr ()
method cloning the current ʻInterpreter, I also changed it to clone under
// Update`.
Interpreter.java
public Object func(Token token) throws Exception {
String name = token.ident.value;
if (local.functions.containsKey(name)) {
throw new Exception("Name was used");
}
if (local.variables.containsKey(name)) {
throw new Exception("Name was used");
}
List<String> paramCheckList = new ArrayList<String>();
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();
// Update
func.context = new Interpreter();
func.context.global = global;
func.context.local = local;
func.context.body = body;
func.name = name;
func.params = token.params;
func.block = token.block;
local.functions.put(name, func);
return null;
}
Changes to the value ()
method and addition of the ʻinteger ()` method.
The value ()
method returns the value if the argument is the value itself, such as 1
.
If it is a variable, it returns the value that the variable holds.
Until now, we have only dealt with ʻIntegeras a value. The corresponding function expression is also the value returned by this method, so it corresponds. Change the return type of the
value () method from ʻInteger
to ʻObject. Now you can return both ʻInteger
and function expressions with the value ()
method.
The reason for adding the ʻinteger () method is This is because the return value of the
value () method has been changed from ʻInteger
to ʻObject. Until now, the return value of the
value () method was guaranteed as ʻInteger
,
The change from ʻInteger to ʻObject
no longer guarantees it.
Instead, the ʻinteger () method guarantees that the value is ʻInteger
.
To influence these changes and ensure that the value is ʻInteger The
value () method was called, Even with the ʻunaryCalc ()
method and the calc ()
method
I am changing to call the ʻinteger () `method instead.
Interpreter.java
public Object value(Object value) throws Exception {
if (value instanceof Integer) {
return value;
} else if (value instanceof Func) {
return value;
} else if (value instanceof Variable) {
Variable v = (Variable) value;
return value(v.value);
}
throw new Exception("right value error");
}
public Integer integer(Object value) throws Exception {
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof Variable) {
Variable v = (Variable) value;
return integer(v.value);
}
throw new Exception("right value error");
}
This is a modification of the func ()
method.
A method that guarantees that the argument is a function.
If the argument is of type Variable
to ʻelse if`,
Added processing to ensure that the value is a function.
Interpreter.java
public Func func(Object value) throws Exception {
if (value instanceof Func) {
return (Func) value;
} else if (value instanceof Variable) {
Variable v = (Variable) value;
return func(v.value);
} else {
throw new Exception("Not a function");
}
}
ʻIsTrue method change. The ʻisTrue
method is the process of resolving a value to a boolean value.
Until now, the value was only ʻInteger, but since functions are now treated as values, When the value was a function, I simply resolved it to
true`.
Interpreter.java
public boolean isTrue(Object value) throws Exception {
if (value instanceof Integer) {
return 0 != ((Integer) value);
} else if (value instanceof Func) {
return true;
} else {
return false;
}
}
The program below using the above implementation
counter = (function() {
var c = 0
return function() {
c = c + 1
return c
}
})()
println(counter())
println(counter())
And
The first println (counter ())
prints 1
,
The next println (counter ())
will output 2
.
Interpreter.java
public static void main(String[] args) throws Exception {
String text = "";
text += "counter = (function() {";
text += " var c = 0";
text += " return function() {";
text += " c = c + 1";
text += " return c";
text += " }";
text += "})()";
text += "println(counter())";
text += "println(counter())";
List<Token> tokens = new Lexer().init(text).tokenize();
List<Token> blk = new Parser().init(tokens).block();
new Interpreter().init(blk).run();
// --> 1
// --> 2
}
That's all for the implementation. Thank you very much.
The full source is available here.
Calc https://github.com/quwahara/Calc/tree/article-14-function-expression-r3/Calc/src/main/java
There is a continuation article.
** Corresponds to a string ** http://qiita.com/quwahara/items/ddcbc8c37b9d442fe0f2
Recommended Posts