【问题标题】:How to build a date/hour grammar with ANTLR v3?如何使用 ANTLR v3 构建日期/小时语法?
【发布时间】:2012-12-27 02:08:03
【问题描述】:

我想使用 Antlr 解析一串法语日期。

我有三种日期:

  • date_day : 2004 年 10 月 3 日(小时/分钟为午夜)
  • date_time : 12h(日、月、年以当前日期补全)
  • date_complete:2004 年 10 月 3 日 12 小时。如您所见date_complete : date_day date_hour

我要解析的文档只是date_daydate_timedate_complete 的链(没有分隔符)。

这是我要解析的字符串示例

3 Octobre 2005 12h 13h 5 Octobre 2004 3 Septembre 2005 11h
Expected : date_complete date_time date_day date_complete

12h
Expected : date_time

3 Octobre 2005 5 Octobre 2004 12h 13h 3 Septembre 2005 11h
Expected : date_day date_complete date_time date_complete

**// NEW REQUIREMENTS**

3 Octobre 2005
Expected : date_day 

3 Octobre 
Expected : date_day 

3 
Expected : date_day 

我尝试了很多东西,Antlr v3 总是说我的语法有歧义:

warning(200): /meleo.dates/src/Grammar.g:25:48: 
Decision can match input such as "{FRI, MON..TUE, WED} TWO_DIGITS DECEMBER FOUR_DIGITS {FRI..HOURG, MON..WED}" using multiple alternatives: 1, 2
As a result, alternative(s) 2 were disabled for that input
 |---> date_day (date_day | date_complete | date_hour)+

该语法的正确写法是什么?

语法如下:

grammar MeleoDates;

options {
  language = Java;
}

@header {
  package meleo.data.dates ; 

  import rainstudios.meleo.crawler.data.Dates ;
  import rainstudios.meleo.crawler.data.EventDate ;
}

@lexer::header {
  package meleo.data.dates ;   

  import rainstudios.meleo.crawler.data.EventDate ;
 }

input           returns [Dates dates] 
                @init {Dates r = new Dates() ; } : 
                (   date 
                    {r.addDay($date.date);}
                    DATE_SEP?)+ 
                EOF
                    {$dates = r ;}
                ;

date            returns [EventDate date] :
                (date_complete)=> date_complete 
                    {$date = $date_complete.date;}
                | date_day 
                    {$date = $date_day.date;}
                | date_time 
                    {$date = $date_time.date;}
                ;

date_complete  returns  [EventDate date]   
                @init   {EventDateBuilder builder = new EventDateBuilder() ; } : 
                 day=date_day 
                    {builder.addDay($day.date);}
                 HOUR_SEP? 
                 time=date_time 
                    {builder.addTime($time.date);}
                    {$date = builder.toDate();}
                ;

date_day        returns [EventDate date] 
                @init   {EventDateBuilder builder = new EventDateBuilder() ; } :
                (
                dayOfWeek=( 
                     MON
                   | TUE
                   | WED
                   | THU
                   | FRI
                   | SAT
                   | SUN
                )?
                (day=INT)=> INT 
                    {builder.addDay($day.text);}
                (   m=ID 
                        {builder.addMonth($m.text);}
                    year=INT ?
                        {builder.addMonth($year.text);}
                )?
                )
                    {$date = builder.toDate();}
                ;

date_time       returns [EventDate date]  
                @init   {EventDateBuilder builder = new EventDateBuilder() ; } :
                    TIME 
                    {builder.addTime($TIME.text);}
                    {$date = builder.toDate();}
                ;

month   : DECEMBER | JANUARY ;

MON 
 : 'lundi'  
 | 'lun' 
 ;

 TUE 
 : 'mardi'  
 | 'mar' 
 ;

 WED 
 : 'mercredi'  
 | 'mer' 
 ;

 THU 
 : 'jeudi'  
 | 'jeu' 
 ;

 FRI 
 : 'venredi'  
 | 'ven' 
 ;

 SAT 
 : 'samedi'  
 | 'sam' 
 ;

 SUN 
 : 'dimanche'  
 | 'dim' 
 ;

DECEMBER    : 'dec' | 'decembre' ;
JANUARY     : 'jan' | 'janvier' ;

DATE_SEP    : 'et'| ',' | '-'; 
HOUR_SEP    : 'à' | 'a' ;
INT         : ('0'..'9')+;
TIME_SEP    : ':'  | 'h' ;
TIME        : INT TIME_SEP INT?;
ID          : ('a'..'z'|'A'..'Z')+;

WS : (' ' | '\t' | '\n' | '\r' | '\f')+ {$channel = HIDDEN;};

** 已编辑:添加了新要求(date_day 的可选月份和年份)**

【问题讨论】:

  • 你能把你的语法贴在这里吗?
  • 现在按照@tenterhook 的建议使用语法

标签: java antlr antlr3


【解决方案1】:

考虑使用句法谓词:

input      : date+;
date       : (date_complete) => date_complete
           | date_day
           | date_time
           ;

