【问题标题】:ANTLR4-based lexer loses syntax hightlighting during typing on NetBeans基于 ANTLR4 的词法分析器在 NetBeans 上键入时失去语法高亮显示
【发布时间】:2014-05-27 11:10:10
【问题描述】:

我使用 ANTLR4 语法 编写了一个简单的词法分析器和解析器,为 NetBeans 7.3 制作了一个语言插件,以帮助团队更快地编写我们的布局文件(XHTML 和小部件定义的混合形式)的 XHTML 标记,但具有自定义属性、特征,并且与 XHTML 语法有一些差异)。

模板文件示例:

<div style="dyn_layout_panel">
    @symbol@
    <w_label=label, text="Try to close this window" />
    <w_buttonclose=button, text = "CLOSE", on_press=press_close />
    <w_buttonterminate=button, text="TERMINATE", on_press=press_terminate />
    <w_mydatepicker=datepicker, parent=tab0, ary=[10, "str", /regex/i], start_date=2013-10-05, on_selected=datepicker_selected />
    <w_myeditbox=editbox, parent=tab0, validation=USER_REGEX, validation_regex=/^[0-9]+[a-z]*$/i,
        validation_msg="User regex don't match editbox contents.", on_keyreturn=tab0_editbox_keyreturn />
    <div style="dyn_layout_panel">
        $SYMBOL_2$
        Some text that make a text node.
    </div>
</div>

我使用 AnltrWorks 2 编写和调试词法分析器和解析器,一切似乎都很好,在 NetBeans 中我也没有遇到任何异常,并且解析器工作正常,但 在编辑/键入期间,我在光标。

问题截图:

为每次击键添加调试控制台输出我看到词法分析器正确进入 IN_TAGIN_WIDGET 模式,但在 WHITESPACE 之后返回到默认模式并匹配标记内的其余文本作为 TEXT_NODE 标记。

我知道词法分析器一次只能有一个活动模式,所以因为它在 IN_TAG 或 IN_WIDGET 模式下匹配 TEXT_NODE 规则?

词法分析器语法文件:

lexer grammar LayoutLexer;

COMMENT
    :   '/*' .*? '*/' -> channel(HIDDEN)
    ;

WS  :   ( ' '
        | '\t'
        | EOL
        )+? -> channel(HIDDEN)
        ;

WDG_START_OPEN : '<w_' PROPERTY -> pushMode(IN_WIDGET) ;
WDG_END_OPEN : '</w_' PROPERTY -> pushMode(IN_WIDGET) ;
TAG_START_OPEN : '<' ATTRIBUTE -> pushMode(IN_TAG) ;
TAG_END_OPEN : '</' ATTRIBUTE -> pushMode(IN_TAG) ;

EXT_REF
    :   ( ('@' REF_NAME '@') | ('$' SYMBOL '$') | ('§' REF_NAME '§') )
    ;

fragment
REF_NAME
    :   ( [a-z]+ [0-9a-z_]*? )
    ;

fragment
EOL :   ( '\r\n' | '\n\r' | '\n' )
    ;

EQUAL
    :   '='
    ;

TEXT_NODE
    :   ( (~('\r'|'\n'|'<'|'@'|'$'|'§'))+ )
    ;

ERROR
    :   ( .+? )
    ;

mode IN_TAG;

TAG_CLOSE : '>' -> popMode ;
TAG_EMPTY_CLOSE : '/>' -> popMode ;

TAG_WS : WS -> type(WS), channel(HIDDEN) ;
TAG_COMMENT : COMMENT -> type(COMMENT), channel(HIDDEN) ;

TAG_EQ : EQUAL -> type(EQUAL) ;

ATTRIBUTE
    :   ( LITERAL [0-9a-zA-Z_]* )
    ;

VAL
    :  ( '"' ( ESC_SEQ | ~('\\'|'"') )*? '"'
    |  '\'' ( ESC_SEQ | ~('\\'|'\'') )*? '\'' )
    ;

TAG_ERR : ERROR -> type(ERROR) ;

mode IN_WIDGET;

WDG_CLOSE : '>' -> popMode ;
WDG_EMPTY_CLOSE : '/>' -> popMode ;

WDG_WS : WS -> type(WS), mode(IN_WIDGET), channel(HIDDEN) ;
WDG_COMMENT : COMMENT -> type(COMMENT), channel(HIDDEN) ;

WDG_EQ : EQUAL -> type(EQUAL), pushMode(WDG_ASSIGN) ;

COMMA
    :   ','
    ;

fragment
MINUS
    :   '-'
    ;

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

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
    ;

