# -*- coding: utf-8 -*-
# Copyright (C) 2017      by Juancarlo Añez
# Copyright (C) 2012-2016 by Juancarlo Añez and Thomas Bragg
from __future__ import absolute_import, division, print_function, \
    unicode_literals

import unittest

from grako.exceptions import FailedParse
from grako.tool import compile
from grako.util import trim, ustr
from grako.codegen import codegen
from grako.grammars import EBNFBuffer


class SyntaxTests(unittest.TestCase):
    def test_update_ast(self):
        grammar = '''
            foo = name:"1" [ name: bar ] ;
            bar = { "2" } * ;
        '''
        m = compile(grammar, 'Keywords')
        ast = m.parse('1 2')
        self.assertEqual(['1', ['2']], ast.name)

        grammar = '''
            start = items: { item } * $ ;
            item = @:{ subitem } * "0" ;
            subitem = ?/1+/? ;
        '''
        m = compile(grammar, 'Update')
        ast = m.parse("1101110100", nameguard=False)
        self.assertEqual([['11'], ['111'], ['1'], []], ast.items_)

    def test_include_and_override(self):
        gr = 'included_grammar'
        included_grammar = "plu = 'aaaa';"

        overridden = "%s@override\nplu = 'plu';"
        inclusion = '#include :: %s.ebnf\n' % gr

        including_grammar = overridden % (inclusion)
        whole_grammar = overridden % (included_grammar)

        class FakeIncludesBuffer(EBNFBuffer):
            def get_include(self, source, filename):
                return included_grammar, source + '/' + filename

        compile(FakeIncludesBuffer(whole_grammar), "test")
        compile(FakeIncludesBuffer(including_grammar), "test")

    def test_ast_assignment(self):
        grammar = '''
            n  = @: {"a"}* $ ;
            f  = @+: {"a"}* $ ;
            nn = @: {"a"}*  @: {"b"}* $ ;
            nf = @: {"a"}*  @+: {"b"}* $ ;
            fn = @+: {"a"}* @: {"b"}* $ ;
            ff = @+: {"a"}* @+: {"b"}* $ ;
        '''

        model = compile(grammar, "test")

        def p(input, rule):
            return model.parse(input, start=rule, whitespace='')

        e = self.assertEqual

        e([], p('', 'n'))
        e(['a'], p('a', 'n'))
        e(['a', 'a'], p('aa', 'n'))

        e([[]], p('', 'f'))
        e([['a']], p('a', 'f'))
        e([['a', 'a']], p('aa', 'f'))

        for r in ('nn', 'nf', 'fn', 'ff'):
            e([[], []], p('', r))
            e([['a'], []], p('a', r))
            e([[], ['b']], p('b', r))
            e([['a', 'a'], []], p('aa', r))
            e([[], ['b', 'b']], p('bb', r))
            e([['a', 'a'], ['b']], p('aab', r))

    def test_optional_closure(self):
        grammar = 'start = foo+:"x" foo:{"y"}* {foo:"z"}* ;'
        model = compile(grammar, "test")
        ast = model.parse("xyyzz", nameguard=False)
        self.assertEqual(['x', ['y', 'y'], 'z', 'z'], ast.foo)

        grammar = 'start = foo+:"x" [foo+:{"y"}*] {foo:"z"}* ;'
        model = compile(grammar, "test")
        ast = model.parse("xyyzz", nameguard=False)
        self.assertEqual(['x', ['y', 'y'], 'z', 'z'], ast.foo)

        grammar = 'start = foo+:"x" foo:[{"y"}*] {foo:"z"}* ;'
        model = compile(grammar, "test")
        ast = model.parse("xyyzz", nameguard=False)
        self.assertEqual(['x', ['y', 'y'], 'z', 'z'], ast.foo)

        grammar = 'start = foo+:"x" [foo:{"y"}*] {foo:"z"}* ;'
        model = compile(grammar, "test")
        ast = model.parse("xyyzz", nameguard=False)
        self.assertEqual(['x', ['y', 'y'], 'z', 'z'], ast.foo)

    def test_optional_sequence(self):
        grammar = '''
            start = '1' ['2' '3'] '4' $ ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("1234", nameguard=False)
        self.assertEqual(['1', '2', '3', '4'], ast)

        grammar = '''
            start = '1' foo:['2' '3'] '4' $ ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("1234", nameguard=False)
        self.assertEqual(['2', '3'], ast.foo)

    def test_group_ast(self):
        grammar = '''
            start = '1' ('2' '3') '4' $ ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("1234", nameguard=False)
        self.assertEqual(['1', '2', '3', '4'], ast)

    def test_partial_options(self):
        grammar = '''
            start
                =
                [a]
                [
                    'A' 'A'
                |
                    'A' 'B'
                ]
                $
                ;
            a
                =
                'A' !('A'|'B')
                ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("AB", nameguard=False)
        self.assertEqual(['A', 'B'], ast)

    def test_partial_choice(self):
        grammar = '''
            start
                =
                o:[o]
                x:'A'
                $
                ;
            o
                =
                'A' a:'A'
                |
                'A' b:'B'
                ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("A", nameguard=False)
        self.assertEqual({'x': 'A', 'o': None}, ast)

    def test_new_override(self):
        grammar = '''
            start
                =
                @:'a' {@:'b'}
                $
                ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("abb", nameguard=False)
        self.assertEqual(['a', 'b', 'b'], ast)

    def test_list_override(self):
        grammar = '''
            start
                =
                @+:'a' {@:'b'}
                $
                ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("a", nameguard=False)
        self.assertEqual(['a'], ast)

        grammar = '''
            start
                =
                @:'a' {@:'b'}
                $
                ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("a", nameguard=False)
        self.assertEqual('a', ast)

    def test_based_rule(self):
        grammar = '''\
            start
                =
                b $
                ;


            a
                =
                @:'a'
                ;


            b < a
                =
                {@:'b'}
                ;
            '''
        model = compile(grammar, "test")
        ast = model.parse("abb", nameguard=False)
        self.assertEqual(['a', 'b', 'b'], ast)
        self.assertEqual(trim(grammar), ustr(model))

    def test_rule_include(self):
        grammar = '''
            start = b $;

            a = @:'a' ;
            b = >a {@:'b'} ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("abb", nameguard=False)
        self.assertEqual(['a', 'b', 'b'], ast)

    def test_48_rule_override(self):
        grammar = '''
            start = ab $;

            ab = 'xyz' ;

            @override
            ab = @:'a' {@:'b'} ;
        '''
        model = compile(grammar, "test")
        ast = model.parse("abb", nameguard=False)
        self.assertEqual(['a', 'b', 'b'], ast)

    def test_failed_ref(self):
        grammar = """
            final = object;
            type = /[^\s=()]+/;
            object = '('type')' '{' @:{pair} {',' @:{pair}}* [','] '}';
            pair = key '=' value;
            list = '('type')' '[' @:{object} {',' @:{object}}* [','] ']';
            key = /[^\s=]+/;
            value = @:(string|list|object|unset|boolean|number|null) [','];
            null = '('type')' @:{ 'null' };
            boolean = /(true|false)/;
            unset = '<unset>';
            string = '"' @:/[^"]*/ '"';
            number = /-?[0-9]+/;
        """

        model = compile(grammar, "final")
        codegen(model)
        model.parse('(sometype){boolean = true}')

    def test_empty_match_token(self):
        grammar = """
            table = { row }+ ;
            row = (cell1:cell "|" cell2:cell) "\n";
            cell = /[a-z]+/ ;
        """
        try:
            compile(grammar, "model")
            self.fail('allowed empty token')
        except FailedParse:
            pass

    def test_empty_closure(self):
        grammar = '''
            start = {'x'}+ {} 'y'$;
        '''
        model = compile(grammar, "test")
        codegen(model)
        ast = model.parse("xxxy", nameguard=False)
        self.assertEqual([['x', 'x', 'x'], [], 'y'], ast)

    def test_parseinfo(self):
        grammar = '''
            start = head:{'x'}+ {} tail:'y'$;
        '''
        model = compile(grammar, "test")
        ast = model.parse("xxxy", nameguard=False, parseinfo=True)
        self.assertIsNotNone(ast)
        self.assertIsNotNone(ast.head)
        self.assertIsNotNone(ast.tail)
        self.assertIsNotNone(ast.parseinfo)

    def test_raw_string(self):
        grammar = r'''
            start = r'am\nraw' ;
        '''
        pretty = r'''
            start
                =
                'am\\nraw'
                ;
        '''
        model = compile(grammar, "start")
        print(model.pretty())
        self.assertEqual(trim(pretty), model.pretty())
