import re
import unittest


def pparse(fsm, expr, db, scope):
    print('parse ->', expr)
    stateName = ['begin']
    ctx = []
    mayend = fsm[stateName[0]].end()
    while stateName:
        print('>>',str(stateName),expr)
        mayend = fsm[stateName[0]].end()
        expr, _db, ctx, scope = fsm[stateName[0]].val(expr, db.copy(), scope, ctx)
        #print('->',str(ctx))
        stateName = [   nextState
                           for nextState in fsm[stateName[0]].next()
                           if fsm[nextState].validate(expr) ]
    if not mayend:
        raise Exception('Synthax error in expression %s' % expr)
    print('>>',str(stateName),str(ctx), expr)
    print(expr,'->',str(ctx))
    return expr,ctx

def parse(fsm,_expr,db,scope):
    print('________________')
    print(_expr)
    expr,ret = pparse(fsm,_expr,db,scope)
    if expr:
        print('synthax error on %s cant end like that' % expr)
        raise Exception('synthax error on %s cant end like that' % expr)
    if not ret:
        print('empty return value')
        raise Exception('empty return value')
    print(ret)
    return ret[-1]

_nameregex = r'^([a-z_][A-Za-z0-9_]*)'
_dotregex = r'^\.'
_indexregex = r'^\[([0-9]+)\]'
_stringregex = r"^'([^']*)'"
_numberregex = r"^([0-9]+(\.[0-9]+)?)"
_boolregex = r"^(True)|(False)"
_namedotindexregex = r"[a-z][A-Za-z0-9_]*((\[[0-9]+\])|(\.[a-z][A-Za-z0-9_]+))*"
_valregex = r"('[^']*')|([0-9]+(\.[0-9]+)?)|(%s)|(True)|(False)" % _namedotindexregex
_dictbeginregex = r"^(\{)"
_dictvalregex = r"^([a-z_][a-zA-Z0-9_]*)=>"
_dictcomaregex = r"^(,)"
_dictendregex = r"^(\})"
_listbeginregex = r"^(\[)"
_listvalregex = r"^(%s)"
_listcomaregex = r"^(,)"
_listendregex = r"^(\])"


class Base(object):
    pass

class Begin(Base):
    def __init__(self, beginWith=[]):
        self.beginWith = beginWith

    def validate(self, expr):
        return True

    def next(self):
        return self.beginWith

    def end(self):
        return False

    def val(self, expr, db, scope, ctx=[]):
        return expr, db, ctx, scope


class Coma(Base):
    def validate(self, expr):
        return re.match(r',', expr)

    def next(self):
        return ['bool', 'string', 'number', 'name']

    def end(self):
        return False

    def val(self, expr, db, scope=[], ctx=[]):
        exprMatch = self.validate(expr)
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr, db, ctx, scope


class Name(Base):
    def validate(self, expr):
        return re.match(_nameregex, expr)

    def next(self):
        return ['dot', 'index', 'coma']

    def end(self):
        return True

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        key = exprMatch.group(1)
        if key in db:
            val = db[key]
        else:
            val = []
        ctx.append(val)
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr,db, ctx, scope


class Dot(Base):
    def validate(self, expr):
        return re.match(_dotregex, expr)

    def next(self):
        return ['name']

    def end(self):
        return False

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        if not ctx:
            raise Exception('dot is not permitted here')
        first = ctx[-1]
        rest = ctx[:-1]
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr, first, rest, scope


class Index(Base):
    def validate(self, expr):
        return re.match(_indexregex, expr)

    def next(self):
        return ['dot','coma']

    def end(self):
        return True

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        index = int(exprMatch.group(1))
        if not ctx:
            raise Exception('[index] is not permitted here')
        lst = ctx[-1]
        assert isinstance(lst,list)
        if index < len(lst):
            val = ctx[-1][index]
        else:
            val = []
        ctx.append(val)
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr, db, ctx, scope


class String(Base):
    def validate(self, expr):
        return re.match(_stringregex, expr)

    def next(self):
        return ['coma']

    def end(self):
        return True

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        key = exprMatch.group(1)
        ctx.append(key)
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr, key, ctx, scope

class Number(Base):
    def validate(self, expr):
        return re.match(_numberregex, expr)

    def next(self):
        return ['coma']

    def end(self):
        return True

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        key = int(exprMatch.group(1))
        ctx.append(key)
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr, key, ctx, scope


class Bool(Base):
    def validate(self, expr):
        return re.match(_boolregex, expr)

    def next(self):
        return ['coma']

    def end(self):
        return True

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        val = exprMatch.group(1) == 'True'
        ctx.append(val)
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr, db, ctx, scope


