class Wexpr::Expression

A wexpr expression - based off of libWexpr's API

Expression types:

Parse flags:

Write flags:

Constants

BIN_BINARYDATA_RAW

binarydata types

BIN_BINARYDATA_ZLIB
BIN_EXPRESSIONTYPE_ARRAY
BIN_EXPRESSIONTYPE_BINARYDATA
BIN_EXPRESSIONTYPE_INVALID
BIN_EXPRESSIONTYPE_MAP
BIN_EXPRESSIONTYPE_NULL

type codes for binary

BIN_EXPRESSIONTYPE_VALUE
END_BLOCK_COMMENT
SIZE_U16
SIZE_U32
SIZE_U64
SIZE_U8

these are for porting sizeof() like operations over nicely

START_BLOCK_COMMENT

Public Class Methods

create_from_binary_chunk(data) click to toggle source

Creates an expression from a binary chunk.

# File lib/wexpr/expression.rb, line 72
def self.create_from_binary_chunk(data)
        expr = Expression.create_invalid()
        
        expr.p_parse_from_binary_chunk(data)
        # result unused: remaining part of buffer
        
        return expr
end
create_from_ruby(variable) click to toggle source

Create from a ruby variable. Will convert as: If a nil value, will be a null expression. If an Array, will turn into an array expression (and recurse). If a Hash, will turn into a map expression (and recurse). If a String, and is UTF-8 safe, will turn into a value expression. 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. Otherwise, we just use .to_s and store that. Not sure if best, but we want to catch things like numbers.

# File lib/wexpr/expression.rb, line 132
def self.create_from_ruby(variable)
        case variable
                when NilClass
                        return Expression.create_null()
                        
                when Array
                        e = Expression.create_null()
                        e.change_type(:array)
                        
                        variable.each do |val|
                                e.array_add_element_to_end(
                                        Expression.create_from_ruby(val)
                                )
                        end
                        
                        return e
                        
                when Hash
                        e = Expression.create_null()
                        e.change_type(:map)
                        
                        variable.each do |k, v|
                                ve = Expression.create_from_ruby(v)
                                
                                # key could technically be anything, so convert to string
                                e.map_set_value_for_key(k.to_s, ve)
                        end
                        
                        return e
                        
                when String
                        varC = +variable # + creates a dup mutable string if frozen
                        isUTF8 = varC.force_encoding('UTF-8').valid_encoding?
                        
                        if isUTF8
                                return Expression.create_value(varC)
                        else
                                return Expression.create_binary_representation(variable)
                        end
                        
                else
                        # see if we support to_wexpr
                        # if so, decode it with :returnAsExpression
                        if variable.class.method_defined?(:to_wexpr) && variable.method(:to_wexpr).owner != Object
                                # use that method
                                return variable.to_wexpr([:returnAsExpression])
                        else
                                # might be a number or something else, to_s it
                                return Expression.create_value(variable.to_s)
                        end
        end
end
create_from_string(str, parseFlags=[]) click to toggle source

Create an expression from a string.

# File lib/wexpr/expression.rb, line 31
def self.create_from_string(str, parseFlags=[])
        return self.create_from_string_with_external_reference_table(str, parseFlags, {})
end
create_from_string_with_external_reference_table(str, parseFlags=[], referenceTable={}) click to toggle source

Create an expression from a string with an external reference table.

# File lib/wexpr/expression.rb, line 38
def self.create_from_string_with_external_reference_table(str, parseFlags=[], referenceTable={})
        expr = Expression.new()
        expr.change_type(:invalid)
        
        parserState = PrivateParserState.new()
        parserState.externalReferenceMap=referenceTable
        
        strC = str.dup  # + creates a dup mutable string if frozen
        
        # check that the string is valid UTF-8
        if strC.force_encoding('UTF-8').valid_encoding?
                # now start parsing
                rest = expr.p_parse_from_string(
                        strC, parseFlags, parserState
                )
                
                postRest = Expression.s_trim_front_of_string(rest, parserState)
                if postRest.size != 0
                        raise ExtraDataAfterParsingRootError.new(parserState.line, parserState.column, "Extra data after parsing the root expression: #{postRest}")
                end
                
                if expr.type == :invalid
                        raise EmptyStringError.new(parserState.line, parserState.column, "No expression found [remained invalid]")
                end
        else
                raise InvalidUTF8Error.new(0, 0, "Invalid UTF-8")
        end
        
        return expr
end
create_invalid() click to toggle source

Creates an empty invalid expression.

# File lib/wexpr/expression.rb, line 84
def self.create_invalid()
        expr = Expression.new()
        expr.change_type(:invalid)
        return expr
end
create_null() click to toggle source

Creates a null expression.

