"""
Serialization for the SimpleDMRS format.
"""
from pathlib import Path
from delphin.dmrs import (
DMRS,
EQ_POST,
DMRSSyntaxError,
Link,
Node,
)
from delphin.lnk import Lnk
from delphin.util import Lexer
CODEC_INFO = {
"representation": "dmrs",
}
##############################################################################
##############################################################################
# Pickle-API methods
[docs]
def load(source):
"""
Deserialize SimpleDMRS from a file (handle or filename)
Args:
source (str, file): input filename or file object
Returns:
a list of DMRS objects
"""
if hasattr(source, "read"):
ds = list(_decode(source))
else:
source = Path(source).expanduser()
with source.open() as fh:
ds = list(_decode(fh))
return ds
[docs]
def loads(s, encoding="utf-8"):
"""
Deserialize SimpleDMRS string representations
Args:
s (str): a SimpleDMRS string
Returns:
a list of DMRS objects
"""
ds = list(_decode(s.splitlines()))
return ds
[docs]
def dump(ds, destination, properties=True, lnk=True, indent=False, encoding="utf-8"):
"""
Serialize DMRS objects to SimpleDMRS and write to a file
Args:
ds: an iterator of DMRS objects to serialize
destination: filename or file object where data will be written
properties: if `False`, suppress morphosemantic properties
lnk: if `False`, suppress surface alignments and strings
indent (bool, int): if `True` or an integer value, add
newlines and indentation
encoding (str): if *destination* is a filename, write to the
file with the given encoding; otherwise it is ignored
"""
text = dumps(ds, properties=properties, lnk=lnk, indent=indent)
if hasattr(destination, "write"):
print(text, file=destination)
else:
destination = Path(destination).expanduser()
with destination.open("w", encoding=encoding) as fh:
print(text, file=fh)
[docs]
def dumps(ds, properties=True, lnk=True, indent=False):
"""
Serialize DMRS objects to a SimpleDMRS representation
Args:
ds: an iterator of DMRS objects to serialize
properties: if `False`, suppress variable properties
lnk: if `False`, suppress surface alignments and strings
indent (bool, int): if `True` or an integer value, add
newlines and indentation
Returns:
a SimpleDMRS string representation of a corpus of DMRS objects
"""
return _encode(ds, properties, lnk, indent)
[docs]
def decode(s):
"""
Deserialize a DMRS object from a SimpleDMRS string.
"""
lexer = _SimpleDMRSLexer.lex(s.splitlines())
return _decode_dmrs(lexer)
[docs]
def encode(d, properties=True, lnk=True, indent=False):
"""
Serialize a DMRS object to a SimpleDMRS string.
Args:
d: a DMRS object
properties (bool): if `False`, suppress variable properties
lnk: if `False`, suppress surface alignments and strings
indent (bool, int): if `True` or an integer value, add
newlines and indentation
Returns:
a SimpleDMRS-serialization of the DMRS object
"""
return _encode([d], properties, lnk, indent)
##############################################################################
##############################################################################
# Decoding
_SimpleDMRSLexer = Lexer(
tokens=[
(r"\{", "LBRACE:{"),
(r"\}", "RBRACE:}"),
(r"\[", "LBRACKET:["),
(r"\]", "RBRACKET:]"),
(r"\(", "LPAREN:("),
(r"\)", "RPAREN:)"),
(r"<(?:-?\d+[:#]-?\d+|@\d+|\d+(?: +\d+)*)>", "LNK:a lnk value"),
(r'"([^"\\]*(?:\\.[^"\\]*)*)"', "DQSTRING:a string"),
(r":", "COLON::"),
(r"\/", "SLASH:/"),
(r"=", "EQUALS:="),
(r";", "SEMICOLON:;"),
(r"(--|->)", "ARROW:a link arrow"),
(r'[^\s"\'()\/:;<=>[\]{}]+', "SYMBOL:a symbol"),
(r"[^\s]", "UNEXPECTED"),
],
error_class=DMRSSyntaxError,
)
LBRACE = _SimpleDMRSLexer.tokentypes.LBRACE
RBRACE = _SimpleDMRSLexer.tokentypes.RBRACE
LBRACKET = _SimpleDMRSLexer.tokentypes.LBRACKET
RBRACKET = _SimpleDMRSLexer.tokentypes.RBRACKET
LPAREN = _SimpleDMRSLexer.tokentypes.LPAREN
RPAREN = _SimpleDMRSLexer.tokentypes.RPAREN
LNK = _SimpleDMRSLexer.tokentypes.LNK
DQSTRING = _SimpleDMRSLexer.tokentypes.DQSTRING
COLON = _SimpleDMRSLexer.tokentypes.COLON
SLASH = _SimpleDMRSLexer.tokentypes.SLASH
EQUALS = _SimpleDMRSLexer.tokentypes.EQUALS
SEMICOLON = _SimpleDMRSLexer.tokentypes.SEMICOLON
ARROW = _SimpleDMRSLexer.tokentypes.ARROW
SYMBOL = _SimpleDMRSLexer.tokentypes.SYMBOL
def _decode(lineiter):
lexer = _SimpleDMRSLexer.lex(lineiter)
try:
while lexer.peek():
yield _decode_dmrs(lexer)
except StopIteration:
pass
def _decode_dmrs(lexer):
top = index = lnk = surface = identifier = None
lexer.expect_form("dmrs")
identifier = lexer.accept_type(SYMBOL)
lexer.expect_type(LBRACE)
if lexer.accept_type(LBRACKET):
lnk = _decode_lnk(lexer)
surface = lexer.accept_type(DQSTRING)
graphprops = dict(_decode_properties(lexer))
top = graphprops.get("TOP")
index = graphprops.get("INDEX")
nodes = []
links = []
while lexer.peek()[0] != RBRACE:
nodeid = lexer.expect_type(SYMBOL)
gid, _ = lexer.choice_type(LBRACKET, COLON)
if gid == LBRACKET:
nodes.append(_decode_node(nodeid, lexer))
else:
links.append(_decode_link(nodeid, lexer))
lexer.expect_type(RBRACE)
return DMRS(
top=int(top) if top is not None else None,
index=int(index) if index is not None else None,
nodes=nodes,
links=links,
lnk=lnk,
surface=surface,
identifier=identifier,
)
def _decode_lnk(lexer):
lnk = lexer.accept_type(LNK)
if lnk is not None:
lnk = Lnk(lnk)
return lnk
def _decode_properties(lexer):
props = []
while lexer.peek()[0] != RBRACKET:
prop, _, val = lexer.expect_type(SYMBOL, EQUALS, SYMBOL)
props.append((prop.upper(), val.lower()))
lexer.expect_type(RBRACKET)
return props
def _decode_node(nodeid, lexer):
predicate = lexer.expect_type(SYMBOL)
lnk = _decode_lnk(lexer)
carg = None
if lexer.accept_type(LPAREN):
carg, _ = lexer.expect_type(DQSTRING, RPAREN)
nodetype = lexer.accept_type(SYMBOL)
properties = dict(_decode_properties(lexer))
lexer.expect_type(SEMICOLON)
return Node(
int(nodeid), predicate, type=nodetype, properties=properties, carg=carg, lnk=lnk
)
def _decode_link(start, lexer):
role = lexer.accept_type(SYMBOL)
_, post, _, end, _ = lexer.expect_type(SLASH, SYMBOL, ARROW, SYMBOL, SEMICOLON)
return Link(int(start), int(end), role, post)
##############################################################################
##############################################################################
# Encoding
_node = "{nodeid} [{pred}{lnk}{carg}{sortinfo}];"
_link = "{start}:{pre}/{post} {arrow} {end};"
def _encode(ds, properties, lnk, indent):
if indent is None or indent is False:
indent = None # normalize False to None
delim = " "
else:
if indent is True:
indent = 2
delim = "\n"
return delim.join(_encode_dmrs(d, properties, lnk, indent) for d in ds)
def _encode_dmrs(d, properties, lnk, indent):
if indent is None:
delim = " "
end = " }"
else:
delim = "\n" + " " * indent
end = "\n}"
if d.identifier is None:
start = "dmrs {"
else:
start = f"dmrs {d.identifier} {{"
attrs = _encode_attrs(d, lnk)
nodes = [_encode_node(node, properties, lnk) for node in d.nodes]
links = [_encode_link(link) for link in d.links]
return delim.join([start, *attrs, *nodes, *links]) + end
def _encode_attrs(d, lnk):
attrs = []
if lnk:
if d.lnk:
attrs.append(str(d.lnk))
if d.surface is not None:
attrs.append(f'"{d.surface}"')
if d.top is not None:
attrs.append(f"top={d.top}")
if d.index is not None:
attrs.append(f"index={d.index}")
if attrs:
attrs = ["[{}]".format(" ".join(attrs))]
return attrs
def _encode_node(node, properties, lnk):
return _node.format(
nodeid=node.id,
pred=node.predicate,
lnk=str(node.lnk) if lnk else "",
carg="" if node.carg is None else f'("{node.carg}")',
sortinfo=_encode_sortinfo(node, properties),
)
def _encode_sortinfo(node, properties):
sortinfo = []
# rather than node.sortinfo, construct manually so cvarsort appears first
if node.type is not None and node.type != "u":
sortinfo.append(node.type)
if properties and node.properties:
sortinfo.extend(f"{k}={v}" for k, v in node.properties.items())
if sortinfo:
return " " + " ".join(sortinfo)
return ""
def _encode_link(link):
return _link.format(
start=link.start,
pre=link.role or "",
post=link.post,
arrow="->" if link.role or link.post != EQ_POST else "--",
end=link.end,
)