RFC8259 8259
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.
- checksums.yaml +7 -0
- data/.gitignore +61 -0
- data/.rspec +4 -0
- data/.yardopts +2 -0
- data/Gemfile +58 -0
- data/LICENSE.txt +40 -0
- data/README.txt +1 -0
- data/RFC8259 +899 -0
- data/RFC8259.gemspec +89 -0
- data/Rakefile +86 -0
- data/lib/RFC8259.rb +94 -0
- data/lib/RFC8259/array.rb +136 -0
- data/lib/RFC8259/dumper.rb +255 -0
- data/lib/RFC8259/false.rb +79 -0
- data/lib/RFC8259/null.rb +79 -0
- data/lib/RFC8259/number.rb +149 -0
- data/lib/RFC8259/object.rb +137 -0
- data/lib/RFC8259/parser.ry +275 -0
- data/lib/RFC8259/string.rb +226 -0
- data/lib/RFC8259/true.rb +79 -0
- data/lib/RFC8259/value.rb +96 -0
- data/lib/RFC8259/version.rb +46 -0
- data/spec/RFC8259_spec.rb +305 -0
- data/spec/acceptance/README.txt +66 -0
- data/spec/acceptance/invalid/0001-ws/0001-verical-tab.txt +1 -0
- data/spec/acceptance/invalid/0001-ws/0002-null.txt +0 -0
- data/spec/acceptance/invalid/0001-ws/0003-space-in-number-1.txt +1 -0
- data/spec/acceptance/invalid/0001-ws/0004-space-in-number-2.txt +1 -0
- data/spec/acceptance/invalid/0001-ws/0005-space-in-number-3.txt +1 -0
- data/spec/acceptance/invalid/0001-ws/0006-space-in-number-4.txt +1 -0
- data/spec/acceptance/invalid/0001-ws/0007-space-in-number-5.txt +1 -0
- data/spec/acceptance/invalid/0001-ws/0008-space-in-number-6.txt +1 -0
- data/spec/acceptance/invalid/0001-ws/0009-space-in-literal.txt +1 -0
- data/spec/acceptance/invalid/0001-ws/0010-Unicode-LINE_SEPARATOR.txt +1 -0
- data/spec/acceptance/invalid/0002-comments/0001-C-style.txt +1 -0
- data/spec/acceptance/invalid/0002-comments/0002-C-plusplus-style.txt +2 -0
- data/spec/acceptance/invalid/0002-comments/0003-sh-style.txt +2 -0
- data/spec/acceptance/invalid/0002-comments/0004-python-docstring.txt +3 -0
- data/spec/acceptance/invalid/0002-comments/0005-SQL-style.txt +2 -0
- data/spec/acceptance/invalid/0002-comments/0006-BASIC-style.txt +2 -0
- data/spec/acceptance/invalid/0003-literals/0001-FALSE.txt +1 -0
- data/spec/acceptance/invalid/0003-literals/0002-NULL.txt +1 -0
- data/spec/acceptance/invalid/0003-literals/0003-TRUE.txt +1 -0
- data/spec/acceptance/invalid/0003-literals/0004-NUL.txt +1 -0
- data/spec/acceptance/invalid/0003-literals/0005-nil.txt +1 -0
- data/spec/acceptance/invalid/0003-literals/0006-undef.txt +1 -0
- data/spec/acceptance/invalid/0003-literals/0007-modifier.txt +1 -0
- data/spec/acceptance/invalid/0003-literals/0008-undefined.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0001-omit-zero.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0002-minus-dot.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0003-missing-frac.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0004-missing-exp.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0005-octal.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0006-hexadecimal.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0007-comma.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0008-perl-underscore.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0009-NaN.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0010-Inf.txt +1 -0
- data/spec/acceptance/invalid/0004-numbers/0011-Infinity.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0000-NUL.txt +0 -0
- data/spec/acceptance/invalid/0005-strings/0001-not-terminated.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0002-single-quote.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0003-back-quote.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0004-carriage-return.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0005-line-feed.txt +2 -0
- data/spec/acceptance/invalid/0005-strings/0006-unknown-escape-a.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0007-unknown-escape-perl-style.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0008-unknown-escape-C-style.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0009-unknown-escape-ruby-style.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0010-escape-too-short.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0011-C-string-concat.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0012-perl-string-concat.txt +1 -0
- data/spec/acceptance/invalid/0005-strings/0013-Java-string-concat.txt +1 -0
- data/spec/acceptance/invalid/0006-encodings/0001-CESU-8.txt +1 -0
- data/spec/acceptance/invalid/0006-encodings/0002-Windows-31J.txt +1 -0
- data/spec/acceptance/invalid/0006-encodings/0003-EBCDIC.txt +1 -0
- data/spec/acceptance/invalid/0006-encodings/0004-overlong-utf8.txt +1 -0
- data/spec/acceptance/invalid/0007-arrays/0001-lacks-open.txt +1 -0
- data/spec/acceptance/invalid/0007-arrays/0002-lacks-close.txt +1 -0
- data/spec/acceptance/invalid/0007-arrays/0003-interleaving-parens.txt +1 -0
- data/spec/acceptance/invalid/0007-arrays/0004-dangling-comma.txt +1 -0
- data/spec/acceptance/invalid/0007-arrays/0005-missing-comma.txt +1 -0
- data/spec/acceptance/invalid/0007-arrays/0006-colon-instead-of-comma.txt +1 -0
- data/spec/acceptance/invalid/0008-hashes/0001-key-missing.txt +1 -0
- data/spec/acceptance/invalid/0008-hashes/0002-value-missing.txt +1 -0
- data/spec/acceptance/invalid/0008-hashes/0003-true-key.txt +1 -0
- data/spec/acceptance/invalid/0008-hashes/0004-false-key.txt +1 -0
- data/spec/acceptance/invalid/0008-hashes/0005-null-key.txt +1 -0
- data/spec/acceptance/invalid/0008-hashes/0006-numeric-key.txt +1 -0
- data/spec/acceptance/invalid/0008-hashes/0007-array-key.txt +1 -0
- data/spec/acceptance/invalid/0008-hashes/0008-hash-key.txt +1 -0
- data/spec/acceptance/invalid/0008-hashes/0009-key-not-escaped.txt +4 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0001-JSONP.txt +1 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0002-new-Array.txt +3 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0003-new-Date.txt +1 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0004-new-Error.txt +1 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0005-Math.txt +1 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0006-regular-expression.txt +1 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0007-function.txt +7 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0008-this.txt +1 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0009-plusplus.txt +3 -0
- data/spec/acceptance/invalid/0009-javascriptisms/0010-ternary-operator.txt +1 -0
- data/spec/acceptance/valid/0001-ws/0001-space.json +1 -0
- data/spec/acceptance/valid/0001-ws/0002-tab.json +1 -0
- data/spec/acceptance/valid/0001-ws/0003-lf.json +1 -0
- data/spec/acceptance/valid/0001-ws/0004-cr.json +1 -0
- data/spec/acceptance/valid/0001-ws/0005-before.json +1 -0
- data/spec/acceptance/valid/0001-ws/0006-after.json +1 -0
- data/spec/acceptance/valid/0001-ws/0007-around-comma.json +3 -0
- data/spec/acceptance/valid/0001-ws/0008-around-colon.json +3 -0
- data/spec/acceptance/valid/0002-bare-values/0001-false.json +1 -0
- data/spec/acceptance/valid/0002-bare-values/0002-null.json +1 -0
- data/spec/acceptance/valid/0002-bare-values/0003-true.json +1 -0
- data/spec/acceptance/valid/0002-bare-values/0004-number.json +1 -0
- data/spec/acceptance/valid/0002-bare-values/0005-string.json +1 -0
- data/spec/acceptance/valid/0003-literals/0001-false.json +1 -0
- data/spec/acceptance/valid/0003-literals/0002-null.json +1 -0
- data/spec/acceptance/valid/0003-literals/0003-true.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0000-zero.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0001-one.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0002-two.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0003-three.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0004-four.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0005-five.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0006-six.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0007-seven.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0008-eight.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0009-nine.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0010-ten.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0011-minus.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0012-fraction.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0013-exponent.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0014-exponent-minus.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0015-exponent-plus.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0016-complex.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0017-DBL_MAX.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0018-DBL_MIN.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0019-subnormal-number.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0020-1E400.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0021-pi.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0022-UINT32_MAX.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0023-UINT64_MAX.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0024-INT64_MIN.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0025-high-resolution-zero.json +1 -0
- data/spec/acceptance/valid/0004-numbers/0026-high-resolution-100.json +1 -0
- data/spec/acceptance/valid/0005-strings/0001-empty.json +1 -0
- data/spec/acceptance/valid/0005-strings/0002-basic-latin.json +1 -0
- data/spec/acceptance/valid/0005-strings/0003-escapes.json +1 -0
- data/spec/acceptance/valid/0005-strings/0004-raw-unicode.json +1 -0
- data/spec/acceptance/valid/0005-strings/0005-escaped-unicode.json +1 -0
- data/spec/acceptance/valid/0005-strings/0006-escaped-NUL.json +1 -0
- data/spec/acceptance/valid/0005-strings/0007-escaped-invalid-unicode-still-valid-as-json.json +1 -0
- data/spec/acceptance/valid/0005-strings/0008-ruby-json-gem-cant-handle-this.json +1 -0
- data/spec/acceptance/valid/0005-strings/0009-unescaped-invalid-javascript-still-valid-as-json.json +1 -0
- data/spec/acceptance/valid/0005-strings/0010-escaped-separated-surrogate.json +1 -0
- data/spec/acceptance/valid/0005-strings/0011-escaped-surrogate-then-normal.json +1 -0
- data/spec/acceptance/valid/0006-m17n/0001-genesis.json +6 -0
- data/spec/acceptance/valid/0006-m17n/0002-heart-sutra.json +5 -0
- data/spec/acceptance/valid/0006-m17n/0003-escaped-valid-surrogate-pair.json +1 -0
- data/spec/acceptance/valid/0006-m17n/0004-unescaped-valid-supplementary-multilingual-plane.json +1 -0
- data/spec/acceptance/valid/0007-arrays/0000-empty.json +1 -0
- data/spec/acceptance/valid/0007-arrays/0001-one-element.json +1 -0
- data/spec/acceptance/valid/0007-arrays/0002-multiple-elements.json +33 -0
- data/spec/acceptance/valid/0007-arrays/0003-various-types.json +1 -0
- data/spec/acceptance/valid/0007-arrays/0004-nested.json +17 -0
- data/spec/acceptance/valid/0008-hashes/0000-empty.json +1 -0
- data/spec/acceptance/valid/0008-hashes/0001-onekey.json +1 -0
- data/spec/acceptance/valid/0008-hashes/0002-many-keys.json +5 -0
- data/spec/acceptance/valid/0008-hashes/0003-empty-key.json +3 -0
- data/spec/acceptance/valid/0008-hashes/0004-true-value.json +3 -0
- data/spec/acceptance/valid/0008-hashes/0005-false-value.json +3 -0
- data/spec/acceptance/valid/0008-hashes/0006-null-value.json +3 -0
- data/spec/acceptance/valid/0008-hashes/0007-string-value.json +3 -0
- data/spec/acceptance/valid/0008-hashes/0008-numeric-value.json +3 -0
- data/spec/acceptance/valid/0008-hashes/0009-array-value.json +8 -0
- data/spec/acceptance/valid/0008-hashes/0010-hash-value.json +20 -0
- data/spec/acceptance/valid/0008-hashes/0011-duplicate-key.json +4 -0
- data/spec/acceptance/valid/0008-hashes/0012-duplicate-key-in-different-representations.json +4 -0
- data/spec/acceptance/valid/0009-complicated/0001-jsonschema.json +46 -0
- data/spec/acceptance/valid/0009-complicated/0002-example-in-RFC7159-section-13.json +14 -0
- data/spec/acceptance/valid/0009-complicated/0003-example-in-RFC7159-section-13.json +22 -0
- data/spec/spec_helper.rb +60 -0
- metadata +530 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
#! /your/favourite/path/to/ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
# Copyright (c) 2014 Urabe, Shyouhei. All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# - Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# - Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in
|
14
|
+
# the documentation and/or other materials provided with the
|
15
|
+
# distribution.
|
16
|
+
#
|
17
|
+
# - Neither the name of Internet Society, IETF or IETF Trust, nor the
|
18
|
+
# names of specific contributors, may be used to endorse or promote
|
19
|
+
# products derived from this software without specific prior written
|
20
|
+
# permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
|
34
|
+
# The false, as defined in RFC8259 section 3.
|
35
|
+
class RFC8259::False < RFC8259::Value
|
36
|
+
|
37
|
+
# Parse the AST from parser, and convert into corrsponding value.
|
38
|
+
# @param [::Array] ast the AST, generated by the parser
|
39
|
+
# @return [False] evaluated instance
|
40
|
+
# @raise [ArgumentError] malformed input
|
41
|
+
def self.from_ast ast
|
42
|
+
raise ArgumentError "garbage included: #{ast.inspect}" if ast.length > 1
|
43
|
+
raise ArgumentError "not a false: #{ast.inspect}" if ast[0] != :false
|
44
|
+
return new
|
45
|
+
end
|
46
|
+
|
47
|
+
# convert to Ruby's false
|
48
|
+
# @return [false] the ruby counter part.
|
49
|
+
def plain_old_ruby_object
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
|
53
|
+
# JSON gem compat
|
54
|
+
# @return [::String] JSONified string representation
|
55
|
+
def to_json
|
56
|
+
return 'false'
|
57
|
+
end
|
58
|
+
|
59
|
+
# Equality. All false instances are equal each other, plus a FalseClass
|
60
|
+
# instance is also equal to this.
|
61
|
+
def == other
|
62
|
+
case other when FalseClass, self.class
|
63
|
+
true
|
64
|
+
else
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Local Variables:
|
72
|
+
# mode: ruby
|
73
|
+
# coding: utf-8-unix
|
74
|
+
# indent-tabs-mode: t
|
75
|
+
# tab-width: 3
|
76
|
+
# ruby-indent-level: 3
|
77
|
+
# fill-column: 79
|
78
|
+
# default-justification: full
|
79
|
+
# End:
|
data/lib/RFC8259/null.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#! /your/favourite/path/to/ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
# Copyright (c) 2014 Urabe, Shyouhei. All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# - Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# - Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in
|
14
|
+
# the documentation and/or other materials provided with the
|
15
|
+
# distribution.
|
16
|
+
#
|
17
|
+
# - Neither the name of Internet Society, IETF or IETF Trust, nor the
|
18
|
+
# names of specific contributors, may be used to endorse or promote
|
19
|
+
# products derived from this software without specific prior written
|
20
|
+
# permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
|
34
|
+
# The null, as defined in RFC8259 section 3.
|
35
|
+
class RFC8259::Null < RFC8259::Value
|
36
|
+
|
37
|
+
# Parse the AST from parser, and convert into corrsponding value.
|
38
|
+
# @param [::Array] ast the AST, generated by the parser
|
39
|
+
# @return [Null] evaluated instance
|
40
|
+
# @raise [ArgumentError] malformed input
|
41
|
+
def self.from_ast ast
|
42
|
+
raise ArgumentError "garbage included: #{ast.inspect}" if ast.length > 1
|
43
|
+
raise ArgumentError "not a false: #{ast.inspect}" if ast[0] != :null
|
44
|
+
return new
|
45
|
+
end
|
46
|
+
|
47
|
+
# convert to Ruby's nil
|
48
|
+
# @return [nil] the ruby counter part.
|
49
|
+
def plain_old_ruby_object
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# JSON gem compat
|
54
|
+
# @return [::String] JSONified string representation
|
55
|
+
def to_json
|
56
|
+
return 'null'
|
57
|
+
end
|
58
|
+
|
59
|
+
# Equality. All null instances are equal each other, plus a NilClass
|
60
|
+
# instance is also equal to this.
|
61
|
+
def == other
|
62
|
+
case other when NilClass, self.class
|
63
|
+
true
|
64
|
+
else
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Local Variables:
|
72
|
+
# mode: ruby
|
73
|
+
# coding: utf-8-unix
|
74
|
+
# indent-tabs-mode: t
|
75
|
+
# tab-width: 3
|
76
|
+
# ruby-indent-level: 3
|
77
|
+
# fill-column: 79
|
78
|
+
# default-justification: full
|
79
|
+
# End:
|
@@ -0,0 +1,149 @@
|
|
1
|
+
#! /your/favourite/path/to/ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
# Copyright (c) 2014 Urabe, Shyouhei. All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# - Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# - Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in
|
14
|
+
# the documentation and/or other materials provided with the
|
15
|
+
# distribution.
|
16
|
+
#
|
17
|
+
# - Neither the name of Internet Society, IETF or IETF Trust, nor the
|
18
|
+
# names of specific contributors, may be used to endorse or promote
|
19
|
+
# products derived from this software without specific prior written
|
20
|
+
# permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
|
34
|
+
require 'bigdecimal'
|
35
|
+
|
36
|
+
# The Numbers, as described in RFC8259 section 6.
|
37
|
+
class RFC8259::Number < RFC8259::Value
|
38
|
+
|
39
|
+
# Notie about technical design: this class _could_ have been made much
|
40
|
+
# faster if we implement the whole type-conversion thing by hand. But that
|
41
|
+
# is very bug-prone. So to avoid unnecessary complexity here we took an
|
42
|
+
# approach to first let everything be BigDecimal, then convert to others
|
43
|
+
# like Float.
|
44
|
+
|
45
|
+
# Parse the AST from parser, and convert into corrsponding values.
|
46
|
+
# @param [::Array] ast the AST, generated by the parser
|
47
|
+
# @return [Number] evaluated instance
|
48
|
+
# @raise [ArgumentError] malformed input
|
49
|
+
def self.from_ast ast
|
50
|
+
type, sign, int, frac, exp = *ast
|
51
|
+
raise ArgumentError, "not an object: #{ast.inspect}" if type != :number
|
52
|
+
raise ArgumentError, "not a number: #{ast.inspect}" if int.nil?
|
53
|
+
new sign, int, frac, exp
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Numeric] converted numeric
|
57
|
+
# @note this conversion might lose precision. Use `to_d` if you want
|
58
|
+
# something that fully represents this number.
|
59
|
+
def plain_old_ruby_object
|
60
|
+
if /\A[+-]?\d+\z/ =~ @to_s
|
61
|
+
return to_i
|
62
|
+
else
|
63
|
+
return to_f
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [BigDecimal] lossless conversion to numeric
|
68
|
+
def to_d
|
69
|
+
return @to_d
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [::String] the original string
|
73
|
+
def to_s
|
74
|
+
return @to_s.dup # dup just in case.
|
75
|
+
end
|
76
|
+
|
77
|
+
# JSON gem compat
|
78
|
+
# @return [::String] the original string
|
79
|
+
def to_json *;
|
80
|
+
return to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Float] conversion to float
|
84
|
+
def to_f
|
85
|
+
# This method must be ideoponent so the result is cached
|
86
|
+
unless @to_f
|
87
|
+
num = to_d.to_f
|
88
|
+
@to_f ||= num # ||= to avoid race
|
89
|
+
end
|
90
|
+
return @to_f
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Integer] conversion to integer
|
94
|
+
def to_i
|
95
|
+
# This method must be ideoponent so the result is cached
|
96
|
+
unless @to_i
|
97
|
+
num = to_d.to_i
|
98
|
+
@to_i ||= num # ||= to avoid race
|
99
|
+
end
|
100
|
+
return @to_i
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [::String] the value in string
|
104
|
+
def inspect
|
105
|
+
sprintf "#<%p:%p>", self.class, plain_old_ruby_object
|
106
|
+
end
|
107
|
+
|
108
|
+
# For pretty print (require 'pp' beforehand)
|
109
|
+
# @param [PP] pp the pp
|
110
|
+
def pretty_print pp
|
111
|
+
pp.object_group self do
|
112
|
+
pp.text ':'
|
113
|
+
plain_old_ruby_object.pretty_print pp
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Number equality is _not_ defined in the RFC so we take liberty of defining
|
118
|
+
# that to be mathematical comparison
|
119
|
+
def == other
|
120
|
+
other == @to_d
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
private_class_method:new
|
126
|
+
# @private
|
127
|
+
def initialize sign, int, frac, exp
|
128
|
+
@sign = sign # nil, '-', or '+'
|
129
|
+
@int = int.join
|
130
|
+
@frac = frac && frac.join # nil, or '.dddd..'
|
131
|
+
@exp = exp && exp.join # nil, or 'e+ddd..'
|
132
|
+
|
133
|
+
# pre-cache common computations
|
134
|
+
@to_s = [@sign, @int, @frac, @exp].join.encode(Encoding::US_ASCII) # this must be OK
|
135
|
+
@to_s.freeze # just in case
|
136
|
+
@to_d = BigDecimal.new @to_s
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Local Variables:
|
142
|
+
# mode: ruby
|
143
|
+
# coding: utf-8-unix
|
144
|
+
# indent-tabs-mode: t
|
145
|
+
# tab-width: 3
|
146
|
+
# ruby-indent-level: 3
|
147
|
+
# fill-column: 79
|
148
|
+
# default-justification: full
|
149
|
+
# End:
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#! /your/favourite/path/to/ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
# Copyright (c) 2014 Urabe, Shyouhei. All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# - Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# - Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in
|
14
|
+
# the documentation and/or other materials provided with the
|
15
|
+
# distribution.
|
16
|
+
#
|
17
|
+
# - Neither the name of Internet Society, IETF or IETF Trust, nor the
|
18
|
+
# names of specific contributors, may be used to endorse or promote
|
19
|
+
# products derived from this software without specific prior written
|
20
|
+
# permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
|
34
|
+
# The Objects, as described in RFC8259 section 4.
|
35
|
+
class RFC8259::Object < RFC8259::Value
|
36
|
+
|
37
|
+
# Parse the AST from parser, and convert into corrsponding values.
|
38
|
+
# @param [::Array] ast the AST, generated by the parser
|
39
|
+
# @return [Object] evaluated instance
|
40
|
+
# @raise [ArgumentError] malformed input
|
41
|
+
def self.from_ast ast
|
42
|
+
type, *assoc = *ast
|
43
|
+
raise ArgumentError, "not an object: #{ast.inspect}" if type != :object
|
44
|
+
assoc.map! do |a|
|
45
|
+
a.map! do |b|
|
46
|
+
RFC8259::Value.from_ast b
|
47
|
+
end
|
48
|
+
end
|
49
|
+
new assoc
|
50
|
+
end
|
51
|
+
|
52
|
+
# fetch the key.
|
53
|
+
# @note RFC8259 allows identical key to appear multiple times in an object.
|
54
|
+
# @note This is O(1)
|
55
|
+
# @param [::String, String] key key to look at
|
56
|
+
# @return [ [Value] ] corresponding value(s)
|
57
|
+
def [] key
|
58
|
+
ret = @assoc.select do |(k, _)| k == key end
|
59
|
+
ret.map! do |(_, v)| v end
|
60
|
+
return ret
|
61
|
+
end
|
62
|
+
|
63
|
+
# iterates over the pairs.
|
64
|
+
# @yield [key, value] the pair.
|
65
|
+
def each_pair &b
|
66
|
+
e = Enumerator.new do |y|
|
67
|
+
@assoc.each do |a|
|
68
|
+
y << a
|
69
|
+
end
|
70
|
+
end
|
71
|
+
return block_given? ? e.each(&b) : e
|
72
|
+
end
|
73
|
+
|
74
|
+
alias each each_pair
|
75
|
+
|
76
|
+
# @raise [RuntimeError] keys conflict
|
77
|
+
# @return [::Hash] converted object
|
78
|
+
def plain_old_ruby_object
|
79
|
+
ret = Hash.new
|
80
|
+
@assoc.each do |(k, v)|
|
81
|
+
kk = k.plain_old_ruby_object
|
82
|
+
if ret.include? kk
|
83
|
+
raise RuntimeError, "key #{kk} conflict."
|
84
|
+
else
|
85
|
+
vv = v.plain_old_ruby_object
|
86
|
+
ret.store kk, vv
|
87
|
+
end
|
88
|
+
end
|
89
|
+
return ret
|
90
|
+
end
|
91
|
+
|
92
|
+
alias to_h plain_old_ruby_object
|
93
|
+
alias to_hash plain_old_ruby_object
|
94
|
+
|
95
|
+
# @return [::String] the object in string
|
96
|
+
def inspect
|
97
|
+
hdr = sprintf "#<%p:%#016x {", self.class, self.object_id << 1
|
98
|
+
map = @assoc.map do |(k, v)|
|
99
|
+
sprintf '%p: %p', k.to_s, v
|
100
|
+
end.join ', '
|
101
|
+
hdr << map << '}>'
|
102
|
+
end
|
103
|
+
|
104
|
+
# For pretty print
|
105
|
+
# @param [PP] pp the pp
|
106
|
+
def pretty_print pp
|
107
|
+
hdr = sprintf '#<%p:%#016x', self.class, self.object_id << 1
|
108
|
+
pp.group 1, hdr, '>' do
|
109
|
+
pp.text ' '
|
110
|
+
RFC8259::Dumper.kandr pp, 1, @assoc.each, '{', '}' do |(i, j)|
|
111
|
+
i.pretty_print pp
|
112
|
+
pp.text ': '
|
113
|
+
j.pretty_print pp
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
private_class_method:new
|
120
|
+
# @private
|
121
|
+
def initialize assoc
|
122
|
+
@assoc = assoc
|
123
|
+
@assoc.each {|i| i.freeze }
|
124
|
+
@assoc.freeze
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Local Variables:
|
130
|
+
# mode: ruby
|
131
|
+
# coding: utf-8-unix
|
132
|
+
# indent-tabs-mode: t
|
133
|
+
# tab-width: 3
|
134
|
+
# ruby-indent-level: 3
|
135
|
+
# fill-column: 79
|
136
|
+
# default-justification: full
|
137
|
+
# End:
|
@@ -0,0 +1,275 @@
|
|
1
|
+
#! /your/favourite/path/to/racc
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
# Copyright (c) 2014 Urabe, Shyouhei. All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# - Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# - Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in
|
14
|
+
# the documentation and/or other materials provided with the
|
15
|
+
# distribution.
|
16
|
+
#
|
17
|
+
# - Neither the name of Internet Society, IETF or IETF Trust, nor the
|
18
|
+
# names of specific contributors, may be used to endorse or promote
|
19
|
+
# products derived from this software without specific prior written
|
20
|
+
# permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
|
34
|
+
# This is almost one-to-one translation of RFC8259 section 2 through 7, from
|
35
|
+
# Augmented BNF to Racc BNF. Should be the easiest to verify implementation
|
36
|
+
# against the spec.
|
37
|
+
#
|
38
|
+
# @note This parser has several shift/reduct conflicts. They are all around
|
39
|
+
# handling of white spaces (called "ws"), so can silently be ignored. I also
|
40
|
+
# checked the parser internal and made sure they are OK.
|
41
|
+
class RFC8259::Parser
|
42
|
+
|
43
|
+
options no_result_var
|
44
|
+
expect 28
|
45
|
+
rule
|
46
|
+
|
47
|
+
# Notes about nonterminal's names: in order to make manual verification
|
48
|
+
# easy, all the nonterminals that appear in the RFC are named as such. ABNF
|
49
|
+
# is much concise than plain BNF, so here we added several helper
|
50
|
+
# nonterminals; they are prefixed with "__" so you can distinguish if a
|
51
|
+
# nonterminal is RFC-origin or not.
|
52
|
+
|
53
|
+
# RFC8259 section 2
|
54
|
+
|
55
|
+
JSON_text : ws value ws { val[1] }
|
56
|
+
begin_array : ws "\x5B" ws # [ left square bracket
|
57
|
+
begin_object : ws "\x7B" ws # { left curly bracket
|
58
|
+
end_array : ws "\x5D" ws # ] right square bracket
|
59
|
+
end_object : ws "\x7D" ws # } right curly bracket
|
60
|
+
name_separator : ws "\x3A" ws # : colon
|
61
|
+
value_separator : ws "\x2C" ws # , comma
|
62
|
+
ws : # <- this is the '*' in the ABNF
|
63
|
+
| ws "\x20" # Space
|
64
|
+
| ws "\x09" # Horizontal tab
|
65
|
+
| ws "\x0A" # Line feed or New line
|
66
|
+
| ws "\x0D" # Carriage return
|
67
|
+
|
68
|
+
# RFC8259 section 3
|
69
|
+
|
70
|
+
value : false | null | true | object | array | number | string
|
71
|
+
false : "\x66" "\x61" "\x6c" "\x73" "\x65" { [ :false ] } # false
|
72
|
+
null : "\x6e" "\x75" "\x6c" "\x6c" { [ :null ] } # null
|
73
|
+
true : "\x74" "\x72" "\x75" "\x65" { [ :true ] } # true
|
74
|
+
|
75
|
+
# RFC8259 section 4
|
76
|
+
|
77
|
+
object : begin_object end_object { [ :object ] }
|
78
|
+
| begin_object __members__ end_object { [ :object, *val[1] ] }
|
79
|
+
__members__ : member { val }
|
80
|
+
| __members__ value_separator member { [ *val[0], val[2] ] }
|
81
|
+
member : string name_separator value { [ val[0], val[2] ] }
|
82
|
+
|
83
|
+
# RFC8259 section 5
|
84
|
+
|
85
|
+
array : begin_array end_array { [ :array ] }
|
86
|
+
| begin_array __list__ end_array { [ :array, *val[1] ] }
|
87
|
+
__list__ : value { val }
|
88
|
+
| __list__ value_separator value { [ *val[0], val[2] ] }
|
89
|
+
|
90
|
+
# RFC8259 section 6
|
91
|
+
|
92
|
+
number : __minus_p__ int __frac_p__ __exp_p__ { [ :number, *val ] }
|
93
|
+
__minus_p__ : | minus
|
94
|
+
__frac_p__ : | frac
|
95
|
+
__exp_p__ : | exp
|
96
|
+
decimal_point : "\x2E" # .
|
97
|
+
digit1_9 : "\x31" | "\x32" | "\x33" | "\x34" | "\x35"
|
98
|
+
| "\x36" | "\x37" | "\x38" | "\x39"
|
99
|
+
e : "\x65" | "\x45" # e E
|
100
|
+
exp : e __sign__ __digit_plus__ { val }
|
101
|
+
frac : decimal_point __digit_plus__ { val }
|
102
|
+
int : zero { val }
|
103
|
+
| digit1_9 { val }
|
104
|
+
| digit1_9 __digit_plus__ { [ val[0], *val[1] ] }
|
105
|
+
minus : "\x2D" # -
|
106
|
+
plus : "\x2B" # +
|
107
|
+
zero : "\x30" # 0
|
108
|
+
DIGIT : zero | digit1_9
|
109
|
+
__sign__ : | plus | minus
|
110
|
+
__digit_plus__ : DIGIT { val }
|
111
|
+
| __digit_plus__ DIGIT { [ *val[0], val[1] ] }
|
112
|
+
|
113
|
+
# RFC8259 section 7
|
114
|
+
|
115
|
+
string : quotation_mark quotation_mark { [ :string ] }
|
116
|
+
| quotation_mark __chars__ quotation_mark { [ :string, *val[1] ] }
|
117
|
+
__chars__ : char { val }
|
118
|
+
| __chars__ char { [ *val[0], val[1] ] }
|
119
|
+
char : unescaped | escape __ctrl__ { val.flatten }
|
120
|
+
__ctrl__ : "\x22" # " quotation mark U+0022
|
121
|
+
| "\x5C" # \ reverse solidus U+005C
|
122
|
+
| "\x2F" # / solidus U+002F
|
123
|
+
| "\x62" # b backspace U+0008
|
124
|
+
| "\x66" # f form feed U+000C
|
125
|
+
| "\x6E" # n line feed U+000A
|
126
|
+
| "\x72" # r carriage return U+000D
|
127
|
+
| "\x74" # t tab U+0009
|
128
|
+
| "\x75" # uXXXX U+XXXX
|
129
|
+
HEXDIG HEXDIG HEXDIG HEXDIG { val }
|
130
|
+
escape : "\x5C" # \
|
131
|
+
quotation_mark : "\x22" # "
|
132
|
+
HEXDIG : DIGIT
|
133
|
+
| "\x61" | "\x62" | "\x63" | "\x64" | "\x65" | "\x66"
|
134
|
+
| "\x41" | "\x42" | "\x43" | "\x44" | "\x45" | "\x46"
|
135
|
+
|
136
|
+
# "unescaped" is too much to list up here; use lexer instead.
|
137
|
+
# unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
|
138
|
+
end
|
139
|
+
|
140
|
+
---- inner
|
141
|
+
|
142
|
+
# @param [true, false] accept_bom Whether to accept BOMs
|
143
|
+
# @param [true, false] yydebug Whether to enable debug mode
|
144
|
+
def initialize accept_bom: false, yydebug: false
|
145
|
+
@accept_bom = accept_bom
|
146
|
+
@yydebug = yydebug
|
147
|
+
end
|
148
|
+
|
149
|
+
# Parses str and generates AST. The str must consist of _a_ valid JSON
|
150
|
+
# text, otherwise an exception shall raise.
|
151
|
+
#
|
152
|
+
# @param [#each_char] str IO or String or something to parse
|
153
|
+
# @return [::Array] Parsed AST
|
154
|
+
# @raise [Racc::ParseError] The input is invalid
|
155
|
+
# @raise [Encoding::CompatibilityError] The input is invalid
|
156
|
+
def parse str
|
157
|
+
@state = :init
|
158
|
+
@enum = str.enum_for:each_char
|
159
|
+
firstchar = @enum.peek
|
160
|
+
@lineno = 1
|
161
|
+
@column = 1
|
162
|
+
|
163
|
+
case @enc = firstchar.encoding
|
164
|
+
when Encoding::UTF_8,
|
165
|
+
Encoding::US_ASCII, # true subset of UTF-8
|
166
|
+
Encoding::UTF8_MAC # true subset of UTF-8
|
167
|
+
# RFC8259 sectoin 8.1 explicitly states that the input string must be
|
168
|
+
# UTF-8 encoded. That point is as clear as the sky. All other
|
169
|
+
# encodings are NG. However, what we call the ASCII encoding is the
|
170
|
+
# true subset of UTF-8. A string of ASCII must also be valid as
|
171
|
+
# UTF-8. So we allow this.
|
172
|
+
#
|
173
|
+
# There are disucssions about parsing BOMs. The original RFC4627 said
|
174
|
+
# nothing about BOMs, however its section 3 ("Encoding") cannot be
|
175
|
+
# read as if it expected BOMs. Current RFC8259 _prohibits_ to
|
176
|
+
# generate JSON texts with BOMs but _allows_ to accept.
|
177
|
+
#
|
178
|
+
# This parser can control whether to accept BOMs.
|
179
|
+
if @accept_bom and firstchar == "\u{feff}".encode(@enc)
|
180
|
+
@enum.next # consume
|
181
|
+
end
|
182
|
+
return do_parse
|
183
|
+
else
|
184
|
+
raise Encoding::CompatibilityError, <<-"end".gsub(/[\n\s]+/, ' ')
|
185
|
+
``JSON text exchanged between systems that are not part of a closed
|
186
|
+
ecosystem MUST be encoded using UTF-8'', said RFC8259 section 8.1.
|
187
|
+
The given string is NOT in the encoding (but #{@enc.inspect}).
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
def nl
|
194
|
+
@nl ||= Regexp.new('[\r\n]'.encode(@enc))
|
195
|
+
end
|
196
|
+
|
197
|
+
def sp
|
198
|
+
@nl ||= Regexp.new('\s'.encode(@enc))
|
199
|
+
end
|
200
|
+
|
201
|
+
def nm
|
202
|
+
@nl ||= Regexp.new('\d'.encode(@enc))
|
203
|
+
end
|
204
|
+
|
205
|
+
def next_token
|
206
|
+
chr = @enum.next
|
207
|
+
tok = chr.encode(Encoding::UTF_8) # dfault
|
208
|
+
newline, @newline = @newline, nl.match(chr)
|
209
|
+
if newline
|
210
|
+
@lineno += 1
|
211
|
+
@column = 1
|
212
|
+
else
|
213
|
+
@column += 1
|
214
|
+
end
|
215
|
+
case @state
|
216
|
+
when :string then # recap: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
|
217
|
+
case chr.ord
|
218
|
+
when 0x20..0x21 then tok = :unescaped
|
219
|
+
when 0x22 then @state = :init # "
|
220
|
+
when 0x23..0x5B then tok = :unescaped
|
221
|
+
when 0x5C then @state = :escaped # \
|
222
|
+
when 0x5D..0x10FFFF then tok = :unescaped
|
223
|
+
else @state = :string # NG unicode
|
224
|
+
end
|
225
|
+
when :init then @state = (chr.ord == '"'.ord) ? :string : :init
|
226
|
+
when :escaped then @state = (chr.ord == 'u'.ord) ? :u1 : :string
|
227
|
+
when :u1 then @state = :u2
|
228
|
+
when :u2 then @state = :u3
|
229
|
+
when :u3 then @state = :u4
|
230
|
+
when :u4 then @state = :string
|
231
|
+
end
|
232
|
+
return tok, chr
|
233
|
+
rescue StopIteration
|
234
|
+
return false, @enum
|
235
|
+
end
|
236
|
+
|
237
|
+
def on_error id, val, stack
|
238
|
+
reason = case @state
|
239
|
+
when :string
|
240
|
+
'this character is not allowed in a string; escape it.'
|
241
|
+
when :u1, :u2, :u3, :u4
|
242
|
+
'\uXXXX must exactly be a four-letter hexadecimal sequence.'
|
243
|
+
else
|
244
|
+
case val
|
245
|
+
when "'"
|
246
|
+
'you must use " to quote strings'
|
247
|
+
when '}', ']', ','
|
248
|
+
'possible extra (dangling) comma?'
|
249
|
+
when ':'
|
250
|
+
'possible confusion of {} vs []?'
|
251
|
+
when sp
|
252
|
+
'possible space inside of a number?'
|
253
|
+
when nm
|
254
|
+
'possible lack of +/- in exponent?'
|
255
|
+
else
|
256
|
+
'unexpected character'
|
257
|
+
end
|
258
|
+
end
|
259
|
+
msg = sprintf 'Syntax error near line %d, char %d (%p) @ %p: %s',
|
260
|
+
@lineno, @column, val, @enum, reason
|
261
|
+
raise Racc::ParseError, msg
|
262
|
+
end
|
263
|
+
|
264
|
+
---- footer
|
265
|
+
|
266
|
+
#
|
267
|
+
# Local Variables:
|
268
|
+
# mode: ruby
|
269
|
+
# coding: utf-8-unix
|
270
|
+
# indent-tabs-mode: t
|
271
|
+
# tab-width: 3
|
272
|
+
# ruby-indent-level: 3
|
273
|
+
# fill-column: 79
|
274
|
+
# default-justification: full
|
275
|
+
# End:
|