Example #1
0
def test_iterators():
    i				= cpppo.chaining()
    j				= cpppo.chainable( i )
    k				= cpppo.chainable( 'abc' )
    assert i is j
    try:
        next( i )
        assert False, "stream with no iterable should raise StopIteration"
    except StopIteration:
        pass
    assert k is not j
    assert isinstance( k, cpppo.chaining )

    assert cpppo.peekable( i ) is i
    p				= cpppo.peekable( '123' )
    assert cpppo.peekable( p ) is p
    assert cpppo.chainable( p ) is not p
    assert list( p ) == ['1', '2', '3']
    assert p.sent == 3
    p.push('x')
    assert p.sent == 2
    assert list( p ) == ['x']
    assert list( p ) == []


    i.chain('abc')
    i.chain('')
    i.chain( '123' )
    assert list( i ) == ['a','b','c','1','2','3']
    assert i.sent == 6

    i.chain( 'y' )
    i.push( 'x' )
    assert list( i ) == ['x','y']

    i.chain( None )
    try:
        next( i )
        assert False, "Expected TypeError to be raised"
    except TypeError:
        pass
    except Exception as e:
        assert False, "Expected TypeError, not %r" % ( e )


    r				= cpppo.rememberable( '123' )
    assert next( r ) == '1'
    assert r.memory == [ '1' ]
    try:
        r.push( 'x' )
        assert False, "Should have rejected push of inconsistent symbol"
    except AssertionError:
        pass
    assert r.sent == 1
    r.push( '1' )
    assert r.sent == 0
    assert r.memory == []
    assert list( r ) == r.memory == [ '1', '2','3' ]
Example #2
0
def test_iterators():
    i = cpppo.chaining()
    j = cpppo.chainable(i)
    k = cpppo.chainable("abc")
    assert i is j
    try:
        next(i)
        assert False, "stream with no iterable should raise StopIteration"
    except StopIteration:
        pass
    assert k is not j
    assert isinstance(k, cpppo.chaining)

    assert cpppo.peekable(i) is i
    p = cpppo.peekable("123")
    assert cpppo.peekable(p) is p
    assert cpppo.chainable(p) is not p
    assert list(p) == ["1", "2", "3"]
    assert p.sent == 3
    p.push("x")
    assert p.sent == 2
    assert list(p) == ["x"]
    assert list(p) == []

    i.chain("abc")
    i.chain("")
    i.chain("123")
    assert list(i) == ["a", "b", "c", "1", "2", "3"]
    assert i.sent == 6

    i.chain("y")
    i.push("x")
    assert list(i) == ["x", "y"]

    i.chain(None)
    try:
        next(i)
        assert False, "Expected TypeError to be raised"
    except TypeError:
        pass
    except Exception as e:
        assert False, "Expected TypeError, not %r" % (e)

    r = cpppo.rememberable("123")
    assert next(r) == "1"
    assert r.memory == ["1"]
    try:
        r.push("x")
        assert False, "Should have rejected push of inconsistent symbol"
    except AssertionError:
        pass
    assert r.sent == 1
    r.push("1")
    assert r.sent == 0
    assert r.memory == []
    assert list(r) == r.memory == ["1", "2", "3"]
Example #3
0
def echo_server( conn, addr ):
    """Serve one echo client 'til EOF; then close the socket"""
    source			= cpppo.chainable()
    with echo_machine( "echo_%s" % addr[1] ) as echo_line:
        eof			= False
        while not eof:
            data		= cpppo.dotdict()
            # See if a line has been recognized, stopping at terminal state.  If this machine
            # is ended early due to an EOF, it should still terminate in a terminal state
            for mch, sta in echo_line.run( source=source, data=data ):
                if sta is not None:
                    continue
                # Non-transition; check for input, blocking if non-terminal and none left.  On
                # EOF, terminate early; this will raise a GeneratorExit.
                timeout		= 0 if echo_line.terminal or source.peek() is not None else None
                msg		= network.recv( conn, timeout=timeout )
                if msg is not None:
                    eof		= not len( msg )
                    log.info( "%s recv: %5d: %s", echo_line.name_centered(), len( msg ),
                              "EOF" if eof else cpppo.reprlib.repr( msg ))
                    source.chain( msg )
                    if eof:
                        break
            # Terminal state (or EOF).
            log.detail( "%s: byte %5d: data: %r", echo_line.name_centered(), source.sent, data )
            if echo_line.terminal:
                conn.send( data.echo )
        
        log.info( "%s done", echo_line.name_centered() )
Example #4
0
def test_tnet_machinery():
    # parsing integers
    path = "machinery"
    data = cpppo.dotdict()
    source = cpppo.chainable(b'123:')
    with cpppo.integer_bytes(name="SIZE", context="size",
                             terminal=True) as SIZE:
        with contextlib.closing(SIZE.run(source=source, data=data,
                                         path=path)) as engine:
            for m, s in engine:
                if s is None:
                    break
        log.info("After SIZE: %r", data)
        assert SIZE.terminal
    assert data.machinery.size == 123

    # repeat, limited by parent context's 'value' in data
    source.chain(b"abc" * 123)
    with tnet.data_parser(name="DATA", context="data",
                          repeat="..size") as DATA:
        with contextlib.closing(DATA.run(source=source, data=data,
                                         path=path)) as engine:
            for m, s in engine:
                if s is None:
                    break
        log.info("After DATA: %r", data)
Example #5
0
File: tnet.py Project: ywong3/cpppo
def tnet_server( conn, addr ):
    """Serve one tnet client 'til EOF; then close the socket"""
    source			= cpppo.chainable()
    with tnet_machine( "tnet_%s" % addr[1] ) as tnet_mesg:
        eof			= False
        while not eof:
            data		= cpppo.dotdict()
            # Loop blocking for input, while we've consumed input from source since the last time.
            # If we hit this again without having used any input, we know we've hit a symbol
            # unacceptable to the state machine; stop
            for mch, sta in tnet_mesg.run( source=source, data=data ):
                if sta is not None:
                    continue
                # Non-transition; check for input, blocking if non-terminal and none left.  On
                # EOF, terminate early; this will raise a GeneratorExit.
                timeout		= 0 if tnet_mesg.terminal or source.peek() is not None else None
                msg		= network.recv( conn, timeout=timeout ) # blocking
                if msg is not None:
                    eof		= not len( msg )
                    log.info( "%s: recv: %5d: %s", tnet_mesg.name_centered(), len( msg ),
                              "EOF" if eof else cpppo.reprlib.repr( msg )) 
                    source.chain( msg )
                    if eof:
                        break

            # Terminal state (or EOF).
            log.detail( "%s: byte %5d: data: %r", tnet_mesg.name_centered(), source.sent, data )
            if tnet_mesg.terminal:
                res			= json.dumps( data.tnet.type.input, indent=4, sort_keys=True )
                conn.send(( res + "\n\n" ).encode( "utf-8" ))
    
        log.info( "%s done", tnet_mesg.name_centered() )
