Skip to content

Commit 20cab6f

Browse files
committed
added python to current state
Since there were a lot of changes in the project since the python part started they will have to get integrated in the new structure.
1 parent c4d0523 commit 20cab6f

31 files changed

Lines changed: 1712 additions & 0 deletions

cuvee/src/cuvee/python/Expr.scala

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
package cuvee.python
2+
3+
import pythonparse.Ast
4+
import cuvee.pure.Expr
5+
import cuvee.pure.And
6+
import cuvee.pure.Var
7+
import cuvee.pure.Assoc
8+
import cuvee.pure.Sugar
9+
import cuvee.pure.Lit
10+
import cuvee.pure.Forall
11+
import cuvee.pure.Store
12+
import cuvee.pure.Old
13+
import cuvee.pure.Param
14+
import cuvee.pure.Is
15+
import cuvee.pure.Exists
16+
17+
object cExpr {
18+
val pyAdd = state.funs("pyAdd", 2)
19+
val pySub = state.funs("pySub", 2)
20+
val pyNegate = state.funs("pyNegate", 1)
21+
val pyTimes = state.funs("pyTimes", 2)
22+
val pyFloorDiv = state.funs("pyFloorDiv", 2)
23+
// TODO only support if boogie has this operator
24+
// val pyPow = state.funs("pyPow", 2)
25+
val pyMod = state.funs("pyMod", 2)
26+
27+
val pyEquals = state.funs("pyEquals", 2)
28+
val pyGreaterThan = state.funs("pyGreaterThan", 2)
29+
val pyGreaterEquals = state.funs("pyGreaterEquals", 2)
30+
val pyLessThan = state.funs("pyLessThan", 2)
31+
val pyLessEquals = state.funs("pyLessEquals", 2)
32+
33+
object pyAnd
34+
extends Sugar.commutative(state.funs("pyAnd", 2), pyTrue(), Assoc.right)
35+
object pyOr
36+
extends Sugar.commutative(state.funs("pyOr", 2), pyFalse(), Assoc.right)
37+
38+
val pyNot = state.funs("pyNot", 1)
39+
val pyImplies = state.funs("pyImplies", 2)
40+
val pyIte = state.funs("pyIte", 3)
41+
42+
val pyIn = state.funs("pyIn", 2)
43+
val pySelect = state.funs("pySelect", 2)
44+
val pyStore = state.funs("pyStore", 3)
45+
val pySlice = state.funs("pySlice", 3)
46+
val pyConcat = state.funs("pyConcat", 3)
47+
val pyShift = state.funs("pyShift", 2)
48+
49+
/** Maps pythonparse.Ast.expr to fitting cuvee.pure.Expr
50+
*
51+
* This function calls helper functions, which in turn call pymap back. It
52+
* could be writeen in one function, making the recursion more obvious, but
53+
* this would come at the cost of readability.
54+
*/
55+
def pymap(expr: Ast.expr): Expr = expr match {
56+
case Ast.expr.BoolOp(op, values) => boolOp(op, values)
57+
case Ast.expr.UnaryOp(op, operand) => unaryOp(op, operand)
58+
case Ast.expr.BinOp(left, op, right) => binaryOp(left, op, right)
59+
case Ast.expr.IfExp(test, body, orelse) =>
60+
pyIte(this.pymap(test), this.pymap(body), this.pymap(orelse))
61+
case Ast.expr.Compare(
62+
Ast.expr.Call(
63+
Ast.expr.Name(Ast.identifier("type"), _),
64+
Seq(expr),
65+
_,
66+
_,
67+
_
68+
),
69+
Seq(Ast.cmpop.Eq),
70+
Seq(Ast.expr.Name(pyType, _))
71+
) =>
72+
defType(pyType, expr)
73+
case Ast.expr.Compare(
74+
Ast.expr.Call(
75+
Ast.expr.Name(Ast.identifier("type"), _),
76+
Seq(expr1),
77+
_,
78+
_,
79+
_
80+
),
81+
Seq(Ast.cmpop.Eq),
82+
Seq(
83+
Ast.expr.Call(
84+
Ast.expr.Name(Ast.identifier("type"), _),
85+
Seq(expr2),
86+
_,
87+
_,
88+
_
89+
)
90+
)
91+
) =>
92+
defType(expr1, expr2)
93+
case Ast.expr.Compare(
94+
Ast.expr.Call(
95+
Ast.expr.Name(Ast.identifier("type"), _),
96+
Seq(expr),
97+
_,
98+
_,
99+
_
100+
),
101+
Seq(Ast.cmpop.NotEq),
102+
Seq(Ast.expr.Name(pyType, _))
103+
) =>
104+
pyNot(defType(pyType, expr))
105+
case Ast.expr.Compare(
106+
Ast.expr.Call(
107+
Ast.expr.Name(Ast.identifier("type"), _),
108+
Seq(expr1),
109+
_,
110+
_,
111+
_
112+
),
113+
Seq(Ast.cmpop.NotEq),
114+
Seq(
115+
Ast.expr.Call(
116+
Ast.expr.Name(Ast.identifier("type"), _),
117+
Seq(expr2),
118+
_,
119+
_,
120+
_
121+
)
122+
)
123+
) =>
124+
defType(expr1, expr2)
125+
case Ast.expr.Compare(left, Seq(op), Seq(comparator)) =>
126+
comp(left, op, comparator)
127+
case Ast.expr.Call(
128+
Ast.expr.Attribute(Ast.expr.Name(Ast.identifier("self"), _), func, _),
129+
args,
130+
_,
131+
_,
132+
_
133+
) =>
134+
call(func, args)
135+
case Ast.expr.Call(Ast.expr.Name(func, _), args, _, _, _) =>
136+
call(func, args)
137+
case Ast.expr.Subscript(values, slice, _) =>
138+
this.slice(values, slice)
139+
case Ast.expr.List(elems, _) =>
140+
pyArray(
141+
initArray(pyDefaultArray(), elems.toList),
142+
Lit(elems.length, int)
143+
)
144+
case Ast.expr.Attribute(
145+
Ast.expr.Name(Ast.identifier("self"), _),
146+
attr,
147+
_
148+
) =>
149+
Var("self." + attr.name, value)
150+
case Ast.expr.Num(n: BigInt) => pyInt(Lit(n, int))
151+
case Ast.expr.Name(id, _) => name(id)
152+
case _ => cuvee.undefined
153+
}
154+
155+
/* The python ast considers land and lor as n-ary operators with n >= 2.
156+
*/
157+
def boolOp(op: Ast.boolop, values: Seq[Ast.expr]): Expr = op match {
158+
case Ast.boolop.And => pyAnd(values.toList map pymap)
159+
case Ast.boolop.Or => pyOr(values.toList map pymap)
160+
}
161+
162+
def unaryOp(op: Ast.unaryop, operand: Ast.expr): Expr = op match {
163+
case Ast.unaryop.Not => pyNot(pymap(operand))
164+
case Ast.unaryop.USub => pyNegate(pymap(operand))
165+
case _ => cuvee.undefined
166+
}
167+
168+
def comp(
169+
left: Ast.expr,
170+
op: Ast.cmpop,
171+
comparator: Ast.expr
172+
): Expr = op match {
173+
case Ast.cmpop.Eq => pyEquals(pymap(left), pymap(comparator))
174+
case Ast.cmpop.NotEq => pyNot(pyEquals(pymap(left), pymap(comparator)))
175+
case Ast.cmpop.Lt => pyLessThan(pymap(left), pymap(comparator))
176+
case Ast.cmpop.LtE => pyLessEquals(pymap(left), pymap(comparator))
177+
case Ast.cmpop.Gt => pyGreaterThan(pymap(left), pymap(comparator))
178+
case Ast.cmpop.GtE => pyGreaterEquals(pymap(left), pymap(comparator))
179+
case Ast.cmpop.In => pyIn(pymap(left), pymap(comparator))
180+
case Ast.cmpop.NotIn => pyNot(pyIn(pymap(left), pymap(comparator)))
181+
case Ast.cmpop.Is => cuvee.undefined
182+
case Ast.cmpop.IsNot => cuvee.undefined
183+
}
184+
185+
/* Assigns static types to pyValues. They are defined in the parsed file
186+
* via 'requires'. This is necessary since smtlib does not have dynamic types.
187+
*/
188+
def defType(pyType: Ast.identifier, expr: Ast.expr): Expr =
189+
pyType.name match {
190+
case "int" => pyBool(Is(pymap(expr), pyInt))
191+
case "bool" => pyBool(Is(pymap(expr), pyBool))
192+
case "list" => pyBool(Is(pymap(expr), pyArray))
193+
}
194+
195+
/* Type equality implies: It could be any (available) type, which one does not matter.
196+
* The interesting part is: The types of both arguments are the same*/
197+
def defType(left: Ast.expr, right: Ast.expr): Expr = {
198+
val types = List(
199+
pyEquals(pyBool(Is(pymap(left), pyInt)), pyBool(Is(pymap(right), pyInt))),
200+
pyEquals(
201+
pyBool(Is(pymap(left), pyBool)),
202+
pyBool(Is(pymap(right), pyBool))
203+
),
204+
pyEquals(
205+
pyBool(Is(pymap(left), pyArray)),
206+
pyBool(Is(pymap(right), pyArray))
207+
)
208+
)
209+
pyOr(types)
210+
}
211+
212+
def binaryOp(
213+
left: Ast.expr,
214+
op: Ast.operator,
215+
right: Ast.expr
216+
): Expr = op match {
217+
case Ast.operator.Add => pyAdd(pymap(left), pymap(right))
218+
case Ast.operator.Sub => pySub(pymap(left), pymap(right))
219+
case Ast.operator.Mult => pyTimes(pymap(left), pymap(right))
220+
case Ast.operator.FloorDiv => pyFloorDiv(pymap(left), pymap(right))
221+
case Ast.operator.Mod => pyMod(pymap(left), pymap(right))
222+
// case Ast.operator.Pow => pyPow(pymap(left), pymap(right))
223+
case _ => cuvee.undefined
224+
}
225+
226+
def slice(values: Ast.expr, slice: Ast.slice) = slice match {
227+
case Ast.slice.Index(i) => pySelect(pymap(values), pymap(i))
228+
case Ast.slice.Slice(Some(lower), Some(upper), None) =>
229+
pySlice(pymap(values), pymap(lower), pymap(upper))
230+
case Ast.slice.Slice(Some(lower), None, None) =>
231+
pySlice(pymap(values), pymap(lower), pyInt(pyGetLength(pymap(values))))
232+
case Ast.slice.Slice(None, Some(upper), None) =>
233+
pySlice(pymap(values), pyInt(Lit(0, int)), pymap(upper))
234+
case Ast.slice.Slice(None, None, None) =>
235+
pySlice(
236+
pymap(values),
237+
pyInt(Lit(0, int)),
238+
pyInt(pyGetLength(pymap(values)))
239+
)
240+
case _ =>
241+
cuvee.error("Currently only default steps for slice are supported.");
242+
}
243+
244+
def call(func: Ast.identifier, args: Seq[Ast.expr]): Expr = func.name match {
245+
case "old" => Old(pymap(args(0)))
246+
case "out" | "result" => pyResult
247+
case "len" => pyInt(pyGetLength(pymap(args(0))))
248+
case "imp" => pyImplies(pymap(args(0)), pymap(args(1)))
249+
case "forall" =>
250+
args match {
251+
case Seq(
252+
elem,
253+
Ast.expr
254+
.Lambda(
255+
Ast.arguments(arguments: Seq[Ast.expr.Name], _, _, _),
256+
body
257+
)
258+
) =>
259+
pyBool(
260+
Forall(
261+
arguments.toList map formal,
262+
pyIsTrue(
263+
pyImplies(
264+
pyBool(conditions(arguments.toList, elem)),
265+
pymap(body)
266+
)
267+
)
268+
)
269+
)
270+
}
271+
case "exists" =>
272+
args match {
273+
case Seq(
274+
elem,
275+
Ast.expr
276+
.Lambda(
277+
Ast.arguments(arguments: Seq[Ast.expr.Name], _, _, _),
278+
body
279+
)
280+
) =>
281+
pyBool(
282+
Exists(
283+
arguments.toList map formal,
284+
pyIsTrue(
285+
pyImplies(
286+
pyBool(conditions(arguments.toList, elem)),
287+
pymap(body)
288+
)
289+
)
290+
)
291+
)
292+
}
293+
}
294+
295+
def formal(name: Ast.expr.Name) = Var(name.id.name, value)
296+
297+
/* Builds boundaries for targets of quanitifers.
298+
* e.g. forall x. 0 <= x < array.Length
299+
*/
300+
def conditions(names: List[Ast.expr.Name], elem: Ast.expr): Expr = {
301+
val isPyInt =
302+
names flatMap (x => List(Is(pymap(x), pyInt))) // TODO other types?
303+
val boundaries = names flatMap (x =>
304+
List(
305+
pyIsTrue(pyLessEquals(pyInt(Lit(0, int)), pymap(x))),
306+
pyIsTrue(
307+
pyLessThan(pymap(x), pyInt(pyGetLength(pymap(elem))))
308+
)
309+
)
310+
)
311+
And(isPyInt ++ boundaries)
312+
}
313+
314+
/* Python lists with a static length, defined via requires.
315+
*/
316+
def initArray(id: Expr, elems: List[Ast.expr], index: Int = 0): Expr =
317+
elems match {
318+
case Nil => id
319+
case head :: next =>
320+
Store(
321+
initArray(id, next, index + 1),
322+
Lit(index, int),
323+
pymap(head)
324+
)
325+
}
326+
327+
def name(id: Ast.identifier): Expr = id match {
328+
case Ast.identifier("True") => pyTrue()
329+
case Ast.identifier("False") => pyFalse()
330+
case Ast.identifier("None") => pyNone()
331+
case _ => Var(id.name, value)
332+
}
333+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package cuvee.python
2+
3+
import fastparse._
4+
import pythonparse.Ast
5+
import cuvee.State
6+
7+
class Parser {
8+
def parsePython(lines: String): List[Ast.stmt] = {
9+
val parser: P[_] => P[Any] = pythonparse.Statements.file_input(_)
10+
11+
parse(lines, parser) match {
12+
case fastparse.Parsed.Success(value: Seq[Ast.stmt], index) =>
13+
value.toList
14+
case fastparse.Parsed.Failure(label, index, extra) =>
15+
throw new RuntimeException("Parse Error")
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)