added lambdas and a return statement

This commit is contained in:
hneemann 2018-03-16 10:06:21 +01:00
parent c03e29a710
commit 05b19ddcfb
10 changed files with 184 additions and 56 deletions

View File

@ -19,6 +19,7 @@ public class Context {
private final Context parent;
private final StringBuilder code;
private HashMap<String, Object> map;
private boolean functionContext = false;
/**
* Creates a new context
@ -156,6 +157,45 @@ public class Context {
return parent.length();
}
/**
* Flags this context as context belonging to a function call.
* This allows to use the return statement.
*
* @return this for chained calls
*/
public Context isFunctionContext() {
functionContext = true;
return this;
}
/**
* Returns from a function call.
*
* @param returnValue the return value
* @throws HGSEvalException HGSEvalException
*/
public void returnFromFunc(Object returnValue) throws HGSEvalException {
if (!functionContext)
throw new HGSEvalException("The return statement is allowed only in a function!");
throw new ReturnException(returnValue);
}
/**
* Returns a function from this context
*
* @param funcName the functions name
* @return the function
* @throws HGSEvalException HGSEvalException
*/
public FuncAdapter getFunction(String funcName) throws HGSEvalException {
Object fObj = getVar(funcName);
if (fObj instanceof FuncAdapter)
return (FuncAdapter) fObj;
else
throw new HGSEvalException("Variable '" + funcName + "' is not a function");
}
private static final class FunctionPrint extends Function {
private FunctionPrint() {
@ -163,7 +203,7 @@ public class Context {
}
@Override
public Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException {
public Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException {
for (Expression arg : args)
c.print(arg.value(c).toString());
return null;
@ -177,7 +217,7 @@ public class Context {
}
@Override
public Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException {
public Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException {
c.print(format(c, args));
return null;
}
@ -190,7 +230,7 @@ public class Context {
}
@Override
public Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException {
public Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException {
return format(c, args);
}
}
@ -213,7 +253,7 @@ public class Context {
}
@Override
public Object calcValue(Context c, ArrayList<Expression> args) {
public Object callWithExpressions(Context c, ArrayList<Expression> args) {
try {
args.get(0).value(c);
return true;
@ -233,4 +273,28 @@ public class Context {
throw new HGSEvalException(args[0].toString());
}
}
/**
* Exception used to return a value from a function
*/
public static final class ReturnException extends HGSEvalException {
private final Object returnValue;
/**
* Creates a new instance
*
* @param returnValue the return value
*/
ReturnException(Object returnValue) {
super("return");
this.returnValue = returnValue;
}
/**
* @return the return value
*/
public Object getReturnValue() {
return returnValue;
}
}
}

View File

@ -7,6 +7,7 @@ package de.neemann.digital.hdl.hgs;
import de.neemann.digital.core.Bits;
import de.neemann.digital.hdl.hgs.function.FirstClassFunction;
import de.neemann.digital.hdl.hgs.function.FirstClassFunctionCall;
import de.neemann.digital.hdl.hgs.refs.*;
import java.io.IOException;
@ -179,6 +180,10 @@ public class Parser {
while (!nextIs(CLOSEDBRACE))
s.add(parseStatement());
return s.optimize();
case RETURN:
Expression retExp = parseExpression();
expect(SEMICOLON);
return c -> c.returnFromFunc(retExp.value(c));
default:
throw newUnexpectedToken(token);
}
@ -413,7 +418,7 @@ public class Parser {
return exp;
case FUNC:
FirstClassFunction func = parseFunction();
return c -> func;
return c -> new FirstClassFunctionCall(func, c);
default:
throw newUnexpectedToken(t);
}

View File

@ -18,7 +18,7 @@ class Tokenizer {
UNKNOWN, IDENT, AND, OR, XOR, NOT, OPEN, CLOSE, NUMBER, EOL, EOF, SHIFTLEFT, SHIFTRIGHT, COMMA, EQUAL,
ADD, SUB, MUL, GREATER, LESS, DIV, MOD, END, IF, ELSE, FOR, WHILE, SEMICOLON, NOTEQUAL, STRING,
OPENBRACE, CLOSEDBRACE, CODEEND, OPENSQUARE, CLOSEDSQUARE, DOT, STATIC, FUNC, GREATEREQUAL, LESSEQUAL,
REPEAT, UNTIL
REPEAT, RETURN, UNTIL
}
private static HashMap<String, Token> statementMap = new HashMap<>();
@ -31,6 +31,7 @@ class Tokenizer {
statementMap.put("func", Token.FUNC);
statementMap.put("repeat", Token.REPEAT);
statementMap.put("until", Token.UNTIL);
statementMap.put("return", Token.RETURN);
}
private final Reader in;

View File

@ -5,48 +5,33 @@
*/
package de.neemann.digital.hdl.hgs.function;
import de.neemann.digital.hdl.hgs.Context;
import de.neemann.digital.hdl.hgs.HGSEvalException;
import de.neemann.digital.hdl.hgs.Statement;
import java.util.ArrayList;
/**
* callable first class function
* Description of a first class function.
*/
public class FirstClassFunction extends FuncAdapter {
public class FirstClassFunction {
private final ArrayList<String> args;
private final Statement st;
private final Statement statement;
/**
* Creates a new instance
*
* @param args the names of the arguments
* @param st the function body
* @param statement the function body
*/
public FirstClassFunction(ArrayList<String> args, Statement st) {
super(args.size());
public FirstClassFunction(ArrayList<String> args, Statement statement) {
this.args = args;
this.st = st;
this.statement = statement;
}
/**
* Evaluates this function
*
* @param args the arguments
* @return the result
* @throws HGSEvalException HGSEvalException
*/
@Override
public Object f(Object... args) throws HGSEvalException {
Context c = new Context();
for (int i = 0; i < args.length; i++)
c.setVar(this.args.get(i), args[i]);
st.execute(c);
if (c.contains("return"))
return c.getVar("return");
else
throw new HGSEvalException("A function must define the variable 'return'!");
ArrayList<String> getArgs() {
return args;
}
Statement getStatement() {
return statement;
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2018 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.hdl.hgs.function;
import de.neemann.digital.hdl.hgs.Context;
import de.neemann.digital.hdl.hgs.HGSEvalException;
/**
* A call to a first class function
*/
public final class FirstClassFunctionCall extends FuncAdapter {
private final FirstClassFunction func;
private final Context capturedContext;
/**
* Creates a new instance
*
* @param func the function
* @param context the captured context
*/
public FirstClassFunctionCall(FirstClassFunction func, Context context) {
super(func.getArgs().size());
this.func = func;
this.capturedContext = context;
}
@Override
protected Object f(Object... args) throws HGSEvalException {
Context c = new Context(capturedContext).isFunctionContext();
for (int i = 0; i < args.length; i++)
c.setVar(func.getArgs().get(i), args[i]);
try {
func.getStatement().execute(c);
return null;
} catch (Context.ReturnException e) {
return e.getReturnValue();
}
}
}

View File

@ -25,10 +25,7 @@ public abstract class FuncAdapter extends Function {
}
@Override
public Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException {
if (getArgCount() != args.size())
throw new HGSEvalException("wrong number of arguments! found: " + args.size() + ", expected: " + getArgCount());
public Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException {
Object[] data = new Object[args.size()];
for (int i = 0; i < args.size(); i++)
data[i] = args.get(i).value(c);
@ -36,11 +33,25 @@ public abstract class FuncAdapter extends Function {
}
/**
* The function
* Evaluates this function.
*
* @param args the evaluated arguments
* @param args the arguments
* @return the result
* @throws HGSEvalException HGSEvalException
*/
protected abstract Object f(Object... args) throws HGSEvalException;
/**
* Use this method to call the function from your java code.
*
* @param args the arguments of this function
* @return the function result
* @throws HGSEvalException HGSEvalException
*/
public Object call(Object... args) throws HGSEvalException {
if (getArgCount() >= 0 && getArgCount() != args.length)
throw new HGSEvalException("wrong number of arguments! found: " + args.length + ", expected: " + getArgCount());
return f(args);
}
}

View File

@ -42,5 +42,5 @@ public abstract class Function {
* @return the value
* @throws HGSEvalException HGSEvalException
*/
public abstract Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException;
public abstract Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException;
}

View File

@ -38,8 +38,12 @@ public class ReferenceToFunc implements Reference {
@Override
public Object get(Context context) throws HGSEvalException {
Object funcObj = parent.get(context);
if (funcObj instanceof Function)
return ((Function) funcObj).calcValue(context, args);
if (funcObj instanceof Function) {
final Function func = (Function) funcObj;
if (func.getArgCount() >= 0 && func.getArgCount() != args.size())
throw new HGSEvalException("wrong number of arguments! found: " + args.size() + ", expected: " + func.getArgCount());
return func.callWithExpressions(context, args);
}
throw new HGSEvalException("Value is not a function!");
}
}

View File

@ -36,14 +36,14 @@ public class VHDLTemplate implements VHDLEntity {
.addFunc("value", new FunctionValue())
.addFunc("beginGenericPort", new Function(0) {
@Override
public Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException {
public Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException {
c.setVar("portStartPos", c.length());
return null;
}
})
.addFunc("endGenericPort", new Function(0) {
@Override
public Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException {
public Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException {
int start = Value.toInt(c.getVar("portStartPos"));
String portDecl = c.toString().substring(start);
c.setVar("portDecl", portDecl);
@ -52,7 +52,7 @@ public class VHDLTemplate implements VHDLEntity {
})
.addFunc("registerGeneric", new Function(-1) {
@Override
public Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException {
public Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException {
List<Generic> generics;
if (c.contains("generics"))
generics = (List<Generic>) c.getVar("generics");

View File

@ -300,25 +300,25 @@ public class ParserTest extends TestCase {
}
public void testFirstClassFunctionStatic() throws IOException, ParserException, HGSEvalException {
Parser p = new Parser("<? @f=func(a){return=a*a+2;}; print(f(4));?>");
Parser p = new Parser("<? @f=func(a){return a*a+2;}; print(f(4));?>");
p.parse();
Object fObj = p.getStaticContext().getVar("f");
assertTrue(fObj instanceof FirstClassFunction);
FirstClassFunction f = (FirstClassFunction) fObj;
assertEquals(11L, f.f(3));
assertTrue(fObj instanceof FuncAdapter);
FuncAdapter f = (FuncAdapter) fObj;
assertEquals(11L, f.call(3));
}
public void testFirstClassFunction() throws IOException, ParserException, HGSEvalException {
assertEquals("18", exec("<? f=func(a){return=a*a+2;}; print(f(4));?>").toString());
assertEquals("5", exec("<? f=func(a,b){return=a+2*b;}; print(f(1,2));?>").toString());
assertEquals("13", exec("<? f=func(a,b){return=a+2*b;}; print(f(1,a*2));?>",
assertEquals("18", exec("<? f=func(a){return a*a+2;}; print(f(4));?>").toString());
assertEquals("5", exec("<? f=func(a,b){return a+2*b;}; print(f(1,2));?>").toString());
assertEquals("13", exec("<? f=func(a,b){return a+2*b;}; print(f(1,a*2));?>",
new Context().setVar("a", 3)).toString());
assertEquals("18", exec("<? m=newMap(); m.f=func(a){return=newMap(); return.v=a*a+2;}; print(m.f(4).v);?>").toString());
assertEquals("18", exec("<? m=newList(); m[0]=func(a){ l=newList(); l[0]=a*a+2; return=l;}; print(m[0](4)[0]);?>").toString());
assertEquals("18", exec("<? m=newMap(); m.f=func(a){m=newMap(); m.v=a*a+2; return m;}; print(m.f(4).v);?>").toString());
assertEquals("18", exec("<? m=newList(); m[0]=func(a){ l=newList(); l[0]=a*a+2; return l;}; print(m[0](4)[0]);?>").toString());
try {
assertEquals("18", exec("<? f=func(a){return=a;}; f(1)=5; ?>").toString());
assertEquals("18", exec("<? f=func(a){return a;}; f(1)=5; ?>").toString());
fail();
} catch (HGSEvalException e) {
}
@ -326,9 +326,25 @@ public class ParserTest extends TestCase {
public void testFirstClassFunctionOutput() throws IOException, ParserException, HGSEvalException {
assertEquals("testtext12testtext15",
exec("<? f=func(a){ ?>testtext<? print(a*3); return=output; }; print(f(4),f(5));?>").toString());
exec("<? f=func(a){ ?>testtext<? print(a*3); return output; }; print(f(4),f(5));?>").toString());
}
public void testFirstClassFunctionLambda() throws IOException, ParserException, HGSEvalException {
Context c = exec("<? outer=5; f=func(x) {return x+outer;}; ?>");
FuncAdapter f = c.getFunction("f");
assertEquals(6L,f.call(1));
assertEquals(7L,f.call(2));
c = exec("<? f=func(x){ return func(u){return u*x;};}; a=f(2); b=f(5); ?>");
FuncAdapter a = c.getFunction("a");
FuncAdapter b = c.getFunction("b");
assertEquals(4L,a.call(2));
assertEquals(6L,a.call(3));
assertEquals(10L,b.call(2));
assertEquals(15L,b.call(3));
}
public void testPanic() throws IOException, ParserException, HGSEvalException {
Statement s = new Parser("<? if (i>1) panic(\"myError\"); ?>").parse();