Skip to content

Commit

Permalink
support x:T notation in rule arguments, update documentation, improve…
Browse files Browse the repository at this point in the history
… [...] scope parsing testing
  • Loading branch information
parrt committed Nov 16, 2016
1 parent 03fa75e commit 5aae2ed
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 32 deletions.
13 changes: 11 additions & 2 deletions doc/parser-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,17 @@ The attributes defined within those [...] can be used like any other variable. H
add[int x] returns [int result] : '+=' INT {$result = $x + $INT.int;} ;
```

As with the grammar level, you can specify rule-level named actions. For rules, the valid names are init and after. As the names imply, parsers execute init actions immediately before trying to match the associated rule and execute after actions immediately after matching the rule. ANTLR after actions do not execute as part of the finally code block of the generated rule function. Use the ANTLR finally action to place code in the generated rule function finally code block.
The actions come after any argument, return value, or local attribute definition actions. The row rule preamble from Section 10.2, Accessing Token and Rule Attributes illustrates the syntax nicely:
The args, locals, and return `[...]` are generally in the target language but with some constraints. The `[...]` string is a comma-separated list of declarations either with prefix or postfix type notation or no-type notation. The elements can have initializer such as `[int x = 32, float y]` but don't go too crazy as we are parsing this generic text manually in [ScopeParser](https://github.com/antlr/antlr4/blob/master/tool/src/org/antlr/v4/parse/ScopeParser.java).

* Java, CSharp, C++ use `int x` notation but C++ must use a slightly altered notation for array references, `int[] x`, to fit in the *type* *id* syntax.
* Go and Swift give the type after the variable name, but Swift requires a `:` in between. Go `i int`, Swift `i:int`. For Go target, you must either use `int i` or `i:int`.
* Python and JavaScript don't specify static types so actions are just identifier lists such as `[i,j]`.

Technically any target could use either notation. For examples, see [TestScopeParsing](https://github.com/antlr/antlr4/blob/master/tool-testsuite/test/org/antlr/v4/test/tool/TestScopeParsing.java).

As with the grammar level, you can specify rule-level named actions. For rules, the valid names are `init` and `after`. As the names imply, parsers execute init actions immediately before trying to match the associated rule and execute after actions immediately after matching the rule. ANTLR after actions do not execute as part of the finally code block of the generated rule function. Use the ANTLR finally action to place code in the generated rule function finally code block.

The actions come after any argument, return value, or local attribute definition actions. The `row` rule preamble from Section 10.2, Accessing Token and Rule Attributes illustrates the syntax nicely:
actions/CSV.g4

```
Expand Down
91 changes: 70 additions & 21 deletions tool-testsuite/test/org/antlr/v4/test/tool/TestScopeParsing.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,46 +30,95 @@

package org.antlr.v4.test.tool;

import org.antlr.v4.misc.Utils;
import org.antlr.v4.parse.ScopeParser;
import org.antlr.v4.test.runtime.java.BaseJavaTest;
import org.antlr.v4.tool.Attribute;
import org.antlr.v4.tool.Grammar;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;

import static org.junit.Assert.assertEquals;

