class Mingle::MingleParser

Constants

QUANTS
RANGE_CLOSE
RANGE_OPEN

Public Class Methods

consume_string( s ) { |p| ... } click to toggle source
# File lib/mingle.rb, line 1673
def self.consume_string( s )
 
    p = self.for_string( s )
    yield( p ).tap { p.check_trailing }
end
for_string( s ) click to toggle source
# File lib/mingle.rb, line 1669
def self.for_string( s )
    self.new( :lexer => MingleLexer.as_instance( s ) )
end

Public Instance Methods

check_trailing() { || ... } click to toggle source
# File lib/mingle.rb, line 1262
def check_trailing
    
    res = yield if block_given?
    
    unless eof?

        tok, _ = read_tok
        fail_parse( "Unexpected token: #{tok}" )
    end

    res
end
expect_declared_type_name() click to toggle source
# File lib/mingle.rb, line 1366
def expect_declared_type_name
    expect_typed( DeclaredTypeName )
end
expect_identified_name() click to toggle source
# File lib/mingle.rb, line 1381
def expect_identified_name
    
    ns = expect_namespace

    names = []

    begin
        if tok = poll_special( SpecialToken::FORWARD_SLASH )
            names << expect_identifier
        end
    end while tok

    fail_parse( "Missing name", next_loc ) if names.empty?

    MingleIdentifiedName.send( :new, :namespace => ns, :names => names )
end
expect_identifier() click to toggle source
# File lib/mingle.rb, line 1342
def expect_identifier
    expect_typed( MingleIdentifier )
end
expect_namespace() click to toggle source
# File lib/mingle.rb, line 1347
def expect_namespace
 
    parts = [ expect_identifier ]

    begin
        if colon = poll_special( SpecialToken::COLON )
            parts << expect_identifier
        end
    end while colon

    poll_special( SpecialToken::ASPERAND ) or
        fail_unexpected_token( "':' or '@'" )

    ver = expect_identifier
    
    MingleNamespace.send( :new, :parts => parts, :version => ver )
end
expect_number() click to toggle source
# File lib/mingle.rb, line 1327
def expect_number
 
    tok, _ = peek_tok

    if neg = ( tok == SpecialToken::MINUS )
        read_tok
    end

    ParsedNumber.new(
        :negative => neg,
        :num => expect_typed( NumericToken )
    )
end
expect_qname() click to toggle source
# File lib/mingle.rb, line 1371
def expect_qname
    
    ns = expect_namespace
    expect_special( "type path", SpecialToken::FORWARD_SLASH )
    nm = expect_declared_type_name

    QualifiedTypeName.send( :new, :namespace => ns, :name => nm )
end
expect_type_reference() click to toggle source
# File lib/mingle.rb, line 1651
def expect_type_reference
    
    at = expect_atomic_type_reference
    
    poll_quants.inject( at ) do |typ, quant|
        case quant
        when SpecialToken::ASTERISK
            ListTypeReference.send( 
                :new, :element_type => typ, :allows_empty => true )
        when SpecialToken::PLUS
            ListTypeReference.send(
                :new, :element_type => typ, :allows_empty => false )
        when SpecialToken::QUESTION_MARK
            NullableTypeReference.send( :new, :type => typ )
        end
    end
end

Private Instance Methods

adjacent_ints?( min, max ) click to toggle source
# File lib/mingle.rb, line 1561
def adjacent_ints?( min, max )
    
    case min
    when MingleInt32, MingleInt64, MingleUint32, MingleUint64
        min.num.succ == max.num
    else false
    end
end
cast_range_value( val, nm, bound ) click to toggle source
# File lib/mingle.rb, line 1489
def cast_range_value( val, nm, bound )
    
    return nil if val.nil?

    typ = AtomicTypeReference.send( :new, :name => nm )

    unless COMPARABLE_TYPES.include?( typ )
        fail_restriction_type( typ, "range" )
    end

    check_allow_cast( val, typ, bound )

    # s is a StringToken or a ParsedNumber
    s = val.is_a?( StringToken ) ? val.val : val.external_form

    exec_cast( MingleString.new( s ), typ )
end
check_allow_cast( val, typ, bound ) click to toggle source
# File lib/mingle.rb, line 1454
def check_allow_cast( val, typ, bound )

    err_desc = nil

    case val
    when StringToken
        err_desc = "string" if NUM_TYPES.include?( typ )
    when ParsedNumber
        if NUM_TYPES.include?( typ )
            if INT_TYPES.include?( typ ) && ( ! val.integer? )
                err_desc = "decimal" 
            end
        else 
            err_desc = "number"
        end
    end

    if err_desc
        raise RestrictionTypeError.new( 
            "Got #{err_desc} as #{bound} value for range" )
    end
