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 2d891ced2..b6a32b367 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/Context.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/Context.java @@ -12,6 +12,7 @@ import de.neemann.digital.lang.Lang; import java.util.ArrayList; import java.util.HashMap; +import java.util.Locale; /** * The evaluation context @@ -254,7 +255,7 @@ public class Context { for (int i = 1; i < args.size(); i++) eval.add(args.get(i).value(c)); - return String.format(Value.toString(args.get(0).value(c)), eval.toArray()); + return String.format(Locale.US, Value.toString(args.get(0).value(c)), eval.toArray()); } private static final class FunctionIsPresent extends InnerFunction { 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 b4f59f8e4..3789d3e9b 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/Parser.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/Parser.java @@ -70,7 +70,7 @@ public class Parser { /** * Creates a new instance * - * @param reader the reader to parse + * @param reader the reader to parse * @param srcFile the source file name if any */ public Parser(Reader reader, String srcFile) { @@ -323,6 +323,14 @@ public class Parser { } } + private double convToDouble(String num) throws ParserException { + try { + return Double.parseDouble(num); + } catch (NumberFormatException e) { + throw newParserException("not a number: " + tok.getIdent()); + } + } + private ParserException newUnexpectedToken(Tokenizer.Token token) { String name = token == IDENT ? tok.getIdent() : token.name(); return newParserException("unexpected Token: " + name); @@ -356,13 +364,13 @@ public class Parser { case NOTEQUAL: return c -> !Value.equals(a.value(c), b.value(c)); case LESS: - return c -> Value.toLong(a.value(c)) < Value.toLong(b.value(c)); + return c -> Value.less(a.value(c), b.value(c)); case LESSEQUAL: - return c -> Value.toLong(a.value(c)) <= Value.toLong(b.value(c)); + return c -> Value.lessEqual(a.value(c), b.value(c)); case GREATER: - return c -> Value.toLong(a.value(c)) > Value.toLong(b.value(c)); + return c -> Value.less(b.value(c), a.value(c)); case GREATEREQUAL: - return c -> Value.toLong(a.value(c)) >= Value.toLong(b.value(c)); + return c -> Value.lessEqual(b.value(c), a.value(c)); default: throw newUnexpectedToken(t); } @@ -435,7 +443,7 @@ public class Parser { while (nextIs(SUB)) { Expression a = ac; Expression b = parseMul(); - ac = c -> Value.toLong(a.value(c)) - Value.toLong(b.value(c)); + ac = c -> Value.sub(a.value(c), b.value(c)); } return ac; } @@ -445,7 +453,7 @@ public class Parser { while (nextIs(MUL)) { Expression a = ac; Expression b = parseDiv(); - ac = c -> Value.toLong(a.value(c)) * Value.toLong(b.value(c)); + ac = c -> Value.mul(a.value(c), b.value(c)); } return ac; } @@ -455,7 +463,7 @@ public class Parser { while (nextIs(DIV)) { Expression a = ac; Expression b = parseMod(); - ac = c -> Value.toLong(a.value(c)) / Value.toLong(b.value(c)); + ac = c -> Value.div(a.value(c), b.value(c)); } return ac; } @@ -480,6 +488,9 @@ public class Parser { case NUMBER: long num = convToLong(tok.getIdent()); return c -> num; + case DOUBLE: + double d = convToDouble(tok.getIdent()); + return c -> d; case STRING: String s = tok.getIdent(); return c -> s; 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 1cfdc42cf..2bde538e6 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, RETURN, COLON, UNTIL + REPEAT, RETURN, COLON, UNTIL, DOUBLE } private static HashMap statementMap = new HashMap<>(); @@ -275,6 +275,9 @@ class Tokenizer { c = readChar(); if (isNumberChar(c) || isHexChar(c) || c == 'x' || c == 'X') { builder.append((char) c); + } else if (c == '.') { + builder.append((char) c); + token = Token.DOUBLE; } else { unreadChar(c); wasChar = false; diff --git a/src/main/java/de/neemann/digital/hdl/hgs/Value.java b/src/main/java/de/neemann/digital/hdl/hgs/Value.java index f9a843e96..15f6893c1 100644 --- a/src/main/java/de/neemann/digital/hdl/hgs/Value.java +++ b/src/main/java/de/neemann/digital/hdl/hgs/Value.java @@ -123,7 +123,9 @@ public final class Value { * @return true if both values are equal */ public static boolean equals(Object a, Object b) { - if (a instanceof Number && b instanceof Number) + if (a instanceof Double || b instanceof Double) + return a.equals(b); + else if (a instanceof Number && b instanceof Number) return ((Number) a).longValue() == ((Number) b).longValue(); else if (a instanceof String || b instanceof String) return a.toString().equals(b.toString()); @@ -140,6 +142,8 @@ public final class Value { * @throws HGSEvalException HGSEvalException */ public static Object add(Object a, Object b) throws HGSEvalException { + if (a instanceof Double || b instanceof Double) + return toDouble(a) + toDouble(b); if (a instanceof Number && b instanceof Number) return ((Number) a).longValue() + ((Number) b).longValue(); if (a instanceof String || b instanceof String) @@ -147,6 +151,54 @@ public final class Value { throw new HGSEvalException("arguments must be int or string, not " + a.getClass().getSimpleName() + "+" + b.getClass().getSimpleName()); } + /** + * Subtracts two values + * + * @param a a value + * @param b a value + * @return the sum + * @throws HGSEvalException HGSEvalException + */ + public static Object sub(Object a, Object b) throws HGSEvalException { + if (a instanceof Double || b instanceof Double) + return toDouble(a) - toDouble(b); + if (a instanceof Number && b instanceof Number) + return ((Number) a).longValue() - ((Number) b).longValue(); + throw new HGSEvalException("arguments must be int or double, not " + a.getClass().getSimpleName() + "+" + b.getClass().getSimpleName()); + } + + /** + * Multiplies two values + * + * @param a a value + * @param b a value + * @return the product + * @throws HGSEvalException HGSEvalException + */ + public static Object mul(Object a, Object b) throws HGSEvalException { + if (a instanceof Double || b instanceof Double) + return toDouble(a) * toDouble(b); + if (a instanceof Number && b instanceof Number) + return ((Number) a).longValue() * ((Number) b).longValue(); + throw new HGSEvalException("arguments must be int or double, not " + a.getClass().getSimpleName() + "+" + b.getClass().getSimpleName()); + } + + /** + * Divides two numbers + * + * @param a a value + * @param b a value + * @return the quotient + * @throws HGSEvalException HGSEvalException + */ + public static Object div(Object a, Object b) throws HGSEvalException { + if (a instanceof Double || b instanceof Double) + return toDouble(a) / toDouble(b); + if (a instanceof Number && b instanceof Number) + return ((Number) a).longValue() / ((Number) b).longValue(); + throw new HGSEvalException("arguments must be int or double, not " + a.getClass().getSimpleName() + "+" + b.getClass().getSimpleName()); + } + /** * Performs an or operation * @@ -202,6 +254,42 @@ public final class Value { return !toBool(value); } + /** + * Helper compare two values + * + * @param a a value + * @param b a value + * @return true if a<b + * @throws HGSEvalException HGSEvalException + */ + public static boolean less(Object a, Object b) throws HGSEvalException { + if (a instanceof Double || b instanceof Double) + return toDouble(a) < toDouble(b); + if (a instanceof Number && b instanceof Number) + return toLong(a) < toLong(b); + if (a instanceof String && b instanceof String) + return a.toString().compareTo(b.toString()) < 0; + throw new HGSEvalException("arguments must be int, double or string, not " + a.getClass().getSimpleName() + "+" + b.getClass().getSimpleName()); + } + + /** + * Helper compare two values + * + * @param a a value + * @param b a value + * @return true if a<=b + * @throws HGSEvalException HGSEvalException + */ + public static boolean lessEqual(Object a, Object b) throws HGSEvalException { + if (a instanceof Double || b instanceof Double) + return toDouble(a) <= toDouble(b); + if (a instanceof Number && b instanceof Number) + return toLong(a) <= toLong(b); + if (a instanceof String && b instanceof String) + return a.toString().compareTo(b.toString()) <= 0; + throw new HGSEvalException("arguments must be int, double or string, not " + a.getClass().getSimpleName() + "+" + b.getClass().getSimpleName()); + } + /** * Trims spaces at the right side of the string. * 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 a5e9139c5..0132885b2 100644 --- a/src/test/java/de/neemann/digital/hdl/hgs/ParserTest.java +++ b/src/test/java/de/neemann/digital/hdl/hgs/ParserTest.java @@ -78,6 +78,21 @@ public class ParserTest extends TestCase { assertEquals("Hallo_true", new Parser("\"Hallo_\" + (1<2)").parseExp().value(new Context())); } + public void testParseExpArithDouble() throws IOException, ParserException, HGSEvalException { + assertEquals(4.0, new Parser("1.5+2.5").parseExp().value(new Context())); + assertEquals(0.5, new Parser("1.5-1").parseExp().value(new Context())); + assertEquals(3.0, new Parser("1.5*2").parseExp().value(new Context())); + assertEquals(3.0, new Parser("2*1.5").parseExp().value(new Context())); + assertEquals(1L, new Parser("3/2").parseExp().value(new Context())); + assertEquals(1.5, new Parser("3.0/2").parseExp().value(new Context())); + assertEquals(1.5, new Parser("3/2.0").parseExp().value(new Context())); + assertEquals(1.5, new Parser("3.0/2.0").parseExp().value(new Context())); + assertEquals(true, new Parser("1.0001>1").parseExp().value(new Context())); + assertEquals(false, new Parser("1>1.0001").parseExp().value(new Context())); + assertEquals(false, new Parser("1.0001<1").parseExp().value(new Context())); + assertEquals(true, new Parser("1<1.0001").parseExp().value(new Context())); + } + public void testParseExpCompare() throws IOException, ParserException, HGSEvalException { assertEquals(true, new Parser("5=5").parseExp().value(new Context())); assertEquals(true, new Parser("\"Hello\"=\"Hello\"").parseExp().value(new Context())); @@ -85,6 +100,10 @@ public class ParserTest extends TestCase { assertEquals(false, new Parser("5!=5").parseExp().value(new Context())); assertEquals(false, new Parser("\"Hello\"!=\"Hello\"").parseExp().value(new Context())); assertEquals(true, new Parser("\"Hello\"!=\"World\"").parseExp().value(new Context())); + assertEquals(true, new Parser("\"a\"<\"b\"").parseExp().value(new Context())); + assertEquals(false, new Parser("\"b\"<\"a\"").parseExp().value(new Context())); + assertEquals(false, new Parser("\"a\">\"b\"").parseExp().value(new Context())); + assertEquals(true, new Parser("\"b\">\"a\"").parseExp().value(new Context())); assertEquals(false, new Parser("5<5").parseExp().value(new Context())); assertEquals(true, new Parser("4<5").parseExp().value(new Context())); @@ -265,6 +284,10 @@ public class ParserTest extends TestCase { Context c = new Context().declareVar("Bits", 17); exec("", c); assertEquals("hex=11;", c.toString()); + + c = new Context().declareVar("freq", Math.PI*100); + exec("", c); + assertEquals("f=314.16;", c.toString()); } public void testComment() throws IOException, ParserException, HGSEvalException {