fragment
HEX_DIGIT
    :   [0-9a-fA-F]
    ;

fragment
DIGIT
    :   [0-9]
    ;

fragment
HEX_NUMBER
    :   '0x' HEX_DIGIT+
    ;

fragment
HTML_NUMBER
    :   (INT_NUMBER | FLOAT_NUMBER) HTML_UNITS
    ;

fragment
FLOAT_NUMBER
    :   MINUS? INT_NUMBER '.' DIGIT+
    ;

fragment
INT_NUMBER
    :   MINUS? DIGIT+
    ;

EVENT_HANDLER
    :   'on_' PROPERTY
    ;

PROPERTY
    :   ( LITERAL [0-9a-zA-Z_]* )
    ;

fragment
LITERAL 
    :   ( LITERAL_U | LITERAL_L )
    ;

fragment
LITERAL_U
    :   [A-Z]+
    ;

fragment
LITERAL_L
    :   [a-z]+
    ;

WDG_ERR : ERROR -> type(ERROR) ;

mode WDG_ASSIGN;

PHP_REF
    : ( LITERAL_L ('_' | LITERAL_L | [0-9])* ) -> popMode
    ;

VALUE : (WDG_VAL | ARRAY) -> popMode;

ASGN_WS : WS -> type(WS), channel(HIDDEN);
ASGN_COMMA : COMMA -> type(COMMA);

ARY_START
    :   '[' 
    ;

ARY_END
    :   ']'
    ;

BIT_OR
    :   '|'
    ;

ARRAY
    :   ARY_START ARY_VALUE (ASGN_COMMA ARY_VALUE)* ARY_END
    ;

fragment
ARY_VALUE : ASGN_WS? WDG_VAL ASGN_WS? -> type(VALUE);

fragment
WDG_VAL
    :   (STRING
    |   UTC_DATE
    |   HEX_NUMBER
    |   HTML_NUMBER
    |   FLOAT_NUMBER
    |   INT_NUMBER
    |   BOOLEAN
    |   BITFIELD
    |   REGEX
    |   CSS_CLASS)
    ;

fragment
HTML_UNITS
    :   ('%'|'in'|'cm'|'mm'|'em'|'ex'|'pt'|'pc'|'px')
    ;

fragment
BOOLEAN
    :   ('true'|'false')
    ;

fragment
BITFIELD
    :   SYMBOL (WS? BIT_OR WS? SYMBOL)*
    ;

SYMBOL
    :   LITERAL_U [0-9A-Z_]*
    ;

UTC_DATE
    :   (DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT)
    ;

REGEX
    : ('/' ('\\'.|.)*? '/' ('g'|'m'|'i')* )
    ;

CSS_CLASS
    : ( LITERAL_L ('-' | '_' | LITERAL_L | [0-9])* )
    ;

WDG_ASSIGN_ERR : ERROR -> type(ERROR), popMode;

解析器语法文件:

parser grammar LayoutParser;

options 
{
    tokenVocab=LayoutLexer;
    language=Java;
}

document : (element | TEXT_NODE | EXT_REF)* EOF;

element
locals
[
    String currentTag
]
    : ( ( html_open_tag (element | TEXT_NODE | EXT_REF)* html_close_tag )
    | ( wdg_open_tag (element | TEXT_NODE | EXT_REF)* wdg_close_tag )
    | ( html_empty_tag | wdg_empty_tag ) )
    ;

html_empty_tag
    : TAG_START_OPEN (ATTRIBUTE EQUAL VAL)* TAG_EMPTY_CLOSE
    ;

html_open_tag
    : ( tag=TAG_START_OPEN (ATTRIBUTE EQUAL VAL)* TAG_CLOSE )
        {$element::currentTag = $tag.text.substring(1);}
    ;

html_close_tag
    : tag=TAG_END_OPEN TAG_CLOSE
        {
            if (!$element::currentTag.equals($tag.text.substring(2)))
                notifyErrorListeners("HTML tag mismatch '" + $element::currentTag + "' - '" + $tag.text.substring(2) + "'");
        }
    ;

wdg_empty_tag
    : WDG_START_OPEN EQUAL PHP_REF ( COMMA (wdg_prop | wdg_event) )* WDG_EMPTY_CLOSE
    ;

wdg_open_tag
    : tag=WDG_START_OPEN EQUAL PHP_REF ( COMMA (wdg_prop | wdg_event) )* WDG_CLOSE
        {$element::currentTag = $tag.text.substring(1);}
    ;

wdg_close_tag
    : tag=WDG_END_OPEN WDG_CLOSE
        {
            if (!$element::currentTag.equals($tag.text.substring(2)))
                notifyErrorListeners("Widget alias mismatch '" + $element::currentTag + "' - '" + $tag.text + "'");
        }
    ;

