wexpr 0.1.0
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 +76 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +38 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/wexprTool +211 -0
- data/lib/wexpr.rb +34 -0
- data/lib/wexpr/exception.rb +66 -0
- data/lib/wexpr/expression.rb +1375 -0
- data/lib/wexpr/object_ext.rb +13 -0
- data/lib/wexpr/private_parser_state.rb +38 -0
- data/lib/wexpr/uvlq64.rb +70 -0
- data/lib/wexpr/version.rb +3 -0
- data/wexpr.gemspec +39 -0
- metadata +106 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
module Wexpr
|
3
|
+
##
|
4
|
+
# Common base class for Wexpr Exceptions
|
5
|
+
class Exception < RuntimeError
|
6
|
+
attr_reader :line
|
7
|
+
attr_reader :column
|
8
|
+
|
9
|
+
def initialize(line, column, message)
|
10
|
+
super(message)
|
11
|
+
@line = line
|
12
|
+
@column = column
|
13
|
+
@message = message
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
return "#{@line}:#{@column} #{@message}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# specific instances
|
22
|
+
|
23
|
+
class MapMissingEndParenError < Exception
|
24
|
+
end
|
25
|
+
|
26
|
+
class ExtraDataAfterParsingRootError < Exception
|
27
|
+
end
|
28
|
+
|
29
|
+
class EmptyStringError < Exception
|
30
|
+
end
|
31
|
+
|
32
|
+
class InvalidUTF8Error < Exception
|
33
|
+
end
|
34
|
+
|
35
|
+
class InvalidStringEscapeError < Exception
|
36
|
+
end
|
37
|
+
|
38
|
+
class ArrayMissingEndParenError < Exception
|
39
|
+
end
|
40
|
+
|
41
|
+
class MapKeyMustBeAValueError < Exception
|
42
|
+
end
|
43
|
+
|
44
|
+
class MapNoValueError < Exception
|
45
|
+
end
|
46
|
+
|
47
|
+
class ReferenceMissingEndBracketError < Exception
|
48
|
+
end
|
49
|
+
|
50
|
+
class ReferenceInvalidNameError < Exception
|
51
|
+
end
|
52
|
+
|
53
|
+
class ReferenceInsertMissingEndBracketError < Exception
|
54
|
+
end
|
55
|
+
|
56
|
+
class ReferenceUnknownReferenceError < Exception
|
57
|
+
end
|
58
|
+
|
59
|
+
class BinaryChunkNotBigEnoughError < Exception
|
60
|
+
end
|
61
|
+
|
62
|
+
class BinaryChunkNotBigEnoughError < Exception
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,1375 @@
|
|
1
|
+
|
2
|
+
require_relative './exception'
|
3
|
+
require_relative './private_parser_state'
|
4
|
+
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
module Wexpr
|
8
|
+
#
|
9
|
+
# A wexpr expression - based off of libWexpr's API
|
10
|
+
#
|
11
|
+
# Expression types:
|
12
|
+
# - :null - Null expression (null/nil)
|
13
|
+
# - :value - A value. Can be a number, quoted, or token. Must be UTF-8 safe.
|
14
|
+
# - :array - An array of items where order matters.
|
15
|
+
# - :map - An array of items where each pair is a key/value pair. Not ordered. Keys are required to be values.
|
16
|
+
# - :binarydata - A set of binary data, which is encoded in text format as base64.
|
17
|
+
# - :invalid - Invalid expression - not filled in or usable
|
18
|
+
#
|
19
|
+
# Parse flags:
|
20
|
+
# - [none currently specified]
|
21
|
+
#
|
22
|
+
# Write flags:
|
23
|
+
# - :humanReadable - If set, will add newlines and indentation to make it more readable.
|
24
|
+
#
|
25
|
+
class Expression
|
26
|
+
# --- Construction functions
|
27
|
+
|
28
|
+
#
|
29
|
+
# Create an expression from a string.
|
30
|
+
#
|
31
|
+
def self.create_from_string(str, parseFlags=[])
|
32
|
+
return self.create_from_string_with_external_reference_table(str, parseFlags, {})
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Create an expression from a string with an external reference table.
|
37
|
+
#
|
38
|
+
def self.create_from_string_with_external_reference_table(str, parseFlags=[], referenceTable={})
|
39
|
+
expr = Expression.new()
|
40
|
+
expr.change_type(:invalid)
|
41
|
+
|
42
|
+
parserState = PrivateParserState.new()
|
43
|
+
parserState.externalReferenceMap=referenceTable
|
44
|
+
|
45
|
+
strC = +str # + creates a dup mutable string if frozen
|
46
|
+
|
47
|
+
# check that the string is valid UTF-8
|
48
|
+
if strC.force_encoding('UTF-8').valid_encoding?
|
49
|
+
# now start parsing
|
50
|
+
rest = expr.p_parse_from_string(
|
51
|
+
strC, parseFlags, parserState
|
52
|
+
)
|
53
|
+
|
54
|
+
postRest = Expression.s_trim_front_of_string(rest, parserState)
|
55
|
+
if postRest.size != 0
|
56
|
+
raise ExtraDataAfterParsingRootError.new(parserState.line, parserState.column, "Extra data after parsing the root expression: #{postRest}")
|
57
|
+
end
|
58
|
+
|
59
|
+
if expr.type == :invalid
|
60
|
+
raise EmptyStringError.new(parserState.line, parserState.column, "No expression found [remained invalid]")
|
61
|
+
end
|
62
|
+
else
|
63
|
+
raise InvalidUTF8Error.new(0, 0, "Invalid UTF-8")
|
64
|
+
end
|
65
|
+
|
66
|
+
return expr
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Creates an expression from a binary chunk.
|
71
|
+
#
|
72
|
+
def self.create_from_binary_chunk(data)
|
73
|
+
expr = Expression.create_invalid()
|
74
|
+
|
75
|
+
expr.p_parse_from_binary_chunk(data)
|
76
|
+
# result unused: remaining part of buffer
|
77
|
+
|
78
|
+
return expr
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Creates an empty invalid expression.
|
83
|
+
#
|
84
|
+
def self.create_invalid()
|
85
|
+
expr = Expression.new()
|
86
|
+
expr.change_type(:invalid)
|
87
|
+
return expr
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Creates a null expression.
|
92
|
+
#
|
93
|
+
def self.create_null()
|
94
|
+
expr = Expression.new()
|
95
|
+
expr.change_type(:null)
|
96
|
+
return expr
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Creates a value expression with the given string being the value.
|
101
|
+
#
|
102
|
+
def self.create_value(val)
|
103
|
+
expr = Expression.create_null()
|
104
|
+
if expr != nil
|
105
|
+
expr.change_type(:value)
|
106
|
+
expr.value_set(val)
|
107
|
+
end
|
108
|
+
|
109
|
+
return expr
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Create a copy of an expression. You own the copy - deep copy.
|
114
|
+
#
|
115
|
+
def create_copy()
|
116
|
+
expr = Expression.create_null()
|
117
|
+
|
118
|
+
expr.p_copy_from(self)
|
119
|
+
|
120
|
+
return expr
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# Create from a ruby variable. Will convert as:
|
125
|
+
# If a nil value, will be a null expression.
|
126
|
+
# If an Array, will turn into an array expression (and recurse).
|
127
|
+
# If a Hash, will turn into a map expression (and recurse).
|
128
|
+
# If a String, and is UTF-8 safe, will turn into a value expression.
|
129
|
+
# If a String, and is not UTF-8 safe, will turn into a binary expression. [this case is weird due to how ruby handles memory.
|
130
|
+
# Otherwise, we just use .to_s and store that. Not sure if best, but we want to catch things like numbers.
|
131
|
+
#
|
132
|
+
def self.create_from_ruby(variable)
|
133
|
+
case variable
|
134
|
+
when NilClass
|
135
|
+
return Expression.create_null()
|
136
|
+
|
137
|
+
when Array
|
138
|
+
e = Expression.create_null()
|
139
|
+
e.change_type(:array)
|
140
|
+
|
141
|
+
variable.each do |val|
|
142
|
+
e.array_add_element_to_end(
|
143
|
+
Expression.create_from_ruby(val)
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
return e
|
148
|
+
|
149
|
+
when Hash
|
150
|
+
e = Expression.create_null()
|
151
|
+
e.change_type(:map)
|
152
|
+
|
153
|
+
variable.each do |k, v|
|
154
|
+
ve = Expression.create_from_ruby(v)
|
155
|
+
|
156
|
+
# key could technically be anything, so convert to string
|
157
|
+
e.map_set_value_for_key(k.to_s, ve)
|
158
|
+
end
|
159
|
+
|
160
|
+
return e
|
161
|
+
|
162
|
+
when String
|
163
|
+
varC = +variable # + creates a dup mutable string if frozen
|
164
|
+
isUTF8 = varC.force_encoding('UTF-8').valid_encoding?
|
165
|
+
|
166
|
+
if isUTF8
|
167
|
+
return Expression.create_value(varC)
|
168
|
+
else
|
169
|
+
return Expression.create_binary_representation(variable)
|
170
|
+
end
|
171
|
+
|
172
|
+
else
|
173
|
+
# might be a number or something else, to_s it
|
174
|
+
return Expression.create_value(variable.to_s)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# --- Information
|
179
|
+
|
180
|
+
#
|
181
|
+
# Return the type of the expression
|
182
|
+
#
|
183
|
+
def type()
|
184
|
+
return @type
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# Change the type of the expression. Invalidates all data currently in the expression.
|
189
|
+
#
|
190
|
+
def change_type(type)
|
191
|
+
# first destroy
|
192
|
+
@value=nil; remove_instance_variable(:@value)
|
193
|
+
@binarydata=nil; remove_instance_variable(:@binarydata)
|
194
|
+
@array=nil; remove_instance_variable(:@array)
|
195
|
+
@map=nil; remove_instance_variable(:@map)
|
196
|
+
|
197
|
+
# then set
|
198
|
+
@type = type
|
199
|
+
|
200
|
+
# then init
|
201
|
+
if @type == :value
|
202
|
+
@value = ""
|
203
|
+
elsif @type == :binarydata
|
204
|
+
@binarydata = ""
|
205
|
+
elsif @type == :array
|
206
|
+
@array = []
|
207
|
+
elsif @type == :map
|
208
|
+
@map = {}
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# Create a string which represents the expression.
|
214
|
+
#
|
215
|
+
def create_string_representation(indent, writeFlags=[])
|
216
|
+
newBuf = self.p_append_string_representation_to_buffer(writeFlags, indent, "")
|
217
|
+
|
218
|
+
return newBuf
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# Create the binary representation for the current chunk. This contains an expression chunk and all of its child chunks, but NOT the file header.
|
223
|
+
#
|
224
|
+
def create_binary_representation()
|
225
|
+
buf = ""
|
226
|
+
|
227
|
+
# format is:
|
228
|
+
# size, type, data
|
229
|
+
|
230
|
+
if @type == :null
|
231
|
+
# data is 0x0
|
232
|
+
buf += UVLQ64::write(0)
|
233
|
+
buf += [BIN_EXPRESSIONTYPE_NULL].pack('C')
|
234
|
+
|
235
|
+
elsif @type == :value
|
236
|
+
val = self.value
|
237
|
+
valLength = val.bytes.size
|
238
|
+
|
239
|
+
buf += UVLQ64::write(valLength)
|
240
|
+
buf += [BIN_EXPRESSIONTYPE_VALUE].pack('C')
|
241
|
+
buf += val.bytes.pack('C*')
|
242
|
+
|
243
|
+
elsif @type == :array
|
244
|
+
# first pass, figure out the total size
|
245
|
+
sizeOfArrayContents = 0
|
246
|
+
|
247
|
+
for i in 0 ... self.array_count()
|
248
|
+
# TODO: dont create the entire representation twice - ability to ask for size
|
249
|
+
child = self.array_at(i)
|
250
|
+
childBuffer = child.create_binary_representation()
|
251
|
+
|
252
|
+
sizeOfArrayContents += childBuffer.bytes.size
|
253
|
+
end
|
254
|
+
|
255
|
+
# header
|
256
|
+
buf += UVLQ64::write(sizeOfArrayContents)
|
257
|
+
buf += [BIN_EXPRESSIONTYPE_ARRAY].pack('C*')
|
258
|
+
|
259
|
+
# data
|
260
|
+
for i in 0 ... self.array_count()
|
261
|
+
child = self.array_at(i)
|
262
|
+
childBuffer = child.create_binary_representation()
|
263
|
+
|
264
|
+
buf += childBuffer
|
265
|
+
end
|
266
|
+
|
267
|
+
# done
|
268
|
+
|
269
|
+
elsif @type == :map
|
270
|
+
# first pass, figure out the total size
|
271
|
+
sizeOfMapContents = 0
|
272
|
+
|
273
|
+
for i in 0 ... self.map_count()
|
274
|
+
# TODO: dont create the entire representation twice - ability to ask for sizes
|
275
|
+
mapKey = self.map_key_at(i)
|
276
|
+
mapKeyLen = mapKey.bytes.size
|
277
|
+
|
278
|
+
# write the map key as a new value
|
279
|
+
keySizeSize = UVLQ64::byte_size(mapKeyLen)
|
280
|
+
keySize = keySizeSize + SIZE_U8 + mapKeyLen
|
281
|
+
sizeOfMapContents += keySize
|
282
|
+
|
283
|
+
# write the map value
|
284
|
+
mapValue = self.map_value_at(i)
|
285
|
+
childBuffer = mapValue.create_binary_representation()
|
286
|
+
sizeOfMapContents += childBuffer.bytes.size
|
287
|
+
end
|
288
|
+
|
289
|
+
# second pass, write the header and pairs
|
290
|
+
buf += UVLQ64::write(sizeOfMapContents)
|
291
|
+
buf += [BIN_EXPRESSIONTYPE_MAP].pack('C')
|
292
|
+
|
293
|
+
for i in 0 ... self.map_count()
|
294
|
+
mapKey = self.map_key_at(i)
|
295
|
+
mapKeyLen = mapKey.bytes.size
|
296
|
+
|
297
|
+
# write the map key as a new value
|
298
|
+
buf += UVLQ64::write(mapKeyLen)
|
299
|
+
buf += [BIN_EXPRESSIONTYPE_VALUE].pack('C')
|
300
|
+
buf += mapKey.bytes.pack('C*')
|
301
|
+
|
302
|
+
# write the map value
|
303
|
+
mapValue = self.map_value_at(i)
|
304
|
+
childBuffer = mapValue.create_binary_representation()
|
305
|
+
|
306
|
+
buf += childBuffer
|
307
|
+
end
|
308
|
+
|
309
|
+
# done
|
310
|
+
elsif @type == :binarydata
|
311
|
+
binData = self.binarydata
|
312
|
+
|
313
|
+
buf += UVLQ64::write(binData.bytes.size+1) # +1 for compression method
|
314
|
+
buf += [BIN_EXPRESSIONTYPE_BINARYDATA].pack('C')
|
315
|
+
buf += [BIN_BINARYDATA_RAW].pack('C') # for now we never compress
|
316
|
+
buf += binData.bytes.pack('C*')
|
317
|
+
|
318
|
+
else
|
319
|
+
raise Exception.new(0, 0, "Unknown type")
|
320
|
+
end
|
321
|
+
|
322
|
+
return buf
|
323
|
+
end
|
324
|
+
|
325
|
+
# --- Values
|
326
|
+
|
327
|
+
#
|
328
|
+
# Return the value of the expression. Will return nil if not a value
|
329
|
+
#
|
330
|
+
def value()
|
331
|
+
if @type != :value
|
332
|
+
return nil
|
333
|
+
end
|
334
|
+
|
335
|
+
return @value
|
336
|
+
end
|
337
|
+
|
338
|
+
#
|
339
|
+
# Set the value of the expression.
|
340
|
+
#
|
341
|
+
def value_set(str)
|
342
|
+
if @type != :value
|
343
|
+
return
|
344
|
+
end
|
345
|
+
|
346
|
+
@value = str
|
347
|
+
end
|
348
|
+
|
349
|
+
# --- Binary Data
|
350
|
+
|
351
|
+
#
|
352
|
+
# Return the binary data of the expression. Will return nil if not binary data.
|
353
|
+
#
|
354
|
+
def binarydata()
|
355
|
+
if @type != :binarydata
|
356
|
+
return nil
|
357
|
+
end
|
358
|
+
|
359
|
+
return @binarydata
|
360
|
+
end
|
361
|
+
|
362
|
+
#
|
363
|
+
# Set the binary data of the expression.
|
364
|
+
#
|
365
|
+
def binarydata_set(buffer)
|
366
|
+
if @type != :binarydata
|
367
|
+
return nil
|
368
|
+
end
|
369
|
+
|
370
|
+
@binarydata = buffer
|
371
|
+
end
|
372
|
+
|
373
|
+
# --- Array
|
374
|
+
|
375
|
+
#
|
376
|
+
# Return the length of the array
|
377
|
+
#
|
378
|
+
def array_count()
|
379
|
+
if @type != :array
|
380
|
+
return nil
|
381
|
+
end
|
382
|
+
|
383
|
+
return @array.count
|
384
|
+
end
|
385
|
+
|
386
|
+
#
|
387
|
+
# Return an object at the given index
|
388
|
+
#
|
389
|
+
def array_at(index)
|
390
|
+
if @type != :array
|
391
|
+
return nil
|
392
|
+
end
|
393
|
+
|
394
|
+
return @array[index]
|
395
|
+
end
|
396
|
+
|
397
|
+
#
|
398
|
+
# Add an element to the end of the array
|
399
|
+
#
|
400
|
+
def array_add_element_to_end(element)
|
401
|
+
if @type != :array
|
402
|
+
return nil
|
403
|
+
end
|
404
|
+
|
405
|
+
@array << element
|
406
|
+
end
|
407
|
+
|
408
|
+
# --- Map
|
409
|
+
|
410
|
+
#
|
411
|
+
# Return the number of elements in the map
|
412
|
+
#
|
413
|
+
def map_count()
|
414
|
+
if @type != :map
|
415
|
+
return nil
|
416
|
+
end
|
417
|
+
|
418
|
+
return @map.count
|
419
|
+
end
|
420
|
+
|
421
|
+
#
|
422
|
+
# Return the key at a given index
|
423
|
+
#
|
424
|
+
def map_key_at(index)
|
425
|
+
if @type != :map
|
426
|
+
return nil
|
427
|
+
end
|
428
|
+
|
429
|
+
return @map.keys[index]
|
430
|
+
end
|
431
|
+
|
432
|
+
#
|
433
|
+
# Return the value at a given index
|
434
|
+
#
|
435
|
+
def map_value_at(index)
|
436
|
+
if @type != :map
|
437
|
+
return nil
|
438
|
+
end
|
439
|
+
|
440
|
+
return @map.values[index]
|
441
|
+
end
|
442
|
+
|
443
|
+
#
|
444
|
+
# Return the value for a given key in the map
|
445
|
+
#
|
446
|
+
def map_value_for_key(key)
|
447
|
+
if @type != :map
|
448
|
+
return nil
|
449
|
+
end
|
450
|
+
|
451
|
+
return @map[key]
|
452
|
+
end
|
453
|
+
|
454
|
+
#
|
455
|
+
# Set the value for a given key in the map. Will overwrite if already exists.
|
456
|
+
#
|
457
|
+
def map_set_value_for_key(key, value)
|
458
|
+
if @type != :map
|
459
|
+
return nil
|
460
|
+
end
|
461
|
+
|
462
|
+
@map[key] = value
|
463
|
+
end
|
464
|
+
|
465
|
+
# --- Ruby helpers
|
466
|
+
|
467
|
+
#
|
468
|
+
# Output as a string - we do a compact style
|
469
|
+
#
|
470
|
+
def to_s()
|
471
|
+
return self.create_string_representation(0, [])
|
472
|
+
end
|
473
|
+
|
474
|
+
#
|
475
|
+
# Convert to a ruby type.
|
476
|
+
# Types will convert as follows:
|
477
|
+
# - :null - Returns nil
|
478
|
+
# - :value - Returns the value as a string.
|
479
|
+
# - :array - Returns as an array.
|
480
|
+
# - :map - Returns as a hash.
|
481
|
+
# - :binarydata - Returns the binary data as a ruby string.
|
482
|
+
# - :invalid - Throws an exception
|
483
|
+
#
|
484
|
+
def to_ruby()
|
485
|
+
case @type
|
486
|
+
when :null
|
487
|
+
return nil
|
488
|
+
|
489
|
+
when :value
|
490
|
+
return @value
|
491
|
+
|
492
|
+
when :array
|
493
|
+
a=[]
|
494
|
+
for i in 0..self.array_count-1
|
495
|
+
a << self.array_at(i).to_ruby
|
496
|
+
end
|
497
|
+
return a
|
498
|
+
|
499
|
+
when :map
|
500
|
+
m={}
|
501
|
+
for i in 0..self.map_count-1
|
502
|
+
k = self.map_key_at(i)
|
503
|
+
v = self.map_value_at(i)
|
504
|
+
|
505
|
+
m[k] = v.to_ruby
|
506
|
+
end
|
507
|
+
|
508
|
+
return m
|
509
|
+
when :binarydata
|
510
|
+
# um, direct i guess - its raw/pure data
|
511
|
+
return @binarydata
|
512
|
+
else
|
513
|
+
raise Exception.new(0,0,"Invalid type to convert to ruby: #{@type}")
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
# array operator for array and map
|
518
|
+
def [](index)
|
519
|
+
if @type == :array
|
520
|
+
return self.array_at(index)
|
521
|
+
elsif @type == :map
|
522
|
+
return self.map_value_for_key(index)
|
523
|
+
end
|
524
|
+
|
525
|
+
# wrong type
|
526
|
+
return nil
|
527
|
+
end
|
528
|
+
|
529
|
+
# -------------------- PRIVATE ----------------------------
|
530
|
+
|
531
|
+
def self.s_is_newline(c)
|
532
|
+
return c == "\n"
|
533
|
+
end
|
534
|
+
|
535
|
+
def self.s_is_whitespace(c)
|
536
|
+
# we put \r in whitespace and not newline so its counted as a column instead of a line, cause windows.
|
537
|
+
# we dont support classic macos style newlines properly as a side effect.
|
538
|
+
return (c == ' ' or c == "\t" or c ==" \r" or self.s_is_newline(c))
|
539
|
+
end
|
540
|
+
|
541
|
+
def self.s_is_not_bareword_safe(c)
|
542
|
+
return (c == '*' \
|
543
|
+
or c == '#' \
|
544
|
+
or c == '@' \
|
545
|
+
or c == '(' or c == ')' \
|
546
|
+
or c == '[' or c == ']' \
|
547
|
+
or c == '^' \
|
548
|
+
or c == '<' or c == '>' \
|
549
|
+
or c == '"' \
|
550
|
+
or c == ';' \
|
551
|
+
or self.s_is_whitespace(c) \
|
552
|
+
)
|
553
|
+
end
|
554
|
+
|
555
|
+
def self.s_is_escape_valid(c)
|
556
|
+
return (c == '"' || c == 'r' || c == 'n' || c == 't' || c == "\\")
|
557
|
+
end
|
558
|
+
|
559
|
+
def self.s_value_for_escape(c)
|
560
|
+
return case c
|
561
|
+
when '"' then '"'
|
562
|
+
when 'r' then "\r"
|
563
|
+
when 'n' then "\n"
|
564
|
+
when 't' then "\t"
|
565
|
+
when "\\" then "\\"
|
566
|
+
else
|
567
|
+
0 # invalid escape
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
def self.s_requires_escape(c)
|
572
|
+
return (c == '"' || c == "\r" || c == "\n" || c == "\t" || c == "\\")
|
573
|
+
end
|
574
|
+
|
575
|
+
def self.s_escape_for_value(c)
|
576
|
+
return case c
|
577
|
+
when '"' then '"'
|
578
|
+
when "\r" then "r"
|
579
|
+
when "\n" then "n"
|
580
|
+
when "\t" then "t"
|
581
|
+
when "\\" then "\\"
|
582
|
+
else
|
583
|
+
0 # invalid escape
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
# trims the given string by removing whitespace or comments from the beginning of the string
|
588
|
+
def self.s_trim_front_of_string(str, parserState)
|
589
|
+
while true
|
590
|
+
if str.size == 0
|
591
|
+
return str
|
592
|
+
end
|
593
|
+
|
594
|
+
first = str[0]
|
595
|
+
|
596
|
+
# skip whitespace
|
597
|
+
if self.s_is_whitespace(first)
|
598
|
+
str = str[1..-1]
|
599
|
+
|
600
|
+
if self.s_is_newline(first)
|
601
|
+
parserState.line += 1
|
602
|
+
parserState.column = 1
|
603
|
+
else
|
604
|
+
parserState.column += 1
|
605
|
+
end
|
606
|
+
|
607
|
+
# comment
|
608
|
+
elsif first == ';'
|
609
|
+
isTillNewline = true
|
610
|
+
|
611
|
+
if str.size >= 4
|
612
|
+
if str[0..3] == Expression::START_BLOCK_COMMENT
|
613
|
+
isTillNewline = false
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
endIndex = isTillNewline \
|
618
|
+
? str.index("\n") \
|
619
|
+
: str.index(Expression::END_BLOCK_COMMENT)
|
620
|
+
|
621
|
+
lengthToSkip = isTillNewline ? 1 : Expression::END_BLOCK_COMMENT.size
|
622
|
+
|
623
|
+
# move forward columns/rows as needed
|
624
|
+
parserState.move_forward_based_on_string(
|
625
|
+
str[0 .. (endIndex == nil ? (str.size - 1) : (endIndex + lengthToSkip - 1))]
|
626
|
+
)
|
627
|
+
|
628
|
+
if endIndex == nil or endIndex > (str.size - lengthToSkip)
|
629
|
+
str = "" # dead
|
630
|
+
else # slice
|
631
|
+
str = str[endIndex+lengthToSkip..-1] # skip the comment
|
632
|
+
end
|
633
|
+
else
|
634
|
+
break
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
return str
|
639
|
+
end
|
640
|
+
|
641
|
+
# will copy out the value of the string to a new buffer, will parse out quotes as needed
|
642
|
+
def self.s_create_value_of_string(str, parserState)
|
643
|
+
# two pass:
|
644
|
+
# first pass, get the length of the size
|
645
|
+
# second pass, store the buffer
|
646
|
+
|
647
|
+
bufferLength = 0
|
648
|
+
isQuotedString = false
|
649
|
+
isEscaped = false
|
650
|
+
pos = 0 # position we're parsing at
|
651
|
+
|
652
|
+
if str[0] == '"'
|
653
|
+
isQuotedString = true
|
654
|
+
pos += 1
|
655
|
+
end
|
656
|
+
|
657
|
+
while pos < str.size
|
658
|
+
c = str[pos]
|
659
|
+
|
660
|
+
if isQuotedString
|
661
|
+
if isEscaped
|
662
|
+
# we're in an escape. Is it valid?
|
663
|
+
if self.s_is_escape_valid(c)
|
664
|
+
bufferLength += 1 # counts
|
665
|
+
isEscaped = false # escape ended
|
666
|
+
else
|
667
|
+
raise InvalidStringEscapeError.new(parserState.line, parserState.column, "Invalid escape found in the string")
|
668
|
+
end
|
669
|
+
else
|
670
|
+
if c == '"'
|
671
|
+
# end quote, part of us
|
672
|
+
pos += 1
|
673
|
+
break
|
674
|
+
elsif c == "\\"
|
675
|
+
# we're escaping
|
676
|
+
isEscaped = true
|
677
|
+
else
|
678
|
+
# otherwise it's a character
|
679
|
+
bufferLength += 1
|
680
|
+
end
|
681
|
+
end
|
682
|
+
else
|
683
|
+
# have we ended the word?
|
684
|
+
if self.s_is_not_bareword_safe(c)
|
685
|
+
# ended - not part of us
|
686
|
+
break
|
687
|
+
end
|
688
|
+
|
689
|
+
# otherwise, its a character
|
690
|
+
bufferLength += 1
|
691
|
+
end
|
692
|
+
|
693
|
+
pos += 1
|
694
|
+
end
|
695
|
+
|
696
|
+
if bufferLength == 0 and !isQuotedString # cannot have an empty barewords string
|
697
|
+
raise EmptyStringError.new(parserState.line, parserState.column, "Was told to parse an empty string")
|
698
|
+
end
|
699
|
+
|
700
|
+
endVal = pos
|
701
|
+
|
702
|
+
# we now know our buffer size and the string has been checked
|
703
|
+
# ... not that we needed this in ruby
|
704
|
+
buffer = ""
|
705
|
+
writePos = 0
|
706
|
+
pos = 0
|
707
|
+
if isQuotedString
|
708
|
+
pos = 1
|
709
|
+
end
|
710
|
+
|
711
|
+
while writePos < bufferLength
|
712
|
+
c = str[pos]
|
713
|
+
|
714
|
+
if isQuotedString
|
715
|
+
if isEscaped
|
716
|
+
escapedValue = self.s_value_for_escape(c)
|
717
|
+
buffer[writePos] = escapedValue
|
718
|
+
writePos += 1
|
719
|
+
isEscaped = false
|
720
|
+
else
|
721
|
+
if c == "\\"
|
722
|
+
# we're escaping
|
723
|
+
isEscaped = true
|
724
|
+
else
|
725
|
+
# otherwise it's a character
|
726
|
+
buffer[writePos] = c
|
727
|
+
writePos += 1
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
else
|
732
|
+
# it's a character
|
733
|
+
buffer[writePos] = c
|
734
|
+
writePos += 1
|
735
|
+
end
|
736
|
+
|
737
|
+
# next character
|
738
|
+
pos += 1
|
739
|
+
end
|
740
|
+
|
741
|
+
return buffer, endVal
|
742
|
+
end
|
743
|
+
|
744
|
+
# returns information about a string
|
745
|
+
def self.s_wexpr_value_string_properties(ref)
|
746
|
+
props = {
|
747
|
+
:isbarewordsafe => true, # default to being safe
|
748
|
+
:needsescaping => false # but we don't need escaping
|
749
|
+
}
|
750
|
+
|
751
|
+
ref.each_char do |c|
|
752
|
+
# for now we cant escape so that stays false
|
753
|
+
# bareword safe we're just check for a few symbols
|
754
|
+
|
755
|
+
# see any symbols that makes it not bareword safe?
|
756
|
+
if self.s_is_not_bareword_safe(c)
|
757
|
+
props[:isbarewordsafe] = false
|
758
|
+
break
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
762
|
+
if ref.length == 0
|
763
|
+
props[:isbarewordsafe] = false # empty string is not safe since that will be nothing
|
764
|
+
end
|
765
|
+
|
766
|
+
return props
|
767
|
+
end
|
768
|
+
|
769
|
+
# returns the indent for the given amount
|
770
|
+
def self.s_indent(indent)
|
771
|
+
return "\t" * indent
|
772
|
+
end
|
773
|
+
|
774
|
+
# copy an expression into self. lhs should be null cause we dont cleanup ourself atm (ruby note: might not be true).
|
775
|
+
def p_copy_from(rhs)
|
776
|
+
# copy recursively
|
777
|
+
case rhs.type
|
778
|
+
when :value
|
779
|
+
self.change_type(:value)
|
780
|
+
self.value_set(rhs.value)
|
781
|
+
|
782
|
+
when :binarydata
|
783
|
+
self.change_type(:binarydata)
|
784
|
+
self.binarydata_set(rhs.binarydata)
|
785
|
+
|
786
|
+
when :array
|
787
|
+
self.change_type(:array)
|
788
|
+
|
789
|
+
c = rhs.array_count()
|
790
|
+
|
791
|
+
for i in 0..c-1
|
792
|
+
child = rhs.array_at(i)
|
793
|
+
childCopy = child.create_copy()
|
794
|
+
|
795
|
+
# add to our array
|
796
|
+
self.array_add_element_to_end(childCopy)
|
797
|
+
end
|
798
|
+
|
799
|
+
when :map
|
800
|
+
self.change_type(:map)
|
801
|
+
|
802
|
+
c = rhs.map_count()
|
803
|
+
|
804
|
+
for i in 0..c-1
|
805
|
+
key = rhs.map_key_at(i)
|
806
|
+
value = rhs.map_value_at(i)
|
807
|
+
|
808
|
+
# add to our map
|
809
|
+
self.map_set_value_for_key(key, value)
|
810
|
+
end
|
811
|
+
|
812
|
+
else
|
813
|
+
# ignore - we dont handle this type
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
# returns the part of the buffer remaining
|
818
|
+
# will load into self, setitng up everything. Assumes we're empty/null to start.
|
819
|
+
def p_parse_from_binary_chunk(data)
|
820
|
+
if data.size < (1 + SIZE_U8)
|
821
|
+
raise BinaryChunkNotBigEnoughError.new(0, 0, "Chunk not big enough for header: size was #{data.size}")
|
822
|
+
end
|
823
|
+
|
824
|
+
size, dataNew = UVLQ64::read(data)
|
825
|
+
sizeSize = (data.size - dataNew.size)
|
826
|
+
chunkType = data[sizeSize].unpack('C')[0]
|
827
|
+
readAmount = sizeSize + SIZE_U8
|
828
|
+
|
829
|
+
if chunkType == BIN_EXPRESSIONTYPE_NULL
|
830
|
+
# nothing more to do
|
831
|
+
self.change_type(:null)
|
832
|
+
|
833
|
+
return data[readAmount .. -1]
|
834
|
+
|
835
|
+
elsif chunkType == BIN_EXPRESSIONTYPE_VALUE
|
836
|
+
# data is the entire binary data
|
837
|
+
self.change_type(:value)
|
838
|
+
self.value_set(
|
839
|
+
data[readAmount...readAmount+size]
|
840
|
+
)
|
841
|
+
|
842
|
+
readAmount += size
|
843
|
+
|
844
|
+
return data[readAmount .. -1]
|
845
|
+
|
846
|
+
elsif chunkType == BIN_EXPRESSIONTYPE_ARRAY
|
847
|
+
# data is child chunks
|
848
|
+
self.change_type(:array)
|
849
|
+
|
850
|
+
curPos = 0
|
851
|
+
|
852
|
+
# build children as needed
|
853
|
+
while curPos < size
|
854
|
+
# read a new element
|
855
|
+
startSize = size-curPos
|
856
|
+
|
857
|
+
childExpr = Expression.create_invalid()
|
858
|
+
remaining = childExpr.p_parse_from_binary_chunk(
|
859
|
+
data[readAmount+curPos...readAmount+curPos+startSize]
|
860
|
+
)
|
861
|
+
|
862
|
+
if remaining == nil
|
863
|
+
# failure when parsing the array
|
864
|
+
raise Exception.new(0, 0, "Failure when parsing array")
|
865
|
+
end
|
866
|
+
|
867
|
+
curPos += (startSize - remaining.size)
|
868
|
+
|
869
|
+
# otherwise, add it
|
870
|
+
self.array_add_element_to_end(childExpr)
|
871
|
+
end
|
872
|
+
|
873
|
+
readAmount += curPos
|
874
|
+
return data[readAmount .. -1]
|
875
|
+
|
876
|
+
elsif chunkType == BIN_EXPRESSIONTYPE_MAP
|
877
|
+
# data is key, value chunks
|
878
|
+
self.change_type(:map)
|
879
|
+
|
880
|
+
curPos = 0
|
881
|
+
|
882
|
+
# build children as needed
|
883
|
+
while curPos < size
|
884
|
+
# read a new key
|
885
|
+
startSize = size - curPos
|
886
|
+
|
887
|
+
keyExpression = Expression.create_invalid()
|
888
|
+
remaining = keyExpression.p_parse_from_binary_chunk(
|
889
|
+
data[readAmount+curPos ... readAmount+curPos+startSize]
|
890
|
+
)
|
891
|
+
|
892
|
+
if remaining == nil
|
893
|
+
raise Exception.new(0, 0, "Failure when parsing map key")
|
894
|
+
end
|
895
|
+
|
896
|
+
keySize = startSize - remaining.size
|
897
|
+
curPos += keySize
|
898
|
+
|
899
|
+
# now parse the value
|
900
|
+
valueExpr = Expression.create_invalid()
|
901
|
+
remaining = valueExpr.p_parse_from_binary_chunk(
|
902
|
+
remaining
|
903
|
+
)
|
904
|
+
|
905
|
+
if remaining == nil
|
906
|
+
raise Exception.new(0, 0, "Failure when parsing map value")
|
907
|
+
end
|
908
|
+
|
909
|
+
curPos += (startSize - remaining.size - keySize)
|
910
|
+
|
911
|
+
# now add it
|
912
|
+
self.map_set_value_for_key(keyExpression.value, valueExpr)
|
913
|
+
end
|
914
|
+
|
915
|
+
readAmount += curPos
|
916
|
+
return data[readAmount .. -1]
|
917
|
+
|
918
|
+
elsif chunkType == BIN_EXPRESSIONTYPE_BINARYDATA
|
919
|
+
# data is the entire binary data
|
920
|
+
# first byte is the compression method
|
921
|
+
compression = data[readAmount].unpack('C')[0]
|
922
|
+
|
923
|
+
if compression == 0x0 # NONE
|
924
|
+
# simple and raw
|
925
|
+
self.change_type(:binarydata)
|
926
|
+
self.binarydata_set(
|
927
|
+
data[readAmount+1...readAmount+size]
|
928
|
+
)
|
929
|
+
|
930
|
+
readAmount += size
|
931
|
+
return data[readAmount .. -1]
|
932
|
+
else
|
933
|
+
raise Exception.new(0, 0, "Unknown compression method for binary data")
|
934
|
+
end
|
935
|
+
|
936
|
+
else
|
937
|
+
# unknown type
|
938
|
+
raise BinaryChunkNotBigEnoughError.new(0, 0, "Unknown chunk type to read: was #{chunkType}")
|
939
|
+
end
|
940
|
+
end
|
941
|
+
|
942
|
+
# returns the part of the string remaining
|
943
|
+
# will load into self, setting up everything. Assumes we're empty/null to start.
|
944
|
+
def p_parse_from_string(str, parseFlags, parserState)
|
945
|
+
if str.size == 0
|
946
|
+
raise EmptyStringError(parserState.line, parserState.column, "Was told to parse an empty string")
|
947
|
+
end
|
948
|
+
|
949
|
+
# now we parse
|
950
|
+
str = Expression.s_trim_front_of_string(str, parserState)
|
951
|
+
|
952
|
+
if str.size == 0
|
953
|
+
return "" # nothing left to parse
|
954
|
+
end
|
955
|
+
|
956
|
+
# start parsing types
|
957
|
+
# if first two characters are #(, we're an array
|
958
|
+
# if @( we're a map
|
959
|
+
# if [] we're a ref
|
960
|
+
# if <> we're a binary string
|
961
|
+
# otherwise we're a value.
|
962
|
+
if str.size >= 2 and str[0..1] == "#("
|
963
|
+
# we're an array
|
964
|
+
@type = :array
|
965
|
+
@array = []
|
966
|
+
|
967
|
+
# move our string forward
|
968
|
+
str = str[2..-1]
|
969
|
+
parserState.column += 2
|
970
|
+
|
971
|
+
# continue building children as needed
|
972
|
+
while true
|
973
|
+
str = Expression.s_trim_front_of_string(str, parserState)
|
974
|
+
|
975
|
+
if str.size == 0
|
976
|
+
raise ArrayMissingEndParenError.new(parserState.line, parserState.column, "An array was missing its ending paren")
|
977
|
+
end
|
978
|
+
|
979
|
+
if str[0] == ")"
|
980
|
+
break # done
|
981
|
+
else
|
982
|
+
# parse as a new expression
|
983
|
+
newExpression = Expression.create_null()
|
984
|
+
str = newExpression.p_parse_from_string(str, parseFlags, parserState)
|
985
|
+
|
986
|
+
# otherwise, add it to our array
|
987
|
+
@array << newExpression
|
988
|
+
end
|
989
|
+
end
|
990
|
+
|
991
|
+
str = str[1..-1] # remove the end array
|
992
|
+
parserState.column += 1
|
993
|
+
|
994
|
+
# done with array
|
995
|
+
return str
|
996
|
+
elsif str.size >= 2 and str[0..1] == "@("
|
997
|
+
# we're a map
|
998
|
+
@type = :map
|
999
|
+
@map = {}
|
1000
|
+
|
1001
|
+
# move our string accordingly
|
1002
|
+
str = str[2..-1]
|
1003
|
+
parserState.column += 2
|
1004
|
+
|
1005
|
+
# build our children as needed
|
1006
|
+
while true
|
1007
|
+
str = Expression.s_trim_front_of_string(str, parserState)
|
1008
|
+
|
1009
|
+
if str.size == 0
|
1010
|
+
raise MapMissingEndParenError.new(parserState.line, parserState.column, "A Map was missing its ending paren")
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
if str.size >= 1 and str[0] == ")" # end map
|
1014
|
+
break # done
|
1015
|
+
else
|
1016
|
+
# parse as a new expression - we'll alternate keys and values
|
1017
|
+
# keep our previous position just in case the value is bad
|
1018
|
+
prevLine = parserState.line
|
1019
|
+
prevColumn = parserState.column
|
1020
|
+
|
1021
|
+
keyExpression = Expression.create_null()
|
1022
|
+
str = keyExpression.p_parse_from_string(str, parseFlags, parserState)
|
1023
|
+
|
1024
|
+
if keyExpression.type != :value
|
1025
|
+
raise MapKeyMustBeAValueError.new(prevLine, prevColumn, "Map keys must be a value")
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
valueExpression = Expression.create_invalid()
|
1029
|
+
str = valueExpression.p_parse_from_string(str, parseFlags, parserState)
|
1030
|
+
|
1031
|
+
if valueExpression.type == :invalid
|
1032
|
+
# it wasn't filled in! no key found.
|
1033
|
+
# TODO: this changes the error from an upper level, so we'd need to catch and rethrow for this
|
1034
|
+
raise MapNoValueError.new(prevLine, prevColumn, "Map key must have a value")
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
# ok we have the key and the value
|
1038
|
+
self.map_set_value_for_key(keyExpression.value, valueExpression)
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
# remove the end map
|
1043
|
+
str = str[1..-1]
|
1044
|
+
parserState.column += 1
|
1045
|
+
|
1046
|
+
# done with map
|
1047
|
+
return str
|
1048
|
+
|
1049
|
+
elsif str.size >= 1 and str[0] == "["
|
1050
|
+
# the current expression being processed is the one the attribute will be linked to.
|
1051
|
+
|
1052
|
+
# process till the closing ]
|
1053
|
+
endingBracketIndex = str.index ']'
|
1054
|
+
if endingBracketIndex == nil
|
1055
|
+
raise ReferenceMissingEndBracketError.new(parserState.line, parserState.column, "A reference [] is missing its ending bracket")
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
refName = str[1..endingBracketIndex-1]
|
1059
|
+
|
1060
|
+
# validate the contents
|
1061
|
+
invalidName = false
|
1062
|
+
for i in 0..refName.size-1
|
1063
|
+
v = refName[i]
|
1064
|
+
|
1065
|
+
isAlpha = (v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z')
|
1066
|
+
isNumber = (v >= '0' && v <= '9')
|
1067
|
+
isUnder = (v == '_')
|
1068
|
+
|
1069
|
+
if i == 0 and (isAlpha || isUnder)
|
1070
|
+
elsif i != 0 and (isAlpha or isNumber or isUnder)
|
1071
|
+
else
|
1072
|
+
invalidName = true
|
1073
|
+
break
|
1074
|
+
end
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
if invalidName
|
1078
|
+
raise ReferenceInvalidNameError.new(parserState.line, parserState.column, "A reference doesn't have a valid name")
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
# move forward
|
1082
|
+
parserState.move_forward_based_on_string(str[0..endingBracketIndex+1-1])
|
1083
|
+
str = str[endingBracketIndex+1..-1]
|
1084
|
+
|
1085
|
+
# continue parsing at the same level : stored the reference name
|
1086
|
+
resultString = self.p_parse_from_string(str, parseFlags, parserState)
|
1087
|
+
|
1088
|
+
# now bind the ref - creating a copy of what was made. This will be used for the template.
|
1089
|
+
parserState.internalReferenceMap[refName] = self.create_copy()
|
1090
|
+
|
1091
|
+
# and continue
|
1092
|
+
return resultString
|
1093
|
+
|
1094
|
+
elsif str.size >= 2 and str[0..1] == "*["
|
1095
|
+
# parse the reference name
|
1096
|
+
endingBracketIndex = str.index ']'
|
1097
|
+
if endingBracketIndex == nil
|
1098
|
+
raise ReferenceInsertMissingEndBracketError.new(parserState.line, parserState.column, "A reference insert *[] is missing its ending bracket")
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
refName = str[2 .. endingBracketIndex-1]
|
1102
|
+
|
1103
|
+
# move forward
|
1104
|
+
parserState.move_forward_based_on_string(
|
1105
|
+
str[0 .. endingBracketIndex+1-1]
|
1106
|
+
)
|
1107
|
+
|
1108
|
+
str = str[endingBracketIndex+1 .. -1]
|
1109
|
+
|
1110
|
+
referenceExpr = parserState.internalReferenceMap[refName]
|
1111
|
+
|
1112
|
+
if referenceExpr == nil
|
1113
|
+
# try again with the external if we have it
|
1114
|
+
if parserState.externalReferenceMap != nil
|
1115
|
+
referenceExpr = parserState.externalReferenceMap[refName]
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
if referenceExpr == nil
|
1120
|
+
# not found
|
1121
|
+
raise ReferenceUnknownReferenceError.new(parserState.line, parserState.column, "Tried to insert a reference, but couldn't find it.")
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
# copy this into ourself
|
1125
|
+
self.p_copy_from(referenceExpr)
|
1126
|
+
|
1127
|
+
return str
|
1128
|
+
|
1129
|
+
# null expressions will be treated as a value and then parsed seperately
|
1130
|
+
|
1131
|
+
elsif str.size >= 1 and str[0] == "<"
|
1132
|
+
# look for the ending >
|
1133
|
+
endingQuote = str.index '>'
|
1134
|
+
if endingQuote == nil
|
1135
|
+
# not found
|
1136
|
+
raise BinaryDataNoEndingError.new(parserState.line, parserState.column, "Tried to find the ending > for binary data, but not found.")
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
# we require strict so we fail out on incorrect padding
|
1140
|
+
outBuf = Base64.strict_decode64(str[1 .. endingQuote-1]) # -1 for starting quote, ending was not part
|
1141
|
+
if outBuf == nil
|
1142
|
+
raise BinaryDataInvalidBase64Error.new(parserState.line, parserState.column, "Unable to decode the base64 data")
|
1143
|
+
end
|
1144
|
+
|
1145
|
+
@type = :binarydata
|
1146
|
+
@binarydata = outBuf
|
1147
|
+
|
1148
|
+
parserState.move_forward_based_on_string(str[0..endingQuote+1-1])
|
1149
|
+
|
1150
|
+
return str[endingQuote+1 .. -1]
|
1151
|
+
|
1152
|
+
elsif str.size >= 1 # its a value : must be at least one character
|
1153
|
+
val, endPos = Expression.s_create_value_of_string(str, parserState)
|
1154
|
+
|
1155
|
+
# was it a null/nil string?
|
1156
|
+
if val == "nil" or val == "null"
|
1157
|
+
@type = :null
|
1158
|
+
else
|
1159
|
+
@type = :value
|
1160
|
+
@value = val
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
parserState.move_forward_based_on_string(str[0..endPos-1])
|
1164
|
+
|
1165
|
+
return str[endPos .. -1]
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
# otherwise, we have no idea what happened
|
1169
|
+
return ""
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
# NOTE THESE BUFFERS ARE ACTUALLY MUTABLE
|
1173
|
+
#
|
1174
|
+
# Human Readable notes:
|
1175
|
+
# even though you pass an indent, we assume you're already indented for the start of the object
|
1176
|
+
# we assume this so that an object for example as a key-value will be writen in the correct spot.
|
1177
|
+
# if it writes multiple lines, we will use the given indent to predict.
|
1178
|
+
# it will end after writing all data, no newline generally at the end.
|
1179
|
+
#
|
1180
|
+
def p_append_string_representation_to_buffer(flags, indent, buffer)
|
1181
|
+
writeHumanReadable = flags.include?(:humanReadable)
|
1182
|
+
type = self.type()
|
1183
|
+
newBuf = buffer.clone
|
1184
|
+
|
1185
|
+
if type == :null
|
1186
|
+
newBuf += "null"
|
1187
|
+
return newBuf
|
1188
|
+
|
1189
|
+
elsif type == :value
|
1190
|
+
# value - always write directly
|
1191
|
+
v = self.value
|
1192
|
+
props = Expression.s_wexpr_value_string_properties(v)
|
1193
|
+
|
1194
|
+
if not props[:isbarewordsafe]
|
1195
|
+
newBuf += '"'
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
# do per character so we can escape as needed
|
1199
|
+
for c in v.chars
|
1200
|
+
if Expression.s_requires_escape(c)
|
1201
|
+
newBuf += "\\"
|
1202
|
+
newBuf += Expression.s_escape_for_value(c)
|
1203
|
+
else
|
1204
|
+
newBuf += c
|
1205
|
+
end
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
if not props[:isbarewordsafe]
|
1209
|
+
newBuf += '"'
|
1210
|
+
end
|
1211
|
+
|
1212
|
+
return newBuf
|
1213
|
+
|
1214
|
+
elsif type == :binarydata
|
1215
|
+
# binary data - encode as Base64
|
1216
|
+
v = Base64.strict_encode64(self.binarydata)
|
1217
|
+
|
1218
|
+
newBuf += "<#{v}>"
|
1219
|
+
|
1220
|
+
return newBuf
|
1221
|
+
|
1222
|
+
elsif type == :array
|
1223
|
+
arraySize = self.array_count
|
1224
|
+
|
1225
|
+
if arraySize == 0
|
1226
|
+
# straightforward : always empty structure
|
1227
|
+
newBuf += "#()"
|
1228
|
+
return newBuf
|
1229
|
+
end
|
1230
|
+
|
1231
|
+
# otherwise we have items
|
1232
|
+
|
1233
|
+
# array: human readable we'll write each one on its own line
|
1234
|
+
if writeHumanReadable
|
1235
|
+
newBuf += "#(\n"
|
1236
|
+
else
|
1237
|
+
newBuf += "#("
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
for i in 0..arraySize-1
|
1241
|
+
obj = self.array_at(i)
|
1242
|
+
|
1243
|
+
# if human readable we need to indent the line, output the object, then add a newline
|
1244
|
+
if writeHumanReadable
|
1245
|
+
newBuf += Expression.s_indent(indent+1)
|
1246
|
+
|
1247
|
+
# now add our normal
|
1248
|
+
newBuf = obj.p_append_string_representation_to_buffer(flags, indent+1, newBuf)
|
1249
|
+
|
1250
|
+
# add the newline
|
1251
|
+
newBuf += "\n"
|
1252
|
+
else
|
1253
|
+
# if not human readable, we need to either output theo bject, or put a space then the object
|
1254
|
+
if i > 0
|
1255
|
+
# we need a space
|
1256
|
+
newBuf += " "
|
1257
|
+
end
|
1258
|
+
|
1259
|
+
# now add our normal
|
1260
|
+
newBuf = obj.p_append_string_representation_to_buffer(flags, indent, newBuf)
|
1261
|
+
end
|
1262
|
+
end
|
1263
|
+
|
1264
|
+
# done with the core of the array
|
1265
|
+
# if human readable, indent and add the end array
|
1266
|
+
# otherwise, just add the end array
|
1267
|
+
if writeHumanReadable
|
1268
|
+
newBuf += Expression.s_indent(indent)
|
1269
|
+
end
|
1270
|
+
newBuf += ")"
|
1271
|
+
|
1272
|
+
# and done
|
1273
|
+
return newBuf
|
1274
|
+
|
1275
|
+
elsif type == :map
|
1276
|
+
mapSize = self.map_count()
|
1277
|
+
|
1278
|
+
if mapSize == 0
|
1279
|
+
# straightforward - always empty structure
|
1280
|
+
newBuf += "@()"
|
1281
|
+
return newBuf
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
# otherwise we have items
|
1285
|
+
|
1286
|
+
# map : human readable we'll write each one on its own line
|
1287
|
+
if writeHumanReadable
|
1288
|
+
newBuf += "@(\n"
|
1289
|
+
else
|
1290
|
+
newBuf += "@("
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
for i in 0..mapSize-1
|
1294
|
+
key = self.map_key_at(i)
|
1295
|
+
if key == nil
|
1296
|
+
next # we shouldnt ever get an empty key, but its possible currently in the case of dereffing in a key for some reason : @([a]a b *[a] c)
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
value = self.map_value_at(i)
|
1300
|
+
|
1301
|
+
# if human readable, indent the line, output the key, space, object, newline
|
1302
|
+
if writeHumanReadable
|
1303
|
+
newBuf += Expression.s_indent(indent+1)
|
1304
|
+
newBuf += key
|
1305
|
+
newBuf += " "
|
1306
|
+
|
1307
|
+
# add the value
|
1308
|
+
newBuf = value.p_append_string_representation_to_buffer(flags, indent+1, newBuf)
|
1309
|
+
|
1310
|
+
# add the newline
|
1311
|
+
newBuf += "\n"
|
1312
|
+
else
|
1313
|
+
# if not human readable, just output with spaces as needed
|
1314
|
+
if i > 0
|
1315
|
+
# we need a space
|
1316
|
+
newBuf += " "
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
# now key, space, value
|
1320
|
+
newBuf += key
|
1321
|
+
newBuf += " "
|
1322
|
+
|
1323
|
+
newBuf = value.p_append_string_representation_to_buffer(flags, indent, newBuf)
|
1324
|
+
end
|
1325
|
+
end
|
1326
|
+
|
1327
|
+
# done with the core of the map
|
1328
|
+
# if human readable, indent and add the end map
|
1329
|
+
# otherwise, just add the end map
|
1330
|
+
if writeHumanReadable
|
1331
|
+
newBuf += Expression.s_indent(indent)
|
1332
|
+
end
|
1333
|
+
|
1334
|
+
newBuf += ")"
|
1335
|
+
|
1336
|
+
# and done
|
1337
|
+
return newBuf
|
1338
|
+
|
1339
|
+
else
|
1340
|
+
raise Exception.new(0,0,"p_append_string_representation_to_buffer - Unknown type to generate string for: {}", type)
|
1341
|
+
end
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
START_BLOCK_COMMENT = ";(--"
|
1345
|
+
END_BLOCK_COMMENT = "--)"
|
1346
|
+
|
1347
|
+
# these are for porting sizeof() like operations over nicely
|
1348
|
+
SIZE_U8 = 1
|
1349
|
+
SIZE_U16 = 2
|
1350
|
+
SIZE_U32 = 4
|
1351
|
+
SIZE_U64 = 8
|
1352
|
+
|
1353
|
+
# type codes for binary
|
1354
|
+
BIN_EXPRESSIONTYPE_NULL = 0x00
|
1355
|
+
BIN_EXPRESSIONTYPE_VALUE = 0x01
|
1356
|
+
BIN_EXPRESSIONTYPE_ARRAY = 0x02
|
1357
|
+
BIN_EXPRESSIONTYPE_MAP = 0x03
|
1358
|
+
BIN_EXPRESSIONTYPE_BINARYDATA = 0x04
|
1359
|
+
BIN_EXPRESSIONTYPE_INVALID = 0xFF
|
1360
|
+
|
1361
|
+
# binarydata types
|
1362
|
+
BIN_BINARYDATA_RAW = 0x00 # no compression
|
1363
|
+
BIN_BINARYDATA_ZLIB = 0x01 # zlib deflate compression
|
1364
|
+
|
1365
|
+
# ----------------- MEMBERS ---------------
|
1366
|
+
|
1367
|
+
# @type - The type of the expression as a Symbol
|
1368
|
+
# @value - If @type == :value, the string value
|
1369
|
+
# @binarydata - If @type == :binarydata, the data
|
1370
|
+
# @array - If @type == :array, will be a ruby array containing the child Expressions.
|
1371
|
+
# @map - If @type == :map, will be a ruby hash containing teh child Expressions.
|
1372
|
+
|
1373
|
+
end
|
1374
|
+
end
|
1375
|
+
|