Example #6
0
def tnet_server( conn, addr ):
    """Serve one tnet client 'til EOF; then close the socket"""
    source			= cpppo.chainable()
    with tnet_machine( "tnet_%s" % addr[1] ) as tnet_mesg:
        eof			= False
        while not eof:
            data		= cpppo.dotdict()
            # Loop blocking for input, while we've consumed input from source since the last time.
            # If we hit this again without having used any input, we know we've hit a symbol
            # unacceptable to the state machine; stop
            for mch, sta in tnet_mesg.run( source=source, data=data ):
                if sta is not None:
                    continue
                # Non-transition; check for input, blocking if non-terminal and none left.  On
                # EOF, terminate early; this will raise a GeneratorExit.
                timeout		= 0 if tnet_mesg.terminal or source.peek() is not None else None
                msg		= network.recv( conn, timeout=timeout ) # blocking
                if msg is not None:
                    eof		= not len( msg )
                    log.info( "%s: recv: %5d: %s", tnet_mesg.name_centered(), len( msg ),
                              "EOF" if eof else reprlib.repr( msg )) 
                    source.chain( msg )
                    if eof:
                        break

            # Terminal state (or EOF).
            log.detail( "%s: byte %5d: data: %r", tnet_mesg.name_centered(), source.sent, data )
            if tnet_mesg.terminal:
                res			= json.dumps( data.tnet.type.input, indent=4, sort_keys=True )
                conn.send(( res + "\n\n" ).encode( "utf-8" ))
    
        log.info( "%s done", tnet_mesg.name_centered() )
Example #7
0
File: echo.py Project: wotori/cpppo
def echo_server(conn, addr):
    """Serve one echo client 'til EOF; then close the socket"""
    source = cpppo.chainable()
    with echo_machine("echo_%s" % addr[1]) as echo_line:
        eof = False
        while not eof:
            data = cpppo.dotdict()
            # See if a line has been recognized, stopping at terminal state.  If this machine
            # is ended early due to an EOF, it should still terminate in a terminal state
            for mch, sta in echo_line.run(source=source, data=data):
                if sta is not None:
                    continue
                # Non-transition; check for input, blocking if non-terminal and none left.  On
                # EOF, terminate early; this will raise a GeneratorExit.
                timeout = 0 if echo_line.terminal or source.peek(
                ) is not None else None
                msg = network.recv(conn, timeout=timeout)
                if msg is not None:
                    eof = not len(msg)
                    log.info("%s recv: %5d: %s", echo_line.name_centered(),
                             len(msg),
                             "EOF" if eof else cpppo.reprlib.repr(msg))
                    source.chain(msg)
                    if eof:
                        break
            # Terminal state (or EOF).
            log.detail("%s: byte %5d: data: %r", echo_line.name_centered(),
                       source.sent, data)
            if echo_line.terminal:
                conn.send(data.echo)

        log.info("%s done", echo_line.name_centered())
Example #8
0
def test_struct():
    dtp				= cpppo.type_bytes_array_symbol
    abt				= cpppo.type_bytes_iter
    ctx				= 'val'
    a				= cpppo.state_input( "First",  alphabet=abt, typecode=dtp, context=ctx )
    a[True] = b 		= cpppo.state_input( "Second", alphabet=abt, typecode=dtp, context=ctx )
    b[True] = c 		= cpppo.state_input( "Third",  alphabet=abt, typecode=dtp, context=ctx )
    c[True] = d			= cpppo.state_input( "Fourth", alphabet=abt, typecode=dtp, context=ctx )
    d[None] 			= cpppo.state_struct( "int32", context=ctx,
                                                      format=str("<i"),
                                                      terminal=True )
    machine			= cpppo.dfa( initial=a )
    with machine:
        material		= b'\x01\x02\x03\x80\x99'
        segment			= 3
        source			= cpppo.chainable()
        log.info( "States; %r input, by %d", material, segment )
        inp			= None
        data			= cpppo.dotdict()
        path			= "struct"
        sequence		= machine.run( source=source, path=path, data=data )
        for num in range( 10 ):
            try:
                mch,sta		= next( sequence )
                inp		= source.peek()
            except StopIteration:
                inp		= source.peek()
                log.info( "%s <- %-10.10r test done", cpppo.centeraxis( mch, 25, clip=True ), inp )
                break
            log.info( "%s <- %-10.10r test rcvd", cpppo.centeraxis( mch, 25, clip=True ), inp )
            if sta is None:
                log.info( "%s <- %-10.10r test no next state", cpppo.centeraxis( mch, 25, clip=True ), inp )
            if inp is None:
                if not material:
                    log.info( "%s <- %-10.10r test source finished", cpppo.centeraxis( mch, 25, clip=True ), inp )
                # Will load consecutive empty iterables; chainable must handle
                source.chain( material[:segment] )
                material		= material[segment:]
                inp			= source.peek()
                log.info( "%s <- %-10.10r test chain", cpppo.centeraxis( mch, 25, clip=True ), inp )
    
            if num == 0: assert inp == b'\x01'[0]; assert sta.name == "First"
            if num == 1: assert inp == b'\x02'[0]; assert sta.name == "Second"
            if num == 2: assert inp == b'\x03'[0]; assert sta.name == "Third"
            if num == 3: assert inp == b'\x80'[0]; assert sta is None
            if num == 4: assert inp == b'\x80'[0]; assert sta.name == "Fourth"
            if num == 5: assert inp == b'\x99'[0]; assert sta.name == "int32"
            if num == 6: assert inp == b'\x99'[0]; assert sta.name == "int32"
        assert inp == b'\x99'[0]
        assert num == 6
        assert sta.name == "int32"
        assert data.struct.val == -2147286527
Example #9
0
 def __init__(self, host, port=None, timeout=None):
     """Connect to the EtherNet/IP client, waiting  """
     self.addr = (host or address[0], port or address[1])
     self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     self.conn.connect(self.addr)
     self.session = None
     self.source = cpppo.chainable()
     self.data = None
     # Parsers
     self.engine = None  # EtherNet/IP frame parsing in progress
     self.frame = enip.enip_machine(terminal=True)
     self.cip = enip.CIP(terminal=True)  # Parses a CIP   request in an EtherNet/IP frame
     self.lgx = logix.Logix().parser  # Parses a Logix request in an EtherNet/IP CIP request
Example #10
0
 def __init__(self, host, port=None, timeout=None):
     """Connect to the EtherNet/IP client, waiting  """
     self.addr = (host or address[0], port or address[1])
     self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     self.conn.connect(self.addr)
     self.session = None
     self.source = cpppo.chainable()
     self.data = None
     # Parsers
     self.engine = None  # EtherNet/IP frame parsing in progress
     self.frame = enip.enip_machine(terminal=True)
     self.cip = enip.CIP(
         terminal=True)  # Parses a CIP   request in an EtherNet/IP frame
     self.lgx = logix.Logix(
     ).parser  # Parses a Logix request in an EtherNet/IP CIP request
