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.
@@ -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
+