Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Commit

Permalink
Change unit delimiter from [ ] to | (#5621)
Browse files Browse the repository at this point in the history
* Change unit delimiter from [ ] to |

...as the previous solution was conflicting with the XClosure rule.

The new syntax uses the pipe symbol as a delimiter. As long as the
unit symbols match the ID rule, they can be written as is, otherwise
they will have to noted as strings. In Script.xtext, some symbols are
included as keywords in order to facilitate the most commonly used
ones. For everything else, strings must be used.

The notation therefore looks like the following examples, whereby all
of them evaluate to the same QuantityType:

   20|°C
   20 | °C
   "20|"°C"
   20 | "°C"

The XBase language doesn't define bitwise operators, therefore I currently
do not see any potential conflics with other rules. The pipe symbol is used
in XClosure, but doesn't match the preceeding Number. Therefore this should
be okay.

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
  • Loading branch information
Simon Kaufmann authored and kaikreuzer committed May 24, 2018
1 parent 4e8fcd9 commit 626ed81
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public void testInterpreter() throws ScriptParsingException, ScriptExecutionExce
@SuppressWarnings("null")
@Test
public void testAssignQuantityType() throws ScriptParsingException, ScriptExecutionException {
runScript("NumberA.state = 20.0 [°C] as org.eclipse.smarthome.core.types.State");
runScript("NumberA.state = 20.0|°C as org.eclipse.smarthome.core.types.State");

State numberState = itemRegistry.get(NUMBER_ITEM_TEMPERATURE).getState();
assertNotNull(numberState);
Expand All @@ -123,7 +123,13 @@ public void testGreaterThanWithItemState() throws ScriptExecutionException, Scri
Item numberItem = itemRegistry.get(NUMBER_ITEM_TEMPERATURE);
((NumberItem) numberItem).setState(new QuantityType<>("20 °C"));

assertTrue(runScript("NumberA.state > 20 [°F]"));
assertTrue(runScript("NumberA.state > 20|°F"));
}

@Test
public void testSpacesDoNotMatter() throws ScriptExecutionException, ScriptParsingException {
assertTrue(runScript("20|°C == 20 | °C"));
assertTrue(runScript("20|\"°C\" == 20 | \"°C\""));
}

@SuppressWarnings("null")
Expand All @@ -132,7 +138,7 @@ public void testGreaterEqualsWithItemState() throws ScriptExecutionException, Sc
Item numberItem = itemRegistry.get(NUMBER_ITEM_TEMPERATURE);
((NumberItem) numberItem).setState(new QuantityType<>("20 °C"));

assertTrue(runScript("NumberA.state >= 20 [°C]"));
assertTrue(runScript("NumberA.state >= 20|°C"));
}

@SuppressWarnings("null")
Expand All @@ -141,7 +147,7 @@ public void testLessThanWithItemState() throws ScriptExecutionException, ScriptP
Item numberItem = itemRegistry.get(NUMBER_ITEM_TEMPERATURE);
((NumberItem) numberItem).setState(new QuantityType<>("20 °F"));

assertTrue(runScript("NumberA.state < 20 [°C]"));
assertTrue(runScript("NumberA.state < 20|°C"));
}

@SuppressWarnings("null")
Expand All @@ -150,7 +156,7 @@ public void testLessEqualsWithItemState() throws ScriptExecutionException, Scrip
Item numberItem = itemRegistry.get(NUMBER_ITEM_TEMPERATURE);
((NumberItem) numberItem).setState(new QuantityType<>("19 °F"));

assertTrue(runScript("NumberA.state <= 20 [°F]"));
assertTrue(runScript("NumberA.state <= 20|°F"));
}

@SuppressWarnings("null")
Expand All @@ -159,7 +165,7 @@ public void testEqualsWithItemState() throws ScriptExecutionException, ScriptPar
Item numberItem = itemRegistry.get(NUMBER_ITEM_TEMPERATURE);
((NumberItem) numberItem).setState(new QuantityType<>("20 °C"));

assertTrue(runScript("NumberA.state == 20 [°C]"));
assertTrue(runScript("NumberA.state == 20|°C"));
}

@SuppressWarnings("null")
Expand All @@ -168,7 +174,7 @@ public void testNotEqualsWithItemState() throws ScriptExecutionException, Script
Item numberItem = itemRegistry.get(NUMBER_ITEM_TEMPERATURE);
((NumberItem) numberItem).setState(new QuantityType<>("20 °C"));

