portal: added wip for PI validation page
[myslice.git] / manifold / util / clause.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Implements a clause
5 # - a "tree" (more precisely a predecessor map, typically computed thanks to a DFS) 
6 # - a set of needed fields (those queried by the user)
7 #
8 # Copyright (C) UPMC Paris Universitas
9 # Authors:
10 #   Jordan AugĂ©       <jordan.auge@lip6.fr> 
11 #   Marc-Olivier Buob <marc-olivier.buob@lip6.fr>
12
13 import pyparsing as pp
14 import operator, re
15
16 from manifold.util.predicate import Predicate
17 from types                 import StringTypes
18
19 # XXX When to use Keyword vs. Regex vs. CaselessLiteral
20 # XXX capitalization ?
21
22 # Instead of CaselessLiteral, try using CaselessKeyword. Keywords are better
23 # choice for grammar keywords, since they inherently avoid mistaking the leading
24 # 'in' of 'inside' as the keyword 'in' in your grammar.
25
26
27 class Clause(object):
28
29     def __new__(cls, *args, **kwargs):
30         if len(args) == 1 and isinstance(args[0], StringTypes):
31             return ClauseStringParser().parse(args[0])
32         return super(Clause, cls).__new__(cls, *args, **kwargs)
33
34     def __init__(self, *args, **kwargs):
35         if len(args) == 2:
36             # unary
37             self.operator = Predicate.operators[args[0]]
38             self.operands = [args[1]]
39         elif len(args) == 3:
40             self.operator = Predicate.operators[args[1]]
41             self.operands = [args[0], args[2]]
42         else:
43             raise Exception, "Clause can only be unary or binary"
44                 
45     def opstr(self, operator):
46         ops = [string for string, op in Predicate.operators.items() if op == operator]
47         return ops[0] if ops else ''
48
49     def __repr__(self):
50         if len(self.operands) == 1:
51             return "%s(%s)" % (self.operator, self.operands[0])
52         else:
53             return "(%s %s %s)" % (self.operands[0], self.opstr(self.operator), self.operands[1])
54
55 class ClauseStringParser(object):
56
57     def __init__(self):
58         """
59         BNF HERE
60         """
61
62         #integer = pp.Word(nums)
63         #floatNumber = pp.Regex(r'\d+(\.\d*)?([eE]\d+)?')
64         point = pp.Literal( "." )
65         e     = pp.CaselessLiteral( "E" )
66
67         # Regex string representing the set of possible operators
68         # Example : ">=|<=|!=|>|<|="
69         OPERATOR_RX = '|'.join([re.sub('\|', '\|', o) for o in Predicate.operators.keys()])
70
71         # predicate
72         field = pp.Word(pp.alphanums + '_')
73         operator = pp.Regex(OPERATOR_RX).setName("operator")
74         value = pp.QuotedString('"') #| pp.Combine( pp.Word( "+-"+ pp.nums, pp.nums) + pp.Optional( point + pp.Optional( pp.Word( pp.nums ) ) ) + pp.Optional( e + pp.Word( "+-"+pp.nums, pp.nums ) ) )
75
76         predicate = (field + operator + value).setParseAction(self.handlePredicate)
77
78         # clause of predicates
79         and_op = pp.CaselessLiteral("and") | pp.Keyword("&&")
80         or_op  = pp.CaselessLiteral("or")  | pp.Keyword("||")
81         not_op = pp.Keyword("!")
82
83         predicate_precedence_list = [
84             (not_op, 1, pp.opAssoc.RIGHT, lambda x: self.handleClause(*x)),
85             (and_op, 2, pp.opAssoc.LEFT,  lambda x: self.handleClause(*x)),
86             (or_op,  2, pp.opAssoc.LEFT,  lambda x: self.handleClause(*x))
87         ]
88         clause = pp.operatorPrecedence(predicate, predicate_precedence_list)
89
90         self.bnf = clause
91
92     def handlePredicate(self, args):
93         return Predicate(*args)
94
95     def handleClause(self, args):
96         return Clause(*args)
97
98     def parse(self, string):
99         return self.bnf.parseString(string,parseAll=True)
100
101 if __name__ == "__main__":
102     print ClauseStringParser().parse('country == "Europe" || ts > "01-01-2007" && country == "France"')
103     print Clause('country == "Europe" || ts > "01-01-2007" && country == "France"')