class DictBegin(Base):
    def validate(self, expr):
        return re.match(_dictbeginregex, expr)

    def next(self):
        return ['dictval','dictend']

    def end(self):
        return False

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        expr = expr.replace(exprMatch.group(0), '', 1)
        ctx.append({})
        return expr, db, ctx, scope


class DictVal(Base):
    '''
    recursive dictionary is only possible by declarring dictionary first then refer it.
    '''

    def validate(self, expr):
        return re.match(_dictvalregex, expr)

    def next(self):
        return ['dictcoma', 'dictend']

    def end(self):
        return False

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        dc = ctx[-1]
        assert isinstance(dc,dict)
        key = exprMatch.group(1)
        if key in dc:
            raise Exception("immutability error key(%s) is already bound in dictionary")
        expr = expr.replace(exprMatch.group(0), '', 1)
        expr, val = pparse(fsm, expr, db, scope)
        dc[key] = val[-1]
        #ctx[-1] = dc
        return expr, db, ctx, scope


class DictComa(Base):
    def validate(self, expr):
        return re.match(_dictcomaregex, expr)

    def end(self):
        return False

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr, db, ctx, scope

    def next(self):
        return ['dictval']


class DictEnd(Base):
    def validate(self, expr):
        return re.match(_dictendregex, expr)

    def next(self):
        return []

    def end(self):
        return True

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr, db, ctx, scope


class LList(Base):
    def validate(self, expr):
        return re.match(_listbeginregex, expr)

    def next(self):
        return ['coma']

    def end(self):
        return False

    def val(self, expr, db, scope, ctx={}):
        exprMatch = self.validate(expr)
        expr = expr.replace(exprMatch.group(0), '', 1)
        expr,tmp = pparse(fsm, expr, db, scope)
        expr = expr.replace(re.match(_listendregex, expr).group(0), '', 1)
        ctx.append(tmp)
        return expr, db, ctx, scope



class NameSpace(Base):
    def validate(self, expr):
        return re.match(r"^([a-z][a-zA-Z0-9_]*)::", expr)

    def next(self):
        return ['function']

    def end(self):
        return False

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        ns = exprMatch.group(1)
        if ns not in scope:
            raise Exception("undefined namespace %s in scope %s" % (ns,scope.keys()))
        scope = scope[ns]
        expr = expr.replace(exprMatch.group(0), '', 1)
        return expr, db, ctx, scope


class Function(Base):
    def validate(self, expr):
        return re.match(r"^([a-z][a-zA-Z0-9_]*)\(", expr)

    def next(self):
        return ['coma']

    def end(self):
        return True

    def val(self, expr, db, scope, ctx=[]):
        exprMatch = self.validate(expr)
        fnname = exprMatch.group(1)
        expr = expr.replace(exprMatch.group(0), '', 1)
        print('subparse ->',expr)
        expr, val = pparse(fsm, expr, db, scope)
        print('subparsed ->',val,expr)
        exprEndMatch = re.match(r"^\)", expr)
        expr = expr.replace(exprEndMatch.group(0), '', 1)
        if fnname not in scope:
            raise Exception('undefined function %s in scope %s' % (fnname,scope.keys()))
        if not val:
            raise Exception('returned empty')
        ret = scope[fnname](val[0])
        ctx.append(ret)
        return expr, db, ctx,  scope



fsm = {
    'begin': Begin(['namespace', 
                    'function', 
                    'bool', 
                    'string', 
                    'number', 
                    'name', 
                    'dictbegin', 
                    'list']),
    'name': Name(),
    'dot': Dot(),
    'index': Index(),
    'string': String(),
    'number': Number(),
    'coma' : Coma(),
    'bool': Bool(),
    'dictbegin': DictBegin(),
    'dictval': DictVal(),
    'dictcoma': DictComa(),
    'dictend': DictEnd(),
    'list': LList(),
    'namespace': NameSpace(),
    'function': Function()
}


