tomlrb 1.2.7 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,10 +7,14 @@ module Tomlrb
7
7
  @current = @output
8
8
  @stack = []
9
9
  @array_names = []
10
+ @current_table = []
11
+ @keys = Keys.new
10
12
  @symbolize_keys = options[:symbolize_keys]
11
13
  end
12
14
 
13
15
  def set_context(identifiers, is_array_of_tables: false)
16
+ @current_table = identifiers.dup
17
+ @keys.add_table_key identifiers, is_array_of_tables
14
18
  @current = @output
15
19
 
16
20
  deal_with_array_of_tables(identifiers, is_array_of_tables) do |identifierz|
@@ -43,14 +47,19 @@ module Tomlrb
43
47
  if is_array_of_tables
44
48
  last_identifier = last_identifier.to_sym if @symbolize_keys
45
49
  @current[last_identifier] ||= []
50
+ raise ParseError, "Cannot use key #{last_identifier} for both table and array at once" unless @current[last_identifier].respond_to?(:<<)
46
51
  @current[last_identifier] << {}
47
52
  @current = @current[last_identifier].last
48
53
  end
49
54
  end
50
55
 
51
56
  def assign(k)
52
- k = k.to_sym if @symbolize_keys
53
- @current[k] = @stack.pop
57
+ @keys.add_pair_key k, @current_table
58
+ current = @current
59
+ while key = k.shift
60
+ key = key.to_sym if @symbolize_keys
61
+ current = assign_key_path(current, key, k.empty?)
62
+ end
54
63
  end
55
64
 
56
65
  def push(o)
@@ -64,10 +73,159 @@ module Tomlrb
64
73
  def end_(type)
65
74
  array = []
66
75
  while (value = @stack.pop) != [type]
67
- raise ParseError, 'Unclosed table' unless value
76
+ raise ParseError, 'Unclosed table' if value.nil?
68
77
  array.unshift(value)
69
78
  end
70
79
  array
71
80
  end
