Source on GitHub

Module paroxython.normalize_predicate

Process an input string to interpret it as a known predicate.

Expand source code Browse GitHub
from typing import Callable, Tuple

import regex  # type: ignore

from .compare_spans import compare_spans
from .goodies import print_warning, print_fail
from .user_types import Predicate


def normalize_predicate(
    predicate: str,
    search_not_1: Callable = regex.compile(r"not\s+").search,
    search_not_2: Callable = regex.compile(r"\s+not").search,
    sub_is: Callable = regex.compile(r" is\b|\bis ").sub,
    sub_forbidden_chars: Callable = regex.compile(r"[^xy<=≤]").sub,
    sub_identity: Callable = regex.compile(r"^(x=y|y=x)$").sub,
    sub_one_x: Callable = regex.compile(r"^([^x]*)x([^x]*)$").sub,
    sub_one_y: Callable = regex.compile(r"^([^y]*)y([^y]*)$").sub,
) -> Tuple[Predicate, bool]:
    predicate = predicate.lower().strip()
    negated = True
    if predicate.startswith("!"):
        predicate = predicate[1:]
    elif search_not_1(predicate):
        predicate = predicate.replace("not ", "")
    elif search_not_2(predicate):
        predicate = predicate.replace(" not", "")
    else:
        negated = False
    original = predicate = predicate.strip()
    predicate = sub_is("", predicate)  # Suppress "is" when it is followed or preceded by a space.
    if predicate not in compare_spans:
        predicate = predicate.replace("<=", "≤").replace("==", "=")  # Convert usual operators.
        predicate = sub_forbidden_chars("", predicate)  # Ignore all other characters.
        predicate = sub_identity("x=y≤x=y", predicate)  # Treat the special case of identity.
        predicate = sub_one_x(r"\1x≤x\2", predicate)  # If there is only one x, expand it into x≤x.
        predicate = sub_one_y(r"\1y≤y\2", predicate)  # If there is only one y, expand it into y≤y.
        if original != predicate:
            print_warning(f"predicate '{original}' normalized into '{predicate}'.")
        if predicate not in compare_spans:
            print_fail(f"Malformed predicate '{predicate}' in the pipeline.")
    return (compare_spans[predicate], negated)

Functions

def normalize_predicate(predicate: str,
search_not_1: Callable = <built-in method search of regex>,
search_not_2: Callable = <built-in method search of regex>,
sub_is: Callable = <built-in method sub of regex>,
sub_forbidden_chars: Callable = <built-in method sub of regex>,
sub_identity: Callable = <built-in method sub of regex>,
sub_one_x: Callable = <built-in method sub of regex>,
sub_one_y: Callable = <built-in method sub of regex>
) ‑> Tuple[Callable[[Tuple[int, int], Tuple[int, int]], bool], bool]

Ensure that the given predicate is correct or can be salvaged.

Description

The system first determines whether the predicate is negated or not, which can be expressed either by an exclamation mark prefix or the word “not”. In both cases, the negation marker is extracted from the given string.

The resulting string is then normalized, i.e. replaced by a key of the dictionary compare_spans defined in paroxython.compare_spans. The verb "is" is ignored, except when it constitutes the whole string (thus, "is after" comes down to "after"). When an operator is not an Allen's name or a predefined synonym, the following normalization process is applied:

  1. Replace any <= by .
  2. Replace any == by =.
  3. Strip all characters distinct from "=", "<", "≤", "x" and "y".
  4. If the string is "x=y" or "y=x", replace it by "x=y≤x=y".
  5. If the string contains only one x (resp. y), expand it into x≤x (resp. y≤y).

This allows the user to be understood when entering formulas like x == y, x <= y or y1 < x1 == x2 <= y2.

Examples

Browse test_normalize_predicate.py on GitHub.

Args

predicate : str
Plain string to be interpreted as a predicate.
search_not_1 : Callable, optional
Function searching "not ".
search_not_2 : Callable, optional
Function searching " not".
sub_is : Callable, optional
Function replacing " is" or "is ".
sub_forbidden_chars : Callable, optional
Function replacing all characters not in "xy<=≤".
sub_identity : Callable, optional
Function replacing "x=y" or "y=x".
sub_one_x : Callable, optional
Function replacing isolated"x".
sub_one_y : Callable, optional
Function replacing isolated"y".

Note

No default argument to be explicitly provided.

Raises

ValueError
Raised when the input string is malformed beyond recognition.

Returns

Tuple[Predicate, bool]
A tuple whose first member is a function (more precisely, a value of the dictionary compare_spans), and second member is a boolean indicating whether the result of this predicate must be negated.
Expand source code Browse GitHub
def normalize_predicate(
    predicate: str,
    search_not_1: Callable = regex.compile(r"not\s+").search,
    search_not_2: Callable = regex.compile(r"\s+not").search,
    sub_is: Callable = regex.compile(r" is\b|\bis ").sub,
    sub_forbidden_chars: Callable = regex.compile(r"[^xy<=≤]").sub,
    sub_identity: Callable = regex.compile(r"^(x=y|y=x)$").sub,
    sub_one_x: Callable = regex.compile(r"^([^x]*)x([^x]*)$").sub,
    sub_one_y: Callable = regex.compile(r"^([^y]*)y([^y]*)$").sub,
) -> Tuple[Predicate, bool]:
    predicate = predicate.lower().strip()
    negated = True
    if predicate.startswith("!"):
        predicate = predicate[1:]
    elif search_not_1(predicate):
        predicate = predicate.replace("not ", "")
    elif search_not_2(predicate):
        predicate = predicate.replace(" not", "")
    else:
        negated = False
    original = predicate = predicate.strip()
    predicate = sub_is("", predicate)  # Suppress "is" when it is followed or preceded by a space.
    if predicate not in compare_spans:
        predicate = predicate.replace("<=", "≤").replace("==", "=")  # Convert usual operators.
        predicate = sub_forbidden_chars("", predicate)  # Ignore all other characters.
        predicate = sub_identity("x=y≤x=y", predicate)  # Treat the special case of identity.
        predicate = sub_one_x(r"\1x≤x\2", predicate)  # If there is only one x, expand it into x≤x.
        predicate = sub_one_y(r"\1y≤y\2", predicate)  # If there is only one y, expand it into y≤y.
        if original != predicate:
            print_warning(f"predicate '{original}' normalized into '{predicate}'.")
        if predicate not in compare_spans:
            print_fail(f"Malformed predicate '{predicate}' in the pipeline.")
    return (compare_spans[predicate], negated)