# File lib/wexpr/expression.rb, line 93
def self.create_null()
        expr = Expression.new()
        expr.change_type(:null)
        return expr
end
create_value(val) click to toggle source

Creates a value expression with the given string being the value.

# File lib/wexpr/expression.rb, line 102
def self.create_value(val)
        expr = Expression.create_null()
        if expr != nil
                expr.change_type(:value)
                expr.value_set(val)
        end
        
        return expr
end
s_create_value_of_string(str, parserState) click to toggle source

will copy out the value of the string to a new buffer, will parse out quotes as needed

# File lib/wexpr/expression.rb, line 649
def self.s_create_value_of_string(str, parserState)
        # two pass:
        # first pass, get the length of the size
        # second pass, store the buffer
        
        bufferLength = 0
        isQuotedString = false
        isEscaped = false
        pos = 0 # position we're parsing at
        
        if str[0] == '"'
                isQuotedString = true
                pos += 1
        end
        
        while pos < str.size
                c = str[pos]
                
                if isQuotedString
                        if isEscaped
                                # we're in an escape. Is it valid?
                                if self.s_is_escape_valid(c)
                                        bufferLength += 1 # counts
                                        isEscaped = false # escape ended
                                else
                                        raise InvalidStringEscapeError.new(parserState.line, parserState.column, "Invalid escape found in the string")
                                end
                        else
                                if c == '"'
                                        # end quote, part of us
                                        pos += 1
                                        break
                                elsif c == "\\"
                                        # we're escaping
                                        isEscaped = true
                                else
                                        # otherwise it's a character
                                        bufferLength += 1
                                end
                        end
                else
                        # have we ended the word?
                        if self.s_is_not_bareword_safe(c)
                                # ended - not part of us
                                break
                        end
                        
                        # otherwise, its a character
                        bufferLength += 1
                end
                
                pos += 1
        end
        
        if bufferLength == 0 and !isQuotedString # cannot have an empty barewords string
                raise EmptyStringError.new(parserState.line, parserState.column, "Was told to parse an empty string")
        end
        
        endVal = pos
        
        # we now know our buffer size and the string has been checked
        # ... not that we needed this in ruby
        buffer = ""
        writePos = 0
        pos = 0
        if isQuotedString
                pos = 1
        end
        
        while writePos < bufferLength
                c = str[pos]
        
                if isQuotedString
                        if isEscaped
                                escapedValue = self.s_value_for_escape(c)
                                buffer[writePos] = escapedValue
                                writePos += 1
                                isEscaped = false
                        else
                                if c == "\\"
                                        # we're escaping
                                        isEscaped = true
                                else
                                        # otherwise it's a character
                                        buffer[writePos] = c
                                        writePos += 1
                                end
                        end
                        
                else
                        # it's a character
                        buffer[writePos] = c
                        writePos += 1
                end
                
                # next character
                pos += 1
        end
        
        return buffer, endVal
end
s_escape_for_value(c) click to toggle source
# File lib/wexpr/expression.rb, line 582
def self.s_escape_for_value(c)
        return case c
                when '"' then '"'
                when "\r" then "r"
                when "\n" then "n"
                when "\t" then "t"
                when "\\" then "\\"
        else
                0 # invalid escape
        end
end
s_indent(indent) click to toggle source

returns the indent for the given amount

# File lib/wexpr/expression.rb, line 777
def self.s_indent(indent)
        return "\t" * indent
end
s_is_escape_valid(c) click to toggle source
# File lib/wexpr/expression.rb, line 562
def self.s_is_escape_valid(c)
        return (c == '"' || c == 'r' || c == 'n' || c == 't' || c == "\\")
end
s_is_newline(c) click to toggle source

——————– PRIVATE —————————-

# File lib/wexpr/expression.rb, line 538
def self.s_is_newline(c)
        return c == "\n"
end
s_is_not_bareword_safe(c) click to toggle source
# File lib/wexpr/expression.rb, line 548
def self.s_is_not_bareword_safe(c)
        return (c == '*'               \
                or c == '#'                \
                or c == '@'                \
                or c == '(' or c == ')'    \
                or c == '[' or c == ']'    \
                or c == '^'                \
                or c == '<' or c == '>'    \
                or c == '"'                \
                or c == ';'                \
                or self.s_is_whitespace(c) \
        )
end
s_is_whitespace(c) click to toggle source
# File lib/wexpr/expression.rb, line 542
def self.s_is_whitespace(c)
        # we put \r in whitespace and not newline so its counted as a column instead of a line, cause windows.
        # we dont support classic macos style newlines properly as a side effect.
        return (c == ' ' or c == "\t" or c == "\r" or self.s_is_newline(c))
