good start on * and ? in rewrites
[git-p4: depot-paths = "//depot/code/antlr4/main/": change = 8826]
This commit is contained in:
parent
fbc92258f6
commit
df80cb7622
|
@ -1,11 +1,15 @@
|
||||||
grammar T;
|
grammar T;
|
||||||
options {output=AST;}
|
options {output=AST;}
|
||||||
tokens {I;}
|
tokens {I;}
|
||||||
|
/*
|
||||||
a : A b C -> ^(A ^(b C))
|
a : A b C -> ^(A ^(b C))
|
||||||
| B
|
| B
|
||||||
;
|
;
|
||||||
|
|
||||||
b : B | C ;
|
b : B | C ;
|
||||||
|
*/
|
||||||
|
|
||||||
|
c : A B C -> (A B C*)* ;
|
||||||
|
|
||||||
A : 'a';
|
A : 'a';
|
||||||
B : 'b';
|
B : 'b';
|
||||||
|
|
|
@ -2,7 +2,6 @@ import org.antlr.v4.Tool;
|
||||||
import org.antlr.v4.automata.ParserATNFactory;
|
import org.antlr.v4.automata.ParserATNFactory;
|
||||||
import org.antlr.v4.runtime.*;
|
import org.antlr.v4.runtime.*;
|
||||||
import org.antlr.v4.runtime.atn.ATN;
|
import org.antlr.v4.runtime.atn.ATN;
|
||||||
import org.antlr.v4.runtime.tree.Tree;
|
|
||||||
import org.antlr.v4.semantics.SemanticPipeline;
|
import org.antlr.v4.semantics.SemanticPipeline;
|
||||||
import org.antlr.v4.tool.*;
|
import org.antlr.v4.tool.*;
|
||||||
|
|
||||||
|
@ -13,8 +12,8 @@ public class TestT {
|
||||||
TLexer t = new TLexer(new ANTLRFileStream(args[0]));
|
TLexer t = new TLexer(new ANTLRFileStream(args[0]));
|
||||||
CommonTokenStream tokens = new CommonTokenStream(t);
|
CommonTokenStream tokens = new CommonTokenStream(t);
|
||||||
TParser p = new TParser(tokens);
|
TParser p = new TParser(tokens);
|
||||||
ParserRuleContext ret = p.a();
|
// ParserRuleContext ret = p.a();
|
||||||
System.out.println(((Tree)ret.tree).toStringTree());
|
// System.out.println(((Tree)ret.tree).toStringTree());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dump() throws Exception {
|
public static void dump() throws Exception {
|
||||||
|
|
|
@ -389,6 +389,28 @@ TreeRewrite(tr, locals, preamble, ops) ::= <<
|
||||||
_localctx.tree = _root0;
|
_localctx.tree = _root0;
|
||||||
>>
|
>>
|
||||||
|
|
||||||
|
RewriteIteratorDecl(d) ::= "Iterator <d.name>;"
|
||||||
|
RewriteIteratorInit(i) ::= "<i.decl.name> = <i.decl.listName>.iterator();"
|
||||||
|
RewriteIteratorName(elemName,level) ::= "it<level>_<elemName>"
|
||||||
|
|
||||||
|
RewriteTreeOptional(o, locals, preamble, ops) ::= <<
|
||||||
|
// ?
|
||||||
|
<locals; separator="\n">
|
||||||
|
<preamble; separator="\n">
|
||||||
|
//if ( true ) {
|
||||||
|
<ops; separator="\n">
|
||||||
|
//}
|
||||||
|
>>
|
||||||
|
|
||||||
|
RewriteTreeClosure(c, locals, preamble, ops) ::= <<
|
||||||
|
// *
|
||||||
|
<locals; separator="\n">
|
||||||
|
<preamble; separator="\n">
|
||||||
|
//while ( <c.iteratorDecls:{d | <d.name>.hasNext()}; separator=" || "> ) {
|
||||||
|
<ops; separator="\n">
|
||||||
|
//}
|
||||||
|
>>
|
||||||
|
|
||||||
RewriteTreeStructure(t, locals, preamble, ops) ::= <<
|
RewriteTreeStructure(t, locals, preamble, ops) ::= <<
|
||||||
{
|
{
|
||||||
<locals; separator="\n">
|
<locals; separator="\n">
|
||||||
|
@ -397,15 +419,11 @@ RewriteTreeStructure(t, locals, preamble, ops) ::= <<
|
||||||
_adaptor.addChild(<labelref(t.enclosingBlock.rootDecl)>, <labelref(t.rootDecl)>);
|
_adaptor.addChild(<labelref(t.enclosingBlock.rootDecl)>, <labelref(t.rootDecl)>);
|
||||||
}
|
}
|
||||||
>>
|
>>
|
||||||
RewriteIteratorDecl(d) ::= "Iterator <d.name>;"
|
|
||||||
RewriteIteratorInit(i) ::= "<i.decl.name> = <i.decl.listName>.iterator();"
|
|
||||||
RewriteIteratorName(elemName,level) ::= "it<level>_<elemName>"
|
|
||||||
|
|
||||||
RewriteTokenRef(t) ::= "_adaptor.addChild(<t.enclosingBlock.rootDecl.name>, <t.decl.name>.next());"
|
RewriteTokenRef(t) ::= "_adaptor.addChild(<t.rootName>, <t.decl.name>.next());"
|
||||||
RewriteTokenRefIsRoot(t) ::= <%
|
RewriteTokenRefIsRoot(t) ::= <<
|
||||||
<labelref(t.enclosingBlock.rootDecl)> =
|
<t.rootName> = _adaptor.becomeRoot(<t.decl.name>.next(), <t.rootName>);
|
||||||
_adaptor.becomeRoot(<t.decl.name>.next(), <labelref(t.enclosingBlock.rootDecl)>);
|
>>
|
||||||
%>
|
|
||||||
RewriteRuleRef(r) ::= "_adaptor.addChild(<r.enclosingBlock.rootDecl.name>, <r.decl.name>.next());"
|
RewriteRuleRef(r) ::= "_adaptor.addChild(<r.enclosingBlock.rootDecl.name>, <r.decl.name>.next());"
|
||||||
RewriteRuleRefIsRoot(r) ::= <%
|
RewriteRuleRefIsRoot(r) ::= <%
|
||||||
<labelref(r.enclosingBlock.rootDecl)> =
|
<labelref(r.enclosingBlock.rootDecl)> =
|
||||||
|
|
|
@ -79,9 +79,13 @@ public abstract class BlankOutputModelFactory implements OutputModelFactory {
|
||||||
|
|
||||||
// AST REWRITES
|
// AST REWRITES
|
||||||
|
|
||||||
public TreeRewrite treeRewrite(GrammarAST ast, int rewriteLevel) { return null; }
|
public TreeRewrite treeRewrite(GrammarAST ast) { return null; }
|
||||||
|
|
||||||
public RewriteTreeStructure rewrite_tree(GrammarAST root, int rewriteLevel) { return null; }
|
public RewriteTreeOptional rewrite_optional(GrammarAST ast) { return null; }
|
||||||
|
|
||||||
|
public RewriteTreeClosure rewrite_closure(GrammarAST ast) { return null; }
|
||||||
|
|
||||||
|
public RewriteTreeStructure rewrite_tree(GrammarAST root) { return null; }
|
||||||
|
|
||||||
public List<SrcOp> rewrite_ruleRef(GrammarAST ID, boolean isRoot) { return null; }
|
public List<SrcOp> rewrite_ruleRef(GrammarAST ID, boolean isRoot) { return null; }
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,10 @@ public class CodeGeneratorExtension {
|
||||||
|
|
||||||
public TreeRewrite treeRewrite(TreeRewrite r) { return r; }
|
public TreeRewrite treeRewrite(TreeRewrite r) { return r; }
|
||||||
|
|
||||||
|
public RewriteTreeOptional rewrite_optional(RewriteTreeOptional o) { return o; }
|
||||||
|
|
||||||
|
public RewriteTreeClosure rewrite_closure(RewriteTreeClosure c) { return c; }
|
||||||
|
|
||||||
public RewriteTreeStructure rewrite_tree(RewriteTreeStructure t) { return t; }
|
public RewriteTreeStructure rewrite_tree(RewriteTreeStructure t) { return t; }
|
||||||
|
|
||||||
public List<SrcOp> rewrite_ruleRef(List<SrcOp> ops) { return ops; }
|
public List<SrcOp> rewrite_ruleRef(List<SrcOp> ops) { return ops; }
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
package org.antlr.v4.codegen;
|
package org.antlr.v4.codegen;
|
||||||
|
|
||||||
import org.antlr.v4.codegen.model.*;
|
import org.antlr.v4.codegen.model.*;
|
||||||
import org.antlr.v4.codegen.model.decl.CodeBlock;
|
import org.antlr.v4.codegen.model.decl.*;
|
||||||
import org.antlr.v4.tool.*;
|
import org.antlr.v4.tool.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -93,6 +93,10 @@ public abstract class DefaultOutputModelFactory extends BlankOutputModelFactory
|
||||||
return currentBlock;
|
return currentBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCodeBlockLevel() { return controller.walker.codeBlockLevel; }
|
||||||
|
|
||||||
|
public int getTreeLevel() { return controller.walker.treeLevel; }
|
||||||
|
|
||||||
// MISC
|
// MISC
|
||||||
|
|
||||||
public static List<SrcOp> list(Object... values) {
|
public static List<SrcOp> list(Object... values) {
|
||||||
|
@ -105,5 +109,13 @@ public abstract class DefaultOutputModelFactory extends BlankOutputModelFactory
|
||||||
}
|
}
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Decl getCurrentDeclForName(String name) {
|
||||||
|
for (Decl d : getCurrentBlock().locals.elements()) {
|
||||||
|
if ( d.name.equals(name) ) return d;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,11 @@ public class OutputModelController implements OutputModelFactory {
|
||||||
/** Post-processing CodeGeneratorExtension objects; done in order given. */
|
/** Post-processing CodeGeneratorExtension objects; done in order given. */
|
||||||
public List<CodeGeneratorExtension> extensions = new ArrayList<CodeGeneratorExtension>();
|
public List<CodeGeneratorExtension> extensions = new ArrayList<CodeGeneratorExtension>();
|
||||||
|
|
||||||
|
/** While walking code in rules, this is set to the tree walker that
|
||||||
|
* triggers actions.
|
||||||
|
*/
|
||||||
|
public SourceGenTriggers walker;
|
||||||
|
|
||||||
public OutputModelController(OutputModelFactory factory) {
|
public OutputModelController(OutputModelFactory factory) {
|
||||||
this.delegate = factory;
|
this.delegate = factory;
|
||||||
}
|
}
|
||||||
|
@ -76,9 +81,9 @@ public class OutputModelController implements OutputModelFactory {
|
||||||
GrammarASTAdaptor adaptor = new GrammarASTAdaptor(r.ast.token.getInputStream());
|
GrammarASTAdaptor adaptor = new GrammarASTAdaptor(r.ast.token.getInputStream());
|
||||||
GrammarAST blk = (GrammarAST)r.ast.getFirstChildWithType(ANTLRParser.BLOCK);
|
GrammarAST blk = (GrammarAST)r.ast.getFirstChildWithType(ANTLRParser.BLOCK);
|
||||||
CommonTreeNodeStream nodes = new CommonTreeNodeStream(adaptor,blk);
|
CommonTreeNodeStream nodes = new CommonTreeNodeStream(adaptor,blk);
|
||||||
SourceGenTriggers genTriggers = new SourceGenTriggers(nodes, this);
|
walker = new SourceGenTriggers(nodes, this);
|
||||||
try {
|
try {
|
||||||
function.code = DefaultOutputModelFactory.list(genTriggers.block(null, null)); // walk AST of rule alts/elements
|
function.code = DefaultOutputModelFactory.list(walker.block(null, null)); // walk AST of rule alts/elements
|
||||||
}
|
}
|
||||||
catch (Exception e){
|
catch (Exception e){
|
||||||
e.printStackTrace(System.err);
|
e.printStackTrace(System.err);
|
||||||
|
@ -278,14 +283,27 @@ public class OutputModelController implements OutputModelFactory {
|
||||||
|
|
||||||
// REWRITES
|
// REWRITES
|
||||||
|
|
||||||
public TreeRewrite treeRewrite(GrammarAST ast, int rewriteLevel) {
|
public TreeRewrite treeRewrite(GrammarAST ast) {
|
||||||
TreeRewrite r = delegate.treeRewrite(ast, rewriteLevel);
|
TreeRewrite r = delegate.treeRewrite(ast);
|
||||||
for (CodeGeneratorExtension ext : extensions) r = ext.treeRewrite(r);
|
for (CodeGeneratorExtension ext : extensions) r = ext.treeRewrite(r);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RewriteTreeStructure rewrite_tree(GrammarAST root, int rewriteLevel) {
|
public RewriteTreeOptional rewrite_optional(GrammarAST ast) {
|
||||||
RewriteTreeStructure t = delegate.rewrite_tree(root, rewriteLevel);
|
RewriteTreeOptional o = delegate.rewrite_optional(ast);
|
||||||
|
for (CodeGeneratorExtension ext : extensions) o = ext.rewrite_optional(o);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RewriteTreeClosure rewrite_closure(GrammarAST ast) {
|
||||||
|
List<GrammarAST> refs = getElementReferencesShallow(ast);
|
||||||
|
RewriteTreeClosure c = delegate.rewrite_closure(ast);
|
||||||
|
for (CodeGeneratorExtension ext : extensions) c = ext.rewrite_closure(c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RewriteTreeStructure rewrite_tree(GrammarAST root) {
|
||||||
|
RewriteTreeStructure t = delegate.rewrite_tree(root);
|
||||||
for (CodeGeneratorExtension ext : extensions) t = ext.rewrite_tree(t);
|
for (CodeGeneratorExtension ext : extensions) t = ext.rewrite_tree(t);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
@ -323,4 +341,76 @@ public class OutputModelController implements OutputModelFactory {
|
||||||
public void setCurrentBlock(CodeBlock blk) { delegate.setCurrentBlock(blk); }
|
public void setCurrentBlock(CodeBlock blk) { delegate.setCurrentBlock(blk); }
|
||||||
|
|
||||||
public CodeBlock getCurrentBlock() { return delegate.getCurrentBlock(); }
|
public CodeBlock getCurrentBlock() { return delegate.getCurrentBlock(); }
|
||||||
|
|
||||||
|
public int getCodeBlockLevel() { return delegate.getCodeBlockLevel(); }
|
||||||
|
|
||||||
|
public int getTreeLevel() { return delegate.getTreeLevel(); }
|
||||||
|
|
||||||
|
// SUPPORT
|
||||||
|
|
||||||
|
/** Given (('?'|'*') (REWRITE_BLOCK (ALT ...))) return list of element refs at
|
||||||
|
* top level of REWRITE_BLOCK.
|
||||||
|
*/
|
||||||
|
public List<GrammarAST> getElementReferencesShallow(GrammarAST ebnfRoot) {
|
||||||
|
if ( ebnfRoot.getType()!=ANTLRParser.CLOSURE &&
|
||||||
|
ebnfRoot.getType()!=ANTLRParser.OPTIONAL )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
GrammarAST blkAST = (GrammarAST)ebnfRoot.getChild(0);
|
||||||
|
if ( blkAST.getType()!=ANTLRParser.REWRITE_BLOCK ) return null;
|
||||||
|
GrammarAST altAST = (GrammarAST)blkAST.getChild(0);
|
||||||
|
if ( altAST.getType()!=ANTLRParser.ALT ) return null;
|
||||||
|
|
||||||
|
IntervalSet elementTokenTypes = getRewriteElementTokenTypeSet();
|
||||||
|
Alternative alt = getCurrentAlt();
|
||||||
|
List<GrammarAST> elems = new ArrayList<GrammarAST>();
|
||||||
|
for (Object o : altAST.getChildren()) {
|
||||||
|
GrammarAST ref = (GrammarAST)o;
|
||||||
|
if ( elementTokenTypes.member(ref.getType()) ) {
|
||||||
|
boolean imaginary = ref.getType()==ANTLRParser.TOKEN_REF &&
|
||||||
|
!alt.tokenRefs.containsKey(ref.getText());
|
||||||
|
if ( !imaginary ) elems.add(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elems;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Given (('?'|'*') (REWRITE_BLOCK (ALT ...))) return list of element refs at
|
||||||
|
* or below toplevel REWRITE_BLOCK.
|
||||||
|
*/
|
||||||
|
public List<GrammarAST> getElementReferencesDeep(GrammarAST ebnfRoot) {
|
||||||
|
if ( ebnfRoot.getType()!=ANTLRParser.CLOSURE &&
|
||||||
|
ebnfRoot.getType()!=ANTLRParser.OPTIONAL )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
GrammarAST blkAST = (GrammarAST)ebnfRoot.getChild(0);
|
||||||
|
if ( blkAST.getType()!=ANTLRParser.REWRITE_BLOCK ) return null;
|
||||||
|
GrammarAST altAST = (GrammarAST)blkAST.getChild(0);
|
||||||
|
if ( altAST.getType()!=ANTLRParser.ALT ) return null;
|
||||||
|
|
||||||
|
List<GrammarAST> elems = new ArrayList<GrammarAST>();
|
||||||
|
Alternative alt = getCurrentAlt();
|
||||||
|
IntervalSet elementTokenTypes = getRewriteElementTokenTypeSet();
|
||||||
|
List<GrammarAST> refs = altAST.getNodesWithType(elementTokenTypes);
|
||||||
|
if ( refs!=null ) {
|
||||||
|
for (GrammarAST ref : refs) {
|
||||||
|
boolean imaginary = ref.getType()==ANTLRParser.TOKEN_REF &&
|
||||||
|
!alt.tokenRefs.containsKey(ref.getText());
|
||||||
|
if ( !imaginary ) elems.add(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return elems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntervalSet getRewriteElementTokenTypeSet() {
|
||||||
|
IntervalSet elementTokenTypes = new IntervalSet();
|
||||||
|
elementTokenTypes.add(ANTLRParser.TOKEN_REF); // might be imaginary
|
||||||
|
elementTokenTypes.add(ANTLRParser.RULE_REF);
|
||||||
|
elementTokenTypes.add(ANTLRParser.STRING_LITERAL);
|
||||||
|
elementTokenTypes.add(ANTLRParser.LABEL);
|
||||||
|
return elementTokenTypes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,9 +98,13 @@ public interface OutputModelFactory {
|
||||||
// Though dealing with ASTs, we must deal with here since these are
|
// Though dealing with ASTs, we must deal with here since these are
|
||||||
// triggered from elements in ANTLR's internal GrammarAST
|
// triggered from elements in ANTLR's internal GrammarAST
|
||||||
|
|
||||||
TreeRewrite treeRewrite(GrammarAST ast, int rewriteLevel);
|
TreeRewrite treeRewrite(GrammarAST ast);
|
||||||
|
|
||||||
RewriteTreeStructure rewrite_tree(GrammarAST root, int rewriteLevel);
|
RewriteTreeOptional rewrite_optional(GrammarAST ast);
|
||||||
|
|
||||||
|
RewriteTreeClosure rewrite_closure(GrammarAST ast);
|
||||||
|
|
||||||
|
RewriteTreeStructure rewrite_tree(GrammarAST root);
|
||||||
|
|
||||||
List<SrcOp> rewrite_ruleRef(GrammarAST ID, boolean isRoot);
|
List<SrcOp> rewrite_ruleRef(GrammarAST ID, boolean isRoot);
|
||||||
|
|
||||||
|
@ -110,6 +114,8 @@ public interface OutputModelFactory {
|
||||||
|
|
||||||
// CONTEXT MANIPULATION
|
// CONTEXT MANIPULATION
|
||||||
|
|
||||||
|
// TODO: move to controller?
|
||||||
|
|
||||||
OutputModelObject getRoot();
|
OutputModelObject getRoot();
|
||||||
|
|
||||||
void setRoot(OutputModelObject root);
|
void setRoot(OutputModelObject root);
|
||||||
|
@ -129,4 +135,8 @@ public interface OutputModelFactory {
|
||||||
void setCurrentBlock(CodeBlock blk);
|
void setCurrentBlock(CodeBlock blk);
|
||||||
|
|
||||||
CodeBlock getCurrentBlock();
|
CodeBlock getCurrentBlock();
|
||||||
|
|
||||||
|
int getCodeBlockLevel();
|
||||||
|
|
||||||
|
int getTreeLevel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class ParserASTExtension extends CodeGeneratorExtension {
|
||||||
@Override
|
@Override
|
||||||
public CodeBlockForAlt alternative(CodeBlockForAlt blk) {
|
public CodeBlockForAlt alternative(CodeBlockForAlt blk) {
|
||||||
Alternative alt = factory.getCurrentAlt();
|
Alternative alt = factory.getCurrentAlt();
|
||||||
if ( !alt.hasRewrite() ) blk.addLocalDecl( new RootDecl(factory, 0) );
|
if ( !alt.hasRewrite() ) blk.addLocalDecl( new RootDecl(factory) );
|
||||||
return blk;
|
return blk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -169,29 +169,49 @@ public class ParserFactory extends DefaultOutputModelFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean needsImplicitLabel(GrammarAST ID, LabeledOp op) {
|
public boolean needsImplicitLabel(GrammarAST ID, LabeledOp op) {
|
||||||
return op.getLabels().size()==0 &&
|
return op.getLabels().size()==0 &&
|
||||||
(getCurrentAlt().tokenRefsInActions.containsKey(ID.getText()) ||
|
(getCurrentAlt().tokenRefsInActions.containsKey(ID.getText()) ||
|
||||||
getCurrentAlt().ruleRefsInActions.containsKey(ID.getText()));
|
getCurrentAlt().ruleRefsInActions.containsKey(ID.getText()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// AST REWRITE
|
// AST REWRITE
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TreeRewrite treeRewrite(GrammarAST ast, int rewriteLevel) {
|
public TreeRewrite treeRewrite(GrammarAST ast) {
|
||||||
return new TreeRewrite(this, rewriteLevel);
|
return new TreeRewrite(this, getTreeLevel(), getCodeBlockLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RewriteTreeStructure rewrite_tree(GrammarAST root, int rewriteLevel) {
|
public RewriteTreeOptional rewrite_optional(GrammarAST ast) {
|
||||||
return new RewriteTreeStructure(this, root, rewriteLevel);
|
List<GrammarAST> refs = controller.getElementReferencesDeep(ast);
|
||||||
|
return new RewriteTreeOptional(this, ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RewriteTreeClosure rewrite_closure(GrammarAST ast) {
|
||||||
|
RewriteTreeClosure c =
|
||||||
|
new RewriteTreeClosure(this, ast, getTreeLevel(), getCodeBlockLevel());
|
||||||
|
List<GrammarAST> refs = controller.getElementReferencesShallow(ast);
|
||||||
|
if ( refs!=null ) {
|
||||||
|
for (GrammarAST ref : refs) {
|
||||||
|
RewriteIteratorDecl d = new RewriteIteratorDecl(this, ref);
|
||||||
|
c.addLocalDecl(d);
|
||||||
|
c.iteratorDecls.add(d);
|
||||||
|
RewriteIteratorInit init = new RewriteIteratorInit(this, d);
|
||||||
|
c.addPreambleOp(init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RewriteTreeStructure rewrite_tree(GrammarAST root) {
|
||||||
|
return new RewriteTreeStructure(this, root, getTreeLevel(), getCodeBlockLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SrcOp> rewrite_ruleRef(GrammarAST ID, boolean isRoot) {
|
public List<SrcOp> rewrite_ruleRef(GrammarAST ID, boolean isRoot) {
|
||||||
RewriteIteratorDecl d = new RewriteIteratorDecl(this, ID, 0);
|
RewriteIteratorDecl d = new RewriteIteratorDecl(this, ID);
|
||||||
getCurrentBlock().addLocalDecl(d);
|
|
||||||
RewriteIteratorInit init = new RewriteIteratorInit(this, d);
|
|
||||||
getCurrentBlock().addPreambleOp(init);
|
|
||||||
RewriteRuleRef ruleRef;
|
RewriteRuleRef ruleRef;
|
||||||
if ( isRoot ) ruleRef = new RewriteRuleRefIsRoot(this, ID, d);
|
if ( isRoot ) ruleRef = new RewriteRuleRefIsRoot(this, ID, d);
|
||||||
else ruleRef = new RewriteRuleRef(this, ID, d);
|
else ruleRef = new RewriteRuleRef(this, ID, d);
|
||||||
|
@ -199,13 +219,12 @@ public class ParserFactory extends DefaultOutputModelFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SrcOp> rewrite_tokenRef(GrammarAST ID, boolean isRoot) {
|
public List<SrcOp> rewrite_tokenRef(GrammarAST ID, boolean isRoot) {
|
||||||
RewriteIteratorDecl d = new RewriteIteratorDecl(this, ID, 0);
|
String itName = gen.target.getRewriteIteratorName(ID, getCodeBlockLevel());
|
||||||
getCurrentBlock().addLocalDecl(d);
|
String rootName = gen.target.getRootName(getTreeLevel());
|
||||||
RewriteIteratorInit init = new RewriteIteratorInit(this, d);
|
RewriteIteratorDecl d = (RewriteIteratorDecl)getCurrentDeclForName(itName);
|
||||||
getCurrentBlock().addPreambleOp(init);
|
|
||||||
RewriteTokenRef tokenRef;
|
RewriteTokenRef tokenRef;
|
||||||
if ( isRoot ) tokenRef = new RewriteTokenRefIsRoot(this, ID, d);
|
if ( isRoot ) tokenRef = new RewriteTokenRefIsRoot(this, ID, rootName, d);
|
||||||
else tokenRef = new RewriteTokenRef(this, ID, d);
|
else tokenRef = new RewriteTokenRef(this, ID, rootName, d);
|
||||||
return list(tokenRef);
|
return list(tokenRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,8 @@ import java.util.HashMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@members {
|
@members {
|
||||||
public int codeBlockLevel = 0;
|
public int codeBlockLevel = -1;
|
||||||
|
public int treeLevel = -1;
|
||||||
public OutputModelFactory factory;
|
public OutputModelFactory factory;
|
||||||
public SourceGenTriggers(TreeNodeStream input, OutputModelFactory factory) {
|
public SourceGenTriggers(TreeNodeStream input, OutputModelFactory factory) {
|
||||||
this(input);
|
this(input);
|
||||||
|
@ -194,7 +195,9 @@ elementOption
|
||||||
|
|
||||||
rewrite returns [Rewrite code]
|
rewrite returns [Rewrite code]
|
||||||
: {
|
: {
|
||||||
$code = factory.treeRewrite($start, codeBlockLevel++);
|
treeLevel = 0;
|
||||||
|
codeBlockLevel++;
|
||||||
|
$code = factory.treeRewrite($start);
|
||||||
CodeBlock save = factory.getCurrentBlock();
|
CodeBlock save = factory.getCurrentBlock();
|
||||||
factory.setCurrentBlock($code);
|
factory.setCurrentBlock($code);
|
||||||
}
|
}
|
||||||
|
@ -229,7 +232,7 @@ rewriteTreeAlt returns [List<SrcOp> omos]
|
||||||
rewriteTreeElement returns [List<SrcOp> omos]
|
rewriteTreeElement returns [List<SrcOp> omos]
|
||||||
: rewriteTreeAtom[false] {$omos = $rewriteTreeAtom.omos;}
|
: rewriteTreeAtom[false] {$omos = $rewriteTreeAtom.omos;}
|
||||||
| rewriteTree {$omos = $rewriteTree.omos;}
|
| rewriteTree {$omos = $rewriteTree.omos;}
|
||||||
| rewriteTreeEbnf {$omos = $rewriteTreeEbnf.omos;}
|
| rewriteTreeEbnf {$omos = DefaultOutputModelFactory.list($rewriteTreeEbnf.op);}
|
||||||
;
|
;
|
||||||
|
|
||||||
rewriteTreeAtom[boolean isRoot] returns [List<SrcOp> omos]
|
rewriteTreeAtom[boolean isRoot] returns [List<SrcOp> omos]
|
||||||
|
@ -244,15 +247,32 @@ rewriteTreeAtom[boolean isRoot] returns [List<SrcOp> omos]
|
||||||
| ACTION
|
| ACTION
|
||||||
;
|
;
|
||||||
|
|
||||||
rewriteTreeEbnf returns [List<SrcOp> omos]
|
rewriteTreeEbnf returns [CodeBlock op]
|
||||||
: ^('?' ^(REWRITE_BLOCK rewriteTreeAlt))
|
: ^( (a=OPTIONAL|a=CLOSURE)
|
||||||
| ^('*' ^(REWRITE_BLOCK rewriteTreeAlt))
|
^( REWRITE_BLOCK
|
||||||
|
{
|
||||||
|
codeBlockLevel++;
|
||||||
|
if ( $a.getType()==OPTIONAL ) $op = factory.rewrite_optional($start);
|
||||||
|
else $op = factory.rewrite_closure($start);
|
||||||
|
CodeBlock save = factory.getCurrentBlock();
|
||||||
|
factory.setCurrentBlock($op);
|
||||||
|
}
|
||||||
|
alt=rewriteTreeAlt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$op.addOps($alt.omos);
|
||||||
|
factory.setCurrentBlock(save);
|
||||||
|
codeBlockLevel--;
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
rewriteTree returns [List<SrcOp> omos]
|
rewriteTree returns [List<SrcOp> omos]
|
||||||
: {
|
: {
|
||||||
|
codeBlockLevel++;
|
||||||
|
treeLevel++;
|
||||||
List<SrcOp> elems = new ArrayList<SrcOp>();
|
List<SrcOp> elems = new ArrayList<SrcOp>();
|
||||||
RewriteTreeStructure t = factory.rewrite_tree($start, codeBlockLevel++);
|
RewriteTreeStructure t = factory.rewrite_tree($start);
|
||||||
CodeBlock save = factory.getCurrentBlock();
|
CodeBlock save = factory.getCurrentBlock();
|
||||||
factory.setCurrentBlock(t);
|
factory.setCurrentBlock(t);
|
||||||
}
|
}
|
||||||
|
@ -264,6 +284,7 @@ rewriteTree returns [List<SrcOp> omos]
|
||||||
t.ops = elems;
|
t.ops = elems;
|
||||||
$omos = DefaultOutputModelFactory.list(t);
|
$omos = DefaultOutputModelFactory.list(t);
|
||||||
factory.setCurrentBlock(save);
|
factory.setCurrentBlock(save);
|
||||||
|
treeLevel--;
|
||||||
codeBlockLevel--;
|
codeBlockLevel--;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.antlr.v4.codegen.OutputModelFactory;
|
||||||
import org.antlr.v4.codegen.model.decl.CodeBlock;
|
import org.antlr.v4.codegen.model.decl.CodeBlock;
|
||||||
|
|
||||||
public class Rewrite extends CodeBlock {
|
public class Rewrite extends CodeBlock {
|
||||||
public Rewrite(OutputModelFactory factory) {
|
public Rewrite(OutputModelFactory factory, int treeLevel, int codeBlockLevel) {
|
||||||
super(factory);
|
super(factory, treeLevel, codeBlockLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ public class RuleFunction extends OutputModelObject {
|
||||||
public ATNState startState;
|
public ATNState startState;
|
||||||
public int index;
|
public int index;
|
||||||
public Collection<Attribute> args = null;
|
public Collection<Attribute> args = null;
|
||||||
|
public Rule rule;
|
||||||
|
|
||||||
@ModelElement public List<SrcOp> code;
|
@ModelElement public List<SrcOp> code;
|
||||||
@ModelElement public OrderedHashSet<Decl> locals; // TODO: move into ctx?
|
@ModelElement public OrderedHashSet<Decl> locals; // TODO: move into ctx?
|
||||||
|
@ -59,13 +60,10 @@ public class RuleFunction extends OutputModelObject {
|
||||||
@ModelElement public Action finallyAction;
|
@ModelElement public Action finallyAction;
|
||||||
@ModelElement public List<SrcOp> postamble;
|
@ModelElement public List<SrcOp> postamble;
|
||||||
|
|
||||||
public RuleFunction(OutputModelFactory factory) {
|
|
||||||
super(factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuleFunction(OutputModelFactory factory, Rule r) {
|
public RuleFunction(OutputModelFactory factory, Rule r) {
|
||||||
super(factory);
|
super(factory);
|
||||||
this.name = r.name;
|
this.name = r.name;
|
||||||
|
this.rule = r;
|
||||||
if ( r.modifiers!=null && r.modifiers.size()>0 ) {
|
if ( r.modifiers!=null && r.modifiers.size()>0 ) {
|
||||||
this.modifiers = new ArrayList<String>();
|
this.modifiers = new ArrayList<String>();
|
||||||
for (GrammarAST t : r.modifiers) modifiers.add(t.getText());
|
for (GrammarAST t : r.modifiers) modifiers.add(t.getText());
|
||||||
|
|
|
@ -37,8 +37,12 @@ import org.antlr.v4.tool.GrammarAST;
|
||||||
public class RewriteTokenRef extends SrcOp {
|
public class RewriteTokenRef extends SrcOp {
|
||||||
/** Which iterator decl are we associated with? */
|
/** Which iterator decl are we associated with? */
|
||||||
public RewriteIteratorDecl decl;
|
public RewriteIteratorDecl decl;
|
||||||
public RewriteTokenRef(OutputModelFactory factory, GrammarAST ast, RewriteIteratorDecl decl) {
|
public String rootName;
|
||||||
|
public RewriteTokenRef(OutputModelFactory factory, GrammarAST ast,
|
||||||
|
String rootName, RewriteIteratorDecl decl)
|
||||||
|
{
|
||||||
super(factory, ast);
|
super(factory, ast);
|
||||||
|
this.rootName = rootName;
|
||||||
this.decl = decl;
|
this.decl = decl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,9 @@ import org.antlr.v4.codegen.model.decl.RewriteIteratorDecl;
|
||||||
import org.antlr.v4.tool.GrammarAST;
|
import org.antlr.v4.tool.GrammarAST;
|
||||||
|
|
||||||
public class RewriteTokenRefIsRoot extends RewriteTokenRef {
|
public class RewriteTokenRefIsRoot extends RewriteTokenRef {
|
||||||
public RewriteTokenRefIsRoot(OutputModelFactory factory, GrammarAST ast, RewriteIteratorDecl decl) {
|
public RewriteTokenRefIsRoot(OutputModelFactory factory, GrammarAST ast,
|
||||||
super(factory, ast, decl);
|
String rootName, RewriteIteratorDecl decl)
|
||||||
|
{
|
||||||
|
super(factory, ast, rootName, decl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
[The "BSD license"]
|
||||||
|
Copyright (c) 2011 Terence Parr
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
3. The name of the author may not be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.antlr.v4.codegen.model.ast;
|
||||||
|
|
||||||
|
import org.antlr.v4.codegen.OutputModelFactory;
|
||||||
|
import org.antlr.v4.codegen.model.decl.*;
|
||||||
|
import org.antlr.v4.tool.GrammarAST;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class RewriteTreeClosure extends CodeBlock {
|
||||||
|
public List<Decl> iteratorDecls = new ArrayList<Decl>();
|
||||||
|
|
||||||
|
public RewriteTreeClosure(OutputModelFactory factory, GrammarAST ast,
|
||||||
|
int treeLevel, int codeBlockLevel)
|
||||||
|
{
|
||||||
|
super(factory, treeLevel, codeBlockLevel);
|
||||||
|
this.ast = ast;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,15 +27,19 @@
|
||||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.antlr.v4.codegen.model.decl;
|
package org.antlr.v4.codegen.model.ast;
|
||||||
|
|
||||||
import org.antlr.v4.codegen.OutputModelFactory;
|
import org.antlr.v4.codegen.OutputModelFactory;
|
||||||
|
import org.antlr.v4.codegen.model.decl.*;
|
||||||
|
import org.antlr.v4.tool.GrammarAST;
|
||||||
|
|
||||||
public class NestedDecl extends Decl {
|
import java.util.*;
|
||||||
public int level;
|
|
||||||
|
|
||||||
public NestedDecl(OutputModelFactory factory, String name, int level) {
|
public class RewriteTreeOptional extends CodeBlock {
|
||||||
super(factory, name);
|
public List<Decl> iteratorDecls = new ArrayList<Decl>();
|
||||||
this.level = level;
|
|
||||||
|
public RewriteTreeOptional(OutputModelFactory factory, GrammarAST ast) {
|
||||||
|
super(factory);
|
||||||
|
this.ast = ast;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -34,10 +34,14 @@ import org.antlr.v4.tool.GrammarAST;
|
||||||
|
|
||||||
/** ^(A B C) */
|
/** ^(A B C) */
|
||||||
public class RewriteTreeStructure extends TreeRewrite {
|
public class RewriteTreeStructure extends TreeRewrite {
|
||||||
|
public int treeLevel;
|
||||||
|
|
||||||
public RewriteTreeStructure(OutputModelFactory factory,
|
public RewriteTreeStructure(OutputModelFactory factory,
|
||||||
GrammarAST ast,
|
GrammarAST ast,
|
||||||
int level)
|
int treeLevel,
|
||||||
|
int codeBlockLevel)
|
||||||
{
|
{
|
||||||
super(factory, level);
|
super(factory, treeLevel, codeBlockLevel);
|
||||||
|
this.treeLevel = treeLevel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,11 @@ import org.antlr.v4.codegen.model.Rewrite;
|
||||||
import org.antlr.v4.codegen.model.decl.RootDecl;
|
import org.antlr.v4.codegen.model.decl.RootDecl;
|
||||||
|
|
||||||
public class TreeRewrite extends Rewrite {
|
public class TreeRewrite extends Rewrite {
|
||||||
public int level;
|
|
||||||
public RootDecl rootDecl;
|
public RootDecl rootDecl;
|
||||||
|
|
||||||
public TreeRewrite(OutputModelFactory factory, int level) {
|
public TreeRewrite(OutputModelFactory factory, int treeLevel, int codeBlockLevel) {
|
||||||
super(factory);
|
super(factory, treeLevel, codeBlockLevel);
|
||||||
this.level = level;
|
rootDecl = new RootDecl(factory);
|
||||||
rootDecl = new RootDecl(factory, level); // track here too for easy access in template
|
|
||||||
addLocalDecl(rootDecl);
|
addLocalDecl(rootDecl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,9 @@ import org.antlr.v4.runtime.misc.OrderedHashSet;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class CodeBlock extends SrcOp {
|
public class CodeBlock extends SrcOp {
|
||||||
|
public int codeBlockLevel;
|
||||||
|
public int treeLevel;
|
||||||
|
|
||||||
@ModelElement public OrderedHashSet<Decl> locals;
|
@ModelElement public OrderedHashSet<Decl> locals;
|
||||||
@ModelElement public List<SrcOp> preamble;
|
@ModelElement public List<SrcOp> preamble;
|
||||||
@ModelElement public List<SrcOp> ops;
|
@ModelElement public List<SrcOp> ops;
|
||||||
|
@ -44,6 +47,12 @@ public class CodeBlock extends SrcOp {
|
||||||
super(factory);
|
super(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CodeBlock(OutputModelFactory factory, int treeLevel, int codeBlockLevel) {
|
||||||
|
super(factory);
|
||||||
|
this.treeLevel = treeLevel;
|
||||||
|
this.codeBlockLevel = codeBlockLevel;
|
||||||
|
}
|
||||||
|
|
||||||
/** Add local var decl */
|
/** Add local var decl */
|
||||||
public void addLocalDecl(Decl d) {
|
public void addLocalDecl(Decl d) {
|
||||||
if ( locals==null ) locals = new OrderedHashSet<Decl>();
|
if ( locals==null ) locals = new OrderedHashSet<Decl>();
|
||||||
|
@ -60,4 +69,9 @@ public class CodeBlock extends SrcOp {
|
||||||
if ( ops==null ) ops = new ArrayList<SrcOp>();
|
if ( ops==null ) ops = new ArrayList<SrcOp>();
|
||||||
ops.add(op);
|
ops.add(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addOps(List<SrcOp> ops) {
|
||||||
|
if ( this.ops==null ) this.ops = new ArrayList<SrcOp>();
|
||||||
|
this.ops.addAll(ops);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,10 +32,13 @@ package org.antlr.v4.codegen.model.decl;
|
||||||
import org.antlr.v4.codegen.OutputModelFactory;
|
import org.antlr.v4.codegen.OutputModelFactory;
|
||||||
import org.antlr.v4.tool.GrammarAST;
|
import org.antlr.v4.tool.GrammarAST;
|
||||||
|
|
||||||
public class RewriteIteratorDecl extends NestedDecl {
|
public class RewriteIteratorDecl extends Decl {
|
||||||
public String listName;
|
public String listName;
|
||||||
public RewriteIteratorDecl(OutputModelFactory factory, GrammarAST elem, int level) {
|
public RewriteIteratorDecl(OutputModelFactory factory,
|
||||||
super(factory, factory.getGenerator().target.getRewriteIteratorName(elem, level), level);
|
GrammarAST elem)
|
||||||
|
{
|
||||||
|
super(factory, factory.getGenerator().target
|
||||||
|
.getRewriteIteratorName(elem, factory.getCodeBlockLevel()));
|
||||||
listName = factory.getGenerator().target.getElementListName(elem);
|
listName = factory.getGenerator().target.getElementListName(elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,9 @@ package org.antlr.v4.codegen.model.decl;
|
||||||
|
|
||||||
import org.antlr.v4.codegen.OutputModelFactory;
|
import org.antlr.v4.codegen.OutputModelFactory;
|
||||||
|
|
||||||
public class RootDecl extends NestedDecl {
|
public class RootDecl extends Decl {
|
||||||
public RootDecl(OutputModelFactory factory, int level) {
|
public RootDecl(OutputModelFactory factory) {
|
||||||
super(factory, factory.getGenerator().target.getRootName(level), level);
|
super(factory,
|
||||||
|
factory.getGenerator().target.getRootName(factory.getCodeBlockLevel()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,7 +159,6 @@ public class Rule implements AttributeResolver {
|
||||||
return refs;
|
return refs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: called frequently; make it more efficient
|
|
||||||
public MultiMap<String, LabelElementPair> getLabelDefs() {
|
public MultiMap<String, LabelElementPair> getLabelDefs() {
|
||||||
MultiMap<String, LabelElementPair> defs =
|
MultiMap<String, LabelElementPair> defs =
|
||||||
new MultiMap<String, LabelElementPair>();
|
new MultiMap<String, LabelElementPair>();
|
||||||
|
|
Loading…
Reference in New Issue