81
+
82
+ private
83
+
84
+ def assign_key_path(current, key, key_emptied)
85
+ if key_emptied
86
+ raise ParseError, "Cannot overwrite value with key #{key}" unless current.kind_of?(Hash)
87
+ current[key] = @stack.pop
88
+ return current
89
+ end
90
+ current[key] ||= {}
91
+ current = current[key]
92
+ current
93
+ end
94
+ end
95
+
96
+ class Keys
97
+ def initialize
98
+ @keys = {}
99
+ end
100
+
101
+ def add_table_key(keys, is_array_of_tables = false)
102
+ self << [keys, [], is_array_of_tables]
103
+ end
104
+
105
+ def add_pair_key(keys, context)
106
+ self << [context, keys, false]
107
+ end
108
+
109
+ def <<(keys)
110
+ table_keys, pair_keys, is_array_of_tables = keys
111
+ current = @keys
112
+ current = append_table_keys(current, table_keys, pair_keys.empty?, is_array_of_tables)
113
+ append_pair_keys(current, pair_keys, table_keys.empty?, is_array_of_tables)
114
+ end
115
+
116
+ private
117
+
118
+ def append_table_keys(current, table_keys, pair_keys_empty, is_array_of_tables)
119
+ table_keys.each_with_index do |key, index|
120
+ declared = (index == table_keys.length - 1) && pair_keys_empty
121
+ if index == 0
122
+ current = find_or_create_first_table_key(current, key, declared, is_array_of_tables)
123
+ else
124
+ current = current << [key, :table, declared, is_array_of_tables]
125
+ end
126
+ end
127
+
128
+ current.clear_children if is_array_of_tables
129
+ current
130
+ end
131
+
132
+ def find_or_create_first_table_key(current, key, declared, is_array_of_tables)
133
+ existed = current[key]
134
+ if existed && existed.type == :pair
135
+ raise Key::KeyConflict, "Key #{key} is already used as #{existed.type} key"
136
+ end
137
+ if existed && existed.declared? && declared && ! is_array_of_tables
138
+ raise Key::KeyConflict, "Key #{key} is already used"
139
+ end
140
+ k = existed || Key.new(key, :table, declared)
141
+ current[key] = k
142
+ k
143
+ end
144
+
145
+ def append_pair_keys(current, pair_keys, table_keys_empty, is_array_of_tables)
146
+ pair_keys.each_with_index do |key, index|
147
+ declared = index == pair_keys.length - 1
148
+ if index == 0 && table_keys_empty
149
+ current = find_or_create_first_pair_key(current, key, declared, table_keys_empty)
150
+ else
151
+ key = current << [key, :pair, declared, is_array_of_tables]
152
+ current = key
153
+ end
154
+ end
155
+ end
156
+
157
+ def find_or_create_first_pair_key(current, key, declared, table_keys_empty)
158
+ existed = current[key]
159
+ if existed && existed.declared? && (existed.type == :pair) && declared && table_keys_empty
160
+ raise Key::KeyConflict, "Key #{key} is already used"
161
+ end
162
+ k = Key.new(key, :pair, declared)
163
+ current[key] = k
164
+ k
165
+ end
166
+ end
167
+
168
+ class Key
169
+ class KeyConflict < ParseError; end
170
+
171
+ attr_reader :key, :type
172
+
173
+ def initialize(key, type, declared = false)
174
+ @key = key
175
+ @type = type
176
+ @declared = declared
177
+ @children = {}
178
+ end
179
+
180
+ def declared?
181
+ @declared
182
+ end
183
+
184
+ def <<(key_type_declared)
185
+ key, type, declared, is_array_of_tables = key_type_declared
186
+ existed = @children[key]
187
+ validate_already_declared_as_different_key(type, declared, existed)
188
+ validate_already_declared_as_non_array_table(type, is_array_of_tables, declared, existed)
189
+ validate_path_already_created_as_different_type(type, declared, existed)
190
+ validate_path_already_declared_as_different_type(type, declared, existed)
191
+ validate_already_declared_as_same_key(declared, existed)
192
+ @children[key] = existed || self.class.new(key, type, declared)
193
+ end
194
+
195
+ def clear_children
196
+ @children.clear
197
+ end
198
+
199
+ private
200
+
201
+ def validate_already_declared_as_different_key(type, declared, existed)
202
+ if declared && existed && existed.declared? && existed.type != type
203
+ raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
204
+ end
205
+ end
206
+
207
+ def validate_already_declared_as_non_array_table(type, is_array_of_tables, declared, existed)
208
+ if declared && type == :table && existed && existed.declared? && ! is_array_of_tables
209
+ raise KeyConflict, "Key #{existed.key} is already used"
210
+ end
211
+ end
212
+
213
+ def validate_path_already_created_as_different_type(type, declared, existed)
214
+ if declared && (type == :table) && existed && (existed.type == :pair) && (! existed.declared?)
215
+ raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
216
+ end
217
+ end
218
+
219
+ def validate_path_already_declared_as_different_type(type, declared, existed)
220
+ if ! declared && (type == :pair) && existed && (existed.type == :pair) && existed.declared?
221
+ raise KeyConflict, "Key #{key} is already used as #{type} key"
222
+ end
223
+ end
224
+
225
+ def validate_already_declared_as_same_key(declared, existed)
226
+ if existed && ! existed.declared? && declared
227
+ raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
228
+ end
229
+ end
72
230
  end
73
231
  end
