import re
import unittest
import logging

logger = logging.getLogger(__name__)

'''
SPEC CHANGE !!!!!
now predicate will be accessible as expreesion synthac that retrun bool

SPEC
~ quine operator
$ payload operator

will you use doc generate instead of a routine ?
how can you do it?
{
	"static_str" : "static str",
	"static_num" : 2,
	"static_lst" : [],
	"eval_on_create" : "=>$.index",
	"static_lambda" : "($.index)",
	"static_str_expression" : "%.index"
}
any json document is  callable
all str starting with => will be runned with given parameter
how to implement lambda chain and predicate ?
well it have to support many executable paradigm
then lambda and lambdarray work the same.
but any dict will search for predicate, doc, and return
doc is the only place where a dict is templated with =>
return is a straight lambda?

memoised parser
store parse by acces query , not code itself
$.what('love') is stored with '$.what' as key
it does not make sense....
i think memoize will keep lambda itself or function hash for function
and that it.
THINKABOUTIT
curryed function cant just put aside one value and recuperate it later. this is why I will give it a stack
maybe we are missing the point of structural programming
by implementing {key => value} ans [value]
who does exactly {"key":value} and [value]
maybe we should just let straight json here
that mean no string int bool declaration too
RULE
variable start with lowercase, then [index] (param) {key} and . are authorised
function and namespace start with uppercase
the only way to reach an odd name like Peach or 23l337 will use {}
lambda are string, and a relative functor is generated on the fly
TODO
closure mecanism where a lambda is declared with alternative self
predicate as a valid expression that return bool type
<type> synthax
_ operator, mean nothing
closure as {predicate=>[...],routine=>[...],return=>...}
predicate
list comprehension
function lib
user function
TODONT
WISHLIST
sql sugar synthax %SELECT FROM WHERE%()
regex sugar synthax /^[^\.]+$/gmi()
filename sugar synthax #./#
bitsynthax <...>
'''

class Context(object):
    expr = ""
    lib = {}
    itself = {}
    fn = []

def pparse(fsm, expr,  lib, itself={}):
    logger.debug('pparse(%s, %s, %s)' % (fsm, expr, lib))
    stateName = ['begin']
    ctx = Context()
    ctx.expr = expr
    ctx.lib = lib
    ctx.itself = itself
    ctx.fn = []
    mayend = fsm[stateName[0]].end()
    while stateName:
        logger.debug('pparse(...) # %s' % stateName)
        mayend = fsm[stateName[0]].end()
        ctx.expr = ctx.expr.strip()
        ctx = fsm[stateName[0]].parse(ctx)
        stateName = [nextState
                     for nextState in fsm[stateName[0]].next()
                     if nextState in fsm
                     if fsm[nextState].validate(ctx.expr.strip())]
    if not mayend:
        logger.error('pparse(...) ! Synthax error in expression [%s]' % expr)
        raise Exception('Synthax error in expression [%s]' % expr)
    logger.debug('pparse(...) -> (%s, %s)' % (expr, ctx))
    return ctx.expr.strip(), ctx.fn

#if each curry carry a stack, it will permit stackless operation
def evallst(fnlst,arg):
    ret = arg
    for fn in fnlst:
        ret = fn(ret,arg)
    return ret

_nope = lambda ctx: []


class Base(object):
    def __init__(self, beginWith=[], canend=False):
        self.beginWith = beginWith
        self.canend = canend

    def validate(self, expr):
        raise NotImplementedError("Unimplemented")

    def next(self):
        return self.beginWith

    def end(self):
        return self.canend

    def parse(self, ctx):
        raise NotImplementedError("Unimplemented")


