improve DSL parsing
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
index c42b799..7e65aa4 100644
--- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
@@ -29,6 +29,7 @@
 import org.apache.brooklyn.camp.spi.resolve.PlanInterpreter.PlanInterpreterAdapter;
 import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode;
 import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode.Role;
+import org.apache.brooklyn.core.resolve.jackson.WrappedValue;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
@@ -159,6 +160,10 @@
             return ((QuotedString)f).unwrapped();
         }
 
+        if (f instanceof PropertyAccess) {
+            return ((PropertyAccess)f).getSelector();
+        }
+
         throw new IllegalArgumentException("Unexpected element in parse tree: '"+f+"' (type "+(f!=null ? f.getClass() : null)+")");
     }
     
@@ -201,6 +206,9 @@
         }
         try {
             Object index = propAccess.getSelector();
+            while (index instanceof PropertyAccess) {
+                index = ((PropertyAccess)index).getSelector();
+            }
             if (index instanceof QuotedString) {
                 index = ((QuotedString)index).unwrapped();
             }
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java
index 846a1ef..db82823 100644
--- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java
@@ -50,35 +50,32 @@
     public Object next() {
         int start = index;
 
-        boolean isProperty = (index > 0 && (expression.charAt(index -1) == '[' )) || // for [x] syntax - indexes in lists
+        boolean isAlreadyInBracket = (index > 0 && (expression.charAt(index -1) == '[' )) || // for [x] syntax - indexes in lists
             (index > 1 && (expression.charAt(index -1) == '"' && expression.charAt(index -2) == '[')) ;  // for ["x"] syntax - string properties and map keys
-        if (!isProperty) {
+        if (!isAlreadyInBracket) {
+            // feel like this block shouldn't be used; not sure it is
             skipWhitespace();
             if (index >= expression.length())
                 throw new IllegalStateException("Unexpected end of expression to parse, looking for content since position " + start);
 
             if (expression.charAt(index) == '"') {
                 // assume a string, that is why for property syntax using [x] or ["x"], we skip this part
-                int stringStart = index;
-                index++;
-                do {
-                    if (index >= expression.length())
-                        throw new IllegalStateException("Unexpected end of expression to parse, looking for close quote since position " + stringStart);
-                    char c = expression.charAt(index);
-                    if (c == '"') break;
-                    if (c == '\\') index++;
-                    index++;
-                } while (true);
-                index++;
-                return new QuotedString(expression.substring(stringStart, index));
+                return nextAsQuotedString();
             }
         }
         // not a string, must be a function (or chain thereof)
         List<Object> result = new MutableList<Object>();
 
+        skipWhitespace();
         int fnStart = index;
-        isProperty =  expression.charAt(fnStart) == '[';
-        if(!isProperty) {
+        boolean isBracketed = expression.charAt(fnStart) == '[';
+        if (isBracketed) {
+            if (isAlreadyInBracket)
+                throw new IllegalStateException("Nested brackets not permitted, at position "+start);
+            // proceed to below
+
+        } else {
+            // non-bracketed property access, skip separators
             do {
                 if (index >= expression.length())
                     break;
@@ -93,40 +90,74 @@
                 index++;
             } while (true);
         }
-        String fn = isProperty ? "" : expression.substring(fnStart, index);
-        if (fn.length()==0 && !isProperty)
-            throw new IllegalStateException("Expected a function name or double-quoted string at position "+start);
-        skipWhitespace();
 
-        if (index < expression.length() && ( expression.charAt(index)=='(' || expression.charAt(index)=='[')) {
+        String fn = isBracketed ? "" : expression.substring(fnStart, index);
+        if (fn.length()==0 && !isBracketed)
+            throw new IllegalStateException("Expected a function name or double-quoted string at position "+start);
+
+        if (index < expression.length() && ( isBracketed || expression.charAt(index)=='(')) {
             // collect arguments
             int parenStart = index;
             List<Object> args = new MutableList<>();
-            if (expression.charAt(index)=='[') {
+            if (expression.charAt(index)=='[' && !isBracketed) {
                 if (!fn.isEmpty()) {
                     return new PropertyAccess(fn);
                 }
-                if (expression.charAt(index +1)=='"') { index ++;} // for ["x"] syntax needs to be increased to extract the name of the property correctly
+                // not sure comes here
+                if (isBracketed)
+                    throw new IllegalStateException("Nested brackets not permitted, at position "+start);
+                isBracketed = true;
             }
-            index ++;
+            index++;
+            boolean justAdded = false;
             do {
                 skipWhitespace();
                 if (index >= expression.length())
                     throw new IllegalStateException("Unexpected end of arguments to function '"+fn+"', no close parenthesis matching character at position "+parenStart);
                 char c = expression.charAt(index);
-                if(isProperty && c =='"') { index++; break; } // increasing the index for ["x"] syntax to account for the presence of the '"'
-                if (c==')'|| c == ']') break;
-                if (c==',') {
-                    if (args.isEmpty())
-                        throw new IllegalStateException("Invalid character at position"+index);
-                    index++;
-                } else {
-                    if (!args.isEmpty() && !isProperty)
-                        throw new IllegalStateException("Expected , before position"+index);
+
+                if (c=='"') {
+                    if (justAdded)
+                        throw new IllegalStateException("Expected , before quoted string at position "+index);
+
+                    QuotedString next = nextAsQuotedString();
+                    if (isBracketed) args.add(next.unwrapped());
+                    else args.add(next);
+                    justAdded = true;
+                    continue;
                 }
+
+                if (c == ']') {
+                    if (!isBracketed)
+                        throw new IllegalStateException("Mismatched close bracket at position "+index);
+                    justAdded = false;
+                    break;
+                }
+                if (c==')') {
+                    justAdded = false;
+                    break;
+                }
+                if (c==',') {
+                    if (!justAdded)
+                        throw new IllegalStateException("Invalid character at position "+index);
+                    justAdded = false;
+                    index++;
+                    continue;
+                }
+
+                if (justAdded)  // did have this but don't think need it?: && !isProperty)
+                    throw new IllegalStateException("Expected , before position "+index);
+
                 args.add(next()); // call with first letter of the property
+                justAdded = true;
             } while (true);
 
+            if (justAdded) {
+                if (isBracketed) {
+                    throw new IllegalStateException("Expected ] at position " + index);
+                }
+            }
+
             if (fn.isEmpty()) {
                 Object arg = args.get(0);
                 if (arg instanceof PropertyAccess) {
@@ -182,10 +213,27 @@
         } else {
             // previously we returned a null-arg function; now we treat as explicit property access,
             // and places that need it as a function with arguments from a map key convert it on to new FunctionWithArgs(selector, null)
+
+            // ideally we'd have richer types here; sometimes it is property access, but sometimes just a wrapped non-quoted constant
             return new PropertyAccess(fn);
         }
     }
 
+    private QuotedString nextAsQuotedString() {
+        int stringStart = index;
+        index++;
+        do {
+            if (index >= expression.length())
+                throw new IllegalStateException("Unexpected end of expression to parse, looking for close quote since position " + stringStart);
+            char c = expression.charAt(index);
+            if (c == '"') break;
+            if (c == '\\') index++;
+            index++;
+        } while (true);
+        index++;
+        return new QuotedString(expression.substring(stringStart, index));
+    }
+
     private void skipWhitespace() {
         while (index<expression.length() && Character.isWhitespace(expression.charAt(index)))
             index++;
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/PropertyAccess.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/PropertyAccess.java
index 30ff04d..51578e1 100644
--- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/PropertyAccess.java
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/PropertyAccess.java
@@ -18,6 +18,9 @@
  */
 package org.apache.brooklyn.camp.brooklyn.spi.dsl.parse;
 
+import java.util.Objects;
+
+/** access a property in a map, position in a list, or otherwise a non-string (simple) expression being passed to a function */
 public class PropertyAccess {
     private final Object selector;
 
@@ -33,4 +36,15 @@
     public String toString() {
         return "PropertyAccess[" +selector + "]";
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof PropertyAccess)) return false;
+        return Objects.equals(selector, ((PropertyAccess)obj).selector);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(selector);
+    }
 }
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowExpressionsYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowExpressionsYamlTest.java
index 10c76f5..8c15898 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowExpressionsYamlTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WorkflowExpressionsYamlTest.java
@@ -367,8 +367,14 @@
         app.config().set(ConfigKeys.newConfigKey(Object.class, "index"), 0);
         Asserts.assertEquals( get.apply(MutableList.of(42), "$brooklyn:config(\"index\")"), 42);
 
+        Asserts.assertEquals( get.apply(MutableMap.of("k", MutableList.of(42)), "k"), MutableList.of(42));
         Asserts.assertEquals( get.apply(MutableMap.of("k", MutableList.of(42)), ".k"), MutableList.of(42));
         Asserts.assertEquals( get.apply(MutableMap.of("k", MutableList.of(42)), "[\"k\"][0]"), 42);
         Asserts.assertEquals( get.apply(MutableMap.of("k", MutableList.of(42)), ".k[0]"), 42);
+
+        Asserts.assertEquals( get.apply(MutableMap.of("w-hyphen", 42), "w-hyphen"), 42);
+        Asserts.assertFailsWith(()->get.apply(MutableMap.of("w-hyphen", 42), ".w-hyphen"),
+                Asserts.expectedFailureContainsIgnoreCase("unexpected character", "position 30"));
+        Asserts.assertEquals( get.apply(MutableMap.of("w-hyphen", 42), "[\"w-hyphen\"]"), 42);
     }
 }
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslParseTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslParseTest.java
index 79d2b67..62e366c 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslParseTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslParseTest.java
@@ -24,11 +24,13 @@
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.parse.FunctionWithArgs;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.parse.PropertyAccess;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.parse.QuotedString;
+import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
 import org.testng.annotations.Test;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Function;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
@@ -55,11 +57,20 @@
     }
     
     public void testParseMultiArgMultiTypeFunction() {
-        // TODO Parsing "f(\"x\", 1)" fails, because it interprets 1 as a function rather than a number. Is that expected?
         Object fx = new DslParser("f(\"x\", \"y\")").parse();
         fx = Iterables.getOnlyElement( (List<?>)fx );
         assertEquals( ((FunctionWithArgs)fx).getFunction(), "f" );
         assertEquals( ((FunctionWithArgs)fx).getArgs(), ImmutableList.of(new QuotedString("\"x\""), new QuotedString("\"y\"")));
+
+        fx = new DslParser("f(\"x\", 1)").parse();
+        fx = Iterables.getOnlyElement( (List<?>)fx );
+        assertEquals( ((FunctionWithArgs)fx).getFunction(), "f" );
+        assertEquals( ((FunctionWithArgs)fx).getArgs(), ImmutableList.of(new QuotedString("\"x\""), new PropertyAccess("1")));
+
+        fx = new DslParser("$brooklyn:formatString(\"%s-%s\", parent().attributeWhenReady(\"host.address\"), $brooklyn:attributeWhenReady(\"host.address\"))").parse();
+        fx = Iterables.getOnlyElement( (List<?>)fx );
+        Asserts.assertInstanceOf(fx, FunctionWithArgs.class);
+        Asserts.assertPasses((FunctionWithArgs)fx, fx0 -> Asserts.assertSize(fx0.getArgs(), 3));
     }
 
     
@@ -161,6 +172,22 @@
     }
 
     @Test
+    public void testParseMapValueVariousWays() {
+        Function<String,Object> accessor = suffix -> ((List) new DslParser("$brooklyn:literal(\"ignored\")"+suffix).parse()).get(1);
+        Asserts.assertPasses(accessor.apply("[\"a-b\"]"), v -> Asserts.assertEquals( ((PropertyAccess)v).getSelector(), "a-b" ));
+        Asserts.assertPasses(accessor.apply(".[\"a-b\"]"), v -> Asserts.assertEquals( ((PropertyAccess)v).getSelector(), "a-b" ));
+
+        Asserts.assertPasses(accessor.apply(".a"), v -> Asserts.assertEquals( ((PropertyAccess)v).getSelector(), "a" ));
+        Asserts.assertPasses(accessor.apply("[\"a\"]"), v -> Asserts.assertEquals( ((PropertyAccess)v).getSelector(), "a" ));
+
+        Asserts.assertFailsWith(() -> accessor.apply("a"), Asserts.expectedFailureContainsIgnoreCase(
+                "unexpected character", " 28 ", ")a"));
+        Asserts.assertFailsWith(() -> accessor.apply(".a-b"), Asserts.expectedFailureContainsIgnoreCase(
+                "unexpected character", " 30 ", "a-b"));
+    }
+
+
+        @Test
     public void testParseFunctionExplicit() {
         List fx = (List) new DslParser("$brooklyn:function.foo()").parse();
         assertEquals( ((FunctionWithArgs)fx.get(0)).getFunction(), "$brooklyn:function.foo" );
diff --git a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
index fedcd94..25459e8 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
@@ -1188,6 +1188,18 @@
         fail(Strings.isBlank(message) ? "Failed "+condition+": "+object : message);
     }
 
+    public static <T> void assertPasses(T object, java.util.function.Consumer<T> condition) {
+        assertPasses(object, condition, null);
+    }
+    public static <T> void assertPasses(T object, java.util.function.Consumer<T> condition, String message) {
+        try {
+            condition.accept(object);
+        } catch (Throwable t) {
+            if (message!=null) throw new AssertionError(message, t);
+            else throw Exceptions.propagate(t);
+        }
+    }
+
     public static void assertStringContains(String input, String phrase1ToContain, String ...optionalOtherPhrasesToContain) {
         if (input==null) fail("Input is null.");
         if (phrase1ToContain!=null) {