refactoring and added a static context

This commit is contained in:
hneemann 2018-03-12 19:36:03 +01:00
parent a5d104076a
commit 11588d270f
9 changed files with 193 additions and 134 deletions

View File

@ -42,7 +42,7 @@ public class Context {
*
* @param name the name
* @return the value
* @throws EvalException {@link EvalException
* @throws EvalException EvalException
*/
public Object getVar(String name) throws EvalException {
Object v = map.get(name);

View File

@ -84,7 +84,7 @@ public interface Expression {
static Object add(Object a, Object b) throws EvalException {
if (a instanceof Number && b instanceof Number)
return ((Number) a).longValue() + ((Number) b).longValue();
if (a instanceof String && b instanceof String)
if (a instanceof String || b instanceof String)
return a.toString() + b.toString();
throw new EvalException("arguments must be int or string, not " + a.getClass().getSimpleName() + "+" + b.getClass().getSimpleName());
}

View File

@ -29,6 +29,7 @@ public class Parser {
private final Tokenizer tok;
private HashMap<String, Function> functions;
private Context staticContext;
/**
* Create a new instance
@ -47,23 +48,37 @@ public class Parser {
public Parser(Reader reader) {
tok = new Tokenizer(reader);
functions = new HashMap<>();
functions.put("format", new FunctionFormat());
addFunction("format", new FunctionFormat());
functions.put("isset", new FunctionIsSet());
addFunction("isset", new FunctionIsSet());
functions.put("newList", new Function(0) {
addFunction("newList", new Function(0) {
@Override
public Object calcValue(Context c, ArrayList<Expression> args) {
return new ArrayList<>();
}
});
functions.put("newMap", new Function(0) {
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;
}
/**
@ -78,12 +93,27 @@ public class Parser {
String text = tok.readText();
if (text.length() > 0)
s.add(c -> c.print(text));
while (!isToken(EOF))
s.add(parseStatement());
while (!isToken(EOF)) {
if (isToken(STATIC)) {
Statement stat = parseStatements();
try {
stat.execute(staticContext);
} catch (EvalException e) {
throw newParserException("error evaluating static code: " + e.getMessage());
}
} else
s.add(parseStatement());
}
return s.optimize();
}
/**
* @return the static context of this template
*/
public Context getStaticContext() {
return staticContext;
}
private Statement parseStatements() throws IOException, ParserException {
if (isToken(OPENBRACE)) {
Statements s = new Statements();

View File

@ -1,77 +0,0 @@
/*
* 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;
import java.io.*;
/**
* Represents a template
*/
public class Template {
private final Statement s;
/**
* Creates a new template.
*
* @param text the text to parse
* @throws IOException IOException
* @throws ParserException ParserException
*/
public Template(String text) throws IOException, ParserException {
this(new StringReader(text));
}
/**
* Creates a new template.
*
* @param in the text to parse
* @throws IOException IOException
* @throws ParserException ParserException
*/
public Template(InputStream in) throws IOException, ParserException {
this(new InputStreamReader(in));
}
/**
* Creates a new template.
*
* @param reader the text to parse
* @throws IOException IOException
* @throws ParserException ParserException
*/
public Template(Reader reader) throws IOException, ParserException {
try {
this.s = new Parser(reader).parse();
} finally {
reader.close();
}
}
/**
* Evaluates the template with an empty context
*
* @return the context
* @throws EvalException EvalException
*/
public Context execute() throws EvalException {
return execute(new Context());
}
/**
* Evaluates the template with the given context.
* Create a new context every time this method is called!
*
* @param c the context
* @return the context
* @throws EvalException EvalException
*/
public Context execute(Context c) throws EvalException {
s.execute(c);
return c;
}
}

View File

@ -17,7 +17,7 @@ public class Tokenizer {
enum Token {
UNKNOWN, IDENT, AND, OR, XOR, NOT, OPEN, CLOSE, NUMBER, EOL, EOF, SHIFTLEFT, SHIFTRIGHT, COMMA, EQUAL,
ADD, SUB, MUL, GREATER, SMALER, DIV, MOD, END, IF, ELSE, FOR, WHILE, SEMICOLON, NOTEQUAL, STRING,
OPENBRACE, CLOSEDBRACE, CODEEND, OPENSQUARE, CLOSEDSQUARE, DOT, PRINT, PRINTF
OPENBRACE, CLOSEDBRACE, CODEEND, OPENSQUARE, CLOSEDSQUARE, DOT, PRINT, STATIC, PRINTF
}
private static HashMap<String, Token> statementMap = new HashMap<>();
@ -112,6 +112,9 @@ public class Tokenizer {
case '.':
token = Token.DOT;
break;
case '@':
token = Token.STATIC;
break;
case ';':
token = Token.SEMICOLON;
break;

View File

@ -0,0 +1,37 @@
/*
* 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.EvalException;
import de.neemann.digital.hdl.hgs.Expression;
import java.util.ArrayList;
/**
* Simple function adapter to implement a function with one argument of type long
*/
public abstract class FuncAdapter extends Function {
/**
* Creates a new function
*/
public FuncAdapter() {
super(1);
}
@Override
public Object calcValue(Context c, ArrayList<Expression> args) throws EvalException {
return f(Expression.toLong(args.get(0).value(c)));
}
/**
* The function
*
* @param n the argument
* @return the result
*/
protected abstract Object f(long n);
}

View File

@ -32,23 +32,29 @@ public class ReferenceToArray implements Reference {
@Override
public void set(Context context, Object value) throws EvalException {
Object ar = parent.get(context);
if (ar instanceof List) {
final List list = (List) ar;
final int index = Expression.toInt(this.index.value(context));
while (list.size() <= index)
Object listObj = parent.get(context);
if (listObj instanceof List) {
final List list = (List) listObj;
final int i = Expression.toInt(index.value(context));
if (i < 0)
throw new EvalException("index out of bounds: " + i);
while (list.size() <= i)
list.add(null);
list.set(index, value);
list.set(i, value);
} else
throw new EvalException("not an array: " + ar);
throw new EvalException("not an array: " + listObj);
}
@Override
public Object get(Context context) throws EvalException {
Object ar = parent.get(context);
if (ar instanceof List)
return ((List) ar).get(Expression.toInt(index.value(context)));
else
throw new EvalException("not an array: " + ar);
Object listObj = parent.get(context);
if (listObj instanceof List) {
final List list = (List) listObj;
final int i = Expression.toInt(index.value(context));
if (i >= list.size() || i < 0)
throw new EvalException("index out of bounds: " + i);
return list.get(i);
} else
throw new EvalException("not an array: " + listObj);
}
}

View File

@ -31,7 +31,7 @@ public class ReferenceToStruct implements Reference {
Key key = (Key) k.get(null);
KEY_MAP.put(key.getKey(), key);
} catch (IllegalAccessException e) {
throw new RuntimeException("error ecessing the Keys");
throw new RuntimeException("error accessing the Keys");
}
}
}
@ -54,7 +54,7 @@ public class ReferenceToStruct implements Reference {
if (m instanceof Map)
((Map) m).put(name, value);
else
throw new EvalException("not a map");
throw new EvalException("not a map: " + m);
}
@Override
@ -62,13 +62,13 @@ public class ReferenceToStruct implements Reference {
Object m = parent.get(context);
if (m instanceof Map)
return ((Map) m).get(name);
if (m instanceof ElementAttributes) {
else if (m instanceof ElementAttributes) {
Key key = KEY_MAP.get(name);
if (key == null)
throw new EvalException("invallid key: " + name);
throw new EvalException("invalid key: " + name);
return ((ElementAttributes) m).get(key);
} else
throw new EvalException("not a map");
throw new EvalException("not a map: " + m);
}

View File

@ -7,9 +7,10 @@ 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.FuncAdapter;
import junit.framework.TestCase;
import java.io.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -36,13 +37,8 @@ public class ParserTest extends TestCase {
assertTrue(true);
}
try {
new Parser("1+\"Hallo\"").parseExpression().value(new Context());
fail();
} catch (EvalException e) {
assertTrue(true);
}
assertEquals("Hallo4", new Parser("\"Hallo\" + (2*2)").parseExpression().value(new Context()));
assertEquals("Hallo_true", new Parser("\"Hallo_\" + (1<2)").parseExpression().value(new Context()));
}
public void testParseExpressionCompare() throws IOException, ParserException, EvalException {
@ -82,73 +78,89 @@ public class ParserTest extends TestCase {
.value(new Context()
.setVar("a", true)));
assertEquals("true", new Template("<? if (1) print(\"true\"); ?>").execute().toString());
assertEquals("", new Template("<? if (0) print(\"true\"); ?>").execute().toString());
assertEquals("true", exec("<? if (1) print(\"true\"); ?>").toString());
assertEquals("", exec("<? if (0) print(\"true\"); ?>").toString());
}
private Context exec(String code) throws IOException, ParserException, EvalException {
return exec(code, new Context());
}
private Context exec(String code, Context c) throws IOException, ParserException, EvalException {
new Parser(code).parse().execute(c);
return c;
}
public void testParseTemplateSimple() throws IOException, ParserException, EvalException {
Context c = new Template("Hello World!").execute();
assertEquals("Hello World!", c.toString());
assertEquals("Hello World!", exec("Hello World!", new Context()).toString());
}
private Context exec(Statement s) throws EvalException {
return exec(s, new Context());
}
private Context exec(Statement s, Context c) throws EvalException {
s.execute(c);
return c;
}
public void testParseTemplateVariable() throws IOException, ParserException, EvalException {
Context c = new Template("Hello <? =a ?> World!").execute(new Context().setVar("a", "My"));
Context c = exec("Hello <? =a ?> World!", new Context().setVar("a", "My"));
assertEquals("Hello My World!", c.toString());
}
public void testParseTemplateCodeOnly() throws IOException, ParserException, EvalException {
Context c = new Template("<? =a ?>").execute(new Context().setVar("a", "My"));
Context c = exec("<? =a ?>", new Context().setVar("a", "My"));
assertEquals("My", c.toString());
}
public void testParseTemplatePrint() throws IOException, ParserException, EvalException {
Context c = new Template("Hello <? print(\"My\"); ?> World!").execute();
Context c = exec("Hello <? print(\"My\"); ?> World!");
assertEquals("Hello My World!", c.toString());
}
public void testParseTemplateFor() throws IOException, ParserException, EvalException {
Context c = new Template("Hello <? for (i=0;i<10;i++) print(i); ?> World!").execute();
Context c = exec("Hello <? for (i=0;i<10;i++) print(i); ?> World!");
assertEquals("Hello 0123456789 World!", c.toString());
}
public void testParseTemplateForStatements() throws IOException, ParserException, EvalException {
Context c = new Template("<? for (i=0;i<10;i++) { print(i, 9-i); } ?>").execute();
Context c = exec("<? for (i=0;i<10;i++) { print(i, 9-i); } ?>");
assertEquals("09182736455463728190", c.toString());
}
public void testParseTemplateForNested() throws IOException, ParserException, EvalException {
Context c = new Template("Hello <? for (i=0;i<10;i++) { ?>n<? } ?> World!").execute();
Context c = exec("Hello <? for (i=0;i<10;i++) { ?>n<? } ?> World!");
assertEquals("Hello nnnnnnnnnn World!", c.toString());
}
public void testParseTemplateForNested2() throws IOException, ParserException, EvalException {
Context c = new Template("Hello <? for (i=0;i<3;i++) { ?>(<? for(j=0;j<2;j++) { ?>:<? } ?>)<? } ?> World!").execute();
Context c = exec("Hello <? for (i=0;i<3;i++) { ?>(<? for(j=0;j<2;j++) { ?>:<? } ?>)<? } ?> World!");
assertEquals("Hello (::)(::)(::) World!", c.toString());
}
public void testParseTemplateForNested3() throws IOException, ParserException, EvalException {
Context c = new Template("Hello <? for (i=1;i<4;i++) { ?>(<? for(j=1;j<3;j++) { print(i*j); } ?>)<? } ?> World!").execute();
Context c = exec("Hello <? for (i=1;i<4;i++) { ?>(<? for(j=1;j<3;j++) { print(i*j); } ?>)<? } ?> World!");
assertEquals("Hello (12)(24)(36) World!", c.toString());
}
public void testParseTemplateElementAttibutes() throws IOException, ParserException, EvalException {
ElementAttributes attr = new ElementAttributes().set(Keys.BITS, 5);
Context c = new Context().setVar("elem", attr);
new Template("bits=<?=elem.Bits?>;").execute(c);
Context c = exec("bits=<?=elem.Bits?>;", new Context().setVar("elem", attr));
assertEquals("bits=5;", c.toString());
}
public void testParseTemplateFormat() throws IOException, ParserException, EvalException {
ElementAttributes attr = new ElementAttributes().set(Keys.BITS, 17);
Context c = new Context().setVar("elem", attr);
new Template("<? a=format(\"hex=%x;\",elem.Bits); print(a);?>").execute(c);
exec("<? a=format(\"hex=%x;\",elem.Bits); print(a);?>", c);
assertEquals("hex=11;", c.toString());
}
public void testParseTemplateArray() throws IOException, ParserException, EvalException {
Context c = new Context().setVar("a", new ArrayList());
new Template("<? a[0]=1; a[1]=7; print(a[1]); ?>;").execute(c);
exec("<? a[0]=1; a[1]=7; print(a[1]); ?>;", c);
assertEquals("7;", c.toString());
Object lo = c.getVar("a");
assertTrue(lo instanceof List);
@ -159,7 +171,7 @@ public class ParserTest extends TestCase {
}
public void testParseTemplateMap() throws IOException, ParserException, EvalException {
Context c = new Template("<? m=newMap(); m.test=newMap(); m.test.val=7; print(m.test.val); ?>;").execute();
Context c = exec("<? m=newMap(); m.test=newMap(); m.test.val=7; print(m.test.val); ?>;");
assertEquals("7;", c.toString());
Object mo = c.getVar("m");
assertTrue(mo instanceof Map);
@ -168,15 +180,63 @@ public class ParserTest extends TestCase {
assertEquals(7L, ((Map) mo).get("val"));
}
public void testParseTemplateMapError() throws IOException, ParserException {
try {
Context c = exec("<? m=1; m.test=2; ?>;");
fail();
} catch (EvalException e) {
assertTrue(true);
}
}
public void testParseTemplateArrayError() throws IOException, ParserException {
try {
Context c = exec("<? m=1; m[0]=2; ?>;");
fail();
} catch (EvalException e) {
assertTrue(true);
}
}
public void testParseTemplateIsSet() throws IOException, ParserException, EvalException {
Template t = new Template("<? if (isset(m)) print(m); else print(\"false\"); ?>;");
assertEquals("false;", t.execute().toString());
assertEquals("4;", t.execute(new Context().setVar("m", 4)).toString());
Statement t = new Parser("<? if (isset(m)) print(m); else print(\"false\"); ?>;").parse();
assertEquals("false;", exec(t).toString());
assertEquals("4;", exec(t, new Context().setVar("m", 4)).toString());
}
public void testComment() throws IOException, ParserException, EvalException {
Template t = new Template("<? // comment\nprint(\"false\"); // zzz\n ?>;");
assertEquals("false;", t.execute().toString());
Context c = exec("<? // comment\nprint(\"false\"); // zzz\n ?>;");
assertEquals("false;", c.toString());
}
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();
assertEquals("a : in std_logic;",
exec(s, new Context()
.setVar("elem", new ElementAttributes())).toString());
assertEquals("a : in std_logic_vector(5 downto 0);",
exec(s, new Context()
.setVar("elem", new ElementAttributes().setBits(6))).toString());
}
public void testStatic() throws IOException, ParserException {
Parser p = new Parser("generic a; <? @gen[0]=\"a\"; ?>");
final ArrayList<Object> generics = new ArrayList<>();
p.getStaticContext().setVar("gen", generics);
p.parse();
assertEquals(1, generics.size());
assertEquals("a", generics.get(0));
}