wdg_prop
    : PROPERTY (EQUAL (ARRAY | VALUE | PHP_REF | UTC_DATE | REGEX | CSS_CLASS))?
    ;

wdg_event
    : EVENT_HANDLER EQUAL PHP_REF
    ;

【问题讨论】:

    标签: java netbeans syntax-highlighting antlr4 lexer


    【解决方案1】:

    根据语法高亮的实现,在对输入进行词法分析以进行语法高亮时,IDE 可能会或可能不会从文档的开头开始。如果它不是从文档的开头开始,那么在返回任何标记之前,您需要确保词法分析器实例以正确的模式初始化(​​_mode_modeStack 字段都需要初始化为正确的词法分析开始时的状态)。

    如果您的词法分析器在词法分析期间读取或写入任何自定义字段,您可能还需要恢复这些字段。

    示例

    其他效率说明

    • 您的 REF_NAMEVALSTRING 规则使用不需要非贪婪的非贪婪循环。在每个规则中,将+? 更改为+ 并将*? 更改为*

    • 您的WSERROR 规则使用非贪婪运算符+?,这相当于根本没有闭包。在这些情况下不必要地使用非贪婪运算符只会降低您的词法分析器的速度。要保留现有行为,您可以从这些规则中删除 +?(替换为 + 会改变行为)。

    附加功能说明

    • ANTLR 4 在词法分析期间不执行任何错误纠正。如果输入与标记不匹配,则输入根本与标记不匹配。此问题尤其会影响您的 VALSTRING 标记,在添加结束 "' 字符之前不会突出显示语法。对于突出显示这些类型标记的语法,我更喜欢在词法分析器中使用附加模式,允许我为嵌入在字符串中的转义序列生成单独的标记,以及在行尾突出显示未终止字符串的语法(除非您的语言允许字符串跨越多行,在这种情况下,您会在输入结束时停止)。

    【讨论】:

    • 我完全理解您的意思,感谢您对语法规则的提示。我将尝试从当前光标位置设置正确的模式(我对 ANTLR 和 NetBeans 平台真的很陌生)。抱歉,我没有足够的代表来投票。
    • 我已经看过上面的链接代码,我几乎以使用token.getStopIndex() 的解决方案结束。我只有最后一个问题,ANTLR 词法分析器是否可以从当前位置返回/前进到已知标记(如果存在)?非常感谢您的宝贵时间。
    【解决方案2】:

    供日后参考

    所有问题都与我对 NetBeans Lexer&lt;T&gt; 类的错误实现有关;网络上的许多教程都没有考虑到词法分析器可能有多个模式,并且必须在词法分析器分配/发布之间备份和恢复词法分析器状态如 280Z28 所述 em>。

    这是我用来使语法高亮一致的代码:

    public class LayoutEditorLexer implements Lexer<LayoutTokenId> {
    
        private LexerRestartInfo<LayoutTokenId> info;
        private LayoutLexer lexer;
    
        private class LexerState {
            public int Mode = -1;
            public IntegerStack Stack = null;
            public LexerState(int mode, IntegerStack stack)
            {
                Mode = mode;
                Stack = new IntegerStack(stack);
            }
        }
    
        public LayoutEditorLexer(LexerRestartInfo<LayoutTokenId> info) {
            this.info = info;
    
            AntlrCharStream charStream = new AntlrCharStream(info.input(), "LayoutEditor", false);
            lexer = new LayoutLexer(charStream);
            lexer.removeErrorListeners();
            lexer.addErrorListener(ErrorListener.INSTANCE);
            LexerState lexerMode = (LexerState)info.state();
            if (lexerMode != null)
            {
                lexer._mode = lexerMode.Mode;
                lexer._modeStack.addAll(lexerMode.Stack);
            }
        }
    
        @Override
        public org.netbeans.api.lexer.Token<LayoutTokenId> nextToken() {
            Token token = lexer.nextToken();
            int ttype = token.getType();
    
            if (ttype != LayoutLexer.EOF)
            {
                LayoutTokenId tokenId = LayoutLanguageHierarchy.getToken(ttype);
                return info.tokenFactory().createToken(tokenId);
            }
            return null;
        }
    
        @Override
        public Object state()
        {
            // Here many tutorials simply returns null.
            return new LexerState(lexer._mode, lexer._modeStack);
        }
    
        @Override
        public void release()
        {
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-24
      • 1970-01-01
      • 1970-01-01
      • 2021-08-10
      • 1970-01-01
      相关资源
      最近更新 更多