Разбор интерполяции строк в ANTLR - PullRequest
4 голосов
/ 05 декабря 2009

Я работаю над простым DSL для работы со строками для внутренних целей и хотел бы, чтобы язык поддерживал интерполяцию строк, как это используется в Ruby.

Например:

name = "Bob"
msg = "Hello ${name}!"
print(msg)   # prints "Hello Bob!"

Я пытаюсь реализовать свой синтаксический анализатор в ANTLRv3, но я довольно неопытен с использованием ANTLR, поэтому я не уверен, как реализовать эту функцию. До сих пор я указывал свои строковые литералы в лексере, но в этом случае мне, очевидно, потребуется обработать содержимое интерполяции в синтаксическом анализаторе.

Моя текущая строковая литеральная грамматика выглядит так:

STRINGLITERAL : '"' ( StringEscapeSeq | ~( '\\' | '"' | '\r' | '\n' ) )* '"' ;
fragment StringEscapeSeq : '\\' ( 't' | 'n' | 'r' | '"' | '\\' | '$' | ('0'..'9')) ;

Перемещение обработки строкового литерала в синтаксический анализатор, кажется, заставляет все остальное перестать работать как следует. Беглый поиск в интернете не дал никакой информации. Любые предложения о том, как начать это?

1 Ответ

12 голосов
/ 08 декабря 2009

Я не эксперт ANTLR, но вот возможная грамматика:

grammar Str;

parse
    :    ((Space)* statement (Space)* ';')+ (Space)* EOF
    ;

statement
    :    print | assignment
    ;

print
    :    'print' '(' (Identifier | stringLiteral) ')' 
    ;

assignment
    :    Identifier (Space)* '=' (Space)* stringLiteral
    ;

stringLiteral
    :    '"' (Identifier | EscapeSequence | NormalChar | Space | Interpolation)* '"'
    ;

Interpolation
    :    '${' Identifier '}'
    ;

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

EscapeSequence
    :    '\\' SpecialChar
    ;

SpecialChar
    :     '"' | '\\' | '$'
    ;

Space
    :    (' ' | '\t' | '\r' | '\n')
    ;

NormalChar
    :    ~SpecialChar
    ;

Как вы заметили, внутри примера грамматики есть пара (Space)*. Это потому, что stringLiteral является правилом синтаксического анализа вместо правила лексера . Поэтому при маркировке исходного файла лексер не может знать, является ли пробел частью строкового литерала или это просто пробел внутри исходного файла, который можно игнорировать.

Я протестировал пример с небольшим Java-классом, и все работало как ожидалось:

/* the same grammar, but now with a bit of Java code in it */
grammar Str;

@parser::header {
    package antlrdemo;
    import java.util.HashMap;
}

@lexer::header {
    package antlrdemo;
}

@parser::members {
    HashMap<String, String> vars = new HashMap<String, String>();
}

parse
    :    ((Space)* statement (Space)* ';')+ (Space)* EOF
    ;

statement
    :    print | assignment
    ;

print
    :    'print' '(' 
         (    id=Identifier    {System.out.println("> "+vars.get($id.text));} 
         |    st=stringLiteral {System.out.println("> "+$st.value);}
         ) 
         ')' 
    ;

assignment
    :    id=Identifier (Space)* '=' (Space)* st=stringLiteral {vars.put($id.text, $st.value);}
    ;

stringLiteral returns [String value]
    :    '"'
        {StringBuilder b = new StringBuilder();} 
        (    id=Identifier           {b.append($id.text);}
        |    es=EscapeSequence       {b.append($es.text);}
        |    ch=(NormalChar | Space) {b.append($ch.text);}
        |    in=Interpolation        {b.append(vars.get($in.text.substring(2, $in.text.length()-1)));}
        )* 
        '"'
        {$value = b.toString();}
    ;

Interpolation
    :    '${' i=Identifier '}'
    ;

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

EscapeSequence
    :    '\\' SpecialChar
    ;

SpecialChar
    :     '"' | '\\' | '$'
    ;

Space
    :    (' ' | '\t' | '\r' | '\n')
    ;

NormalChar
    :    ~SpecialChar
    ;

И класс с основным методом для проверки всего этого:

package antlrdemo;

import org.antlr.runtime.*;

public class ANTLRDemo {
    public static void main(String[] args) throws RecognitionException {
        String source = "name = \"Bob\";        \n"+
                "msg = \"Hello ${name}\";       \n"+
                "print(msg);                    \n"+
                "print(\"Bye \\${for} now!\");    ";
        ANTLRStringStream in = new ANTLRStringStream(source);
        StrLexer lexer = new StrLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        StrParser parser = new StrParser(tokens);
        parser.parse();
    }
}

, который выдает следующий вывод:

> Hello Bob
> Bye \${for} now!

Опять же, я не эксперт, но это (по крайней мере) дает вам способ ее решить.

НТН.

...