end
s_requires_escape(c) click to toggle source
# File lib/wexpr/expression.rb, line 578
def self.s_requires_escape(c)
        return (c == '"' || c == "\r" || c == "\n" || c == "\t" || c == "\\")
end
s_trim_front_of_string(str, parserState) click to toggle source

trims the given string by removing whitespace or comments from the beginning of the string

# File lib/wexpr/expression.rb, line 595
def self.s_trim_front_of_string(str, parserState)
        while true
                if str.size == 0
                        return str
                end
                
                first = str[0]
                
                # skip whitespace
                if self.s_is_whitespace(first)
                        str = str[1..-1]
                        
                        if self.s_is_newline(first)
                                parserState.line += 1
                                parserState.column = 1
                        else
                                parserState.column += 1
                        end
                
                # comment
                elsif first == ';'
                        isTillNewline = true
                        
                        if str.size >= 4
                                if str[0..3] == Expression::START_BLOCK_COMMENT
                                        isTillNewline = false
                                end
                        end
                        
                        endIndex = isTillNewline \
                                ? str.index("\n") \
                                : str.index(Expression::END_BLOCK_COMMENT)
                        
                        lengthToSkip = isTillNewline ? 1 : Expression::END_BLOCK_COMMENT.size
                        
                        # move forward columns/rows as needed
                        parserState.move_forward_based_on_string(
                                str[0 .. (endIndex == nil ? (str.size - 1) : (endIndex + lengthToSkip - 1))]
                        )
                        
                        if endIndex == nil or endIndex > (str.size - lengthToSkip)
                                str = "" # dead
                        else # slice
                                str = str[endIndex+lengthToSkip..-1] # skip the comment
                        end
                else
                        break
                end
        end
        
        return str
end
s_value_for_escape(c) click to toggle source
# File lib/wexpr/expression.rb, line 566
def self.s_value_for_escape(c)
        return case c
                when '"' then '"'
                when 'r' then "\r"
                when 'n' then "\n"
                when 't' then "\t"
                when "\\" then "\\"
        else
                0 # invalid escape
        end
end
s_wexpr_value_string_properties(ref) click to toggle source

returns information about a string

# File lib/wexpr/expression.rb, line 752
def self.s_wexpr_value_string_properties(ref)
        props = {
                :isbarewordsafe => true, # default to being safe
                :needsescaping => false # but we don't need escaping
        }
        
        ref.each_char do |c|
                # for now we cant escape so that stays false
                # bareword safe we're just check for a few symbols
                
                # see any symbols that makes it not bareword safe?
                if self.s_is_not_bareword_safe(c)
                        props[:isbarewordsafe] = false
                        break
                end
        end
        
        if ref.length == 0
                props[:isbarewordsafe] = false # empty string is not safe since that will be nothing
        end
        
        return props
end
s_write_string_escaped(v, props) click to toggle source

Will write the string escaped, and with quotes around it if needed.

# File lib/wexpr/expression.rb, line 1183
def self.s_write_string_escaped(v, props)
        buf = ''

        if not props[:isbarewordsafe]
                buf += '"'
        end
                
        # do per character so we can escape as needed
        for c in v.chars
                if Expression.s_requires_escape(c)
                        buf += "\\"
                        buf += Expression.s_escape_for_value(c)
                else
                        buf += c
                end
        end
                
        if not props[:isbarewordsafe]
                buf += '"'
        end

        return buf
end

Public Instance Methods

[](index) click to toggle source

array operator for array and map

# File lib/wexpr/expression.rb, line 525
def [](index)
        if @type == :array
                return self.array_at(index)
        elsif @type == :map
                return self.map_value_for_key(index)
        end
        
        # wrong type
        return nil
end
array_add_element_to_end(element) click to toggle source

Add an element to the end of the array

# File lib/wexpr/expression.rb, line 407
def array_add_element_to_end(element)
        if @type != :array
                return nil
        end
        
        @array << element
end
array_at(index) click to toggle source

Return an object at the given index

# File lib/wexpr/expression.rb, line 396
def array_at(index)
        if @type != :array
                return nil
        end
        
        return @array[index]
end
array_count() click to toggle source

Return the length of the array

# File lib/wexpr/expression.rb, line 385
def array_count()
        if @type != :array
                return nil
        end
        
        return @array.count
end
binarydata() click to toggle source

Return the binary data of the expression. Will return nil if not binary data.

# File lib/wexpr/expression.rb, line 361
def binarydata()
        if @type != :binarydata
                return nil
        end
        
        return @binarydata
end
binarydata_set(buffer) click to toggle source

Set the binary data of the expression.

# File lib/wexpr/expression.rb, line 372
def binarydata_set(buffer)
        if @type != :binarydata
                return nil
        end
        
        @binarydata = buffer
end
change_type(type) click to toggle source