end
check_range_restriction_syntax( opts ) click to toggle source
# File lib/mingle.rb, line 1522
def check_range_restriction_syntax( opts )

    min_closed, min_sx, max_sx, max_closed = 
        opts.values_at( :min_closed, :min_syntax, :max_syntax, :max_closed )

    err_desc, loc =
        case
        when min_sx.nil? && max_sx.nil? && ( min_closed || max_closed )
            [ " ", opts[ :open_loc ] ]
        when min_sx.nil? && min_closed then [ " low ", opts[ :open_loc ] ]
        when max_sx.nil? && max_closed then [ " high ", opts[ :close_loc ] ]
        end

    fail_parse( "Infinite#{err_desc}range must be open", loc ) if loc
end
check_satisfiable_range( opts ) click to toggle source
# File lib/mingle.rb, line 1571
def check_satisfiable_range( opts )
    
    min_closed, min, max, max_closed = 
        opts.values_at( :min_closed, :min, :max, :max_closed )

    # If min,max are nil this is an infinite range and therefore
    # satisifiable (bounds will have been checked to be open elsewhere)
    return if min.nil? && max.nil? 

    cmp, failed = min <=> max, false

    case
    when cmp == 0 then failed = ! ( min_closed && max_closed )
    when cmp > 0 then failed = true
    else
        unless min_closed || max_closed
            failed = adjacent_ints?( min, max )
        end
    end

    raise RestrictionTypeError, "Unsatisfiable range" if failed
end
eof?() click to toggle source
# File lib/mingle.rb, line 1209
def eof?
    @saved == nil && @lexer.eof?
end
exec_cast( ms, typ ) click to toggle source
# File lib/mingle.rb, line 1478
def exec_cast( ms, typ )
    
    begin
        Mingle.cast_value( MingleString.new( ms ), typ )
    rescue MingleTimestamp::Rfc3339FormatError => e
        e = RestrictionTypeError.new( e.message ) if typ == TYPE_TIMESTAMP
        raise e
    end
end
expect_atomic_type_reference() click to toggle source
# File lib/mingle.rb, line 1621
def expect_atomic_type_reference
    
    nm = resolve_type_name( expect_type_name )
    
    skip_ws
    restr = nil

    if poll_special( SpecialToken::TILDE )
        skip_ws
        restr = expect_type_restriction( nm )
    end

    AtomicTypeReference.send( :new, :name => nm, :restriction => restr )
end
expect_pattern_restriction( nm ) click to toggle source
# File lib/mingle.rb, line 1427
def expect_pattern_restriction( nm )
    
    str, loc = expect_typed_loc( StringToken )

    if nm == QNAME_STRING
        begin
            RegexRestriction.new( :ext_pattern => str.val )
        rescue RegexpError => e
            raise RestrictionTypeError, "Invalid regex: #{e.message}"
        end
    else
        fail_restriction_type( nm, "regex" )
    end
end
expect_range_restriction( nm ) click to toggle source
# File lib/mingle.rb, line 1595
def expect_range_restriction( nm )
    
    opts = read_range_restriction_syntax( nm )
    check_range_restriction_syntax( opts )
 
    opts[ :min ] = cast_range_value( opts[ :min_syntax ], nm, "min" )
    opts[ :max ] = cast_range_value( opts[ :max_syntax ], nm, "max" )

    check_satisfiable_range( opts )

    RangeRestriction.new( opts )
end
expect_special_loc( desc, *spec ) click to toggle source
# File lib/mingle.rb, line 1289
def expect_special_loc( desc, *spec )
    
    res = poll_special_loc( *spec ) 

    if tok = res[ 0 ] 
        res
    else
        fail_unexpected_token( desc )
    end
end
expect_type_name() click to toggle source
# File lib/mingle.rb, line 1399
def expect_type_name

    tok, _ = peek_tok
    
    case tok
    when MingleIdentifier then expect_qname
    when DeclaredTypeName then expect_declared_type_name
    else fail_unexpected_token( "identifier or declared type name" )
    end
end
expect_type_restriction( nm ) click to toggle source
# File lib/mingle.rb, line 1609
def expect_type_restriction( nm )

    tok, _ = peek_tok

    case 
    when tok.is_a?( StringToken ) then expect_pattern_restriction( nm )
    when RANGE_OPEN.include?( tok ) then expect_range_restriction( nm )
    else fail_unexpected_token( "type restriction" )
    end
end
expect_typed( typ ) click to toggle source
# File lib/mingle.rb, line 1322
def expect_typed( typ )
    expect_typed_loc( typ )[ 0 ]
end
expect_typed_loc( typ ) click to toggle source

Does not allow for unread of its result

