<filename>test/test_pattern.py
######################################################################
# charrange unit test
# -----
# Copyright (c) Kiruse 2021. Licensed under MIT License.
from __future__ import annotations
import sys
from os.path import dirname
sys.path.insert(0, dirname(dirname(__file__)))

from typing import *
from parfact import any, capproxy, capture, char, chars, digit, digits, letter, letters, nl, optional, sequence, string, ws, charset, Parser
from parfact.automaton import ParseError
import pytest

def test_pattern_char():
    Parser(char('a', terminal=True)).parse('a')
    Parser(char('ü', terminal=True)).parse('ü')
    Parser(char('字', terminal=True)).parse('字')
    with pytest.raises(ParseError, match='^unexpected token b'):
        Parser(char('a', terminal=True)).parse('b')
    with pytest.raises(ParseError, match='^unexpected EOF'):
        Parser(char('a', terminal=True)).parse('')

def test_capture_char():
    mycap = capture()
    Parser(char('a', terminal=True, cap=mycap)).parse('a')
    assert mycap.val == 'a'
    
    Parser(char('字', terminal=True, cap=mycap)).parse('字')
    assert mycap.val == '字'

def test_pattern_chars():
    parser = Parser(chars('a-z', terminal=True))
    parser('abc' * 10)
    with pytest.raises(ParseError, match='^unexpected token 1'):
        parser('abc123')
    with pytest.raises(ParseError, match='^unexpected token  '):
        parser('abc def')
    with pytest.raises(ParseError, match='^unexpected EOF'):
        parser('')
    
    parser = Parser(chars.until(':', terminal=True))
    parser('oh noes foo bar baz')
    with pytest.raises(ParseError, match='^unexpected token :'):
        parser('see: a fuego')

def test_capture_chars():
    captured = None
    def callback(m: str):
        nonlocal captured
        assert captured is None
        captured = m
    
    Parser(chars('a-z', terminal=True, cap=capture().oncap(callback))).parse('abc' * 10)
    assert captured == 'abc' * 10
    
    mycap = capture()
    Parser(chars.until(':', terminal=True, cap=mycap)).parse('oh noes foo bar baz')
    assert mycap.val == 'oh noes foo bar baz'

def test_pattern_string():
    Parser(string('foo bar', terminal=True)).parse('foo bar')
    Parser(string('bar foo', terminal=True)).parse('bar foo')
    with pytest.raises(ParseError, match='^unexpected EOF'):
        Parser(string('foo bar', terminal=True)).parse('foo')

def test_capture_string():
    mycap = capture()
    Parser(string('foo bar', terminal=True, cap=mycap)).parse('foo bar')
    assert mycap.val == 'foo bar'
    
    Parser(string('bar foo', terminal=True, cap=mycap)).parse('bar foo')
    assert mycap.val == 'bar foo'

def test_pattern_optional():
    Parser(optional(char('a'), terminal=True)).parse('')
    Parser(optional(char('a'), terminal=True)).parse('a')
    with pytest.raises(ParseError, match='^unexpected token b'):
        Parser(optional(char('a'), terminal=True)).parse('b')

def test_capture_optional():
    mycap = capture()
    Parser(optional(char('a', cap=mycap), terminal=True)).parse('a')
    assert mycap.val == 'a'
    
    mycap.reset() # must manually reset because optional never leaves q0
    Parser(optional(char('a', cap=mycap), terminal=True)).parse('')
    assert mycap.val is None

def test_pattern_ws():
    parser = Parser(ws(terminal=True))
    parser(' ')
    parser('\t')
    parser(' \t')
    with pytest.raises(ParseError, match='^unexpected token 1'):
        parser('  1  ')

def test_pattern_nl():
    parser = Parser(nl(terminal=True))
    parser('\n')
    parser('\r')
    parser('\r\n\n')
    with pytest.raises(ParseError, match='^unexpected token 1'):
        parser('\n\n1\n\n')

def test_pattern_any():
    parser = Parser(any(terminal=True).equivalent('any', letter(), digit()))
    parser('a')
    parser('2')
    with pytest.raises(ParseError, match='^unexpected token -'):
        parser('-')
    with pytest.raises(ParseError, match='^unexpected EOF'):
        parser('')

