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 Context parent;
private final StringBuilder code; private final StringBuilder code;
private HashMap<String, Object> map; private HashMap<String, Object> map;
private boolean functionContext = false;
/** /**
* Creates a new context * Creates a new context
@ -156,6 +157,45 @@ public class Context {
return parent.length(); 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 static final class FunctionPrint extends Function {
private FunctionPrint() { private FunctionPrint() {
@ -163,7 +203,7 @@ public class Context {
} }
@Override @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) for (Expression arg : args)
c.print(arg.value(c).toString()); c.print(arg.value(c).toString());
return null; return null;
@ -177,7 +217,7 @@ public class Context {
} }
@Override @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)); c.print(format(c, args));
return null; return null;
} }
@ -190,7 +230,7 @@ public class Context {
} }
@Override @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); return format(c, args);
} }
} }
@ -213,7 +253,7 @@ public class Context {
} }
@Override @Override
public Object calcValue(Context c, ArrayList<Expression> args) { public Object callWithExpressions(Context c, ArrayList<Expression> args) {
try { try {
args.get(0).value(c); args.get(0).value(c);
return true; return true;
@ -233,4 +273,28 @@ public class Context {
throw new HGSEvalException(args[0].toString()); 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.core.Bits;
import de.neemann.digital.hdl.hgs.function.FirstClassFunction; import de.neemann.digital.hdl.hgs.function.FirstClassFunction;
import de.neemann.digital.hdl.hgs.function.FirstClassFunctionCall;
import de.neemann.digital.hdl.hgs.refs.*; import de.neemann.digital.hdl.hgs.refs.*;
import java.io.IOException; import java.io.IOException;
@ -179,6 +180,10 @@ public class Parser {
while (!nextIs(CLOSEDBRACE)) while (!nextIs(CLOSEDBRACE))
s.add(parseStatement()); s.add(parseStatement());
return s.optimize(); return s.optimize();
case RETURN:
Expression retExp = parseExpression();
expect(SEMICOLON);
return c -> c.returnFromFunc(retExp.value(c));
default: default:
throw newUnexpectedToken(token); throw newUnexpectedToken(token);
} }
@ -413,7 +418,7 @@ public class Parser {
return exp; return exp;
case FUNC: case FUNC:
FirstClassFunction func = parseFunction(); FirstClassFunction func = parseFunction();
return c -> func; return c -> new FirstClassFunctionCall(func, c);
default: default:
throw newUnexpectedToken(t); 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, 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, 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, OPENBRACE, CLOSEDBRACE, CODEEND, OPENSQUARE, CLOSEDSQUARE, DOT, STATIC, FUNC, GREATEREQUAL, LESSEQUAL,
REPEAT, UNTIL REPEAT, RETURN, UNTIL
} }
private static HashMap<String, Token> statementMap = new HashMap<>(); private static HashMap<String, Token> statementMap = new HashMap<>();
@ -31,6 +31,7 @@ class Tokenizer {
statementMap.put("func", Token.FUNC); statementMap.put("func", Token.FUNC);
statementMap.put("repeat", Token.REPEAT); statementMap.put("repeat", Token.REPEAT);
statementMap.put("until", Token.UNTIL); statementMap.put("until", Token.UNTIL);
statementMap.put("return", Token.RETURN);
} }
private final Reader in; private final Reader in;

View File

@ -5,48 +5,33 @@
*/ */
package de.neemann.digital.hdl.hgs.function; 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 de.neemann.digital.hdl.hgs.Statement;
import java.util.ArrayList; 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 ArrayList<String> args;
private final Statement st; private final Statement statement;
/** /**
* Creates a new instance * Creates a new instance
* *
* @param args the names of the arguments * @param args the names of the arguments
* @param st the function body * @param statement the function body
*/ */
public FirstClassFunction(ArrayList<String> args, Statement st) { public FirstClassFunction(ArrayList<String> args, Statement statement) {
super(args.size());
this.args = args; this.args = args;
this.st = st; this.statement = statement;
} }
/** ArrayList<String> getArgs() {
* Evaluates this function return args;
*
* @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'!");
} }
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 @Override
public Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException { public Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException {
if (getArgCount() != args.size())
throw new HGSEvalException("wrong number of arguments! found: " + args.size() + ", expected: " + getArgCount());
Object[] data = new Object[args.size()]; Object[] data = new Object[args.size()];
for (int i = 0; i < args.size(); i++) for (int i = 0; i < args.size(); i++)
data[i] = args.get(i).value(c); 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 * @return the result
* @throws HGSEvalException HGSEvalException * @throws HGSEvalException HGSEvalException
*/ */
protected abstract Object f(Object... args) throws 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 * @return the value
* @throws HGSEvalException HGSEvalException * @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 @Override
public Object get(Context context) throws HGSEvalException { public Object get(Context context) throws HGSEvalException {
Object funcObj = parent.get(context); Object funcObj = parent.get(context);
if (funcObj instanceof Function) if (funcObj instanceof Function) {
return ((Function) funcObj).calcValue(context, args); 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!"); throw new HGSEvalException("Value is not a function!");
} }
} }