@RunWith(Parameterized.class)
public class TestScopeParsing extends BaseJavaTest {
String[] argPairs = {
"", "{}",
" ", "{}",
"int i", "{i=int i}",
"int[] i, int j[]", "{i=int[] i, j=int [] j}",
"Map<A,B>[] i, int j[]", "{i=Map<A,B>[] i, j=int [] j}",
"Map<A,List<B>>[] i", "{i=Map<A,List<B>>[] i}",
static String[] argPairs = {
"", "",
" ", "",
"int i", "i:int",
"int[] i, int j[]", "i:int[], j:int []",
"Map<A,B>[] i, int j[]", "i:Map<A,B>[], j:int []",
"Map<A,List<B>>[] i", "i:Map<A,List<B>>[]",
"int i = 34+a[3], int j[] = new int[34]",
"{i=int i= 34+a[3], j=int [] j= new int[34]}",
"char *foo32[3] = {1,2,3}", "{3=char *foo32[] 3= {1,2,3}}",
"String[] headers", "{headers=String[] headers}",
"i:int=34+a[3], j:int []=new int[34]",
"char *[3] foo = {1,2,3}", "foo:char *[3]={1,2,3}", // not valid C really, C is "type name" however so this is cool (this was broken in 4.5 anyway)
"String[] headers", "headers:String[]",

// python/ruby style
"i", "{i=null i}",
"i,j", "{i=null i, j=null j}",
"i,j, k", "{i=null i, j=null j, k=null k}",
"i", "i",
"i,j", "i, j",
"i\t,j, k", "i, j, k",

// swift style
"x: int", "x:int",
"x :int", "x:int",
"x:int", "x:int",
"x:int=3", "x:int=3",
"r:Rectangle=Rectangle(fromLength: 6, fromBreadth: 12)", "r:Rectangle=Rectangle(fromLength: 6, fromBreadth: 12)",
"p:pointer to int", "p:pointer to int",
"a: array[3] of int", "a:array[3] of int",
"a \t:\tfunc(array[3] of int)", "a:func(array[3] of int)",
"x:int, y:float", "x:int, y:float",
"x:T?, f:func(array[3] of int), y:int", "x:T?, f:func(array[3] of int), y:int",

// go is postfix type notation like "x int" but must use either "int x" or "x:int" in [...] actions
"float64 x = 3", "x:float64=3",
"map[string]int x", "x:map[string]int",
};

String input;
String output;

public TestScopeParsing(String input, String output) {
this.input = input;
this.output = output;
}

@Before
@Override
public void testSetUp() throws Exception {
super.testSetUp();
}

@Test public void testArgs() throws Exception {
for (int i = 0; i < argPairs.length; i+=2) {
String input = argPairs[i];
String expected = argPairs[i+1];
Grammar dummy = new Grammar("grammar T; a:'a';");
String actual = ScopeParser.parseTypedArgList(null, input, dummy).attributes.toString();
assertEquals(expected, actual);
}
@Test
public void testArgs() throws Exception {
Grammar dummy = new Grammar("grammar T; a:'a';");

LinkedHashMap<String, Attribute> attributes = ScopeParser.parseTypedArgList(null, input, dummy).attributes;
List<String> out = new ArrayList<>();
for (String arg : attributes.keySet()) {
Attribute attr = attributes.get(arg);
out.add(attr.toString());
}
String actual = Utils.join(out.toArray(), ", ");
assertEquals(output, actual);
}

@Parameterized.Parameters(name="{0}")
public static Collection<Object[]> getAllTestDescriptors() {
List<Object[]> tests = new ArrayList<>();
for (int i = 0; i < argPairs.length; i+=2) {
String arg = argPairs[i];
String output = argPairs[i+1];
tests.add(new Object[]{arg,output});
}
return tests;
}
}
6 changes: 3 additions & 3 deletions tool/src/org/antlr/v4/parse/ScopeParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

import org.antlr.runtime.BaseRecognizer;
import org.antlr.runtime.CommonToken;
import org.antlr.v4.runtime.misc.IntegerList;
import org.antlr.v4.runtime.misc.Pair;
import org.antlr.v4.tool.Attribute;
import org.antlr.v4.tool.AttributeDict;
Expand All @@ -49,7 +48,8 @@
* rule[arg1, arg2, ..., argN] returns [ret1, ..., retN]
* <p>
* text is target language dependent. Java/C#/C/C++ would
* use "int i" but ruby/python would use "i".
* use "int i" but ruby/python would use "i". Languages with
* postfix types like Go, Swift use "x : T" notation or "T x".
*/
public class ScopeParser {
/**
Expand Down Expand Up @@ -97,7 +97,7 @@ public static Attribute parseAttributeDef(ActionAST action, Pair<String, Integer
int equalsIndex = decl.a.indexOf('=');
if (equalsIndex > 0) {
// everything after the '=' is the init value
attr.initValue = decl.a.substring(equalsIndex + 1, decl.a.length());
attr.initValue = decl.a.substring(equalsIndex + 1, decl.a.length()).trim();
rightEdgeOfDeclarator = equalsIndex - 1;
}

Expand Down
13 changes: 8 additions & 5 deletions tool/src/org/antlr/v4/tool/Attribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@

import org.antlr.runtime.Token;

/** Track the names of attributes define in arg lists, return values,
/** Track the names of attributes defined in arg lists, return values,
* scope blocks etc...
*/
public class Attribute {
/** The entire declaration such as "String foo;" */
/** The entire declaration such as "String foo" or "x:int" */
public String decl;

/** The type; might be empty such as for Python which has no static typing */
Expand Down Expand Up @@ -66,8 +66,11 @@ public Attribute(String name, String decl) {
@Override
public String toString() {
if ( initValue!=null ) {
return type+" "+name+"="+initValue;
return name+":"+type+"="+initValue;
}
return type+" "+name;
if ( type!=null ) {
return name+":"+type;
}
return name;
}
}
}
2 changes: 1 addition & 1 deletion tool/src/org/antlr/v4/tool/AttributeDict.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class AttributeDict {
predefinedTokenDict.add(new Attribute("int"));
}

public static enum DictType {
public enum DictType {
ARG, RET, LOCAL, TOKEN,
PREDEFINED_RULE, PREDEFINED_LEXER_RULE,
}
Expand Down

0 comments on commit 5aae2ed

Please sign in to comment.