compactdata 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c7a0c33715643aef4cce1e216b64a24536a75ab4c6cd4f0c274fc12b75b46351
4
+ data.tar.gz: eda5c5dcf280d1ab706a02018932b8afef8ece3582d2bfaec8d2d5f5550c470e
5
+ SHA512:
6
+ metadata.gz: cb7c8d70c740071124fc27faf9f2bf45707c6c6de71037deb1469914c3a7e3ecf078b9b00fe78a193cb68227bb687aa8cc81e322275c4e431d7d9c171fb9c67a
7
+ data.tar.gz: 69d14879fdcaca94c22f94f7707ae64f6fdbf89b5b0338c98fc18c02b6b7ded1f5c8daf2bdac4ec2b413e94440203609e834683dd48e497fe48cd636c2d3f25a
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .idea
10
+ *.gem
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
14
+
15
+ Gemfile.lock
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ 0.0.1
2
+ ===
3
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in compactdata.gemspec
6
+ group :development, :test do
7
+ gem 'debase'
8
+ gem 'ruby-debug-ide'
9
+ gem 'pry'
10
+ end
11
+
12
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 NUM Technology Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # CompactData Parser
2
+
3
+ This Ruby gem is a CompactData parser and requires Ruby 2.6.0.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'compactdata'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or clone the repository and build and install it yourself as:
18
+
19
+ $ rake install
20
+
21
+ or if that fails:
22
+
23
+ $ sudo rake install
24
+
25
+ ## Usage
26
+
27
+ In `irb`:
28
+
29
+ ```ruby
30
+ 2.6.0 :001 > require 'compactdata'
31
+ => true
32
+ 2.6.0 :002 > CompactData.parse 'a=b'
33
+ => {"a"=>"b"}
34
+ 2.6.0 :003 >
35
+ ```
36
+ In a Ruby file:
37
+
38
+ ```ruby
39
+ require 'compactdata'
40
+
41
+ str='s=CompactData'
42
+ pretty = true
43
+ result = CompactData.parse str
44
+ puts result
45
+
46
+ ```
47
+ where `str` is the CompactData to be parsed.
48
+
49
+ ## Development
50
+
51
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
52
+
53
+ ## License
54
+
55
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'COMPACTDATA/VERSION'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'compactdata'
7
+ spec.version = COMPACTDATA::VERSION
8
+ spec.authors = ['Elliott Brown']
9
+ spec.email = ['mail@elliottinvent.com']
10
+
11
+ spec.summary = 'A Ruby parser for the CompactData serialisation format.'
12
+ spec.homepage = 'https://gitlab.com/NUMTechnology/CompactData/Libraries/compactdata-ruby'
13
+ spec.license = 'MIT'
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_development_dependency 'rake', '~> 12.3'
25
+ spec.add_development_dependency 'rspec', '~> 3.0'
26
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ # The CompactData namespace
6
+ module CompactData
7
+ # Interpreter-specific errors
8
+ class ParserError < StandardError
9
+ end
10
+
11
+ # CompactData String to Ruby Hash/Array/primitive
12
+ def self.parse(str)
13
+ CompactData::Parser.parse_compactdata(str).to_j
14
+ end
15
+
16
+ # Ruby Hash/Array/primitive to CompactData
17
+ def self.generate(obj)
18
+ CompactData::Model::CompactData.new(CompactData::Model.to_compactdata(obj)).to_m
19
+ end
20
+ end
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CompactData
4
+ # A Model module for CompactData
5
+ module Model
6
+ # The root of a CompactData object
7
+ class CompactData
8
+ attr_reader :value
9
+
10
+ def initialize(value)
11
+ @value = value
12
+ end
13
+
14
+ def to_s
15
+ if @value.instance_of? Array
16
+ @value.map(&:to_s)
17
+ else
18
+ "CompactData: #{@value}"
19
+ end
20
+ end
21
+
22
+ def to_j
23
+ @value.nil? ? 'null' : @value.to_j
24
+ end
25
+
26
+ def to_m
27
+ if @value.instance_of?(CompactDataMap)
28
+ @value.items.map(&:to_m).join(';')
29
+ else
30
+ @value.to_m
31
+ end
32
+ end
33
+ end
34
+
35
+ # A CompactData Array
36
+ class CompactDataArray
37
+ attr_reader :items
38
+
39
+ def initialize(items)
40
+ @items = items
41
+ end
42
+
43
+ def to_s
44
+ items = @items.map(&:to_s)
45
+ "CompactDataArray: #{items}"
46
+ end
47
+
48
+ def to_j
49
+ @items.map(&:to_j)
50
+ end
51
+
52
+ def to_m
53
+ list = @items.map(&:to_m).join ';'
54
+ "[#{list}]"
55
+ end
56
+ end
57
+
58
+ # A CompactData Map
59
+ class CompactDataMap
60
+ attr_reader :items
61
+
62
+ def initialize(items)
63
+ @items = items
64
+ end
65
+
66
+ def to_s
67
+ items = @items.map(&:to_s)
68
+ "CompactDataMap: #{items}"
69
+ end
70
+
71
+ def to_j
72
+ result = @items.map { |p| [p.key, p.value.to_j] }
73
+ result.to_h
74
+ end
75
+
76
+ def to_m
77
+ compactdata = @items.map(&:to_m).join ';'
78
+ "(#{compactdata})"
79
+ end
80
+ end
81
+
82
+ # A CompactData Primitive
83
+ class CompactDataPrimitive
84
+ attr_reader :value
85
+
86
+ def initialize(value)
87
+ @value = value
88
+ end
89
+
90
+ def to_s
91
+ "CompactDataPrimitive: #{@value}"
92
+ end
93
+
94
+ def to_m
95
+ @value.to_s
96
+ end
97
+ end
98
+
99
+ # A CompactData PAir
100
+ class CompactDataPair
101
+ attr_reader :key, :value
102
+
103
+ def initialize(key, value)
104
+ @key = key
105
+ @value = value
106
+ end
107
+
108
+ def to_s
109
+ "CompactDataPair: #{key}=#{@value}"
110
+ end
111
+
112
+ def to_j
113
+ { @key => @value.to_j }
114
+ end
115
+
116
+ def to_m
117
+ key = UTIL.non_string_primitive?(@key) ? "\"#{@key}\"" : @key
118
+ if @value.instance_of?(CompactDataPair)
119
+ "#{key}(#{@value.to_m})"
120
+ elsif @value.instance_of?(CompactDataMap) || @value.instance_of?(CompactDataArray)
121
+ "#{key}#{@value.to_m}"
122
+ else
123
+ "#{key}=#{@value.to_m}"
124
+ end
125
+ end
126
+ end
127
+
128
+ # A CompactData FLOAT
129
+ class CompactDataFloat
130
+ attr_reader :value
131
+
132
+ def initialize(value)
133
+ @value = value
134
+ end
135
+
136
+ def to_s
137
+ "CompactDataFloat: #{@value}"
138
+ end
139
+
140
+ def to_j
141
+ @value
142
+ end
143
+
144
+ def to_m
145
+ format('%.16G', @value).sub('E+', 'E')
146
+ end
147
+ end
148
+
149
+ # A CompactData Integer
150
+ class CompactDataInteger
151
+ attr_reader :value
152
+
153
+ def initialize(value)
154
+ @value = value
155
+ end
156
+
157
+ def to_s
158
+ "CompactDataInteger: #{@value}"
159
+ end
160
+
161
+ def to_j
162
+ @value
163
+ end
164
+
165
+ def to_m
166
+ @value.to_s
167
+ end
168
+ end
169
+
170
+ # A CompactData String
171
+ class CompactDataString
172
+ attr_reader :value
173
+
174
+ def initialize(value)
175
+ @value = value
176
+ end
177
+
178
+ def to_s
179
+ "CompactDataString: #{@value}"
180
+ end
181
+
182
+ def to_j
183
+ @value
184
+ end
185
+
186
+ def to_m
187
+ UTIL.escape_and_quote @value
188
+ end
189
+ end
190
+
191
+ # A CompactData Quoted String
192
+ class CompactDataQuoted
193
+ attr_reader :value
194
+
195
+ def initialize(value)
196
+ @value = value
197
+ end
198
+
199
+ def to_s
200
+ "CompactDataQuoted: #{@value}"
201
+ end
202
+
203
+ def to_j
204
+ @value
205
+ end
206
+
207
+ def to_m
208
+ UTIL.escape_and_quote @value
209
+ end
210
+ end
211
+
212
+ # A CompactData Boolean or NULL
213
+ class CompactDataBoolNull
214
+ def initialize(value)
215
+ @value = value
216
+ end
217
+
218
+ CompactData_NULL = CompactDataBoolNull.new nil
219
+ CompactData_TRUE = CompactDataBoolNull.new true
220
+ CompactData_FALSE = CompactDataBoolNull.new false
221
+
222
+ def to_s
223
+ "CompactDataBoolNull: #{@value}"
224
+ end
225
+
226
+ def to_j
227
+ @value
228
+ end
229
+
230
+ def to_m
231
+ @value.nil? ? 'null' : @value
232
+ end
233
+ end
234
+
235
+ # Convert a Ruby Hash/Array/Primitive to a CompactData object.
236
+ def self.to_compactdata(obj)
237
+ if obj.instance_of? Float
238
+ CompactDataFloat.new obj
239
+ elsif obj.instance_of? Integer
240
+ CompactDataInteger.new obj
241
+ elsif obj.instance_of? String
242
+ CompactDataString.new obj
243
+ elsif obj.instance_of? Hash
244
+ if obj.keys.length == 1
245
+ key = obj.keys[0]
246
+ CompactDataPair.new key, to_compactdata(obj[key])
247
+ else
248
+ CompactDataMap.new(obj.keys.map { |k| CompactDataPair.new k, to_compactdata(obj[k]) })
249
+ end
250
+ elsif obj.instance_of? Array
251
+ CompactDataArray.new(obj.map { |i| to_compactdata i })
252
+ elsif obj.instance_of? TrueClass
253
+ CompactDataBoolNull::CompactData_TRUE
254
+ elsif obj.instance_of? FalseClass
255
+ CompactDataBoolNull::CompactData_FALSE
256
+ elsif obj.instance_of? NilClass
257
+ CompactDataBoolNull::CompactData_NULL
258
+ else
259
+ puts "Cannot handle #{obj}"
260
+ exit
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,294 @@
1
+ # frozen_string_literal: true
2
+ module CompactData
3
+ # A Parser module
4
+ module Parser
5
+ REPLACEMENTS = {
6
+ '\t' => "\t",
7
+ '\n' => "\n",
8
+ '\b' => "\b",
9
+ '\f' => "\f",
10
+ '\r' => "\r",
11
+ '~t' => "\t",
12
+ '~n' => "\n",
13
+ '~b' => "\b",
14
+ '~f' => "\f",
15
+ '~r' => "\r",
16
+ '~\\' => '\\',
17
+ '\\\\' => '\\',
18
+ '~~' => '~',
19
+ '\~' => '~',
20
+ '~(' => '(',
21
+ '\(' => '(',
22
+ '~)' => ')',
23
+ '\)' => ')',
24
+ '~}' => '}',
25
+ '\}' => '}',
26
+ '~[' => '[',
27
+ '\[' => '[',
28
+ '~]' => ']',
29
+ '\]' => ']',
30
+ '~;' => ';',
31
+ '\;' => ';',
32
+ '~`' => '`',
33
+ '\`' => '`',
34
+ '~"' => '"',
35
+ '"' => '"',
36
+ '~=' => '=',
37
+ '\=' => '='
38
+ }.freeze
39
+
40
+ def self.parse_compactdata(str)
41
+ tokens = CompactData::Tokeniser.tokenise str
42
+ if tokens.nil? || tokens.empty?
43
+ CompactData::Model::CompactData.new nil
44
+ elsif root_primitive? tokens[0]
45
+ CompactData::Model::CompactData.new tokens[0].value
46
+ else
47
+ result = parse_structures(tokens)
48
+ return CompactData::Model::CompactData.new result[0] if result.length == 1
49
+
50
+ return CompactData::Model::CompactData.new CompactData::Model::CompactDataMap.new(result) if all_pairs?(result)
51
+
52
+ CompactData::Model::CompactData.new CompactData::Model::CompactDataArray.new(result)
53
+ end
54
+ end
55
+
56
+ def self.root_primitive?(token)
57
+ %i[string quoted null true false integer float].include?(token)
58
+ end
59
+
60
+ def self.parse_structures(tokens)
61
+ result = []
62
+ until tokens.empty?
63
+ result.push parse_compactdata_value(tokens)
64
+ expect_separator = tokens.shift
65
+ if !expect_separator.nil? && expect_separator.type != :struct_sep
66
+ raise ParserError, "Expected ';' near #{tokens}"
67
+ end
68
+ end
69
+ result
70
+ end
71
+
72
+ def self.parse_compactdata_value(tokens)
73
+ first_token = tokens.shift
74
+
75
+ case first_token.type
76
+ when :lbracket
77
+ tokens.unshift first_token
78
+ return parse_compactdata_array tokens
79
+ when :lparen
80
+ tokens.unshift first_token
81
+ return parse_compactdata_map tokens
82
+ when :string, :quoted
83
+ peek = tokens[0]
84
+ key = first_token.value
85
+ if !peek.nil? && peek.type == :equals
86
+ tokens.shift
87
+ return CompactData::Model::CompactDataPair.new replace_escapes(unquote(key)), parse_pair_value(tokens)
88
+ end
89
+
90
+ if !peek.nil? && (peek.type == :lbracket || peek.type == :lparen)
91
+ return CompactData::Model::CompactDataPair.new replace_escapes(unquote(key)), parse_pair_value(tokens)
92
+ end
93
+
94
+ if peek.nil? || peek.type == :struct_sep || peek.type == :rparen || peek.type == :rbracket
95
+ return CompactData::Model::CompactDataString.new(replace_escapes(first_token.value)) if first_token.type == :string
96
+
97
+ return CompactData::Model::CompactDataQuoted.new(replace_escapes(unquote(first_token.value)))
98
+ end
99
+ when :integer
100
+ return CompactData::Model::CompactDataInteger.new first_token.value
101
+ when :float
102
+ return CompactData::Model::CompactDataFloat.new first_token.value
103
+ when :null
104
+ return CompactData::Model::CompactDataBoolNull::CompactData_NULL
105
+ when true
106
+ return CompactData::Model::CompactDataBoolNull::CompactData_TRUE
107
+ when false
108
+ return CompactData::Model::CompactDataBoolNull::CompactData_FALSE
109
+ else
110
+ tokens.unshift first_token
111
+ maybe_primitive = parse_primitive tokens
112
+ return maybe_primitive unless maybe_primitive.nil?
113
+ end
114
+ raise ParserError, "Unexpected token: \'#{first_token}\'"
115
+ end
116
+
117
+ def self.parse_pair_value(tokens)
118
+ first_token = tokens.shift
119
+ case first_token.type
120
+ when :lbracket
121
+ tokens.unshift first_token
122
+ return parse_compactdata_array tokens
123
+ when :lparen
124
+ tokens.unshift first_token
125
+ return parse_compactdata_map tokens
126
+ when :string, :quoted
127
+ peek = tokens[0]
128
+
129
+ raise ParserError, "Unexpected token: \'#{first_token}\'" if !peek.nil? && peek.type == :equals
130
+
131
+ if !peek.nil? && (peek.type == :lbracket || peek.type == :lparen)
132
+ raise ParserError, "Unexpected token: \'#{first_token}\'"
133
+ end
134
+
135
+ if peek.nil? || peek.type == :struct_sep || peek.type == :rparen || peek.type == :rbracket
136
+ return CompactData::Model::CompactDataString.new replace_escapes(first_token.value) if first_token.type == :string
137
+
138
+ return CompactData::Model::CompactDataQuoted.new replace_escapes(unquote(first_token.value))
139
+ end
140
+ raise ParserError, "Unexpected token: \'#{first_token}\'"
141
+ when :integer
142
+ return CompactData::Model::CompactDataInteger.new first_token.value
143
+ when :float
144
+ return CompactData::Model::CompactDataFloat.new first_token.value
145
+ when :null
146
+ return CompactData::Model::CompactDataBoolNull::CompactData_NULL
147
+ when true
148
+ return CompactData::Model::CompactDataBoolNull::CompactData_TRUE
149
+ when false
150
+ return CompactData::Model::CompactDataBoolNull::CompactData_FALSE
151
+ else
152
+ tokens.unshift first_token
153
+ maybe_primitive = parse_primitive tokens
154
+ return maybe_primitive unless maybe_primitive.nil?
155
+ end
156
+ raise ParserError, "Unexpected token: \'#{first_token}\'"
157
+ end
158
+
159
+ def self.parse_primitive(tokens)
160
+ result = nil
161
+ tok = tokens.shift
162
+
163
+ case tok.type
164
+ when :lparen, :rparen, :lbracket, :rbracket, :equals
165
+ raise ParserError, "Unexpected token: \'#{tok}\'" if tokens.empty?
166
+
167
+ tokens.unshift tok
168
+ return nil
169
+ when :null
170
+ result = CompactData::Model::CompactDataBoolNull::CompactData_NULL
171
+ when true
172
+ result = CompactData::Model::CompactDataBoolNull::CompactData_TRUE
173
+ when false
174
+ result = CompactData::Model::CompactDataBoolNull::CompactData_FALSE
175
+ when :quoted
176
+ result = CompactData::Model::CompactDataQuoted.new replace_escapes(unquote(tok.value))
177
+ when :string
178
+ result = CompactData::Model::CompactDataString.new replace_escapes(tok.value)
179
+ when :integer
180
+ result = CompactData::Model::CompactDataInteger.new tok.value
181
+ when :float
182
+ result = CompactData::Model::CompactDataFloat.new tok.value
183
+ else
184
+ raise ParserError, "Unknown token type in: \'#{tok}\'"
185
+ end
186
+
187
+ peek = tokens[0]
188
+ raise ParserError, 'Only one primitive allowed at the root.' if !peek.nil? && peek.type == :struct_sep
189
+
190
+ if !peek.nil? && (peek.type == :lparen || peek.type == :lbracket || peek.type == :equals)
191
+ s.unshift tok
192
+ return nil
193
+ elsif !peek.nil?
194
+ raise ParserError, "Unexpected token: \'#{peek}\'"
195
+ end
196
+ result
197
+ end
198
+
199
+ def self.parse_compactdata_map(tokens)
200
+ first_token = tokens.shift
201
+ entries = []
202
+
203
+ until tokens.empty?
204
+ peek = tokens[0]
205
+ if !peek.nil? && peek.type == :rparen
206
+ tokens.shift
207
+ break
208
+ end
209
+
210
+ mp = parse_compactdata_value tokens
211
+ entries.push mp
212
+
213
+ peek = tokens[0]
214
+
215
+ raise ParserError, "Expected ')' near #{first_token}" if peek.nil?
216
+
217
+ case peek.type
218
+ when :rparen
219
+ tokens.shift
220
+ break
221
+ when :struct_sep
222
+ tokens.shift
223
+ peek = tokens[0]
224
+ raise ParserError, "Unexpected ; before ] at #{peek}" if !peek.nil? && peek.type == :rparen
225
+ end
226
+ end
227
+ CompactData::Model::CompactDataMap.new entries
228
+ end
229
+
230
+ def self.parse_compactdata_array(tokens)
231
+ first_token = tokens.shift
232
+ entries = []
233
+
234
+ until tokens.empty?
235
+ peek = tokens[0]
236
+ if !peek.nil? && peek.type == :rbracket
237
+ tokens.shift
238
+ break
239
+ end
240
+
241
+ mp = parse_compactdata_value tokens
242
+ entries.push mp
243
+
244
+ peek = tokens[0]
245
+
246
+ raise ParserError, "Expected ']' near #{first_token}" if peek.nil?
247
+
248
+ case peek.type
249
+ when :rbracket
250
+ tokens.shift
251
+ break
252
+ when :struct_sep
253
+ tokens.shift
254
+ peek = tokens[0]
255
+ raise ParserError, "Unexpected ; before ] at #{peek}" if !peek.nil? && peek.type == :rparen
256
+ end
257
+ end
258
+ CompactData::Model::CompactDataArray.new entries
259
+ end
260
+
261
+ def self.unquote(str)
262
+ return str unless str.instance_of?(String)
263
+
264
+ if (str.start_with?('`') && str.end_with?('`')) || (str.start_with?('"') && str.end_with?('"'))
265
+ str[1..-2]
266
+ else
267
+ str
268
+ end
269
+ end
270
+
271
+ def self.replace_escapes(str)
272
+ return str unless str.instance_of?(String)
273
+
274
+ result = str
275
+ i = 0
276
+ while i < str.length
277
+ REPLACEMENTS.each_pair do |key, value|
278
+ if result.slice(i..).start_with?(key)
279
+ result = result.sub(key, value)
280
+ break
281
+ end
282
+ end
283
+ i += 1
284
+ end
285
+ result
286
+ end
287
+
288
+ def self.all_pairs?(arr)
289
+ result = true
290
+ arr.each { |i| result = false unless i.instance_of? CompactData::Model::CompactDataPair }
291
+ result
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CompactData
4
+ module Tokeniser
5
+ # A parsing context for a CompactData String
6
+ class Context
7
+ WS = " \t\r\n"
8
+ INTEGER_REGEX = '^-?(?:0|[1-9]\d*)$'
9
+ FLOAT_REGEX = '^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$'
10
+ NON_STRING_TOKENS = '[]();"=`'
11
+
12
+ def initialize(str)
13
+ @str = str
14
+ @tokens = []
15
+ @tok_start = 0
16
+ end
17
+
18
+ def parse
19
+ while next_token
20
+ end
21
+ @tokens
22
+ end
23
+
24
+ def next_token
25
+ while @tok_start < @str.length
26
+ break unless WS.include?(@str[@tok_start])
27
+
28
+ @tok_start += 1
29
+ end
30
+
31
+ return false if @tok_start >= @str.length
32
+
33
+ case @str[@tok_start]
34
+ when '('
35
+ tok_type = :lparen
36
+ tok_end = @tok_start + 1
37
+ when ')'
38
+ tok_type = :rparen
39
+ tok_end = @tok_start + 1
40
+ when '['
41
+ tok_type = :lbracket
42
+ tok_end = @tok_start + 1
43
+ when ']'
44
+ tok_type = :rbracket
45
+ tok_end = @tok_start + 1
46
+ when ';'
47
+ tok_type = :struct_sep
48
+ tok_end = @tok_start + 1
49
+ when '='
50
+ tok_type = :equals
51
+ tok_end = @tok_start + 1
52
+ when '"'
53
+ tok_type = :quoted
54
+ tok_end = scan_to_end_of_quoted(@str, @tok_start, '"')
55
+ when '`'
56
+ tok_type = :quoted
57
+ tok_end = scan_to_end_of_quoted(@str, @tok_start, '`')
58
+ when '{'
59
+ ## Braced strings must include the braces in the string
60
+ tok_type = :quoted
61
+ tok_end = scan_to_end_of_quoted(@str, @tok_start, '}')
62
+ else
63
+ tok_type = :string
64
+ tok_end = scan_to_end_of_string(@str, @tok_start)
65
+ end
66
+
67
+ tok_value = @str[@tok_start..(tok_end - 1)].strip
68
+
69
+ if tok_value.match? INTEGER_REGEX
70
+ number = tok_value.to_i
71
+ @tokens.push Token.new(:integer, number, @tok_start, tok_end)
72
+ elsif tok_value.match? FLOAT_REGEX
73
+ number = tok_value.to_f
74
+ @tokens.push Token.new(:float, number, @tok_start, tok_end)
75
+ elsif tok_value == 'null'
76
+ @tokens.push Token.new(:null, nil, @tok_start, tok_end)
77
+ elsif tok_value == 'true'
78
+ @tokens.push Token.new(true, true, @tok_start, tok_end)
79
+ elsif tok_value == 'false'
80
+ @tokens.push Token.new(false, false, @tok_start, tok_end)
81
+ else
82
+ @tokens.push Token.new(tok_type, tok_value, @tok_start, tok_end)
83
+ end
84
+
85
+ @tok_start = tok_end
86
+ tok_end < @str.length
87
+ end
88
+
89
+ def scan_to_end_of_quoted(str, start, quote_char)
90
+ end_str = start + 1
91
+ while end_str < str.length
92
+ end_char = str[end_str]
93
+ prev_char = str[end_str - 1]
94
+
95
+ break if end_char == quote_char && prev_char != '\\' && prev_char != '~'
96
+
97
+ end_str += 1
98
+ end
99
+ end_str + 1
100
+ end
101
+
102
+ def scan_to_end_of_string(str, start)
103
+ end_str = start + 1
104
+ while end_str < str.length
105
+ break if NON_STRING_TOKENS.include?(str[end_str]) && !escaped?(str, end_str - 1)
106
+
107
+ end_str += 1
108
+ end
109
+ end_str
110
+ end
111
+
112
+ def escaped?(str, pos)
113
+ pos >= 0 && (str[pos] == '~' || str[pos] == '\\')
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compactdata/tokeniser/context'
4
+
5
+ module CompactData
6
+ # A CompactData Tokeniser module
7
+ module Tokeniser
8
+ # A class to represent CompactData tokens
9
+ class Token
10
+ attr_reader :type, :value, :from, :to
11
+
12
+ def initialize(type, value, from, to)
13
+ @type = type
14
+ @value = value
15
+ @from = from
16
+ @to = to
17
+ end
18
+
19
+ def to_s
20
+ "type: #{@type}, from: #{@from}, to: #{@to}, value: \"#{@value}\""
21
+ end
22
+ end
23
+
24
+ def self.tokenise(str)
25
+ Context.new(str).parse
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CompactData
4
+ # Helper functions for CompactData.generate
5
+ module UTIL
6
+ SHOULD_BE_GRAVE_QUOTED = /.*[()\[\];{}="].*/.freeze
7
+ IS_NUMERIC = /^-?[0-9]*\.?[0-9]+(?:[Ee][+-]?[0-9]+)?$/.freeze
8
+
9
+ def self.escape_graves(str)
10
+ return str unless str
11
+
12
+ str.gsub(/`/, '~`')
13
+ end
14
+
15
+ def self.escape_double_quotes(str)
16
+ return str unless str
17
+
18
+ i = str.index('"', 1) # ignore the initial quote
19
+ result = str
20
+ # Don't affect the final quote in the string
21
+ while !i.nil? && i < (result.length - 1)
22
+ result = result.slice(0..(i - 1)) + '~u0022' + result.slice((i + 1)..)
23
+ i = result.index('"', 1)
24
+ end
25
+ result
26
+ end
27
+
28
+ def self.grave_quote_if_necessary(str)
29
+ return str unless str
30
+
31
+ if str.match?(SHOULD_BE_GRAVE_QUOTED) ||
32
+ (str.match?(IS_NUMERIC) && str != '00' && str != '01' && str != '000') ||
33
+ (str.strip.empty? ||
34
+ str.match?(IS_NUMERIC) ||
35
+ str == 'true' ||
36
+ str == 'false' ||
37
+ str == 'null')
38
+ "`#{str}`"
39
+ else
40
+ str
41
+ end
42
+ end
43
+
44
+ def self.double_quote_if_necessary(str)
45
+ if str && str.include?('~`')
46
+ "\"#{str}\""
47
+ else
48
+ str
49
+ end
50
+ end
51
+
52
+ def self.replace_nbsp(str)
53
+ str.gsub('\u00a0', ' ')
54
+ end
55
+
56
+ def self.escape_and_quote(str)
57
+ double_quote_if_necessary(
58
+ grave_quote_if_necessary(
59
+ escape_graves(
60
+ UNICODE.escape(
61
+ replace_nbsp(
62
+ escape_double_quotes(str)
63
+ )
64
+ )
65
+ )
66
+ )
67
+ )
68
+ end
69
+
70
+ def self.non_string_primitive?(str)
71
+ str == 'true' || str == 'false' || str == 'null' || str.match?(IS_NUMERIC)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: false
2
+
3
+ module CompactData
4
+ # Handle escaping of unicode characters.
5
+ module UNICODE
6
+ VERTICAL_TAB = 0x0b
7
+ BACKSPACE = 0x08
8
+ FORMFEED = 0x0c
9
+ LINEFEED = 0x0a
10
+ CARRIAGE_RETURN = 0x0d
11
+ TAB = 0x09
12
+
13
+ def self.escape_char(chr)
14
+ return '' unless chr
15
+
16
+ if chr >= 32 && chr <= 127
17
+ '' << chr
18
+ elsif chr == VERTICAL_TAB
19
+ '~u000B'
20
+ elsif chr == BACKSPACE
21
+ '\\b'
22
+ elsif chr == FORMFEED
23
+ '\\f'
24
+ elsif chr == LINEFEED
25
+ '\\n'
26
+ elsif chr == CARRIAGE_RETURN
27
+ '\\r'
28
+ elsif chr == TAB
29
+ '\\t'
30
+ else
31
+ '' << chr
32
+ end
33
+ end
34
+
35
+ def self.escape(str)
36
+ result = ''
37
+
38
+ str.each_codepoint do |c|
39
+ result << escape_char(c)
40
+ end
41
+ result
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module COMPACTDATA
4
+ VERSION = '0.0.6'
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compactdata/version'
4
+ require 'compactdata/model/model'
5
+ require 'compactdata/tokeniser/tokeniser'
6
+ require 'compactdata/parser/parser'
7
+ require 'compactdata/util/unicode'
8
+ require 'compactdata/util/functions'
9
+ require 'compactdata/interpreter'
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: compactdata
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ platform: ruby
6
+ authors:
7
+ - Elliott Brown
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-03-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '12.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '12.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ description:
42
+ email:
43
+ - mail@elliottinvent.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".DS_Store"
49
+ - ".gitignore"
50
+ - CHANGELOG.md
51
+ - Gemfile
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - compactdata.gemspec
56
+ - lib/compactdata.rb
57
+ - lib/compactdata/interpreter.rb
58
+ - lib/compactdata/model/model.rb
59
+ - lib/compactdata/parser/parser.rb
60
+ - lib/compactdata/tokeniser/context.rb
61
+ - lib/compactdata/tokeniser/tokeniser.rb
62
+ - lib/compactdata/util/functions.rb
63
+ - lib/compactdata/util/unicode.rb
64
+ - lib/compactdata/version.rb
65
+ homepage: https://gitlab.com/NUMTechnology/CompactData/Libraries/compactdata-ruby
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.2.3
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: A Ruby parser for the CompactData serialisation format.
88
+ test_files: []