diff --git a/src/main/java/com/annimon/ownlang/exceptions/UnknownPropertyException.java b/src/main/java/com/annimon/ownlang/exceptions/UnknownPropertyException.java new file mode 100644 index 0000000..4156c59 --- /dev/null +++ b/src/main/java/com/annimon/ownlang/exceptions/UnknownPropertyException.java @@ -0,0 +1,15 @@ +package com.annimon.ownlang.exceptions; + +public final class UnknownPropertyException extends RuntimeException { + + private final String propertyName; + + public UnknownPropertyException(String name) { + super("Unknown property " + name); + this.propertyName = name; + } + + public String getPropertyName() { + return propertyName; + } +} diff --git a/src/main/java/com/annimon/ownlang/lib/StringValue.java b/src/main/java/com/annimon/ownlang/lib/StringValue.java index 8a1ac22..f665d04 100644 --- a/src/main/java/com/annimon/ownlang/lib/StringValue.java +++ b/src/main/java/com/annimon/ownlang/lib/StringValue.java @@ -1,5 +1,6 @@ package com.annimon.ownlang.lib; +import com.annimon.ownlang.exceptions.UnknownPropertyException; import java.util.Objects; /** @@ -15,6 +16,19 @@ public final class StringValue implements Value { public StringValue(String value) { this.value = value; } + + public Value access(Value property) { + switch (property.asString()) { + // Properties + case "length": + return NumberValue.of(length()); + + // Functions + case "trim": + return new FunctionValue(args -> new StringValue(value.trim())); + } + throw new UnknownPropertyException(property.asString()); + } public int length() { return value.length(); @@ -82,5 +96,4 @@ public final class StringValue implements Value { public String toString() { return asString(); } - } diff --git a/src/main/java/com/annimon/ownlang/parser/Parser.java b/src/main/java/com/annimon/ownlang/parser/Parser.java index 2f5a270..58b35ae 100644 --- a/src/main/java/com/annimon/ownlang/parser/Parser.java +++ b/src/main/java/com/annimon/ownlang/parser/Parser.java @@ -782,7 +782,23 @@ public final class Parser { return new ValueExpression(createNumber(current.getText(), 16)); } if (match(TokenType.TEXT)) { - return new ValueExpression(current.getText()); + final ValueExpression strExpr = new ValueExpression(current.getText()); + // "text".property || "text".func() + if (lookMatch(0, TokenType.DOT)) { + if (lookMatch(1, TokenType.WORD) && lookMatch(2, TokenType.LPAREN)) { + match(TokenType.DOT); + return functionChain(new ContainerAccessExpression( + strExpr, Collections.singletonList( + new ValueExpression(consume(TokenType.WORD).getText()) + ))); + } + final List indices = variableSuffix(); + if (indices.isEmpty()) { + return strExpr; + } + return new ContainerAccessExpression(strExpr, indices); + } + return strExpr; } throw new ParseException("Unknown expression: " + current); } diff --git a/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java b/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java index 794b842..27bb676 100644 --- a/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java +++ b/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java @@ -48,6 +48,9 @@ public final class ContainerAccessExpression implements Expression, Accessible { case Types.MAP: return ((MapValue) container).get(lastIndex); + + case Types.STRING: + return ((StringValue) container).access(lastIndex); default: throw new TypeException("Array or map expected. Got " + Types.typeToString(container.type())); diff --git a/src/test/resources/other/stringFunctions.own b/src/test/resources/other/stringFunctions.own new file mode 100644 index 0000000..154f768 --- /dev/null +++ b/src/test/resources/other/stringFunctions.own @@ -0,0 +1,10 @@ +def testLength() { + assertEquals(3, "123".length) + s = "test" + assertEquals(4, s.length) +} + +def testTrim() { + s = " test " + assertEquals("test", s.trim()) +} \ No newline at end of file