edtf 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -8,20 +8,15 @@ Specification](http://www.loc.gov/standards/datetime/spec.html).
8
8
  Compatibility
9
9
  -------------
10
10
 
11
- As of EDTF Specification DRAFT, August 4, 2001:
12
-
13
- * Level 0: fully implemented
14
- * Level 1: fully implemented
15
- * Level 2: implemented features 202, 203, 204, 2041, 207, 208, and 209
11
+ EDTF-Ruby parser fully implements all levels and features of EDTF
12
+ Specification DRAFT, August 4, 2011.
16
13
 
17
14
  The level 2 list extensions (203 and 204) currently return simple Ruby arrays;
18
15
  therefore, advanced behavior (such as 'earlier' or 'later') is parsed correctly
19
16
  but not yet exposed by the Ruby API.
20
17
 
21
- The level 2 extensions 201 and 205 are currently _not_ supported.
22
-
23
18
  EDTF-Ruby has been confirmed to work on the following Ruby implementations:
24
- 1.9.2, 1.8.7, rbx, jruby.
19
+ 1.9.2, 1.8.7, Rubinius, and JRuby.
25
20
 
26
21
 
27
22
  Quickstart
@@ -37,33 +32,35 @@ given a valid EDTF string the return value will either be an (extended) `Date`,
37
32
  > require 'edtf'
38
33
  > d = Date.edtf('1984?')
39
34
  > d.uncertain?
40
- => true
35
+ => true
41
36
  > d.certain!
42
37
  > d.uncertain?
43
- => false
38
+ => false
44
39
  > d = Date.edtf('1999-03-uu')
45
40
  > d.unspecified?
46
- => true
41
+ => true
47
42
  > d.unspecified? :year
48
- => false
43
+ => false
49
44
  > d.unspecified? :day
50
- => true
45
+ => true
51
46
  > Date.edtf('2003-24').winter?
52
- => true
47
+ => true
53
48
  > Date.edtf('196x')
54
- => #<Date: 1960-01-01>...#<Date: 1970-01-01>
49
+ => #<Date: 1960-01-01>...#<Date: 1970-01-01>
55
50
  > Date.edtf('y-17e7').year
56
- => -170000000
51
+ => -170000000
57
52
  > d = Date.edtf('1984-06?/2004-08?')
58
53
  > d.from.uncertain?
59
- => true
60
- > d.each.to_a.length
61
- => 7367 # days between 1984-06 and 2004-08
62
- > Date.edtf('2004-01-01/open').open?
63
- => true
54
+ => true
55
+ > d.to_a.length
56
+ => 7397 # days between 1984-06-01 and 2004-08-31
57
+ > Date.edtf('1582-10/1582-10').to_a.length
58
+ => 21 # number of days in October 1582 (Gregorian calendar)
59
+ > Date.edtf('2004/open').open?
60
+ => true
64
61
 
65
62
 
66
- For additional features take a look at the rdoc, source, and rspec examples.
63
+ For additional features take a look at the RDoc and RSpec examples.
67
64
 
68
65
 
69
66
  Development
@@ -72,16 +69,18 @@ Development
72
69
  $ git clone https://inukshuk@github.com/inukshuk/edtf-ruby.git
73
70
  $ cd edtf-ruby
74
71
  $ bundle install
75
- $ bundle exec rake racc
72
+ $ bundle exec rake racc_debug
76
73
  $ bundle exec rspec spec
77
74
  $ bundle exec cucumber
78
75
 
79
- For extra credit, fork the project on github: pull requests welcome!
76
+ For extra credit, fork the project on GitHub: pull requests welcome!
77
+
80
78
 
81
79
  Credits
82
80
  -------
83
81
 
84
- EDTF-Ruby was written by [Sylvester Keil](http://sylvester.keil.or.at).
82
+ EDTF-Ruby was written by [Sylvester Keil](http://sylvester.keil.or.at) and
83
+ [Namyra](https://github.com/namyra).
85
84
 
86
85
  Published under the terms and conditions of the FreeBSD License; see LICENSE
87
86
  for details.
@@ -31,11 +31,11 @@ Feature: EDTF parser parses date strings
31
31
 
32
32
  @101 @level1
33
33
  Scenarios: uncertain date examples from the specification
34
- | string | year | uncertain-year | month | uncertain-month | day | uncertain-day |
35
- | 1992? | 1992 | yes | 1 | yes | 1 | yes |
36
- | 1984? | 1984 | yes | 1 | yes | 1 | yes |
37
- | 2004-06? | 2004 | yes | 6 | yes | 1 | yes |
38
- | 2004-06-11? | 2004 | yes | 6 | yes | 11 | yes |
34
+ | string | year | uncertain-year | month | uncertain-month | day | uncertain-day |
35
+ | 1992? | 1992 | yes | 1 | no | 1 | no |
36
+ | 1984? | 1984 | yes | 1 | no | 1 | no |
37
+ | 2004-06? | 2004 | yes | 6 | yes | 1 | no |
38
+ | 2004-06-11? | 2004 | yes | 6 | yes | 11 | yes |
39
39
 
40
40
 
41
41
  Scenario Outline: EDTF parses uncertain or approximate date strings
@@ -51,3 +51,26 @@ Feature: EDTF parser parses date strings
51
51
  | string | year | month | day | uncertain | approximate |
52
52
  | 1984~ | 1984 | 1 | 1 | no | yes |
53
53
  | 1984?~ | 1984 | 1 | 1 | yes | yes |
54
+
55
+ Scenario Outline: EDTF parses uncertain or approximate dates
56
+ When I parse the string "<string>"
57
+ And the year should be uncertain? "<?-year>"
58
+ And the month should be uncertain? "<?-month>"
59
+ And the day should be uncertain? "<?-day>"
60
+ And the year should be approximate? "<~-year>"
61
+ And the month should be approximate? "<~-month>"
62
+ And the day should be approximate? "<~-day>"
63
+
64
+ @201 @level2
65
+ Scenarios: uncertain date examples from the specification
66
+ | string | ~-year | ?-year | ~-month | ?-month | ~-day | ?-day |
67
+ | 2004?-06-11 | no | yes | no | no | no | no |
68
+ | 2004-06~-11 | yes | no | yes | no | no | no |
69
+ | 2004-(06)?-11 | no | no | no | yes | no | no |
70
+ | 2004-06-(11)~ | no | no | no | no | yes | no |
71
+ | 2004-(06)?~ | no | no | yes | yes | no | no |
72
+ | 2004-(06-11)? | no | no | no | yes | no | yes |
73
+ | 2004?-06-(11)~ | no | yes | no | no | yes | no |
74
+ | (2004-(06)~)? | no | yes | yes | yes | no | no |
75
+ | 2004-06-(01)~ | no | no | no | no | yes | no |
76
+
@@ -0,0 +1,73 @@
1
+ Feature: Print Date/Time objects as Level 1 EDTF strings
2
+ As a Ruby programmer
3
+ I want to convert Date/Time objects to EDTF strings
4
+
5
+ @001 @level0
6
+ Scenario: Convert simple dates
7
+ Given the date "2004-08-12"
8
+ When I convert the date
9
+ Then the EDTF string should be "2004-08-12"
10
+
11
+ @001 @level0
12
+ Scenario: Convert simple dates with precision
13
+ Given the date "1980-08-24" with precision set to "day"
14
+ When I convert the date
15
+ Then the EDTF string should be "1980-08-24"
16
+ Given the date "1980-08-24" with precision set to "month"
17
+ When I convert the date
18
+ Then the EDTF string should be "1980-08"
19
+ Given the date "1980-08-24" with precision set to "year"
20
+ When I convert the date
21
+ Then the EDTF string should be "1980"
22
+
23
+ @001 @level0
24
+ Scenario: Date Roundtrips
25
+ When I parse the string "2001-02-03"
26
+ When I convert the date
27
+ Then the EDTF string should be "2001-02-03"
28
+ When I parse the string "2001-02"
29
+ When I convert the date
30
+ Then the EDTF string should be "2001-02"
31
+ When I parse the string "2001"
32
+ When I convert the date
33
+ Then the EDTF string should be "2001"
34
+ When I parse the string "-9909"
35
+ When I convert the date
36
+ Then the EDTF string should be "-9909"
37
+ When I parse the string "0000"
38
+ When I convert the date
39
+ Then the EDTF string should be "0000"
40
+
41
+ @002 @level0
42
+ Scenario: DateTime Roundtrips
43
+ When I parse the string "2001-02-03T09:30:01"
44
+ When I convert the date
45
+ Then the EDTF string should be "2001-02-03T09:30:01+00:00"
46
+ When I parse the string "2004-01-01T10:10:10Z"
47
+ When I convert the date
48
+ Then the EDTF string should be "2004-01-01T10:10:10+00:00"
49
+ When I parse the string "2004-01-01T10:10:10+05:00"
50
+ When I convert the date
51
+ Then the EDTF string should be "2004-01-01T10:10:10+05:00"
52
+
53
+ @004 @level0
54
+ Scenario: Interval Roundtrips
55
+ When I parse the string "1964/2008"
56
+ When I convert the date
57
+ Then the EDTF string should be "1964/2008"
58
+ When I parse the string "2004-06/2006-08"
59
+ When I convert the date
60
+ Then the EDTF string should be "2004-06/2006-08"
61
+ When I parse the string "2004-02-01/2005-02-08"
62
+ When I convert the date
63
+ Then the EDTF string should be "2004-02-01/2005-02-08"
64
+ When I parse the string "2004-02-01/2005-02"
65
+ When I convert the date
66
+ Then the EDTF string should be "2004-02-01/2005-02"
67
+ When I parse the string "2004-02-01/2005"
68
+ When I convert the date
69
+ Then the EDTF string should be "2004-02-01/2005"
70
+ When I parse the string "2005/2006-02"
71
+ When I convert the date
72
+ Then the EDTF string should be "2005/2006-02"
73
+
@@ -1,68 +1,15 @@
1
- Feature: Print Date/Time objects as Level 1 EDTF strings
1
+ Feature: Print Date/Time objects as Level 2 EDTF strings
2
2
  As a Ruby programmer
3
3
  I want to convert Date/Time objects to EDTF strings
4
4
 
5
- Scenario: Convert simple dates
6
- Given the date "2004-08-12"
7
- When I convert the date
8
- Then the EDTF string should be "2004-08-12"
9
-
10
- Scenario: Convert simple dates with precision
11
- Given the date "1980-08-24" with precision set to "day"
5
+ @101 @level1
6
+ Scenario: Uncertain or approximate dates
7
+ When I parse the string "2001?"
12
8
  When I convert the date
13
- Then the EDTF string should be "1980-08-24"
14
- Given the date "1980-08-24" with precision set to "month"
9
+ Then the EDTF string should be "2001?"
10
+ When I parse the string "2004-06?"
15
11
  When I convert the date
16
- Then the EDTF string should be "1980-08"
17
- Given the date "1980-08-24" with precision set to "year"
12
+ Then the EDTF string should be "2004-06?"
13
+ When I parse the string "2004-06-11?"
18
14
  When I convert the date
19
- Then the EDTF string should be "1980"
20
-
21
- Scenario: Date Roundtrips
22
- When I parse the string "2001-02-03"
23
- When I convert the date
24
- Then the EDTF string should be "2001-02-03"
25
- When I parse the string "2001-02"
26
- When I convert the date
27
- Then the EDTF string should be "2001-02"
28
- When I parse the string "2001"
29
- When I convert the date
30
- Then the EDTF string should be "2001"
31
- When I parse the string "-9909"
32
- When I convert the date
33
- Then the EDTF string should be "-9909"
34
- When I parse the string "0000"
35
- When I convert the date
36
- Then the EDTF string should be "0000"
37
-
38
- Scenario: DateTime Roundtrips
39
- When I parse the string "2001-02-03T09:30:01"
40
- When I convert the date
41
- Then the EDTF string should be "2001-02-03T09:30:01+00:00"
42
- When I parse the string "2004-01-01T10:10:10Z"
43
- When I convert the date
44
- Then the EDTF string should be "2004-01-01T10:10:10+00:00"
45
- When I parse the string "2004-01-01T10:10:10+05:00"
46
- When I convert the date
47
- Then the EDTF string should be "2004-01-01T10:10:10+05:00"
48
-
49
- Scenario: Interval Roundtrips
50
- When I parse the string "1964/2008"
51
- When I convert the date
52
- Then the EDTF string should be "1964/2008"
53
- When I parse the string "2004-06/2006-08"
54
- When I convert the date
55
- Then the EDTF string should be "2004-06/2006-08"
56
- When I parse the string "2004-02-01/2005-02-08"
57
- When I convert the date
58
- Then the EDTF string should be "2004-02-01/2005-02-08"
59
- When I parse the string "2004-02-01/2005-02"
60
- When I convert the date
61
- Then the EDTF string should be "2004-02-01/2005-02"
62
- When I parse the string "2004-02-01/2005"
63
- When I convert the date
64
- Then the EDTF string should be "2004-02-01/2005"
65
- When I parse the string "2005/2006-02"
66
- When I convert the date
67
- Then the EDTF string should be "2005/2006-02"
68
-
15
+ Then the EDTF string should be "2004-06-11?"
@@ -93,6 +93,19 @@ Then /^the date should be approximate\? "([^"]*)"$/ do |arg1|
93
93
  @date.approximate?.should == !!(arg1 =~ /y(es)?/i)
94
94
  end
95
95
 
96
+ Then /^the year should be approximate\? "([^"]*)"$/ do |arg1|
97
+ @date.approximate?(:year).should == !!(arg1 =~ /y(es)?/i)
98
+ end
99
+
100
+ Then /^the month should be approximate\? "([^"]*)"$/ do |arg1|
101
+ @date.approximate?(:month).should == !!(arg1 =~ /y(es)?/i)
102
+ end
103
+
104
+ Then /^the day should be approximate\? "([^"]*)"$/ do |arg1|
105
+ @date.approximate?(:day).should == !!(arg1 =~ /y(es)?/i)
106
+ end
107
+
108
+
96
109
  Then /^the unspecified string code be "([^"]*)"$/ do |arg1|