assertTrue(runScript("NumberA.state != 10 [°C]"));
assertTrue(runScript("NumberA.state != 10|°C"));
}

@SuppressWarnings("null")
Expand All @@ -182,69 +188,69 @@ public void testGreaterThan_Number_Number() throws ScriptParsingException, Scrip

@Test
public void testCompareGreaterThanQuantityType() throws ScriptParsingException, ScriptExecutionException {
assertTrue(runScript("20.0 [°C] > 20 [°F]"));
assertTrue(runScript("20.0|°C > 20|°F"));
}

@Test
public void testCompareGreaterThanQuantityType_False() throws ScriptParsingException, ScriptExecutionException {
assertFalse(runScript("20.0 [°F] > 20 [°C]"));
assertFalse(runScript("20.0|°F > 20|°C"));
}

@Test
public void testCompareGreaterEqualsThanQuantityType() throws ScriptParsingException, ScriptExecutionException {
assertTrue(runScript("1 [m] >= 100 [cm]"));
assertTrue(runScript("1|m >= 100|cm"));
}

@Test
public void testCompareLessThanQuantityType_False() throws ScriptParsingException, ScriptExecutionException {
assertFalse(runScript("20.0 [°C] < 20 [°F]"));
assertFalse(runScript("20.0|°C < 20|°F"));
}

@Test
public void testCompareLessThanQuantityType() throws ScriptParsingException, ScriptExecutionException {
assertTrue(runScript("20.0 [°F] < 20 [°C]"));
assertTrue(runScript("20.0|°F < 20|°C"));
}

@Test
public void testCompareLessEqualsThanQuantityType() throws ScriptParsingException, ScriptExecutionException {
assertTrue(runScript("100 [cm] <= 1 [m]"));
assertTrue(runScript("100|cm <= 1|m"));
}

@Test
public void testpostUpdateQuantityType() throws ScriptParsingException, ScriptExecutionException {
scriptEngine.newScriptFromString("postUpdate(NumberA, 20.0 [°C])").execute();
scriptEngine.newScriptFromString("sendCommand(NumberA, 20.0 [°F])").execute();
scriptEngine.newScriptFromString("postUpdate(NumberA, 20.0|°C)").execute();
scriptEngine.newScriptFromString("sendCommand(NumberA, 20.0|°F)").execute();
}

@Test
public void testAssignAndCompareQuantityType() throws ScriptParsingException, ScriptExecutionException {
assertFalse(runScript(
"NumberA.state = 20.0 [°C] as org.eclipse.smarthome.core.types.State; NumberA.state < 20 [°F]"));
assertFalse(
runScript("NumberA.state = 20.0|°C as org.eclipse.smarthome.core.types.State; NumberA.state < 20|°F"));
}

@Test
public void testAddQuantityType() throws ScriptParsingException, ScriptExecutionException {
assertThat((QuantityType<?>) runScript("1 [m] + 20 [cm]"), is(QuantityType.valueOf("1.2 m")));
assertThat((QuantityType<?>) runScript("1|m + 20|cm"), is(QuantityType.valueOf("1.2 m")));
}

@Test
public void testSubtractQuantityType() throws ScriptParsingException, ScriptExecutionException {
assertThat((QuantityType<?>) runScript("1 [m] - 20 [cm]"), is(QuantityType.valueOf("0.8 m")));
assertThat((QuantityType<?>) runScript("1|m - 20|cm"), is(QuantityType.valueOf("0.8 m")));
}

@Test
public void testMultiplyQuantityType() throws ScriptParsingException, ScriptExecutionException {
assertThat((QuantityType<?>) runScript("1 [m] * 20 [cm]"), is(QuantityType.valueOf("2000 cm^2")));
assertThat((QuantityType<?>) runScript("1|m * 20|cm"), is(QuantityType.valueOf("2000 cm^2")));
}

@Test
public void testMultiplyQuantityType_Number() throws ScriptParsingException, ScriptExecutionException {
assertThat((QuantityType<?>) runScript("1 [m] * 20"), is(QuantityType.valueOf("20 m")));
assertThat((QuantityType<?>) runScript("1|m * 20"), is(QuantityType.valueOf("20 m")));
}

@Test
public void testDivideQuantityType() throws ScriptParsingException, ScriptExecutionException {
assertThat((QuantityType<?>) runScript("1 [m] / 2 [cm]"), is(QuantityType.valueOf("50")));
assertThat((QuantityType<?>) runScript("1|m / 2|cm"), is(QuantityType.valueOf("50")));
}

