CORD-1385: Autogenerate validation code

Change-Id: I8dda8f78482b382cd5d9c5397070266324d4fab9
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py b/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py
index a929a9c..336373f 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/fol2.py
@@ -2,10 +2,11 @@
 import ast
 import random
 import string
+import jinja2
 from plyxproto.parser import *
 import pdb
 
-BINOPS = ['|', '&', '=>']
+BINOPS = ['|', '&', '->']
 QUANTS = ['exists', 'forall']
 
 
@@ -89,13 +90,14 @@
                 or_expr = ' or '
                 and_expr = ' and '
 
-        if k == '=':
+        if k in ['=','in']:
             v = [self.format_term_for_query(
                 model, term, django=django) for term in v]
             if django:
-                operator = ' = '
+                operator_map = {'=':' = ','in':'__in'}
             else:
-                operator = ' == '
+                operator_map = {'=':' == ','in':'in'}
+            operator = operator_map[k]
             return [q_bracket % operator.join(v)]
         elif k == '|':
             components = [self.fol_to_python_filter(
@@ -105,7 +107,7 @@
             components = [self.fol_to_python_filter(
                 model, x, django=django).pop() for x in v]
             return [and_expr.join(components)]
-        elif k == '=>':
+        elif k == '->':
             components = [self.fol_to_python_filter(
                 model, x, django=django).pop() for x in v]
             return ['~%s | %s' % (components[0], components[1])]
@@ -137,10 +139,10 @@
                     return {'hoist': ['const', fol], 'result': 'True'}
                 else:
                     return {'hoist': [], 'result': fol}
-            elif k == '=':
+            elif k in ['=', 'in']:
                 lhs, rhs = v
                 if not lhs.startswith(var) and not rhs.startswith(var):
-                    return {'hoist': ['=', fol], 'result': 'True'}  # XXX
+                    return {'hoist': [k, fol], 'result': 'True'}  # XXX
                 else:
                     return {'hoist': [], 'result': fol}
             elif k in BINOPS:
@@ -148,7 +150,7 @@
                 rlhs = self.hoist_constants(lhs, var)
                 rrhs = self.hoist_constants(rhs, var)
 
-                if rlhs['hoist'] and rrhs['hoist']:
+                if rlhs['hoist'] and rrhs['hoist'] and rlhs['result']=='True' and llhs['result']=='True':
                     return {'hoist': ['=', fol], 'result': 'True'}
                 elif rlhs['hoist']:
                     return {'hoist': [k, lhs], 'result': rhs}
@@ -196,6 +198,26 @@
             else:
                 return fol
 
+    def gen_validation_function(self, fol, policy_name, message, tag):
+        if not tag:
+            tag = gen_random_string()
+
+        policy_function_name = 'policy_%(policy_name)s_%(random_string)s' % {
+            'policy_name': policy_name, 'random_string': tag}
+        self.verdict_next()
+        function_str = """
+def %(fn_name)s(obj, ctx):
+    if not %(vvar)s: raise ValidationError("%(message)s")
+        """ % {'fn_name': policy_function_name, 'vvar': self.verdict_variable, 'message': message}
+
+        function_ast = self.str_to_ast(function_str)
+        policy_code = self.gen_test(fol, self.verdict_variable)
+
+
+        function_ast.body = [policy_code] + function_ast.body
+
+        return function_ast
+
     def gen_test_function(self, fol, policy_name, tag):
         if not tag:
             tag = gen_random_string()
@@ -213,7 +235,7 @@
 
         function_ast.body = [policy_code] + function_ast.body
 
-        return astunparse.unparse(function_ast)
+        return function_ast
 
     def gen_test(self, fol, verdict_var, bindings=None):
         if isinstance(fol, str):
@@ -221,7 +243,35 @@
 
         (k, v), = fol.items()
 
-        if k == '=':
+        if k == 'python':
+            try:
+                expr_ast = self.str_to_ast(v)
+            except SyntaxError:
+                raise PolicyException('Syntax error in %s' % v)
+
+            if not isinstance(expr_ast, ast.Expr):
+                raise PolicyException(
+                    '%s is not an expression' % expr_ast)
+
+            assignment_str = """
+%(verdict_var)s = (%(escape_expr)s)
+            """ % {'verdict_var': self.verdict_variable, 'escape_expr': v}
+
+            assignment_ast = self.str_to_ast(assignment_str)
+            return assignment_ast
+        elif k == 'not':
+            top_vvar = self.verdict_variable
+            self.verdict_next()
+            sub_vvar = self.verdict_variable
+            block = self.gen_test(v, sub_vvar)
+            assignment_str = """
+%(verdict_var)s = not (%(subvar)s)
+                    """ % {'verdict_var': top_vvar, 'subvar': sub_vvar}
+
+            assignment_ast = self.str_to_ast(assignment_str)
+
+            return ast.Module(body=[block, assignment_ast])
+        elif k in ['=','in']:
             # This is the simplest case, we don't recurse further
             # To use terms that are not simple variables, use
             # the Python escape, e.g. {{ slice.creator is not None }}
@@ -259,18 +309,23 @@
             except TypeError:
                 pass
 
+            if k=='=':
+                operator='=='
+            elif k=='in':
+                operator='in'
+
             comparison_str = """
-%(verdict_var)s = (%(lhs)s == %(rhs)s)
-            """ % {'verdict_var': verdict_var, 'lhs': lhs, 'rhs': rhs}
+%(verdict_var)s = (%(lhs)s %(operator)s %(rhs)s)
+            """ % {'verdict_var': verdict_var, 'lhs': lhs, 'rhs': rhs, 'operator':operator}
 
             comparison_ast = self.str_to_ast(comparison_str)
-
             combined_ast = ast.Module(body=assignments + [comparison_ast])
+
             return combined_ast
         elif k in BINOPS:
             lhs, rhs = v
 
-            top_vvar = self.verdict_variable
+            top_vvar = verdict_var
 
             self.verdict_next()
             lvar = self.verdict_variable
@@ -286,7 +341,7 @@
                 binop = 'and'
             elif k == '|':
                 binop = 'or'
-            elif k == '=>':
+            elif k == '->':
                 binop = 'or'
                 invert = 'not'
 
@@ -360,12 +415,24 @@
 
             return ast.Module(body=[python_ast, negate_ast])
 
-def xproto_fol_to_python_test(fol, model, tag=None):
+def xproto_fol_to_python_test(policy, fol, model, tag=None):
+    if isinstance(fol, jinja2.Undefined):
+        raise Exception('Could not find policy:', policy)
+
     f2p = FOL2Python()
     fol = f2p.hoist_constants(fol)
-    a = f2p.gen_test_function(fol, 'output', tag)
-    return a
+    a = f2p.gen_test_function(fol, policy, tag='enforcer')
+    return astunparse.unparse(a)
 
+def xproto_fol_to_python_validator(policy, fol, model, message, tag=None):
+    if isinstance(fol, jinja2.Undefined):
+        raise Exception('Could not find policy:', policy)
+
+    f2p = FOL2Python()
+    fol = f2p.hoist_constants(fol)
+    a = f2p.gen_validation_function(fol, policy, message, tag='validator')
+    
+    return astunparse.unparse(a)
 
 def main():
     while True:
@@ -374,7 +441,7 @@
         fol_parser = yacc.yacc(module=FOLParser(), start='goal')
 
         val = fol_parser.parse(inp, lexer=fol_lexer)
-        a = xproto_fol_to_python_test(val, 'output')
+        a = xproto_fol_to_python_test('pol', val, 'output', 'Test')
         print a