@@ -0,0 +1,33 @@
1
+ require 'forwardable'
2
+
3
+ module Tomlrb
4
+ class LocalDate
5
+ extend Forwardable
6
+
7
+ def_delegators :@time, :year, :month, :day
8
+
9
+ def initialize(year, month, day)
10
+ @time = Time.new(year, month, day, 0, 0, 0, '-00:00')
11
+ end
12
+
13
+ # @param offset see {LocalDateTime#to_time}
14
+ # @return [Time] 00:00:00 of the date
15
+ def to_time(offset='-00:00')
16
+ return @time if offset == '-00:00'
17
+ Time.new(year, month, day, 0, 0, 0, offset)
18
+ end
19
+
20
+ def to_s
21
+ @time.strftime('%F')
22
+ end
23
+
24
+ def ==(other)
25
+ other.kind_of?(self.class) &&
26
+ to_time == other.to_time
27
+ end
28
+
29
+ def inspect
30
+ "#<#{self.class}: #{to_s}>"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ require 'forwardable'
2
+
3
+ module Tomlrb
4
+ class LocalDateTime
5
+ extend Forwardable
6
+
7
+ def_delegators :@time, :year, :month, :day, :hour, :min, :sec, :usec, :nsec
8
+
9
+ def initialize(year, month, day, hour, min, sec) # rubocop:disable Metrics/ParameterLists
10
+ @time = Time.new(year, month, day, hour, min, sec, '-00:00')
11
+ @sec = sec
12
+ end
13
+
14
+ # @param offset [String, Symbol, Numeric, nil] time zone offset.
15
+ # * when +String+, must be '+HH:MM' format, '-HH:MM' format, 'UTC', 'A'..'I' or 'K'..'Z'. Arguments excluding '+-HH:MM' are supporeted at Ruby >= 2.7.0
16
+ # * when +Symbol+, must be +:dst+(for summar time for local) or +:std+(for standard time).
17
+ # * when +Numeric+, it is time zone offset in second.
18
+ # * when +nil+, local time zone offset is used.
19
+ # @return [Time]
20
+ def to_time(offset='-00:00')
21
+ return @time if offset == '-00:00'
22
+ Time.new(year, month, day, hour, min, @sec, offset)
23
+ end
24
+
25
+ def to_s
26
+ frac = (@sec - sec)
27
+ frac_str = frac == 0 ? '' : "#{frac.to_s[1..-1]}"
28
+ @time.strftime("%FT%T") << frac_str
29
+ end
30
+
31
+ def ==(other)
32
+ other.kind_of?(self.class) &&
33
+ to_time == other.to_time
34
+ end
35
+
36
+ def inspect
37
+ "#<#{self.class}: #{to_s}>"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,38 @@
1
+ require 'forwardable'
2
+
3
+ module Tomlrb
4
+ class LocalTime
5
+ extend Forwardable
6
+
7
+ def_delegators :@time, :hour, :min, :sec, :usec, :nsec
8
+
9
+ def initialize(hour, min, sec)
10
+ @time = Time.new(0, 1, 1, hour, min, sec, '-00:00')
11
+ @sec = sec
12
+ end
13
+
14
+ # @param year [Integer]
15
+ # @param month [Integer]
16
+ # @param day [Integer]
17
+ # @param offset see {LocalDateTime#to_time}
18
+ # @return [Time] the time of the date specified by params
19
+ def to_time(year, month, day, offset='-00:00')
20
+ Time.new(year, month, day, hour, min, @sec, offset)
21
+ end
22
+
23
+ def to_s
24
+ frac = (@sec - sec)
25
+ frac_str = frac == 0 ? '' : "#{frac.to_s[1..-1]}"
26
+ @time.strftime("%T") << frac_str
27
+ end
28
+
29
+ def ==(other)
30
+ other.kind_of?(self.class) &&
31
+ @time == other.to_time(0, 1, 1)
32
+ end
33
+
34
+ def inspect
35
+ "#<#{self.class}: #{to_s}>"
36
+ end
37
+ end
38
+ end
@@ -4,7 +4,7 @@ class Tomlrb::Parser < Tomlrb::GeneratedParser
4
4
 
5
5
  def initialize(tokenizer, **options)
6
6
  @tokenizer = tokenizer
7
- @handler = Tomlrb::Handler.new(options)
7
+ @handler = Tomlrb::Handler.new(**options)
8
8
  super()
9
9
  end
10
10
 