@SuppressWarnings("null")
Expand All @@ -253,68 +259,76 @@ public void testDivideItemState_QuantityType() throws ScriptParsingException, Sc
Item numberItem = itemRegistry.get(NUMBER_ITEM_LENGTH);
((NumberItem) numberItem).setState(new QuantityType<>("1 m"));

assertThat((QuantityType<?>) runScript("val length = NumberC.state as QuantityType; return length / 2 [cm];"),
assertThat((QuantityType<?>) runScript("val length = NumberC.state as QuantityType; return length / 2|cm;"),
is(QuantityType.valueOf("50")));
}

@Test
public void testDivideQuantityType_Number() throws ScriptParsingException, ScriptExecutionException {
assertThat((QuantityType<?>) runScript("1 [m] / 2"), is(QuantityType.valueOf("0.5 m")));
assertThat((QuantityType<?>) runScript("1|m / 2"), is(QuantityType.valueOf("0.5 m")));
}

@Test
public void testDivide_Number_QuantityType() throws ScriptParsingException, ScriptExecutionException {
assertThat((QuantityType<?>) runScript("1 / 2 [m]"), is(new QuantityType<>("0.5 one/m")));
assertThat((QuantityType<?>) runScript("1 / 2|m"), is(new QuantityType<>("0.5 one/m")));
}

@Test
public void testDivide_Number_QuantityType_1() throws ScriptParsingException, ScriptExecutionException {
assertThat((QuantityType<?>) runScript("0.5 [one/m] + 0.5 [one/m]"), is(new QuantityType<>("1 one/m")));
assertThat((QuantityType<?>) runScript("0.5|\"one/m\" + 0.5|\"one/m\""), is(new QuantityType<>("1 one/m")));
}

@Test
public void testDivide_Length_Time() throws ScriptParsingException, ScriptExecutionException {
assertThat((QuantityType<?>) runScript("100 [km] / 1 [h]"), is(new QuantityType<>("100 km/h")));
assertThat((QuantityType<?>) runScript("100|km / 1|h"), is(new QuantityType<>("100 km/h")));
}

@Test
public void testToUnit_QuantityType() throws ScriptParsingException, ScriptExecutionException {
assertThat(runScript("20 [°C].toUnit(\"°F\")"), is(new QuantityType<>("68 °F")));
assertThat(runScript("20|°C.toUnit(\"°F\")"), is(new QuantityType<>("68 °F")));
}

@Test
public void testEquals_QuantityType_Number() throws ScriptParsingException, ScriptExecutionException {
assertThat(runScript("20 [m].equals(20)"), is(false));
assertThat(runScript("20|m.equals(20)"), is(false));
}

@Test
public void testQuantityType_UnitSymbols() throws ScriptParsingException, ScriptExecutionException {
assertThat(runScript("20 [m²]"), is(new QuantityType<>(20, SIUnits.SQUARE_METRE)));
assertThat(runScript("20 [m**2]"), is(new QuantityType<>(20, SIUnits.SQUARE_METRE)));
assertThat(runScript("20 [m³]"), is(new QuantityType<>(20, SIUnits.SQUARE_METRE.multiply(SIUnits.METRE))));
assertThat(runScript("20 [m**3]"), is(new QuantityType<>(20, SIUnits.SQUARE_METRE.multiply(SIUnits.METRE))));
assertThat(runScript("1 [µm]"), is(new QuantityType<>(1, MetricPrefix.MICRO(SIUnits.METRE))));
assertThat(runScript("20|m²"), is(new QuantityType<>(20, SIUnits.SQUARE_METRE)));
assertThat(runScript("20|\"m**2\""), is(new QuantityType<>(20, SIUnits.SQUARE_METRE)));
assertThat(runScript("20|m³"), is(new QuantityType<>(20, SIUnits.SQUARE_METRE.multiply(SIUnits.METRE))));
assertThat(runScript("20|\"m**3\""), is(new QuantityType<>(20, SIUnits.SQUARE_METRE.multiply(SIUnits.METRE))));
assertThat(runScript("1|\"µm\""), is(new QuantityType<>(1, MetricPrefix.MICRO(SIUnits.METRE))));
}