97
110
  @date.unspecified.to_s.should == arg1
98
111
  end
@@ -46,6 +46,7 @@ class Date
46
46
  @precision ||= :day
47
47
  end
48
48
 
49
+ # Sets this Date/Time's precision to the passed-in value.
49
50
  def precision=(precision)
50
51
  precision = precision.to_sym
51
52
  raise ArgumentError, "invalid precision #{precision.inspect}" unless PRECISIONS.include?(precision)
@@ -71,37 +72,37 @@ class Date
71
72
 
72
73
  def_delegators :uncertain, :uncertain?, :certain?
73
74
 
74
- def certain!(*arguments)
75
- uncertain.certain!(*arguments)
75
+ def certain!(arguments = precision_filter)
76
+ uncertain.certain!(arguments)
76
77
  self
77
78
  end
78
79
 
79
- def uncertain!(*arguments)
80
- uncertain.uncertain!(*arguments)
80
+ def uncertain!(arguments = precision_filter)
81
+ uncertain.uncertain!(arguments)
81
82
  self
82
83
  end
83
84
 
84
- def approximate?(*arguments)
85
- approximate.uncertain?(*arguments)
85
+ def approximate?(arguments = precision_filter)
86
+ approximate.uncertain?(arguments)
86
87
  end
87
88
 
