antlr4 代码 语法树_使用ANTLR4,用于代码镜像和基于Web的DSL的Primefaces扩展

  • Post author:
  • Post category:其他


antlr4 代码 语法树


DSL是很酷的东西,但我不清楚它们有什么用。

然后我意识到它们对以下方面有好处:

  • 摆脱复杂的UI

意思是

  • 更快的做事方式

而已。 当我阅读此

博客

时,我得出了这个结论。

如果您的用户是技术人员,并且不惧怕类似SQL的语法处理方式,则DSL特别适合

  • 你有语法高亮
  • 你有代码补全

否则,DSL有点烂。

因此,我不得不向客户提出一些概念证明。 他有模糊的要求,要准确提取团队的需求并不容易(他们需要很多东西,而且他很忙),因此DSL可以在此过程中提供很多帮助,因为人们被迫在他们需要时才想清楚他们的需求面对一种语法(甚至很小的一种)。

因此,我采用了以下技术:

不幸的是,我无法在两个工具中重复使用语法。 实际上,我找不到任何可以做到这一点的解决方案。 至少对于基于Web的JSF解决方案。 而且没有时间学习。 所以我不得不稍微改一下。

首先,我们需要语法。 ANTLR4比ANTLR3更好,因为现在接线代码是通过访问者和侦听器完成的。 语法内没有更多的Java代码。 那很棒,而且更容易使用。

所以你可以有一个这样的语法

grammar Grammar;
options
{
    language = Java;
}
@lexer::header {
  package parsers;
}

@parser::header {
  package parsers;
}
eval     :     expr EOF;
expr    :    'JOB' (jobName)? type 'TARGET' targetList ('START' startExpr)?
startExpr   
    :    'AT' cronTerm
    |    'AFTER' timeAmount timeUnits;
timeAmount: INT;   
jobName: STRING;   
targetList: STRING (',' STRING)*;   
type    :    deleteUser
    |    createUser;

deleteUser: opDelete userName;
createUser: opCreate userName;
opDelete: 'DELETE';
opCreate: 'CREATE';
userName: STRING;
   
cronTerm: '!'? (INT | '-' | '/' | '*' | '>' | '<')+;

timeUnits
    :    'MINUTES'
    |    'HOURS'
    |    'DAYS'
    |    'WEEKS'
    |    'MONTHS';


WS  :   [ \t\r\n]+ -> skip;

STRING
    :  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'
    ;

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

fragment
ESC_SEQ
    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
    |   UNICODE_ESC
    |   OCTAL_ESC
    ;

fragment
OCTAL_ESC
    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7')
    ;

fragment
UNICODE_ESC
    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
    ;

ID  :    ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
    ;

INT :    '0'..'9'+
    ;

要编译语法,请尝试

public static void main(String[] args) {
        String[] arg0 = {"-visitor","/pathto/Grammar.g4"};
        org.antlr.v4.Tool.main(arg0);
    }

然后,ANTLR将为您生成类。

在我们的例子中,我们想访问解析树并检索我们想要的值。 我们这样做扩展了生成的抽象类。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.antlr.v4.runtime.tree.ErrorNode;

import bsh.EvalError;
import bsh.Interpreter;

public class MyLoader2 extends GrammarBaseVisitor<Void> {

    private String jobName,cronTerm,timeUnits,userName,jobType;
    private List<String> targetList;
    private boolean now,errorFound;
    private int timeAmount;   
    private Interpreter bsh = new Interpreter();
   
    private String eval(String s) throws EvaluationException{
        try {
            if (!s.startsWith("\"")){
                return s;
            }
            bsh.eval("String s="+s);
            return (String)bsh.eval("s");
        } catch (EvalError e) {
            throw new EvaluationException(s);
        }
    }
   
