diff --git a/nisaba/scripts/natural_translit/utils/type_op.py b/nisaba/scripts/natural_translit/utils/type_op.py index bd27e183..66872c9b 100644 --- a/nisaba/scripts/natural_translit/utils/type_op.py +++ b/nisaba/scripts/natural_translit/utils/type_op.py @@ -92,14 +92,53 @@ class Thing: values point to the same phoneme in the inventory. """ - def __init__( - self, - alias: str = '', text: str = '', - value: ... = UNSPECIFIED, allow_none: bool = False - ): - self.alias = alias - self.text = text - self.value = value_of(value) if exists(value, allow_none) else self + def __init__(self): + c = class_of(self) + self.alias = '%s_%d' % (c, hash(self)) + self.text = 'Undefined %s' % c + self.value = self + + @classmethod + def named(cls, alias: str) -> 'Thing': + new = cls() + new.set_alias(alias) + new.text = class_and_alias(new) + return new + + @classmethod + def from_value(cls, value: ...) -> 'Thing': + """Makes a Thing from a value. + + Args: + value: If the value is an object with 'value' attribute, the value is + set to value.value in order to avoid nesting values in dynamically + created things and making their equivalence invisible to is_equal(). + If this is undesirable, use store_as(). + + Returns: + Thing + """ + new = cls() + value = value_of(value) + new.text = 'from:%s' % class_and_text(value) + new.value = value_of(value) + return new + + @classmethod + def store_as(cls, alias: str, value: ...) -> 'Thing': + """Makes a Thing with a custom alias and value.""" + new = cls() + new.set_alias(alias) + new.text = 'store_%s:%s' % (new.alias, class_and_text(value)) + new.value = value + return new + + def set_alias(self, alias: str) -> None: + # TODO: ensure alias conforms to inventory field name restrictions. + if alias: + self.alias = alias + else: + debug_message('set_alias', 'empty alias is not allowed') # Union types @@ -141,8 +180,11 @@ def class_of(a: ...) -> str: def text_of(a: ...) -> str: """Returns str() for objects with no text attribute.""" if hasattr(a, 'text'): - return ('Textless %s' % class_of(a)) if is_empty(a.text) else a.text - return a.string() if isinstance(a, pyn.Fst) else str(a) + text = a.text + elif isinstance(a, pyn.Fst): + text = a.string() + else: text = str(a) + return text if text else '' def texts_of(*args) -> str: @@ -154,6 +196,14 @@ def alias_of(a: ...) -> str: return a.alias if hasattr(a, 'alias') and not_empty(a.alias) else text_of(a) +def class_and_alias(a: ...) -> str: + return '%s_%s' % (class_of(a), alias_of(a)) + + +def class_and_text(a: ...) -> str: + return '%s_%s' % (class_of(a), text_of(a)) + + def value_of(a: ...) -> ...: """If a has no value attribute, returns a.""" return a.value if isinstance(a, Thing) else a @@ -270,7 +320,7 @@ def enforce_thing(t: ...) -> Thing: debug_message( 'enforce_thing', 'Thing from %s: %s' % (class_of(t), text_of(t)) ) - return Thing(text=text_of(t), value=t) + return Thing.from_value(t) # Attribute functions with type check. @@ -586,12 +636,12 @@ def enforce_set( try: result.add(element) except TypeError: - result.add(Thing(value=element)) + result.add(Thing.from_value(element)) else: try: result.add(s) except TypeError: - result.add(Thing(value=s)) + result.add(Thing.from_value(s)) return result diff --git a/nisaba/scripts/natural_translit/utils/type_op_test.py b/nisaba/scripts/natural_translit/utils/type_op_test.py index 65028332..a288fca8 100644 --- a/nisaba/scripts/natural_translit/utils/type_op_test.py +++ b/nisaba/scripts/natural_translit/utils/type_op_test.py @@ -23,11 +23,12 @@ D = collections.namedtuple('D', ['k']) _D1 = D('v') -_T0 = t.Thing(alias='T0', text='t0', value=0) -_T1 = t.Thing(alias='T1', text='t1', value=t.UNSPECIFIED) -_T2 = t.Thing(alias='T2', text='', value=_T0) -_T3 = t.Thing(alias='', text='t3', value=_T1) -_T4 = t.Thing(alias='T4', text='t4', value=_T1) +_T0 = t.Thing.store_as(alias='T0', value=0) +_T1 = t.Thing.named(alias='T1') +_T2 = t.Thing.from_value(value=_T0) +_T3 = t.Thing.store_as(alias='', value=_T1) +_T4 = t.Thing.store_as(alias='T4', value=_T1) +_T5 = t.Thing.store_as(alias='T5', value=t.pyn.accep('')) class TypeOpTest(absltest.TestCase): @@ -140,10 +141,10 @@ def test_get_attribute_type(self): ) def test_text_of_thing(self): - self.assertEqual(t.text_of(_T1), 't1') + self.assertEqual(t.text_of(_T1), 'Thing_T1') def test_text_of_thing_empty(self): - self.assertEqual(t.text_of(_T2), 'Textless Thing') + self.assertEqual(t.text_of(_T5), 'store_T5:Fst_') def test_text_of_str(self): self.assertEqual(t.text_of('abc'), 'abc') @@ -167,7 +168,7 @@ def test_alias_of_thing(self): self.assertEqual(t.alias_of(_T1), 'T1') def test_alias_of_thing_empty(self): - self.assertEqual(t.alias_of(_T3), 't3') + self.assertNotEmpty(t.alias_of(_T3)) def test_alias_of_list(self): self.assertEqual(t.alias_of([0, 1]), '[0, 1]') @@ -387,7 +388,7 @@ def test_enforce_dict_namedtuple(self): def test_enforce_dict_thing(self): self.assertEqual( - t.enforce_dict(_T1), {'alias': 'T1', 'text': 't1', 'value': _T1} + t.enforce_dict(_T1), {'alias': 'T1', 'text': 'Thing_T1', 'value': _T1} ) def test_enforce_dict_no_key(self):