88
89
  alias approximately? approximate?
89
90
 
90
- def approximate!(*arguments)
91
- approximate.uncertain!(*arguments)
91
+ def approximate!(arguments = precision_filter)
92
+ approximate.uncertain!(arguments)
92
93
  self
93
94
  end
94
95
 
95
96
  alias approximately! approximate!
96
97
 
97
- def precise?(*arguments)
98
- !approximate?(*arguments)
98
+ def precise?(arguments = precision_filter)
99
+ !approximate?(arguments)
99
100
  end
100
101
 
101
102
  alias precisely? precise?
102
103
 
103
- def precise!(*arguments)
104
- approximate.certain!(*arguments)
104
+ def precise!(arguments = precision_filter)
105
+ approximate.certain!(arguments)
105
106
  self
106
107
  end
107
108
 
@@ -109,15 +110,15 @@ class Date
109
110
 
110
111
  def_delegators :unspecified, :unspecified?, :specified?, :unsepcific?, :specific?
111
112
 
112
- def unspecified!(*arguments)
113
- unspecified.unspecified!(*arguments)
113
+ def unspecified!(arguments = precision_filter)
114
+ unspecified.unspecified!(arguments)
114
115
  self
115
116
  end
116
117
 
117
118
  alias unspecific! unspecified!
118
119
 
119
- def specified!(*arguments)
120
- unspecified.specified!(*arguments)
120
+ def specified!(arguments = precision_filter)
121
+ unspecified.specified!(arguments)
121
122
  self
122
123
  end
123
124
 
@@ -130,11 +131,13 @@ class Date
130
131
  end
131
132
 
132
133
  def edtf
133
- FORMATS.take(values.length).join('-') % values
134
+ FORMATS.take(values.length).join('-') % values << (uncertain? ? '?' : '')
134
135
  end
135
136
 
136
137
  alias to_edtf edtf
137
138
 
139
+ # Returns a the Date of the next day, month, or year depending on the
140
+ # current Date/Time's precision.
138
141
  def next
139
142
  send("next_#{precision}")
140
143
  end
@@ -151,6 +154,8 @@ class Date
151
154
  # def ===(other)
152
155
  # end
153
156
 
157
+ # Returns an array of the current year, month, and day values filtered by
158
+ # the Date/Time's precision.
154
159
  def values
155
160
  precision_filter.map { |p| send(p) }
156
161
  end
@@ -2,9 +2,7 @@
2
2
 
3
3
  class EDTF::Parser
4
4
 
5
- token T Z E X PLUS MINUS COLON SLASH D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 LP RP
6
- UNCERTAIN APPROXIMATE UNSPECIFIED UNKNOWN OPEN LONGYEAR CARET UNMATCHED
7
- DOTS COMMA LBRACE RBRACE LSQUARE RSQUARE
5
+ token T Z E X U UNKNOWN OPEN LONGYEAR UNMATCHED DOTS IUA
8
6
 
9
7
  expect 0
10
8
 
@@ -13,253 +11,421 @@ rule
13
11
  edtf : level_0_expression
14
12
  | level_1_expression
15
13
  | level_2_expression
16
- # | { result = Date.today }
17
-
14
+ ;
18
15
 
19
16
  # ---- Level 0 / ISO 8601 Rules ----
20
-
17
+
18
+ # NB: level 0 intervals are covered by the level 1 interval rules
21
19
  level_0_expression : date
