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 24eea23ce..bcd911f07 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/Context.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/Context.java @@ -5,6 +5,11 @@ */ package de.neemann.digital.hdl.hgs; +import de.neemann.digital.hdl.hgs.function.Func; +import de.neemann.digital.hdl.hgs.function.FunctionFormat; +import de.neemann.digital.hdl.hgs.function.FunctionIsSet; + +import java.util.ArrayList; import java.util.HashMap; /** @@ -14,13 +19,12 @@ public class Context { private final Context parent; private final StringBuilder code; private HashMap map; - private int recordStart = 0; /** * Creates a new context */ public Context() { - this(null, new StringBuilder()); + this(null, true); } /** @@ -29,19 +33,27 @@ public class Context { * @param parent the parent context */ public Context(Context parent) { - this(parent, null); + this(parent, true); } /** * Creates a new context * - * @param parent the parent context - * @param code the code + * @param parent the parent context + * @param enablePrint enables the print, if false, the printing goes to the parent of this context */ - public Context(Context parent, StringBuilder code) { + public Context(Context parent, boolean enablePrint) { this.parent = parent; - this.code = code; + if (enablePrint) + this.code = new StringBuilder(); + else + this.code = null; map = new HashMap<>(); + // some function which are always present + map.put("format", new FunctionFormat()); + map.put("isset", new FunctionIsSet()); + map.put("newMap", new Func(0, args -> new HashMap())); + map.put("newList", new Func(0, args -> new ArrayList())); } /** @@ -104,9 +116,8 @@ public class Context { public Context print(String str) { if (code != null) code.append(str); - else { + else parent.print(str); - } return this; } @@ -115,7 +126,7 @@ public class Context { if (code != null) return code.toString(); else - return map.toString(); + return parent.toString(); } /** diff --git a/src/main/java/de/neemann/digital/hdl/hgs/Expression.java b/src/main/java/de/neemann/digital/hdl/hgs/Expression.java index 0aaa25b55..206a29bc6 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/Expression.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/Expression.java @@ -5,6 +5,8 @@ */ package de.neemann.digital.hdl.hgs; +import de.neemann.digital.hdl.hgs.function.Function; + /** * Simple expression * Created by Helmut.Neemann on 02.12.2016. @@ -73,6 +75,19 @@ public interface Expression { throw new EvalException("must be a string, is a " + value.getClass().getSimpleName()); } + /** + * Converts the given value to a function + * + * @param value the value to convert + * @return the function + * @throws EvalException EvalException + */ + static Function toFunc(Object value) throws EvalException { + if (value instanceof Function) + return (Function) value; + throw new EvalException("must be a function, is a " + value.getClass().getSimpleName()); + } + /** * Compares two values * 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 d569781f9..78bc25e9b 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/Parser.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/Parser.java @@ -6,19 +6,13 @@ package de.neemann.digital.hdl.hgs; import de.neemann.digital.core.Bits; -import de.neemann.digital.hdl.hgs.function.Function; -import de.neemann.digital.hdl.hgs.function.FunctionFormat; -import de.neemann.digital.hdl.hgs.function.FunctionIsSet; -import de.neemann.digital.hdl.hgs.refs.Reference; -import de.neemann.digital.hdl.hgs.refs.ReferenceToArray; -import de.neemann.digital.hdl.hgs.refs.ReferenceToStruct; -import de.neemann.digital.hdl.hgs.refs.ReferenceToVar; +import de.neemann.digital.hdl.hgs.function.FirstClassFunction; +import de.neemann.digital.hdl.hgs.refs.*; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; -import java.util.HashMap; import static de.neemann.digital.hdl.hgs.Tokenizer.Token.*; @@ -28,7 +22,6 @@ import static de.neemann.digital.hdl.hgs.Tokenizer.Token.*; public class Parser { private final Tokenizer tok; - private HashMap functions; private Context staticContext; /** @@ -47,44 +40,13 @@ public class Parser { */ public Parser(Reader reader) { tok = new Tokenizer(reader); - functions = new HashMap<>(); - addFunction("format", new FunctionFormat()); - - addFunction("isset", new FunctionIsSet()); - - addFunction("newList", new Function(0) { - @Override - public Object calcValue(Context c, ArrayList args) { - return new ArrayList<>(); - } - }); - - addFunction("newMap", new Function(0) { - @Override - public Object calcValue(Context c, ArrayList args) { - return new HashMap<>(); - } - }); - staticContext = new Context(); } - /** - * Adds a new function to the parser - * - * @param name the name - * @param function the function - * @return this for chained calls - */ - public Parser addFunction(String name, Function function) { - functions.put(name, function); - return this; - } - /** * Parses the given template source * - * @return the Statemant to execute + * @return the Statement to execute * @throws IOException IOException * @throws ParserException ParserException */ @@ -136,13 +98,8 @@ public class Parser { } else if (isToken(ADD)) { expect(ADD); return c -> ref.set(c, Expression.add(ref.get(c), 1)); - } else if (isToken(OPEN)) { - ArrayList args = parseArgList(); - expect(SEMICOLON); - if (ref instanceof ReferenceToVar) { - return findFunctionStatement(((ReferenceToVar) ref).getName(), args); - } else - throw newParserException("method call on composite var"); + } else if (isToken(SEMICOLON)) { + return ref::get; } else throw newUnexpectedToken(tok.next()); } else if (isToken(CODEEND)) { @@ -223,6 +180,9 @@ public class Parser { Expression index = parseExpression(); expect(CLOSEDSQUARE); r = new ReferenceToArray(r, index); + } else if (isToken(OPEN)) { + ArrayList args = parseArgList(); + r = new ReferenceToFunc(r, args); } else if (isToken(DOT)) { expect(IDENT); r = new ReferenceToStruct(r, tok.getIdent()); @@ -414,13 +374,8 @@ public class Parser { switch (t) { case IDENT: String name = tok.getIdent(); - if (isToken(OPEN)) { - ArrayList args = parseArgList(); - return findFunction(name, args); - } else { - Reference r = parseReference(name); - return r::get; - } + Reference r = parseReference(name); + return r::get; case NUMBER: long num = convToLong(tok.getIdent()); return c -> num; @@ -461,38 +416,4 @@ public class Parser { return new FirstClassFunction(args, st); } - private Expression findFunction(String name, ArrayList args) throws ParserException { - Function f = functions.get(name); - if (f != null) { - if (f.getArgCount() != args.size() && f.getArgCount() >= 0) - throw newParserException("function " + name + " needs " + f.getArgCount() + "arguments, but found " + args.size()); - return c -> f.calcValue(c, args); - } else { - return c -> { - Object func = c.getVar(name); - if (func instanceof FirstClassFunction) - return ((FirstClassFunction) func).calcValue(c, args); - else - throw new EvalException("first class function " + name + " not found"); - }; - } - } - - private Statement findFunctionStatement(String name, ArrayList args) throws ParserException { - Function f = functions.get(name); - if (f != null) { - if (f.getArgCount() != args.size() && f.getArgCount() >= 0) - throw newParserException("function " + name + " needs " + f.getArgCount() + "arguments, but found " + args.size()); - return c -> f.calcValue(c, args); - } else { - return c -> { - Object func = c.getVar(name); - if (func instanceof FirstClassFunction) - ((FirstClassFunction) func).calcValue(c, args); - else - throw new EvalException("first class function " + name + " not found"); - }; - } - } - } diff --git a/src/main/java/de/neemann/digital/hdl/hgs/FirstClassFunction.java b/src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunction.java similarity index 51% rename from src/main/java/de/neemann/digital/hdl/hgs/FirstClassFunction.java rename to src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunction.java index b87f7e2ad..ff80425a7 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/FirstClassFunction.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/function/FirstClassFunction.java @@ -3,14 +3,18 @@ * 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; +package de.neemann.digital.hdl.hgs.function; + +import de.neemann.digital.hdl.hgs.Context; +import de.neemann.digital.hdl.hgs.EvalException; +import de.neemann.digital.hdl.hgs.Statement; import java.util.ArrayList; /** * callable first class function */ -public class FirstClassFunction { +public class FirstClassFunction extends FuncAdapter { private final ArrayList args; private final Statement st; @@ -21,6 +25,7 @@ public class FirstClassFunction { * @param st the function body */ public FirstClassFunction(ArrayList args, Statement st) { + super(args.size()); this.args = args; this.st = st; } @@ -32,30 +37,16 @@ public class FirstClassFunction { * @return the result * @throws EvalException EvalException */ - public Object evaluate(Object... args) throws EvalException { - if (args.length != this.args.size()) - throw new EvalException("wrong number of arguments! found: " + args.length + ", expected: " + this.args.size()); - + @Override + public Object f(Object... args) throws EvalException { Context c = new Context(); for (int i = 0; i < args.length; i++) c.setVar(this.args.get(i), args[i]); st.execute(c); - return c.getVar("return"); + if (c.contains("return")) + return c.getVar("return"); + else + throw new EvalException("A function must define the variable 'return'."); } - /** - * Calls a first class function - * - * @param context the context - * @param args the arguments - * @return the result - * @throws EvalException EvalException - */ - public Object calcValue(Context context, ArrayList args) throws EvalException { - Object[] data = new Object[args.size()]; - for (int i = 0; i < args.size(); i++) - data[i] = args.get(i).value(context); - - return evaluate(data); - } } diff --git a/src/main/java/de/neemann/digital/hdl/hgs/function/Func.java b/src/main/java/de/neemann/digital/hdl/hgs/function/Func.java new file mode 100644 index 000000000..2d580c6f2 --- /dev/null +++ b/src/main/java/de/neemann/digital/hdl/hgs/function/Func.java @@ -0,0 +1,46 @@ +/* + * 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.EvalException; + +/** + * A function. + * Can be used to define a function by a lambda expression. + */ +public class Func extends FuncAdapter { + private final Interface func; + + /** + * Creates a new function + * + * @param argCount the number of arguments + * @param func the function + */ + public Func(int argCount, Interface func) { + super(argCount); + this.func = func; + } + + @Override + protected Object f(Object... args) throws EvalException { + return func.f(args); + } + + /** + * A fimple function + */ + public interface Interface { + /** + * Evaluates the function + * + * @param args the arguments + * @return the result + * @throws EvalException EvalException + */ + Object f(Object... args) throws EvalException; + } +} 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 0d67662b7..ec9142689 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 @@ -12,26 +12,35 @@ import de.neemann.digital.hdl.hgs.Expression; import java.util.ArrayList; /** - * Simple function adapter to implement a function with one argument of type long + * Simple function adapter to implement a function with already evaluated arguments */ public abstract class FuncAdapter extends Function { /** * Creates a new function + * + * @param argCount the number of arguments */ - public FuncAdapter() { - super(1); + public FuncAdapter(int argCount) { + super(argCount); } @Override public Object calcValue(Context c, ArrayList args) throws EvalException { - return f(Expression.toLong(args.get(0).value(c))); + if (getArgCount() != args.size()) + throw new EvalException("wrong number of arguments! found: " + args.size() + ", expected: " + args.size()); + + Object[] data = new Object[args.size()]; + for (int i = 0; i < args.size(); i++) + data[i] = args.get(i).value(c); + return f(data); } /** * The function * - * @param n the argument + * @param args the evaluated arguments * @return the result + * @throws EvalException EvalException */ - protected abstract Object f(long n); + protected abstract Object f(Object... args) throws EvalException; } diff --git a/src/main/java/de/neemann/digital/hdl/hgs/package-info.java b/src/main/java/de/neemann/digital/hdl/hgs/package-info.java index af7d74939..6738dbd4e 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/package-info.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/package-info.java @@ -5,7 +5,7 @@ */ /** - * Simple parser to parse text templates. + * Classes to parse HG Script (HDL Generator Scripting language). * The user should not see any error messages from this template engine. * Therefore a translation of error messages is not necessary! */ diff --git a/src/main/java/de/neemann/digital/hdl/hgs/refs/Reference.java b/src/main/java/de/neemann/digital/hdl/hgs/refs/Reference.java index e3455d7ee..0e43858ca 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/refs/Reference.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/refs/Reference.java @@ -9,7 +9,7 @@ import de.neemann.digital.hdl.hgs.Context; import de.neemann.digital.hdl.hgs.EvalException; /** - * A reference to a variable + * A reference to a value */ public interface Reference { /** 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 new file mode 100644 index 000000000..587890ea2 --- /dev/null +++ b/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToFunc.java @@ -0,0 +1,45 @@ +/* + * 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.refs; + +import de.neemann.digital.hdl.hgs.Context; +import de.neemann.digital.hdl.hgs.EvalException; +import de.neemann.digital.hdl.hgs.Expression; +import de.neemann.digital.hdl.hgs.function.Function; + +import java.util.ArrayList; + +/** + * A reference to a function + */ +public class ReferenceToFunc implements Reference { + private final Reference parent; + private final ArrayList args; + + /** + * Creates a new instance + * + * @param parent the parent reference + * @param args the arguments of the function + */ + public ReferenceToFunc(Reference parent, ArrayList args) { + this.parent = parent; + this.args = args; + } + + @Override + public void set(Context context, Object value) throws EvalException { + throw new EvalException("set to function is not possible"); + } + + @Override + public Object get(Context context) throws EvalException { + Object funcObj = parent.get(context); + if (funcObj instanceof Function) + return ((Function) funcObj).calcValue(context, args); + throw new EvalException("value is not a function"); + } +} diff --git a/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToStruct.java b/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToStruct.java index 12949fc4e..1288c88cf 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToStruct.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToStruct.java @@ -54,5 +54,4 @@ public class ReferenceToStruct implements Reference { throw new EvalException("not a map: " + m); } - } diff --git a/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToVar.java b/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToVar.java index 17227682c..aca863c23 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToVar.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/refs/ReferenceToVar.java @@ -32,11 +32,4 @@ public class ReferenceToVar implements Reference { public Object get(Context context) throws EvalException { return context.getVar(name); } - - /** - * @return the name of the variable - */ - public String getName() { - return name; - } } 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 6001a4a75..6e525a7dc 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 @@ -8,6 +8,7 @@ package de.neemann.digital.hdl.vhdl.lib; import de.neemann.digital.core.element.Key; import de.neemann.digital.core.element.Keys; import de.neemann.digital.hdl.hgs.*; +import de.neemann.digital.hdl.hgs.function.FirstClassFunction; import de.neemann.digital.hdl.hgs.function.FuncAdapter; import de.neemann.digital.hdl.hgs.function.Function; import de.neemann.digital.hdl.model.HDLException; @@ -49,17 +50,20 @@ public class VHDLTemplate implements VHDLEntity { if (inputStream == null) throw new IOException("file not present: " + createFileName(entityName)); try (Reader in = new InputStreamReader(inputStream, "utf-8")) { - Parser p = new Parser(in) - .addFunction("type", new FunctionType()) - .addFunction("value", new FunctionValue()) - .addFunction("beginGenericPort", new Function(0) { + Parser p = new Parser(in); + statements = p.parse(); + staticContext = p.getStaticContext(); + staticContext + .setVar("type", new FunctionType()) + .setVar("value", new FunctionValue()) + .setVar("beginGenericPort", new Function(0) { @Override public Object calcValue(Context c, ArrayList args) throws EvalException { c.setVar("portStartPos", c.length()); return null; } }) - .addFunction("endGenericPort", new Function(0) { + .setVar("endGenericPort", new Function(0) { @Override public Object calcValue(Context c, ArrayList args) throws EvalException { int start = Expression.toInt(c.getVar("portStartPos")); @@ -68,7 +72,7 @@ public class VHDLTemplate implements VHDLEntity { return null; } }) - .addFunction("registerGeneric", new Function(1) { + .setVar("registerGeneric", new Function(1) { @Override public Object calcValue(Context c, ArrayList args) throws EvalException { List generics; @@ -83,8 +87,7 @@ public class VHDLTemplate implements VHDLEntity { return null; } }); - statements = p.parse(); - staticContext = p.getStaticContext(); + } catch (ParserException e) { throw new IOException("error parsing template", e); } @@ -180,7 +183,7 @@ public class VHDLTemplate implements VHDLEntity { if (staticContext.contains("entityName")) { Object funcObj = staticContext.getVar("entityName"); if (funcObj instanceof FirstClassFunction) - name = ((FirstClassFunction) funcObj).evaluate(node.getAttributes()).toString(); + name = ((FirstClassFunction) funcObj).f(node.getAttributes()).toString(); else name = funcObj.toString(); } @@ -208,7 +211,7 @@ public class VHDLTemplate implements VHDLEntity { private Entity(HDLNode node, String name) throws EvalException { this.name = name; - final Context c = new Context(staticContext, new StringBuilder()) + final Context c = new Context(staticContext) .setVar("elem", node.getAttributes()); statements.execute(c); code = c.toString(); @@ -248,13 +251,20 @@ public class VHDLTemplate implements VHDLEntity { } private final static class FunctionType extends FuncAdapter { + + private FunctionType() { + super(1); + } + @Override - protected Object f(long n) { + protected Object f(Object... args) throws EvalException { + int n = Expression.toInt(args[0]); if (n == 1) return "std_logic"; else return "std_logic_vector (" + (n - 1) + " downto 0)"; } + } private final class FunctionValue extends Function { 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 1601687da..87136538c 100644 --- a/src/test/java/de/neemann/digital/hdl/hgs/ParserTest.java +++ b/src/test/java/de/neemann/digital/hdl/hgs/ParserTest.java @@ -7,11 +7,17 @@ package de.neemann.digital.hdl.hgs; import de.neemann.digital.core.element.ElementAttributes; import de.neemann.digital.core.element.Keys; +import de.neemann.digital.hdl.hgs.function.FirstClassFunction; import de.neemann.digital.hdl.hgs.function.FuncAdapter; +import de.neemann.digital.hdl.hgs.function.Function; import junit.framework.TestCase; +import java.io.File; +import java.io.FileReader; import java.io.IOException; +import java.io.Reader; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -222,40 +228,40 @@ public class ParserTest extends TestCase { } public void testAddFunction() throws IOException, ParserException, EvalException { - Statement s = new Parser("a : in ;") - .addFunction("type", new FuncAdapter() { - @Override - protected Object f(long n) { - if (n == 1) - return "std_logic"; - else - return "std_logic_vector(" + (n - 1) + " downto 0)"; - } - }) - .parse(); + Statement s = new Parser("a : in ;").parse(); + Context funcs = new Context().setVar("type", new FuncAdapter(1) { + @Override + protected Object f(Object... args) throws EvalException { + int n = Expression.toInt(args[0]); + if (n == 1) + return "std_logic"; + else + return "std_logic_vector(" + (n - 1) + " downto 0)"; + } + }); assertEquals("a : in std_logic;", - exec(s, new Context() + exec(s, new Context(funcs) .setVar("elem", new ElementAttributes())).toString()); assertEquals("a : in std_logic_vector(5 downto 0);", - exec(s, new Context() + exec(s, new Context(funcs) .setVar("elem", new ElementAttributes().setBits(6))).toString()); } - long flag = 0; + int flag = 0; public void testFunctionAsStatement() throws IOException, ParserException, EvalException { flag = 0; - Statement s = new Parser("a : in ;") - .addFunction("type", new FuncAdapter() { - @Override - protected Object f(long n) { - flag = n; - return null; - } - }) - .parse(); - assertEquals("a : in ;", exec(s).toString()); - assertEquals(7L, flag); + Statement s = new Parser("a : in ;").parse(); + + Context c = new Context().setVar("type", new FuncAdapter(1) { + @Override + protected Object f(Object... args) throws EvalException { + flag = Expression.toInt(args[0]); + return null; + } + }); + assertEquals("a : in ;", exec(s, c).toString()); + assertEquals(7, flag); } public void testStatic() throws IOException, ParserException { @@ -274,7 +280,7 @@ public class ParserTest extends TestCase { Object fObj = p.getStaticContext().getVar("f"); assertTrue(fObj instanceof FirstClassFunction); FirstClassFunction f = (FirstClassFunction) fObj; - assertEquals(11L, f.evaluate(3)); + assertEquals(11L, f.f(3)); } public void testFirstClassFunction() throws IOException, ParserException, EvalException { @@ -282,6 +288,9 @@ public class ParserTest extends TestCase { assertEquals("5", exec("").toString()); assertEquals("13", exec("", new Context().setVar("a", 3)).toString()); + + assertEquals("18", exec("").toString()); + } public void testFirstClassFunctionOutput() throws IOException, ParserException, EvalException {