#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This module offers functions for serializing a Sentence to a list of strings and
deserializing a list of strings to a Sentence.
"""
from ..language import components as lc
from ..environment import entities as emm
[docs]
def serialize(elem, world):
"""
Serializes an object into a list of tokens.
The serialization is done in such a way that only the meaning of the sentence is represented and the
syntactic structure is ignored.
For each object type, the serialization is done as follows:
- Sentence: serializes the describers that represent the semantics.
- Describer: serializes the arguments in the following manner:
..
b<ArgKey> <serialized argument value> e<ArgKey>
- set or list: serializes each of the elements recursively and uses bset/eset or blist/elist to indicate
the beginning or end of the serialization.
- Entity: serializes the Entity's description.
- all other: adds the element in the list of serialized objects.
For the following sentence: "Hans has the red apple and the chicken."
the sentence object can be created using sentences.have(world.player, "has", None, [world.small_apple, world.chicken])
The sentence object will be serialized as a list of tokens:
.. code-block:: python
['<sent>',
'<desc>',
'<Arg-PAG>', '<entity>', 'small', 'person', '</entity>', '</Arg-PAG>',
'<Rel>', 'has', '</Rel>',
'<Arg-PPT>', '<set>', '<entity>', 'red', 'apple', '</entity>', '<entity>', 'chicken', '</entity>', '</set>',
'</Arg-PPT>',
'</desc>',
'</sent>']
The indents are there just to better visualize the serialization.
Parameters
----------
elem : any
The object to be serialized.
world : World
The simulated world.
Returns
-------
cmds : list of strings
The serialized object.
"""
cmds = []
if isinstance(elem, lc.Sentence):
cmds.append('<sent>')
for desc in elem.describers:
cmds += serialize(desc, world)
cmds.append('</sent>')
elif isinstance(elem, lc.Describer):
cmds.append('<desc>')
for argk, argv in elem.args.items():
cmds.append('<'+argk+'>')
val = argv.value
cmds += serialize(val, world)
cmds.append('</'+argk+'>')
cmds.append('</desc>')
elif isinstance(elem, (tuple, list)):
cmds.append('<list>')
for lelem in elem:
cmds += serialize(lelem, world)
cmds.append('</list>')
elif isinstance(elem, set):
cmds.append('<set>')
for lelem in elem:
cmds += serialize(lelem, world)
cmds.append('</set>')
elif isinstance(elem, emm.BaseEntity):
cmds.append('<entity>')
elem_vals = []
if elem.description is not None:
description = elem.description
else:
description = elem.generate_description()
for delem in description.elements:
if delem in elem.properties:
if not isinstance(elem.properties[delem], (tuple, list)):
elem_vals.append(elem.properties[delem])
else:
elem_vals.extend(serialize(elem.properties[delem], world))
elif delem in elem.attributes:
if isinstance(delem, tuple):
elem_vals.extend(serialize(delem, world))
else:
elem_vals.append(delem)
if 'abstract' in elem.attributes and 'abstract' not in description.elements:
elem_vals.append('abstract')
cmds += elem_vals
cmds.append('</entity>')
else:
cmds.append(elem)
return cmds
[docs]
def deserialize(cmds, world, stack=None):
"""
Parses a list of tokens (strings) and converts them into an instance of type Sentence.
The parsing is done in the following manner:
Each time a beginning token is encountered, the corresponding object instance is created and added to a stack.
The beginning keyword indicates that the next few tokens belong to the object instance, and those tokens
are attached to the object instance using the attach_to_element function.
When the end keyword is encountered, the object instance is attached to a parent object
if it exists.
Please check the serialize function for an example of serializing a Sentence object. The deserialization
just reverses this process; it converts the list into a Sentence object.
Parameters
----------
cmds : list
A list of tokens/strings to be deserialized into an object.
world : World
The simulated world.
stack : list, optional
The stack is used for parsing the tokens.
Raises
------
Exception
An exception is raised if there is an error during the parsing.
Returns
-------
last_sent : Sentence
The parsed sentence.
"""
if stack is None:
stack = []
for cmd in cmds:
if cmd is None:
attach_to_element(stack[-1], cmd, world)
elif cmd == '<sent>':
stack.append(lc.Sentence())
elif cmd == '</sent>':
if isinstance(stack[-1], lc.Sentence):
if len(stack) > 1:
attach_to_element(stack[-2], stack[-1], world)
last_sent = stack[-1]
del stack[-1]
if len(stack) == 0:
return last_sent
else:
raise Exception('The last element in the stack should be a lc.Sentence.\nThe stack is:'.format(stack))
elif cmd == '<desc>':
new_desc = lc.Describer()
stack.append(new_desc)
elif cmd == '</desc>':
if isinstance(stack[-1], lc.Describer):
attach_to_element(stack[-2], stack[-1], world)
del stack[-1]
else:
raise Exception('The last element in the stack should be a lc.Describer\nThe stack is:'.format(stack))
elif cmd.startswith('<Arg-') or cmd.startswith('<AM-') or cmd == '<Rel>':
if isinstance(stack[-1], lc.Describer):
if cmd == '<Rel>':
new_arg = lc.RelArg()
else:
new_arg = lc.Arg()
stack[-1].args[cmd[1:-1]] = new_arg
stack.append(new_arg)
else:
raise Exception('The last element in the stack should be a lc.Sentence.\nThe stack is:'.format(stack))
elif cmd.startswith('</Arg-') or cmd.startswith('</AM-') or cmd == '</Rel>':
if isinstance(stack[-1], lc.Arg):
del stack[-1]
else:
raise Exception('The last element in the stack should be a lc.Arg.\nThe stack is:'.format(stack))
elif cmd.startswith('<list>'):
stack.append(list())
elif cmd.startswith('</list>'):
if isinstance(stack[-1], list):
attach_to_element(stack[-2], stack[-1], world)
del stack[-1]
else:
raise Exception('The last element in the stack should be type list.\nThe stack is:'.format(stack))
elif cmd.startswith('<set>'):
stack.append(set())
elif cmd.startswith('</set>'):
if isinstance(stack[-1], set):
attach_to_element(stack[-2], stack[-1], world)
del stack[-1]
else:
raise Exception('The last element in the stack should be type set.\nThe stack is:'.format(stack))
elif cmd.startswith('<entity>'):
stack.append(emm.BaseEntity())
elif cmd.startswith('</entity>'):
if isinstance(stack[-1], emm.BaseEntity):
if 'abstract' not in stack[-1].attributes:
entities_found = world.query_entity_from_db(stack[-1])
else:
entities_found = [emm.BaseEntity(properties=stack[-1].properties,
attributes=stack[-1].attributes)]
if len(entities_found) == 1:
del stack[-1]
attach_to_element(stack[-1], entities_found[0], world)
else:
raise Exception("There is no unique entity with the following properties: {0} and the following "
"attributes: {1} found in the world.".format(stack[-1].properties,
stack[-1].attributes))
else:
raise Exception(
"The last element in the stack should be an instance of BaseEntity.\nThe stack is:".format(stack))
else:
attach_to_element(stack[-1], cmd, world)
[docs]
def attach_to_element(elem, elem_to_attach, world):
""" Attaches a child element (elem_to_attach) to a parent element (elem).
"""
if isinstance(elem, list):
elem.append(elem_to_attach)
elif isinstance(elem, lc.Arg):
elem.value = elem_to_attach
if isinstance(elem, lc.RelArg):
elem.infinitive = elem.compute_infinitive()
elif isinstance(elem, emm.BaseEntity):
elem_attached = False
for desc in world.all_description_objects:
for celem in desc.cand_elements:
if elem_to_attach in world.all_attributes or elem_to_attach == 'abstract':
elem.attributes[elem_to_attach] = None
elem_attached = True
elif celem in world.all_properties \
and ((isinstance(celem, str) and elem_to_attach in getattr(world, celem+"s"))
or (isinstance(celem, tuple) and elem_to_attach in getattr(world, "_".join(celem)+"s"))):
elem.properties[celem] = elem_to_attach
elem_attached = True
elif isinstance(celem, tuple) and isinstance(elem_to_attach, list) and tuple(elem_to_attach) in world.all_attributes:
elem.attributes[tuple(elem_to_attach)] = None
elem_attached = True
elif elem_to_attach in ['entity']: #'the'
elem_attached = True
if elem_attached is False:
raise Exception("{0} is not recognized as a property value nor as an attribute.".format(elem_to_attach))
elif isinstance(elem, lc.Sentence):
elem.describers.append(elem_to_attach)
elif isinstance(elem, set):
elem.add(elem_to_attach)