simplification of first class functions

This commit is contained in:
hneemann 2018-03-13 15:27:39 +01:00
parent 70a1a8bc9d
commit 9f9d564bfa
13 changed files with 223 additions and 174 deletions

View File

@ -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<String, Object> 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();
}
/**

View File

@ -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
*

View File

@ -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<String, Function> 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<Expression> args) {
return new ArrayList<>();
}
});
addFunction("newMap", new Function(0) {
@Override
public Object calcValue(Context c, ArrayList<Expression> 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<Expression> 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<Expression> 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<Expression> 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<Expression> 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<Expression> 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");
};
}
}
}

View File

@ -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<String> args;
private final Statement st;
@ -21,6 +25,7 @@ public class FirstClassFunction {
* @param st the function body
*/
public FirstClassFunction(ArrayList<String> 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<Expression> 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);
}
}

View File

@ -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;
}
}

View File

@ -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<Expression> 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;
}

View File

@ -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!
*/

View File

@ -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 {
/**

View File

@ -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<Expression> args;
/**
* Creates a new instance
*
* @param parent the parent reference
* @param args the arguments of the function
*/
public ReferenceToFunc(Reference parent, ArrayList<Expression> 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");
}
}

View File

@ -54,5 +54,4 @@ public class ReferenceToStruct implements Reference {
throw new EvalException("not a map: " + m);
}
}

View File

@ -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;
}
}

View File

@ -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<Expression> 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<Expression> 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<Expression> args) throws EvalException {
List<String> 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 {

View File

@ -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 <?=type(elem.Bits)?>;")
.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 <?=type(elem.Bits)?>;").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 <? type(7); ?>;")
.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 <? type(7); ?>;").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("<? 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());
}
public void testFirstClassFunctionOutput() throws IOException, ParserException, EvalException {