def test_capture_any():
    def callback(option: str, args: Tuple, kwargs: Dict):
        assert option == 'foo'
        assert foocap.kws['value'] == (('bar',), {})
        
    mycap = capture().oncap(callback)
    foocap, barcap = (capture(), capture())
    parser = Parser(any({
        'foo': sequence(string('foo'), char('='), capproxy('value', foocap, chars(charset.all())), cap=foocap),
        'bar': sequence(string('bar:'), ws(), capproxy('value', barcap, chars(charset.all())), cap=barcap),
    }, terminal=True, cap=mycap))
    parser('foo=bar')
    
    # TODO: Nested test

def test_pattern_sequence():
    Parser(sequence(string('foo'), ws(), string('bar'), terminal=True)).parse('foo bar')
    Parser(sequence(string('foo'), ws(), string('bar'), terminal=True)).parse('foo\t\tbar')
    Parser(sequence(chars.until('='), char('='), chars(charset.all()), terminal=True)).parse('foo=bar')
    
    parser = Parser(sequence(string('foo'), ws(), string('bar')).next(nl(terminal=True)))
    parser('foo bar\n')
    with pytest.raises(ParseError, match='unexpected EOF'):
        parser('foo bar')

def test_capture_sequence():
    def callback(pre, post):
        assert pre  == (('foo',), {})
        assert post == (('bar',), {})
    mycap = capture().oncap(callback)
    parser = Parser(sequence(
        capproxy('pre', mycap, chars.until(':')),
        char(':'),
        capproxy('post', mycap, chars(charset.all())),
        terminal=True, cap=mycap
    ))
    parser('foo:bar')
    assert 'pre' in mycap.kws and 'post' in mycap.kws
    assert mycap.kws['pre']  == (('foo',), {})
    assert mycap.kws['post'] == (('bar',), {})

<filename>test/test_pattern.py
######################################################################
# charrange unit test
# -----
# Copyright (c) Kiruse 2021. Licensed under MIT License.
from __future__ import annotations
import sys
from os.path import dirname
sys.path.insert(0, dirname(dirname(__file__)))

from typing import *
from parfact import any, capproxy, capture, char, chars, digit, digits, letter, letters, nl, optional, sequence, string, ws, charset, Parser
from parfact.automaton import ParseError
import pytest

def test_pattern_char():
    Parser(char('a', terminal=True)).parse('a')
    Parser(char('ü', terminal=True)).parse('ü')
    Parser(char('字', terminal=True)).parse('字')
    with pytest.raises(ParseError, match='^unexpected token b'):
        Parser(char('a', terminal=True)).parse('b')
    with pytest.raises(ParseError, match='^unexpected EOF'):
        Parser(char('a', terminal=True)).parse('')

def test_capture_char():
    mycap = capture()
    Parser(char('a', terminal=True, cap=mycap)).parse('a')
    assert mycap.val == 'a'
    
    Parser(char('字', terminal=True, cap=mycap)).parse('字')
    assert mycap.val == '字'

def test_pattern_chars():
    parser = Parser(chars('a-z', terminal=True))
    parser('abc' * 10)
    with pytest.raises(ParseError, match='^unexpected token 1'):
        parser('abc123')
    with pytest.raises(ParseError, match='^unexpected token  '):
        parser('abc def')
    with pytest.raises(ParseError, match='^unexpected EOF'):
        parser('')
    
    parser = Parser(chars.until(':', terminal=True))
    parser('oh noes foo bar baz')
    with pytest.raises(ParseError, match='^unexpected token :'):
        parser('see: a fuego')

def test_capture_chars():
    captured = None
    def callback(m: str):
        nonlocal captured
        assert captured is None
        captured = m
    
    Parser(chars('a-z', terminal=True, cap=capture().oncap(callback))).parse('abc' * 10)
    assert captured == 'abc' * 10
    
    mycap = capture()
    Parser(chars.until(':', terminal=True, cap=mycap)).parse('oh noes foo bar baz')
    assert mycap.val == 'oh noes foo bar baz'