22
20
  | date_time
23
- # | level_0_interval # --> level_1_interval
21
+ ;
24
22
 
25
23
  date : positive_date
26
24
  | negative_date
25
+ ;
27
26
 
28
- positive_date : year { result = Date.new(val[0]); result.precision = :year }
29
- | year_month { result = Date.new(*val.flatten); result.precision = :month }
30
- | year_month_day { result = Date.new(*val.flatten); result.precision = :day }
31
-
32
- negative_date : MINUS positive_date { result = -val[1] }
27
+ positive_date :
28
+ year { result = Date.new(val[0]); result.precision = :year }
29
+ | year_month { result = Date.new(*val.flatten); result.precision = :month }
30
+ | year_month_day { result = Date.new(*val.flatten); result.precision = :day }
31
+ ;
33
32
 
33
+ negative_date : '-' positive_date { result = -val[1] }
34
34
 
35
- date_time : date T time { result = DateTime.new(val[0].year, val[0].month, val[0].day, *val[2]) }
35
+
36
+ date_time : date T time {
37
+ result = DateTime.new(val[0].year, val[0].month, val[0].day, *val[2])
38
+ }
36
39
 
37
40
  time : base_time
38
41
  | base_time zone_offset { result = val.flatten }
39
-
40
- base_time : hour COLON minute COLON second { result = [val[0], val[2], val[4]] }
42
+
43
+ base_time : hour ':' minute ':' second { result = val.values_at(0, 2, 4) }
41
44
  | midnight
42
-
43
- midnight : D2 D4 COLON D0 D0 COLON D0 D0 { result = [24, 0, 0] }
44
-
45
- zone_offset : Z { result = 0 }
46
- | MINUS zone_offset_hour { result = -1 * val[1] }
47
- | PLUS positive_zone_offset { result = val[1] }
45
+
46
+ midnight : '2' '4' ':' '0' '0' ':' '0' '0' { result = [24, 0, 0] }
47
+
48
+ zone_offset : Z { result = 0 }
49
+ | '-' zone_offset_hour { result = -1 * val[1] }
50
+ | '+' positive_zone_offset { result = val[1] }
51
+ ;
48
52
 
49
53
  positive_zone_offset : zone_offset_hour
