mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
583 lines
17 KiB
Text
583 lines
17 KiB
Text
# -*- racc -*-
|
|
|
|
# Copyright 2011 Sylvester Keil. All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# 1. Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions and the following disclaimer.
|
|
#
|
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
|
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
# EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
#
|
|
# The views and conclusions contained in the software and documentation are
|
|
# those of the authors and should not be interpreted as representing official
|
|
# policies, either expressed or implied, of the copyright holder.
|
|
|
|
class EDTF::Parser
|
|
|
|
token T Z E X U UNKNOWN OPEN LONGYEAR UNMATCHED DOTS UA PUA
|
|
|
|
expect 0
|
|
|
|
rule
|
|
|
|
edtf : level_0_expression
|
|
| level_1_expression
|
|
| level_2_expression
|
|
;
|
|
|
|
# ---- Level 0 / ISO 8601 Rules ----
|
|
|
|
# NB: level 0 intervals are covered by the level 1 interval rules
|
|
level_0_expression : date
|
|
| date_time
|
|
;
|
|
|
|
date : positive_date
|
|
| negative_date
|
|
;
|
|
|
|
positive_date :
|
|
year { result = Date.new(val[0]).year_precision! }
|
|
| year_month { result = Date.new(*val.flatten).month_precision! }
|
|
| year_month_day { result = Date.new(*val.flatten).day_precision! }
|
|
;
|
|
|
|
negative_date : '-' positive_date { result = -val[1] }
|
|
|
|
|
|
date_time : date T time {
|
|
result = DateTime.new(val[0].year, val[0].month, val[0].day, *val[2])
|
|
result.skip_timezone = (val[2].length == 3)
|
|
}
|
|
|
|
time : base_time
|
|
| base_time zone_offset { result = val.flatten }
|
|
|
|
base_time : hour ':' minute ':' second { result = val.values_at(0, 2, 4) }
|
|
| midnight
|
|
|
|
midnight : '2' '4' ':' '0' '0' ':' '0' '0' { result = [24, 0, 0] }
|
|
|
|
zone_offset : Z { result = 0 }
|
|
| '-' zone_offset_hour { result = -1 * val[1] }
|
|
| '+' positive_zone_offset { result = val[1] }
|
|
;
|
|
|
|
positive_zone_offset : zone_offset_hour
|
|
| '0' '0' ':' '0' '0' { result = 0 }
|
|
;
|
|
|
|
|
|
zone_offset_hour : d01_13 ':' minute { result = Rational(val[0] * 60 + val[2], 1440) }
|
|
| '1' '4' ':' '0' '0' { result = Rational(840, 1440) }
|
|
| '0' '0' ':' d01_59 { result = Rational(val[3], 1440) }
|
|
;
|
|
|
|
year : digit digit digit digit {
|
|
result = val.zip([1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b }
|
|
}
|
|
|
|
month : d01_12
|
|
day : d01_31
|
|
|
|
year_month : year '-' month { result = [val[0], val[2]] }
|
|
|
|
# We raise an exception if there are two many days for the month, but
|
|
# do not consider leap years, as the EDTF BNF did not either.
|
|
# NB: an exception will be raised regardless, because the Ruby Date
|
|
# implementation calculates leap years.
|
|
year_month_day : year_month '-' day {
|
|
result = val[0] << val[2]
|
|
if result[2] > 31 || (result[2] > 30 && [2,4,6,9,11].include?(result[1])) || (result[2] > 29 && result[1] == 2)
|
|
raise ArgumentError, "invalid date (invalid days #{result[2]} for month #{result[1]})"
|
|
end
|
|
}
|
|
|
|
hour : d00_23
|
|
minute : d00_59
|
|
second : d00_59
|
|
|
|
# Completely covered by level_1_interval
|
|
# level_0_interval : date '/' date { result = Interval.new(val[0], val[1]) }
|
|
|
|
|
|
# ---- Level 1 Extension Rules ----
|
|
|
|
# NB: Uncertain/approximate Dates are covered by the Level 2 rules
|
|
level_1_expression : unspecified | level_1_interval | long_year_simple | season
|
|
|
|
# uncertain_or_approximate_date : date UA { result = uoa(val[0], val[1]) }
|
|
|
|
unspecified : unspecified_year
|
|
{
|
|
result = Date.new(val[0][0]).year_precision!
|
|
result.unspecified.year[2,2] = val[0][1]
|
|
}
|
|
| unspecified_month
|
|
| unspecified_day
|
|
| unspecified_day_and_month
|
|
;
|
|
|
|
unspecified_year :
|
|
digit digit digit U
|
|
{
|
|
result = [val[0,3].zip([1000,100,10]).reduce(0) { |s,(a,b)| s += a * b }, [false,true]]
|
|
}
|
|
| digit digit U U
|
|
{
|
|
result = [val[0,2].zip([1000,100]).reduce(0) { |s,(a,b)| s += a * b }, [true, true]]
|
|
}
|
|
|
|
unspecified_month : year '-' U U {
|
|
result = Date.new(val[0]).unspecified!(:month)
|
|
result.precision = :month
|
|
}
|
|
|
|
unspecified_day : year_month '-' U U {
|
|
result = Date.new(*val[0]).unspecified!(:day)
|
|
}
|
|
|
|
unspecified_day_and_month : year '-' U U '-' U U {
|
|
result = Date.new(val[0]).unspecified!([:day,:month])
|
|
}
|
|
|
|
|
|
level_1_interval : level_1_start '/' level_1_end {
|
|
result = Interval.new(val[0], val[2])
|
|
}
|
|
|
|
level_1_start : date | partial_uncertain_or_approximate | unspecified | partial_unspecified | UNKNOWN
|
|
|
|
level_1_end : level_1_start | OPEN
|
|
|
|
|
|
long_year_simple :
|
|
LONGYEAR long_year
|
|
{
|
|
result = Date.new(val[1])
|
|
result.precision = :year
|
|
}
|
|
| LONGYEAR '-' long_year
|
|
{
|
|
result = Date.new(-1 * val[2])
|
|
result.precision = :year
|
|
}
|
|
;
|
|
|
|
long_year :
|
|
positive_digit digit digit digit digit {
|
|
result = val.zip([10000,1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b }
|
|
}
|
|
| long_year digit { result = 10 * val[0] + val[1] }
|
|
;
|
|
|
|
|
|
season : year '-' season_number ua {
|
|
result = Season.new(val[0], val[2])
|
|
val[3].each { |ua| result.send(ua) }
|
|
}
|
|
|
|
season_number : '2' '1' { result = 21 }
|
|
| '2' '2' { result = 22 }
|
|
| '2' '3' { result = 23 }
|
|
| '2' '4' { result = 24 }
|
|
;
|
|
|
|
|
|
# ---- Level 2 Extension Rules ----
|
|
|
|
# NB: Level 2 Intervals are covered by the Level 1 Interval rules.
|
|
level_2_expression : season_qualified
|
|
| partial_uncertain_or_approximate
|
|
| partial_unspecified
|
|
| choice_list
|
|
| inclusive_list
|
|
| masked_precision
|
|
| date_and_calendar
|
|
| long_year_scientific
|
|
;
|
|
|
|
|
|
season_qualified : season '^' { result = val[0]; result.qualifier = val[1] }
|
|
|
|
|
|
long_year_scientific :
|
|
long_year_simple E integer
|
|
{
|
|
result = Date.new(val[0].year * 10 ** val[2]).year_precision!
|
|
}
|
|
| LONGYEAR int1_4 E integer
|
|
{
|
|
result = Date.new(val[1] * 10 ** val[3]).year_precision!
|
|
}
|
|
| LONGYEAR '-' int1_4 E integer
|
|
{
|
|
result = Date.new(-1 * val[2] * 10 ** val[4]).year_precision!
|
|
}
|
|
;
|
|
|
|
|
|
date_and_calendar : date '^' { result = val[0]; result.calendar = val[1] }
|
|
|
|
|
|
masked_precision :
|
|
digit digit digit X
|
|
{
|
|
d = val[0,3].zip([1000,100,10]).reduce(0) { |s,(a,b)| s += a * b }
|
|
result = EDTF::Decade.new(d)
|
|
}
|
|
| digit digit X X
|
|
{
|
|
d = val[0,2].zip([1000,100]).reduce(0) { |s,(a,b)| s += a * b }
|
|
result = EDTF::Century.new(d)
|
|
}
|
|
;
|
|
|
|
|
|
choice_list : '[' list ']' { result = val[1].choice! }
|
|
|
|
inclusive_list : '{' list '}' { result = val[1] }
|
|
|
|
list : earlier { result = EDTF::Set.new(val[0]).earlier! }
|
|
| earlier ',' list_elements ',' later { result = EDTF::Set.new([val[0]] + val[2] + [val[4]]).earlier!.later! }
|
|
| earlier ',' list_elements { result = EDTF::Set.new([val[0]] + val[2]).earlier! }
|
|
| earlier ',' later { result = EDTF::Set.new([val[0]] + [val[2]]).earlier!.later! }
|
|
| list_elements ',' later { result = EDTF::Set.new(val[0] + [val[2]]).later! }
|
|
| list_elements { result = EDTF::Set.new(*val[0]) }
|
|
| later { result = EDTF::Set.new(val[0]).later! }
|
|
;
|
|
|
|
list_elements : list_element { result = [val[0]].flatten }
|
|
| list_elements ',' list_element { result = val[0] + [val[2]].flatten }
|
|
;
|
|
|
|
list_element : atomic
|
|
| consecutives
|
|
;
|
|
|
|
atomic : date
|
|
| partial_uncertain_or_approximate
|
|
| unspecified
|
|
;
|
|
|
|
earlier : DOTS date { result = val[1] }
|
|
|
|
later : year_month_day DOTS { result = Date.new(*val[0]).year_precision! }
|
|
| year_month DOTS { result = Date.new(*val[0]).month_precision! }
|
|
| year DOTS { result = Date.new(val[0]).year_precision! }
|
|
;
|
|
|
|
consecutives : year_month_day DOTS year_month_day { result = (Date.new(val[0]).day_precision! .. Date.new(val[2]).day_precision!) }
|
|
| year_month DOTS year_month { result = (Date.new(val[0]).month_precision! .. Date.new(val[2]).month_precision!) }
|
|
| year DOTS year { result = (Date.new(val[0]).year_precision! .. Date.new(val[2]).year_precision!) }
|
|
;
|
|
|
|
partial_unspecified :
|
|
unspecified_year '-' month '-' day
|
|
{
|
|
result = Date.new(val[0][0], val[2], val[4])
|
|
result.unspecified.year[2,2] = val[0][1]
|
|
}
|
|
| unspecified_year '-' U U '-' day
|
|
{
|
|
result = Date.new(val[0][0], 1, val[5])
|
|
result.unspecified.year[2,2] = val[0][1]
|
|
result.unspecified!(:month)
|
|
}
|
|
| unspecified_year '-' U U '-' U U
|
|
{
|
|
result = Date.new(val[0][0], 1, 1)
|
|
result.unspecified.year[2,2] = val[0][1]
|
|
result.unspecified!([:month, :day])
|
|
}
|
|
| unspecified_year '-' month '-' U U
|
|
{
|
|
result = Date.new(val[0][0], val[2], 1)
|
|
result.unspecified.year[2,2] = val[0][1]
|
|
result.unspecified!(:day)
|
|
}
|
|
| year '-' U U '-' day
|
|
{
|
|
result = Date.new(val[0], 1, val[5])
|
|
result.unspecified!(:month)
|
|
}
|
|
;
|
|
|
|
|
|
partial_uncertain_or_approximate : pua_base
|
|
| '(' pua_base ')' UA { result = uoa(val[1], val[3]) }
|
|
|
|
pua_base :
|
|
pua_year { result = val[0].year_precision! }
|
|
| pua_year_month { result = val[0][0].month_precision! }
|
|
| pua_year_month_day { result = val[0].day_precision! }
|
|
|
|
pua_year : year UA { result = uoa(Date.new(val[0]), val[1], :year) }
|
|
|
|
pua_year_month :
|
|
pua_year '-' month ua {
|
|
result = [uoa(val[0].change(:month => val[2]), val[3], [:month, :year])]
|
|
}
|
|
| year '-' month UA {
|
|
result = [uoa(Date.new(val[0], val[2]), val[3], [:year, :month])]
|
|
}
|
|
| year '-(' month ')' UA {
|
|
result = [uoa(Date.new(val[0], val[2]), val[4], [:month]), true]
|
|
}
|
|
| pua_year '-(' month ')' UA {
|
|
result = [uoa(val[0].change(:month => val[2]), val[4], [:month]), true]
|
|
}
|
|
;
|
|
|
|
pua_year_month_day :
|
|
pua_year_month '-' day ua {
|
|
result = uoa(val[0][0].change(:day => val[2]), val[3], val[0][1] ? [:day] : nil)
|
|
}
|
|
| pua_year_month '-(' day ')' UA {
|
|
result = uoa(val[0][0].change(:day => val[2]), val[4], [:day])
|
|
}
|
|
| year '-(' month ')' UA day ua {
|
|
result = uoa(uoa(Date.new(val[0], val[2], val[5]), val[4], :month), val[6], :day)
|
|
}
|
|
| year_month '-' day UA {
|
|
result = uoa(Date.new(val[0][0], val[0][1], val[2]), val[3])
|
|
}
|
|
| year_month '-(' day ')' UA {
|
|
result = uoa(Date.new(val[0][0], val[0][1], val[2]), val[4], [:day])
|
|
}
|
|
| year '-(' month '-' day ')' UA {
|
|
result = uoa(Date.new(val[0], val[2], val[4]), val[6], [:month, :day])
|
|
}
|
|
| year '-(' month '-(' day ')' UA ')' UA {
|
|
result = Date.new(val[0], val[2], val[4])
|
|
result = uoa(result, val[6], [:day])
|
|
result = uoa(result, val[8], [:month, :day])
|
|
}
|
|
| pua_year '-(' month '-' day ')' UA {
|
|
result = val[0].change(:month => val[2], :day => val[4])
|
|
result = uoa(result, val[6], [:month, :day])
|
|
}
|
|
| pua_year '-(' month '-(' day ')' UA ')' UA {
|
|
result = val[0].change(:month => val[2], :day => val[4])
|
|
result = uoa(result, val[6], [:day])
|
|
result = uoa(result, val[8], [:month, :day])
|
|
}
|
|
# | '(' pua_year '-(' month ')' UA ')' UA '-' day ua {
|
|
# result = val[1].change(:month => val[3], :day => val[9])
|
|
# result = uoa(result, val[5], [:month])
|
|
# result = [uoa(result, val[7], [:year]), true]
|
|
# }
|
|
;
|
|
|
|
ua : { result = [] } | UA
|
|
|
|
# ---- Auxiliary Rules ----
|
|
|
|
digit : '0' { result = 0 }
|
|
| positive_digit
|
|
;
|
|
|
|
positive_digit : '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
|
|
|
|
d01_12 : '0' positive_digit { result = val[1] }
|
|
| '1' '0' { result = 10 }
|
|
| '1' '1' { result = 11 }
|
|
| '1' '2' { result = 12 }
|
|
;
|
|
|
|
d01_13 : d01_12
|
|
| '1' '3' { result = 13 }
|
|
;
|
|
|
|
d01_23 : '0' positive_digit { result = val[1] }
|
|
| '1' digit { result = 10 + val[1] }
|
|
| '2' '0' { result = 20 }
|
|
| '2' '1' { result = 21 }
|
|
| '2' '2' { result = 22 }
|
|
| '2' '3' { result = 23 }
|
|
;
|
|
|
|
d00_23 : '0' '0'
|
|
| d01_23
|
|
;
|
|
|
|
d01_29 : d01_23
|
|
| '2' '4' { result = 24 }
|
|
| '2' '5' { result = 25 }
|
|
| '2' '6' { result = 26 }
|
|
| '2' '7' { result = 27 }
|
|
| '2' '8' { result = 28 }
|
|
| '2' '9' { result = 29 }
|
|
;
|
|
|
|
d01_30 : d01_29
|
|
| '3' '0' { result = 30 }
|
|
;
|
|
|
|
d01_31 : d01_30
|
|
| '3' '1' { result = 31 }
|
|
;
|
|
|
|
d01_59 : d01_29
|
|
| '3' digit { result = 30 + val[1] }
|
|
| '4' digit { result = 40 + val[1] }
|
|
| '5' digit { result = 50 + val[1] }
|
|
;
|
|
|
|
d00_59 : '0' '0'
|
|
| d01_59
|
|
;
|
|
|
|
int1_4 : positive_digit { result = val[0] }
|
|
| positive_digit digit { result = 10 * val[0] + val[1] }
|
|
| positive_digit digit digit
|
|
{
|
|
result = val.zip([100,10,1]).reduce(0) { |s,(a,b)| s += a * b }
|
|
}
|
|
| positive_digit digit digit digit
|
|
{
|
|
result = val.zip([1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b }
|
|
}
|
|
;
|
|
|
|
integer : positive_digit { result = val[0] }
|
|
| integer digit { result = 10 * val[0] + val[1] }
|
|
;
|
|
|
|
|
|
|
|
---- header
|
|
require 'strscan'
|
|
|
|
---- inner
|
|
|
|
@defaults = {
|
|
:level => 2,
|
|
:debug => false
|
|
}.freeze
|
|
|
|
class << self; attr_reader :defaults; end
|
|
|
|
attr_reader :options
|
|
|
|
def initialize(options = {})
|
|
@options = Parser.defaults.merge(options)
|
|
end
|
|
|
|
def debug?
|
|
!!(options[:debug] || ENV['DEBUG'])
|
|
end
|
|
|
|
def parse(input)
|
|
parse!(input)
|
|
rescue => e
|
|
warn e.message if debug?
|
|
nil
|
|
end
|
|
|
|
def parse!(input)
|
|
@yydebug = debug?
|
|
@src = StringScanner.new(input)
|
|
do_parse
|
|
end
|
|
|
|
def on_error(tid, value, stack)
|
|
raise ArgumentError,
|
|
"failed to parse date: unexpected '#{value}' at #{stack.inspect}"
|
|
end
|
|
|
|
def apply_uncertainty(date, uncertainty, scope = nil)
|
|
uncertainty.each do |u|
|
|
scope.nil? ? date.send(u) : date.send(u, scope)
|
|
end
|
|
date
|
|
end
|
|
|
|
alias uoa apply_uncertainty
|
|
|
|
def next_token
|
|
case
|
|
when @src.eos?
|
|
nil
|
|
# when @src.scan(/\s+/)
|
|
# ignore whitespace
|
|
when @src.scan(/\(/)
|
|
['(', @src.matched]
|
|
# when @src.scan(/\)\?~-/)
|
|
# [:PUA, [:uncertain!, :approximate!]]
|
|
# when @src.scan(/\)\?-/)
|
|
# [:PUA, [:uncertain!]]
|
|
# when @src.scan(/\)~-/)
|
|
# [:PUA, [:approximate!]]
|
|
when @src.scan(/\)/)
|
|
[')', @src.matched]
|
|
when @src.scan(/\[/)
|
|
['[', @src.matched]
|
|
when @src.scan(/\]/)
|
|
[']', @src.matched]
|
|
when @src.scan(/\{/)
|
|
['{', @src.matched]
|
|
when @src.scan(/\}/)
|
|
['}', @src.matched]
|
|
when @src.scan(/T/)
|
|
[:T, @src.matched]
|
|
when @src.scan(/Z/)
|
|
[:Z, @src.matched]
|
|
when @src.scan(/\?~/)
|
|
[:UA, [:uncertain!, :approximate!]]
|
|
when @src.scan(/\?/)
|
|
[:UA, [:uncertain!]]
|
|
when @src.scan(/~/)
|
|
[:UA, [:approximate!]]
|
|
when @src.scan(/open/i)
|
|
[:OPEN, :open]
|
|
when @src.scan(/unkn?own/i) # matches 'unkown' typo too
|
|
[:UNKNOWN, :unknown]
|
|
when @src.scan(/u/)
|
|
[:U, @src.matched]
|
|
when @src.scan(/x/i)
|
|
[:X, @src.matched]
|
|
when @src.scan(/y/)
|
|
[:LONGYEAR, @src.matched]
|
|
when @src.scan(/e/)
|
|
[:E, @src.matched]
|
|
when @src.scan(/\+/)
|
|
['+', @src.matched]
|
|
when @src.scan(/-\(/)
|
|
['-(', @src.matched]
|
|
when @src.scan(/-/)
|
|
['-', @src.matched]
|
|
when @src.scan(/:/)
|
|
[':', @src.matched]
|
|
when @src.scan(/\//)
|
|
['/', @src.matched]
|
|
when @src.scan(/\s*\.\.\s*/)
|
|
[:DOTS, '..']
|
|
when @src.scan(/\s*,\s*/)
|
|
[',', ',']
|
|
when @src.scan(/\^\w+/)
|
|
['^', @src.matched[1..-1]]
|
|
when @src.scan(/\d/)
|
|
[@src.matched, @src.matched.to_i]
|
|
else @src.scan(/./)
|
|
[:UNMATCHED, @src.rest]
|
|
end
|
|
end
|
|
|
|
|
|
# -*- racc -*-
|