    @Override
    public Void visitTimeAmount(TimeAmountContext ctx) {
        try{
            this.timeAmount = Integer.parseInt(ctx.getText());
        }catch(java.lang.NumberFormatException nfe){
            throw new InvalidTimeAmountException(ctx.getText());
        }

        return super.visitTimeAmount(ctx);
    }
    @Override
    public Void visitUserName(UserNameContext ctx) {
        this.userName = eval(ctx.getText());
        return super.visitUserName(ctx);
    }
    @Override
    public Void visitCronTerm(CronTermContext ctx) {
        this.cronTerm = eval(ctx.getText());
        return super.visitCronTerm(ctx);
    }
    @Override
    public Void visitTimeUnits(TimeUnitsContext ctx) {
        this.timeUnits = ctx.getText();
        return super.visitTimeUnits(ctx);
    }
    @Override
    public Void visitTargetList(TargetListContext ctx) {
        this.targetList = toStringList(ctx.getText());
        return super.visitTargetList(ctx);
    }
    @Override
    public Void visitJobName(JobNameContext ctx) {
        this.jobName = eval(ctx.getText());
        return super.visitJobName(ctx);
    }
    @Override
    public Void visitOpCreate(OpCreateContext ctx) {
        this.jobType = ctx.getText();
        return super.visitOpCreate(ctx);
    }
    @Override
    public Void visitOpDelete(OpDeleteContext ctx) {
        this.jobType = ctx.getText();
        return super.visitOpDelete(ctx);
    }
    private List<String> toStringList(String text) {
        List<String> l = new ArrayList<String>();
        StringTokenizer st = new StringTokenizer(text," ,");
        while(st.hasMoreElements()){
            l.add(eval(st.nextToken()));
        }
        return l;
    }
   
    private Map<String, String> toMapList(String text) throws InvalidItemsException, InvalidKeyvalException {
        Map<String, String> m = new HashMap<String, String>();
        if (text == null || text.trim().length() == 0){
            return m;
        }
        String[] items = text.split(",");
        if (items.length == 0){
            throw new InvalidItemsException();
        }
        for(String item:items){
            String[] keyval = item.split("=");
            if (keyval.length == 2){
                m.put(keyval[0], keyval[1]);
            }else{
                throw new InvalidKeyvalException(keyval.length);
            }
        }
        return m;
    }
    public String getJobName() {
        return jobName;
    }
    public String getCronTerm() {
        return cronTerm;
    }
    public String getTimeUnits() {
        return timeUnits;
    }
    public String getUserName() {
        return userName;
    }
    public String getJobType() {
        return jobType;
    }
    public List<String> getTargetList() {
        return targetList;
    }
    public boolean isNow() {
        return now;
    }
    public int getTimeAmount() {
        return timeAmount;
    }
    @Override
    public Void visitOpNow(OpNowContext ctx) {
        this.now = ctx.getText().equals("NOW");
        return super.visitOpNow(ctx);
    }
    public boolean isErrorFound() {
        return errorFound;
    }
    @Override
    public Void visitErrorNode(ErrorNode node) {
        this.errorFound = true;
        return super.visitErrorNode(node);
    }
}

请注意,

beanshell

解释器用于将“ xyz”之类的字符串评估为xyz。 这对于其中包含转义引号和字符的字符串特别有用。

因此,您有了语法和visiter / loader bean,然后我们可以对其进行测试:

private static MyLoader getLoader(String str){
    ANTLRInputStream input = new ANTLRInputStream(str);
    GrammarLexer lexer = new GrammarLexer(input);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    GrammarParser parser = new GrammarParser(tokens);
    ParseTree tree = parser.eval();
    MyLoader loader = new MyLoader();
    loader.visit(tree);
    return loader;
}

public static void main(String[] args){
    MyLoader loader = getLoader("JOB \"jobName\" CREATE \"myuser\" TARGET \"site1\",\"site2\" START AFTER 1 DAY");
    System.out.println(loader.getJobName());
    System.out.println(loader.getJobType());
}

大。 现在是黑客。 Code Mirror支持自定义语法,但

JSF Primefaces扩展中不存在