Change the type of the expression. Invalidates all data currently in the expression.

# File lib/wexpr/expression.rb, line 197
def change_type(type)
        # first destroy
        @value=nil; remove_instance_variable(:@value)
        @binarydata=nil; remove_instance_variable(:@binarydata)
        @array=nil; remove_instance_variable(:@array)
        @map=nil; remove_instance_variable(:@map)
        
        # then set
        @type = type
        
        # then init
        if @type == :value
                @value = "".freeze
        elsif @type == :binarydata
                @binarydata = ""
        elsif @type == :array
                @array = []
        elsif @type == :map
                @map = {}
        end
end
create_binary_representation() click to toggle source

Create the binary representation for the current chunk. This contains an expression chunk and all of its child chunks, but NOT the file header.

# File lib/wexpr/expression.rb, line 231
def create_binary_representation()
        buf = ""
        
        # format is:
        # size, type, data
        
        if @type == :null
                # data is 0x0
                buf += UVLQ64::write(0)
                buf += [BIN_EXPRESSIONTYPE_NULL].pack('C')
                
        elsif @type == :value
                val = self.value
                valLength = val.bytes.size
                
                buf += UVLQ64::write(valLength)
                buf += [BIN_EXPRESSIONTYPE_VALUE].pack('C')
                buf += val.bytes.pack('C*')
                
        elsif @type == :array
                # first pass, figure out the total size
                sizeOfArrayContents = 0
                
                for i in 0 ... self.array_count()
                        # TODO: dont create the entire representation twice - ability to ask for size
                        child = self.array_at(i)
                        childBuffer = child.create_binary_representation()
                        
                        sizeOfArrayContents += childBuffer.bytes.size
                end
                
                # header
                buf += UVLQ64::write(sizeOfArrayContents)
                buf += [BIN_EXPRESSIONTYPE_ARRAY].pack('C*')
                
                # data
                for i in 0 ... self.array_count()
                        child = self.array_at(i)
                        childBuffer = child.create_binary_representation()
                        
                        buf += childBuffer
                end
                
                # done
                
        elsif @type == :map
                # first pass, figure out the total size
                sizeOfMapContents = 0
                
                for i in 0 ... self.map_count()
                        # TODO: dont create the entire representation twice - ability to ask for sizes
                        mapKey = self.map_key_at(i)
                        mapKeyLen = mapKey.bytes.size
                        
                        # write the map key as a new value
                        keySizeSize = UVLQ64::byte_size(mapKeyLen)
                        keySize = keySizeSize + SIZE_U8 + mapKeyLen
                        sizeOfMapContents += keySize
                        
                        # write the map value
                        mapValue = self.map_value_at(i)
                        childBuffer = mapValue.create_binary_representation()
                        sizeOfMapContents += childBuffer.bytes.size
                end
                
                # second pass, write the header and pairs
                buf += UVLQ64::write(sizeOfMapContents)
                buf += [BIN_EXPRESSIONTYPE_MAP].pack('C')
                
                for i in 0 ... self.map_count()
                        mapKey = self.map_key_at(i)
                        mapKeyLen = mapKey.bytes.size
                        
                        # write the map key as a new value
                        buf += UVLQ64::write(mapKeyLen)
                        buf += [BIN_EXPRESSIONTYPE_VALUE].pack('C')
                        buf += mapKey.bytes.pack('C*')
                        
                        # write the map value
                        mapValue = self.map_value_at(i)
                        childBuffer = mapValue.create_binary_representation()
                        
                        buf += childBuffer
                end
                
                # done
        elsif @type == :binarydata
                binData = self.binarydata
                
                buf += UVLQ64::write(binData.bytes.size+1) # +1 for compression method
                buf += [BIN_EXPRESSIONTYPE_BINARYDATA].pack('C')
                buf += [BIN_BINARYDATA_RAW].pack('C') # for now we never compress
                buf += binData.bytes.pack('C*')
                 
        else
                raise Exception.new(0, 0, "Unknown type")
        end
        
        return buf
end
create_copy() click to toggle source

Create a copy of an expression. You own the copy - deep copy.

# File lib/wexpr/expression.rb, line 115
def create_copy()
        expr = Expression.create_null()
        
        expr.p_copy_from(self)
        
        return expr
end
create_string_representation(indent, writeFlags=[]) click to toggle source

Create a string which represents the expression.

# File lib/wexpr/expression.rb, line 222
def create_string_representation(indent, writeFlags=[])
        newBuf = self.p_append_string_representation_to_buffer(writeFlags, indent, "")
        
        return newBuf
end
map_count() click to toggle source

Return the number of elements in the map

# File lib/wexpr/expression.rb, line 420
def map_count()
        if @type != :map
                return nil
        end
        
        return @map.count