class Begin(Base):
    def validate(self, expr):
        return True

    def parse(self, ctx):
        logger.debug('Begin:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        return ctx


class ItSelf(Base):
    def validate(self, expr):
        return re.match(r'^\~', expr)

    def parse(self, ctx):
        logger.debug('ItSelf:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        ctx.fn.append(lambda scope,payload: ctx.itself)
        return ctx


class Payload(Base):
    def validate(self, expr):
        return re.match(r'^\$', expr)

    def parse(self, ctx):
        logger.debug('Payload:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        #ctx.fn.append(lambda scope,param: param)
        return ctx


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

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('Attribute:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        logger.debug('Name(%s)' % ctx)
        exprMatch = self.validate(ctx.expr)
        key = exprMatch.group(1)
        # closure
        def closure(scope,payload):
	    assert isinstance(scope,dict)
            if key in scope:
                ret = scope[key]
            else:
                ret = []
            return ret

        ctx.fn.append(closure)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('Name(...) -> (%s)' % ctx)
        return ctx


_nameregex = r'^([a-z_][A-Za-z0-9_]*)'
#class Name(Base):
#    def validate(self, expr):
#        return re.match(_nameregex, expr)
#
#    def end(self):
#        return True
#
#    def parse(self, ctx):
#        logger.debug('Name:parse(%s)' % ctx)
#        assert isinstance(ctx, Context)
#        logger.debug('Name(%s)' % ctx)
#        exprMatch = self.validate(ctx.expr)
#        key = exprMatch.group(1)
#        # closure
#        def closure(param):
#            if key in ctx.scope:
#                ret = ctx.scope[key]
#            else:
#                ret = []
#            return ret
#
#        ctx.fn.append(closure)
#        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
#        logger.debug('Name(...) -> (%s' % ctx)
#        return ctx


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

    def parse(self, ctx):
        logger.debug('Dot:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        logger.debug('Dot(%s)' % ctx)
        exprMatch = self.validate(ctx.expr)
        # closure
        def closure(scope,payload):
            assert isinstance(scope, dict)
            return scope

        ctx.fn.append(closure)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('Dot(...) -> (%s)' % ctx)
        return ctx


class Index(Base):
    def validate(self, expr):
        return re.match(r'^\[', expr)

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('Index:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        expr, fns = pparse(fsm, ctx.expr, ctx.lib, ctx.itself)
	ctx.expr = expr.strip()
        ctx.expr = ctx.expr.replace(re.match(r'^\]', ctx.expr).group(0), '', 1)
        # closure
        def closure(scope,payload):
            assert isinstance(scope, list)
            index = evallst(fns,payload)
            assert isinstance(index, int)
            if len(scope) > index:
                ret = scope[index]
            else:
                ret = []
            return ret

        ctx.fn.append(closure)
        return ctx


class Key(Base):
    def validate(self, expr):
        return re.match(r'^\{', expr)

    def validateEnd(self, expr):
        return re.match(r'^\}', expr)

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('Key:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        ctx.expr, fns = pparse(fsm, ctx.expr, ctx.lib, ctx.itself)
        ctx.expr = ctx.expr.replace(self.validateEnd(ctx.expr).group(0), '', 1)
        # closure
        def closure(scope,payload):
            if not isinstance(scope, dict):
                return []
            key = evallst(fns,payload)
            assert isinstance(key, str)
            if key in scope:
                ret = scope[key]
            else:
                ret = []
            return ret

        ctx.fn.append(closure)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('Index(...) -> (%s)' % (ctx))
        return ctx


_stringregex = r"^'([^']*)'"
class String(Base):
    def validate(self, expr):
        return re.match(_stringregex, expr)

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('String:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        logger.debug('String(%s)' % ctx)
        exprMatch = self.validate(ctx.expr)
        ret = exprMatch.group(1)
        ctx.fn.append(lambda scope,payload: ret)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('String(...) -> (%s)' % ctx)
        return ctx


_numberregex = r"^([0-9]+(\.[0-9]+)?)"
class Number(Base):
    def validate(self, expr):
        return re.match(_numberregex, expr)

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('Number:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        logger.debug('Number(%s)' % ctx)
        exprMatch = self.validate(ctx.expr)
        # closure
        def closure(scope,payload):
            if exprMatch.group(2):
                ret = float(exprMatch.group(1))
            else:
                ret = int(exprMatch.group(1))
            return ret

        ctx.fn.append(closure)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('Number(...) -> (%s)' % ctx)
        return ctx


_boolregex = r"^(True)|(False)"
class Bool(Base):
    def validate(self, expr):
        return re.match(_boolregex, expr)

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('Bool:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        logger.debug('Bool(%s)' % ctx)
        exprMatch = self.validate(ctx.expr)
        ret = exprMatch.group(1) == 'True'
        ctx.fn.append(lambda scope,payload: ret)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('Bool(...) -> (%s)' % ctx)
        return ctx


_bindregex = r"^([a-z_][a-zA-Z0-9_]*)[\n\r\t ]*=>"
class Bind(Base):
    def validate(self, expr):
        return re.match(_bindregex, expr)

    def next(self):
        return []

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('Bind:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        key = exprMatch.group(1)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        rest, fns = pparse(fsm, ctx.expr, ctx.lib, ctx.itself)
        ctx.expr = rest
        ctx.fn.append(lambda scope,payload: payload)
        ctx.fn += fns
        ctx.fn.append(lambda scope,payload: {key: scope})
        return ctx


_dictbeginregex = r"^(\{)"
class DictBegin(Base):
    def validate(self, expr):
        return re.match(_dictbeginregex, expr)

    def parse(self, ctx):
        logger.debug('DictBegin:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        ctx.fn.append(lambda scope,payload: {})
        logger.debug('DictBegin(...) -> (%s)' % ctx)
        return ctx


class DictVal(Base):
    def validate(self, expr):
        return True

    def parse(self, ctx):
        logger.debug('DictVal:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        rest, fns = pparse(fsmbind, ctx.expr, ctx.lib, ctx.itself)
        ctx.expr = rest
        # closure
        def closure(scope,payload):
            assert isinstance(scope, dict)
            param = evallst(fns, payload)
            assert isinstance(param, dict)
            assert not [True for key in param.keys() if key in scope]
            scope.update(param)
            return scope

        ctx.fn.append(closure)
        logger.debug('DictVal(...) -> (%s)' % ctx)
        return ctx


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

    def parse(self, ctx):
        logger.debug('DictComa:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('DictComa(...) -> (%s)' % ctx)
        return ctx


_dictendregex = r"^(\})"
class DictEnd(Base):
    def validate(self, expr):
        return re.match(_dictendregex, expr)

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('DictEnd:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('DictEnd(...) -> (%s)' % ctx)
        return ctx


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

    def validateEnd(self, expr):
        return re.match(r'\]', expr)

    def parse(self, ctx):
        logger.debug('LList:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        ctx.fn.append(lambda scope,payload: [])
        logger.debug('LList(...) -> (%s)' % ctx)
        return ctx


class LListVal(Base):
    def validate(self, expr):
        return True

    def parse(self, ctx):
        logger.debug('LListVal:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        rest, fns = pparse(fsm, ctx.expr, ctx.lib, ctx.itself)
        ctx.expr = rest
        # closure
        def closure(scope,payload):
            assert isinstance(scope,list)
            param = evallst(fns,payload)
            scope.append(param)
            return scope

        ctx.fn.append(closure)
        logger.debug('LListVal(...) -> (%s)' % ctx)
        return ctx


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

    def parse(self, ctx):
        logger.debug('LListComa:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('LListComa(...) -> (%s)' % ctx)
        return ctx


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

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('LListEnd:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('LListEnd(...) -> (%s)' % ctx)
        return ctx

def docBuildFn(doc,lib,itself):
	fnlst = []
	if isinstance(doc,dict):
		for key,val in doc.items():
			pass
	elif isinstance(doc,list):
		for val in doc:
			pass
	elif isinstance(doc,bool):
		pass
	elif isinstance(doc,int):
		pass
	elif isinstance(doc,float):
		pass
	elif isinstance(doc,str):
		evalop = re.match(r'^=>',doc.strip())
		if evalop:
			expr = doc.strip().replace(evalop.group(0),'',1)
			expr,sfnlst = pparse(fsm,expr,lib,itself)
		else:
			pass
		

def _makeLLambda(expr, lib, itself={}):
    # (lambda)
    if isinstance(expr, str):
        lambdaExpr = expr
        exprMatch = re.match(r'^\(', expr.strip())
        expr = expr.replace(exprMatch.group(0), '', 1).strip()
        expr, fnlst = pparse(fsm, expr.strip(), lib, itself)
        expr = expr.replace(re.match(r'^\)', expr).group(0), '', 1).strip()
        lambdaExpr = lambdaExpr.replace(expr, '', 1)
        return expr, fnlst
    # ['(lambda1)','(lambda2)'] => lambda2(lambda1(..))
    elif isinstance(expr, list):
        fnlst = []
        for subexpr in expr:
            if not isinstance(subexpr, str):
                raise Exception('monad error')
            rest, _fnlst = _makeLLambda(subexpr, lib, itself)
	    assert not rest #should be empty
            fnlst += _fnlst
        return '', fnlst
    # {"predicate":[],"routine":[],"return":""}
    elif isinstance(expr, dict):
        assert 'code' in expr
        codeFnLst = []
        for code in expr['code']:
            assert 'return' in code
            predicatelst = []
            routinelst = []
            if 'predicate' in code:
                for predicate in code['predicate']:
                    restExpr, fnlst = pparse(fsmpredicate, predicate, lib, itself)
                    assert not restExpr
                    predicatelst = predicatelst + fnlst  # routine will execute only if all predicate say yes
            if 'routine' in code:
                for routine in code['routine']:
                    restExpr, fnlst = pparse(fsmbind, routine, lib, itself)
                    assert not restExpr
                    routinelst.append(fnlst)  # routine will execute in given order
            tcoExpr = code['return']
            restExpr, tcoFn = pparse(fsm, tcoExpr, lib, itself)
            assert not restExpr
            implementation = (predicatelst, routinelst, tcoFn)
            codeFnLst.append(implementation)
        # lamdba
        def matchPredicate(payload, predicateLst):
            for predicate in predicateLst:
                if not predicate(payload):
                    return False
            return True

        def closure(payload):
            retVal = None
            for predicatelst, routinelst, tco in codeFnLst:
                if matchPredicate(payload, predicatelst):
                    pass
                    # apply routine
                    # return tco


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

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('LLambda:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        logger.debug('LLambda(%s)' % ctx)
        rest, _fnlst = _makeLLambda(ctx.expr, ctx.lib, ctx.itself)
        expr = ctx.expr.replace(rest, '', 1)
        ctx.fn.append(lambda scope,payload: expr)
        ctx.expr = rest
        logger.debug('LLambda(...) -> (%s)' % ctx)
        return ctx


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

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('LLambdaCall:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        exprMatch = self.validate(ctx.expr)
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        ctx.expr, fns = pparse(fsm, ctx.expr,ctx.lib, ctx.itself)
        exprEndMatch = re.match(r"^\)", ctx.expr)
        ctx.expr = ctx.expr.replace(exprEndMatch.group(0), '', 1)

        def closure(_lambdaExpr,payload):
            param =evallst(fns,payload)
            rest, _fnlst = _makeLLambda(_lambdaExpr, ctx.lib,ctx.itself)
            ret = evallst(_fnlst,param)
            return ret

        ctx.fn.append(closure)
        return ctx


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

    def parse(self, ctx):
        logger.debug('NameSpace:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        logger.debug('NameSpace(%s)' % ctx)
        exprMatch = self.validate(ctx.expr)
        ns = exprMatch.group(1)
        if ns not in ctx.lib:
            raise Exception("undefined namespace %s in scope %s" % (ns, ctx.lib.keys()))
        ctx.lib = ctx.lib[ns]
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        logger.debug('NameSpace(...) -> (%s)' % ctx)
        return ctx


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

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('Function:parse(%s)' % ctx)
        assert isinstance(ctx, Context)
        logger.debug('Function(%s)' % ctx)
        exprMatch = self.validate(ctx.expr)
        fnname = exprMatch.group(1)
        if fnname not in ctx.lib:
            raise Exception('undefined function %s in scope %s' % (fnname, ctx.lib.keys()))
        ctx.expr = ctx.expr.replace(exprMatch.group(0), '', 1)
        rest, fns = pparse(fsm, ctx.expr, ctx.lib, ctx.itself)
        ctx.expr = rest
        exprEndMatch = re.match(r"^\)", ctx.expr)
        ctx.expr = ctx.expr.replace(exprEndMatch.group(0), '', 1)
        ctx.fn.append(lambda scope,payload: payload)
        ctx.fn += fns
        ctx.fn.append(lambda scope,payload: ctx.lib[fnname](scope))
        logger.debug('Function(...) -> (%s)' % ctx)
        return ctx


def _makePredicate(expr, lib, itself={}, fn=[]):
    expr, leftfn = pparse(fsm, expr, lib, itself)
    expr, operatorfn = pparse(fsmpredicate, expr, lib, itself)
    fn += leftfn
    fn += operatorfn
    return expr, fn


class PredicateEqual(Base):
    def validate(self, expr):
        return re.match(r"^==", expr)

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('PredicateEqual:parse(%s)' % ctx)
        ctx.expr = ctx.expr.replace(self.validate(ctx.expr).group(0), '', 1)
        ctx, rightfn = pparse(fsm, ctx.expr, ctx.lib, ctx.itself)

        def closure(leftArg,payload):
            rightArg = evallst(rightfn, payload)
            result = leftArg == rightArg
            return result

        ctx.fn.append(closure)
        return ctx


class PredicateIs(Base):
    def validate(self, expr):
        return re.match(r"^IS", expr)

    def end(self):
        return True

    def parse(self, ctx):
        logger.debug('PredicateIs:parse(%s)' % ctx)
        ctx.expr = ctx.expr.replace(self.validate(ctx.expr).group(0), '', 1)
        #fn.append(lambda (left, right): left != right)
        return ctx

navlst = ['dot',
          'index',
          'lambdacall',
          'key']
fsm = {
    'begin': Begin(['payload',
                    'itself',
                    'namespace',
                    'function',
                    'bool',
                    'string',
                    'number',
                    #'name',
                    'dictbegin',
                    'listbegin',
                    'lambda']),
    'payload'     : Payload(navlst,True),
    'itself'    : ItSelf(navlst,True),
    'attribute' : Attribute(navlst),  # name.attribute
    'dot'       : Dot(['attribute']),  # name.subname
    'index'     : Index(navlst),  # name[index]
    'key'       : Key(navlst),  # name{key}
    'string'    : String(),  # 'string'
    'number'    : Number(),  # 42
    'bool'      : Bool(),  # True False
    'dictbegin' : DictBegin(['dictval', 'dictend']),  # {
    'dictval'   : DictVal(['dictcoma', 'dictend']),  # name => value
    'dictcoma'  : DictComa(['dictval']),  # ,
    'dictend'   : DictEnd(),  # }
    'listbegin' : LList(['listval']),  # [
    'listval'   : LListVal(['listcoma', 'listend']),  # value
    'listcoma'  : LListComa(['listval']),  # ,
    'listend'   : LListEnd(['lambdacall']),  # ]
    'namespace' : NameSpace(['function']),  # UpperCaseStartingName::
    'function'  : Function(),  # UpperCaseStartingName(value)
    'lambda'    : LLambda(['lambdacall']),  # (expression)
    'lambdacall': LLambdaCall(navlst)  # lowercasestartingname(value)
}

fsmpredicate = {
    'begin': Begin(['equal']),
    'equal': PredicateEqual()
}

fsmbind = {
    'begin': Begin(['bind']),
    'bind': Bind()
}


class Test(unittest.TestCase):
    def test_function_return_bool(self):
        expr = "Test(True)"
        rest,fnlst = pparse(fsm, expr, self.lib)
        self.assertFalse(rest,'should return empty string but got %s' % rest)
        try:
            result = evallst(fnlst,{})
        except Exception as e:
            err = "error while testing expression %s, exception raise %s" % (expr,e)
            raise Exception(err)
        self.assertTrue(isinstance(result, bool))

    def test_ascii_art(self):
        expr = """
        {
          id => 'paramecia',
          tail => [
                'word',
                'word',
                'word'
                ]
        }
        """
        expect = {
            'id': 'paramecia',
            'tail': [
                'word',
                'word',
                'word'
            ]
        }
        rest, fnlst = pparse(fsm, expr, self.lib)
        self.assertFalse(rest,'should be empty')
        result = evallst(fnlst,{})
        self.assertEqual(expect, result)

def buildTest():
    # from http://www.json-generator.com/
    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",
        "answerno": "('no')",
        "cat": "($)",
        "asstr": "(Str($))",
        "fourtytwo": ["(42)"]
    }
    lib = {
        'Str': lambda param: str(param),
        'Test': lambda param: param,
        'Ns': {
            'Hello': lambda who: 'hello ' + str(who)
        }
    }
    setattr(Test, 'db', db)
    setattr(Test, 'lib', lib)
    ###########################################################################
    lst_test_expr_lambda = [
        ('(True)', '(True)', 'test_lambda'),
	('(True)(False)', True, 'test_lambda_exec'),
	('((False))','((False))','test_lambda_ception'),
        ('((False))(True)','(False)','test_lambda_ception_exec'),
	('((False))(True)(True)',False,'test_lambda_ception_exec_ception')
    ]
    def closure_test_expr_lambda(expr, itself, expect, msg):
        def test_expr_lambda(self):
            rest, fnlst = pparse(fsm, expr, self.lib, itself)
            self.assertFalse(rest, 'should be empty but got %s' % rest)
            result = evallst(fnlst,{})
            self.assertEqual(expect, result, msg)
        return test_expr_lambda
    for expr, expect, msg in lst_test_expr_lambda:
        test_meth = closure_test_expr_lambda(expr, db, expect, msg)
        test_meth.__name__ = msg
        setattr(Test, msg, test_meth )
    #########################################################################
    lst_test_expr_raise = [
        ("()(",'test_raise_invalid_lambda'),
        ("Hello(", 'test_raise_invalid_function_unfinished'),
        ("Hello()", 'test_raise_invalid_function_nonary'),
        ("Hello('')", 'test_raise_invalid_function_non_exist_'),
        ("[", 'test_raise_invalid_bracket'),
        ("{", 'test_raise_invalid_curlybrace'),
        ("}", 'test_raise_invalid_curlybrace_again'),
        ("=>", 'test_raise_invalid_bind'),
        ("{asdfaf", 'test_raise_invalid_dict'),
        ("azdaf}", 'test_raise_invalid_dict_ending'),
        ("{aasd=>Sdf", 'test_raise_invalid_dict_val_name'),
        ("{12=>id}", 'test_raise_invalid_dict_key_num'),
        ("{a=>id,a=>2}", 'test_raise_invalid_dict_unref'),
        (".name", 'test_raise_invalid_dot_name'),
        (".[0]",    'test_raise_invalid_name_dot_index'),
        ("name..name", 'test_raise_invalid_name_dot_dot'),
        ("name.", 'test_raise_invalid_name_dot_nothing'),
        ("name name", 'test_raise_invalid_name_space_name'),
        ("name.[0]", 'test_raise_invalid_name_dot'),
        ("name[name]", 'test_raise_invalid_name_index_not_str'),
        ("name[0", 'test_raise_invalid_name_index'),
        ("123asd", 'test_raise_invalid_name_with_num'),
        ("'unfinished string", 'test_raise_invalid_str'),
        ("True.False", 'test_raise_invalid_bool')
    ]
    def closure_test_expr_raise(expr,msg):
        def test_expr_raise(self):
            ok = False
            try:
                pparse(fsm, expr, self.lib)
                self.fail('exception not raised on expression %s' % expr)
            except:
                ok = True
            self.assertTrue(ok)
        return test_expr_raise
    for expr,msg in lst_test_expr_raise:
        test_meth = closure_test_expr_raise(expr,msg)
        test_meth.__name__ = msg
        setattr(Test, msg, test_meth)
    #######################################################################
    lst_test_expr_result = [
        ("['a',1,True,['b',2,False]]", ['a', 1, True, ['b', 2, False]], 'test_listception'),
        ("'a string deh'", "a string deh", 'test_string'),
        ("'256'", "256", 'test_numberstring'),
        ("256", 256, 'test_number'),
        ("('word')", "('word')", 'test_lambda_declaration'),
        ("[($),(2)]", ['($)', '(2)'], 'test_lambda_list'),
        ("[$.index,0,'a',True]", [db['index'], 0, "a", True], 'test_list'),
        ("[$.friends]", [db['friends']], 'test_list_name'),
        ("[$.friends[0].name]", [db['friends'][0]['name']], 'test_list_name_restriction'),
        ("Test(False)", False, 'test_function_bool'),
        ("Test('world')", "world", 'test_function_string'),
        ("Test($.index)", 0, 'test_function_name'),
        ("Test(12)", 12, 'test_function_number'),
        ("Ns::Hello('world')", "hello world", 'test_function_namespace'),
        (
            "{a=>'a',b=>12,c=>True,d=>$.index}",
            {'a': 'a', 'b': 12, 'c': True, 'd': db['index']},
            'test_dict'),
        ("{a=>'a'}", {'a': 'a'}, 'test_dict_string'),
        ("{a=>12}", {'a': 12}, 'test_dict_num'),
        ("{a=>True}", {'a': True}, 'test_dict_bool'),
        ("{a=>$.index}", {'a': db['index']}, 'test_dict_name'),
        (
            "{a=>$.friends[0].name}",
            {'a': db['friends'][0]['name']},
            'test_dict_name_index_name'
        ),
        (
            "{a=>'a',b=>True,c=>3,d=>['word',{a=>2}],e=>{a=>1}}",
            {'a': 'a', 'b': True, 'c': 3, 'd': ['word', {'a': 2}], 'e': {'a': 1}},
            'test_dict_ception'
        ),
        ("$", db, 'payload'),
        ("True", True, 'test_true'),
        ("False", False, 'test_false')
    ]
    def closure_test_expr_result(expr, expect, msg):
        def test_expr_result(self):
            rest, fnlst = pparse(fsm, expr, self.lib)
            self.assertFalse(rest, 'should be empty but got %s' % rest)
            result = evallst(fnlst, self.db)
            self.assertEqual(expect, result, msg)
        return test_expr_result
    for expr, expect, msg in lst_test_expr_result:
        test_meth = closure_test_expr_result(expr, expect, msg)
        test_meth.__name__ = msg
        setattr(Test, msg, test_meth)
    ########################################################################
    lst_test_payload_result = [
        ("$.friends[2]{'name'}", "Mckinney Norman", 'test_scope_key'),
        ("$.friends[$.index].name", db['friends'][db['index']]['name'], 'test_scope_indexref'),
        ("$.index", db['index'], 'test_scope_name_num'),
        ("$.guid", db['guid'], 'test_scope_name_str'),
        ("$.isActive", db['isActive'], 'test_scope_name_bool'),
        ("$.tags", db['tags'], 'test_scope_name_list'),
        ("$.tags[3]", db['tags'][3], 'test_scope_name_list_index'),
        ("$.friends[0]", db['friends'][0], 'test_scope_name_list_dict'),
        ("$.friends[1].name", db['friends'][1]['name'], 'test_scope_name_dict'),
        ("$.cat('this')", "this", 'test_scope_lambda_call_with_param'),
        ("$.fourtytwo(52)", 42, 'test_scope_lambda_list_call'),
        ("$.answerno('')", "no", 'test_scope_lambda_call'),
        ("(Str($))(52)", "52", 'test_scope_lambda_undeclared_call')
    ]
    def closure_test_payload_result(expr, expect, msg):
        def test_payload_result(self):
            rest, fnlst = pparse(fsm, expr, self.lib)
            self.assertFalse(rest, 'should be empty but got %s' % rest)
            result = evallst(fnlst,self.db)
            self.assertEqual(expect, result, msg)
	return test_payload_result
    for expr, expect, msg in lst_test_payload_result:
            test_meth = closure_test_payload_result(expr, expect, msg)
            test_meth.__name__ = msg
            setattr(Test, msg, test_meth)
    ########################################################################
    lst_test_itself_result = [

	("~",db,"test_itself_with_itself"),
        ("~.friends[2]{'name'}", "Mckinney Norman", 'test_itself_key'),
        ("~.friends[~.index].name", 
         db['friends'][db['index']]['name'], 
         'test_itself_indexref'),
        ("~.index", db['index'], 'test_itself_name_num'),
        ("~.guid", db['guid'], 'test_itself_name_str'),
        ("~.isActive", db['isActive'], 'test_itself_name_bool'),
        ("~.tags", db['tags'], 'test_itself_name_list'),
        ("~.tags[3]", db['tags'][3], 'test_itself_name_list_index'),
        ("~.friends[0]", db['friends'][0], 'test_itself_name_list_dict'),
        ("~.friends[1].name", db['friends'][1]['name'], 'test_itself_name_dict'),
        ("~.cat('this')", "this", 'test_itself_lambda_call_with_param'),
        ("~.fourtytwo(52)", 42, 'test_itself_lambda_list_call'),
        ("~.answerno('')", "no", 'test_itself_lambda_call')
    ]
    def closure_test_itself_result(expr, expect, msg):
        def test_itself_result(self):
            rest, fnlst = pparse(fsm, expr, self.lib,self.db)
            self.assertFalse(rest, 'should be empty but got %s' % rest)
            result = evallst(fnlst,self.db)
            self.assertEqual(expect, result, msg)
        return test_itself_result
    for expr, expect, msg in lst_test_itself_result:
            test_meth = closure_test_itself_result(expr, expect, msg)
            test_meth.__name__ = msg
            setattr(Test, msg, test_meth)
    ########################################################################


if __name__ == '__main__':
    logging.basicConfig(filename=__name__+'.log', filemode='w',level=logging.DEBUG)
    buildTest()
    unittest.main()
    #suite = unittest.TestSuite()
    #suite.addTest(Test("test_dict"))
    #runner = unittest.TextTestRunner()
    #runner.run(suite)
