Module: RemLint::DateLiteral
- Defined in:
- lib/remlint/date_literal.rb
Overview
Reads a date out of a run of tokens, the way GetFullDate does.
The classification is FindNumericToken's (src/token.c): a number from
1990 to 5990 is a year, a number from 1 to 31 is a day, and YYYY-MM-DD
is a date all by itself. There is no overlap to resolve -- years start
above the largest day -- so the components can arrive in any order.
Returns nil unless all three components turn up, because a partial date is a different question from a wrong one, and the rules that care about partial dates want to ask it separately.
Constant Summary collapse
- EPOCH_YEAR =
1990- LAST_YEAR =
BASE+YR_RANGEfrom src/custom.h.in. EPOCH_YEAR + 4000
- MAX_DAY =
31- SEPARATORS =
%w[- /].freeze
- MONTHS =
%w[ JANUARY FEBRUARY MARCH APRIL MAY JUNE JULY AUGUST SEPTEMBER OCTOBER NOVEMBER DECEMBER ].freeze
Class Method Summary collapse
- .absorb(tokens, cursor, state) ⇒ Object
-
.at(tokens, index) ⇒ Object
The date at
index, or nil. - .classify(token) ⇒ Object
- .complete(state) ⇒ Object
- .component(token, state) ⇒ Object
-
.iso_date(tokens, cursor) ⇒ Object
2027-01-01reaches the lexer as five tokens, because a hyphen is an operator everywhere else. - .iso_shape?(window) ⇒ Boolean
- .merge(state, components) ⇒ Object
-
.month(word) ⇒ Object
Month names abbreviate like every other keyword:
JanisJANUARYbecause its minimum length is 3. - .number(text) ⇒ Object
-
.scan(tokens, index) ⇒ Object
Scans forward from
indexand returns [date, tokens consumed].
Class Method Details
.absorb(tokens, cursor, state) ⇒ Object
81 82 83 84 85 86 87 88 89 90 |
# File 'lib/remlint/date_literal.rb', line 81 def absorb(tokens, cursor, state) iso = iso_date(tokens, cursor) if iso merge(state, iso) 5 else component(tokens[cursor], state) end end |
.at(tokens, index) ⇒ Object
The date at index, or nil.
77 78 79 |
# File 'lib/remlint/date_literal.rb', line 77 def at(tokens, index) scan(tokens, index).first end |
.classify(token) ⇒ Object
119 120 121 122 123 124 |
# File 'lib/remlint/date_literal.rb', line 119 def classify(token) case token.type when :number then number(token.value) when :name then month(token.value) end end |
.complete(state) ⇒ Object
159 160 161 162 163 |
# File 'lib/remlint/date_literal.rb', line 159 def complete(state) if state.values.none?(&:nil?) CalendarDate.new(state[:year], state[:month], state[:day]) end end |
.component(token, state) ⇒ Object
108 109 110 111 112 113 114 115 116 117 |
# File 'lib/remlint/date_literal.rb', line 108 def component(token, state) value = classify(token) if value.nil? || !state[value.first].nil? 0 else state[value.first] = value.last 1 end end |
.iso_date(tokens, cursor) ⇒ Object
2027-01-01 reaches the lexer as five tokens, because a hyphen is an
operator everywhere else.
94 95 96 97 98 99 100 |
# File 'lib/remlint/date_literal.rb', line 94 def iso_date(tokens, cursor) window = tokens[cursor, 5] if window&.length == 5 && iso_shape?(window) { year: window[0].value.to_i, month: window[2].value.to_i, day: window[4].value.to_i } end end |
.iso_shape?(window) ⇒ Boolean
102 103 104 105 106 |
# File 'lib/remlint/date_literal.rb', line 102 def iso_shape?(window) window[0].type == :number && window[2].type == :number && window[4].type == :number && SEPARATORS.include?(window[1].value) && window[1].value == window[3].value && window[0].value.length == 4 end |
.merge(state, components) ⇒ Object
153 154 155 156 157 |
# File 'lib/remlint/date_literal.rb', line 153 def merge(state, components) components.each do |key, value| state[key] ||= value end end |
.month(word) ⇒ Object
Month names abbreviate like every other keyword: Jan is JANUARY
because its minimum length is 3.
140 141 142 143 144 145 146 |
# File 'lib/remlint/date_literal.rb', line 140 def month(word) keyword = Vocabulary.keyword(word) if keyword&.type == "T_Month" [:month, MONTHS.index(keyword.name) + 1] end end |
.number(text) ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/remlint/date_literal.rb', line 126 def number(text) value = Integer(text, exception: false) if value.nil? nil elsif value >= EPOCH_YEAR && value <= LAST_YEAR [:year, value] elsif value >= 1 && value <= MAX_DAY [:day, value] end end |
.scan(tokens, index) ⇒ Object
Scans forward from index and returns [date, tokens consumed]. Consumed
is zero when nothing date-shaped was there at all.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/remlint/date_literal.rb', line 59 def scan(tokens, index) state = { year: nil, month: nil, day: nil } cursor = index while cursor < tokens.length taken = absorb(tokens, cursor, state) if taken.zero? break end cursor += taken end [complete(state), cursor - index] end |