markov_chain_chat_bot 0.1.3 → 0.1.4

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.
File without changes
File without changes
@@ -0,0 +1,172 @@
1
+ # encoding: UTF-8
2
+ require 'markov_chain'
3
+ require 'strscan'
4
+
5
+ #
6
+ # A chat bot utilizing MarkovChain.
7
+ #
8
+ class MarkovChainChatBot
9
+
10
+ private_class_method :new
11
+
12
+ #
13
+ # +data+ is a map. It may be empty, in this case a brand new
14
+ # MarkovChainChatBot is created. +data+ becomes owned by the returned
15
+ # MarkovChainChatBot.
16
+ #
17
+ # +answer_limit+ is maximum size of the result of #answer().
18
+ #
19
+ def self.from(data, answer_limit = 1000)
20
+ new(data, answer_limit)
21
+ end
22
+
23
+ def initialize(data, answer_limit) # :nodoc:
24
+ @markov_chain =
25
+ if data.empty? then MarkovChain.new(data)
26
+ else MarkovChain.from(data)
27
+ end
28
+ @answer_limit = answer_limit
29
+ end
30
+
31
+ # +data+ passed to MarkovChainChatBot.from().
32
+ def data
33
+ @markov_chain.data
34
+ end
35
+
36
+ #
37
+ # +message+ is String.
38
+ #
39
+ # It returns this (modified) MarkovChainChatBot.
40
+ #
41
+ def learn(message)
42
+ @markov_chain.append!(tokenize(message)).append!([EndOfMessage.new])
43
+ return self
44
+ end
45
+
46
+ #
47
+ # +question+ is String.
48
+ #
49
+ # It returns String.
50
+ #
51
+ def answer(question)
52
+ answer = ""
53
+ previous_token = nil
54
+ catch :out_of_limit do
55
+ for token in @markov_chain.predict()
56
+ break if token.tkn_is_a? EndOfMessage or token.nil?
57
+ delimiter =
58
+ if (previous_token.tkn_is_a? Word and token.tkn_is_a? Word) then " "
59
+ else ""
60
+ end
61
+ answer.append_limited(delimiter + token.tkn_value, @answer_limit)
62
+ previous_token = token
63
+ end
64
+ end
65
+ return answer
66
+ end
67
+
68
+ private
69
+
70
+ # :enddoc:
71
+
72
+ class ::String
73
+
74
+ # appends +appendment+ to this String or throws +:out_of_limit+ if
75
+ # this String will exceed +limit+ after the appending.
76
+ #
77
+ # It returns this (modified) String.
78
+ #
79
+ def append_limited(appendment, limit)
80
+ throw :out_of_limit if self.length + appendment.length > limit
81
+ self << appendment
82
+ return self
83
+ end
84
+
85
+ end
86
+
87
+ #
88
+ # returns Array of Token-s.
89
+ #
90
+ def tokenize(text)
91
+ tokens = []
92
+ s = StringScanner.new(text)
93
+ until s.eos?
94
+ # Word.
95
+ (
96
+ w = s.scan(/([-–]?[a-zA-Zа-яёА-ЯЁ0-9]+)+/) and
97
+ tokens << Word.new(w)
98
+ ) or
99
+ # Punctuation.
100
+ (
101
+ p = s.scan(/([#{WHITESPACE_CHARSET}]|[^\-–a-zA-Zа-яёА-ЯЁ0-9]|[-–](?![a-zA-Zа-яёА-ЯЁ0-9)]))+/o) and begin
102
+ p.gsub(/[#{WHITESPACE_CHARSET}]+/, " ")
103
+ if p != " " then
104
+ tokens << PunctuationMark.new(p)
105
+ end
106
+ true
107
+ end
108
+ ) or
109
+ break
110
+ end
111
+ return tokens
112
+ end
113
+
114
+ # Accessible to #tokenize() only.
115
+ #
116
+ # White space characters as specified in "Unicode Standard Annex #44: Unicode
117
+ # Character Database" (http://www.unicode.org/reports/tr44, specifically
118
+ # http://www.unicode.org/Public/UNIDATA/PropList.txt).
119
+ #
120
+ WHITESPACE_CHARSET = "[\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]"
121
+
122
+ Token = Object
123
+
124
+ class Token
125
+
126
+ def tkn_value
127
+ self[1..-1]
128
+ end
129
+
130
+ def tkn_is_a?(clazz)
131
+ clazz === self
132
+ end
133
+
134
+ end
135
+
136
+ class Word < Token
137
+
138
+ def self.new(value)
139
+ "w" + value
140
+ end
141
+
142
+ def self.===(x)
143
+ x.is_a? String and x[0] == "w"
144
+ end
145
+
146
+ end
147
+
148
+ class PunctuationMark < Token
149
+
150
+ def self.new(value)
151
+ "p" + value
152
+ end
153
+
154
+ def self.===(x)
155
+ x.is_a? String and x[0] == "p"
156
+ end
157
+
158
+ end
159
+
160
+ class EndOfMessage < Token
161
+
162
+ def self.new()
163
+ nil
164
+ end
165
+
166
+ def self.===(x)
167
+ x.nil?
168
+ end
169
+
170
+ end
171
+
172
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markov_chain_chat_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-21 00:00:00.000000000 Z
12
+ date: 2015-12-14 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A chat bot utilizing Markov chains. It speaks Russian and English.
15
15
  email: Lavir.th.Whiolet@gmail.com
@@ -18,10 +18,10 @@ extensions: []
18
18
  extra_rdoc_files:
19
19
  - README
20
20
  files:
21
- - lib/auto_marshalling_map.rb
22
- - lib/chat_bot.rb
23
- - lib/markov_chain.rb
24
- - lib/markov_chain_chat_bot.rb
21
+ - lib/lib/auto_marshalling_map.rb
22
+ - lib/lib/chat_bot.rb
23
+ - lib/lib/markov_chain.rb
24
+ - lib/lib/markov_chain_chat_bot.rb
25
25
  - README
26
26
  homepage: https://github.com/LavirtheWhiolet/markov-chain-bot-module
27
27
  licenses:
@@ -49,3 +49,4 @@ signing_key:
49
49
  specification_version: 3
50
50
  summary: Markov chain chat bot
51
51
  test_files: []
52
+ has_rdoc:
@@ -1,582 +0,0 @@
1
- # encoding: UTF-8
2
- require 'markov_chain'
3
- require 'stringio'
4
-
5
- #
6
- # A chat bot utilizing MarkovChain.
7
- #
8
- class MarkovChainChatBot
9
-
10
- private_class_method :new
11
-
12
- #
13
- # +data+ is a map. It may be empty, in this case a brand new
14
- # MarkovChainChatBot is created. +data+ becomes owned by the returned
15
- # MarkovChainChatBot.
16
- #
17
- # +answer_limit+ is maximum size of the result of #answer().
18
- #
19
- def self.from(data, answer_limit = 1000)
20
- new(data, answer_limit)
21
- end
22
-
23
- def initialize(data, answer_limit) # :nodoc:
24
- @markov_chain =
25
- if data.empty? then MarkovChain.new(data)
26
- else MarkovChain.from(data)
27
- end
28
- @answer_limit = answer_limit
29
- end
30
-
31
- # +data+ passed to MarkovChainChatBot.from().
32
- def data
33
- @markov_chain.data
34
- end
35
-
36
- #
37
- # +message+ is String.
38
- #
39
- # It returns this (modified) MarkovChainChatBot.
40
- #
41
- def learn(message)
42
- @markov_chain.append!(tokenize(message)).append!([EndOfMessage.new])
43
- return self
44
- end
45
-
46
- #
47
- # +question+ is String.
48
- #
49
- # It returns String.
50
- #
51
- def answer(question)
52
- answer = ""
53
- previous_token = nil
54
- catch :out_of_limit do
55
- for token in @markov_chain.predict()
56
- break if token.tkn_is_a? EndOfMessage or token.nil?
57
- delimiter =
58
- if (previous_token.tkn_is_a? Word and token.tkn_is_a? Word) then " "
59
- else ""
60
- end
61
- answer.append_limited(delimiter + token.tkn_value, @answer_limit)
62
- previous_token = token
63
- end
64
- end
65
- return answer
66
- end
67
-
68
- private
69
-
70
- # :enddoc:
71
-
72
- class ::String
73
-
74
- # appends +appendment+ to this String or throws +:out_of_limit+ if
75
- # this String will exceed +limit+ after the appending.
76
- #
77
- # It returns this (modified) String.
78
- #
79
- def append_limited(appendment, limit)
80
- throw :out_of_limit if self.length + appendment.length > limit
81
- self << appendment
82
- return self
83
- end
84
-
85
- end
86
-
87
- Token = Object
88
-
89
- class Token
90
-
91
- def tkn_value
92
- self[1..-1]
93
- end
94
-
95
- def tkn_is_a?(clazz)
96
- clazz === self
97
- end
98
-
99
- end
100
-
101
- class Word < Token
102
-
103
- def self.new(value)
104
- "w" + value
105
- end
106
-
107
- def self.===(x)
108
- x.is_a? String and x[0] == "w"
109
- end
110
-
111
- end
112
-
113
- class PunctuationMark < Token
114
-
115
- def self.new(value)
116
- "p" + value
117
- end
118
-
119
- def self.===(x)
120
- x.is_a? String and x[0] == "p"
121
- end
122
-
123
- end
124
-
125
- class EndOfMessage < Token
126
-
127
- def self.new()
128
- nil
129
- end
130
-
131
- def self.===(x)
132
- x.nil?
133
- end
134
-
135
- end
136
-
137
- #
138
- # returns Array of Token-s.
139
- #
140
- def tokenize(text)
141
- yy_parse(StringIO.new(text))
142
- end
143
-
144
-
145
-
146
- #
147
- # +input+ is IO. It must have working IO#pos, IO#pos= and
148
- # IO#set_encoding() methods.
149
- #
150
- # It may raise YY_SyntaxError.
151
- #
152
- def yy_parse(input)
153
- input.set_encoding("UTF-8", "UTF-8")
154
- context = YY_ParsingContext.new(input)
155
- yy_from_pcv(
156
- yy_nonterm1(context) ||
157
- # TODO: context.worst_error can not be nil here. Prove it.
158
- raise(context.worst_error)
159
- )
160
- end
161
-
162
- # TODO: Allow to pass String to the entry point.
163
-
164
-
165
- # :nodoc:
166
- ### converts value to parser-compatible value (which is always non-false and
167
- ### non-nil).
168
- def yy_to_pcv(value)
169
- if value.nil? then :yy_nil
170
- elsif value == false then :yy_false
171
- else value
172
- end
173
- end
174
-
175
- # :nodoc:
176
- ### converts value got by #yy_to_pcv() to actual value.
177
- def yy_from_pcv(value)
178
- if value == :yy_nil then nil
179
- elsif value == :yy_false then false
180
- else value
181
- end
182
- end
183
-
184
- # :nodoc:
185
- class YY_ParsingContext
186
-
187
- # +input+ is IO.
188
- def initialize(input)
189
- @input = input
190
- @worst_error = nil
191
- end
192
-
193
- attr_reader :input
194
-
195
- # It is YY_SyntaxExpectationError or nil.
196
- attr_accessor :worst_error
197
-
198
- # adds possible error to this YY_ParsingContext.
199
- #
200
- # +error+ is YY_SyntaxExpectationError.
201
- #
202
- def << error
203
- # Update worst_error.
204
- if worst_error.nil? or worst_error.pos < error.pos then
205
- @worst_error = error
206
- elsif worst_error.pos == error.pos then
207
- @worst_error = @worst_error.or error
208
- end
209
- #
210
- return self
211
- end
212
-
213
- end
214
-
215
- # :nodoc:
216
- def yy_string(context, string)
217
- #
218
- string_start_pos = context.input.pos
219
- # Read string.
220
- read_string = context.input.read(string.bytesize)
221
- # Set the string's encoding; check if it fits the argument.
222
- unless read_string and (read_string.force_encoding(Encoding::UTF_8)) == string then
223
- #
224
- context << YY_SyntaxExpectationError.new(yy_displayed(string), string_start_pos)
225
- #
226
- return nil
227
- end
228
- #
229
- return read_string
230
- end
231
-
232
- # :nodoc:
233
- def yy_end?(context)
234
- #
235
- if not context.input.eof?
236
- context << YY_SyntaxExpectationError.new("the end", context.input.pos)
237
- return nil
238
- end
239
- #
240
- return true
241
- end
242
-
243
- # :nodoc:
244
- def yy_begin?(context)
245
- #
246
- if not(context.input.pos == 0)
247
- context << YY_SyntaxExpectationError.new("the beginning", context.input.pos)
248
- return nil
249
- end
250
- #
251
- return true
252
- end
253
-
254
- # :nodoc:
255
- def yy_char(context)
256
- #
257
- char_start_pos = context.input.pos
258
- # Read a char.
259
- c = context.input.getc
260
- #
261
- unless c then
262
- #
263
- context << YY_SyntaxExpectationError.new("a character", char_start_pos)
264
- #
265
- return nil
266
- end
267
- #
268
- return c
269
- end
270
-
271
- # :nodoc:
272
- def yy_char_range(context, from, to)
273
- #
274
- char_start_pos = context.input.pos
275
- # Read the char.
276
- c = context.input.getc
277
- # Check if it fits the range.
278
- # NOTE: c has UTF-8 encoding.
279
- unless c and (from <= c and c <= to) then
280
- #
281
- context << YY_SyntaxExpectationError.new(%(#{yy_displayed from}...#{yy_displayed to}), char_start_pos)
282
- #
283
- return nil
284
- end
285
- #
286
- return c
287
- end
288
-
289
- # :nodoc:
290
- ### The form of +string+ suitable for displaying in messages.
291
- def yy_displayed(string)
292
- if string.length == 1 then
293
- char = string[0]
294
- char_code = char.ord
295
- case char_code
296
- when 0x00...0x20, 0x2028, 0x2029 then %(#{yy_unicode_s char_code})
297
- when 0x20...0x80 then %("#{char}")
298
- when 0x80...Float::INFINITY then %("#{char} (#{yy_unicode_s char_code})")
299
- end
300
- else
301
- %("#{string}")
302
- end
303
- end
304
-
305
- # :nodoc:
306
- ### "U+XXXX" string corresponding to +char_code+.
307
- def yy_unicode_s(char_code)
308
- "U+#{"%04X" % char_code}"
309
- end
310
-
311
- class YY_SyntaxError < Exception
312
-
313
- def initialize(message, pos)
314
- super(message)
315
- @pos = pos
316
- end
317
-
318
- attr_reader :pos
319
-
320
- end
321
-
322
- # :nodoc:
323
- class YY_SyntaxExpectationError < YY_SyntaxError
324
-
325
- #
326
- # +expectations+ are String-s.
327
- #
328
- def initialize(*expectations, pos)
329
- super(nil, pos)
330
- @expectations = expectations
331
- end
332
-
333
- #
334
- # returns other YY_SyntaxExpectationError with #expectations combined.
335
- #
336
- # +other+ is another YY_SyntaxExpectationError.
337
- #
338
- # #pos of this YY_SyntaxExpectationError and +other+ must be equal.
339
- #
340
- def or other
341
- raise %(can not "or" #{YY_SyntaxExpectationError}s with different pos) unless self.pos == other.pos
342
- YY_SyntaxExpectationError.new(*(self.expectations + other.expectations), pos)
343
- end
344
-
345
- def message
346
- expectations = self.expectations.uniq
347
- (
348
- if expectations.size == 1 then expectations.first
349
- else [expectations[0...-1].join(", "), expectations[-1]].join(" or ")
350
- end
351
- ) + " is expected"
352
- end
353
-
354
- protected
355
-
356
- # Private
357
- attr_reader :expectations
358
-
359
- end
360
-
361
- # :nodoc:
362
- def yy_nonterm1(yy_context)
363
- val = nil
364
- (begin
365
- val = []
366
- true
367
- end and while true
368
- yy_vare = yy_context.input.pos
369
- if not(begin; yy_var9 = yy_context.input.pos; (begin
370
- yy_vara = yy_nontermf(yy_context)
371
- if yy_vara then
372
- w = yy_from_pcv(yy_vara)
373
- end
374
- yy_vara
375
- end and begin
376
- val << Word.new(w)
377
- true
378
- end) or (yy_context.input.pos = yy_var9; (begin
379
- yy_varb = yy_nonterm14(yy_context)
380
- if yy_varb then
381
- p = yy_from_pcv(yy_varb)
382
- end
383
- yy_varb
384
- end and begin
385
- val << PunctuationMark.new(p)
386
- true
387
- end)) or (yy_context.input.pos = yy_var9; yy_nonterm24(yy_context) and while true
388
- yy_vard = yy_context.input.pos
389
- if not(yy_nonterm24(yy_context)) then
390
- yy_context.input.pos = yy_vard
391
- break true
392
- end
393
- end); end) then
394
- yy_context.input.pos = yy_vare
395
- break true
396
- end
397
- end) and yy_to_pcv(val)
398
- end
399
-
400
- # :nodoc:
401
- def yy_nontermf(yy_context)
402
- val = nil
403
- (begin
404
- val = ""
405
- true
406
- end and begin; yy_varp = yy_context.input.pos; begin
407
- yy_varq = yy_nontermx(yy_context)
408
- if yy_varq then
409
- val << yy_from_pcv(yy_varq)
410
- end
411
- yy_varq
412
- end or (yy_context.input.pos = yy_varp; (begin
413
- yy_varr = yy_string(yy_context, "-")
414
- if yy_varr then
415
- h = yy_from_pcv(yy_varr)
416
- end
417
- yy_varr
418
- end and begin
419
- yy_varu = yy_context.input.pos
420
- yy_varv = yy_nontermx(yy_context)
421
- yy_context.input.pos = yy_varu
422
- yy_varv
423
- end and begin
424
- val << h
425
- true
426
- end)); end and while true
427
- yy_varw = yy_context.input.pos
428
- if not(begin; yy_varp = yy_context.input.pos; begin
429
- yy_varq = yy_nontermx(yy_context)
430
- if yy_varq then
431
- val << yy_from_pcv(yy_varq)
432
- end
433
- yy_varq
434
- end or (yy_context.input.pos = yy_varp; (begin
435
- yy_varr = yy_string(yy_context, "-")
436
- if yy_varr then
437
- h = yy_from_pcv(yy_varr)
438
- end
439
- yy_varr
440
- end and begin
441
- yy_varu = yy_context.input.pos
442
- yy_varv = yy_nontermx(yy_context)
443
- yy_context.input.pos = yy_varu
444
- yy_varv
445
- end and begin
446
- val << h
447
- true
448
- end)); end) then
449
- yy_context.input.pos = yy_varw
450
- break true
451
- end
452
- end) and yy_to_pcv(val)
453
- end
454
-
455
- # :nodoc:
456
- def yy_nontermx(yy_context)
457
- val = nil
458
- begin; yy_vary = yy_context.input.pos; begin
459
- yy_varz = yy_char_range(yy_context, "a", "z")
460
- if yy_varz then
461
- val = yy_from_pcv(yy_varz)
462
- end
463
- yy_varz
464
- end or (yy_context.input.pos = yy_vary; begin
465
- yy_var10 = yy_char_range(yy_context, "A", "Z")
466
- if yy_var10 then
467
- val = yy_from_pcv(yy_var10)
468
- end
469
- yy_var10
470
- end) or (yy_context.input.pos = yy_vary; begin
471
- yy_var11 = yy_char_range(yy_context, "\u{430}", "\u{44f}")
472
- if yy_var11 then
473
- val = yy_from_pcv(yy_var11)
474
- end
475
- yy_var11
476
- end) or (yy_context.input.pos = yy_vary; begin
477
- yy_var12 = yy_char_range(yy_context, "\u{410}", "\u{42f}")
478
- if yy_var12 then
479
- val = yy_from_pcv(yy_var12)
480
- end
481
- yy_var12
482
- end) or (yy_context.input.pos = yy_vary; begin
483
- yy_var13 = yy_char_range(yy_context, "0", "9")
484
- if yy_var13 then
485
- val = yy_from_pcv(yy_var13)
486
- end
487
- yy_var13
488
- end); end and yy_to_pcv(val)
489
- end
490
-
491
- # :nodoc:
492
- def yy_nonterm14(yy_context)
493
- val = nil
494
- (begin
495
- val = ""
496
- true
497
- end and while true
498
- yy_var1b = yy_context.input.pos
499
- if not(begin
500
- yy_var1a = yy_nonterm24(yy_context)
501
- if yy_var1a then
502
- val << yy_from_pcv(yy_var1a)
503
- end
504
- yy_var1a
505
- end) then
506
- yy_context.input.pos = yy_var1b
507
- break true
508
- end
509
- end and (begin
510
- yy_var1s = yy_context.worst_error
511
- yy_var1t = not(begin
512
- yy_var1u = yy_context.input.pos
513
- yy_var1v = yy_nontermx(yy_context)
514
- yy_context.input.pos = yy_var1u
515
- yy_var1v
516
- end)
517
- if yy_var1t
518
- yy_context.worst_error = yy_var1s
519
- else
520
- # NOTE: No errors were added into context but the error is still there.
521
- yy_context << YY_SyntaxExpectationError.new("different expression", yy_context.input.pos)
522
- end
523
- yy_var1t
524
- end and begin
525
- yy_var1w = yy_char(yy_context)
526
- if yy_var1w then
527
- val << yy_from_pcv(yy_var1w)
528
- end
529
- yy_var1w
530
- end) and while true
531
- yy_var1x = yy_context.input.pos
532
- if not((begin
533
- yy_var1s = yy_context.worst_error
534
- yy_var1t = not(begin
535
- yy_var1u = yy_context.input.pos
536
- yy_var1v = yy_nontermx(yy_context)
537
- yy_context.input.pos = yy_var1u
538
- yy_var1v
539
- end)
540
- if yy_var1t
541
- yy_context.worst_error = yy_var1s
542
- else
543
- # NOTE: No errors were added into context but the error is still there.
544
- yy_context << YY_SyntaxExpectationError.new("different expression", yy_context.input.pos)
545
- end
546
- yy_var1t
547
- end and begin
548
- yy_var1w = yy_char(yy_context)
549
- if yy_var1w then
550
- val << yy_from_pcv(yy_var1w)
551
- end
552
- yy_var1w
553
- end)) then
554
- yy_context.input.pos = yy_var1x
555
- break true
556
- end
557
- end and while true
558
- yy_var23 = yy_context.input.pos
559
- if not(begin
560
- yy_var22 = yy_nonterm24(yy_context)
561
- if yy_var22 then
562
- val << yy_from_pcv(yy_var22)
563
- end
564
- yy_var22
565
- end) then
566
- yy_context.input.pos = yy_var23
567
- break true
568
- end
569
- end) and yy_to_pcv(val)
570
- end
571
-
572
- # :nodoc:
573
- def yy_nonterm24(yy_context)
574
- val = nil
575
- (begin; yy_var27 = yy_context.input.pos; yy_char_range(yy_context, "\t", "\r") or (yy_context.input.pos = yy_var27; yy_string(yy_context, " ")) or (yy_context.input.pos = yy_var27; yy_string(yy_context, "\u{85}")) or (yy_context.input.pos = yy_var27; yy_string(yy_context, "\u{a0}")) or (yy_context.input.pos = yy_var27; yy_string(yy_context, "\u{1680}")) or (yy_context.input.pos = yy_var27; yy_string(yy_context, "\u{180e}")) or (yy_context.input.pos = yy_var27; yy_char_range(yy_context, "\u{2000}", "\u{200a}")) or (yy_context.input.pos = yy_var27; yy_string(yy_context, "\u{2028}")) or (yy_context.input.pos = yy_var27; yy_string(yy_context, "\u{2029}")) or (yy_context.input.pos = yy_var27; yy_string(yy_context, "\u{202f}")) or (yy_context.input.pos = yy_var27; yy_string(yy_context, "\u{205f}")) or (yy_context.input.pos = yy_var27; yy_string(yy_context, "\u{3000}")); end and begin
576
- val = " "
577
- true
578
- end) and yy_to_pcv(val)
579
- end
580
-
581
- end
582
-