@@ -1,16 +1,19 @@
1
1
  class Tomlrb::GeneratedParser
2
- token IDENTIFIER STRING_MULTI STRING_BASIC STRING_LITERAL_MULTI STRING_LITERAL DATETIME INTEGER FLOAT TRUE FALSE
2
+ token IDENTIFIER STRING_MULTI STRING_BASIC STRING_LITERAL_MULTI STRING_LITERAL DATETIME LOCAL_TIME INTEGER NON_DEC_INTEGER FLOAT FLOAT_KEYWORD BOOLEAN NEWLINE EOS
3
3
  rule
4
4
  expressions
5
5
  | expressions expression
6
+ | expressions EOS
6
7
  ;
7
8
  expression
8
9
  : table
9
10
  | assignment
10
11
  | inline_table
12
+ | NEWLINE
11
13
  ;
12
14
  table
13
- : table_start table_continued
15
+ : table_start table_continued NEWLINE
16
+ | table_start table_continued EOS
14
17
  ;
15
18
  table_start
16
19
  : '[' '[' { @handler.start_(:array_of_tables) }
@@ -27,44 +30,87 @@ rule
27
30
  | '.' table_continued
28
31
  ;
29
32
  table_identifier
30
- : IDENTIFIER { @handler.push(val[0]) }
31
- | STRING_BASIC { @handler.push(val[0]) }
32
- | STRING_LITERAL { @handler.push(val[0]) }
33
- | INTEGER { @handler.push(val[0]) }
34
- | TRUE { @handler.push(val[0]) }
35
- | FALSE { @handler.push(val[0]) }
33
+ : table_identifier '.' table_identifier_component { @handler.push(val[2]) }
34
+ | table_identifier '.' FLOAT { val[2].split('.').each { |k| @handler.push(k) } }
35
+ | FLOAT {
36
+ keys = val[0].split('.')
37
+ @handler.start_(:table)
38
+ keys.each { |key| @handler.push(key) }
39
+ }
40
+ | table_identifier_component { @handler.push(val[0]) }
41
+ ;
42
+ table_identifier_component
43
+ : IDENTIFIER
44
+ | STRING_BASIC
45
+ | STRING_LITERAL
46
+ | INTEGER
47
+ | NON_DEC_INTEGER
48
+ | FLOAT_KEYWORD
49
+ | BOOLEAN
36
50
  ;
37
51
  inline_table
38
- : inline_table_start inline_continued
52
+ : inline_table_start inline_table_end
53
+ | inline_table_start inline_continued inline_table_end
39
54
  ;
40
55
  inline_table_start
41
56
  : '{' { @handler.start_(:inline) }
42
57
  ;
43
- inline_continued
44
- : '}' { array = @handler.end_(:inline); @handler.push(Hash[*array]) }
45
- | inline_assignment_key inline_assignment_value inline_next
46
- ;
47
- inline_next
58
+ inline_table_end
48
59
  : '}' {
49
60
  array = @handler.end_(:inline)
50
61
  array.map!.with_index{ |n,i| i.even? ? n.to_sym : n } if @handler.symbolize_keys
51
62
  @handler.push(Hash[*array])
52
63
  }
53
- | ',' inline_continued
64
+ ;
65
+ inline_continued
66
+ : inline_assignment_key inline_assignment_value
67
+ | inline_assignment_key inline_assignment_value inline_next
68
+ ;
69
+ inline_next
70
+ : ',' inline_continued
54
71
  ;
55
72
  inline_assignment_key
56
- : IDENTIFIER { @handler.push(val[0]) }
73
+ : inline_assignment_key '.' IDENTIFIER {
74
+ array = @handler.end_(:inline)
75
+ array.each { |key| @handler.push(key) }
76
+ @handler.start_(:inline)
77
+ @handler.push(val[2])
78
+ }
79
+ | IDENTIFIER { @handler.push(val[0]) }
57
80
  ;
58
81
  inline_assignment_value
59
82
  : '=' value
60
83
  ;
61
84
  assignment
