Repository: allenai/pybart (language: python)

Issue:
"case" edge not added in propagated nmod:x
In `a group of activists including Jack and John`, pyBART adds several "nmod:of" edges (as it should), but doesn't add the corresponding `case` edges to `of`.

in `it was founded by her brother, John`, pyBART adds an `nmod:agent` to `John` (as it should), but doesn't add the `case` edge from `John` to `by`.





Discussion / hints:

Fix patch:
diff --git a/dist/pybart-nlp-2.2.6.tar.gz b/dist/pybart-nlp-2.2.6.tar.gz
deleted file mode 100644
index 4cfa8a5..0000000
Binary files a/dist/pybart-nlp-2.2.6.tar.gz and /dev/null differ
diff --git a/dist/pybart_nlp-2.2.6-py3-none-any.whl b/dist/pybart_nlp-2.2.6-py3-none-any.whl
deleted file mode 100644
index a3b4168..0000000
Binary files a/dist/pybart_nlp-2.2.6-py3-none-any.whl and /dev/null differ
diff --git a/pybart/api.py b/pybart/api.py
index 79e95cb..7e27e40 100644
--- a/pybart/api.py
+++ b/pybart/api.py
@@ -1,10 +1,10 @@
 import math
 
 from .conllu_wrapper import parse_conllu, serialize_conllu, parse_odin, conllu_to_odin, parsed_tacred_json
-from .converter import convert, ConvsCanceler
+from .converter import convert, get_conversion_names as inner_get_conversion_names
 
 
-def convert_bart_conllu(conllu_text, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, preserve_comments=False, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=ConvsCanceler()):
+def convert_bart_conllu(conllu_text, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, preserve_comments=False, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=None):
     parsed, all_comments = parse_conllu(conllu_text)
     converted, _ = convert(parsed, enhance_ud, enhanced_plus_plus, enhanced_extra, conv_iterations, remove_eud_info, remove_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel)
     return serialize_conllu(converted, all_comments, preserve_comments)
@@ -16,7 +16,7 @@ def _convert_bart_odin_sent(doc, enhance_ud, enhanced_plus_plus, enhanced_extra,
     return conllu_to_odin(converted_sents, doc)
 
 
-def convert_bart_odin(odin_json, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=ConvsCanceler()):
+def convert_bart_odin(odin_json, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=None):
     if "documents" in odin_json:
         for doc_key, doc in odin_json["documents"].items():
             odin_json["documents"][doc_key] = _convert_bart_odin_sent(doc, enhance_ud, enhanced_plus_plus, enhanced_extra, conv_iterations, remove_eud_info, remove_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel)
@@ -26,36 +26,36 @@ def convert_bart_odin(odin_json, enhance_ud=True, enhanced_plus_plus=True, enhan
     return odin_json
 
 
-def convert_bart_tacred(tacred_json, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=ConvsCanceler()):
+def convert_bart_tacred(tacred_json, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=None):
     sents = parsed_tacred_json(tacred_json)
     converted_sents, _ = convert(sents, enhance_ud, enhanced_plus_plus, enhanced_extra, conv_iterations, remove_eud_info, remove_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel)
     
     return converted_sents
 
 
-def convert_spacy_doc(doc, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=ConvsCanceler()):
+def convert_spacy_doc(doc, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=None):
     from .spacy_wrapper import parse_spacy_sent, serialize_spacy_doc
     parsed_doc = [parse_spacy_sent(sent) for sent in doc.sents]
     converted, convs_done = convert(parsed_doc, enhance_ud, enhanced_plus_plus, enhanced_extra, conv_iterations, remove_eud_info, remove_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel)
-    return serialize_spacy_doc(doc, converted), parsed_doc, convs_done
+    return serialize_spacy_doc(doc, converted), converted, convs_done
 
 
 class Converter:
-    def __init__(self, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=ConvsCanceler()):
+    def __init__(self, enhance_ud=True, enhanced_plus_plus=True, enhanced_extra=True, conv_iterations=math.inf, remove_eud_info=False, remove_extra_info=False, remove_node_adding_conversions=False, remove_unc=False, query_mode=False, funcs_to_cancel=None):
         self.config = (enhance_ud, enhanced_plus_plus, enhanced_extra, conv_iterations, remove_eud_info, remove_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel)
     
     def __call__(self, doc):
-        serialized_spacy_doc, parsed_doc, convs_done = convert_spacy_doc(doc, *self.config)
-        self._parsed_doc = parsed_doc
+        serialized_spacy_doc, converted_sents, convs_done = convert_spacy_doc(doc, *self.config)
+        self._converted_sents = converted_sents
         self._convs_done = convs_done
         return serialized_spacy_doc
     
-    def get_parsed_doc(self):
-        return self._parsed_doc
+    def get_converted_sents(self):
+        return self._converted_sents
     
     def get_max_convs(self):
         return self._convs_done
 
 
 def get_conversion_names():
-    return ConvsCanceler.get_conversion_names()
+    return inner_get_conversion_names()
diff --git a/pybart/conllu_wrapper.py b/pybart/conllu_wrapper.py
index 4c943db..a55ff67 100644
--- a/pybart/conllu_wrapper.py
+++ b/pybart/conllu_wrapper.py
@@ -1,5 +1,5 @@
 import uuid
-from .graph_token import Token, add_basic_edges
+from .graph_token import Token, add_basic_edges, TokenId
 
 
 def parse_conllu(text):
@@ -26,7 +26,7 @@ def parse_conllu(text):
         if not lines:
             continue
         comments = []
-        sentence = dict()
+        sentence = []
         
         # for each line (either comment or token)
         for line in lines:
@@ -54,11 +54,11 @@ def parse_conllu(text):
             xpos = upos if xpos == '_' else xpos
             
             # add current token to current sentence
-            sentence[int(new_id)] = Token(
-                    int(new_id), form, lemma, upos, xpos, feats, int(head), deprel, deps, misc)
+            sentence.append(Token(
+                    TokenId(int(new_id)), form, lemma, upos, xpos, feats, TokenId(int(head)), deprel, deps, misc))
         
         # add root
-        sentence[0] = Token(0, None, None, None, None, None, None, None, None, None)
+        sentence.append(Token(TokenId(0), None, None, None, None, None, None, None, None, None))
         
         # after parsing entire sentence, add basic deprel edges,
         # and add sentence to output list
@@ -85,8 +85,7 @@ def serialize_conllu(converted, all_comments, preserve_comments=False):
         if preserve_comments:
             comments = ["\n".join(per_sent_comments)]
         
-        # TODO - fix case of more than 9 copy nodes - needs special ordering e.g 1.1 ... 1.9 1.10 and not 1.1 1.10 ... 1.9
-        text.append(comments + [token.get_conllu_string() for (cur_id, token) in sorted(sentence.items()) if cur_id != 0])
+        text.append(comments + [token.get_conllu_string() for token in sorted(sentence, key=lambda tok: tok.get_conllu_field("id")) if token.get_conllu_field("id").major != 0])
     
     return "\n".join(["\n".join(sent) + "\n" for sent in text])
 
@@ -96,16 +95,17 @@ def serialize_conllu(converted, all_comments, preserve_comments=False):
 def parse_odin(odin_json):
     sentences = []
     for sent in odin_json['sentences']:
-        sentence = {0: Token(0, None, None, None, None, None, None, None, None, None)}
+        sentence = list()
         for i, (word, tag, lemma) in enumerate(zip(sent['words'], sent['tags'], sent['lemmas'])):
-            sentence[i + 1] = Token(i + 1, word, lemma, "_", tag, "_", "_", "_", "_", "_")
+            sentence.append(Token(TokenId(i + 1), word, lemma, "_", tag, "_", None, "_", "_", "_"))
         for edge in sent['graphs']['universal-basic']['edges']:
-            sentence[edge['destination'] + 1].set_conllu_field('head', edge['source'] + 1)
+            sentence[edge['destination'] + 1].set_conllu_field('head', TokenId(edge['source'] + 1))
             sentence[edge['destination'] + 1].set_conllu_field('deprel', edge['relation'])
         for root in sent['graphs']['universal-basic']['roots']:
-            sentence[root + 1].set_conllu_field('head', 0)
+            sentence[root + 1].set_conllu_field('head', TokenId(0))
             sentence[root + 1].set_conllu_field('deprel', "root")
-        
+        sentence.append(Token(TokenId(0), None, None, None, None, None, None, None, None, None))
+
         add_basic_edges(sentence)
         sentences.append(sentence)
     
@@ -113,31 +113,35 @@ def parse_odin(odin_json):
 
 
 def _fix_sentence_keep_order(conllu_sentence):
-    sorted_sent = sorted(conllu_sentence.items())
+    sorted_sent = sorted(conllu_sentence)
     addon = 0
-    fixed = dict()
+    fixed = list()
     
-    for iid, token in sorted_sent:
-        if round(iid) != iid:
+    for token in sorted_sent:
+        iid = token.get_conllu_field("id")
+        if token.get_conllu_field("id").minor != 0:
             if "CopyOf" in token.get_conllu_field("misc"):
                 token.set_conllu_field("form", token.get_conllu_field("form") + "[COPY_NODE]")
             addon += 1
         
-        new_id = round(iid) + addon
-        token.set_conllu_field("id", new_id)
-        fixed[new_id] = token
+        new_id = iid.major + addon
+        token.set_conllu_field("id", TokenId(new_id))
+        fixed.append(token)
     
     return fixed
 
 
 def _fix_sentence_push_to_end(conllu_sentence):
-    fixed = dict()
-    
-    for i, (iid, token) in enumerate([(iid2, t) for (iid2, t) in conllu_sentence.items() if iid2 != 0]):
-        if round(iid) != iid:
-            token.set_conllu_field("id", i + 1)
+    fixed = list()
+
+    for i, token in enumerate(conllu_sentence):
+        iid = token.get_conllu_field("id")
+        if iid.major == 0:
+            continue
+        if iid.get_conllue_field("id").major != 0:
+            token.set_conllu_field("id", TokenId(i + 1))
         
-        fixed[i + 1] = token
+        fixed.append(token)
     
     return fixed
 
@@ -158,24 +162,25 @@ def fix_graph(conllu_sentence, odin_sentence, is_basic):
         else:
             odin_sentence["graphs"] = {"universal-enhanced": {"edges": [], "roots": []}}
 
-    for iid, token in conllu_sentence.items():
-        if iid == 0:
+    for iid, token in enumerate(conllu_sentence):
+        if token.get_conllu_field("id").major == 0:
             continue
         
         if is_basic:
             if token.get_conllu_field("deprel").lower().startswith("root"):
-                odin_sentence["graphs"]["universal-basic"]["roots"].append(iid - 1)
+                odin_sentence["graphs"]["universal-basic"]["roots"].append(iid)
             else:
                 odin_sentence["graphs"]["universal-basic"]["edges"].append(
-                    {"source": token.get_conllu_field("head") - 1, "destination": iid - 1,
+                    {"source": token.get_conllu_field("head").major - 1, "destination": iid,
                      "relation": token.get_conllu_field("deprel")})
         else:
-            for head, rel in token.get_new_relations():
-                if rel.lower().startswith("root"):
-                    odin_sentence["graphs"]["universal-enhanced"]["roots"].append(iid - 1)
-                else:
-                    odin_sentence["graphs"]["universal-enhanced"]["edges"].append(
-                        {"source": head.get_conllu_field("id") - 1, "destination": iid - 1, "relation": rel})
+            for head, rels in token.get_new_relations():
+                for rel in rels:
+                    if rel.to_str().lower().startswith("root"):
+                        odin_sentence["graphs"]["universal-enhanced"]["roots"].append(iid)
+                    else:
+                        odin_sentence["graphs"]["universal-enhanced"]["edges"].append(
+                            {"source": head.get_conllu_field("id").major - 1, "destination": iid, "relation": rel.to_str()})
     
     return odin_sentence
 
@@ -184,8 +189,8 @@ def append_odin(odin_sent, fixed_sentence, text):
     cur_sent_text = text
     cur_offset = 0
     
-    for node in list(fixed_sentence.values())[len(odin_sent['words']):]:
-        if node.get_conllu_field('id') == 0:
+    for node in fixed_sentence[len(odin_sent['words']):]:
+        if node.get_conllu_field('id').major == 0:
             continue
         
         if 'words' in odin_sent:
@@ -232,7 +237,7 @@ def conllu_to_odin(conllu_sentences, odin_to_enhance=None, is_basic=False, push_
             fix_offsets(odin_to_enhance['sentences'][i], summed_offset)
         
         # when added nodes appear fix sent
-        if any([round(iid) != iid for iid in conllu_sentence.keys()]):
+        if any([tok.get_conllu_field("id").minor != 0 for tok in conllu_sentence]):
             fixed_sentence = fix_sentence(fixed_sentence, push_new_to_end)
             if odin_to_enhance:
                 odin_to_enhance['sentences'][i], text, cur_offset = append_odin(odin_to_enhance['sentences'][i], fixed_sentence, text)
@@ -246,8 +251,8 @@ def conllu_to_odin(conllu_sentences, odin_to_enhance=None, is_basic=False, push_
         fixed_sentences.append(fixed_sentence)
         odin_sentences.append(fix_graph(
             fixed_sentence, odin_to_enhance['sentences'][i] if odin_to_enhance else
-            {'words': [token.get_conllu_field("form") for token in fixed_sentence.values() if token.get_conllu_field("id") != 0],
-             'tags': [token.get_conllu_field("xpos") for token in fixed_sentence.values() if token.get_conllu_field("id") != 0]},
+            {'words': [token.get_conllu_field("form") for token in fixed_sentence if token.get_conllu_field("id").major != 0],
+             'tags': [token.get_conllu_field("xpos") for token in fixed_sentence if token.get_conllu_field("id").major != 0]},
             is_basic))
     
     if odin_to_enhance:
@@ -257,8 +262,8 @@ def conllu_to_odin(conllu_sentences, odin_to_enhance=None, is_basic=False, push_
     else:
         odin = {"documents": {"": {
             "id": str(uuid.uuid4()),
-            "text": " ".join([token.get_conllu_field("form") for conllu_sentence in fixed_sentences for (_, token) in
-                              (sorted(conllu_sentence.items()) if not push_new_to_end else conllu_sentence.items()) if token.get_conllu_field("id") != 0]),
+            "text": " ".join([token.get_conllu_field("form") for conllu_sentence in fixed_sentences for token in
+                              (sorted(conllu_sentence) if not push_new_to_end else conllu_sentence) if token.get_conllu_field("id").major != 0]),
             "sentences": odin_sentences
         }}, "mentions": []}
     
@@ -274,7 +279,7 @@ def parsed_tacred_json(data):
             sentence[i + 1] = Token(i + 1, t, t, p, p, "_", int(h), dep, "_", "_")
         sentence[0] = Token(0, None, None, None, None, None, None, None, None, None)
         add_basic_edges(sentence)
-        [child.remove_edge(rel, sentence[0]) for child, rel in sentence[0].get_children_with_rels()]
+        [child.remove_edge(rel, sentence[0]) for child, rels in sentence[0].get_children_with_rels() for rel in rels]
         _ = sentence.pop(0)
         sentences.append(sentence)
     
diff --git a/pybart/constraints.py b/pybart/constraints.py
index 3d619ce..00a0d48 100644
--- a/pybart/constraints.py
+++ b/pybart/constraints.py
@@ -1,9 +1,8 @@
 from dataclasses import dataclass, field
-from typing import Sequence, Set, List, Optional
+from typing import Sequence, Set, List, Optional, Callable, Any
 from enum import Enum
 from math import inf
 from abc import ABC, abstractmethod
-import re
 
 
 class FieldNames(Enum):
@@ -23,64 +22,56 @@ def __post_init__(self):
         # validate value's value specifically because str is converted to list and this is hard to debug
         if not isinstance(self.value, list):
             raise ValueError(f"Expected <class 'list'> got {type(self.value)}")
+        object.__setattr__(self, 'value', [v.lower() for v in self.value])
+
+    def satisfied(self, context: Any, get_content_by_field: Callable[[Any, FieldNames], str]) -> bool:
+        return not ((get_content_by_field(context, self.field).lower() in self.value) ^ self.in_sequence)
 
 
 @dataclass(frozen=True)
-class Label(ABC):
+class LabelPresence(ABC):
     @abstractmethod
     def satisfied(self, actual_labels: List[str]) -> Set[str]:
         pass
 
 
 @dataclass(frozen=True)
-class HasLabelFromList(Label):
+class HasLabelFromList(LabelPresence):
     # has at least one edge with value
     value: Sequence[str]
-    is_regex: Sequence[bool] = field(init=False)
+    is_regex: bool = field(default=False, init=False)
 
     def __post_init__(self):
         # validate value's value specifically because str is converted to list and this is hard to debug
         if not isinstance(self.value, list):
             raise ValueError(f"Expected <class 'list'> got {type(self.value)}")
-        new_values = [v[1:-1] if v.startswith('/') and v.endswith('/') else v for v in self.value]
-        new_is_regex = [v.startswith('/') and v.endswith('/') for v in self.value]
-        object.__setattr__(self, 'value', new_values)
-        object.__setattr__(self, 'is_regex', new_is_regex)
+        if len(self.value) == 1 and self.value[0].startswith('/') and self.value[0].endswith('/'):
+            object.__setattr__(self, 'is_regex', True)
 
     def satisfied(self, actual_labels: List[str]) -> Optional[Set[str]]:
-        current_successfully_matched = set()
         # at least one of the constraint strings should match, so return False only if none of them did.
-        for value_option, is_current_regex in zip(self.value, self.is_regex):
-            # for each edged label, check if the label matches the constraint, and store it if it does,
-            #   because it is a positive search (that is at least one label should match)
-            for actual_label in actual_labels:
-                if (is_current_regex and re.match(value_option, actual_label)) or \
-                        (not is_current_regex and value_option == actual_label):
-                    # store the matched label
-                    current_successfully_matched.add(actual_label)
+        if self.is_regex:
+            return set(actual_labels)
+
+        # for each edged label, check if the label matches the constraint, and store it if it does,
+        #   because it is a positive search (that is at least one label should match)
+        current_successfully_matched = [v for v in self.value if v in actual_labels]
+
         if len(current_successfully_matched) == 0:
             return None
-        return current_successfully_matched
+        return set(current_successfully_matched)
 
 
 @dataclass(frozen=True)
-class HasNoLabel(Label):
+class HasNoLabel(LabelPresence):
     # does not have edge with value
     value: str
-    is_regex: bool = field(init=False, default=False)
-
-    def __post_init__(self):
-        if self.value.startswith('/') and self.value.endswith('/'):
-            object.__setattr__(self, 'value', self.value[1:-1])
-            object.__setattr__(self, 'is_regex', True)
 
     def satisfied(self, actual_labels: List[str]) -> Optional[Set[str]]:
         # for each edged label, check if the label matches the constraint, and fail if it does,
         #   because it is a negative search (that is non of the labels should match)
-        for actual_label in actual_labels:
-            if (self.is_regex and re.match(self.value, actual_label)) or \
-                    (not self.is_regex and self.value == actual_label):
-                return None
+        if self.value in actual_labels:
+            return None
         return set()
 
 
@@ -90,8 +81,8 @@ class Token:
     capture: bool = True
     spec: Sequence[Field] = field(default_factory=list)
     optional: bool = False  # is this an optional constraint or required
-    incoming_edges: Sequence[Label] = field(default_factory=list)
-    outgoing_edges: Sequence[Label] = field(default_factory=list)
+    incoming_edges: Sequence[LabelPresence] = field(default_factory=list)
+    outgoing_edges: Sequence[LabelPresence] = field(default_factory=list)
     no_children: bool = False  # should this token have no children or can it
     is_root: bool = False  # should this token have no parents (i.e. it is the root) or can it
 
@@ -100,7 +91,11 @@ class Token:
 class Edge:
     child: str
     parent: str
-    label: Sequence[Label]
+    label: Sequence[LabelPresence]
+    optional: bool = field(init=False, default=False)
+
+    def adjust_optionality(self, is_any_opt):
+        object.__setattr__(self, 'optional', is_any_opt)
 
 
 @dataclass(frozen=True)
@@ -136,7 +131,7 @@ def __post_init__(self):
             raise ValueError("'up-to' distance can't be negative")
 
     def satisfied(self, calculated_distance: int) -> bool:
-        return self.distance >= calculated_distance
+        return 0 <= calculated_distance <= self.distance
 
 
 @dataclass(frozen=True)
@@ -224,6 +219,11 @@ def __post_init__(self):
                 raise ValueError(
                     "Found a token with a no_children/is_root constraint and outgoing_edges/incoming_edges constraint")
 
+        for edge in self.edges:
+            is_child_opt = any(tok.optional for tok in self.tokens if tok.id == edge.child)
+            is_parent_opt = any(tok.optional for tok in self.tokens if tok.id == edge.parent)
+            edge.adjust_optionality(is_child_opt or is_parent_opt)
+
 
 # usage examples:
 #
diff --git a/pybart/converter.py b/pybart/converter.py
index 95642ec..1f95a7d 100644
--- a/pybart/converter.py
+++ b/pybart/converter.py
@@ -5,274 +5,239 @@
 #   3. we look for all fathers as we can have multiple fathers, while in SC they look at first one found.
 
 import sys
-import re
-from math import copysign
+from collections import defaultdict
 import inspect
-from typing import List
 
 from .constraints import *
-from .matcher import *
+from .graph_token import Label, TokenId
+from . import pybart_globals
 
-# constants
+from .matcher import Matcher, NamedConstraint
+from dataclasses import dataclass
+
+# constants   # TODO - english specific
 nmod_advmod_complex = ["back_to", "back_in", "back_at", "early_in", "late_in", "earlier_in"]
 two_word_preps_regular = {"across_from", "along_with", "alongside_of", "apart_from", "as_for", "as_from", "as_of", "as_per", "as_to", "aside_from", "based_on", "close_by", "close_to", "contrary_to", "compared_to", "compared_with", " depending_on", "except_for", "exclusive_of", "far_from", "followed_by", "inside_of", "irrespective_of", "next_to", "near_to", "off_of", "out_of", "outside_of", "owing_to", "preliminary_to", "preparatory_to", "previous_to", "prior_to", "pursuant_to", "regardless_of", "subsequent_to", "thanks_to", "together_with"}
 two_word_preps_complex = {"apart_from", "as_from", "aside_from", "away_from", "close_by", "close_to", "contrary_to", "far_from", "next_to", "near_to", "out_of", "outside_of", "pursuant_to", "regardless_of", "together_with"}
 three_word_preps = {"by_means_of", "in_accordance_with", "in_addition_to", "in_case_of", "in_front_of", "in_lieu_of", "in_place_of", "in_spite_of", "on_account_of", "on_behalf_of", "on_top_of", "with_regard_to", "with_respect_to"}
 clause_relations = ["conj", "xcomp", "ccomp", "acl", "advcl", "acl:relcl", "parataxis", "appos", "list"]
-quant_mod_3w = "(?i:lot|assortment|number|couple|bunch|handful|litany|sheaf|slew|dozen|series|variety|multitude|wad|clutch|wave|mountain|array|spate|string|ton|range|plethora|heap|sort|form|kind|type|version|bit|pair|triple|total)"
-quant_mod_2w = "(?i:lots|many|several|plenty|tons|dozens|multitudes|mountains|loads|pairs|tens|hundreds|thousands|millions|billions|trillions|[0-9]+s)"
-quant_mod_2w_det = "(?i:some|all|both|neither|everyone|nobody|one|two|three|four|five|six|seven|eight|nine|ten|hundred|thousand|million|billion|trillion|[0-9]+)"
-relativizing_word_regex = "(?i:that|what|which|who|whom|whose)"
+quant_mod_3w = ['lot', 'assortment', 'number', 'couple', 'bunch', 'handful', 'litany', 'sheaf', 'slew', 'dozen', 'series', 'variety', 'multitude', 'wad', 'clutch', 'wave', 'mountain', 'array', 'spate', 'string', 'ton', 'range', 'plethora', 'heap', 'sort', 'form', 'kind', 'type', 'version', 'bit', 'pair', 'triple', 'total']
+quant_mod_2w = ['lots', 'many', 'several', 'plenty', 'tons', 'dozens', 'multitudes', 'mountains', 'loads', 'pairs', 'tens', 'hundreds', 'thousands', 'millions', 'billions', 'trillions']
+quant_mod_2w_det = ['some', 'all', 'both', 'neither', 'everyone', 'nobody', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'hundred', 'thousand', 'million', 'billion', 'trillion']
+relativizing_words = ["that", "what", "which", "who", "whom", "whose"]
+relativizers_to_rel = {'where': "nmod", "how": "nmod", "when": "nmod:tmod", "why": "nmod"}  # TODO: UDv1 = nmod
 neg_conjp_prev = ["if_not"]
 neg_conjp_next = ["instead_of", "rather_than", "but_rather", "but_not"]
 and_conjp_next = ["as_well", "but_also"]
-advmod_list = "(here|there|now|later|soon|before|then|today|tomorrow|yesterday|tonight|earlier|early)"
-evidential_list = "^(seem|appear|be|sound)$"
-aspectual_list = "^(begin|continue|delay|discontinue|finish|postpone|quit|resume|start|complete)$"
-reported_list = "^(report|say|declare|announce|tell|state|mention|proclaim|replay|point|inform|explain|clarify|define|expound|describe|illustrate|justify|demonstrate|interpret|elucidate|reveal|confess|admit|accept|affirm|swear|agree|recognise|testify|assert|think|claim|allege|argue|assume|feel|guess|imagine|presume|suggest|argue|boast|contest|deny|refute|dispute|defend|warn|maintain|contradict)$"
-EXTRA_INFO_STUB = 1
-g_remove_enhanced_extra_info = False
-g_remove_bart_extra_info = False
+advmod_list = ['here', 'there', 'now', 'later', 'soon', 'before', 'then', 'today', 'tomorrow', 'yesterday', 'tonight', 'earlier', 'early']
+evidential_list = ['seem', 'appear', 'be', 'sound']
+aspectual_list = ['begin', 'continue', 'delay', 'discontinue', 'finish', 'postpone', 'quit', 'resume', 'start', 'complete']
+reported_list = ['report', 'say', 'declare', 'announce', 'tell', 'state', 'mention', 'proclaim', 'replay', 'point', 'inform', 'explain', 'clarify', 'define', 'expound', 'describe', 'illustrate', 'justify', 'demonstrate', 'interpret', 'elucidate', 'reveal', 'confess', 'admit', 'accept', 'affirm', 'swear', 'agree', 'recognise', 'testify', 'assert', 'think', 'claim', 'allege', 'argue', 'assume', 'feel', 'guess', 'imagine', 'presume', 'suggest', 'argue', 'boast', 'contest', 'deny', 'refute', 'dispute', 'defend', 'warn', 'maintain', 'contradict']
+advcl_non_legit_markers = ["as", "so", "when", "if"]  # TODO - english specific
+adj_pos = ["JJ", "JJR", "JJS"]
+verb_pos = ["VB", "VBD", "VBG", "VBN", "VBP", "VBZ", "MD"]
+noun_pos = ["NN", "NNS", "NNP", "NNPS"]
+pron_pos = ["PRP", "PRP$", "WP", "WP$"]
+subj_options = ["nsubj", "nsubjpass", "csubj", "csubjpass"]  # TODO - UDv1 = pass
+obj_options = ["dobj", "iobj"]  # TODO - UDv1 = pass
 g_remove_node_adding_conversions = False
+g_iids = dict()
+g_cc_assignments = dict()
+g_ud_version = 1  # default UD version we work with is 1
 
 
-class ConvsCanceler:
-    def __init__(self, cancel_list: List[str] = None):
-        self.cancel_list = cancel_list
-        self.original = {func_name: func_pointer for (func_name, func_pointer) in inspect.getmembers(sys.modules[__name__], inspect.isfunction)
-                         if (func_name.startswith("eud") or func_name.startswith("eudpp") or func_name.startswith("extra"))}
-        self._func_names = self.original.keys()
-    
-    def restore_funcs(self):
-        # best effort in cleanup
-        try:
-            for func_name, func_pointer in self.original.items():
-                setattr(sys.modules[__name__], func_name, func_pointer)
-        except KeyError:
-            pass
-    
-    def override_funcs(self):
-        if self.cancel_list:
-            for func_name in self.cancel_list:
-                if func_name not in self._func_names:
-                    raise ValueError(f"{func_name} is not a real function name")
-                setattr(sys.modules[__name__], func_name, lambda *x: None)
-    
-    def update_funcs(self, func_names: List[str]):
-        if self.cancel_list:
-            self.cancel_list.extend(func_names)
-        else:
-            self.cancel_list = func_names
-    
-    def update_funcs_by_prefix(self, prefix: str):
-        func_names = list()
-        for func_name in self._func_names:
-            if func_name.startswith(prefix):
-                func_names.append(func_name)
-        self.update_funcs(func_names)
-    
-    @staticmethod
-    def get_conversion_names():
-        return set(ConvsCanceler()._func_names)
+class ConvTypes(Enum):
+    EUD = 1
+    EUDPP = 2
+    BART = 3
 
 
-def split_by_at(label):
-    # For the rare case which involvs a '@' preposition,
-    # we temporarily replace it with 'at', instead of simply doing rel.split("@")
-    return [x.replace(":at", ":@") for x in label.replace(":@", ":at").split("@")]
+ConvFuncSignature = Callable[[Any, Any], None]
 
 
-def naked_label(label):
-    return split_by_at(label)[0].split(":")[0]
+@dataclass
+class Conversion:
+    conv_type: ConvTypes
+    constraint: Full
+    transformation: ConvFuncSignature
 
+    def __post_init__(self):
+        self.name = self.transformation.__name__
 
-def add_eud_info(orig, extra):
-    at = orig.split("@")
-    base = at[0]
-    if ":" in orig:
-        base = at[0].split(":")[0]
-    return base + ((":" + extra) if not g_remove_enhanced_extra_info else "") + (("@" + at[1]) if len(at) > 1 else "")
 
+def get_eud_info(eud_str):
+    return eud_str if not pybart_globals.g_remove_enhanced_extra_info else None
 
-def add_extra_info(orig, dep, dep_type=None, phrase=None, iid=None, uncertain=False, prevs=None):
-    global g_remove_bart_extra_info
-    
-    source_str = ""
-    if not g_remove_bart_extra_info:
-        iid_str = ""
-        if iid is not None:
-            iid_str = "#" + str(iid)
-        prevs_str = ""
-        if (prevs is not None) and (len(prevs.split("@")) > 1):
-            prevs_str = "+" + prevs.split("@")[-1]
-        dep_args = ", ".join([x for x in [dep_type, phrase, "UNC" if uncertain else None] if x])
-        source_str = "@" + dep + "(" + dep_args + ")" + iid_str + prevs_str
-    
-    return orig + source_str
 
+def get_conversion_names():
+    return {func_name for (func_name, _) in inspect.getmembers(sys.modules[__name__], inspect.isfunction)
+            if (func_name.startswith("eud") or func_name.startswith("eudpp") or func_name.startswith("extra"))}
 
-# correctDependencies - correctSubjPass
-# This method corrects subjects of verbs for which we identified an auxpass,
-# but didn't identify the subject as passive.
-# (includes nsubj/csubj/nsubj:xsubj/csubj:xsubj)
-# correctDependencies - processNames and removeExactDuplicates: have been skipped.
-# processNames for future treatment, removeExactDuplicates for redundancy.
-def eud_correct_subj_pass(sentence):
-    restriction = Restriction(name="root", nested=[[
-        Restriction(gov='auxpass', name="aux"),
-        # the SC regex (which was "^(nsubj|csubj).*$") was changed here
-        # to avoid the need to filter .subjpass relations in the graph-rewriting part
-        Restriction(gov="^(.subj|.subj(?!pass).*)$", name="subj")
-    ]])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    # rewrite graph: for every subject add a 'pass' and replace in graph node
-    for name_space in ret:
-        subj, subj_head, subj_rel = name_space['subj']
-        aux, _, _ = name_space['aux']
-        root, _, _ = name_space['root']
-        if aux.get_conllu_field("id") < root.get_conllu_field("id") < subj.get_conllu_field("id"):
-            continue
-        substitute_rel = re.sub("(?<!x)subj", "subjpass", subj_rel)
-        # in SC they add it to the 'deprel' even if the edge was found in the 'deps' :O
-        subj.replace_edge(subj_rel, substitute_rel, subj_head, subj_head)
-
-
-# add 'agent' to nmods if it is cased by 'by', and have an auxpass sibling
-def eud_passive_agent(sentence):
-    restriction = Restriction(name="gov", nested=[[
-        Restriction(gov='auxpass'),
-        Restriction(name="mod", gov="^(nmod)$", nested=[[
-            Restriction(gov='case', form="^(?i:by)$")
-        ]])
-    ]])
-
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-
-    # rewrite graph: for every nmod add ':agent' to the graph node relation
-    for name_space in ret:
-        gov, _, _ = name_space['gov']
-        mod, _, mod_rel = name_space['mod']
-        mod.replace_edge(mod_rel, add_eud_info(mod_rel, "agent"), gov, gov)
-
-
-# we need to create a concat string for every marker neighbor chain
-# actually it should never happen that they are separate, but nonetheless we add relation if so,
-# this might result in multi-graph
-# e.g 'in front of' should be [in_front_of] if they are all sequential, but [in, front_of],
-# if only 'front' and 'of' are sequential (and 'in' is separated).
-def concat_sequential_tokens(c1, c2, c3):
-    # add the first word
-    sequences = [c1.get_conllu_field('form')]
-    prev = c1
-    if not c2:
-        # we return here because if c2 is None, c3 must be as well
-        return sequences
-    
-    for ci in ([c2, c3] if c3 else [c2]):
-        if prev.get_conllu_field('id') > ci.get_conllu_field('id'):
-            return
-        # concat every following marker, or start a new string if not
-        elif prev.get_conllu_field('id') == ci.get_conllu_field('id') - 1:
-            sequences[-1] += '_' + ci.get_conllu_field('form')
-        else:
-            sequences.append(ci.get_conllu_field('form'))
-        prev = ci
-    return sequences
 
+def udv(udv1_str: str, udv2_str: str) -> str:
+    return udv1_str if g_ud_version == 1 else udv2_str
 
-def prep_patterns_per_type(sentence, restriction):
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        mod, mod_head, mod_rel = name_space['mod']
-        c1, _, _ = name_space['c1']
-        c2, _, _ = name_space['c2'] if 'c2' in name_space else (None, None, None)
-        c3, _, _ = name_space['c3'] if 'c3' in name_space else (None, None, None)
-        
-        sequences = concat_sequential_tokens(c1, c2, c3)
-        if not sequences:
-            continue
-        
-        mod.remove_edge(mod_rel, mod_head)
-        for prep_sequence in sequences:
-            mod.add_edge(add_eud_info(mod_rel, prep_sequence.lower()), mod_head)
-
-
-def prep_patterns_inner(sentence, first_gov, second_gov):
-    restriction_3w = Restriction(name="gov", nested=[[
-        Restriction(name="mod", gov=first_gov, nested=[[
-            Restriction(name="c1", gov=second_gov, nested=[[
-                Restriction(name="c2", gov="mwe"),
-                Restriction(name="c3", gov="mwe", diff="c2")
-            ]])
-        ]])
-    ]])
-    restriction_2w = Restriction(name="gov", nested=[[
-        Restriction(name="mod", gov=first_gov, nested=[[
-            Restriction(name="c1", gov=second_gov, nested=[[
-                Restriction(name="c2", gov="mwe")
-            ]])
-        ]])
-    ]])
-    restriction_1w = Restriction(name="gov", nested=[[
-        Restriction(name="mod", gov=first_gov, nested=[[
-            # here we want to find any one word that marks a modifier,
-            # except cases in which 'by' was used for 'agent' identification,
-            # but the 'exact' notation will prevent those from being caught
-            Restriction(name="c1", gov=second_gov)
-        ]])
-    ]])
-    
-    # NOTE: in SC since they replace the modifier (nmod/advcl/acl) it won't come up again in future matches,
-    # as they use the exact (^$) symbols. and so we imitate this behavior.
-    for rest in [restriction_3w, restriction_2w, restriction_1w]:
-        prep_patterns_per_type(sentence, rest)
 
+def create_mwe(words, head, rel):
+    for i, word in enumerate(words):
+        word.remove_all_edges()
+        word.add_edge(rel, head)
+        if 0 == i:
+            head = word
+            rel = Label("mwe")  # TODO: UDv1 = mwe
 
-def eud_prep_patterns(sentence):
-    prep_patterns_inner(sentence, '^nmod$', 'case')
-    prep_patterns_inner(sentence, '^(advcl|acl)$', '^(mark|case)$')
 
+def reattach_children(old_head, new_head, new_rel=None, cond=None):
+    [child.replace_edge(child_rel, new_rel if new_rel else child_rel, old_head, new_head) for
+     (child, child_rels) in old_head.get_children_with_rels() for child_rel in child_rels if not cond or cond(child_rel)]
 
-def eud_heads_of_conjuncts(sentence):
-    restriction = Restriction(name="new_gov", nested=[[
-        Restriction(name="gov", gov="^((?!root|case).)*$", nested=[[
-             Restriction(name="dep", gov="conj.*")
-        ]])
-    ]])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        gov, gov_head, gov_rel = name_space['gov']
-        dep, _, _ = name_space['dep']
-        
-        # only if the dependant of the conj is not the head of the head of the conj,
-        # and they dont fall under the relcl problem, propagate the relation
-        # NOTE: actually SC restrict this more aggressively.
-        if (gov_head, gov_rel) not in gov.get_extra_info_edges() \
-                and gov_head != dep \
-                and (gov_head, naked_label(gov_rel)) not in [(h, naked_label(r)) for (h, r) in dep.get_new_relations()]:
-            dep.add_edge(gov_rel, gov_head)
+
+def reattach_parents(old_child, new_child, new_rel=None, rel_by_cond=lambda x, y, z: x if x else y):
+    new_child.remove_all_edges()
+    [(old_child.remove_edge(parent_rel, head), new_child.add_edge(rel_by_cond(new_rel, parent_rel, head), head))
+     for (head, parent_rels) in list(old_child.get_new_relations()) for parent_rel in parent_rels]
+
+
+# TODO: english = this entire function
+# resolves the following multi word conj phrases:
+#   a. 'but(cc) not', 'if not', 'instead of', 'rather than', 'but(cc) rather'. GO TO negcc
+#   b. 'as(cc) well as', 'but(cc) also', 'not to mention', '&'. GO TO and
+# NOTE: This is bad practice (and sometimes not successful neither for SC or for us) for the following reasons:
+#   1. Not all parsers mark the same words as cc (if at all), so looking for the cc on a specific word is wrong.
+#       as of this reason, for now we and SC both miss: if-not, not-to-mention
+#   2. Some of the multi-words are already treated as multi-word prepositions (and are henceforth missed):
+#       as of this reason, for now we and SC both miss: instead-of, rather-than
+def get_assignment(sentence, cc):
+    cc_cur_id = cc.get_conllu_field('id').major - 1
+    prev_forms = "_".join([info.get_conllu_field('form') for (iid, info) in enumerate(sentence)
+                           if (cc_cur_id - 1 == iid or cc_cur_id == iid)])
+    next_forms = "_".join([info.get_conllu_field('form') for (iid, info) in enumerate(sentence)
+                           if (cc_cur_id + 1 == iid) or (cc_cur_id == iid)])
+    if next_forms in neg_conjp_next or prev_forms in neg_conjp_prev:
+        return "negcc"
+    elif (next_forms in and_conjp_next) or (cc.get_conllu_field('form') == '&'):
+        return "and"  # TODO: english = and
+    else:
+        return cc.get_conllu_field('form')
+
+
+# In case multiple coordination marker depend on the same governor
+# the one that precedes the conjunct is appended to the conjunction relation or the
+# first one if no preceding marker exists.
+def assign_ccs_to_conjs(sentence):
+    global g_cc_assignments
+    g_cc_assignments = dict()
+    for token in sentence:
+        ccs = []
+        for child, rels in sorted(token.get_children_with_rels(), reverse=True):
+            for rel in rels:
+                if 'cc' == rel.base:
+                    ccs.append(child)
+        i = 0
+        for child, rels in sorted(token.get_children_with_rels(), reverse=True):
+            for rel in rels:
+                if rel.base.startswith('conj'):
+                    if len(ccs) == 0:
+                        g_cc_assignments[child] = (None, 'and')
+                    else:
+                        cc = ccs[i if i < len(ccs) else -1]
+                        g_cc_assignments[child] = (cc, get_assignment(sentence, cc))
+                    i += 1
+
+
+# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #
+
+
+# This method corrects subjects of verbs for which we identified an auxpass,
+# but didn't identify the subject as passive.
+eud_correct_subj_pass_constraint = Full(
+    tokens=[
+        Token(id="aux"),
+        Token(id="pred"),
+        Token(id="subj"),
+    ],
+    edges=[
+        Edge(child="aux", parent="pred", label=[HasLabelFromList(["auxpass"])]),  # TODO - UDv1 = pass
+        Edge(child="subj", parent="pred", label=[HasLabelFromList(["nsubj", "csubj"])]),
+    ],
+    distances=[UptoDistance("subj", "aux", inf)]
+)
+
+
+def eud_correct_subj_pass(sentence, matches):
+    # for every located subject add a 'pass' and replace in graph node
+    for cur_match in matches:
+        subj = cur_match.token("subj")
+        pred = cur_match.token("pred")
+        for subj_rel in cur_match.edge(subj, pred):
+            new_rel = Label(subj_rel.replace("subj", "subjpass"))  # TODO - UDv1 = pass
+            sentence[subj].replace_edge(Label(subj_rel), new_rel, sentence[pred], sentence[pred])
+
+
+# This conversion adds the case information on the label.
+# and adds 'agent' to nmods if it is cased by 'by', and have an auxpass sibling
+# Note - Originally  SC took care of cases in which the words of a multi-word preposition were not adjacent,
+#   but this is too rigorous as it should never happen, so we ignore this case.
+eud_prep_patterns_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="mod"),
+        Token(id="auxpass", optional=True),
+        Token(id="c1"),
+        Token(id="c_nn", optional=True, spec=[Field(field=FieldNames.TAG, value=noun_pos)]),
+        Token(id="c_in", optional=True, spec=[Field(field=FieldNames.TAG, value=["IN", "TO"])]),
+    ],
+    edges=[
+        Edge(child="mod", parent="gov", label=[HasLabelFromList(["advcl", "acl", "nmod"])]),  # TODO - UDv1 = nmod
+        Edge(child="c1", parent="mod", label=[HasLabelFromList(["case", "mark"])]),
+        Edge(child="c_in", parent="c1", label=[HasLabelFromList(["mwe"])]),  # TODO - UDv1 = mwe
+        Edge(child="c_nn", parent="c1", label=[HasLabelFromList(["mwe"])]),  # TODO - UDv1 = mwe
+        Edge(child="auxpass", parent="gov", label=[HasLabelFromList(["auxpass"])]),  # TODO - UDv1 = auxpass
+    ],
+)
+
+
+def eud_prep_patterns(sentence, matches):
+    for cur_match in matches:
+        mod = cur_match.token("mod")
+        gov = cur_match.token("gov")
+        c1 = cur_match.token("c1")
+        c_in = cur_match.token("c_in")
+        c_nn = cur_match.token("c_nn")
+        auxpass = cur_match.token("auxpass")
+        for rel in cur_match.edge(mod, gov):
+            prep_sequence = "_".join([sentence[ci].get_conllu_field("form") for ci in [c1, c_nn, c_in] if ci != -1]).lower()
+            if prep_sequence == "by" and auxpass != -1:  # TODO: english = by
+                prep_sequence = "agent"
+            # TODO: this is because we constraint only on base string and not on Label or parts of it such as eud
+            if any(r.eud is not None for h, rels in sentence[mod].get_new_relations(sentence[gov]) for r in rels):
+                continue
+            sentence[mod].replace_edge(Label(rel), Label(rel, prep_sequence), sentence[gov], sentence[gov])
+
+
+eud_heads_of_conjuncts_constraint = Full(
+    tokens=[
+        Token(id="new_gov"),
+        Token(id="gov"),
+        Token(id="dep")],
+    edges=[
+        Edge(child="gov", parent="new_gov", label=[HasLabelFromList(["/.*/"]), HasNoLabel("case"), HasNoLabel("mark")]),
+        Edge(child="dep", parent="gov", label=[HasLabelFromList(["conj"])]),
+    ],
+)
+
+
+def eud_heads_of_conjuncts(sentence, matches):
+    for cur_match in matches:
+        new_gov = cur_match.token("new_gov")
+        gov = cur_match.token("gov")
+        dep = cur_match.token("dep")
         
-        # NOTE: this is not part of the original SC.
-        # if the shared head is an nmod/acl/advcl, then propagate the case/marker also between the conjuncts.
-        if \
-                (gov_rel.startswith("nmod") and all([not r.startswith("case") for (c, r) in dep.get_children_with_rels()])) or \
-                (re.match("acl|advcl", gov_rel) and all([not re.match("case|mark", r) for (c, r) in dep.get_children_with_rels()])):
-            for c, r in gov.get_children_with_rels():
-                if re.match("case|mark", r):
-                    c.add_edge(r, dep)
+        for rel in cur_match.edge(gov, new_gov):
+            if sentence[gov].get_conllu_field("id") < sentence[new_gov].get_conllu_field("id") < sentence[dep].get_conllu_field("id"):
+                continue
+            if (sentence[new_gov], rel) not in [(h, r.base) for (h, rels) in sentence[dep].get_new_relations() for r in rels]:
+                sentence[dep].add_edge(Label(rel), sentence[new_gov])
         
         # TODO:
         #   for the trees of ambiguous "The boy and the girl who lived told the tale."
@@ -281,7 +246,29 @@ def eud_heads_of_conjuncts(sentence):
         #   "The boy and the girl, who lived, told the tale."
 
 
-# we propagate only subj (for now) as this is what the original code stated:
+eud_case_sons_of_conjuncts_constraint = Full(
+    tokens=[
+        Token(id="new_son"),
+        Token(id="gov"),
+        Token(id="dep", outgoing_edges=[HasNoLabel(child) for child in ["aux", "auxpass", "case", "mark"]])],  # TODO: UDv1 = auxpass
+    edges=[
+        Edge(child="new_son", parent="gov", label=[HasLabelFromList(["aux", "auxpass", "case", "mark"])]),
+        Edge(child="dep", parent="gov", label=[HasLabelFromList(["conj"])]),
+    ],
+)
+
+
+def eud_case_sons_of_conjuncts(sentence, matches):
+    for cur_match in matches:
+        new_son = sentence[cur_match.token("new_son")]
+        gov = cur_match.token("gov")
+        dep = sentence[cur_match.token("dep")]
+
+        rel = list(cur_match.edge(cur_match.token("new_son"), gov))[0]
+        new_son.add_edge(Label(rel), dep)
+        
+
+# NOTE -  we propagate only subj (for now) as this is what SC stated:
 #     cdm july 2010: This bit of code would copy a dobj from the first
 #     clause to a later conjoined clause if it didn't
 #     contain its own dobj or prepc. But this is too aggressive and wrong
@@ -289,43 +276,35 @@ def eud_heads_of_conjuncts(sentence):
 #     (including passivized cases) and so I think we have to not have this
 #     done always, and see no good "sometimes" heuristic.
 #     IF WE WERE TO REINSTATE, SHOULD ALSO NOT ADD OBJ IF THERE IS A ccomp (SBAR).
-def eud_subj_of_conjoined_verbs(sentence):
-    restriction = Restriction(name="gov", nested=[[
-        Restriction(name="conj", gov="conj", no_sons_of=".subj", xpos="(VB|JJ)"),
-        Restriction(name="subj", gov=".subj")
-    ]])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        subj, _, subj_rel = name_space['subj']
-        conj, _, _ = name_space['conj']
-        
-        if subj_rel.endswith("subjpass") and conj.get_conllu_field('xpos') in ["VB", "VBZ", "VBP", "JJ"]:
-            subj_rel = subj_rel[:-4]
-        elif subj_rel.endswith("subj") and "auxpass" in [relation for (child, relation) in conj.get_children_with_rels()]:
-            subj_rel += "pass"
-        
-        subj.add_edge(subj_rel, conj)
-
-
-def xcomp_propagation_per_type(sentence, restriction, is_extra=False):
-    outer_restriction = Restriction(nested=[
-        [restriction, Restriction(name="new_subj", gov=".?obj")],
-        [restriction, Restriction(name="new_subj", gov="nsubj.*")]
-    ])
-    
-    ret = match(sentence.values(), [[outer_restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        new_subj, _, rel = name_space['new_subj']
-        dep, _, _ = name_space['dep']
-        new_subj.add_edge(add_eud_info("nsubj", "xcomp(INF)") if not is_extra else
-                          add_extra_info("nsubj", "xcomp", dep_type="GERUND", prevs=rel), dep)
+eud_subj_of_conjoined_verbs_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="conj", spec=[Field(field=FieldNames.TAG, value=adj_pos + verb_pos)],
+              outgoing_edges=[HasNoLabel(subj_str) for subj_str in subj_options]),  # TODO - alternatively use regex
+        Token(id="subj"),
+        Token(id="auxpass", optional=True),
+    ],
+    edges=[
+        Edge(child="subj", parent="gov", label=[HasLabelFromList(subj_options)]),
+        Edge(child="conj", parent="gov", label=[HasLabelFromList(["conj"])]),
+        Edge(child="auxpass", parent="conj", label=[HasLabelFromList(["auxpass"])]),  # TODO - UDv1 = pass
+    ],
+)
+
+
+def eud_subj_of_conjoined_verbs(sentence, matches):
+    for cur_match in matches:
+        gov = cur_match.token("gov")
+        conj = cur_match.token("conj")
+        subj = cur_match.token("subj")
+        auxpass = cur_match.token("auxpass")
+        for rel in cur_match.edge(subj, gov):
+            subj_rel = rel
+            if subj_rel.endswith("subjpass") and auxpass == -1:
+                subj_rel = subj_rel[:-4]
+            elif subj_rel.endswith("subj") and auxpass != -1:
+                subj_rel += "pass"
+            sentence[subj].add_edge(Label(subj_rel), sentence[conj])
 
 
 # Add extra nsubj dependencies when collapsing basic dependencies.
@@ -344,696 +323,704 @@ def xcomp_propagation_per_type(sentence, restriction, is_extra=False):
 #   There is no nsubj of asking, but the dobj, SEC, is the extra nsubj of require.
 #   Similarly, "The law tells them when to do so"
 #   Instead of nsubj(do, law) we want nsubj(do, them)
-def eud_xcomp_propagation(sentence):
-    to_xcomp_rest = Restriction(name="dep", gov="xcomp", no_sons_of="^(nsubj.*|aux|mark)$", xpos="^(TO)$")
-    basic_xcomp_rest = Restriction(name="dep", gov="xcomp", no_sons_of="nsubj.*", xpos="(?!(^(TO)$)).", nested=[[
-        Restriction(gov="^(aux|mark)$", xpos="(^(TO)$)")
-    ]])
-
-    for xcomp_restriction in [to_xcomp_rest, basic_xcomp_rest]:
-        xcomp_propagation_per_type(sentence, xcomp_restriction)
-
-
-def extra_xcomp_propagation_no_to(sentence):
-    xcomp_no_to_rest = Restriction(name="dep", gov="xcomp", no_sons_of="^(aux|mark|nsubj.*)$", xpos="(VB.?)")
-    
-    xcomp_propagation_per_type(sentence, xcomp_no_to_rest, True)
-
-
-def advcl_or_dep_propagation_per_type(sentence, restriction, type_, unc, iids):
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        dep, _, _ = name_space['dep']
-        if 'new_subj' in name_space:
-            new_subj_str = 'new_subj'
-            cur_iid = None
-            # in case the father has more than one subject, we dont want to take care now, but later.
-            if len([rel for child, rel in name_space['father'][0].get_children_with_rels() if re.match(".subj.*", rel)]) > 1:
+extra_xcomp_propagation_no_to_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="new_subj"),
+        Token(id="xcomp", spec=[Field(field=FieldNames.TAG, value=verb_pos + ["TO", "IN"])],
+              outgoing_edges=[HasNoLabel(subj) for subj in subj_options] + [HasNoLabel("mark"), HasNoLabel("aux")]),
+    ],
+    edges=[
+        Edge(child="xcomp", parent="gov", label=[HasLabelFromList(["xcomp"])]),
+        Edge(child="new_subj", parent="gov", label=[HasLabelFromList(subj_options + obj_options)]),
+    ],
+)
+
+extra_xcomp_propagation_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="new_subj"),
+        Token(id="xcomp", spec=[Field(field=FieldNames.TAG, value=verb_pos)],
+              outgoing_edges=[HasNoLabel(subj) for subj in subj_options]),
+        Token(id="to_marker", spec=[Field(field=FieldNames.TAG, value=["TO", "IN"])]),
+    ],
+    edges=[
+        Edge(child="xcomp", parent="gov", label=[HasLabelFromList(["xcomp"])]),
+        Edge(child="to_marker", parent="xcomp", label=[HasLabelFromList(["mark", "aux"])]),
+        Edge(child="new_subj", parent="gov", label=[HasLabelFromList(subj_options + obj_options)]),
+    ],
+)
+
+
+def xcomp_propagation_per_type(sentence, matches):
+    labels_dict = defaultdict(list)
+    for cur_match in matches:
+        new_subj = cur_match.token("new_subj")
+        xcomp = cur_match.token("xcomp")
+        to_marker = cur_match.token("to_marker")
+        gov = cur_match.token("gov")
+
+        labels = cur_match.edge(new_subj, gov)
+        labels_dict[(gov, xcomp, to_marker)].extend([(new_subj, label) for label in labels])
+
+    for (gov, xcomp, to_marker), labels in labels_dict.items():
+        obj_found = any("obj" in label for _, label in labels)
+        for new_subj, label in labels:
+            if obj_found and "subj" in label:
                 continue
-        else:
-            if dep not in iids:
-                iids[dep] = 0 if len(iids.values()) == 0 else (max(iids.values()) + 1)
-            cur_iid = iids[dep]
-            new_subj_str = 'new_subj_opt'
+            is_xcomp_basic = (to_marker != -1) or (sentence[xcomp].get_conllu_field("xpos") in ["TO", "IN"])
+            if is_xcomp_basic:
+                sentence[new_subj].add_edge(Label("nsubj", eud=get_eud_info("xcomp(INF)")), sentence[xcomp])
+            elif not is_xcomp_basic:
+                sentence[new_subj].add_edge(Label("nsubj", src="xcomp", src_type="GERUND"), sentence[xcomp])
+
+
+def eud_xcomp_propagation(sentence, matches):
+    xcomp_propagation_per_type(sentence, matches)
+
+
+def extra_xcomp_propagation_no_to(sentence, matches):
+    xcomp_propagation_per_type(sentence, matches)
+
+
+# propagate subject and(/or) object as (possible) subject(s) for the son of the `advcl` relation if it has no subject of his own
+extra_advcl_propagation_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="dep", outgoing_edges=[HasNoLabel(value=subj_cur) for subj_cur in subj_options]),
+        Token(id="subj"),
+        Token(id="obj", optional=True),
+        # we allow only a specified list of markers (or no marker at all) - according to research
+        Token(id="mark", optional=True, spec=[Field(field=FieldNames.WORD, value=advcl_non_legit_markers, in_sequence=False)]),
+    ],
+    edges=[
+        Edge(child="subj", parent="gov", label=[HasLabelFromList(subj_options)]),
+        Edge(child="obj", parent="gov", label=[HasLabelFromList(obj_options)]),
+        Edge(child="dep", parent="gov", label=[HasLabelFromList(["advcl"])]),
+        Edge(child="mark", parent="dep", label=[HasLabelFromList(["mark", "aux"])]),
+    ],
+)
+
+
+# propagate subject and(/or) object as (possible) subject(s) for the son of the `dep` relation if it has no subject of his own
+extra_dep_propagation_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="dep", outgoing_edges=[HasNoLabel(value=subj_cur) for subj_cur in subj_options]),
+        Token(id="subj"),
+        Token(id="obj", optional=True),
+        Token(id="mark", optional=True),
+    ],
+    edges=[
+        Edge(child="subj", parent="gov", label=[HasLabelFromList(subj_options)]),
+        Edge(child="obj", parent="gov", label=[HasLabelFromList(obj_options)]),
+        Edge(child="dep", parent="gov", label=[HasLabelFromList(["dep"])]),
+        Edge(child="mark", parent="dep", label=[HasLabelFromList(["mark", "aux"])]),
+    ],
+)
+
+
+def advcl_or_dep_propagation_per_type(sentence, matches, type_, unc):
+    global g_iids
+    for cur_match in matches:
+        dep = sentence[cur_match.token("dep")]
+        subj = sentence[cur_match.token("subj")]
+        obj = cur_match.token("obj")
+        mark = cur_match.token("mark")
+
+        # extract the phrase of the marker, if there is one
+        phrase = sentence[mark].get_conllu_field("form") if mark != -1 else "NULL"
         
-        new_subj, _, rel = name_space[new_subj_str]
-        mark, _, _ = name_space['mark'] if 'mark' in name_space else (None, _, _)
-        phrase = mark.get_conllu_field("form") if mark else "NULL"
-        new_subj.add_edge(add_extra_info("nsubj", type_, phrase=phrase, prevs=rel, iid=cur_iid, uncertain=unc), dep)
-
-
-def extra_advcl_propagation(sentence, iids):
-    advcl_to_rest = Restriction(name="father", nested=[[
-        Restriction(name="dep", gov="advcl", no_sons_of=".subj.*", nested=[[
-            Restriction(name="mark", gov="^(aux|mark)$", form="(^(?i:to)$)")
-        ]]),
-        Restriction(name="new_subj", gov=".?obj")
-    ]])
-    
-    basic_advcl_rest = Restriction(name="father", no_sons_of=".?obj", nested=[[
-        Restriction(name="dep", gov="advcl", no_sons_of=".subj.*", nested=[[
-            Restriction(name="mark", gov="^(aux|mark)$", form="(?!(^(?i:as|so|when|if)$)).")
-        ]]),
-        Restriction(name="new_subj", gov="nsubj.*")
-    ]])
-    basic_advcl_rest_no_mark = Restriction(name="father", no_sons_of=".?obj", nested=[[
-        Restriction(name="dep", gov="advcl", no_sons_of="(.subj.*|aux|mark)"),
-        Restriction(name="new_subj", gov="nsubj.*")
-    ]])
-    
-    for advcl_restriction in [advcl_to_rest, basic_advcl_rest, basic_advcl_rest_no_mark]:
-        advcl_or_dep_propagation_per_type(sentence, advcl_restriction, "advcl", False, iids)
-
-
-def extra_advcl_ambiguous_propagation(sentence, iids):
-    ambiguous_advcl_rest = Restriction(name="father", nested=[[
-        Restriction(name="dep", gov="advcl", no_sons_of=".subj.*", nested=[[
-            Restriction(name="mark", gov="^(aux|mark)$", form="(?!(^(?i:as|so|when|if)$)).")
-        ]]),
-        Restriction(name="new_subj_opt", gov="(.?obj|nsubj.*)")
-    ]])
-    ambiguous_advcl_rest_no_mark = Restriction(name="father", nested=[[
-        Restriction(name="dep", gov="advcl", no_sons_of="(.subj.*|aux|mark)"),
-        Restriction(name="new_subj_opt", gov="(.?obj|nsubj.*)")
-    ]])
-    
-    for advcl_restriction in [ambiguous_advcl_rest, ambiguous_advcl_rest_no_mark]:
-        advcl_or_dep_propagation_per_type(sentence, advcl_restriction, "advcl", False, iids)
-
-
-def extra_of_prep_alteration(sentence):
-    of_prep_rest = Restriction(name="root", nested=[[
-        Restriction(name="father", xpos="NN.*", nested=[[
-            Restriction(name="nmod", xpos="NN.*", gov="nmod", nested=[[
-                Restriction(gov="case", form="(?i:of)")
-            ]])
-        ]])
-    ]])
-    
-    ret = match(sentence.values(), [[of_prep_rest]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        father, _, _ = name_space['father']
-        nmod, _, rel = name_space['nmod']
-        nmod.add_edge(add_extra_info("compound", "nmod", phrase="of", prevs=rel), father)
-
-
-def extra_compound_propagation(sentence):
-    compound_rest = Restriction(name="father", nested=[[
-        Restriction(name="middle_man", gov="(.obj|.subj.*)", xpos="NN.*", nested=[[
-            Restriction(name="compound", gov="compound", xpos="NN.*")
-        ]])
-    ]])
-    
-    ret = match(sentence.values(), [[compound_rest]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        father, _, _ = name_space['father']
-        _, _, rel = name_space['middle_man']
-        compound, _, _ = name_space['compound']
-        pure_rel = split_by_at(rel)[0]
-        if any([re.match("(.obj|.subj.*)", rel) for head, rel in compound.get_new_relations()]):
-            continue
-        compound.add_edge(add_extra_info(pure_rel, "compound", dep_type="NULL", uncertain=True, prevs=rel), father)
-
-
-def extra_amod_propagation(sentence):
-    amod_rest = Restriction(name="father", nested=[[
-        Restriction(name="amod", gov="amod", no_sons_of="nsubj.*")
-    ]])
-    
-    ret = match(sentence.values(), [[amod_rest]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        father, _, _ = name_space['father']
-        amod, _, rel = name_space['amod']
-        father.add_edge(add_extra_info("nsubj", "amod", prevs=rel), amod)
-
-
-def extra_acl_propagation(sentence):
-    restriction = Full(
-        tokens=[
-            Token(id="verb", spec=[Field(FieldNames.TAG, ["/(VB.?)/"])]),
-            Token(id="subj"),
-            Token(id="proxy"),
-            Token(id="acl", outgoing_edges=[HasNoLabel("/.subj.*/")]),
-            Token(id="to", spec=[Field(FieldNames.TAG, ["TO"])])],
-        edges=[
-            Edge(child="subj", parent="verb", label=[HasLabelFromList(["/.subj.*/"])]),
-            Edge(child="proxy", parent="verb", label=[HasLabelFromList(["/.*/"])]),
-            Edge(child="acl", parent="proxy", label=[HasLabelFromList(["/acl(?!:relcl)/"])]),
-            Edge(child="to", parent="acl", label=[HasLabelFromList(["mark"])])
-        ],
-    )
-
-    # part1: take care of all acl's that are marked by 'to'
-    acl_to_rest = Restriction(name="root_or_so", nested=[[
-        Restriction(name="verb", xpos="(VB.?)", nested=[[
-            Restriction(name="subj", gov=".subj.*"),
-            Restriction(name="father", diff="subj", nested=[[
-                Restriction(name="acl", gov="acl(?!:relcl)", no_sons_of="nsubj.*", nested=[[
-                    Restriction(name="to", gov="mark", xpos="TO")
-                ]])
-            ]])
-        ]])
-    ]])
-    
-    ret = match(sentence.values(), [[acl_to_rest]])
-    if ret:
-        for name_space in ret:
-            subj, _, _ = name_space['subj']
-            acl, _, rel = name_space['acl']
-            subj.add_edge(add_extra_info("nsubj", "acl", dep_type="NULL", phrase='to', prevs=rel), acl)
-    
-    # part2: take care of all acl's that are not marked by 'to'
-    acl_rest = Restriction(name="father", nested=[[
-        Restriction(name="acl", gov="acl(?!:relcl)", no_sons_of="(nsubj.*|mark)")  # TODO: validate that mark can be here only 'to'.
-    ]])
-    
-    ret = match(sentence.values(), [[acl_rest]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        father, _, _ = name_space['father']
-        acl, _, rel = name_space['acl']
-        father.add_edge(add_extra_info("nsubj", "acl", dep_type="NULL", phrase="REDUCED", prevs=rel), acl)
-
-
-def extra_dep_propagation(sentence, iids):
-    dep_rest = Restriction(name="father", no_sons_of = ".?obj", nested=[[
-        Restriction(name="dep", gov="dep", no_sons_of=".subj.*"),
-        Restriction(name="new_subj", gov="(nsubj.*)")
-    ]])
-
-    ambiguous_dea_rest = Restriction(name="father", nested=[[
-        Restriction(name="dep", gov="dep", no_sons_of=".subj.*"),
-        Restriction(name="new_subj_opt", gov="(.?obj|nsubj.*)")
-    ]])
-    
-    for rest in [dep_rest, ambiguous_dea_rest]:
-        advcl_or_dep_propagation_per_type(sentence, rest, "dep", True, iids)
-
-
-# TODO - unify with other nmods props
-def extra_subj_obj_nmod_propagation_of_nmods(sentence):
-    rest = Restriction(name="receiver", nested=[[
-        Restriction(name="mediator", gov="(dobj|.subj.*|nmod)", nested=[[
-            Restriction(name="nmod", gov="nmod", nested=[
-                [Restriction(name="like", gov="case", form="like")],
-                [Restriction(name="such_as", gov="case", form="such", nested= [[
-                    Restriction(gov="mwe", form="as")
-                ]])],
-                [Restriction(name="including", gov="case", form="including")]
-            ])
-        ]])
-    ]])
-
-    ret = match(sentence.values(), [[rest]])
-    if not ret:
-        return
-
-    for name_space in ret:
-        nmod, _, nmod_rel = name_space['nmod']
-        receiver, _, _ = name_space['receiver']
-        mediator_rel = name_space['mediator'][2]
-        
-        phrase = [prep for prep in ["like", "such_as", "including"] if prep in name_space][0]
-        nmod.add_edge(add_extra_info(split_by_at(mediator_rel)[0], "nmod", phrase=phrase, prevs=mediator_rel), receiver)
-
-
-def conj_propagation_of_nmods_per_type(sentence, rest, dont_check_precedence=False):
-    ret = match(sentence.values(), [[rest]])
-    if not ret:
-        return
-
-    # TODO: move this code to a global func, and share code with other cc-assignments
-    cc_assignments = dict()
-    for token in sentence.values():
-        ccs = []
-        for child, rel in sorted(token.get_children_with_rels(), reverse=True):
-            if 'cc' == rel:
-                ccs.append(child.get_conllu_field("form"))
-        i = 0
-        for child, rel in sorted(token.get_children_with_rels(), reverse=True):
-            if rel.startswith('conj'):
-                if len(ccs) == 0:
-                    cc_assignments[child] = 'and'
-                else:
-                    cc_assignments[child] = ccs[i if i < len(ccs) else -1]
-                i += 1
-        
-    for name_space in ret:
-        nmod, _, nmod_rel = name_space['nmod']
-        receiver, _, _ = name_space['receiver']
-        conj, _, _ = name_space['conj'] if 'conj' in name_space else name_space['receiver']
+        # decide if we need ALTERNATIVES for the propagation. That is, if we have both subject and object to propagate we give them ALT=some_id,
+        # unless marker was `to`, in which we propagate only the object as we do in the xcomp case.
+        if obj == -1 or phrase == "to":  # TODO - english = to
+            cur_iid = None
+        else:
+            # get the id for this token or update the global ids for this new one.
+            if dep not in g_iids:
+                g_iids[dep] = 0 if len(g_iids.values()) == 0 else (max(g_iids.values()) + 1)
+            cur_iid = g_iids[dep]
+
+        # decide wether to propagte both the subject or object or both according to the criteria mentioned before
+        if phrase != "to" or obj == -1:
+            subj.add_edge(Label("nsubj", src=type_, phrase=phrase, iid=cur_iid, uncertain=unc), dep)
+        if obj != -1:
+            sentence[obj].add_edge(Label("nsubj", src=type_, phrase=phrase, iid=cur_iid, uncertain=unc), dep)
+
+
+def extra_advcl_propagation(sentence, matches):
+    advcl_or_dep_propagation_per_type(sentence, matches, "advcl", False)
+
+
+def extra_dep_propagation(sentence, matches):
+    advcl_or_dep_propagation_per_type(sentence, matches, "dep", True)
+
+
+# here we add a compound relation for each nmod:of relation between two nouns
+extra_of_prep_alteration_constraint = Full(
+    tokens=[
+        Token(id="nmod_of", spec=[Field(field=FieldNames.TAG, value=noun_pos)]),
+        Token(id="gov", spec=[Field(field=FieldNames.TAG, value=noun_pos)]),
+        Token(id="case", spec=[Field(field=FieldNames.WORD, value=["of"])]),  # TODO - english = of
+    ],
+    edges=[
+        Edge(child="case", parent="nmod_of", label=[HasLabelFromList(["case"])]),
+        Edge(child="nmod_of", parent="gov", label=[HasLabelFromList(["nmod"])]),  # TODO - UDv1 = nmod
+    ],
+)
+
+
+def extra_of_prep_alteration(sentence, matches):
+    for cur_match in matches:
+        gov = cur_match.token("gov")
+        nmod_of = cur_match.token("nmod_of")
+        sentence[nmod_of].add_edge(Label("compound", src="nmod", phrase="of"), sentence[gov])  # TODO - english = of
+
+
+# here we propagate subjects and objects from the compounds parent down to the compound, if they both are nouns
+extra_compound_propagation_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="middle_man", spec=[Field(field=FieldNames.TAG, value=noun_pos)]),
+        Token(id="compound", spec=[Field(field=FieldNames.TAG, value=noun_pos)],
+              # TODO: this validates that the compound does not have the propagated relation,
+              #  and since we dont know in advance which relation exactly is going to be propagated,
+              #  we add a rigorous HasNoLabel constraint for each
+              incoming_edges=[HasNoLabel(arg) for arg in subj_options + obj_options]),
+    ],
+    edges=[
+        Edge(child="middle_man", parent="gov", label=[HasLabelFromList(subj_options + obj_options)]),
+        Edge(child="compound", parent="middle_man", label=[HasLabelFromList(["compound"])]),  # TODO - UDv1 = nmod
+    ],
+)
+
+
+def extra_compound_propagation(sentence, matches):
+    for cur_match in matches:
+        gov = cur_match.token("gov")
+        compound = cur_match.token("compound")
+        middle_man = cur_match.token("middle_man")
+        for rel in cur_match.edge(middle_man, gov):
+            sentence[compound].add_edge(Label(rel, src="compound", src_type="NULL", uncertain=True), sentence[gov])
+
+
+# here we add a subject relation for each amod relation (but in the opposite direction)
+extra_amod_propagation_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="amod", outgoing_edges=[HasNoLabel(arg) for arg in subj_options]),
+    ],
+    edges=[
+        Edge(child="amod", parent="gov", label=[HasLabelFromList(["amod"])]),
+    ],
+)
+
+
+def extra_amod_propagation(sentence, matches):
+    for cur_match in matches:
+        gov = cur_match.token("gov")
+        amod = cur_match.token("amod")
+        sentence[gov].add_edge(Label("nsubj", src="amod"), sentence[amod])
+
+
+# acl part1: take care of all acl's (not acl:relcl) that are marked by 'to'
+# this case is pretty specific. it means that if we have a verb with subject and object and the object has an acl relation,
+# the new subject connection would be between the verb's subject and the acl subordinate, and not from the acl head relation (the object).
+extra_acl_to_propagation_constraint = Full(
+    tokens=[
+        Token(id="verb", spec=[Field(FieldNames.TAG, verb_pos)]),
+        Token(id="subj", spec=[Field(FieldNames.TAG, pron_pos + noun_pos)]),
+        # we dont want the object/proxy to be the same as `subj` itself
+        Token(id="proxy", spec=[Field(FieldNames.TAG, pron_pos + noun_pos)], incoming_edges=[HasNoLabel(subj_cur) for subj_cur in subj_options]),
+        Token(id="acl", spec=[Field(FieldNames.TAG, verb_pos)], outgoing_edges=[HasNoLabel(subj_cur) for subj_cur in subj_options]),
+        Token(id="to", spec=[Field(FieldNames.TAG, ["TO"])])],
+    edges=[
+        Edge(child="subj", parent="verb", label=[HasLabelFromList(subj_options)]),
+        Edge(child="proxy", parent="verb", label=[HasLabelFromList(obj_options)]),  # TODO - originally it was `.*` regex, what else could be here?
+        Edge(child="acl", parent="proxy", label=[HasLabelFromList(["acl"])]),
+        Edge(child="to", parent="acl", label=[HasLabelFromList(["mark"])])
+    ],
+)
+
+
+def extra_acl_to_propagation(sentence, matches):
+    for cur_match in matches:
+        subj = sentence[cur_match.token("subj")]
+        acl = sentence[cur_match.token("acl")]
+        to = sentence[cur_match.token("to")]
+        subj.add_edge(Label("nsubj", src="acl", src_type="NULL", phrase=to.get_conllu_field("form")), acl)
+
+
+# acl part2: take care of all acl's (not acl:relcl) that are not marked by 'to'
+extra_acl_propagation_constraint = Full(
+    tokens=[
+        Token(id="father", spec=[Field(FieldNames.TAG, pron_pos + noun_pos)],
+              incoming_edges=[HasNoLabel(subj_cur) for subj_cur in subj_options + ["mark"]]),
+        Token(id="acl", spec=[Field(FieldNames.TAG, verb_pos)], outgoing_edges=[HasNoLabel(subj_cur) for subj_cur in subj_options]),
+    ],
+    edges=[
+        Edge(child="acl", parent="father", label=[HasLabelFromList(["acl"])]),
+    ],
+)
+
+
+def extra_acl_propagation(sentence, matches):
+    for cur_match in matches:
+        father = sentence[cur_match.token("father")]
+        acl = sentence[cur_match.token("acl")]
+        father.add_edge(Label("nsubj", src="acl", src_type="NULL", phrase="REDUCED"), acl)
+
+
+extra_subj_obj_nmod_propagation_of_nmods_constraint = Full(
+    tokens=[
+        Token(id="receiver"),
+        Token(id="mediator", spec=[Field(FieldNames.TAG, noun_pos + pron_pos)]),
+        Token(id="modifier"),
+        Token(id="specifier", spec=[Field(FieldNames.WORD, ["like", "such", "including"])]),  # TODO: english = like/such/including
+        # this is needed for the `such as` case, as it is a multi word preposition
+        Token(id="as", optional=True, spec=[Field(FieldNames.WORD, ["as"])]),  # TODO: english = as
+        Token(id="case", optional=True, spec=[Field(FieldNames.TAG, ["IN", "TO"])]),
+    ],
+    edges=[
+        Edge(child="mediator", parent="receiver", label=[HasLabelFromList(subj_options + obj_options + ["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="modifier", parent="mediator", label=[HasLabelFromList(["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="specifier", parent="modifier", label=[HasLabelFromList(["case"])]),
+        Edge(child="as", parent="specifier", label=[HasLabelFromList(["mwe"])]),  # TODO: UDv1 = mwe
+        Edge(child="case", parent="mediator", label=[HasLabelFromList(["case"])]),
+    ],
+)
+
+
+def extra_subj_obj_nmod_propagation_of_nmods(sentence, matches):
+    for cur_match in matches:
+        modifier = cur_match.token("modifier")
+        receiver = cur_match.token("receiver")
+        mediator = cur_match.token("mediator")
+        specifier = cur_match.token("specifier")
+        case = cur_match.token("case")
         
-        if '.' not in str(receiver.get_conllu_field("id")) and \
-                (dont_check_precedence or nmod.get_conllu_field("id") > receiver.get_conllu_field("id")):
-            nmod.add_edge(add_extra_info(split_by_at(nmod_rel)[0], "conj", uncertain=True, phrase=cc_assignments[conj], prevs=nmod_rel), receiver)
-
-
-def extra_conj_propagation_of_nmods(sentence):
-    son_rest = Restriction(name="receiver", no_sons_of="nmod", nested=[[
-        Restriction(name="conj", gov="conj", nested=[[
-            Restriction(name="nmod", gov="nmod(?!(.*@|:poss.*))")
-        ]])
-    ]])
-
-    father_rest = Restriction(nested=[[
-        Restriction(name="receiver", gov="conj"),  # TODO: validate no_sons_of="nmod" isn't needed.
-        Restriction(name="nmod", gov="nmod(?!(.*@|:poss.*))")
-    ]])
-    
-    for conj_restriction in [son_rest, father_rest]:
-        conj_propagation_of_nmods_per_type(sentence, conj_restriction)
-
-
-def extra_conj_propagation_of_poss(sentence):
-    poss_rest = Restriction(nested=[[
-        Restriction(name="receiver", no_sons_of="(nmod:poss.*|det)", gov="conj", xpos="(?!(PRP|NNP.?|WP))"),
-        Restriction(name="nmod", gov="nmod:poss(?!.*@)")
-    ]])
-    
-    conj_propagation_of_nmods_per_type(sentence, poss_rest, True)
-
-
-# phenomena: indexicals
-def extra_advmod_propagation(sentence):
-    advmod_rest = Restriction(name="gov", nested=[[
-        Restriction(name="middle_man", gov="(nmod.*)", nested=[[
-            Restriction(name="advmod", gov="advmod", form=advmod_list),
-            Restriction(name="case", gov="case")
-        ]])
-    ]])
-    ret = match(sentence.values(), [[advmod_rest]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        advmod, _, advmod_rel = name_space['advmod']
-        _, _, middle_man_rel = name_space['middle_man']
-        gov, _, _ = name_space['gov']
-        case, _, _ = name_space['case']
+        # build the phrase according to the specific specifier
+        phrase = sentence[specifier].get_conllu_field("form")
+        if phrase == "such":
+            as_ = cur_match.token("as")
+            # if no `as` case, then this is not really `such as`
+            if as_ == -1:
+                return
+            phrase = phrase + "_" + sentence[as_].get_conllu_field("form")
         
-        if gov not in advmod.get_parents():
-            advmod.add_edge(add_extra_info(split_by_at(advmod_rel)[0], "nmod", dep_type="INDEXICAL", phrase=case.get_conllu_field("form"), uncertain=True, prevs=middle_man_rel), gov)
-
+        # we loop just in case there are more than one of object/subject/modifier relations between the receiver and mediator
+        for label in cur_match.edge(mediator, receiver):
+            sentence[modifier].add_edge(Label(label, src="nmod", phrase=phrase), sentence[receiver])
+            # also propagate the attached case in case of modifier relation between the receiver and mediator
+            if label == "nmod" and case != -1:  # TODO: UDv1 = nmod
+                sentence[case].add_edge(
+                    Label("case", eud=sentence[case].get_conllu_field("form").lower(), src="nmod", phrase=phrase), sentence[modifier])
+
+
+def conj_propagation_of_nmods_per_type(sentence, matches, specific_nmod_rel):
+    for cur_match in matches:
+        nmod = sentence[cur_match.token("nmod")]
+        receiver = sentence[cur_match.token("receiver")]
+        conj = cur_match.token("conj")
+        case = cur_match.token("case")
+
+        # this prevents propagating modifiers to added nodes
+        if receiver.get_conllu_field("id").minor:
+            continue
 
+        conj_per_type = sentence[conj] if conj != -1 else receiver
+        nmod.add_edge(
+            Label(specific_nmod_rel, eud=None if case == -1 else sentence[case].get_conllu_field("form").lower(),
+                  src="conj", uncertain=True, phrase=g_cc_assignments[conj_per_type][1] if conj_per_type in g_cc_assignments else None), receiver)
+
+
+extra_conj_propagation_of_nmods_backwards_constraint = Full(
+    tokens=[
+        Token(id="receiver", outgoing_edges=[HasNoLabel("nmod")]),  # TODO: UDv1 = nmod
+        Token(id="conj"),
+        Token(id="nmod"),
+        Token(id="case")
+    ],
+    edges=[
+        Edge(child="conj", parent="receiver", label=[HasLabelFromList(["conj"])]),
+        Edge(child="nmod", parent="conj", label=[HasLabelFromList(["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="case", parent="nmod", label=[HasLabelFromList(["case"])])
+    ],
+)
+
+
+def extra_conj_propagation_of_nmods_backwards(sentence, matches):
+    conj_propagation_of_nmods_per_type(sentence, matches, "nmod")# TODO: UDv1 = nmod
+
+
+extra_conj_propagation_of_nmods_forward_constraint = Full(
+    tokens=[
+        Token(id="father"),
+        # TODO: validate if HasNoLabel("nmod") is really needed.
+        Token(id="receiver", outgoing_edges=[HasNoLabel("nmod")]),  # TODO: UDv1 = nmod
+        Token(id="nmod"),
+        Token(id="case")
+    ],
+    edges=[
+        Edge(child="receiver", parent="father", label=[HasLabelFromList(["conj"])]),
+        Edge(child="nmod", parent="father", label=[HasLabelFromList(["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="case", parent="nmod", label=[HasLabelFromList(["case"])])
+    ],
+    distances=[
+        # this will prevent the propagation to create a backward modifier relation
+        UptoDistance("receiver", "nmod", inf)
+    ]
+)
+
+
+def extra_conj_propagation_of_nmods_forward(sentence, matches):
+    conj_propagation_of_nmods_per_type(sentence, matches, "nmod")# TODO: UDv1 = nmod
+
+
+
+extra_conj_propagation_of_poss_constraint = Full(
+    tokens=[
+        Token(id="father"),
+        # we need the spec, to prevent propagation of possessive modifiers to pronouns ("my her"), and to proper nouns ("his U.S.A"),
+        # and the `det` restriction to prevent propagation of possessive modifiers to definite phrases ("my the man")
+        Token(id="receiver", spec=[Field(field=FieldNames.TAG, value=pron_pos + ["NNP", "NNPS"], in_sequence=False)],
+              outgoing_edges=[HasNoLabel("nmod:poss"), HasNoLabel("det")]),  # TODO: UDv1 = nmod
+        Token(id="nmod"),
+    ],
+    edges=[
+        Edge(child="receiver", parent="father", label=[HasLabelFromList(["conj"])]),
+        Edge(child="nmod", parent="father", label=[HasLabelFromList(["nmod:poss"])]),  # TODO: UDv1 = nmod
+    ],
+)
+
+
+def extra_conj_propagation_of_poss(sentence, matches):
+    conj_propagation_of_nmods_per_type(sentence, matches, "nmod:poss")# TODO: UDv1 = nmod
+
+
+# Here we connect directly the advmod to a predicate that is mediated by an nmod
+# We do this for the set of advmods the corresponds to the phenomenon known as indexicals
+extra_advmod_propagation_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="middle_man"),
+        Token(id="advmod", spec=[Field(field=FieldNames.WORD, value=advmod_list)]),
+        Token(id="case"),
+    ],
+    edges=[
+        Edge(child="middle_man", parent="gov", label=[HasLabelFromList(["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="advmod", parent="middle_man", label=[HasLabelFromList(["advmod"])]),
+        Edge(child="case", parent="middle_man", label=[HasLabelFromList(["case"])]),
+    ],
+)
+
+
+def extra_advmod_propagation(sentence, matches):
+    for cur_match in matches:
+        gov = cur_match.token("gov")
+        case = cur_match.token("case")
+        advmod = cur_match.token("advmod")
+        sentence[advmod].add_edge(Label("advmod", src="nmod", src_type="INDEXICAL", phrase=sentence[case].get_conllu_field("form"), uncertain=True), sentence[gov])
+
+
+# here we connect the nmod to a predicate which is mediated by an advmod,
+# and forming a multi-word preposition from the combination of the advmod and current preposition
 # "I went back to prison"
-def extra_nmod_advmod_reconstruction(sentence):
-    # the reason for the form restriction: we dont want to catch "all in all"
-    nmod_advmod_rest = Restriction(name="gov", nested=[[
-        Restriction(name="advmod", gov="advmod", form="(?!(^(?i:all)$))", nested=[[
-            Restriction(name="nmod", gov="nmod", nested=[[
-                Restriction(name="case", gov="case")
-            ]])
-        ]])
-    ]])
-    ret = match(sentence.values(), [[nmod_advmod_rest]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        advmod, _, advmod_rel = name_space['advmod']
-        nmod, _, nmod_rel = name_space['nmod']
-        case, _, case_rel = name_space['case']
-        gov, _, _ = name_space['gov']
-        
-        # we dont want to catch "as much as" or any "as ADVMOD as-NMOD"
-        if ("as", "advmod") in [(child.get_conllu_field("form").lower(), rel) for child, rel in advmod.get_children_with_rels()]:
-            continue
-        
+extra_nmod_advmod_reconstruction_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="nmod"),
+        # reason for WORD constraint: we dont want to catch the phrase "all in all"
+        # reason for outgoing constraint: we dont want to catch phrases like "as much as" (implemented a bit rigorously)
+        Token(id="advmod", spec=[Field(field=FieldNames.WORD, value=["all"], in_sequence=False)],
+              outgoing_edges=[HasNoLabel("advmod")]),
+        Token(id="case"),
+    ],
+    edges=[
+        Edge(child="advmod", parent="gov", label=[HasLabelFromList(["advmod"])]),
+        Edge(child="nmod", parent="advmod", label=[HasLabelFromList(["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="case", parent="nmod", label=[HasLabelFromList(["case"])]),
+    ],
+    concats=[
+        TokenPair(two_word_preps_regular, "advmod", "case", in_set=False),
+        TokenPair(two_word_preps_complex, "advmod", "case", in_set=False)
+    ]
+)
+
+
+def extra_nmod_advmod_reconstruction(sentence, matches):
+    for cur_match in matches:
+        gov = sentence[cur_match.token("gov")]
+        case = sentence[cur_match.token("case")]
+        advmod = sentence[cur_match.token("advmod")]
+        nmod = sentence[cur_match.token("nmod")]
+
+        # validate that nmod and fov are not connected already
         if gov in nmod.get_parents():
             continue
-        
+
+        # here we split the rewrite step to two behviors, depending on the advmod+preposition concatination,
+        # if it's not part of nmod_advmod_complex: we remove the advmod from the governor
+        # and connect it as a multi word case to the nmod, and downgrade the original case to be its mwe son.
+        # in any way we connect the nmod to the governor
         mwe = advmod.get_conllu_field("form").lower() + "_" + case.get_conllu_field("form").lower()
         if mwe in nmod_advmod_complex:
-            nmod.add_edge(add_extra_info(add_eud_info(split_by_at(nmod_rel)[0], case.get_conllu_field("form").lower()), "advmod_prep"), gov)
-        else:
-            advmod.replace_edge(advmod_rel, add_extra_info(split_by_at(case_rel)[0], "advmod_prep"), gov, nmod)
-            case.replace_edge(case_rel, add_extra_info("mwe", "advmod_prep"), nmod, advmod)
-            nmod.replace_edge(nmod_rel, add_extra_info(add_eud_info(split_by_at(nmod_rel)[0], mwe), "advmod_prep"), advmod, gov)
-
-
-def extra_appos_propagation(sentence):
-    appos_rest = Restriction(name="gov", nested=[[
-        Restriction(name="appos", gov="appos")
-    ]])
-    ret = match(sentence.values(), [[appos_rest]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        appos, _, _ = name_space['appos']
-        gov, _, _ = name_space['gov']
-        
-        for (gov_head, gov_in_rel) in gov.get_new_relations():
-            if (gov_head, gov_in_rel) not in appos.get_new_relations():
-                appos.add_edge(add_extra_info(split_by_at(gov_in_rel)[0], "appos", prevs=gov_in_rel), gov_head)
-        
-        for (gov_son, gov_out_rel) in gov.get_children_with_rels():
-            if re.match("(acl|amod)", gov_out_rel) and (gov_son, gov_out_rel) not in appos.get_children_with_rels():
-                gov_son.add_edge(add_extra_info(split_by_at(gov_out_rel)[0], "appos", prevs=gov_out_rel), appos)
-
-
-# find the closest cc to the conj with precedence for left hand ccs
-def attach_best_cc(conj, ccs, noun, verb):
-    closest_cc = None
-    closest_dist = None
-    for cur_cc in ccs:
-        cur_dist = conj.dist(cur_cc)
-        if (not closest_cc) or \
-                ((copysign(1, closest_dist) == copysign(1, cur_dist)) and (abs(cur_dist) < abs(closest_dist))) or \
-                ((copysign(1, closest_dist) != copysign(1, cur_dist)) and (copysign(1, cur_dist) == -1)):
-            closest_cc = cur_cc
-            closest_dist = conj.dist(closest_cc)
-    if closest_cc:
-        closest_cc.replace_edge("cc", "cc", noun, verb)
-
-# Purpose: TODO
-# Notes:
-#   1. formerly we did this as long as we find what to change, and each time change only one match, instead of fixing all matches found each time.
-#       As every change we did might effect what can be found next, and old relations that were matched might be out dated.
-#       But since this was a bad practice: (a) we dont use the matching properly, and (b) we use while true which might run forever,
-#       and (c) we didn't kept a record of any case that could be harmed.
-#       So we reverted it to regular behavior. When we do find such a case, we should record it and handle it more appropriately
-#   2. old_root's xpos restriction comes to make sure we catch only non verbal copulas to reconstruct
-#       (even though it should have been 'aux' instead of 'cop').
-#   3. we want to catch all instances at once (hence the use of 'all') of any possible (hence the use of 'opt') old_root's child.
-# class CopulaReconstruction:
-#     @staticmethod
-#     def get_restriction() -> Restriction:
-#         return Restriction(name="father", nested=[[
-#             Restriction(name="old_root", xpos="(?!(VB.?))", nested=[[
-#                 Restriction(name="cop", gov="cop"),
-#                 Restriction(opt=True, all=True, name='regular_children', xpos="?!(TO)",  # avoid catching to-mark
-#                             gov="discourse|punct|advcl|xcomp|ccomp|expl|parataxis|mark)"),
-#                 Restriction(opt=True, all=True, name='subjs', gov="(.subj.*)"),
-#                 # here catch to-mark or aux (hence VB), or advmod (hence W?RB) to transfer to the copula
-#                 Restriction(opt=True, all=True, name='to_cop', gov="(mark|aux.*|advmod)", xpos="(TO|VB|W?RB)"),
-#                 Restriction(opt=True, all=True, name='cases', gov="case"),
-#                 Restriction(opt=True, all=True, name='conjs', gov="conj", xpos="(VB.?)"),  # xpos rest -> transfer only conjoined verbs to the STATE
-#                 Restriction(opt=True, all=True, name='ccs', gov="cc")
-#             ]])
-#         ]])
-#
-#     @staticmethod
-#     def rewrite(hit_ns: Dict[str, Tuple[Token, Token, str]], sentence: List[Token] = None) -> None:
-#         cop, _, cop_rel = hit_ns['cop']
-#         old_root = hit_ns['old_root'][0]
-#
-#         # add STATE node or nominate the copula as new root if we shouldn't add new nodes
-#         if not g_remove_node_adding_conversions:
-#             new_id = cop.get_conllu_field('id') + 0.1
-#             new_root = cop.copy(new_id=new_id, form="STATE", lemma="_", upos="_", xpos="_", feats="_", head="_", deprel="_", deps=None)
-#             sentence[new_id] = new_root
-#             # 'cop' becomes 'ev' (for event/evidential) to the new root
-#             cop.replace_edge(cop_rel, "ev", old_root, new_root)
-#         else:
-#             new_root = cop
-#             new_root.remove_edge(cop_rel, old_root)
-#
-#         # transfer old-root's outgoing relation to new-root
-#         for head, rel in old_root.get_new_relations():
-#             old_root.remove_edge(rel, head)
-#             new_root.add_edge(rel, head)
-#
-#         # transfer all old-root's children that are to be transferred
-#         for child, _, rel in hit_ns['regular_children'] + hit_ns['subjs']:
-#             child.replace_edge(rel, rel, old_root, new_root)
-#         for cur_to_cop, _, rel in hit_ns['to_cop']:
-#             cur_to_cop.replace_edge(rel, rel, old_root, cop)
-#         for conj, _, rel in hit_ns['conjs']:
-#             conj.replace_edge(rel, rel, old_root, new_root)
-#             # find best 'cc' to attach the new root as compliance with the transferred 'conj'.
-#             attach_best_cc(conj, list(zip(*hit_ns['ccs']))[0], old_root, new_root)
-#
-#         # only if old_root is an adjective
-#         if re.match("JJ.?", old_root.get_conllu_field("xpos")):
-#             # update old-root's outgoing relation: for each subj add a 'amod' relation to the adjective.
-#             for subj in hit_ns['subjs']:
-#                 old_root.add_edge(add_extra_info("amod", "cop"), subj)
-#
-#         # connect the old_root as son of the new_root with the proper complement
-#         old_root.add_edge("xcomp" if 'cases' not in hit_ns else udv("nmod", "obl"), new_root)
-
-
-def extra_inner_weak_modifier_verb_reconstruction(sentence, cop_rest, evidential):
-    # NOTE: we do this as long as we find what to change, and each time change only one match, instead of fixing all matches found each time.
-    #   As every change we do might change what can be found next, and old relations that are matched might be out dated.
-    #   But this is bad practice. we dont use the matching properly, and we use while true which might run forever!
-    found = set()
-    while True:
-        ret = match(sentence.values(), [[cop_rest]])
-        if not ret:
-            return
-        cur_found = [
-            "".join([k + str(v[0].get_conllu_field("id")) + str(v[1].get_conllu_field("id") if v[1] else v[1]) + str(v[2]) for k, v in ns.items()])
-            for ns in ret]
-        if not any([ns not in found for ns in cur_found]):
-            return
-        
-        # As we might catch unwanted constructions (which the matcher couldnt handle), we added this while to find the first true match.
-        for i, name_space in enumerate(ret):
-            found.add(cur_found[i])
-            old_root, _, _ = name_space['old_root']
-            # the evidential should be the predecessor of the STATE as the cop is (even though he is also the old root of the construct).
-            predecessor, _, _ = name_space['cop'] if 'cop' in name_space else name_space['old_root']
-            
-            # The old_root's father cant be 'STATE' or connect via ev. as it means we were already handled.
-            #   The old_root's children cant be 'xcomp'(+'JJ') or 'ccomp' as they are handled separately.
-            if any((head.get_conllu_field("form") == "STATE") or (split_by_at(rel)[0] == 'ev') for head, rel in old_root.get_new_relations()):
-                old_root = None
-                predecessor = None
-                continue
-            else:
-                break
-        # this means we didnt find any good old_root
-        if not old_root:
-            return
-        
-        if not g_remove_node_adding_conversions:
-            new_id = predecessor.get_conllu_field('id') + 0.1
-            new_root = predecessor.copy(new_id=new_id, form="STATE", lemma="_", upos="_", xpos="_", feats="_", head="_", deprel="_", deps=None)
-            sentence[new_id] = new_root
+            nmod.add_edge(Label("nmod", eud=get_eud_info(case.get_conllu_field("form").lower()), src="advmod_prep"), gov)  # TODO: UDv1 = nmod
         else:
-            new_root = predecessor
-
-        # transfer old-root's outgoing relation to new-root
-        for head, rel in old_root.get_new_relations():
-            old_root.remove_edge(rel, head)
-            new_root.add_edge(rel, head)
-
-        # transfer all old-root's children that are to be transferred
-        ccs = [cc_child for cc_child, cc_rel in old_root.get_children_with_rels() if cc_rel == "cc"]
-        subjs = []
-        new_out_rel = "xcomp"
-        new_amod = old_root
-        for child, rel in old_root.get_children_with_rels():
-            if re.match("((.subj.*)|discourse|punct|advcl|xcomp|ccomp|expl|parataxis)", rel):
-                # transfer any of the following children
+            advmod.replace_edge(Label("advmod"), Label("case", src="advmod_prep"), gov, nmod)
+            case.replace_edge(Label("case"), Label("mwe", src="advmod_prep"), nmod, advmod)
+            nmod.replace_edge(Label("nmod"), Label("nmod", eud=get_eud_info(mwe), src="advmod_prep"), advmod, gov)  # TODO: UDv1 = nmod
+
+
+extra_appos_propagation_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="appos"),
+        Token(id="gov_parent", optional=True),
+        Token(id="gov_son", optional=True),
+    ],
+    edges=[
+        Edge(child="gov", parent="gov_parent", label=[HasLabelFromList(["/.*/"])]),
+        # This is a hand made list of legitimate sons to propagate - so it can be enlarged
+        Edge(child="gov_son", parent="gov", label=[HasLabelFromList(["acl", "amod", "case"])]),
+        Edge(child="appos", parent="gov", label=[HasLabelFromList(["appos"])]),
+    ],
+)
+
+
+def extra_appos_propagation(sentence, matches):
+    for cur_match in matches:
+        gov_parent = cur_match.token("gov_parent")
+        gov_son = cur_match.token("gov_son")
+        gov = cur_match.token("gov")
+        appos = cur_match.token("appos")
+        for label in cur_match.edge(gov, gov_parent):
+            sentence[appos].add_edge(Label(label, src="appos"), sentence[gov_parent])
+        for label in cur_match.edge(gov_son, gov):
+            sentence[gov_son].add_edge(Label(label, src="appos"), sentence[appos])
+
+
+def reattach_children_copula(old_root, new_root, cop):
+    subjs = []
+    new_out_rel = "xcomp"
+    for child, rels in old_root.get_children_with_rels():
+        for rel in rels:
+            # Note - transfer 'conj' only if it is a verb conjunction,
+            #   as we want it to be attached to the new (verb/state) root
+            if rel.base in subj_options + ["discourse", "punct", "advcl", "xcomp", "ccomp", "expl", "parataxis"] \
+                    or (rel.base == "mark" and child.get_conllu_field('xpos') != 'TO') \
+                    or (("conj" == rel.base) and (child.get_conllu_field("xpos") in verb_pos)):
+                # transfer any of the following children and any non 'to' markers
                 child.replace_edge(rel, rel, old_root, new_root)
+                # attach the best 'cc' to the new root as compliance with the transferred 'conj'
+                if child in g_cc_assignments and g_cc_assignments[child][0]:
+                    g_cc_assignments[child][0].replace_edge(Label("cc"), Label("cc"), old_root, new_root)
                 # store subj children for later
-                if re.match("(.subj.*)", rel):
+                if rel.base in subj_options:
                     subjs.append(child)
-                # since only non verbal xcomps children get here, this child becomes an amod connection candidate (see later on)
-                elif (rel == "xcomp") and evidential:
-                    new_amod = child
-            elif rel == "mark":
-                if child.get_conllu_field('xpos') != 'TO':
-                    # transfer any non 'to' markers
-                    child.replace_edge(rel, rel, old_root, new_root)
-                elif not evidential:
-                    # make the 'to' be the son of the 'be' evidential (instead the copula old).
-                    child.replace_edge(rel, rel, old_root, predecessor)
-                # else: child is 'to' but it is the evidential case, so no need to change it's father.
-            elif re.match("(aux.*|advmod)", rel) and not evidential:
-                # simply these are to be transferred only in the copula case, to the 'cop' itself, as it is in the evidential case.
-                child.replace_edge(rel, rel, old_root, predecessor)
-            elif re.match("(case)", rel):
-                new_out_rel = "nmod"
-            elif "cop" == rel:
-                if g_remove_node_adding_conversions:
-                    child.remove_edge(rel, old_root)
-                else:
+            elif rel.base in ["mark", "aux", "auxpass", "advmod"]:  # TODO: and not evidential: TODO: UDv1 = auxpass
+                # simply these are to be transferred to the 'cop' itself, as it is in the evidential case.
+                # and make the 'to' be the son of the 'be' evidential (instead the copula old).
+                child.replace_edge(rel, rel, old_root, cop)
+            elif rel.base == "case":
+                new_out_rel = "nmod"  # TODO: UDv1 = nmod
+            elif rel.base == "cop":
+                if not g_remove_node_adding_conversions:
                     # 'cop' becomes 'ev' (for event/evidential) to the new root
-                    child.replace_edge(rel, "ev", old_root, new_root)
-            elif ("conj" == rel) and re.match("(VB.?)", child.get_conllu_field("xpos")) and not evidential:
-                # transfer 'conj' only if it is a verb conjunction, as we want it to be attached to the new (verb/state) root
+                    child.replace_edge(rel, Label("ev"), old_root, new_root)
+            # else: 'compound', 'nmod', 'acl:relcl', 'amod', 'det', 'nmod:poss', 'nummod', 'nmod:tmod', ('cc', 'conj')
+
+    return subjs, new_out_rel, old_root
+
+
+def reattach_children_evidential(old_root, new_root, predecessor):
+    subjs = []
+    new_amod = None
+    for child, rels in old_root.get_children_with_rels():
+        for rel in rels:
+            # Note - transfer 'conj' only if it is a verb conjunction,
+            #   as we want it to be attached to the new (verb/state) root
+            if rel.base in subj_options + ["discourse", "punct", "advcl", "xcomp", "ccomp", "expl", "parataxis", "conj", "cc"] \
+                    or (rel.base == "mark" and child.get_conllu_field('xpos') != 'TO'):
+                # transfer any of the following children and any non 'to' markers
                 child.replace_edge(rel, rel, old_root, new_root)
-                # find best 'cc' to attach the new root as compliance with the transferred 'conj'.
-                attach_best_cc(child, ccs, old_root, new_root)
-            elif re.match("nmod(?!:poss)", rel) and evidential:
+                # store subj children for later
+                if rel.base in subj_options:
+                    subjs.append(child)
+                # since only non verbal xcomps children get here, this child becomes an amod connection candidate
+                elif rel.base == "xcomp":
+                    new_amod = child
+            elif rel.base == "nmod":  # TODO: UDv1 = nmod
                 child.replace_edge(rel, rel, old_root, new_root)
-            # else: {'compound', 'nmod', 'acl:relcl', 'amod', 'det', 'nmod:poss', 'nummod', 'nmod:tmod', some: 'cc', 'conj'}
-        
+    return subjs, 'ev', new_amod
+
+
+def extra_inner_weak_modifier_verb_reconstruction(sentence, matches, evidential):
+    for cur_match in matches:
+        cop = cur_match.token("cop")
+        old_root = sentence[cur_match.token("old_root")]
+
+        # the evidential should be the predecessor of the STATE as the cop is
+        # (even though he is also the old root of the construct).
+        predecessor = sentence[cop] if cop != -1 else old_root
+
+        # add STATE node or nominate the copula as new root if we shouldn't add new nodes
+        if not g_remove_node_adding_conversions:
+            new_id = TokenId(predecessor.get_conllu_field('id').major,
+                             predecessor.get_conllu_field('id').minor + 1)
+            if new_id in [t.get_conllu_field("id") for t in sentence]:
+                return
+            new_root = predecessor.copy(
+                new_id=new_id, form="STATE", lemma="_", upos="_", xpos="_", feats="_", head="_", deprel="_", deps=None)
+            sentence.append(new_root)
+        else:
+            new_root = predecessor
+
+        # transfer old-root's parents to new-root
+        reattach_parents(old_root, new_root)
+
+        if evidential:
+            subjs, new_out_rel, new_amod = reattach_children_evidential(old_root, new_root, predecessor)
+        else:
+            subjs, new_out_rel, new_amod = reattach_children_copula(old_root, new_root, predecessor)
+
         # update old-root's outgoing relation: for each subj add a 'amod' relation to the adjective.
-        #   new_amod can be the old_root if it was a copula construct, or the old_root's 'xcomp' son if not.
-        if re.match("JJ.?", new_amod.get_conllu_field("xpos")):
+        #   new_amod can be the old_root if it was a copula construct, or the old_root's 'xcomp' son if evidential
+        #   (if he had an xcomp son, otherwise new_amod will be None, so we validate).
+        if new_amod and new_amod.get_conllu_field("xpos") in adj_pos:
+            # update old-root's outgoing relation: for each subj add a 'amod' relation to the adjective.
             for subj in subjs:
-                new_amod.add_edge(add_extra_info("amod", "cop"), subj)
-        
+                new_amod.add_edge(Label("amod", src="cop"), subj)
+
         # connect the old_root as son of the new_root as 'ev' if it was an evidential root,
         # or with the proper complement if it was an adjectival root under the copula construct
-        old_root.add_edge('ev' if evidential else new_out_rel, new_root)
-
-
-def per_type_weak_modified_verb_reconstruction(sentence, rest, type_, ccomp_case):
-    # Copied NOTE from extra_inner_weak_modifier_verb_reconstruction: we do this as long as we find what to change,
-    #   and each time change only one match,instead of fixing all matches found each time.
-    #   As every change we do might change what can be found next, and old relations that are matched might be out dated.
-    #   But this is bad practice. we dont use the matching properly, and we use while true which might run forever!
-    found = set()
-    while True:
-        ret = match(sentence.values(), [[rest]])
-        if not ret:
-            return
-        cur_found = ["".join([k + str(v[0].get_conllu_field("id")) + str(v[1].get_conllu_field("id") if v[1] else v[1]) + str(v[2]) for k, v in ns.items()]) for ns in ret]
-        if not any([ns not in found for ns in cur_found]):
-            return
-        
-        name_space = ret[0]
-        found.add(cur_found[0])
-        old_root, _, _ = name_space['old_root']
-        new_root, _, _ = name_space['new_root']
-        
-        # transfer old-root's outgoing relation to new-root
-        for head, rel in old_root.get_new_relations():
-            old_root.remove_edge(rel, head)
-            new_root.add_edge(rel, head)
-        
-        # transfer
-        for child, rel in old_root.get_children_with_rels():
-            if child == new_root:
-                new_root.remove_edge(rel, old_root)
-                # find lowest 'ev' of the new root, and make us his 'ev' son
-                inter_root = new_root
-                ev_sons = [c for c,r in inter_root.get_children_with_rels() if 'ev' == r.split('@')[0]]
-                while ev_sons:
-                    inter_root = ev_sons[0]  # TODO2: change to 'ev' son with lowest index?
-                    if inter_root == new_root:
-                        break
-                    ev_sons = [c for c,r in inter_root.get_children_with_rels() if 'ev' == r.split('@')[0]]
-                old_root.add_edge(add_extra_info('ev', rel, dep_type=type_), inter_root)
-            elif rel == "mark":
-                # see notes in copula
-                if child.get_conllu_field('xpos') != 'TO':
-                    child.replace_edge(rel, rel, old_root, new_root) # TODO3: is this needed maybe all markers shouldnt be moved?
-            elif re.match("(.subj.*)", rel):
-                # transfer the subj only if it is not the special case of ccomp
-                if not ccomp_case:
-                    child.replace_edge(rel, rel, old_root, new_root)
-            elif re.match("(?!advmod|aux.*|cc|conj).*", rel):
-                child.replace_edge(rel, rel, old_root, new_root)  # TODO4: consult regarding all cases in the world.
-
-
-def extra_copula_reconstruction(sentence):
-    # NOTE: the xpos restriction comes to make sure we catch only non verbal copulas to reconstruct
-    #   (even though it should have been 'aux' instead of 'cop')
-    cop_rest = Restriction(name="father", nested=[[
-        Restriction(name="old_root", xpos="(?!(VB.?))", nested=[[
-            Restriction(name="cop", gov="cop"),
-        ]])
-    ]])
-
-    extra_inner_weak_modifier_verb_reconstruction(sentence, cop_rest, False)
-
-
-def extra_evidential_reconstruction(sentence):
-    # part1: find all evidential with no following(xcomp that is) main verb,
-    #   and add a new node and transfer to him the rootness, like in copula
-    # NOTE: we avoid the auxiliary sense of the evidential (in the 'be' case), with the gov restriction
-    ev_rest = Restriction(name="father", nested=[[
-        Restriction(name="old_root", gov="(?!aux.*).", xpos="(VB.?)", lemma=evidential_list, nested=[[
-            Restriction(name="new_root", gov="(xcomp|nmod)", xpos="(JJ.*|NN.*)"),
-        ]])
-    ]])
-    
+        old_root.add_edge(Label(new_out_rel), new_root)
+
+
+# NOTE: the xpos restriction comes to make sure we catch only non verbal copulas to reconstruct
+#   (even though it should have been 'aux' instead of 'cop')
+extra_copula_reconstruction_constraint = Full(
+    tokens=[
+        Token(id="old_root", spec=[Field(field=FieldNames.TAG, value=verb_pos, in_sequence=False)]),
+        Token(id="cop"),
+    ],
+    edges=[
+        Edge(child="cop", parent="old_root", label=[HasLabelFromList(["cop"])]),
+    ],
+)
+
+
+def extra_copula_reconstruction(sentence, matches):
+    extra_inner_weak_modifier_verb_reconstruction(sentence, matches, False)
+
+
+# part1: find all evidential with no following(xcomp that is) main verb,
+#   and add a new node and transfer to him the rootness, like in copula
+# NOTE: we avoid the auxiliary sense of the evidential (in the 'be' case), with the gov restriction
+extra_evidential_basic_reconstruction_constraint = Full(
+    tokens=[
+        Token(id="old_root", incoming_edges=[HasNoLabel("aux"), HasNoLabel("auxpass")],  # TODO: UDv1 = auxpass
+              spec=[Field(field=FieldNames.TAG, value=verb_pos), Field(field=FieldNames.LEMMA, value=evidential_list)]),
+        Token(id="new_root", spec=[Field(field=FieldNames.TAG, value=noun_pos + adj_pos)]),
+    ],
+    edges=[
+        Edge(child="new_root", parent="old_root", label=[HasLabelFromList(["xcomp", "nmod"])]),  # TODO: UDv1 = nmod
+    ],
+)
+
+
+def extra_evidential_basic_reconstruction(sentence, matches):
     if not g_remove_node_adding_conversions:
-        extra_inner_weak_modifier_verb_reconstruction(sentence, ev_rest, True)
-    
-    # part2: find all evidential with following(xcomp that is) main verb,
-    #   and transfer to the main verb rootness
-    # NOTE:
-    #   1. xpos rest. avoids adjectives as we already treated them.
-    ev_xcomp_rest = Restriction(name="father", nested=[[
-        Restriction(name="old_root", xpos="(VB.?)", lemma=evidential_list, nested=[[
-            Restriction(name="new_root", gov="xcomp", xpos="(?!(JJ.*|NN.*))"),
-        ]])
-    ]])
-
-    per_type_weak_modified_verb_reconstruction(sentence, ev_xcomp_rest, "EVIDENTIAL", False)
-    
-    ev_ccomp_rest = Restriction(name="father", nested=[[
-        Restriction(name="old_root", xpos="(VB.?)", lemma=evidential_list, nested=[[
-            Restriction(name="new_root", gov="(ccomp)"),
-        ]])
-    ]])
-    
-    per_type_weak_modified_verb_reconstruction(sentence, ev_ccomp_rest, "EVIDENTIAL", True)
+        extra_inner_weak_modifier_verb_reconstruction(sentence, matches, True)
 
 
-def extra_aspectual_reconstruction(sentence):
-    aspect_xcomp_rest = Restriction(name="father", nested=[[
-        Restriction(name="old_root", xpos="(VB.?)", lemma=aspectual_list, nested=[[
-            Restriction(name="new_root", gov="xcomp", xpos="(?!JJ)"),
-        ]])
-    ]])
-    
-    per_type_weak_modified_verb_reconstruction(sentence, aspect_xcomp_rest, "ASPECTUAL", False)
+def per_type_weak_modified_verb_reconstruction(sentence, matches, type_):
+    for cur_match in matches:
+        old_root = sentence[cur_match.token('old_root')]
+        new_root = sentence[cur_match.token('new_root')]
 
+        # assume either xcomp or ccomp
+        ccomp_or_xcomp = list(cur_match.edge(cur_match.token('new_root'), cur_match.token('old_root')))[0]
 
-def extra_reported_evidentiality(sentence):
-    reported_rest = Restriction(name="father", nested=[[
-        Restriction(name="ev", lemma=reported_list, nested=[[
-            Restriction(name="new_root", gov="ccomp")
-        ]])
-    ]])
-    
-    ret = match(sentence.values(), [[reported_rest]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        ev, _, _ = name_space['ev']
-        new_root, _, _ = name_space['new_root']
-        
-        ev.add_edge(add_extra_info("ev", "ccomp", dep_type="REPORTED"), new_root)
+        # transfer old-root's parents to new-root
+        reattach_parents(old_root, new_root)
 
+        # transfer
+        for child, rels in old_root.get_children_with_rels():
+            for rel in rels:
+                if rel.base == "mark":
+                    # see notes in copula
+                    if child.get_conllu_field('xpos') != 'TO':
+                        child.replace_edge(rel, rel, old_root, new_root)
+                elif rel.base in subj_options:
+                    # transfer the subj only if it is not the special case of ccomp
+                    if ccomp_or_xcomp != "ccomp":
+                        child.replace_edge(rel, rel, old_root, new_root)
+                elif rel.base in ["advmod", "aux", "auxpass", "cc", "conj"]:  # TODO: UDv1 = auxpass
+                    child.replace_edge(rel, rel, old_root, new_root)  # TODO4: consult regarding all cases in the world.
+
+        # find lowest 'ev' of the new root, and make us his 'ev' son
+        inter_root = new_root
+        ev_sons = [c for c, rels in inter_root.get_children_with_rels() for r in rels if 'ev' == r.base]
+        while ev_sons:
+            inter_root = ev_sons[0]  # TODO2: change to 'ev' son with lowest index?
+            if inter_root == new_root:
+                break
+            ev_sons = [c for c, rels in inter_root.get_children_with_rels() for r in rels if 'ev' == r.base]
+        old_root.add_edge(Label('ev', src=ccomp_or_xcomp, src_type=type_), inter_root)
 
-def create_mwe(words, head, rel):
-    for i, word in enumerate(words):
-        word.remove_all_edges()
-        word.add_edge(rel, head)
-        if 0 == i:
-            head = word
-            rel = "mwe"
+
+# part2: find all evidential with following(xcomp that is) main verb,
+#   and transfer to the main verb rootness
+extra_evidential_xcomp_reconstruction_constraint = Full(
+    tokens=[
+        Token(id="old_root", spec=[
+            Field(field=FieldNames.TAG, value=verb_pos), Field(field=FieldNames.LEMMA, value=evidential_list)]),
+        Token(id="new_root", spec=[Field(field=FieldNames.TAG, value=noun_pos + adj_pos, in_sequence=False)]),
+    ],
+    edges=[
+        Edge(child="new_root", parent="old_root", label=[HasLabelFromList(["xcomp", "ccomp"])])
+    ],
+)
 
 
-def is_prep_seq(words, preps):
-    return "_".join([word.get_conllu_field('form') for word in words]) in preps
+def extra_evidential_xcomp_reconstruction(sentence, matches):
+    per_type_weak_modified_verb_reconstruction(sentence, matches, "EVIDENTIAL")
 
 
-def reattach_children(old_head, new_head):
-    # store them before we make change to the original list
-    [child.replace_edge(child_rel, child_rel, old_head, new_head) for (child, child_rel) in old_head.get_children_with_rels()]
+extra_aspectual_reconstruction_constraint = Full(
+    tokens=[
+        Token(id="old_root", spec=[
+            Field(field=FieldNames.TAG, value=verb_pos), Field(field=FieldNames.LEMMA, value=aspectual_list)]),
+        Token(id="new_root", spec=[Field(field=FieldNames.TAG, value=adj_pos, in_sequence=False)]),
+    ],
+    edges=[
+        Edge(child="new_root", parent="old_root", label=[HasLabelFromList(["xcomp"])]),
+    ],
+)
 
 
-def split_concats_by_index(prep_list, prep_len):
-    out = list()
-    for i in range(prep_len):
-        out.append("|".join([prep.split("_")[i] for prep in prep_list]))
-    return out
+def extra_aspectual_reconstruction(sentence, matches):
+    per_type_weak_modified_verb_reconstruction(sentence, matches, "ASPECTUAL")
+
+
+# TODO: documentation!
+extra_reported_evidentiality_constraint = Full(
+    tokens=[
+        Token(id="ev", spec=[Field(field=FieldNames.LEMMA, value=reported_list)]),
+        Token(id="new_root"),
+    ],
+    edges=[
+        Edge(child="new_root", parent="ev", label=[HasLabelFromList(["ccomp"])]),
+    ],
+)
+
+
+def extra_reported_evidentiality(sentence, matches):
+    for cur_match in matches:
+        ev = cur_match.token("ev")
+        new_root = cur_match.token("new_root")
+        sentence[ev].add_edge(Label("ev", src="ccomp", src_type="REPORTED"), sentence[new_root])
 
 
 # for example The street is across from you.
@@ -1043,40 +1030,28 @@ def split_concats_by_index(prep_list, prep_len):
 # would be replaced with:
 #   case(you-6, across-4)
 #   mwe(across-4, from-5)
-def eudpp_process_simple_2wp(sentence):
-    restriction = Full(
-        tokens=[
-            Token(id="w1", outgoing_edges=[HasNoLabel("/.*/")]),
-            Token(id="w2", outgoing_edges=[HasNoLabel("/.*/")]),
-            Token(id="common_parent")],
-        edges=[
-            Edge(child="w1", parent="common_parent", label=[HasLabelFromList(["case", "advmod"])]),
-            Edge(child="w2", parent="common_parent", label=[HasLabelFromList(["case"])])
-        ],
-        distances=[ExactDistance("w1", "w2", distance=0)],
-        concats=[TokenPair(two_word_preps_regular, "w1", "w2")]
-    )
-    
-    forms = split_concats_by_index(two_word_preps_regular, 2)
-
-    restriction = Restriction(nested=[[
-        Restriction(gov="(case|advmod)", no_sons_of=".*", name="w1", form="^" + forms[0] + "$"),
-        Restriction(gov="case", no_sons_of=".*", follows="w1", name="w2", form="^" + forms[1] + "$")
-    ]])
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        w1, w1_head, w1_rel = name_space['w1']
-        w2, w2_head, w2_rel = name_space['w2']
-        
-        # check if words really form a prepositional phrase
-        if not is_prep_seq([w1, w2], two_word_preps_regular):
-            continue
-        
+eudpp_process_simple_2wp_constraint = Full(
+    tokens=[
+        Token(id="w1", no_children=True),
+        Token(id="w2", no_children=True),
+        Token(id="common_parent")],
+    edges=[
+        Edge(child="w1", parent="common_parent", label=[HasLabelFromList(["case", "advmod"])]),
+        Edge(child="w2", parent="common_parent", label=[HasLabelFromList(["case"])])
+    ],
+    distances=[ExactDistance("w1", "w2", distance=0)],
+    concats=[TokenPair(two_word_preps_regular, "w1", "w2")]
+)
+
+
+def eudpp_process_simple_2wp(sentence, matches):
+    for cur_match in matches:
+        common_parent = sentence[cur_match.token("common_parent")]
+        w1 = sentence[cur_match.token("w1")]
+        w2 = sentence[cur_match.token("w2")]
+
         # create multi word expression
-        create_mwe([w1, w2], w1_head, "case")
+        create_mwe([w1, w2], common_parent, Label("case"))
 
 
 # for example: He is close to me.
@@ -1092,45 +1067,39 @@ def eudpp_process_simple_2wp(sentence):
 #   case(me-6, close-3)
 #   mwe(close-3, to-4)
 #   root(ROOT-0, me-6)
-def eudpp_process_complex_2wp(sentence):
-    forms = split_concats_by_index(two_word_preps_complex, 2)
-
-    inner_rest = Restriction(gov="nmod", name="gov2", nested=[[
-        Restriction(name="w2", no_sons_of=".*", form="^" + forms[1] + "$")
-    ]])
-    restriction = Restriction(name="gov", nested=[[
-        Restriction(name="w1", followed_by="w2", form="^" + forms[0] + "$", nested=[
-            [inner_rest, Restriction(name="cop", gov="cop")],  # TODO: after adding the copula reconstuction, maybe this would be redundant
-            [inner_rest]
-        ])
-    ]])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-
-    for name_space in ret:
-        w1, _, w1_rel = name_space['w1']
-        w2, _, _ = name_space['w2']
-        gov, _, _ = name_space['gov']
-        gov2, _, gov2_rel = name_space['gov2']
-        cop, _, _ = name_space['cop'] if "cop" in name_space else (None, None, None)
-        
-        # check if words really form a prepositional phrase
-        if not is_prep_seq([w1, w2], two_word_preps_complex):
-            continue
-        
-        # Determine the relation to use for gov2's governor
-        if (w1_rel.lower() == "root") or (cop and (w1_rel in clause_relations)):
-            gov2.replace_edge(gov2_rel, w1_rel, w1, gov)
-        else:
-            gov2.replace_edge(gov2_rel, gov2_rel, w1, gov)
-        
+eudpp_process_complex_2wp_constraint = Full(
+    tokens=[
+        Token(id="w1"),
+        Token(id="w2", no_children=True),
+        Token(id="proxy")],
+    edges=[
+        Edge(child="proxy", parent="w1", label=[HasLabelFromList(["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="w2", parent="proxy", label=[HasLabelFromList(["case"])])
+    ],
+    distances=[ExactDistance("w1", "w2", distance=0)],
+    concats=[TokenPair(two_word_preps_complex, "w1", "w2")]
+)
+
+
+def eudpp_process_complex_2wp(sentence, matches):
+    for cur_match in matches:
+        w1 = sentence[cur_match.token("w1")]
+        w2 = sentence[cur_match.token("w2")]
+        proxy = sentence[cur_match.token("proxy")]
+
+        # assuming its either and not both
+        proxy_rel = list(cur_match.edge(cur_match.token("proxy"), cur_match.token("w1")))[0]
+
+        # make the proxy the head of the phrase
+        reattach_parents(w1, proxy, new_rel=Label(proxy_rel),
+                         # choose the relation to the new head to be according to a list of labels
+                         rel_by_cond=lambda x, y, z: x if y.base not in (clause_relations + ["root"]) else y)
+
         # reattach w1 sons to gov2.
-        reattach_children(w1, gov2)
+        reattach_children(w1, proxy)
         
         # create multi word expression
-        create_mwe([w1, w2], gov2, "case")
+        create_mwe([w1, w2], proxy, Label("case"))
 
 
 # for example: He is close to me.
@@ -1148,63 +1117,44 @@ def eudpp_process_complex_2wp(sentence):
 #   mwe(in-3, front-4)
 #   mwe(in-3, of-5)
 #   root(ROOT-0, you-6)
-def eudpp_process_3wp(sentence):
-    restriction = Full(
-        tokens=[
-            Token(id="w1", outgoing_edges=[HasNoLabel("/.*/")]),
-            Token(id="w2"),
-            Token(id="w3", outgoing_edges=[HasNoLabel("/.*/")]),
-            Token(id="proxy")],
-        edges=[
-            Edge(child="proxy", parent="w2", label=[HasLabelFromList(["/nmod|acl|advcl).*/"])]),
-            Edge(child="w1", parent="w2", label=[HasLabelFromList(["case"])]),
-            Edge(child="w3", parent="proxy", label=[HasLabelFromList(["case", "mark"])])
-        ],
-        distances=[ExactDistance("w1", "w2", distance=0), ExactDistance("w2", "w3", distance=0)],
-        concats=[TokenTriplet(three_word_preps, "w1", "w2", "w3")]
-    )
-    
-    forms = split_concats_by_index(three_word_preps, 3)
-    
-    restriction = Restriction(name="gov", nested=[[
-        Restriction(name="w2", followed_by="w3", follows="w1", form="^" + forms[1] + "$", nested=[[
-            Restriction(name="gov2", gov="(nmod|acl|advcl)", nested=[[
-                Restriction(name="w3", gov="(case|mark)", no_sons_of=".*", form="^" + forms[2] + "$")
-            ]]),
-            Restriction(name="w1", gov="^(case)$", no_sons_of=".*", form="^" + forms[0] + "$")
-        ]])
-    ]])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        w1, _, _ = name_space['w1']
-        w2, _, w2_rel = name_space['w2']
-        w3, _, _ = name_space['w3']
-        gov, _, _ = name_space['gov']
-        gov2, _, gov2_rel = name_space['gov2']
-        
-        # check if words really form a prepositional phrase
-        if not is_prep_seq([w1, w2, w3], three_word_preps):
-            continue
-        
-        # Determine the relation to use
-        # TODO - replace with reattach_parents, which is something like:
-        #   [(old_child.remove_edge(rel, head) and new_child.add_edge(rel if new_rel is None else new_rel, head)) for (head, rel) in old_head.get_new_relations()]
-        if (w2_rel == "nmod") and (gov2_rel in ["acl", "advcl"]):
-            gov2.replace_edge(gov2_rel, gov2_rel, w2, gov)
-            case = "mark"
-        else:
-            gov2.replace_edge(gov2_rel, w2_rel, w2, gov)
-            case = "case"
+eudpp_process_3wp_constraint = Full(
+    tokens=[
+        Token(id="w1", no_children=True),
+        Token(id="w2"),
+        Token(id="w3", no_children=True),
+        Token(id="proxy")],
+    edges=[
+        Edge(child="proxy", parent="w2", label=[HasLabelFromList(["nmod", "acl", "advcl"])]),  # TODO: UDv1 = nmod
+        Edge(child="w1", parent="w2", label=[HasLabelFromList(["case"])]),
+        Edge(child="w3", parent="proxy", label=[HasLabelFromList(["case", "mark"])])
+    ],
+    distances=[ExactDistance("w1", "w2", distance=0), ExactDistance("w2", "w3", distance=0)],
+    concats=[TokenTriplet(three_word_preps, "w1", "w2", "w3")]
+)
+
+
+def eudpp_process_3wp(sentence, matches):
+    for cur_match in matches:
+        w1 = sentence[cur_match.token("w1")]
+        w2 = sentence[cur_match.token("w2")]
+        w3 = sentence[cur_match.token("w3")]
+        proxy = sentence[cur_match.token("proxy")]
+
+        # assuming its either and not both
+        case = list(cur_match.edge(cur_match.token("w3"), cur_match.token("proxy")))[0]
+        proxy_rel = list(cur_match.edge(cur_match.token("proxy"), cur_match.token("w2")))[0]
+
+        # make the proxy the head of the phrase
+        reattach_parents(w2, proxy, new_rel=Label(proxy_rel),
+                         # fix acl to advcl if the new head is a verb, or to root if should be
+                         rel_by_cond=lambda x, y, z: y if x.base not in ["acl", "advcl"] or y.base != "nmod" else
+                         (Label("advcl") if z.get_conllu_field("xpos") in verb_pos and x.base == "acl" else x))
 
         # reattach w2 sons to gov2
-        reattach_children(w2, gov2)
+        reattach_children(w2, proxy)
         
         # create multi word expression
-        create_mwe([w1, w2, w3], gov2, case)
+        create_mwe([w1, w2, w3], proxy, Label(case))
 
 
 # The following two methods corrects Partitives and light noun constructions,
@@ -1220,311 +1170,199 @@ def eudpp_process_3wp(sentence):
 #   mwe(A-1, couple-2,)
 #   mwe(A-1, of-3)
 #   root(ROOT-0, people-4)
-def demote_per_type(sentence, restriction):
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        old_gov, old_gov_head, old_gov_rel = name_space['w1']
-        w2, w2_head, w2_rel = name_space['w2']
-        gov2, gov2_head, gov2_rel = name_space['gov2']
-        
+def demote_per_type(sentence, matches):
+    for cur_match in matches:
+        gov2 = sentence[cur_match.token("gov2")]
+        old_gov = sentence[cur_match.token("w1")]
+        w2 = sentence[cur_match.token("w2")]
+        w3 = cur_match.token("w3")
+        det = cur_match.token("det")
+
         words = [old_gov, w2]
-        if 'w3' in name_space:
-            w3, _, _ = name_space['w3']
-            words += [w3]
+        if w3 != -1:
+            words += [sentence[w3]]
             # run over what we 'though' to be the old_gov, as this is a 3-word mwe
-            old_gov, old_gov_head, old_gov_rel = name_space['w2']
-        elif 'det' in name_space:
+            old_gov = w2
+        elif det != -1:
             # NOTE: this is not done in SC, but should have been by THE PAPER.
             # adding the following determiner to the mwe.
-            det, _, _ = name_space['det']
-            words += [det]
-        
-        [child.replace_edge(rel, rel, old_gov, gov2) for (child, rel) in old_gov.get_children_with_rels() if rel == "case"]
-        gov2.replace_edge(gov2_rel, old_gov_rel, old_gov, old_gov_head)
-        create_mwe(words, gov2, add_eud_info("det", "qmod"))
-        # TODO: consider bringing back the 'if statement': [... if rel in ["punct", "acl", "acl:relcl", "amod"]]
-        [child.replace_edge(rel, rel, gov2_head, gov2) for (child, rel) in gov2_head.get_children_with_rels() if rel != "mwe"]
-
-
-def eudpp_demote_quantificational_modifiers(sentence):
-    quant_3w = Restriction(nested=[[
-        Restriction(name="w2", no_sons_of="amod", form=quant_mod_3w, followed_by="w3", nested=[[
-            Restriction(name="w1", gov="det", form="(?i:an?)"),
-            Restriction(name="gov2", gov="nmod", xpos="(NN.*|PRP.*)", nested=[[
-                Restriction(name="w3", gov="case", form="(?i:of)")
-            ]])
-        ]])
-    ]])
-    
-    quant_2w = Restriction(nested=[[
-        Restriction(name="w1", form=quant_mod_2w, followed_by="w2", nested=[[
-            Restriction(name="gov2", gov="nmod", xpos="(NN.*|PRP.*)", nested=[[
-                Restriction(name="w2", gov="case", form="(?i:of)")
-            ]])
-        ]])
-    ]])
-    
-    quant_2w_det = Restriction(nested=[[
-        Restriction(name="w1", form=quant_mod_2w_det, followed_by="w2", nested=[[
-            Restriction(name="gov2", gov="nmod", xpos="(NN.*)", nested=[[
-                Restriction(name="det", gov="det"),
-                Restriction(name="w2", gov="case", form="(?i:of)", followed_by="det")
-            ]])
-        ],
-            [Restriction(name="gov2", gov="nmod", xpos="(PRP.*)", nested=[[
-                Restriction(name="w2", gov="case", form="(?i:of)")
-            ]])
-        ]])
-    ]])
-    
-    for rl in [quant_3w, quant_2w, quant_2w_det]:
-        demote_per_type(sentence, rl)
-
-
-def assign_refs(ret):
-    ref_assignments = dict()
-    descendents = dict()
-    for name_space in ret:
-        for level in ['child_ref', 'grand_ref']:
-            if level in name_space:
-                mods_descendent = (name_space[level][0].get_conllu_field('id'), name_space[level])
-                if name_space['mod'] in descendents:
-                    descendents[name_space['mod']].append(mods_descendent)
-                else:
-                    descendents[name_space['mod']] = [mods_descendent]
-    
-    for mod, mods_descendents in descendents.items():
-        leftmost_descendent = sorted(mods_descendents)[0]
-        ref_assignments[mod] = leftmost_descendent[1]
-    
-    return ref_assignments
-
+            words += [sentence[det]]
 
-# Look for ref rules for a given word. We look through the
-# children and grandchildren of the acl:relcl dependency, and if any
-# children or grandchildren is a that/what/which/etc word,
-# we take the leftmost that/what/which/etc word as the dependent
-# for the ref TypedDependency.
+        reattach_parents(old_gov, gov2)
+        create_mwe(words, gov2, Label("det", "qmod"))
+        # TODO: consider bringing back the 'if statement': [... if rel in ["punct", "acl", "acl:relcl", "amod"]]
+        reattach_children(old_gov, gov2, cond=lambda x: x.base != "mwe")  # TODO: UDv1 = mwe
+
+
+eudpp_demote_quantificational_modifiers_3w_constraint = Full(
+    tokens=[
+        Token(id="w1", spec=[Field(FieldNames.WORD, ["a", "an"])]),  # TODO: english = a, an
+        Token(id="w2", spec=[Field(FieldNames.WORD, quant_mod_3w)]),
+        Token(id="w3", spec=[Field(FieldNames.WORD, ["of"])]),  # TODO: english = of
+        Token(id="gov2", spec=[Field(FieldNames.TAG, noun_pos + pron_pos)]),
+    ],
+    edges=[
+        Edge(child="gov2", parent="w2", label=[HasLabelFromList(["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="w1", parent="w2", label=[HasLabelFromList(["det"])]),
+        Edge(child="w3", parent="gov2", label=[HasLabelFromList(["case"])]),
+    ],
+    distances=[ExactDistance("w1", "w2", distance=0), ExactDistance("w2", "w3", distance=0)],
+)
+
+
+def eudpp_demote_quantificational_modifiers_3w(sentence, matches):
+    demote_per_type(sentence, matches)
+
+
+eudpp_demote_quantificational_modifiers_2w_constraint = Full(
+    tokens=[
+        Token(id="w1", spec=[Field(FieldNames.WORD, quant_mod_2w)]),
+        Token(id="w2", spec=[Field(FieldNames.WORD, ["of"])]),  # TODO: english = of
+        Token(id="gov2", outgoing_edges=[HasNoLabel("det")], spec=[Field(FieldNames.TAG, noun_pos + pron_pos)]),
+    ],
+    edges=[
+        Edge(child="gov2", parent="w1", label=[HasLabelFromList(["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="w2", parent="gov2", label=[HasLabelFromList(["case"])]),
+    ],
+    distances=[ExactDistance("w1", "w2", distance=0)]
+)
+
+
+def eudpp_demote_quantificational_modifiers_2w(sentence, matches):
+    demote_per_type(sentence, matches)
+
+
+eudpp_demote_quantificational_modifiers_det_constraint = Full(
+    tokens=[
+        Token(id="w1", spec=[Field(FieldNames.WORD, quant_mod_2w_det)]),
+        Token(id="w2", spec=[Field(FieldNames.WORD, ["of"])]),  # TODO: english = of
+        Token(id="gov2", spec=[Field(FieldNames.TAG, noun_pos + pron_pos)]),
+        Token(id="det", optional=True),
+    ],
+    edges=[
+        Edge(child="gov2", parent="w1", label=[HasLabelFromList(["nmod"])]),  # TODO: UDv1 = nmod
+        Edge(child="w2", parent="gov2", label=[HasLabelFromList(["case"])]),
+        Edge(child="det", parent="gov2", label=[HasLabelFromList(["det"])]),
+    ],
+    distances=[ExactDistance("w1", "w2", distance=0), ExactDistance("w2", "det", distance=0)]
+)
+
+
+def eudpp_demote_quantificational_modifiers_det(sentence, matches):
+    demote_per_type(sentence, matches)
+
+
+# Look for ref rules for a given word.
+# Unlike what was done in SC, we dont look through the grandchildren, but only through the children
+# of the acl:relcl dependency, and assume only one so we dont look for the leftmost. (as this is redundant)
+# If any children is a that/what/which/etc word, we take him as the dependent for the ref TypedDependency.
 # Then we collapse the referent relation such as follows. e.g.:
 # "The man that I love ... " dobj(love, that) -> ref(man, that) dobj(love, man)
-def add_ref_and_collapse_general(sentence, enhanced_plus_plus, enhanced_extra):
-    child_rest = Restriction(name="child_ref", form=relativizing_word_regex)
-    grandchild_rest = Restriction(nested=[[
-        Restriction(name="grand_ref", form=relativizing_word_regex)
-    ]])
-    restriction = Restriction(name="gov", nested=[[
-        Restriction(name="mod", gov='acl:relcl', nested=[
-            [grandchild_rest, child_rest],
-            [grandchild_rest],
-            [child_rest],
-            []
-        ]),
-    ]])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    ref_assignments = assign_refs(ret)
-
-    for name_space in ret:
-        gov, gov_head, gov_rel = name_space['gov']
-        if ref_assignments and name_space['mod'] in ref_assignments:
-            if not enhanced_plus_plus:
-                continue
-            leftmost, leftmost_head, leftmost_rel = ref_assignments[name_space['mod']]
-            if gov not in leftmost.get_parents():
-                leftmost.replace_edge(leftmost_rel, "ref", leftmost_head, gov)
-                [child.replace_edge(rel, rel, leftmost, gov) for child, rel in leftmost.get_children_with_rels()]
-                gov.add_edge(leftmost_rel, leftmost_head, extra_info=EXTRA_INFO_STUB)
-        # this is for reduce-relative-clause
-        elif enhanced_extra:
-            leftmost_head, _, prevs_rel = name_space['mod']
-            rels_with_pos = {(relation, child.get_conllu_field('xpos')): child.get_conllu_field('form') for (child, relation) in leftmost_head.get_children_with_rels()}
-            rels_only = [rel for (rel, pos) in rels_with_pos.keys()]
-
-            phrase = "REDUCED"
-            if ("nsubj" not in rels_only) and ("nsubjpass" not in rels_only):
-                leftmost_rel = 'nsubj'
-            # some relativizers that were simply missing on the eUD.
-            elif 'where' in [child.get_conllu_field('form') for child in leftmost_head.get_children()]:
-                leftmost_rel = 'nmod'
-                phrase = 'where'
-            elif 'how' in [child.get_conllu_field('form') for child in leftmost_head.get_children()]:
-                leftmost_rel = 'nmod'
-                phrase = 'how'
-            elif 'when' in [child.get_conllu_field('form') for child in leftmost_head.get_children()]:
-                leftmost_rel = 'nmod:tmod'
-                phrase = 'when'
-            elif 'why' in [child.get_conllu_field('form') for child in leftmost_head.get_children()]:
-                leftmost_rel = add_eud_info('nmod', 'because_of')
-                phrase = 'why'
-            
-            # continue with *reduced* relcl, cased of orphan case/marker should become nmod and not obj
-            elif ('nmod', 'RB') in rels_with_pos:
-                leftmost_rel = add_eud_info('nmod', rels_with_pos[('nmod', 'RB')])
-            elif ('advmod', 'RB') in rels_with_pos:
-                leftmost_rel = add_eud_info('nmod', rels_with_pos[('advmod', 'RB')])
-            elif ('nmod', 'IN') in rels_with_pos:
-                leftmost_rel = add_eud_info('nmod', rels_with_pos[('nmod', 'IN')])
-            
-            # NOTE: I couldn't find an example for the following commented out very specific adjusment. TODO - remove in near future.
-            # # this is a special case in which its not the head of the relative clause who get the nmod connection but one of its objects,
-            # # as sometimes the relcl modifies the should-have-been-inner-object's-modifier
-            # elif 'dobj' in rels_only:
-            #     objs = [child for child, rel in leftmost_head.get_children_with_rels() if rel == 'dobj']
-            #     found = False
-            #     for obj in objs:
-            #         rels_with_pos_obj = {(relation, child.get_conllu_field('xpos')): child for
-            #                              (child, relation) in obj.get_children_with_rels()}
-            #         if (('nmod', 'IN') in rels_with_pos_obj) or (('nmod', 'RB') in rels_with_pos_obj):
-            #             case = rels_with_pos_obj[('nmod', 'IN')] if ('nmod', 'IN') in rels_with_pos_obj else rels_with_pos_obj[('nmod', 'RB')]
-            #             gov.add_edge(add_extra_info(add_eud_info("nmod", case.get_conllu_field('form')), "reduced-relcl"), obj, extra_info=EXTRA_INFO_STUB)
-            #             case.add_edge(add_extra_info("case", "reduced-relcl"), gov)
-            #             found = True
-            #             break
-            #     if found:
-            #         continue
-            #     # this means we didn't find so rel should be dobj
-            #     leftmost_rel = 'dobj'
-            else:
-                leftmost_rel = 'dobj'
-            gov.add_edge(add_extra_info(leftmost_rel, "acl", dep_type="RELCL", phrase=phrase, prevs=prevs_rel), leftmost_head, extra_info=EXTRA_INFO_STUB)
-
-
-def eudpp_add_ref_and_collapse(sentence):
-    add_ref_and_collapse_general(sentence, True, False)
-
-
-def extra_add_ref_and_collapse(sentence):
-    add_ref_and_collapse_general(sentence, False, True)
-
-
-# resolves the following multi word conj phrases:
-#   a. 'but(cc) not', 'if not', 'instead of', 'rather than', 'but(cc) rather'. GO TO negcc
-#   b. 'as(cc) well as', 'but(cc) also', 'not to mention', '&'. GO TO and
-# NOTE: This is bad practice (and sometimes not successful neither for SC or for us) for the following reasons:
-#   1. Not all parsers mark the same words as cc (if at all), so looking for the cc on a specific word is wrong.
-#       as of this reason, for now we and SC both miss: if-not, not-to-mention
-#   2. Some of the multi-words are already treated as multi-word prepositions (and are henceforth missed):
-#       as of this reason, for now we and SC both miss: instead-of, rather-than
-def get_assignment(sentence, cc):
-    cc_cur_id = cc.get_conllu_field('id')
-    prev_forms = "_".join([info.get_conllu_field('form') for (iid, info) in sentence.items()
-                           if iid != 0 and (cc_cur_id - 1 == iid or cc_cur_id == iid)])
-    next_forms = "_".join([info.get_conllu_field('form') for (iid, info) in sentence.items()
-                           if cc_cur_id + 1 == iid or cc_cur_id == iid])
-    if next_forms in neg_conjp_next or prev_forms in neg_conjp_prev:
-        return "negcc"
-    elif (next_forms in and_conjp_next) or (cc.get_conllu_field('form') == '&'):
-        return "and"
-    else:
-        return cc.get_conllu_field('form')
-
-
-# In case multiple coordination marker depend on the same governor
-# the one that precedes the conjunct is appended to the conjunction relation or the
-# first one if no preceding marker exists.
-def assign_ccs_to_conjs(sentence, ret):
-    cc_assignments = dict()
-    ccs = []
-    conjs = []
-    for name_space in ret:
-        ccs.append((name_space['cc'][0].get_conllu_field('id'), name_space['cc']))
-        conjs.append((name_space['conj'][0].get_conllu_field('id'), name_space['conj']))
-    
-    sorted_ccs_and_conjs = sorted(ccs + conjs)
-    _, cur_cc = sorted(ccs)[0]
-    for _, (cc_or_conj, head, rel) in sorted_ccs_and_conjs:
-        if rel == "cc":
-            cur_cc = (cc_or_conj, head, rel)
+eudpp_add_ref_and_collapse_constraint = Full(
+    tokens=[
+        Token(id="relativizer", spec=[Field(FieldNames.WORD, relativizing_words + list(relativizers_to_rel.keys()))]),
+        Token(id="gov"),
+        Token(id="mod")
+    ],
+    edges=[
+        Edge(child="mod", parent="gov", label=[HasLabelFromList(["acl:relcl"])]),  # TODO: english: relcl
+        Edge(child="relativizer", parent="mod", label=[HasLabelFromList(["/.*/"])])
+    ],
+)
+
+
+def eudpp_add_ref_and_collapse(sentence, matches):
+    for cur_match in matches:
+        gov = sentence[cur_match.token('gov')]
+        mod = sentence[cur_match.token('mod')]
+        relativizer = sentence[cur_match.token('relativizer')]
+
+        # this is a pretty basic case so we can assume only one label
+        label = list(cur_match.edge(cur_match.token('relativizer'), cur_match.token('mod')))[0]
+        text = relativizer.get_conllu_field("form").lower()
+        # some relativizers that were simply missing on the eUD, we added them as nmods
+        new_label = Label(relativizers_to_rel[text], eud=text) if text in relativizers_to_rel else Label(label)
+
+        reattach_children(relativizer, gov)
+        relativizer.replace_edge(Label(label), Label("ref"), mod, gov)
+        gov.add_edge(new_label, mod)
+
+
+# this is for reduce-relative-clause
+extra_add_ref_and_collapse_constraint = Full(
+    tokens=[
+        # this will prevent changing the non-reduced relcl
+        Token(id="gov", outgoing_edges=[HasNoLabel("ref")]),
+        Token(id="mod"),
+        Token("subj", optional=True),
+        Token("prep", optional=True, spec=[Field(FieldNames.TAG, ["RB", "IN"])])
+    ],
+    edges=[
+        Edge(child="mod", parent="gov", label=[HasLabelFromList(["acl:relcl"])]),  # TODO: english: relcl
+        Edge(child="subj", parent="mod", label=[HasLabelFromList(subj_options)]),
+        Edge(child="prep", parent="mod", label=[HasLabelFromList(["advmod", "nmod", "case", "mark"])]),  # TODO: UDv1: nmod
+    ],
+)
+
+
+def extra_add_ref_and_collapse(sentence, matches):
+    for cur_match in matches:
+        gov = sentence[cur_match.token('gov')]
+        mod = sentence[cur_match.token('mod')]
+        subj = cur_match.token('subj')
+        prep = cur_match.token('prep')
+
+        eud = None
+        if subj == -1:
+            leftmost_rel = "nsubj"
+        # cased of orphan case/marker should become nmod and not obj
+        elif prep != -1:
+            leftmost_rel = 'nmod'  # TODO: UDv1 = nmod
+            eud = sentence[prep].get_conllu_field("form").lower()
+            # replace the orphan prep to ba a case relation
+            # TODO: but actually this is a bit harsh,
+            #   for example "after" and "before" should remain 'advmod', maybe refix it in future
+            old_rel = list(cur_match.edge(prep, cur_match.token('mod')))[0]
+            sentence[prep].replace_edge(Label(old_rel), Label("case"), mod, mod)
         else:
-            cc_assignments[((cc_or_conj, head, rel), cur_cc)] = get_assignment(sentence, cur_cc[0])
-    return cc_assignments
+            leftmost_rel = 'dobj'  # TODO: UDv1 = dobj
+        gov.add_edge(Label(leftmost_rel, eud=get_eud_info(eud), src="acl", src_type="RELCL", phrase="REDUCED"), mod)
 
 
 # Adds the type of conjunction to all conjunct relations
-# Some multi-word coordination markers are collapsed to conj:and or conj:negcc
-def eud_conj_info(sentence):
-    restriction = Restriction(name="gov", nested=[[
-        Restriction(name="cc", gov="^(cc)$"),
-        Restriction(name="conj", gov="^(conj)$")
-    ]])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    # assign ccs to conjs according to precedence
-    cc_assignments = assign_ccs_to_conjs(sentence, ret)
-    
-    for name_space in ret:
-        gov, _, _ = name_space['gov']
-        cc, _, cc_rel = name_space['cc']
-        conj, _, conj_rel = name_space['conj']
-
-        if ((conj, gov, conj_rel), (cc, gov, cc_rel)) not in cc_assignments:
+eud_conj_info_constraint = Full(
+    tokens=[
+        Token(id="gov"),
+        Token(id="conj")],
+    edges=[
+        Edge(child="conj", parent="gov", label=[HasLabelFromList(["conj"])]),
+    ],
+)
+
+
+def eud_conj_info(sentence, matches):
+    for cur_match in matches:
+        gov = sentence[cur_match.token("gov")]
+        conj = sentence[cur_match.token("conj")]
+
+        if conj not in g_cc_assignments:
             continue
-        cc_assignment = cc_assignments[((conj, gov, conj_rel), (cc, gov, cc_rel))]
-        
-        conj.replace_edge(conj_rel, add_eud_info(conj_rel, cc_assignment), gov, gov)
+
+        for rel in cur_match.edge(cur_match.token("conj"), cur_match.token("gov")):
+            conj.replace_edge(Label(rel), Label(rel, g_cc_assignments[conj][1]), gov, gov)
 
 
-# The label of the conjunct relation includes the conjunction type
-# because if the verb has multiple cc relations then it can be impossible
-# to infer which coordination marker belongs to which conjuncts.
-def expand_per_type(sentence, restriction, is_pp):
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
+def create_new_node(sentence, to_copy, nodes_copied, last_copy_id):
+    # create a copy node,
+    to_copy_id = to_copy.get_conllu_field('id')
+    nodes_copied = 1 if to_copy_id != last_copy_id else nodes_copied + 1
+    last_copy_id = to_copy_id
+    new_id = TokenId(to_copy_id.major, to_copy_id.minor + nodes_copied)
     
-    # assign ccs to conjs according to precedence
-    cc_assignments = assign_ccs_to_conjs(sentence, ret)
+    copy_node = to_copy.copy(new_id=new_id, head="_", deprel="_", misc=f"CopyOf={str(to_copy.get_conllu_field('id'))}")
+    sentence.append(copy_node)
     
-    nodes_copied = 0
-    last_copy_id = -1
-    for name_space in ret:
-        gov, _, gov_rel = name_space['gov']
-        to_copy, _, _ = name_space['to_copy']
-        cc, cc_head, cc_rel = name_space['cc']
-        conj, conj_head, conj_rel = name_space['conj']
-        
-        if ((conj, conj_head, conj_rel), (cc, cc_head, cc_rel)) not in cc_assignments:
-            continue
-        cc_assignment = cc_assignments[((conj, gov, conj_rel), (cc, gov, cc_rel))]
-        
-        # Check if we already copied this node in this same match (as it is hard to restrict that).
-        # This is relevant only for the prep type.
-        if (not is_pp) and any(node.get_conllu_field("misc") == f"CopyOf={int(to_copy.get_conllu_field('id'))}" for node in sentence.values()):
-            continue
-        
-        # create a copy node,
-        # add conj:cc_info('to_copy', copy_node)
-        nodes_copied = 1 if to_copy.get_conllu_field('id') != last_copy_id else nodes_copied + 1
-        last_copy_id = to_copy.get_conllu_field('id')
-        new_id = to_copy.get_conllu_field('id') + (0.1 * nodes_copied)
-        copy_node = to_copy.copy(
-            new_id=new_id,
-            head="_",
-            deprel="_",
-            misc="CopyOf=%d" % to_copy.get_conllu_field('id'))
-        copy_node.add_edge(add_eud_info("conj", cc_assignment), to_copy)
-        sentence[new_id] = copy_node
-        
-        if is_pp:
-            # replace cc('gov', 'cc') with cc('to_copy', 'cc')
-            # NOTE: this is not mentioned in THE PAPER, but is done in SC (and makes sense).
-            cc.replace_edge(cc_rel, cc_rel, cc_head, to_copy)
-            
-            # replace conj('gov', 'conj') with e.g nmod(copy_node, 'conj')
-            conj.replace_edge(conj_rel, gov_rel, conj_head, copy_node)
-        else:
-            # copy relation from modifier to new node e.g nmod:from(copy_node, 'modifier')
-            modifier, _, modifier_rel = name_space['modifier']
-            modifier.add_edge(add_eud_info(modifier_rel, conj.get_conllu_field('form')), copy_node)
+    return copy_node, nodes_copied, last_copy_id
 
 
 # Expands PPs with conjunctions such as in the sentence
@@ -1536,239 +1374,323 @@ def expand_per_type(sentence, restriction, is_pp):
 # while those where removed:
 #   cc(France-4, and-5)
 #   conj(France-4, Serbia-7)
-# After that, expands prepositions with conjunctions such as in the sentence
+eudpp_expand_pp_conjunctions_constraint = Full(
+    tokens=[
+        Token(id="to_copy"),
+        Token(id="gov", outgoing_edges=[HasLabelFromList(["case", "mark"])]),
+        Token(id="conj"),
+        Token(id="already_copied", optional=True),
+    ],
+    edges=[
+        Edge(child="gov", parent="to_copy", label=[HasLabelFromList(["nmod", "acl", "advcl"])]),
+        Edge(child="conj", parent="gov", label=[HasLabelFromList(["conj"])]),
+        Edge(child="already_copied", parent="to_copy", label=[HasLabelFromList(["conj"])]),
+    ],
+)
+
+
+def eudpp_expand_pp_conjunctions(sentence, matches):
+    nodes_copied = 0
+    last_copy_id = -1
+    for cur_match in matches:
+        gov = sentence[cur_match.token('gov')]
+        to_copy = sentence[cur_match.token('to_copy')]
+        conj = sentence[cur_match.token('conj')]
+        already_copied = cur_match.token('already_copied')
+
+        if conj not in g_cc_assignments:
+            continue
+
+        # Check if we already copied this node in this same match (as it is hard to restrict that).
+        if already_copied != -1 and \
+                any(node.get_conllu_field("misc") == f"CopyOf={str(to_copy.get_conllu_field('id'))}" for node in sentence):
+            return
+
+        cc_tok, cc_rel = g_cc_assignments[conj]
+
+        copy_node, nodes_copied, last_copy_id = create_new_node(sentence, to_copy, nodes_copied, last_copy_id)
+        copy_node.add_edge(Label("conj", eud=get_eud_info(cc_rel)), to_copy)
+
+        # replace cc('gov', 'cc') with cc('to_copy', 'cc')
+        # NOTE: this is not mentioned in THE PAPER, but is done in SC (and makes sense).
+        if cc_tok:
+            cc_tok.replace_edge(Label("cc"), Label("cc"), gov, to_copy)
+
+        for rel in cur_match.edge(cur_match.token('gov'), cur_match.token('to_copy')):
+            # replace conj('gov', 'conj') with e.g nmod(copy_node, 'conj')
+            conj.remove_all_edges()
+            conj.add_edge(Label(rel), copy_node)
+
+
+# expands prepositions with conjunctions such as in the sentence
 # "Bill flies to and from Serbia." by copying the verb resulting
 # in the following new relations:
 #   conj:and(flies, flies')
 #   nmod(flies', Serbia)
-def eudpp_expand_pp_or_prep_conjunctions(sentence):
-    pp_restriction = Restriction(name="to_copy", nested=[[
-        Restriction(name="gov", gov="^(nmod|acl|advcl)$", nested=[[
-            Restriction(gov="case"),
-            Restriction(name="cc", gov="^(cc)$"),
-            Restriction(name="conj", gov="conj", nested=[[
-                Restriction(gov="case")
-            ]])
-        ]])
-    ]])
-    
-    prep_restriction = Restriction(name="to_copy", nested=[[
-        Restriction(name="modifier", nested=[[
-            Restriction(name="gov", gov="case", nested=[[
-                Restriction(name="cc", gov="^(cc)$"),
-                Restriction(name="conj", gov="conj")
-            ]])
-        ]])
-    ]])
-    
-    for rl, is_pp in [(pp_restriction, True), (prep_restriction, False)]:
-        expand_per_type(sentence, rl, is_pp)
-
+eudpp_expand_prep_conjunctions_constraint = Full(
+    tokens=[
+        Token(id="to_copy"),
+        Token(id="already_copied", optional=True),
+        Token(id="modifier"),
+        Token(id="gov", outgoing_edges=[HasLabelFromList(["cc"])]),
+        Token(id="conj"),
+    ],
+    edges=[
+        Edge(child="modifier", parent="to_copy", label=[HasLabelFromList(["nmod", "acl", "advcl"])]),
+        Edge(child="already_copied", parent="to_copy", label=[HasLabelFromList(["conj"])]),
+        Edge(child="gov", parent="modifier", label=[HasLabelFromList(["case", "mark"])]),
+        Edge(child="conj", parent="gov", label=[HasLabelFromList(["conj"])]),
+    ],
+)
+
+
+def eudpp_expand_prep_conjunctions(sentence, matches):
+    nodes_copied = 0
+    last_copy_id = -1
+    for cur_match in matches:
+        to_copy = sentence[cur_match.token('to_copy')]
+        conj = sentence[cur_match.token('conj')]
+        modifier = sentence[cur_match.token('modifier')]
+        already_copied = cur_match.token('already_copied')
 
-# TODO: remove when moving to UD-version2
-def extra_fix_nmod_npmod(sentence):
-    restriction = Restriction(nested=[[
-        Restriction(name="npmod", gov="^nmod:npmod$")
-    ]])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        npmod, npmod_head, npmod_rel = name_space['npmod']
-        npmod.replace_edge(npmod_rel, "compound", npmod_head, npmod_head)
-
-
-def extra_hyphen_reconstruction(sentence):
-    restriction = Restriction(name="subj", nested=[[
-        Restriction(name="verb", gov="^(amod)$", xpos="VB.", nested=[[
-            Restriction(name="hyphen", form="-", gov="^(punct)$", xpos="HYPH"),
-            Restriction(name="noun", gov="^(compound)$", xpos="NN.?")
-        ]]),
-    ]])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        subj, _, subj_rel = name_space['subj']
-        verb, _, _ = name_space['verb']
-        noun, _, noun_rel = name_space['noun']
+        if conj not in g_cc_assignments:
+            continue
         
-        subj.add_edge(add_extra_info("nsubj", "compound", dep_type="HYPHEN", prevs=subj_rel), verb)
-        noun.add_edge(add_extra_info("nmod", "compound", dep_type="HYPHEN", prevs=noun_rel), verb)
+        # Check if we already copied this node in this same match (as it is hard to restrict that).
+        if already_copied != -1 and \
+                any(node.get_conllu_field("misc") == f"CopyOf={str(to_copy.get_conllu_field('id'))}" for node in sentence):
+            return
+        
+        copy_node, nodes_copied, last_copy_id = create_new_node(sentence, to_copy, nodes_copied, last_copy_id)
+        copy_node.add_edge(Label("conj", eud=get_eud_info(g_cc_assignments[conj][1])), to_copy)
+        
+        # copy relation from modifier to new node e.g nmod:from(copy_node, 'modifier')
+        for rel in cur_match.edge(cur_match.token('modifier'), cur_match.token('to_copy')):
+            modifier.add_edge(Label(rel, eud=get_eud_info(conj.get_conllu_field('form'))), copy_node)
+
+
+# TODO: UDv1 specific
+# TODO: Documentation
+extra_fix_nmod_npmod_constraint = Full(
+    tokens=[
+        Token(id="npmod"),
+        Token(id="gov"),
+    ],
+    edges=[
+        Edge(child="npmod", parent="gov", label=[HasLabelFromList(["nmod:npmod"])]),
+    ],
+)
+
+
+def extra_fix_nmod_npmod(sentence, matches):
+    for cur_match in matches:
+        gov = cur_match.token("gov")
+        npmod = cur_match.token("npmod")
+        for rel in cur_match.edge(npmod, gov):
+            sentence[npmod].replace_edge(Label(rel), Label("compound"), sentence[gov], sentence[gov])
+
+
+# TODO - add documentation!
+extra_hyphen_reconstruction_constraint = Full(
+    tokens=[
+        Token(id="subj"),
+        Token(id="verb", spec=[Field(field=FieldNames.TAG, value=list(set(verb_pos) - {"VB"}))]),
+        Token(id="hyphen", spec=[
+            Field(field=FieldNames.TAG, value=["HYPH"]), Field(field=FieldNames.WORD, value=["-"])]),
+        Token(id="noun", spec=[Field(field=FieldNames.TAG, value=noun_pos)]),
+    ],
+    edges=[
+        Edge(child="verb", parent="subj", label=[HasLabelFromList(["amod"])]),
+        Edge(child="hyphen", parent="verb", label=[HasLabelFromList(["punct"])]),
+        Edge(child="noun", parent="verb", label=[HasLabelFromList(["compound"])]),
+    ],
+)
+
+
+def extra_hyphen_reconstruction(sentence, matches):
+    for cur_match in matches:
+        subj = cur_match.token("subj")
+        verb = cur_match.token("verb")
+        noun = cur_match.token("noun")
+        sentence[subj].add_edge(Label("nsubj", src="compound", src_type="HYPHEN"), sentence[verb])
+        sentence[noun].add_edge(Label("nmod", src="compound", src_type="HYPHEN"), sentence[verb])  # TODO - UDv1 = nmod
 
 
 # The bottle was broken by me.
-def extra_passive_alteration(sentence):
-    restriction = Full(
-        tokens=[
-            Token(id="predicate"),
-            Token(id="subjpass"),
-            Token(id="agent", optional=True),
-            Token(id="by", optional=True, spec=[Field(FieldNames.WORD, ["^(?i:by)$"])])],
-        edges=[
-            Edge(child="subjpass", parent="predicate", label=[HasLabelFromList(["/.subjpass/"])]),
-            Edge(child="agent", parent="predicate", label=[HasLabelFromList(["/^(nmod(:agent)?)$/"])]),
-            Edge(child="by", parent="agent", label=[HasLabelFromList(["case"])]),
-            Edge(child="subjpass", parent="predicate", label=[HasNoLabel(".obj")])
-        ]
-    )
-
-    restriction = Restriction(name="predicate", nested=[
-        [
-            Restriction(name="subjpass", gov=".subjpass"),
-            Restriction(name="agent", gov="^(nmod(:agent)?)$", nested=[[
-                Restriction(form="^(?i:by)$")
-            ]])
-        ],
-        [Restriction(name="subjpass", gov=".subjpass")]
-    ])
-    
-    ret = match(sentence.values(), [[restriction]])
-    if not ret:
-        return
-    
-    for name_space in ret:
-        subj, _, subj_rel = name_space['subjpass']
-        predicate, _, _ = name_space['predicate']
-        if subj.match_rel(".obj", predicate):
-            # avoid fixing a fixed passive.
-            continue
-        if 'agent' in name_space:
-            # TODO validate 'by' is in the namespace as well
-            agent, _, agent_rel = name_space['agent']
-            agent.add_edge(add_extra_info("nsubj", "passive", prevs=agent_rel), predicate)
+extra_passive_alteration_constraint = Full(
+    tokens=[
+        Token(id="predicate"),
+        # the no-object lookup will prevent repeatedly converting this conversion
+        Token(id="subjpass", incoming_edges=[HasNoLabel(obj) for obj in obj_options]),
+        Token(id="agent", optional=True, spec=[Field(FieldNames.TAG, noun_pos + pron_pos)]),
+        Token(id="by", optional=True, spec=[Field(FieldNames.WORD, ["by"])]),  # TODO - english specific
+        Token(id="predicates_obj", optional=True)],
+    edges=[
+        Edge(child="subjpass", parent="predicate", label=[HasLabelFromList(["nsubjpass", "csubjpass"])]),  # TODO - UDv1 = pass
+        # TODO: maybe nmod:agent is redundant as we always look at the basic label and agent is part of EUD (afaik)
+        Edge(child="agent", parent="predicate", label=[HasLabelFromList(["nmod", "nmod:agent"])]),  # TODO: UDv1 = nmod
+        Edge(child="by", parent="agent", label=[HasLabelFromList(["case"])]),
+        Edge(child="predicates_obj", parent="predicate", label=[HasLabelFromList(obj_options)]),
+    ]
+)
+
+
+def extra_passive_alteration(sentence, matches):
+    for cur_match in matches:
+        subj = sentence[cur_match.token("subjpass")]
+        predicate = sentence[cur_match.token("predicate")]
+        agent = cur_match.token("agent")
+        by = cur_match.token("by")
+        predicates_obj = cur_match.token("predicates_obj")
         
-        # the special case of csubj (divided into ccomp and xcomp according to 'that' and 'to' subordinates.
-        subj_new_rel = "dobj"
-        # TODO -
-        #   1. this entire part can be refactored using a global reuseable function.
-        #   2. there are also: an advcl option, what in addition to that for ccomp, and the 'obj' condition is weird.
-        #   3. actually I can't reproduce any of these cases using the current model, but they still exists in theory. so IDKWTD
-        if subj_rel.startswith("csubj"):
-            for child, rel in subj.get_children_with_rels():
-                if (rel == "mark") and (child.get_conllu_field("form") == "to"):
-                    subj_new_rel = "xcomp"
-                elif ("obj" in rel) and (child.get_conllu_field("form") == "that") and (child.get_conllu_field("xpos") == "IN"):
-                    subj_new_rel = "ccomp"
-        elif "dobj" in [rel for (_, rel) in predicate.get_children_with_rels()]:
-            subj_new_rel = "iobj"
+        # reverse the agent to be subject
+        if agent != -1 and by != -1:
+            sentence[agent].add_edge(Label("nsubj", src="passive"), predicate)
         
-        subj.add_edge(add_extra_info(subj_new_rel, "passive", prevs=subj_rel), predicate)
-    
+        # what kind of object to assign
+        # NOTE:
+        #   when there is csubjpass, there are also theoretical cases in which the relation shouldn't be object,
+        #   but xcomp/ccomp/advcl (depending on the markers). but I couldn't reproduce these cases so they have been removed
+        subj_new_rel = "dobj" if predicates_obj == -1 else "iobj"  # TODO: UDv1 = obj
 
-def convert_sentence(sentence, iids):
-    # The order of eud and eudpp is according to the order of the original CoreNLP.
-    # The extra are our enhancements in which been added where we thought it best.
-    
-    eud_correct_subj_pass(sentence)  # correctDependencies - correctSubjPass
+        # reverse the passivised subject
+        subj.add_edge(Label(subj_new_rel, src="passive"), predicate)
 
-    eudpp_process_simple_2wp(sentence)  # processMultiwordPreps: processSimple2WP
-    eudpp_process_complex_2wp(sentence)  # processMultiwordPreps: processComplex2WP
-    eudpp_process_3wp(sentence)  # processMultiwordPreps: process3WP
-    eudpp_demote_quantificational_modifiers(sentence)  # demoteQuantificationalModifiers
 
-    extra_nmod_advmod_reconstruction(sentence)
-    
-    extra_copula_reconstruction(sentence)
-    extra_evidential_reconstruction(sentence)
-    extra_aspectual_reconstruction(sentence)
-    extra_reported_evidentiality(sentence)
-    extra_fix_nmod_npmod(sentence)
-    extra_hyphen_reconstruction(sentence)
-
-    eudpp_expand_pp_or_prep_conjunctions(sentence)  # add copy nodes: expandPPConjunctions, expandPrepConjunctions
-
-    eud_passive_agent(sentence)  # addCaseMarkerInformation
-    eud_heads_of_conjuncts(sentence)  # treatCC
-    eud_prep_patterns(sentence)  # addCaseMarkerInformation
-    eud_conj_info(sentence)  # addConjInformation
-
-    extra_add_ref_and_collapse(sentence)
-    eudpp_add_ref_and_collapse(sentence)  # referent: addRef, collapseReferent
-
-    eud_subj_of_conjoined_verbs(sentence)  # treatCC
-    eud_xcomp_propagation(sentence)  # addExtraNSubj
-
-    extra_of_prep_alteration(sentence)
-    extra_compound_propagation(sentence)
-    extra_xcomp_propagation_no_to(sentence)
-    extra_advcl_propagation(sentence, iids)
-    extra_advcl_ambiguous_propagation(sentence, iids)
-    extra_acl_propagation(sentence)
-    extra_dep_propagation(sentence, iids)
-    extra_conj_propagation_of_nmods(sentence)
-    extra_conj_propagation_of_poss(sentence)
-    extra_advmod_propagation(sentence)
-    extra_appos_propagation(sentence)
-    extra_subj_obj_nmod_propagation_of_nmods(sentence)
-    extra_passive_alteration(sentence)
-    
-    return sentence
+# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #
 
 
-def override_funcs(enhanced, enhanced_plus_plus, enhanced_extra, remove_enhanced_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel):
+def remove_funcs(conversions, enhanced, enhanced_plus_plus, enhanced_extra, remove_enhanced_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel):
     if not enhanced:
-        funcs_to_cancel.update_funcs_by_prefix('eud_')
+        conversions = {conversion.name: conversion for conversion in conversions.values() if conversion.conv_type != ConvTypes.EUD}
     if not enhanced_plus_plus:
-        funcs_to_cancel.update_funcs_by_prefix('eudpp_')
+        conversions = {conversion.name: conversion for conversion in conversions.values() if conversion.conv_type != ConvTypes.EUDPP}
     if not enhanced_extra:
-        funcs_to_cancel.update_funcs_by_prefix('extra_')
+        conversions = {conversion.name: conversion for conversion in conversions.values() if conversion.conv_type != ConvTypes.BART}
     if remove_enhanced_extra_info:
-        funcs_to_cancel.update_funcs(['eud_passive_agent', 'eud_conj_info'])
+        conversions.pop('eud_conj_info')
+        conversions.pop('eud_prep_patterns')
     if remove_node_adding_conversions:
-        funcs_to_cancel.update_funcs(['eudpp_expand_pp_or_prep_conjunctions'])  # no need to cancel extra_inner_weak_modifier_verb_reconstruction as we have a special treatment there
+        # no need to cancel extra_inner_weak_modifier_verb_reconstruction as we have a special treatment there
+        conversions.pop('eudpp_expand_prep_conjunctions')
+        conversions.pop('eudpp_expand_pp_conjunctions')
     if remove_unc:
-        funcs_to_cancel.update_funcs(['extra_dep_propagation', 'extra_compound_propagation', 'extra_conj_propagation_of_poss', 'extra_conj_propagation_of_nmods', 'extra_advmod_propagation', 'extra_advcl_ambiguous_propagation'])
+        for func_name in ['extra_dep_propagation', 'extra_compound_propagation', 'extra_conj_propagation_of_poss', 'extra_conj_propagation_of_nmods_forward', 'extra_conj_propagation_of_nmods_backwards', 'extra_advmod_propagation', 'extra_advcl_ambiguous_propagation']:
+            conversions.pop(func_name)
     if query_mode:
-        all_funcs = ConvsCanceler.get_conversion_names()
-        all_funcs.difference_update(['extra_nmod_advmod_reconstruction', 'extra_copula_reconstruction', 'extra_evidential_reconstruction', 'extra_inner_weak_modifier_verb_reconstruction', 'extra_aspectual_reconstruction', 'eud_correct_subj_pass', 'eud_passive_agent', 'eud_conj_info', 'eud_prep_patterns', 'eudpp_process_simple_2wp', 'eudpp_process_complex_2wp', 'eudpp_process_3wp', 'eudpp_demote_quantificational_modifiers'])
-        funcs_to_cancel.update_funcs(all_funcs)
-    
-    funcs_to_cancel.override_funcs()
-
+        for func_name in conversions.keys():
+            if func_name in ['extra_nmod_advmod_reconstruction', 'extra_copula_reconstruction', 'extra_evidential_basic_reconstruction', 'extra_evidential_xcomp_reconstruction', 'extra_inner_weak_modifier_verb_reconstruction', 'extra_aspectual_reconstruction', 'eud_correct_subj_pass', 'eud_conj_info', 'eud_prep_patterns', 'eudpp_process_simple_2wp', 'eudpp_process_complex_2wp', 'eudpp_process_3wp', 'eudpp_demote_quantificational_modifiers']:
+                conversions.pop(func_name)
+    if funcs_to_cancel:
+        for func_to_cancel in funcs_to_cancel:
+            conversions.pop(func_to_cancel)
 
-def get_rel_set(converted_sentences):
-    return set([(head.get_conllu_field("form"), rel, tok.get_conllu_field("form")) for sent in converted_sentences for tok in sent.values() for (head, rel) in tok.get_new_relations()])
+    return conversions
 
 
-def on_last_iter_convs(sentence):
-    # TODO: after refactoring, if the match and replace system is more concise
-    #   maybe it would be better to simply check that the subject didnt cpme from an amod.
-    extra_amod_propagation(sentence)
-    return sentence
+def get_rel_set(converted_sentence):
+    return set([(str(head.get_conllu_field("id")), rel.to_str(), str(tok.get_conllu_field("id"))) for tok in converted_sentence
+                for (head, rels) in tok.get_new_relations() for rel in rels])
 
 
-def convert(parsed, enhanced, enhanced_plus_plus, enhanced_extra, conv_iterations, remove_enhanced_extra_info, remove_bart_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel):
-    global g_remove_enhanced_extra_info, g_remove_bart_extra_info, g_remove_node_adding_conversions
-    g_remove_enhanced_extra_info = remove_enhanced_extra_info
-    g_remove_bart_extra_info = remove_bart_extra_info
-    g_remove_node_adding_conversions = remove_node_adding_conversions
-    iids = dict()
-    
-    override_funcs(enhanced, enhanced_plus_plus, enhanced_extra, remove_enhanced_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel)
-    
-    # we iterate till convergence or till user defined maximum is reached - the first to come.
-    converted_sentences = parsed
+def convert_sentence(sentence: Sequence[Token], conversions, matcher: Matcher, conv_iterations: int):
+    last_converted_sentence = None
     i = 0
+    on_last_iter = ["extra_amod_propagation"]
+    do_last_iter = []
+    # we iterate till convergence or till user defined maximum is reached - the first to come.
     while i < conv_iterations:
-        last_converted_sentences = get_rel_set(converted_sentences)
-        temp = []
-        for sentence in converted_sentences:
-            temp.append(convert_sentence(sentence, iids))
-        converted_sentences = temp
-        if get_rel_set(converted_sentences) == last_converted_sentences:
+        last_converted_sentence = get_rel_set(sentence)
+        m = matcher(sentence)
+        for conv_name in m.names():
+            if conv_name in on_last_iter:
+                do_last_iter.append(conv_name)
+                continue
+            matches = m.matches_for(conv_name)
+            conversions[conv_name].transformation(sentence, matches)
+        if get_rel_set(sentence) == last_converted_sentence:
             break
         i += 1
 
-    # here we run some conversions that we believe should run only once and after all other conversions
-    temp = []
-    for sent in converted_sentences:
-        temp.append(on_last_iter_convs(sent))
-    converted_sentences = temp
-    
-    funcs_to_cancel.restore_funcs()
-    return converted_sentences, i
+    for conv_name in do_last_iter:
+        m = matcher(sentence)
+        matches = m.matches_for(conv_name)
+        conversions[conv_name].transformation(sentence, matches)
+        if get_rel_set(sentence) != last_converted_sentence:
+            i += 1
+
+    return i
+
+
+def init_conversions():
+    # TODO - update Full to each constraint
+
+    conversion_list = [
+        Conversion(ConvTypes.EUD, eud_correct_subj_pass_constraint, eud_correct_subj_pass),
+        Conversion(ConvTypes.EUDPP, eudpp_process_simple_2wp_constraint, eudpp_process_simple_2wp),
+        Conversion(ConvTypes.EUDPP, eudpp_process_complex_2wp_constraint, eudpp_process_complex_2wp),
+        Conversion(ConvTypes.EUDPP, eudpp_process_3wp_constraint, eudpp_process_3wp),
+        Conversion(ConvTypes.EUDPP, eudpp_demote_quantificational_modifiers_3w_constraint, eudpp_demote_quantificational_modifiers_3w),
+        Conversion(ConvTypes.EUDPP, eudpp_demote_quantificational_modifiers_2w_constraint, eudpp_demote_quantificational_modifiers_2w),
+        Conversion(ConvTypes.EUDPP, eudpp_demote_quantificational_modifiers_det_constraint, eudpp_demote_quantificational_modifiers_det),
+        Conversion(ConvTypes.BART, extra_nmod_advmod_reconstruction_constraint, extra_nmod_advmod_reconstruction),
+        Conversion(ConvTypes.BART, extra_copula_reconstruction_constraint, extra_copula_reconstruction),
+        Conversion(ConvTypes.BART, extra_evidential_xcomp_reconstruction_constraint, extra_evidential_xcomp_reconstruction),
+        Conversion(ConvTypes.BART, extra_evidential_basic_reconstruction_constraint, extra_evidential_basic_reconstruction),
+        Conversion(ConvTypes.BART, extra_aspectual_reconstruction_constraint, extra_aspectual_reconstruction),
+        Conversion(ConvTypes.BART, extra_reported_evidentiality_constraint, extra_reported_evidentiality),
+        Conversion(ConvTypes.BART, extra_fix_nmod_npmod_constraint, extra_fix_nmod_npmod),
+        Conversion(ConvTypes.BART, extra_hyphen_reconstruction_constraint, extra_hyphen_reconstruction),
+        Conversion(ConvTypes.EUD, eud_case_sons_of_conjuncts_constraint, eud_case_sons_of_conjuncts),
+        Conversion(ConvTypes.EUDPP, eudpp_expand_pp_conjunctions_constraint, eudpp_expand_pp_conjunctions),
+        Conversion(ConvTypes.EUDPP, eudpp_expand_prep_conjunctions_constraint, eudpp_expand_prep_conjunctions),
+        Conversion(ConvTypes.EUD, eud_heads_of_conjuncts_constraint, eud_heads_of_conjuncts),
+        Conversion(ConvTypes.EUD, eud_prep_patterns_constraint, eud_prep_patterns),
+        Conversion(ConvTypes.EUD, eud_conj_info_constraint, eud_conj_info),
+        Conversion(ConvTypes.EUDPP, eudpp_add_ref_and_collapse_constraint, eudpp_add_ref_and_collapse),
+        Conversion(ConvTypes.BART, extra_add_ref_and_collapse_constraint, extra_add_ref_and_collapse),
+        Conversion(ConvTypes.EUD, eud_subj_of_conjoined_verbs_constraint, eud_subj_of_conjoined_verbs),
+        Conversion(ConvTypes.EUD, extra_xcomp_propagation_constraint, eud_xcomp_propagation),
+        Conversion(ConvTypes.BART, extra_of_prep_alteration_constraint, extra_of_prep_alteration),
+        Conversion(ConvTypes.BART, extra_compound_propagation_constraint, extra_compound_propagation),
+        Conversion(ConvTypes.BART, extra_xcomp_propagation_no_to_constraint, extra_xcomp_propagation_no_to),
+        Conversion(ConvTypes.BART, extra_advcl_propagation_constraint, extra_advcl_propagation),
+        Conversion(ConvTypes.BART, extra_acl_to_propagation_constraint, extra_acl_to_propagation),
+        Conversion(ConvTypes.BART, extra_acl_propagation_constraint, extra_acl_propagation),
+        Conversion(ConvTypes.BART, extra_dep_propagation_constraint, extra_dep_propagation),
+        Conversion(ConvTypes.BART, extra_conj_propagation_of_nmods_backwards_constraint, extra_conj_propagation_of_nmods_backwards),
+        Conversion(ConvTypes.BART, extra_conj_propagation_of_nmods_forward_constraint, extra_conj_propagation_of_nmods_forward),
+        Conversion(ConvTypes.BART, extra_conj_propagation_of_poss_constraint, extra_conj_propagation_of_poss),
+        Conversion(ConvTypes.BART, extra_advmod_propagation_constraint, extra_advmod_propagation),
+        Conversion(ConvTypes.BART, extra_appos_propagation_constraint, extra_appos_propagation),
+        Conversion(ConvTypes.BART, extra_subj_obj_nmod_propagation_of_nmods_constraint, extra_subj_obj_nmod_propagation_of_nmods),
+        Conversion(ConvTypes.BART, extra_passive_alteration_constraint, extra_passive_alteration),
+        Conversion(ConvTypes.BART, extra_amod_propagation_constraint, extra_amod_propagation)
+    ]
+    return {conversion.name: conversion for conversion in conversion_list}
+
+
+def convert(parsed, enhanced, enhanced_plus_plus, enhanced_extra, conv_iterations, remove_enhanced_extra_info,
+            remove_bart_extra_info, remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel, ud_version=1):
+    global g_remove_node_adding_conversions, g_iids, g_ud_version
+    pybart_globals.g_remove_enhanced_extra_info = remove_enhanced_extra_info
+    pybart_globals.g_remove_bart_extra_info = remove_bart_extra_info
+    g_remove_node_adding_conversions = remove_node_adding_conversions
+    g_ud_version = ud_version
+
+    conversions = init_conversions()
+    remove_funcs(conversions, enhanced, enhanced_plus_plus, enhanced_extra, remove_enhanced_extra_info,
+                 remove_node_adding_conversions, remove_unc, query_mode, funcs_to_cancel)
+    matcher = Matcher([NamedConstraint(conversion_name, conversion.constraint)
+                       for conversion_name, conversion in conversions.items()])
+
+    i = 0
+    updated = []
+    for sentence in parsed:
+        g_iids = dict()
+        sentence_as_list = [t for t in sentence if t.get_conllu_field("id").major != 0]
+        assign_ccs_to_conjs(sentence_as_list)
+        i = max(i, convert_sentence(sentence_as_list, conversions, matcher, conv_iterations))
+        updated.append(sentence_as_list)
+
+    return updated, i
diff --git a/pybart/graph_token.py b/pybart/graph_token.py
index 563f2a7..cb7e610 100644
--- a/pybart/graph_token.py
+++ b/pybart/graph_token.py
@@ -1,15 +1,62 @@
-import re
+from dataclasses import dataclass, field
+from .pybart_globals import *
 
 
-class Token(object):
+@dataclass
+class TokenId:
+    major: int
+    minor: int = 0
+    token_str: str = field(init=False)
+
+    def __post_init__(self):
+        object.__setattr__(self, 'token_str', f"{self.major}.{self.minor}" if self.minor else f"{self.major}")
+
+    def __str__(self):
+        return self.token_str
+
+    def __lt__(self, other):
+        return self.major < other.major or (self.major == other.major and self.minor < other.minor)
+
+@dataclass
+class Label:
+    base: str
+    eud: str = None
+    src: str = None
+    src_type: str = None
+    phrase: str = None
+    uncertain: bool = False
+    iid: int = None
+
+    def to_str(self, no_bart=False):
+        eud = ""
+        if self.eud is not None and not g_remove_enhanced_extra_info:
+            eud = ":" + self.eud
+
+        bart = ""
+        if not no_bart:
+            if self.src is not None and not g_remove_bart_extra_info:
+                iid_str = "" if self.iid is None else "#" + str(self.iid)
+                dep_args = ", ".join(x for x in filter(None, [self.src_type, self.phrase, "UNC" if self.uncertain else None]))
+                bart = "@" + self.src + "(" + dep_args + ")" + iid_str
+
+        return self.base + eud + bart
+
+    def with_no_bart(self):
+        return self.to_str(no_bart=True)
+
+    # operator overloading: less than
+    def __lt__(self, other):
+        return self.to_str() < other.to_str()
+
+
+class Token:
     def __init__(self, new_id, form, lemma, upos, xpos, feats, head, deprel, deps, misc):
         # format of CoNLL-U as described here: https://universaldependencies.org/format.html
         self._conllu_info = {"id": new_id, "form": form, "lemma": lemma, "upos": upos, "xpos": xpos,
                              "feats": feats, "head": head, "deprel": deprel, "deps": deps, "misc": misc}
         self._children_list = []
         self._new_deps = dict()
-        self._extra_info_edges = dict()
-    
+
     def copy(self, new_id=None, form=None, lemma=None, upos=None, xpos=None, feats=None, head=None, deprel=None, deps=None, misc=None):
         new_id_copy, form_copy, lemma_copy, upos_copy, xpos_copy, feats_copy, head_copy, deprel_copy, deps_copy, misc_copy = self._conllu_info.values()
         return Token(new_id if new_id else new_id_copy,
@@ -33,12 +80,13 @@ def get_children(self):
         return self._children_list
     
     def get_children_with_rels(self):
-        return [(child, relation[1]) for child in self.get_children() for relation in child.get_new_relations(self)]
-    
+        return [(child, list(child.get_new_relations(self))[0][1]) for child in self.get_children()]
+
     def get_conllu_string(self):
         # for 'deps' field, we need to sort the new relations and then add them with '|' separation,
         # as required by the format.
-        self._conllu_info["deps"] = "|".join([str(a.get_conllu_field('id')) + ":" + b for (a, b) in sorted(self.get_new_relations())])
+        sorted_ = sorted((h, sorted(rels)) for h, rels in self.get_new_relations())
+        self._conllu_info["deps"] = "|".join([str(a.get_conllu_field('id')) + ":" + bb.to_str() for (a, b) in sorted_ for bb in b])
         return "\t".join([str(v) for v in self._conllu_info.values()])
     
     def set_conllu_field(self, field, val):
@@ -46,40 +94,21 @@ def set_conllu_field(self, field, val):
     
     def get_conllu_field(self, field):
         return self._conllu_info[field]
-    
-    def is_root_node(self):
-        return 0 == self.get_conllu_field('id')
-    
-    def is_root_rel(self):
-        # TODO - maybe we want to validate here (or/and somewhere else) that a root is an only parent
-        return 0 in [parent.get_conllu_field('id') for parent in self.get_parents()]
-    
+
     def get_parents(self):
         return self._new_deps.keys()
     
-    def get_extra_info_edges(self):
-        return self._extra_info_edges
-    
     def get_new_relations(self, given_head=None):
-        new_deps_pairs = []
-        for head, edges in self._new_deps.items():
-            # having more than one edge should really never happen
-            for edge in edges:
-                if not given_head or given_head == head:
-                    new_deps_pairs.append((head, edge))
-        
-        return new_deps_pairs
-    
-    def match_rel(self, str_to_match, head):
-        ret = []
-        # having more than one edge should really never happen
-        for edge in self._new_deps[head]:
-            m = re.match(str_to_match, edge)
-            if m:
-                ret.append(edge)
-        return ret
-    
-    def add_edge(self, rel, head, extra_info=None):
+        if given_head:
+            if given_head in self._new_deps:
+                return [(given_head, self._new_deps[given_head])]
+            else:
+                return dict().items()
+        else:
+            return self._new_deps.items()
+
+    def add_edge(self, rel, head):
+        assert isinstance(rel, Label)
         if head in self._new_deps:
             if rel in self._new_deps[head]:
                 return
@@ -87,21 +116,17 @@ def add_edge(self, rel, head, extra_info=None):
         else:
             self._new_deps[head] = [rel]
             head.add_child(self)
-        if extra_info:
-            self._extra_info_edges[(head, rel)] = extra_info
     
     def remove_edge(self, rel, head):
+        assert isinstance(rel, Label)
         if head in self._new_deps and rel in self._new_deps[head]:
             self._new_deps[head].remove(rel)
             if not self._new_deps[head]:
                 self._new_deps.pop(head)
                 head.remove_child(self)
-            if (head, rel) in self._extra_info_edges:
-                self._extra_info_edges.pop((head, rel))
     
     def remove_all_edges(self):
-        for head, edge in self.get_new_relations():
-            self.remove_edge(edge, head)
+        _ = [self.remove_edge(edge, head) for head, edges in list(self.get_new_relations()) for edge in edges]
     
     def replace_edge(self, old_rel, new_rel, old_head, new_head):
         self.remove_edge(old_rel, old_head)
@@ -110,9 +135,6 @@ def replace_edge(self, old_rel, new_rel, old_head, new_head):
     # operator overloading: less than
     def __lt__(self, other):
         return self.get_conllu_field('id') < other.get_conllu_field('id')
-    
-    def dist(self, other):
-        return other.get_conllu_field('id') - self.get_conllu_field('id')
 
 
 def add_basic_edges(sentence):
@@ -121,11 +143,8 @@ def add_basic_edges(sentence):
     Args:
         (dict) The parsed sentence.
     """
-    for (cur_id, token) in sentence.items():
-        if cur_id == 0:
-            continue
-        
+    for (cur_id, token) in enumerate(sentence):
         # add the relation
         head = token.get_conllu_field('head')
-        if head != "_":
-            sentence[cur_id].add_edge(token.get_conllu_field('deprel'), sentence[token.get_conllu_field('head')])
+        if head is not None:
+            sentence[cur_id].add_edge(Label(token.get_conllu_field('deprel')), sentence[head.major - 1])
diff --git a/pybart/matcher.py b/pybart/matcher.py
index 07d03f4..3463941 100644
--- a/pybart/matcher.py
+++ b/pybart/matcher.py
@@ -1,141 +1,363 @@
-import re
-from collections import namedtuple
+# usage example:
+# conversions = [SomeConversion, SomeConversion1, ...]
+# matcher = Matcher(NamedConstraint(conversion.get_name(), conversion.get_constraint()) for conversion in conversions)
+#
+# def convert_sentence(sentence, matcher) -> sentence:
+#     while … # till convergence of the sentence
+#         m = matcher(doc)
+#         for conv_name in m.names():
+#             matches = m.matches_for(conv_name)
+#             transform(matches…) # TBD
+#         sentence = ...
+#
+# for doc in docs:
+#     convert_sentence(sentence, matcher)
 
+from dataclasses import replace
+from collections import defaultdict
+from typing import NamedTuple, Sequence, Mapping, Any, List, Tuple, Generator, Dict, Optional
+from .constraints import *
+from .graph_token import Token as BartToken
 
-# TODO - old matcher's restriction, should be removed
-fields = ('name', 'gov', 'no_sons_of', 'form', 'lemma', 'xpos', 'follows', 'followed_by', 'diff', 'nested')
-Restriction = namedtuple('Restriction', fields, defaults=(None,) * len(fields))
 
+# ********************************************* BartSentence functionality *********************************************
 
-# ----------------------------------------- matching functions ----------------------------------- #
-# TODO - when writing the new matcher: same node cannot take two roles in structure.
 
+field_by_field = {FieldNames.WORD: "form", FieldNames.TAG: "xpos", FieldNames.LEMMA: "lemma"}
 
-def named_nodes_restrictions(restriction, named_nodes):
-    if restriction.name:
-        child, _, _ = named_nodes[restriction.name]
-    else:
+
+def get_content_by_field(tok: BartToken, cur_field: FieldNames) -> str:
+    return tok.get_conllu_field(field_by_field[cur_field])
+
+
+# gets the verbatim of a token in position i in the sentence
+def get_text(sentence: Sequence[BartToken], i: int) -> str:
+    return sentence[i].get_conllu_field("form")
+
+
+def get_children_count(sentence: Sequence[BartToken], parent: int) -> int:
+    return len(sentence[parent].get_children())
+
+
+# returns a string list of labels connecting child to parent (or incoming/outgoing to/from child/parent respectively
+def get_labels(sentence: Sequence[BartToken], child: int = None, parent: int = None) -> List[str]:
+    labels = []
+    if child is not None:
+        if parent is not None:
+            parent = sentence[parent]
+        labels = [rel.base for _, rels in sentence[child].get_new_relations(given_head=parent) for rel in rels]
+    elif parent is not None:
+        labels = [rel.base for _, rels in sentence[parent].get_children_with_rels() for rel in rels]
+
+    return labels
+
+
+# function that checks that a sequence of Label constraints is satisfied
+def get_matched_labels(label_constraints: Sequence[LabelPresence], actual_labels: List[str]) -> Optional[Set[str]]:
+    successfully_matched = set()
+    # we need to satisfy all constraints in the sequence, so if one fails, return None
+    for constraint in label_constraints:
+        satisfied_labels = constraint.satisfied(actual_labels)
+        if satisfied_labels is None:
+            return None
+        successfully_matched.update(satisfied_labels)
+    return successfully_matched
+
+
+# ****************************************************** Matcher *******************************************************
+
+class MatchingResult:
+    def __init__(self, name2index: Mapping[str, int], indices2label: Mapping[Tuple[int, int], Set[str]]):
+        self.name2index = name2index
+        self.indices2label = indices2label
+
+    # return the index of the specific matched token in the sentence according to its name
+    def token(self, name: str) -> int:
+        # since optional tokens can have no match, thus should be legitimate to get here their names
+        # so we return -1 to inform this
+        return self.name2index.get(name, -1)
+
+    # return the set of captured labels between the two tokens, given their indices
+    def edge(self, t1: int, t2: int) -> Set[str]:
+        return self.indices2label.get((t1, t2), [])
+
+
+class GlobalMatcher:
+    def __init__(self, constraint: Full):
+        self.constraint = constraint
+        self.captured_labels = defaultdict(set)
+        # list of token ids that don't require a capture
+        self.dont_capture_names = [token.id for token in constraint.tokens if not token.capture]
+
+    # filter a single match group according to distance constraints
+    def _filter_distance_constraints(self, match: Mapping[str, int]) -> bool:
+        # check that the distance between the tokens is not more than or exactly as required
+        for distance in self.constraint.distances:
+            # Note - we assume that if a token is not in the match dict, then it was an optional one,
+            #   and thus we can skip on this distance constraint
+            if distance.token1 not in match or distance.token2 not in match:
+                continue
+            calculated_distance = match[distance.token2] - match[distance.token1] - 1
+            if not distance.satisfied(calculated_distance):
+                return False
         return True
-    
-    if restriction.follows:
-        follows, _, _ = named_nodes[restriction.follows]
-        if child.get_conllu_field('id') - 1 != follows.get_conllu_field('id'):
-            return False
-    
-    if restriction.followed_by:
-        followed, _, _ = named_nodes[restriction.followed_by]
-        if child.get_conllu_field('id') + 1 != followed.get_conllu_field('id'):
-            return False
-    
-    if restriction.diff:
-        diff, _, _ = named_nodes[restriction.diff]
-        if child == diff:
-            return False
-    
-    return True
-
-
-def match_child(child, restriction, head):
-    if restriction.form:
-        if child.is_root_node() or not re.match(restriction.form, child.get_conllu_field('form')):
-            return
-    
-    if restriction.lemma:
-        if child.is_root_node() or not re.match(restriction.lemma, child.get_conllu_field('lemma')):
-            return
-    
-    if restriction.xpos:
-        if child.is_root_node() or not re.match(restriction.xpos, child.get_conllu_field('xpos')):
-            return
-    
-    # if no head (first level words)
-    relations = [None]
-    if restriction.gov:
-        relations = child.match_rel(restriction.gov, head)
-        if len(relations) == 0:
-            return
-    elif head:
-        relations = [b for a, b in child.get_new_relations(head)]
-    
-    if restriction.no_sons_of:
-        if False in [len(grandchild.match_rel(restriction.no_sons_of, child)) == 0 for grandchild in
-                     child.get_children()]:
-            return
-    
-    nested = []
-    if restriction.nested:
-        nested = match(child.get_children(), restriction.nested, child)
-        if nested is None:
+
+    # filter a single match group according to concat constraints
+    def _filter_concat_constraints(self, match: Mapping[str, int], sentence: Sequence[BartToken]) -> bool:
+        # check for a two-word or three-word phrase match in a given phrases list
+        for concat in self.constraint.concats:
+            token_names = concat.get_token_names()
+            # Note - we assume that if a token is not in the match dict, then it was an optional one,
+            #   and thus we can skip on this concat constraint
+            if len(set(concat.get_token_names()).difference(match)) > 0:
+                continue
+            word_indices = [match[token_name] for token_name in token_names]
+            if not concat.satisfied("_".join(get_text(sentence, w) for w in word_indices)):
+                return False
+        return True
+
+    @staticmethod
+    def _try_merge(base_assignment: Mapping[str, int], new_assignment: Mapping[str, int]) -> Mapping[str, int]:
+        # try to merge two assignment if they do not contradict
+        for k, v in new_assignment.items():
+            if v != base_assignment.get(k, v):
+                return {}
+        merged_assignment = {**base_assignment, **new_assignment}
+        # a merge shouldnt consist of duplicate values, so remove it
+        if len(set(merged_assignment.values())) < len(merged_assignment.values()):
+            return {}
+        return merged_assignment
+
+    def _filter_edge_constraints(self, matches: Mapping[str, List[int]], sentence: Sequence[BartToken]) \
+            -> List[Tuple[bool, List[Dict[str, int]]]]:
+        edges_assignments = list()
+        # pick possible assignments according to the edge constraint
+        for edge in self.constraint.edges:
+            edge_assignments = []
+            # try each pair as a candidate
+            # Note - we assume that if a token is not in the matches dict, then it was an optional one,
+            #   and thus we can skip on this edge constraint
+            # Note2 - we assume that if a node is not mentioned in any edge constraint,
+            #   then it is a redundant token-constraint
+            for child in matches.get(edge.child, []):
+                for parent in matches.get(edge.parent, []):
+                    not_optional = True
+                    if child == parent:
+                        continue
+                    # check if edge constraint is satisfied
+                    captured_labels = None
+                    actual_labels = get_labels(sentence, child=child, parent=parent)
+                    if actual_labels:
+                        captured_labels = get_matched_labels(edge.label, actual_labels)
+                    if captured_labels is None:
+                        continue
+                    # TODO - compare the speed of non-edge filtering here to the current post-merging location:
+                    #   "if self._filter(assignment, sentence)"
+                    # store all captured labels according to the child-parent token pair
+                    self.captured_labels[(edge.child, child, edge.parent, parent)].update(captured_labels)
+                    # keep the filtered assignment for further merging
+                    edge_assignments.append({edge.child: child, edge.parent: parent})
+            if edge_assignments:
+                edges_assignments.append((edge.optional, edge_assignments))
+            elif not edge.optional:
+                return []
+        return edges_assignments
+
+    @staticmethod
+    def _merge_edges_assignments(edges_assignments: List[Tuple[bool, List[Dict[str, int]]]]) -> List[Dict[str, int]]:
+        merges = []
+        # for each list of possible assignments of an edge
+        for edge_is_optional, edge_assignments in edges_assignments:
+            new_merges = []
+            # for each merged assignment. (we need an empty dictionary for the first cycle to start with)
+            for merged in (merges if merges else [{}]):
+                # for each possible assignment in the current list
+                edge_added = False
+                for assignment in edge_assignments:
+                    # try to merge (see that there is no contradiction on hashing)
+                    just_merged = GlobalMatcher._try_merge(merged, assignment)
+                    if just_merged:
+                        edge_added = True
+                        new_merges.append(just_merged)
+                # this is in case we couldnt merge any new assignment of an optional edge to an existing merge,
+                # we simply add the merge as is
+                if not edge_added and edge_is_optional:
+                    new_merges.append(merged)
+            if not new_merges:
+                return []
+            merges = new_merges
+
+        return merges
+
+    def apply(self, matches: Mapping[str, List[int]], sentence: Sequence[BartToken]) \
+            -> Generator[MatchingResult, None, None]:
+        filtered = self._filter_edge_constraints(matches, sentence)
+        merges = self._merge_edges_assignments(filtered)
+
+        for merged_assignment in merges:
+            # TODO - compare the speed of non-edge filtering here to the on-going edge filter (see previous todo)
+            if self._filter_distance_constraints(merged_assignment) and \
+                    self._filter_concat_constraints(merged_assignment, sentence):
+                # keep only required captures
+                _ = [merged_assignment.pop(name, None) for name in self.dont_capture_names]
+                captured_labels = {(v1, v2): labels for (k1, v1, k2, v2), labels in self.captured_labels.items()
+                                   if k1 in merged_assignment and merged_assignment[k1] == v1 and
+                                   k2 in merged_assignment and merged_assignment[k2] == v2}
+                # append assignment to output
+                yield MatchingResult(merged_assignment, captured_labels)
+
+
+class TokenMatcher:
+    def __init__(self, constraints: Sequence[Token]):
+        # store the no_children/incoming/outgoing token constraints according to the token id for post spacy matching
+        self.no_children = dict()
+        self.incoming_constraints = dict()
+        self.outgoing_constraints = dict()
+        self.spec_constraints = dict()
+        self.required_tokens = set()
+        for constraint in constraints:
+            self.no_children[constraint.id] = constraint.no_children
+            self.incoming_constraints[constraint.id] = constraint.incoming_edges
+            self.outgoing_constraints[constraint.id] = constraint.outgoing_edges
+            self.spec_constraints[constraint.id] = constraint.spec
+            if not constraint.optional:
+                self.required_tokens.add(constraint.id)
+
+    def _post_local_matcher(self, matched_tokens: Mapping[str, List[int]], sentence: Sequence[BartToken]) \
+            -> Mapping[str, List[int]]:
+        # handles incoming and outgoing label constraints (still in token level)
+        checked_tokens = defaultdict(list)
+        for name, token_indices in matched_tokens.items():
+            for token in token_indices:
+                if self.no_children[name] and (get_children_count(sentence, token) != 0):
+                    continue
+                if self.outgoing_constraints[name]:
+                    if get_matched_labels(self.outgoing_constraints[name], get_labels(sentence, parent=token)) is None:
+                        continue
+                if self.incoming_constraints[name]:
+                    if get_matched_labels(self.incoming_constraints[name], get_labels(sentence, child=token)) is None:
+                        continue
+                # TODO - consider adding a 'token in self.required_tokens' validation here for optimization
+                checked_tokens[name].append(token)
+        return checked_tokens
+
+    def _match_tokens(self, sentence: Sequence[BartToken]) -> Mapping[str, List[int]]:
+        matched_tokens = defaultdict(list)
+
+        for con_name, field_cons in self.spec_constraints.items():
+            if not field_cons:
+                matched_tokens[con_name] = list(range(len(sentence)))
+                continue
+            for i, tok in enumerate(sentence):
+                satisfied_tok = True
+                for field_con in field_cons:
+                    if not field_con.satisfied(tok, get_content_by_field):
+                        satisfied_tok = False
+                        break
+                if satisfied_tok:
+                    matched_tokens[con_name].append(i)
+
+        return matched_tokens
+
+    def apply(self, sentence: Sequence[BartToken]) -> Optional[Mapping[str, List[int]]]:
+        # match tokens according to their basic per-token/local features
+        matched_tokens = self._match_tokens(sentence)
+
+        # extra token matching out of spacy's scope
+        matched_tokens = self._post_local_matcher(matched_tokens, sentence)
+
+        # reverse validate the 'optional' constraint
+        if len(self.required_tokens.difference(set(matched_tokens.keys()))) != 0:
+            return None
+
+        return matched_tokens
+
+
+class Match:
+    def __init__(self, token_matchers: Mapping[str, TokenMatcher],
+                 global_matchers: Mapping[str, GlobalMatcher], sentence: Sequence[BartToken]):
+        assert token_matchers.keys() == global_matchers.keys()
+        self.token_matchers = token_matchers
+        self.global_matchers = global_matchers
+        self.sentence = sentence
+
+    def names(self) -> List[str]:
+        # return constraint-name list
+        return list(self.token_matchers.keys())
+
+    def matches_for(self, name: str) -> Generator[MatchingResult, None, None]:
+        # token match
+        matches = self.token_matchers[name].apply(self.sentence)
+        if matches is None:
             return
-    
-    if restriction.name:
-        ret = []
-        for rel in relations:
-            if nested:
-                for d in nested:
-                    d[restriction.name] = (child, head, rel)
-                    ret.append(d)
-            else:
-                ret.append(dict({restriction.name: (child, head, rel)}))
-        return ret
-    
-    return nested
-    
-
-def match_rest(children, restriction, head):
-    ret = []
-    restriction_satisfied = False
-    for child in children:
-        child_ret = match_child(child, restriction, head)
-        
-        # we check to see for None because empty list is a legit return value
-        if child_ret is None:
+
+        # filter
+        yield from self.global_matchers[name].apply(matches, self.sentence)
+
+
+class NamedConstraint(NamedTuple):
+    name: str
+    constraint: Full
+
+
+# add TokenConstraints based on the other non-token constraints (optimization step).
+def preprocess_constraint(constraint: Full) -> Full:
+    # for each edge store the labels that could be filtered as incoming or outgoing token constraints
+    #   in the parent or child accordingly
+    outs = defaultdict(list)
+    ins = defaultdict(list)
+    for edge in constraint.edges:
+        # skip HasNoLabel as they check for non existing label between two nodes,
+        #   and if we add a token constraint it would be to harsh
+        if isinstance(edge.label, HasNoLabel):
             continue
-        else:
-            restriction_satisfied = True
-        ret += child_ret
-    
-    if not restriction_satisfied:
-        return None
-    return ret
-
-
-def match_rl(children, restriction_list, head):
-    ret = []
-    for restriction in restriction_list:
-        rest_ret = match_rest(children, restriction, head)
-        
-        # if one restriction was violated, return empty list.
-        if rest_ret is None:
-            return None
-        
-        # every new rest_ret should be merged to any previous rest_ret
-        ret = rest_ret if not ret else \
-            [{**ns_ret, **ns_rest_ret} for ns_rest_ret in rest_ret for ns_ret in ret]
-        # fix ret in case we have two identical name_spaces
-        ret = [r for i, r in enumerate(ret) if r not in ret[i + 1:]]
-        
-        ret_was_empty_beforehand = False
-        if not ret:
-            ret_was_empty_beforehand = True
-        for named_nodes in ret:
-            # this is done here because we want to check cross restrictions
-            # TODO - move the following information from here:
-            #   rules regarding the usage of non graph restrictions (follows, followed_by, diff):
-            #   1. must be after sibling rest's that they refer to
-            #       or in the outer rest of a nested that they refer to
-            #   2. must have names for themselves
-            if not named_nodes_restrictions(restriction, named_nodes):
-                ret.remove(named_nodes)
-        if (not ret) and (not ret_was_empty_beforehand):
-            return None
-    
-    return ret
-    
-
-def match(children, restriction_lists, head=None):
-    for restriction_list in restriction_lists:
-        ret = match_rl(children, restriction_list, head)
-        if ret is not None:
-            return ret
-    return
+        for tok in constraint.tokens:
+            # we dont want to apply a constraint that came from an optional token, on a required token
+            if tok.id == edge.child and not tok.optional:
+                outs[edge.parent].extend(list(edge.label))
+            if tok.id == edge.parent and not tok.optional:
+                ins[edge.child].extend(list(edge.label))
+
+    # for each concat store the single words of the concat
+    #   with their correspondent token for token level WORD constraint
+    words = defaultdict(set)
+    for concat in constraint.concats:
+        zipped_concat = list(zip(*[tuple(t.split("_")) for t in concat.tuple_set]))
+        if isinstance(concat, TokenPair) or isinstance(concat, TokenTriplet):
+            words[concat.token1].update(set(zipped_concat[0]))
+            words[concat.token2].update(set(zipped_concat[1]))
+        if isinstance(concat, TokenTriplet):
+            words[concat.token3].update(set(zipped_concat[2]))
+
+    # rebuild the constraint (as it is immutable)
+    tokens = []
+    for token in constraint.tokens:
+        incoming_edges = list(token.incoming_edges) + ins.get(token.id, [])
+        outgoing_edges = (list(token.outgoing_edges) + outs.get(token.id, [])) if not token.no_children else []
+        # add the word constraint to an existing WORD field if exists
+        word_fields = [replace(s, value=list(words.get(token.id, set()).union(s.value)))
+                       for s in token.spec if s.field == FieldNames.WORD]
+        if (len(word_fields) == 0) and (token.id in words):
+            # simply create a new WORD field
+            word_fields = [Field(FieldNames.WORD, list(words[token.id]))]
+        # attach the replaced/newly-formed word constraint to the rest of the token spec
+        spec = [s for s in token.spec if s.field != FieldNames.WORD] + word_fields
+        tokens.append(replace(token, spec=spec, incoming_edges=incoming_edges, outgoing_edges=outgoing_edges))
+    return replace(constraint, tokens=tokens)
+
+
+class Matcher:
+    def __init__(self, constraints: Sequence[NamedConstraint]):
+        self.token_matchers = dict()
+        self.global_matchers = dict()
+        for constraint in constraints:
+            # preprocess the constraints (optimizations)
+            preprocessed_constraint = preprocess_constraint(constraint.constraint)
+
+            # initialize internal matchers
+            self.token_matchers[constraint.name] = TokenMatcher(preprocessed_constraint.tokens)
+            self.global_matchers[constraint.name] = GlobalMatcher(preprocessed_constraint)
+
+    # apply the matching process on a given sentence
+    def __call__(self, sentence: Sequence[BartToken]) -> Match:
+        return Match(self.token_matchers, self.global_matchers, sentence)
diff --git a/pybart/new_matcher.py b/pybart/new_matcher.py
deleted file mode 100644
index aee1205..0000000
--- a/pybart/new_matcher.py
+++ /dev/null
@@ -1,324 +0,0 @@
-# TODO - define the Graph class we will work with as sentence
-# usage example:
-# conversions = [SomeConversion, SomeConversion1, ...]
-# matcher = Matcher(NamedConstraint(conversion.get_name(), conversion.get_constraint()) for conversion in conversions)
-#
-# def convert_sentence(sentence, matcher) -> sentence:
-#     while … # till convergence of the sentence
-#         m = matcher(doc)
-#         for conv_name in m.names():
-#             matches = m.matches_for(conv_name)
-#             transform(matches…) # TBD
-#         sentence = ...
-#
-# for doc in docs:
-#     convert_sentence(sentence, matcher)
-
-from dataclasses import replace
-from collections import defaultdict
-from typing import NamedTuple, Sequence, Mapping, Any, List, Tuple, Generator, Dict, Optional
-from spacy.vocab import Vocab
-from spacy.matcher import Matcher as SpacyMatcher
-from .constraints import *
-
-
-# function that checks that a sequence of Label constraints is satisfied
-def get_matched_labels(label_constraints: Sequence[Label], actual_labels: List[str]) -> Optional[Set[str]]:
-    successfully_matched = set()
-    # we need to satisfy all constraints in the sequence, so if one fails, return None
-    # TODO - optional optimization step:
-    #   _ = [successfully_matched.update(constraint.satisfied(actual_labels)) for constraint in label_constraints]
-    for constraint in label_constraints:
-        satisfied_labels = constraint.satisfied(actual_labels)
-        if satisfied_labels is None:
-            return None
-        successfully_matched.update(satisfied_labels)
-    return successfully_matched
-
-
-class MatchingResult:
-    def __init__(self, name2index: Mapping[str, int], indices2label: Mapping[Tuple[int, int], Set[str]]):
-        self.name2index = name2index
-        self.indices2label = indices2label
-    
-    # return the index of the specific matched token in the sentence according to its name
-    def token(self, name: str) -> int:
-        # since optional tokens can have no match, thus should be legitimate to get here their names
-        # so we return -1 to inform this
-        return self.name2index.get(name, -1)
-    
-    # return the set of captured labels between the two tokens, given their indices
-    def edge(self, t1: int, t2: int) -> Set[str]:
-        return self.indices2label.get((t1, t2), None)
-
-
-class GlobalMatcher:
-    def __init__(self, constraint: Full):
-        self.constraint = constraint
-        self.captured_labels = defaultdict(set)
-        # list of token ids that don't require a capture
-        self.dont_capture_names = [token.id for token in constraint.tokens if not token.capture]
-    
-    # filter a single match group according to distance constraints
-    def _filter_distance_constraints(self, match: Mapping[str, int]) -> bool:
-        # check that the distance between the tokens is not more than or exactly as required
-        for distance in self.constraint.distances:
-            # Note - we assume that if a token is not in the match dict, then it was an optional one,
-            #   and thus we can skip on this distance constraint
-            if distance.token1 not in match or distance.token2 not in match:
-                continue
-            # TODO - why would we need sentence.index
-            calculated_distance = match[distance.token2] - match[distance.token1] - 1
-            if not distance.satisfied(calculated_distance):
-                return False
-        return True
-
-    # filter a single match group according to concat constraints
-    def _filter_concat_constraints(self, match: Mapping[str, int], sentence) -> bool:
-        # check for a two-word or three-word phrase match in a given phrases list
-        for concat in self.constraint.concats:
-            token_names = concat.get_token_names()
-            # Note - we assume that if a token is not in the match dict, then it was an optional one,
-            #   and thus we can skip on this concat constraint
-            if len(set(concat.get_token_names()).difference(match)) > 0:
-                continue
-            word_indices = [match[token_name] for token_name in token_names]
-            if not concat.satisfied("_".join(sentence.get_text(w) for w in word_indices)):
-                return False
-        return True
-
-    @staticmethod
-    def _try_merge(base_assignment: Mapping[str, int], new_assignment: Mapping[str, int]) -> Mapping[str, int]:
-        # try to merge two assignment if they do not contradict
-        for k, v in new_assignment.items():
-            if v != base_assignment.get(k, v):
-                return {}
-        return {**base_assignment, **new_assignment}
-
-    def _filter_edge_constraints(self, matches: Mapping[str, List[int]], sentence) -> List[List[Dict[str, int]]]:
-        edges_assignments = list()
-        # pick possible assignments according to the edge constraint
-        for edge in self.constraint.edges:
-            edge_assignments = []
-            # try each pair as a candidate
-            # Note - we assume that if a token is not in the matches dict, then it was an optional one,
-            #   and thus we can skip on this edge constraint
-            # Note2 - we assume that if a node is not mentioned in any edge constraint,
-            #   then it is a redundant token-constraint (TODO - validate this assumption)
-            for child in matches.get(edge.child, []):
-                for parent in matches.get(edge.parent, []):
-                    # check if edge constraint is satisfied
-                    captured_labels = \
-                        get_matched_labels(edge.label, sentence.get_labels(child=child, parent=parent))
-                    if captured_labels is None:
-                        continue
-                    # TODO - compare the speed of non-edge filtering here to the current post-merging location:
-                    #   "if self._filter(assignment, sentence)"
-                    # store all captured labels according to the child-parent token pair
-                    self.captured_labels[(edge.child, child, edge.parent, parent)].update(captured_labels)
-                    # keep the filtered assignment for further merging
-                    edge_assignments.append({edge.child: child, edge.parent: parent})
-            if edge_assignments:
-                edges_assignments.append(edge_assignments)
-        return edges_assignments
-
-    @staticmethod
-    def _merge_edges_assignments(edges_assignments: List[List[Dict[str, int]]]) -> List[Dict[str, int]]:
-        merges = []
-        # for each list of possible assignments of an edge
-        for edge_assignments in edges_assignments:
-            new_merges = []
-            # for each merged assignment. (we need an empty dictionary for the first cycle to start with)
-            for merged in (merges if merges else [{}]):
-                # for each possible assignment in the current list
-                for assignment in edge_assignments:
-                    # try to merge (see that there is no contradiction on hashing)
-                    just_merged = GlobalMatcher._try_merge(merged, assignment)
-                    if just_merged:
-                        new_merges.append(just_merged)
-            if not new_merges:
-                return []
-            merges = new_merges
-
-        return merges
-
-    def apply(self, matches: Mapping[str, List[int]], sentence) -> Generator[MatchingResult, None, None]:
-        filtered = self._filter_edge_constraints(matches, sentence)
-        merges = self._merge_edges_assignments(filtered)
-
-        for merged_assignment in merges:
-            # TODO - compare the speed of non-edge filtering here to the on-going edge filter (see previous todo)
-            if self._filter_distance_constraints(merged_assignment) and \
-                    self._filter_concat_constraints(merged_assignment, sentence):
-                # keep only required captures
-                _ = [merged_assignment.pop(name, None) for name in self.dont_capture_names]
-                captured_labels = {(v1, v2): labels for (k1, v1, k2, v2), labels in self.captured_labels.items()
-                                   if merged_assignment[k1] == v1 and merged_assignment[k2] == v2}
-                # append assignment to output
-                yield MatchingResult(merged_assignment, captured_labels)
-
-
-class TokenMatcher:
-    def __init__(self, constraints: Sequence[Token], vocab: Vocab):
-        self.vocab = vocab
-        self.matcher = SpacyMatcher(vocab)
-        # store the no_children/incoming/outgoing token constraints according to the token id for post spacy matching
-        self.no_children = {constraint.id: constraint.no_children for constraint in constraints}
-        self.incoming_constraints = {constraint.id: constraint.incoming_edges for constraint in constraints}
-        self.outgoing_constraints = {constraint.id: constraint.outgoing_edges for constraint in constraints}
-        
-        # convert the constraint to a list of matchable token patterns
-        self.required_tokens = set()
-        for token_name, is_required, matchable_pattern in self._make_patterns(constraints):
-            # add it to the matchable list
-            self.matcher.add(token_name, None, [matchable_pattern])
-            if is_required:
-                self.required_tokens.add(token_name)
-    
-    # convert the constraint to a spacy pattern
-    @staticmethod
-    def _make_patterns(constraints: Sequence[Token]) -> List[Tuple[str, bool, Dict[str, Dict[str, Sequence[str]]]]]:
-        patterns = []
-        for constraint in constraints:
-            pattern = dict()
-            for spec in constraint.spec:
-                # TODO - the list is comprised of either strings or regexes - so spacy's 'IN' or 'REGEX' won't do
-                #   for now, assuming a list is given (hence the use of "IN"/"NOT_IN")
-                in_or_not_in = "IN" if spec.in_sequence else "NOT_IN"
-                if spec.field == FieldNames.WORD:
-                    # TODO - check if you need non LOWER case (i.e. TEXT)
-                    pattern["LOWER"] = {in_or_not_in: spec.value}
-                elif spec.field == FieldNames.LEMMA:
-                    pattern["LEMMA"] = {in_or_not_in: spec.value}
-                elif spec.field == FieldNames.TAG:
-                    pattern["TAG"] = {in_or_not_in: spec.value}
-                elif spec.field == FieldNames.ENTITY:
-                    pattern["ENT_TYPE"] = {in_or_not_in: spec.value}
-            
-            patterns.append((constraint.id, not constraint.optional, pattern))
-        return patterns
-    
-    def _post_spacy_matcher(self, matched_tokens: Mapping[str, List[int]], sentence) -> Mapping[str, List[int]]:
-        # handles incoming and outgoing label constraints (still in token level)
-        checked_tokens = defaultdict(list)
-        for name, token_indices in matched_tokens.items():
-            for token in token_indices:
-                if self.no_children[name] and (len(sentence.get_labels(parent=token)) != 0):
-                    continue
-                out_matched = get_matched_labels(self.outgoing_constraints[name], sentence.get_labels(parent=token))
-                in_matched = get_matched_labels(self.incoming_constraints[name], sentence.get_labels(child=token))
-                if in_matched is None or out_matched is None:
-                    # TODO - consider adding a 'token in self.required_tokens' validation here for optimization
-                    continue
-                checked_tokens[name].append(token)
-        return checked_tokens
-    
-    def apply(self, sentence) -> Optional[Mapping[str, List[int]]]:
-        matched_tokens = defaultdict(list)
-        
-        # apply spacy's token-level match
-        doc = sentence.doc()
-        matches = self.matcher(doc)
-        # validate the span and store each matched token
-        for match_id, start, end in matches:
-            token_name = self.vocab.strings[match_id]
-            # assert that we matched no more than single token
-            if end - 1 != start:
-                continue
-            token = doc[start]
-            matched_tokens[token_name].append(token.i)
-
-        # extra token matching out of spacy's scope
-        matched_tokens = self._post_spacy_matcher(matched_tokens, sentence)
-
-        # reverse validate the 'optional' constraint
-        if len(self.required_tokens.difference(set(matched_tokens.keys()))) != 0:
-            return None
-        
-        return matched_tokens
-
-
-class Match:
-    def __init__(self, token_matchers: Mapping[str, TokenMatcher],
-                 global_matchers: Mapping[str, GlobalMatcher], sentence):
-        assert token_matchers.keys() == global_matchers.keys()
-        self.token_matchers = token_matchers
-        self.global_matchers = global_matchers
-        self.sentence = sentence
-    
-    def names(self) -> List[str]:
-        # return constraint-name list
-        return list(self.token_matchers.keys())
-    
-    def matches_for(self, name: str) -> Generator[MatchingResult, None, None]:
-        # token match
-        matches = self.token_matchers[name].apply(self.sentence)
-        if matches is None:
-            return
-        
-        # filter
-        yield from self.global_matchers[name].apply(matches, self.sentence)
-
-
-class NamedConstraint(NamedTuple):
-    name: str
-    constraint: Full
-
-
-# add TokenConstraints based on the other non-token constraints (optimization step).
-def preprocess_constraint(constraint: Full) -> Full:
-    # for each edge store the labels that could be filtered as incoming or outgoing token constraints
-    #   in the parent or child accordingly
-    outs = defaultdict(list)
-    ins = defaultdict(list)
-    for edge in constraint.edges:
-        # skip HasNoLabel as they check for non existing label between two nodes,
-        #   and if we add a token constraint it would be to harsh
-        if isinstance(edge.label, HasNoLabel):
-            continue
-        outs[edge.parent].extend(list(edge.label))
-        ins[edge.child].extend(list(edge.label))
-
-    # for each concat store the single words of the concat
-    #   with their correspondent token for token level WORD constraint
-    words = defaultdict(set)
-    for concat in constraint.concats:
-        zipped_concat = list(zip(*[tuple(t.split("_")) for t in concat.tuple_set]))
-        if isinstance(concat, TokenPair) or isinstance(concat, TokenTriplet):
-            words[concat.token1].update(set(zipped_concat[0]))
-            words[concat.token2].update(set(zipped_concat[1]))
-        if isinstance(concat, TokenTriplet):
-            words[concat.token3].update(set(zipped_concat[2]))
-
-    # rebuild the constraint (as it is immutable)
-    tokens = []
-    for token in constraint.tokens:
-        incoming_edges = list(token.incoming_edges) + ins.get(token.id, [])
-        outgoing_edges = (list(token.outgoing_edges) + outs.get(token.id, [])) if not token.no_children else []
-        # add the word constraint to an existing WORD field if exists
-        word_fields = [replace(s, value=list(words.get(token.id, set()).union(s.value)))
-                       for s in token.spec if s.field == FieldNames.WORD]
-        if (len(word_fields) == 0) and (token.id in words):
-            # simply create a new WORD field
-            word_fields = [Field(FieldNames.WORD, list(words[token.id]))]
-        # attach the replaced/newly-formed word constraint to the rest of the token spec
-        spec = [s for s in token.spec if s.field != FieldNames.WORD] + word_fields
-        tokens.append(replace(token, spec=spec, incoming_edges=incoming_edges, outgoing_edges=outgoing_edges))
-    return replace(constraint, tokens=tokens)
-
-
-class Matcher:
-    def __init__(self, constraints: Sequence[NamedConstraint], vocab: Vocab):
-        self.token_matchers = dict()
-        self.global_matchers = dict()
-        for constraint in constraints:
-            # preprocess the constraints (optimizations)
-            preprocessed_constraint = preprocess_constraint(constraint.constraint)
-            
-            # initialize internal matchers
-            self.token_matchers[constraint.name] = TokenMatcher(preprocessed_constraint.tokens, vocab)
-            self.global_matchers[constraint.name] = GlobalMatcher(preprocessed_constraint)
-    
-    # apply the matching process on a given sentence
-    def __call__(self, sentence) -> Match:
-        return Match(self.token_matchers, self.global_matchers, sentence)
diff --git a/pybart/pybart_globals.py b/pybart/pybart_globals.py
new file mode 100644
index 0000000..7876959
--- /dev/null
+++ b/pybart/pybart_globals.py
@@ -0,0 +1,2 @@
+g_remove_enhanced_extra_info = False
+g_remove_bart_extra_info = False
\ No newline at end of file
diff --git a/pybart/spacy_wrapper.py b/pybart/spacy_wrapper.py
index 7f1a0d1..95c28c1 100644
--- a/pybart/spacy_wrapper.py
+++ b/pybart/spacy_wrapper.py
@@ -3,7 +3,7 @@
 from spacy import attrs
 import numpy as np
 
-from .graph_token import Token, add_basic_edges
+from .graph_token import Token, add_basic_edges, TokenId
 
 NUM_OF_BITS = struct.calcsize("P") * 8
 
@@ -13,18 +13,18 @@
 
 
 def parse_spacy_sent(sent):
-    sentence = dict()
+    sentence = []
 
     offset = min(tok.i for tok in sent)
     
     for i, tok in enumerate(sent):
         # add current token to current sentence
-        sentence[tok.i + 1 - offset] = Token(
-            tok.i + 1 - offset, tok.text, tok.lemma_, tok.pos_, tok.tag_, "_", (tok.head.i + 1 - offset) if tok.head.i != tok.i else 0,
-            tok.dep_.lower(), "_", "_")
+        sentence.append(Token(
+            TokenId(tok.i + 1 - offset), tok.text, tok.lemma_, tok.pos_, tok.tag_, "_",
+            TokenId((tok.head.i + 1 - offset) if tok.head.i != tok.i else 0), tok.dep_.lower(), "_", "_"))
     
     # add root
-    sentence[0] = Token(0, None, None, None, None, None, None, None, None, None)
+    sentence.append(Token(TokenId(0), None, None, None, None, None, None, None, None, None))
     
     # after parsing entire sentence, add basic deprel edges,
     # and add sentence to output list
@@ -34,34 +34,12 @@ def parse_spacy_sent(sent):
 
 
 def parse_bart_label(rel, is_state_head_node):
-    # For the rare case which involvs a '@' preposition,
-    # we temporarily replace it with 'at', instead of simply doing rel.split("@")
-    rel_l = [x.replace(":at", ":@") for x in rel.replace(":@", ":at").split("@")]
+    if rel.src is not None:
+        src = (rel.src,) + tuple(filter(None, [rel.src_type, rel.phrase]))
+    else:
+        src = "UD" if not is_state_head_node else "BART"
     
-    # defaults
-    src = "UD" if not is_state_head_node else "BART"
-    alt = None
-    unc = False
-    
-    # if new info exists parse it
-    if len(rel_l) > 1:
-        # alternatives info
-        if len(rel_l[1].split("#")) > 1:
-            alt = int(rel_l[1].split("#")[1].split("+")[0])
-        # extra info
-        src = rel_l[1].split("(")[0]
-        extras = rel_l[1].split("(")[1].split(")")[0].split(", ")
-        if '' in extras:
-            extras.remove('')
-        # uncertinty info
-        if "UNC" in extras:
-            unc = True
-            extras.remove("UNC")
-        if len(extras) > 0:
-            src = (src,) + tuple(extras)
-    new_rel = rel_l[0]
-    
-    return new_rel, src, unc, alt
+    return rel.with_no_bart(), src, bool(rel.uncertain), rel.iid
 
 
 def serialize_spacy_doc(orig_doc, converted_sentences):
@@ -74,21 +52,21 @@ def serialize_spacy_doc(orig_doc, converted_sentences):
     
     for orig_span, converted_sentence in zip(orig_doc.sents, converted_sentences):
         # remove redundant dummy-root-node
-        converted = {iid: tok for iid, tok in converted_sentence.items() if iid != 0}
         orig = orig_span.as_doc()
-        
+
         # get attributes of original doc
         orig_attrs = orig.to_array(attrs_)
         
         # append copied attributes for new nodes
         new_nodes_attrs = []
-        for iid, tok in converted.items():
-            if int(iid) != iid:
-                new_node_attrs = list(orig_attrs[int(iid) - 1])
+        for tok in converted_sentence:
+            iid = tok.get_conllu_field("id")
+            if iid.minor:
+                new_node_attrs = list(orig_attrs[iid.major - 1])
                 
                 # here we fix the relative head he is pointing to,
                 # in case it is a negative number we need to cast it to its unsigned synonym
-                relative = int(iid) - (len(orig_attrs) + len(new_nodes_attrs) + 1)
+                relative = iid.major - (len(orig_attrs) + len(new_nodes_attrs) + 1)
                 new_node_attrs[attrs_.index('HEAD')] = relative + (2**NUM_OF_BITS if relative < 0 else 0)
                 
                 new_nodes_attrs.append(new_node_attrs)
@@ -101,9 +79,9 @@ def serialize_spacy_doc(orig_doc, converted_sentences):
         # fix whitespaces in case of new nodes: take original spaces. change the last one if there are new nodes.
         #   add spaces for each new nodes, except for last
         spaces += [t.whitespace_ if not ((i + 1 == len(orig)) and (len(new_nodes_attrs) > 0)) else ' ' for i, t in enumerate(orig)] + \
-                  [' ' if i + 1 < len(converted.keys()) else '' for i, iid in enumerate(converted.keys()) if int(iid) != iid]
+                  [' ' if i + 1 < len(converted_sentence) else '' for i, t in enumerate(converted_sentence) if t.get_conllu_field("id").minor]
         spaces[-1] = ' '
-        words += [t.get_conllu_field("form") for iid, t in converted.items()]
+        words += [t.get_conllu_field("form") for t in converted_sentence]
     
     # form new doc including new nodes and set attributes
     spaces[-1] = ''
@@ -112,29 +90,28 @@ def serialize_spacy_doc(orig_doc, converted_sentences):
     
     j = 0
     for converted_sentence in converted_sentences:
-        converted = {iid: tok for iid, tok in converted_sentence.items() if iid != 0}
-
         # store spacy ids for head indices extraction later on
-        spacy_ids = {iid: (spacy_i + j) for spacy_i, iid in enumerate(converted.keys())}
+        spacy_ids = {str(tok.get_conllu_field("id")): (spacy_i + j) for spacy_i, tok in enumerate(converted_sentence)}
         
         # set new info for all tokens per their head lists
-        for i, bart_tok in enumerate(converted.values()):
+        for i, bart_tok in enumerate(converted_sentence):
             spacy_tok = new_doc[i + j]
-            for head, rel in bart_tok.get_new_relations():
-                # extract spacy correspondent head id
-                head_tok = new_doc[spacy_ids[head.get_conllu_field("id")] if head.get_conllu_field("id") != 0 else spacy_tok.i]
-                # parse stringish label
-                is_state_head_node = ((head_tok.text == "STATE") and (head.get_conllu_field("id") != int(head.get_conllu_field("id")))) or \
-                                     (bart_tok.get_conllu_field("id") != int(bart_tok.get_conllu_field("id")))
-                new_rel, src, unc, alt = parse_bart_label(rel, is_state_head_node=is_state_head_node)
-                # add info to token
-                spacy_tok._.parent_list.append({'head': head_tok, 'rel': new_rel, 'src': src, 'alt': alt, 'unc': unc})
+            for head, rels in bart_tok.get_new_relations():
+                for rel in rels:
+                    # extract spacy correspondent head id
+                    head_tok = new_doc[spacy_ids[str(head.get_conllu_field("id"))] if head.get_conllu_field("id").major != 0 else spacy_tok.i]
+                    # parse stringish label
+                    is_state_head_node = ((head_tok.text == "STATE") and head.get_conllu_field("id").minor) or \
+                                         bart_tok.get_conllu_field("id").minor
+                    new_rel, src, unc, alt = parse_bart_label(rel, is_state_head_node=is_state_head_node)
+                    # add info to token
+                    spacy_tok._.parent_list.append({'head': head_tok.i, 'rel': new_rel, 'src': src, 'alt': alt, 'unc': unc})
             
             # fix sentence boundaries, need to turn off is_parsed bool as it prevents setting the boundaries
             new_doc.is_parsed = False
             spacy_tok.is_sent_start = False if i != 0 else True
             new_doc.is_parsed = True
         
-        j += len(converted)
+        j += len(converted_sentence)
     
     return new_doc
diff --git a/setup.py b/setup.py
index fcb9ccd..ff78dc6 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
 
 setuptools.setup(
     name="pybart-nlp",
-    version="2.2.6",
+    version="3.0.1",
     author="Aryeh Tiktinsky",
     author_email="aryehgigi@gmail.com",
     description="python converter from UD-tree to BART-graph representations",


Test patch:
diff --git a/tests/expected_handcrafted_tests_output.conllu b/tests/expected_handcrafted_tests_output.conllu
index 0508d65..19e5f92 100644
--- a/tests/expected_handcrafted_tests_output.conllu
+++ b/tests/expected_handcrafted_tests_output.conllu
@@ -77,7 +77,7 @@
 11      me      _       PRP     PRP     _       9       nmod    5:advcl	            _
 
 # test:eudpp_process_complex_2wp-blackbox3
-1       As      _       IN      IN      _       2       case    4:case      	_
+1       As      _       IN      IN      _       2       advmod    4:advmod      	_
 2       close   _       NN      NN      _       7       nmod    4:case      	_
 3       to      _       TO      TO      _       4       case    2:mwe       	_
 4       me      _       PRP     PRP     _       2       nmod    7:nmod      	_
@@ -114,31 +114,31 @@
 3       front   _       NN      NN      _       9       nmod    2:mwe       		_
 4       of      _       IN      IN      _       6       mark    2:mwe       		_
 5       being   _       VBG     VBG     _       6       cop     6:cop       		_
-6       first   _       JJ      JJ      _       3       acl     9:acl       	_
+6       first   _       JJ      JJ      _       3       acl     9:advcl       	_
 7       as      _       IN      IN      _       9       advmod  9:advmod   		_
 8       can     _       MD      MD      _       9       aux     9:aux      		_
 9       be      _       VB      VB      _       0       root    0:root      		_
 
-# test:eudpp_demote_quantificational_modifiers-blackbox1
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox1
 1       A       _       DT      DT      _       2       det     4:det:qmod  _
 2       couple  _       NN      NN      _       0       root    1:mwe       _
 3       of      _       IN      IN      _       4       case    1:mwe       _
 4       people  _       NNS     NNS     _       2       nmod    0:root      _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox2
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox2
 1       An      _       DT      DT      _       3       det     3:det       _
 2       angry   _       JJ      JJ      _       3       amod    3:amod      _
 3       ton     _       NN      NN      _       0       root    0:root      _
 4       of      _       IN      IN      _       5       case    5:case      _
 5       people  _       NNS     NNS     _       3       nmod    3:nmod	_
 
-# test:eudpp_demote_quantificational_modifiers-blackbox3
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox3
 1       An      _       DT      DT      _       2       det     2:det       _
 2       horde   _       NN      NN      _       0       root    0:root      _
 3       of      _       IN      IN      _       4       case    4:case      _
 4       people  _       NNS     NNS     _       2       nmod    2:nmod   _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox4
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox4
 1       A       _       DT      DT      _       2       det     2:det       _
 2       ton     _       NN      NN      _       0       root    0:root      _
 3       or      _       CC      CC      _       4       cc      4:cc        _
@@ -146,19 +146,33 @@
 5       of      _       IN      IN      _       6       case    6:case      _
 6       people  _       NNS     NNS     _       2       nmod    2:nmod   _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox5
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox5
 1       A       _       DT      DT      _       2       det     2:det       _
 2       ton     _       NN      NN      _       0       root    0:root      _
 3       of      _       IN      IN      _       4       case    4:case      _
-4       what    _       WP      WP      _       2       nmod    2:nmod   _
+4       working    _       VB      VB      _       2       nmod    2:nmod   _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox6
+# test:eudpp_demote_quantificational_modifiers_2w-blackbox1
 1       Several _       JJ      JJ      _       5       nsubj   3:det:qmod  _
 2       of      _       IN      IN      _       3       case    1:mwe       	_
 3       us      _       PRP     PRP     _       1       nmod    5:nsubj     	_
 4       were    _       VBD     VBD     _       5       aux     5:aux       	_
 5       playing _       VBG     VBG     _       0       root    0:root      	_
 6       while   _       IN      IN      _       12      mark    12:mark     	_
+7       some    _       DT      DT      _       12      nsubj   12:nsubj 	_
+8       of      _       IN      IN      _       10      case    10:case       	_
+9       the     _       DT      DT      _       10      det     10:det       	_
+10      others  _       NNS     NNS     _       7       nmod    7:nmod    	_
+11      are     _       VBP     VBP     _       12      aux     12:aux      	_
+12      reading _       VBG     VBG     _       5       advcl   5:advcl	_
+
+# test:eudpp_demote_quantificational_modifiers_det-blackbox1
+1       Several _       JJ      JJ      _       5       nsubj   5:nsubj  _
+2       of      _       IN      IN      _       3       case    3:case       	_
+3       us      _       PRP     PRP     _       1       nmod    1:nmod     	_
+4       were    _       VBD     VBD     _       5       aux     5:aux       	_
+5       playing _       VBG     VBG     _       0       root    0:root      	_
+6       while   _       IN      IN      _       12      mark    12:mark     	_
 7       some    _       DT      DT      _       12      nsubj   10:det:qmod 	_
 8       of      _       IN      IN      _       10      case    7:mwe       	_
 9       the     _       DT      DT      _       10      det     7:mwe       	_
@@ -166,7 +180,7 @@
 11      are     _       VBP     VBP     _       12      aux     12:aux      	_
 12      reading _       VBG     VBG     _       5       advcl   5:advcl	_
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox1
+# test:eudpp_expand_pp_conjunctions-blackbox1
 1	Bill	Bill	PROPN	NNP	_	2	nsubj	2:nsubj         	_
 2	flies	fly		VERB	VBZ	_	0	root	0:root				_
 2.1	flies	fly		VERB	VBZ	_	_	_		2:conj:and			CopyOf=2
@@ -176,7 +190,7 @@
 6	from	from	ADP		IN	_	7	case	7:case				_
 7	Serbia	Serbia	PROPN	NNP	_	4	conj	2.1:nmod    		_
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox2
+# test:eudpp_expand_pp_conjunctions-blackbox2
 1       Bill    _       NNP     NNP     _       2       nsubj   2:nsubj                                     	_
 2       flies   _       VBZ     VBZ     _       0       root    0:root      									_
 2.1     flies   _       VBZ     VBZ     _       _       _       2:conj:and  						   			CopyOf=2
@@ -200,7 +214,7 @@
 17      from    _       IN      IN      _       18      case    18:case     									_
 18      Israel  _       NNP     NNP     _       4       conj    2.4:nmod	                                    _
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox3
+# test:eudpp_expand_pp_conjunctions-blackbox3
 1       Bill    _       NNP     NNP     _       2       nsubj   2:nsubj              		_
 2       flies   _       VBZ     VBZ     _       0       root    0:root      				_
 2.1     flies   _       VBZ     VBZ     _       _       _       2:conj:and        			CopyOf=2
@@ -219,7 +233,7 @@
 14      from    _       IN      IN      _       15      case    15:case     				_
 15      Italy   _       NNP     NNP     _       12      conj    10.1:nmod         			_
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox4
+# test:eudpp_expand_prep_conjunctions-blackbox1
 1       Bill    _       NNP     NNP     _       2       nsubj   2:nsubj               	_
 2       flies   _       VBZ     VBZ     _       0       root    0:root  				_
 2.1     flies   _       VBZ     VBZ     _       _       _    	2:conj:and      		CopyOf=2
@@ -228,39 +242,6 @@
 5       from    _       IN      IN      _       3       conj	3:conj  	  		 	_
 6       France  _       NNP     NNP     _       2       nmod	2:nmod|2.1:nmod:from	_
 
-# test:eud_passive_agent-blackbox1
-1       He      _       PRP     PRP     _       3       nsubjpass   3:nsubjpass		_
-2       was     _       VBD     VBD     _       3       auxpass 	3:auxpass       _
-3       chosen  _       VBN     VBN     _       0       root    	0:root       	_
-4       by      _       IN      IN      _       5       case    	5:case       	_
-5       me      _       PRP     PRP     _       3       nmod    	3:nmod:agent	_
-
-# test:eud_passive_agent-blackbox2
-1       He      _       PRP     PRP     _       3       nsubjpass   3:nsubjpass     _
-2       was     _       VBD     VBD     _       3       auxpass 	3:auxpass       _
-3       chosen  _       VBN     VBN     _       0       root    	0:root       	_
-4       by      _       IN      IN      _       5       case    	5:case       	_
-5       me      _       PRP     PRP     _       3       nmod    	3:nmod:agent	_
-6       and     _       CC      CC      _       3       cc      	3:cc       		_
-7       I       _       PRP     PRP     _       9       nsubjpass   9:nsubjpass     _
-8       was     _       VBD     VBD     _       9       auxpass 	9:auxpass       _
-9       chosen  _       VBN     VBN     _       3       conj    	3:conj  		_
-10      by      _       IN      IN      _       11      case    	11:case       	_
-11      you     _       PRP     PRP     _       9       nmod    	9:nmod:agent    _
-
-# test:eud_passive_agent-blackbox3
-1       They    	_       PRP     PRP     _       2       nsubj   2:nsubj     _
-2       chose   	_       VBD     VBD     _       0       root    0:root      _
-3       by      	_       IN      IN      _       4       case    4:case      _
-4       themselves  _       PRP     PRP     _       2       nmod    2:nmod  	_
-
-# test:eud_passive_agent-blackbox4
-1       He      _       PRP     PRP     _       3       nsubjpass	3:nsubjpass	_
-2       was     _       VBD     VBD     _       3       auxpass 	3:auxpass   _
-3       chosen  _       VBN     VBN     _       0       root    	0:root      _
-4       with    _       IN      IN      _       5       case    	5:case      _
-5       me      _       PRP     PRP     _       3       nmod    	3:nmod  	_
-
 # test:eudpp_add_ref_and_collapse-blackbox1
 1       The     _       DT      DT      _       2       det     	2:det       				_
 2       boy     _       NN      NN      _       11      nsubj   	4:nsubj|11:nsubj    		_
@@ -276,59 +257,15 @@
 12      the     _       DT      DT      _       13      det     	13:det       				_
 13      tale    _       NN      NN      _       11      dobj    	11:dobj       				_
 
-# test:eudpp_add_ref_and_collapse-whitebox1
-1       The     the     DET     DT      _       2       det     	2:det   		_
-2       boy     boy     NOUN    NN      _       0       root    	0:root|4:nsubj  _
-3       who     who     CASE    CASE    _       4       nsubj     	2:ref   		_
-4       bla     bla     NOUN    NN      _       7       nsubj   	7:nsubj 		_
-5       whom    whom    CASE    CASE    _       7       nsubj   	7:nsubj 		_
-6       bla     bla     DET     DT      _       7       nsubj   	7:nsubj 		_
-7       lived   live    NOUN    NN      _       2       acl:relcl	2:acl:relcl     _
-
-# test:eudpp_add_ref_and_collapse-whitebox2
-1       The     the     DET     DT      _       2       det     	2:det   		_
-2       boy     boy     NOUN    NN      _       0       root    	0:root|7:nsubj  _
-3       who     who     CASE    CASE    _       7       nsubj   	2:ref   		_
-4       bla     bla     NOUN    NN      _       7       nsubj   	7:nsubj 		_
-5       whom    whom    CASE    CASE    _       6       nsubj   	6:nsubj 		_
-6       bla     bla     DET     DT      _       7       nsubj		7:nsubj 		_
-7       lived   live    NOUN    NN      _       2       acl:relcl	2:acl:relcl     _
-
-# test:eudpp_add_ref_and_collapse-whitebox3
-1       The     the     DET     DT      _       2       det     	2:det   		_
-2       boy     boy     NOUN    NN      _       0       root    	0:root|4:nsubj  _
-3       who     who     CASE    CASE    _       4       nsubj     	2:ref   		_
-4       bla     bla     NOUN    NN      _       7       nsubj   	7:nsubj 		_
-5       whom    whom    CASE    CASE    _       4       nsubj   	4:nsubj 		_
-6       bla     bla     DET     DT      _       7       nsubj   	7:nsubj 		_
-7       lived   live    NOUN    NN      _       2       acl:relcl	2:acl:relcl     _
-
-# test:eudpp_add_ref_and_collapse-whitebox4
-1       The     the     DET     DT      _       2       det     	2:det   		_
-2       boy     boy     NOUN    NN      _       0       root    	0:root|7:nsubj  _
-3       who     who     CASE    CASE    _       7       nsubj     	2:ref   		_
-4       bla     bla     NOUN    NN      _       7       nsubj   	7:nsubj 		_
-5       whom    whom    CASE    CASE    _       7       nsubj   	7:nsubj 		_
-6       bla     bla     DET     DT      _       7       nsubj   	7:nsubj 		_
-7       lived   live    NOUN    NN      _       2       acl:relcl   2:acl:relcl     _
-
-# test:eudpp_add_ref_and_collapse-whitebox5
-1       The     the     DET     DT      _       2       det     	2:det   		_
-2       boy     boy     NOUN    NN      _       0       root    	0:root|4:nsubj  _
-3       who     who     CASE    CASE    _       1       nsubj   	1:nsubj 		_
-4       bla     bla     NOUN    NN      _       7       nsubj   	7:nsubj 		_
-5       whom    whom    CASE    CASE    _       4       nsubj     	2:ref   		_
-6       bla     bla     DET     DT      _       7       nsubj   	7:nsubj 		_
-7       lived   live    NOUN    NN      _       2       acl:relcl	2:acl:relcl     _
-
-# test:eudpp_add_ref_and_collapse-whitebox6
-1       The     the     DET     DT      _       2       det     	2:det   		_
-2       boy     boy     NOUN    NN      _       0       root    	0:root|7:nsubj  _
-3       who     who     CASE    CASE    _       1       nsubj   	1:nsubj 		_
-4       bla     bla     NOUN    NN      _       7       nsubj   	7:nsubj 		_
-5       whom    whom    CASE    CASE    _       7       nsubj   	2:ref   		_
-6       bla     bla     DET     DT      _       7       nsubj   	7:nsubj 		_
-7       lived   live    NOUN    NN      _       2       acl:relcl   2:acl:relcl		_
+# test:eudpp_add_ref_and_collapse-blackbox2
+1       I       _       PRP     PRP     _       2       nsubj   2:nsubj _
+2       understand      _       VBP     VBP     _       0       root    0:root  _
+3       the     _       DT      DT      _       4       det     4:det   _
+4       reason  _       NN      NN      _       2       dobj    2:dobj|7:nmod:why        _
+5       why     _       WRB     WRB     _       7       advmod  4:ref        _
+6       they    _       PRP     PRP     _       7       nsubj   7:nsubj _
+7       did     _       VBD     VBD     _       4       acl:relcl       4:acl:relcl     _
+8       it      _       PRP     PRP     _       7       dobj    7:dobj  _
 
 # test:eud_subj_of_conjoined_verbs-blackbox1
 1       The     _       DT      DT      _       2       det     	2:det   			_
@@ -366,12 +303,23 @@
 1       He      _       PRP     PRP     _       2       nsubj   2:nsubj _
 2       saw     _       VBD     VBD     _       0       root    0:root  _
 3       them    _       PRP     PRP     _       2       dobj    2:dobj  _
-5       in      _       IN      IN      _       7       case    7:case|9:case|11:case   _
-6       the     _       DT      DT      _       7       det     7:det   _
-7       air     _       NN      NN      _       2       nmod    2:nmod  _
-9       ground  _       NN      NN      _       7       conj    2:nmod|7:conj   _
-10      and     _       CC      CC      _       7       cc      7:cc    _
-11      water   _       NN      NN      _       7       conj    2:nmod|7:conj   _
+4       in      _       IN      IN      _       6       case    6:case   _
+5       the     _       DT      DT      _       6       det     6:det   _
+6       air     _       NN      NN      _       2       nmod    2:nmod  _
+7       ground  _       NN      NN      _       6       conj    2:nmod|6:conj   _
+8       and     _       CC      CC      _       6       cc      6:cc    _
+9       water   _       NN      NN      _       6       conj    2:nmod|6:conj   _
+
+# test:eud_case_sons_of_conjuncts-blackbox1
+1       He      _       PRP     PRP     _       2       nsubj   2:nsubj _
+2       saw     _       VBD     VBD     _       0       root    0:root  _
+3       them    _       PRP     PRP     _       2       dobj    2:dobj  _
+4       in      _       IN      IN      _       6       case    6:case|7:case|9:case   _
+5       the     _       DT      DT      _       6       det     6:det   _
+6       air     _       NN      NN      _       2       nmod    2:nmod  _
+7       ground  _       NN      NN      _       6       conj    6:conj  _
+8       and     _       CC      CC      _       6       cc      6:cc    _
+9       water   _       NN      NN      _       6       conj    6:conj  _
 
 # test:eud_xcomp_propagation-blackbox1
 1       He      _       PRP     PRP     _       2       nsubj   	2:nsubj|4:nsubj:xcomp(INF)	_
@@ -417,12 +365,6 @@
 12      a       _       DT      DT      _       13      det     13:det  				_
 13      trip    _       NN      NN      _       10      nmod	10:nmod     		_
 
-# test:eud_xcomp_propagation-blackbox5
-1       He      _       PRP     PRP     _       2       nsubj   2:nsubj|4:nsubj:xcomp(INF)	_
-2       decided _       VBD     VBD     _       0       root    0:root  				_
-3       not     _       RB      RB      _       4       neg     4:neg   				_
-4       to      _       TO      TO      _       2       xcomp   2:xcomp 				_
-
 # test:eud_prep_patterns-blackbox1
 1       I       _       PRP     PRP     _       2       nsubj   2:nsubj      _
 2       like    _       VBP     VBP     _       0       root    0:root       _
@@ -449,6 +391,47 @@
 8       feel    _       VB      VB      _       6       ccomp   6:ccomp             _
 9       bad     _       JJ      JJ      _       8       xcomp   8:xcomp             _
 
+# test:eud_prep_patterns-blackbox4
+1       I       _       PRP     PRP     _       2       nsubj   2:nsubj             _
+2       passed  _       VBP     VBP     _       0       root    0:root              _
+3       in      _       IN      IN      _       6       case    6:case              _
+4       front   _       NN      NN      _       3       mwe     3:mwe               _
+5       of      _       IN      IN      _       3       mwe     3:mwe               _
+6       dogs    _       NNS     NNS     _       2       nmod    2:nmod:in_front_of  _
+
+# test:eud_prep_patterns-blackbox5
+1       He      _       PRP     PRP     _       3       nsubjpass   3:nsubjpass		_
+2       was     _       VBD     VBD     _       3       auxpass 	3:auxpass       _
+3       chosen  _       VBN     VBN     _       0       root    	0:root       	_
+4       by      _       IN      IN      _       5       case    	5:case       	_
+5       me      _       PRP     PRP     _       3       nmod    	3:nmod:agent	_
+
+# test:eud_prep_patterns-blackbox6
+1       He      _       PRP     PRP     _       3       nsubjpass   3:nsubjpass     _
+2       was     _       VBD     VBD     _       3       auxpass 	3:auxpass       _
+3       chosen  _       VBN     VBN     _       0       root    	0:root       	_
+4       by      _       IN      IN      _       5       case    	5:case       	_
+5       me      _       PRP     PRP     _       3       nmod    	3:nmod:agent	_
+6       and     _       CC      CC      _       3       cc      	3:cc       		_
+7       I       _       PRP     PRP     _       9       nsubjpass   9:nsubjpass     _
+8       was     _       VBD     VBD     _       9       auxpass 	9:auxpass       _
+9       chosen  _       VBN     VBN     _       3       conj    	3:conj  		_
+10      by      _       IN      IN      _       11      case    	11:case       	_
+11      you     _       PRP     PRP     _       9       nmod    	9:nmod:agent    _
+
+# test:eud_prep_patterns-blackbox7
+1       They    	_       PRP     PRP     _       2       nsubj   2:nsubj     _
+2       chose   	_       VBD     VBD     _       0       root    0:root      _
+3       by      	_       IN      IN      _       4       case    4:case      _
+4       themselves  _       PRP     PRP     _       2       nmod    2:nmod:by  	_
+
+# test:eud_prep_patterns-blackbox8
+1       He      _       PRP     PRP     _       3       nsubjpass	3:nsubjpass	_
+2       was     _       VBD     VBD     _       3       auxpass 	3:auxpass   _
+3       chosen  _       VBN     VBN     _       0       root    	0:root      _
+4       with    _       IN      IN      _       5       case    	5:case      _
+5       me      _       PRP     PRP     _       3       nmod    	3:nmod:with  	_
+
 # test:eud_conj_info-blackbox1
 1       I       _       PRP     PRP     _       2       nsubj   2:nsubj         _
 2       love    _       VBP     VBP     _       0       root    0:root          _
@@ -456,9 +439,9 @@
 4       but     _       CC      CC      _       3       cc      3:cc            _
 5       also    _       RB      RB      _       6       advmod  6:advmod        _
 6       her     _       PRP     PRP     _       3       conj    3:conj:and      _
-8       but     _       CC      CC      _       3       cc      3:cc            _
-9       not     _       RB      RB      _       10      neg     10:neg          _
-10      myself  _       PRP     PRP     _       3       conj    3:conj:negcc    _
+7       but     _       CC      CC      _       3       cc      3:cc            _
+8       not     _       RB      RB      _       9       neg     9:neg           _
+9       myself  _       PRP     PRP     _       3       conj    3:conj:negcc    _
 
 # test:extra_aspectual_reconstruction-blackbox1
 1       FOX     	_           NN      NN      _       2       nsubj   3:nsubj                 _
@@ -482,6 +465,12 @@
 4       to      _       TO      TO      _       5       case    5:case  _
 5       friends _       VB      VB      _       3       nmod    3:nmod  _
 
+# test:eud_xcomp_propagation_no_to-blackbox2
+1       He      _       PRP     PRP     _       2       nsubj   2:nsubj|4:nsubj:xcomp(INF)	_
+2       decided _       VBD     VBD     _       0       root    0:root  				_
+3       not     _       RB      RB      _       4       neg     4:neg   				_
+4       to      _       TO      TO      _       2       xcomp   2:xcomp 				_
+
 # test:extra_advcl_propagation-blackbox1
 1       I       _       PRP     PRP     _       2       nsubj   2:nsubj 					_
 2       told    _       VBD     VBD     _       0       root    0:root  					_
@@ -508,7 +497,7 @@
 6       a       _       DT      DT      _       7       det     7:det       					_
 7       cake    _       NN      NN      _       5       dobj    5:dobj       					_
 
-# test:extra_acl_propagation-blackbox1
+# test:extra_acl_to_propagation-blackbox1
 1       Putin   _       NNP     NNP     _       3       nsubj   3:nsubj|6:nsubj@acl(NULL, to)   _
 2       has     _       VBZ     VBZ     _       3       aux     3:aux					        _
 3       made    _       VBN     VBN     _       0       root    0:root					        _
@@ -519,7 +508,7 @@
 8       with    _       IN      IN      _       9       case    9:case       					_
 9       Hu      _       NNP     NNP     _       6       nmod    6:nmod	    			        _
 
-# test:extra_acl_propagation-blackbox2
+# test:extra_acl_propagation-blackbox1
 1       soldiers	_       NNS     NNS     _       3       nsubj   3:nsubj	                            _
 2       have    	_       VBP     VBP     _       3       aux     3:aux       					    _
 3       discovered	_       VBN     VBN     _       0       root    0:root					            _
@@ -538,16 +527,16 @@
 6       kidnapped   _       VBN     VBN     _       2       dep     2:dep					            _
 7       nearby  	_       RB      RB      _       6       advmod  6:advmod				            _
 
-# test:extra_conj_propagation_of_nmods-blackbox1
+# test:extra_conj_propagation_of_nmods_forward-blackbox1
 1       he      _       PRP     PRP     _       3       nsubjpass   3:nsubjpass                   	_
 2       was     _       VBD     VBD     _       3       auxpass 	3:auxpass       				_
 3       wounded _       VBN     VBN     _       0       root    	0:root       					_
 4       and     _       CC      CC      _       3       cc      	3:cc       						_
 5       trapped _       VBN     VBN     _       3       conj    	3:conj             				_
 6       in      _       IN      IN      _       7       case    	7:case       					_
-7       Korea   _       NNP     NNP     _       3       nmod    	3:nmod|5:nmod@conj(and, UNC)    _
+7       Korea   _       NNP     NNP     _       3       nmod    	3:nmod|5:nmod:in@conj(and, UNC)    _
 
-# test:extra_conj_propagation_of_nmods-blackbox2
+# test:extra_conj_propagation_of_nmods_backwards-blackbox1
 1       he      _       PRP     PRP     _       3       nsubjpass	3:nsubjpass 	            	_
 2       was     _       VBD     VBD     _       3       auxpass 	3:auxpass       				_
 3       wounded _       VBN     VBN     _       0       root    	0:root  						_
@@ -555,9 +544,9 @@
 5       and     _       CC      CC      _       3       cc      	3:cc    						_
 6       trapped _       VBN     VBN     _       3       conj        3:conj            				_
 7       in      _       IN      IN      _       8       case		8:case  						_
-8       Korea   _       NNP     NNP     _       6       nmod		3:nmod@conj(and, UNC)|6:nmod 	_
+8       Korea   _       NNP     NNP     _       6       nmod		3:nmod:in@conj(and, UNC)|6:nmod 	_
 
-# test:extra_conj_propagation_of_nmods-blackbox3
+# test:extra_conj_propagation_of_nmods_backwards-blackbox2
 1       he      	_       PRP     PRP     _       3       nsubjpass	3:nsubjpass             _
 2       was     	_       VBD     VBD     _       3       auxpass 	3:auxpass       		_
 3       wounded 	_       VBN     VBN     _       0       root    	0:root  				_
@@ -716,18 +705,7 @@
 4       place   _       NN      NN      _       2       dobj    2:dobj|6:nmod:in@acl(RELCL, REDUCED)    _
 5       we      _       PRP     PRP     _       6       nsubj   6:nsubj _
 6       visited _       VBD     VBD     _       4       acl:relcl       4:acl:relcl     _
-7       in      _       RB      RB      _       6       advmod  6:advmod        _
-
-# test:extra_add_ref_and_collapse-blackbox3
-1       I       _       PRP     PRP     _       2       nsubj   2:nsubj _
-2       understand      _       VBP     VBP     _       0       root    0:root  _
-3       the     _       DT      DT      _       4       det     4:det   _
-4       reason  _       NN      NN      _       2       dobj    2:dobj|7:nmod:because_of@acl(RELCL, why)        _
-5       why     _       WRB     WRB     _       7       advmod  7:advmod        _
-6       they    _       PRP     PRP     _       7       nsubj   7:nsubj _
-7       did     _       VBD     VBD     _       4       acl:relcl       4:acl:relcl     _
-8       it      _       PRP     PRP     _       7       dobj    7:dobj  _
-
+7       in      _       RB      RB      _       6       advmod  6:case        _
 
 # test:extra_hyphen_reconstruction-blackbox1
 1       I       _       PRP     PRP     _       2       nsubj   2:nsubj _
@@ -813,27 +791,27 @@
 4.1     STATE   _       _       _       _       _       _       2:xcomp _
 5       queen   _       NN      NN      _       2       xcomp   4.1:xcomp       _
 
-# test:extra_evidential_reconstruction-blackbox1
+# test:extra_evidential_basic_reconstruction-blackbox1
 1       He      _       PRP     PRP     _       2       nsubj   2.1:nsubj       _
 2       seems   seem    VBZ     VBZ     _       0       root    2.1:ev  _
 2.1     STATE   _       _       _       _       _       _       0:root  _
 3       smart   _       JJ      JJ      _       2       xcomp   1:amod@cop()|2.1:xcomp  _
 
-# test:extra_evidential_reconstruction-blackbox2
+# test:extra_evidential_basic_reconstruction-blackbox2
 1       He      _       PRP     PRP     _       2       nsubj   2.1:nsubj       _
 2       seems   seem    VBZ     VBZ     _       0       root    2.1:ev  _
 2.1     STATE   _       _       _       _       _       _       0:root  _
 3       the     _       DT      DT      _       4       det     4:det   _
 4       king    _       NN      NN      _       2       xcomp   2.1:xcomp       _
 
-# test:extra_evidential_reconstruction-blackbox3
+# test:extra_evidential_basic_reconstruction-blackbox3
 1       He      _       PRP     PRP     _       2       nsubj   2.1:nsubj       _
 2       seems   seem    VBZ     VBZ     _       0       root    2.1:ev  _
 2.1     STATE   _       _       _       _       _       _       0:root  _
 3       from    _       IN      IN      _       4       case    4:case  _
 4       Europe  _       NNP     NNP     _       2       nmod    2.1:nmod        _
 
-# test:extra_evidential_reconstruction-blackbox4
+# test:extra_evidential_basic_reconstruction-blackbox4
 # TODO: unwanted but expected when running without copula reconstruction
 1       He      _       PRP     PRP     _       2       nsubj   2.1:nsubj       _
 2       seems   seem    VBZ     VBZ     _       0       root    2.1:ev  _
@@ -842,17 +820,16 @@
 4       be      _       VB      VB      _       5       cop     5:cop   _
 5       smart   _       JJ      JJ      _       2       xcomp   1:amod@cop()|2.1:xcomp  _
 
-# test:extra_evidential_reconstruction-blackbox5
+# test:extra_evidential_xcomp_reconstruction-blackbox1
 1       He      _       PRP     PRP     _       2       nsubj   4:nsubj _
 2       seems   seem    VBZ     VBZ     _       0       root    4:ev@xcomp(EVIDENTIAL)  _
 3       to      _       TO      TO      _       4       mark    4:mark  _
 4       appear  appear  VB      VB      _       2       xcomp   0:root  _
 5       early   _       RB      RB      _       4       advmod  4:advmod        _
 
-# test:extra_evidential_reconstruction-blackbox6
-1       He      _       PRP     PRP     _       2       nsubj   4.1:nsubj       _
+# test:extra_evidential_xcomp_reconstruction-blackbox2
+1       He      _       PRP     PRP     _       2       nsubj   4:nsubj       _
 2       seems   seem    VBZ     VBZ     _       0       root    4:ev@xcomp(EVIDENTIAL)  _
 3       to      _       TO      TO      _       4       mark    4:mark  _
-4       appear  appear  VB      VB      _       2       xcomp   4.1:ev  _
-4.1     STATE   _       _       _       _       _       _       0:root  _
-5       funny   _       JJ      JJ      _       4       xcomp   4.1:xcomp       _
+4       appear  appear  VB      VB      _       2       xcomp   0:root  _
+5       funny   _       JJ      JJ      _       4       xcomp   4:xcomp       _
diff --git a/tests/expected_handcrafted_tests_output_combined.conllu b/tests/expected_handcrafted_tests_output_combined.conllu
index 3baf0fc..f17befc 100644
--- a/tests/expected_handcrafted_tests_output_combined.conllu
+++ b/tests/expected_handcrafted_tests_output_combined.conllu
@@ -41,7 +41,7 @@
 2	street	_	NN	NN	_	6	nsubj	3.1:nsubj	_
 3	is	_	VBZ	VBZ	_	6	cop	3.1:ev	_
 3.1	STATE	_	_	_	_	_	_	0:root	_
-3.2	STATE	_	_	_	_	_	_	3.1:conj:and	CopyOf=3
+3.2	STATE	_	_	_	_	_	_	3.1:conj:and	CopyOf=3.1
 4	across	_	IN	IN	_	6	advmod	6:case	_
 5	from	_	IN	IN	_	6	case	4:mwe	_
 6	you	_	PRP	PRP	_	0	root	3.1:nmod:across_from	_
@@ -58,7 +58,7 @@
 4	across	_	IN	IN	_	7	advmod	3:advmod	_
 5	ahead	_	IN	IN	_	7	case	7:case	_
 6	from	_	IN	IN	_	7	case	7:case	_
-7	you	_	PRP	PRP	_	0	root	3.1:nmod:ahead|3.1:nmod:from	_
+7	you	_	PRP	PRP	_	0	root	3.1:nmod:ahead	_
 
 # test:eudpp_process_complex_2wp-blackbox1
 1	He	_	PRP	PRP	_	3	nsubj	2.1:nsubj	_
@@ -84,7 +84,7 @@
 11	me	_	PRP	PRP	_	9	nmod	8.1:nmod:close_by	_
 
 # test:eudpp_process_complex_2wp-blackbox3
-1	As	_	IN	IN	_	2	case	4:case	_
+1	As	_	IN	IN	_	2	advmod	4:advmod	_
 2	close	_	NN	NN	_	7	nmod	4:case	_
 3	to	_	TO	TO	_	4	case	2:mwe	_
 4	me	_	PRP	PRP	_	2	nmod	7:nmod:close_to	_
@@ -124,32 +124,32 @@
 3	front	_	NN	NN	_	9	nmod	2:mwe	_
 4	of	_	IN	IN	_	6	mark	2:mwe	_
 5	being	_	VBG	VBG	_	6	cop	5.1:ev	_
-5.1	STATE	_	_	_	_	_	_	9:acl:in_front_of	_
+5.1	STATE	_	_	_	_	_	_	9:advcl:in_front_of	_
 6	first	_	JJ	JJ	_	3	acl	5.1:nmod:as	_
 7	as	_	IN	IN	_	9	advmod	9:advmod	_
 8	can	_	MD	MD	_	9	aux	9:aux	_
 9	be	_	VB	VB	_	0	root	0:root	_
 
-# test:eudpp_demote_quantificational_modifiers-blackbox1
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox1
 1	A	_	DT	DT	_	2	det	4:det:qmod	_
 2	couple	_	NN	NN	_	0	root	1:mwe	_
 3	of	_	IN	IN	_	4	case	1:mwe	_
 4	people	_	NNS	NNS	_	2	nmod	0:root	_
 
-# test:eudpp_demote_quantificational_modifiers-blackbox2
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox2
 1	An	_	DT	DT	_	3	det	3:det	_
 2	angry	_	JJ	JJ	_	3	amod	3:amod	_
 3	ton	_	NN	NN	_	0	root	0:root|2:nsubj@amod()	_
 4	of	_	IN	IN	_	5	case	5:case	_
 5	people	_	NNS	NNS	_	3	nmod	3:compound@nmod(of)|3:nmod:of	_
 
-# test:eudpp_demote_quantificational_modifiers-blackbox3
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox3
 1	An	_	DT	DT	_	2	det	2:det	_
 2	horde	_	NN	NN	_	0	root	0:root	_
 3	of	_	IN	IN	_	4	case	4:case	_
 4	people	_	NNS	NNS	_	2	nmod	2:compound@nmod(of)|2:nmod:of	_
 
-# test:eudpp_demote_quantificational_modifiers-blackbox4
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox4
 1	A	_	DT	DT	_	2	det	2:det	_
 2	ton	_	NN	NN	_	0	root	0:root	_
 3	or	_	CC	CC	_	4	cc	4:cc	_
@@ -157,13 +157,13 @@
 5	of	_	IN	IN	_	6	case	6:case	_
 6	people	_	NNS	NNS	_	2	nmod	2:compound@nmod(of)|2:nmod:of	_
 
-# test:eudpp_demote_quantificational_modifiers-blackbox5
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox5
 1	A	_	DT	DT	_	2	det	2:det	_
 2	ton	_	NN	NN	_	0	root	0:root	_
 3	of	_	IN	IN	_	4	case	4:case	_
-4	what	_	WP	WP	_	2	nmod	2:nmod:of	_
+4	working	_	VB	VB	_	2	nmod	2:nmod:of	_
 
-# test:eudpp_demote_quantificational_modifiers-blackbox6
+# test:eudpp_demote_quantificational_modifiers_2w-blackbox1
 1	Several	_	JJ	JJ	_	5	nsubj	3:det:qmod	_
 2	of	_	IN	IN	_	3	case	1:mwe	_
 3	us	_	PRP	PRP	_	1	nmod	5:nsubj	_
@@ -177,7 +177,21 @@
 11	are	_	VBP	VBP	_	12	aux	12:aux	_
 12	reading	_	VBG	VBG	_	5	advcl	5:advcl:while	_
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox1
+# test:eudpp_demote_quantificational_modifiers_det-blackbox1
+1	Several	_	JJ	JJ	_	5	nsubj	3:det:qmod	_
+2	of	_	IN	IN	_	3	case	1:mwe	_
+3	us	_	PRP	PRP	_	1	nmod	5:nsubj	_
+4	were	_	VBD	VBD	_	5	aux	5:aux	_
+5	playing	_	VBG	VBG	_	0	root	0:root	_
+6	while	_	IN	IN	_	12	mark	12:mark	_
+7	some	_	DT	DT	_	12	nsubj	10:det:qmod	_
+8	of	_	IN	IN	_	10	case	7:mwe	_
+9	the	_	DT	DT	_	10	det	7:mwe	_
+10	others	_	NNS	NNS	_	7	nmod	12:nsubj	_
+11	are	_	VBP	VBP	_	12	aux	12:aux	_
+12	reading	_	VBG	VBG	_	5	advcl	5:advcl:while	_
+
+# test:eudpp_expand_pp_conjunctions-blackbox1
 1	Bill	Bill	PROPN	NNP	_	2	nsubj	2:nsubj|2.1:nsubj	_
 2	flies	fly	VERB	VBZ	_	0	root	0:root	_
 2.1	flies	fly	VERB	VBZ	_	_	_	2:conj:and	CopyOf=2
@@ -187,7 +201,7 @@
 6	from	from	ADP	IN	_	7	case	7:case	_
 7	Serbia	Serbia	PROPN	NNP	_	4	conj	2.1:nmod:from	_
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox2
+# test:eudpp_expand_pp_conjunctions-blackbox2
 1	Bill	_	NNP	NNP	_	2	nsubj	2:nsubj|2.1:nsubj|2.2:nsubj|2.3:nsubj|2.4:nsubj	_
 2	flies	_	VBZ	VBZ	_	0	root	0:root	_
 2.1	flies	_	VBZ	VBZ	_	_	_	2:conj:and	CopyOf=2
@@ -211,7 +225,7 @@
 17	from	_	IN	IN	_	18	case	18:case	_
 18	Israel	_	NNP	NNP	_	4	conj	2.4:nmod:from	_
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox3
+# test:eudpp_expand_pp_conjunctions-blackbox3
 1	Bill	_	NNP	NNP	_	2	nsubj	2:nsubj|2.1:nsubj	_
 2	flies	_	VBZ	VBZ	_	0	root	0:root	_
 2.1	flies	_	VBZ	VBZ	_	_	_	2:conj:and	CopyOf=2
@@ -230,7 +244,7 @@
 14	from	_	IN	IN	_	15	case	15:case	_
 15	Italy	_	NNP	NNP	_	12	conj	10.1:nmod:from	_
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox4
+# test:eudpp_expand_prep_conjunctions-blackbox1
 1	Bill	_	NNP	NNP	_	2	nsubj	2:nsubj|2.1:nsubj	_
 2	flies	_	VBZ	VBZ	_	0	root	0:root	_
 2.1	flies	_	VBZ	VBZ	_	_	_	2:conj:and	CopyOf=2
@@ -239,39 +253,6 @@
 5	from	_	IN	IN	_	3	conj	3:conj:and	_
 6	France	_	NNP	NNP	_	2	nmod	2:nmod:to|2.1:nmod:from	_
 
-# test:eud_passive_agent-blackbox1
-1	He	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass	_
-2	was	_	VBD	VBD	_	3	auxpass	3:auxpass	_
-3	chosen	_	VBN	VBN	_	0	root	0:root	_
-4	by	_	IN	IN	_	5	case	5:case	_
-5	me	_	PRP	PRP	_	3	nmod	3:nmod:agent|3:nsubj@passive()	_
-
-# test:eud_passive_agent-blackbox2
-1	He	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass	_
-2	was	_	VBD	VBD	_	3	auxpass	3:auxpass	_
-3	chosen	_	VBN	VBN	_	0	root	0:root	_
-4	by	_	IN	IN	_	5	case	5:case	_
-5	me	_	PRP	PRP	_	3	nmod	3:nmod:agent|3:nsubj@passive()	_
-6	and	_	CC	CC	_	3	cc	3:cc	_
-7	I	_	PRP	PRP	_	9	nsubjpass	9:dobj@passive()|9:nsubjpass	_
-8	was	_	VBD	VBD	_	9	auxpass	9:auxpass	_
-9	chosen	_	VBN	VBN	_	3	conj	3:conj:and	_
-10	by	_	IN	IN	_	11	case	11:case	_
-11	you	_	PRP	PRP	_	9	nmod	9:nmod:agent|9:nsubj@passive()	_
-
-# test:eud_passive_agent-blackbox3
-1	They	_	PRP	PRP	_	2	nsubj	2:nsubj	_
-2	chose	_	VBD	VBD	_	0	root	0:root	_
-3	by	_	IN	IN	_	4	case	4:case	_
-4	themselves	_	PRP	PRP	_	2	nmod	2:nmod:by	_
-
-# test:eud_passive_agent-blackbox4
-1	He	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass	_
-2	was	_	VBD	VBD	_	3	auxpass	3:auxpass	_
-3	chosen	_	VBN	VBN	_	0	root	0:root	_
-4	with	_	IN	IN	_	5	case	5:case	_
-5	me	_	PRP	PRP	_	3	nmod	3:nmod:with	_
-
 # test:eudpp_add_ref_and_collapse-blackbox1
 1	The	_	DT	DT	_	2	det	2:det	_
 2	boy	_	NN	NN	_	11	nsubj	4:nsubj|11:nsubj	_
@@ -287,59 +268,15 @@
 12	the	_	DT	DT	_	13	det	13:det	_
 13	tale	_	NN	NN	_	11	dobj	11:dobj	_
 
-# test:eudpp_add_ref_and_collapse-whitebox1
-1	The	the	DET	DT	_	2	det	2:det	_
-2	boy	boy	NOUN	NN	_	0	root	0:root|4:nsubj|7:nsubj	_
-3	who	who	CASE	CASE	_	4	nsubj	2:ref	_
-4	bla	bla	NOUN	NN	_	7	nsubj	7:nsubj	_
-5	whom	whom	CASE	CASE	_	7	nsubj	2:ref	_
-6	bla	bla	DET	DT	_	7	nsubj	7:nsubj	_
-7	lived	live	NOUN	NN	_	2	acl:relcl	2:acl:relcl	_
-
-# test:eudpp_add_ref_and_collapse-whitebox2
-1	The	the	DET	DT	_	2	det	2:det	_
-2	boy	boy	NOUN	NN	_	0	root	0:root|7:nsubj	_
-3	who	who	CASE	CASE	_	7	nsubj	2:ref	_
-4	bla	bla	NOUN	NN	_	7	nsubj	7:nsubj	_
-5	whom	whom	CASE	CASE	_	6	nsubj	6:nsubj	_
-6	bla	bla	DET	DT	_	7	nsubj	7:nsubj	_
-7	lived	live	NOUN	NN	_	2	acl:relcl	2:acl:relcl	_
-
-# test:eudpp_add_ref_and_collapse-whitebox3
-1	The	the	DET	DT	_	2	det	2:det	_
-2	boy	boy	NOUN	NN	_	0	root	0:root|4:nsubj|7:dobj@acl(RELCL, REDUCED)	_
-3	who	who	CASE	CASE	_	4	nsubj	2:ref	_
-4	bla	bla	NOUN	NN	_	7	nsubj	7:nsubj	_
-5	whom	whom	CASE	CASE	_	4	nsubj	2:ref	_
-6	bla	bla	DET	DT	_	7	nsubj	7:nsubj	_
-7	lived	live	NOUN	NN	_	2	acl:relcl	2:acl:relcl	_
-
-# test:eudpp_add_ref_and_collapse-whitebox4
-1	The	the	DET	DT	_	2	det	2:det	_
-2	boy	boy	NOUN	NN	_	0	root	0:root|7:nsubj	_
-3	who	who	CASE	CASE	_	7	nsubj	2:ref	_
-4	bla	bla	NOUN	NN	_	7	nsubj	7:nsubj	_
-5	whom	whom	CASE	CASE	_	7	nsubj	7:nsubj	_
-6	bla	bla	DET	DT	_	7	nsubj	7:nsubj	_
-7	lived	live	NOUN	NN	_	2	acl:relcl	2:acl:relcl	_
-
-# test:eudpp_add_ref_and_collapse-whitebox5
-1	The	the	DET	DT	_	2	det	2:det	_
-2	boy	boy	NOUN	NN	_	0	root	0:root|4:nsubj|7:dobj@acl(RELCL, REDUCED)	_
-3	who	who	CASE	CASE	_	1	nsubj	1:nsubj	_
-4	bla	bla	NOUN	NN	_	7	nsubj	7:nsubj	_
-5	whom	whom	CASE	CASE	_	4	nsubj	2:ref	_
-6	bla	bla	DET	DT	_	7	nsubj	7:nsubj	_
-7	lived	live	NOUN	NN	_	2	acl:relcl	2:acl:relcl	_
-
-# test:eudpp_add_ref_and_collapse-whitebox6
-1	The	the	DET	DT	_	2	det	2:det	_
-2	boy	boy	NOUN	NN	_	0	root	0:root|7:nsubj	_
-3	who	who	CASE	CASE	_	1	nsubj	1:nsubj	_
-4	bla	bla	NOUN	NN	_	7	nsubj	7:nsubj	_
-5	whom	whom	CASE	CASE	_	7	nsubj	2:ref	_
-6	bla	bla	DET	DT	_	7	nsubj	7:nsubj	_
-7	lived	live	NOUN	NN	_	2	acl:relcl	2:acl:relcl	_
+# test:eudpp_add_ref_and_collapse-blackbox2
+1	I	_	PRP	PRP	_	2	nsubj	2:nsubj	_
+2	understand	_	VBP	VBP	_	0	root	0:root	_
+3	the	_	DT	DT	_	4	det	4:det	_
+4	reason	_	NN	NN	_	2	dobj	2:dobj|7:nmod:why	_
+5	why	_	WRB	WRB	_	7	advmod	4:ref	_
+6	they	_	PRP	PRP	_	7	nsubj	7:nsubj	_
+7	did	_	VBD	VBD	_	4	acl:relcl	4:acl:relcl	_
+8	it	_	PRP	PRP	_	7	dobj	7:dobj	_
 
 # test:eud_subj_of_conjoined_verbs-blackbox1
 1	The	_	DT	DT	_	2	det	2:det	_
@@ -374,15 +311,30 @@
 5	river	_	NN	NN	_	0	root	0:root|2:nsubj@amod()|4:nsubj@amod()	_
 
 # test:eud_heads_of_conjuncts-blackbox3
-1       He      _       PRP     PRP     _       2       nsubj   2:nsubj _
-2       saw     _       VBD     VBD     _       0       root    0:root  _
-3       them    _       PRP     PRP     _       2       dobj    2:dobj  _
-5       in      _       IN      IN      _       7       case    7:case|9:case|11:case   _
-6       the     _       DT      DT      _       7       det     7:det   _
-7       air     _       NN      NN      _       2       nmod    2:nmod:in       _
-9       ground  _       NN      NN      _       7       conj    2:nmod:in|7:conj:and    _
-10      and     _       CC      CC      _       7       cc      7:cc    _
-11      water   _       NN      NN      _       7       conj    2:nmod:in|7:conj:and    _
+1       He      _       PRP     PRP     _       2       nsubj   2:nsubj|2.1:nsubj|2.2:nsubj _
+2       saw     _       VBD     VBD     _       0       root    0:root                      _
+2.1     saw	    _	    VBD	    VBD	    _	    _	    _	    2:conj:and                  CopyOf=2
+2.2 	saw	    _	    VBD	    VBD	    _	    _	    _	    2:conj:and	                CopyOf=2
+3       them    _       PRP     PRP     _       2       dobj    2:dobj                      _
+4       in      _       IN      IN      _       6       case    6:case|7:case|9:case        _
+5       the     _       DT      DT      _       6       det     6:det                       _
+6       air     _       NN      NN      _       2       nmod    2:nmod:in                   _
+7       ground  _       NN      NN      _       6       conj    2.1:nmod:in                 _
+8       and     _       CC      CC      _       6       cc      2:cc                        _
+9       water   _       NN      NN      _       6       conj    2.2:nmod:in                 _
+
+# test:eud_case_sons_of_conjuncts-blackbox1
+1       He      _       PRP     PRP     _       2       nsubj   2:nsubj|2.1:nsubj|2.2:nsubj _
+2       saw     _       VBD     VBD     _       0       root    0:root                      _
+2.1     saw	    _	    VBD	    VBD	    _	    _	    _	    2:conj:and                  CopyOf=2
+2.2 	saw	    _	    VBD	    VBD	    _	    _	    _	    2:conj:and	                CopyOf=2
+3       them    _       PRP     PRP     _       2       dobj    2:dobj                      _
+4       in      _       IN      IN      _       6       case    6:case|7:case|9:case        _
+5       the     _       DT      DT      _       6       det     6:det                       _
+6       air     _       NN      NN      _       2       nmod    2:nmod:in                   _
+7       ground  _       NN      NN      _       6       conj    2.1:nmod:in                 _
+8       and     _       CC      CC      _       6       cc      2:cc                        _
+9       water   _       NN      NN      _       6       conj    2.2:nmod:in                 _
 
 # test:eud_xcomp_propagation-blackbox1
 1	He	_	PRP	PRP	_	2	nsubj	2:nsubj|4:nsubj:xcomp(INF)	_
@@ -429,12 +381,6 @@
 12	a	_	DT	DT	_	13	det	13:det	_
 13	trip	_	NN	NN	_	10	nmod	10:nmod:for	_
 
-# test:eud_xcomp_propagation-blackbox5
-1	He	_	PRP	PRP	_	2	nsubj	2:nsubj|4:nsubj:xcomp(INF)	_
-2	decided	_	VBD	VBD	_	0	root	0:root	_
-3	not	_	RB	RB	_	4	neg	4:neg	_
-4	to	_	TO	TO	_	2	xcomp	2:xcomp	_
-
 # test:eud_prep_patterns-blackbox1
 1	I	_	PRP	PRP	_	2	nsubj	2:nsubj	_
 2	like	_	VBP	VBP	_	0	root	0:root	_
@@ -461,6 +407,47 @@
 8	feel	_	VB	VB	_	6	ccomp	6:ccomp	_
 9	bad	_	JJ	JJ	_	8	xcomp	8:xcomp	_
 
+# test:eud_prep_patterns-blackbox4
+1       I       _       PRP     PRP     _       2       nsubj   2:nsubj             _
+2       passed  _       VBP     VBP     _       0       root    0:root              _
+3       in      _       IN      IN      _       6       case    6:case              _
+4       front   _       NN      NN      _       3       mwe     3:mwe               _
+5       of      _       IN      IN      _       3       mwe     3:mwe               _
+6       dogs    _       NNS     NNS     _       2       nmod    2:nmod:in_front_of  _
+
+# test:eud_prep_patterns-blackbox5
+1	He	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass	_
+2	was	_	VBD	VBD	_	3	auxpass	3:auxpass	_
+3	chosen	_	VBN	VBN	_	0	root	0:root	_
+4	by	_	IN	IN	_	5	case	5:case	_
+5	me	_	PRP	PRP	_	3	nmod	3:nmod:agent|3:nsubj@passive()	_
+
+# test:eud_prep_patterns-blackbox6
+1	He	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass	_
+2	was	_	VBD	VBD	_	3	auxpass	3:auxpass	_
+3	chosen	_	VBN	VBN	_	0	root	0:root	_
+4	by	_	IN	IN	_	5	case	5:case	_
+5	me	_	PRP	PRP	_	3	nmod	3:nmod:agent|3:nsubj@passive()	_
+6	and	_	CC	CC	_	3	cc	3:cc	_
+7	I	_	PRP	PRP	_	9	nsubjpass	9:dobj@passive()|9:nsubjpass	_
+8	was	_	VBD	VBD	_	9	auxpass	9:auxpass	_
+9	chosen	_	VBN	VBN	_	3	conj	3:conj:and	_
+10	by	_	IN	IN	_	11	case	11:case	_
+11	you	_	PRP	PRP	_	9	nmod	9:nmod:agent|9:nsubj@passive()	_
+
+# test:eud_prep_patterns-blackbox7
+1	They	_	PRP	PRP	_	2	nsubj	2:nsubj	_
+2	chose	_	VBD	VBD	_	0	root	0:root	_
+3	by	_	IN	IN	_	4	case	4:case	_
+4	themselves	_	PRP	PRP	_	2	nmod	2:nmod:by	_
+
+# test:eud_prep_patterns-blackbox8
+1	He	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass	_
+2	was	_	VBD	VBD	_	3	auxpass	3:auxpass	_
+3	chosen	_	VBN	VBN	_	0	root	0:root	_
+4	with	_	IN	IN	_	5	case	5:case	_
+5	me	_	PRP	PRP	_	3	nmod	3:nmod:with	_
+
 # test:eud_conj_info-blackbox1
 1	I	_	PRP	PRP	_	2	nsubj	2:nsubj	_
 2	love	_	VBP	VBP	_	0	root	0:root	_
@@ -468,9 +455,9 @@
 4	but	_	CC	CC	_	3	cc	3:cc	_
 5	also	_	RB	RB	_	6	advmod	6:advmod	_
 6	her	_	PRP	PRP	_	3	conj	2:dobj|3:conj:and	_
-8	but	_	CC	CC	_	3	cc	3:cc	_
-9	not	_	RB	RB	_	10	neg	10:neg	_
-10	myself	_	PRP	PRP	_	3	conj	2:dobj|3:conj:negcc	_
+7	but	_	CC	CC	_	3	cc	3:cc	_
+8	not	_	RB	RB	_	9	neg	9:neg	_
+9	myself	_	PRP	PRP	_	3	conj	2:dobj|3:conj:negcc	_
 
 # test:extra_aspectual_reconstruction-blackbox1
 1	FOX	_	NN	NN	_	2	nsubj	3:nsubj	_
@@ -494,6 +481,12 @@
 4	to	_	TO	TO	_	5	case	5:case	_
 5	friends	_	VB	VB	_	3	nmod	3:nmod:to	_
 
+# test:eud_xcomp_propagation_no_to-blackbox2
+1	He	_	PRP	PRP	_	2	nsubj	2:nsubj|4:nsubj:xcomp(INF)	_
+2	decided	_	VBD	VBD	_	0	root	0:root	_
+3	not	_	RB	RB	_	4	neg	4:neg	_
+4	to	_	TO	TO	_	2	xcomp	2:xcomp	_
+
 # test:extra_advcl_propagation-blackbox1
 1	I	_	PRP	PRP	_	2	nsubj	2:nsubj	_
 2	told	_	VBD	VBD	_	0	root	0:root	_
@@ -520,7 +513,7 @@
 6	a	_	DT	DT	_	7	det	7:det	_
 7	cake	_	NN	NN	_	5	dobj	5:dobj	_
 
-# test:extra_acl_propagation-blackbox1
+# test:extra_acl_to_propagation-blackbox1
 1	Putin	_	NNP	NNP	_	3	nsubj	3:nsubj|6:nsubj@acl(NULL, to)	_
 2	has	_	VBZ	VBZ	_	3	aux	3:aux	_
 3	made	_	VBN	VBN	_	0	root	0:root	_
@@ -531,7 +524,7 @@
 8	with	_	IN	IN	_	9	case	9:case	_
 9	Hu	_	NNP	NNP	_	6	nmod	6:nmod:with	_
 
-# test:extra_acl_propagation-blackbox2
+# test:extra_acl_propagation-blackbox1
 1	soldiers	_	NNS	NNS	_	3	nsubj	3:nsubj	_
 2	have	_	VBP	VBP	_	3	aux	3:aux	_
 3	discovered	_	VBN	VBN	_	0	root	0:root	_
@@ -550,18 +543,18 @@
 6	kidnapped	_	VBN	VBN	_	2	dep	2:dep	_
 7	nearby	_	RB	RB	_	6	advmod	6:advmod	_
 
-# test:extra_conj_propagation_of_nmods-blackbox1
+# test:extra_conj_propagation_of_nmods_forward-blackbox1
 1	he	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass|5:dobj@passive()|5:nsubjpass	_
-2	was	_	VBD	VBD	_	3	auxpass	3:auxpass	_
+2	was	_	VBD	VBD	_	3	auxpass	3:auxpass|5:auxpass	_
 3	wounded	_	VBN	VBN	_	0	root	0:root	_
 4	and	_	CC	CC	_	3	cc	3:cc	_
 5	trapped	_	VBN	VBN	_	3	conj	3:conj:and	_
 6	in	_	IN	IN	_	7	case	7:case	_
 7	Korea	_	NNP	NNP	_	3	nmod	3:nmod:in|5:nmod:in@conj(and, UNC)	_
 
-# test:extra_conj_propagation_of_nmods-blackbox2
+# test:extra_conj_propagation_of_nmods_backwards-blackbox1
 1	he	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass|6:dobj@passive()|6:nsubjpass	_
-2	was	_	VBD	VBD	_	3	auxpass	3:auxpass	_
+2	was	_	VBD	VBD	_	3	auxpass	3:auxpass|6:auxpass	_
 3	wounded	_	VBN	VBN	_	0	root	0:root	_
 4	heavily	_	RB	RB	_	3	advmod	3:advmod	_
 5	and	_	CC	CC	_	3	cc	3:cc	_
@@ -569,8 +562,8 @@
 7	in	_	IN	IN	_	8	case	8:case	_
 8	Korea	_	NNP	NNP	_	6	nmod	3:nmod:in@conj(and, UNC)|6:nmod:in	_
 
-# test:extra_conj_propagation_of_nmods-blackbox3
-1	he	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass|8:dobj@passive()|8:nsubjpass	_
+# test:extra_conj_propagation_of_nmods_backwards-blackbox2
+1	he	_	PRP	PRP	_	3	nsubjpass	3:dobj@passive()|3:nsubjpass|8:nsubj	_
 2	was	_	VBD	VBD	_	3	auxpass	3:auxpass	_
 3	wounded	_	VBN	VBN	_	0	root	0:root	_
 4	in	_	IN	IN	_	5	case	5:case	_
@@ -728,17 +721,7 @@
 4	place	_	NN	NN	_	2	dobj	2:dobj|6:nmod:in@acl(RELCL, REDUCED)	_
 5	we	_	PRP	PRP	_	6	nsubj	6:nsubj	_
 6	visited	_	VBD	VBD	_	4	acl:relcl	4:acl:relcl	_
-7	in	_	RB	RB	_	6	advmod	6:advmod	_
-
-# test:extra_add_ref_and_collapse-blackbox3
-1	I	_	PRP	PRP	_	2	nsubj	2:nsubj	_
-2	understand	_	VBP	VBP	_	0	root	0:root	_
-3	the	_	DT	DT	_	4	det	4:det	_
-4	reason	_	NN	NN	_	2	dobj	2:dobj|7:nmod:because_of@acl(RELCL, why)	_
-5	why	_	WRB	WRB	_	7	advmod	7:advmod	_
-6	they	_	PRP	PRP	_	7	nsubj	7:nsubj	_
-7	did	_	VBD	VBD	_	4	acl:relcl	4:acl:relcl	_
-8	it	_	PRP	PRP	_	7	dobj	7:dobj	_
+7	in	_	RB	RB	_	6	advmod	6:case	_
 
 # test:extra_hyphen_reconstruction-blackbox1
 1	I	_	PRP	PRP	_	2	nsubj	2:nsubj	_
@@ -772,7 +755,7 @@
 9	spreading	_	VBG	VBG	_	8	xcomp	8:xcomp	_
 
 # test:extra_copula_reconstruction-blackbox1
-1	She	_	PRP	PRP	_	3	nsubj	2.1:nsubj|3:nsubj@amod()+cop()	_
+1	She	_	PRP	PRP	_	3	nsubj	2.1:nsubj|3:nsubj@amod()	_
 2	is	_	VBZ	VBZ	_	3	cop	2.1:ev	_
 2.1	STATE	_	_	_	_	_	_	0:root	_
 3	smart	_	JJ	JJ	_	0	root	1:amod@cop()|2.1:xcomp	_
@@ -824,27 +807,27 @@
 4.1	STATE	_	_	_	_	_	_	2:xcomp	_
 5	queen	_	NN	NN	_	2	xcomp	4.1:xcomp	_
 
-# test:extra_evidential_reconstruction-blackbox1
-1	He	_	PRP	PRP	_	2	nsubj	2.1:nsubj|3:nsubj@amod()+cop()	_
+# test:extra_evidential_basic_reconstruction-blackbox1
+1	He	_	PRP	PRP	_	2	nsubj	2.1:nsubj|3:nsubj@amod()	_
 2	seems	seem	VBZ	VBZ	_	0	root	2.1:ev	_
 2.1	STATE	_	_	_	_	_	_	0:root	_
 3	smart	_	JJ	JJ	_	2	xcomp	1:amod@cop()|2.1:xcomp	_
 
-# test:extra_evidential_reconstruction-blackbox2
+# test:extra_evidential_basic_reconstruction-blackbox2
 1	He	_	PRP	PRP	_	2	nsubj	2.1:nsubj	_
 2	seems	seem	VBZ	VBZ	_	0	root	2.1:ev	_
 2.1	STATE	_	_	_	_	_	_	0:root	_
 3	the	_	DT	DT	_	4	det	4:det	_
 4	king	_	NN	NN	_	2	xcomp	2.1:xcomp	_
 
-# test:extra_evidential_reconstruction-blackbox3
+# test:extra_evidential_basic_reconstruction-blackbox3
 1	He	_	PRP	PRP	_	2	nsubj	2.1:nsubj	_
 2	seems	seem	VBZ	VBZ	_	0	root	2.1:ev	_
 2.1	STATE	_	_	_	_	_	_	0:root	_
 3	from	_	IN	IN	_	4	case	4:case	_
 4	Europe	_	NNP	NNP	_	2	nmod	2.1:nmod:from	_
 
-# test:extra_evidential_reconstruction-blackbox4
+# test:extra_evidential_basic_reconstruction-blackbox4
 1	He	_	PRP	PRP	_	2	nsubj	4.1:nsubj	_
 2	seems	seem	VBZ	VBZ	_	0	root	4:ev@xcomp(EVIDENTIAL)	_
 3	to	_	TO	TO	_	5	mark	4:mark	_
@@ -852,20 +835,20 @@
 4.1	STATE	_	_	_	_	_	_	0:root	_
 5	smart	_	JJ	JJ	_	2	xcomp	4.1:xcomp	_
 
-# test:extra_evidential_reconstruction-blackbox5
+# test:extra_evidential_xcomp_reconstruction-blackbox1
 1	He	_	PRP	PRP	_	2	nsubj	4:nsubj	_
 2	seems	seem	VBZ	VBZ	_	0	root	4:ev@xcomp(EVIDENTIAL)	_
 3	to	_	TO	TO	_	4	mark	4:mark	_
 4	appear	appear	VB	VB	_	2	xcomp	0:root	_
 5	early	_	RB	RB	_	4	advmod	4:advmod	_
 
-# test:extra_evidential_reconstruction-blackbox6
-1	He	_	PRP	PRP	_	2	nsubj	4.1:nsubj	_
+# test:extra_evidential_xcomp_reconstruction-blackbox2
+1	He	_	PRP	PRP	_	2	nsubj	4.1:nsubj|5:nsubj@amod()	_
 2	seems	seem	VBZ	VBZ	_	0	root	4:ev@xcomp(EVIDENTIAL)	_
 3	to	_	TO	TO	_	4	mark	4:mark	_
 4	appear	appear	VB	VB	_	2	xcomp	4.1:ev	_
 4.1	STATE	_	_	_	_	_	_	0:root	_
-5	funny	_	JJ	JJ	_	4	xcomp	4.1:xcomp	_
+5	funny	_	JJ	JJ	_	4	xcomp	1:amod@cop()|4.1:xcomp	_
 
 # test:no_node_adding-blackbox1
 1       He      _       PRP     PRP     _       2       nsubj   2:nsubj       _
@@ -873,7 +856,7 @@
 3       smart   _       JJ      JJ      _       2       xcomp   2:xcomp       _
 
 # test:no_node_adding-blackbox2
-1       He      _       PRP     PRP     _       3       nsubj   2:nsubj|3:nsubj@amod()+cop()    _
+1       He      _       PRP     PRP     _       3       nsubj   2:nsubj|3:nsubj@amod()    _
 2       is      _       VBZ     VBZ     _       3       cop     0:root                          _
 3       smart   _       JJ      JJ      _       0       root    1:amod@cop()|2:xcomp            _
 
diff --git a/tests/handcrafted_tests.conllu b/tests/handcrafted_tests.conllu
index 793af1c..7c5c84c 100644
--- a/tests/handcrafted_tests.conllu
+++ b/tests/handcrafted_tests.conllu
@@ -86,7 +86,7 @@
 
 # test:eudpp_process_complex_2wp-blackbox3
 # purpose: test for gov2's relation type (nmod)
-1       As      _       IN      IN      _       2       case    _       _
+1       As      _       IN      IN      _       2       advmod    _       _
 2       close   _       NN      NN      _       7       nmod    _       _
 3       to      _       TO      TO      _       4       case    _       _
 4       me      _       PRP     PRP     _       2       nmod    _       _
@@ -131,14 +131,14 @@
 8       can     _       MD      MD      _       9       aux     _       _
 9       be      _       VB      VB      _       0       root    _       _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox1
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox1
 # purpose: test basic functionality of 3w + 'A'
 1       A       _       DT      DT      _       2       det     _       _
 2       couple  _       NN      NN      _       0       root    _       _
 3       of      _       IN      IN      _       4       case    _       _
 4       people  _       NNS     NNS     _       2       nmod    _       _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox2
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox2
 # purpose: test with an amod
 1       An      _       DT      DT      _       3       det     _       _
 2       angry   _       JJ      JJ      _       3       amod    _       _
@@ -146,14 +146,14 @@
 4       of      _       IN      IN      _       5       case    _       _
 5       people  _       NNS     NNS     _       3       nmod    _       _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox3
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox3
 # purpose: test with word out of dict
 1       An      _       DT      DT      _       2       det     _       _
 2       horde   _       NN      NN      _       0       root    _       _
 3       of      _       IN      IN      _       4       case    _       _
 4       people  _       NNS     NNS     _       2       nmod    _       _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox4
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox4
 # purpose: test with w3 not sequential
 1       A       _       DT      DT      _       2       det     _       _
 2       ton     _       NN      NN      _       0       root    _       _
@@ -162,14 +162,14 @@
 5       of      _       IN      IN      _       6       case    _       _
 6       people  _       NNS     NNS     _       2       nmod    _       _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox5
+# test:eudpp_demote_quantificational_modifiers_3w-blackbox5
 # purpose: test gov2 not as NN or PRP
 1       A       _       DT      DT      _       2       det     _       _
 2       ton     _       NN      NN      _       0       root    _       _
 3       of      _       IN      IN      _       4       case    _       _
-4       what    _       WP      WP      _       2       nmod    _       _
+4       working    _       VB      VB      _       2       nmod    _       _
 
-# test:eudpp_demote_quantificational_modifiers-blackbox6
+# test:eudpp_demote_quantificational_modifiers_2w-blackbox1
 # purpose: test basic functionality of 2w and 2w+det. also test two matches in one sentence
 1       Several _       JJ      JJ      _       5       nsubj   _       _
 2       of      _       IN      IN      _       3       case    _       _
@@ -184,7 +184,22 @@
 11      are     _       VBP     VBP     _       12      aux     _       _
 12      reading _       VBG     VBG     _       5       advcl   _       _
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox1
+# test:eudpp_demote_quantificational_modifiers_det-blackbox1
+# purpose: test basic functionality of 2w and 2w+det. also test two matches in one sentence
+1       Several _       JJ      JJ      _       5       nsubj   _       _
+2       of      _       IN      IN      _       3       case    _       _
+3       us      _       PRP     PRP     _       1       nmod    _       _
+4       were    _       VBD     VBD     _       5       aux     _       _
+5       playing _       VBG     VBG     _       0       root    _       _
+6       while   _       IN      IN      _       12      mark    _       _
+7       some    _       DT      DT      _       12      nsubj   _       _
+8       of      _       IN      IN      _       10      case    _       _
+9       the     _       DT      DT      _       10      det     _       _
+10      others  _       NNS     NNS     _       7       nmod    _       _
+11      are     _       VBP     VBP     _       12      aux     _       _
+12      reading _       VBG     VBG     _       5       advcl   _       _
+
+# test:eudpp_expand_pp_conjunctions-blackbox1
 # purpose: test basic functionality
 1	Bill	Bill	PROPN	NNP	_	2	nsubj	_	_
 2	flies	fly		VERB	VBZ	_	0	root	_	_
@@ -194,7 +209,7 @@
 6	from	from	ADP		IN	_	7	case	_	_
 7	Serbia	Serbia	PROPN	NNP	_	4	conj	_	_
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox2
+# test:eudpp_expand_pp_conjunctions-blackbox2
 # purpose: test multiple ccs and conjs, also test for multi word conj phrases
 1       Bill    _       NNP     NNP     _       2       nsubj   _       _
 2       flies   _       VBZ     VBZ     _       0       root    _       _
@@ -215,7 +230,7 @@
 17      from    _       IN      IN      _       18      case    _     	_
 18      Israel  _       NNP     NNP     _       4       conj    _		_
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox3
+# test:eudpp_expand_pp_conjunctions-blackbox3
 # purpose: test two separate matches in one sentence
 1       Bill    _       NNP     NNP     _       2       nsubj   _       _
 2       flies   _       VBZ     VBZ     _       0       root    _       _
@@ -233,7 +248,7 @@
 14      from    _       IN      IN      _       15      case    _       _
 15      Italy   _       NNP     NNP     _       12      conj    _       _
 
-# test:eudpp_expand_pp_or_prep_conjunctions-blackbox4
+# test:eudpp_expand_prep_conjunctions-blackbox1
 # purpose: test prep
 1       Bill    _       NNP     NNP     _       2       nsubj   _       _
 2       flies   _       VBZ     VBZ     _       0       root    _       _
@@ -242,43 +257,6 @@
 5       from    _       IN      IN      _       3       conj    _       _
 6       France  _       NNP     NNP     _       2       nmod    _       _
 
-# test:eud_passive_agent-blackbox1
-# purpose: test basic functionality
-1       He      _       PRP     PRP     _       3       nsubjpass   _       _
-2       was     _       VBD     VBD     _       3       auxpass 	_       _
-3       chosen  _       VBN     VBN     _       0       root    	_       _
-4       by      _       IN      IN      _       5       case    	_       _
-5       me      _       PRP     PRP     _       3       nmod    	_       _
-
-# test:eud_passive_agent-blackbox2
-# purpose: test two matches in one sentence
-1       He      _       PRP     PRP     _       3       nsubjpass   _       _
-2       was     _       VBD     VBD     _       3       auxpass 	_       _
-3       chosen  _       VBN     VBN     _       0       root    	_       _
-4       by      _       IN      IN      _       5       case    	_       _
-5       me      _       PRP     PRP     _       3       nmod    	_       _
-6       and     _       CC      CC      _       3       cc      	_       _
-7       I       _       PRP     PRP     _       9       nsubjpass   _       _
-8       was     _       VBD     VBD     _       9       auxpass 	_       _
-9       chosen  _       VBN     VBN     _       3       conj    	_       _
-10      by      _       IN      IN      _       11      case    	_       _
-11      you     _       PRP     PRP     _       9       nmod    	_       _
-
-# test:eud_passive_agent-blackbox3
-# purpose: test with no auxpass
-1       They    	_       PRP     PRP     _       2       nsubj   _       _
-2       chose   	_       VBD     VBD     _       0       root    _       _
-3       by      	_       IN      IN      _       4       case    _       _
-4       themselves  _       PRP     PRP     _       2       nmod    _       _
-
-# test:eud_passive_agent-blackbox4
-# purpose: test with no by
-1       He      _       PRP     PRP     _       3       nsubjpass	_       _
-2       was     _       VBD     VBD     _       3       auxpass		_       _
-3       chosen  _       VBN     VBN     _       0       root    	_       _
-4       with    _       IN      IN      _       5       case   		_       _
-5       me      _       PRP     PRP     _       3       nmod    	_       _
-
 # test:eudpp_add_ref_and_collapse-blackbox1
 # purpose: test two matches in one sentence
 1       The     _       DT      DT      _       2       det     	_       _
@@ -295,65 +273,17 @@
 12      the     _       DT      DT      _       13      det     	_       _
 13      tale    _       NN      NN      _       11      dobj    	_       _
 
-# test:eudpp_add_ref_and_collapse-whitebox1
-# purpose: child is lefter than grandchild
-1	The		the		DET		DT		_	2	det			_	_
-2	boy		boy		NOUN	NN		_	0	root		_	_
-3	who		who		CASE	CASE	_	4	nsubj		_	_
-4	bla		bla		NOUN	NN		_	7	nsubj		_	_
-5	whom	whom	CASE	CASE	_	7	nsubj		_	_
-6	bla		bla		DET		DT		_	7	nsubj		_	_
-7	lived	live	NOUN	NN		_	2	acl:relcl	_	_
-
-# test:eudpp_add_ref_and_collapse-whitebox2
-# purpose: grandchild is lefter than child
-1	The		the		DET		DT		_	2	det			_	_
-2	boy		boy		NOUN	NN		_	0	root		_	_
-3	who		who		CASE	CASE	_	7	nsubj		_	_
-4	bla		bla		NOUN	NN		_	7	nsubj		_	_
-5	whom	whom	CASE	CASE	_	6	nsubj		_	_
-6	bla		bla		DET		DT		_	7	nsubj		_	_
-7	lived	live	NOUN	NN		_	2	acl:relcl	_	_
-
-# test:eudpp_add_ref_and_collapse-whitebox3
-# purpose: two grandchildren to same child
-1	The		the		DET		DT		_	2	det			_	_
-2	boy		boy		NOUN	NN		_	0	root		_	_
-3	who		who		CASE	CASE	_	4	nsubj		_	_
-4	bla		bla		NOUN	NN		_	7	nsubj		_	_
-5	whom	whom	CASE	CASE	_	4	nsubj		_	_
-6	bla		bla		DET		DT		_	7	nsubj		_	_
-7	lived	live	NOUN	NN		_	2	acl:relcl	_	_
-
-# test:eudpp_add_ref_and_collapse-whitebox4
-# purpose: two children to same child
-1	The		the		DET		DT		_	2	det			_	_
-2	boy		boy		NOUN	NN		_	0	root		_	_
-3	who		who		CASE	CASE	_	7	nsubj		_	_
-4	bla		bla		NOUN	NN		_	7	nsubj		_	_
-5	whom	whom	CASE	CASE	_	7	nsubj		_	_
-6	bla		bla		DET		DT		_	7	nsubj		_	_
-7	lived	live	NOUN	NN		_	2	acl:relcl	_	_
-
-# test:eudpp_add_ref_and_collapse-whitebox5
-# purpose: left most relative-word is not descentant of acl-relcl, and grandchild is true leftmost
-1	The		the		DET		DT		_	2	det			_	_
-2	boy		boy		NOUN	NN		_	0	root		_	_
-3	who		who		CASE	CASE	_	1	nsubj		_	_
-4	bla		bla		NOUN	NN		_	7	nsubj		_	_
-5	whom	whom	CASE	CASE	_	4	nsubj		_	_
-6	bla		bla		DET		DT		_	7	nsubj		_	_
-7	lived	live	NOUN	NN		_	2	acl:relcl	_	_
-
-# test:eudpp_add_ref_and_collapse-whitebox6
-# purpose: left most relative-word is not descentant of acl-relcl, and child is true leftmost
-1	The		the		DET		DT		_	2	det			_	_
-2	boy		boy		NOUN	NN		_	0	root		_	_
-3	who		who		CASE	CASE	_	1	nsubj		_	_
-4	bla		bla		NOUN	NN		_	7	nsubj		_	_
-5	whom	whom	CASE	CASE	_	7	nsubj		_	_
-6	bla		bla		DET		DT		_	7	nsubj		_	_
-7	lived	live	NOUN	NN		_	2	acl:relcl	_	_
+
+# test:eudpp_add_ref_and_collapse-blackbox2
+# purpose: test non-reduced case that eud missed
+1       I       _       PRP     PRP     _       2       nsubj   _       _
+2       understand      _       VBP     VBP     _       0       root    _       _
+3       the     _       DT      DT      _       4       det     _       _
+4       reason  _       NN      NN      _       2       dobj    _       _
+5       why     _       WRB     WRB     _       7       advmod  _       _
+6       they    _       PRP     PRP     _       7       nsubj   _       _
+7       did     _       VBD     VBD     _       4       acl:relcl       _       _
+8       it      _       PRP     PRP     _       7       dobj    _       _
 
 # test:eud_subj_of_conjoined_verbs-blackbox1
 # purpose: test basic functionality, with different voice
@@ -396,12 +326,24 @@
 1       He      _       PRP     PRP     _       2       nsubj   _       _
 2       saw     _       VBD     VBD     _       0       root    _       _
 3       them    _       PRP     PRP     _       2       dobj    _       _
-5       in      _       IN      IN      _       7       case    _       _
-6       the     _       DT      DT      _       7       det     _       _
-7       air     _       NN      NN      _       2       nmod    _       _
-9       ground  _       NN      NN      _       7       conj    _       _
-10      and     _       CC      CC      _       7       cc      _       _
-11      water   _       NN      NN      _       7       conj    _       _
+4       in      _       IN      IN      _       6       case    _       _
+5       the     _       DT      DT      _       6       det     _       _
+6       air     _       NN      NN      _       2       nmod    _       _
+7       ground  _       NN      NN      _       6       conj    _       _
+8       and     _       CC      CC      _       6       cc      _       _
+9       water   _       NN      NN      _       6       conj    _       _
+
+# test:eud_case_sons_of_conjuncts-blackbox1
+# purpose: test special case of case propagation when head is nmod
+1       He      _       PRP     PRP     _       2       nsubj   _       _
+2       saw     _       VBD     VBD     _       0       root    _       _
+3       them    _       PRP     PRP     _       2       dobj    _       _
+4       in      _       IN      IN      _       6       case    _       _
+5       the     _       DT      DT      _       6       det     _       _
+6       air     _       NN      NN      _       2       nmod    _       _
+7       ground  _       NN      NN      _       6       conj    _       _
+8       and     _       CC      CC      _       6       cc      _       _
+9       water   _       NN      NN      _       6       conj    _       _
 
 # test:eud_xcomp_propagation-blackbox1
 # purpose: test basic functionality, with subject, and with verb that starts with 'to..' just to test the 'to' regex
@@ -451,13 +393,6 @@
 12      a       _       DT      DT      _       13      det     _       _
 13      trip    _       NN      NN      _       10      nmod    _       _
 
-# test:eud_xcomp_propagation-blackbox5
-# purpose: test special case of 'to' with no following verb
-1       He      _       PRP     PRP     _       2       nsubj   _       _
-2       decided _       VBD     VBD     _       0       root    _       _
-3       not     _       RB      RB      _       4       neg     _       _
-4       to      _       TO      TO      _       2       xcomp   _       _
-
 # test:eud_prep_patterns-blackbox1
 # purpose: test basic case of preposition added to nmod
 1       I       _       PRP     PRP     _       2       nsubj   _       _
@@ -487,6 +422,52 @@
 8       feel    _       VB      VB      _       6       ccomp   _       _
 9       bad     _       JJ      JJ      _       8       xcomp   _       _
 
+# test:eud_prep_patterns-blackbox4
+# purpose: test case of 3 word preposition added to nmod
+1       I       _       PRP     PRP     _       2       nsubj   _       _
+2       passed  _       VBP     VBP     _       0       root    _       _
+3       in      _       IN      IN      _       6       case    _       _
+4       front   _       NN      NN      _       3       mwe     _       _
+5       of      _       IN      IN      _       3       mwe     _       _
+6       dogs    _       NNS     NNS     _       2       nmod    _       _
+
+# test:eud_prep_patterns-blackbox5
+# purpose: test basic functionality
+1       He      _       PRP     PRP     _       3       nsubjpass   _       _
+2       was     _       VBD     VBD     _       3       auxpass 	_       _
+3       chosen  _       VBN     VBN     _       0       root    	_       _
+4       by      _       IN      IN      _       5       case    	_       _
+5       me      _       PRP     PRP     _       3       nmod    	_       _
+
+# test:eud_prep_patterns-blackbox6
+# purpose: test two matches in one sentence
+1       He      _       PRP     PRP     _       3       nsubjpass   _       _
+2       was     _       VBD     VBD     _       3       auxpass 	_       _
+3       chosen  _       VBN     VBN     _       0       root    	_       _
+4       by      _       IN      IN      _       5       case    	_       _
+5       me      _       PRP     PRP     _       3       nmod    	_       _
+6       and     _       CC      CC      _       3       cc      	_       _
+7       I       _       PRP     PRP     _       9       nsubjpass   _       _
+8       was     _       VBD     VBD     _       9       auxpass 	_       _
+9       chosen  _       VBN     VBN     _       3       conj    	_       _
+10      by      _       IN      IN      _       11      case    	_       _
+11      you     _       PRP     PRP     _       9       nmod    	_       _
+
+# test:eud_prep_patterns-blackbox7
+# purpose: test with no auxpass
+1       They    	_       PRP     PRP     _       2       nsubj   _       _
+2       chose   	_       VBD     VBD     _       0       root    _       _
+3       by      	_       IN      IN      _       4       case    _       _
+4       themselves  _       PRP     PRP     _       2       nmod    _       _
+
+# test:eud_prep_patterns-blackbox8
+# purpose: test with no by
+1       He      _       PRP     PRP     _       3       nsubjpass	_       _
+2       was     _       VBD     VBD     _       3       auxpass		_       _
+3       chosen  _       VBN     VBN     _       0       root    	_       _
+4       with    _       IN      IN      _       5       case   		_       _
+5       me      _       PRP     PRP     _       3       nmod    	_       _
+
 # test:eud_conj_info-blackbox1
 # purpose: test cc's added to conj
 1       I       _       PRP     PRP     _       2       nsubj   _       _
@@ -495,9 +476,9 @@
 4       but     _       CC      CC      _       3       cc      _       _
 5       also    _       RB      RB      _       6       advmod  _       _
 6       her     _       PRP     PRP     _       3       conj    _       _
-8       but     _       CC      CC      _       3       cc      _       _
-9       not     _       RB      RB      _       10      neg     _       _
-10      myself  _       PRP     PRP     _       3       conj    _       _
+7       but     _       CC      CC      _       3       cc      _       _
+8       not     _       RB      RB      _       9       neg     _       _
+9       myself  _       PRP     PRP     _       3       conj    _       _
 
 # test:extra_aspectual_reconstruction-blackbox1
 # purpose: test basic case of aspectual verb followed by an clausal complement verb
@@ -524,6 +505,13 @@
 4       to      _   TO      TO      _       5       case    _       _
 5       friends _   VB      VB      _       3       nmod    _       _
 
+# test:eud_xcomp_propagation_no_to-blackbox2
+# purpose: test special case of 'to' with no following verb
+1       He      _       PRP     PRP     _       2       nsubj   _       _
+2       decided _       VBD     VBD     _       0       root    _       _
+3       not     _       RB      RB      _       4       neg     _       _
+4       to      _       TO      TO      _       2       xcomp   _       _
+
 # test:extra_advcl_propagation-blackbox1
 # purpose: test special case of a 'to' connecting to verbs in advcl fashion.
 1       I       _       PRP     PRP     _       2       nsubj   _       _
@@ -553,7 +541,7 @@
 6       a       _       DT      DT      _       7       det     _       _
 7       cake    _       NN      NN      _       5       dobj    _       _
 
-# test:extra_acl_propagation-blackbox1
+# test:extra_acl_to_propagation-blackbox1
 # purpose: test noun modifying clauses(acl+infinitive)
 1       Putin   _       NNP     NNP     _       3       nsubj   _       _
 2       has     _       VBZ     VBZ     _       3       aux     _       _
@@ -565,7 +553,7 @@
 8       with    _       IN      IN      _       9       case    _       _
 9       Hu      _       NNP     NNP     _       6       nmod    _       _
 
-# test:extra_acl_propagation-blackbox2
+# test:extra_acl_propagation-blackbox1
 # purpose: test noun modifying clauses(acl+participle)
 1       soldiers	_       NNS     NNS     _       3       nsubj   _       _
 2       have    	_       VBP     VBP     _       3       aux     _       _
@@ -586,7 +574,7 @@
 6       kidnapped   _       VBN     VBN     _       2       dep     _       _
 7       nearby  	_       RB      RB      _       6       advmod  _       _
 
-# test:extra_conj_propagation_of_nmods-blackbox1
+# test:extra_conj_propagation_of_nmods_forward-blackbox1
 # purpose: test basic functionality, where nmod is attached to father.
 1       he      _       PRP     PRP     _       3       nsubjpass	_       _
 2       was     _       VBD     VBD     _       3       auxpass 	_       _
@@ -596,7 +584,7 @@
 6       in      _       IN      IN      _       7       case    	_       _
 7       Korea   _       NNP     NNP     _       3       nmod    	_       _
 
-# test:extra_conj_propagation_of_nmods-blackbox2
+# test:extra_conj_propagation_of_nmods_backwards-blackbox1
 # purpose: test basic functionality, where nmod is attached to son.
 1       he      _       PRP     PRP     _       3       nsubjpass	_       _
 2       was     _       VBD     VBD     _       3       auxpass 	_       _
@@ -607,7 +595,7 @@
 7       in      _       IN      IN      _       8       case    	_       _
 8       Korea   _       NNP     NNP     _       6       nmod    	_       _
 
-# test:extra_conj_propagation_of_nmods-blackbox3
+# test:extra_conj_propagation_of_nmods_backwards-blackbox2
 # purpose: test no propagation when governor has nmods of his own.
 1       he      	_       PRP     PRP     _       3       nsubjpass	_       _
 2       was     	_       VBD     VBD     _       3       auxpass 	_       _
@@ -788,17 +776,6 @@
 6       visited _       VBD     VBD     _       4       acl:relcl       _       _
 7       in      _       RB      RB      _       6       advmod    _     _
 
-# test:extra_add_ref_and_collapse-blackbox3
-# purpose: test non-reduced case that eud missed
-1       I       _       PRP     PRP     _       2       nsubj   _       _
-2       understand      _       VBP     VBP     _       0       root    _       _
-3       the     _       DT      DT      _       4       det     _       _
-4       reason  _       NN      NN      _       2       dobj    _       _
-5       why     _       WRB     WRB     _       7       advmod  _       _
-6       they    _       PRP     PRP     _       7       nsubj   _       _
-7       did     _       VBD     VBD     _       4       acl:relcl       _       _
-8       it      _       PRP     PRP     _       7       dobj    _       _
-
 # test:extra_hyphen_reconstruction-blackbox1
 # purpose: test basic case
 1       I       _       PRP     PRP     _       2       nsubj   _       _
@@ -879,38 +856,38 @@
 4       be      _       VB      VB      _       5       cop     _       _
 5       queen   _       NN      NN      _       2       xcomp   _       _
 
-# test:extra_evidential_reconstruction-blackbox1
+# test:extra_evidential_basic_reconstruction-blackbox1
 1       He      _       PRP     PRP     _       2       nsubj   _       _
 2       seems   seem       VBZ     VBZ     _       0       root    _       _
 3       smart   _       JJ      JJ      _       2       xcomp   _       _
 
-# test:extra_evidential_reconstruction-blackbox2
+# test:extra_evidential_basic_reconstruction-blackbox2
 1       He      _       PRP     PRP     _       2       nsubj   _       _
 2       seems   seem       VBZ     VBZ     _       0       root    _       _
 3       the     _       DT      DT      _       4       det     _       _
 4       king    _       NN      NN      _       2       xcomp   _       _
 
-# test:extra_evidential_reconstruction-blackbox3
+# test:extra_evidential_basic_reconstruction-blackbox3
 1       He      _       PRP     PRP     _       2       nsubj   _       _
 2       seems   seem       VBZ     VBZ     _       0       root    _       _
 3       from    _       IN      IN      _       4       case    _       _
 4       Europe  _       NNP     NNP     _       2       nmod    _       _
 
-# test:extra_evidential_reconstruction-blackbox4
+# test:extra_evidential_basic_reconstruction-blackbox4
 1       He      _       PRP     PRP     _       2       nsubj   _       _
 2       seems   seem       VBZ     VBZ     _       0       root    _       _
 3       to      _       TO      TO      _       5       mark    _       _
 4       be      _       VB      VB      _       5       cop     _       _
 5       smart   _       JJ      JJ      _       2       xcomp   _       _
 
-# test:extra_evidential_reconstruction-blackbox5
+# test:extra_evidential_xcomp_reconstruction-blackbox1
 1       He      _       PRP     PRP     _       2       nsubj   _       _
 2       seems   seem    VBZ     VBZ     _       0       root    _       _
 3       to      _       TO      TO      _       4       mark    _       _
 4       appear  appear  VB      VB      _       2       xcomp   _       _
 5       early   _       RB      RB      _       4       advmod  _       _
 
-# test:extra_evidential_reconstruction-blackbox6
+# test:extra_evidential_xcomp_reconstruction-blackbox2
 1       He      _       PRP     PRP     _       2       nsubj   _       _
 2       seems   seem    VBZ     VBZ     _       0       root    _       _
 3       to      _       TO      TO      _       4       mark    _       _
diff --git a/tests/test_conversions.py b/tests/test_conversions.py
index 90c1f1f..eeb5316 100644
--- a/tests/test_conversions.py
+++ b/tests/test_conversions.py
@@ -4,14 +4,13 @@
 
 import pybart
 from pybart.conllu_wrapper import parse_conllu, serialize_conllu
-from pybart import converter
+from pybart import converter, pybart_globals
 from pybart import api
 from pybart.graph_token import add_basic_edges
-from pybart.converter import convert, ConvsCanceler
+from pybart.converter import convert
 
 
 class TestConversions:
-    test_names = set()
     out = dict()
     gold = dict()
     gold_combined = dict()
@@ -28,7 +27,6 @@ def setup_class(cls):
                 splited = comment.split('# test:')
                 if (len(splited) == 2) and (splited[0] == ''):
                     test_name = splited[1].split("-")[0]
-                    cls.test_names.add(test_name)
                     specification = splited[1].split("-")[1]
                     if test_name in cls.out:
                         cls.out[test_name][specification] = sentence
@@ -57,36 +55,33 @@ def setup_class(cls):
     
     @staticmethod
     def setup_method():
-        pybart.converter.g_remove_enhanced_extra_info = False
-        pybart.converter.g_remove_aryeh_extra_info = False
+        pybart_globals.g_remove_enhanced_extra_info = False
+        pybart_globals.g_remove_bart_extra_info = False
         pybart.converter.g_remove_node_adding_conversions = False
     
     @classmethod
     def common_logic(cls, cur_name):
         name = cur_name.split("test_")[1]
-        tested_func = getattr(converter, name)
         for spec, sent_ in cls.out[name].items():
-            sent = {k: v.copy() for k, v in sent_.items()}
+            sent = [v.copy() for v in sent_]
             add_basic_edges(sent)
-            try:
-                tested_func(sent)
-            except TypeError:
-                iids = dict()
-                tested_func(sent, iids)
-            serialized_conllu = serialize_conllu([sent], [None], False)
+            converted, _ = convert([sent], True, True, True, math.inf, False, False, False, False, False,
+                                   funcs_to_cancel=list(set(api.get_conversion_names()).difference({name, "extra_inner_weak_modifier_verb_reconstruction"})))
+            serialized_conllu = serialize_conllu(converted, [None], False)
             for gold_line, out_line in zip(cls.gold[name][spec], serialized_conllu.split("\n")):
-                assert gold_line == out_line.split(), spec + str([print(s) for s in serialized_conllu.split("\n")])
+                assert out_line.split() == gold_line, spec + str(print("\n")) + str([print(s) for s in serialized_conllu.split("\n")])
     
     @classmethod
     def common_logic_combined(cls, cur_name, rnac=False):
         name = cur_name.split("test_combined_")[1]
         for spec, sent_ in cls.out[name].items():
-            sent = {k: v.copy() for k, v in sent_.items()}
+            sent = [v.copy() for v in sent_]
             add_basic_edges(sent)
-            converted, _ = convert([sent], True, True, True, math.inf, False, False, rnac, False, False, ConvsCanceler())
+            converted, _ = \
+                convert([sent], True, True, True, math.inf, False, False, rnac, False, False, funcs_to_cancel=[])
             serialized_conllu = serialize_conllu(converted, [None], False)
             for gold_line, out_line in zip(cls.gold_combined[name][spec], serialized_conllu.split("\n")):
-                assert gold_line == out_line.split(), spec + str([print(s) for s in serialized_conllu.split("\n")])
+                assert out_line.split() == gold_line, spec + str(print("\n")) + str([print(s) for s in serialized_conllu.split("\n")])
 
     def test_no_node_adding(self):
         self.common_logic_combined("test_combined_no_node_adding", rnac=True)
diff --git a/tests/test_matcher.py b/tests/test_matcher.py
index 82cf7cc..6bf038a 100644
--- a/tests/test_matcher.py
+++ b/tests/test_matcher.py
@@ -1,8 +1,55 @@
 import math
-from pybart.new_matcher import *
-import spacy
+import pytest
+from pybart.spacy_wrapper import parse_spacy_sent
+from pybart.matcher import *
+from pybart import matcher
+from pybart.graph_token import Token as BartToken, TokenId
 
-nlp = spacy.load("en_ud_model_sm")
+
+def stub_get_text(stub_self, i):
+    return {0: "test1", 1: "test2", 2: "test3"}[i]
+
+
+def stub_get_labels(stub_self, child=0, parent=0):
+    if child == 1:
+        if parent == 2:
+            return ["bad_label"]
+        else:
+            return ["some_label"]
+    elif parent == 3 or parent == 2:
+        return ["some_label"]
+    else:
+        return []
+
+
+@pytest.fixture(scope="session", autouse=True)
+def cleanup(request):
+    keep_get_text = matcher.get_text
+    matcher.get_text = stub_get_text
+    keep_get_labels = matcher.get_labels
+    matcher.get_labels = stub_get_labels
+
+    def remove_test_dir():
+        matcher.get_text = keep_get_text
+        matcher.get_labels = keep_get_labels
+    request.addfinalizer(remove_test_dir)
+
+
+sentences = [[BartToken(TokenId(1), "He", "he", "", "PRP", "", TokenId(2), "nsubj", "", ""),
+              BartToken(TokenId(2), "went", "go", "", "VBD", "", TokenId(0), "root", "", ""),
+              BartToken(TokenId(3), "home", "home", "", "RB", "", TokenId(2), "advmod", "", ""),
+              BartToken(TokenId(4), "today", "today", "", "NN", "", TokenId(2), "nmod:tmod", "", "")],
+             [BartToken(TokenId(1), "test", "test", "", "NN", "", TokenId(1), "compound", "", ""),
+              BartToken(TokenId(2), "sentence", "sentence", "", "NN", "", TokenId(0), "root", "", ""),
+              BartToken(TokenId(3), "1", "1", "", "CD", "", TokenId(1), "nummod", "", "")],
+             [BartToken(TokenId(1), "He", "", "", "PRP", "", TokenId(2), "nsubj", "", ""),
+              BartToken(TokenId(2), "wanted", "", "", "VBD", "", TokenId(0), "root", "", ""),
+              BartToken(TokenId(3), "to", "", "", "TO", "", TokenId(4), "mark", "", ""),
+              BartToken(TokenId(4), "go", "", "", "VBD", "", TokenId(2), "xcomp", "", "")],
+             [BartToken(TokenId(1), "He", "", "", "PRP", "", TokenId(2), "nsubj", "", ""),
+              BartToken(TokenId(2), "went", "", "", "VBD", "", TokenId(0), "root", "", ""),
+              BartToken(TokenId(3), "home", "", "", "RB", "", TokenId(2), "advmod", "", "")]
+             ]
 
 
 def init_full_constraints():
@@ -33,17 +80,17 @@ def try_helper(try_code, exception_str, exception):
 
 class TestConstraints:
     def test_has_label_from_list(self):
-        label_con = HasLabelFromList(['/nmod.*/', 'nsubj'])
-        assert {"nmod", "nmod:of", "nsubj"} == \
+        label_con = HasLabelFromList(['nmod', 'nsubj'])
+        assert {"nmod", "nsubj"} == \
                label_con.satisfied(["bla_nmod", "nmod", "nmod:of", "nsubj", "nsubjpass"])
         assert label_con.satisfied(["bla_nmod", "nsubjpass"]) is None
 
     def test_has_no_label(self):
-        no_label_con1 = HasNoLabel('/nmod.*/')
+        no_label_con1 = HasNoLabel('nmod')
         no_label_con2 = HasNoLabel('nsubj')
-        assert no_label_con1.satisfied(["bla_nmod", "nsubj", "nsubjpass"]) == set()
+        assert no_label_con1.satisfied(["bla_nmod", "nsubj", "nsubjpass", "nmod:of"]) == set()
         assert no_label_con2.satisfied(["bla_nmod", "nmod", "nmod:of", "nsubjpass"]) == set()
-        assert no_label_con1.satisfied(["bla_nmod", "nmod:of", "nsubj"]) is None
+        assert no_label_con1.satisfied(["bla_nmod", "nmod", "nsubj"]) is None
         assert no_label_con2.satisfied(["bla_nmod", "nsubj", "nsubjpass"]) is None
 
     def test_exact_distance(self):
@@ -109,9 +156,8 @@ def test_sanity(self):
         conversions = [("conversion1", Full(
             tokens=[Token("tok2"), Token("tok1", spec=[Field(FieldNames.WORD, ["went"])])],
             edges=[Edge("tok1", "tok2", [HasLabelFromList(["some_label"])])]))]
-        matcher = Matcher([NamedConstraint(name, constraint) for name, constraint in conversions], nlp.vocab)
+        matcher = Matcher([NamedConstraint(name, constraint) for name, constraint in conversions])
 
-        sentences = [StubSentence("He went home today")]
         for sent in sentences:
             m = matcher(sent)
             for conv_name in m.names():
@@ -123,14 +169,14 @@ def test_sanity(self):
 
     def test_get_matched_labels(self):
         assert {"nmod:of", "bla"} == get_matched_labels(
-            [HasLabelFromList(["/nmod.*/", "nsubj", "bla"]), HasNoLabel("dobj")], ["nmod:of", "bla"])
+            [HasLabelFromList(["nmod:of", "nsubj", "bla"]), HasNoLabel("dobj")], ["nmod:of", "bla"])
         assert set() == get_matched_labels([HasNoLabel("dobj")], ["nsubjpass"])
         assert get_matched_labels(
-            [HasLabelFromList(["/nmod.*/", "nsubj", "bla"]), HasNoLabel("dobj")], ["nsubjpass"]) is None
+            [HasLabelFromList(["nmod", "nsubj", "bla"]), HasNoLabel("dobj")], ["nsubjpass"]) is None
         assert get_matched_labels(
-            [HasLabelFromList(["/nmod.*/", "nsubj", "bla"]), HasNoLabel("dobj")], ["nsubj", "dobj"]) is None
+            [HasLabelFromList(["nmod", "nsubj", "bla"]), HasNoLabel("dobj")], ["nsubj", "dobj"]) is None
         assert get_matched_labels(
-            [HasLabelFromList(["/nmod.*/", "nsubj", "bla"]), HasNoLabel("dobj")], ["dobj"]) is None
+            [HasLabelFromList(["nmod", "nsubj", "bla"]), HasNoLabel("dobj")], ["dobj"]) is None
 
     def test_filter_distance_constraints(self):
         gm = GlobalMatcher(Full(tokens=[Token("tok1"), Token("tok2"), Token("tok3", optional=True)],
@@ -142,8 +188,8 @@ def test_filter_concat_constraints(self):
         gm = GlobalMatcher(Full(tokens=[Token("tok1"), Token("tok2"), Token("tok3", optional=True)],
                                 concats=[TokenPair({"test1_test2"}, "tok1", "tok2"),
                                          TokenPair({"test1_test3"}, "tok1", "tok3")]))
-        assert gm._filter_concat_constraints({"tok1": 0, "tok2": 1}, StubSentence())
-        assert not gm._filter_concat_constraints({"tok1": 0, "tok2": 2}, StubSentence())
+        assert gm._filter_concat_constraints({"tok1": 0, "tok2": 1}, sentences[0])
+        assert not gm._filter_concat_constraints({"tok1": 0, "tok2": 2}, sentences[0])
 
     def test_try_merge(self):
         assert GlobalMatcher._try_merge({"a": 1, "b": 2, "c": 3}, {"b": 2, "c": 3, "d": 4}) == \
@@ -162,33 +208,33 @@ def test_filter_edge_constraints(self):
         # no parents
         assert gm._filter_edge_constraints({"tok1": [2]}, None) == []
         # get_matched_labels returns None (and that's it)
-        assert gm._filter_edge_constraints({"tok1": [1], "tok2": [2]}, StubSentence()) == []
+        assert gm._filter_edge_constraints({"tok1": [1], "tok2": [2]}, sentences[0]) == []
         # get_matched_labels returns None (once, but there are more results)
-        assert gm._filter_edge_constraints({"tok1": [1], "tok2": [3, 4]}, StubSentence()) == \
-               [[{"tok1": 1, "tok2": 3}, {"tok1": 1, "tok2": 4}]]
+        assert gm._filter_edge_constraints({"tok1": [1], "tok2": [3, 4]}, sentences[0]) == \
+               [(False, [{"tok1": 1, "tok2": 3}, {"tok1": 1, "tok2": 4}])]
 
         # more than one edge_assignments
         gm = GlobalMatcher(Full(tokens=[Token("tok1"), Token("tok2"), Token("tok3")], edges=[
             Edge("tok1", "tok2", [HasLabelFromList(["some_label"])]),
             Edge("tok1", "tok3", [HasLabelFromList(["some_label"])])]))
-        assert gm._filter_edge_constraints({"tok1": [1], "tok2": [3, 4], "tok3": [5, 6]}, StubSentence()) == \
-               [[{"tok1": 1, "tok2": 3}, {"tok1": 1, "tok2": 4}], [{"tok1": 1, "tok3": 5}, {"tok1": 1, "tok3": 6}]]
+        assert gm._filter_edge_constraints({"tok1": [1], "tok2": [3, 4], "tok3": [5, 6]}, sentences[0]) == \
+               [(False, [{"tok1": 1, "tok2": 3}, {"tok1": 1, "tok2": 4}]), (False, [{"tok1": 1, "tok3": 5}, {"tok1": 1, "tok3": 6}])]
 
     def test_merge_edges_assignments(self):
         # no edges_assignments
         assert GlobalMatcher._merge_edges_assignments([]) == []
         # big unsuccessful merge
         assert GlobalMatcher._merge_edges_assignments([
-            [{"a": 1, "b": 2}, {"a": 1, "b": 3}, {"a": 4, "b": 5}],
-            [{"b": 3, "c": 6}, {"b": 5, "c": 8}, {"b": 7, "c": 9}],
-            [{"e": 100, "f": 200}],
-            [{"c": 10, "d": 11}, {"c": 10, "d": 12}, {"c": 1, "d": 2}]]) == []
+            (False, [{"a": 1, "b": 2}, {"a": 1, "b": 3}, {"a": 4, "b": 5}]),
+            (False, [{"b": 3, "c": 6}, {"b": 5, "c": 8}, {"b": 7, "c": 9}]),
+            (False, [{"e": 100, "f": 200}]),
+            (False, [{"c": 10, "d": 11}, {"c": 10, "d": 12}, {"c": 1, "d": 2}])]) == []
         # big successful merge
         assert GlobalMatcher._merge_edges_assignments([
-            [{"a": 1, "b": 2}, {"a": 1, "b": 3}, {"a": 4, "b": 5}],
-            [{"b": 3, "c": 6}, {"b": 5, "c": 8}, {"b": 5, "c": 7}, {"b": 7, "c": 9}],
-            [{"e": 100, "f": 200}],
-            [{"c": 6, "d": 11}, {"c": 6, "d": 2}, {"c": 8, "d": 1000}, {"c": 10, "d": 12}, {"c": 1, "d": 2}]]) == \
+            (False, [{"a": 1, "b": 2}, {"a": 1, "b": 3}, {"a": 4, "b": 5}]),
+            (False, [{"b": 3, "c": 6}, {"b": 5, "c": 8}, {"b": 5, "c": 7}, {"b": 7, "c": 9}]),
+            (False, [{"e": 100, "f": 200}]),
+            (False, [{"c": 6, "d": 11}, {"c": 6, "d": 2}, {"c": 8, "d": 1000}, {"c": 10, "d": 12}, {"c": 1, "d": 2}])]) == \
             [{"a": 1, "b": 3, "c": 6, "d": 11, "e": 100, "f": 200},
              {"a": 1, "b": 3, "c": 6, "d": 2, "e": 100, "f": 200},
              {"a": 4, "b": 5, "c": 8, "d": 1000, "e": 100, "f": 200}]
@@ -196,57 +242,45 @@ def test_merge_edges_assignments(self):
     def test_gm_apply(self):
         # no merges
         gm = GlobalMatcher(Full())
-        assert len(list(gm.apply({}, StubSentence()))) == 0
+        assert len(list(gm.apply({}, sentences[0]))) == 0
         # only one merge that fails a distance or concat filter
         gm = GlobalMatcher(Full(tokens=[Token("tok1"), Token("tok2"), Token("tok3", capture=False)],
                                 edges=[Edge("tok1", "tok2", [HasLabelFromList(["some_label"])]),
                                        Edge("tok1", "tok3", [HasLabelFromList(["some_label"])])],
                                 distances=[ExactDistance("tok1", "tok2", 10)]))
-        assert len(list(gm.apply({"tok1": [1], "tok2": [3]}, StubSentence()))) == 0
+        assert len(list(gm.apply({"tok1": [1], "tok2": [3]}, sentences[0]))) == 0
         # sanity (but have redundant token names so we can validate they are filtered)
-        for res in gm.apply({"tok1": [1], "tok2": [12], "tok3": [2]}, StubSentence()):
+        for res in gm.apply({"tok1": [1], "tok2": [12], "tok3": [2]}, sentences[0]):
             assert res.edge(1, 12) == {"some_label"}
             assert res.token("tok1") == 1
             assert res.token("tok2") == 12
             assert res.token("tok3") == -1
 
-    def test_make_patterns(self):
-        assert [
-                   ("1", False, {"LOWER": {"NOT_IN": ["tok1", "token1"]}, "LEMMA": {"IN": ["tok", "token"]}}),
-                   ("2", True, {"TAG": {"NOT_IN": ["VB", "JJ"]}, "ENT_TYPE": {"IN": ["PER", "ORG"]}})] == \
-               TokenMatcher._make_patterns([
-                   Token("1", optional=True, spec=[
-                       Field(in_sequence=False, field=FieldNames.WORD, value=["tok1", "token1"]),
-                       Field(in_sequence=True, field=FieldNames.LEMMA, value=["tok", "token"])]),
-                   Token("2", spec=[
-                       Field(in_sequence=False, field=FieldNames.TAG, value=["VB", "JJ"]),
-                       Field(in_sequence=True, field=FieldNames.ENTITY, value=["PER", "ORG"])])])
-
     def test_post_spacy_matcher(self):
         # empty matched_tokens
-        tm = TokenMatcher([], nlp.vocab)
-        assert {} == tm._post_spacy_matcher({}, StubSentence())
+        tm = TokenMatcher([])
+        assert {} == tm._post_local_matcher({}, sentences[0])
 
         # have one no_children with children, have one out_matched/in_matched as None, have one successful token
         tm = TokenMatcher([Token("tok1", no_children=True),
                            Token("tok2", outgoing_edges=[HasNoLabel("some_label")]),
-                           Token("tok3")], nlp.vocab)
-        assert {"tok2": [5], "tok3": [3, 4]} == \
-               tm._post_spacy_matcher({"tok1": [3], "tok2": [3, 5], "tok3": [3, 4]}, StubSentence())
+                           Token("tok3")])
+        assert {'tok1': [3], "tok2": [5], "tok3": [3, 4]} == \
+               tm._post_local_matcher({"tok1": [3], "tok2": [3, 5], "tok3": [3, 4]}, sentences[0])
 
     def test_tm_apply(self):
         # no matches with optional
-        tm = TokenMatcher([Token("tok1", optional=True, spec=[Field(FieldNames.WORD, ["by"])])], nlp.vocab)
-        assert {} == tm.apply(StubSentence("test sentence 1"))
+        tm = TokenMatcher([Token("tok1", optional=True, spec=[Field(FieldNames.WORD, ["by"])])])
+        assert {} == tm.apply(sentences[1])
 
         # no matches with no optional
-        tm = TokenMatcher([Token("tok1", spec=[Field(FieldNames.WORD, ["by"])])], nlp.vocab)
-        assert tm.apply(StubSentence("test sentence 1")) is None
+        tm = TokenMatcher([Token("tok1", spec=[Field(FieldNames.WORD, ["by"])])])
+        assert tm.apply(sentences[1]) is None
 
         # matches
         tm = TokenMatcher([Token("tok1", spec=[Field(FieldNames.WORD, ["he"])]),
-                           Token("verb", spec=[Field(FieldNames.TAG, ["VBD", "VB"])])], nlp.vocab)
-        assert {"tok1": [0], "verb": [1, 3]} == tm.apply(StubSentence("He wanted to go"))
+                           Token("verb", spec=[Field(FieldNames.TAG, ["VBD", "VB"])])])
+        assert {"tok1": [0], "verb": [1, 3]} == tm.apply(sentences[2])
 
 
 class TestMatch:
@@ -257,13 +291,13 @@ def test_matched_for(self):
         constraint3 = Full(tokens=[Token("tok2"), Token("tok1", spec=[Field(FieldNames.WORD, ["he"])])],
                            edges=[Edge("tok1", "tok2", [HasLabelFromList(["some_label"])])])
         match = Match(
-            {"constraint1": TokenMatcher(constraint.tokens, nlp.vocab),
-             "constraint2": TokenMatcher(constraint2.tokens, nlp.vocab),
-             "constraint3": TokenMatcher(constraint3.tokens, nlp.vocab)},
+            {"constraint1": TokenMatcher(constraint.tokens),
+             "constraint2": TokenMatcher(constraint2.tokens),
+             "constraint3": TokenMatcher(constraint3.tokens)},
             {"constraint1": GlobalMatcher(constraint),
              "constraint2": GlobalMatcher(constraint2),
              "constraint3": GlobalMatcher(constraint3)},
-            StubSentence("He went home"))
+            sentences[3])
         assert len(list(match.matches_for("constraint1"))) == 0
         assert len(list(match.matches_for("constraint2"))) == 0
         assert len(list(match.matches_for("constraint3"))) == 1
@@ -294,27 +328,3 @@ def test_preprocess_constraint():
     assert ret.tokens[2].spec[0].field == FieldNames.WORD
 
 
-class StubSentence:
-    # TODO - this is temporary until we have real sentence class
-    #   Actually unnecessarily as we dont need to test here sentence, so a decoy is sufficient
-    spec = {0: "test1", 1: "test2", 2: "test3"}
-
-    def __init__(self, sent=""):
-        self.sent = sent
-
-    def get_text(self, i):
-        return self.spec[i]
-
-    def get_labels(self, child=0, parent=0):
-        if child == 1:
-            if parent == 2:
-                return ["bad_label"]
-            else:
-                return ["some_label"]
-        elif parent == 3 or parent == 2:
-            return ["some_label"]
-        else:
-            return []
-
-    def doc(self):
-        return nlp(self.sent)