From 91467c521a6b34b86b7e8aad6e3a40578bb0dea5 Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 21:38:59 +1200 Subject: [PATCH 1/5] peg: add new expression: left_recursion. --- tests/peppa.peg | 5 +- tests/test_peppa_spec.json | 168 ++++++++++++++++++++++++++++++++++++- 2 files changed, 171 insertions(+), 2 deletions(-) diff --git a/tests/peppa.peg b/tests/peppa.peg index c2aeea6..3cdb30f 100644 --- a/tests/peppa.peg +++ b/tests/peppa.peg @@ -19,7 +19,10 @@ decorator = "@" @cut ( name = reference; @lifted -expression = choice; +expression = left_recursion; + +@nonterminal +left_recursion = choice ("|" reference choice)?; @nonterminal choice = sequence ("/" sequence)*; diff --git a/tests/test_peppa_spec.json b/tests/test_peppa_spec.json index 725fd29..e402847 100644 --- a/tests/test_peppa_spec.json +++ b/tests/test_peppa_spec.json @@ -5,7 +5,7 @@ "entry": "grammar", "tests": [ { - "desc": "x", + "N": "Dot expression", "I": "x = .;", "O": [ { @@ -43,5 +43,171 @@ ] } ] + }, + + { + "desc": "Left Recursion", + "grammar_file": "./peppa.peg", + "entry": "grammar", + "tests": [ + { + "N": "Left recursion ", + "I": "x = \"x\" | x \"x\";", + "O": [ + { + "slice": [ + 0, + 16 + ], + "type": "grammar", + "children": [ + { + "slice": [ + 0, + 16 + ], + "type": "rule", + "children": [ + { + "slice": [ + 0, + 1 + ], + "type": "name" + }, + { + "slice": [ + 4, + 15 + ], + "type": "left_recursion", + "children": [ + { + "slice": [ + 4, + 7 + ], + "type": "literal" + }, + { + "slice": [ + 10, + 11 + ], + "type": "reference" + }, + { + "slice": [ + 12, + 15 + ], + "type": "literal" + } + ] + } + ] + } + ] + } + ] + }, + { + "N": "Left recursion precedence is lower than choice", + "I": "x = \"x\" / \"y\" | x \"x\" / \"y\";", + "O": [ + { + "slice": [ + 0, + 28 + ], + "type": "grammar", + "children": [ + { + "slice": [ + 0, + 28 + ], + "type": "rule", + "children": [ + { + "slice": [ + 0, + 1 + ], + "type": "name" + }, + { + "slice": [ + 4, + 27 + ], + "type": "left_recursion", + "children": [ + { + "slice": [ + 4, + 14 + ], + "type": "choice", + "children": [ + { + "slice": [ + 4, + 7 + ], + "type": "literal" + }, + { + "slice": [ + 10, + 13 + ], + "type": "literal" + } + ] + }, + { + "slice": [ + 16, + 17 + ], + "type": "reference" + }, + { + "slice": [ + 18, + 27 + ], + "type": "choice", + "children": [ + { + "slice": [ + 18, + 21 + ], + "type": "literal" + }, + { + "slice": [ + 24, + 27 + ], + "type": "literal" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "N": "Left recursion with wrong reference is invalid. This is checked in runtime", + "I": "x = \"x\" | y \"x\"; y = \"y\";" + } + ] } ] From 18d42015899008dc591ae99e94c17cac7dba0fc0 Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 21:39:14 +1200 Subject: [PATCH 2/5] tests: support ignoring some test specs. --- scripts/check_spec.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/check_spec.py b/scripts/check_spec.py index 2c7a70e..e76c9c4 100644 --- a/scripts/check_spec.py +++ b/scripts/check_spec.py @@ -14,7 +14,7 @@ def test_spec(): print("invalid json spec") exit(1) - failed, total = 0, 0 + failed, ignored, total = 0, 0, 0 for spec in specs: for test in spec['tests']: total += 1 @@ -56,7 +56,7 @@ def test_spec(): f"GOT:\n{proc.stderr.decode('utf-8')}\n" ) failed += 1 - else: + elif 'E' in test: assert proc.returncode != 0, proc.stderr if proc.stderr.decode('utf-8').strip() != test['E']: print( @@ -66,8 +66,10 @@ def test_spec(): f"GOT:\n{proc.stderr.decode('utf-8')}" ) failed += 1 + else: + ignored += 1 - print("total: %d, failed: %d" % (total, failed)) + print("total: %d, failed: %d, ignored: %d" % (total, failed, ignored)) if failed: exit(1) From bbad23b674c48196b4e81ef87edd62cfe56ef11b Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 22:13:55 +1200 Subject: [PATCH 3/5] peg: add | symbol in peg specification. --- peppa.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/peppa.c b/peppa.c index 8d3178b..32d99f7 100644 --- a/peppa.c +++ b/peppa.c @@ -617,6 +617,7 @@ struct P4_Expression { struct { P4_Expression* lhs; P4_Expression* rhs; + P4_String recur_ref; }; }; }; @@ -881,6 +882,7 @@ P4_PRIVATE(P4_Error) P4_PegEvalRange(P4_Node* node, P4_Result* result P4_PRIVATE(P4_Error) P4_PegEvalMembers(P4_Node* node, P4_Expression* expr, P4_Result* result); P4_PRIVATE(P4_Error) P4_PegEvalSequence(P4_Node* node, P4_Result* result); P4_PRIVATE(P4_Error) P4_PegEvalChoice(P4_Node* node, P4_Result* result); +P4_PRIVATE(P4_Error) P4_PegEvalLeftRecursion(P4_Node* node, P4_Result* result); P4_PRIVATE(P4_Error) P4_PegEvalPositive(P4_Node* node, P4_Result* result); P4_PRIVATE(P4_Error) P4_PegEvalNegative(P4_Node* node, P4_Result* result); P4_PRIVATE(P4_Error) P4_PegEvalRepeat(P4_Node* node, P4_Result* result); @@ -2256,6 +2258,7 @@ match_left_recursion(P4_Source* s, P4_Expression* e) { /* if no match, puke the parsed whitespace, if any. */ if (no_match(s)) { if (space) { set_position(s, whitespace_startpos); } + rescue_error(s); break; } @@ -2684,6 +2687,7 @@ P4_CreateLeftRecursion(P4_Expression* lhs, P4_Expression* rhs) { expr->name = NULL; expr->lhs = lhs; expr->rhs = rhs; + expr->recur_ref = NULL; return expr; } @@ -3417,6 +3421,7 @@ P4_DeleteExpression(P4_Expression* expr) { case P4_LeftRecursion: P4_DeleteExpression(expr->lhs); P4_DeleteExpression(expr->rhs); + if (expr->recur_ref) P4_FREE(expr->recur_ref); default: break; } @@ -3938,13 +3943,24 @@ P4_Grammar* P4_CreatePegGrammar () { )); catch_err(P4_SetGrammarRuleFlag(grammar, "insensitive", P4_FLAG_TIGHT)); + catch_err(P4_AddSequenceWithMembers(grammar, "left_recursion", 2, + P4_CreateReference("choice"), + P4_CreateZeroOrOnce(P4_CreateSequenceWithMembers(4, + P4_CreateLiteral("|", true), + P4_CreateCut(), + P4_CreateReference("reference"), + P4_CreateReference("choice") + )) + )); + catch_err(P4_SetGrammarRuleFlag(grammar, "left_recursion", P4_FLAG_NON_TERMINAL)); + catch_err(P4_AddJoin(grammar, "choice", "/", "sequence")); catch_err(P4_SetGrammarRuleFlag(grammar, "choice", P4_FLAG_NON_TERMINAL)); catch_err(P4_AddOnceOrMore(grammar, "sequence", P4_CreateReference("repeat"))); catch_err(P4_SetGrammarRuleFlag(grammar, "sequence", P4_FLAG_NON_TERMINAL)); - catch_err(P4_AddReference(grammar, "expression", "choice")); + catch_err(P4_AddReference(grammar, "expression", "left_recursion")); catch_err(P4_SetGrammarRuleFlag(grammar, "expression", P4_FLAG_LIFTED)); catch_err(P4_AddReference(grammar, "name", "reference")); @@ -4304,6 +4320,36 @@ P4_PegEvalChoice(P4_Node* node, P4_Result* result) { return err; } +P4_PRIVATE(P4_Error) +P4_PegEvalLeftRecursion(P4_Node* node, P4_Result* result) { + P4_Error err = P4_Ok; + P4_Expression* lhs = NULL; + P4_Expression* rhs = NULL; + P4_String ref = NULL; + + catch_err(P4_PegEvalExpression(node->head, result)); + lhs = unwrap_expr(result); + + if (node->head->next != NULL) { + catch_err(P4_PegEvalRuleName(node->head->next, &ref)); + catch_err(P4_PegEvalExpression(node->tail, result)); + rhs = unwrap_expr(result); + + catch_oom(result->expr = P4_CreateLeftRecursion(lhs, rhs)); + result->expr->recur_ref = ref; + } else { + result->expr = lhs; + } + + return P4_Ok; + +finalize: + P4_DeleteExpression(lhs); + P4_DeleteExpression(rhs); + P4_FREE(ref); + return err; +} + P4_PRIVATE(P4_Error) P4_PegEvalPositive(P4_Node* node, P4_Result* result) { P4_Error err = P4_Ok; @@ -4611,6 +4657,9 @@ P4_PegEvalExpression(P4_Node* node, P4_Result* result) { if (strcmp(node->rule_name, "choice") == 0) return P4_PegEvalChoice(node, result); + if (strcmp(node->rule_name, "left_recursion") == 0) + return P4_PegEvalLeftRecursion(node, result); + if (strcmp(node->rule_name, "positive") == 0) return P4_PegEvalPositive(node, result); @@ -4633,7 +4682,7 @@ P4_PegEvalExpression(P4_Node* node, P4_Result* result) { return P4_PegEvalBackReference(node, result); UNREACHABLE(); - panicf("Unreachable: node %p is not a peg expression", node); + panicf("Unreachable: node %p (%s) is not a peg expression", node, node->rule_name); } P4_PUBLIC P4_Error From 1d6eae8a70a425eadcf2e418f18b67c914724a77 Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 22:14:54 +1200 Subject: [PATCH 4/5] tests: add basic left recursion test cases. --- tests/CMakeLists.txt | 1 + tests/peppa.peg | 2 +- tests/test_left_recursion_spec.json | 100 ++++++++++++++++++++++++ tests/test_peg.c | 5 ++ tests/test_peppa_spec.json | 117 +++++++++++++++++++++++++++- 5 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 tests/test_left_recursion_spec.json diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9f464bf..277ccd9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -75,6 +75,7 @@ set( test_insensitive_spec test_sequence_spec test_choice_spec + test_left_recursion_spec test_lookahead_spec test_cut_spec test_repeat_spec diff --git a/tests/peppa.peg b/tests/peppa.peg index 3cdb30f..8693f43 100644 --- a/tests/peppa.peg +++ b/tests/peppa.peg @@ -22,7 +22,7 @@ name = reference; expression = left_recursion; @nonterminal -left_recursion = choice ("|" reference choice)?; +left_recursion = choice ("|" @cut reference choice)?; @nonterminal choice = sequence ("/" sequence)*; diff --git a/tests/test_left_recursion_spec.json b/tests/test_left_recursion_spec.json new file mode 100644 index 0000000..b96faab --- /dev/null +++ b/tests/test_left_recursion_spec.json @@ -0,0 +1,100 @@ +[ + { + "grammar": "A = \"a\" | A \"a\";", + "entry": "A", + "tests": [ + { + "I": "a", + "O": [], + "N": "Expression \"a\" produces no node. So the result is empty." + }, + { + "I": "aa", + "O": [{"slice": [0, 2], "type": "A"}], + "N": "Expression `A \"a\"` produces node covering two chars." + }, + { + "I": "aaa", + "O": [ + { + "slice": [0, 3], + "type": "A", + "children": [ + { + "slice": [0, 2], + "type": "A" + } + ] + } + ] + }, + { + "I": "", + "E": "MatchError: line 1:1, expect A" + } + ] + }, + + { + "grammar": "A = B | A B;\nB = \"b\";", + "entry": "A", + "tests": [ + { + "I": "b", + "O": [{"slice": [0, 1], "type": "B"}] + }, + { + "I": "bb", + "O": [ + { + "slice": [0, 2], + "type": "A", + "children": [ + { + "slice": [0, 1], + "type": "B" + }, + { + "slice": [1, 2], + "type": "B" + } + ] + } + ] + }, + { + "I": "bbb", + "O": [ + { + "slice": [0, 3], + "type": "A", + "children": [ + { + "slice": [0, 2], + "type": "A", + "children": [ + { + "slice": [0, 1], + "type": "B" + }, + { + "slice": [1, 2], + "type": "B" + } + ] + }, + { + "slice": [2, 3], + "type": "B" + } + ] + } + ] + }, + { + "I": "", + "E": "MatchError: line 1:1, expect A" + } + ] + } +] diff --git a/tests/test_peg.c b/tests/test_peg.c index f33e607..b254af0 100644 --- a/tests/test_peg.c +++ b/tests/test_peg.c @@ -146,6 +146,10 @@ void test_sequence(void) { ASSERT_PEG_PARSE("sequence", "\"a\" \"b\"", P4_Ok, "[{\"slice\":[0,7],\"type\":\"sequence\",\"children\":[{\"slice\":[0,3],\"type\":\"literal\"},{\"slice\":[4,7],\"type\":\"literal\"}]}]"); } +void test_left_recursion(void) { + ASSERT_PEG_PARSE("left_recursion", "\"x\" | x \"x\"", P4_Ok, "[{\"slice\":[0,11],\"type\":\"left_recursion\",\"children\":[{\"slice\":[0,3],\"type\":\"literal\"},{\"slice\":[6,7],\"type\":\"reference\"},{\"slice\":[8,11],\"type\":\"literal\"}]}]"); +} + void test_repeat(void) { ASSERT_PEG_PARSE("repeat", "\"a\"*", P4_Ok, "[{\"slice\":[0,4],\"type\":\"repeat\",\"children\":[{\"slice\":[0,3],\"type\":\"literal\"},{\"slice\":[3,4],\"type\":\"zeroormore\"}]}]"); ASSERT_PEG_PARSE("repeat", "\"a\"+", P4_Ok, "[{\"slice\":[0,4],\"type\":\"repeat\",\"children\":[{\"slice\":[0,3],\"type\":\"literal\"},{\"slice\":[3,4],\"type\":\"onceormore\"}]}]"); @@ -433,6 +437,7 @@ int main(void) { RUN_TEST(test_positive); RUN_TEST(test_negative); RUN_TEST(test_choice); + RUN_TEST(test_left_recursion); RUN_TEST(test_sequence); RUN_TEST(test_repeat); RUN_TEST(test_dot); diff --git a/tests/test_peppa_spec.json b/tests/test_peppa_spec.json index e402847..55474ed 100644 --- a/tests/test_peppa_spec.json +++ b/tests/test_peppa_spec.json @@ -1,4 +1,21 @@ [ + { + "desc": "Number", + "grammar_file": "./peppa.peg", + "entry": "number", + "tests": [ + { + "I": "0", + "O": [ + { + "slice": [0, 1], + "type": "number" + } + ] + } + ] + }, + { "desc": "Primary", "grammar_file": "./peppa.peg", @@ -205,7 +222,105 @@ ] }, { - "N": "Left recursion with wrong reference is invalid. This is checked in runtime", + "N": "Grouping choices within left recursion is highly recommended", + "I": "x = (\"x\" / \"y\") | x (\"x\" / \"y\");", + "O": [ + { + "slice": [ + 0, + 32 + ], + "type": "grammar", + "children": [ + { + "slice": [ + 0, + 32 + ], + "type": "rule", + "children": [ + { + "slice": [ + 0, + 1 + ], + "type": "name" + }, + { + "slice": [ + 4, + 31 + ], + "type": "left_recursion", + "children": [ + { + "slice": [ + 5, + 14 + ], + "type": "choice", + "children": [ + { + "slice": [ + 5, + 8 + ], + "type": "literal" + }, + { + "slice": [ + 11, + 14 + ], + "type": "literal" + } + ] + }, + { + "slice": [ + 18, + 19 + ], + "type": "reference" + }, + { + "slice": [ + 21, + 30 + ], + "type": "choice", + "children": [ + { + "slice": [ + 21, + 24 + ], + "type": "literal" + }, + { + "slice": [ + 27, + 30 + ], + "type": "literal" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "N": "Left recursion reference must not be included in parentheses.", + "I": "x = \"x\" | (x \"x\");", + "E": "CutError: line 1:11, expect reference" + }, + { + "N": "Left recursion with wrong reference is invalid. This is checked in runtime.", "I": "x = \"x\" | y \"x\"; y = \"y\";" } ] From 5c069740f94e285ec1dffc6bb73bf0f5149fca48 Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 22:46:11 +1200 Subject: [PATCH 5/5] peg: raise syntax error if left recursion rule name unmatches recur ref. --- peppa.c | 9 + tests/test_left_recursion_spec.json | 366 ++++++++++++++++++++++++++++ 2 files changed, 375 insertions(+) diff --git a/peppa.c b/peppa.c index 32d99f7..ddf2ea5 100644 --- a/peppa.c +++ b/peppa.c @@ -4538,6 +4538,15 @@ P4_PegEvalGrammarRule(P4_Node* node, P4_Result* result) { expr = unwrap_expr(result); assert(expr != NULL, "failed to eval grammar rule expression"); + + /* special case: for left recursion, rule name must be identical to recursion referece. */ + if (expr->kind == P4_LeftRecursion && strcmp(expr->recur_ref, rule_name) != 0) { + catch_err( + P4_PegError, + P4_EvalRaisef(result, "unmatched left recursion rule name: %s, %s", rule_name, expr->recur_ref) + ); + } + expr->name = rule_name; expr->flag = rule_flag; diff --git a/tests/test_left_recursion_spec.json b/tests/test_left_recursion_spec.json index b96faab..2732e10 100644 --- a/tests/test_left_recursion_spec.json +++ b/tests/test_left_recursion_spec.json @@ -1,4 +1,14 @@ [ + { + "grammar": "A = \"a\" | B \"a\"; B = \"b\";", + "entry": "A", + "tests": [ + { + "I": "a", + "E": "grammar syntax error: PegError: unmatched left recursion rule name: A, B." + } + ] + }, { "grammar": "A = \"a\" | A \"a\";", "entry": "A", @@ -96,5 +106,361 @@ "E": "MatchError: line 1:1, expect A" } ] + }, + + { + "grammar": "S = E | S op E; op = \"+\" / \"-\"; E = [0-9];", + "entry": "S", + "tests": [ + { + "I": "1", + "O": [{"slice": [0, 1], "type": "E"}] + }, + { + "I": "1+2", + "O": [ + { + "slice": [0, 3], + "type": "S", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "op"}, + {"slice": [2, 3], "type": "E"} + ] + } + ] + }, + { + "I": "1-2+3", + "O": [ + { + "slice": [0, 5], + "type": "S", + "children": [ + { + "slice": [0, 3], + "type": "S", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "op"}, + {"slice": [2, 3], "type": "E"} + ] + }, + {"slice": [3, 4], "type": "op"}, + {"slice": [4, 5], "type": "E"} + ] + } + ] + } + ] + }, + + { + "grammar": "S = F | S addop F; F = E | F mulop E; addop = \"+\"; mulop = \"*\"; E = [0-9];", + "entry": "S", + "tests": [ + { + "I": "1", + "O": [{"slice": [0, 1], "type": "E"}] + }, + { + "I": "1+2+3", + "O": [ + { + "slice": [0, 5], + "type": "S", + "children": [ + { + "slice": [0, 3], + "type": "S", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "addop"}, + {"slice": [2, 3], "type": "E"} + ] + }, + {"slice": [3, 4], "type": "addop"}, + {"slice": [4, 5], "type": "E"} + ] + } + ] + }, + { + "I": "1*2+3", + "O": [ + { + "slice": [0, 5], + "type": "S", + "children": [ + { + "slice": [0, 3], + "type": "F", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "mulop"}, + {"slice": [2, 3], "type": "E"} + ] + }, + {"slice": [3, 4], "type": "addop"}, + {"slice": [4, 5], "type": "E"} + ] + } + ] + }, + { + "I": "1+2*3", + "O": [ + { + "slice": [0, 5], + "type": "S", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "addop"}, + { + "slice": [2, 5], + "type": "F", + "children": [ + {"slice": [2, 3], "type": "E"}, + {"slice": [3, 4], "type": "mulop"}, + {"slice": [4, 5], "type": "E"} + ] + } + ] + } + ] + }, + { + "I": "1*2*3", + "O": [ + { + "slice": [0, 5], + "type": "F", + "children": [ + { + "slice": [0, 3], + "type": "F", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "mulop"}, + {"slice": [2, 3], "type": "E"} + ] + }, + {"slice": [3, 4], "type": "mulop"}, + {"slice": [4, 5], "type": "E"} + ] + } + ] + }, + { + "I": "1+2+3+4", + "O": [ + { + "slice": [0, 7], + "type": "S", + "children": [ + { + "slice": [0, 5], + "type": "S", + "children": [ + { + "slice": [0, 3], + "type": "S", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "addop"}, + {"slice": [2, 3], "type": "E"} + ] + }, + {"slice": [3, 4], "type": "addop"}, + {"slice": [4, 5], "type": "E"} + ] + }, + {"slice": [5, 6], "type": "addop"}, + {"slice": [6, 7], "type": "E"} + ] + } + ] + }, + { + "I": "1+2+3*4", + "O": [ + { + "slice": [0, 7], + "type": "S", + "children": [ + { + "slice": [0, 3], + "type": "S", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "addop"}, + {"slice": [2, 3], "type": "E"} + ] + }, + {"slice": [3, 4], "type": "addop"}, + { + "slice": [4, 7], + "type": "F", + "children": [ + {"slice": [4, 5], "type": "E"}, + {"slice": [5, 6], "type": "mulop"}, + {"slice": [6, 7], "type": "E"} + ] + } + ] + } + ] + }, + { + "I": "1+2*3+4", + "O": [ + { + "slice": [0, 7], + "type": "S", + "children": [ + { + "slice": [0, 5], + "type": "S", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "addop"}, + { + "slice": [2, 5], + "type": "F", + "children": [ + {"slice": [2, 3], "type": "E"}, + {"slice": [3, 4], "type": "mulop"}, + {"slice": [4, 5], "type": "E"} + ] + } + ] + }, + {"slice": [5, 6], "type": "addop"}, + {"slice": [6, 7], "type": "E"} + ] + } + ] + }, + { + "I": "1*2+3+4", + "O": [ + { + "slice": [0, 7], + "type": "S", + "children": [ + { + "slice": [0, 5], + "type": "S", + "children": [ + { + "slice": [0, 3], + "type": "F", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "mulop"}, + {"slice": [2, 3], "type": "E"} + ] + }, + {"slice": [3, 4], "type": "addop"}, + {"slice": [4, 5], "type": "E"} + ] + }, + {"slice": [5, 6], "type": "addop"}, + {"slice": [6, 7], "type": "E"} + ] + } + ] + }, + { + "I": "1*2*3+4", + "O": [ + { + "slice": [0, 7], + "type": "S", + "children": [ + { + "slice": [0, 5], + "type": "F", + "children": [ + { + "slice": [0, 3], + "type": "F", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "mulop"}, + {"slice": [2, 3], "type": "E"} + ] + }, + {"slice": [3, 4], "type": "mulop"}, + {"slice": [4, 5], "type": "E"} + ] + }, + {"slice": [5, 6], "type": "addop"}, + {"slice": [6, 7], "type": "E"} + ] + } + ] + }, + { + "I": "1+2*3*4", + "O": [ + { + "slice": [0, 7], + "type": "S", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "addop"}, + { + "slice": [2, 7], + "type": "F", + "children": [ + { + "slice": [2, 5], + "type": "F", + "children": [ + {"slice": [2, 3], "type": "E"}, + {"slice": [3, 4], "type": "mulop"}, + {"slice": [4, 5], "type": "E"} + ] + }, + {"slice": [5, 6], "type": "mulop"}, + {"slice": [6, 7], "type": "E"} + ] + } + ] + } + ] + }, + { + "I": "1*2*3*4", + "O": [ + { + "slice": [0, 7], + "type": "F", + "children": [ + { + "slice": [0, 5], + "type": "F", + "children": [ + { + "slice": [0, 3], + "type": "F", + "children": [ + {"slice": [0, 1], "type": "E"}, + {"slice": [1, 2], "type": "mulop"}, + {"slice": [2, 3], "type": "E"} + ] + }, + {"slice": [3, 4], "type": "mulop"}, + {"slice": [4, 5], "type": "E"} + ] + }, + {"slice": [5, 6], "type": "mulop"}, + {"slice": [6, 7], "type": "E"} + ] + } + ] + } + ] } ]