diff --git a/pytato/array.py b/pytato/array.py index 904e2575b07fc7100ab8de783bbb96cd2f635dbc..c0379d73c2ebd57129ffce77590e8394e67b9a87 100644 --- a/pytato/array.py +++ b/pytato/array.py @@ -33,13 +33,13 @@ is referenced from :class:`DataArray`. import collections.abc +import pytato.typing as ptype from pytools import single_valued, is_single_valued -from contracts import contract -from pymbolic.primitives import Expression # noqa -from pytato.contract import c_identifier, ArrayInterface # noqa +from typing import Optional, Union, List, Dict +from pymbolic.primitives import Expression -class DottedName: +class DottedName(ptype.TagInterface): """ .. attribute:: name_parts @@ -52,12 +52,13 @@ class DottedName: object. """ - @contract(name_parts='list[>0](str,c_identifier)') - def __init__(self, name_parts): + def __init__(self, name_parts: List[str]): + assert len(name_parts) > 0 + assert all(check_c_identifier(p) for p in name_parts) self.name_parts = name_parts -class Namespace: +class Namespace(ptype.NamespaceInterface): # Possible future extension: .parent attribute """ .. attribute:: symbol_table @@ -70,14 +71,14 @@ class Namespace: def __init__(self): self.symbol_table = {} - @contract(name='str,c_identifier', value=ArrayInterface) - def assign(self, name, value): + def assign(self, name: ptype.NameType, value: ptype.ArrayInterface): + assert ptype.check_name(name) if name in self.symbol_table: raise ValueError(f"'{name}' is already assigned") self.symbol_table[name] = value -class Array(ArrayInterface): +class Array(ptype.ArrayInterface): """ A base class (abstract interface + supplemental functionality) for lazily @@ -156,10 +157,12 @@ class Array(ArrayInterface): purposefully so. """ - @contract(namespace=Namespace, - name='None | (str,c_identifier)', - tags='None | dict($DottedName:$DottedName)') - def __init__(self, namespace, name, tags=None): + def __init__(self, namespace: Namespace, + name: Optional[ptype.NameType], + tags: Optional[ptype.TagsType] = None): + assert ptype.check_name(name) + assert ptype.check_tags(tags) + if tags is None: tags = {} @@ -181,9 +184,8 @@ class Array(ArrayInterface): def ndim(self): return len(self.shape) - @contract(dotted_name=DottedName, - args='(None | $DottedName)') - def with_tag(self, dotted_name, args=None): + def with_tag(self, dotted_name: DottedName, + args: Optional[DottedName] = None): """ Returns a copy of *self* tagged with *dotted_name* and arguments *args* @@ -193,12 +195,11 @@ class Array(ArrayInterface): if args is None: pass - @contract(dotted_name=DottedName) - def without_tag(self, dotted_name): + def without_tag(self, dotted_name: DottedName): pass - @contract(name='str,c_identifier') - def with_name(self, name): + def with_name(self, name: ptype.NameType): + assert ptype.check_name(name) self.namespace.assign_name(name, self) return self.copy(name=name) @@ -224,8 +225,8 @@ class DictOfNamedArrays(collections.abc.Mapping): arithmetic. """ - @contract(data='dict((str,c_identifier):$ArrayInterface)') - def __init__(self, data): + def __init__(self, data: Dict[ptype.NameType, ptype.ArrayInterface]): + assert all(ptype.check_name(key) for key in data.keys()) self._data = data if not is_single_valued(ary.namespace for ary in data.values()): @@ -235,12 +236,12 @@ class DictOfNamedArrays(collections.abc.Mapping): def namespace(self): return single_valued(ary.namespace for ary in self._data.values()) - @contract(name='str,c_identifier') - def __contains__(self, name): + def __contains__(self, name: ptype.NameType): + assert ptype.check_name(name) return name in self._data - @contract(name='str,c_identifier') - def __getitem__(self, name): + def __getitem__(self, name: ptype.NameType): + assert ptype.check_name(name) return self._data[name] def __iter__(self): @@ -314,12 +315,11 @@ class Placeholder(Array): # Not tied to this, open for discussion about how to implement this. return self._shape - # TODO: check for meaningfulness of the shape expressions - @contract(namespace=Namespace, - name='str,c_identifier', - shape='seq((int,>0) | (str,c_identifier) | $Expression)', - tags='None | dict($DottedName:$DottedName)') - def __init__(self, namespace, name, shape, tags=None): + def __init__(self, namespace: ptype.NamespaceInterface, + name: ptype.NameType, shape: ptype.ShapeType, + tags: Optional[ptype.TagsType] = None): + assert ptype.check_shape(shape) + # namespace, name and tags will be checked in super().__init__() super().__init__( namespace=namespace, name=name, diff --git a/pytato/contract.py b/pytato/contract.py deleted file mode 100644 index 941bad947e705c527b151dcde8a3307a7a2878af..0000000000000000000000000000000000000000 --- a/pytato/contract.py +++ /dev/null @@ -1,44 +0,0 @@ -__copyright__ = """ -Copyright (C) 2020 Andreas Kloeckner -Copyright (C) 2020 Matt Wala -Copyright (C) 2020 Xiaoyu Wei -""" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -import re -from contracts import new_contract, ContractsMeta - - -C_IDENTIFIER = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$") - - -@new_contract -def c_identifier(x): - """Tests whether a string is a valid variable name in C. - """ - return re.match(C_IDENTIFIER, x) is not None - - -class ArrayInterface(): - """Abstract class for types implementing the Array interface. - """ - __metaclass__ = ContractsMeta diff --git a/pytato/typing.py b/pytato/typing.py new file mode 100644 index 0000000000000000000000000000000000000000..27f07f5b712e9edd19ec8b8ee5737d55520a8b37 --- /dev/null +++ b/pytato/typing.py @@ -0,0 +1,99 @@ +__copyright__ = """ +Copyright (C) 2020 Andreas Kloeckner +Copyright (C) 2020 Matt Wala +Copyright (C) 2020 Xiaoyu Wei +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +__doc__ = """Interface classes and type specifications. +Each type is paired with a check_* function that, when used together, achieves +contracts-like functionality.""" + +import re +from pymbolic.primitives import Expression +from abc import ABC +from typing import Optional, Union, Dict, Tuple + +# {{{ abstract classes + + +class NamespaceInterface(): + __metaclass__ = ABC + + +class TagInterface(): + __metaclass__ = ABC + + +class ArrayInterface(): + """Abstract class for types implementing the Array interface. + """ + __metaclass__ = ABC + +# }}} End abstract classes + +# {{{ name type + + +NameType = str +C_IDENTIFIER = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$") + + +def check_name(name: NameType) -> bool: + assert re.match(C_IDENTIFIER, name) is not None, \ + f"{name} is not a C identifier" + return True + +# }}} End name type + +# {{{ tags type + + +TagsType = Dict[TagInterface, TagInterface] + + +def check_tags(tags: TagsType) -> bool: + # assuming TagInterface implementation gurantees correctness + return True + +# }}} End tags type + +# {{{ shape type + + +ShapeComponentType = Union[int, Expression, str] +ShapeType = Tuple[ShapeComponentType, ...] + + +def check_shape(shape: ShapeType, + ns: Optional[NamespaceInterface] = None) -> bool: + for s in shape: + if isinstance(s, int): + assert s > 0, f"size parameter must be positive (got {s})" + elif isinstance(s, str): + assert check_name(s) + elif isinstance(Expression) and ns is not None: + # TODO: check expression in namespace + pass + return True + +# }}} End shape type diff --git a/setup.py b/setup.py index 153fe38a2b8dcfa7f73698615272e552c2910c7f..c56a780a28b3f0c16e322287be9a15400c11ae92 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ setup(name="pytato", install_requires=[ "loo.py", - "pycontracts", ], author="Andreas Kloeckner, Matt Wala, Xiaoyu Wei",