# File lib/mingle.rb, line 1308
def expect_typed_loc( typ )
    
    if @saved
        if ( id = @saved[ 0 ] ).is_a?( typ )
            @saved.tap { @saved = nil }
        else
            fail_parse( "Expected identifier" )
        end
    else
        @lexer.expect_token( typ )
    end
end
fail_parse( msg, err_loc = loc ) click to toggle source
# File lib/mingle.rb, line 1234
def fail_parse( msg, err_loc = loc )
    raise MingleParseError.new( :err => msg, :loc => err_loc )
end
fail_restriction_type( found, desc ) click to toggle source
# File lib/mingle.rb, line 1421
def fail_restriction_type( found, desc )
    raise RestrictionTypeError.new( 
        "Invalid target type for #{desc} restriction: #{found}" )
end
fail_unexpected_token( desc ) click to toggle source
# File lib/mingle.rb, line 1239
def fail_unexpected_token( desc )
    
    tok, err_loc = read_tok

    unless tok
        tok, err_loc = "END", @lexer.create_loc( 1 )
    end

    fail_parse( "Expected #{desc} but found: #{tok}", err_loc )
end
loc() click to toggle source
# File lib/mingle.rb, line 1199
def loc
    @saved ? @saved[ 1 ] : @lexer.create_loc
end
next_loc() click to toggle source
# File lib/mingle.rb, line 1204
def next_loc
    @saved ? @saved[ 1 ] : @lexer.create_loc( 1 )
end
peek_tok( typ = nil ) click to toggle source
# File lib/mingle.rb, line 1229
def peek_tok( typ = nil )
    read_tok.tap { |pair| unread_tok( *pair ) }
end
poll_quants() click to toggle source
# File lib/mingle.rb, line 1637
def poll_quants
    
    res = []

    begin
        if tok = poll_special( *QUANTS )
            res << tok
        end
    end while tok

    res
end
poll_special_loc( *specs ) click to toggle source
# File lib/mingle.rb, line 1276
def poll_special_loc( *specs )
    
    tok, loc = read_tok 

    unless specs.include?( tok )
        unread_tok( tok, loc )
        tok, loc = nil, nil
    end

    [ tok, loc ]
end
read_range_bound( err_desc, toks, close_test ) click to toggle source

Expects one of the tokens in toks, and returns the pair [ true, loc ] or [ false, loc ] based on whether the expected pair had the token matching close_test

# File lib/mingle.rb, line 1446
def read_range_bound( err_desc, toks, close_test )

    pair = expect_special_loc( err_desc, *toks )

    [ pair[ 0 ] == close_test, pair[ 1 ] ]
end
read_range_restriction_syntax( nm ) click to toggle source
# File lib/mingle.rb, line 1539
def read_range_restriction_syntax( nm )
    
    res = {}

    res[ :min_closed ], res[ :open_loc ] = 
        read_range_bound( 
            "range open", RANGE_OPEN, SpecialToken::OPEN_BRACKET )
 
    res[ :min_syntax ] = read_range_value( nm )
    skip_ws
    expect_special( ",", SpecialToken::COMMA )
    res[ :max_syntax ] = read_range_value( nm )
    skip_ws

    res[ :max_closed ], res[ :close_loc ] = 
        read_range_bound( 
            "range close", RANGE_CLOSE, SpecialToken::CLOSE_BRACKET )
    
    res
end
read_range_value( nm ) click to toggle source
# File lib/mingle.rb, line 1508
def read_range_value( nm )
    
    tok, _ = peek_tok

    val = case
    when tok == SpecialToken::COMMA || RANGE_CLOSE.include?( tok ) then nil
    when tok == SpecialToken::MINUS || tok.is_a?( NumericToken )
        expect_number
    when tok.is_a?( StringToken ) then expect_typed( StringToken )
    else fail_unexpected_token( "range value" )
    end
end
read_tok( typ = nil ) click to toggle source
# File lib/mingle.rb, line 1214
def read_tok( typ = nil )

    pair, @saved = @saved, nil
    pair || ( @lexer.eof? ? [ nil, nil ] : @lexer.read_token( typ ) )
end
resolve_type_name( nm ) click to toggle source
# File lib/mingle.rb, line 1411
def resolve_type_name( nm )
    
    if res = QNAME_RESOLV_MAP[ nm ]
        res
    else
        nm
    end
end
skip_ws() click to toggle source
# File lib/mingle.rb, line 1251
def skip_ws
    begin
        tok, loc = read_tok
        unless tok.is_a?( WhitespaceToken )
            unread_tok( tok, loc )
            tok = nil
        end
    end while tok
end
unread_tok( tok, loc ) click to toggle source
# File lib/mingle.rb, line 1221
def unread_tok( tok, loc )
    
    pair = [ tok, loc ]
    raise "Attempt to unread #{pair} while @saved is #@saved" if @saved
    @saved = tok ? pair : nil
end