class test(unittest.TestCase):
    def setUp(self):
        # from http://www.json-generator.com/
        self.db = {
            "_id": "56004f237312c964d528fc54",
            "index": 0,
            "guid": "8d780a2d-ac24-4be3-8ef6-eca6cbbee423",
            "isActive": True,
            "balance": "$1,510.18",
            "picture": "http://placehold.it/32x32",
            "age": 28,
            "eyeColor": "blue",
            "name": "Bridgett Beach",
            "gender": "female",
            "company": "MANTRIX",
            "email": "bridgettbeach@mantrix.com",
            "phone": "+1 (936) 470-2164",
            "address": "141 Russell Street, Yettem, Marshall Islands, 3033",
            "about": "Veniam ipsum eu veniam ipsum ullamco. Occaecat incididunt pariatur ex pariatur est qui officia sint laborum minim nulla occaecat consequat reprehenderit. Anim irure pariatur ipsum amet eiusmod et nisi. In adipisicing sit pariatur elit ut consectetur qui. Incididunt officia et sit tempor fugiat pariatur fugiat non adipisicing ad ex irure culpa.\r\n",
            "registered": "2015-03-06T01:58:40 +05:00",
            "latitude": -50.749588,
            "longitude": 34.268301,
            "tags": ["do", "aliquip", "cillum", "ullamco", "fugiat", "eu", "amet"],
            "friends": [
                {"id": 0, "name": "Deborah Randall"},
                {"id": 1, "name": "English Steele"},
                {"id": 2, "name": "Mckinney Norman"}
            ],
            "greeting": "Hello, Bridgett Beach! You have 8 unread messages.",
            "favoriteFruit": "strawberry"
        }
        self.scope = {
            'test': lambda param: param,
            'ns' : {
                'hello' : lambda who : 'hello '+str(who)
            }
        }

    def test_listception(self):
	expr, result = ("['a',1,True,['b',2,False]]",['a',1,True,['b',2,False]])
	self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_true(self):
        expr, result = ("True", True)
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_false(self):
        expr, result = ("False", False)
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_string(self):
        expr, result = ("'a string deh'", "a string deh")
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_numberstring(self):
        expr, result = ("'256'", "256")
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_number(self):
        expr, result = ("256", 256)
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_name_num(self):
        expr, result = ("index", self.db['index'])
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_name_str(self):
        expr, result = ("guid", self.db['guid'])
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_name_bool(self):
        expr, result = ("isActive", self.db['isActive'])
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_name_list(self):
        expr, result = ("tags", self.db['tags'])
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_name_list_index(self):
        expr, result = ("tags[3]", self.db['tags'][3])
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_name_list_dict(self):
        expr, result = ("friends[0]", self.db['friends'][0])
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_name_dict(self):
        expr, result = ("friends[1].name", self.db['friends'][1]['name'])
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_name_invalid(self):
        expr = ".name"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_name_invalid2(self):
        expr = ".[0]"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_name_invalid3(self):
        expr = "name..name"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_name_invalid4(self):
        expr = "name."
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_name_invalid5(self):
        expr = "name name"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_name_invalid6(self):
        expr = "name.[0]"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_name_invalid7(self):
        expr = "name[name]"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_name_invalid8(self):
        expr = "name[0"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_name_invalid9(self):
        expr = "123asd"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_string_invalidA(self):
        expr = "'unfinished string"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_bool_invalidB(self):
        expr = "True.False"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_dict(self):
        expr, result = ("{a=>'a',b=>12,c=>True,d=>index}", {'a': 'a', 'b': 12, 'c': True, 'd': self.db['index']})
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_dict_string(self):
        expr, result = ("{a=>'a'}", {'a': 'a'})
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_dict_num(self):
        expr, result = ("{a=>12}", {'a': 12})
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_dict_bool(self):
        expr, result = ("{a=>True}", {'a': True})
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_dict_name(self):
        expr, result = ("{a=>index}", {'a': self.db['index']})
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_dict_name_index_name(self):
        expr, result = ("{a=>friends[0].name}", {'a': self.db['friends'][0]['name']})
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_dict_invalid(self):
        expr = "{"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_dict_invalid2(self):
        expr = "}"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_dict_invalid3(self):
        expr = "=>"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_dict_invalid4(self):
        expr = "{asdfaf"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_dict_invalid5(self):
        expr = "azdaf}"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_dict_invalid8(self):
        expr = "{aasd=>Sdf"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_dict_invalid9(self):
        expr = "{12=>id}"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_dict_invalidA(self):
        expr = "{a=>id,a=>2}"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_list(self):
        expr, result = ("[index,0,'a',True]", [self.db['index'], 0, "a", True])
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_list_name(self):
        expr, result = ("[friends[0].name]", [self.db['friends'][0]['name']])
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_list_invalid(self):
        expr = "["
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_function_bool(self):
        expr, result = ("test(False)", False)
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_function_string(self):
        expr, result = ("test('world')", "world")
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_function_name(self):
        expr, result = ("test(index)", 0)
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_function_number(self):
        expr, result = ("test(12)", 12)
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_function_namespace(self):
        expr, result = ("ns::hello('world')", "hello world")
        self.assertEqual(result, parse(fsm, expr, self.db, self.scope))

    def test_function_undefinied(self):
        expr = "hello('')"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_function_invalid(self):
        expr = "hello()"
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)

    def test_function_invalid2(self):
        expr = "hello("
        self.assertRaises(Exception, parse, fsm, expr, self.db, self.scope)


if __name__ == '__main__':
    unittest.main()