end
map_key_at(index) click to toggle source

Return the key at a given index

# File lib/wexpr/expression.rb, line 431
def map_key_at(index)
        if @type != :map
                return nil
        end
        
        return @map.keys[index]
end
map_set_value_for_key(key, value) click to toggle source

Set the value for a given key in the map. Will overwrite if already exists.

# File lib/wexpr/expression.rb, line 464
def map_set_value_for_key(key, value)
        if @type != :map
                return nil
        end
        
        @map[key] = value
end
map_value_at(index) click to toggle source

Return the value at a given index

# File lib/wexpr/expression.rb, line 442
def map_value_at(index)
        if @type != :map
                return nil
        end
        
        return @map.values[index]
end
map_value_for_key(key) click to toggle source

Return the value for a given key in the map

# File lib/wexpr/expression.rb, line 453
def map_value_for_key(key)
        if @type != :map
                return nil
        end
        
        return @map[key]
end
p_append_string_representation_to_buffer(flags, indent, buffer) click to toggle source

NOTE THESE BUFFERS ARE ACTUALLY MUTABLE

Human Readable notes: even though you pass an indent, we assume you're already indented for the start of the object we assume this so that an object for example as a key-value will be writen in the correct spot. if it writes multiple lines, we will use the given indent to predict. it will end after writing all data, no newline generally at the end.

# File lib/wexpr/expression.rb, line 1215
def p_append_string_representation_to_buffer(flags, indent, buffer)
        writeHumanReadable = flags.include?(:humanReadable)
        type = self.type()
        newBuf = buffer.clone
        
        if type == :null
                newBuf += "null"
                return newBuf
                
        elsif type == :value
                # value - always write directly
                v = self.value
                props = Expression.s_wexpr_value_string_properties(v)
                
                newBuf += Expression.s_write_string_escaped(v, props)
                
                return newBuf
                
        elsif type == :binarydata
                # binary data - encode as Base64
                v = Base64.strict_encode64(self.binarydata)
                
                newBuf += "<#{v}>"
                
                return newBuf
                
        elsif type == :array
                arraySize = self.array_count
                
                if arraySize == 0
                        # straightforward : always empty structure
                        newBuf += "#()"
                        return newBuf
                end
                
                # otherwise we have items
                
                # array: human readable we'll write each one on its own line
                if writeHumanReadable
                        newBuf += "#(\n"
                else
                        newBuf += "#("
                end
                
                for i in 0..arraySize-1
                        obj = self.array_at(i)
                        
                        # if human readable we need to indent the line, output the object, then add a newline
                        if writeHumanReadable
                                newBuf += Expression.s_indent(indent+1)
                                
                                # now add our normal
                                newBuf = obj.p_append_string_representation_to_buffer(flags, indent+1, newBuf)
                                
                                # add the newline
                                newBuf += "\n"
                        else
                                # if not human readable, we need to either output theo bject, or put a space then the object
                                if i > 0
                                        # we need a space
                                        newBuf += " "
                                end
                                
                                # now add our normal
                                newBuf = obj.p_append_string_representation_to_buffer(flags, indent, newBuf)
                        end
                end
                
                # done with the core of the array
                # if human readable, indent and add the end array
                # otherwise, just add the end array
                if writeHumanReadable
                        newBuf += Expression.s_indent(indent)
                end
                newBuf += ")"
                
                # and done
                return newBuf
                
        elsif type == :map
                mapSize = self.map_count()
                
                if mapSize == 0
                        # straightforward - always empty structure
                        newBuf += "@()"
                        return newBuf
                end
                
                # otherwise we have items
                
                # map : human readable we'll write each one on its own line
                if writeHumanReadable
                        newBuf += "@(\n"
                else
                        newBuf += "@("
                end
                
                for i in 0..mapSize-1
                        key = self.map_key_at(i)
                        if key == nil
                                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)
                        end
                        
                        value = self.map_value_at(i)
                        
                        # if human readable, indent the line, output the key, space, object, newline
                        if writeHumanReadable
                                newBuf += Expression.s_indent(indent+1)
                                newBuf += Expression.s_write_string_escaped(key, Expression.s_wexpr_value_string_properties(key))
                                newBuf += " "
                                
                                # add the value
                                newBuf = value.p_append_string_representation_to_buffer(flags, indent+1, newBuf)
                                
                                # add the newline
                                newBuf += "\n"
                        else
                                # if not human readable, just output with spaces as needed
                                if i > 0
                                        # we need a space
                                        newBuf += " "
                                end
                                
                                # now key, space, value
                                newBuf += Expression.s_write_string_escaped(key, Expression.s_wexpr_value_string_properties(key))
                                newBuf += " "
                                
                                newBuf = value.p_append_string_representation_to_buffer(flags, indent, newBuf)
                        end
                end
                
                # done with the core of the map
                # if human readable, indent and add the end map
                # otherwise, just add the end map
                if writeHumanReadable
                        newBuf += Expression.s_indent(indent)
                end
                
                newBuf += ")"
                
                # and done
                return newBuf

        else
                raise Exception.new(0,0,"p_append_string_representation_to_buffer - Unknown type to generate string for: {}", type)
        end