View File

@ -36,14 +36,14 @@ public class VHDLTemplate implements VHDLEntity {
.addFunc("value", new FunctionValue()) .addFunc("value", new FunctionValue())
.addFunc("beginGenericPort", new Function(0) { .addFunc("beginGenericPort", new Function(0) {
@Override @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()); c.setVar("portStartPos", c.length());
return null; return null;
} }
}) })
.addFunc("endGenericPort", new Function(0) { .addFunc("endGenericPort", new Function(0) {
@Override @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")); int start = Value.toInt(c.getVar("portStartPos"));
String portDecl = c.toString().substring(start); String portDecl = c.toString().substring(start);
c.setVar("portDecl", portDecl); c.setVar("portDecl", portDecl);
@ -52,7 +52,7 @@ public class VHDLTemplate implements VHDLEntity {
}) })
.addFunc("registerGeneric", new Function(-1) { .addFunc("registerGeneric", new Function(-1) {
@Override @Override
public Object calcValue(Context c, ArrayList<Expression> args) throws HGSEvalException { public Object callWithExpressions(Context c, ArrayList<Expression> args) throws HGSEvalException {
List<Generic> generics; List<Generic> generics;
if (c.contains("generics")) if (c.contains("generics"))
generics = (List<Generic>) c.getVar("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 { 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(); p.parse();
Object fObj = p.getStaticContext().getVar("f"); Object fObj = p.getStaticContext().getVar("f");
assertTrue(fObj instanceof FirstClassFunction); assertTrue(fObj instanceof FuncAdapter);
FirstClassFunction f = (FirstClassFunction) fObj; FuncAdapter f = (FuncAdapter) fObj;
assertEquals(11L, f.f(3)); assertEquals(11L, f.call(3));
} }
public void testFirstClassFunction() throws IOException, ParserException, HGSEvalException { public void testFirstClassFunction() throws IOException, ParserException, HGSEvalException {
assertEquals("18", exec("<? f=func(a){return=a*a+2;}; print(f(4));?>").toString()); 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("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("13", exec("<? f=func(a,b){return a+2*b;}; print(f(1,a*2));?>",
new Context().setVar("a", 3)).toString()); 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=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()); 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 { 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(); fail();
} catch (HGSEvalException e) { } catch (HGSEvalException e) {
} }
@ -326,9 +326,25 @@ public class ParserTest extends TestCase {
public void testFirstClassFunctionOutput() throws IOException, ParserException, HGSEvalException { public void testFirstClassFunctionOutput() throws IOException, ParserException, HGSEvalException {
assertEquals("testtext12testtext15", 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 { public void testPanic() throws IOException, ParserException, HGSEvalException {
Statement s = new Parser("<? if (i>1) panic(\"myError\"); ?>").parse(); Statement s = new Parser("<? if (i>1) panic(\"myError\"); ?>").parse();