jsonerino 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f7de1a3c85b62c80b26f5099f936d426b18cff799a158b58b6e946fb1267db3d
4
+ data.tar.gz: 8a7823ff56d374751196550adecf7fd994eff8dc41f0b76c3ddc7523ee1b3ef5
5
+ SHA512:
6
+ metadata.gz: ecfd85b22b2667b39f76c7d105f9ade3bf439fb675a3bd1c92646a501055444500a684b6d6669ef1958658704f45a8ab302c5dbd432a9eea2c96e6e881e73245
7
+ data.tar.gz: 4c1777f865965078e57820d6c58545dc11fd3fd9d8711797f3da7d26eaa04d1a61ec89732e56e253b85b53aa65202d84fd03bfab55d7192a099a35132f237c97
@@ -0,0 +1,19 @@
1
+ name: Linters
2
+
3
+ on: pull_request
4
+
5
+ jobs:
6
+ rubocop:
7
+ name: Rubocop
8
+ runs-on: ubuntu-18.04
9
+ steps:
10
+ - uses: actions/checkout@v2
11
+ - uses: actions/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.6.x
14
+ - name: Setup Rubocop
15
+ run: |
16
+ gem install --no-document rubocop:'~>0.81.0' # https://docs.rubocop.org/en/stable/installation/
17
+ [ -f .rubocop.yml ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/ruby/.rubocop.yml
18
+ - name: Rubocop Report
19
+ run: rubocop --color
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ pkg/*
3
+ *.gem
4
+ .bundle
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,99 @@
1
+ AllCops:
2
+
3
+ Exclude:
4
+
5
+ - "Guardfile"
6
+
7
+ - "Rakefile"
8
+
9
+
10
+
11
+ DisplayCopNames: true
12
+
13
+
14
+
15
+ Layout/LineLength:
16
+
17
+ Max: 120
18
+
19
+ Metrics/MethodLength:
20
+
21
+ Max: 20
22
+
23
+ Metrics/AbcSize:
24
+
25
+ Max: 50
26
+
27
+ Metrics/ClassLength:
28
+
29
+ Max: 150
30
+
31
+ Metrics/BlockLength:
32
+
33
+ ExcludedMethods: ['describe']
34
+
35
+ Max: 30
36
+
37
+
38
+
39
+
40
+
41
+ Style/Documentation:
42
+
43
+ Enabled: false
44
+
45
+ Style/ClassAndModuleChildren:
46
+
47
+ Enabled: false
48
+
49
+ Style/EachForSimpleLoop:
50
+
51
+ Enabled: false
52
+
53
+ Style/AndOr:
54
+
55
+ Enabled: false
56
+
57
+ Style/DefWithParentheses:
58
+
59
+ Enabled: false
60
+
61
+ Style/FrozenStringLiteralComment:
62
+
63
+ EnforcedStyle: never
64
+
65
+
66
+
67
+ Layout/HashAlignment:
68
+
69
+ EnforcedColonStyle: key
70
+
71
+ Layout/ExtraSpacing:
72
+
73
+ AllowForAlignment: false
74
+
75
+ Layout/MultilineMethodCallIndentation:
76
+
77
+ Enabled: true
78
+
79
+ EnforcedStyle: indented
80
+
81
+ Lint/RaiseException:
82
+
83
+ Enabled: false
84
+
85
+ Lint/StructNewOverride:
86
+
87
+ Enabled: false
88
+
89
+ Style/HashEachMethods:
90
+
91
+ Enabled: false
92
+
93
+ Style/HashTransformKeys:
94
+
95
+ Enabled: false
96
+
97
+ Style/HashTransformValues:
98
+
99
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # Ruby JSON Parser/Linter
2
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/jsonerino.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jsonerino/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jsonerino"
8
+ spec.version = Jsonerino::VERSION
9
+ spec.authors = ["WinterCore"]
10
+ spec.email = ["hogobbl@gmail.com"]
11
+ spec.summary = %q{JSON parser/linter}
12
+ spec.description = %q{JSON parser/linter}
13
+ spec.homepage = "https://github.com/WinterCore/microverse-ruby-capstone-json-parser"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rubocop", "~> 0.81.0"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ end
@@ -0,0 +1,86 @@
1
+ module Jsonerino
2
+ class JsonValue
3
+ attr_accessor :start, :finish
4
+ end
5
+
6
+ class JsonNull < JsonValue
7
+ def resolve
8
+ nil
9
+ end
10
+ end
11
+
12
+ class JsonBool < JsonValue
13
+ attr_reader :value
14
+
15
+ def initialize(value)
16
+ @value = value
17
+ end
18
+
19
+ def resolve
20
+ @value
21
+ end
22
+ end
23
+
24
+ class JsonNumber < JsonValue
25
+ attr_reader :value
26
+
27
+ def initialize(value)
28
+ @value = value
29
+ end
30
+
31
+ def resolve
32
+ @value
33
+ end
34
+ end
35
+
36
+ class JsonString < JsonValue
37
+ attr_reader :value
38
+
39
+ def initialize(value)
40
+ @value = value
41
+ end
42
+
43
+ def resolve
44
+ @value
45
+ end
46
+ end
47
+
48
+ class JsonArray < JsonValue
49
+ attr_reader :value
50
+
51
+ def initialize()
52
+ @value = []
53
+ end
54
+
55
+ def push(value)
56
+ raise 'Value is not instnace of JsonValue' unless value.is_a? JsonValue
57
+
58
+ @value.push(value)
59
+ end
60
+
61
+ def resolve
62
+ @value.map(&:resolve)
63
+ end
64
+ end
65
+
66
+ class JsonObject < JsonValue
67
+ attr_reader :value
68
+
69
+ def initialize()
70
+ @value = {}
71
+ end
72
+
73
+ def push(key, value)
74
+ raise 'Value is not instnace of JsonValue' unless value.is_a? JsonValue
75
+
76
+ @value[key] = value
77
+ end
78
+
79
+ def resolve
80
+ @value.keys.each do |key|
81
+ @value[key] = @value[key].resolve
82
+ end
83
+ @value
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,15 @@
1
+ module Jsonerino
2
+ module Helpers
3
+ def self.numeric?(chr)
4
+ !chr.match(/^[0-9]$/).nil?
5
+ end
6
+
7
+ def self.hex?(chr)
8
+ !chr.match(/^\h+$/).nil?
9
+ end
10
+
11
+ def self.alphanumeric?(chr)
12
+ !chr.match(/^\w+$/).nil?
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,150 @@
1
+ require_relative './token'
2
+ require_relative './helpers'
3
+
4
+ module Jsonerino
5
+ class Lexer
6
+ SIMPLE_TOKENS_MATCHER = [
7
+ ['{', Token::TOKEN_LCURLY],
8
+ ['}', Token::TOKEN_RCURLY],
9
+ ['[', Token::TOKEN_LBRACKET],
10
+ [']', Token::TOKEN_RBRACKET],
11
+ [':', Token::TOKEN_COLON],
12
+ [',', Token::TOKEN_COMMA]
13
+ ].freeze
14
+
15
+ SIMPLE_ESCAPE_SEQUENCES_MATCHER = [
16
+ ['"', '"'],
17
+ ['b', "\b"],
18
+ ['f', "\f"],
19
+ ['r', "\r"],
20
+ %W[n \n],
21
+ %W[t \t]
22
+ ].freeze
23
+
24
+ attr_reader :i
25
+
26
+ def initialize(contents)
27
+ @contents = contents
28
+ @i = 0
29
+ @l = 1 # Line number (row)
30
+ @li = 1 # Character in current line (column)
31
+ @c = contents[@i]
32
+ end
33
+
34
+ def next_token
35
+ skip_whitespace
36
+
37
+ while more_content?
38
+
39
+ v = collect_simple_token
40
+ return v if v
41
+
42
+ # Numbers in JSON are only allowed to start with a minus sign
43
+ return collect_number if number?
44
+
45
+ return collect_string if @c == '"'
46
+
47
+ return collect_id if Helpers.alphanumeric?(@c)
48
+
49
+ raise "Unexpected character '#{@c}'"
50
+ end
51
+ nil
52
+ end
53
+
54
+ private
55
+
56
+ def number?
57
+ Helpers.numeric?(@c) || @c == '-'
58
+ end
59
+
60
+ def skip_whitespace
61
+ while [' ', "\n", "\t"].include?(@c)
62
+ c = advance
63
+ if c == "\n"
64
+ @li = 1
65
+ @l += 1
66
+ end
67
+ end
68
+ end
69
+
70
+ def advance
71
+ return unless more_content?
72
+
73
+ @i += 1
74
+ @li += 1
75
+ c = @c
76
+ @c = @contents[@i]
77
+ c
78
+ end
79
+
80
+ def collect_id
81
+ start_i = @li
82
+ str = ''
83
+ while @c && Helpers.alphanumeric?(@c)
84
+ str += @c
85
+ advance
86
+ end
87
+ Token.new Token::TOKEN_ID, str, start_i, @li, @l
88
+ end
89
+
90
+ def collect_simple_token
91
+ start_i = @li
92
+ token = Lexer::SIMPLE_TOKENS_MATCHER.find { |x| x[0] == @c }
93
+ return nil unless token
94
+
95
+ Token.new token[1], advance, start_i, @li, @l
96
+ end
97
+
98
+ def collect_string
99
+ start_i = @li
100
+ advance
101
+ str = ''
102
+
103
+ while @c && @c != '"'
104
+ str += @c == '\\' ? collect_escape_sequence : @c
105
+ advance
106
+ end
107
+ advance
108
+
109
+ Token.new Token::TOKEN_STRING, str, start_i, @li, @l
110
+ end
111
+
112
+ def collect_escape_sequence
113
+ advance
114
+
115
+ if (escape = Lexer::SIMPLE_ESCAPE_SEQUENCES_MATCHER.find { |x| x[0] == @c })
116
+ escape[1]
117
+ elsif @c == 'u'
118
+ collect_unicode_escape
119
+ else
120
+ @c
121
+ end
122
+ end
123
+
124
+ def collect_unicode_escape
125
+ str_number = 4.times.inject('0x') do |str|
126
+ advance
127
+ raise "Invalid unicode escape sequence #{@c}" unless Helpers.hex?(@c)
128
+
129
+ str + @c
130
+ end
131
+ str_number.to_i.chr
132
+ end
133
+
134
+ def collect_number
135
+ start_i = @li
136
+ str_number = ''
137
+ loop do
138
+ str_number += @c
139
+ advance
140
+ break unless @c && (Helpers.numeric?(@c) || ['.', '-', '+', 'e', 'E'].include?(@c))
141
+ end
142
+ number = str_number.match?(/[\.e]/i) ? str_number.to_f : str_number.to_i
143
+ Token.new Token::TOKEN_NUMBER, number, start_i, @li, @l
144
+ end
145
+
146
+ def more_content?
147
+ @i < @contents.length
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,3 @@
1
+ module Jsonerino
2
+ class JsonParseError < StandardError; end
3
+ end
@@ -0,0 +1,106 @@
1
+ require_relative './token'
2
+ require_relative './ast'
3
+ require_relative './helpers'
4
+ require_relative './parse_error'
5
+
6
+ module Jsonerino
7
+ class Parser
8
+ def initialize(lexer)
9
+ @lexer = lexer
10
+ @current_token = lexer.next_token
11
+ end
12
+
13
+ def parse
14
+ raise_end_of_data unless @current_token
15
+
16
+ v = parse_json_value
17
+ raise_unexpected_token unless v
18
+ v
19
+ end
20
+
21
+ def parse_json_value
22
+ case @current_token.token
23
+ when Token::TOKEN_LBRACKET
24
+ parse_array
25
+ when Token::TOKEN_LCURLY
26
+ parse_object
27
+ when Token::TOKEN_STRING
28
+ JsonString.new consume(Token::TOKEN_STRING)
29
+ when Token::TOKEN_NUMBER
30
+ JsonNumber.new consume(Token::TOKEN_NUMBER)
31
+ when Token::TOKEN_ID
32
+ parse_id
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def consume(token)
39
+ if @current_token.nil?
40
+ raise_end_of_data
41
+ elsif token != @current_token.token
42
+ raise_unexpected_token
43
+ end
44
+
45
+ value = @current_token.value
46
+ @current_token = @lexer.next_token
47
+ value
48
+ end
49
+
50
+ def raise_unexpected_token
51
+ message = "Unexpected token '#{@current_token.value}'"\
52
+ " at line #{@current_token.line} column #{@current_token.start} of the JSON data"
53
+ raise JsonParseError.new, message
54
+ end
55
+
56
+ def raise_end_of_data
57
+ raise JsonParseError.new, 'End of data while reading object contents of the JSON data'
58
+ end
59
+
60
+ def parse_object
61
+ consume Token::TOKEN_LCURLY
62
+ obj = JsonObject.new
63
+ if @current_token && @current_token.token != Token::TOKEN_RCURLY
64
+ loop do
65
+ key = consume(Token::TOKEN_STRING)
66
+ consume(Token::TOKEN_COLON)
67
+ value = parse
68
+ obj.push key, value
69
+ break if @current_token && @current_token.token != Token::TOKEN_COMMA
70
+
71
+ consume(Token::TOKEN_COMMA)
72
+ end
73
+ end
74
+ consume Token::TOKEN_RCURLY
75
+ obj
76
+ end
77
+
78
+ def parse_array
79
+ consume Token::TOKEN_LBRACKET
80
+ arr = JsonArray.new
81
+ if @current_token && @current_token.token != Token::TOKEN_RBRACKET
82
+ loop do
83
+ arr.push parse
84
+ break if @current_token && @current_token.token != Token::TOKEN_COMMA
85
+
86
+ consume(Token::TOKEN_COMMA)
87
+ end
88
+ end
89
+ consume Token::TOKEN_RBRACKET
90
+ arr
91
+ end
92
+
93
+ def parse_id
94
+ case consume(Token::TOKEN_ID)
95
+ when 'true'
96
+ JsonBool.new true
97
+ when 'false'
98
+ JsonBool.new false
99
+ when 'null'
100
+ JsonNull.new
101
+ else
102
+ raise_unexpected_token
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,23 @@
1
+ module Jsonerino
2
+ class Token
3
+ TOKEN_ID = :token_id
4
+ TOKEN_STRING = :token_string
5
+ TOKEN_NUMBER = :token_number
6
+ TOKEN_LCURLY = :token_lcurly
7
+ TOKEN_RCURLY = :token_rcurly
8
+ TOKEN_LBRACKET = :token_lbracket
9
+ TOKEN_RBRACKET = :token_rbracket
10
+ TOKEN_COMMA = :token_comma
11
+ TOKEN_COLON = :token_colon
12
+
13
+ attr_reader :token, :value, :start, :finish, :line
14
+
15
+ def initialize(token, value, start, finish, line)
16
+ @token = token
17
+ @value = value
18
+ @start = start
19
+ @finish = finish
20
+ @line = line
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Jsonerino
2
+ VERSION = '0.1.1'
3
+ end
data/lib/jsonerino.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'jsonerino/parser'
2
+ require 'jsonerino/lexer'
3
+
4
+ module Jsonerino
5
+ def self.parse(str)
6
+ lexer = Jsonerino::Lexer.new str
7
+ parser = Jsonerino::Parser.new lexer
8
+ parser.parse.resolve
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ RSpec.configure do |config|
2
+ config.expect_with :rspec do |expectations|
3
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
4
+ end
5
+
6
+ config.mock_with :rspec do |mocks|
7
+ mocks.verify_partial_doubles = true
8
+ end
9
+ config.shared_context_metadata_behavior = :apply_to_host_groups
10
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsonerino
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - WinterCore
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.81.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.81.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: JSON parser/linter
70
+ email:
71
+ - hogobbl@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".github/workflows/linters.yml"
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".rubocop.yml"
80
+ - Gemfile
81
+ - README.md
82
+ - Rakefile
83
+ - jsonerino.gemspec
84
+ - lib/jsonerino.rb
85
+ - lib/jsonerino/ast.rb
86
+ - lib/jsonerino/helpers.rb
87
+ - lib/jsonerino/lexer.rb
88
+ - lib/jsonerino/parse_error.rb
89
+ - lib/jsonerino/parser.rb
90
+ - lib/jsonerino/token.rb
91
+ - lib/jsonerino/version.rb
92
+ - spec/spec_helper.rb
93
+ homepage: https://github.com/WinterCore/microverse-ruby-capstone-json-parser
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubygems_version: 3.0.3
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: JSON parser/linter
116
+ test_files:
117
+ - spec/spec_helper.rb