end
p_copy_from(rhs) click to toggle source

copy an expression into self. lhs should be null cause we dont cleanup ourself atm (ruby note: might not be true).

# File lib/wexpr/expression.rb, line 782
def p_copy_from(rhs)
        # copy recursively
        case rhs.type
                when :value
                        self.change_type(:value)
                        self.value_set(rhs.value)
                        
                when :binarydata
                        self.change_type(:binarydata)
                        self.binarydata_set(rhs.binarydata)
                        
                when :array
                        self.change_type(:array)
                        
                        c = rhs.array_count()
                        
                        for i in 0..c-1
                                child = rhs.array_at(i)
                                childCopy = child.create_copy()
                        
                                # add to our array
                                self.array_add_element_to_end(childCopy)
                        end
                        
                when :map
                        self.change_type(:map)
                        
                        c = rhs.map_count()
                        
                        for i in 0..c-1
                                key = rhs.map_key_at(i)
                                value = rhs.map_value_at(i)
                                
                                # add to our map
                                self.map_set_value_for_key(key, value)
                        end
                        
                else
                        # ignore - we dont handle this type
        end
end
p_parse_from_binary_chunk(data) click to toggle source

returns the part of the buffer remaining will load into self, setitng up everything. Assumes we're empty/null to start.

# File lib/wexpr/expression.rb, line 826
def p_parse_from_binary_chunk(data)
        if data.size < (1 + SIZE_U8) 
                raise BinaryChunkNotBigEnoughError.new(0, 0, "Chunk not big enough for header: size was #{data.size}")
        end
        
        size, dataNew = UVLQ64::read(data)
        sizeSize = (data.size - dataNew.size)
        chunkType = data[sizeSize].unpack('C')[0]
        readAmount = sizeSize + SIZE_U8
        
        if chunkType == BIN_EXPRESSIONTYPE_NULL
                # nothing more to do
                self.change_type(:null)
                
                return data[readAmount .. -1]
                
        elsif chunkType == BIN_EXPRESSIONTYPE_VALUE
                # data is the entire binary data
                self.change_type(:value)
                self.value_set(
                        data[readAmount...readAmount+size]
                )
                
                readAmount += size
                
                return data[readAmount .. -1]
                
        elsif chunkType == BIN_EXPRESSIONTYPE_ARRAY
                # data is child chunks
                self.change_type(:array)
                
                curPos = 0
                
                # build children as needed
                while curPos < size
                        # read a new element
                        startSize = size-curPos
                        
                        childExpr = Expression.create_invalid()
                        remaining = childExpr.p_parse_from_binary_chunk(
                                data[readAmount+curPos...readAmount+curPos+startSize]
                        )
                        
                        if remaining == nil
                                # failure when parsing the array
                                raise Exception.new(0, 0, "Failure when parsing array")
                        end
                        
                        curPos += (startSize - remaining.size)
                        
                        # otherwise, add it
                        self.array_add_element_to_end(childExpr)
                end
                
                readAmount += curPos
                return data[readAmount .. -1]
                
        elsif chunkType == BIN_EXPRESSIONTYPE_MAP
                # data is key, value chunks
                self.change_type(:map)
                
                curPos = 0
                
                # build children as needed
                while curPos < size
                        # read a new key
                        startSize = size - curPos
                        
                        keyExpression = Expression.create_invalid()
                        remaining = keyExpression.p_parse_from_binary_chunk(
                                data[readAmount+curPos ... readAmount+curPos+startSize]
                        )
                        
                        if remaining == nil
                                raise Exception.new(0, 0, "Failure when parsing map key")
                        end
                        
                        keySize = startSize - remaining.size
                        curPos += keySize
                        
                        # now parse the value
                        valueExpr = Expression.create_invalid()
                        remaining = valueExpr.p_parse_from_binary_chunk(
                                remaining
                        )
                        
                        if remaining == nil
                                raise Exception.new(0, 0, "Failure when parsing map value")
                        end
                        
                        curPos += (startSize - remaining.size - keySize)
                        
                        # now add it
                        self.map_set_value_for_key(keyExpression.value, valueExpr)
                end
                
                readAmount += curPos
                return data[readAmount .. -1]
                
        elsif chunkType == BIN_EXPRESSIONTYPE_BINARYDATA
                # data is the entire binary data
                # first byte is the compression method
                compression = data[readAmount].unpack('C')[0]
                
                if compression == 0x0 # NONE
                        # simple and raw
                        self.change_type(:binarydata)
                        self.binarydata_set(
                                data[readAmount+1...readAmount+size]
                        )
                        
                        readAmount += size
                        return data[readAmount .. -1]
                else
                        raise Exception.new(0, 0, "Unknown compression method for binary data")
                end
                
        else
                # unknown type
                raise BinaryChunkNotBigEnoughError.new(0, 0, "Unknown chunk type to read: was #{chunkType}")
        end