def test_pattern_string():
    Parser(string('foo bar', terminal=True)).parse('foo bar')
    Parser(string('bar foo', terminal=True)).parse('bar foo')
    with pytest.raises(ParseError, match='^unexpected EOF'):
        Parser(string('foo bar', terminal=True)).parse('foo')

def test_capture_string():
    mycap = capture()
    Parser(string('foo bar', terminal=True, cap=mycap)).parse('foo bar')
    assert mycap.val == 'foo bar'
    
    Parser(string('bar foo', terminal=True, cap=mycap)).parse('bar foo')
    assert mycap.val == 'bar foo'

def test_pattern_optional():
    Parser(optional(char('a'), terminal=True)).parse('')
    Parser(optional(char('a'), terminal=True)).parse('a')
    with pytest.raises(ParseError, match='^unexpected token b'):
        Parser(optional(char('a'), terminal=True)).parse('b')

def test_capture_optional():
    mycap = capture()
    Parser(optional(char('a', cap=mycap), terminal=True)).parse('a')
    assert mycap.val == 'a'
    
    mycap.reset() # must manually reset because optional never leaves q0
    Parser(optional(char('a', cap=mycap), terminal=True)).parse('')
    assert mycap.val is None

def test_pattern_ws():
    parser = Parser(ws(terminal=True))
    parser(' ')
    parser('\t')
    parser(' \t')
    with pytest.raises(ParseError, match='^unexpected token 1'):
        parser('  1  ')

def test_pattern_nl():
    parser = Parser(nl(terminal=True))
    parser('\n')
    parser('\r')
    parser('\r\n\n')
    with pytest.raises(ParseError, match='^unexpected token 1'):
        parser('\n\n1\n\n')

def test_pattern_any():
    parser = Parser(any(terminal=True).equivalent('any', letter(), digit()))
    parser('a')
    parser('2')
    with pytest.raises(ParseError, match='^unexpected token -'):
        parser('-')
    with pytest.raises(ParseError, match='^unexpected EOF'):
        parser('')

def test_capture_any():
    def callback(option: str, args: Tuple, kwargs: Dict):
        assert option == 'foo'
        assert foocap.kws['value'] == (('bar',), {})
        
    mycap = capture().oncap(callback)
    foocap, barcap = (capture(), capture())
    parser = Parser(any({
        'foo': sequence(string('foo'), char('='), capproxy('value', foocap, chars(charset.all())), cap=foocap),
        'bar': sequence(string('bar:'), ws(), capproxy('value', barcap, chars(charset.all())), cap=barcap),
    }, terminal=True, cap=mycap))
    parser('foo=bar')
    
    # TODO: Nested test

def test_pattern_sequence():
    Parser(sequence(string('foo'), ws(), string('bar'), terminal=True)).parse('foo bar')
    Parser(sequence(string('foo'), ws(), string('bar'), terminal=True)).parse('foo\t\tbar')
    Parser(sequence(chars.until('='), char('='), chars(charset.all()), terminal=True)).parse('foo=bar')
    
    parser = Parser(sequence(string('foo'), ws(), string('bar')).next(nl(terminal=True)))
    parser('foo bar\n')
    with pytest.raises(ParseError, match='unexpected EOF'):
        parser('foo bar')

def test_capture_sequence():
    def callback(pre, post):
        assert pre  == (('foo',), {})
        assert post == (('bar',), {})
    mycap = capture().oncap(callback)
    parser = Parser(sequence(
        capproxy('pre', mycap, chars.until(':')),
        char(':'),
        capproxy('post', mycap, chars(charset.all())),
        terminal=True, cap=mycap
    ))
    parser('foo:bar')
    assert 'pre' in mycap.kws and 'post' in mycap.kws
    assert mycap.kws['pre']  == (('foo',), {})
    assert mycap.kws['post'] == (('bar',), {})

###################################################################### # charrange unit test # ----- # Copyright (c) Kiruse 2021. Licensed under MIT License. # must manually reset because optional never leaves q0 # TODO: Nested test