50
- | D0 D0 COLON D0 D0 { result = 0 }
51
-
52
-
53
- zone_offset_hour : d01_13 COLON minute { result = Rational(val[0] * 60 + val[2], 1440) }
54
- | D1 D4 COLON D0 D0 { result = Rational(840, 1440) }
55
- | D0 D0 COLON d01_59 { result = Rational(val[3], 1440) }
56
-
57
- year : digit digit digit digit { result = val.zip([1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b } }
58
-
54
+ | '0' '0' ':' '0' '0' { result = 0 }
55
+ ;
56
+
57
+
58
+ zone_offset_hour : d01_13 ':' minute { result = Rational(val[0] * 60 + val[2], 1440) }
59
+ | '1' '4' ':' '0' '0' { result = Rational(840, 1440) }
60
+ | '0' '0' ':' d01_59 { result = Rational(val[3], 1440) }
61
+ ;
62
+
63
+ year : digit digit digit digit {
64
+ result = val.zip([1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b }
65
+ }
66
+
59
67
  month : d01_12
60
-
61
- year_month : year MINUS month { result = [val[0], val[2]] }
62
-
68
+
69
+ year_month : year '-' month { result = [val[0], val[2]] }
70
+
63
71
  # We raise an exception if there are two many days for the month, but
64
72
  # do not consider leap years, as the EDTF BNF did not either.
65
73
  # NB: an exception will be raised regardless, because the Ruby Date
66
74
  # implementation calculates leap years.
67
- year_month_day : year_month MINUS d01_31 { result = val[0] << val[2]; raise ArgumentError, "invalid date (invalid days #{result[2]} for month #{result[1]})" if result[2] > 31 || (result[2] > 30 && [2,4,6,9,11].include?(result[1])) || (result[2] > 29 && result[1] == 2) }
75
+ year_month_day : year_month '-' d01_31 {
76
+ result = val[0] << val[2]
77
+ if result[2] > 31 || (result[2] > 30 && [2,4,6,9,11].include?(result[1])) || (result[2] > 29 && result[1] == 2)
78
+ raise ArgumentError, "invalid date (invalid days #{result[2]} for month #{result[1]})"
79
+ end
80
+ }
68
81
 
69
- hour : d00_23
70
-
82
+ hour : d00_23
71
83
  minute : d00_59
72
-
73
84
  second : d00_59
74
-
75
- # covered by level_1_interval
76
- # level_0_interval : date SLASH date { result = Interval.new(val[0], val[1]) }
85
+
86
+ # Completely covered by level_1_interval
87
+ # level_0_interval : date '/' date { result = Interval.new(val[0], val[1]) }
88
+
77
89
 
78
90
  # ---- Level 1 Extension Rules ----
79
-
80
- level_1_expression : uncertain_or_approximate_date
81
- | unspecified
82
- | level_1_interval
83
- | long_year_simple
84
- | season
85
-
86
91
 
87
- uncertain_or_approximate_date : date uncertain_or_approximate { result = val[0]; val[1].each { |m| result.send(m) } }
88
-
89
- uncertain_or_approximate : UNCERTAIN { result = [:uncertain!] }
90
- | APPROXIMATE { result = [:approximate!] }
91
- | UNCERTAIN APPROXIMATE { result = [:uncertain!, :approximate!] }
92
-
92
+ # NB: Uncertain/approximate Dates are covered by the Level 2 rules
93
+ level_1_expression : unspecified | level_1_interval | long_year_simple | season
94
+
95
+ # uncertain_or_approximate_date : date ua { result = uoa(val[0], val[1]) }
93
96
 
94
- unspecified : unspecified_year { result = Date.new(val[0][0]); result.unspecified.year[2,2] = val[0][1]; result.precision = :year }
97
+ unspecified : unspecified_year
98
+ {
99
+ result = Date.new(val[0][0])
100
+ result.unspecified.year[2,2] = val[0][1]
101
+ result.precision = :year
102
+ }
95
103
  | unspecified_month
96
104
  | unspecified_day
97
105
  | unspecified_day_and_month
98
-
99
- unspecified_year : digit digit digit UNSPECIFIED { result = [val[0,3].zip([1000,100,10]).reduce(0) { |s,(a,b)| s += a * b }, [false,true]] }
100
- | digit digit UNSPECIFIED UNSPECIFIED { result = [val[0,2].zip([1000,100]).reduce(0) { |s,(a,b)| s += a * b }, [true, true]] }
101
-
102
- unspecified_month : year MINUS UNSPECIFIED UNSPECIFIED { result = Date.new(val[0]).unspecified!(:month); result.precision = :month }
103
-
104
- unspecified_day : year_month MINUS UNSPECIFIED UNSPECIFIED { result = Date.new(*val[0]).unspecified!(:day) }
105
-
106
- unspecified_day_and_month : year MINUS UNSPECIFIED UNSPECIFIED MINUS UNSPECIFIED UNSPECIFIED { result = Date.new(val[0]).unspecified!([:day,:month]) }
106
+ ;
107
+
108
+ unspecified_year :
109
+ digit digit digit U
110
+ {
111
+ result = [val[0,3].zip([1000,100,10]).reduce(0) { |s,(a,b)| s += a * b }, [false,true]]
112
+ }
113
+ | digit digit U U
114
+ {
115
+ result = [val[0,2].zip([1000,100]).reduce(0) { |s,(a,b)| s += a * b }, [true, true]]
116
+ }
117
+
118
+ unspecified_month : year '-' U U {
119
+ result = Date.new(val[0]).unspecified!(:month)
120
+ result.precision = :month
121
+ }
107
122
 
123
+ unspecified_day : year_month '-' U U {
124
+ result = Date.new(*val[0]).unspecified!(:day)
125
+ }
108
126
 
109
- level_1_interval : level_1_start SLASH level_1_end { result = Interval.new(val[0], val[2]) }
127
+ unspecified_day_and_month : year '-' U U '-' U U {
128
+ result = Date.new(val[0]).unspecified!([:day,:month])
129
+ }
110
130
 
111
- level_1_start : date
112
- | uncertain_or_approximate_date
113
- | UNKNOWN { result = :unknown }
114
-
115
- level_1_end : level_1_start
116
- | OPEN { result = :open }
117
131
 
132
+ level_1_interval : level_1_start '/' level_1_end {
133
+ result = Interval.new(val[0], val[2])
134
+ }
118
135
 
119
- long_year_simple : LONGYEAR long_year { result = Date.new(val[1]); result.precision = :year }
120
- | LONGYEAR MINUS long_year { result = Date.new(-1 * val[2]); result.precision = :year }
121
-
122
- long_year : positive_digit digit digit digit digit { result = val.zip([10000,1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b } }
123
- | long_year digit { result = 10 * val[0] + val[1] }
136
+ level_1_start : date | internal_uncertain_or_approximate_date | UNKNOWN
137
+
138
+ level_1_end : level_1_start | OPEN
139
+
140
+
141
+ long_year_simple :
142
+ LONGYEAR long_year
143
+ {
144
+ result = Date.new(val[1])
145
+ result.precision = :year
146
+ }
147
+ | LONGYEAR '-' long_year
148
+ {
149
+ result = Date.new(-1 * val[2])
150
+ result.precision = :year
151
+ }
152
+ ;
153
+
154
+ long_year :
155
+ positive_digit digit digit digit digit {
156
+ result = val.zip([10000,1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b }
157
+ }
158
+ | long_year digit { result = 10 * val[0] + val[1] }
159
+ ;
124
160
 
125
161
 
126
- season : year MINUS season_number { result = Season.new(val[0], val[2]) }
162
+ season : year '-' season_number { result = Season.new(val[0], val[2]) }
127
163
 
128
- season_number : D2 D1 { result = 21 }
129
- | D2 D2 { result = 22 }
130
- | D2 D3 { result = 23 }
131
- | D2 D4 { result = 24 }
164
+ season_number : '2' '1' { result = 21 }
165
+ | '2' '2' { result = 22 }
166
+ | '2' '3' { result = 23 }
167
+ | '2' '4' { result = 24 }
168
+ ;
132
169
 
133
170
 
134
171
  # ---- Level 2 Extension Rules ----
135
-
172
+
173
+ # NB: Level 2 Intervals are covered by the Level 1 Interval rules.
136
174
  level_2_expression : season_qualified
137
- # | internal_uncertain_or_approximate
175
+ | internal_uncertain_or_approximate_date
138
176
  | internal_unspecified
139
177
  | choice_list
140
178
  | inclusive_list
141
179
  | masked_precision
142
- # | level_2_interval
143
180
  | date_and_calendar
144
181
  | long_year_scientific
145
-
182
+ ;
146
183
 
147
- season_qualified : season CARET { result = val[0]; result.qualifier = val[1] }
148
184
 
185
+ season_qualified : season '^' { result = val[0]; result.qualifier = val[1] }
149
186
 
150
- long_year_scientific : long_year_simple E integer { result = Date.new(val[0].year * 10 ** val[2]); result.precision = :year }
151
- | LONGYEAR int1_4 E integer { result = Date.new(val[1] * 10 ** val[3]); result.precision = :year }
152
- | LONGYEAR MINUS int1_4 E integer { result = Date.new(-1 * val[2] * 10 ** val[4]); result.precision = :year }
153
-
154
187
 
155
- date_and_calendar : date CARET { result = val[0]; result.calendar = val[1] }
156
-
188
+ long_year_scientific :
189
+ long_year_simple E integer
190
+ {
191
+ result = Date.new(val[0].year * 10 ** val[2])
192
+ result.precision = :year
193
+ }
194
+ | LONGYEAR int1_4 E integer
195
+ {
196
+ result = Date.new(val[1] * 10 ** val[3])
197
+ result.precision = :year
198
+ }
199
+ | LONGYEAR '-' int1_4 E integer
200
+ {
201
+ result = Date.new(-1 * val[2] * 10 ** val[4])
202
+ result.precision = :year
203
+ }
204
+ ;
157
205
 
158
- masked_precision : digit digit digit X { d = val[0,3].zip([1000,100,10]).reduce(0) { |s,(a,b)| s += a * b }; result = Date.new(d) ... Date.new(d+10) }
159
- | digit digit X X { d = val[0,2].zip([1000,100]).reduce(0) { |s,(a,b)| s += a * b }; result = Date.new(d) ... Date.new(d+100) }
160
206
 
161
-
162
- choice_list : LSQUARE list RSQUARE { result = val[1] }
163
-
164
- inclusive_list : LBRACE list RBRACE { result = val[1] }
165
-
166
- list : earlier { result = [val[0]] }
167
- | earlier COMMA list_elements COMMA later { result = [val[0]] + val[2] + [val[4]] }
168
- | earlier COMMA list_elements { result = [val[0]] + val[2] }
169
- | earlier COMMA later { result = [val[0]] + [val[2]] }
170
- | list_elements COMMA later { result = val[0] + [val[2]] }
207
+ date_and_calendar : date '^' { result = val[0]; result.calendar = val[1] }
208
+
209
+
210
+ masked_precision :
211
+ digit digit digit X
212
+ {
213
+ d = val[0,3].zip([1000,100,10]).reduce(0) { |s,(a,b)| s += a * b }
214
+ result = Date.new(d) ... Date.new(d+10)
215
+ }
216
+ | digit digit X X
217
+ {
218
+ d = val[0,2].zip([1000,100]).reduce(0) { |s,(a,b)| s += a * b }
219
+ result = Date.new(d) ... Date.new(d+100)
220
+ }
221
+ ;
222
+
223
+
224
+ choice_list : '[' list ']' { result = val[1] }
225
+
226
+ inclusive_list : '{' list '}' { result = val[1] }
227
+
228
+ list : earlier { result = val }
229
+ | earlier ',' list_elements ',' later { result = [val[0]] + val[2] + [val[4]] }
230
+ | earlier ',' list_elements { result = [val[0]] + val[2] }
231
+ | earlier ',' later { result = [val[0]] + [val[2]] }
232
+ | list_elements ',' later { result = val[0] + [val[2]] }
171
233
  | list_elements
172
- | later { result = [val[0]] }
173
-
174
- list_elements : list_element { result = [val[0]].flatten }
175
- | list_elements COMMA list_element { result = val[0] + [val[2]].flatten }
176
-
234
+ | later { result = val }
235
+ ;
236
+
237
+ list_elements : list_element { result = [val[0]].flatten }
238
+ | list_elements ',' list_element { result = val[0] + [val[2]].flatten }
239
+ ;
240
+
177
241
  list_element : date
178
- # | date_with_internal_uncertainty
179
- | uncertain_or_approximate_date
242
+ | internal_uncertain_or_approximate_date
180
243
  | unspecified
181
- | consecutives { result = val[0].map { |d| Date.new(*d) } }
182
-
183
- earlier : DOTS date { result = val[1] }
184
-
185
- later : year_month_day DOTS { result = Date.new(*val[0]); result.precision = :day }
186
- | year_month DOTS { result = Date.new(*val[0]); result.precision = :month }
187
- | year DOTS { result = Date.new(val[0]); result.precision = :year }
188
-
244
+ | consecutives { result = val[0].map { |d| Date.new(*d) } }
245
+ ;
246
+
247
+ earlier : DOTS date { result = val[1] }
248
+
249
+ later : year_month_day DOTS { result = Date.new(*val[0]); result.precision = :day }
250
+ | year_month DOTS { result = Date.new(*val[0]); result.precision = :month }
251
+ | year DOTS { result = Date.new(val[0]); result.precision = :year }
252
+ ;
253
+
189
254
  consecutives : year_month_day DOTS year_month_day
190
255
  | year_month DOTS year_month
191
- | year DOTS year { result = (val[0]..val[2]).to_a.map }
192
-
193
-
194
- internal_unspecified : unspecified_year MINUS month MINUS d01_31 { result = Date.new(val[0][0], val[2], val[4]); result.unspecified.year[2,2] = val[0][1] }
195
- | unspecified_year MINUS UNSPECIFIED UNSPECIFIED MINUS d01_31 { result = Date.new(val[0][0], 1, val[5]); result.unspecified.year[2,2] = val[0][1]; result.unspecified!(:month) }
196
- | unspecified_year MINUS UNSPECIFIED UNSPECIFIED MINUS UNSPECIFIED UNSPECIFIED { result = Date.new(val[0][0], 1, 1); result.unspecified.year[2,2] = val[0][1]; result.unspecified!([:month, :day]) }
197
- | unspecified_year MINUS month MINUS UNSPECIFIED UNSPECIFIED { result = Date.new(val[0][0], val[2], 1); result.unspecified.year[2,2] = val[0][1]; result.unspecified!(:day) }
198
- | year MINUS UNSPECIFIED UNSPECIFIED MINUS d01_31 { result = Date.new(val[0], 1, val[5]); result.unspecified!(:month) }
199
-
256
+ | year DOTS year { result = (val[0]..val[2]).to_a.map }
257
+ ;
258
+
259
+ internal_unspecified :
260
+ unspecified_year '-' month '-' d01_31
261
+ {
262
+ result = Date.new(val[0][0], val[2], val[4])
263
+ result.unspecified.year[2,2] = val[0][1]
264
+ }
265
+ | unspecified_year '-' U U '-' d01_31
266
+ {
267
+ result = Date.new(val[0][0], 1, val[5])
268
+ result.unspecified.year[2,2] = val[0][1]
269
+ result.unspecified!(:month)
270
+ }
271
+ | unspecified_year '-' U U '-' U U
272
+ {
273
+ result = Date.new(val[0][0], 1, 1)
274
+ result.unspecified.year[2,2] = val[0][1]
275
+ result.unspecified!([:month, :day])
276
+ }
277
+ | unspecified_year '-' month '-' U U
278
+ {
279
+ result = Date.new(val[0][0], val[2], 1)
280
+ result.unspecified.year[2,2] = val[0][1]
281
+ result.unspecified!(:day)
282
+ }
283
+ | year '-' U U '-' d01_31
284
+ {
285
+ result = Date.new(val[0], 1, val[5])
286
+ result.unspecified!(:month)
287
+ }
288
+ ;
289
+
290
+ internal_uncertain_or_approximate_date : internal_uncertain_or_approximate
291
+ | '(' internal_uncertain_or_approximate ')' ua { result = uoa(val[1], val[3]) }
292
+
293
+ internal_uncertain_or_approximate :
294
+ iua_year { result = val[0]; result.precision = :year }
295
+ | iua_year_month { result = val[0]; result.precision = :month }
296
+ | iua_year_month_day
297
+
298
+ iua_year : year ua { result = uoa(Date.new(val[0]), val[1], :year) }
299
+
300
+ iua_year_month :
301
+ iua_year '-' month opt_ua
302
+ {
303
+ result = uoa(val[0].change(:month => val[2]), val[3], [:month, :year])
304
+ }
305
+ | '(' iua_year IUA month opt_ua
306
+ {
307
+ result = uoa(uoa(val[1], val[2], :year).change(:month => val[3]), val[4], :month)
308
+ }
309
+ | year '-' month ua
310
+ {
311
+ result = uoa(Date.new(val[0], val[2]), val[3], [:year, :month])
312
+ }
313
+ | year '-' '(' month ')' ua
314
+ {
315
+ result = uoa(Date.new(val[0], val[3]), val[5], [:month])
316
+ }
317
+ ;
318
+
319
+ iua_year_month_day :
320
+ iua_year_month '-' d01_31 opt_ua
321
+ {
322
+ result = uoa(val[0].change(:day => val[2]), val[3])
323
+ }
324
+ | iua_year_month '-' '(' d01_31 ')' ua
325
+ {
326
+ result = uoa(val[0].change(:day => val[3]), val[5], [:day])
327
+ }
328
+ | '(' iua_year_month IUA d01_31 opt_ua
329
+ {
330
+ result = uoa(uoa(val[1], val[2], [:year, :month]).change(:day => val[3]), val[4], :day)
331
+ }
332
+ | year '-' '(' month IUA d01_31 opt_ua
333
+ {
334
+ result = uoa(uoa(Date.new(val[0], val[3], val[5]), val[4], :month), val[6], :day)
335
+ }
336
+ | year_month '-' d01_31 ua
337
+ {
338
+ result = uoa(Date.new(val[0][0], val[0][1], val[2]), val[3])
339
+ }
340
+ | year_month '-' '(' d01_31 ')' ua
341
+ {
342
+ result = uoa(Date.new(val[0][0], val[0][1], val[3]), val[5], [:day])
343
+ }
344
+ | year '-' '(' month '-' d01_31 ')' ua
345
+ {
346
+ result = uoa(Date.new(val[0], val[3], val[5]), val[7], [:month, :day])
347
+ }
348
+ ;
349
+
350
+ opt_ua : { result = [] } | ua
351
+
352
+ ua : '?'
353
+ | '~'
354
+ | '?' '~' { result = val.flatten }
355
+ ;
200
356
 
201
357
  # ---- Auxiliary Rules ----
202
358
 
203
- digit : D0 { result = 0 }
359
+ digit : '0' { result = 0 }
204
360
  | positive_digit
205
-
206
- positive_digit : D1 { result = 1 }
207
- | D2 { result = 2 }
208
- | D3 { result = 3 }
209
- | D4 { result = 4 }
210
- | D5 { result = 5 }
211
- | D6 { result = 6 }
212
- | D7 { result = 7 }
213
- | D8 { result = 8 }
214
- | D9 { result = 9 }
215
-
216
- d01_12 : D0 positive_digit { result = val[1] }
217
- | D1 D0 { result = 10 }
218
- | D1 D1 { result = 11 }
219
- | D1 D2 { result = 12 }
361
+ ;
362
+
363
+ positive_digit : '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
364
+
365
+ d01_12 : '0' positive_digit { result = val[1] }
366
+ | '1' '0' { result = 10 }
367
+ | '1' '1' { result = 11 }
368
+ | '1' '2' { result = 12 }
369
+ ;
220
370
 
221
371
  d01_13 : d01_12
222
- | D1 D3 { result = 13 }
223
-
224
- d01_23 : D0 positive_digit { result = val[1] }
225
- | D1 digit { result = 10 + val[1] }
226
- | D2 D0 { result = 20 }
227
- | D2 D1 { result = 21 }
228
- | D2 D2 { result = 22 }
229
- | D2 D3 { result = 23 }
230
-
231
- d00_23 : D0 D0 { result = 0 }
372
+ | '1' '3' { result = 13 }
373
+ ;
374
+
375
+ d01_23 : '0' positive_digit { result = val[1] }
376
+ | '1' digit { result = 10 + val[1] }
377
+ | '2' '0' { result = 20 }
378
+ | '2' '1' { result = 21 }
379
+ | '2' '2' { result = 22 }
380
+ | '2' '3' { result = 23 }
381
+ ;
382
+
383
+ d00_23 : '0' '0'
232
384
  | d01_23
385
+ ;
233
386
 
234
387
  d01_29 : d01_23
235
- | D2 D4 { result = 24 }
236
- | D2 D5 { result = 25 }
237
- | D2 D6 { result = 26 }
238
- | D2 D7 { result = 27 }
239
- | D2 D8 { result = 28 }
240
- | D2 D9 { result = 29 }
388
+ | '2' '4' { result = 24 }
389
+ | '2' '5' { result = 25 }
390
+ | '2' '6' { result = 26 }
391
+ | '2' '7' { result = 27 }
392
+ | '2' '8' { result = 28 }
393
+ | '2' '9' { result = 29 }
394
+ ;
241
395
 
242
396
  d01_30 : d01_29
243
- | D3 D0 { result = 30 }
397
+ | '3' '0' { result = 30 }
398
+ ;
244
399
 
245
400
  d01_31 : d01_30
246
- | D3 D1 { result = 31 }
247
-
401
+ | '3' '1' { result = 31 }
402
+ ;
403
+
248
404
  d01_59 : d01_29
249
- | D3 digit { result = 30 + val[1] }
250
- | D4 digit { result = 40 + val[1] }
251
- | D5 digit { result = 50 + val[1] }
252
-
253
- d00_59 : D0 D0 { result = 0 }
405
+ | '3' digit { result = 30 + val[1] }
406
+ | '4' digit { result = 40 + val[1] }
407
+ | '5' digit { result = 50 + val[1] }
408
+ ;
409
+
410
+ d00_59 : '0' '0'
254
411
  | d01_59
255
-
256
- int1_4 : positive_digit { result = val[0] }
257
- | positive_digit digit { result = 10 * val[0] + val[1] }
258
- | positive_digit digit digit { result = val.zip([100,10,1]).reduce(0) { |s,(a,b)| s += a * b } }
259
- | positive_digit digit digit digit { result = val.zip([1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b } }
412
+ ;
413
+
414
+ int1_4 : positive_digit { result = val[0] }
415
+ | positive_digit digit { result = 10 * val[0] + val[1] }
416
+ | positive_digit digit digit
417
+ {
418
+ result = val.zip([100,10,1]).reduce(0) { |s,(a,b)| s += a * b }
419
+ }
420
+ | positive_digit digit digit digit
421
+ {
422
+ result = val.zip([1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b }
423
+ }
424
+ ;
260
425
 
261
426
  integer : positive_digit { result = val[0] }
262
- | integer digit { result = 10 * val[0] + val[1] }
427
+ | integer digit { result = 10 * val[0] + val[1] }
428
+ ;
263
429
 
264
430
 
265
431
 
@@ -291,63 +457,78 @@ require 'strscan'
291
457
  warn "failed to parse extended date time %s (%s) %s" % [val.inspect, token_to_str(tid) || '?', vstack.inspect]
292
458
  end
293
459
 
460
+ def apply_uncertainty(date, uncertainty, scope = nil)
461
+ uncertainty.each do |u|
462
+ scope.nil? ? date.send(u) : date.send(u, scope)
463
+ end
464
+ date
465
+ end
466
+
467
+ alias uoa apply_uncertainty
468
+
294
469
  def next_token
295
- case
296
- when @src.eos?
297
- nil
298
- # when @src.scan(/\s+/)
299
- # ignore whitespace
300
- when @src.scan(/\(/)
301
- [:LP, @src.matched]
302
- when @src.scan(/\)/)
303
- [:RP, @src.matched]
304
- when @src.scan(/\[/)
305
- [:LSQUARE, @src.matched]
306
- when @src.scan(/\]/)
307
- [:RSQUARE, @src.matched]
308
- when @src.scan(/\{/)
309
- [:LBRACE, @src.matched]
310
- when @src.scan(/\}/)
311
- [:RBRACE, @src.matched]
312
- when @src.scan(/T/)
313
- [:T, @src.matched]
314
- when @src.scan(/Z/)
315
- [:Z, @src.matched]
316
- when @src.scan(/\?/)
317
- [:UNCERTAIN, @src.matched]
318
- when @src.scan(/~/)
319
- [:APPROXIMATE, @src.matched]
320
- when @src.scan(/open/i)
321
- [:OPEN, @src.matched]
322
- when @src.scan(/unkn?own/i) # matches 'unkown' typo too
323
- [:UNKNOWN, @src.matched]
324
- when @src.scan(/u/)
325
- [:UNSPECIFIED, @src.matched]
326
- when @src.scan(/x/i)
327
- [:X, @src.matched]
328
- when @src.scan(/y/)
329
- [:LONGYEAR, @src.matched]
330
- when @src.scan(/e/)
331
- [:E, @src.matched]
332
- when @src.scan(/\+/)
333
- [:PLUS, @src.matched]
334
- when @src.scan(/-/)
335
- [:MINUS, @src.matched]
336
- when @src.scan(/:/)
337
- [:COLON, @src.matched]
338
- when @src.scan(/\//)
339
- [:SLASH, @src.matched]
340
- when @src.scan(/\s*\.\.\s*/)
341
- [:DOTS, '..']
342
- when @src.scan(/\s*,\s*/)
343
- [:COMMA, ',']
344
- when @src.scan(/\^\w+/)
345
- [:CARET, @src.matched[1..-1]]
346
- when @src.scan(/\d/)
347
- [['D', @src.matched].join.intern, @src.matched]
348
- else @src.scan(/./)
349
- [:UNMATCHED, @src.rest]
350
- end
470
+ case
471
+ when @src.eos?
472
+ nil
473
+ # when @src.scan(/\s+/)
474
+ # ignore whitespace
475
+ when @src.scan(/\(/)
476
+ ['(', @src.matched]
477
+ when @src.scan(/\)\?~-/)
478
+ [:IUA, [:uncertain!, :approximate!]]
479
+ when @src.scan(/\)\?-/)
480
+ [:IUA, [:uncertain!]]
481
+ when @src.scan(/\)~-/)
482
+ [:IUA, [:approximate!]]
483
+ when @src.scan(/\)/)
484
+ [')', @src.matched]
485
+ when @src.scan(/\[/)
486
+ ['[', @src.matched]
487
+ when @src.scan(/\]/)
488
+ [']', @src.matched]
489
+ when @src.scan(/\{/)
490
+ ['{', @src.matched]
491
+ when @src.scan(/\}/)
492
+ ['}', @src.matched]
493
+ when @src.scan(/T/)
494
+ [:T, @src.matched]
495
+ when @src.scan(/Z/)
496
+ [:Z, @src.matched]
497
+ when @src.scan(/\?/)
498
+ ['?', [:uncertain!]]
499
+ when @src.scan(/~/)
500
+ ['~', [:approximate!]]
501
+ when @src.scan(/open/i)
502
+ [:OPEN, :open]
503
+ when @src.scan(/unkn?own/i) # matches 'unkown' typo too
504
+ [:UNKNOWN, :unknown]
505
+ when @src.scan(/u/)
506
+ [:U, @src.matched]
507
+ when @src.scan(/x/i)
508
+ [:X, @src.matched]
509
+ when @src.scan(/y/)
510
+ [:LONGYEAR, @src.matched]
511
+ when @src.scan(/e/)
512
+ [:E, @src.matched]
513
+ when @src.scan(/\+/)
514
+ ['+', @src.matched]
515
+ when @src.scan(/-/)
516
+ ['-', @src.matched]
517
+ when @src.scan(/:/)
518
+ [':', @src.matched]
519
+ when @src.scan(/\//)
520
+ ['/', @src.matched]
521
+ when @src.scan(/\s*\.\.\s*/)
522
+ [:DOTS, '..']
523
+ when @src.scan(/\s*,\s*/)
524
+ [',', ',']
525
+ when @src.scan(/\^\w+/)
526
+ ['^', @src.matched[1..-1]]
527
+ when @src.scan(/\d/)
528
+ [@src.matched, @src.matched.to_i]
529
+ else @src.scan(/./)
530
+ [:UNMATCHED, @src.rest]
531
+ end
351
532
  end
352
533
 
353
534