end
p_parse_from_string(str, parseFlags, parserState) click to toggle source

returns the part of the string remaining will load into self, setting up everything. Assumes we're empty/null to start.

# File lib/wexpr/expression.rb, line 951
def p_parse_from_string(str, parseFlags, parserState)
        if str.size == 0
                raise EmptyStringError(parserState.line, parserState.column, "Was told to parse an empty string")
        end
        
        # now we parse
        str = Expression.s_trim_front_of_string(str, parserState)
        
        if str.size == 0
                return "" # nothing left to parse
        end
        
        # start parsing types
        # if first two characters are #(, we're an array
        # if @( we're a map
        # if [] we're a ref
        # if <> we're a binary string
        # otherwise we're a value.
        if str.size >= 2 and str[0..1] == "#("
                # we're an array
                @type = :array
                @array = []
                
                # move our string forward
                str = str[2..-1]
                parserState.column += 2
                
                # continue building children as needed
                while true
                        str = Expression.s_trim_front_of_string(str, parserState)
                        
                        if str.size == 0
                                raise ArrayMissingEndParenError.new(parserState.line, parserState.column, "An array was missing its ending paren")
                        end
                        
                        if str[0] == ")"
                                break # done
                        else
                                # parse as a new expression
                                newExpression = Expression.create_null()
                                str = newExpression.p_parse_from_string(str, parseFlags, parserState)
                                
                                # otherwise, add it to our array
                                @array << newExpression
                        end
                end
                
                str = str[1..-1] # remove the end array
                parserState.column += 1
                
                # done with array
                return str
        elsif str.size >= 2 and str[0..1] == "@("
                # we're a map
                @type = :map
                @map = {}
                
                # move our string accordingly
                str = str[2..-1]
                parserState.column += 2
                
                # build our children as needed
                while true
                        str = Expression.s_trim_front_of_string(str, parserState)
                        
                        if str.size == 0
                                raise MapMissingEndParenError.new(parserState.line, parserState.column, "A Map was missing its ending paren")
                        end
                        
                        if str.size >= 1 and str[0] == ")" # end map
                                break # done
                        else
                                # parse as a new expression - we'll alternate keys and values
                                # keep our previous position just in case the value is bad
                                prevLine = parserState.line
                                prevColumn = parserState.column
                                
                                keyExpression = Expression.create_null()
                                str = keyExpression.p_parse_from_string(str, parseFlags, parserState)
                                
                                if keyExpression.type != :value
                                        raise MapKeyMustBeAValueError.new(prevLine, prevColumn, "Map keys must be a value")
                                end
                                
                                valueExpression = Expression.create_invalid()
                                str = valueExpression.p_parse_from_string(str, parseFlags, parserState)
                                
                                if valueExpression.type == :invalid
                                        # it wasn't filled in! no key found.
                                        # TODO: this changes the error from an upper level, so we'd need to catch and rethrow for this
                                        raise MapNoValueError.new(prevLine, prevColumn, "Map key must have a value")
                                end
                                
                                # ok we have the key and the value
                                self.map_set_value_for_key(keyExpression.value, valueExpression)
                        end
                end
                
                # remove the end map
                str = str[1..-1]
                parserState.column += 1
                
                # done with map
                return str
                
        elsif str.size >= 1 and str[0] == "["
                # the current expression being processed is the one the attribute will be linked to.
                
                # process till the closing ]
                endingBracketIndex = str.index ']'
                if endingBracketIndex == nil
                        raise ReferenceMissingEndBracketError.new(parserState.line, parserState.column, "A reference [] is missing its ending bracket")
                end
                
                refName = str[1..endingBracketIndex-1]
                
                # validate the contents
                invalidName = false
                for i in 0..refName.size-1
                        v = refName[i]
                        
                        isAlpha = (v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z')
                        isNumber = (v >= '0' && v <= '9')
                        isUnder = (v == '_')
                        
                        if i == 0 and (isAlpha || isUnder)
                        elsif i != 0 and (isAlpha or isNumber or isUnder)
                        else
                                invalidName = true
                                break
                        end
                end
                
                if invalidName
                        raise ReferenceInvalidNameError.new(parserState.line, parserState.column, "A reference doesn't have a valid name")
                end
                
                # move forward
                parserState.move_forward_based_on_string(str[0..endingBracketIndex+1-1])
                str = str[endingBracketIndex+1..-1]
                
                # continue parsing at the same level : stored the reference name
                resultString = self.p_parse_from_string(str, parseFlags, parserState)
                
                # now bind the ref - creating a copy of what was made. This will be used for the template.
                parserState.internalReferenceMap[refName] = self.create_copy()
                
                # and continue
                return resultString
                
        elsif str.size >= 2 and str[0..1] == "*["
                # parse the reference name
                endingBracketIndex = str.index ']'
                if endingBracketIndex == nil
                        raise ReferenceInsertMissingEndBracketError.new(parserState.line, parserState.column, "A reference insert *[] is missing its ending bracket")
                end
                
                refName = str[2 .. endingBracketIndex-1]
                
                # move forward
                parserState.move_forward_based_on_string(
                        str[0 .. endingBracketIndex+1-1]
                )
                
                str = str[endingBracketIndex+1 .. -1]
                
                referenceExpr = parserState.internalReferenceMap[refName]
                
                if referenceExpr == nil
                        # try again with the external if we have it
                        if parserState.externalReferenceMap != nil
                                referenceExpr = parserState.externalReferenceMap[refName]
                        end
                end
                
                if referenceExpr == nil
                        # not found
                        raise ReferenceUnknownReferenceError.new(parserState.line, parserState.column, "Tried to insert a reference, but couldn't find it.")
                end
                
                # copy this into ourself
                self.p_copy_from(referenceExpr)
                
                return str
                
        # null expressions will be treated as a value and then parsed seperately
                
        elsif str.size >= 1 and str[0] == "<"
                # look for the ending >
                endingQuote = str.index '>'
                if endingQuote == nil
                        # not found
                        raise BinaryDataNoEndingError.new(parserState.line, parserState.column, "Tried to find the ending > for binary data, but not found.")
                end

                # we require strict so we fail out on incorrect padding
                outBuf = Base64.strict_decode64(str[1 .. endingQuote-1]) # -1 for starting quote, ending was not part
                if outBuf == nil
                        raise BinaryDataInvalidBase64Error.new(parserState.line, parserState.column, "Unable to decode the base64 data")
                end
                
                @type = :binarydata
                @binarydata = outBuf
                
                parserState.move_forward_based_on_string(str[0..endingQuote+1-1])
                
                return str[endingQuote+1 .. -1]
                
        elsif str.size >= 1 # its a value : must be at least one character
                val, endPos = Expression.s_create_value_of_string(str, parserState)
                
                # was it a null/nil string?
                if val == "nil" or val == "null"
                        @type = :null
                else
                        @type = :value
                        @value = val.dup.freeze
                end
                
                parserState.move_forward_based_on_string(str[0..endPos-1])
                
                return str[endPos .. -1]
        end
        
        # otherwise, we have no idea what happened
        return ""
end
to_ruby() click to toggle source

Convert to a ruby type. Types will convert as follows:

  • :null - Returns nil

  • :value - Returns the value as a string.

  • :array - Returns as an array.

  • :map - Returns as a hash.

  • :binarydata - Returns the binary data as a ruby string.

  • :invalid - Throws an exception

# File lib/wexpr/expression.rb, line 491
def to_ruby()
        case @type
                when :null
                        return nil
                        
                when :value
                        return @value
                        
                when :array
                        a=[]
                        for i in 0..self.array_count-1
                                a << self.array_at(i).to_ruby
                        end
                        return a
                        
                when :map
                        m={}
                        for i in 0..self.map_count-1
                                k = self.map_key_at(i)
                                v = self.map_value_at(i)
                                
                                m[k] = v.to_ruby
                        end
                        
                        return m
                when :binarydata
                        # um, direct i guess - its raw/pure data
                        return @binarydata
                else
                        raise Exception.new(0,0,"Invalid type to convert to ruby: #{@type}")
        end
end
to_s() click to toggle source

Output as a string - we do a compact style

# File lib/wexpr/expression.rb, line 477
def to_s()
        return self.create_string_representation(0, [])
end
type() click to toggle source

Return the type of the expression

# File lib/wexpr/expression.rb, line 190
def type()
        return @type
end
value() click to toggle source

Return the value of the expression. Will return nil if not a value

# File lib/wexpr/expression.rb, line 337
def value()
        if @type != :value
                return nil
        end
        
        return @value
end
value_set(str) click to toggle source

Set the value of the expression.

# File lib/wexpr/expression.rb, line 348
def value_set(str)
        if @type != :value
                return
        end
        
        @value = str.dup.freeze
end