/*
* Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC
* All rights reserved.
*
* The source code of this document is proprietary work, and is not licensed for
* distribution. For information about licensing, contact Sam Harwell at:
* sam@tunnelvisionlabs.com
*/
package org.antlr.works.editor.grammar.experimental;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.antlr.netbeans.editor.text.DocumentSnapshot;
import org.antlr.netbeans.editor.text.OffsetRegion;
import org.antlr.netbeans.editor.text.SnapshotPositionRegion;
import org.antlr.v4.runtime.Dependents;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RuleDependencies;
import org.antlr.v4.runtime.RuleDependency;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.antlr.works.editor.antlr4.parsing.ParseTrees;
import org.antlr.works.editor.grammar.codemodel.impl.ChannelModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.FileModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.ImportDeclarationModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.LabelModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.LexerRuleModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.ModeModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.ParameterModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.ParserRuleModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.RuleModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.TokenRuleModelImpl;
import org.antlr.works.editor.grammar.codemodel.impl.TokenVocabDeclarationModelImpl;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.ArgActionParameterContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.ArgActionParametersContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.ChannelsSpecContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.DelegateGrammarContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.GrammarSpecContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.IdContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.LabeledElementContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.LexerRuleContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.LocalsSpecContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.ModeSpecContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.OptionContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.ParserRuleSpecContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.RuleReturnsContext;
import org.antlr.works.editor.grammar.experimental.generated.AbstractGrammarParser.TokensSpecContext;
import org.antlr.works.editor.grammar.experimental.generated.GrammarParserBaseListener;
import org.antlr.works.editor.grammar.semantics.LiteralLexerRuleValueVisitor;
import org.antlr.works.editor.grammar.semantics.LiteralLexerRuleVisitor;
import org.antlr.works.editor.grammar.semantics.SuppressTokenTypeVisitor;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
/**
*
* @author Sam Harwell
*/
public class CodeModelBuilderListener extends GrammarParserBaseListener {
private final Project project;
private final DocumentSnapshot snapshot;
private final TokenStream tokens;
// final result
private FileModelImpl fileModel;
private final Deque<ModeModelImpl> modeModelStack = new ArrayDeque<>();
private final Deque<RuleModelImpl> ruleModelStack = new ArrayDeque<>();
private final Deque<Collection<ChannelModelImpl>> channelContainerStack = new ArrayDeque<>();
private final Deque<Collection<ModeModelImpl>> modeContainerStack = new ArrayDeque<>();
private final Deque<Collection<RuleModelImpl>> ruleContainerStack = new ArrayDeque<>();
private final Deque<Collection<ParameterModelImpl>> parameterContainerStack = new ArrayDeque<>();
private final Deque<Collection<ParameterModelImpl>> returnValueContainerStack = new ArrayDeque<>();
private final Deque<Collection<ParameterModelImpl>> localContainerStack = new ArrayDeque<>();
private final Deque<Collection<LabelModelImpl>> labelContainerStack = new ArrayDeque<>();
private final Deque<Map<String, Collection<TerminalNode>>> labelUses = new ArrayDeque<>();
public CodeModelBuilderListener(DocumentSnapshot snapshot, TokenStream tokens) {
FileObject fileObject = snapshot.getVersionedDocument().getFileObject();
this.project = fileObject != null ? FileOwnerQuery.getOwner(fileObject) : null;
this.snapshot = snapshot;
this.tokens = tokens;
}
public FileModelImpl getFileModel() {
return fileModel;
}
@Override
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_grammarSpec, version=0, dependents=Dependents.PARENTS)
public void enterGrammarSpec(GrammarSpecContext ctx) {
FileObject packageFolder = snapshot.getVersionedDocument().getFileObject().getParent();
FileObject projectFolder = project != null ? project.getProjectDirectory() : null;
String packagePath;
if (projectFolder != null) {
packagePath = FileUtil.getRelativePath(projectFolder, packageFolder);
} else {
packagePath = packageFolder.getNameExt();
}
FileObject fileObject = snapshot.getVersionedDocument().getFileObject();
this.fileModel = new FileModelImpl(fileObject, project, packagePath);
this.channelContainerStack.push(this.fileModel.getChannels());
this.modeContainerStack.push(this.fileModel.getModes());
this.ruleContainerStack.push(this.fileModel.getRules());
}
@Override
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_grammarSpec, version=0, dependents=Dependents.PARENTS)
public void exitGrammarSpec(GrammarSpecContext ctx) {
this.fileModel.freeze();
this.modeContainerStack.pop();
this.ruleContainerStack.pop();
}
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_parserRuleSpec, version=0, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_lexerRule, version=0, dependents=Dependents.PARENTS),
})
private void handleEnterRule(ParserRuleContext ctx, TerminalNode name) {
String ruleName = name != null ? name.getText() : "?";
RuleModelImpl ruleModel;
if (ctx instanceof GrammarParser.ParserRuleSpecContext) {
ruleModel = new ParserRuleModelImpl(ruleName, fileModel, name, ctx);
} else if (ctx instanceof GrammarParser.LexerRuleContext) {
LexerRuleContext lexerRuleContext = (LexerRuleContext)ctx;
boolean isFragment = lexerRuleContext.FRAGMENT() != null;
boolean generateTokenType = !SuppressTokenTypeVisitor.INSTANCE.visit(ctx);
String literal = null;
if (generateTokenType && LiteralLexerRuleVisitor.INSTANCE.visit(ctx)) {
TerminalNode terminal = LiteralLexerRuleValueVisitor.INSTANCE.visit(ctx);
literal = terminal != null ? terminal.getSymbol().getText() : null;
}
ruleModel = new LexerRuleModelImpl(ruleName, modeModelStack.peek(), isFragment, generateTokenType, literal, fileModel, name, ctx);
} else {
throw new UnsupportedOperationException();
}
ruleContainerStack.peek().add(ruleModel);
ruleModelStack.push(ruleModel);
parameterContainerStack.push(ruleModel.getParameters());
returnValueContainerStack.push(ruleModel.getReturnValues());
localContainerStack.push(ruleModel.getLocals());
labelContainerStack.push(ruleModel.getLabels());
labelUses.push(new HashMap<String, Collection<TerminalNode>>());
}
private void handleExitRule(ParserRuleContext ctx) {
Collection<LabelModelImpl> labels = labelContainerStack.peek();
for (Map.Entry<String, Collection<TerminalNode>> labelUsage : labelUses.peek().entrySet()) {
//labels.add(new LabelModelImpl(labelUsage.getKey(), labelUsage.getValue()));
labels.add(new LabelModelImpl(labelUsage.getKey(), fileModel, labelUsage.getValue(), null));
}
ruleModelStack.pop();
parameterContainerStack.pop();
returnValueContainerStack.pop();
localContainerStack.pop();
labelContainerStack.pop();
labelUses.pop();
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_parserRuleSpec, version=0, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_argActionParameters, version=0, dependents=Dependents.SELF),
})
public void enterParserRuleSpec(ParserRuleSpecContext ctx) {
handleEnterRule(ctx, ctx.RULE_REF());
ArgActionParametersContext ctxparameters = ctx.argActionParameters();
if (ctxparameters != null) {
handleParameters(ctxparameters.parameters, parameterContainerStack.peek());
}
}
@Override
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_parserRuleSpec, version=0, dependents=Dependents.PARENTS)
public void exitParserRuleSpec(ParserRuleSpecContext ctx) {
handleExitRule(ctx);
}
@Override
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_lexerRule, version=0, dependents=Dependents.PARENTS)
public void enterLexerRule(LexerRuleContext ctx) {
handleEnterRule(ctx, ctx.TOKEN_REF());
}
@Override
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_lexerRule, version=0, dependents=Dependents.PARENTS)
public void exitLexerRule(LexerRuleContext ctx) {
handleExitRule(ctx);
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_tokensSpec, version=6, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_id, version=1, dependents=Dependents.DESCENDANTS),
})
public void enterTokensSpec(TokensSpecContext ctx) {
for (IdContext id : ctx.id()) {
TerminalNode token = ParseTrees.getStartNode(id);
if (token == null) {
continue;
}
String tokenName = token.getText();
RuleModelImpl ruleModel = new TokenRuleModelImpl(tokenName, null, fileModel, token, ctx);
ruleContainerStack.peek().add(ruleModel);
}
}
@Override
public void exitTokensSpec(TokensSpecContext ctx) {
super.exitTokensSpec(ctx);
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_channelsSpec, version=6, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_id, version=1, dependents=Dependents.DESCENDANTS),
})
public void enterChannelsSpec(ChannelsSpecContext ctx) {
for (IdContext id : ctx.id()) {
TerminalNode token = ParseTrees.getStartNode(id);
if (token == null) {
continue;
}
String channelName = token.getText();
ChannelModelImpl ruleModel = new ChannelModelImpl(channelName, fileModel, token, ctx);
channelContainerStack.peek().add(ruleModel);
}
}
@Override
public void exitChannelsSpec(ChannelsSpecContext ctx) {
super.exitChannelsSpec(ctx);
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_modeSpec, version=3, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_id, version=1, dependents=Dependents.DESCENDANTS),
})
public void enterModeSpec(ModeSpecContext ctx) {
String name = null;
IdContext nameId = ctx.id();
TerminalNode nameToken = ParseTrees.getStartNode(nameId);
if (nameToken != null) {
name = nameToken.getText();
}
if (name == null || name.isEmpty()) {
name = "?mode?";
}
ModeModelImpl modeModel = new ModeModelImpl(name, fileModel, nameToken, ctx);
modeContainerStack.peek().add(modeModel);
modeModelStack.push(modeModel);
ruleContainerStack.push(modeModel.getRules());
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_modeSpec, version=3, dependents=Dependents.PARENTS),
//@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_id, version=1),
})
public void exitModeSpec(ModeSpecContext ctx) {
modeModelStack.pop();
ruleContainerStack.pop();
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_ruleReturns, version=0, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_argActionParameters, version=0, dependents=Dependents.SELF),
})
public void enterRuleReturns(RuleReturnsContext ctx) {
ArgActionParametersContext values = ctx.argActionParameters();
if (values != null) {
handleParameters(values.parameters, returnValueContainerStack.peek());
}
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_localsSpec, version=0, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_argActionParameters, version=0, dependents=Dependents.SELF),
})
public void enterLocalsSpec(LocalsSpecContext ctx) {
ArgActionParametersContext values = ctx.argActionParameters();
if (values != null) {
handleParameters(values.parameters, localContainerStack.peek());
}
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_labeledElement, version=5, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_id, version=1, dependents=Dependents.DESCENDANTS),
})
public void enterLabeledElement(LabeledElementContext ctx) {
IdContext label = ctx.label;
if (label != null) {
TerminalNode nameToken = ParseTrees.getStartNode(label);
String name = nameToken.getText();
Collection<TerminalNode> uses = labelUses.peek().get(name);
if (uses == null) {
uses = new ArrayList<>();
labelUses.peek().put(name, uses);
}
uses.add(nameToken);
}
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_option, version=3, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_id, version=1, dependents=Dependents.DESCENDANTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_optionValue, version=0, dependents=Dependents.SELF),
})
public void enterOption(OptionContext ctx) {
IdContext id = ctx.id();
if (id != null && id.start != null) {
String optionName = id.start.getText();
if ("tokenVocab".equals(optionName)) {
String vocabName = GrammarParser.getOptionValue(ctx.optionValue());
if (vocabName != null && !vocabName.isEmpty()) {
fileModel.getTokenVocabDeclaration().add(new TokenVocabDeclarationModelImpl(vocabName, fileModel));
}
}
}
}
@Override
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_delegateGrammar, version=0, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_id, version=1, dependents=Dependents.DESCENDANTS),
})
public void enterDelegateGrammar(DelegateGrammarContext ctx) {
List<? extends IdContext> nodes = ctx.id();
if (nodes.isEmpty()) {
return;
}
String name = getText(nodes.get(0));
String target = getText(nodes.get(nodes.size() - 1));
ImportDeclarationModelImpl importDeclarationModel =
new ImportDeclarationModelImpl(name, target, fileModel, ParseTrees.getStartNode(ctx), ctx);
fileModel.getImportDeclarations().add(importDeclarationModel);
}
@Override
public void visitTerminal(TerminalNode node) {
if (node.getSymbol().getType() == GrammarLexer.TOKEN_REF) {
if (node.getSymbol().getText().equals("EOF")) {
ruleModelStack.peek().setExplicitEof(true);
}
}
}
private SnapshotPositionRegion getSpan(ParserRuleContext context) {
if (context == null) {
return null;
}
Interval sourceInterval = ParseTrees.getSourceInterval(context);
if (sourceInterval != null && sourceInterval.b + 1 >= sourceInterval.a) {
return new SnapshotPositionRegion(snapshot, OffsetRegion.fromBounds(sourceInterval.a, sourceInterval.b + 1));
}
return null;
}
@RuleDependencies({
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_argActionParameter, version=7, dependents=Dependents.PARENTS),
@RuleDependency(recognizer=GrammarParser.class, rule=GrammarParser.RULE_argActionParameterType, version=0, dependents=Dependents.SELF),
})
private void handleParameters(@NullAllowed Collection<ArgActionParameterContext> contexts, @NonNull Collection<ParameterModelImpl> models) {
if (contexts == null) {
return;
}
for (ArgActionParameterContext context : contexts) {
String type = getText(context.type);
TerminalNode name = ParseTrees.findTerminalNode(context, context.name);
ParameterModelImpl parameter = new ParameterModelImpl(name != null ? name.getText() : "?", type, fileModel, name, context);
models.add(parameter);
}
}
private String getText(ParserRuleContext context) {
if (context == null || context.start == null || context.stop == null) {
return "";
}
return tokens.getText(context.start, context.stop);
}
}