。 因此,我打开了resources-codemirror-1.2.0.jar,打开了/META-INF/resources/primefaces-extensions/codemirror/mode/modes.js文件,对其进行了

格式化

(以便我可以阅读),然后我刚刚选择了最简单的语言作为我的新自定义sintax荧光笔!

我改名了

(...)
}, "xml"), CodeMirror.defineMIME("text/x-markdown", "markdown"), CodeMirror.defineMode("mylanguage", function (e) {
(...)
    var t = e.indentUnit,
        n, i = r(["site", "type", "targetList"]),
        s = r(["AT","AFTER","CREATE","MINUTES","HOURS","TARGET","MONTHS","JOB","DAYS","DELETE","START","WEEKS" ]),
(...)
}), CodeMirror.defineMIME("text/x-mylanguage", "mylanguage"), CodeMirror.defineMode("ntriples", function () {(...)

那些在“ s = r”中用大写字母表示的标记是将被突出显示的标记,而在“ i = r”中的那些标记则是将被突出显示的标记。 为什么我们都想要? 因为第二种类型是“占位符”,我的意思是,我们将它们用于自动填充内容。

好,那么您的JSF xhtml页面将如下所示

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:p="http://primefaces.org/ui"
    xmlns:pe="http://primefaces.org/ui/extensions">

<h:body>
<h:form id="form">
<pe:codeMirror
    id="codeMirror"
    style="width:600px;" 
    mode="myLanguage" 
    widgetVar="myCodeMirror" 
    theme="eclipse"
    value="#{myMB.script}"
    lineNumbers="true"             
    completeMethod="#{myMB.complete}" 
    extraKeys="{ 'Ctrl-Space': function(cm) { PF('myCodeMirror').complete(); }}"/> 
<p:commandButton value="Verify" action="#{myMB.verify}" />
(...)

现在,我们需要自动完成功能。 这是无聊的部分。 您必须手动完成大部分的完成工作,因为没有上下文信息(请记住,我没有时间学习……),所以快速而肮脏的方式是这样的

in myMB

    public List<String> complete(final CompleteEvent event) {
        try {
            return this.myEJB.complete(event.getToken());
        } catch (Exception e) {
            jsfUtilEJB.addErrorMessage(e,"Could not complete");
            return null;
        }
    } 

in myEJB

private static final String    SITE    = "site_";
public List<String> complete(String token) throws Exception {
    if (token == null || token.trim().length() == 0){
        return null;
    }else{
        List<String> suggestions = new ArrayList<String>();
       
        switch(token){
            //first search variables
            case "targetlist":
                for(String v:TARGETS){
                    suggestions.add(v);   
                }
                break;
            case "site":
                List<Site> allSites = this.baseService.getSiteDAO().getAll();
                for(Site s:allSites){
                    suggestions.add("\""+SITE+s.getName()+"\"");
                }
                break;
            case "type":
                suggestions.add("DELETE \"userName\"");
                suggestions.add("CREATE \"userName\"");
                break;
            case "AT":
                suggestions.add("AT \"cronExpression\"");
                suggestions.add("AT \"0 * * * * * * *\"");
                break;
            case "AFTER":
                for(int a:AMOUNTS){
                    for(String u:UNITS){
                        if (a == 1){
                            suggestions.add("AFTER"+" "+a+" "+u);
                        }else{
                            suggestions.add("AFTER"+" "+a+" "+u+"S");
                        }
                    }
                }
                break;
            case "TARGET":
                for(String v:TARGETS){
                    suggestions.add("TARGET "+v+"");   
                }
                break;
            case "JOB":
                suggestions.add("JOB \"jobName\" \ntype \nTARGET targetlist \nSTART");
                break;
            case "START":
                suggestions.add("START AT \"cronExpression\"");
                suggestions.add("START AT \"0 * * * * * * *\"");
                for(int a:AMOUNTS){
                    for(String u:UNITS){
                        if (a == 1){
                            suggestions.add("START AFTER"+" "+a+" "+u);
                        }else{
                            suggestions.add("START AFTER"+" "+a+" "+u+"S");
                        }
                    }
                }
                suggestions.add("START NOW");
                break;
            case "DELETE":
                suggestions.add("DELETE \"userName\"");
                break;
            case "CREATE":
                suggestions.add("CREATE \"userName\"");
                break;

            default:
                if (token.startsWith(SITE)){
                    List<Site> matchedSites = this.baseService.getSiteDAO().getByPattern(token.substring(SITE.length())+"*");
                    for(Site s:matchedSites){
                        suggestions.add("\""+SITE+s.getName()+"\"");
                    }
                }else{
                    //then search substrings
                    for(String kw:KEYWORDS){
                        if (kw.toLowerCase().startsWith(token.toLowerCase())){
                            suggestions.add(kw);
                        }
                    }
                }
        }//end switch
       
        //remove dups and sort
        Set<String> ts = new TreeSet<String>(suggestions);           
        return new ArrayList<String>(ts);
    }
}

private static final int[] AMOUNTS = {1,5,10};
private static final String[] UNITS = {"MINUTE","HOUR","DAY","WEEK","MONTH"};
private static final String[] TARGETS = {"site"};

/*
 * KEYWORDS are basic suggestions
 */
private static final String[] KEYWORDS = {"AT","AFTER","CREATE","MINUTES","HOURS","TARGET","MONTHS","JOB","DAYS","DELETE","START","WEEKS"};

因此,关键字的自动填充内容将仅向您显示字段和更多关键字,而“占位符”(还记得jar中的codemirror javascript中的小写关键字吗?)是从数据库中检索到的动态值(用于实际值)完成的。 另外,您可以使用部分字符串来检索以子字符串开头的那些字符串,如下所示:


屏幕截图

当然,在JPA中类似模式的搜索可以这样执行:

public abstract class GenericDAO<E> {
   
    protected EntityManager entityManager;
   
    private Class<E> clazz;
    private EntityType<E> pClass;


    @SuppressWarnings("unchecked")
    public GenericDAO(EntityManager entityManager) {
        this.entityManager = entityManager;
    ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
    this.clazz = (Class<E>) genericSuperclass.getActualTypeArguments()[0];

    EntityManagerFactory emf = this.entityManager.getEntityManagerFactory();
    Metamodel metamodel = emf.getMetamodel();
    this.pClass = metamodel.entity(clazz);
    }

    public List<E> getByPattern(String pattern) {
        pattern = pattern.replace("?", "_").replace("*", "%");
   
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<E> q = cb.createQuery(clazz);
        Root<E> entity = q.from(clazz);
        SingularAttribute<E, String> singularAttribute = (SingularAttribute<E, String>) pClass.getDeclaredSingularAttribute(getNameableField(clazz));
        Path<String> path = entity.get(singularAttribute);
        q.where(cb.like(path, pattern));
        q.select(entity);
   
        TypedQuery<E> tq = entityManager.createQuery(q);
        List<E> all = tq.getResultList();
   
        return all;
    }

    private String getNameableField(Class<E> clazz) {
        for(Field f : clazz.getDeclaredFields()) {
            for(Annotation a : f.getAnnotations()) {
                if(a.annotationType() == Nameable.class) {
                    return f.getName();
                }
            }
        }
        return null;
    }
(...)

其中Nameable是您的实体类的注释:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Nameable {
}

用它来注释实体类中的单个列,即String。 像这样:

@Entity
@Table(uniqueConstraints=@UniqueConstraint(columnNames={"name"}))
public class Site implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = 8008732613898597654L;
   
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
   
    @Nameable
    @Column(nullable=false)
    private String name;
(...)

当然,“验证”按钮只是获取您的脚本并将其推入加载器。

翻译自:

https://www.javacodegeeks.com/2014/01/playing-with-antlr4-primefaces-extensions-for-code-mirror-and-web-based-dsls.html

antlr4 代码 语法树