Example #11
0
def test_regex_demo():
    regex			= str( '(ab+)((,[ ]*)(ab+))*' )
    machine			= cpppo.regex( name=str( 'demo' ), initial=regex )
    data			= cpppo.dotdict()
    with machine:
        source			= cpppo.chainable( str( 'abbb, abb, ab' ))
        for i,(m,s) in enumerate( machine.run( source=source, data=data )):
            log.info( "%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", m.name_centered(),
                      i, s, source.sent, source.peek(), data )
        assert i == 14
        assert source.sent == 13

    regexstr, lego, machine, initial = cpppo.state_input.from_regex(
            regex, alphabet=cpppo.type_str_iter, encoder=None, 
            typecode=cpppo.type_str_array_symbol, context=None )
    assert str( lego ) == "ab+(, *ab+)*"
    assert str( machine ) == """\
Example #12
0
def test_tnet_machinery():
    # parsing integers
    path = "machinery"
    SIZE = cpppo.integer_bytes(name="SIZE", context="size")
    data = cpppo.dotdict()
    source = cpppo.chainable(b'123:')
    with SIZE:
        for m, s in SIZE.run(source=source, data=data, path=path):
            if s is None:
                break
    log.info("After SIZE: %r", data)
    assert s and s.terminal
    assert data.machinery.size == 123

    # repeat, limited by parent context's 'value' in data
    DATA = tnet.data_parser(name="DATA", context="data", repeat="..size")
    source.chain(b"abc" * 123)
    with DATA:
        for m, s in DATA.run(source=source, data=data, path=path):
            if s is None:
                break
    log.info("After DATA: %r", data)
def test_tnet_machinery():
    # parsing integers
    path			= "machinery"
    SIZE			= cpppo.integer_bytes( name="SIZE", context="size", terminal=True )
    data			= cpppo.dotdict()
    source			= cpppo.chainable( b'123:' )
    with SIZE:
        for m,s in SIZE.run( source=source, data=data, path=path ):
            if s is None:
                break
    log.info( "After SIZE: %r", data )
    assert SIZE.terminal
    assert data.machinery.size == 123

    # repeat, limited by parent context's 'value' in data
    DATA			= tnet.data_parser(
        name="DATA", context="data", repeat="..size" )
    source.chain( b"abc" * 123 )
    with DATA:
        for m,s in DATA.run( source=source, data=data, path=path ):
            if s is None:
                break
    log.info( "After DATA: %r", data )
Example #14
0
def test_regex():
    # This forces plain strings in 2.x, unicode in 3.x (counteracts import unicode_literals above)
    regex = str('a*b.*x')
    machine = cpppo.regex(name=str('test1'), initial=regex)
    with machine:
        source = cpppo.chainable(str('aaab1230xoxx'))
        sequence = machine.run(source=source)
        for num in range(20):
            try:
                mch, sta = next(sequence)
                inp = source.peek()
            except StopIteration:
                inp = source.peek()
                log.info("%s <- %-10.10r test done",
                         cpppo.centeraxis(mch, 25, clip=True), inp)
                break
            log.info("%s <- %-10.10r test rcvd",
                     cpppo.centeraxis(mch, 25, clip=True), inp)
            if sta is None:
                log.info("%s <- %-10.10r test no next state",
                         cpppo.centeraxis(mch, 25, clip=True), inp)
            if inp is None:
                log.info("%s <- %-10.10r test source finished",
                         cpppo.centeraxis(mch, 25, clip=True), inp)

            # Initial state does *not* consume a source symbol
            if num == 0:
                assert inp == 'a'
                assert sta.name == "0'"
                assert source.sent == 0
            if num == 1:
                assert inp == 'a'
                assert sta.name == "0"
                assert source.sent == 0
            if num == 2:
                assert inp == 'a'
                assert sta.name == "0"
                assert source.sent == 1
            if num == 3:
                assert inp == 'a'
                assert sta.name == "0"
                assert source.sent == 2
            if num == 4:
                assert inp == 'b'
                assert sta.name == "2"
            if num == 5:
                assert inp == '1'
                assert sta.name == "2"
            if num == 6:
                assert inp == '2'
                assert sta.name == "2"
            if num == 7:
                assert inp == '3'
                assert sta.name == "2"
            if num == 8:
                assert inp == '0'
                assert sta.name == "2"
            if num == 9:
                assert inp == 'x'
                assert sta.name == "3"
            if num == 10:
                assert inp == 'o'
                assert sta.name == "2"  # Trans. from term. to non-term. state!))
            if num == 11:
                assert inp == 'x'
                assert sta.name == "3"
            if num == 12:
                assert inp == 'x'
                assert sta.name == "3"
            if num == 13:
                assert inp == None
                assert sta is None
            assert num < 14
        assert inp is None
        assert num == 14
        assert sta is None and machine.current.name == '3'

    regex = str('.*')
    machine = cpppo.regex(name=str('dot'), initial=regex, terminal=True)
    data = cpppo.dotdict()
    with machine:
        source = cpppo.chainable(str('aaab1230xoxx\0'))
        try:
            for i, (m, s) in enumerate(machine.run(source=source, data=data)):
                log.info("%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r",
                         m.name_centered(), i, s, source.sent, source.peek(),
                         data)
        except cpppo.NonTerminal:
            pass
        assert machine.terminal
        assert i == 14
        assert source.sent == 13
        if sys.version_info[0] < 3:
            assert data.input.input.tostring() == 'aaab1230xoxx\x00'
        else:
            assert data.input.input.tounicode() == 'aaab1230xoxx\x00'

    regex = str('[^xyz]*')
    machine = cpppo.regex(name=str('not_xyz'), initial=regex)
    data = cpppo.dotdict()
    with machine:
        source = cpppo.chainable(str('aaab1230xoxx\0'))
        try:
            for i, (m, s) in enumerate(machine.run(source=source, data=data)):
                log.info("%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r",
                         m.name_centered(), i, s, source.sent, source.peek(),
                         data)
        except cpppo.NonTerminal:
            pass
        assert not machine.terminal
        assert i == 9
        assert source.sent == 8
        if sys.version_info[0] < 3:
            assert data.input.input.tostring() == 'aaab1230'
        else:
            assert data.input.input.tounicode() == 'aaab1230'

    regex = str('[^\x00]*')
    machine = cpppo.regex(name=str('not_NUL'), initial=regex)
    data = cpppo.dotdict()
    with machine:
        source = cpppo.chainable(str('aaab1230xoxx\0'))
        for i, (m, s) in enumerate(machine.run(source=source, data=data)):
            log.info("%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r",
                     m.name_centered(), i, s, source.sent, source.peek(), data)
        assert i == 13
        assert source.sent == 12
        if sys.version_info[0] < 3:
            assert data.input.input.tostring() == 'aaab1230xoxx'
        else:
            assert data.input.input.tounicode() == 'aaab1230xoxx'
Example #15
0
def test_dfa():
    # Simple DFA with states consuming no input.  A NULL (None) state transition
    # doesn't require input for state change.  The Default (True) transition
    # requires input to make the transition, but none of these states consume
    # it, so it'll be left over at the end.
    a = cpppo.state("Initial")
    a[None] = b = cpppo.state("Middle")
    b[True] = cpppo.state("Terminal", terminal=True)

    source = cpppo.chainable()
    i = a.run(source=source)
    m, s = next(i)
    assert m is None
    assert s is not None and s.name == "Middle"
    try:
        next(i)
        assert False, "Expected no more non-transition events"
    except StopIteration:
        pass

    machine = cpppo.dfa(initial=a)

    with machine:
        log.info("DFA:")
        for initial in machine.initial.nodes():
            for inp, target in initial.edges():
                log.info(
                    "%s <- %-10.10r -> %s" %
                    (cpppo.centeraxis(initial, 25, clip=True), inp, target))

        # Running with no input will yield the initial state, with None input; since it is a NULL
        # state (no input processed), it will simply attempt to transition.  This will require the
        # next input from source, which is empty, so it will return input,state=(None, None)
        # indicating a non-terminal state and no input left.  This gives the caller an opportunity
        # to reload input and try again.  If a loop is detected (same state and input conditions
        # seen repeatedly), the DFA will terminate; if not in a terminal state, an exception will be
        # raised.
        log.info("States; No input")
        source = cpppo.chainable()
        sequence = machine.run(source=source)
        for num in range(10):
            try:
                mch, sta = next(sequence)
            except StopIteration:
                sequence = None
                break
            except cpppo.NonTerminal as e:
                assert "non-terminal state" in str(e)
                break

            inp = source.peek()
            log.info("%s <- %r" % (cpppo.centeraxis(mch, 25, clip=True), inp))
            if num == 0:
                assert inp is None
                assert sta.name == "Initial"
            if num == 1:
                assert inp is None
                assert sta.name == "Middle"
            if num == 2:
                assert inp is None
                assert sta is None  # And no more no-input transitions
            assert num < 3  # If we get here, we didn't detect loop
        assert num == 3

        # since the iterator did not stop cleanly (after processing a state's input,
        # and then trying to determine the next state), it'll continue indefinitely
        assert sta is None
        assert sequence is not None

        # Try with some input loaded into source stream, using an identical generator sequence.
        # Only the first element is gotten, and is reused for every NULL state transition, and is
        # left over at the end.
        log.info("States; 'abc' input")
        assert source.peek() is None
        source.chain(b'abc')
        assert source.peek() == b'a'[0]  # python2: str, python3: int
        sequence = machine.run(source=source)
        for num in range(10):
            try:
                mch, sta = next(sequence)
            except StopIteration:
                break
            inp = source.peek()
            log.info("%s <- %r", cpppo.centeraxis(mch, 25, clip=True), inp)
            if num == 0:
                assert inp == b'a'[0]
                assert sta.name == "Initial"
            if num == 1:
                assert inp == b'a'[0]
                assert sta.name == "Middle"
            if num == 2:
                assert inp == b'a'[0]
                assert sta.name == "Terminal"
            assert num < 3
        assert num == 3
        assert inp == b'a'[0]
        assert sta.name == "Terminal"
Example #16
0
def test_codecs():
    # In Python3, the greenery.fsm is able to handle the Unicode str type; under
    # Python2, it can sanely only handle the non-Unicode str type.
    if sys.version_info[0] < 3:
        return

    # Test parsing of greenery.fsm/lego regexes specified in Unicode.  Then,
    # generate corresponding cpppo state machines that accept Unicode input
    # symbols, and byte input symbols.  These tests will accept as much of the
    # input as matches the regular expression.


    texts 			= [
        'pi: π',
        'abcdé\u4500123',
        'This contains π,π and more πs',
        'a 480Ω resistor',
        ]
    tests			= [
        ('[^π]*(π[^π]*)+',	True),	# Optional non-π's, followed by at least one string of π and non-π's
        ('[^π]*[^π]',		False) 	# Any number of non-π, ending in a non-π
        ]

    for text in texts:
        for re,tr in tests:
            # First, convert the unicode regex to a state machine in unicode symbols.  Only if both
            # the dfa and its sub-state are "terminal", will it be terminal.
            with cpppo.regex(
                    name='pies',  context="pies", initial=re, terminal=True ) as pies:
                original		= text
                source			= cpppo.chainable( original )
                data			= cpppo.dotdict()
                try:
                    for mch, sta in pies.run( source=source, data=data ):
                        pass
                except cpppo.NonTerminal:
                    pass
                accepted		= pies.terminal and data.pies.input.tounicode() == original
                log.info( "%s ends w/ re %s: %s: %r", pies.name_centered(), re,
                          "string accepted" if accepted else "string rejected", data )
            
                # Each of these are greedy, and so run 'til the end of input (next state is None); they
                # collect the full input string, unless they run into a non-matching input.
                expected		= tr == ('π' in text )
                assert accepted == expected

    for text in texts:
        # Then convert the unicode regex to a state machine in bytes symbols.
        # Our encoder generates 1 or more bytes for each unicode symbol.
        for re,tr in tests:
            original		= text.encode( 'utf-8' ) # u'...' --> b'...'
            source		= cpppo.chainable( original )
            data		= cpppo.dotdict()

            with cpppo.regex(
                    name='pies', context="pies", initial=re, terminal=True,
                    regex_alphabet=int,
                    regex_typecode='B',
                    regex_encoder=lambda s: ( b for b in s.encode( 'utf-8' ))) as pies:
                try:
                    for mch, sta in pies.run( source=source, data=data ):
                        pass
                except cpppo.NonTerminal:
                    pass
                accepted		= pies.terminal and data.pies.input.tobytes() == original
                log.detail( "%s ends w/ re: %s: %s: %r", pies.name_centered(), re,
                          "string accepted" if accepted else "string rejected", data )
                expected		= tr == ('π' in text )
                assert accepted == expected
                assert original.startswith( data.pies.input.tobytes() )
Example #17
0
def test_regex():
    # This forces plain strings in 2.x, unicode in 3.x (counteracts import unicode_literals above)
    regex			= str('a*b.*x')
    machine			= cpppo.regex( name=str('test1'), initial=regex )
    with machine:
        source			= cpppo.chainable( str('aaab1230xoxx') )
        sequence		= machine.run( source=source )
        for num in range( 20 ):
            try:
                mch,sta		= next( sequence )
                inp		= source.peek()
            except StopIteration:
                inp		= source.peek()
                log.info( "%s <- %-10.10r test done", cpppo.centeraxis( mch, 25, clip=True ), inp )
                break
            log.info( "%s <- %-10.10r test rcvd", cpppo.centeraxis( mch, 25, clip=True ), inp )
            if sta is None:
                log.info( "%s <- %-10.10r test no next state", cpppo.centeraxis( mch, 25, clip=True ), inp )
            if inp is None:
                log.info( "%s <- %-10.10r test source finished", cpppo.centeraxis( mch, 25, clip=True ), inp )
    
            # Initial state does *not* consume a source symbol
            if num == 0: assert inp == 'a'; assert sta.name == "0'"; assert source.sent == 0
            if num == 1: assert inp == 'a'; assert sta.name == "0";  assert source.sent == 0
            if num == 2: assert inp == 'a'; assert sta.name == "0";  assert source.sent == 1
            if num == 3: assert inp == 'a'; assert sta.name == "0";  assert source.sent == 2
            if num == 4: assert inp == 'b'; assert sta.name == "2"
            if num == 5: assert inp == '1'; assert sta.name == "2"
            if num == 6: assert inp == '2'; assert sta.name == "2"
            if num == 7: assert inp == '3'; assert sta.name == "2"
            if num == 8: assert inp == '0'; assert sta.name == "2"
            if num == 9: assert inp == 'x'; assert sta.name == "3"
            if num ==10: assert inp == 'o'; assert sta.name == "2" # Trans. from term. to non-term. state!))
            if num ==11: assert inp == 'x'; assert sta.name == "3"
            if num ==12: assert inp == 'x'; assert sta.name == "3"
            if num ==13: assert inp ==None; assert sta is None
            assert num < 14
        assert inp is None
        assert num == 14
        assert sta is None and machine.current.name == '3'

    regex			= str('.*')
    machine			= cpppo.regex( name=str('dot'), initial=regex, terminal=True )
    data			= cpppo.dotdict()
    with machine:
        source			= cpppo.chainable( str('aaab1230xoxx\0') )
        try:
            for i,(m,s) in enumerate( machine.run( source=source, data=data )):
                log.info( "%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", m.name_centered(),
                          i, s, source.sent, source.peek(), data )
        except cpppo.NonTerminal:
            pass
        assert machine.terminal
        assert i == 14
        assert source.sent == 13
        if sys.version_info[0] < 3:
            assert data.input.input.tostring()  == 'aaab1230xoxx\x00'
        else:
            assert data.input.input.tounicode() == 'aaab1230xoxx\x00'

    regex			= str('[^xyz]*')
    machine			= cpppo.regex( name=str('not_xyz'), initial=regex )
    data			= cpppo.dotdict()
    with machine:
        source			= cpppo.chainable( str('aaab1230xoxx\0') )
        try:
            for i,(m,s) in enumerate( machine.run( source=source, data=data )):
                log.info( "%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", m.name_centered(),
                          i, s, source.sent, source.peek(), data )
        except cpppo.NonTerminal:
            pass
        assert not machine.terminal
        assert i == 9
        assert source.sent == 8
        if sys.version_info[0] < 3:
            assert data.input.input.tostring()  == 'aaab1230'
        else:
            assert data.input.input.tounicode() == 'aaab1230'

    regex			= str('[^\x00]*')
    machine			= cpppo.regex( name=str('not_NUL'), initial=regex )
    data			= cpppo.dotdict()
    with machine:
        source			= cpppo.chainable( str('aaab1230xoxx\0') )
        for i,(m,s) in enumerate( machine.run( source=source, data=data )):
            log.info( "%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r", m.name_centered(),
                      i, s, source.sent, source.peek(), data )
        assert i == 13
        assert source.sent == 12
        if sys.version_info[0] < 3:
            assert data.input.input.tostring()  == 'aaab1230xoxx'
        else:
            assert data.input.input.tounicode() == 'aaab1230xoxx'
Example #18
0
def test_codecs():
    # In Python3, the greenery.fsm is able to handle the Unicode str type; under
    # Python2, it can sanely only handle the non-Unicode str type.
    if sys.version_info.major < 3:
        return

    # Test parsing of greenery.fsm/lego regexes specified in Unicode.  Then,
    # generate corresponding cpppo state machines that accept Unicode input
    # symbols, and byte input symbols.

    texts = ["pi: π", "abcdé\u4500123", "This contains π,π and more πs", "a 480Ω resistor"]

    for text in texts:
        # First, convert the unicode regex to a state machine in unicode symbols.
        with cpppo.regex(name="pies", context="pies", initial=".*π.*", terminal=True) as pies:
            source = cpppo.chainable(text)
            data = cpppo.dotdict()
            try:
                for mch, sta in pies.run(source=source, data=data):
                    pass
            except cpppo.NonTerminal:
                pass

            log.info(
                "%s ends: %s: %r", pies.name_centered(), "string accepted" if pies.terminal else "string rejected", data
            )

            # Each of these are greedy, and so run 'til the end of input (next state
            # is None); they collect the full input string.
            assert pies.terminal == ("π" in text)
            assert data.pies.input.tounicode() == text

    for text in texts:
        # Then convert the unicode regex to a state machine in bytes symbols.
        # Our encoder generates 1 or more bytes for each unicode symbol.

        pies = cpppo.regex(
            name="pies",
            context="pies",
            initial=".*π.*",
            terminal=True,
            regex_alphabet=int,
            regex_typecode="B",
            regex_encoder=lambda s: (b for b in s.encode("utf-8")),
        )

        source = cpppo.chainable(text.encode("utf-8"))
        data = cpppo.dotdict()

        with pies:
            try:
                for mch, sta in pies.run(source=source, data=data):
                    pass
            except cpppo.NonTerminal:
                pass

            log.info(
                "%s ends: %s: %r", pies.name_centered(), "string accepted" if pies.terminal else "string rejected", data
            )

            assert pies.terminal == ("π" in text)
            assert data.pies.input.tobytes().decode("utf-8") == text
Example #19
0
def test_codecs():
    # In Python3, the greenery.fsm is able to handle the Unicode str type; under
    # Python2, it can sanely only handle the non-Unicode str type.
    if sys.version_info[0] < 3:
        return

    # Test parsing of greenery.fsm/lego regexes specified in Unicode.  Then,
    # generate corresponding cpppo state machines that accept Unicode input
    # symbols, and byte input symbols.  These tests will accept as much of the
    # input as matches the regular expression.

    texts = [
        'pi: π',
        'abcdé\u4500123',
        'This contains π,π and more πs',
        'a 480Ω resistor',
    ]
    tests = [
        (
            '[^π]*(π[^π]*)+', True
        ),  # Optional non-π's, followed by at least one string of π and non-π's
        ('[^π]*[^π]', False)  # Any number of non-π, ending in a non-π
    ]

    for text in texts:
        for re, tr in tests:
            # First, convert the unicode regex to a state machine in unicode symbols.  Only if both
            # the dfa and its sub-state are "terminal", will it be terminal.
            with cpppo.regex(name='pies',
                             context="pies",
                             initial=re,
                             terminal=True) as pies:
                original = text
                source = cpppo.chainable(original)
                data = cpppo.dotdict()
                try:
                    for mch, sta in pies.run(source=source, data=data):
                        pass
                except cpppo.NonTerminal:
                    pass
                accepted = pies.terminal and data.pies.input.tounicode(
                ) == original
                log.info("%s ends w/ re %s: %s: %r", pies.name_centered(), re,
                         "string accepted" if accepted else "string rejected",
                         data)

                # Each of these are greedy, and so run 'til the end of input (next state is None); they
                # collect the full input string, unless they run into a non-matching input.
                expected = tr == ('π' in text)
                assert accepted == expected

    for text in texts:
        # Then convert the unicode regex to a state machine in bytes symbols.
        # Our encoder generates 1 or more bytes for each unicode symbol.
        for re, tr in tests:
            original = text.encode('utf-8')  # u'...' --> b'...'
            source = cpppo.chainable(original)
            data = cpppo.dotdict()

            with cpppo.regex(name='pies',
                             context="pies",
                             initial=re,
                             terminal=True,
                             regex_alphabet=int,
                             regex_typecode='B',
                             regex_encoder=lambda s:
                             (b for b in s.encode('utf-8'))) as pies:
                try:
                    for mch, sta in pies.run(source=source, data=data):
                        pass
                except cpppo.NonTerminal:
                    pass
                accepted = pies.terminal and data.pies.input.tobytes(
                ) == original
                log.detail(
                    "%s ends w/ re: %s: %s: %r", pies.name_centered(), re,
                    "string accepted" if accepted else "string rejected", data)
                expected = tr == ('π' in text)
                assert accepted == expected
                assert original.startswith(data.pies.input.tobytes())
Example #20
0
def tnet_from( conn, addr,
               server	= cpppo.dotdict({'done': False}),
               timeout	= None,
               latency	= None,
               ignore	= None,
               source	= None ):	# Provide a cpppo.chainable, if desire, to receive into and parse from
    """Parse and yield TNET messages from a socket w/in timeout, blocking 'til server.done or EOF
    between messages.  If ignore contains symbols, they are ignored between TNET messages (eg. b'\n').

    An absense of a TNET string within 'timeout' will yield None, allowing the user to decide to
    fail or continue trying for another 'timeout' period.  A 0 timeout will "poll", and a None
    timeout will simply wait forever (the default).  If desired, a separate 'latency' can be
    supplied, in order to pop out regularly and check server.done (eg. to allow a server Thread to
    exit cleanly).

    """
    if source is None:
        source			= cpppo.chainable()
    with tnet_machine( "tnet_%s" % addr[1] ) as engine:
        eof			= False
        while not ( eof or server.done ):
            while ignore and source.peek() and source.peek() in ignore:
                next( source )
            data		= cpppo.dotdict()
            started		= cpppo.timer()		# When did we start the current attempt at a TNET string?
            for mch,sta in engine.run( source=source, data=data ):
                if sta is not None or source.peek() is not None:
                    continue
                # Non-transition state, and we need more data: check for more data, enforce timeout.
                # Waits up to latency, or remainder of timeout -- or forever, if both are None.
                duration	= cpppo.timer() - started
                msg		= None
                while msg is None and not server.done: # Get input, forever or 'til server.done
                    remains	= latency if timeout is None else min(	# If no timeout, wait for latency (or forever, if None)
                        timeout if latency is None else latency,	# Or, we know timeout is numeric; get min of any latency
                        max( timeout - duration, 0 ))			#  ... and remaining unused timeout
                    log.info( "%s: After %7.3fs, awaiting symbols (after %d processed) w/ %s recv timeout",
                              engine.name_centered(), duration, source.sent,
                              remains if remains is None else ( "%7.3fs" % remains ))
                    msg		= network.recv( conn, timeout=remains )
                    duration	= cpppo.timer() - started
                    if msg is None and timeout is not None and duration >= timeout:
                        # No data w/in given timeout expiry!  Inform the consumer, and then try again w/ fresh timeout.
                        log.info( "%s: After %7.3fs, no TNET message after %7.3fs recv timeout",
                              engine.name_centered(), duration, remains )
                        yield None
                        started	= cpppo.timer()
                # Only way to get here without EOF/data, is w/ server.done
                if server.done:
                    break
                assert msg is not None
                # Got EOF or data
                eof		= len( msg ) == 0
                log.info( "%s: After %7.3fs, recv: %5d: %s",
                          engine.name_centered(), duration, len( msg ),
                          'EOF' if eof else cpppo.reprlib.repr( msg ))
                if eof:
                    break
                source.chain( msg )

            # Terminal state, or EOF, or server.done.  Only yield another TNET message if terminal. 
            duration		= cpppo.timer() - started
            if engine.terminal:
                log.debug( "%s: After %7.3fs, found a TNET: %r", engine.name_centered(), duration, data.tnet.type.input )
                yield data.tnet.type.input # Could be a 0:~ / null ==> None

        log.detail( "%s: done w/ %s", engine.name_centered(),
                    ', '.join( ['EOF'] if eof else [] + ['done'] if server.done else [] ))
Example #21
0
def test_codecs():
    # In Python3, the greenery.fsm is able to handle the Unicode str type; under
    # Python2, it can sanely only handle the non-Unicode str type.
    if sys.version_info.major < 3:
        return

    # Test parsing of greenery.fsm/lego regexes specified in Unicode.  Then,
    # generate corresponding cpppo state machines that accept Unicode input
    # symbols, and byte input symbols.

    texts = [
        'pi: π',
        'abcdé\u4500123',
        'This contains π,π and more πs',
        'a 480Ω resistor',
    ]

    for text in texts:
        # First, convert the unicode regex to a state machine in unicode symbols.
        with cpppo.regex(name='pies',
                         context="pies",
                         initial='.*π.*',
                         terminal=True) as pies:
            source = cpppo.chainable(text)
            data = cpppo.dotdict()
            try:
                for mch, sta in pies.run(source=source, data=data):
                    pass
            except cpppo.NonTerminal:
                pass

            log.info("%s ends: %s: %r", pies.name_centered(),
                     "string accepted" if pies.terminal else "string rejected",
                     data)

            # Each of these are greedy, and so run 'til the end of input (next state
            # is None); they collect the full input string.
            assert pies.terminal == ('π' in text)
            assert data.pies.input.tounicode() == text

    for text in texts:
        # Then convert the unicode regex to a state machine in bytes symbols.
        # Our encoder generates 1 or more bytes for each unicode symbol.

        pies = cpppo.regex(name='pies',
                           context="pies",
                           initial='.*π.*',
                           terminal=True,
                           regex_alphabet=int,
                           regex_typecode='B',
                           regex_encoder=lambda s:
                           (b for b in s.encode('utf-8')))

        source = cpppo.chainable(text.encode('utf-8'))
        data = cpppo.dotdict()

        with pies:
            try:
                for mch, sta in pies.run(source=source, data=data):
                    pass
            except cpppo.NonTerminal:
                pass

            log.info("%s ends: %s: %r", pies.name_centered(),
                     "string accepted" if pies.terminal else "string rejected",
                     data)

            assert pies.terminal == ('π' in text)
            assert data.pies.input.tobytes().decode('utf-8') == text
Example #22
0
def test_regex():
    # This forces plain strings in 2.x, unicode in 3.x (counteracts import unicode_literals above)
    regex = str('a*b.*x')
    machine = cpppo.regex(name=str('test1'), initial=regex)
    with machine:
        source = cpppo.chainable(str('aaab1230xoxx'))
        sequence = machine.run(source=source)
        for num in range(20):
            try:
                mch, sta = next(sequence)
                inp = source.peek()
            except StopIteration:
                inp = source.peek()
                log.info("%s <- %-10.10r test done",
                         cpppo.centeraxis(mch, 25, clip=True), inp)
                break
            log.info("%s <- %-10.10r test rcvd",
                     cpppo.centeraxis(mch, 25, clip=True), inp)
            if sta is None:
                log.info("%s <- %-10.10r test no next state",
                         cpppo.centeraxis(mch, 25, clip=True), inp)
            if inp is None:
                log.info("%s <- %-10.10r test source finished",
                         cpppo.centeraxis(mch, 25, clip=True), inp)

            # Initial state does *not* consume a source symbol
            if num == 0:
                assert inp == 'a'
                assert sta.name == "0'"
                assert source.sent == 0
            if num == 1:
                assert inp == 'a'
                assert sta.name == "0"
                assert source.sent == 0
            if num == 2:
                assert inp == 'a'
                assert sta.name == "0"
                assert source.sent == 1
            if num == 3:
                assert inp == 'a'
                assert sta.name == "0"
                assert source.sent == 2
            if num == 4:
                assert inp == 'b'
                assert sta.name == "2"
            if num == 5:
                assert inp == '1'
                assert sta.name == "2"
            if num == 6:
                assert inp == '2'
                assert sta.name == "2"
            if num == 7:
                assert inp == '3'
                assert sta.name == "2"
            if num == 8:
                assert inp == '0'
                assert sta.name == "2"
            if num == 9:
                assert inp == 'x'
                assert sta.name == "3"
            if num == 10:
                assert inp == 'o'
                assert sta.name == "2"  # Trans. from term. to non-term. state!))
            if num == 11:
                assert inp == 'x'
                assert sta.name == "3"
            if num == 12:
                assert inp == 'x'
                assert sta.name == "3"
            if num == 13:
                assert inp == None
                assert sta is None
            assert num < 14
        assert inp is None
        assert num == 14
        assert sta is None and machine.current.name == '3'
Example #23
0
def test_CIP_HART(repeat=1):
    """HART protocol enip CIP messages
    """
    enip.lookup_reset()  # Flush out any existing CIP Objects for a fresh start

    ENIP = enip.enip_machine(context='enip')
    CIP = enip.CIP()
    # We'll use a HART Message Router, to handle its expanded porfolio of commands
    MR = HART(instance_id=1)

    for pkt, tst in client.recycle(CIP_HART_tests, times=repeat):
        # Parse just the CIP portion following the EtherNet/IP encapsulation header
        data = cpppo.dotdict()
        source = cpppo.chainable(pkt)
        with ENIP as machine:
            for i, (m, s) in enumerate(machine.run(source=source, data=data)):
                log.detail("%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r",
                           machine.name_centered(), i, s, source.sent,
                           source.peek(), data)
        # In a real protocol implementation, an empty header (EOF with no input at all) is
        # acceptable; it indicates a session closed by the client.
        if not data:
            log.normal("EtherNet/IP Request: Empty (session terminated): %s",
                       enip.enip_format(data))
            continue

        if log.isEnabledFor(logging.NORMAL):
            log.normal("EtherNet/IP Request: %s", enip.enip_format(data))

        # Parse the encapsulated .input
        with CIP as machine:
            for i, (m, s) in enumerate(
                    machine.run(path='enip',
                                source=cpppo.peekable(
                                    data.enip.get('input', b'')),
                                data=data)):
                log.detail("%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r",
                           machine.name_centered(), i, s, source.sent,
                           source.peek(), data)

        if log.isEnabledFor(logging.NORMAL):
            log.normal("EtherNet/IP CIP Request: %s", enip.enip_format(data))

        # Assume the request in the CIP's CPF items are HART requests.
        # Now, parse the encapsulated message(s).  We'll assume it is destined for a HART Object.
        if 'enip.CIP.send_data' in data:
            for item in data.enip.CIP.send_data.CPF.item:
                if 'unconnected_send.request' in item:
                    # An Unconnected Send that contained an encapsulated request (ie. not just a Get
                    # Attribute All)
                    with MR.parser as machine:
                        if log.isEnabledFor(logging.NORMAL):
                            log.normal(
                                "Parsing %3d bytes using %s.parser, from %s",
                                len(item.unconnected_send.request.input), MR,
                                enip.enip_format(item))
                        # Parse the unconnected_send.request.input octets, putting parsed items into the
                        # same request context
                        for i, (m, s) in enumerate(
                                machine.run(
                                    source=cpppo.peekable(
                                        item.unconnected_send.request.input),
                                    data=item.unconnected_send.request)):
                            log.detail(
                                "%s #%3d -> %10.10s; next byte %3d: %-10.10r: %r",
                                machine.name_centered(), i, s, source.sent,
                                source.peek(), data)
                    # Post-processing of some parsed items is only performed after lock released!
                    if log.isEnabledFor(logging.NORMAL):
                        log.normal(
                            "Parsed  %3d bytes using %s.parser, into %s",
                            len(item.unconnected_send.request.input), MR,
                            enip.enip_format(data))
        try:
            for k, v in tst.items():
                assert data[k] == v, ("data[%r] == %r\n"
                                      "expected:   %r" % (k, data[k], v))
        except:
            log.warning("%r not in data, or != %r: %s", k, v,
                        enip.enip_format(data))
            raise

        # Ensure that we can get the original EtherNet/IP CIP back
        for k in list(data.keys()):
            if k.endswith('input') and 'sender_context' not in k:
                log.detail("del data[%r]", k)
                del data[k]
        try:
            # First reconstruct any SendRRData CPF items, containing encapsulated requests/responses
            if 'enip.CIP.send_data' in data:
                cpf = data.enip.CIP.send_data
                for item in cpf.CPF.item:
                    if 'unconnected_send' in item:
                        item.unconnected_send.request.input = bytearray(
                            MR.produce(item.unconnected_send.request))
                        log.normal("Produce HART message from: %r",
                                   item.unconnected_send.request)

            # Next, reconstruct the CIP Register, ListIdentity, ListServices, or SendRRData.  The CIP.produce must
            # be provided the EtherNet/IP header, because it contains data (such as .command)
            # relevant to interpreting the .CIP... contents.
            data.enip.input = bytearray(enip.CIP.produce(data.enip))
            # And finally the EtherNet/IP encapsulation itself
            data.input = bytearray(enip.enip_encode(data.enip))
            log.detail("EtherNet/IP CIP Request produced payload: %r",
                       bytes(data.input))
            assert data.input == pkt, "original:\n" + hexdump(
                pkt) + "\nproduced:\n" + hexdump(data.input)
        except Exception as exc:
            log.warning(
                "Exception %s; Invalid packet produced from EtherNet/IP CIP data: %s",
                exc, enip.enip_format(data))
            raise
Example #24
0
def test_dfa():
    # Simple DFA with states consuming no input.  A NULL (None) state transition
    # doesn't require input for state change.  The Default (True) transition
    # requires input to make the transition, but none of these states consume
    # it, so it'll be left over at the end.
    a 				= cpppo.state( "Initial" )
    a[None] = b			= cpppo.state( "Middle" )
    b[True]			= cpppo.state( "Terminal", terminal=True )

    source			= cpppo.chainable()
    i				= a.run( source=source )
    m,s				= next( i )
    assert m is None
    assert s is not None and s.name == "Middle"
    try:
        next( i )
        assert False, "Expected no more non-transition events"
    except StopIteration:
        pass

    machine			= cpppo.dfa( initial=a )

    with machine:
        log.info( "DFA:" )
        for initial in machine.initial.nodes():
            for inp,target in initial.edges():
                log.info( "%s <- %-10.10r -> %s" % ( cpppo.centeraxis( initial, 25, clip=True ),
                                                     inp, target ))

        # Running with no input will yield the initial state, with None input; since it is a NULL
        # state (no input processed), it will simply attempt to transition.  This will require the
        # next input from source, which is empty, so it will return input,state=(None, None)
        # indicating a non-terminal state and no input left.  This gives the caller an opportunity
        # to reload input and try again.  If a loop is detected (same state and input conditions
        # seen repeatedly), the DFA will terminate; if not in a terminal state, an exception will be
        # raised.
        log.info( "States; No input" )
        source			= cpppo.chainable()
        sequence		= machine.run( source=source )
        for num in range( 10 ):
            try:
                mch,sta		= next( sequence )
            except StopIteration:
                sequence	= None
                break
            except cpppo.NonTerminal as e:
                assert "non-terminal state" in str( e )
                break

            inp			= source.peek()
            log.info( "%s <- %r" % ( cpppo.centeraxis( mch, 25, clip=True ), inp ))
            if num == 0: assert inp is None; assert sta.name == "Initial"
            if num == 1: assert inp is None; assert sta.name == "Middle"
            if num == 2: assert inp is None; assert sta is None	# And no more no-input transitions
            assert num < 3 # If we get here, we didn't detect loop
        assert num == 3

        # since the iterator did not stop cleanly (after processing a state's input,
        # and then trying to determine the next state), it'll continue indefinitely
        assert sta is None
        assert sequence is not None
    
        # Try with some input loaded into source stream, using an identical generator sequence.
        # Only the first element is gotten, and is reused for every NULL state transition, and is
        # left over at the end.
        log.info( "States; 'abc' input" )
        assert source.peek() is None
        source.chain( b'abc' )
        assert source.peek() == b'a'[0] # python2: str, python3: int
        sequence		= machine.run( source=source )
        for num in range( 10 ):
            try:
                mch,sta		= next( sequence )
            except StopIteration:
                break
            inp			= source.peek()
            log.info( "%s <- %r", cpppo.centeraxis( mch, 25, clip=True ), inp )
            if num == 0: assert inp == b'a'[0]; assert sta.name == "Initial"
            if num == 1: assert inp == b'a'[0]; assert sta.name == "Middle"
            if num == 2: assert inp == b'a'[0]; assert sta.name == "Terminal"
            assert num < 3
        assert num == 3
        assert inp == b'a'[0]
        assert sta.name == "Terminal"
Example #25
0
def test_regex():
    # This forces plain strings in 2.x, unicode in 3.x (counteracts import unicode_literals above)
    regex = str("a*b.*x")
    machine = cpppo.regex(name=str("test1"), initial=regex)
    with machine:
        source = cpppo.chainable(str("aaab1230xoxx"))
        sequence = machine.run(source=source)
        for num in range(20):
            try:
                mch, sta = next(sequence)
                inp = source.peek()
            except StopIteration:
                inp = source.peek()
                log.info("%s <- %-10.10r test done", cpppo.centeraxis(mch, 25, clip=True), inp)
                break
            log.info("%s <- %-10.10r test rcvd", cpppo.centeraxis(mch, 25, clip=True), inp)
            if sta is None:
                log.info("%s <- %-10.10r test no next state", cpppo.centeraxis(mch, 25, clip=True), inp)
            if inp is None:
                log.info("%s <- %-10.10r test source finished", cpppo.centeraxis(mch, 25, clip=True), inp)

            # Initial state does *not* consume a source symbol
            if num == 0:
                assert inp == "a"
                assert sta.name == "0'"
                assert source.sent == 0
            if num == 1:
                assert inp == "a"
                assert sta.name == "0"
                assert source.sent == 0
            if num == 2:
                assert inp == "a"
                assert sta.name == "0"
                assert source.sent == 1
            if num == 3:
                assert inp == "a"
                assert sta.name == "0"
                assert source.sent == 2
            if num == 4:
                assert inp == "b"
                assert sta.name == "2"
            if num == 5:
                assert inp == "1"
                assert sta.name == "2"
            if num == 6:
                assert inp == "2"
                assert sta.name == "2"
            if num == 7:
                assert inp == "3"
                assert sta.name == "2"
            if num == 8:
                assert inp == "0"
                assert sta.name == "2"
            if num == 9:
                assert inp == "x"
                assert sta.name == "3"
            if num == 10:
                assert inp == "o"
                assert sta.name == "2"  # Trans. from term. to non-term. state!))
            if num == 11:
                assert inp == "x"
                assert sta.name == "3"
            if num == 12:
                assert inp == "x"
                assert sta.name == "3"
            if num == 13:
                assert inp == None
                assert sta is None
            assert num < 14
        assert inp is None
        assert num == 14
        assert sta is None and machine.current.name == "3"