@Test
public void testCompare_QuantityType_ONE_Number() throws ScriptParsingException, ScriptExecutionException {
assertThat(runScript("1 == 1 [one]"), is(true));
assertThat(runScript("1 [one] == 1"), is(true));
assertThat(runScript("1 == 1|one"), is(true));
assertThat(runScript("1|one == 1"), is(true));

assertThat(runScript("1 != 2 [one]"), is(true));
assertThat(runScript("2 [one] != 1"), is(true));
assertThat(runScript("1 != 2|one"), is(true));
assertThat(runScript("2|one != 1"), is(true));

assertThat(runScript("1 < 2 [one]"), is(true));
assertThat(runScript("1 [one] < 2"), is(true));
assertThat(runScript("1 < 2|one"), is(true));
assertThat(runScript("1|one < 2"), is(true));

assertThat(runScript("1 <= 1 [one]"), is(true));
assertThat(runScript("1 [one] <= 1"), is(true));
assertThat(runScript("1 <= 1|one"), is(true));
assertThat(runScript("1|one <= 1"), is(true));

assertThat(runScript("2 > 1 [one]"), is(true));
assertThat(runScript("2 [one] > 1"), is(true));
assertThat(runScript("2 > 1|one"), is(true));
assertThat(runScript("2|one > 1"), is(true));

assertThat(runScript("1 >= 1 [one]"), is(true));
assertThat(runScript("1 [one] >= 1"), is(true));
assertThat(runScript("1 >= 1|one"), is(true));
assertThat(runScript("1|one >= 1"), is(true));
}

@Test
public void testNoXbaseConflicts() throws ScriptParsingException, ScriptExecutionException {
assertEquals(42, (int) runScript("(1..3).forEach[x |println(x)]; return 42;"));
assertEquals(42, (int) runScript("92 % 50"));
assertEquals(true, (boolean) runScript("1 == 1 || 1 != 2"));
assertEquals("\\", runScript("return \"\\\\\""));
}

private Item createNumberItem(String numberItemName, Class<@NonNull ?> dimension) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,27 @@ XLiteral returns XExpression:
QuantityLiteral
;

QuantityLiteral returns XExpression :
{QuantityLiteral} value=Number unit=UNIT
QuantityLiteral returns XExpression:
{QuantityLiteral}
value=Number '|' unit=AbstractUnit
;

AbstractUnit:
StringUnit | IDUnit | SpecificUnit
;

StringUnit:
value=STRING
;

terminal UNIT:
'[' ('a' .. 'z'|'A' .. 'Z'|'°'|'/'|'Ω'|'℃'|'%'|'^'|'µ'|'²'|'³'|'**'|'-'|'0' .. '9')+ ']'
IDUnit:
value=ID
;

SpecificUnit:
value=COMMON_UNIT_SYMBOLS
;

COMMON_UNIT_SYMBOLS:
'°C' | '°F' | 'Ω' | '℃' | '°' | '%' | 'm²' | 'm³'
;
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public class ScriptInterpreter extends XbaseInterpreter {
}

def protected Object doEvaluate(QuantityLiteral literal, IEvaluationContext context, CancelIndicator indicator) {
return QuantityType.valueOf(literal.value + " " + literal.unit.substring(1,literal.unit.length-1));
return QuantityType.valueOf(literal.value + " " + literal.unit.value);
}

override Object _doEvaluate(XCastedExpression castedExpression, IEvaluationContext context,
Expand Down
19 changes: 13 additions & 6 deletions docs/_posts/2018-02-22-units-of-measurement.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,27 @@ From Paper UI, Basic UI and Classic UI numerous widgets bound to number items wi
The classic rule engine in Eclipse SmartHome also has full support of the new QuantityType. It can be used in arithmetic calculations and comparison with automatic unit conversion.
The `Weather_Temperature` item from the example above with the fixed unit kelvin can be compared against other QuantityType instances with other units from the Temperature dimension.

The script:
The scripts

```
20.0 [°C] > 20 [°F]
20.0|"°C" > 20|"°F"
```

evaluates to `true` as well as
and

```
postUpdate(myTemperature, 20.0 [°C])
20.0|°C > 20|°F
```

will create a new QuantityType and update the Number item `myTemperature`.
Note that a QuantityType in scripts must be written as `<value> [<unit>]`.
both evaluate to `true` and

```
postUpdate(myTemperature, 20.0|°C)
```

will create a new QuantityType and update the Number item `myTemperature` to 20°C.

Note that a QuantityType in scripts and rules must be written as `<value>|"<unit>"`. Some frequently used units and those which are valid identifiers can also ommit the quotation marks and can conveniently be written as `<value>|<unit>` (e.g. `20|°C`)

##### Coding the UoM
###### Packages
Expand Down

0 comments on commit 626ed81

Please sign in to comment.