这实际上是告诉 ANTLR 在尝试匹配它找到的任何东西之前尝试date_complete(这可能不是技术上准确的描述,但你明白了)。如果没有这个,date 规则可以匹配具有相同输入的多个选项,而 ANTLR(无论如何,v3)无法解决这个问题。

这是一个完整的测试语法:

grammar AmbiguousDates;


input           : date+ EOF;

date            : (date_complete)=> date_complete 
                    {System.out.println("date_complete: " + $date_complete.str);}
                | date_day 
                    {System.out.println("date_day: " + $date_day.str);}
                | date_time 
                    {System.out.println("date_time: " + $date_time.str);}
                ;

date_complete   returns [String str]
                : date_day date_time 
                    {$str = String.format("\%s \%s", $date_day.str, $date_time.str);}
                ;

date_day        returns [String str]
                : day=INT ID year=INT 
                    {$str = String.format("\%s \%s \%s", $day.text, $ID.text, $year.text);}
                ;

date_time       returns [String str]
                : TIME 
                    {$str = $TIME.text;}
                ;

INT     : ('0'..'9')+;
TIME    : INT 'h';
ID      : ('a'..'z'|'A'..'Z')+;
WS      : (' '|'\t'|'\f'|'\r'|'\n')+ {skip();};

输入

3 Octobre 2005 12h 13h 5 Octobre 2004 3 Septembre 2005 11h

输出

date_complete: 3 Octobre 2005 12h
date_time: 13h
date_day: 5 Octobre 2004
date_complete: 3 Septembre 2005 11h

【讨论】:

  • 现在,我想选择月份和年份。 date_day returns [String str] : day=INT ID? year=INT? 但我又遇到了可怕的模棱两可的语法。有什么想法吗?
  • @Nameiscarl date_day 无法判断 INT INT 是指“date_day=day year”还是“date_day=day date_day=day”。尝试制定词法分析器规则,例如匹配一个或两个数字的DAY 和匹配四个数字的YEAR,然后将date_day 更改为DAY ID? YEAR? 如果遇到问题,开始一个新问题可能更容易。跨度>
【解决方案2】:

我认为使用 ANTLR 不会有任何收获。您可以改为使用SimpleDateFormat#parse 以及一些额外的工作来检查尾随小时(即“h”)标记以实现您的目标,如下所示:

打包问题;

import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Locale;

public class FrenchDateParser {
    private static SimpleDateFormat date_complete = new SimpleDateFormat("d MMMM yyyy h", Locale.FRENCH);

    private static SimpleDateFormat date_day = new SimpleDateFormat("d MMMM yyyy", Locale.FRENCH);

    private static SimpleDateFormat date_time = new SimpleDateFormat("h", Locale.FRENCH);

    private static String parse(String input) {
        ParsePosition parsePosition = new ParsePosition(0);
        StringBuilder stringBuilder = new StringBuilder();
        int inputSize = input.length();
        while (parsePosition.getIndex() < inputSize) {
            int startingParsePositionIndex = parsePosition.getIndex();

            if (date_complete.parse(input, parsePosition) != null) {
                if (input.charAt(parsePosition.getIndex()) == 'h') {
                    stringBuilder.append("date_complete ");
                    parsePosition.setIndex(parsePosition.getIndex() + 1);
                    continue;
                }
                parsePosition.setIndex(startingParsePositionIndex);
            }

            if (date_day.parse(input, parsePosition) != null) {
                stringBuilder.append("date_day ");
                continue;
            }

            if (date_time.parse(input, parsePosition) != null) {
                if (input.charAt(parsePosition.getIndex()) == 'h') {
                    stringBuilder.append("date_time ");
                    parsePosition.setIndex(parsePosition.getIndex() + 1);
                    continue;
                }
                parsePosition.setIndex(startingParsePositionIndex);
            }

            throw new IllegalArgumentException("Unable to parse input [" + input + "]");
        }
        return stringBuilder.toString().trim();
    }

    public static void main(String... args) throws ParseException {
        String[] inputs = {"3 Octobre 2005 12h 13h 5 Octobre 2004 3 Septembre 2005 11h", "12h",
                "3 Octobre 2005 5 Octobre 2004 12h 13h 3 Septembre 2005 11h"};

        String[] expecteds = {"date_complete date_time date_day date_complete", "date_time",
                "date_day date_complete date_time date_complete"};

        for (int i = 0; i < inputs.length; i++) {
            String actual = parse(inputs[i]);
            System.out.println(expecteds[i].equals(actual));
        }
    }
}

【讨论】:

  • 为了这个问题,我简化了 date_day 和 date_time。
  • 使用 ANTLR 仍然不能确保有效的日期 - 例如处理闰年 - 并且确实会产生您想要的默认值(例如“date_day”小时/分钟是午夜)。
  • 因此,仍然没有看到 ANTLR 在 Java 的内置解析器中为您提供了什么。使用 Java 的内置日期解析器可以为您做两件事 - 解析和验证。
  • 下一步是添加区间:“从 12 小时 30 分到 15 小时 45 分”和许多其他内容,例如解析一些自然语言“午夜、中午、明天”。一个合适的解析器看起来是要走的路。
猜你喜欢
  • 1970-01-01
  • 2012-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多