text: #!/usr/bin/env python2.7
  # coding=utf-8
  '''
  Parser for Abstract Meaning Represention (AMR) annotations in Penman format.
  A *parsing expression grammar* (PEG) for AMRs is specified in amr.peg
  and the AST is built by the Parsimonious library (https://github.com/erikrose/parsimonious).
  The resulting graph is represented with the AMR class.
  When called directly, this script runs some cursory unit tests.
  
  If the AMR has ISI-style inline alignments, those are stored in the AMR object as well.
  
  TODO: Include the smatch evaluation code
  (released at http://amr.isi.edu/evaluation.html under the MIT License).
  
  @author: Nathan Schneider (nschneid@inf.ed.ac.uk)
  @since: 2015-05-05
  '''
  from __future__ import print_function
  
  import os
  import glob
  import re
  from collections import defaultdict, Counter
  
  from nltk.parse import DependencyGraph
  from parsimonious.exceptions import ParseError
  from parsimonious.grammar import Grammar
  
  
  def clean_grammar_file(s):
      return re.sub('\n[ \t]+', ' ', re.sub(r'#.*', '', s.replace('\t', ' ').replace('`', '_backtick')))
  
  
  def path_to_amr_peg():
      files = glob.iglob('../../**', recursive=True)
      for file in files:
          file_path, filename = os.path.split(file)
          if filename == 'amr.peg':
              return file
  
  
  with open(path_to_amr_peg()) as inF:
      grammar = Grammar(clean_grammar_file(inF.read()))
  
  
  class Var(object):
      def __init__(self, name):
          self._name = name
  
      def is_constant(self):
          return False
  
      def __repr__(self):
          return 'Var(' + self._name + ')'
  
      def __str__(self):
          return self._name
  
      def __call__(self, align_key='', append=False):
          return self._name + (align_key if append else '')
  
      def __eq__(self, that):
          return type(that) == type(self) and self._name == that._name
  
      def __hash__(self):
          return hash(repr(self))
  
  
  class Concept(object):
      RE_FRAME_NUM = re.compile(r'-\d\d$')
  
      def __init__(self, name):
          self._name = name
  
      def is_constant(self):
          return False
  
      def is_frame(self):
          return self.RE_FRAME_NUM.search(self._name) is not None
  
      def __repr__(self):
          return 'Concept(' + self._name + ')'
  
      def __str__(self, align_key='', **kwargs):
          return self._name + align_key
  
      def __call__(self, *args, **kwargs):
          return self.__str__(*args, **kwargs)
  
      def __eq__(self, that):
          return type(that) == type(self) and self._name == that._name
  
      def __hash__(self):
          return hash(repr(self))
  
  
  class AMRConstant(object):
      def __init__(self, value):
          self._value = value
  
      def is_constant(self):
          return True
  
      def is_frame(self):
          return False
  
      def __repr__(self):
          return 'Const(' + self._value + ')'
  
      def __str__(self, align_key='', **kwargs):
          return self._value + align_key
  
      def __call__(self, *args, **kwargs):
          return self.__str__(*args, **kwargs)
  
      def __eq__(self, that):
          return type(that) == type(self) and self._value == that._value
  
      def __hash__(self):
          return hash(repr(self))
  
  
  class AMRString(AMRConstant):
      def __str__(self, align_key='', **kwargs):
          return '"' + self._value + '"' + align_key
  
      def __repr__(self):
          return '"' + self._value + '"'
  
  
  class AMRNumber(AMRConstant):
      def __repr__(self):
          return 'Num(' + self._value + ')'
  
  
  class AMRError(Exception):
      pass
  
  
  class AMRSyntaxError(Exception):
      pass
  
  
  class AMR(DependencyGraph):
      '''
      An AMR annotation. Constructor parses the Penman notation.
      Does not currently provide functionality for manipulating the AMR structure,
      but subclassing from DependencyGraph does provide the contains_cycle() method.
  
      >>> s = """                                    \
      (b / business :polarity -                      \
         :ARG1-of (r / resemble-01                   \
                     :ARG2 (b2 / business            \
                               :mod (s / show-04)))) \
      """
      >>> a = AMR(s)
      >>> a
      (b / business :polarity -
          :ARG1-of (r / resemble-01
              :ARG2 (b2 / business
                  :mod (s / show-04))))
      >>> a.reentrancies()
      Counter()
      >>> a.contains_cycle()
      False
  
      >>> a = AMR("(h / hug-01 :ARG0 (y / you) :ARG1 y :mode imperative)")
      >>> a
      (h / hug-01
          :ARG0 (y / you)
          :ARG1 y
          :mode imperative)
      >>> a.reentrancies()
      Counter({Var(y): 1})
      >>> a.contains_cycle()
      False
  
      >>> a = AMR('(h / hug-01 :ARG1 (p / person :ARG0-of h))')
      >>> a
      (h / hug-01
          :ARG1 (p / person
              :ARG0-of h))
      >>> a.reentrancies()
      Counter({Var(h): 1})
      >>> a.triples()     #doctest:+NORMALIZE_WHITESPACE
      [(Var(TOP), ':top', Var(h)), (Var(h), ':instance-of', Concept(hug-01)),
       (Var(h), ':ARG1', Var(p)), (Var(p), ':instance-of', Concept(person)),
       (Var(p), ':ARG0-of', Var(h))]
      >>> sorted(a.contains_cycle(), key=str)
      [Var(h), Var(p)]
  
      >>> a = AMR('(h / hug-01 :ARG0 (y / you) :mode imperative \
      :ARG1 (p / person :ARG0-of (w / want-01 :ARG1 h)))')
      >>> # Hug someone who wants you to!
      >>> sorted(a.contains_cycle(), key=str)
      [Var(h), Var(p), Var(w)]
  
      >>> a = AMR('(w / wizard    \
      :name (n / name :op1 "Albus" :op2 "Percival" :op3 "Wulfric" :op4 "Brian" :op5 "Dumbledore"))')
      >>> a
      (w / wizard
          :name (n / name
              :op1 "Albus"
              :op2 "Percival"
              :op3 "Wulfric"
              :op4 "Brian"
              :op5 "Dumbledore"))
  
      # with automatic alignments
      # at_0 a_1 glance_2 i_3 can_4 distinguish_5 china_6 from_7 arizona_8 ._9
      >>> a = AMR('(p / possible~e.4 :domain~e.1 (d / distinguish-01~e.5 :arg0 (i / i~e.3) \
      :arg1 (c / country :wiki~e.7 "china"~e.6 :name (n / name :op1 "china"~e.6))          \
      :arg2 (s / state :wiki~e.7 "arizona"~e.8 :name (n2 / name :op1 "arizona"~e.8))       \
      :manner~e.0 (g / glance-01~e.2 :arg0 i)))')
      >>> a
      (p / possible~e.4
          :domain~e.1 (d / distinguish-01~e.5
              :arg0 (i / i~e.3)
              :arg1 (c / country
                  :wiki~e.7 "china"~e.6
                  :name (n / name
                      :op1 "china"~e.6))
              :arg2 (s / state
                  :wiki~e.7 "arizona"~e.8
                  :name (n2 / name
                      :op1 "arizona"~e.8))
              :manner~e.0 (g / glance-01~e.2
                  :arg0 i)))
      >>> sorted(a.alignments().items(), key=str)  #doctest:+NORMALIZE_WHITESPACE
      [((Var(c), ':wiki', "china"), 'e.6'),
       ((Var(d), ':instance-of', Concept(distinguish-01)), 'e.5'),
       ((Var(g), ':instance-of', Concept(glance-01)), 'e.2'),
       ((Var(i), ':instance-of', Concept(i)), 'e.3'),
       ((Var(n), ':op1', "china"), 'e.6'),
       ((Var(n2), ':op1', "arizona"), 'e.8'),
       ((Var(p), ':instance-of', Concept(possible)), 'e.4'),
       ((Var(s), ':wiki', "arizona"), 'e.8')]
      >>> sorted(a.role_alignments().items(), key=str)  #doctest:+NORMALIZE_WHITESPACE
      [((Var(c), ':wiki', "china"), 'e.7'),
       ((Var(d), ':manner', Var(g)), 'e.0'),
       ((Var(p), ':domain', Var(d)), 'e.1'),
       ((Var(s), ':wiki', "arizona"), 'e.7')]
      >>> print(a(alignments=False))
      (p / possible
          :domain (d / distinguish-01
              :arg0 (i / i)
              :arg1 (c / country
                  :wiki "china"
                  :name (n / name
                      :op1 "china"))
              :arg2 (s / state
                  :wiki "arizona"
                  :name (n2 / name
                      :op1 "arizona"))
              :manner (g / glance-01
                  :arg0 i)))
  
      >>> a = AMR("(h / hug-01~e.2 :polarity~e.1 -~e.1 :ARG0 (y / you~e.3) :ARG1 y \
                   :mode~e.0 imperative~e.5 :result (s / silly-01~e.4 :ARG1 y))", \
                  "Do n't hug yourself silly !".split())
      >>> a
      (h / hug-01~e.2[hug] :polarity~e.1[n't] -~e.1[n't]
          :ARG0 (y / you~e.3[yourself])
          :ARG1 y
          :mode~e.0[Do] imperative~e.5[!]
          :result (s / silly-01~e.4[silly]
              :ARG1 y))
  
      >>> a = AMR("(h / hug-01~e.3 :ARG0 (a / and~e.1 :op1 (y / you~e.0) :op2 (i / i~e.2)) \
                   :ARG1 (a2 / and~e.5 :op1 (s / she~e.4) :op2 (h2 / he~e.6)))", \
                  "You and I hug her and him".split())
      >>> a
      (h / hug-01~e.3[hug]
          :ARG0 (a / and~e.1[and]
              :op1 (y / you~e.0[You])
              :op2 (i / i~e.2[I]))
          :ARG1 (a2 / and~e.5[and]
              :op1 (s / she~e.4[her])
              :op2 (h2 / he~e.6[him])))
  
      >>> a = AMR("(a / and~e.5 :op1 (e / eat-01~e.2 :ARG0 (c / cat~e.1,8 :poss~e.0,8 (i / i~e.0,8)) \
                   :time~e.3 (d / dawn~e.4)) \
                   :op2 (e2 / eat-01~e.2 :ARG0 (c2 / cat~e.1,6 :poss~e.6 (y / you~e.6)) \
                   :time~e.7 (a2 / after~e.7 :op1 e~e.9)))", \
                  "my cat eats at dawn and yours after mine does".split())
      >>> a
      (a / and~e.5[and]
          :op1 (e / eat-01~e.2[eats]
              :ARG0 (c / cat~e.1,8[cat,mine]
                  :poss~e.0,8[my,mine] (i / i~e.0,8[my,mine]))
              :time~e.3[at] (d / dawn~e.4[dawn]))
          :op2 (e2 / eat-01~e.2[eats]
              :ARG0 (c2 / cat~e.1,6[cat,yours]
                  :poss~e.6[yours] (y / you~e.6[yours]))
              :time~e.7[after] (a2 / after~e.7[after]
                  :op1 e~e.9[does])))
  
      >>> a = AMR('(r / reduce-01~e.8 :ARG0 (t / treat-04~e.0 :ARG1~e.1 (c / cell-line~e.3,4 \
                  :mod (d2 / disease :name (n3 / name :op1 "CRC"~e.2))) :ARG2~e.5 (s / small-molecule \
                  :name (n / name :op1 "U0126"~e.6))) :ARG1 (l / level~e.11 :quant-of (n6 / nucleic-acid \
                  :name (n4 / name :op1 "mRNA"~e.10) :ARG0-of (e2 / encode-01 :ARG1 (p / protein \
                  :name (n5 / name :op1 "serpinE2"~e.9))))) :manner~e.7 (m / marked~e.7) \
                  :ARG0-of (i / indicate-01~e.13 :ARG1~e.14 (l2 / likely-01~e.19 \
                  :ARG1 (d / depend-01~e.20 :ARG0 (e3 / express-03~e.15 :ARG2 p~e.17) \
                  :ARG1~e.21 (a / activity-06~e.23 :ARG0 (e / enzyme \
                  :name (n2 / name :op1 "ERK"~e.22)))))))', \
                  "Treatment of CRC cell lines with U0126 markedly reduced serpinE2 mRNA levels , indicating that expression of serpinE2 is likely dependent of ERK activity".split())
      >>> a
      (r / reduce-01~e.8[reduced]
          :ARG0 (t / treat-04~e.0[Treatment]
              :ARG1~e.1[of] (c / cell-line~e.3,4[cell,lines]
                  :mod (d2 / disease
                      :name (n3 / name
                          :op1 "CRC"~e.2[CRC])))
              :ARG2~e.5[with] (s / small-molecule
                  :name (n / name
                      :op1 "U0126"~e.6[U0126])))
          :ARG1 (l / level~e.11[levels]
              :quant-of (n6 / nucleic-acid
                  :name (n4 / name
                      :op1 "mRNA"~e.10[mRNA])
                  :ARG0-of (e2 / encode-01
                      :ARG1 (p / protein
                          :name (n5 / name
                              :op1 "serpinE2"~e.9[serpinE2])))))
          :manner~e.7[markedly] (m / marked~e.7[markedly])
          :ARG0-of (i / indicate-01~e.13[indicating]
              :ARG1~e.14[that] (l2 / likely-01~e.19[likely]
                  :ARG1 (d / depend-01~e.20[dependent]
                      :ARG0 (e3 / express-03~e.15[expression]
                          :ARG2 p~e.17[serpinE2])
                      :ARG1~e.21[of] (a / activity-06~e.23[activity]
                          :ARG0 (e / enzyme
                              :name (n2 / name
                                  :op1 "ERK"~e.22[ERK])))))))
      '''
  
      def __init__(self, anno, tokens=None):
          '''
          Given a Penman annotation string for a single rooted AMR, construct the data structure.
          Triples are stored internally in an order that preserves the layout of the
          annotation (even though this doesn't matter for the "pure" graph).
          (Whitespace is normalized, however.)
  
          Will raise an AMRSyntaxError if notationally malformed, or an AMRError if
          there is not a 1-to-1 mapping between (unique) variables and concepts.
          Will not check details such as the appropriateness of relation/role names
          or constants. Does not currently read or store metadata about the AMR.
          '''
          self._v2c = {}
          self._triples = []
          self._constants = set()
          self._alignments = {}
          self._role_alignments = {}
          self._tokens = tokens
  
          self.nodes = defaultdict(lambda: {'address': None,
                                            'type': None,
                                            'head': None,
                                            'rel': None,
                                            'word': None,
                                            'deps': []})
          # Emulate the DependencyGraph (superclass) data structures somewhat.
          # There are some differences, e.g., in AMR it is possible for a node to have
          # multiple dependents with the same relation; so here, 'deps' is simply a list
          # of dependents, not a mapping from relation types to dependents.
          # In typical depenency graphs, 'word' is a word in the sentence
          # and 'address' is its index; here, both point to the object representing
          # the node's AMR variable, concept, or constant.
  
          TOP = Var('TOP')
          self.nodes[TOP]['address'] = self.nodes[TOP]['word'] = TOP
          self.nodes[TOP]['type'] = 'TOP'
          if anno:
              self._anno = anno
              msg = ''
              try:
                  p = grammar.parse(anno)
              except ParseError as e:
                  msg += '\n' + str(e)
                  p = None
              if p is None:
                  raise AMRSyntaxError('Well-formedness error in annotation:\n' + anno.strip() + msg)
              self._analyze(p)
  
      def triples(self, head=None, rel=None, dep=None, normalize_inverses=False,
                  normalize_mod=False):  # overrides superclass implementation
          '''
          Returns a list of head-relation-dependent triples in the AMR.
          Can be filtered by specifying a value (or iterable of allowed values) for:
            - 'head': head variable(s)
            - 'rel': relation label(s) (string(s) starting with ":"), or "core" for all :ARGx roles,
              or "non-core" for all other relations. See also role_triples().
            - 'dep': dependent variable(s)/concept(s)/constant(s)
          Boolean options:
            - 'normalize_inverses': transform (h,':REL-of',d) relations to (d,':REL',h)
            - 'normalize_mod': transform ':mod' to ':domain-of' (before normalizing inverses,
              if applicable)
  
          >>> a = AMR('(h / hug-01 :ARG1 (p / person :ARG0-of h))')
          >>> a.triples(head=Var('h'))
          [(Var(h), ':instance-of', Concept(hug-01)), (Var(h), ':ARG1', Var(p))]
          >>> a.triples(head=Var('p'), rel=':instance-of')
          [(Var(p), ':instance-of', Concept(person))]
          >>> a.triples(rel=[':top',':instance-of'])
          [(Var(TOP), ':top', Var(h)), (Var(h), ':instance-of', Concept(hug-01)), (Var(p), ':instance-of', Concept(person))]
          >>> a.triples(rel='core')
          [(Var(h), ':ARG1', Var(p)), (Var(p), ':ARG0-of', Var(h))]
          >>> a.triples(rel='core', normalize_inverses=True)
          [(Var(h), ':ARG1', Var(p)), (Var(h), ':ARG0', Var(p))]
          '''
          tt = (trip for trip in self._triples)
          if normalize_mod:
              tt = ((h, ':domain-of', d) if r == ':mod' else (h, r, d) for h, r, d in tt)
          if normalize_inverses:
              tt = ((y, r[:-3], x) if r.endswith('-of') else (x, r, y) for x, r, y in tt)
          if head:
              tt = ((h, r, d) for h, r, d in tt if h in (head if hasattr(head, '__iter__') else (head,)))
          if rel:
              if rel == 'core':
                  tt = ((h, r, d) for h, r, d in tt if r.startswith(':ARG'))
              elif rel == 'non-core':
                  tt = ((h, r, d) for h, r, d in tt if not r.startswith(':ARG'))
              else:
                  tt = ((h, r, d) for h, r, d in tt if r in (rel if hasattr(rel, '__iter__') else (rel)))
          if dep:
              tt = ((h, r, d) for h, r, d in tt if d in (dep if hasattr(dep, '__iter__') else (dep,)))
          return list(tt)
  
      def role_triples(self, **kwargs):
          '''
          Same as triples(), but limited to roles (excludes :instance-of, :instance, and :top relations).
  
          >>> a = AMR('(h / hug-01 :ARG1 (p / person :ARG0-of h))')
          >>> a.role_triples()
          [(Var(h), ':ARG1', Var(p)), (Var(p), ':ARG0-of', Var(h))]
          >>> a.role_triples(head=Var('h'))
          [(Var(h), ':ARG1', Var(p))]
          '''
          tt = [(h, r, d) for h, r, d in self.triples(**kwargs) if r not in (':instance', ':instance-of', ':top')]
          return tt
  
      def constants(self):
          return self._constants
  
      def concept(self, variable):
          return self._v2c[variable]
  
      def concepts(self):
          return self._v2c.items()
  
      def var2concept(self):
          return dict(self._v2c)
  
      def alignments(self):
          return dict(self._alignments)
  
      def role_alignments(self):
          return dict(self._role_alignments)
  
      def tokens(self):
          return self._tokens
  
      def reentrancies(self):
          '''Counts the number of times each variable is mentioned in the annotation
          beyond the one where it receives a concept. Non-reentrant variables are not
          included in the output.'''
          c = defaultdict(int)
          for h, r, d in self.triples():
              if isinstance(d, Var):
                  c[d] += 1
              elif isinstance(d, Concept):
                  c[h] -= 1
          return Counter(c) + Counter()  # the addition removes non-positive entries
  
      # def __repr__(self):
      #    return 'AMR(v2c='+repr(self._v2c)+', triples='+repr(self._triples)+', constants='+repr(self._constants)+')'
  
      def __call__(self, *args, **kwargs):
          return self.__str__(*args, **kwargs)
  
      def __str__(self, alignments=True, tokens=True, compressed=False, indent=' ' * 4):
          '''
          Assumes triples are stored in a sensible order (reflecting how they are encountered in a valid AMR).
  
          >>> a = AMR('(p / person :ARG0-of (h / hug-01 :ARG0 p :ARG1 p) :mod (s / strange))')
          >>> # people who hug themselves and are strange
          >>> print(str(a))
          (p / person
              :ARG0-of (h / hug-01
                  :ARG0 p
                  :ARG1 p)
              :mod (s / strange))
          '''
  
          def alignment_str(align_key):
              s = '~' + align_key
              if tokens:  # alignment key is like "e.10" (single token offset) or "e.10,11" (multiple)
                  s += '[' + ','.join(tokens[int(woffset)] for woffset in align_key.split('.')[1].split(',')) + ']'
              return s
  
          s = ''
          stack = []
          instance_fulfilled = None
          align = role_align = {}
          if alignments:
              if tokens:
                  tokens = self.tokens()
              align = {k: alignment_str(align_key) for k, align_key in self._alignments.items()}
              role_align = {k: alignment_str(align_key) for k, align_key in self._role_alignments.items()}
          concept_stack_depth = {
              None: 0}  # size of the stack when the :instance-of triple was encountered for the variable
          for h, r, d in self.triples() + [(None, None, None)]:
              align_key = align.get((h, r, d), '')
              role_align_key = role_align.get((h, r, d), '')
              if r == ':top':
                  s += '(' + d()
                  stack.append((h, r, d))
                  instance_fulfilled = False
              elif r == ':instance-of':
                  s += ' / ' + d(align_key)
                  instance_fulfilled = True
                  concept_stack_depth[h] = len(stack)
              elif h == stack[-1][2] and r == ':polarity':  # polarity gets to be on the same line as the concept
                  s += ' ' + r + role_align_key + ' ' + d(align_key)
              else:
                  while len(stack) > concept_stack_depth[h]:
                      h2, r2, d2 = stack.pop()
                      if instance_fulfilled is False:
                          # just a variable or constant with no concept hanging off of it
                          # so we have an extra paren to get rid of
                          align_key2 = align.get((h2, r2, d2), '')
                          s = s[:-len(d2(align_key2)) - 1] + d2(align_key2, append=not instance_fulfilled)
                      else:
                          s += ')'
                      instance_fulfilled = None
                  if d is not None:
                      s += '\n' + indent * len(stack) + r + role_align_key + ' (' + d(align_key)
                      stack.append((h, r, d))
                      instance_fulfilled = False
          return s
  
      def __repr__(self):
          return str(self)
  
      def _analyze(self, p):
          '''Analyze the AST produced by parsimonious.'''
          v2c = {}  # variable -> concept
          allvars = set()  # all vars mentioned in the AMR
          elts = {}  # for interning variables, concepts, constants, etc.
          consts = set()  # all constants used in the AMR
  
          def intern_elt(x):
              return elts.setdefault(x, x)
  
          def walk(n):  # (v / concept...)
              triples = []
              deps = []
              v = None
              for ch in n.children:
                  t = ch.expr_name
                  if t == 'BAREVAR':
                      v = intern_elt(Var(ch.text))
                      allvars.add(v)
                  elif t == 'CONCEPT':
                      assert v is not None
                      if v in v2c:
                          raise AMRError('Variable has multiple concepts: ' + str(v) + '\n' + self._anno)
                      concept_node, alignment_node = ch.children
                      c = intern_elt(Concept(concept_node.text))
                      v2c[v] = c
                      self.add_node({'address': c, 'word': c, 'type': 'CONCEPT',
                                     'rel': ':instance-of', 'head': v, 'deps': []})
                      deps.append(c)
                      triple = (v, ':instance-of', c)
                      triples.append(triple)
                      if alignment_node.text:
                          self._alignments[triple] = alignment_node.text[1:]
                  elif t == '' and ch.children:
                      for ch2 in ch.children:
                          _part, RELpart, _part, Ypart = ch2.children
                          rel, relalignment = RELpart.children
                          rel = rel.text
                          assert rel is not None
                          assert len(Ypart.children) == 1
                          q = Ypart.children[0]
                          tq = q.expr_name
                          n2 = None
                          triples2 = []
                          deps2 = []
                          qalign = None
                          if tq == 'X':
                              n2, triples2, deps2 = walk(q)
                          elif tq == 'NAMEDCONST':
                              qleft, qalign = q.children
                              n2 = intern_elt(AMRConstant(qleft.text))
                              consts.add(n2)
                          elif tq == 'VAR':
                              qleft, qalign = q.children
                              n2 = intern_elt(Var(qleft.text))
                              allvars.add(n2)
                          elif tq == 'STR':
                              quote1, qstr, quote2, qalign = q.children
                              n2 = intern_elt(AMRString(qstr.text))
                              consts.add(n2)
                          elif tq == 'NUM':
                              qleft, qalign = q.children
                              n2 = intern_elt(AMRNumber(qleft.text))
                              consts.add(n2)
                          assert n2 is not None
                          self.add_node({'address': n2, 'word': n2, 'type': tq,
                                         'rel': rel, 'head': v})
                          self.nodes[n2]['deps'].extend(deps2)
                          deps.append(n2)
                          triple = (v, rel, n2)
                          triples.append(triple)
                          if qalign and qalign.text:
                              self._alignments[triple] = qalign.text[1:]
                          if relalignment.text:
                              self._role_alignments[triple] = relalignment.text[1:]
                          triples.extend(triples2)
              return v, triples, deps
  
          assert p.expr_name == 'ALL'
  
          n = None
          for ch in p.children:
              if ch.expr_name == 'X':
                  assert n is None  # only one top-level node per AMR
                  n, triples, deps = walk(ch)
                  self.add_node({'address': n, 'word': n, 'type': 'VAR',
                                 'rel': ':top', 'head': intern_elt(Var('TOP'))})
                  self.nodes[n]['deps'].extend(deps)
                  triples = [(intern_elt(Var('TOP')), ':top', n)] + triples
  
          if allvars - set(v2c.keys()):
              raise AMRError('Unbound variable(s): ' + ','.join(map(str, allvars - set(v2c.keys()))) + '\n' + self._anno)
  
          # All is well, so store the resulting data
          self._v2c = v2c
          self._triples = triples
          self._constants = consts
  
  
  good_tests = [
      '''(h / hot)''',
      '''(h / hot :mode expressive)''',
      '''(h / hot :mode "expressive")''',
      '''(h / hot :domain h)''',
      '''(h / hot :domain h~e.0)''',
      '''  (  h  /  hot   :mode  expressive  )   ''',
      '''  (  h
      /
      hot
      :mode
      expressive
      )   ''',
      '''(h / hot
           :mode expressive
           :mod (e / emoticon
                :value ":)"))''',
      '''(n / name :op1 "Washington")''',
      '''(s / state :name (n / name :op1 "Washington"))''',
      '''(s / state :name (n / name :op1 "Ohio"))
      ''',
      '''(s / state :name (n / name :op1 "Washington")
          )
      ''',
      '''(s / state
      :name (n / name :op1 "Washington"))''',
      '''(s / state :name (n / name :op1 "Washington")
          :wiki "http://en.wikipedia.org/wiki/Washington_(state)")
      ''',
      '''(f / film :name (n / name :op1 "Victor/Victoria")
          :wiki "http://en.wikipedia.org/wiki/Victor/Victoria_(1995_film)")
      ''',
      '''(g / go-01 :polarity -
            :ARG0 (b / boy))''',
      '''(a / and
      :op1 (l / love-01 :ARG0 (b / boy) :ARG1 (g / girl))
      :op2 (l2 / love-01 :ARG0 g :ARG1 b)
      )''',
      '''(d / date-entity :month 2 :day 29 :year 2012 :time "16:30" :timezone "PST"
                 :weekday (w / wednesday))''',
      '''(a / and :op1 (d / day :quant 40) :op2 (n / night :quant 40))''',
      '''(e / earthquake
                 :location (c / country-region :wiki "Tōhoku_region"
                       :name (n / name :op1 "Tohoku"))
                 :quant (s / seismic-quantity :quant 9.3)
                 :time (d / date-entity :year 23 :era "Heisei"
                       :calendar (c2 / country :wiki "Japan"
                             :name (n2 / name :op1 "Japan"))))''',
      ''' (d / date-entity :polite +
                 :time (a / amr-unknown))'''
  ]
  
  sembad_tests = [  # not a syntax error, but malformed in terms of variables
      '''(h / hot :mod (h / hot))''',
      '''(h / hot :mod q)'''
  ]
  
  bad_tests = [
      '''h / hot :mode expressive''',
      '''(h~e.0 / hot :domain h)''',
      '''(hot :mode expressive)''',
      '''(h/hot :mode expressive)''',
      '''(h / hot :mode )''',
      '''(h / hot :mode expressive''',
      '''(h / hot :mode (expressive))''',
      '''(h / hot :mode (e / ))''',
      '''((h / hot :mode expressive)''',
      '''(h / hot :mode expressive)
  
      x''',
      '''(s / state :name (n / name :op1 "  Washington  "))''',
      '''(s / state :name (n / name :op1 "Washington")
  
          )
      ''',
      '''(s / state
  
      :name (n / name :op1 "Washington"))''',
      '''(e / earthquake
                 :location (c / country-region :wiki "Tōhoku_region"
                       :name (n / name :op1 "Tohoku"))
                 :quant (s / seismic-quantity :quant 9.3.1)
                 :time (d / date-entity :year 23 :era "Heisei"
                       :calendar (c2 / country :wiki "Japan"
                             :name (n2 / name :op1 "Japan"))))'''
  ]
  
  
  def test():
      for good in good_tests:
          try:
              AMR(good)
          except AMRSyntaxError:
              print('Should be valid!')
              print(good)
          except AMRError:
              print('Should be valid!')
              print(good)
  
      for sembad in sembad_tests:
          try:
              AMR(sembad)
          except AMRSyntaxError:
              print('Parse should work!')
              print(sembad)
          except AMRError:
              pass  # should trigger exception
          else:
              print('Should be invalid!')
              print(sembad)
  
      for bad in bad_tests:
          try:
              AMR(bad)
          except AMRSyntaxError:
              pass
          else:
              print('Parse should fail!')
              print(bad)
  
  
  if __name__ == '__main__':
      test()
      import doctest
  
      doctest.testmod()
  