62
- : IDENTIFIER '=' value { @handler.assign(val[0]) }
63
- | STRING_BASIC '=' value { @handler.assign(val[0]) }
64
- | STRING_LITERAL '=' value { @handler.assign(val[0]) }
65
- | INTEGER '=' value { @handler.assign(val[0]) }
66
- | TRUE '=' value { @handler.assign(val[0]) }
67
- | FALSE '=' value { @handler.assign(val[0]) }
85
+ : assignment_key '=' value EOS {
86
+ keys = @handler.end_(:keys)
87
+ @handler.push(keys.pop)
88
+ @handler.assign(keys)
89
+ }
90
+ | assignment_key '=' value NEWLINE {
91
+ keys = @handler.end_(:keys)
92
+ @handler.push(keys.pop)
93
+ @handler.assign(keys)
94
+ }
95
+ ;
96
+ assignment_key
97
+ : assignment_key '.' assignment_key_component { @handler.push(val[2]) }
98
+ | assignment_key '.' FLOAT { val[2].split('.').each { |k| @handler.push(k) } }
99
+ | FLOAT {
100
+ keys = val[0].split('.')
101
+ @handler.start_(:keys)
102
+ keys.each { |key| @handler.push(key) }
103
+ }
104
+ | assignment_key_component { @handler.start_(:keys); @handler.push(val[0]) }
105
+ ;
106
+ assignment_key_component
107
+ : IDENTIFIER
108
+ | STRING_BASIC
109
+ | STRING_LITERAL
110
+ | INTEGER
111
+ | NON_DEC_INTEGER
112
+ | FLOAT_KEYWORD
113
+ | BOOLEAN
68
114
  ;
69
115
  array
70
116
  : start_array array_continued
@@ -72,10 +118,12 @@ rule
72
118
  array_continued
73
119
  : ']' { array = @handler.end_(:array); @handler.push(array) }
74
120
  | value array_next
121
+ | NEWLINE array_continued
75
122
  ;
76
123
  array_next
77
124
  : ']' { array = @handler.end_(:array); @handler.push(array) }
78
125
  | ',' array_continued
126
+ | NEWLINE array_continued
79
127
  ;
80
128
  start_array
81
129
  : '[' { @handler.start_(:array) }
@@ -91,10 +139,37 @@ rule
91
139
  ;
92
140
  literal
93
141
  | FLOAT { result = val[0].to_f }
142
+ | FLOAT_KEYWORD {
143
+ v = val[0]
144
+ result = if v.end_with?('nan')
145
+ Float::NAN
146
+ else
147
+ (v[0] == '-' ? -1 : 1) * Float::INFINITY
148
+ end
149
+ }
94
150
  | INTEGER { result = val[0].to_i }
95
- | TRUE { result = true }
96
- | FALSE { result = false }
97
- | DATETIME { result = Time.new(*val[0])}
151
+ | NON_DEC_INTEGER {
152
+ base = case val[0][1]
153
+ when "x" then 16
154
+ when "o" then 8
155
+ when "b" then 2
156
+ end
157
+ result = val[0].to_i(base)
158
+ }
159
+ | BOOLEAN { result = val[0] == 'true' ? true : false }
160
+ | DATETIME {
161
+ v = val[0]
162
+ result = if v[6].nil?
163
+ if v[4].nil?
164
+ LocalDate.new(v[0], v[1], v[2])
165
+ else
166
+ LocalDateTime.new(v[0], v[1], v[2], v[3] || 0, v[4] || 0, v[5].to_f)
167
+ end
168
+ else
169
+ Time.new(v[0], v[1], v[2], v[3] || 0, v[4] || 0, v[5].to_f, v[6])
170
+ end
171
+ }
172
+ | LOCAL_TIME { result = LocalTime.new(*val[0]) }
98
173
  ;
99
174
  string
100
175
  : STRING_MULTI { result = StringUtils.replace_escaped_chars(StringUtils.multiline_replacements(val[0])) }