diff --git a/src/main/java/de/neemann/digital/hdl/hgs/Context.java b/src/main/java/de/neemann/digital/hdl/hgs/Context.java index c6f13dbcd..8c629f2d4 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/Context.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/Context.java @@ -19,6 +19,7 @@ public class Context { private final Context parent; private final StringBuilder code; private HashMap 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 args) throws HGSEvalException { + public Object callWithExpressions(Context c, ArrayList 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 args) throws HGSEvalException { + public Object callWithExpressions(Context c, ArrayList args) throws HGSEvalException { c.print(format(c, args)); return null; } @@ -190,7 +230,7 @@ public class Context { } @Override - public Object calcValue(Context c, ArrayList args) throws HGSEvalException { + public Object callWithExpressions(Context c, ArrayList args) throws HGSEvalException { return format(c, args); } } @@ -213,7 +253,7 @@ public class Context { } @Override - public Object calcValue(Context c, ArrayList args) { + public Object callWithExpressions(Context c, ArrayList 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; + } + } } diff --git a/src/main/java/de/neemann/digital/hdl/hgs/Parser.java b/src/main/java/de/neemann/digital/hdl/hgs/Parser.java index b2030f56a..f58d98fa1 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/Parser.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/Parser.java @@ -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); } diff --git a/src/main/java/de/neemann/digital/hdl/hgs/Tokenizer.java b/src/main/java/de/neemann/digital/hdl/hgs/Tokenizer.java index 071a885c6..ebb1e5d23 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/Tokenizer.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/Tokenizer.java @@ -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 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; diff --git a/src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunction.java b/src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunction.java index 79a5815ac..bd780d2c4 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunction.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunction.java @@ -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 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 args the names of the arguments + * @param statement the function body */ - public FirstClassFunction(ArrayList args, Statement st) { - super(args.size()); + public FirstClassFunction(ArrayList 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 getArgs() { + return args; } + Statement getStatement() { + return statement; + } } diff --git a/src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunctionCall.java b/src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunctionCall.java new file mode 100644 index 000000000..78e80cad5 --- /dev/null +++ b/src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunctionCall.java @@ -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(); + } + } +} diff --git a/src/main/java/de/neemann/digital/hdl/hgs/function/FuncAdapter.java b/src/main/java/de/neemann/digital/hdl/hgs/function/FuncAdapter.java index 54f693a4f..5449d3c17 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/function/FuncAdapter.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/function/FuncAdapter.java @@ -25,10 +25,7 @@ public abstract class FuncAdapter extends Function { } @Override - public Object calcValue(Context c, ArrayList 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 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); + } + } diff --git a/src/main/java/de/neemann/digital/hdl/hgs/function/Function.java b/src/main/java/de/neemann/digital/hdl/hgs/function/Function.java index bbf01f110..6054c43ae 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/function/Function.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/function/Function.java @@ -42,5 +42,5 @@ public abstract class Function { * @return the value * @throws HGSEvalException HGSEvalException */ - public abstract Object calcValue(Context c, ArrayList args) throws HGSEvalException; + public abstract Object callWithExpressions(Context c, ArrayList args) throws HGSEvalException; } diff --git a/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToFunc.java b/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToFunc.java index 92ecce3ad..45fd0b24a 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToFunc.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToFunc.java @@ -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!"); } } diff --git a/src/main/java/de/neemann/digital/hdl/vhdl/lib/VHDLTemplate.java b/src/main/java/de/neemann/digital/hdl/vhdl/lib/VHDLTemplate.java index fe6649872..c3820adb4 100644 --- a/src/main/java/de/neemann/digital/hdl/vhdl/lib/VHDLTemplate.java +++ b/src/main/java/de/neemann/digital/hdl/vhdl/lib/VHDLTemplate.java @@ -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 args) throws HGSEvalException { + public Object callWithExpressions(Context c, ArrayList args) throws HGSEvalException { c.setVar("portStartPos", c.length()); return null; } }) .addFunc("endGenericPort", new Function(0) { @Override - public Object calcValue(Context c, ArrayList args) throws HGSEvalException { + public Object callWithExpressions(Context c, ArrayList 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 args) throws HGSEvalException { + public Object callWithExpressions(Context c, ArrayList args) throws HGSEvalException { List generics; if (c.contains("generics")) generics = (List) c.getVar("generics"); diff --git a/src/test/java/de/neemann/digital/hdl/hgs/ParserTest.java b/src/test/java/de/neemann/digital/hdl/hgs/ParserTest.java index a6653df78..c53d4b596 100644 --- a/src/test/java/de/neemann/digital/hdl/hgs/ParserTest.java +++ b/src/test/java/de/neemann/digital/hdl/hgs/ParserTest.java @@ -300,25 +300,25 @@ public class ParserTest extends TestCase { } public void testFirstClassFunctionStatic() throws IOException, ParserException, HGSEvalException { - Parser p = new Parser(""); + Parser p = new Parser(""); 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("").toString()); - assertEquals("5", exec("").toString()); - assertEquals("13", exec("", + assertEquals("18", exec("").toString()); + assertEquals("5", exec("").toString()); + assertEquals("13", exec("", new Context().setVar("a", 3)).toString()); - assertEquals("18", exec("").toString()); - assertEquals("18", exec("").toString()); + assertEquals("18", exec("").toString()); + assertEquals("18", exec("").toString()); try { - assertEquals("18", exec("").toString()); + assertEquals("18", exec("").toString()); fail(); } catch (HGSEvalException e) { } @@ -326,9 +326,25 @@ public class ParserTest extends TestCase { public void testFirstClassFunctionOutput() throws IOException, ParserException, HGSEvalException { assertEquals("testtext12testtext15", - exec("testtext").toString()); + exec("testtext").toString()); } + public void testFirstClassFunctionLambda() throws IOException, ParserException, HGSEvalException { + Context c = exec(""); + FuncAdapter f = c.getFunction("f"); + assertEquals(6L,f.call(1)); + assertEquals(7L,f.call(2)); + + c = exec(""); + 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("1) panic(\"myError\"); ?>").parse();