add more test case for expression parser
Project: http://git-wip-us.apache.org/repos/asf/incubator-weex/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-weex/commit/3344bb49 Tree: http://git-wip-us.apache.org/repos/asf/incubator-weex/tree/3344bb49 Diff: http://git-wip-us.apache.org/repos/asf/incubator-weex/diff/3344bb49 Branch: refs/heads/0.16-dev Commit: 3344bb491c8048eedc9a7f564bc5573af147c24f Parents: 7a9b30c Author: jianbai.gbj <jianbai....@alibaba-inc.com> Authored: Wed Sep 20 12:12:12 2017 +0800 Committer: jianbai.gbj <jianbai....@alibaba-inc.com> Committed: Wed Sep 20 12:12:12 2017 +0800 ---------------------------------------------------------------------- .../taobao/weex/dom/binding/WXStatement.java | 2 +- .../com/taobao/weex/el/parse/Operators.java | 29 +++- .../java/com/taobao/weex/el/parse/Parser.java | 66 +++++---- .../java/com/taobao/weex/el/parse/Token.java | 2 +- .../weex/ui/component/binding/Statements.java | 138 ++++++++++--------- .../java/com/taobao/weex/el/ParserTest.java | 32 ++++- .../ui/component/binding/BindingValueTest.java | 18 +-- .../ui/component/binding/StatementTest.java | 15 +- 8 files changed, 183 insertions(+), 119 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/3344bb49/android/sdk/src/main/java/com/taobao/weex/dom/binding/WXStatement.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/dom/binding/WXStatement.java b/android/sdk/src/main/java/com/taobao/weex/dom/binding/WXStatement.java index c11aaec..e17c708 100644 --- a/android/sdk/src/main/java/com/taobao/weex/dom/binding/WXStatement.java +++ b/android/sdk/src/main/java/com/taobao/weex/dom/binding/WXStatement.java @@ -44,7 +44,7 @@ public class WXStatement implements Map<String, Object>,Cloneable { * '[[repeat]]': { * '@expression': 'dataList', * '@index': 'index', - * '@label': 'item' + * '@alias': 'item' * } * } * </code> http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/3344bb49/android/sdk/src/main/java/com/taobao/weex/el/parse/Operators.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/el/parse/Operators.java b/android/sdk/src/main/java/com/taobao/weex/el/parse/Operators.java index 42dcf67..610b058 100644 --- a/android/sdk/src/main/java/com/taobao/weex/el/parse/Operators.java +++ b/android/sdk/src/main/java/com/taobao/weex/el/parse/Operators.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Stack; /** * Created by furture on 2017/8/28. @@ -74,6 +75,18 @@ public class Operators { } } } + if(context instanceof Stack){ + Stack stack = (Stack) context; + for(int index=stack.size()-1; index >= 0; index--){ + Object value = stack.get(index); + if(value instanceof Map){ + Map map = (Map) value; + if(map.containsKey(key)){ + return map.get(key); + } + } + } + } if(context instanceof Map){ return ((Map) context).get(key); @@ -438,6 +451,8 @@ public class Operators { OPERATORS_PRIORITY.put(SPACE_STR, 0); OPERATORS_PRIORITY.put(ARRAY_SEPRATOR_STR, 0); OPERATORS_PRIORITY.put(ARRAY_END_STR, 0); + + OPERATORS_PRIORITY.put(OR, 1); OPERATORS_PRIORITY.put(AND, 1); @@ -469,12 +484,16 @@ public class Operators { } - public static final List<String> KEYWORDS = new ArrayList<>(); + public static final Object keywordValue(String keyword){ + return KEYWORDS.get(keyword); + } + + public static final Map<String,Object> KEYWORDS = new HashMap<>(); static { - KEYWORDS.add("null"); - KEYWORDS.add("true"); - KEYWORDS.add("false"); - KEYWORDS.add("undefined"); + KEYWORDS.put("null", null); + KEYWORDS.put("true", Boolean.TRUE); + KEYWORDS.put("false", Boolean.FALSE); + KEYWORDS.put("undefined", null); } } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/3344bb49/android/sdk/src/main/java/com/taobao/weex/el/parse/Parser.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/el/parse/Parser.java b/android/sdk/src/main/java/com/taobao/weex/el/parse/Parser.java index c2f2b9b..76a6eed 100644 --- a/android/sdk/src/main/java/com/taobao/weex/el/parse/Parser.java +++ b/android/sdk/src/main/java/com/taobao/weex/el/parse/Parser.java @@ -20,9 +20,7 @@ package com.taobao.weex.el.parse; import com.taobao.weex.WXEnvironment; import com.taobao.weex.utils.WXLogUtils; - import java.util.ArrayList; -import java.util.Collections; import java.util.List; @@ -36,18 +34,16 @@ public class Parser { private int position; private ArrayStack<Token> stacks; private ArrayStack<Symbol> operators; - private ArrayStack<Symbol> punctuations; public Parser(String code){ this.code = code; this.position = 0; this.stacks = new ArrayStack<>(); this.operators = new ArrayStack<>(); - this.punctuations = new ArrayStack<>(); } - public Token parse(){ + public final Token parse(){ while (hasNextToken()){ scanNextToken(); } @@ -79,15 +75,11 @@ public class Parser { } - char scanNextToken(){ + final char scanNextToken(){ char ch = nextToken(); if(ch == Operators.DOLLAR){ position++; return ch; - }else if(ch == Operators.ARRAY_SEPRATOR){ - punctuations.push(new Symbol(String.valueOf(ch), position)); - position++; - return ch; }else if(Character.isJavaIdentifierStart(ch)){ scanIdentifier(); }else if (ch == Operators.BRACKET_START || ch == Operators.BLOCK_START) { @@ -115,7 +107,7 @@ public class Parser { } - void scanArray(){ + final void scanArray(){ int stackSize = stacks.size(); int opSize = operators.size(); int type = Token.TYPE_IDENTIFIER; @@ -304,31 +296,55 @@ public class Parser { * */ void scanIf(){ Operator operator = new Operator(Operators.CONDITION_IF_STRING, Token.TYPE_OPERATOR); - int minSize = 0; + int selfIndex = 0; + doStackOperators(0); if(operators.size() > 0){ - minSize = Math.max(operators.peek().pos, minSize); + selfIndex = Math.max(operators.peek().pos, selfIndex); } - if(stacks.size() > minSize){ + if(stacks.size() > selfIndex){ operator.self = stacks.pop(); } + + int stackSize = stacks.size(); + int leftOperatorSize = operators.size(); position++; while (hasNextToken()){ if(scanNextToken() == Operators.CONDITION_IF_MIDDLE){ break; } } - while (stacks.size() > minSize){ + while (operators.size() > leftOperatorSize){ + Symbol symbol = operators.pop(); + doOperator(symbol); + } + + while (stacks.size() > stackSize){ operator.first = stacks.pop(); } - if(hasNextToken()){ + int rightOperatorsSize = operators.size(); + while (hasNextToken()){ scanNextToken(); - while (stacks.size() > minSize){ - operator.second = stacks.pop(); + if(hasNextToken()){ + scanNextToken(); + } + if(operators.size() <= rightOperatorsSize){ + break; } } + doStackOperators(rightOperatorsSize); + while (stacks.size() > stackSize){ + operator.second = stacks.pop(); + } stacks.push(operator); } + private final void doStackOperators(int operatorSize){ + while (operators.size() > operatorSize){ + Symbol symbol = operators.pop(); + doOperator(symbol); + } + } + /** * 1+e6 * .00 @@ -336,7 +352,7 @@ public class Parser { * 100 * 199e5 * */ - void scanNumber(){ + final void scanNumber(){ boolean isInt = true; int start = position; if(code.charAt(position) == 'e' || code.charAt(position) == '.'){ @@ -371,7 +387,7 @@ public class Parser { } - void scanString(){ + final void scanString(){ int start = position; ArrayStack operator = new ArrayStack(); char quote = code.charAt(start); @@ -407,7 +423,7 @@ public class Parser { * ${item.dd} * $item.dd * */ - void scanIdentifier(){ + final void scanIdentifier(){ int start = position; position++; while (hasNext()){ @@ -427,7 +443,7 @@ public class Parser { el = el.substring(Operators.DOLLAR_STR.length()); } int type = Token.TYPE_IDENTIFIER; - if(Operators.KEYWORDS.contains(el)){ + if(Operators.KEYWORDS.containsKey(el)){ if(!(!operators.isEmpty() && Operators.isDot(operators.peek().op))){ type = Token.TYPE_KEYWORD; } @@ -437,13 +453,13 @@ public class Parser { } - boolean hasNext(){ + final boolean hasNext(){ return position < code.length(); } - boolean hasNextToken(){ + final boolean hasNextToken(){ while (hasNext()){ char ch = code.charAt(position); if(ch == ' '){ @@ -455,7 +471,7 @@ public class Parser { return false; } - char nextToken(){ + final char nextToken(){ char ch = code.charAt(position); while (ch == ' '){ position ++; http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/3344bb49/android/sdk/src/main/java/com/taobao/weex/el/parse/Token.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/el/parse/Token.java b/android/sdk/src/main/java/com/taobao/weex/el/parse/Token.java index d3bcf5c..03e496a 100644 --- a/android/sdk/src/main/java/com/taobao/weex/el/parse/Token.java +++ b/android/sdk/src/main/java/com/taobao/weex/el/parse/Token.java @@ -69,7 +69,7 @@ public class Token { return 0; } }else if(type == TYPE_KEYWORD){ - return token; + return Operators.keywordValue(token); } throw new IllegalArgumentException("unhandled token type " + type); } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/3344bb49/android/sdk/src/main/java/com/taobao/weex/ui/component/binding/Statements.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/binding/Statements.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/binding/Statements.java index 7d42706..6f4e3cd 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/binding/Statements.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/binding/Statements.java @@ -128,83 +128,87 @@ public class Statements { // execute v-for content if(vfor != null){ int renderIndex = parent.indexOf(component); - Token listBlock = (Token) vfor.get(WXStatement.WX_FOR_LIST); - String indexKey = vfor.getString(WXStatement.WX_FOR_INDEX); - String itemKey = vfor.getString(WXStatement.WX_FOR_ITEM); - Object data = null; - if(listBlock != null) { - data = listBlock.execute(context); - } - if((data instanceof List || data instanceof Map)){ - - Collection collection = null; - Map map = null; - if(data instanceof List){ - collection = (List)data; - }else{ - map = (Map)data; - collection = map.keySet(); + if(vfor.get(WXStatement.WX_FOR_LIST) instanceof Token){ + Token listBlock = (Token) vfor.get(WXStatement.WX_FOR_LIST); + String indexKey = vfor.getString(WXStatement.WX_FOR_INDEX); + String itemKey = vfor.getString(WXStatement.WX_FOR_ITEM); + Object data = null; + if(listBlock != null) { + data = listBlock.execute(context); } - Map<String, Object> loop = new HashMap<>(); - int index = 0; - for(Object item : collection){ - Object key = null; - Object value = item; - if(map == null){ - key = index; - value = item; - }else{ - key = item; - value = map.get(item); - } - if(indexKey != null){ - loop.put(indexKey, key); - } + if((data instanceof List || data instanceof Map)){ - if(itemKey != null){ - loop.put(itemKey, value); + Collection collection = null; + Map map = null; + if(data instanceof List){ + collection = (List)data; }else{ - context.push(value); - } - if(loop.size() > 0){ - context.push(loop); + map = (Map)data; + collection = map.keySet(); } + Map<String, Object> loop = new HashMap<>(); + int index = 0; + for(Object item : collection){ + Object key = null; + Object value = item; + if(map == null){ + key = index; + value = item; + }else{ + key = item; + value = map.get(item); + } + if(indexKey != null){ + loop.put(indexKey, key); + } + + if(itemKey != null){ + loop.put(itemKey, value); + }else{ + context.push(value); + } + if(loop.size() > 0){ + context.push(loop); + } - if(vif != null){ - if(!Operators.isTrue(vif.execute(context))){ - continue; + if(vif != null){ + if(!Operators.isTrue(vif.execute(context))){ + continue; + } } - } - //find resuable renderNode - WXComponent renderNode = null; - if(renderIndex < parent.getChildCount()){ - renderNode = parent.getChild(renderIndex); - //check is same statment, if true, it is usabled. - if(!isCreateFromNodeStatement(renderNode, component)){ - renderNode = null; + //find resuable renderNode + WXComponent renderNode = null; + if(renderIndex < parent.getChildCount()){ + renderNode = parent.getChild(renderIndex); + //check is same statment, if true, it is usabled. + if(!isCreateFromNodeStatement(renderNode, component)){ + renderNode = null; + } + } + //none resuable render node, create node, add to parent, but clear node's statement + if(renderNode == null){ + renderNode = copyComponentTree(component, parent); + WXDomObject renderNodeDomObject = (WXDomObject) renderNode.getDomObject(); + renderNodeDomObject.getAttrs().setStatement(null); // clear node's statement + parentDomObject.add(renderNodeDomObject, renderIndex); + parent.addChild(renderNode, renderIndex); + parent.createChildViewAt(renderIndex); + renderNode.applyLayoutAndEvent(renderNode); + renderNode.bindData(renderNode); + } + doBindingAttrsEventAndRenderChildNode(renderNode, domObject, context); + renderIndex++; + if(loop.size() > 0){ + context.push(loop); + } + if(itemKey == null) { + context.pop(); } - } - //none resuable render node, create node, add to parent, but clear node's statement - if(renderNode == null){ - renderNode = copyComponentTree(component, parent); - WXDomObject renderNodeDomObject = (WXDomObject) renderNode.getDomObject(); - renderNodeDomObject.getAttrs().setStatement(null); // clear node's statement - parentDomObject.add(renderNodeDomObject, renderIndex); - parent.addChild(renderNode, renderIndex); - parent.createChildViewAt(renderIndex); - renderNode.applyLayoutAndEvent(renderNode); - renderNode.bindData(renderNode); - } - doBindingAttrsEventAndRenderChildNode(renderNode, domObject, context); - renderIndex++; - if(loop.size() > 0){ - context.push(loop); - } - if(itemKey == null) { - context.pop(); } } + }else{ + WXLogUtils.e("StatementsVFor", vfor.toJSONString() + " not call vfor block, for pre compile"); } //after v-for execute, remove component created pre v-for. for(;renderIndex<parent.getChildCount(); renderIndex++){ http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/3344bb49/android/sdk/src/test/java/com/taobao/weex/el/ParserTest.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/test/java/com/taobao/weex/el/ParserTest.java b/android/sdk/src/test/java/com/taobao/weex/el/ParserTest.java index 8494a84..e265b5e 100644 --- a/android/sdk/src/test/java/com/taobao/weex/el/ParserTest.java +++ b/android/sdk/src/test/java/com/taobao/weex/el/ParserTest.java @@ -80,20 +80,33 @@ public class ParserTest extends TestCase { Assert.assertEquals(1, Parser.parse("0?2:1").execute(null)); Assert.assertEquals(1, Parser.parse("0?2:1").execute(null)); Assert.assertEquals(4, Parser.parse("0?1:0?3:4").execute(null)); - Assert.assertEquals(3.0, Parser.parse("0?1:0+1?3:4").execute(null)); + Assert.assertEquals(3, Parser.parse("0?1:0+1?3:4").execute(null)); + Assert.assertEquals("hello world", Parser.parse("item.name ? item.name : false").execute(this.createContext())); + Assert.assertEquals(10, Parser.parse("item.name ? item[1] : false").execute(this.createContext())); + Assert.assertEquals("hello world", Parser.parse("item.name == null ? false : item.name").execute(this.createContext())); + Assert.assertEquals(false, Parser.parse("true ? false : item.name").execute(this.createContext())); + Assert.assertEquals(false, Parser.parse("true ? false : item.name.work").execute(this.createContext())); + Assert.assertEquals("hello world", Parser.parse("item.name ? item.name : false").execute(this.createContext())); + Assert.assertEquals(null, Parser.parse("item.name ? item.name.not : false").execute(this.createContext())); + + } public void testDebug(){ + + + System.out.println("execute " + Parser.parse("true ? false : item.name").execute(this.createContext())); + + show("true ? false : item.name"); + /** Parser.parse("item[1]").execute(this.createContext()); Token block = Parser.parse("{{{item.name}}}"); - /** - * - * */ - show("item.[1]"); + show("true ? item.name : false"); show("((true) && 2 > 1) && (1) && (1)"); System.out.println(block.execute(createContext()) + " " + Double.parseDouble(".0e6")); show("1 > -1"); + */ } @@ -135,13 +148,20 @@ public class ParserTest extends TestCase { Assert.assertFalse(Operators.isTrue(Parser.parse("1 ? null : true").execute(createContext()))); Assert.assertFalse(Operators.isTrue(Parser.parse("1 ? undefined : true").execute(createContext()))); Assert.assertFalse(Operators.isTrue(Parser.parse("1 ? \"\" : true").execute(createContext()))); + Assert.assertEquals("hello world", Parser.parse("true ? item.name : false").execute(createContext())); + Assert.assertEquals("hello world", Parser.parse("item.name ? item.name : false").execute(createContext())); + Assert.assertEquals(true, Parser.parse("true").execute(createContext())); + Assert.assertEquals(false, Parser.parse("false").execute(createContext())); + Assert.assertEquals(null, Parser.parse("null").execute(createContext())); + Assert.assertEquals(null, Parser.parse("undefined").execute(createContext())); + } public void testParse(){ Assert.assertEquals(1, Parser.parse("0?2:1").execute(null)); Assert.assertEquals(4, Parser.parse("0?1:0?3:4").execute(null)); - Assert.assertEquals(3.0, Parser.parse("0?1:0+1?3:4").execute(null)); + Assert.assertEquals(3, Parser.parse("0?1:0+1?3:4").execute(null)); Parser.parse("item.code \"string test \" ( item.ddd) .item 1000 ccc ? ddd : 0"); Parser.parse("1+e6"); show(null); http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/3344bb49/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/BindingValueTest.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/BindingValueTest.java b/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/BindingValueTest.java index cfd4e7a..162877e 100644 --- a/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/BindingValueTest.java +++ b/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/BindingValueTest.java @@ -33,21 +33,23 @@ import java.util.Stack; public class BindingValueTest extends TestCase { public void testBindingValue(){ - Assert.assertTrue(getBindingValue("true ? true : false", createContext()).equals("true")); - Assert.assertTrue(getBindingValue("true ? item.name : false", createContext()).equals("hello world")); - Assert.assertTrue(getBindingValue("false ? item.name : false", createContext()).equals("false")); - Assert.assertTrue(getBindingValue("item.name ? item.name : false", createContext()).equals("hello world")); - Assert.assertTrue(getBindingValue("item.name > 0 ? item.name : false", createContext()).equals("false")); - Assert.assertTrue(getBindingValue("? item.name : false", createContext()).equals("false")); + System.out.println(Parser.parse("true ? item.name : false").execute(createContext())); + Assert.assertEquals(getValue("true ? true : false", createContext()), true); + Assert.assertEquals(getValue("true ? item.name : false", createContext()), "hello world"); + Assert.assertEquals(getValue("false ? item.name : false", createContext()), false); + Assert.assertEquals(getValue("item.name ? item.name : false", createContext()), "hello world"); + Assert.assertEquals(getValue("item.name > 0 ? item.name : false", createContext()), false); + Assert.assertEquals(getValue("? item.name : false", createContext()), false); } public void testDebug(){ - Assert.assertTrue(getBindingValue("? item.name : false", createContext()).equals("false")); + System.out.println(Parser.parse("true ? item.name : false")); + Assert.assertTrue(getValue("false ? item.name : false", createContext()).toString().equals("false")); } - private Object getBindingValue(String expression, Stack context){ + private Object getValue(String expression, Stack context){ return Parser.parse(expression).execute(context); } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/3344bb49/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/StatementTest.java ---------------------------------------------------------------------- diff --git a/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/StatementTest.java b/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/StatementTest.java index 43f17c0..3566816 100644 --- a/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/StatementTest.java +++ b/android/sdk/src/test/java/com/taobao/weex/ui/component/binding/StatementTest.java @@ -28,6 +28,7 @@ import com.taobao.weex.dom.WXCellDomObject; import com.taobao.weex.dom.WXDomObject; import com.taobao.weex.dom.WXEvent; import com.taobao.weex.dom.WXTextDomObject; +import com.taobao.weex.dom.binding.ELUtils; import com.taobao.weex.dom.binding.WXStatement; import com.taobao.weex.dom.flex.Spacing; import com.taobao.weex.el.parse.ArrayStack; @@ -78,7 +79,7 @@ public class StatementTest { Statements.doRenderComponent(cell, createContext(count)); Assert.assertTrue(cell.getChildCount() == 1); WXDiv div = (WXDiv) cell.getChild(0); - Assert.assertTrue(div.getChildCount() == count); + Assert.assertEquals(div.getChildCount(), count); Assert.assertNotNull(div.getChild(0).getDomObject()); Assert.assertNotNull(((WXDomObject)div.getChild(0).getDomObject()).getAttrs().getStatement()); Assert.assertNull(((WXDomObject)div.getChild(1).getDomObject()).getAttrs().getStatement()); @@ -144,11 +145,13 @@ public class StatementTest { cell.addChild(div); WXText text = new WXText(WXSDKInstanceTest.createInstance(), new WXTextDomObject(), div); WXStatement statement = new WXStatement(); - statement.put("repeat", JSON.parse("{\n" + - " '@exp': 'dataList',\n" + - " '@key': 'index',\n" + - " '@label': 'item'\n" + - " }")); + statement.put("[[repeat]]", + ELUtils.vforBlock( + JSON.parse("{\n" + + " '@expression': 'dataList',\n" + + " '@index': 'index',\n" + + " '@alias': 'item'\n" + + " }"))); WXDomObject domObject = (WXDomObject) text.getDomObject(); domObject.getAttrs().setStatement(statement); div.addChild(text);