├── .gitignore ├── LICENSE ├── README.org ├── examples ├── blocksworld-domain.pddl ├── blocksworld-problem.pddl ├── blocksworld.py ├── briefcaseworld-domain.pddl └── briefcaseworld.py ├── mockups ├── aaa.py ├── bbb.py ├── blocksworld-mockup.py ├── ccc.py ├── ddd.py └── eee.py └── pddl_in_python ├── __init__.py └── stacktrace.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | #* 3 | /__pycache__/ 4 | *.pyc 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Masataro Asai 4 | Copyright (c) 2021 International Business Machines 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | 2 | 3 | * PDDL in Python -- Python DSL for writing a PDDL 4 | 5 | A minimal implementation of a DSL which allows people to write PDDL in python. 6 | Based on parsing python's AST. 7 | 8 | Author: Masataro Asai 9 | 10 | License: MIT. 11 | 12 | # It is much more cumbersome to write it in python than in Lisp 13 | # because python lacks the similar level of flexibility. 14 | 15 | Example in examples/blocksworld.py: 16 | 17 | #+begin_src python 18 | class Blocksworld(Domain): 19 | def move_b_to_b(bm, bf, bt): 20 | if clear[bm] and clear[bt] and on[bm, bf]: 21 | clear[bt] = False 22 | on[bm, bf] = False 23 | on[bm, bt] = True 24 | clear[bf] = True 25 | 26 | def move_b_to_t(bm, bf): 27 | if clear[bm] and on[bm, bf]: 28 | on[bm, bf] = False 29 | on_table[bm] = True 30 | clear[bf] = True 31 | 32 | def move_t_to_b(bm, bt): 33 | if clear[bm] and clear[bt] and on_table[bm]: 34 | clear[bt] = False 35 | on_table[bm] = False 36 | on[bm, bt] = True 37 | 38 | print(Blocksworld()) 39 | #+end_src 40 | 41 | will print 42 | 43 | #+begin_src lisp 44 | (domain blocksworld 45 | (:requirement :strips) 46 | (:types) 47 | (:predicates 48 | (clear ?x0) 49 | (on ?x0 ?x1) 50 | (on-table ?x0)) 51 | (:action move-b-to-b :parameters (?bm ?bf ?bt) 52 | :preconditions 53 | (and 54 | (clear ?bm) 55 | (clear ?bt) 56 | (on ?bm ?bf)) 57 | :effects 58 | (and 59 | (not (clear ?bt)) 60 | (not (on ?bm ?bf)) 61 | (on ?bm ?bt) 62 | (clear ?bf))) 63 | (:action move-b-to-t :parameters (?bm ?bf) 64 | :preconditions 65 | (and 66 | (clear ?bm) 67 | (on ?bm ?bf)) 68 | :effects 69 | (and 70 | (not (on ?bm ?bf)) 71 | (on-table ?bm) 72 | (clear ?bf))) 73 | (:action move-t-to-b :parameters (?bm ?bt) 74 | :preconditions 75 | (and 76 | (clear ?bm) 77 | (clear ?bt) 78 | (on-table ?bm)) 79 | :effects 80 | (and 81 | (not (clear ?bt)) 82 | (not (on-table ?bm)) 83 | (on ?bm ?bt)))) 84 | #+end_src 85 | 86 | Example in examples/briefcaseworld.py: 87 | 88 | #+begin_src python 89 | class location: 90 | pass 91 | class portable: 92 | pass 93 | class Briefcaseworld(Domain): 94 | def move(m : location, l : location): 95 | if is_at[m]: 96 | is_at[l] = True 97 | is_at[m] = False 98 | for x in all(portable): # current python syntax does not allow annotating loop variable 99 | if _in[x]: 100 | at[x,l] = True 101 | at[x,m] = False 102 | 103 | def take_out(x : portable): 104 | if _in[x]: 105 | _in[x] = False 106 | 107 | def put_in(x : portable, l : location): 108 | if not _in[x] and at[x,l] and is_at[l]: 109 | _in[x] = True 110 | 111 | print(Briefcaseworld()) 112 | #+end_src 113 | 114 | will print 115 | 116 | #+begin_src lisp 117 | (domain briefcaseworld 118 | (:requirement :strips) 119 | (:types 120 | portable - object) 121 | (:predicates 122 | (is-at ?x0) 123 | (in ?x0) 124 | (at ?x0 ?x1)) 125 | (:action move :parameters (?m - location ?l - location) 126 | :preconditions 127 | (is-at ?m) 128 | :effects 129 | (and 130 | (is-at ?l) 131 | (not (is-at ?m)) 132 | (forall (?x - portable) 133 | (when (in ?x) 134 | (and 135 | (at ?x ?l) 136 | (not (at ?x ?m))))))) 137 | (:action put-in :parameters (?x - portable ?l - location) 138 | :preconditions 139 | (and 140 | (not (in ?x)) 141 | (at ?x ?l) 142 | (is-at ?l)) 143 | :effects 144 | (in ?x)) 145 | (:action take-out :parameters (?x - portable) 146 | :preconditions 147 | (in ?x) 148 | :effects 149 | (not (in ?x)))) 150 | #+end_src 151 | -------------------------------------------------------------------------------- /examples/blocksworld-domain.pddl: -------------------------------------------------------------------------------- 1 | (define (domain blocksworld) 2 | (:predicates (clear ?x) 3 | (on-table ?x) 4 | (on ?x ?y)) 5 | 6 | (:action move-b-to-b 7 | :parameters (?bm ?bf ?bt) 8 | :precondition (and (clear ?bm) (clear ?bt) (on ?bm ?bf)) 9 | :effect (and (not (clear ?bt)) (not (on ?bm ?bf)) 10 | (on ?bm ?bt) (clear ?bf))) 11 | 12 | (:action move-b-to-t 13 | :parameters (?bm ?bf) 14 | :precondition (and (clear ?bm) (on ?bm ?bf)) 15 | :effect (and (not (on ?bm ?bf)) 16 | (on-table ?bm) (clear ?bf))) 17 | 18 | (:action move-t-to-b 19 | :parameters (?bm ?bt) 20 | :precondition (and (clear ?bm) (clear ?bt) (on-table ?bm)) 21 | :effect (and (not (clear ?bt)) (not (on-table ?bm)) 22 | (on ?bm ?bt)))) 23 | 24 | -------------------------------------------------------------------------------- /examples/blocksworld-problem.pddl: -------------------------------------------------------------------------------- 1 | (define (problem BW-rand-5) 2 | (:domain blocksworld) 3 | (:objects b1 b2 b3 b4 b5 - block) 4 | (:init 5 | (handempty) 6 | (ontable b1) 7 | (on b2 b4) 8 | (on b3 b2) 9 | (on b4 b1) 10 | (ontable b5) 11 | (clear b3) 12 | (clear b5)) 13 | (:goal 14 | (and 15 | (on b1 b5) 16 | (on b3 b2) 17 | (on b4 b3) 18 | (on b5 b4)))) 19 | -------------------------------------------------------------------------------- /examples/blocksworld.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | sys.path.insert(0, '../') 5 | 6 | from pddl_in_python import Domain 7 | 8 | class Blocksworld(Domain): 9 | def move_b_to_b(bm, bf, bt): 10 | if clear[bm] and clear[bt] and on[bm, bf]: 11 | clear[bt] = False 12 | on[bm, bf] = False 13 | on[bm, bt] = True 14 | clear[bf] = True 15 | 16 | def move_b_to_t(bm, bf): 17 | if clear[bm] and on[bm, bf]: 18 | on[bm, bf] = False 19 | on_table[bm] = True 20 | clear[bf] = True 21 | 22 | def move_t_to_b(bm, bt): 23 | if clear[bm] and clear[bt] and on_table[bm]: 24 | # clear[bt] = False 25 | # on_table[bm] = False 26 | # on[bm, bt] = True 27 | # 28 | # tuple assignment also works 29 | clear[bt], on_table[bm], on[bm, bt] = False, False, True 30 | 31 | 32 | print(Blocksworld()) 33 | -------------------------------------------------------------------------------- /examples/briefcaseworld-domain.pddl: -------------------------------------------------------------------------------- 1 | (define (domain briefcase) 2 | (:requirements :adl) 3 | (:types portable location) 4 | (:predicates (at ?y - portable ?x - location) 5 | (in ?x - portable) 6 | (is-at ?x - location)) 7 | 8 | 9 | (:action move 10 | :parameters (?m ?l - location) 11 | :precondition (is-at ?m) 12 | :effect (and (is-at ?l) (not (is-at ?m)) 13 | (forall (?x - portable) 14 | (when (in ?x) 15 | (and (at ?x ?l) (not (at ?x ?m))))))) 16 | 17 | (:action take-out 18 | :parameters (?x - portable) 19 | :precondition (in ?x) 20 | :effect (not (in ?x))) 21 | 22 | (:action put-in 23 | :parameters (?x - portable ?l - location) 24 | :precondition (and (not (in ?x)) (at ?x ?l) (is-at ?l)) 25 | :effect (in ?x))) 26 | 27 | -------------------------------------------------------------------------------- /examples/briefcaseworld.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | sys.path.insert(0, '../') 5 | 6 | from pddl_in_python import Domain 7 | 8 | # I could use either type hint notation e.g. m : location, or default value e.g. m = locaiton. 9 | # unfortunately, the current python interpreter looks for a class definition in the read-time. 10 | 11 | class location: 12 | pass 13 | class portable: 14 | pass 15 | class Briefcaseworld(Domain): 16 | def move(m : location, l : location): 17 | if is_at[m]: 18 | is_at[l] = True 19 | is_at[m] = False 20 | for x in all(portable): # current python syntax does not allow annotating loop variable 21 | if _in[x]: 22 | at[x,l] = True 23 | at[x,m] = False 24 | 25 | def take_out(x : portable): 26 | if _in[x]: 27 | _in[x] = False 28 | 29 | def put_in(x : portable, l : location): 30 | if not _in[x] and at[x,l] and is_at[l]: 31 | _in[x] = True 32 | 33 | print(Briefcaseworld()) 34 | -------------------------------------------------------------------------------- /mockups/aaa.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class A: 4 | a : str 5 | b : int 6 | 7 | print(A.__dict__) 8 | print(A().__dict__) 9 | 10 | A.a = "a" 11 | 12 | print(A.__dict__) 13 | print(A().__dict__) 14 | print(A.a) 15 | 16 | A.a = 1 17 | 18 | print(A.__dict__) 19 | print(A().__dict__) 20 | print(A.a) 21 | print(A().a) 22 | print(A.b) 23 | -------------------------------------------------------------------------------- /mockups/bbb.py: -------------------------------------------------------------------------------- 1 | 2 | def aaa(x): 3 | pass 4 | 5 | print(aaa.__name__) 6 | print(aaa.__dict__) 7 | print(vars(aaa)) 8 | 9 | import inspect 10 | print(inspect.signature(aaa).parameters) 11 | -------------------------------------------------------------------------------- /mockups/blocksworld-mockup.py: -------------------------------------------------------------------------------- 1 | 2 | class Variable: 3 | pass 4 | 5 | class Predicate: 6 | pass 7 | 8 | class Condition: 9 | pass 10 | 11 | 12 | def predicate(fn): 13 | pass 14 | def preconditions(fn): 15 | pass 16 | def effects(fn): 17 | pass 18 | def domain(fn): 19 | pass 20 | 21 | 22 | 23 | 24 | 25 | @domain 26 | class Blocksworld: 27 | 28 | # predicates 29 | 30 | # type annotation styles. these are not good because we can't specify the parameter names. 31 | # Wrapping them will end up being redundant. 32 | clear: Callable[[Object], bool] 33 | on_table: Callable[[Object], bool] 34 | on: Callable[[Object, Object], bool] 35 | 36 | clear: Predicate[Object] 37 | on_table: Predicate[Object] 38 | on: Predicate[Object, Object] 39 | 40 | # redundant 41 | clear: Predicate[Object("x")] 42 | on_table: Predicate[Object("x")] 43 | on: Predicate[Object("x"), Object("y")] 44 | 45 | clear: Predicate[1] 46 | on_table: Predicate[1] 47 | on: Predicate[2] 48 | 49 | # class attribute style 50 | 51 | # redundant 52 | clear = Predicate("x") 53 | on_table = Predicate("x") 54 | on = Predicate("x","y") 55 | 56 | # using class initialization code. XXX this does not have access to class instance object 57 | Predicate("clear", "x") 58 | Predicate("on", "x","y") 59 | 60 | # ugly 61 | @classmethod 62 | .... 63 | 64 | # Sexp style 65 | predicates = [ 66 | ["clear", "x"], 67 | ["on_table", "x"], 68 | ["on", "x", "y"] 69 | ] 70 | 71 | # decorator style. can use inspect.signature(fn).parameters 72 | @predicate 73 | def clear(x): 74 | pass 75 | @predicate 76 | def on_table(x): 77 | pass 78 | @predicate 79 | def on(x, y): 80 | pass 81 | 82 | # runtime error and fix approach 83 | def predicates(): 84 | clear(x) 85 | on_table(x) 86 | on(x, y) 87 | 88 | 89 | # constants 90 | table : Object 91 | 92 | table = Object() 93 | 94 | objects = ["table"] 95 | 96 | # actions 97 | # type declaration style --- same as predicates, not informative when producing pddls 98 | move_b_to_b: Callable[[Object, Object, Object], Action] 99 | 100 | # declrator style 101 | @preconditions 102 | def move_b_to_b(bm, bf, bt): 103 | return clear(bm) and clear(bt) and on(bm, bf) 104 | @effects 105 | def move_b_to_b(bm, bf, bt): 106 | return not clear(bt) and not on(bm, bf) and on(bm, bt) and clear(bf) 107 | 108 | @preconditions 109 | def move_b_to_t(bm, bf): 110 | return clear(bm) and on(bm, bf) 111 | @effects 112 | def move_b_to_t(bm, bf): 113 | return not on(bm, bf) and on_table(bm) and clear(bf) 114 | 115 | @preconditions 116 | def move_t_to_b(bm, bt): 117 | return clear(bm) and clear(bt) and on_table(bm) 118 | @effects 119 | def move_t_to_b(bm, bt): 120 | return not clear(bt) and not on_table(bm) and on(bm, bt) 121 | 122 | 123 | -------------------------------------------------------------------------------- /mockups/ccc.py: -------------------------------------------------------------------------------- 1 | 2 | """moduledoc""" 3 | print(locals()) 4 | print(globals()) 5 | 6 | 7 | class A: 8 | """classdoc""" 9 | print("hi") 10 | print(A) 11 | print(locals()) 12 | print(globals()) 13 | pass 14 | 15 | print(A()) 16 | -------------------------------------------------------------------------------- /mockups/ddd.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # doesnt work 4 | 5 | # def fn(): 6 | # clear(x) 7 | # 8 | # 9 | # def eval_with_fix(f): 10 | # try: 11 | # f() 12 | # except NameError as e: 13 | # print(e) 14 | # name = re.findall("name '(\w+)' is not defined",str(e))[0] 15 | # print(name) 16 | # 17 | # fail = True 18 | # args = [] 19 | # while fail: 20 | # argstring = ','.join(args) 21 | # print(f"def {name}({argstring}): pass") 22 | # print(exec(f"def {name}({argstring}): pass",globals())) 23 | # try: 24 | # f() 25 | # fail = False 26 | # except NameError as e: 27 | # print(e) 28 | # argname = re.findall("name '(\w+)' is not defined",str(e))[0] 29 | # print(argname) 30 | # args.extend(argname) 31 | # return 32 | # 33 | # eval_with_fix(fn) 34 | 35 | def fn(): 36 | clear("x") 37 | 38 | 39 | def eval_with_fix(f): 40 | fail = True 41 | f.undefined = {} 42 | while fail: 43 | try: 44 | f() 45 | fail = False 46 | except NameError as e: 47 | print(e) 48 | name = re.findall("name '(\w+)' is not defined",str(e))[0] 49 | print(name) 50 | 51 | body = f"def {name}(*args): {f.__name__}.undefined['{name}'] = args" 52 | print(body) 53 | print(exec(body,globals())) 54 | return 55 | 56 | eval_with_fix(fn) 57 | print(vars(fn)) 58 | -------------------------------------------------------------------------------- /mockups/eee.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | import inspect 3 | import ast 4 | 5 | def fn(x): 6 | return 1 7 | 8 | print(ast.dump(ast.parse(inspect.getsource(fn)))) 9 | 10 | class A: 11 | def a(x): 12 | y[a], y[b] = True, False 13 | y = x[1] 14 | y[1] = x 15 | y["a"] = x 16 | y[1,x] = x 17 | y = True 18 | print(x) 19 | if x and not y: 20 | print(x) 21 | return 1 22 | 23 | print(inspect.getsource(A.a)) # has an indent 24 | print(ast.dump(ast.parse(inspect.getsource(A)))) 25 | 26 | parsed = ast.parse(textwrap.dedent(inspect.getsource(A.a))) 27 | print(ast.dump(parsed)) 28 | print(parsed.body) 29 | print(parsed.body[0]) 30 | print(parsed.body[0].name) 31 | print(parsed.body[0].args.posonlyargs) 32 | print(parsed.body[0].args.args) 33 | print([arg.arg for arg in parsed.body[0].args.args]) 34 | print(parsed.body[0].body) 35 | for body in parsed.body[0].body: 36 | print(ast.dump(body)) 37 | 38 | 39 | # fail 40 | # def fn2(x): 41 | # unknownfn() = 1 42 | 43 | 44 | # fail 45 | def fn3(x): 46 | unknownfn[1] = 1 47 | 48 | print(ast.dump(ast.parse(inspect.getsource(fn3)))) 49 | -------------------------------------------------------------------------------- /pddl_in_python/__init__.py: -------------------------------------------------------------------------------- 1 | from . import stacktrace 2 | import warnings 3 | import ast 4 | import textwrap 5 | import functools 6 | import inspect 7 | from dataclasses import dataclass 8 | from typing import Optional 9 | 10 | @dataclass 11 | class Variable: 12 | name : str 13 | type : Optional[str] = None 14 | def __str__(self): 15 | if self.type: 16 | return f"?{self.name} - {self.type}" 17 | else: 18 | return f"?{self.name}" 19 | 20 | @dataclass 21 | class Object(Variable): 22 | def __str__(self): 23 | if self.type: 24 | return f"{self.name} - {self.type}" 25 | else: 26 | return f"{self.name}" 27 | 28 | 29 | @dataclass 30 | class Condition: 31 | def __and__(a, b): 32 | return And([a, b]) 33 | def __or__(a, b): 34 | return Or([a, b]) 35 | def __invert__(a): 36 | return Not(a) 37 | 38 | @dataclass 39 | class Predicate(Condition): 40 | name : str 41 | args : list[Variable] 42 | def __str__(self): 43 | s = f"({self.name}" 44 | for arg in self.args: 45 | s += f" {arg}" 46 | s += ")" 47 | return s 48 | 49 | @dataclass 50 | class And(Condition): 51 | conditions : list[Condition] 52 | def __str__(self): 53 | s = f"(and" 54 | for c in self.conditions: 55 | s += textwrap.indent(f"\n{c}"," ") 56 | s += ")" 57 | return s 58 | @dataclass 59 | class Or(Condition): 60 | conditions : list[Condition] 61 | def __str__(self): 62 | s = f"(or" 63 | for c in self.conditions: 64 | s += textwrap.indent(f"\n{c}"," ") 65 | s += ")" 66 | return s 67 | @dataclass 68 | class Not(Condition): 69 | a : Condition 70 | def __str__(self): 71 | s = f"(not {self.a})" 72 | return s 73 | @dataclass 74 | class When(Condition): 75 | test : Condition 76 | body : Condition 77 | def __str__(self): 78 | s = f"(when {self.test}" 79 | s += textwrap.indent(f"\n{self.body}"," ") 80 | s += ")" 81 | return s 82 | 83 | 84 | @dataclass 85 | class Quantifier(Condition): 86 | variables : list[Variable] 87 | body : Condition 88 | quantifier : str 89 | def __str__(self): 90 | s = f"({self.quantifier} (" 91 | for i, v in enumerate(self.variables): 92 | if i != 0: 93 | s += " " 94 | s += str(v) 95 | s += ")" 96 | s += textwrap.indent(f"\n{self.body}"," ") 97 | s += ")" 98 | return s 99 | 100 | 101 | @dataclass 102 | class Action: 103 | name: str 104 | params: list[str] 105 | preconditions : Optional[Condition] 106 | effects : Optional[Condition] 107 | def __str__(self): 108 | s = "" 109 | s += f"(:action {self.name} :parameters (" 110 | first = True 111 | for p in self.params: 112 | if not first: 113 | s += f" " 114 | s += f"{p}" 115 | first = False 116 | s += f")" 117 | if self.preconditions: 118 | s += textwrap.indent(f"\n:preconditions\n{self.preconditions}"," ") 119 | if self.effects: 120 | s += textwrap.indent(f"\n:effects\n{self.effects}"," ") 121 | s += ")" 122 | return s 123 | 124 | 125 | # Note: All methods must begin with __ . 126 | # Otherwise, the method itself is recognized as an action by the parser. 127 | 128 | def kebab(s): 129 | return "-".join([ sub for sub in s.split("_") if sub != ""]) 130 | 131 | class Domain: 132 | def __init__(self): 133 | self.__actions__ = {} 134 | self.__predicates__ = {} 135 | self.__types__ = {} 136 | for name in dir(self): 137 | method = getattr(self,name) 138 | if name[0] != "_" and callable(method): 139 | self.__actions__[name] = self.__parse(method) 140 | 141 | def __parse(self,action): 142 | action = ast.parse(textwrap.dedent(inspect.getsource(action))).body[0] 143 | name = kebab(action.name) 144 | args = [Variable(arg.arg,arg.annotation.id) if arg.annotation else Variable(arg.arg, None) 145 | for arg in action.args.args] 146 | 147 | def parse_toplevel(body): 148 | if isinstance(body[0],ast.If): 149 | if len(body) > 1: 150 | # it has several conditional effects 151 | return Action(name, args, None, parse_effects(body)) 152 | else: 153 | # it has preconditions 154 | return Action(name, args, *parse_precondition(body[0])) 155 | elif isinstance(body[0],ast.Assign): 156 | # no precondition 157 | return Action(name, args, None, parse_effects(body)) 158 | else: 159 | assert False, f"invalid body in {ast.unparse(action)}: should start from If or Assign AST" 160 | 161 | def parse_precondition(stmt): 162 | assert isinstance(stmt,ast.If) 163 | return parse_condition(stmt.test), parse_effects(stmt.body) 164 | 165 | def parse_conditional_effect(stmt): 166 | assert isinstance(stmt,ast.If) 167 | return When( 168 | parse_condition(stmt.test), 169 | parse_effects(stmt.body)) 170 | 171 | def parse_condition(stmt): 172 | if isinstance(stmt,ast.BoolOp): 173 | results = [ parse_condition(elem) for elem in stmt.values] 174 | if isinstance(stmt.op,ast.And): 175 | return And(results) 176 | elif isinstance(stmt.op,ast.Or): 177 | return Or(results) 178 | else: 179 | assert False, f"unsupported op: {ast.unparse(stmt)}" 180 | elif isinstance(stmt,ast.UnaryOp): 181 | assert isinstance(stmt.op, ast.Not) 182 | return ~ parse_predicate(stmt.operand) 183 | elif isinstance(stmt,ast.Subscript): 184 | return parse_predicate(stmt) 185 | else: 186 | assert False, f"unsupported op: {ast.unparse(stmt)}" 187 | 188 | def parse_effects(body): 189 | if len(body) == 1: 190 | return parse_effect(body[0]) 191 | else: 192 | return And([ parse_effect(stmt) for stmt in body ]) 193 | 194 | def parse_effect(stmt): 195 | if isinstance(stmt,ast.If): 196 | return parse_conditional_effect(stmt) 197 | elif isinstance(stmt,ast.For): 198 | assert isinstance(stmt.iter, ast.Call) 199 | assert stmt.iter.func.id in ["all", "any"] 200 | targets = [ arg.id for arg in maybe_iter_tuple(stmt.target)] 201 | types = [ None for _ in targets ] 202 | for i, type in enumerate(stmt.iter.args): 203 | assert isinstance(type, ast.Name), f"unsupported ast for type: {ast.unparse(arg)}" 204 | types[i] = type.id 205 | add_type(type.id) 206 | if stmt.iter.func.id == "all": 207 | quantifier = "forall" 208 | if stmt.iter.func.id == "any": 209 | quantifier = "exists" 210 | return Quantifier( 211 | variables = [Variable(arg, type) for arg, type in zip(targets, types)], 212 | body = parse_effects(stmt.body), 213 | quantifier = quantifier, 214 | ) 215 | elif isinstance(stmt,ast.Assign): 216 | # allows tuple assignments too 217 | assert len(stmt.targets) == 1 218 | results = [] 219 | targets = list(maybe_iter_tuple(stmt.targets[0])) 220 | values = list(maybe_iter_tuple(stmt.value)) 221 | assert len(targets) == len(values) 222 | for target, value in zip(targets, values): 223 | if value.value: 224 | results.append(parse_predicate(target)) 225 | else: 226 | results.append(~parse_predicate(target)) 227 | if len(results) > 1: 228 | return And(results) 229 | else: 230 | return results[0] 231 | else: 232 | assert False, f"unsupported statement in {ast.unparse(stmt)}" 233 | 234 | def maybe_iter_tuple(node): 235 | if isinstance(node, ast.Tuple): 236 | for elt in node.elts: 237 | yield elt 238 | else: 239 | yield node 240 | 241 | def parse_predicate(predicate): 242 | assert isinstance(predicate,ast.Subscript) 243 | assert isinstance(predicate.value,ast.Name) 244 | name = kebab(predicate.value.id) 245 | args = [ parse_arg(elt) for elt in maybe_iter_tuple(predicate.slice) ] 246 | if name not in self.__predicates__: 247 | self.__predicates__[name] = Predicate(name, [ Variable(f"x{i}") for i, _ in enumerate(args)]) 248 | return Predicate(name, args) 249 | 250 | def parse_arg(arg): 251 | if isinstance(arg,ast.Constant): 252 | return Variable(arg.value) 253 | if isinstance(arg,ast.Name): 254 | return Variable(kebab(arg.id)) 255 | assert False 256 | 257 | def add_type(name,parent="object"): 258 | self.__types__[name] = Object(name,parent) 259 | 260 | try: 261 | return parse_toplevel(action.body) 262 | except: 263 | stacktrace.format() 264 | 265 | def __str__(self): 266 | try: 267 | return self.__str() 268 | except: 269 | stacktrace.format() 270 | 271 | def __str(self): 272 | s = f"(domain {self.__class__.__name__.lower()}" 273 | s += textwrap.indent(f"\n(:requirement :strips)"," ") 274 | s += textwrap.indent(f"\n(:types"," ") 275 | for type in self.__types__.values(): 276 | s += textwrap.indent(f"\n{type}"," ") 277 | s += ")" 278 | s += textwrap.indent(f"\n(:predicates"," ") 279 | for predicate in self.__predicates__.values(): 280 | s += textwrap.indent(f"\n{predicate}"," ") 281 | s += ")" 282 | for action in self.__actions__.values(): 283 | s += textwrap.indent(f"\n{action}"," ") 284 | s += ")" 285 | return s 286 | 287 | -------------------------------------------------------------------------------- /pddl_in_python/stacktrace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os.path 4 | import sys, traceback, types, linecache 5 | import numpy as np 6 | 7 | def print_object(o,include_private=False,threshold=3): 8 | maxlinelen=1000 9 | maxlen=20 10 | def get(key): 11 | if isinstance(o, dict): 12 | return o[key] 13 | else: 14 | return getattr(o, key) 15 | def include(key): 16 | return (include_private or ("__" not in key)) \ 17 | and not isinstance(get(key),types.FunctionType) \ 18 | and not isinstance(get(key),types.ModuleType) \ 19 | and not isinstance(get(key),type) 20 | def remove_array(thing): 21 | if isinstance(thing,np.ndarray): 22 | return "".format(str(thing.dtype),thing.shape) 23 | else: 24 | return thing 25 | 26 | def printer(thing): 27 | if isinstance(thing,list): 28 | if len(thing) > threshold: 29 | return [printer(remove_array(o)) for o, _ in [*list(zip(thing, range(threshold))),(f"...<{len(thing)-threshold} more>",None)]] 30 | else: 31 | return [printer(remove_array(o)) for o in thing] 32 | elif isinstance(thing,tuple): 33 | if len(thing) > threshold: 34 | return tuple([printer(remove_array(o)) for o in [*list(zip(thing, range(threshold))),(f"...<{len(thing)-threshold} more>",None)]]) 35 | else: 36 | return tuple([printer(remove_array(o)) for o in thing]) 37 | elif isinstance(thing,dict): 38 | return {k:printer(remove_array(v)) for k,v in thing.items()} 39 | elif isinstance(thing,str): 40 | return thing[:500] 41 | elif isinstance(thing,bytes): 42 | return thing[:500] 43 | else: 44 | return remove_array(thing) 45 | 46 | try: 47 | zip(o) 48 | except TypeError: 49 | print(o) 50 | return 51 | for key in o: 52 | try: 53 | if include(key): 54 | maxlen = max(maxlen,len(key)) 55 | except: 56 | pass 57 | for key in o: 58 | try: 59 | if include(key): 60 | print("{} = {}".format(key.rjust(maxlen+4),repr(printer(get(key))))[:maxlinelen],file=sys.stderr) 61 | except Exception as e: 62 | print("{} = Error printing object : {}".format(str(key).rjust(maxlen),e),file=sys.stderr) 63 | 64 | def format(exit=True,threshold=3): 65 | np.set_printoptions(threshold=25,formatter=None) 66 | print("Fancy Traceback (most recent call last):",file=sys.stderr) 67 | type, value, tb = sys.exc_info() 68 | 69 | for f, f_lineno in traceback.walk_tb(tb): 70 | co = f.f_code 71 | f_filename = co.co_filename 72 | f_name = co.co_name 73 | linecache.lazycache(f_filename, f.f_globals) 74 | f_locals = f.f_locals 75 | f_line = linecache.getline(f_filename, f_lineno).strip() 76 | 77 | print(" ","File",os.path.relpath(f_filename),"line",f_lineno,"function",f_name,":",f_line,file=sys.stderr) 78 | print_object(f_locals,threshold=threshold) 79 | print(file=sys.stderr) 80 | 81 | 82 | print(file=sys.stderr) 83 | print(*(traceback.format_exception_only(type,value)),file=sys.stderr) 84 | if exit: 85 | sys.exit(1) 86 | 87 | def fn1(): 88 | a = 1 89 | b = 0 90 | fn2(a,b) 91 | 92 | def fn2(a,b): 93 | return a/b 94 | 95 | if __name__ == '__main__': 96 | try: 97 | fn1() 98 | except Exception: 99 | format() 100 | print("## standard stack trace ########################################",file=sys.stderr) 101 | traceback.print_exc() 102 | --------------------------------------------------------------------------------