From 68f1ed7d3ea55cb5e050e6dc1ffb7bc0ff171323 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Tue, 31 Dec 2024 13:59:44 +0300 Subject: [PATCH 01/21] set up ruff --- .gitignore | 3 +++ ruff.toml | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 ruff.toml diff --git a/.gitignore b/.gitignore index dc1fee2..c51f90c 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,6 @@ prof/ *.report .idea pytest-coverage.txt + +# VS Code config directory +.vscode diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..5b36623 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,53 @@ +include = ["pyformlang"] + +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +line-length = 80 +indent-width = 4 +target-version = "py38" + +[lint] +select = [ + "E", # pycodestyle + "W", # pycodestyle + "N", # pep8-naming + "D", # pydocstyle + "DOC", # pydoclint +] + +[lint.pydocstyle] +convention = "google" + +[format] +quote-style = "double" +indent-style = "space" +line-ending = "auto" +docstring-code-format = false +docstring-code-line-length = "dynamic" From 849815aa3cdc7be68c2a756c4fed3ccee72f6dab Mon Sep 17 00:00:00 2001 From: bygu4 Date: Wed, 1 Jan 2025 20:43:14 +0300 Subject: [PATCH 02/21] update module __init__ file docs --- doc/conf.py | 2 +- doc/modules/context_free_grammar.rst | 2 +- doc/modules/feature_context_free_grammar.rst | 4 +- doc/modules/fst.rst | 2 +- pyformlang/__init__.py | 58 +++++++------ pyformlang/cfg/__init__.py | 52 +++++++----- pyformlang/fcfg/__init__.py | 62 +++++++++----- pyformlang/fcfg/feature_structure.py | 2 +- pyformlang/finite_automaton/__init__.py | 85 ++++++++++--------- pyformlang/fst/__init__.py | 31 ++++--- pyformlang/indexed_grammar/__init__.py | 69 ++++++++------- pyformlang/objects/__init__.py | 12 +-- pyformlang/objects/cfg_objects/__init__.py | 14 +-- .../finite_automaton_objects/__init__.py | 12 +-- pyformlang/objects/pda_objects/__init__.py | 14 +-- pyformlang/objects/regex_objects/__init__.py | 20 +++-- pyformlang/pda/__init__.py | 45 +++++----- pyformlang/regular_expression/__init__.py | 29 ++++--- pyformlang/rsa/__init__.py | 26 +++--- ruff.toml | 4 + 20 files changed, 305 insertions(+), 240 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 1fe6057..d4135e5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -40,7 +40,7 @@ 'sphinx.ext.mathjax', 'sphinx.ext.napoleon', 'sphinx.ext.todo', - 'sphinx.ext.viewcode' + 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. diff --git a/doc/modules/context_free_grammar.rst b/doc/modules/context_free_grammar.rst index ac40b9d..0e5e579 100644 --- a/doc/modules/context_free_grammar.rst +++ b/doc/modules/context_free_grammar.rst @@ -1,4 +1,4 @@ -Context Free Grammar +Context-Free Grammar ==================== .. automodule:: pyformlang.cfg diff --git a/doc/modules/feature_context_free_grammar.rst b/doc/modules/feature_context_free_grammar.rst index c4ab808..4e805e7 100644 --- a/doc/modules/feature_context_free_grammar.rst +++ b/doc/modules/feature_context_free_grammar.rst @@ -1,5 +1,5 @@ -Feature Context Free Grammar +Feature Context-Free Grammar ============================ .. automodule:: pyformlang.fcfg - :members: \ No newline at end of file + :members: diff --git a/doc/modules/fst.rst b/doc/modules/fst.rst index 3205691..6ecdf67 100644 --- a/doc/modules/fst.rst +++ b/doc/modules/fst.rst @@ -2,4 +2,4 @@ Finite State Transducer ======================= .. automodule:: pyformlang.fst - :members: \ No newline at end of file + :members: diff --git a/pyformlang/__init__.py b/pyformlang/__init__.py index cd1b1cc..a5503f4 100644 --- a/pyformlang/__init__.py +++ b/pyformlang/__init__.py @@ -1,29 +1,29 @@ -""" -Pyformlang -========== -Pyformlang is a python module to perform operation on formal languages. +"""Pyformlang is a python module to perform operation on formal languages. + How to use the documentation ---------------------------- Documentation is available in two formats: docstrings directly in the code and a readthedocs website: https://pyformlang.readthedocs.io. + Available subpackages --------------------- -regular_expression - Regular Expressions -finite_automaton - Finite automata (deterministic, non-deterministic, with/without epsilon - transitions -fst - Finite State Transducers -cfg - Context-Free Grammar -pda - Push-Down Automata -Indexed Grammar - Indexed Grammar -rsa - Recursive automaton - +:mod:`pyformlang.regular_expression`: + Regular Expressions. +:mod:`pyformlang.finite_automaton`: + Finite Automata (deterministic, non-deterministic, + with/without epsilon transitions). +:mod:`pyformlang.fst`: + Finite State Transducers. +:mod:`pyformlang.cfg`: + Context-Free Grammars. +:mod:`pyformlang.pda`: + Push-Down Automata. +:mod:`pyformlang.indexed_grammar`: + Indexed Grammars. +:mod:`pyformlang.rsa`: + Recursive Automata. +:mod:`pyformlang.fcfg`: + Context-Free Grammars with Features. """ from . import finite_automaton @@ -36,11 +36,13 @@ from . import fcfg -__all__ = ["finite_automaton", - "regular_expression", - "cfg", - "fst", - "indexed_grammar", - "pda", - "rsa", - "fcfg"] +__all__ = [ + "finite_automaton", + "regular_expression", + "cfg", + "fst", + "indexed_grammar", + "pda", + "rsa", + "fcfg", +] diff --git a/pyformlang/cfg/__init__.py b/pyformlang/cfg/__init__.py index 7e3e283..8873770 100644 --- a/pyformlang/cfg/__init__.py +++ b/pyformlang/cfg/__init__.py @@ -1,34 +1,40 @@ -""" +"""This module implements functions related to context-free grammars. + :mod:`pyformlang.cfg` ===================== -This submodule implements functions related to context-free grammars. - Available Classes ----------------- - -CFG - The main context-free grammar class -Production - A class to represent a production in a CFG -Variable - A context-free grammar variable -Terminal - A context-free grammar terminal -Epsilon - The epsilon symbol (special terminal) - +:class:`pyformlang.cfg.CFG`: + The main context-free grammar class. +:class:`pyformlang.cfg.Production`: + A class to represent a production in a CFG. +:class:`pyformlang.cfg.CFGObject`: + A general CFG object representation. +:class:`pyformlang.cfg.Variable`: + A variable in context-free grammar. +:class:`pyformlang.cfg.Terminal`: + A terminal in context-free grammar. +:class:`pyformlang.cfg.Epsilon`: + The epsilon symbol (special terminal). +:class:`pyformlang.cfg.ParseTree`: + A parse tree of the grammar. +:class:`pyformlang.cfg.DerivationDoesNotExist`: + An exception that occurs if the given word cannot + be derived from the grammar. """ from .cfg import CFG, CFGObject, Variable, Terminal, Epsilon, Production from .parse_tree import ParseTree, DerivationDoesNotExist -__all__ = ["CFGObject", - "Variable", - "Terminal", - "Epsilon", - "Production", - "CFG", - "ParseTree", - "DerivationDoesNotExist"] +__all__ = [ + "CFG", + "Production", + "CFGObject", + "Variable", + "Terminal", + "Epsilon", + "ParseTree", + "DerivationDoesNotExist", +] diff --git a/pyformlang/fcfg/__init__.py b/pyformlang/fcfg/__init__.py index 3824940..d2da9a2 100644 --- a/pyformlang/fcfg/__init__.py +++ b/pyformlang/fcfg/__init__.py @@ -1,18 +1,38 @@ -""" -:mod:`pyformlang.fcfg` -======================= - -This submodule implements functions related to feature-based grammars. +"""This module implements functions related to feature-based grammars. +:mod:`pyformlang.fcfg` +====================== Available Classes ----------------- +:class:`pyformlang.fcfg.FCFG`: + A context-free grammar with features. +:class:`pyformlang.fcfg.FeatureStructure`: + A feature structure containing constraints. +:class:`pyformlang.fcfg.FeatureProduction`: + A production in the FCFG. +:class:`pyformlang.fcfg.CFGObject`: + The general CFG object used in FCFG. +:class:`pyformlang.fcfg.Variable`: + A variable in FCFG. +:class:`pyformlang.fcfg.Terminal`: + A terminal in FCFG. +:class:`pyformlang.fcfg.Epsilon`: + The epsilon terminal. +:class:`pyformlang.fcfg.ParseTree`: + A parse tree of the grammar. +:class:`pyformlang.fcfg.NotParsableException`: + An exception that occurs when the given grammar cannot be parsed. +:class:`pyformlang.fcfg.ContentAlreadyExistsException`: + An exception raised when trying to add content that already exists. +:class:`pyformlang.fcfg.FeatureStructuresNotCompatibleException`: + An exception raised when trying to unify incompatible structures. +:class:`pyformlang.fcfg.PathDoesNotExistsException`: + An exception raised when looking for a path that does not exist. Sources ------- - -Daniel Jurafsky and James H. Martin, Speech and Language Processing - +Daniel Jurafsky and James H. Martin, Speech and Language Processing. """ from .fcfg import FCFG, CFGObject, \ @@ -24,15 +44,17 @@ PathDoesNotExistsException -__all__ = ["FCFG", - "FeatureStructure", - "FeatureProduction", - "CFGObject", - "Variable", - "Terminal", - "Epsilon", - "ParseTree", - "NotParsableException", - "ContentAlreadyExistsException", - "FeatureStructuresNotCompatibleException", - "PathDoesNotExistsException"] +__all__ = [ + "FCFG", + "FeatureStructure", + "FeatureProduction", + "CFGObject", + "Variable", + "Terminal", + "Epsilon", + "ParseTree", + "NotParsableException", + "ContentAlreadyExistsException", + "FeatureStructuresNotCompatibleException", + "PathDoesNotExistsException", +] diff --git a/pyformlang/fcfg/feature_structure.py b/pyformlang/fcfg/feature_structure.py index c7e7651..0c828a2 100644 --- a/pyformlang/fcfg/feature_structure.py +++ b/pyformlang/fcfg/feature_structure.py @@ -12,7 +12,7 @@ class PathDoesNotExistsException(Exception): class FeatureStructuresNotCompatibleException(Exception): - """Raised when trying to unify uncompatible structures""" + """Raised when trying to unify incompatible structures""" class FeatureStructure: diff --git a/pyformlang/finite_automaton/__init__.py b/pyformlang/finite_automaton/__init__.py index 6fec7db..84b7fd7 100644 --- a/pyformlang/finite_automaton/__init__.py +++ b/pyformlang/finite_automaton/__init__.py @@ -1,59 +1,62 @@ -""" +"""This module deals with finite state automata. + :mod:`pyformlang.finite_automaton` ================================== -This module deals with finite state automata. - Available Classes ----------------- - -:class:`~pyformlang.finite_automaton.FiniteAutomaton` - A general representation of automata. Cannot be used directly. -:class:`~pyformlang.finite_automaton.DeterministicFiniteAutomaton` - A deterministic finite automaton -:class:`~pyformlang.finite_automaton.NondeterministicFiniteAutomaton` - A non-deterministic finite automaton, without epsilon transitions -:class:`~pyformlang.finite_automaton.EpsilonNFA` - A non-deterministic finite automaton, with epsilon transitions -:class:`~pyformlang.finite_automaton.TransitionFunction` - A deterministic transition function -:class:`~pyformlang.finite_automaton.NondeterministicTransitionFunction` - A non-deterministic transition function -:class:`~pyformlang.finite_automaton.State` - A state (or node) in an automaton -:class:`~pyformlang.finite_automaton.Symbol` - A symbol (part of the alphabet) in an automaton -:class:`~pyformlang.finite_automaton.Epsilon` - The epsilon (or empty) symbol -:class:`~pyformlang.finite_automaton.DuplicateTransitionError` - An error that occurs when trying to add a non-deterministic edge to a \ - deterministic automaton -:class:`~pyformlang.finite_automaton.InvalidEpsilonTransition` - An exception that occurs when adding an epsilon transition to a \ +:class:`pyformlang.finite_automaton.FiniteAutomaton`: + An abstract class representing the general finite automaton. +:class:`pyformlang.finite_automaton.EpsilonNFA`: + A non-deterministic finite automaton with epsilon transitions. +:class:`pyformlang.finite_automaton.NondeterministicFiniteAutomaton`: + A non-deterministic finite automaton without epsilon transitions. +:class:`pyformlang.finite_automaton.DeterministicFiniteAutomaton`: + A deterministic finite automaton. +:class:`pyformlang.finite_automaton.State`: + A state (or node) in an automaton. +:class:`pyformlang.finite_automaton.Symbol`: + A symbol (part of the alphabet) in an automaton. +:class:`pyformlang.finite_automaton.Epsilon`: + The epsilon (or empty) symbol. +:class:`pyformlang.finite_automaton.TransitionFunction`: + An interface representing the transition function in a finite automaton. +:class:`pyformlang.finite_automaton.NondeterministicTransitionFunction`: + A non-deterministic transition function. +:class:`pyformlang.finite_automaton.DeterministicTransitionFunction`: + A deterministic transition function. +:class:`pyformlang.finite_automaton.DuplicateTransitionError`: + An error that occurs when trying to add a non-deterministic edge to a + deterministic automaton. +:class:`pyformlang.finite_automaton.InvalidEpsilonTransition`: + An exception that occurs when adding an epsilon transition to a non-epsilon NFA. - """ from .finite_automaton import FiniteAutomaton, State, Symbol, Epsilon from .deterministic_finite_automaton import DeterministicFiniteAutomaton from .nondeterministic_finite_automaton import NondeterministicFiniteAutomaton from .epsilon_nfa import EpsilonNFA +from .transition_function import TransitionFunction +from .nondeterministic_transition_function import \ + NondeterministicTransitionFunction from .deterministic_transition_function import \ (DeterministicTransitionFunction, DuplicateTransitionError, InvalidEpsilonTransition) -from .nondeterministic_transition_function import \ - NondeterministicTransitionFunction -__all__ = ["FiniteAutomaton", - "DeterministicFiniteAutomaton", - "NondeterministicFiniteAutomaton", - "EpsilonNFA", - "State", - "Symbol", - "Epsilon", - "DeterministicTransitionFunction", - "NondeterministicTransitionFunction", - "DuplicateTransitionError", - "InvalidEpsilonTransition"] +__all__ = [ + "FiniteAutomaton", + "EpsilonNFA", + "NondeterministicFiniteAutomaton", + "DeterministicFiniteAutomaton", + "State", + "Symbol", + "Epsilon", + "TransitionFunction", + "NondeterministicTransitionFunction", + "DeterministicTransitionFunction", + "DuplicateTransitionError", + "InvalidEpsilonTransition", +] diff --git a/pyformlang/fst/__init__.py b/pyformlang/fst/__init__.py index b7fdb5f..3d82a1a 100644 --- a/pyformlang/fst/__init__.py +++ b/pyformlang/fst/__init__.py @@ -1,22 +1,29 @@ -""" +"""This module deals with finite state transducers. + :mod:`pyformlang.fst` ===================== -This module deals with finite state transducers. - Available Classes ----------------- - -FST - A Finite State Transducer - +:class:`pyformlang.fst.FST`: + A Finite State Transducer. +:class:`pyformlang.fst.TransitionFunction`: + A transition function in FST. +:class:`pyformlang.fst.State`: + A state in FST. +:class:`pyformlang.fst.Symbol`: + A symbol in FST. +:class:`pyformlang.fst.Epsilon`: + An epsilon symbol. """ from .fst import FST, TransitionFunction, State, Symbol, Epsilon -__all__ = ["FST", - "TransitionFunction", - "State", - "Symbol", - "Epsilon"] +__all__ = [ + "FST", + "TransitionFunction", + "State", + "Symbol", + "Epsilon", +] diff --git a/pyformlang/indexed_grammar/__init__.py b/pyformlang/indexed_grammar/__init__.py index 00e6f18..73fb0ee 100644 --- a/pyformlang/indexed_grammar/__init__.py +++ b/pyformlang/indexed_grammar/__init__.py @@ -1,45 +1,54 @@ -""" -:mod:`pyformlang.indexed_grammar` -================================== +"""This module deals with indexed grammars. -This module deals with indexed grammars. +:mod:`pyformlang.indexed_grammar` +================================= Available Classes ----------------- - -IndexedGrammar - An indexed grammar -Rules - A representation of a set of indexed grammar rules -EndRule - An end rule, turning a variable into a terminal -ConsumptionRule - A consumption rule, consuming something from the stack -ProductionRule - A production rule, pushing something on the stack -DuplicationRule - A duplication rule, duplicating the stack - +:class:`pyformlang.indexed_grammar.IndexedGrammar`: + An indexed grammar. +:class:`pyformlang.indexed_grammar.Rules`: + A representation of a set of indexed grammar rules. +:class:`pyformlang.indexed_grammar.ReducedRule`: + An indexed grammar rule of any of possible forms. +:class:`pyformlang.indexed_grammar.ConsumptionRule`: + A consumption rule, consuming something from the stack. +:class:`pyformlang.indexed_grammar.EndRule`: + An end rule, turning a variable into a terminal. +:class:`pyformlang.indexed_grammar.ProductionRule`: + A production rule, pushing something on the stack. +:class:`pyformlang.indexed_grammar.DuplicationRule`: + A duplication rule, duplicating the stack. +:class:`pyformlang.indexed_grammar.CFGObject`: + A general CFG object used in indexed grammars. +:class:`pyformlang.indexed_grammar.Variable`: + A variable in indexed grammars. +:class:`pyformlang.indexed_grammar.Terminal`: + A terminal in indexed grammars. +:class:`pyformlang.indexed_grammar.Epsilon`: + An epsilon terminal. """ +from .indexed_grammar import IndexedGrammar from .rules import Rules from .reduced_rule import ReducedRule from .consumption_rule import ConsumptionRule from .end_rule import EndRule from .production_rule import ProductionRule from .duplication_rule import DuplicationRule -from .indexed_grammar import IndexedGrammar from ..objects.cfg_objects import CFGObject, Variable, Terminal, Epsilon -__all__ = ["Rules", - "ReducedRule", - "ConsumptionRule", - "EndRule", - "ProductionRule", - "DuplicationRule", - "IndexedGrammar", - "CFGObject", - "Variable", - "Terminal", - "Epsilon"] +__all__ = [ + "IndexedGrammar", + "Rules", + "ReducedRule", + "ConsumptionRule", + "EndRule", + "ProductionRule", + "DuplicationRule", + "CFGObject", + "Variable", + "Terminal", + "Epsilon", +] diff --git a/pyformlang/objects/__init__.py b/pyformlang/objects/__init__.py index abc7ae7..24ab46a 100644 --- a/pyformlang/objects/__init__.py +++ b/pyformlang/objects/__init__.py @@ -1,4 +1,4 @@ -""" Collection of object representations """ +"""This module deals with formal object representations.""" from . import finite_automaton_objects from . import cfg_objects @@ -6,7 +6,9 @@ from . import pda_objects -__all__ = ["finite_automaton_objects", - "cfg_objects", - "regex_objects", - "pda_objects"] +__all__ = [ + "finite_automaton_objects", + "cfg_objects", + "regex_objects", + "pda_objects", +] diff --git a/pyformlang/objects/cfg_objects/__init__.py b/pyformlang/objects/cfg_objects/__init__.py index a999974..6c3d71f 100644 --- a/pyformlang/objects/cfg_objects/__init__.py +++ b/pyformlang/objects/cfg_objects/__init__.py @@ -1,4 +1,4 @@ -""" CFG object representations """ +"""CFG object representations.""" from .cfg_object import CFGObject from .variable import Variable @@ -7,8 +7,10 @@ from .production import Production -__all__ = ["CFGObject", - "Variable", - "Terminal", - "Epsilon", - "Production"] +__all__ = [ + "CFGObject", + "Variable", + "Terminal", + "Epsilon", + "Production", +] diff --git a/pyformlang/objects/finite_automaton_objects/__init__.py b/pyformlang/objects/finite_automaton_objects/__init__.py index b2d9074..3eea2d4 100644 --- a/pyformlang/objects/finite_automaton_objects/__init__.py +++ b/pyformlang/objects/finite_automaton_objects/__init__.py @@ -1,4 +1,4 @@ -""" Finite automaton object representations """ +"""Finite automaton object representations.""" from .finite_automaton_object import FiniteAutomatonObject from .state import State @@ -6,7 +6,9 @@ from .epsilon import Epsilon -__all__ = ["FiniteAutomatonObject", - "State", - "Symbol", - "Epsilon"] +__all__ = [ + "FiniteAutomatonObject", + "State", + "Symbol", + "Epsilon", +] diff --git a/pyformlang/objects/pda_objects/__init__.py b/pyformlang/objects/pda_objects/__init__.py index de26f00..0e7e29a 100644 --- a/pyformlang/objects/pda_objects/__init__.py +++ b/pyformlang/objects/pda_objects/__init__.py @@ -1,4 +1,4 @@ -""" PDA object representations """ +"""PDA object representations.""" from .pda_object import PDAObject from .state import State @@ -7,8 +7,10 @@ from .epsilon import Epsilon -__all__ = ["PDAObject", - "State", - "Symbol", - "StackSymbol", - "Epsilon"] +__all__ = [ + "PDAObject", + "State", + "Symbol", + "StackSymbol", + "Epsilon", +] diff --git a/pyformlang/objects/regex_objects/__init__.py b/pyformlang/objects/regex_objects/__init__.py index f7d9944..b4dbbd9 100644 --- a/pyformlang/objects/regex_objects/__init__.py +++ b/pyformlang/objects/regex_objects/__init__.py @@ -1,13 +1,15 @@ -""" Regex object representations """ +"""Regex object representations.""" from .regex_objects import * -__all__ = ["Node", - "Operator", - "Symbol", - "Concatenation", - "Union", - "KleeneStar", - "Epsilon", - "Empty"] +__all__ = [ + "Node", + "Operator", + "Symbol", + "Concatenation", + "Union", + "KleeneStar", + "Epsilon", + "Empty", +] diff --git a/pyformlang/pda/__init__.py b/pyformlang/pda/__init__.py index 201cffc..ff339ec 100644 --- a/pyformlang/pda/__init__.py +++ b/pyformlang/pda/__init__.py @@ -1,23 +1,22 @@ -""" -:mod:`pyformlang.pda` -================================== +"""This module deals with push-down automata. -This module deals with push-down automata. +:mod:`pyformlang.pda` +===================== Available Classes ----------------- - -PDA - A Push-Down Automaton -State - A push-down automaton state -Symbol - A push-down automaton symbol -StackSymbol - A push-down automaton stack symbol -Epsilon - A push-down automaton epsilon symbol - +:class:`pyformlang.pda.PDA`: + A push-down automaton. +:class:`pyformlang.pda.TransitionFunction`: + A transition function in push-down automaton. +:class:`pyformlang.pda.State`: + A state in push-down automaton. +:class:`pyformlang.pda.Symbol`: + A symbol in push-down automaton. +:class:`pyformlang.pda.StackSymbol`: + A stack symbol in push-down automaton. +:class:`pyformlang.pda.Epsilon`: + The epsilon symbol. """ from .pda import PDA @@ -25,9 +24,11 @@ from ..objects.pda_objects import State, Symbol, StackSymbol, Epsilon -__all__ = ["PDA", - "TransitionFunction", - "State", - "Symbol", - "StackSymbol", - "Epsilon"] +__all__ = [ + "PDA", + "TransitionFunction", + "State", + "Symbol", + "StackSymbol", + "Epsilon", +] diff --git a/pyformlang/regular_expression/__init__.py b/pyformlang/regular_expression/__init__.py index 5b8e6fe..05c11c7 100644 --- a/pyformlang/regular_expression/__init__.py +++ b/pyformlang/regular_expression/__init__.py @@ -1,26 +1,27 @@ -""" -:mod:`pyformlang.regular_expression` -==================================== - -This module deals with regular expression. +"""This module deals with regular expressions. By default, this module does not use the standard way to write regular expressions. Please read the documentation of Regex for more information. +:mod:`pyformlang.regular_expression` +==================================== + Available Classes ----------------- - -:class:`~pyformlang.regular_expression.Regex` - A regular expression -:class:`~pyformlang.regular_expression.PythonRegex` - A regular expression closer to Python format -:class:`~pyformlang.regular_expression.MisformedRegexError` - An error occurring when the input regex is incorrect - +:class:`pyformlang.regular_expression.Regex`: + A regular expression. +:class:`pyformlang.regular_expression.PythonRegex`: + A regular expression closer to Python format. +:class:`pyformlang.regular_expression.MisformedRegexError`: + An error occurring when the input regex is incorrect. """ from .regex import Regex from .python_regex import PythonRegex, MisformedRegexError -__all__ = ["Regex", "PythonRegex", "MisformedRegexError"] +__all__ = [ + "Regex", + "PythonRegex", + "MisformedRegexError", +] diff --git a/pyformlang/rsa/__init__.py b/pyformlang/rsa/__init__.py index 66d25d2..1dc2d4e 100644 --- a/pyformlang/rsa/__init__.py +++ b/pyformlang/rsa/__init__.py @@ -1,8 +1,14 @@ -""" +"""This module deals with recursive automata. + :mod:`pyformlang.rsa` -==================================== +===================== -This module deals with recursive automaton. +Available Classes +----------------- +:class:`pyformlang.rsa.RecursiveAutomaton`: + A recursive automaton. +:class:`pyformlang.rsa.Box`: + A constituent part of a recursive automaton. References ---------- @@ -11,19 +17,13 @@ Comon H., Finkel A. (eds) Computer Aided Verification. CAV 2001. Lecture Notes in Computer Science, vol 2102. Springer, Berlin, Heidelberg. https://doi.org/10.1007/3-540-44585-4_18 - -Available Classes ------------------ - -:class:`~pyformlang.rsa.RecursiveAutomaton` - A recursive automaton -:class:`~pyformlang.rsa.Box` - A constituent part of a recursive automaton - """ from .recursive_automaton import RecursiveAutomaton from .box import Box -__all__ = ["RecursiveAutomaton", "Box"] +__all__ = [ + "RecursiveAutomaton", + "Box", +] diff --git a/ruff.toml b/ruff.toml index 5b36623..fc5cbe0 100644 --- a/ruff.toml +++ b/ruff.toml @@ -42,6 +42,10 @@ select = [ "DOC", # pydoclint ] +ignore = [ + "D416", +] + [lint.pydocstyle] convention = "google" From 3eb42352e0c393757e2f1ff255920a7a39625634 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Wed, 1 Jan 2025 21:03:18 +0300 Subject: [PATCH 03/21] change custom exception names to conventional ones --- pyformlang/cfg/__init__.py | 6 ++--- pyformlang/cfg/cyk_table.py | 4 ++-- pyformlang/cfg/llone_parser.py | 8 +++---- pyformlang/cfg/parse_tree.py | 4 ++-- pyformlang/cfg/recursive_decent_parser.py | 8 +++---- pyformlang/cfg/tests/test_cfg.py | 6 ++--- .../cfg/tests/test_recursive_decent_parser.py | 4 ++-- pyformlang/fcfg/__init__.py | 24 +++++++++---------- pyformlang/fcfg/fcfg.py | 8 +++---- pyformlang/fcfg/feature_structure.py | 12 +++++----- pyformlang/fcfg/tests/test_fcfg.py | 8 +++---- .../fcfg/tests/test_feature_structure.py | 13 ++++++---- pyformlang/finite_automaton/__init__.py | 6 ++--- .../deterministic_transition_function.py | 4 ++-- .../nondeterministic_finite_automaton.py | 4 ++-- .../test_deterministic_finite_automaton.py | 4 ++-- .../test_deterministic_transition_function.py | 4 ++-- .../test_nondeterministic_finite_automaton.py | 4 ++-- 18 files changed, 67 insertions(+), 64 deletions(-) diff --git a/pyformlang/cfg/__init__.py b/pyformlang/cfg/__init__.py index 8873770..dc938ea 100644 --- a/pyformlang/cfg/__init__.py +++ b/pyformlang/cfg/__init__.py @@ -19,13 +19,13 @@ The epsilon symbol (special terminal). :class:`pyformlang.cfg.ParseTree`: A parse tree of the grammar. -:class:`pyformlang.cfg.DerivationDoesNotExist`: +:class:`pyformlang.cfg.DerivationDoesNotExistError`: An exception that occurs if the given word cannot be derived from the grammar. """ from .cfg import CFG, CFGObject, Variable, Terminal, Epsilon, Production -from .parse_tree import ParseTree, DerivationDoesNotExist +from .parse_tree import ParseTree, DerivationDoesNotExistError __all__ = [ @@ -36,5 +36,5 @@ "Terminal", "Epsilon", "ParseTree", - "DerivationDoesNotExist", + "DerivationDoesNotExistError", ] diff --git a/pyformlang/cfg/cyk_table.py b/pyformlang/cfg/cyk_table.py index eb2b692..f0f4bc6 100644 --- a/pyformlang/cfg/cyk_table.py +++ b/pyformlang/cfg/cyk_table.py @@ -5,7 +5,7 @@ from typing import Dict, List, Set, Iterable, Tuple, Any from .formal_grammar import FormalGrammar -from .parse_tree import ParseTree, DerivationDoesNotExist +from .parse_tree import ParseTree, DerivationDoesNotExistError from ..objects.cfg_objects import CFGObject, Terminal ProductionsDict = Dict[Tuple[CFGObject, ...], List[CFGObject]] @@ -111,7 +111,7 @@ def get_parse_tree(self) -> ParseTree: parse_tree : :class:`~pyformlang.cfg.ParseTree` """ if not self._normal_form.start_symbol or not self.generate_word(): - raise DerivationDoesNotExist + raise DerivationDoesNotExistError if not self._word: return ParseTree(self._normal_form.start_symbol) root = [ diff --git a/pyformlang/cfg/llone_parser.py b/pyformlang/cfg/llone_parser.py index 661cf68..7a5f111 100644 --- a/pyformlang/cfg/llone_parser.py +++ b/pyformlang/cfg/llone_parser.py @@ -3,7 +3,7 @@ from typing import Dict, List, Set, Iterable, Tuple, Hashable from .cfg import CFG, Production -from .parse_tree import ParseTree, NotParsableException +from .parse_tree import ParseTree, NotParsableError from .set_queue import SetQueue from .utils import get_productions_d from ..objects.cfg_objects import CFGObject, Epsilon @@ -223,7 +223,7 @@ def get_llone_parse_tree(self, word: Iterable[Hashable]) -> ParseTree: """ if not self._cfg.start_symbol: - raise NotParsableException + raise NotParsableError word = [to_terminal(x) for x in word if x != Epsilon()] word.append("$") # type: ignore word = word[::-1] @@ -246,6 +246,6 @@ def get_llone_parse_tree(self, word: Iterable[Hashable]) -> ParseTree: current.sons.append(new_node) stack.append(new_node) else: - raise NotParsableException + raise NotParsableError current.sons = current.sons[::-1] - raise NotParsableException + raise NotParsableError diff --git a/pyformlang/cfg/parse_tree.py b/pyformlang/cfg/parse_tree.py index 74c6b29..67c536c 100644 --- a/pyformlang/cfg/parse_tree.py +++ b/pyformlang/cfg/parse_tree.py @@ -114,9 +114,9 @@ def write_as_dot(self, filename: str) -> None: write_dot(self.to_networkx(), filename) -class DerivationDoesNotExist(Exception): +class DerivationDoesNotExistError(Exception): """Exception raised when the word cannot be derived""" -class NotParsableException(Exception): +class NotParsableError(Exception): """When the grammar cannot be parsed (parser not powerful enough)""" diff --git a/pyformlang/cfg/recursive_decent_parser.py b/pyformlang/cfg/recursive_decent_parser.py index ad6e43c..3dfc2a5 100644 --- a/pyformlang/cfg/recursive_decent_parser.py +++ b/pyformlang/cfg/recursive_decent_parser.py @@ -5,7 +5,7 @@ from typing import List, Iterable, Tuple, Optional, Hashable from .cfg import CFG -from .parse_tree import ParseTree, NotParsableException +from .parse_tree import ParseTree, NotParsableError from ..objects.cfg_objects import CFGObject, Variable, Terminal, Epsilon from ..objects.cfg_objects.utils import to_terminal @@ -63,13 +63,13 @@ def get_parse_tree(self, word: Iterable[Hashable], left: bool = True) \ """ if not self._cfg.start_symbol: - raise NotParsableException + raise NotParsableError word = [to_terminal(x) for x in word if x != Epsilon()] parse_tree = ParseTree(self._cfg.start_symbol) starting_expansion: Expansion = [(self._cfg.start_symbol, parse_tree)] if self._get_parse_tree_sub(word, starting_expansion, left): return parse_tree - raise NotParsableException + raise NotParsableError def _match(self, word: List[Terminal], @@ -144,6 +144,6 @@ def is_parsable(self, word: Iterable[Hashable], left: bool = True) -> bool: """ try: self.get_parse_tree(word, left) - except NotParsableException: + except NotParsableError: return False return True diff --git a/pyformlang/cfg/tests/test_cfg.py b/pyformlang/cfg/tests/test_cfg.py index dfb11e6..5519171 100644 --- a/pyformlang/cfg/tests/test_cfg.py +++ b/pyformlang/cfg/tests/test_cfg.py @@ -4,7 +4,7 @@ from pyformlang.pda import PDA from pyformlang.cfg import Production, Variable, Terminal, CFG, Epsilon -from pyformlang.cfg.cyk_table import DerivationDoesNotExist +from pyformlang.cfg.cyk_table import DerivationDoesNotExistError from pyformlang.finite_automaton import DeterministicFiniteAutomaton from pyformlang.finite_automaton import State from pyformlang.finite_automaton import Symbol @@ -701,7 +701,7 @@ def test_get_leftmost_derivation(self): [ter_a, var_a, var_b], [ter_a, ter_a, var_b], [ter_a, ter_a, ter_b]] - with pytest.raises(DerivationDoesNotExist): + with pytest.raises(DerivationDoesNotExistError): cfg.get_cnf_parse_tree([]) def test_get_rightmost_derivation(self): @@ -732,7 +732,7 @@ def test_derivation_does_not_exist(self): ter_a = Terminal("a") ter_b = Terminal("b") cfg = CFG(productions=[], start_symbol=var_s) - with pytest.raises(DerivationDoesNotExist): + with pytest.raises(DerivationDoesNotExistError): parse_tree = cfg.get_cnf_parse_tree([ter_a, ter_b]) parse_tree.get_rightmost_derivation() diff --git a/pyformlang/cfg/tests/test_recursive_decent_parser.py b/pyformlang/cfg/tests/test_recursive_decent_parser.py index 18ce9e2..99ca514 100644 --- a/pyformlang/cfg/tests/test_recursive_decent_parser.py +++ b/pyformlang/cfg/tests/test_recursive_decent_parser.py @@ -3,7 +3,7 @@ # pylint: disable=missing-function-docstring from pyformlang.cfg import CFG, Variable, Terminal from pyformlang.cfg.recursive_decent_parser import \ - RecursiveDecentParser, NotParsableException + RecursiveDecentParser, NotParsableError import pytest @@ -51,7 +51,7 @@ def test_get_parsing_tree(self, parser): ] def test_no_parse_tree(self, parser): - with pytest.raises(NotParsableException): + with pytest.raises(NotParsableError): parser.get_parse_tree([")"]) assert not (parser.is_parsable([")"])) diff --git a/pyformlang/fcfg/__init__.py b/pyformlang/fcfg/__init__.py index d2da9a2..961ee0f 100644 --- a/pyformlang/fcfg/__init__.py +++ b/pyformlang/fcfg/__init__.py @@ -21,13 +21,13 @@ The epsilon terminal. :class:`pyformlang.fcfg.ParseTree`: A parse tree of the grammar. -:class:`pyformlang.fcfg.NotParsableException`: +:class:`pyformlang.fcfg.NotParsableError`: An exception that occurs when the given grammar cannot be parsed. -:class:`pyformlang.fcfg.ContentAlreadyExistsException`: +:class:`pyformlang.fcfg.ContentAlreadyExistsError`: An exception raised when trying to add content that already exists. -:class:`pyformlang.fcfg.FeatureStructuresNotCompatibleException`: +:class:`pyformlang.fcfg.FeatureStructuresNotCompatibleError`: An exception raised when trying to unify incompatible structures. -:class:`pyformlang.fcfg.PathDoesNotExistsException`: +:class:`pyformlang.fcfg.PathDoesNotExistError`: An exception raised when looking for a path that does not exist. Sources @@ -36,12 +36,12 @@ """ from .fcfg import FCFG, CFGObject, \ - Variable, Terminal, Epsilon, ParseTree, NotParsableException + Variable, Terminal, Epsilon, ParseTree, NotParsableError from .feature_production import FeatureProduction from .feature_structure import FeatureStructure, \ - ContentAlreadyExistsException, \ - FeatureStructuresNotCompatibleException, \ - PathDoesNotExistsException + ContentAlreadyExistsError, \ + FeatureStructuresNotCompatibleError, \ + PathDoesNotExistError __all__ = [ @@ -53,8 +53,8 @@ "Terminal", "Epsilon", "ParseTree", - "NotParsableException", - "ContentAlreadyExistsException", - "FeatureStructuresNotCompatibleException", - "PathDoesNotExistsException", + "NotParsableError", + "ContentAlreadyExistsError", + "FeatureStructuresNotCompatibleError", + "PathDoesNotExistError", ] diff --git a/pyformlang/fcfg/fcfg.py b/pyformlang/fcfg/fcfg.py index a2c82c3..28bd50b 100644 --- a/pyformlang/fcfg/fcfg.py +++ b/pyformlang/fcfg/fcfg.py @@ -6,10 +6,10 @@ from pyformlang.cfg import CFG, CFGObject, \ Variable, Terminal, Epsilon, ParseTree, Production from pyformlang.cfg.cfg import is_special_text, EPSILON_SYMBOLS -from pyformlang.cfg.llone_parser import NotParsableException +from pyformlang.cfg.llone_parser import NotParsableError from .feature_structure import FeatureStructure, \ - FeatureStructuresNotCompatibleException + FeatureStructuresNotCompatibleError from .feature_production import FeatureProduction from .state import State, StateProcessed from ..objects.cfg_objects.utils import to_terminal @@ -117,7 +117,7 @@ def get_parse_tree(self, word: Iterable[Hashable]) -> ParseTree: word = [to_terminal(x) for x in word if x != Epsilon()] final_state = self._get_final_state(word) if final_state is None: - raise NotParsableException + raise NotParsableError return final_state.parse_tree def _get_final_state(self, word: List[Terminal]) -> Optional[State]: @@ -270,7 +270,7 @@ def _completer(state: State, copy_right_considered = copy_right.get_feature_by_path( [str(next_state.positions[2])]) copy_right_considered.unify(copy_left) - except FeatureStructuresNotCompatibleException: + except FeatureStructuresNotCompatibleError: continue parse_tree = next_state.parse_tree parse_tree.sons.append(state.parse_tree) diff --git a/pyformlang/fcfg/feature_structure.py b/pyformlang/fcfg/feature_structure.py index 0c828a2..4e2d30d 100644 --- a/pyformlang/fcfg/feature_structure.py +++ b/pyformlang/fcfg/feature_structure.py @@ -3,15 +3,15 @@ from typing import Dict, List, Iterable, Tuple, Optional, Hashable -class ContentAlreadyExistsException(Exception): +class ContentAlreadyExistsError(Exception): """Exception raised when we want to add a content that already exists""" -class PathDoesNotExistsException(Exception): +class PathDoesNotExistError(Exception): """Raised when looking for a path that does not exist""" -class FeatureStructuresNotCompatibleException(Exception): +class FeatureStructuresNotCompatibleError(Exception): """Raised when trying to unify incompatible structures""" @@ -73,7 +73,7 @@ def add_content(self, When the feature already exists """ if content_name in self._content: - raise ContentAlreadyExistsException() + raise ContentAlreadyExistsError() self._content[content_name] = feature_structure def add_content_path(self, @@ -132,7 +132,7 @@ def get_feature_by_path(self, path: List[str] = None) -> "FeatureStructure": return self current = self.get_dereferenced() if path[0] not in current.content: - raise PathDoesNotExistsException() + raise PathDoesNotExistError() return current.content[path[0]].get_feature_by_path(path[1:]) def unify(self, other: "FeatureStructure") -> None: @@ -164,7 +164,7 @@ def unify(self, other: "FeatureStructure") -> None: elif other_dereferenced.value is None: other_dereferenced.pointer = current_dereferenced else: - raise FeatureStructuresNotCompatibleException() + raise FeatureStructuresNotCompatibleError() else: other_dereferenced.pointer = current_dereferenced for feature in other_dereferenced.content: diff --git a/pyformlang/fcfg/tests/test_fcfg.py b/pyformlang/fcfg/tests/test_fcfg.py index 7163f35..d9767ed 100644 --- a/pyformlang/fcfg/tests/test_fcfg.py +++ b/pyformlang/fcfg/tests/test_fcfg.py @@ -1,9 +1,9 @@ """Test a FCFG""" from pyformlang.cfg import Variable, Terminal, Production -from pyformlang.cfg import DerivationDoesNotExist +from pyformlang.cfg import DerivationDoesNotExistError from pyformlang.cfg.parse_tree import ParseTree -from pyformlang.cfg.llone_parser import NotParsableException +from pyformlang.cfg.llone_parser import NotParsableError from pyformlang.fcfg.fcfg import FCFG from pyformlang.fcfg.feature_production import FeatureProduction from pyformlang.fcfg.feature_structure import FeatureStructure @@ -243,7 +243,7 @@ def test_from_text(self, fcfg_text: str): fcfg = FCFG.from_text(fcfg_text) self._sub_tests_contains1(fcfg) parse_tree = fcfg.get_parse_tree(["this", "flight", "serves"]) - with pytest.raises(NotParsableException): + with pytest.raises(NotParsableError): fcfg.get_parse_tree(["these", "flight", "serves"]) assert "Det" in str(parse_tree) @@ -279,5 +279,5 @@ def test_get_leftmost_derivation(self): [ter_a, var_a, var_b], [ter_a, ter_a, var_b], [ter_a, ter_a, ter_b]] - with pytest.raises(DerivationDoesNotExist): + with pytest.raises(DerivationDoesNotExistError): fcfg.get_cnf_parse_tree([]) diff --git a/pyformlang/fcfg/tests/test_feature_structure.py b/pyformlang/fcfg/tests/test_feature_structure.py index 6270abc..a15225e 100644 --- a/pyformlang/fcfg/tests/test_feature_structure.py +++ b/pyformlang/fcfg/tests/test_feature_structure.py @@ -1,6 +1,9 @@ """Testing of the feature structure""" -from pyformlang.fcfg.feature_structure import FeatureStructure, PathDoesNotExistsException, \ - ContentAlreadyExistsException, FeatureStructuresNotCompatibleException +from pyformlang.fcfg.feature_structure import \ + (FeatureStructure, + PathDoesNotExistError, + ContentAlreadyExistsError, + FeatureStructuresNotCompatibleError) import pytest @@ -18,11 +21,11 @@ def test_creation(self): assert len(feature_structure.content) == 0 assert feature_structure.pointer == None assert feature_structure.get_feature_by_path().value == None - with pytest.raises(PathDoesNotExistsException): + with pytest.raises(PathDoesNotExistError): feature_structure.get_feature_by_path(["NUMBER"]) feature_structure.add_content("NUMBER", FeatureStructure("sg")) assert feature_structure.get_feature_by_path(["NUMBER"]).value == "sg" - with pytest.raises(ContentAlreadyExistsException): + with pytest.raises(ContentAlreadyExistsError): feature_structure.add_content("NUMBER", FeatureStructure("sg")) feature_structure = _get_agreement_subject_number_person() assert feature_structure.get_feature_by_path(["SUBJECT", "AGREEMENT", "NUMBER"]).value == "sg" @@ -38,7 +41,7 @@ def test_unify2(self): """Second test to unify""" left = FeatureStructure("pl") right = FeatureStructure("sg") - with pytest.raises(FeatureStructuresNotCompatibleException): + with pytest.raises(FeatureStructuresNotCompatibleError): left.unify(right) def test_unify3(self): diff --git a/pyformlang/finite_automaton/__init__.py b/pyformlang/finite_automaton/__init__.py index 84b7fd7..b1ebfa5 100644 --- a/pyformlang/finite_automaton/__init__.py +++ b/pyformlang/finite_automaton/__init__.py @@ -28,7 +28,7 @@ :class:`pyformlang.finite_automaton.DuplicateTransitionError`: An error that occurs when trying to add a non-deterministic edge to a deterministic automaton. -:class:`pyformlang.finite_automaton.InvalidEpsilonTransition`: +:class:`pyformlang.finite_automaton.InvalidEpsilonTransitionError`: An exception that occurs when adding an epsilon transition to a non-epsilon NFA. """ @@ -43,7 +43,7 @@ from .deterministic_transition_function import \ (DeterministicTransitionFunction, DuplicateTransitionError, - InvalidEpsilonTransition) + InvalidEpsilonTransitionError) __all__ = [ @@ -58,5 +58,5 @@ "NondeterministicTransitionFunction", "DeterministicTransitionFunction", "DuplicateTransitionError", - "InvalidEpsilonTransition", + "InvalidEpsilonTransitionError", ] diff --git a/pyformlang/finite_automaton/deterministic_transition_function.py b/pyformlang/finite_automaton/deterministic_transition_function.py index 7b2f9a7..1f8f21e 100644 --- a/pyformlang/finite_automaton/deterministic_transition_function.py +++ b/pyformlang/finite_automaton/deterministic_transition_function.py @@ -6,7 +6,7 @@ from .nondeterministic_transition_function import \ NondeterministicTransitionFunction -from .nondeterministic_finite_automaton import InvalidEpsilonTransition +from .nondeterministic_finite_automaton import InvalidEpsilonTransitionError from ..objects.finite_automaton_objects import State, Symbol, Epsilon @@ -64,7 +64,7 @@ def add_transition(self, """ if symb_by == Epsilon(): - raise InvalidEpsilonTransition() + raise InvalidEpsilonTransitionError() s_to_old = self.get_next_state(s_from, symb_by) if s_to_old is not None and s_to_old != s_to: raise DuplicateTransitionError(s_from, diff --git a/pyformlang/finite_automaton/nondeterministic_finite_automaton.py b/pyformlang/finite_automaton/nondeterministic_finite_automaton.py index 7f56f9f..574ab21 100644 --- a/pyformlang/finite_automaton/nondeterministic_finite_automaton.py +++ b/pyformlang/finite_automaton/nondeterministic_finite_automaton.py @@ -117,7 +117,7 @@ def add_transition(self, s_to: Hashable) -> int: symb_by = to_symbol(symb_by) if symb_by == Epsilon(): - raise InvalidEpsilonTransition + raise InvalidEpsilonTransitionError return super().add_transition(s_from, symb_by, s_to) def copy(self) -> "NondeterministicFiniteAutomaton": @@ -155,6 +155,6 @@ def from_epsilon_nfa(cls, enfa: EpsilonNFA) \ return nfa -class InvalidEpsilonTransition(Exception): +class InvalidEpsilonTransitionError(Exception): """Exception raised when an epsilon transition is created in non-epsilon NFA""" diff --git a/pyformlang/finite_automaton/tests/test_deterministic_finite_automaton.py b/pyformlang/finite_automaton/tests/test_deterministic_finite_automaton.py index cf936cf..84b7aaa 100644 --- a/pyformlang/finite_automaton/tests/test_deterministic_finite_automaton.py +++ b/pyformlang/finite_automaton/tests/test_deterministic_finite_automaton.py @@ -8,7 +8,7 @@ from pyformlang.finite_automaton import DeterministicFiniteAutomaton from pyformlang.finite_automaton import State, Symbol, Epsilon from pyformlang.finite_automaton import DeterministicTransitionFunction -from pyformlang.finite_automaton import InvalidEpsilonTransition +from pyformlang.finite_automaton import InvalidEpsilonTransitionError class TestDeterministicFiniteAutomaton: @@ -224,7 +224,7 @@ def test_epsilon_refused(self): dfa = DeterministicFiniteAutomaton() state0 = State(0) state1 = State(1) - with pytest.raises(InvalidEpsilonTransition): + with pytest.raises(InvalidEpsilonTransitionError): dfa.add_transition(state0, Epsilon(), state1) def test_cyclic(self): diff --git a/pyformlang/finite_automaton/tests/test_deterministic_transition_function.py b/pyformlang/finite_automaton/tests/test_deterministic_transition_function.py index b04e631..4ec0c8a 100644 --- a/pyformlang/finite_automaton/tests/test_deterministic_transition_function.py +++ b/pyformlang/finite_automaton/tests/test_deterministic_transition_function.py @@ -7,7 +7,7 @@ from pyformlang.finite_automaton import DeterministicTransitionFunction from pyformlang.finite_automaton import State, Symbol, Epsilon from pyformlang.finite_automaton import \ - DuplicateTransitionError, InvalidEpsilonTransition + DuplicateTransitionError, InvalidEpsilonTransitionError class TestDeterministicTransitionFunction: @@ -102,7 +102,7 @@ def test_invalid_epsilon(self): s_from = State(0) s_to = State(1) epsilon = Epsilon() - with pytest.raises(InvalidEpsilonTransition): + with pytest.raises(InvalidEpsilonTransitionError): transition_function.add_transition(s_from, epsilon, s_to) def test_get_transitions_from(self): diff --git a/pyformlang/finite_automaton/tests/test_nondeterministic_finite_automaton.py b/pyformlang/finite_automaton/tests/test_nondeterministic_finite_automaton.py index bcf66ae..844656a 100644 --- a/pyformlang/finite_automaton/tests/test_nondeterministic_finite_automaton.py +++ b/pyformlang/finite_automaton/tests/test_nondeterministic_finite_automaton.py @@ -7,7 +7,7 @@ from pyformlang.finite_automaton import NondeterministicFiniteAutomaton from pyformlang.finite_automaton import DeterministicFiniteAutomaton from pyformlang.finite_automaton import State, Symbol, Epsilon -from pyformlang.finite_automaton import InvalidEpsilonTransition +from pyformlang.finite_automaton import InvalidEpsilonTransitionError class TestNondeterministicFiniteAutomaton: @@ -113,7 +113,7 @@ def test_epsilon_refused(self): dfa = NondeterministicFiniteAutomaton() state0 = State(0) state1 = State(1) - with pytest.raises(InvalidEpsilonTransition): + with pytest.raises(InvalidEpsilonTransitionError): dfa.add_transition(state0, Epsilon(), state1) def test_word_generation(self): From 8c5bde24d29a0ac1181b24a2cba6f7b568b61db2 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Wed, 1 Jan 2025 22:03:15 +0300 Subject: [PATCH 04/21] update sphinx config for using type annotations --- doc/conf.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index d4135e5..3d44833 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -40,7 +40,6 @@ 'sphinx.ext.mathjax', 'sphinx.ext.napoleon', 'sphinx.ext.todo', - 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. @@ -295,6 +294,11 @@ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False +# Use type hints in both signature and description. +autodoc_typehints = "both" + +# Use type annotations for documenting the return type. +napoleon_use_rtype = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} From 043ecfaec1db4e4af9f1efa89d976518942524c5 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 02:04:36 +0300 Subject: [PATCH 05/21] update finite_automaton module docs --- .../deterministic_finite_automaton.py | 170 ++++---- .../deterministic_transition_function.py | 81 ++-- .../finite_automaton/doubly_linked_list.py | 17 +- .../finite_automaton/doubly_linked_node.py | 16 +- pyformlang/finite_automaton/epsilon_nfa.py | 301 +++++++------- .../finite_automaton/finite_automaton.py | 387 +++++++++--------- .../hopcroft_processing_list.py | 18 +- .../nondeterministic_finite_automaton.py | 101 +++-- .../nondeterministic_transition_function.py | 128 +++--- pyformlang/finite_automaton/partition.py | 16 +- .../finite_automaton/transition_function.py | 123 +++++- pyformlang/finite_automaton/utils.py | 23 +- 12 files changed, 747 insertions(+), 634 deletions(-) diff --git a/pyformlang/finite_automaton/deterministic_finite_automaton.py b/pyformlang/finite_automaton/deterministic_finite_automaton.py index 3856f72..9dc77a3 100644 --- a/pyformlang/finite_automaton/deterministic_finite_automaton.py +++ b/pyformlang/finite_automaton/deterministic_finite_automaton.py @@ -1,6 +1,4 @@ -""" -Representation of a deterministic finite automaton -""" +"""Representation of a deterministic finite automaton.""" from typing import Iterable, AbstractSet, Optional, Hashable, Any @@ -15,27 +13,24 @@ class DeterministicFiniteAutomaton(NondeterministicFiniteAutomaton): - """ Represents a deterministic finite automaton - - This class represents a deterministic finite automaton. + """Representation of a deterministic finite automaton. Parameters ---------- - states : set of :class:`~pyformlang.finite_automaton.State`, optional - A finite set of states - input_symbols : set of :class:`~pyformlang.finite_automaton.Symbol`, optional - A finite set of input symbols - transition_function : \ - :class:`~pyformlang.finite_automaton.TransitionFunction`, optional - Takes as arguments a state and an input symbol and returns a state. - start_state : :class:`~pyformlang.finite_automaton.State`, optional - A start state, element of states - final_states : set of :class:`~pyformlang.finite_automaton.State`, optional + states: + A finite set of states. + input_symbols: + A finite set of input symbols. + transition_function: + A function that takes as arguments a state and an input symbol and + returns a state. + start_state: + A start state, element of states. + final_states: A set of final or accepting states. It is a subset of states. Examples -------- - >>> dfa = DeterministicFiniteAutomaton() Creates an empty deterministic finite automaton. @@ -64,7 +59,6 @@ class DeterministicFiniteAutomaton(NondeterministicFiniteAutomaton): Checks if the automaton recognize the word composed of a single letter, \ "abc". - """ def __init__(self, @@ -73,6 +67,7 @@ def __init__(self, transition_function: DeterministicTransitionFunction = None, start_state: Hashable = None, final_states: AbstractSet[Hashable] = None) -> None: + """Initializes the deterministic finite automaton.""" start_states = {start_state} if start_state is not None else None super().__init__(states, input_symbols, @@ -84,28 +79,25 @@ def __init__(self, @property def start_state(self) -> Optional[State]: - """ Gets the start state """ + """Gets the start state of the DFA.""" return list(self._start_states)[0] if self._start_states else None def add_start_state(self, state: Hashable) -> int: - """ Set an initial state + """Sets an initial state of the DFA. Parameters - ----------- - state : :class:`~pyformlang.finite_automaton.State` - The new initial state + ---------- + state: + The initial state to set. Returns - ---------- - done : int - 1 is correctly added + ------- + 1 is correctly added. Examples -------- - >>> dfa = DeterministicFiniteAutomaton() >>> dfa.add_start_state(0) - """ state = to_state(state) self._start_states = {state} @@ -113,25 +105,22 @@ def add_start_state(self, state: Hashable) -> int: return 1 def remove_start_state(self, state: Hashable) -> int: - """ remove an initial state + """Remove the initial state from the DFA. Parameters - ----------- - state : :class:`~pyformlang.finite_automaton.State` - The new initial state + ---------- + state: + The initial state to remove. Returns ---------- - done : int - 1 is correctly added + 1 is correctly removed. Examples -------- - >>> dfa = DeterministicFiniteAutomaton() >>> dfa.add_start_state(0) >>> dfa.remove_start_state(0) - """ state = to_state(state) if self._start_states == {state}: @@ -141,34 +130,43 @@ def remove_start_state(self, state: Hashable) -> int: def get_next_state(self, s_from: Hashable, symb_by: Hashable) \ -> Optional[State]: - """ Make a call of deterministic transition function """ + """Makes a call of deterministic transition function. + + Parameters + ---------- + s_from: + A state to make a transition from. + symb_by: + A symbol to make a transition with. + + Returns + ------- + The next state defined by the transition function. + """ s_from = to_state(s_from) symb_by = to_symbol(symb_by) return self._transition_function.get_next_state(s_from, symb_by) def accepts(self, word: Iterable[Hashable]) -> bool: - """ Checks whether the dfa accepts a given word + """Checks whether the DFA accepts a given word. Parameters ---------- - word : iterable of :class:`~pyformlang.finite_automaton.Symbol` - A sequence of input symbols + word: + A sequence of input symbols. Returns - ---------- - is_accepted : bool - Whether the word is accepted or not + ------- + Whether the word is accepted or not. Examples -------- - >>> dfa = DeterministicFiniteAutomaton() >>> dfa.add_transitions([(0, "abc", 1), (0, "d", 1)]) >>> dfa.add_start_state(0) >>> dfa.add_final_state(1) >>> dfa.accepts(["abc"]) True - """ word = [to_symbol(x) for x in word] current_state = self.start_state @@ -179,35 +177,29 @@ def accepts(self, word: Iterable[Hashable]) -> bool: return current_state is not None and self.is_final_state(current_state) def is_deterministic(self) -> bool: - """ Checks whether an automaton is deterministic + """Checks whether an automaton is deterministic. Returns - ---------- - is_deterministic : bool - Whether the automaton is deterministic + ------- + Whether the automaton is deterministic. Examples -------- - >>> dfa = DeterministicFiniteAutomaton() >>> dfa.is_deterministic() True - """ return True def copy(self) -> "DeterministicFiniteAutomaton": - """ Copies the current DFA + """Copies the current DFA. Returns - ---------- - enfa : :class:`~pyformlang.finite_automaton\ - .DeterministicFiniteAutomaton` - A copy of the current DFA + ------- + A copy of the current DFA. Examples -------- - >>> dfa = DeterministicFiniteAutomaton() >>> dfa.add_transitions([(0, "abc", 1), (0, "d", 1)]) >>> dfa.add_start_state(0) @@ -215,7 +207,6 @@ def copy(self) -> "DeterministicFiniteAutomaton": >>> dfa_copy = dfa.copy() >>> dfa.is_equivalent_to(dfa_copy) True - """ return self._copy_to(DeterministicFiniteAutomaton()) @@ -229,17 +220,14 @@ def _get_previous_transitions(self) -> PreviousTransitions: return previous_transitions def minimize(self) -> "DeterministicFiniteAutomaton": - """ Minimize the current DFA + """Minimize the current DFA. Returns - ---------- - dfa : :class:`~pyformlang.deterministic_finite_automaton\ - .DeterministicFiniteAutomaton` - The minimal DFA + ------- + A minimal DFA equivalent to the current one. Examples -------- - >>> dfa = DeterministicFiniteAutomaton() >>> dfa.add_transitions([(0, "abc", 1), (0, "d", 1)]) >>> dfa.add_start_state(0) @@ -247,7 +235,6 @@ def minimize(self) -> "DeterministicFiniteAutomaton": >>> dfa_minimal = dfa.minimize() >>> dfa.is_equivalent_to(dfa_minimal) True - """ if not self._start_states or not self._final_states: res = DeterministicFiniteAutomaton() @@ -286,30 +273,50 @@ def minimize(self) -> "DeterministicFiniteAutomaton": @classmethod def from_epsilon_nfa(cls, enfa: EpsilonNFA) \ -> "DeterministicFiniteAutomaton": - """ Builds dfa equivalent to the given enfa """ + """Builds DFA equivalent to the given ENFA. + + Parameters + ---------- + enfa: + A nondeterministic FA with epsilon transitions. + + Returns + ------- + A deterministic automaton equivalent to `enfa`. + """ return cls._from_epsilon_nfa_internal(enfa, True) @classmethod def from_nfa(cls, nfa: NondeterministicFiniteAutomaton) \ -> "DeterministicFiniteAutomaton": - """ Builds dfa equivalent to the given nfa """ + """Builds DFA equivalent to the given NFA. + + Parameters + ---------- + nfa: + A nondeterministic FA without epsilon transitions. + + Returns + ------- + A deterministic automaton equivalent to `nfa`. + """ return cls._from_epsilon_nfa_internal(nfa, False) @classmethod def _from_epsilon_nfa_internal(cls, enfa: EpsilonNFA, eclose: bool) \ -> "DeterministicFiniteAutomaton": - """ Builds dfa equivalent to the given automaton + """Builds DFA equivalent to the given automaton. Parameters ---------- - eclose : bool - Whether to use the epsilon closure or not + enfa: + A nondeterministic FA with epsilon transitions. + eclose: + Whether to use the epsilon closure or not. Returns - ---------- - dfa : :class:`~pyformlang.finite_automaton\ - .DeterministicFiniteAutomaton` - A dfa equivalent to the current nfa + ------- + A deterministic automaton equivalent to `enfa`. """ dfa = DeterministicFiniteAutomaton() # Add Eclose @@ -386,27 +393,25 @@ def _get_partition(self) -> Partition: return partition def __eq__(self, other: Any) -> bool: + """Checks whether the DFA is equal to the given object.""" if not isinstance(other, DeterministicFiniteAutomaton): return False return self.is_equivalent_to(other) def is_equivalent_to(self, other: "DeterministicFiniteAutomaton") -> bool: - """ Check whether two automata are equivalent + """Checks whether two automata are equivalent. Parameters ---------- - other : :class:`~pyformlang.deterministic_finite_automaton\ - .FiniteAutomaton` - A sequence of input symbols + other: + An automaton to check the equivalence to. Returns - ---------- - are_equivalent : bool - Whether the two automata are equivalent or not + ------- + Whether the two automata are equivalent or not Examples -------- - >>> dfa = DeterministicFiniteAutomaton() >>> dfa.add_transitions([(0, "abc", 1), (0, "d", 1)]) >>> dfa.add_start_state(0) @@ -414,7 +419,6 @@ def is_equivalent_to(self, other: "DeterministicFiniteAutomaton") -> bool: >>> dfa_minimal = dfa.minimize() >>> dfa.is_equivalent_to(dfa_minimal) True - """ self_minimal = self.minimize() other_minimal = other.minimize() diff --git a/pyformlang/finite_automaton/deterministic_transition_function.py b/pyformlang/finite_automaton/deterministic_transition_function.py index 1f8f21e..9f50617 100644 --- a/pyformlang/finite_automaton/deterministic_transition_function.py +++ b/pyformlang/finite_automaton/deterministic_transition_function.py @@ -1,6 +1,4 @@ -""" -A deterministic transition function -""" +"""A deterministic transition function.""" from typing import Optional @@ -11,57 +9,49 @@ class DeterministicTransitionFunction(NondeterministicTransitionFunction): - """A deterministic transition function in a finite automaton - - This is a deterministic transition function. + """A deterministic transition function in a finite automaton. Attributes ---------- - _transitions : dict - A dictionary which contains the transitions of a finite automaton + _transitions: + A dictionary which contains the transitions of a finite automaton. Examples -------- - >>> transition = TransitionFunction() >>> transition.add_transition(State(0), Symbol("a"), State(1)) Creates a transition function and adds a transition. - """ def add_transition(self, s_from: State, symb_by: Symbol, s_to: State) -> int: - """ Adds a new transition to the function + """Adds a new transition to the function. Parameters ---------- - s_from : :class:`~pyformlang.finite_automaton.State` - The source state - symb_by : :class:`~pyformlang.finite_automaton.Symbol` - The transition symbol - s_to : :class:`~pyformlang.finite_automaton.State` - The destination state - + s_from: + The source state. + symb_by: + The transition symbol. + s_to: + The destination state. Returns - -------- - done : int - Always 1 + ------- + Always 1. Raises - -------- + ------ DuplicateTransitionError - If the transition already exists + If the transition already exists. Examples -------- - >>> transition = TransitionFunction() >>> transition.add_transition(State(0), Symbol("a"), State(1)) - """ if symb_by == Epsilon(): raise InvalidEpsilonTransitionError() @@ -74,29 +64,45 @@ def add_transition(self, return super().add_transition(s_from, symb_by, s_to) def get_next_state(self, s_from: State, symb_by: Symbol) -> Optional[State]: - """ Make a call of deterministic transition function """ + """Makes a call of deterministic transition function. + + Parameters + ---------- + s_from: + A state to make a transition from. + symb_by: + A symbol to make a transition with. + + Returns + ------- + The next state defined by the transition function. + """ next_state = self(s_from, symb_by) return list(next_state)[0] if next_state else None def is_deterministic(self) -> bool: - """ Whether the transition function is deterministic """ + """Checks whether the transition function is deterministic. + + Returns + ------- + Whether the transition function is deterministic. + """ return True class DuplicateTransitionError(Exception): - """ Signals a duplicated transition + """Signals of a duplicated transition. Parameters ---------- - s_from : :class:`~pyformlang.finite_automaton.State` - The source state - symb_by : :class:`~pyformlang.finite_automaton.Symbol` - The transition symbol - s_to : :class:`~pyformlang.finite_automaton.State` - The wanted new destination state - s_to_old : :class:`~pyformlang.finite_automaton.State` - The old destination state - + s_from: + The source state. + symb_by: + The transition symbol. + s_to: + The wanted new destination state. + s_to_old: + The old destination state. """ def __init__(self, @@ -104,6 +110,7 @@ def __init__(self, symb_by: Symbol, s_to: State, s_to_old: State) -> None: + """Creates an instance of duplicate transition exception.""" super().__init__("Transition from " + str(s_from) + " by " + str(symb_by) + " goes to " + str(s_to_old) + " not " + str(s_to)) diff --git a/pyformlang/finite_automaton/doubly_linked_list.py b/pyformlang/finite_automaton/doubly_linked_list.py index d1345e9..1f021d8 100644 --- a/pyformlang/finite_automaton/doubly_linked_list.py +++ b/pyformlang/finite_automaton/doubly_linked_list.py @@ -1,4 +1,4 @@ -"""A doubly linked list""" +"""A doubly linked list.""" from typing import Iterable, Optional, Any @@ -6,16 +6,22 @@ class DoublyLinkedList(Iterable[DoublyLinkedNode]): - """ A doubly linked list """ + """A doubly linked list.""" def __init__(self) -> None: + """Initializes the list.""" self.first: Optional[DoublyLinkedNode] = None self.last: Optional[DoublyLinkedNode] = None self.size = 0 self._current_node: Optional[DoublyLinkedNode] = None def append(self, value: Any) -> DoublyLinkedNode: - """ Appends an element """ + """Appends the given element. + + Returns + ------- + The added node. + """ if self.last is not None: self.last = self.last.append(value) else: @@ -26,7 +32,7 @@ def append(self, value: Any) -> DoublyLinkedNode: return self.last def delete(self, node: DoublyLinkedNode) -> None: - """ Delete an element """ + """Deletes the given node from the list.""" if node.next_node is not None: node.next_node.previous_node = node.previous_node else: @@ -38,13 +44,16 @@ def delete(self, node: DoublyLinkedNode) -> None: self.size -= 1 def __len__(self) -> int: + """Gets the length of the list.""" return self.size def __iter__(self) -> "DoublyLinkedList": + """Initializes the list iterator.""" self._current_node = self.first return self def __next__(self) -> DoublyLinkedNode: + """Gets the next element of the iterator.""" if self._current_node is None: raise StopIteration res = self._current_node diff --git a/pyformlang/finite_automaton/doubly_linked_node.py b/pyformlang/finite_automaton/doubly_linked_node.py index 7621fff..701564e 100644 --- a/pyformlang/finite_automaton/doubly_linked_node.py +++ b/pyformlang/finite_automaton/doubly_linked_node.py @@ -1,33 +1,31 @@ -"""Linked nodes in both direction""" +"""Nodes linked in both directions.""" from typing import Optional, Any class DoublyLinkedNode: - """Represents doubly linked list of nodes from a doubly linked list""" + """A node in the doubly linked list.""" def __init__(self, next_node: "DoublyLinkedNode" = None, previous_node: "DoublyLinkedNode" = None, value: Any = None) -> None: + """Initializes the node.""" self.next_node: Optional[DoublyLinkedNode] = next_node self.previous_node: Optional[DoublyLinkedNode] = previous_node self.value: Any = value def append(self, value: Any) -> "DoublyLinkedNode": - """ - Append a new node with the given value + """Appends a new node with the given value. Parameters ---------- - value : any - The value if the new node + value: + A value of the new node. Returns ------- - new_node: - The created node - + The created node. """ next_node = DoublyLinkedNode(self.next_node, self, value) self.next_node = next_node diff --git a/pyformlang/finite_automaton/epsilon_nfa.py b/pyformlang/finite_automaton/epsilon_nfa.py index 09af9e7..85bf4d3 100644 --- a/pyformlang/finite_automaton/epsilon_nfa.py +++ b/pyformlang/finite_automaton/epsilon_nfa.py @@ -1,6 +1,4 @@ -""" -Nondeterministic Automaton with epsilon transitions -""" +"""A Nondeterministic Automaton with epsilon transitions.""" from typing import Iterable, Set, AbstractSet, Hashable from networkx import MultiDiGraph @@ -13,27 +11,24 @@ class EpsilonNFA(FiniteAutomaton): - """ Represents an epsilon NFA + """A Nondeterministic Automaton with epsilon transitions. Parameters ---------- - states : set of :class:`~pyformlang.finite_automaton.State`, optional - A finite set of states - input_symbols : set of :class:`~pyformlang.finite_automaton.Symbol`, \ - optional - A finite set of input symbols - transition_function : \ - :class:`~pyformlang.finite_automaton.NondeterministicTransitionFunction`\ -, optional - Takes as arguments a state and an input symbol and returns a state. - start_state : set of :class:`~pyformlang.finite_automaton.State`, optional - A start state, element of states - final_states : set of :class:`~pyformlang.finite_automaton.State`, optional + states: + A finite set of states. + input_symbols: + A finite set of input symbols. + transition_function: + A function that takes as arguments a state and an input symbol and + returns a set of states. + start_states: + A set of start or initial states. It is a subset of states. + final_states: A set of final or accepting states. It is a subset of states. Examples -------- - >>> enfa = EpsilonNFA() Creates an empty epsilon non-deterministic automaton. @@ -54,7 +49,6 @@ class EpsilonNFA(FiniteAutomaton): False Checks if the automaton is deterministic. - """ def __init__( @@ -64,6 +58,7 @@ def __init__( transition_function: NondeterministicTransitionFunction = None, start_states: AbstractSet[Hashable] = None, final_states: AbstractSet[Hashable] = None) -> None: + """Initializes an epsilon NFA.""" super().__init__() self._states = {to_state(x) for x in states or set()} self._input_symbols = {to_symbol(x) for x in input_symbols or set()} @@ -78,20 +73,18 @@ def _get_next_states_iterable( self, current_states: Iterable[State], symbol: Symbol) -> Set[State]: - """ Gives the set of next states, starting from a set of states + """Gives the set of next states, starting from a set of states. Parameters ---------- - current_states : iterable of \ - :class:`~pyformlang.finite_automaton.State` - The considered list of states - symbol : Symbol - The symbol of the link + current_states: + The considered list of states. + symbol: + The symbol of the link. Returns - ---------- - next_states : set of :class:`~pyformlang.finite_automaton.State` - The next of resulting states + ------- + The next of resulting states. """ next_states = set() for current_state in current_states: @@ -99,21 +92,19 @@ def _get_next_states_iterable( return next_states def accepts(self, word: Iterable[Hashable]) -> bool: - """ Checks whether the epsilon nfa accepts a given word + """Checks whether the epsilon NFA accepts a given word. Parameters ---------- - word : iterable of :class:`~pyformlang.finite_automaton.Symbol` - A sequence of input symbols + word: + A sequence of input symbols. Returns - ---------- - is_accepted : bool - Whether the word is accepted or not + ------- + Whether the word is accepted or not. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -124,7 +115,6 @@ def accepts(self, word: Iterable[Hashable]) -> bool: >>> enfa.accepts(["epsilon"]) False - """ word = [to_symbol(x) for x in word] current_states = self.eclose_iterable(self._start_states) @@ -137,21 +127,19 @@ def accepts(self, word: Iterable[Hashable]) -> bool: return any(self.is_final_state(x) for x in current_states) def eclose_iterable(self, states: Iterable[Hashable]) -> Set[State]: - """ Compute the epsilon closure of a collection of states + """Computes the epsilon closure of a collection of states. Parameters ---------- - states : iterable of :class:`~pyformlang.finite_automaton.State` - The source states + states: + The source states. Returns - --------- - states : set of :class:`~pyformlang.finite_automaton.State` - The epsilon closure of the source state + ------- + The epsilon closure of the source state. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -167,21 +155,19 @@ def eclose_iterable(self, states: Iterable[Hashable]) -> Set[State]: return res def eclose(self, state: Hashable) -> Set[State]: - """ Compute the epsilon closure of a state + """Computes the epsilon closure of a state. Parameters ---------- - state : :class:`~pyformlang.finite_automaton.State` - The source state + state: + The source state. Returns - --------- - states : set of :class:`~pyformlang.finite_automaton.State` - The epsilon closure of the source state + ------- + The epsilon closure of the source state. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -189,7 +175,6 @@ def eclose(self, state: Hashable) -> Set[State]: >>> enfa.add_final_state(1) >>> enfa.eclose(0) {2} - """ state = to_state(state) to_process = [state] @@ -204,19 +189,14 @@ def eclose(self, state: Hashable) -> Set[State]: return processed def is_deterministic(self) -> bool: - """ Checks whether an automaton is deterministic + """Checks whether an automaton is deterministic. Returns - ---------- - is_deterministic : bool - Whether the automaton is deterministic - - Examples - -------- + ------- + Whether the automaton is deterministic. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -224,23 +204,20 @@ def is_deterministic(self) -> bool: >>> enfa.add_final_state(1) >>> enfa.is_deterministic() False - """ return len(self._start_states) <= 1 \ and self._transition_function.is_deterministic() \ and all({x} == self.eclose(x) for x in self._states) def copy(self) -> "EpsilonNFA": - """ Copies the current Epsilon NFA + """Copies the current Epsilon NFA. Returns - ---------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - A copy of the current Epsilon NFA + ------- + A copy of the current Epsilon NFA. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -249,35 +226,32 @@ def copy(self) -> "EpsilonNFA": >>> enfa_copy = enfa.copy() >>> enfa.is_equivalent_to(enfa_copy) True - """ return self._copy_to(EpsilonNFA()) @classmethod def from_networkx(cls, graph: MultiDiGraph) -> "EpsilonNFA": - """ - Import a networkx graph into an finite state automaton. \ - The imported graph requires to have the good format, i.e. to come \ - from the function to_networkx + """Imports a networkx graph into an finite state automaton. + + The imported graph requires to have the good format, i.e. to come + from the function to_networkx. Parameters ---------- - graph : - The graph representation of the automaton + graph: + A graph representation of the automaton. Returns ------- - enfa : - A epsilon nondeterministic finite automaton read from the graph + A epsilon nondeterministic finite automaton read from the graph. - TODO - ------- - * We lose the type of the node value if going through a dot file - * Explain the format + Todo + ---- + * We lose the type of the node value if going through a dot file. + * Explain the format. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -285,7 +259,6 @@ def from_networkx(cls, graph: MultiDiGraph) -> "EpsilonNFA": >>> enfa.add_final_state(1) >>> graph = enfa.to_networkx() >>> enfa_from_nx = EpsilonNFA.from_networkx(graph) - """ enfa = EpsilonNFA() for s_from in graph: @@ -303,20 +276,17 @@ def from_networkx(cls, graph: MultiDiGraph) -> "EpsilonNFA": return enfa def get_complement(self) -> "EpsilonNFA": - """ Get the complement of the current Epsilon NFA + """Gets the complement of the current Epsilon NFA. Equivalent to: - - >>> -automaton + >>> -automaton Returns - ---------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - A complement automaton + ------- + A complement automaton. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -328,7 +298,6 @@ def get_complement(self) -> "EpsilonNFA": >>> enfa_complement.accepts(["abc"]) False - """ enfa = self.copy() trash = self.__get_new_state("Trash") @@ -351,35 +320,31 @@ def get_complement(self) -> "EpsilonNFA": return enfa def __neg__(self) -> "EpsilonNFA": - """ Get the complement of the current Epsilon NFA + """Gets the complement of the current Epsilon NFA. Returns - ---------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - A complement automaton + ------- + A complement automaton. """ return self.get_complement() def get_intersection(self, other: "EpsilonNFA") -> "EpsilonNFA": - """ Computes the intersection of two Epsilon NFAs + """Computes the intersection of two Epsilon NFAs. Equivalent to: - - >>> automaton0 and automaton1 + >>> automaton0 & automaton1 Parameters ---------- - other : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The other Epsilon NFA + other: + the other Epsilon NFA. Returns - --------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The intersection of the two Epsilon NFAs + ------- + The intersection of the two Epsilon NFAs. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -395,7 +360,6 @@ def get_intersection(self, other: "EpsilonNFA") -> "EpsilonNFA": >>> enfa_inter.accepts(["d"]) True - """ enfa = EpsilonNFA() symbols = list(self.symbols.intersection(other.symbols)) @@ -423,22 +387,34 @@ def get_intersection(self, other: "EpsilonNFA") -> "EpsilonNFA": return enfa def __and__(self, other: "EpsilonNFA") -> "EpsilonNFA": - """ Computes the intersection of two Epsilon NFAs + """Computes the intersection of two Epsilon NFAs. Parameters ---------- - other : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The other Epsilon NFA + other: + The other Epsilon NFA. Returns --------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The intersection of the two Epsilon NFAs + The intersection of the two Epsilon NFAs. """ return self.get_intersection(other) def get_union(self, other: "EpsilonNFA") -> "EpsilonNFA": - """ Computes the union with given Epsilon NFA """ + """Computes the union with given Epsilon NFA. + + Equivalent to: + >>> automaton0 | automaton1 + + Parameters + ---------- + other: + The other Epsilon NFA. + + Returns + ------- + The union of the two Epsilon NFAs. + """ union = EpsilonNFA() self.__copy_transitions_marked(self, union, 0) self.__copy_transitions_marked(other, union, 1) @@ -455,11 +431,34 @@ def get_union(self, other: "EpsilonNFA") -> "EpsilonNFA": return union def __or__(self, other: "EpsilonNFA") -> "EpsilonNFA": - """ Computes the union with given Epsilon NFA """ + """Computes the union with given Epsilon NFA. + + Parameters + ---------- + other: + The other Epsilon NFA. + + Returns + ------- + The union of the two Epsilon NFAs. + """ return self.get_union(other) def concatenate(self, other: "EpsilonNFA") -> "EpsilonNFA": - """ Computes the concatenation of two Epsilon NFAs """ + """Computes the concatenation of two Epsilon NFAs. + + Equivalent to: + >>> automaton0 + automaton1 + + Parameters + ---------- + other: + The other Epsilon NFA. + + Returns + ------- + The concatenation of the two Epsilon NFAs. + """ concatenation = EpsilonNFA() self.__copy_transitions_marked(self, concatenation, 0) self.__copy_transitions_marked(other, concatenation, 1) @@ -475,29 +474,36 @@ def concatenate(self, other: "EpsilonNFA") -> "EpsilonNFA": return concatenation def __add__(self, other: "EpsilonNFA") -> "EpsilonNFA": - """ Computes the concatenation of two Epsilon NFAs """ + """Computes the concatenation of two Epsilon NFAs. + + Parameters + ---------- + other: + The other Epsilon NFA. + + Returns + ------- + The concatenation of the two Epsilon NFAs. + """ return self.concatenate(other) def get_difference(self, other: "EpsilonNFA") -> "EpsilonNFA": - """ Computes the difference with another Epsilon NFA + """Computes the difference with another Epsilon NFA. Equivalent to: - - >>> automaton0 - automaton1 + >>> automaton0 - automaton1 Parameters ---------- - other : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The other Epsilon NFA + other: + The other Epsilon NFA. Returns --------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The difference with the other epsilon NFA + The difference with the other epsilon NFA. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -513,7 +519,6 @@ def get_difference(self, other: "EpsilonNFA") -> "EpsilonNFA": >>> enfa_diff.accepts(["abc"]) True - """ other = other.copy() for symbol in self._input_symbols: @@ -521,37 +526,31 @@ def get_difference(self, other: "EpsilonNFA") -> "EpsilonNFA": return self.get_intersection(other.get_complement()) def __sub__(self, other: "EpsilonNFA") -> "EpsilonNFA": - """ Compute the difference with another Epsilon NFA - - Equivalent to: - >> automaton0 - automaton1 + """Computes the difference with another Epsilon NFA. Parameters ---------- - other : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The other Epsilon NFA + other: + The other Epsilon NFA. Returns - --------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The difference with the other epsilon NFA + ------- + The difference with the other epsilon NFA. """ return self.get_difference(other) def reverse(self) -> "EpsilonNFA": - """ Compute the reversed EpsilonNFA + """Computes the reversed Epsilon NFA. Equivalent to: - >> ~automaton + >> ~automaton Returns - --------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The reversed automaton + ------- + The reversed Epsilon NFA. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (1, "d", 2)]) >>> enfa.add_start_state(0) @@ -559,7 +558,6 @@ def reverse(self) -> "EpsilonNFA": >>> enfa_reverse = enfa.reverse() >>> enfa_reverse.accepts(["d", "abc"]) True - """ enfa = EpsilonNFA() for state0 in self._states: @@ -575,17 +573,21 @@ def reverse(self) -> "EpsilonNFA": return enfa def __invert__(self) -> "EpsilonNFA": - """ Compute the reversed EpsilonNFA + """Compute the reversed Epsilon NFA. Returns - --------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - The reversed automaton + ------- + The reversed Epsilon NFA. """ return self.reverse() def kleene_star(self) -> "EpsilonNFA": - """ Compute the kleene closure of current EpsilonNFA """ + """Computes the kleene closure of current Epsilon NFA. + + Returns + ------- + The kleene closure of current Epsilon NFA. + """ new_start = self.__get_new_state("Start") kleene_closure = EpsilonNFA(start_states={new_start}, final_states={new_start}) @@ -597,16 +599,14 @@ def kleene_star(self) -> "EpsilonNFA": return kleene_closure def is_empty(self) -> bool: - """ Checks if the language represented by the FSM is empty or not + """Checks if the language represented by the ENFA is empty or not. Returns - ---------- - is_empty : bool - Whether the language is empty or not + ------- + Whether the language is empty or not. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -614,7 +614,6 @@ def is_empty(self) -> bool: >>> enfa.add_final_state(1) >>> enfa.is_empty() False - """ to_process = [] processed = set() @@ -637,13 +636,11 @@ def is_empty(self) -> bool: return True def __bool__(self) -> bool: + """Checks if the language represented by the ENFA is empty or not.""" return not self.is_empty() def __get_new_state(self, prefix: str) -> State: - """ - Get a state that wasn't previously in automaton - starting with given string. - """ + """Gets a new state in the automaton starting with given prefix.""" existing_values = set(state.value for state in self.states) while prefix in existing_values: prefix += '`' @@ -653,7 +650,7 @@ def __get_new_state(self, prefix: str) -> State: def __copy_transitions_marked(fa_to_add_from: FiniteAutomaton, fa_to_add_to: FiniteAutomaton, mark: int) -> None: - """ Copy transitions from one FA to another with each state marked """ + """Copies transitions from one FA to another with each state marked.""" for s_from, symb_by, s_to in fa_to_add_from: fa_to_add_to.add_transition((mark, s_from.value), symb_by, @@ -661,5 +658,5 @@ def __copy_transitions_marked(fa_to_add_from: FiniteAutomaton, @staticmethod def __combine_state_pair(state0: State, state1: State) -> State: - """ Combine two states """ + """Combines the two given states.""" return State(str(state0.value) + "; " + str(state1.value)) diff --git a/pyformlang/finite_automaton/finite_automaton.py b/pyformlang/finite_automaton/finite_automaton.py index 30ce648..2662a95 100644 --- a/pyformlang/finite_automaton/finite_automaton.py +++ b/pyformlang/finite_automaton/finite_automaton.py @@ -1,4 +1,4 @@ -""" A general finite automaton representation """ +"""A general finite automaton representation.""" from typing import Dict, List, Set, Tuple, \ Iterable, Iterator, Optional, Hashable, Any, TypeVar @@ -17,28 +17,26 @@ class FiniteAutomaton(Iterable[Tuple[State, Symbol, State]]): - """ Represents a general finite automaton + """A general finite automaton representation. Attributes ---------- - _states : set of :class:`~pyformlang.finite_automaton.State`, optional - A finite set of states - _input_symbols : set of :class:`~pyformlang.finite_automaton.Symbol`, \ - optional - A finite set of input symbols - _transition_function : \ - :class:`~pyformlang.finite_automaton.NondeterministicTransitionFunction`\ - , optional - Takes as arguments a state and an input symbol and returns a state. - _start_state : set of :class:`~pyformlang.finite_automaton.State`, optional - A start state, element of states - _final_states : set of :class:`~pyformlang.finite_automaton.State`, \ - optional - A set of final or accepting states. It is a subset of states. + _states: + A finite set of states. + _input_symbols: + A finite set of input symbols. + _transition_function: + A function that takes as arguments a state and an input symbol and + returns a set of states. + _start_states: + A set of start or initial states. It is a subset of _states. + _final_states: + A set of final or accepting states. It is a subset of _states. """ @abstractmethod def __init__(self) -> None: + """Initializes a finite automaton.""" self._states: Set[State] self._input_symbols: Set[Symbol] self._transition_function: TransitionFunction @@ -47,62 +45,47 @@ def __init__(self) -> None: @property def states(self) -> Set[State]: - """ Gives the states - - Returns - ---------- - states : set of :class:`~pyformlang.finite_automaton.State` - The states - """ + """Gets the states of the automaton.""" return self._states @property def symbols(self) -> Set[Symbol]: - """The symbols""" + """Gets the input alphabet of the automaton.""" return self._input_symbols @property def start_states(self) -> Set[State]: - """The start states""" + """Gets the start states of the automaton.""" return self._start_states @property def final_states(self) -> Set[State]: - """The final states""" + """Gets the final states of the automaton.""" return self._final_states def add_transition(self, s_from: Hashable, symb_by: Hashable, s_to: Hashable) -> int: - """ Adds a transition to the nfa + """Adds the given transition to the automaton. Parameters ---------- - s_from : :class:`~pyformlang.finite_automaton.State` - The source state - symb_by : :class:`~pyformlang.finite_automaton.Symbol` - The transition symbol - s_to : :class:`~pyformlang.finite_automaton.State` - The destination state - + s_from: + The source state. + symb_by: + The transition symbol. + s_to: + The destination state. Returns - -------- - done : int - Always 1 - - Raises - -------- - DuplicateTransitionError - If the transition already exists + ------- + Always 1 Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transition(0, "abc", 1) - """ s_from = to_state(s_from) symb_by = to_symbol(symb_by) @@ -116,33 +99,23 @@ def add_transition(self, def add_transitions(self, transitions_list: \ Iterable[Tuple[Hashable, Hashable, Hashable]]) -> int: - """ - Adds several transitions to the automaton + """Adds several transitions to the automaton. Parameters ---------- - transitions_list : list of triples of (s_from, symb_by, s_to) - A list of all the transitions represented as triples as they \ - would be used in add_transition + transitions_list: + A list of all the transitions represented as triples as they + would be used in add_transition. Returns - -------- - done : int - Always 1 - - Raises - -------- - DuplicateTransitionError - If the transition already exists + ------- + Always 1 Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) - - """ temp = 0 for s_from, symb_by, s_to in transitions_list: @@ -153,30 +126,26 @@ def remove_transition(self, s_from: Hashable, symb_by: Hashable, s_to: Hashable) -> int: - """ Remove a transition of the nfa + """Removes a transition from the automaton. Parameters ---------- - s_from : :class:`~pyformlang.finite_automaton.State` - The source state - symb_by : :class:`~pyformlang.finite_automaton.Symbol` - The transition symbol - s_to : :class:`~pyformlang.finite_automaton.State` - The destination state - + s_from: + The source state. + symb_by: + The transition symbol. + s_to: + The destination state. Returns -------- - done : int - 1 if the transition existed, 0 otherwise + 1 if the transition existed, 0 otherwise. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transition(0, "abc", 1) >>> enfa.remove_transition(0, "abc", 1) - """ s_from = to_state(s_from) symb_by = to_symbol(symb_by) @@ -186,46 +155,40 @@ def remove_transition(self, s_to) def get_number_transitions(self) -> int: - """ Gives the number of transitions + """Gets the number of transitions in the automaton. Returns - ---------- - n_transitions : int - The number of deterministic transitions + ------- + The number of transitions in the automaton. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) >>> enfa.get_number_transitions() 3 - """ return self._transition_function.get_number_transitions() def add_start_state(self, state: Hashable) -> int: - """ Set an initial state + """Adds an initial state to the automaton. Parameters - ----------- - state : :class:`~pyformlang.finite_automaton.State` - The new initial state + ---------- + state: + The initial state to add. Returns - ---------- - done : int - 1 is correctly added + ------- + 1 is correctly added. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) >>> enfa.add_start_state(0) - """ state = to_state(state) self._start_states.add(state) @@ -233,27 +196,24 @@ def add_start_state(self, state: Hashable) -> int: return 1 def remove_start_state(self, state: Hashable) -> int: - """ remove an initial state + """Removes an initial state from the automaton. Parameters ----------- - state : :class:`~pyformlang.finite_automaton.State` - The new initial state + state: + The initial state to remove. Returns - ---------- - done : int - 1 is correctly added + ------- + 1 is correctly removed. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.remove_start_state(0) - """ state = to_state(state) if state in self._start_states: @@ -262,27 +222,24 @@ def remove_start_state(self, state: Hashable) -> int: return 0 def add_final_state(self, state: Hashable) -> int: - """ Adds a new final state + """Adds a new final state to the automaton. Parameters - ----------- - state : :class:`~pyformlang.finite_automaton.State` - A new final state + ---------- + state: + A final state to add. Returns - ---------- - done : int - 1 is correctly added + ------- + 1 is correctly added. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) - """ state = to_state(state) self._final_states.add(state) @@ -290,21 +247,19 @@ def add_final_state(self, state: Hashable) -> int: return 1 def remove_final_state(self, state: Hashable) -> int: - """ Remove a final state + """Removes a final state from the automaton. Parameters - ----------- - state : :class:`~pyformlang.finite_automaton.State` - A final state to remove + ---------- + state: + A final state to remove. Returns - ---------- - done : int - 0 if it was not a final state, 1 otherwise + ------- + 0 if it was not a final state, 1 otherwise. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -318,31 +273,27 @@ def remove_final_state(self, state: Hashable) -> int: return 1 return 0 - def __call__(self, s_from: Hashable, symb_by: Hashable) -> Set[State]: - """ Gives the states obtained after calling a symbol on a state - Calls the transition function + def __call__(self, s_from: Hashable, symb_by: Hashable) -> Set[State]: + """Calls the transition function of the automaton. Parameters - ----------- - state : :class:`~pyformlang.finite_automaton.State` - The source state - symbol : :class:`~pyformlang.finite_automaton.Symbol` - The symbol, optional if we want all transitions + ---------- + state: + The source state. + symbol: + The transition symbol. Returns - ---------- - states : list of :class:`~pyformlang.finite_automaton.State` - The next states + ------- + The next states defined by the transition function. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) >>> enfa(0, "abc") [1] - """ s_from = to_state(s_from) symb_by = to_symbol(symb_by) @@ -350,7 +301,7 @@ def __call__(self, s_from: Hashable, symb_by: Hashable) -> Set[State]: def __contains__(self, transition: Tuple[Hashable, Hashable, Hashable]) -> bool: - """ Whether the given transition is present in finite automaton """ + """Checks if the given transition is present in finite automaton.""" s_from, symb_by, s_to = transition s_from = to_state(s_from) symb_by = to_symbol(symb_by) @@ -359,31 +310,49 @@ def __contains__(self, def get_transitions_from(self, s_from: Hashable) \ -> Iterable[Tuple[Symbol, State]]: - """ Gets transitions from the given state """ + """Gets transitions from the given state. + + Parameters + ---------- + s_from: + A state to get transitions from. + + Yields + ------ + Pairs of transition symbol and destination state. + """ s_from = to_state(s_from) return self._transition_function.get_transitions_from(s_from) def get_next_states_from(self, s_from: Hashable) -> Set[State]: - """ Gets a set of states that are next to the given one """ + """Gets a set of states that are next to the given one. + + Parameters + ---------- + s_from: + A state to get next states from. + + Returns + ------- + A set of next states defined by the transition function. + """ s_from = to_state(s_from) return self._transition_function.get_next_states_from(s_from) def is_final_state(self, state: Hashable) -> bool: - """ Checks if a state is final + """Checks if a state is final in the automaton. Parameters ----------- - state : :class:`~pyformlang.finite_automaton.State` - The state to check + state: + The state to check. Returns - ---------- - is_final : bool - Whether the state is final or not + ------- + Whether the state is final or not. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -391,48 +360,42 @@ def is_final_state(self, state: Hashable) -> bool: >>> enfa.add_final_state(1) >>> enfa.is_final_state(1) True - """ state = to_state(state) return state in self._final_states def add_symbol(self, symbol: Hashable) -> None: - """ Add a symbol + """Adds the given symbol to the input alphabet. Parameters - ----------- - symbol : :class:`~pyformlang.finite_automaton.Symbol` - The symbol + ---------- + symbol: + The symbol to add. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_symbol("a") - """ symbol = to_symbol(symbol) self._input_symbols.add(symbol) def to_fst(self) -> FST: - """ Turns the finite automaton into a finite state transducer + """Turns the finite automaton into a finite state transducer. - The transducers accepts only the words in the language of the \ - automaton and output the input word + The transducers accepts only the words in the language of the + automaton and output the input word. Returns - ---------- - fst : :class:`~pyformlang.fst.FST` - The equivalent FST + ------- + The equivalent FST. Examples -------- - >>> enfa = EpsilonNFA() >>> fst = enfa.to_fst() >>> fst.states {} - """ fst = FST() for start_state in self._start_states: @@ -447,17 +410,14 @@ def to_fst(self) -> FST: return fst def is_acyclic(self) -> bool: - """ - Checks if the automaton is acyclic + """Checks if the automaton is acyclic. Returns ------- - is_acyclic : bool - Whether the automaton is acyclic or not + Whether the automaton is acyclic or not. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -465,7 +425,6 @@ def is_acyclic(self) -> bool: >>> enfa.add_final_state(1) >>> enfa.is_acyclic() True - """ to_process = [] for state in self._start_states: @@ -484,24 +443,20 @@ def is_acyclic(self) -> bool: return True def to_networkx(self) -> MultiDiGraph: - """ - Transform the current automaton into a networkx graph + """Transforms the current automaton into a networkx graph. Returns ------- - graph : networkx.MultiDiGraph - A networkx MultiDiGraph representing the automaton + A networkx MultiDiGraph representing the automaton. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> graph = enfa.to_networkx() - """ graph = MultiDiGraph() for state in self._states: @@ -522,25 +477,27 @@ def to_networkx(self) -> MultiDiGraph: @classmethod @abstractmethod def from_networkx(cls, graph: MultiDiGraph) -> "FiniteAutomaton": - """ - Import a networkx graph into an finite state automaton. \ - The imported graph requires to have the good format, i.e. to come \ - from the function to_networkx + """Import a networkx graph into an finite state automaton. + + The imported graph requires to have the good format, i.e. to come + from the function to_networkx. + + Returns + ------- + The imported finite automaton. """ raise NotImplementedError def write_as_dot(self, filename: str) -> None: - """ - Write the automaton in dot format into a file + """Writes the automaton in dot format into a file. Parameters ---------- - filename : str - The filename where to write the dot file + filename: + A name of the file to write the dot file to. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -552,13 +509,31 @@ def write_as_dot(self, filename: str) -> None: @abstractmethod def accepts(self, word: Iterable[Hashable]) -> bool: - """ Checks whether the finite automaton accepts a given word """ + """Checks whether the finite automaton accepts a given word. + + Parameters + ---------- + word: + an iterable of input symbols. + + Returns + ------- + Whether the given word is accepted or not. + """ raise NotImplementedError def get_accepted_words(self, max_length: Optional[int] = None) \ -> Iterable[List[Symbol]]: - """ - Gets words accepted by the finite automaton. + """Gets words accepted by the finite automaton. + + Parameters + ---------- + max_length: + A max length of the generated words. + + Yields + ------ + Words accepted by current automaton. """ if max_length is not None and max_length < 0: return @@ -586,9 +561,12 @@ def get_accepted_words(self, max_length: Optional[int] = None) \ yield current_word def _get_states_leading_to_final(self) -> Set[State]: - """ - Gets a set of states from which one - of the final states can be reached. + """Gets a set of states that lead to final ones from start. + + Returns + ------- + A set of states that are on path from start state + to one of the final states. """ leading_to_final = self.final_states.copy() visited = set() @@ -615,7 +593,7 @@ def _get_states_leading_to_final(self) -> Set[State]: return leading_to_final def _get_reachable_states(self) -> Set[State]: - """ Get all states which are reachable """ + """Gets all states which are reachable in the automaton.""" visited = set() states_to_process = deque(self.start_states) while states_to_process: @@ -627,47 +605,61 @@ def _get_reachable_states(self) -> Set[State]: return visited def __len__(self) -> int: - """Number of transitions""" + """Gets the number of transitions in the automaton.""" return len(self._transition_function) def __iter__(self) -> Iterator[Tuple[State, Symbol, State]]: + """Yields the transitions described by the transition function.""" yield from self._transition_function def to_dict(self) -> Dict[State, Dict[Symbol, Set[State]]]: - """ - Get the dictionary representation of the transition function. The \ - keys of the dictionary are the source nodes. The items are \ - dictionaries where the keys are the symbols of the transitions and \ + """Get the dictionary representation of the transition function. + + The keys of the dictionary are the source nodes. The items are + dictionaries where the keys are the symbols of the transitions and the items are the set of target nodes. Returns ------- - transition_dict : dict - The transitions as a dictionary. + The transition function as a dictionary. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa_dict = enfa.to_dict() - """ return self._transition_function.to_dict() @abstractmethod def copy(self: AutomatonT) -> AutomatonT: - """ Copies the current Finite Automaton instance """ + """Copies the current Finite Automaton instance. + + Returns + ------- + The copy of current finite automaton. + """ raise NotImplementedError def __copy__(self: AutomatonT) -> AutomatonT: + """Copies the current Finite Automaton instance.""" return self.copy() def _copy_to(self, fa_to_copy_to: AutomatonT) -> AutomatonT: - """ Copies current automaton properties to the given one """ + """Copies current automaton properties to the given one. + + Parameters + ---------- + fa_to_copy_to: + An automaton to copy current properties to. + + Returns + ------- + The given automaton instance after copying. + """ for start in self._start_states: fa_to_copy_to.add_start_state(start) for final in self._final_states: @@ -684,14 +676,21 @@ def _copy_to(self, fa_to_copy_to: AutomatonT) -> AutomatonT: @abstractmethod def is_deterministic(self) -> bool: - """ Checks if the automaton is deterministic """ + """Checks if current automaton is deterministic. + + Returns + ------- + Whether the automaton is deterministic or not. + """ raise NotImplementedError @staticmethod def __try_add(set_to_add_to: Set[Any], element_to_add: Any) -> bool: - """ - Tries to add a given element to the given set. - Returns True if element was added, otherwise False. + """Tries to add a given element to the given set. + + Returns + ------- + True if element was added, otherwise False. """ initial_length = len(set_to_add_to) set_to_add_to.add(element_to_add) @@ -699,7 +698,7 @@ def __try_add(set_to_add_to: Set[Any], element_to_add: Any) -> bool: @staticmethod def __add_start_state_to_graph(graph: MultiDiGraph, state: State) -> None: - """ Adds a starting node to a given graph """ + """Adds a starting node to a given graph.""" graph.add_node("starting_" + str(state), label="", shape=None, diff --git a/pyformlang/finite_automaton/hopcroft_processing_list.py b/pyformlang/finite_automaton/hopcroft_processing_list.py index 3997754..f8d1371 100644 --- a/pyformlang/finite_automaton/hopcroft_processing_list.py +++ b/pyformlang/finite_automaton/hopcroft_processing_list.py @@ -1,5 +1,6 @@ -""" A representation for Hopcroft minimization algorithm -For internal usage +"""A representation for Hopcroft minimization algorithm. + +For internal usage. """ from typing import Dict, List, Set, Tuple @@ -9,11 +10,10 @@ class HopcroftProcessingList: - """ A representation for Hopcroft minimization algorithm - For internal usage - """ + """A representation for Hopcroft minimization algorithm.""" def __init__(self, n_states: int, symbols: Set[Symbol]) -> None: + """Initializes the processing list.""" self._reverse_symbols: Dict[Symbol, int] = {} for i, symbol in enumerate(symbols): self._reverse_symbols[symbol] = i @@ -21,22 +21,22 @@ def __init__(self, n_states: int, symbols: Set[Symbol]) -> None: self._elements: List[Tuple[int, Symbol]] = [] def is_empty(self) -> bool: - """Check if empty""" + """Checks if the list is empty.""" return len(self._elements) == 0 def contains(self, class_name: int, symbol: Symbol) -> bool: - """ Check containment """ + """Checks the containment of the given element in the list.""" i_symbol = self._reverse_symbols[symbol] return self._inclusion[class_name, i_symbol] def insert(self, class_name: int, symbol: Symbol) -> None: - """ Insert new element """ + """Inserts the given element to the list.""" i_symbol = self._reverse_symbols[symbol] self._inclusion[class_name, i_symbol] = True self._elements.append((class_name, symbol)) def pop(self) -> Tuple[int, Symbol]: - """ Pop an element """ + """Pops an element from the list.""" res = self._elements.pop() i_symbol = self._reverse_symbols[res[1]] self._inclusion[res[0], i_symbol] = False diff --git a/pyformlang/finite_automaton/nondeterministic_finite_automaton.py b/pyformlang/finite_automaton/nondeterministic_finite_automaton.py index 574ab21..1cf11b0 100644 --- a/pyformlang/finite_automaton/nondeterministic_finite_automaton.py +++ b/pyformlang/finite_automaton/nondeterministic_finite_automaton.py @@ -1,6 +1,4 @@ -""" -Representation of a nondeterministic finite automaton -""" +"""Representation of a nondeterministic finite automaton.""" from typing import Iterable, Hashable @@ -10,30 +8,27 @@ class NondeterministicFiniteAutomaton(EpsilonNFA): - """ Represents a nondeterministic finite automaton + """Representation of a nondeterministic finite automaton. - This class represents a nondeterministic finite automaton, where epsilon \ + This class represents a nondeterministic finite automaton, where epsilon transition are forbidden. Parameters ---------- - states : set of :class:`~pyformlang.finite_automaton.State`, optional - A finite set of states - input_symbols : set of :class:`~pyformlang.finite_automaton.Symbol`, \ - optional - A finite set of input symbols - transition_function : \ - :class:`~pyformlang.finite_automaton.NondeterministicTransitionFunction`\ - , optional - Takes as arguments a state and an input symbol and returns a state. - start_state : :class:`~pyformlang.finite_automaton.State`, optional - A start state, element of states - final_states : set of :class:`~pyformlang.finite_automaton.State`, optional + states: + A finite set of states. + input_symbols: + A finite set of input symbols. + transition_function: + A function that takes as arguments a state and an input symbol + and returns a set of states. + start_states: + A set of start or initial states. It is a subset of states. + final_states: A set of final or accepting states. It is a subset of states. Examples -------- - >>> nfa = NondeterministicFiniteAutomaton() Creates the NFA. @@ -55,32 +50,28 @@ class NondeterministicFiniteAutomaton(EpsilonNFA): >>> nfa.is_deterministic() False - """ def accepts(self, word: Iterable[Hashable]) -> bool: - """ Checks whether the nfa accepts a given word + """Checks whether the nfa accepts a given word. Parameters ---------- - word : iterable of :class:`~pyformlang.finite_automaton.Symbol` - A sequence of input symbols + word: + A sequence of input symbols. Returns - ---------- - is_accepted : bool - Whether the word is accepted or not + ------- + Whether the word is accepted or not. Examples -------- - >>> nfa = NondeterministicFiniteAutomaton() >>> nfa.add_transitions([(0, "a", 1), (0, "a", 2)]) >>> nfa.add_start_state(0) >>> nfa.add_final_state(1) >>> nfa.accepts(["a"]) True - """ word = [to_symbol(x) for x in word] current_states = self._start_states @@ -90,23 +81,20 @@ def accepts(self, word: Iterable[Hashable]) -> bool: return any(self.is_final_state(x) for x in current_states) def is_deterministic(self) -> bool: - """ Checks whether an automaton is deterministic + """Checks whether an automaton is deterministic. Returns - ---------- - is_deterministic : bool - Whether the automaton is deterministic + ------- + Whether the automaton is deterministic. Examples -------- - >>> nfa = NondeterministicFiniteAutomaton() >>> nfa.add_transitions([(0, "a", 1), (0, "a", 2)]) >>> nfa.add_start_state(0) >>> nfa.add_final_state(1) >>> nfa.is_deterministic() False - """ return len(self._start_states) <= 1 and \ self._transition_function.is_deterministic() @@ -115,26 +103,53 @@ def add_transition(self, s_from: Hashable, symb_by: Hashable, s_to: Hashable) -> int: + """Adds the given transition to the NFA. + + Parameters + ---------- + s_from: + The source state. + symb_by: + The transition symbol. + s_to: + The destination state. + + Returns + ------- + Always 1. + + Raises + ------ + InvalidEpsilonTransitionError + When trying to add an epsilon transition. + """ symb_by = to_symbol(symb_by) if symb_by == Epsilon(): raise InvalidEpsilonTransitionError return super().add_transition(s_from, symb_by, s_to) def copy(self) -> "NondeterministicFiniteAutomaton": - """ Copies the current NFA instance """ + """Copies the current NFA. + + Returns + ------- + The copy of current finite automaton. + """ return self._copy_to(NondeterministicFiniteAutomaton()) @classmethod def from_epsilon_nfa(cls, enfa: EpsilonNFA) \ -> "NondeterministicFiniteAutomaton": - """ Builds nfa equivalent to the given enfa + """Builds NFA equivalent to the given Epsilon NFA. - Returns + Parameters ---------- - dfa : :class:`~pyformlang.finite_automaton. \ - NondeterministicFiniteAutomaton` - A non-deterministic finite automaton equivalent to the current \ - nfa, with no epsilon transition + enfa: + A nondeterministic finite automaton with epsilon transitions. + + Returns + ------- + An equivalent automaton without epsilon transitions. """ nfa = NondeterministicFiniteAutomaton() for state in enfa.start_states: @@ -156,5 +171,7 @@ def from_epsilon_nfa(cls, enfa: EpsilonNFA) \ class InvalidEpsilonTransitionError(Exception): - """Exception raised when an epsilon transition is created in - non-epsilon NFA""" + """An exception signaling of invalid epsilon transition. + + Raised when an epsilon transition is created in non-epsilon NFA. + """ diff --git a/pyformlang/finite_automaton/nondeterministic_transition_function.py b/pyformlang/finite_automaton/nondeterministic_transition_function.py index 4815cbe..fb6a815 100644 --- a/pyformlang/finite_automaton/nondeterministic_transition_function.py +++ b/pyformlang/finite_automaton/nondeterministic_transition_function.py @@ -1,6 +1,4 @@ -""" -A nondeterministic transition function -""" +"""A nondeterministic transition function of finite automaton.""" from typing import Dict, Set, Iterable, Tuple from copy import deepcopy @@ -10,51 +8,51 @@ class NondeterministicTransitionFunction(TransitionFunction): - """ A nondeterministic transition function in a finite automaton. + """A nondeterministic transition function of finite automaton. The difference with a deterministic transition is that the return value is - a set of States + a set of States. + + Attributes + ---------- + _transitions: + The transition function as a dictionary. Examples -------- - >>> transition = NondeterministicTransitionFunction() >>> transition.add_transition(State(0), Symbol("a"), State(1)) Creates a transition function and adds a transition. - """ def __init__(self) -> None: + """Creates an empty nondeterministic transition function.""" self._transitions: Dict[State, Dict[Symbol, Set[State]]] = {} def add_transition(self, s_from: State, symb_by: Symbol, s_to: State) -> int: - """ Adds a new transition to the function + """Adds the given transition to the function. Parameters ---------- - s_from : :class:`~pyformlang.finite_automaton.State` - The source state - symb_by : :class:`~pyformlang.finite_automaton.Symbol` - The transition symbol - s_to : :class:`~pyformlang.finite_automaton.State` - The destination state - + s_from: + The source state. + symb_by: + The transition symbol. + s_to: + The destination state. Returns - -------- - done : int - Always 1 + ------- + Always 1. Examples -------- - >>> transition = NondeterministicTransitionFunction() >>> transition.add_transition(State(0), Symbol("a"), State(1)) - """ if s_from in self._transitions: if symb_by in self._transitions[s_from]: @@ -70,30 +68,26 @@ def remove_transition(self, s_from: State, symb_by: Symbol, s_to: State) -> int: - """ Removes a transition from the function + """Removes the given transition from the function. Parameters ---------- - s_from : :class:`~pyformlang.finite_automaton.State` - The source state - symb_by : :class:`~pyformlang.finite_automaton.Symbol` - The transition symbol - s_to : :class:`~pyformlang.finite_automaton.State` - The destination state - + s_from: + The source state. + symb_by: + The transition symbol. + s_to: + The destination state. Returns -------- - done : int - 1 is the transition was found, 0 otherwise + 1 if the transition was found, 0 otherwise. Examples -------- - >>> transition = NondeterministicTransitionFunction() >>> transition.add_transition(State(0), Symbol("a"), State(1)) >>> transition.remove_transition(State(0), Symbol("a"), State(1)) - """ if s_from in self._transitions and \ symb_by in self._transitions[s_from] and \ @@ -103,21 +97,18 @@ def remove_transition(self, return 0 def get_number_transitions(self) -> int: - """ Gives the number of transitions describe by the function + """Gets the number of transitions described by the function. Returns - ---------- - n_transitions : int - The number of transitions + ------- + The number of transitions described by the function. Examples -------- - >>> transition = NondeterministicTransitionFunction() >>> transition.add_transition(State(0), Symbol("a"), State(1)) >>> transition.get_number_transitions() 1 - """ counter = 0 for transitions in self._transitions.values(): @@ -126,20 +117,18 @@ def get_number_transitions(self) -> int: return counter def __call__(self, s_from: State, symb_by: Symbol) -> Set[State]: - """ Calls the transition function as a real function + """Calls the transition function as a real function. Parameters ---------- - s_from : :class:`~pyformlang.finite_automaton.State` - The source state - symb_by : :class:`~pyformlang.finite_automaton.Symbol` - The transition symbol + s_from: + The source state. + symb_by: + The transition symbol. Returns - ---------- - s_from : set :class:`~pyformlang.finite_automaton.State` - Set of destination states - + ------- + A set of destination states. """ if s_from in self._transitions: if symb_by in self._transitions[s_from]: @@ -148,56 +137,59 @@ def __call__(self, s_from: State, symb_by: Symbol) -> Set[State]: def get_transitions_from(self, s_from: State) \ -> Iterable[Tuple[Symbol, State]]: - """ Gets transitions from the given state """ + """Gets transitions from the given state. + + Parameters + ---------- + s_from: + A state to get transitions from. + + Yields + ------ + Pairs of transition symbol and destination state. + """ if s_from in self._transitions: for symb_by, states_to in self._transitions[s_from].items(): for state_to in states_to: yield symb_by, state_to def get_edges(self) -> Iterable[Tuple[State, Symbol, State]]: - """ Gets the edges + """Gets the edges of graph described by the function. - Returns - ---------- - edges : generator of (:class:`~pyformlang.finite_automaton.State`, \ - :class:`~pyformlang.finite_automaton.Symbol`,\ - :class:`~pyformlang.finite_automaton.State`) - A generator of edges + Yields + ------ + The edges as state and symbol tuples. """ for s_from in self._transitions: for symb_by, s_to in self.get_transitions_from(s_from): yield s_from, symb_by, s_to def to_dict(self) -> Dict[State, Dict[Symbol, Set[State]]]: - """ - Get the dictionary representation of the transition function. The keys - of the dictionary are the source nodes. The items are dictionaries - where the keys are the symbols of the transitions and the items are - the set of target nodes. + """Get the dictionary representation of the transition function. + + The keys of the dictionary are the source nodes. The items are + dictionaries where the keys are the symbols of the transitions and + the items are the set of target nodes. Returns ------- - transition_dict : dict - The transitions as a dictionary. + The transitions as a dictionary. """ return deepcopy(self._transitions) def is_deterministic(self) -> bool: - """ Whether the transition function is deterministic + """Whether the transition function is deterministic. Returns - ---------- - is_deterministic : bool - Whether the function is deterministic + ------- + Whether the function is deterministic. Examples -------- - >>> transition = NondeterministicTransitionFunction() >>> transition.add_transition(State(0), Symbol("a"), State(1)) >>> transition.is_deterministic() True - """ for transitions in self._transitions.values(): for s_to in transitions.values(): diff --git a/pyformlang/finite_automaton/partition.py b/pyformlang/finite_automaton/partition.py index 77be2d6..1595639 100644 --- a/pyformlang/finite_automaton/partition.py +++ b/pyformlang/finite_automaton/partition.py @@ -1,4 +1,5 @@ -"""Class to manage partitions used in Hopcroft minimization algorithm +"""Class to manage partitions used in Hopcroft minimization algorithm. + For internal usage. """ @@ -10,9 +11,10 @@ class Partition: - """Class to manage partitions used in Hopcroft minimization algorithm""" + """Class to manage partitions used in Hopcroft minimization algorithm.""" def __init__(self, n_states: int) -> None: + """Initializes the partition.""" self._class_names: Dict[State, int] = {} # States to class index # Class idx to states self.part: List[DoublyLinkedList] = \ @@ -22,7 +24,7 @@ def __init__(self, n_states: int) -> None: self._counter = 0 # Number of classes def add_class(self, new_class: Iterable[State]) -> None: - """Adds a new class""" + """Adds a new class.""" index = self._counter self._counter += 1 for element in new_class: @@ -31,7 +33,7 @@ def add_class(self, new_class: Iterable[State]) -> None: self._place[element] = node def move_to_new_class(self, elements_to_move: Iterable[State]) -> None: - """Move elements to a new class""" + """Moves elements to a new class.""" for element in elements_to_move: place = self._place[element] class_name = self._class_names[element] @@ -39,7 +41,7 @@ def move_to_new_class(self, elements_to_move: Iterable[State]) -> None: self.add_class(elements_to_move) def get_valid_sets(self, inverse: Iterable[State]) -> List[int]: - """Get the valid sets""" + """Gets the valid sets.""" class_names = [0] * self._counter for element in inverse: class_names[self._class_names[element]] += 1 @@ -47,7 +49,7 @@ def get_valid_sets(self, inverse: Iterable[State]) -> List[int]: if value != 0 and value != len(self.part[i])] def split(self, to_split: int, splitter: Iterable[State]) -> int: - """ Splits """ + """Splits the classes.""" elements_to_move = [] for element in splitter: if self._class_names[element] == to_split: @@ -56,7 +58,7 @@ def split(self, to_split: int, splitter: Iterable[State]) -> int: return self._counter - 1 def get_groups(self) -> List[List[State]]: - """ Get the groups """ + """Gets the groups.""" res = [] for i in range(self._counter): res.append([x.value for x in self.part[i]]) diff --git a/pyformlang/finite_automaton/transition_function.py b/pyformlang/finite_automaton/transition_function.py index 2a283aa..b50dc8f 100644 --- a/pyformlang/finite_automaton/transition_function.py +++ b/pyformlang/finite_automaton/transition_function.py @@ -1,6 +1,4 @@ -""" -General transition function representation -""" +"""A general transition function representation.""" from typing import Dict, Set, Tuple, Iterable, Iterator from abc import abstractmethod @@ -9,14 +7,28 @@ class TransitionFunction(Iterable[Tuple[State, Symbol, State]]): - """ General transition function representation """ + """A general transition function representation.""" @abstractmethod def add_transition(self, s_from: State, symb_by: Symbol, s_to: State) -> int: - """ Adds a new transition to the function """ + """Adds the given transition to the function. + + Parameters + ---------- + s_from: + The source state. + symb_by: + The transition symbol. + s_to: + The destination state. + + Returns + ------- + Always 1. + """ raise NotImplementedError @abstractmethod @@ -24,38 +36,97 @@ def remove_transition(self, s_from: State, symb_by: Symbol, s_to: State) -> int: - """ Removes a transition from the function """ + """Removes the given transition from the function. + + Parameters + ---------- + s_from: + The source state. + symb_by: + The transition symbol. + s_to: + The destination state. + + Returns + -------- + 1 if the transition was found, 0 otherwise. + """ raise NotImplementedError @abstractmethod def get_number_transitions(self) -> int: - """ Gives the number of transitions described by the function """ + """Gets the number of transitions described by the function. + + Returns + ------- + The number of transitions described by the function. + """ raise NotImplementedError def __len__(self) -> int: + """Gets the number of transitions described by the function.""" return self.get_number_transitions() @abstractmethod def __call__(self, s_from: State, symb_by: Symbol) -> Set[State]: - """ - Calls the transition function - as a real function for given state and symbol. + """Calls the transition function as a real function. + + Parameters + ---------- + s_from: + The source state. + symb_by: + The transition symbol. + + Returns + ------- + A set of destination states. """ raise NotImplementedError def __contains__(self, transition: Tuple[State, Symbol, State]) -> bool: - """ Whether the given transition is present in the function """ + """Checks if the given transition is present in the function. + + Parameters + ---------- + transition: + The transition to check containment of. + + Returns + ------- + Whether the given transition is present in the function. + """ s_from, symb_by, s_to = transition return s_to in self(s_from, symb_by) @abstractmethod def get_transitions_from(self, s_from: State) \ -> Iterable[Tuple[Symbol, State]]: - """ Gets transitions from the given state """ + """Gets transitions from the given state. + + Parameters + ---------- + s_from: + A state to get transitions from. + + Yields + ------ + Pairs of transition symbol and destination state. + """ raise NotImplementedError def get_next_states_from(self, s_from: State) -> Set[State]: - """ Gets a set of states that are next to the given one """ + """Gets a set of states that are next to the given one. + + Parameters + ---------- + s_from: + A state to get next states from. + + Returns + ------- + A set of next states defined by the transition function. + """ next_states = set() for _, next_state in self.get_transitions_from(s_from): next_states.add(next_state) @@ -63,18 +134,38 @@ def get_next_states_from(self, s_from: State) -> Set[State]: @abstractmethod def get_edges(self) -> Iterable[Tuple[State, Symbol, State]]: - """ Gets the edges """ + """Gets the edges of graph described by the function. + + Yields + ------ + The edges as state and symbol tuples. + """ raise NotImplementedError def __iter__(self) -> Iterator[Tuple[State, Symbol, State]]: + """Yields the transitions described by the transition function.""" yield from self.get_edges() @abstractmethod def to_dict(self) -> Dict[State, Dict[Symbol, Set[State]]]: - """ Gets the dictionary representation of the transition function """ + """Get the dictionary representation of the transition function. + + The keys of the dictionary are the source nodes. The items are + dictionaries where the keys are the symbols of the transitions and + the items are the set of target nodes. + + Returns + ------- + The transitions as a dictionary. + """ raise NotImplementedError @abstractmethod def is_deterministic(self) -> bool: - """ Whether the transition function is deterministic """ + """Whether the transition function is deterministic. + + Returns + ------- + Whether the function is deterministic. + """ raise NotImplementedError diff --git a/pyformlang/finite_automaton/utils.py b/pyformlang/finite_automaton/utils.py index d488604..1cab074 100644 --- a/pyformlang/finite_automaton/utils.py +++ b/pyformlang/finite_automaton/utils.py @@ -1,4 +1,4 @@ -""" Utility for finite automata """ +"""Utility for finite automata.""" from typing import Dict, List, AbstractSet, Iterable, Optional from numpy import empty @@ -7,17 +7,16 @@ def to_single_state(l_states: Iterable[State]) -> State: - """ Merge a list of states + """Merge a list of states. Parameters ---------- - l_states : list of :class:`~pyformlang.finite_automaton.State` - A list of states + l_states: + A list of states to merge into one. Returns - ---------- - state : :class:`~pyformlang.finite_automaton.State` - The merged state + ------- + The merged state. """ values = [] for state in l_states: @@ -30,14 +29,12 @@ def to_single_state(l_states: Iterable[State]) -> State: class PreviousTransitions: - """ - Previous transitions for deterministic automata - minimization algorithm. - """ + """Previous transitions for the DFA minimization algorithm.""" def __init__(self, states: AbstractSet[State], symbols: AbstractSet[Symbol]) -> None: + """Initializes the transitions of DFA.""" self._to_index_state: Dict[State, int] = {} for i, state in enumerate(states): self._to_index_state[state] = i + 1 @@ -51,7 +48,7 @@ def add(self, next0: Optional[State], symbol: Symbol, state: State) -> None: - """ Internal """ + """Add the given transition to the conversion.""" i_next0 = self._to_index_state[next0] if next0 else 0 i_symbol = self._to_index_symbol[symbol] if self._conversion[i_next0, i_symbol] is None: @@ -60,7 +57,7 @@ def add(self, self._conversion[i_next0, i_symbol].append(state) def get(self, next0: Optional[State], symbol: Symbol) -> List[State]: - """ Internal """ + """Get previous states according to the given transition.""" i_next0 = self._to_index_state[next0] if next0 else 0 i_symbol = self._to_index_symbol[symbol] return self._conversion[i_next0, i_symbol] or [] From 98638b630a7c11bfa75950022c00c5a1ede0caf6 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 03:01:33 +0300 Subject: [PATCH 06/21] update regular_expression docs --- pyformlang/regular_expression/python_regex.py | 14 +- pyformlang/regular_expression/regex.py | 267 ++++++++---------- pyformlang/regular_expression/regex_reader.py | 38 ++- 3 files changed, 150 insertions(+), 169 deletions(-) diff --git a/pyformlang/regular_expression/python_regex.py b/pyformlang/regular_expression/python_regex.py index 0d446eb..66e73d0 100644 --- a/pyformlang/regular_expression/python_regex.py +++ b/pyformlang/regular_expression/python_regex.py @@ -1,6 +1,4 @@ -""" -A class to read Python format regex -""" +"""A class to read Python format regex.""" from typing import List, Tuple, Union, Pattern from re import compile as compile_regex @@ -56,7 +54,7 @@ class PythonRegex(Regex): - """ Represents a regular expression as used in Python. + r"""Represents a regular expression as used in Python. It adds the following features to the basic regex: @@ -70,9 +68,9 @@ class PythonRegex(Regex): Parameters ---------- - python_regex : Union[str, Pattern[str]] - The regex represented as a string or a compiled regex ( - re.compile(...)) + python_regex: + The regex represented as a string or a compiled regex + (re.compile(...)). Raises ------ @@ -95,10 +93,10 @@ class PythonRegex(Regex): True >>> p_regex.accepts(["d"]) False - """ def __init__(self, python_regex: Union[str, Pattern[str]]) -> None: + """Initializes the regex in python format.""" if isinstance(python_regex, str): compile_regex(python_regex) # Check if it is valid else: diff --git a/pyformlang/regular_expression/regex.py b/pyformlang/regular_expression/regex.py index 9fee921..abede98 100644 --- a/pyformlang/regular_expression/regex.py +++ b/pyformlang/regular_expression/regex.py @@ -1,6 +1,4 @@ -""" -Representation of a regular expression -""" +"""Representation of a regular expression.""" from typing import List, Iterable, Tuple, Optional @@ -16,10 +14,10 @@ class Regex(RegexReader): - """ Represents a regular expression + r"""Representation of a regular expression. - Pyformlang implements the operators of textbooks, which deviate slightly \ - from the operators in Python. For a representation closer to Python one, \ + Pyformlang implements the operators of textbooks, which deviate slightly + from the operators in Python. For a representation closer to Python one, please use :class:`~pyformlang.regular_expression.PythonRegex` * The concatenation can be represented either by a space or a dot (.) @@ -27,21 +25,21 @@ class Regex(RegexReader): * The Kleene star is represented by * * The epsilon symbol can either be "epsilon" or $ - It is also possible to use parentheses. All symbols except the space, ., \ - |, +, *, (, ), epsilon and $ can be part of the alphabet. All \ - other common regex operators (such as []) are syntactic sugar that can be \ - reduced to the previous operators. Another main difference is that the \ - alphabet is not reduced to single characters as it is the case in Python. \ - For example, "python" is a single symbol in Pyformlang, whereas it is the \ - concatenation of six symbols in regular Python. + It is also possible to use parentheses. All symbols except the space, ., + |, +, *, (, ), epsilon and $ can be part of the alphabet. All + other common regex operators (such as []) are syntactic sugar that can be + reduced to the previous operators. Another main difference is that the + alphabet is not reduced to single characters as it is the case in Python. + For example, "python" is a single symbol in Pyformlang, whereas it is the + concatenation of six symbols in regular Python. All special characters except epsilon can be escaped with a backslash (\ double backslash \\ in strings). Parameters ---------- - regex : str - The regex represented as a string + regex: + The regex represented as a string. Raises ------ @@ -50,7 +48,6 @@ class Regex(RegexReader): Examples -------- - >>> regex = Regex("abc|d") Check if the symbol "abc" is accepted @@ -84,26 +81,24 @@ class Regex(RegexReader): Give the equivalent finite-state automaton >>> regex_concat.to_epsilon_nfa() - """ def __init__(self, regex: str) -> None: + """Initializes the regex from the given string.""" super().__init__(regex) self.sons: List[Regex] # type: ignore self._counter = 0 self._enfa: Optional[EpsilonNFA] = None def get_number_symbols(self) -> int: - """ Gives the number of symbols in the regex + """Gets the number of symbols in the regex. Returns - ---------- - n_symbols : int - The number of symbols in the regex + ------- + The number of symbols in the regex. Examples -------- - >>> regex = Regex("a|b*") >>> regex.get_number_symbols() 2 @@ -115,52 +110,58 @@ def get_number_symbols(self) -> int: return 1 def get_number_operators(self) -> int: - """ Gives the number of operators in the regex + """Gets the number of operators in the regex. Returns - ---------- - n_operators : int - The number of operators in the regex + ------- + The number of operators in the regex. Examples -------- - >>> regex = Regex("a|b*") >>> regex.get_number_operators() 2 The two operators are "|" and "*". - """ if self.sons: return 1 + sum(son.get_number_operators() for son in self.sons) return 0 def to_minimal_dfa(self) -> DeterministicFiniteAutomaton: - """ Builds minimal dfa from current regex """ + """Builds a minimal DFA from current regex. + + Returns + ------- + The minimal DFA equivalent to the current regex. + """ enfa = self._to_epsilon_nfa_internal() dfa = DeterministicFiniteAutomaton.from_epsilon_nfa(enfa) return dfa.minimize() def to_epsilon_nfa(self) -> EpsilonNFA: - """ Transforms the regular expression into an epsilon NFA + """Transforms the regular expression into an epsilon NFA. Returns - ---------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - An epsilon NFA equivalent to the regex + ------- + An epsilon NFA equivalent to the regex. Examples -------- - >>> regex = Regex("abc|d") >>> regex.to_epsilon_nfa() - """ return self._to_epsilon_nfa_internal().copy() def _to_epsilon_nfa_internal(self) -> EpsilonNFA: - """ Transforms the regular expression into an epsilon NFA """ + """Transforms the regular expression into an epsilon NFA. + + For internal use to prevent protected `enfa` member modification. + + Returns + ------- + An epsilon NFA equivalent to the regex. + """ if self._enfa is None: self._enfa = EpsilonNFA() s_initial = self._set_and_get_initial_state_in_enfa(self._enfa) @@ -182,14 +183,16 @@ def _process_to_enfa(self, enfa: EpsilonNFA, s_from: State, s_to: State) -> None: - """ Internal function to add a regex to a given epsilon NFA + """Internal function to add a regex to a given epsilon NFA. Parameters ---------- - s_from : :class:`~pyformlang.finite_automaton.State` - The source state - s_to : :class:`~pyformlang.finite_automaton.State` - The destination state + enfa: + Epsilon NFA to add the regex to. + s_from: + The source state. + s_to: + The destination state. """ if self.sons: self._process_to_enfa_when_sons(enfa, s_from, s_to) @@ -277,27 +280,25 @@ def _get_next_state_enfa(self) -> State: return s_final def get_tree_str(self, depth: int = 0) -> str: - """ Get a string representation of the tree behind the regex + """Get a string representation of the tree behind the regex. Parameters ---------- - depth: int - The current depth, 0 by default + depth: + The current depth, 0 by default. + Returns ------- - representation: str - The tree representation + The tree representation of the regex. Examples -------- - >>> regex = Regex("abc|d*") >>> print(regex.get_tree_str()) Operator(Union) Symbol(abc) Operator(Kleene Star) Symbol(d) - """ temp = " " * depth + str(self.head) + "\n" for son in self.sons: @@ -305,31 +306,27 @@ def get_tree_str(self, depth: int = 0) -> str: return temp def to_cfg(self, starting_symbol: str = "S") -> CFG: - """ - Turns the regex into a context-free grammar + """Turns the regex into a context-free grammar. Parameters ---------- - starting_symbol : :class:`~pyformlang.cfg.Variable`, optional - The starting symbol + starting_symbol: + The starting symbol of the grammar. Returns ------- - cfg : :class:`~pyformlang.cfg.CFG` - An equivalent context-free grammar + An equivalent context-free grammar. Examples -------- - >>> regex = Regex("(a|b)* c") >>> my_cfg = regex.to_cfg() >>> my_cfg.contains(["c"]) True - """ productions, _ = self._get_production(starting_symbol) cfg_res = CFG(start_symbol=to_variable(starting_symbol), - productions=set(productions)) + productions=set(productions)) return cfg_res def _get_production(self, current_symbol: str, count: int = 0) \ @@ -348,27 +345,26 @@ def _get_production(self, current_symbol: str, count: int = 0) \ return next_productions, count def __repr__(self) -> str: + """Gets the string representation of the regex.""" return self.head.get_str_repr([str(son) for son in self.sons]) def union(self, other: "Regex") -> "Regex": - """ Makes the union with another regex + """Makes the union with another regex. Equivalent to: - >>> regex0 or regex1 + >>> regex0 | regex1 Parameters ---------- - other : :class:`~pyformlang.regular_expression.Regex` - The other regex + other: + The other regex. Returns - ---------- - regex : :class:`~pyformlang.regular_expression.Regex` - The union of the two regex + ------- + The union of the two regexps. Examples -------- - >>> regex0 = Regex("a b") >>> regex1 = Regex("c") >>> regex_union = regex0.union(regex1) @@ -377,9 +373,8 @@ def union(self, other: "Regex") -> "Regex": Or equivalently: - >>> regex_union = regex0 or regex1 + >>> regex_union = regex0 | regex1 >>> regex_union.accepts(["a", "b"]) - """ regex = Regex("") regex.head = Union() @@ -387,21 +382,19 @@ def union(self, other: "Regex") -> "Regex": return regex def __or__(self, other: "Regex") -> "Regex": - """ Makes the union with another regex + """Makes the union with another regex. Parameters ---------- - other : :class:`~pyformlang.regular_expression.Regex` - The other regex + other: + The other regex. Returns - ---------- - regex : :class:`~pyformlang.regular_expression.Regex` - The union of the two regex + ------- + The union of the two regexps. Examples -------- - >>> regex0 = Regex("a b") >>> regex1 = Regex("c") >>> regex_union = regex0.union(regex1) @@ -412,31 +405,29 @@ def __or__(self, other: "Regex") -> "Regex": Or equivalently: - >>> regex_union = regex0 or regex1 + >>> regex_union = regex0 | regex1 >>> regex_union.accepts(["a", "b"]) True """ return self.union(other) def concatenate(self, other: "Regex") -> "Regex": - """ Concatenates a regular expression with an other one + """Concatenates a regular expression with another one. Equivalent to: - >>> regex0 + regex1 + >>> regex0 + regex1 Parameters ---------- - other : :class:`~pyformlang.regular_expression.Regex` - The other regex + other: + The other regex. Returns - ---------- - regex : :class:`~pyformlang.regular_expression.Regex` - The concatenation of the two regex + ------- + The concatenation of the two regexps. Examples -------- - >>> regex0 = Regex("a b") >>> regex1 = Regex("c") >>> regex_union = regex0.concatenate(regex1) @@ -457,21 +448,19 @@ def concatenate(self, other: "Regex") -> "Regex": return regex def __add__(self, other: "Regex") -> "Regex": - """ Concatenates a regular expression with an other one + """Concatenates a regular expression with another one. Parameters ---------- - other : :class:`~pyformlang.regular_expression.Regex` - The other regex + other: + The other regex. Returns - ---------- - regex : :class:`~pyformlang.regular_expression.Regex` - The concatenation of the two regex + ------- + The concatenation of the two regexps. Examples -------- - >>> regex0 = Regex("a b") >>> regex1 = Regex("c") >>> regex_union = regex0.concatenate(regex1) @@ -485,28 +474,24 @@ def __add__(self, other: "Regex") -> "Regex": >>> regex_union = regex0 + regex1 >>> regex_union.accepts(["a", "b", "c"]) True - """ return self.concatenate(other) def kleene_star(self) -> "Regex": - """ Makes the kleene star of the current regex + """Gets the kleene star of the current regex. Returns - ---------- - regex : :class:`~pyformlang.regular_expression.Regex` - The kleene star of the current regex + ------- + The kleene star of the current regex. Examples -------- - >>> regex = Regex("a") >>> regex_kleene = regex.kleene_star() >>> regex_kleene.accepts([]) True >>> regex_kleene.accepts(["a", "a", "a"]) True - """ regex = Regex("") regex.head = KleeneStar() @@ -514,19 +499,19 @@ def kleene_star(self) -> "Regex": return regex def from_string(self, regex_str: str) -> "Regex": - """ Construct a regex from a string. For internal usage. + """Construct a regex from a string. - Equivalent to the constructor of Regex + Equivalent to the constructor of Regex. Parameters ---------- - regex_str : str - The string representation of the regex + regex_str: + The string representation of the regex. Returns ------- - regex : :class:`~pyformlang.regular_expression.Regex` - The regex + regex: + The regex as a string. Examples -------- @@ -535,49 +520,42 @@ def from_string(self, regex_str: str) -> "Regex": , which is equivalent to: >>> Regex("a b c") - """ return Regex(regex_str) def accepts(self, word: Iterable[str]) -> bool: - """ - Check if a word matches (completely) the regex + """Check if a word matches (completely) the regex. Parameters ---------- - word : iterable of str - The word to check + word: + The word to check. Returns ------- - is_accepted : bool - Whether the word is recognized or not + Whether the word is recognized or not. Examples -------- - >>> regex = Regex("abc|d") Check if the symbol "abc" is accepted >>> regex.accepts(["abc"]) True - """ return self._to_epsilon_nfa_internal().accepts(word) @classmethod def from_finite_automaton(cls, automaton: FiniteAutomaton) -> "Regex": - """ Creates a regular expression from given finite automaton + """Creates a regular expression from given finite automaton. Returns - ---------- - regex : :class:`~pyformlang.regular_expression.Regex` - A regular expression equivalent to the current Epsilon NFA + ------- + A regular expression equivalent to the given finite automaton. Examples -------- - >>> enfa = EpsilonNFA() >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ (0, "epsilon", 2)]) @@ -586,7 +564,6 @@ def from_finite_automaton(cls, automaton: FiniteAutomaton) -> "Regex": >>> regex = enfa.to_regex() >>> regex.accepts(["abc"]) True - """ copies = [automaton.copy() for _ in automaton.final_states] final_states = list(automaton.final_states) @@ -605,15 +582,16 @@ def from_finite_automaton(cls, automaton: FiniteAutomaton) -> "Regex": @classmethod def _get_regex_simple(cls, automaton: FiniteAutomaton) -> str: - """ Get the regex of an automaton when it only composed of a start and - a final state + """Gets the regex of the automaton in a simple form. + + Gets the regex of an automaton when it only composed of a start and + a final state. CAUTION: For internal use only! Returns - ---------- - regex : str - A regex representing the automaton + ------- + A regex representing the automaton. """ if not automaton.final_states or not automaton.start_states: return "" @@ -636,19 +614,18 @@ def _get_regex_simple(cls, automaton: FiniteAutomaton) -> str: @classmethod def _get_bi_transitions(cls, automaton: FiniteAutomaton) \ -> Tuple[str, str, str, str]: - """ Internal method to compute the transition in the case of a \ - simple automaton + """Compute the transition in the case of a simple automaton. Returns - start_to_start : str - The transition from the start state to the start state - start_to_end : str - The transition from the start state to the end state - end_to_start : str - The transition from the end state to the start state - end_to_end : str - The transition from the end state to the end state - ---------- + ------- + start_to_start: + The transition from the start state to the start state. + start_to_end: + The transition from the start state to the end state. + end_to_start: + The transition from the end state to the start state. + end_to_end: + The transition from the end state to the end state. """ start = list(automaton.start_states)[0] end = list(automaton.final_states)[0] @@ -674,13 +651,13 @@ def _get_bi_transitions(cls, automaton: FiniteAutomaton) \ @classmethod def _remove_all_basic_states(cls, automaton: FiniteAutomaton) -> None: - """ Remove all states which are not the start state or a final state + """Remove all states which are not the start state or a final state. CAREFUL: This method modifies the current automaton, for internal usage only! The function _create_or_transitions is supposed to be called before - calling this function + calling this function. """ cls._create_or_transitions(automaton) states = automaton.states.copy() @@ -691,7 +668,7 @@ def _remove_all_basic_states(cls, automaton: FiniteAutomaton) -> None: @classmethod def _remove_state(cls, automaton: FiniteAutomaton, state: State) -> None: - """ Removes a given state from the epsilon NFA + """Removes a given state from the epsilon NFA. CAREFUL: This method modifies the current automaton, for internal usage only! @@ -701,9 +678,8 @@ def _remove_state(cls, automaton: FiniteAutomaton, state: State) -> None: Parameters ---------- - state : :class:`~pyformlang.finite_automaton.State` - The state to remove - + state: + The state to remove. """ # First compute all endings out_transitions = {} @@ -737,9 +713,9 @@ def _remove_state(cls, automaton: FiniteAutomaton, state: State) -> None: @classmethod def _create_or_transitions(cls, automaton: FiniteAutomaton) -> None: - """ Creates a OR transition instead of several connections + """Creates a OR transition instead of several connections. - CAREFUL: This method modifies the automaton and is designed for \ + CAREFUL: This method modifies the automaton and is designed for internal use only! """ for state in automaton.states: @@ -770,7 +746,7 @@ def __get_regex_sub(cls, start_to_end: str, end_to_start: str, end_to_end: str) -> str: - """ Combines the transitions in the regex simple function """ + """Combines the transitions in the regex simple function.""" if not start_to_end: return "" temp, part1 = cls.__get_temp(start_to_end, end_to_start, end_to_end) @@ -789,10 +765,7 @@ def __get_temp(cls, start_to_end: str, end_to_start: str, end_to_end: str) -> Tuple[str, str]: - """ - Gets a temp values in the computation - of the simple automaton regex. - """ + """Gets a temp values in the computation of the simple FA regex.""" temp = "epsilon" if (start_to_end != "epsilon" or end_to_end != "epsilon" diff --git a/pyformlang/regular_expression/regex_reader.py b/pyformlang/regular_expression/regex_reader.py index 157847b..e999366 100644 --- a/pyformlang/regular_expression/regex_reader.py +++ b/pyformlang/regular_expression/regex_reader.py @@ -1,6 +1,4 @@ -""" -A class to read regex -""" +"""A class to parse regular expressions.""" from typing import List, Optional from re import sub @@ -16,12 +14,25 @@ class RegexReader: + """A class to parse regular expressions. + + Parses the given regex. + + Attributes + ---------- + head: + A root of the tree representing the regex. + sons: + The child regexps of the current one. + + Parameters + ---------- + regex: + The regex to parse. """ - A class to parse regular expressions - """ - # pylint: disable=too-few-public-methods def __init__(self, regex: str) -> None: + """Parses the given regex.""" self._current_node: Optional[Node] = None self.head: Node = Empty() self.sons: List[RegexReader] = [] @@ -93,7 +104,7 @@ def _compute_precedent_when_not_kleene_nor_union(self) -> None: 0, self._end_current_group) def _compute_precedence(self) -> None: - """ Add parenthesis for the first group in indicate precedence """ + """Adds parenthesis for the first group in indicate precedence.""" self._setup_precedence() if isinstance(self._current_node, KleeneStar): self._add_parenthesis_around_part_of_componants( @@ -112,7 +123,7 @@ def _set_next_end_group_and_node(self) -> None: self._components[self._end_current_group]) def _set_end_first_group_in_components(self, idx_from: int = 0) -> None: - """ Gives the end of the first group """ + """Gives the end of the first group.""" if idx_from >= len(self._components): self._end_current_group = idx_from elif self._components[idx_from] == ")": @@ -174,17 +185,16 @@ def _check_is_valid_single_first_symbol(self, first_symbol: Node) -> None: raise MisformedRegexError(MISFORMED_MESSAGE, self._regex) def from_string(self, regex_str: str) -> "RegexReader": - """ - Read a regex from a string + """Reads a regex from a string. + Parameters ---------- - regex_str : str - A regular expression + regex_str: + A regular expression to read. Returns ------- - parsed_regex : :class:`~pyformlang.regular_expression.RegexReader` - The parsed regex + The parsed regex. """ return RegexReader(regex_str) From 3becdfde95fccecc0f1f100303c1d3b4dd4b0f4a Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 03:20:59 +0300 Subject: [PATCH 07/21] update rsa module docs --- pyformlang/rsa/box.py | 41 ++++++------ pyformlang/rsa/recursive_automaton.py | 96 +++++++++++++-------------- 2 files changed, 64 insertions(+), 73 deletions(-) diff --git a/pyformlang/rsa/box.py b/pyformlang/rsa/box.py index d1cdf55..280bf0f 100644 --- a/pyformlang/rsa/box.py +++ b/pyformlang/rsa/box.py @@ -1,6 +1,4 @@ -""" -Representation of a box for recursive automaton -""" +"""Representation of a box for recursive automaton.""" from typing import Set, Hashable, Any @@ -11,71 +9,70 @@ class Box: - """ Represents a box for recursive automaton - - This class represents a box for recursive automaton + """Representation of a box for recursive automaton. Parameters ---------- - enfa : :class:`~pyformlang.finite_automaton.EpsilonNFA` - A epsilon nfa - nonterminal : :class:`~pyformlang.finite_automaton.Symbol` - A nonterminal for epsilon nfa - + dfa: + The deterministic automaton to describe the box. + nonterminal: + A nonterminal for `dfa`. """ def __init__(self, dfa: DeterministicFiniteAutomaton, nonterminal: Hashable) -> None: + """Initializes the box.""" self._dfa = dfa self._nonterminal = to_symbol(nonterminal) @property def dfa(self) -> DeterministicFiniteAutomaton: - """ Box's dfa """ + """Gets the deterministic automaton of the box.""" return self._dfa @property def nonterminal(self) -> Symbol: - """ Box's nonterminal """ + """Gets the nonterminal of the box.""" return self._nonterminal @property def start_states(self) -> Set[State]: - """ The start states """ + """Gets the start states of the box.""" return self._dfa.start_states @property def final_states(self) -> Set[State]: - """ The final states """ + """Gets the final states of the box.""" return self._dfa.final_states def is_equivalent_to(self, other: "Box") -> bool: - """ Check whether two boxes are equivalent + """Checks whether two boxes are equivalent. Parameters ---------- - other : :class:`~pyformlang.rsa.Box` - A sequence of input symbols + other: + An other box. Returns - ---------- - are_equivalent : bool - Whether the two boxes are equivalent or not + ------- + Whether the two boxes are equivalent or not. """ return self._dfa.is_equivalent_to(other.dfa) \ and self.nonterminal == other.nonterminal def __eq__(self, other: Any) -> bool: + """Checks whether the current box is equal to the given object.""" if not isinstance(other, Box): return False return self.is_equivalent_to(other) def __hash__(self) -> int: + """Gets the hash of the box.""" return hash(self.nonterminal) def to_subgraph_dot(self) -> str: - """Creates a named subgraph representing a box""" + """Creates a named subgraph representing a box.""" graph = self._dfa.to_networkx() strange_nodes = [] nonterminal = str(self.nonterminal) \ diff --git a/pyformlang/rsa/recursive_automaton.py b/pyformlang/rsa/recursive_automaton.py index 4c9edbe..56690c1 100644 --- a/pyformlang/rsa/recursive_automaton.py +++ b/pyformlang/rsa/recursive_automaton.py @@ -1,6 +1,4 @@ -""" -Representation of a recursive automaton -""" +"""Representation of a recursive automaton.""" from typing import Dict, Set, AbstractSet, Optional, Hashable, Any @@ -13,22 +11,20 @@ class RecursiveAutomaton: - """ Represents a recursive automaton - - This class represents a recursive automaton. + """Representation of a recursive automaton. Parameters ---------- - start_box : :class:`~pyformlang.rsa.Box` - Start box - boxes : set of :class:`~pyformlang.rsa.Box` - A finite set of boxes - + start_box: + A start box of the recursive automaton. + boxes: + A finite set of boxes. """ def __init__(self, start_box: Box, boxes: AbstractSet[Box]) -> None: + """Initializes the recursive automaton.""" self._nonterminal_to_box: Dict[Symbol, Box] = {} self._start_nonterminal = start_box.nonterminal if start_box not in boxes: @@ -38,62 +34,58 @@ def __init__(self, @property def nonterminals(self) -> Set[Symbol]: - """ The set of nonterminals """ + """Gets the set of nonterminals of the automaton.""" return set(self._nonterminal_to_box.keys()) @property def boxes(self) -> Set[Box]: - """ The set of boxes """ + """Gets the set of boxes of the automaton.""" return set(self._nonterminal_to_box.values()) @property def start_nonterminal(self) -> Symbol: - """ The start nonterminal """ + """Gets the start nonterminal of the automaton.""" return self._start_nonterminal @property def start_box(self) -> Box: - """ The start box """ + """Gets the start box of the automaton.""" return self._nonterminal_to_box[self.start_nonterminal] def get_box_by_nonterminal(self, nonterminal: Hashable) -> Optional[Box]: - """ - Box by nonterminal + """Gets a box by the given nonterminal. Parameters ---------- - nonterminal: :class:`~pyformlang.finite_automaton.Symbol` | str - the nonterminal of which represents a box + nonterminal: + A nonterminal representing a box. Returns - ----------- - box : :class:`~pyformlang.rsa.Box` | None - box represented by given nonterminal + ------- + The box represented by the given nonterminal. """ - nonterminal = to_symbol(nonterminal) return self._nonterminal_to_box.get(nonterminal, None) def get_number_boxes(self) -> int: - """ Size of set of boxes """ + """Gets the number of boxes in the current automaton.""" return len(self._nonterminal_to_box) @classmethod def from_regex(cls, regex: Regex, start_nonterminal: Hashable) \ -> "RecursiveAutomaton": - """ Create a recursive automaton from regular expression + """Creates a recursive automaton from regular expression. Parameters - ----------- - regex : :class:`~pyformlang.regular_expression.Regex` - The regular expression - start_nonterminal : :class:`~pyformlang.finite_automaton.Symbol` | str - The start nonterminal for the recursive automaton + ---------- + regex: + The regular expression to create automaton from. + start_nonterminal: + The start nonterminal for the recursive automaton. Returns - ----------- - rsa : :class:`~pyformlang.rsa.RecursiveAutomaton` - The new recursive automaton built from regular expression + ------- + The new recursive automaton built from regular expression. """ start_nonterminal = to_symbol(start_nonterminal) box = Box(regex.to_minimal_dfa(), start_nonterminal) @@ -102,21 +94,18 @@ def from_regex(cls, regex: Regex, start_nonterminal: Hashable) \ @classmethod def from_ebnf(cls, text: str, start_nonterminal: Hashable = "S") \ -> "RecursiveAutomaton": - """ Create a recursive automaton from ebnf \ - (ebnf = Extended Backus-Naur Form) + """Creates a recursive automaton from Extended Backus-Naur Form. Parameters ----------- - text : str - The text of transform - start_nonterminal : \ - :class:`~pyformlang.finite_automaton.Symbol` | str, optional - The start nonterminal, S by default + text: + The text of transform. + start_nonterminal: + The start nonterminal. Returns - ----------- - rsa : :class:`~pyformlang.rsa.RecursiveAutomaton` - The new recursive automaton built from context-free grammar + ------- + The new recursive automaton built from context-free grammar. """ start_nonterminal = to_symbol(start_nonterminal) productions: Dict[Hashable, str] = {} @@ -148,29 +137,34 @@ def from_ebnf(cls, text: str, start_nonterminal: Hashable = "S") \ return RecursiveAutomaton(start_box, boxes) def is_equal_to(self, other: "RecursiveAutomaton") -> bool: - """ - Check whether two recursive automata are equals by boxes. + """Check whether two recursive automata are equal by boxes. + Not equivalency in terms of formal languages theory, just mapping boxes Parameters ---------- - other : :class:`~pyformlang.rsa.RecursiveAutomaton` - The input recursive automaton + other: + The other recursive automaton. Returns - ---------- - are_equivalent : bool - Whether the two recursive automata are equals or not + ------- + Whether the two recursive automata are equal or not. """ return self.boxes == other.boxes def __eq__(self, other: Any) -> bool: + """Checks if the current automaton is equal to the given object.""" if not isinstance(other, RecursiveAutomaton): return False return self.is_equal_to(other) def to_dot(self) -> str: - """ Create dot representation of recursive automaton """ + """Creates dot representation of recursive automaton. + + Returns + ------- + The dot representation of current recursive automaton. + """ dot_string = 'digraph "" {' for box in self._nonterminal_to_box.values(): dot_string += f'\n{box.to_subgraph_dot()}' From f940643f3c5261bce4b723e0660662d2436d557e Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 04:06:44 +0300 Subject: [PATCH 08/21] include inherited members with sphinx --- doc/modules/context_free_grammar.rst | 1 + doc/modules/feature_context_free_grammar.rst | 1 + doc/modules/finite_automaton.rst | 1 + doc/modules/fst.rst | 1 + doc/modules/indexed_grammar.rst | 1 + doc/modules/push_down_automata.rst | 1 + doc/modules/regular_expression.rst | 1 + doc/modules/rsa.rst | 1 + 8 files changed, 8 insertions(+) diff --git a/doc/modules/context_free_grammar.rst b/doc/modules/context_free_grammar.rst index 0e5e579..2416ec8 100644 --- a/doc/modules/context_free_grammar.rst +++ b/doc/modules/context_free_grammar.rst @@ -3,3 +3,4 @@ Context-Free Grammar .. automodule:: pyformlang.cfg :members: + :inherited-members: diff --git a/doc/modules/feature_context_free_grammar.rst b/doc/modules/feature_context_free_grammar.rst index 4e805e7..1210592 100644 --- a/doc/modules/feature_context_free_grammar.rst +++ b/doc/modules/feature_context_free_grammar.rst @@ -3,3 +3,4 @@ Feature Context-Free Grammar .. automodule:: pyformlang.fcfg :members: + :inherited-members: diff --git a/doc/modules/finite_automaton.rst b/doc/modules/finite_automaton.rst index e044547..d55166a 100644 --- a/doc/modules/finite_automaton.rst +++ b/doc/modules/finite_automaton.rst @@ -3,3 +3,4 @@ Finite Automaton .. automodule:: pyformlang.finite_automaton :members: + :inherited-members: diff --git a/doc/modules/fst.rst b/doc/modules/fst.rst index 6ecdf67..a8b9c47 100644 --- a/doc/modules/fst.rst +++ b/doc/modules/fst.rst @@ -3,3 +3,4 @@ Finite State Transducer .. automodule:: pyformlang.fst :members: + :inherited-members: diff --git a/doc/modules/indexed_grammar.rst b/doc/modules/indexed_grammar.rst index ef23a17..f9b969e 100644 --- a/doc/modules/indexed_grammar.rst +++ b/doc/modules/indexed_grammar.rst @@ -3,3 +3,4 @@ Indexed Grammar .. automodule:: pyformlang.indexed_grammar :members: + :inherited-members: diff --git a/doc/modules/push_down_automata.rst b/doc/modules/push_down_automata.rst index 2f52b02..b4b1d50 100644 --- a/doc/modules/push_down_automata.rst +++ b/doc/modules/push_down_automata.rst @@ -3,3 +3,4 @@ Push-Down Automata .. automodule:: pyformlang.pda :members: + :inherited-members: diff --git a/doc/modules/regular_expression.rst b/doc/modules/regular_expression.rst index 1c6d27c..b0b13fd 100644 --- a/doc/modules/regular_expression.rst +++ b/doc/modules/regular_expression.rst @@ -3,3 +3,4 @@ Regular Expression .. automodule:: pyformlang.regular_expression :members: + :inherited-members: diff --git a/doc/modules/rsa.rst b/doc/modules/rsa.rst index a959272..9a1ca09 100644 --- a/doc/modules/rsa.rst +++ b/doc/modules/rsa.rst @@ -3,3 +3,4 @@ Recursive State Automaton .. automodule:: pyformlang.rsa :members: + :inherited-members: From fa74e59eab52f5088112c1127ad829e7845b49d1 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 13:50:42 +0300 Subject: [PATCH 09/21] update object module docs --- pyformlang/objects/base_epsilon.py | 10 +- pyformlang/objects/base_terminal.py | 5 +- pyformlang/objects/cfg_objects/cfg_object.py | 10 +- pyformlang/objects/cfg_objects/epsilon.py | 4 +- pyformlang/objects/cfg_objects/production.py | 31 ++-- pyformlang/objects/cfg_objects/terminal.py | 12 +- pyformlang/objects/cfg_objects/utils.py | 6 +- pyformlang/objects/cfg_objects/variable.py | 10 +- .../finite_automaton_objects/epsilon.py | 8 +- .../finite_automaton_object.py | 11 +- .../objects/finite_automaton_objects/state.py | 14 +- .../finite_automaton_objects/symbol.py | 13 +- .../objects/finite_automaton_objects/utils.py | 14 +- pyformlang/objects/formal_object.py | 23 ++- pyformlang/objects/pda_objects/epsilon.py | 4 +- pyformlang/objects/pda_objects/pda_object.py | 11 +- .../objects/pda_objects/stack_symbol.py | 10 +- pyformlang/objects/pda_objects/state.py | 10 +- pyformlang/objects/pda_objects/symbol.py | 10 +- pyformlang/objects/pda_objects/utils.py | 8 +- .../objects/regex_objects/regex_objects.py | 142 +++++++++++------- pyformlang/objects/regex_objects/utils.py | 4 +- 22 files changed, 212 insertions(+), 158 deletions(-) diff --git a/pyformlang/objects/base_epsilon.py b/pyformlang/objects/base_epsilon.py index 17c06b2..3037fd9 100644 --- a/pyformlang/objects/base_epsilon.py +++ b/pyformlang/objects/base_epsilon.py @@ -1,4 +1,4 @@ -""" General epsilon representation """ +"""General epsilon representation.""" from typing import Any @@ -9,26 +9,28 @@ class BaseEpsilon(BaseTerminal): - """ An epsilon transition + """An epsilon transition. Examples -------- - >>> epsilon = Epsilon() - """ def __init__(self) -> None: + """Initializes the epsilon terminal.""" super().__init__("epsilon") def __eq__(self, other: Any) -> bool: + """Check if the epsilon is equal to the given object.""" return isinstance(other, BaseEpsilon) \ or not isinstance(other, FormalObject) and other in EPSILON_SYMBOLS def __hash__(self) -> int: + """Gets the hash of the epsilon symbol.""" return super().__hash__() def __repr__(self) -> str: + """Gets the string representation of the epsilon symbol.""" return "epsilon" def _is_equal_to(self, other: FormalObject) -> bool: diff --git a/pyformlang/objects/base_terminal.py b/pyformlang/objects/base_terminal.py index 1b37802..2b42544 100644 --- a/pyformlang/objects/base_terminal.py +++ b/pyformlang/objects/base_terminal.py @@ -1,4 +1,4 @@ -""" General terminal representation """ +"""General terminal representation.""" from abc import abstractmethod @@ -6,10 +6,11 @@ class BaseTerminal(FormalObject): - """ General terminal representation """ + """General terminal representation.""" @abstractmethod def __repr__(self): + """Gets the string representation of the terminal.""" raise NotImplementedError def _is_equal_to(self, other: FormalObject) -> bool: diff --git a/pyformlang/objects/cfg_objects/cfg_object.py b/pyformlang/objects/cfg_objects/cfg_object.py index e347e1e..a51ab6e 100644 --- a/pyformlang/objects/cfg_objects/cfg_object.py +++ b/pyformlang/objects/cfg_objects/cfg_object.py @@ -1,4 +1,4 @@ -""" An object in a CFG (Variable and Terminal)""" +"""An object in a CFG (Variable and Terminal).""" from abc import abstractmethod @@ -6,15 +6,15 @@ class CFGObject(FormalObject): - """ An object in a CFG + """An object in a CFG. Parameters ----------- - value : any - The value of the object + value: + The value of the object. """ @abstractmethod def to_text(self) -> str: - """ Turns the object into a text format """ + """Turns the object into a text format.""" raise NotImplementedError diff --git a/pyformlang/objects/cfg_objects/epsilon.py b/pyformlang/objects/cfg_objects/epsilon.py index 35eba40..b14c29d 100644 --- a/pyformlang/objects/cfg_objects/epsilon.py +++ b/pyformlang/objects/cfg_objects/epsilon.py @@ -1,8 +1,8 @@ -""" An epsilon terminal """ +"""An epsilon terminal in CFG.""" from .terminal import Terminal from ..base_epsilon import BaseEpsilon class Epsilon(BaseEpsilon, Terminal): - """ An epsilon terminal """ + """An epsilon terminal in CFG.""" diff --git a/pyformlang/objects/cfg_objects/production.py b/pyformlang/objects/cfg_objects/production.py index 5e9bd2e..9ce5a33 100644 --- a/pyformlang/objects/cfg_objects/production.py +++ b/pyformlang/objects/cfg_objects/production.py @@ -1,4 +1,4 @@ -""" A production or rule of a CFG """ +"""A production or rule of a CFG.""" from typing import List, Set, Any @@ -9,14 +9,14 @@ class Production: - """ A production or rule of a CFG + """A production or rule of a CFG. Parameters ---------- - head : :class:`~pyformlang.cfg.Variable` - The head of the production - body : iterable of :class:`~pyformlang.cfg.CFGObject` - The body of the production + head: + The head of the production. + body: + The body of the production. """ __slots__ = ["_body", "_head", "_hash"] @@ -25,6 +25,7 @@ def __init__(self, head: Variable, body: List[CFGObject], filtering: bool = True) -> None: + """Initializes the production.""" if filtering: self._body = [x for x in body if not isinstance(x, Epsilon)] else: @@ -34,48 +35,48 @@ def __init__(self, @property def head(self) -> Variable: - """Gets the head variable""" + """Gets the head variable of the production.""" return self._head @property def body(self) -> List[CFGObject]: - """Gets the body objects""" + """Gets the body objects of the production.""" return self._body @property def variables(self) -> Set[Variable]: - """Gets variables used in the production""" + """Gets variables used in the production.""" return {self.head} | {object for object in self.body if isinstance(object, Variable)} @property def terminals(self) -> Set[Terminal]: - """Gets terminals used in the production""" + """Gets terminals used in the production.""" return {object for object in self.body if isinstance(object, Terminal) and object != Epsilon()} def __eq__(self, other: Any) -> bool: + """Checks if current production is equal to the given object.""" if not isinstance(other, Production): return False return self.head == other.head and self.body == other.body def __hash__(self) -> int: + """Gets the hash of the production.""" if self._hash is None: self._hash = sum(map(hash, self._body)) + hash(self._head) return self._hash def __repr__(self) -> str: + """Gets the string representation of the production.""" return str(self.head) + " -> " + " ".join([str(x) for x in self.body]) def is_normal_form(self) -> bool: - """ - Tells is the production is in Chomsky Normal Form + """Tells if the production is in Chomsky Normal Form. Returns ------- - is_normal_form : bool - If the production is in CNF - + Whether the production is in CNF. """ if len(self._body) == 2: return (isinstance(self._body[0], Variable) and diff --git a/pyformlang/objects/cfg_objects/terminal.py b/pyformlang/objects/cfg_objects/terminal.py index 68f4852..78a6f5f 100644 --- a/pyformlang/objects/cfg_objects/terminal.py +++ b/pyformlang/objects/cfg_objects/terminal.py @@ -1,16 +1,24 @@ -""" A terminal in a CFG """ +"""A terminal in a CFG.""" from .cfg_object import CFGObject from ..base_terminal import BaseTerminal class Terminal(BaseTerminal, CFGObject): - """ A terminal in a CFG """ + """A terminal in a CFG. + + Parameters + ---------- + value: + A value of the terminal. + """ def __repr__(self) -> str: + """Gets the string representation of the terminal.""" return f"Terminal({self})" def to_text(self) -> str: + """Gets a formatted string representing the terminal.""" text = str(self._value) if text and text[0].isupper(): return '"TER:' + text + '"' diff --git a/pyformlang/objects/cfg_objects/utils.py b/pyformlang/objects/cfg_objects/utils.py index 76dbb96..705d68d 100644 --- a/pyformlang/objects/cfg_objects/utils.py +++ b/pyformlang/objects/cfg_objects/utils.py @@ -1,4 +1,4 @@ -""" Utility for cfg object creation """ +"""Utility for CFG object creation.""" from typing import Hashable @@ -8,14 +8,14 @@ def to_variable(given: Hashable) -> Variable: - """ Transformation into a variable """ + """Transforms the given object into a variable.""" if isinstance(given, Variable): return given return Variable(given) def to_terminal(given: Hashable) -> Terminal: - """ Transformation into a terminal """ + """Transforms the given object into a terminal.""" if given == Epsilon(): return Epsilon() if isinstance(given, Terminal): diff --git a/pyformlang/objects/cfg_objects/variable.py b/pyformlang/objects/cfg_objects/variable.py index 85c78f5..665b839 100644 --- a/pyformlang/objects/cfg_objects/variable.py +++ b/pyformlang/objects/cfg_objects/variable.py @@ -1,4 +1,4 @@ -""" A variable in a CFG """ +"""A variable in a CFG.""" from string import ascii_uppercase @@ -7,18 +7,20 @@ class Variable(CFGObject): - """ An variable in a CFG + """An variable in a CFG. Parameters ----------- - value : any - The value of the variable + value: + The value of the variable. """ def __repr__(self) -> str: + """Gets the string representation of the variable.""" return f"Variable({self})" def to_text(self) -> str: + """Gets a formatted string representing the variable.""" text = str(self._value) if text and text[0] not in ascii_uppercase: return '"VAR:' + text + '"' diff --git a/pyformlang/objects/finite_automaton_objects/epsilon.py b/pyformlang/objects/finite_automaton_objects/epsilon.py index 1c4f887..d9df367 100644 --- a/pyformlang/objects/finite_automaton_objects/epsilon.py +++ b/pyformlang/objects/finite_automaton_objects/epsilon.py @@ -1,17 +1,13 @@ -""" -Represents an epsilon transition -""" +"""An epsilon symbol in finite automaton.""" from .symbol import Symbol from ..base_epsilon import BaseEpsilon class Epsilon(BaseEpsilon, Symbol): - """ An epsilon transition + """An epsilon symbol in finite automaton. Examples -------- - >>> epsilon = Epsilon() - """ diff --git a/pyformlang/objects/finite_automaton_objects/finite_automaton_object.py b/pyformlang/objects/finite_automaton_objects/finite_automaton_object.py index 4666636..c1c195c 100644 --- a/pyformlang/objects/finite_automaton_objects/finite_automaton_object.py +++ b/pyformlang/objects/finite_automaton_objects/finite_automaton_object.py @@ -1,6 +1,4 @@ -""" -Represents an object of a finite state automaton -""" +"""Representation of an object of a finite state automaton.""" from abc import abstractmethod @@ -8,14 +6,15 @@ class FiniteAutomatonObject(FormalObject): - """ Represents an object in a finite state automaton + """Representation of an object of a finite state automaton. Parameters ---------- - value: any - The value of the object + value: + The value of the object. """ @abstractmethod def __repr__(self) -> str: + """Gets a string representation of the object.""" raise NotImplementedError diff --git a/pyformlang/objects/finite_automaton_objects/state.py b/pyformlang/objects/finite_automaton_objects/state.py index 41244c3..1299a17 100644 --- a/pyformlang/objects/finite_automaton_objects/state.py +++ b/pyformlang/objects/finite_automaton_objects/state.py @@ -1,28 +1,26 @@ -""" -Representation of a state in a finite state automaton -""" +"""A state in a finite automaton.""" from .finite_automaton_object import FiniteAutomatonObject from ..formal_object import FormalObject class State(FiniteAutomatonObject): - """ A state in a finite automaton + """A state in a finite automaton. Parameters ---------- - value : any - The value of the state + value: + The value of the state. Examples - ---------- + -------- >>> from pyformlang.finite_automaton import State >>> State("A") A - """ def __repr__(self) -> str: + """Gets a string representation of the state.""" return f"State({self})" def _is_equal_to(self, other: FormalObject) -> bool: diff --git a/pyformlang/objects/finite_automaton_objects/symbol.py b/pyformlang/objects/finite_automaton_objects/symbol.py index 090692d..be890e7 100644 --- a/pyformlang/objects/finite_automaton_objects/symbol.py +++ b/pyformlang/objects/finite_automaton_objects/symbol.py @@ -1,25 +1,24 @@ -""" -This module describe a symbol in a finite automaton. -""" +"""A symbol in a finite automaton.""" from .finite_automaton_object import FiniteAutomatonObject from ..base_terminal import BaseTerminal class Symbol(BaseTerminal, FiniteAutomatonObject): - """ A symbol in a finite automaton + """A symbol in a finite automaton. Parameters ---------- - value : any - The value of the symbol + value: + The value of the symbol. Examples - ---------- + -------- >>> from pyformlang.finite_automaton import Symbol >>> Symbol("A") A """ def __repr__(self) -> str: + """Gets a string representation of the symbol.""" return f"Symbol({self})" diff --git a/pyformlang/objects/finite_automaton_objects/utils.py b/pyformlang/objects/finite_automaton_objects/utils.py index 462596e..9a966c8 100644 --- a/pyformlang/objects/finite_automaton_objects/utils.py +++ b/pyformlang/objects/finite_automaton_objects/utils.py @@ -1,4 +1,4 @@ -""" Utility for finite automaton object creation """ +"""Utility for finite automaton object creation.""" from typing import Hashable @@ -8,12 +8,12 @@ def to_state(given: Hashable) -> State: - """ Transforms the input into a state + """Transforms the given object into a state. Parameters ---------- - given : any - What we want to transform + given: + What we want to transform. """ if isinstance(given, State): return given @@ -21,12 +21,12 @@ def to_state(given: Hashable) -> State: def to_symbol(given: Hashable) -> Symbol: - """ Transforms the input into a symbol + """Transforms the given object into a symbol. Parameters ---------- - given : any - What we want to transform + given: + What we want to transform. """ if given == Epsilon(): return Epsilon() diff --git a/pyformlang/objects/formal_object.py b/pyformlang/objects/formal_object.py index 31d88f5..82b426d 100644 --- a/pyformlang/objects/formal_object.py +++ b/pyformlang/objects/formal_object.py @@ -1,45 +1,56 @@ -""" General object representation """ +"""General formal object representation.""" from typing import Hashable, Optional, Any from abc import abstractmethod class FormalObject: - """ General object representation """ + """General formal object representation. + + Parameters + ---------- + value: + A value of the object. + """ def __init__(self, value: Hashable) -> None: + """Initializes the object.""" self._value = value self._hash = None self.index: Optional[int] = None @property def value(self) -> Hashable: - """ Gets the value of the object + """Gets the value of the object. Returns - --------- - value : any - The value of the object + -------- + The value of the object. """ return self._value def __eq__(self, other: Any) -> bool: + """Checks if current formal object is equal to the given object.""" if not isinstance(other, FormalObject): return self.value == other return self._is_equal_to(other) and other._is_equal_to(self) def __hash__(self) -> int: + """Gets the hash current formal object.""" if self._hash is None: self._hash = hash(self._value) return self._hash def __str__(self) -> str: + """Gets the string value of the object.""" return str(self._value) @abstractmethod def __repr__(self) -> str: + """Gets the string representation of the object.""" raise NotImplementedError @abstractmethod def _is_equal_to(self, other: "FormalObject") -> bool: + """Checks equality of two formal objects.""" raise NotImplementedError diff --git a/pyformlang/objects/pda_objects/epsilon.py b/pyformlang/objects/pda_objects/epsilon.py index 710d922..b55216c 100644 --- a/pyformlang/objects/pda_objects/epsilon.py +++ b/pyformlang/objects/pda_objects/epsilon.py @@ -1,8 +1,8 @@ -""" An epsilon symbol """ +"""An epsilon symbol in push-down automaton.""" from .stack_symbol import StackSymbol from ..base_epsilon import BaseEpsilon class Epsilon(BaseEpsilon, StackSymbol): - """ An epsilon symbol """ + """An epsilon symbol in push-down automaton.""" diff --git a/pyformlang/objects/pda_objects/pda_object.py b/pyformlang/objects/pda_objects/pda_object.py index ce70176..7db0ad6 100644 --- a/pyformlang/objects/pda_objects/pda_object.py +++ b/pyformlang/objects/pda_objects/pda_object.py @@ -1,4 +1,4 @@ -""" Basic PDA object representation """ +"""Basic PDA object representation.""" from abc import abstractmethod @@ -6,8 +6,15 @@ class PDAObject(FormalObject): - """ Basic PDA object representation """ + """Basic PDA object representation. + + Parameters + ---------- + value: + The value of the object. + """ @abstractmethod def __repr__(self) -> str: + """Gets a string representation of the object.""" raise NotImplementedError diff --git a/pyformlang/objects/pda_objects/stack_symbol.py b/pyformlang/objects/pda_objects/stack_symbol.py index c22f29e..b0a9fbc 100644 --- a/pyformlang/objects/pda_objects/stack_symbol.py +++ b/pyformlang/objects/pda_objects/stack_symbol.py @@ -1,20 +1,20 @@ -""" A StackSymbol in a pushdown automaton """ +"""A stack symbol in a push-down automaton.""" from .symbol import Symbol from ..formal_object import FormalObject class StackSymbol(Symbol): - """ A StackSymbol in a pushdown automaton + """A stack symbol in a push-down automaton. Parameters ---------- - value : any - The value of the state - + value: + The value of the stack symbol. """ def __repr__(self) -> str: + """Gets a string representation of the stack symbol.""" return f"StackSymbol({self})" def _is_equal_to(self, other: FormalObject) -> bool: diff --git a/pyformlang/objects/pda_objects/state.py b/pyformlang/objects/pda_objects/state.py index 8b0a385..815a2e6 100644 --- a/pyformlang/objects/pda_objects/state.py +++ b/pyformlang/objects/pda_objects/state.py @@ -1,20 +1,20 @@ -""" A State in a pushdown automaton """ +"""A State in a push-down automaton.""" from .pda_object import PDAObject from ..formal_object import FormalObject class State(PDAObject): - """ A State in a pushdown automaton + """A State in a push-down automaton. Parameters ---------- - value : any - The value of the state - + value: + The value of the state. """ def __repr__(self) -> str: + """Gets a string representation of the state.""" return f"State({self})" def _is_equal_to(self, other: FormalObject) -> bool: diff --git a/pyformlang/objects/pda_objects/symbol.py b/pyformlang/objects/pda_objects/symbol.py index 78843de..484459b 100644 --- a/pyformlang/objects/pda_objects/symbol.py +++ b/pyformlang/objects/pda_objects/symbol.py @@ -1,18 +1,18 @@ -""" A Symbol in a pushdown automaton """ +"""A Symbol in a push-down automaton.""" from .pda_object import PDAObject from ..base_terminal import BaseTerminal class Symbol(BaseTerminal, PDAObject): - """ A Symbol in a pushdown automaton + """A Symbol in a push-down automaton. Parameters ---------- - value : any - The value of the state - + value: + The value of the symbol. """ def __repr__(self) -> str: + """Gets a string representation of the symbol.""" return f"Symbol({self})" diff --git a/pyformlang/objects/pda_objects/utils.py b/pyformlang/objects/pda_objects/utils.py index 6fc0f37..85797d0 100644 --- a/pyformlang/objects/pda_objects/utils.py +++ b/pyformlang/objects/pda_objects/utils.py @@ -1,4 +1,4 @@ -""" Utility for pda object creation """ +"""Utility for PDA object creation.""" from typing import Hashable @@ -9,14 +9,14 @@ def to_state(given: Hashable) -> State: - """ Convert to a state """ + """Converts the given object to a state.""" if isinstance(given, State): return given return State(given) def to_symbol(given: Hashable) -> Symbol: - """ Convert to a symbol """ + """Converts the given object to a symbol.""" if given == Epsilon(): return Epsilon() if isinstance(given, Symbol): @@ -25,7 +25,7 @@ def to_symbol(given: Hashable) -> Symbol: def to_stack_symbol(given: Hashable) -> StackSymbol: - """ Convert to a stack symbol """ + """Converts the given object to a stack symbol.""" if given == Epsilon(): return Epsilon() if isinstance(given, StackSymbol): diff --git a/pyformlang/objects/regex_objects/regex_objects.py b/pyformlang/objects/regex_objects/regex_objects.py index 9522e05..9a1f12c 100644 --- a/pyformlang/objects/regex_objects/regex_objects.py +++ b/pyformlang/objects/regex_objects/regex_objects.py @@ -1,6 +1,4 @@ -""" -Representation of some objects used in regex. -""" +"""Representation of some objects used in regex.""" from typing import List, Iterable from abc import abstractmethod @@ -10,144 +8,167 @@ class Node: - """ Represents a node in the tree representation of a regex + """Represents a node in the tree representation of a regex. Parameters ---------- - value : str - The value of the node + value: + The value of the node. """ def __init__(self, value: str) -> None: + """Initializes the node.""" self._value = value @property def value(self) -> str: - """ Give the value of the node + """Gets the value of the node. Returns - ---------- - value : str - The value of the node + ------- + The value of the node. """ return self._value + @abstractmethod + def __repr__(self) -> str: + """Gets a string representation of the node.""" + raise NotImplementedError + @abstractmethod def get_str_repr(self, sons_repr: Iterable[str]) -> str: - """ - The string representation of the node + """Gets the string representation of the node with given sons. Parameters ---------- - sons_repr : iterable of str - The sons representations + sons_repr: + The sons representations. Returns ------- - repr : str - The representation of this node - + The representation of this node. """ raise NotImplementedError @abstractmethod def get_cfg_rules(self, current_symbol: str, sons: Iterable[str]) \ -> List[Production]: - """ Gets the rules for a context-free grammar to represent the \ - operator""" + """Gets CFG productions representing the current node. + + Parameters + ---------- + current_symbol: + A head of the productions. + sons: + The son representations. + + Returns + ------- + The productions representing the node. + """ raise NotImplementedError class Operator(Node): - """ Represents an operator + """Represents an operator. Parameters ---------- - value : str - The value of the operator + value: + The value of the operator. """ def __repr__(self) -> str: + """Gets a string representation of the operator node.""" return "Operator(" + str(self._value) + ")" def get_str_repr(self, sons_repr: Iterable[str]) -> str: - """ Get the string representation """ + """Get the string representation of the operator with sons.""" raise NotImplementedError def get_cfg_rules(self, current_symbol: str, sons: Iterable[str]) \ -> List[Production]: - """ Gets the rules for a context-free grammar to represent the \ - operator""" + """Gets CFG productions representing the operator.""" raise NotImplementedError class Symbol(Node): - """ Represents a symbol + """Represents a symbol. Parameters ---------- - value : str - The value of the symbol + value: + The value of the symbol. """ + def __repr__(self) -> str: + """Gets a string representation of the symbol node.""" + return "Symbol(" + str(self._value) + ")" + def get_str_repr(self, sons_repr: Iterable[str]) -> str: + """Gets a string representation of the symbol with given sons.""" return str(self.value) def get_cfg_rules(self, current_symbol: str, sons: Iterable[str]) \ -> List[Production]: - """ Gets the rules for a context-free grammar to represent the \ - operator""" + """Gets CFG productions representing the symbol.""" return [Production( to_variable(current_symbol), [to_terminal(self.value)])] - def __repr__(self) -> str: - return "Symbol(" + str(self._value) + ")" - class Concatenation(Operator): - """ Represents a concatenation - """ + """Represents a concatenation.""" + + def __init__(self) -> None: + """Initializes the concatenation operator.""" + super().__init__("Concatenation") def get_str_repr(self, sons_repr: Iterable[str]) -> str: + """Gets a string representation of the operator with given sons.""" return "(" + ".".join(sons_repr) + ")" def get_cfg_rules(self, current_symbol: str, sons: Iterable[str]) \ -> List[Production]: + """Gets CFG productions representing the operator.""" return [Production( to_variable(current_symbol), [to_variable(son) for son in sons])] - def __init__(self) -> None: - super().__init__("Concatenation") - class Union(Operator): - """ Represents a union - """ + """Represents a union.""" + + def __init__(self) -> None: + """Initializes the union operator.""" + super().__init__("Union") def get_str_repr(self, sons_repr: Iterable[str]) -> str: + """Gets a string representation of the operator with given sons.""" return "(" + "|".join(sons_repr) + ")" def get_cfg_rules(self, current_symbol: str, sons: Iterable[str]) \ -> List[Production]: + """Gets CFG productions representing the operator.""" return [Production( to_variable(current_symbol), [to_variable(son)]) for son in sons] - def __init__(self) -> None: - super().__init__("Union") - class KleeneStar(Operator): - """ Represents an epsilon symbol - """ + """Represents a kleene star operator.""" + + def __init__(self) -> None: + """Initializes the kleene star operator.""" + super().__init__("Kleene Star") def get_str_repr(self, sons_repr: Iterable[str]) -> str: + """Gets a string representation of the operator with given sons.""" return "(" + ".".join(sons_repr) + ")*" def get_cfg_rules(self, current_symbol: str, sons: Iterable[str]) \ -> List[Production]: + """Gets CFG productions representing the operator.""" return [ Production( to_variable(current_symbol), []), @@ -159,40 +180,49 @@ def get_cfg_rules(self, current_symbol: str, sons: Iterable[str]) \ [to_variable(son) for son in sons]) ] - def __init__(self) -> None: - super().__init__("Kleene Star") - class Epsilon(Symbol): - """ Represents an epsilon symbol - """ + """Represents an epsilon symbol.""" + + def __init__(self) -> None: + """Initializes the epsilon symbol.""" + super().__init__("Epsilon") def get_str_repr(self, sons_repr: Iterable[str]) -> str: + """Gets a string representation of the symbol with given sons.""" return "$" def get_cfg_rules(self, current_symbol: str, sons: Iterable[str]) \ -> List[Production]: + """Gets CFG productions representing the symbol.""" return [Production(to_variable(current_symbol), [])] - def __init__(self) -> None: - super().__init__("Epsilon") - class Empty(Symbol): - """ Represents an empty symbol - """ + """Represents an empty symbol.""" def __init__(self) -> None: + """Initializes the empty symbol.""" super().__init__("Empty") def get_cfg_rules(self, current_symbol: str, sons: Iterable[str]) \ -> List[Production]: + """Gets CFG productions representing the symbol.""" return [] class MisformedRegexError(Exception): - """ Error for misformed regex """ + """Error for misformed regex.""" def __init__(self, message: str, regex: str) -> None: + """Initializes the misformed regex exception. + + Parameters + ---------- + message: + A message to show. + regex: + A regex that caused the error. + """ super().__init__(message + " Regex: " + regex) self._regex = regex diff --git a/pyformlang/objects/regex_objects/utils.py b/pyformlang/objects/regex_objects/utils.py index 5c061f9..f8a2ea1 100644 --- a/pyformlang/objects/regex_objects/utils.py +++ b/pyformlang/objects/regex_objects/utils.py @@ -1,4 +1,4 @@ -""" Utility for regex object creation """ +"""Utility for regex object creation.""" from .regex_objects import Symbol, Node, \ Empty, Concatenation, Union, KleeneStar, Epsilon @@ -17,7 +17,7 @@ def to_node(value: str) -> Node: - """ Transforms a given value into a node """ + """Transforms a given value into a node.""" if not value: res = Empty() elif value in CONCATENATION_SYMBOLS: From 3531c20a8aa4b044e3910af75472b24b02d49059 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 15:28:51 +0300 Subject: [PATCH 10/21] update cfg module docs --- pyformlang/cfg/cfg.py | 291 +++++++++--------- pyformlang/cfg/cfg_variable_converter.py | 15 +- pyformlang/cfg/cyk_table.py | 37 ++- pyformlang/cfg/formal_grammar.py | 142 ++++++--- pyformlang/cfg/llone_parser.py | 45 +-- pyformlang/cfg/parse_tree.py | 47 ++- pyformlang/cfg/recursive_decent_parser.py | 79 ++--- pyformlang/cfg/set_queue.py | 10 +- pyformlang/cfg/utils.py | 10 +- .../deterministic_transition_function.py | 2 + 10 files changed, 360 insertions(+), 318 deletions(-) diff --git a/pyformlang/cfg/cfg.py b/pyformlang/cfg/cfg.py index 1b5762e..3ac4076 100644 --- a/pyformlang/cfg/cfg.py +++ b/pyformlang/cfg/cfg.py @@ -1,4 +1,4 @@ -""" A context free grammar """ +"""A context-free grammar representation.""" from string import ascii_uppercase from copy import deepcopy @@ -25,18 +25,18 @@ class CFG(FormalGrammar): - """ A class representing a context free grammar + """A class representing a context-free grammar. Parameters ---------- - variables : set of :class:`~pyformlang.cfg.Variable`, optional - The variables of the CFG - terminals : set of :class:`~pyformlang.cfg.Terminal`, optional - The terminals of the CFG - start_symbol : :class:`~pyformlang.cfg.Variable`, optional - The start symbol - productions : set of :class:`~pyformlang.cfg.Production`, optional - The productions or rules of the CFG + variables: + The variables of the CFG. + terminals: + The terminals of the CFG. + start_symbol: + The start symbol, element of variables. + productions: + The productions or rules of the CFG. """ # pylint: disable=too-many-instance-attributes @@ -46,6 +46,7 @@ def __init__(self, terminals: AbstractSet[Hashable] = None, start_symbol: Hashable = None, productions: Iterable[Production] = None) -> None: + """Initializes the grammar.""" super().__init__() self._variables = {to_variable(x) for x in variables or set()} self._terminals = {to_terminal(x) for x in terminals or set()} @@ -61,28 +62,26 @@ def __init__(self, self._added_impacts: Set[CFGObject] = set() def get_generating_symbols(self) -> Set[CFGObject]: - """ Gives the objects which are generating in the CFG + """Gets the objects which are generating in the CFG. Returns - ---------- - generating_symbols : set of :class:`~pyformlang.cfg.CFGObject` - The generating symbols of the CFG + ------- + The generating symbols of the CFG. """ return self._get_generating_or_nullable(False) def get_nullable_symbols(self) -> Set[CFGObject]: - """ Gives the objects which are nullable in the CFG + """Gets the objects which are nullable in the CFG. Returns - ---------- - nullable_symbols : set of :class:`~pyformlang.cfg.CFGObject` - The nullable symbols of the CFG + ------- + The nullable symbols of the CFG. """ return self._get_generating_or_nullable(True) def _get_generating_or_nullable(self, nullable: bool = False) \ -> Set[CFGObject]: - """ Merge of nullable and generating """ + """Merge of nullable and generating.""" to_process: List[CFGObject] = [Epsilon()] g_symbols: Set[CFGObject] = {Epsilon()} @@ -136,12 +135,11 @@ def _set_impacts_and_remaining_lists(self) -> None: (head, index_impact)) def generate_epsilon(self) -> bool: - """ Whether the grammar generates epsilon or not + """Checks if the grammar generates epsilon. Returns - ---------- - generate_epsilon : bool - Whether epsilon is generated or not by the CFG + ------- + Whether epsilon is generated by the CFG. """ generate_epsilon: Set[CFGObject] = {Epsilon()} to_process: List[CFGObject] = [Epsilon()] @@ -172,12 +170,11 @@ def generate_epsilon(self) -> bool: return False def get_reachable_symbols(self) -> Set[CFGObject]: - """ Gives the objects which are reachable in the CFG + """Gets the objects which are reachable in the CFG. Returns - ---------- - reachable_symbols : set of :class:`~pyformlang.cfg.CFGObject` - The reachable symbols of the CFG + ------- + The reachable symbols of the CFG. """ if not self.start_symbol: return set() @@ -199,12 +196,11 @@ def get_reachable_symbols(self) -> Set[CFGObject]: return r_symbols def remove_useless_symbols(self) -> "CFG": - """ Removes useless symbols in a CFG + """Removes useless symbols in a CFG. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - The CFG without useless symbols + ------- + The CFG without useless symbols. """ generating = self.get_generating_symbols() productions = [x for x in self._productions @@ -221,12 +217,11 @@ def remove_useless_symbols(self) -> "CFG": return CFG(new_var, new_ter, self._start_symbol, productions) def remove_epsilon(self) -> "CFG": - """ Removes the epsilon of a cfg + """Removes the epsilon from the CFG. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - The CFG without epsilons + ------- + The CFG without epsilons. """ new_productions = [] nullables = self.get_nullable_symbols() @@ -239,12 +234,11 @@ def remove_epsilon(self) -> "CFG": new_productions) def get_unit_pairs(self) -> Set[Tuple[Variable, Variable]]: - """ Finds all the unit pairs + """Finds all the unit pairs in the CFG. Returns - ---------- - unit_pairs : set of tuple of :class:`~pyformlang.cfg.Variable` - The unit pairs + ------- + The unit pairs. """ unit_pairs = set() for variable in self._variables: @@ -264,12 +258,11 @@ def get_unit_pairs(self) -> Set[Tuple[Variable, Variable]]: return unit_pairs def eliminate_unit_productions(self) -> "CFG": - """ Eliminate all the unit production in the CFG + """Eliminates all the unit production in the CFG. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - A new CFG equivalent without unit productions + ------- + A new CFG equivalent without unit productions. """ unit_pairs = self.get_unit_pairs() productions = [x @@ -287,7 +280,7 @@ def eliminate_unit_productions(self) -> "CFG": productions) def _get_productions_with_only_single_terminals(self) -> List[Production]: - """ Remove the terminals involved in a body of length more than 1 """ + """Removes the terminals involved in a body of length more than 1.""" term_to_var = {} new_productions = [] for terminal in self._terminals: @@ -324,7 +317,7 @@ def _get_next_free_variable(self, idx: int, prefix: str) \ def _decompose_productions(self, productions: Iterable[Production]) \ -> List[Production]: - """ Decompose productions """ + """Decomposes productions.""" idx = 0 new_productions = [] done = {} @@ -354,21 +347,19 @@ def _decompose_productions(self, productions: Iterable[Production]) \ return new_productions def to_normal_form(self) -> "CFG": - """ Gets the Chomsky Normal Form of a CFG + """Gets the Chomsky Normal Form of a CFG. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - A new CFG equivalent in the CNF form + ------- + An equivalent grammar in the CNF form. Warnings - --------- - As described in Hopcroft's textbook, a normal form does not generate \ - the epsilon word. So, the grammar generated by this function is \ - equivalent to the original grammar except if this grammar generates \ - the epsilon word. In that case, the language of the generated grammar \ + -------- + As described in Hopcroft's textbook, a normal form does not generate + the epsilon word. So, the grammar generated by this function is + equivalent to the original grammar except if this grammar generates + the epsilon word. In that case, the language of the generated grammar contains the same word as before, except the epsilon word. - """ nullables = self.get_nullable_symbols() unit_pairs = self.get_unit_pairs() @@ -394,18 +385,16 @@ def to_normal_form(self) -> "CFG": productions=set(new_productions)) def substitute(self, substitution: Dict[Terminal, "CFG"]) -> "CFG": - """ Substitutes CFG to terminals in the current CFG + """Substitutes CFG to terminals in the current CFG. Parameters - ----------- - substitution : dict of :class:`~pyformlang.cfg.Terminal` to \ - :class:`~pyformlang.cfg.CFG` - A substitution + ---------- + substitution: + A substitution to make. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - A new CFG recognizing the substitution + ------- + A new CFG recognizing the substitution. """ idx = 0 new_variables_d = {} @@ -453,20 +442,19 @@ def substitute(self, substitution: Dict[Terminal, "CFG"]) -> "CFG": set(productions)) def union(self, other: "CFG") -> "CFG": - """ Makes the union of two CFGs + """Makes the union of two CFGs. Equivalent to: - >> cfg0 or cfg1 + >> cfg0 | cfg1 Parameters ---------- - other : :class:`~pyformlang.cfg.CFG` - The other CFG to unite with + other: + The other CFG to unite with. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - The CFG resulting of the union of the two CFGs + ------- + The CFG resulting of the union of the two CFGs. """ start_temp = Variable("#STARTUNION#") temp_0 = Terminal("#0UNION#") @@ -481,35 +469,33 @@ def union(self, other: "CFG") -> "CFG": temp_1: other}) def __or__(self, other: "CFG") -> "CFG": - """ Makes the union of two CFGs + """Makes the union of two CFGs. Parameters ---------- - other : :class:`~pyformlang.cfg.CFG` - The other CFG to unite with + other: + The other CFG to unite with. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - The CFG resulting of the union of the two CFGs + ------- + The CFG resulting of the union of the two CFGs. """ return self.union(other) def concatenate(self, other: "CFG") -> "CFG": - """ Makes the concatenation of two CFGs + """Makes the concatenation of two CFGs. Equivalent to: - >> cfg0 + cfg1 + >> cfg0 + cfg1 Parameters ---------- - other : :class:`~pyformlang.cfg.CFG` - The other CFG to concatenate with + other: + The other CFG to concatenate with. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - The CFG resulting of the concatenation of the two CFGs + ------- + The CFG resulting of the concatenation of the two CFGs. """ start_temp = Variable("#STARTCONC#") temp_0 = Terminal("#0CONC#") @@ -523,27 +509,25 @@ def concatenate(self, other: "CFG") -> "CFG": temp_1: other}) def __add__(self, other: "CFG") -> "CFG": - """ Makes the concatenation of two CFGs + """Makes the concatenation of two CFGs. Parameters ---------- - other : :class:`~pyformlang.cfg.CFG` - The other CFG to concatenate with + other: + The other CFG to concatenate with. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - The CFG resulting of the concatenation of the two CFGs + ------- + The CFG resulting of the concatenation of the two CFGs. """ return self.concatenate(other) def get_closure(self) -> "CFG": - """ Gets the closure of the CFG (*) + """Gets the closure of the CFG (*). Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - The closure of the current CFG + ------- + The closure of the current CFG. """ start_temp = Variable("#STARTCLOS#") temp_1 = Terminal("#1CLOS#") @@ -557,12 +541,11 @@ def get_closure(self) -> "CFG": return cfg_temp.substitute({temp_1: self}) def get_positive_closure(self) -> "CFG": - """ Gets the positive closure of the CFG (+) + """Gets the positive closure of the CFG (+). Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - The positive closure of the current CFG + ------- + The positive closure of the current CFG. """ start_temp = Variable("#STARTPOSCLOS#") var_temp = Variable("#VARPOSCLOS#") @@ -578,15 +561,14 @@ def get_positive_closure(self) -> "CFG": return cfg_temp.substitute({temp_1: self}) def reverse(self) -> "CFG": - """ Reverse the current CFG + """Reverses the current CFG. Equivalent to: >> ~cfg Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - Reverse the current CFG + ------- + The reverse of current grammar. """ productions = [] for production in self._productions: @@ -598,44 +580,42 @@ def reverse(self) -> "CFG": productions) def __invert__(self) -> "CFG": - """ Reverse the current CFG + """Reverses the current CFG. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - Reverse the current CFG + ------- + The reverse of current grammar. """ return self.reverse() def is_empty(self) -> bool: - """ Says whether the CFG is empty or not + """Checks whether the CFG is empty or not. Returns - ---------- - is_empty : bool - Whether the CFG is empty or not + ------- + Whether the CFG is empty or not. - TODO - ---------- - Can be optimized + Todo + ---- + Can be optimized. """ return self._start_symbol not in self.get_generating_symbols() def __bool__(self) -> bool: + """Checks whether the CFG is empty or not.""" return not self.is_empty() def contains(self, word: Iterable[Hashable]) -> bool: - """ Gives the membership of a word to the grammar + """Checks if the given word is in the grammar. Parameters ---------- - word : iterable of :class:`~pyformlang.cfg.Terminal` - The word to check + word: + The word to check. Returns - ---------- - contains : bool - Whether word if in the CFG or not + ------- + Whether the word is in the CFG or not. """ # Remove epsilons word = [to_terminal(x) for x in word if x != Epsilon()] @@ -643,48 +623,44 @@ def contains(self, word: Iterable[Hashable]) -> bool: return cyk_table.generate_word() def __contains__(self, word: Iterable[Hashable]) -> bool: + """Checks if the given word is in the grammar.""" return self.contains(word) def get_cnf_parse_tree(self, word: Iterable[Hashable]) -> ParseTree: - """ - Get a parse tree of the CNF of this grammar + """Gets a parse tree of the CNF of this grammar. Parameters ---------- - word : iterable of :class:`~pyformlang.cfg.Terminal` - The word to look for + word: + The word to look for. Returns ------- - derivation : :class:`~pyformlang.cfg.ParseTree` - The parse tree + The parse tree of the given word in the CNF. + Raises + ------ + DerivationDoesNotExistError + If the word cannot be derived. """ word = [to_terminal(x) for x in word if x != Epsilon()] cyk_table = CYKTable(self, word) return cyk_table.get_parse_tree() def intersection(self, other: DeterministicFiniteAutomaton) -> "CFG": - """ Gives the intersection of the current CFG with an other object + """Gets the intersection of the CFG with the given automaton. Equivalent to: - >> cfg and regex + >> cfg & dfa Parameters ---------- - other : any - The other object + other: + The automaton to intersect with. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - A CFG representing the intersection with the other object - - Raises - ---------- - NotImplementedError - When trying to intersect with something else than a regex or a - finite automaton + ------- + A CFG representing the intersection. """ if self.is_empty() or other.is_empty(): return CFG() @@ -787,30 +763,33 @@ def _get_all_bodies(production: Production, for state_q in states] def __and__(self, other: DeterministicFiniteAutomaton) -> "CFG": - """ Gives the intersection of the current CFG with an other object + """Gets the intersection of the CFG with the given automaton. + + Equivalent to: + >> cfg & dfa Parameters ---------- - other : any - The other object + other: + The automaton to intersect with. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - A CFG representing the intersection with the other object - - Raises - ---------- + ------- + A CFG representing the intersection. """ return self.intersection(other) def get_words(self, max_length: int = -1) -> Iterable[List[Terminal]]: - """ Get the words generated by the CFG + """Gets the words generated by the CFG. Parameters ---------- - max_length : int - The maximum length of the words to return + max_length: + The maximum length of the words to return. + + Yields + ------ + Words contained in the grammar. """ nullables = self.get_nullable_symbols() if self.start_symbol in nullables: @@ -872,12 +851,11 @@ def get_words(self, max_length: int = -1) -> Iterable[List[Terminal]]: return def is_finite(self) -> bool: - """ Tests if the grammar is finite or not + """Checks if the grammar is finite. Returns - ---------- - is_finite : bool - Whether the grammar is finite or not + ------- + Whether the grammar is finite or not. """ normal = self.to_normal_form() di_graph = DiGraph() @@ -893,7 +871,12 @@ def is_finite(self) -> bool: return False def copy(self) -> "CFG": - """ Copies the Context Free Grammar """ + """Copies the current Context Free Grammar. + + Returns + ------- + A copy of current Context Free Grammar. + """ return CFG._copy_from(self) @classmethod diff --git a/pyformlang/cfg/cfg_variable_converter.py b/pyformlang/cfg/cfg_variable_converter.py index bd8dd10..7386e3d 100644 --- a/pyformlang/cfg/cfg_variable_converter.py +++ b/pyformlang/cfg/cfg_variable_converter.py @@ -1,4 +1,4 @@ -"""A CFG Variable Converter""" +"""A converter into CFG variables.""" from typing import Dict, List, AbstractSet, Tuple, Optional, Hashable @@ -7,11 +7,12 @@ class CFGVariableConverter: - """A CFG Variable Converter""" + """A converter into CFG variables.""" def __init__(self, states: AbstractSet[FormalObject], stack_symbols: AbstractSet[FormalObject]) -> None: + """Initializes the converter.""" self._counter = 0 self._inverse_states_d: Dict[FormalObject, int] = {} self._counter_state = 0 @@ -31,7 +32,7 @@ def __init__(self, range(len(states))] def _get_state_index(self, state: FormalObject) -> int: - """Get the state index""" + """Gets the state index.""" if state.index is None: if state not in self._inverse_states_d: self._inverse_states_d[state] = self._counter_state @@ -40,7 +41,7 @@ def _get_state_index(self, state: FormalObject) -> int: return state.index def _get_symbol_index(self, symbol: FormalObject) -> int: - """Get the symbol index""" + """Gets the symbol index.""" if symbol.index is None: if symbol not in self._inverse_stack_symbol_d: self._inverse_stack_symbol_d[symbol] = self._counter_symbol @@ -52,7 +53,7 @@ def to_cfg_combined_variable(self, state0: FormalObject, stack_symbol: FormalObject, state1: FormalObject) -> Variable: - """ Conversion used in the to_pda method """ + """Conversion used in the PDA to CFG transformation.""" i_stack_symbol, i_state0, i_state1 = self._get_indexes( stack_symbol, state0, state1) prev = self._conversions[i_state0][i_stack_symbol][i_state1] @@ -78,7 +79,7 @@ def set_valid(self, state0: FormalObject, stack_symbol: FormalObject, state1: FormalObject) -> None: - """Set valid""" + """Set valid.""" i_stack_symbol, i_state0, i_state1 = self._get_indexes( stack_symbol, state0, state1) prev = self._conversions[i_state0][i_stack_symbol][i_state1] @@ -88,7 +89,7 @@ def is_valid_and_get(self, state0: FormalObject, stack_symbol: FormalObject, state1: FormalObject) -> Optional[Variable]: - """Check if valid and get""" + """Check if valid and get.""" i_state0 = self._get_state_index(state0) i_stack_symbol = self._get_symbol_index(stack_symbol) i_state1 = self._get_state_index(state1) diff --git a/pyformlang/cfg/cyk_table.py b/pyformlang/cfg/cyk_table.py index f0f4bc6..b4396fb 100644 --- a/pyformlang/cfg/cyk_table.py +++ b/pyformlang/cfg/cyk_table.py @@ -1,6 +1,4 @@ -""" -Representation of a CYK table -""" +"""Representation of a CYK parsing table.""" from typing import Dict, List, Set, Iterable, Tuple, Any @@ -13,17 +11,18 @@ class CYKTable: - """ - A CYK table + """Representation of a CYK parsing table. Parameters ---------- - cfg : A context-free grammar - word : iterable of Terminals - The word from which we construct the CYK table + grammar: + A grammar to create CYK table from. + word: + A word from which we construct the CYK table. """ def __init__(self, grammar: FormalGrammar, word: List[Terminal]) -> None: + """Initializes the CYK table.""" self._normal_form: FormalGrammar = grammar.to_normal_form() self._generate_epsilon: bool = grammar.generate_epsilon() self._word: List[Terminal] = word @@ -83,12 +82,11 @@ def _initialize_cyk_table(self) -> None: (start_window, start_window + window_size)] = set() def generate_word(self) -> bool: - """ - Checks is the word is generated + """Checks is the word is generated by the grammar. + Returns ------- - is_generated : bool - + Whether the grammar contains the word. """ if not self._word: return self._generate_epsilon @@ -103,12 +101,16 @@ def _generates_all_terminals(self) -> bool: return generate_all_terminals def get_parse_tree(self) -> ParseTree: - """ - Give the parse tree associated with this CYK Table + """Gets the parse tree associated with this CYK Table. Returns ------- - parse_tree : :class:`~pyformlang.cfg.ParseTree` + A parsing tree of the word in the CNF. + + Raises + ------ + DerivationDoesNotExistError + If the word cannot be derived. """ if not self._normal_form.start_symbol or not self.generate_word(): raise DerivationDoesNotExistError @@ -122,12 +124,13 @@ def get_parse_tree(self) -> ParseTree: class CYKNode(ParseTree): - """A node in the CYK table""" + """A node in the CYK table.""" def __init__(self, value: CFGObject, left_son: "CYKNode" = None, right_son: "CYKNode" = None) -> None: + """Initializes the node.""" super().__init__(value) self.value = value self.left_son = left_son @@ -138,9 +141,11 @@ def __init__(self, self.sons.append(right_son) def __eq__(self, other: Any) -> bool: + """Checks if the node is equal to the given object.""" if isinstance(other, CYKNode): return self.value == other.value return self.value == other def __hash__(self) -> int: + """Gets the hash of the node.""" return hash(self.value) diff --git a/pyformlang/cfg/formal_grammar.py b/pyformlang/cfg/formal_grammar.py index 75300d8..88d467e 100644 --- a/pyformlang/cfg/formal_grammar.py +++ b/pyformlang/cfg/formal_grammar.py @@ -1,4 +1,4 @@ -""" Basic grammar representation """ +"""A general grammar representation.""" from typing import Set, AbstractSet, Iterable, Optional, Hashable, TypeVar, Type from abc import abstractmethod @@ -10,7 +10,19 @@ class FormalGrammar: - """ Basic grammar representation """ + """A general grammar representation. + + Attributes + ---------- + _variables: + A finite set of variables of the grammar. + _terminals: + A finite set of terminals of the grammar. + _start_symbol: + The start symbol of the grammar, element of `_variables`. + _productions: + A finite set of productions or rules of the grammar. + """ @abstractmethod def __init__(self, @@ -18,6 +30,7 @@ def __init__(self, terminals: AbstractSet[Hashable] = None, start_symbol: Hashable = None, productions: Iterable[Production] = None) -> None: + """Initializes the grammar.""" self._variables: Set[Variable] self._terminals: Set[Terminal] self._start_symbol: Optional[Variable] @@ -25,70 +38,84 @@ def __init__(self, @property def variables(self) -> Set[Variable]: - """ Gives the variables + """Gets the variables of the grammar. Returns - ---------- - variables : set of :class:`~pyformlang.cfg.Variable` - The variables of the CFG + ------- + The variables of the grammar. """ return self._variables @property def terminals(self) -> Set[Terminal]: - """ Gives the terminals + """Gets the terminals of the grammar. Returns - ---------- - terminals : set of :class:`~pyformlang.cfg.Terminal` - The terminals of the CFG + ------- + The terminals of the grammar. """ return self._terminals @property def productions(self) -> Set[Production]: - """ Gives the productions + """Gets the productions of the grammar. Returns - ---------- - productions : set of :class:`~pyformlang.cfg.Production` - The productions of the CFG + ------- + The productions of the grammar. """ return self._productions @property def start_symbol(self) -> Optional[Variable]: - """ Gives the start symbol + """Gets the start symbol of the grammar. Returns - ---------- - start_variable : :class:`~pyformlang.cfg.Variable` - The start symbol of the CFG + ------- + The start symbol of the grammar. """ return self._start_symbol def add_production(self, production: Production) -> None: - """ Adds the given production to the grammar """ + """Adds the given production to the grammar. + + Parameters + ---------- + production: + The production to add. + """ self.variables.update(production.variables) self.terminals.update(production.terminals) self.productions.add(production) def add_start_symbol(self, symbol: Hashable) -> None: - """ Adds the start symbol to the grammar """ + """Adds the start symbol to the grammar. + + Parameters + ---------- + symbol: + The start symbol to set for the grammar. + """ symbol = to_variable(symbol) self.variables.add(symbol) self._start_symbol = symbol def remove_start_symbol(self) -> None: - """ Removes the start symbol from the grammar """ + """Removes the start symbol from the grammar.""" self._start_symbol = None @abstractmethod def copy(self: GrammarT) -> GrammarT: - """ Copies the grammar """ + """Copies the grammar. + + Returns + ------- + A copy of current grammar. + """ raise NotImplementedError def __copy__(self: GrammarT) -> GrammarT: + """Copies the grammar.""" return self.copy() @classmethod @@ -100,34 +127,42 @@ def _copy_from(cls: Type[GrammarT], other: GrammarT) -> GrammarT: @abstractmethod def generate_epsilon(self) -> bool: - """ Whether the grammar generates epsilon or not """ + """Checks if the grammar generates epsilon. + + Returns + ------- + Whether epsilon is generated by the grammar. + """ raise NotImplementedError @abstractmethod def to_normal_form(self) -> "FormalGrammar": - """ Gets Chomsky normal form of the grammar """ + """Gets the Chomsky Normal Form of current grammar. + + Returns + ------- + An equivalent grammar in the CNF. + """ raise NotImplementedError def is_normal_form(self) -> bool: - """ - Whether the current grammar is in Chomsky Normal Form + """Checks if the current grammar is in Chomsky Normal Form. Returns ------- - is_normal_form : bool - If the current grammar is in CNF + Whether the current grammar is in CNF. """ return all( production.is_normal_form() for production in self._productions) def to_text(self) -> str: - """ - Turns the grammar into its string representation. This might lose some\ - type information and the start_symbol. + """Turns the grammar into its string representation. + + This might lose some type information and the start_symbol. + Returns ------- - text : str - The grammar as a string. + The grammar as a string. """ res = [] for production in self._productions: @@ -141,8 +176,8 @@ def from_text( text: str, start_symbol: Optional[Hashable] = Variable("S")) \ -> GrammarT: - """ - Read a context free grammar from a text. + """Read a grammar from the given text. + The text contains one rule per line. The structure of a production is: head -> body1 | body2 | ... | bodyn @@ -159,15 +194,14 @@ def from_text( Parameters ---------- - text : str - The text of transform - start_symbol : str, optional - The start symbol, S by default + text: + The text of transform. + start_symbol: + The start symbol of the new grammar. Returns ------- - cfg : :class:`~pyformlang.cfg.CFG` - A context free grammar. + A grammar obtained from the given text. """ variables = set() productions = set() @@ -184,6 +218,19 @@ def _read_text(cls, productions: Set[Production], terminals: Set[Terminal], variables: Set[Variable]) -> None: + """Reads the given text and updates the given grammar properties. + + Parameters + ---------- + text: + The text to read. + productions: + Grammar productions to update from the text. + terminals: + Grammar terminals to update from the text. + variables: + Grammar variables to update from the text. + """ for line in text.splitlines(): line = line.strip() if not line: @@ -197,4 +244,17 @@ def _read_line(cls, productions: Set[Production], terminals: Set[Terminal], variables: Set[Variable]) -> None: + """Reads the given line and updates the given grammar properties. + + Parameters + ---------- + line: + The line to read. + productions: + Grammar productions to update from the given line. + terminals: + Grammar terminals to update from the given line. + variables: + Grammar variables to update from the given line. + """ raise NotImplementedError diff --git a/pyformlang/cfg/llone_parser.py b/pyformlang/cfg/llone_parser.py index 7a5f111..599ba86 100644 --- a/pyformlang/cfg/llone_parser.py +++ b/pyformlang/cfg/llone_parser.py @@ -1,4 +1,4 @@ -""" LL(1) Parser """ +"""The LL(1) Parser of CFG.""" from typing import Dict, List, Set, Iterable, Tuple, Hashable @@ -15,20 +15,20 @@ class LLOneParser: - """ - A LL(1) parser + """The LL(1) Parser of CFG. Parameters ---------- - cfg : :class:`~pyformlang.cfg.CFG` - A context-free Grammar + cfg: + A Context-Free Grammar to parse. """ def __init__(self, cfg: CFG) -> None: + """Initializes the parser.""" self._cfg = cfg def get_first_set(self) -> ParserSet: - """ Used in LL(1) """ + """Gets a first set used in LL(1).""" # Algorithm from: # https://www.geeksforgeeks.org/first-set-in-syntax-analysis/ triggers = self._get_triggers() @@ -96,7 +96,7 @@ def _get_triggers(self) -> Triggers: return triggers def get_follow_set(self) -> ParserSet: - """ Get follow set """ + """Gets a follow set.""" first_set = self.get_first_set() triggers = self._get_triggers_follow_set(first_set) follow_set, to_process = self._initialize_follow_set(first_set) @@ -151,9 +151,14 @@ def _get_triggers_follow_set(self, return follow_set def get_llone_parsing_table(self) -> ParsingTable: - """ Get the LL(1) parsing table + """Gets the LL(1) parsing table. + From: - https://www.slideshare.net/MahbuburRahman273/ll1-parser-in-compilers + https://www.slideshare.net/MahbuburRahman273/ll1-parser-in-compilers + + Returns + ------- + The parsing table as a dictionary. """ first_set = self.get_first_set() follow_set = self.get_follow_set() @@ -188,12 +193,11 @@ def get_llone_parsing_table(self) -> ParsingTable: return llone_parsing_table def is_llone_parsable(self) -> bool: - """ - Checks whether the grammar can be parse with the LL(1) parser. + """Checks if the grammar can be parsed with the LL(1) parser. Returns ------- - is_parsable : bool + Whether the grammar is parsable. """ parsing_table = self.get_llone_parsing_table() for variable in parsing_table.values(): @@ -203,24 +207,21 @@ def is_llone_parsable(self) -> bool: return True def get_llone_parse_tree(self, word: Iterable[Hashable]) -> ParseTree: - """ - Get LL(1) parse Tree + """Gets the LL(1) parse tree. Parameters ---------- - word : list - The word to parse + word: + The word to parse. Returns ------- - parse_tree : :class:`~pyformlang.cfg.ParseTree` - The parse tree + The parse tree of given word in the CFG. Raises - -------- - NotParsableException - When the word cannot be parsed - + ------ + NotParsableError + When the word cannot be parsed. """ if not self._cfg.start_symbol: raise NotParsableError diff --git a/pyformlang/cfg/parse_tree.py b/pyformlang/cfg/parse_tree.py index 67c536c..124503b 100644 --- a/pyformlang/cfg/parse_tree.py +++ b/pyformlang/cfg/parse_tree.py @@ -1,4 +1,4 @@ -""" A parse Tree """ +"""A parse tree of the grammar.""" from typing import List @@ -9,24 +9,31 @@ class ParseTree: - """ A parse tree """ + """A parse tree of the grammar. + + Attributes + ---------- + value: + A value of the root of the tree. + sons: + The child trees of current parse tree. + """ def __init__(self, value: CFGObject) -> None: + """Initializes the parse tree.""" self.value = value self.sons: List[ParseTree] = [] def __repr__(self) -> str: + """Gets the string representation of the parse tree.""" return "ParseTree(" + str(self.value) + ", " + str(self.sons) + ")" def get_leftmost_derivation(self) -> List[List[CFGObject]]: - """ - Get the leftmost derivation + """Gets the leftmost derivation of the tree. Returns ------- - derivation : list of list of :class:`~pyformlang.cfg.CFGObject` - The derivation - + The leftmost derivation. """ if len(self.sons) == 0 and isinstance(self.value, Variable): return [[self.value], []] @@ -49,14 +56,11 @@ def get_leftmost_derivation(self) -> List[List[CFGObject]]: return res def get_rightmost_derivation(self) -> List[List[CFGObject]]: - """ - Get the leftmost derivation + """Gets the rightmost derivation of the tree. Returns ------- - derivation : list of list of :class:`~pyformlang.cfg.CFGObject` - The derivation - + The rightmost derivation. """ if len(self.sons) == 0 and isinstance(self.value, Variable): return [[self.value], []] @@ -76,14 +80,11 @@ def get_rightmost_derivation(self) -> List[List[CFGObject]]: return res def to_networkx(self) -> DiGraph: - """ - Transforms the tree into a Networkx Directed Graph + """Transforms the tree into a Networkx Directed Graph. Returns ------- - tree : networkx.Digraph - The tree in Networkx format. - + The tree in Networkx format. """ tree = DiGraph() tree.add_node("ROOT", label=self.value.value) @@ -102,21 +103,19 @@ def to_networkx(self) -> DiGraph: return tree def write_as_dot(self, filename: str) -> None: - """ - Write the parse tree in dot format into a file + """Writes the parse tree in dot format into a file. Parameters ---------- - filename : str - The filename where to write the dot file - + filename: + The filename where to write the dot file. """ write_dot(self.to_networkx(), filename) class DerivationDoesNotExistError(Exception): - """Exception raised when the word cannot be derived""" + """Exception raised when the word cannot be derived.""" class NotParsableError(Exception): - """When the grammar cannot be parsed (parser not powerful enough)""" + """Raised when the grammar cannot be parsed (parser not powerful enough).""" diff --git a/pyformlang/cfg/recursive_decent_parser.py b/pyformlang/cfg/recursive_decent_parser.py index 3dfc2a5..36b7a7a 100644 --- a/pyformlang/cfg/recursive_decent_parser.py +++ b/pyformlang/cfg/recursive_decent_parser.py @@ -1,6 +1,4 @@ -""" -A recursive decent parser. -""" +"""A recursive decent parser of CFG.""" from typing import List, Iterable, Tuple, Optional, Hashable @@ -25,42 +23,38 @@ def _get_index_to_extend(current_expansion: Expansion, left: bool) \ class RecursiveDecentParser: - """ - A recursive Top-Down parser - - Parameters - ---------- - cfg : :class:`~pyformlang.cfg.CFG` - A context-free Grammar + """A recursive Top-Down parser of CFG. + Parameters + ---------- + cfg: + A Context-Free Grammar to parse. """ def __init__(self, cfg: CFG) -> None: + """Initializes the parser.""" self._cfg = cfg def get_parse_tree(self, word: Iterable[Hashable], left: bool = True) \ -> ParseTree: - """ - Get a parse tree for a given word - - Parameters - ---------- - word : list - The word to parse - left - If we do the recursive from the left or the right(left by \ - default) - - Returns - ------- - parse_tree : :class:`~pyformlang.cfg.ParseTree` - The parse tree - - Raises - -------- - NotParsableException - When the word cannot be parsed + """Gets a parse tree of the given word. + + Parameters + ---------- + word: + The word to parse. + left: + If we do the recursive from the left or the right + (left by default). + + Returns + ------- + The parse tree of the given word. + Raises + ------ + NotParsableError + When the word cannot be parsed. """ if not self._cfg.start_symbol: raise NotParsableError @@ -116,31 +110,26 @@ def _get_parse_tree_sub(self, return False def is_parsable(self, word: Iterable[Hashable], left: bool = True) -> bool: - """ - Whether a word is parsable or not + """Whether the given word is parsable or not. Parameters ---------- - word : list - The word to parse - left - If we do the recursive from the left or the right(left by \ - default) + word: + The word to parse. + left: + If we do the recursive from the left or the right + (left by default). Returns ------- - is_parsable : bool - If the word is parsable + Whether the word is parsable. Raises - -------- - NotParsableException - When the word cannot be parsed + ------ RecursionError - If the recursion goes too deep. This error occurs because some \ - the algorithm is not guaranteed to terminate with left/right \ + If the recursion goes too deep. This error occurs because some + the algorithm is not guaranteed to terminate with left/right recursive grammars. - """ try: self.get_parse_tree(word, left) diff --git a/pyformlang/cfg/set_queue.py b/pyformlang/cfg/set_queue.py index b46f37b..f60bc68 100644 --- a/pyformlang/cfg/set_queue.py +++ b/pyformlang/cfg/set_queue.py @@ -1,26 +1,28 @@ -""" A queue with non duplicate elements""" +"""A queue with non duplicate elements.""" from typing import Any class SetQueue: - """ A queue with non duplicate elements""" + """A queue with non duplicate elements.""" def __init__(self) -> None: + """Initializes the queue.""" self._to_process = [] self._processing = set() def append(self, value: Any) -> None: - """ Append an element """ + """Appends an element to the queue.""" if value not in self._processing: self._to_process.append(value) self._processing.add(value) def pop(self) -> Any: - """ Pop an element """ + """Pops an element from the queue.""" popped = self._to_process.pop() self._processing.remove(popped) return popped def __bool__(self) -> bool: + """Checks if the queue is empty.""" return bool(self._to_process) diff --git a/pyformlang/cfg/utils.py b/pyformlang/cfg/utils.py index 590ac39..7db97ec 100644 --- a/pyformlang/cfg/utils.py +++ b/pyformlang/cfg/utils.py @@ -1,4 +1,4 @@ -""" Internal Usage only """ +"""Internal Context-Free Grammar utility.""" from typing import Dict, List, Iterable, AbstractSet @@ -6,7 +6,7 @@ def is_special_text(text: str) -> bool: - """ Check if the input is given an explicit type """ + """Checks if the input is given an explicit type.""" return len(text) > 5 and \ (text[0:5] == '"VAR:' or text[0:5] == '"TER:') and \ text[-1] == '"' @@ -15,7 +15,7 @@ def is_special_text(text: str) -> bool: def remove_nullable_production_sub(body: List[CFGObject], nullables: AbstractSet[CFGObject]) \ -> List[List[CFGObject]]: - """ Recursive sub function to remove nullable objects """ + """Recursive sub function to remove nullable objects.""" if not body: return [[]] all_next = remove_nullable_production_sub(body[1:], nullables) @@ -31,7 +31,7 @@ def remove_nullable_production_sub(body: List[CFGObject], def remove_nullable_production(production: Production, nullables: AbstractSet[CFGObject]) \ -> List[Production]: - """ Get all combinations of productions rules after removing nullable """ + """Gets all combinations of productions rules after removing nullable.""" next_prod_l = remove_nullable_production_sub(production.body, nullables) res = [Production(production.head, prod_l) @@ -42,7 +42,7 @@ def remove_nullable_production(production: Production, def get_productions_d(productions: Iterable[Production]) \ -> Dict[Variable, List[Production]]: - """ Get productions as a dictionary """ + """Gets productions as a dictionary.""" productions_d: Dict[Variable, List[Production]] = {} for production in productions: production_head = productions_d.setdefault(production.head, []) diff --git a/pyformlang/finite_automaton/deterministic_transition_function.py b/pyformlang/finite_automaton/deterministic_transition_function.py index 9f50617..8127667 100644 --- a/pyformlang/finite_automaton/deterministic_transition_function.py +++ b/pyformlang/finite_automaton/deterministic_transition_function.py @@ -45,6 +45,8 @@ def add_transition(self, Raises ------ + InvalidEpsilonTransitionError + When trying to add an epsilon transition. DuplicateTransitionError If the transition already exists. From 039cfe63287cfe3393e0f8ba32652624859a6beb Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 20:14:13 +0300 Subject: [PATCH 11/21] update fcfg module docs --- pyformlang/fcfg/fcfg.py | 65 ++++---- pyformlang/fcfg/feature_production.py | 24 +-- pyformlang/fcfg/feature_structure.py | 154 +++++++++---------- pyformlang/fcfg/state.py | 26 ++-- pyformlang/objects/cfg_objects/production.py | 2 + 5 files changed, 139 insertions(+), 132 deletions(-) diff --git a/pyformlang/fcfg/fcfg.py b/pyformlang/fcfg/fcfg.py index 28bd50b..fd81156 100644 --- a/pyformlang/fcfg/fcfg.py +++ b/pyformlang/fcfg/fcfg.py @@ -1,4 +1,4 @@ -"""Feature Context-Free Grammar""" +"""A Feature Context-Free Grammar.""" from typing import List, Set, Tuple, AbstractSet, Iterable, Optional, Hashable from string import ascii_uppercase @@ -16,22 +16,22 @@ class FCFG(CFG): - """ A class representing a feature context-free grammar + """A class representing a feature context-free grammar. Parameters ---------- - variables : set of :class:`~pyformlang.cfg.Variable`, optional - The variables of the FCFG - terminals : set of :class:`~pyformlang.cfg.Terminal`, optional - The terminals of the FCFG - start_symbol : :class:`~pyformlang.cfg.Variable`, optional - The start symbol - productions : set of :class:`~pyformlang.fcfg.FeatureProduction`, optional - The feature productions or rules of the FCFG + variables: + The variables of the FCFG. + terminals: + The terminals of the FCFG. + start_symbol: + The start symbol of the FCFG, element of `variables`. + productions: + The productions or rules of the FCFG. + Use `FeatureProduction` class to add features. Examples -------- - Creation of a FCFG from a textual description. >>> fcfg = FCFG.from_text(\"\"\" @@ -55,7 +55,6 @@ class FCFG(CFG): >>> fcfg.contains(["this", "flight", "serves"]) True - """ def __init__(self, @@ -63,16 +62,23 @@ def __init__(self, terminals: AbstractSet[Hashable] = None, start_symbol: Hashable = None, productions: Iterable[Production] = None) -> None: + """Initializes the feature-based grammar.""" super().__init__(variables, terminals, start_symbol, productions) self._productions: Set[FeatureProduction] @property def feature_productions(self) -> Set[FeatureProduction]: - """ Gets the feature productions of the grammar """ + """Gets the feature productions of the grammar.""" return self._productions def add_production(self, production: Production) -> None: - """ Adds given production to the grammar """ + """Adds the given production to the grammar. + + Parameters + ---------- + production: + The production to add. + """ if not isinstance(production, FeatureProduction): production = FeatureProduction(production.head, production.body, @@ -81,37 +87,35 @@ def add_production(self, production: Production) -> None: super().add_production(production) def contains(self, word: Iterable[Hashable]) -> bool: - """ Gives the membership of a word to the grammar + """Checks if the word is contained in the grammar. Parameters ---------- - word : iterable of :class:`~pyformlang.cfg.Terminal` - The word to check + word: + The word to check. Returns - ---------- - contains : bool - Whether word if in the FCFG or not + ------- + Whether the word is in the FCFG or not. """ word = [to_terminal(x) for x in word if x != Epsilon()] return self._get_final_state(word) is not None def get_parse_tree(self, word: Iterable[Hashable]) -> ParseTree: - """ Gives the parse tree for a sentence, if possible + """Gets the parse tree for a sentence, if possible. Parameters ---------- - word : iterable of :class:`~pyformlang.cfg.Terminal` - The word to check + word: + The word to parse. Returns - ---------- - parse_tree : :class:`~pyformlang.cfg.ParseTree` - The parse tree + ------- + The parse tree of the given word. Raises ------ - NotParsableException + NotParsableError When the word is not parsable. """ word = [to_terminal(x) for x in word if x != Epsilon()] @@ -162,7 +166,12 @@ def _get_final_state(self, word: List[Terminal]) -> Optional[State]: return None def copy(self) -> "FCFG": - """ Copies the FCFG """ + """Copies the current FCFG. + + Returns + ------- + A copy of current feature-based grammar. + """ return FCFG._copy_from(self) @classmethod diff --git a/pyformlang/fcfg/feature_production.py b/pyformlang/fcfg/feature_production.py index 19c7b1b..c40aeb0 100644 --- a/pyformlang/fcfg/feature_production.py +++ b/pyformlang/fcfg/feature_production.py @@ -1,4 +1,4 @@ -"""Production rules with features""" +"""Production rules with features.""" from typing import List, Iterable @@ -8,19 +8,21 @@ class FeatureProduction(Production): - """ A feature production or rule of a FCFG + """A feature production or rule of a FCFG. Parameters ---------- - head : :class:`~pyformlang.cfg.Variable` - The head of the production - body : iterable of :class:`~pyformlang.cfg.CFGObject` - The body of the production - head_feature : :class:`~pyformlang.fcfg.FeatureStructure` - The feature structure of the head - body_features : Iterable of :class:`~pyformlang.fcfg.FeatureStructure` + head: + The head of the production. + body: + The body of the production. + head_feature: + The feature structure of the head. + body_features: The feature structures of the elements of the body. Must be the same size as the body. + filtering: + Whether to ignore the epsilon terminals in body. """ def __init__(self, @@ -29,6 +31,7 @@ def __init__(self, head_feature: FeatureStructure, body_features: Iterable[FeatureStructure], filtering: bool = True) -> None: + """Initializes the feature production.""" super().__init__(head, body, filtering) self._features = FeatureStructure() self._features.add_content("head", head_feature) @@ -37,10 +40,11 @@ def __init__(self, @property def features(self) -> FeatureStructure: - """The merged features of the production rules""" + """Gets the merged features of the production rules.""" return self._features def __repr__(self) -> str: + """Gets the string representation of the feature grammar.""" res = [self.head.to_text()] cond_head = str(self._features.get_feature_by_path(["head"])) if cond_head: diff --git a/pyformlang/fcfg/feature_structure.py b/pyformlang/fcfg/feature_structure.py index 4e2d30d..91ed9f1 100644 --- a/pyformlang/fcfg/feature_structure.py +++ b/pyformlang/fcfg/feature_structure.py @@ -1,132 +1,127 @@ -"""Feature Structure""" +"""The feature structure containing constraints.""" from typing import Dict, List, Iterable, Tuple, Optional, Hashable class ContentAlreadyExistsError(Exception): - """Exception raised when we want to add a content that already exists""" + """Exception raised when we want to add a content that already exists.""" class PathDoesNotExistError(Exception): - """Raised when looking for a path that does not exist""" + """Raised when looking for a path that does not exist.""" class FeatureStructuresNotCompatibleError(Exception): - """Raised when trying to unify incompatible structures""" + """Raised when trying to unify incompatible structures.""" class FeatureStructure: - """ The feature structure containing constraints + """The feature structure containing constraints. Parameters ---------- - value : Any, optional - The value of the feature, if defined - + value: + The value of the feature, if defined. """ def __init__(self, value: Hashable = None) -> None: + """Initializes the feature structure.""" self._content: Dict[str, FeatureStructure] = {} self._value = value self._pointer: Optional[FeatureStructure] = None @property def content(self) -> Dict[str, "FeatureStructure"]: - """Gets the content of the current node""" + """Gets the content of the current node.""" return self._content @property def pointer(self) -> Optional["FeatureStructure"]: - """Gets the pointer of the current node""" + """Gets the pointer of the current node.""" return self._pointer @pointer.setter def pointer(self, new_pointer: "FeatureStructure") -> None: - """Set the value of the pointer""" + """Sets the value of the pointer.""" self._pointer = new_pointer @property def value(self) -> Hashable: - """Gets the value associated to the current node""" + """Gets the value associated with the current node.""" return self._value if self.pointer is None else self.pointer.value @value.setter def value(self, new_value: Hashable) -> None: - """Gets the value associated to the current node""" + """Sets the value associated with the current node.""" self._value = new_value def add_content(self, content_name: str, feature_structure: "FeatureStructure") -> None: - """Add content to the current feature structure. + """Adds content to the current feature structure. Parameters ---------- - content_name : str - The name of the new feature - feature_structure : :class:`~pyformlang.fcfg.FeatureStructure` - The value of this new feature + content_name: + The name of the new feature. + feature_structure: + The value of this new feature. Raises - ---------- - ContentAlreadyExistsException - When the feature already exists + ------ + ContentAlreadyExistsError + When the feature already exists. """ if content_name in self._content: - raise ContentAlreadyExistsError() + raise ContentAlreadyExistsError self._content[content_name] = feature_structure def add_content_path(self, content_name: str, feature_structure: "FeatureStructure", path: List[str]) -> None: - """Add content to the current feature structure at a specific path + """Adds content to the current feature structure at a specific path. Parameters ---------- - content_name : str - The name of the new feature - feature_structure : :class:`~pyformlang.fcfg.FeatureStructure` - The value of this new feature - path : Iterable of str + content_name: + The name of the new feature. + feature_structure: + The value of this new feature. + path: The path where to add the new feature. Raises - ---------- - ContentAlreadyExistsException - When the feature already exists - PathDoesNotExistsException - When the path does not exist + ------ + ContentAlreadyExistsError + When the feature already exists. + PathDoesNotExistsError + When the path does not exist. """ to_modify = self.get_feature_by_path(path) to_modify.add_content(content_name, feature_structure) def get_dereferenced(self) -> "FeatureStructure": - """ - Get the dereferences version of the feature structure. - For internal usage. - """ + """Gets the dereferenced version of the feature structure.""" return self._pointer.get_dereferenced() \ if self._pointer is not None else self def get_feature_by_path(self, path: List[str] = None) -> "FeatureStructure": - """ Get a feature at a given path. + """Gets a feature at the given path. Parameters - ----------- - path : List of str, optional - The path to the new feature. + ---------- + path: + The path to the feature. Returns ------- - feature_structure : :class:`~pyformlang.fcfg.FeatureStructure` - The feature structure at the end of the path. + The feature structure at the end of the path. Raises - ---------- - PathDoesNotExistsException - When the path does not exist - + ------ + PathDoesNotExistError + When the path does not exist. """ if not path or path is None: return self @@ -136,18 +131,18 @@ def get_feature_by_path(self, path: List[str] = None) -> "FeatureStructure": return current.content[path[0]].get_feature_by_path(path[1:]) def unify(self, other: "FeatureStructure") -> None: - """Unify the current structure with another one. + """Unifies the current structure with another one. Modifies the current structure. Parameters ---------- - other : :class:`~pyformlang.fcfg.FeatureStructure` + other: The other feature structure to unify. Raises - ---------- - FeatureStructuresNotCompatibleException + ------ + FeatureStructuresNotCompatibleError When the feature structure cannot be unified. """ current_dereferenced = self.get_dereferenced() @@ -174,17 +169,16 @@ def unify(self, other: "FeatureStructure") -> None: other_dereferenced.content[feature]) def subsumes(self, other: "FeatureStructure") -> bool: - """Check whether the current feature structure subsumes another one. + """Checks whether the current feature structure subsumes another one. Parameters ---------- - other : :class:`~pyformlang.fcfg.FeatureStructure` - The other feature structure to unify. + other: + The other feature structure. Returns - ---------- - subsumes : bool - Whether the current feature structure subsumes the one. + ------- + Whether the current feature structure subsumes the other one. """ current_dereferenced = self.get_dereferenced() other_dereferenced = other.get_dereferenced() @@ -199,13 +193,11 @@ def subsumes(self, other: "FeatureStructure") -> bool: return True def get_all_paths(self) -> List[List[str]]: - """ Get the list of all path in the feature structure + """Gets the list of all path in the feature structure. Returns -------- - paths : List of string lists - The paths - + A list of paths in the feature structure. """ res = [] for feature, content in self._content.items(): @@ -217,6 +209,7 @@ def get_all_paths(self) -> List[List[str]]: return res def __repr__(self) -> str: + """Gets the string representation of the feature structure.""" res = [] for path in self.get_all_paths(): if path: @@ -230,18 +223,16 @@ def __repr__(self) -> str: def copy(self, already_copied: Dict["FeatureStructure", "FeatureStructure"] = None) \ -> "FeatureStructure": - """Copies the current feature structure + """Copies the current feature structure. Parameters ---------- - already_copied : dict - A dictionary containing the parts already copied. - For internal usage. + already_copied: + A dictionary containing the parts already copied. Returns - ---------- - fs : :class:`~pyformlang.fcfg.FeatureStructure` - The copied feature structure + ------- + The copied feature structure. """ if already_copied is None: already_copied = {} @@ -261,21 +252,18 @@ def from_text(cls, text: str, structure_variables: Dict[str, "FeatureStructure"] = None) \ -> "FeatureStructure": - """ Construct a feature structure from a text. + """Constructs a feature structure from the given text. Parameters - ----------- - text : str - The text to parse - structure_variables : \ - dict of (str, :class:`~pyformlang.fcfg.FeatureStructure`), optional - Existing structure variables. + ---------- + text: + The text to parse. + structure_variables: + The existing structure variables. Returns - -------- - feature_structure : :class:`~pyformlang.fcfg.FeatureStructure` - The parsed feature structure - + ------- + The parsed feature structure. """ if structure_variables is None: structure_variables = {} @@ -301,8 +289,8 @@ def _find_closing_bracket(condition: str, return -1 -class ParsingException(Exception): - """When there is a problem during parsing.""" +class ParsingError(Exception): + """Raised when there is a problem during parsing.""" def _preprocess_conditions(conditions: str, @@ -328,14 +316,14 @@ def _preprocess_conditions(conditions: str, elif current == "[": end_bracket = _find_closing_bracket(conditions, pos) if end_bracket == -1: - raise ParsingException() + raise ParsingError() current_value = _preprocess_conditions( conditions, pos + 1, end_bracket) pos = end_bracket + 1 elif current == "(": end_bracket = _find_closing_bracket(conditions, pos, "(", ")") if end_bracket == -1: - raise ParsingException() + raise ParsingError() reference = conditions[pos+1: end_bracket] pos = end_bracket + 1 elif current == ",": diff --git a/pyformlang/fcfg/state.py b/pyformlang/fcfg/state.py index b7768b2..4920b2c 100644 --- a/pyformlang/fcfg/state.py +++ b/pyformlang/fcfg/state.py @@ -1,4 +1,4 @@ -"""Internal usage states""" +"""States for internal usage.""" from typing import Dict, List, Iterable, Tuple @@ -13,45 +13,49 @@ class State: - """For internal usage""" + """State for internal usage.""" def __init__(self, production: FeatureProduction, positions: Positions, feature_stucture: FeatureStructure, parse_tree: ParseTree) -> None: + """Initializes the state.""" self.production = production self.positions = positions self.feature_stucture = feature_stucture self.parse_tree = parse_tree def get_key(self) -> StateKey: - """Get the key of the state""" + """Gets the key of the state.""" return self.production, self.positions def is_incomplete(self) -> bool: - """Check if a state is incomplete""" + """Checks if a state is incomplete.""" return self.positions[2] < len(self.production.body) def next_is_variable(self) -> bool: - """Check if the next symbol to process is a variable""" + """Checks if the next symbol to process is a variable.""" return isinstance(self.production.body[self.positions[2]], Variable) def next_is_symbol(self, symbol: Terminal) -> bool: - """Check if the next symbol matches a given word""" + """Check if the next symbol matches the given symbol.""" return self.production.body[self.positions[2]] == symbol class StateProcessed: - """For internal usage""" + """List of processed states for internal usage.""" def __init__(self, size: int) -> None: + """Initializes the list of processed states.""" self.processed: ProcessedStates = [{} for _ in range(size)] def add(self, i: int, element: State) -> bool: - """ - Add a state to the processed states. - Returns if the insertion was successful or not. + """Adds the given state to the processed states. + + Returns + ------- + Whether the insertion was successful or not. """ key = element.get_key() if key not in self.processed[i]: @@ -63,6 +67,6 @@ def add(self, i: int, element: State) -> bool: return True def generator(self, i: int) -> Iterable[State]: - """Generates a collection of all the states at a given position""" + """Generates a collection of all the states at the given position.""" for states in self.processed[i].values(): yield from states diff --git a/pyformlang/objects/cfg_objects/production.py b/pyformlang/objects/cfg_objects/production.py index 9ce5a33..58a8d76 100644 --- a/pyformlang/objects/cfg_objects/production.py +++ b/pyformlang/objects/cfg_objects/production.py @@ -17,6 +17,8 @@ class Production: The head of the production. body: The body of the production. + filtering: + Whether to ignore the epsilon terminals in body. """ __slots__ = ["_body", "_head", "_hash"] From 63f914ab25e2c5f259c215ddca9420c1a83490da Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 20:33:56 +0300 Subject: [PATCH 12/21] select flake8-annotations rules in ruff config --- pyformlang/objects/base_terminal.py | 2 +- pyformlang/regular_expression/regex_reader.py | 2 +- ruff.toml | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyformlang/objects/base_terminal.py b/pyformlang/objects/base_terminal.py index 2b42544..b3a36fd 100644 --- a/pyformlang/objects/base_terminal.py +++ b/pyformlang/objects/base_terminal.py @@ -9,7 +9,7 @@ class BaseTerminal(FormalObject): """General terminal representation.""" @abstractmethod - def __repr__(self): + def __repr__(self) -> str: """Gets the string representation of the terminal.""" raise NotImplementedError diff --git a/pyformlang/regular_expression/regex_reader.py b/pyformlang/regular_expression/regex_reader.py index e999366..3ae4852 100644 --- a/pyformlang/regular_expression/regex_reader.py +++ b/pyformlang/regular_expression/regex_reader.py @@ -69,7 +69,7 @@ def _get_parenthesis_depths(self) -> List[int]: depths.append(depths[-1] + _get_parenthesis_value(component)) return depths[1:] - def _begins_with_parenthesis_components(self): + def _begins_with_parenthesis_components(self) -> bool: return self._components[0] == "(" def _setup_precedence_when_not_trivial(self) -> None: diff --git a/ruff.toml b/ruff.toml index fc5cbe0..40789bc 100644 --- a/ruff.toml +++ b/ruff.toml @@ -40,10 +40,12 @@ select = [ "N", # pep8-naming "D", # pydocstyle "DOC", # pydoclint + "ANN", # flake8-annotations ] ignore = [ "D416", + "ANN401", ] [lint.pydocstyle] From ba14a95d240b47a5bff07d3ac3fb80ce9f904aeb Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 22:34:40 +0300 Subject: [PATCH 13/21] update pda module docs --- pyformlang/finite_automaton/epsilon_nfa.py | 2 +- .../finite_automaton/finite_automaton.py | 4 +- .../nondeterministic_transition_function.py | 2 +- .../finite_automaton/transition_function.py | 2 +- pyformlang/pda/pda.py | 335 +++++++++--------- pyformlang/pda/transition_function.py | 92 +++-- pyformlang/pda/utils.py | 14 +- pyformlang/regular_expression/regex.py | 7 +- 8 files changed, 257 insertions(+), 201 deletions(-) diff --git a/pyformlang/finite_automaton/epsilon_nfa.py b/pyformlang/finite_automaton/epsilon_nfa.py index 85bf4d3..e73e9ed 100644 --- a/pyformlang/finite_automaton/epsilon_nfa.py +++ b/pyformlang/finite_automaton/epsilon_nfa.py @@ -499,7 +499,7 @@ def get_difference(self, other: "EpsilonNFA") -> "EpsilonNFA": The other Epsilon NFA. Returns - --------- + ------- The difference with the other epsilon NFA. Examples diff --git a/pyformlang/finite_automaton/finite_automaton.py b/pyformlang/finite_automaton/finite_automaton.py index 2662a95..28004d9 100644 --- a/pyformlang/finite_automaton/finite_automaton.py +++ b/pyformlang/finite_automaton/finite_automaton.py @@ -138,7 +138,7 @@ def remove_transition(self, The destination state. Returns - -------- + ------- 1 if the transition existed, 0 otherwise. Examples @@ -199,7 +199,7 @@ def remove_start_state(self, state: Hashable) -> int: """Removes an initial state from the automaton. Parameters - ----------- + ---------- state: The initial state to remove. diff --git a/pyformlang/finite_automaton/nondeterministic_transition_function.py b/pyformlang/finite_automaton/nondeterministic_transition_function.py index fb6a815..e612860 100644 --- a/pyformlang/finite_automaton/nondeterministic_transition_function.py +++ b/pyformlang/finite_automaton/nondeterministic_transition_function.py @@ -80,7 +80,7 @@ def remove_transition(self, The destination state. Returns - -------- + ------- 1 if the transition was found, 0 otherwise. Examples diff --git a/pyformlang/finite_automaton/transition_function.py b/pyformlang/finite_automaton/transition_function.py index b50dc8f..b4a281c 100644 --- a/pyformlang/finite_automaton/transition_function.py +++ b/pyformlang/finite_automaton/transition_function.py @@ -48,7 +48,7 @@ def remove_transition(self, The destination state. Returns - -------- + ------- 1 if the transition was found, 0 otherwise. """ raise NotImplementedError diff --git a/pyformlang/pda/pda.py b/pyformlang/pda/pda.py index 8a492ed..706be8d 100644 --- a/pyformlang/pda/pda.py +++ b/pyformlang/pda/pda.py @@ -1,4 +1,4 @@ -""" We represent here a push-down automaton """ +"""A representation of the push-down automaton.""" from typing import Dict, List, Set, AbstractSet, \ Iterator, Iterable, Tuple, Type, Optional, Hashable, Any @@ -38,25 +38,26 @@ class PDA(Iterable[Transition]): - """ Representation of a pushdown automaton + """A representation of the push-down automaton. Parameters ---------- - states : set of :class:`~pyformlang.pda.State`, optional - A finite set of states - input_symbols : set of :class:`~pyformlang.pda.Symbol`, optional - A finite set of input symbols - stack_alphabet : set of :class:`~pyformlang.pda.StackSymbol`, optional - A finite stack alphabet - transition_function : :class:`~pyformlang.pda.TransitionFunction`, optional - Takes as arguments a state, an input symbol and a stack symbol and - returns a state and a string of stack symbols push on the stacked to - replace X - start_state : :class:`~pyformlang.pda.State`, optional - A start state, element of states - start_stack_symbol : :class:`~pyformlang.pda.StackSymbol`, optional - The stack is initialized with this stack symbol - final_states : set of :class:`~pyformlang.pda.State`, optional + states: + A finite set of states. + input_symbols: + A finite set of input symbols. + stack_alphabet: + A finite stack alphabet. + transition_function: + A function that takes as arguments a state, an input symbol and a + stack symbol and returns a state and a string of stack symbols push + on the stacked to replace X. + start_state: + A start state, element of states. + start_stack_symbol: + The stack is initialized with this stack symbol, + element of stack alphabet. + final_states: A set of final or accepting states. It is a subset of states. """ @@ -70,6 +71,7 @@ def __init__(self, start_state: Hashable = None, start_stack_symbol: Hashable = None, final_states: AbstractSet[Hashable] = None) -> None: + """Initializes the push-down automaton.""" # pylint: disable=too-many-arguments self._states = {to_state(x) for x in states or set()} self._input_symbols = {to_symbol(x) for x in input_symbols or set()} @@ -89,92 +91,65 @@ def __init__(self, @property def states(self) -> Set[State]: - """ - Get the states fo the PDA - Returns - ------- - states : iterable of :class:`~pyformlang.pda.State` - The states of the PDA - """ + """Gets the states of the PDA.""" return self._states @property def input_symbols(self) -> Set[PDASymbol]: - """ - The input symbols of the PDA - - Returns - ------- - input_symbols : iterable of :class:`~pyformlang.pda.Symbol` - The input symbols of the PDA - """ + """Gets the input symbols of the PDA.""" return self._input_symbols @property def stack_symbols(self) -> Set[StackSymbol]: - """ - The stack symbols of the PDA - - Returns - ------- - stack_symbols : iterable of :class:`~pyformlang.pda.StackSymbol` - The stack symbols of the PDA - """ + """Gets the stack symbols of the PDA.""" return self._stack_alphabet @property def start_state(self) -> Optional[State]: - """ Get start state """ + """Gets start state of the PDA.""" return self._start_state @property def start_stack_symbol(self) -> Optional[StackSymbol]: - """ Get start stack symbol """ + """Gets start stack symbol of the PDA.""" return self._start_stack_symbol @property def final_states(self) -> Set[State]: - """ - The final states of the PDA - Returns - ------- - final_states : iterable of :class:`~pyformlang.pda.State` - The final states of the PDA - - """ + """Gets the final states of the PDA.""" return self._final_states def set_start_state(self, start_state: Hashable) -> None: - """ Sets the start state to the automaton + """Sets the start state of the automaton. Parameters ---------- - start_state : :class:`~pyformlang.pda.State` - The start state + start_state: + The start state to set. """ start_state = to_state(start_state) self._states.add(start_state) self._start_state = start_state def set_start_stack_symbol(self, start_stack_symbol: Hashable) -> None: - """ Sets the start stack symbol to the automaton + """Sets the start stack symbol of the automaton. Parameters ---------- - start_stack_symbol : :class:`~pyformlang.pda.StackSymbol` - The start stack symbol + start_stack_symbol: + The start stack symbol to set. """ start_stack_symbol = to_stack_symbol(start_stack_symbol) self._stack_alphabet.add(start_stack_symbol) self._start_stack_symbol = start_stack_symbol def add_final_state(self, state: Hashable) -> None: - """ Adds a final state to the automaton + """Adds a final state to the automaton. Parameters ---------- - state : :class:`~pyformlang.pda.State` - The state to add + state: + The final state to add. """ state = to_state(state) self._final_states.add(state) @@ -185,20 +160,20 @@ def add_transition(self, stack_from: Hashable, s_to: Hashable, stack_to: Iterable[Hashable]) -> None: - """ Add a transition to the PDA + """Adds the given transition to the PDA. Parameters ---------- - s_from : :class:`~pyformlang.pda.State` - The starting symbol - input_symbol : :class:`~pyformlang.pda.Symbol` - The input symbol for the transition - stack_from : :class:`~pyformlang.pda.StackSymbol` - The stack symbol of the transition - s_to : :class:`~pyformlang.pda.State` - The new state - stack_to : list of :class:`~pyformlang.pda.StackSymbol` - The string of stack symbol which replace the stack_from + s_from: + The starting state of the transition. + input_symbol: + The input symbol of the transition. + stack_from: + The source stack symbol of the transition. + s_to: + The target state of the transition. + stack_to: + The sequence of stack symbols to replace `stack_from` with. """ # pylint: disable=too-many-arguments s_from = to_state(s_from) @@ -221,13 +196,12 @@ def add_transition(self, stack_to) def add_transitions(self, transitions: Iterable[InputTransition]) -> None: - """ - Adds several transitions + """Adds several transitions to the PDA. Parameters ---------- - transitions : - Transitions as they would be given to add_transition + transitions: + Transitions as they would be given to `add_transition`. """ for s_from, input_symbol, stack_from, s_to, stack_to in transitions: self.add_transition(s_from, input_symbol, stack_from, @@ -239,7 +213,21 @@ def remove_transition(self, stack_from: Hashable, s_to: Hashable, stack_to: Iterable[Hashable]) -> None: - """ Remove the given transition from the PDA """ + """Removes the given transition from the PDA. + + Parameters + ---------- + s_from: + The starting state of the transition. + input_symbol: + The input symbol of the transition. + stack_from: + The source stack symbol of the transition. + s_to: + The target state of the transition. + stack_to: + The target stack symbol sequence of the transition. + """ s_from = to_state(s_from) input_symbol = to_symbol(input_symbol) stack_from = to_stack_symbol(stack_from) @@ -252,12 +240,11 @@ def remove_transition(self, stack_to) def get_number_transitions(self) -> int: - """ Gets the number of transitions in the PDA + """Gets the number of transitions in the PDA. Returns - ---------- - n_transitions : int - The number of transitions + ------- + The number of transitions in the automaton. """ return self._transition_function.get_number_transitions() @@ -265,14 +252,38 @@ def __call__(self, s_from: Hashable, input_symbol: Hashable, stack_from: Hashable) -> TransitionValues: - """ Calls transition function with given arguments """ + """Makes a call of the transition function of the PDA. + + Parameters + ---------- + s_from: + The starting state of the transition. + input_symbol: + The input symbol of the transition. + stack_from: + The source stack symbol of the transition. + + Returns + ------- + A set of target state and target stack pairs. + """ s_from = to_state(s_from) input_symbol = to_symbol(input_symbol) stack_from = to_stack_symbol(stack_from) return self._transition_function(s_from, input_symbol, stack_from) def __contains__(self, transition: InputTransition) -> bool: - """ Whether the given transition is present in the PDA """ + """Checks if the given transition is present in the PDA. + + Parameters + ---------- + transition: + The transition to check containment of. + + Returns + ------- + Whether the given transition is present in the automaton. + """ s_from, input_symbol, stack_from, s_to, stack_to = transition s_from = to_state(s_from) input_symbol = to_symbol(input_symbol) @@ -282,18 +293,19 @@ def __contains__(self, transition: InputTransition) -> bool: return (s_to, stack_to) in self(s_from, input_symbol, stack_from) def __iter__(self) -> Iterator[Transition]: - """ Gets an iterator of transitions of the PDA """ + """Yields the transitions of current PDA.""" yield from self._transition_function def to_final_state(self) -> "PDA": - """ Turns the current PDA that accepts a language L by empty stack \ - to another PDA that accepts the same language L by final state + """Converts current PDA to the final state accepting one. + + Turns the current PDA that accepts a language L by empty stack + to another PDA that accepts the same language L by final state. Returns - ---------- - new_pda : :class:`~pyformlang.pda.PDA` - The new PDA which accepts by final state the language that \ - was accepted by empty stack + ------- + The new PDA which accepts by final state the language that was + accepted by empty stack. """ new_start = self.__get_next_free("#STARTTOFINAL#", State, @@ -326,14 +338,15 @@ def to_final_state(self) -> "PDA": {new_end}) def to_empty_stack(self) -> "PDA": - """ Turns the current PDA that accepts a language L by final state to \ - another PDA that accepts the same language L by empty stack + """Converts current PDA to the empty stack accepting one. + + Turns the current PDA that accepts a language L by final state to + another PDA that accepts the same language L by empty stack. Returns - ---------- - new_pda : :class:`~pyformlang.pda.PDA` - The new PDA which accepts by empty stack the language that was \ - accepted by final state + ------- + The new PDA which accepts by empty stack the language that was + accepted by final state. """ new_start = self.__get_next_free("#STARTEMPTYS#", State, @@ -369,14 +382,14 @@ def to_empty_stack(self) -> "PDA": new_stack_symbol) def to_cfg(self) -> CFG: - """ Turns the language L generated by this PDA when accepting \ - on empty \ - stack into a CFG that accepts the same language L + """Converts current PDA to the Context-Free Grammar. + + Turns the language L generated by this PDA when accepting on empty + stack into a CFG that accepts the same language L. Returns - ---------- - new_cfg : :class:`~pyformlang.cfg.CFG` - The equivalent CFG + ------- + The equivalent Context-Free Grammar. """ variable_converter = CFGVariableConverter(self._states, self._stack_alphabet) @@ -447,7 +460,7 @@ def _generate_all_rules(self, ss_by: List[StackSymbol], variable_converter: CFGVariableConverter) \ -> List[List[CFGObject]]: - """ Generates the rules in the CFG conversion """ + """Generates the rules in the CFG conversion.""" if not ss_by: return [[]] if len(ss_by) == 1: @@ -518,13 +531,19 @@ def _initialize_production_from_start_in_to_cfg( @classmethod def from_cfg(cls, cfg: CFG) -> "PDA": - """ Converts the CFG to a PDA that generates on empty stack an \ - equivalent language + """Builds a PDA from the given Context-Free Grammar. - Returns + Converts the CFG to a PDA that generates on empty stack an + equivalent language. + + Parameters ---------- - new_pda : :class:`~pyformlang.pda.PDA` - The equivalent PDA when accepting on empty stack + cfg: + Context-Free Grammar to build a PDA from. + + Returns + ------- + The equivalent PDA when accepting on empty stack. """ state = State("q") pda_symbol_converter = PDASymbolConverter(cfg.terminals, cfg.variables) @@ -558,32 +577,23 @@ def from_cfg(cls, cfg: CFG) -> "PDA": return new_pda def intersection(self, other: DeterministicFiniteAutomaton) -> "PDA": - """ Gets the intersection of the language L generated by the \ - current PDA when accepting by final state with something else + """Gets an intersection of current PDA with the given DFA. - Currently, it only works for regular languages (represented as \ - regular expressions or finite automata) as the intersection \ - between two PDAs is not context-free (it cannot be represented \ - with a PDA). + Gets the intersection of the language L generated by the + current PDA when accepting by final state with the given + deterministic finite automaton. Equivalent to: - >> pda and regex + >> pda & dfa Parameters ---------- - other : any - The other part of the intersection + other: + The deterministic finite automaton to intersect with. Returns - ---------- - new_pda : :class:`~pyformlang.pda.PDA` - The pda resulting of the intersection - - Raises - ---------- - NotImplementedError - When intersecting with something else than a regex or a finite - automaton + ------- + The PDA resulting of the intersection. """ if not self.start_state or not other.start_state or other.is_empty(): return PDA() @@ -632,38 +642,29 @@ def intersection(self, other: DeterministicFiniteAutomaton) -> "PDA": return pda def __and__(self, other: DeterministicFiniteAutomaton) -> "PDA": - """ Gets the intersection of the current PDA with something else + """Gets an intersection of current PDA with the given DFA. - Equivalent to: - >> pda and regex + Gets the intersection of the language L generated by the + current PDA when accepting by final state with the given + deterministic finite automaton. Parameters ---------- - other : any - The other part of the intersection + other: + The deterministic finite automaton to intersect with. Returns - ---------- - new_pda : :class:`~pyformlang.pda.PDA` - The pda resulting of the intersection - - Raises - ---------- - NotImplementedError - When intersecting with something else than a regex or a finite - automaton + ------- + The PDA resulting of the intersection. """ return self.intersection(other) def to_networkx(self) -> MultiDiGraph: - """ - Transform the current pda into a networkx graph + """Transforms the current PDA into a networkx graph. Returns ------- - graph : networkx.MultiDiGraph - A networkx MultiDiGraph representing the pda - + A networkx MultiDiGraph representing the PDA. """ graph = MultiDiGraph() for state in self._states: @@ -693,24 +694,23 @@ def to_networkx(self) -> MultiDiGraph: @classmethod def from_networkx(cls, graph: MultiDiGraph) -> "PDA": - """ - Import a networkx graph into a PDA. \ - The imported graph requires to have the good format, i.e. to come \ - from the function to_networkx + """Import a networkx graph into a PDA. + + The imported graph requires to have the good format, i.e. to come + from the function to_networkx. Parameters ---------- - graph : - The graph representation of the PDA + graph: + The graph representation of the PDA. Returns ------- - pda : - A PDA automaton read from the graph + A PDA automaton read from the graph. - TODO - ------- - * Explain the format + Todo + ---- + * Explain the format. """ pda = PDA() for s_from in graph: @@ -741,19 +741,22 @@ def from_networkx(cls, graph: MultiDiGraph) -> "PDA": return pda def write_as_dot(self, filename: str) -> None: - """ - Write the PDA in dot format into a file + """Writes the PDA in dot format into a file. Parameters ---------- - filename : str - The filename where to write the dot file - + filename: + The filename where to write the dot file. """ write_dot(self.to_networkx(), filename) def copy(self) -> "PDA": - """ Copies the Push-down Automaton """ + """Copies the current Push-Down Automaton. + + Returns + ------- + A copy of current Push-Down Automaton. + """ return PDA(self.states, self.input_symbols, self.stack_symbols, @@ -763,22 +766,22 @@ def copy(self) -> "PDA": self.final_states) def __copy__(self) -> "PDA": + """Copies the current PDA.""" return self.copy() def to_dict(self) -> Dict[TransitionKey, TransitionValues]: - """ - Get the transitions of the PDA as a dictionary + """Gets the transition function of the PDA as a dictionary. + Returns ------- - transitions : dict - The transitions + The transition function of the PDA as a dictionary. """ return self._transition_function.to_dict() @staticmethod def __add_start_state_to_graph(graph: MultiDiGraph, state: State) -> None: - """ Adds a starting node to a given graph """ + """Adds a starting node to a given graph.""" graph.add_node("starting_" + str(state), label="", shape=None, @@ -799,7 +802,7 @@ def __prepend_input_symbol_to_the_bodies(bodies: List[List[CFGObject]], def __get_next_free(prefix: str, type_generating: Type, to_check: Iterable[Any]) -> Any: - """ Get free next state or symbol """ + """Gets free next state or symbol.""" idx = 0 new_var = type_generating(prefix) while new_var in to_check: diff --git a/pyformlang/pda/transition_function.py b/pyformlang/pda/transition_function.py index 4801376..a586065 100644 --- a/pyformlang/pda/transition_function.py +++ b/pyformlang/pda/transition_function.py @@ -1,4 +1,4 @@ -""" A transition function in a pushdown automaton """ +"""A transition function in a push-down automaton.""" from typing import Dict, Set, Iterator, Iterable, Tuple from copy import deepcopy @@ -12,9 +12,10 @@ class TransitionFunction(Iterable[Transition]): - """ A transition function in a pushdown automaton """ + """A transition function in a push-down automaton.""" def __init__(self) -> None: + """Creates an empty PDA transition function.""" self._transitions: Dict[TransitionKey, TransitionValues] = {} # pylint: disable=too-many-arguments @@ -24,20 +25,20 @@ def add_transition(self, stack_from: StackSymbol, s_to: State, stack_to: Tuple[StackSymbol, ...]) -> None: - """ Add a transition to the function + """Adds the given transition to the function. Parameters ---------- - s_from : :class:`~pyformlang.pda.State` - The starting symbol - input_symbol : :class:`~pyformlang.pda.Symbol` - The input symbol - stack_from : :class:`~pyformlang.pda.StackSymbol` - The stack symbol of the transition - s_to : :class:`~pyformlang.pda.State` - The new state - stack_to : list of :class:`~pyformlang.pda.StackSymbol` - The string of stack symbol which replace the stack_from + s_from: + The starting state of the transition. + input_symbol: + The input symbol of the transition. + stack_from: + The source stack symbol of the transition. + s_to: + The target state of the transition. + stack_to: + The sequence of stack symbols to replace `stack_from` with. """ temp_in = (s_from, input_symbol, stack_from) temp_out = (s_to, stack_to) @@ -52,17 +53,30 @@ def remove_transition(self, stack_from: StackSymbol, s_to: State, stack_to: Tuple[StackSymbol, ...]) -> None: - """ Remove the given transition from the function """ + """Removes the given transition from the function. + + Parameters + ---------- + s_from: + The starting state of the transition. + input_symbol: + The input symbol of the transition. + stack_from: + The source stack symbol of the transition. + s_to: + The target state of the transition. + stack_to: + The target stack symbol sequence of the transition. + """ key = (s_from, input_symbol, stack_from) self._transitions.get(key, set()).discard((s_to, stack_to)) def get_number_transitions(self) -> int: - """ Gets the number of transitions + """Gets the number of transitions described by the function. Returns - ---------- - n_transitions : int - The number of transitions + ------- + The number of transitions in the function. """ return sum(len(x) for x in self._transitions.values()) @@ -70,24 +84,50 @@ def __call__(self, s_from: State, input_symbol: Symbol, stack_from: StackSymbol) -> TransitionValues: + """Makes a call of the transition function. + + Parameters + ---------- + s_from: + The starting state of the transition. + input_symbol: + The input symbol of the transition. + stack_from: + The source stack symbol of the transition. + + Returns + ------- + A set of target state and target stack pairs. + """ return self._transitions.get((s_from, input_symbol, stack_from), set()) def __contains__(self, transition: Transition) -> bool: + """Checks if the given transition is present in the function. + + Parameters + ---------- + transition: + The transition to check containment of. + + Returns + ------- + Whether the given transition is present in the function. + """ key, value = transition return value in self(*key) def __iter__(self) -> Iterator[Transition]: + """Yields the transitions described by the transition function.""" for key, values in self._transitions.items(): for value in values: yield key, value def copy(self) -> "TransitionFunction": - """ Copy the current transition function + """Copies the current transition function. Returns - ---------- - new_tf : :class:`~pyformlang.pda.TransitionFunction` - The copy of the transition function + ------- + A copy of the transition function. """ new_tf = TransitionFunction() for temp_in, temp_out in self: @@ -95,8 +135,14 @@ def copy(self) -> "TransitionFunction": return new_tf def __copy__(self) -> "TransitionFunction": + """Copies the current transition function.""" return self.copy() def to_dict(self) -> Dict[TransitionKey, TransitionValues]: - """Get the dictionary representation of the transitions""" + """Gets the dictionary representation of the transition function. + + Returns + ------- + The transition function as a dictionary. + """ return deepcopy(self._transitions) diff --git a/pyformlang/pda/utils.py b/pyformlang/pda/utils.py index e1b0dea..7e22cc4 100644 --- a/pyformlang/pda/utils.py +++ b/pyformlang/pda/utils.py @@ -1,4 +1,4 @@ -""" Useful functions for a PDA """ +"""Useful functions for the PDA.""" from typing import Dict, Set, Iterable, Optional from numpy import empty @@ -12,12 +12,13 @@ class PDAStateConverter: - """Combines PDA and FA states""" + """Combines PDA and FA states.""" # pylint: disable=too-few-public-methods def __init__(self, states_pda: Set[PDAState], states_dfa: Set[FAState]) -> None: + """Initializes the state converter.""" self._inverse_state_pda = {} for i, state in enumerate(states_pda): self._inverse_state_pda[state] = i @@ -30,7 +31,7 @@ def __init__(self, def to_pda_combined_state(self, state_pda: PDAState, state_other: FAState) -> PDAState: - """ To PDA state in the intersection function """ + """Combines given PDA and FA states for the intersection function.""" i_state_pda = self._inverse_state_pda[state_pda] i_state_other = self._inverse_state_dfa[state_other] if self._conversions[i_state_pda, i_state_other] is None: @@ -40,11 +41,12 @@ def to_pda_combined_state(self, class PDASymbolConverter: - """Creates Objects for a PDA""" + """Creates Objects for a PDA.""" def __init__(self, terminals: Iterable[Terminal], variables: Iterable[Variable]) -> None: + """Initializes the symbol converter.""" self._inverse_symbol: Dict[CFGObject, Optional[Symbol]] = {} self._inverse_stack_symbol: Dict[CFGObject, Optional[StackSymbol]] = {} for terminal in terminals: @@ -54,7 +56,7 @@ def __init__(self, self._inverse_stack_symbol[variable] = None def get_symbol_from(self, symbol: CFGObject) -> Symbol: - """Get a symbol""" + """Gets a PDA symbol from the given object.""" if isinstance(symbol, CFGEpsilon): return PDAEpsilon() inverse_symbol = self._inverse_symbol[symbol] @@ -67,7 +69,7 @@ def get_symbol_from(self, symbol: CFGObject) -> Symbol: def get_stack_symbol_from(self, stack_symbol: CFGObject) \ -> StackSymbol: - """Get a stack symbol""" + """Gets a PDA stack symbol from the given object.""" if isinstance(stack_symbol, CFGEpsilon): return PDAEpsilon() inverse_stack_symbol = self._inverse_stack_symbol[stack_symbol] diff --git a/pyformlang/regular_expression/regex.py b/pyformlang/regular_expression/regex.py index abede98..4d901fb 100644 --- a/pyformlang/regular_expression/regex.py +++ b/pyformlang/regular_expression/regex.py @@ -156,7 +156,7 @@ def to_epsilon_nfa(self) -> EpsilonNFA: def _to_epsilon_nfa_internal(self) -> EpsilonNFA: """Transforms the regular expression into an epsilon NFA. - For internal use to prevent protected `enfa` member modification. + For internal usage to prevent protected `_enfa` member modification. Returns ------- @@ -550,6 +550,11 @@ def accepts(self, word: Iterable[str]) -> bool: def from_finite_automaton(cls, automaton: FiniteAutomaton) -> "Regex": """Creates a regular expression from given finite automaton. + Parameters + ---------- + automaton: + A finite automaton to build the regex from. + Returns ------- A regular expression equivalent to the given finite automaton. From ffc4aaf728ba451800d2f81ff5fbda01f74a545c Mon Sep 17 00:00:00 2001 From: bygu4 Date: Thu, 2 Jan 2025 23:59:37 +0300 Subject: [PATCH 14/21] update fst module docs --- pyformlang/cfg/cfg.py | 11 +- pyformlang/fcfg/feature_structure.py | 10 +- pyformlang/finite_automaton/epsilon_nfa.py | 4 +- .../finite_automaton/finite_automaton.py | 2 +- pyformlang/fst/fst.py | 278 ++++++++++-------- pyformlang/fst/transition_function.py | 83 +++++- pyformlang/fst/utils.py | 43 ++- pyformlang/pda/pda.py | 4 +- 8 files changed, 254 insertions(+), 181 deletions(-) diff --git a/pyformlang/cfg/cfg.py b/pyformlang/cfg/cfg.py index 3ac4076..f8d8981 100644 --- a/pyformlang/cfg/cfg.py +++ b/pyformlang/cfg/cfg.py @@ -445,7 +445,7 @@ def union(self, other: "CFG") -> "CFG": """Makes the union of two CFGs. Equivalent to: - >> cfg0 | cfg1 + >>> cfg0 | cfg1 Parameters ---------- @@ -486,7 +486,7 @@ def concatenate(self, other: "CFG") -> "CFG": """Makes the concatenation of two CFGs. Equivalent to: - >> cfg0 + cfg1 + >>> cfg0 + cfg1 Parameters ---------- @@ -564,7 +564,7 @@ def reverse(self) -> "CFG": """Reverses the current CFG. Equivalent to: - >> ~cfg + >>> ~cfg Returns ------- @@ -651,7 +651,7 @@ def intersection(self, other: DeterministicFiniteAutomaton) -> "CFG": """Gets the intersection of the CFG with the given automaton. Equivalent to: - >> cfg & dfa + >>> cfg & dfa Parameters ---------- @@ -765,9 +765,6 @@ def _get_all_bodies(production: Production, def __and__(self, other: DeterministicFiniteAutomaton) -> "CFG": """Gets the intersection of the CFG with the given automaton. - Equivalent to: - >> cfg & dfa - Parameters ---------- other: diff --git a/pyformlang/fcfg/feature_structure.py b/pyformlang/fcfg/feature_structure.py index 91ed9f1..eeac15b 100644 --- a/pyformlang/fcfg/feature_structure.py +++ b/pyformlang/fcfg/feature_structure.py @@ -95,7 +95,7 @@ def add_content_path(self, ------ ContentAlreadyExistsError When the feature already exists. - PathDoesNotExistsError + PathDoesNotExistError When the path does not exist. """ to_modify = self.get_feature_by_path(path) @@ -127,7 +127,7 @@ def get_feature_by_path(self, path: List[str] = None) -> "FeatureStructure": return self current = self.get_dereferenced() if path[0] not in current.content: - raise PathDoesNotExistError() + raise PathDoesNotExistError return current.content[path[0]].get_feature_by_path(path[1:]) def unify(self, other: "FeatureStructure") -> None: @@ -159,7 +159,7 @@ def unify(self, other: "FeatureStructure") -> None: elif other_dereferenced.value is None: other_dereferenced.pointer = current_dereferenced else: - raise FeatureStructuresNotCompatibleError() + raise FeatureStructuresNotCompatibleError else: other_dereferenced.pointer = current_dereferenced for feature in other_dereferenced.content: @@ -316,14 +316,14 @@ def _preprocess_conditions(conditions: str, elif current == "[": end_bracket = _find_closing_bracket(conditions, pos) if end_bracket == -1: - raise ParsingError() + raise ParsingError current_value = _preprocess_conditions( conditions, pos + 1, end_bracket) pos = end_bracket + 1 elif current == "(": end_bracket = _find_closing_bracket(conditions, pos, "(", ")") if end_bracket == -1: - raise ParsingError() + raise ParsingError reference = conditions[pos+1: end_bracket] pos = end_bracket + 1 elif current == ",": diff --git a/pyformlang/finite_automaton/epsilon_nfa.py b/pyformlang/finite_automaton/epsilon_nfa.py index e73e9ed..d4b4422 100644 --- a/pyformlang/finite_automaton/epsilon_nfa.py +++ b/pyformlang/finite_automaton/epsilon_nfa.py @@ -234,7 +234,7 @@ def from_networkx(cls, graph: MultiDiGraph) -> "EpsilonNFA": """Imports a networkx graph into an finite state automaton. The imported graph requires to have the good format, i.e. to come - from the function to_networkx. + from the function `to_networkx`. Parameters ---------- @@ -543,7 +543,7 @@ def reverse(self) -> "EpsilonNFA": """Computes the reversed Epsilon NFA. Equivalent to: - >> ~automaton + >>> ~automaton Returns ------- diff --git a/pyformlang/finite_automaton/finite_automaton.py b/pyformlang/finite_automaton/finite_automaton.py index 28004d9..5962541 100644 --- a/pyformlang/finite_automaton/finite_automaton.py +++ b/pyformlang/finite_automaton/finite_automaton.py @@ -480,7 +480,7 @@ def from_networkx(cls, graph: MultiDiGraph) -> "FiniteAutomaton": """Import a networkx graph into an finite state automaton. The imported graph requires to have the good format, i.e. to come - from the function to_networkx. + from the function `to_networkx`. Returns ------- diff --git a/pyformlang/fst/fst.py b/pyformlang/fst/fst.py index 9d5c14e..0dc3ecc 100644 --- a/pyformlang/fst/fst.py +++ b/pyformlang/fst/fst.py @@ -1,4 +1,4 @@ -""" Finite State Transducer """ +"""Representation of a Finite State Transducer.""" from typing import Dict, List, Set, AbstractSet, \ Tuple, Iterator, Iterable, Hashable @@ -16,7 +16,24 @@ class FST(Iterable[Transition]): - """ Representation of a Finite State Transducer""" + """Representation of a Finite State Transducer. + + Parameters + ---------- + states: + A finite set of states. + input_symbols: + A finite set of symbols, the input alphabet. + output_symbols: + A finite set of symbols, the output alphabet. + transition_function: + A function that takes as arguments a state and an input symbol + and returns a state and a string in output alphabet. + start_states: + A set of start states, subset of states. + final_states: + A set of final states, subset of states. + """ def __init__(self, states: AbstractSet[Hashable] = None, @@ -25,6 +42,7 @@ def __init__(self, transition_function: TransitionFunction = None, start_states: AbstractSet[Hashable] = None, final_states: AbstractSet[Hashable] = None) -> None: + """Initializes the Finite State Transducer.""" self._states = {to_state(x) for x in states or set()} self._input_symbols = {to_symbol(x) for x in input_symbols or set()} self._output_symbols = {to_symbol(x) for x in output_symbols or set()} @@ -36,57 +54,27 @@ def __init__(self, @property def states(self) -> Set[State]: - """ Get the states of the FST - - Returns - ---------- - states : set of any - The states - """ + """Gets the states of the FST.""" return self._states @property def input_symbols(self) -> Set[Symbol]: - """ Get the input symbols of the FST - - Returns - ---------- - input_symbols : set of any - The input symbols of the FST - """ + """Gets the input symbols of the FST.""" return self._input_symbols @property def output_symbols(self) -> Set[Symbol]: - """ Get the output symbols of the FST - - Returns - ---------- - output_symbols : set of any - The output symbols of the FST - """ + """Gets the output symbols of the FST.""" return self._output_symbols @property def start_states(self) -> Set[State]: - """ Get the start states of the FST - - Returns - ---------- - start_states : set of any - The start states of the FST - """ + """Gets the start states of the FST.""" return self._start_states @property def final_states(self) -> Set[State]: - """ Get the final states of the FST - - Returns - ---------- - final_states : set of any - The final states of the FST - """ + """Gets the final states of the FST.""" return self._final_states def add_transition(self, @@ -94,18 +82,18 @@ def add_transition(self, input_symbol: Hashable, s_to: Hashable, output_symbols: Iterable[Hashable]) -> None: - """ Add a transition to the FST + """Adds the given transition to the FST. Parameters - ----------- - s_from : any - The source state - input_symbol : any - The symbol to read - s_to : any - The destination state - output_symbols : iterable of Any - The symbols to output + ---------- + s_from: + The source state. + input_symbol: + The symbol to read. + s_to: + The destination state. + output_symbols: + The symbols to output. """ s_from = to_state(s_from) input_symbol = to_symbol(input_symbol) @@ -123,13 +111,12 @@ def add_transition(self, output_symbols) def add_transitions(self, transitions: Iterable[InputTransition]) -> None: - """ - Adds several transitions to the FST + """Adds several transitions to the FST. Parameters ---------- - transitions_list : list of tuples - The tuples have the form (s_from, in_symbol, s_to, out_symbols) + transitions: + Tuples of form (s_from, in_symbol, s_to, out_symbols). """ for s_from, input_symbol, s_to, output_symbols in transitions: self.add_transition(s_from, @@ -142,7 +129,19 @@ def remove_transition(self, input_symbol: Hashable, s_to: Hashable, output_symbols: Iterable[Hashable]) -> None: - """ Removes the given transition from the FST """ + """Removes the given transition from the FST. + + Parameters + ---------- + s_from: + The source state. + input_symbol: + The symbol to read. + s_to: + The destination state. + output_symbols: + The symbols to output. + """ s_from = to_state(s_from) input_symbol = to_symbol(input_symbol) s_to = to_state(s_to) @@ -153,34 +152,33 @@ def remove_transition(self, output_symbols) def get_number_transitions(self) -> int: - """ Get the number of transitions in the FST + """Gets the number of transitions in the FST. Returns - ---------- - n_transitions : int - The number of transitions + ------- + The number of transitions in the FST. """ return self._transition_function.get_number_transitions() def add_start_state(self, start_state: Hashable) -> None: - """ Add a start state + """Adds a start state to the FST. Parameters ---------- - start_state : any - The start state + start_state: + The start state to add. """ start_state = to_state(start_state) self._states.add(start_state) self._start_states.add(start_state) def add_final_state(self, final_state: Hashable) -> None: - """ Add a final state + """Adds a final state to the FST. Parameters ---------- - final_state : any - The final state to add + final_state: + The final state to add. """ final_state = to_state(final_state) self._final_states.add(final_state) @@ -188,13 +186,35 @@ def add_final_state(self, final_state: Hashable) -> None: def __call__(self, s_from: Hashable, input_symbol: Hashable) \ -> TransitionValues: - """ Calls the transition function of the FST """ + """Makes a call of the transition function of the FST. + + Parameters + ---------- + s_from: + The source state. + input_symbol: + The symbol to read. + + Returns + ------- + A set of destination state and output string pairs. + """ s_from = to_state(s_from) input_symbol = to_symbol(input_symbol) return self._transition_function(s_from, input_symbol) def __contains__(self, transition: InputTransition) -> bool: - """ Whether the given transition is present in the FST """ + """Checks if the given transition is present in the FST. + + Parameters + ---------- + transition: + The transition to check containment of. + + Returns + ------- + Whether the given transition is present in the FST. + """ s_from, input_symbol, s_to, output_symbols = transition s_from = to_state(s_from) input_symbol = to_symbol(input_symbol) @@ -203,26 +223,25 @@ def __contains__(self, transition: InputTransition) -> bool: return (s_to, output_symbols) in self(s_from, input_symbol) def __iter__(self) -> Iterator[Transition]: - """ Gets an iterator of transitions of the FST """ + """Yields the transitions of current FST.""" yield from self._transition_function def translate(self, input_word: Iterable[Hashable], max_length: int = -1) -> Iterable[List[Symbol]]: - """ Translate a string into another using the FST + """Translates the given string into another one using the FST. Parameters ---------- - input_word : iterable of any - The word to translate - max_length : int, optional - The maximum size of the output word, to prevent infinite \ - generation due to epsilon transitions + input_word: + The word to translate. + max_length: + The maximum size of the output word, to prevent infinite + generation due to epsilon transitions. Returns - ---------- - output_word : iterable of any - The translation of the input word + ------- + The translation of the input word. """ # (remaining in the input, generated so far, current_state) input_word = [to_symbol(x) for x in input_word if x != Epsilon()] @@ -254,18 +273,19 @@ def translate(self, next_state)) def union(self, other_fst: "FST") -> "FST": - """ - Makes the union of two fst + """Makes the union of two FSTs. + + Equivalent to: + >>> fst0 | fst1 + Parameters ---------- - other_fst : :class:`~pyformlang.fst.FST` - The other FST + other_fst: + The FST to get union with. Returns ------- - union_fst : :class:`~pyformlang.fst.FST` - A new FST which is the union of the two given FST - + A new FST which is the union of the two given FSTs. """ state_renaming = self._get_state_renaming(other_fst) union_fst = FST() @@ -275,18 +295,16 @@ def union(self, other_fst: "FST") -> "FST": return union_fst def __or__(self, other_fst: "FST") -> "FST": - """ - Makes the union of two fst + """Makes the union of two FSTs. + Parameters ---------- - other_fst : :class:`~pyformlang.fst.FST` - The other FST + other_fst: + The FST to get union with. Returns ------- - union_fst : :class:`~pyformlang.fst.FST` - A new FST which is the union of the two given FST - + A new FST which is the union of the two given FSTs. """ return self.union(other_fst) @@ -332,18 +350,19 @@ def _add_start_states_to(self, state_renaming.get_renamed_state(state, idx)) def concatenate(self, other_fst: "FST") -> "FST": - """ - Makes the concatenation of two fst + """Makes the concatenation of two FSTs. + + Equivalent to: + >>> fst0 + fst1 + Parameters ---------- - other_fst : :class:`~pyformlang.fst.FST` - The other FST + other_fst: + The FST to concatenate. Returns ------- - fst_concatenate : :class:`~pyformlang.fst.FST` - A new FST which is the concatenation of the two given FST - + A new FST which is the concatenation of the two given FSTs. """ state_renaming = self._get_state_renaming(other_fst) fst_concatenate = FST() @@ -363,18 +382,16 @@ def concatenate(self, other_fst: "FST") -> "FST": return fst_concatenate def __add__(self, other: "FST") -> "FST": - """ - Makes the concatenation of two fst + """Makes the concatenation of two FSTs. + Parameters ---------- - other : :class:`~pyformlang.fst.FST` - The other FST + other_fst: + The FST to concatenate. Returns ------- - fst_concatenate : :class:`~pyformlang.fst.FST` - A new FST which is the concatenation of the two given FST - + A new FST which is the concatenation of the two given FSTs. """ return self.concatenate(other) @@ -385,13 +402,11 @@ def _get_state_renaming(self, other_fst: "FST") -> StateRenaming: return state_renaming def kleene_star(self) -> "FST": - """ - Computes the kleene star of the FST + """Computes the kleene star of the FST. Returns ------- - fst_star : :class:`~pyformlang.fst.FST` - A FST representing the kleene star of the FST + A FST representing the kleene star of current FST. """ fst_star = FST() state_renaming = StateRenaming() @@ -417,14 +432,11 @@ def kleene_star(self) -> "FST": return fst_star def to_networkx(self) -> MultiDiGraph: - """ - Transform the current fst into a networkx graph + """Transforms the current FST into a networkx graph. Returns ------- - graph : networkx.MultiDiGraph - A networkx MultiDiGraph representing the fst - + A networkx MultiDiGraph representing the FST. """ graph = MultiDiGraph() for state in self._states: @@ -452,24 +464,23 @@ def to_networkx(self) -> MultiDiGraph: @classmethod def from_networkx(cls, graph: MultiDiGraph) -> "FST": - """ - Import a networkx graph into an finite state transducer. \ - The imported graph requires to have the good format, i.e. to come \ - from the function to_networkx + """Imports a networkx graph into an Finite State Transducer. + + The imported graph requires to have the good format, i.e. to come + from the function `to_networkx`. Parameters ---------- - graph : - The graph representation of the FST + graph: + The graph representation of the FST. Returns ------- - enfa : - A FST read from the graph + A FST read from the graph. - TODO - ------- - * Explain the format + Todo + ---- + * Explain the format. """ fst = FST() for s_from in graph: @@ -490,19 +501,22 @@ def from_networkx(cls, graph: MultiDiGraph) -> "FST": return fst def write_as_dot(self, filename: str) -> None: - """ - Write the FST in dot format into a file + """Writes the FST in dot format into a file. Parameters ---------- - filename : str - The filename where to write the dot file - + filename: + The filename where to write the dot file. """ write_dot(self.to_networkx(), filename) def copy(self) -> "FST": - """ Copies the FST """ + """Copies the current FST. + + Returns + ------- + A copy of current FST. + """ return FST(states=self.states, input_symbols=self.input_symbols, output_symbols=self.output_symbols, @@ -511,8 +525,14 @@ def copy(self) -> "FST": final_states=self.final_states) def __copy__(self) -> "FST": + """Copies the current FST.""" return self.copy() def to_dict(self) -> Dict[TransitionKey, TransitionValues]: - """Gives the transitions as a dictionary""" + """Gets the transition function of the FST as a dictionary. + + Returns + ------- + The transition function of the FST as a dictionary. + """ return self._transition_function.to_dict() diff --git a/pyformlang/fst/transition_function.py b/pyformlang/fst/transition_function.py index 9f75805..f1a2dfc 100644 --- a/pyformlang/fst/transition_function.py +++ b/pyformlang/fst/transition_function.py @@ -1,4 +1,4 @@ -""" The transition function of Finite State Transducer """ +"""The transition function of Finite State Transducer.""" from typing import Dict, Set, Tuple, Iterator, Iterable from copy import deepcopy @@ -12,9 +12,10 @@ class TransitionFunction(Iterable[Transition]): - """ The transition function of Finite State Transducer """ + """The transition function of Finite State Transducer.""" def __init__(self) -> None: + """Creates an empty FST transition function.""" self._transitions: Dict[TransitionKey, TransitionValues] = {} def add_transition(self, @@ -22,7 +23,19 @@ def add_transition(self, input_symbol: Symbol, s_to: State, output_symbols: Tuple[Symbol, ...]) -> None: - """ Adds given transition to the function """ + """Adds the given transition to the function. + + Parameters + ---------- + s_from: + The source state. + input_symbol: + The symbol to read. + s_to: + The destination state. + output_symbols: + The symbols to output. + """ key = (s_from, input_symbol) value = (s_to, output_symbols) self._transitions.setdefault(key, set()).add(value) @@ -32,47 +45,91 @@ def remove_transition(self, input_symbol: Symbol, s_to: State, output_symbols: Tuple[Symbol, ...]) -> None: - """ Removes given transition from the function """ + """Removes the given transition from the function. + + Parameters + ---------- + s_from: + The source state. + input_symbol: + The symbol to read. + s_to: + The destination state. + output_symbols: + The symbols to output. + """ key = (s_from, input_symbol) value = (s_to, output_symbols) self._transitions.get(key, set()).discard(value) def get_number_transitions(self) -> int: - """ Gets the number of transitions in the function + """Gets the number of transitions in the function. Returns - ---------- - n_transitions : int - The number of transitions + ------- + The number of transitions in the function. """ return sum(len(x) for x in self._transitions.values()) def __call__(self, s_from: State, input_symbol: Symbol) \ -> TransitionValues: - """ Calls the transition function """ + """Makes a call of the transition function. + + Parameters + ---------- + s_from: + The source state. + input_symbol: + The symbol to read. + + Returns + ------- + A set of destination state and output string pairs. + """ return self._transitions.get((s_from, input_symbol), set()) def __contains__(self, transition: Transition) -> bool: - """ Whether the given transition is present in the function """ + """Checks if the given transition is present in the function. + + Parameters + ---------- + transition: + The transition to check containment of. + + Returns + ------- + Whether the given transition is present in the function. + """ key, value = transition return value in self(*key) def __iter__(self) -> Iterator[Transition]: - """ Gets an iterator of transitions of the function """ + """Yields the transitions described by the transition function.""" for key, values in self._transitions.items(): for value in values: yield key, value def copy(self) -> "TransitionFunction": - """ Copies the transition function """ + """Copies the current transition function. + + Returns + ------- + A copy of the transition function. + """ new_tf = TransitionFunction() for key, value in self: new_tf.add_transition(*key, *value) return new_tf def __copy__(self) -> "TransitionFunction": + """Copies the current transition function.""" return self.copy() def to_dict(self) -> Dict[TransitionKey, TransitionValues]: - """ Gives the transition function as a dictionary """ + """Gets the dictionary representation of the transition function. + + Returns + ------- + The transition function as a dictionary. + """ return deepcopy(self._transitions) diff --git a/pyformlang/fst/utils.py b/pyformlang/fst/utils.py index 0a6c243..b2b0826 100644 --- a/pyformlang/fst/utils.py +++ b/pyformlang/fst/utils.py @@ -1,4 +1,4 @@ -""" Utility for FST """ +"""Utility for FST.""" from typing import Dict, Set, Iterable, Tuple @@ -7,21 +7,22 @@ class StateRenaming: - """ Class for renaming the states in FST """ + """Class for renaming the states in FST.""" def __init__(self) -> None: + """Initializes the state renaming.""" self._state_renaming: Dict[Tuple[str, int], str] = {} self._seen_states: Set[str] = set() def add_state(self, state: State, idx: int) -> None: - """ - Add a state + """Adds a state to the renaming. + Parameters ---------- - state : State - The state to add - idx : int - The index of the FST + state: + The state to add. + idx: + The index of the FST. """ current_name = str(state) if current_name in self._seen_states: @@ -37,33 +38,31 @@ def add_state(self, state: State, idx: int) -> None: self._seen_states.add(current_name) def add_states(self, states: Iterable[State], idx: int) -> None: - """ - Add states + """Adds multiple states to the renaming. + Parameters ---------- - states : Iterable of States - The states to add - idx : int - The index of the FST + states: + The states to add. + idx: + The index of the FST. """ for state in states: self.add_state(state, idx) def get_renamed_state(self, state: State, idx: int) -> State: - """ - Get the renaming. + """Renames the given state. Parameters ---------- - state : State - The state to rename - idx : int - The index of the FST + state: + The state to rename. + idx: + The index of the FST. Returns ------- - new_name : State - Renamed state + The renamed state. """ renaming = self._state_renaming[(str(state), idx)] return to_state(renaming) diff --git a/pyformlang/pda/pda.py b/pyformlang/pda/pda.py index 706be8d..ef4e451 100644 --- a/pyformlang/pda/pda.py +++ b/pyformlang/pda/pda.py @@ -584,7 +584,7 @@ def intersection(self, other: DeterministicFiniteAutomaton) -> "PDA": deterministic finite automaton. Equivalent to: - >> pda & dfa + >>> pda & dfa Parameters ---------- @@ -697,7 +697,7 @@ def from_networkx(cls, graph: MultiDiGraph) -> "PDA": """Import a networkx graph into a PDA. The imported graph requires to have the good format, i.e. to come - from the function to_networkx. + from the function `to_networkx`. Parameters ---------- From ecf67cb5117ede0b50d0277b72625d6b8e69b005 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Fri, 3 Jan 2025 00:56:52 +0300 Subject: [PATCH 15/21] include Symbol in rsa package, ignore empty __init__ files with Ruff --- pyformlang/rsa/__init__.py | 5 ++++- ruff.toml | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyformlang/rsa/__init__.py b/pyformlang/rsa/__init__.py index 1dc2d4e..9cbeaae 100644 --- a/pyformlang/rsa/__init__.py +++ b/pyformlang/rsa/__init__.py @@ -9,6 +9,8 @@ A recursive automaton. :class:`pyformlang.rsa.Box`: A constituent part of a recursive automaton. +:class:`pyformlang.rsa.Symbol`: + A nonterminal of the Box. References ---------- @@ -20,10 +22,11 @@ """ from .recursive_automaton import RecursiveAutomaton -from .box import Box +from .box import Box, Symbol __all__ = [ "RecursiveAutomaton", "Box", + "Symbol", ] diff --git a/ruff.toml b/ruff.toml index 40789bc..9faafcd 100644 --- a/ruff.toml +++ b/ruff.toml @@ -44,6 +44,7 @@ select = [ ] ignore = [ + "D104", "D416", "ANN401", ] From 1470cb9940ac85d201e534a1d00d06e583e8f80a Mon Sep 17 00:00:00 2001 From: bygu4 Date: Fri, 3 Jan 2025 01:15:34 +0300 Subject: [PATCH 16/21] update package file docs --- pyformlang/__init__.py | 27 +++++++++++++---------- pyformlang/cfg/__init__.py | 16 +++++++------- pyformlang/fcfg/__init__.py | 24 ++++++++++---------- pyformlang/finite_automaton/__init__.py | 24 ++++++++++---------- pyformlang/fst/__init__.py | 10 ++++----- pyformlang/indexed_grammar/__init__.py | 22 +++++++++--------- pyformlang/pda/__init__.py | 12 +++++----- pyformlang/regular_expression/__init__.py | 6 ++--- pyformlang/rsa/__init__.py | 6 ++--- 9 files changed, 75 insertions(+), 72 deletions(-) diff --git a/pyformlang/__init__.py b/pyformlang/__init__.py index a5503f4..d4d721f 100644 --- a/pyformlang/__init__.py +++ b/pyformlang/__init__.py @@ -1,29 +1,32 @@ """Pyformlang is a python module to perform operation on formal languages. -How to use the documentation ----------------------------- -Documentation is available in two formats: docstrings directly -in the code and a readthedocs website: https://pyformlang.readthedocs.io. +:mod:`pyformlang` +================= Available subpackages --------------------- -:mod:`pyformlang.regular_expression`: +:mod:`~pyformlang.regular_expression`: Regular Expressions. -:mod:`pyformlang.finite_automaton`: +:mod:`~pyformlang.finite_automaton`: Finite Automata (deterministic, non-deterministic, with/without epsilon transitions). -:mod:`pyformlang.fst`: +:mod:`~pyformlang.fst`: Finite State Transducers. -:mod:`pyformlang.cfg`: +:mod:`~pyformlang.cfg`: Context-Free Grammars. -:mod:`pyformlang.pda`: +:mod:`~pyformlang.pda`: Push-Down Automata. -:mod:`pyformlang.indexed_grammar`: +:mod:`~pyformlang.indexed_grammar`: Indexed Grammars. -:mod:`pyformlang.rsa`: +:mod:`~pyformlang.rsa`: Recursive Automata. -:mod:`pyformlang.fcfg`: +:mod:`~pyformlang.fcfg`: Context-Free Grammars with Features. + +How to use the documentation +---------------------------- +Documentation is available in two formats: docstrings directly +in the code and a readthedocs website: https://pyformlang.readthedocs.io. """ from . import finite_automaton diff --git a/pyformlang/cfg/__init__.py b/pyformlang/cfg/__init__.py index dc938ea..ae36c51 100644 --- a/pyformlang/cfg/__init__.py +++ b/pyformlang/cfg/__init__.py @@ -5,21 +5,21 @@ Available Classes ----------------- -:class:`pyformlang.cfg.CFG`: +:class:`~pyformlang.cfg.CFG`: The main context-free grammar class. -:class:`pyformlang.cfg.Production`: +:class:`~pyformlang.cfg.Production`: A class to represent a production in a CFG. -:class:`pyformlang.cfg.CFGObject`: +:class:`~pyformlang.cfg.CFGObject`: A general CFG object representation. -:class:`pyformlang.cfg.Variable`: +:class:`~pyformlang.cfg.Variable`: A variable in context-free grammar. -:class:`pyformlang.cfg.Terminal`: +:class:`~pyformlang.cfg.Terminal`: A terminal in context-free grammar. -:class:`pyformlang.cfg.Epsilon`: +:class:`~pyformlang.cfg.Epsilon`: The epsilon symbol (special terminal). -:class:`pyformlang.cfg.ParseTree`: +:class:`~pyformlang.cfg.ParseTree`: A parse tree of the grammar. -:class:`pyformlang.cfg.DerivationDoesNotExistError`: +:class:`~pyformlang.cfg.DerivationDoesNotExistError`: An exception that occurs if the given word cannot be derived from the grammar. """ diff --git a/pyformlang/fcfg/__init__.py b/pyformlang/fcfg/__init__.py index 961ee0f..57e8cb3 100644 --- a/pyformlang/fcfg/__init__.py +++ b/pyformlang/fcfg/__init__.py @@ -5,29 +5,29 @@ Available Classes ----------------- -:class:`pyformlang.fcfg.FCFG`: +:class:`~pyformlang.fcfg.FCFG`: A context-free grammar with features. -:class:`pyformlang.fcfg.FeatureStructure`: +:class:`~pyformlang.fcfg.FeatureStructure`: A feature structure containing constraints. -:class:`pyformlang.fcfg.FeatureProduction`: +:class:`~pyformlang.fcfg.FeatureProduction`: A production in the FCFG. -:class:`pyformlang.fcfg.CFGObject`: +:class:`~pyformlang.fcfg.CFGObject`: The general CFG object used in FCFG. -:class:`pyformlang.fcfg.Variable`: +:class:`~pyformlang.fcfg.Variable`: A variable in FCFG. -:class:`pyformlang.fcfg.Terminal`: +:class:`~pyformlang.fcfg.Terminal`: A terminal in FCFG. -:class:`pyformlang.fcfg.Epsilon`: +:class:`~pyformlang.fcfg.Epsilon`: The epsilon terminal. -:class:`pyformlang.fcfg.ParseTree`: +:class:`~pyformlang.fcfg.ParseTree`: A parse tree of the grammar. -:class:`pyformlang.fcfg.NotParsableError`: +:class:`~pyformlang.fcfg.NotParsableError`: An exception that occurs when the given grammar cannot be parsed. -:class:`pyformlang.fcfg.ContentAlreadyExistsError`: +:class:`~pyformlang.fcfg.ContentAlreadyExistsError`: An exception raised when trying to add content that already exists. -:class:`pyformlang.fcfg.FeatureStructuresNotCompatibleError`: +:class:`~pyformlang.fcfg.FeatureStructuresNotCompatibleError`: An exception raised when trying to unify incompatible structures. -:class:`pyformlang.fcfg.PathDoesNotExistError`: +:class:`~pyformlang.fcfg.PathDoesNotExistError`: An exception raised when looking for a path that does not exist. Sources diff --git a/pyformlang/finite_automaton/__init__.py b/pyformlang/finite_automaton/__init__.py index b1ebfa5..0b66c82 100644 --- a/pyformlang/finite_automaton/__init__.py +++ b/pyformlang/finite_automaton/__init__.py @@ -5,30 +5,30 @@ Available Classes ----------------- -:class:`pyformlang.finite_automaton.FiniteAutomaton`: +:class:`~pyformlang.finite_automaton.FiniteAutomaton`: An abstract class representing the general finite automaton. -:class:`pyformlang.finite_automaton.EpsilonNFA`: +:class:`~pyformlang.finite_automaton.EpsilonNFA`: A non-deterministic finite automaton with epsilon transitions. -:class:`pyformlang.finite_automaton.NondeterministicFiniteAutomaton`: +:class:`~pyformlang.finite_automaton.NondeterministicFiniteAutomaton`: A non-deterministic finite automaton without epsilon transitions. -:class:`pyformlang.finite_automaton.DeterministicFiniteAutomaton`: +:class:`~pyformlang.finite_automaton.DeterministicFiniteAutomaton`: A deterministic finite automaton. -:class:`pyformlang.finite_automaton.State`: +:class:`~pyformlang.finite_automaton.State`: A state (or node) in an automaton. -:class:`pyformlang.finite_automaton.Symbol`: +:class:`~pyformlang.finite_automaton.Symbol`: A symbol (part of the alphabet) in an automaton. -:class:`pyformlang.finite_automaton.Epsilon`: +:class:`~pyformlang.finite_automaton.Epsilon`: The epsilon (or empty) symbol. -:class:`pyformlang.finite_automaton.TransitionFunction`: +:class:`~pyformlang.finite_automaton.TransitionFunction`: An interface representing the transition function in a finite automaton. -:class:`pyformlang.finite_automaton.NondeterministicTransitionFunction`: +:class:`~pyformlang.finite_automaton.NondeterministicTransitionFunction`: A non-deterministic transition function. -:class:`pyformlang.finite_automaton.DeterministicTransitionFunction`: +:class:`~pyformlang.finite_automaton.DeterministicTransitionFunction`: A deterministic transition function. -:class:`pyformlang.finite_automaton.DuplicateTransitionError`: +:class:`~pyformlang.finite_automaton.DuplicateTransitionError`: An error that occurs when trying to add a non-deterministic edge to a deterministic automaton. -:class:`pyformlang.finite_automaton.InvalidEpsilonTransitionError`: +:class:`~pyformlang.finite_automaton.InvalidEpsilonTransitionError`: An exception that occurs when adding an epsilon transition to a non-epsilon NFA. """ diff --git a/pyformlang/fst/__init__.py b/pyformlang/fst/__init__.py index 3d82a1a..b70cb7f 100644 --- a/pyformlang/fst/__init__.py +++ b/pyformlang/fst/__init__.py @@ -5,15 +5,15 @@ Available Classes ----------------- -:class:`pyformlang.fst.FST`: +:class:`~pyformlang.fst.FST`: A Finite State Transducer. -:class:`pyformlang.fst.TransitionFunction`: +:class:`~pyformlang.fst.TransitionFunction`: A transition function in FST. -:class:`pyformlang.fst.State`: +:class:`~pyformlang.fst.State`: A state in FST. -:class:`pyformlang.fst.Symbol`: +:class:`~pyformlang.fst.Symbol`: A symbol in FST. -:class:`pyformlang.fst.Epsilon`: +:class:`~pyformlang.fst.Epsilon`: An epsilon symbol. """ diff --git a/pyformlang/indexed_grammar/__init__.py b/pyformlang/indexed_grammar/__init__.py index 73fb0ee..78a2fab 100644 --- a/pyformlang/indexed_grammar/__init__.py +++ b/pyformlang/indexed_grammar/__init__.py @@ -5,27 +5,27 @@ Available Classes ----------------- -:class:`pyformlang.indexed_grammar.IndexedGrammar`: +:class:`~pyformlang.indexed_grammar.IndexedGrammar`: An indexed grammar. -:class:`pyformlang.indexed_grammar.Rules`: +:class:`~pyformlang.indexed_grammar.Rules`: A representation of a set of indexed grammar rules. -:class:`pyformlang.indexed_grammar.ReducedRule`: +:class:`~pyformlang.indexed_grammar.ReducedRule`: An indexed grammar rule of any of possible forms. -:class:`pyformlang.indexed_grammar.ConsumptionRule`: +:class:`~pyformlang.indexed_grammar.ConsumptionRule`: A consumption rule, consuming something from the stack. -:class:`pyformlang.indexed_grammar.EndRule`: +:class:`~pyformlang.indexed_grammar.EndRule`: An end rule, turning a variable into a terminal. -:class:`pyformlang.indexed_grammar.ProductionRule`: +:class:`~pyformlang.indexed_grammar.ProductionRule`: A production rule, pushing something on the stack. -:class:`pyformlang.indexed_grammar.DuplicationRule`: +:class:`~pyformlang.indexed_grammar.DuplicationRule`: A duplication rule, duplicating the stack. -:class:`pyformlang.indexed_grammar.CFGObject`: +:class:`~pyformlang.indexed_grammar.CFGObject`: A general CFG object used in indexed grammars. -:class:`pyformlang.indexed_grammar.Variable`: +:class:`~pyformlang.indexed_grammar.Variable`: A variable in indexed grammars. -:class:`pyformlang.indexed_grammar.Terminal`: +:class:`~pyformlang.indexed_grammar.Terminal`: A terminal in indexed grammars. -:class:`pyformlang.indexed_grammar.Epsilon`: +:class:`~pyformlang.indexed_grammar.Epsilon`: An epsilon terminal. """ diff --git a/pyformlang/pda/__init__.py b/pyformlang/pda/__init__.py index ff339ec..e12bc50 100644 --- a/pyformlang/pda/__init__.py +++ b/pyformlang/pda/__init__.py @@ -5,17 +5,17 @@ Available Classes ----------------- -:class:`pyformlang.pda.PDA`: +:class:`~pyformlang.pda.PDA`: A push-down automaton. -:class:`pyformlang.pda.TransitionFunction`: +:class:`~pyformlang.pda.TransitionFunction`: A transition function in push-down automaton. -:class:`pyformlang.pda.State`: +:class:`~pyformlang.pda.State`: A state in push-down automaton. -:class:`pyformlang.pda.Symbol`: +:class:`~pyformlang.pda.Symbol`: A symbol in push-down automaton. -:class:`pyformlang.pda.StackSymbol`: +:class:`~pyformlang.pda.StackSymbol`: A stack symbol in push-down automaton. -:class:`pyformlang.pda.Epsilon`: +:class:`~pyformlang.pda.Epsilon`: The epsilon symbol. """ diff --git a/pyformlang/regular_expression/__init__.py b/pyformlang/regular_expression/__init__.py index 05c11c7..483193a 100644 --- a/pyformlang/regular_expression/__init__.py +++ b/pyformlang/regular_expression/__init__.py @@ -8,11 +8,11 @@ Available Classes ----------------- -:class:`pyformlang.regular_expression.Regex`: +:class:`~pyformlang.regular_expression.Regex`: A regular expression. -:class:`pyformlang.regular_expression.PythonRegex`: +:class:`~pyformlang.regular_expression.PythonRegex`: A regular expression closer to Python format. -:class:`pyformlang.regular_expression.MisformedRegexError`: +:class:`~pyformlang.regular_expression.MisformedRegexError`: An error occurring when the input regex is incorrect. """ diff --git a/pyformlang/rsa/__init__.py b/pyformlang/rsa/__init__.py index 9cbeaae..09f4926 100644 --- a/pyformlang/rsa/__init__.py +++ b/pyformlang/rsa/__init__.py @@ -5,11 +5,11 @@ Available Classes ----------------- -:class:`pyformlang.rsa.RecursiveAutomaton`: +:class:`~pyformlang.rsa.RecursiveAutomaton`: A recursive automaton. -:class:`pyformlang.rsa.Box`: +:class:`~pyformlang.rsa.Box`: A constituent part of a recursive automaton. -:class:`pyformlang.rsa.Symbol`: +:class:`~pyformlang.rsa.Symbol`: A nonterminal of the Box. References From e9e62a4347f3d80eefe0b2915e70022edde147a5 Mon Sep 17 00:00:00 2001 From: bygu4 Date: Fri, 3 Jan 2025 03:13:42 +0300 Subject: [PATCH 17/21] update indexed_grammar docs --- pyformlang/cfg/formal_grammar.py | 28 +---- .../deterministic_finite_automaton.py | 4 +- pyformlang/finite_automaton/epsilon_nfa.py | 2 +- .../finite_automaton/finite_automaton.py | 4 +- .../indexed_grammar/consumption_rule.py | 69 ++++------- .../indexed_grammar/duplication_rule.py | 60 ++++----- pyformlang/indexed_grammar/end_rule.py | 63 +++------- pyformlang/indexed_grammar/indexed_grammar.py | 115 +++++++----------- pyformlang/indexed_grammar/production_rule.py | 74 ++++------- pyformlang/indexed_grammar/reduced_rule.py | 64 +++------- pyformlang/indexed_grammar/rule_ordering.py | 97 ++++++++------- pyformlang/indexed_grammar/rules.py | 101 ++++++--------- pyformlang/indexed_grammar/utils.py | 52 +++++--- pyformlang/pda/pda.py | 8 +- pyformlang/regular_expression/regex.py | 5 +- 15 files changed, 279 insertions(+), 467 deletions(-) diff --git a/pyformlang/cfg/formal_grammar.py b/pyformlang/cfg/formal_grammar.py index 88d467e..5ee3958 100644 --- a/pyformlang/cfg/formal_grammar.py +++ b/pyformlang/cfg/formal_grammar.py @@ -38,42 +38,22 @@ def __init__(self, @property def variables(self) -> Set[Variable]: - """Gets the variables of the grammar. - - Returns - ------- - The variables of the grammar. - """ + """Gets the variables of the grammar.""" return self._variables @property def terminals(self) -> Set[Terminal]: - """Gets the terminals of the grammar. - - Returns - ------- - The terminals of the grammar. - """ + """Gets the terminals of the grammar.""" return self._terminals @property def productions(self) -> Set[Production]: - """Gets the productions of the grammar. - - Returns - ------- - The productions of the grammar. - """ + """Gets the productions of the grammar.""" return self._productions @property def start_symbol(self) -> Optional[Variable]: - """Gets the start symbol of the grammar. - - Returns - ------- - The start symbol of the grammar. - """ + """Gets the start symbol of the grammar.""" return self._start_symbol def add_production(self, production: Production) -> None: diff --git a/pyformlang/finite_automaton/deterministic_finite_automaton.py b/pyformlang/finite_automaton/deterministic_finite_automaton.py index 9dc77a3..ca7bf98 100644 --- a/pyformlang/finite_automaton/deterministic_finite_automaton.py +++ b/pyformlang/finite_automaton/deterministic_finite_automaton.py @@ -113,7 +113,7 @@ def remove_start_state(self, state: Hashable) -> int: The initial state to remove. Returns - ---------- + ------- 1 is correctly removed. Examples @@ -408,7 +408,7 @@ def is_equivalent_to(self, other: "DeterministicFiniteAutomaton") -> bool: Returns ------- - Whether the two automata are equivalent or not + Whether the two automata are equivalent or not. Examples -------- diff --git a/pyformlang/finite_automaton/epsilon_nfa.py b/pyformlang/finite_automaton/epsilon_nfa.py index d4b4422..c25e058 100644 --- a/pyformlang/finite_automaton/epsilon_nfa.py +++ b/pyformlang/finite_automaton/epsilon_nfa.py @@ -395,7 +395,7 @@ def __and__(self, other: "EpsilonNFA") -> "EpsilonNFA": The other Epsilon NFA. Returns - --------- + ------- The intersection of the two Epsilon NFAs. """ return self.get_intersection(other) diff --git a/pyformlang/finite_automaton/finite_automaton.py b/pyformlang/finite_automaton/finite_automaton.py index 5962541..c0fbda8 100644 --- a/pyformlang/finite_automaton/finite_automaton.py +++ b/pyformlang/finite_automaton/finite_automaton.py @@ -80,7 +80,7 @@ def add_transition(self, Returns ------- - Always 1 + Always 1. Examples -------- @@ -109,7 +109,7 @@ def add_transitions(self, transitions_list: \ Returns ------- - Always 1 + Always 1. Examples -------- diff --git a/pyformlang/indexed_grammar/consumption_rule.py b/pyformlang/indexed_grammar/consumption_rule.py index b59609f..8e3ded6 100644 --- a/pyformlang/indexed_grammar/consumption_rule.py +++ b/pyformlang/indexed_grammar/consumption_rule.py @@ -1,6 +1,6 @@ -""" -Representation of a consumption rule, i.e. a rule that consumes something on \ -the stack +"""Representation of a consumption rule. + +A rule that consumes something on the stack. """ from typing import List, Set, Hashable, Any @@ -12,91 +12,67 @@ class ConsumptionRule(ReducedRule): - """ Contains a representation of a consumption rule, i.e. a rule of the \ - form: - C[ r sigma] -> B[sigma] + """Representation of a consumption rule. + + It is a rule of form: + C[r sigma] -> B[sigma] Parameters ---------- - f_param : any - The consumed symbol - left : any - The non terminal on the left (here C) - right : any - The non terminal on the right (here B) + f_param: + The consumed symbol, "r" here. + left_term: + The non terminal on the left, "C" here. + right_term: + The non terminal on the right, "B" here. """ def __init__(self, f_param: Hashable, left_term: Hashable, right_term: Hashable) -> None: + """Initializes the consumption rule.""" self._f = to_terminal(f_param) self._left_term = to_variable(left_term) self._right_term = to_variable(right_term) @property def f_parameter(self) -> Terminal: - """Gets the symbol which is consumed - - Returns - ---------- - f : any - The symbol being consumed by the rule - """ + """Gets the symbol consumed by the rule.""" return self._f @property def production(self) -> Terminal: + """Gets the symbol produced by the rule.""" raise NotImplementedError @property def left_term(self) -> Variable: - """Gets the symbol on the left of the rule - - left : any - The left symbol of the rule - """ + """Gets a nonterminal on the left of the rule.""" return self._left_term @property def right_term(self) -> Variable: - """Gets the symbol on the right of the rule - - right : any - The right symbol - """ + """Gets a nonterminal on the right of the rule.""" return self._right_term @property def right_terms(self) -> List[CFGObject]: - """Gives the non-terminals on the right of the rule - - Returns - --------- - right_terms : iterable of any - The right terms of the rule - """ + """Gets a list of right terms of the rule.""" return [self._right_term] @property def non_terminals(self) -> Set[Variable]: - """Gets the non-terminals used in the rule - - non_terminals : iterable of any - The non_terminals used in the rule - """ + """Gets the nonterminals used in the rule.""" return {self._left_term, self._right_term} @property def terminals(self) -> Set[Terminal]: - """Gets the terminals used in the rule - - terminals : set of any - The terminals used in the rule - """ + """Gets the terminals used in the rule.""" return {self._f} def __eq__(self, other: Any) -> bool: + """Checks if the rule is equal to the given object.""" if not isinstance(other, ConsumptionRule): return False return other.left_term == self.left_term \ @@ -104,4 +80,5 @@ def __eq__(self, other: Any) -> bool: and other.f_parameter == self.f_parameter def __repr__(self) -> str: + """Gets a string representation of the rule.""" return f"{self._left_term} [ {self._f} ] -> {self._right_term}" diff --git a/pyformlang/indexed_grammar/duplication_rule.py b/pyformlang/indexed_grammar/duplication_rule.py index de9238f..eb5e7df 100644 --- a/pyformlang/indexed_grammar/duplication_rule.py +++ b/pyformlang/indexed_grammar/duplication_rule.py @@ -1,5 +1,6 @@ -""" -A representation of a duplication rule, i.e. a rule that duplicates the stack +"""Representation of a duplication rule. + +A rule that duplicates the stack. """ from typing import List, Set, Hashable, Any @@ -11,90 +12,73 @@ class DuplicationRule(ReducedRule): - """Represents a duplication rule, i.e. a rule of the form: + """Representation of a duplication rule. + + It is a rule of form: A[sigma] -> B[sigma] C[sigma] Parameters ---------- - left_term : any - The non-terminal on the left of the rule (A here) - right_term0 : any - The first non-terminal on the right of the rule (B here) - right_term1 : any - The second non-terminal on the right of the rule (C here) + left_term: + The non-terminal on the left of the rule, "A" here. + right_term0: + The first non-terminal on the right of the rule, "B" here. + right_term1: + The second non-terminal on the right of the rule, "C" here. """ def __init__(self, left_term: Hashable, right_term0: Hashable, right_term1: Hashable) -> None: + """Initializes the duplication rule.""" self._left_term = to_variable(left_term) self._right_terms = (to_variable(right_term0), to_variable(right_term1)) @property def f_parameter(self) -> Terminal: + """Gets the symbol consumed by the rule.""" raise NotImplementedError @property def production(self) -> Terminal: + """Gets the symbol produced by the rule.""" raise NotImplementedError @property def left_term(self) -> Variable: - """Gives the non-terminal on the left of the rule - - Returns - --------- - left_term : any - The left term of the rule - """ + """Gets a nonterminal on the left of the rule.""" return self._left_term @property def right_term(self) -> CFGObject: + """Gets the single right term of the rule.""" raise NotImplementedError @property def right_terms(self) -> List[CFGObject]: - """Gives the non-terminals on the right of the rule - - Returns - --------- - right_terms : iterable of any - The right terms of the rule - """ + """Gets a list of right terms of the rule.""" return list(self._right_terms) @property def non_terminals(self) -> Set[Variable]: - """Gives the set of non-terminals used in this rule - - Returns - --------- - non_terminals : iterable of any - The non terminals used in this rule - """ + """Gets the nonterminals used in the rule.""" return {self._left_term, *self._right_terms} @property def terminals(self) -> Set[Terminal]: - """Gets the terminals used in the rule - - Returns - ---------- - terminals : set of any - The terminals used in this rule - """ + """Gets the terminals used in the rule.""" return set() def __eq__(self, other: Any) -> bool: + """Checks if the rule is equal to the given object.""" if not isinstance(other, DuplicationRule): return False return other.left_term == self._left_term \ and other.right_terms == self.right_terms def __repr__(self) -> str: - """Gives a string representation of the rule, ignoring the sigmas""" + """Gets a string representation of the rule.""" return f"{self._left_term} -> " \ + f"{self._right_terms[0]} {self._right_terms[1]}" diff --git a/pyformlang/indexed_grammar/end_rule.py b/pyformlang/indexed_grammar/end_rule.py index 1433ca9..a776932 100644 --- a/pyformlang/indexed_grammar/end_rule.py +++ b/pyformlang/indexed_grammar/end_rule.py @@ -1,5 +1,6 @@ -""" -Represents a end rule, i.e. a rule which give only a terminal +"""Representation of an end rule. + +A rule which gives only a terminal. """ from typing import List, Set, Hashable, Any @@ -11,90 +12,66 @@ class EndRule(ReducedRule): - """Represents an end rule, i.e. a rule of the form: + """Representation of an end rule. + + It is a rule of form: A[sigma] -> a Parameters ----------- - left : any - The non-terminal on the left, "A" here - right : any - The terminal on the right, "a" here + left_term: + The non-terminal on the left, "A" here. + right_term: + The terminal on the right, "a" here. """ def __init__(self, left_term: Hashable, right_term: Hashable) -> None: + """Initializes the end rule.""" self._left_term = to_variable(left_term) self._right_term = to_terminal(right_term) @property def f_parameter(self) -> Terminal: + """Gets the symbol consumed by the rule.""" raise NotImplementedError @property def production(self) -> Terminal: + """Gets the symbol produced by the rule.""" raise NotImplementedError @property def left_term(self) -> Variable: - """Gets the non-terminal on the left of the rule - - Returns - --------- - left_term : any - The left non-terminal of the rule - """ + """Gets a nonterminal on the left of the rule.""" return self._left_term @property def right_term(self) -> Terminal: - """Gets the terminal on the right of the rule - - Returns - ---------- - right_term : any - The right terminal of the rule - """ + """Gets a terminal on the right of the rule.""" return self._right_term @property def right_terms(self) -> List[CFGObject]: - """Gives the terminals on the right of the rule - - Returns - --------- - right_terms : iterable of any - The right terms of the rule - """ + """Gets a list of right terms of the rule.""" return [self._right_term] @property def non_terminals(self) -> Set[Variable]: - """Gets the non-terminals used - - Returns - ---------- - non_terminals : iterable of any - The non terminals used in this rule - """ + """Gets the nonterminals used in the rule.""" return {self._left_term} @property def terminals(self) -> Set[Terminal]: - """Gets the terminals used - - Returns - ---------- - terminals : set of any - The terminals used in this rule - """ + """Gets the terminals used in the rule.""" return {self._right_term} def __eq__(self, other: Any) -> bool: + """Checks if the rule is equal to the given object.""" if not isinstance(other, EndRule): return False return other.left_term == self.left_term \ and other.right_term == self.right_term def __repr__(self) -> str: - """Gets the string representation of the rule""" + """Gets a string representation of the rule.""" return f"{self._left_term} -> {self._right_term}" diff --git a/pyformlang/indexed_grammar/indexed_grammar.py b/pyformlang/indexed_grammar/indexed_grammar.py index 0037222..0be3c6e 100644 --- a/pyformlang/indexed_grammar/indexed_grammar.py +++ b/pyformlang/indexed_grammar/indexed_grammar.py @@ -1,6 +1,4 @@ -""" -Representation of an indexed grammar -""" +"""Representation of an indexed grammar.""" # pylint: disable=cell-var-from-loop @@ -20,19 +18,20 @@ class IndexedGrammar: - """ Describes an indexed grammar. + """Representation of an indexed grammar. Parameters ---------- - rules : :class:`~pyformlang.indexed_grammar.Rules` - The rules of the grammar, in reduced form put into a Rule - start_variable : Any, optional - The start symbol of the indexed grammar + rules: + The rules of the grammar. + start_variable: + The start symbol of the indexed grammar. """ def __init__(self, rules: Rules, start_variable: Hashable = "S") -> None: + """Initializes the indexed grammar.""" self._rules = rules self._start_variable = to_variable(start_variable) # Precompute all non-terminals @@ -54,44 +53,32 @@ def __init__(self, @property def rules(self) -> Rules: - """ Get the rules of the grammar """ + """Gets the rules of the grammar.""" return self._rules @property def start_variable(self) -> Variable: - """ Get the start variable of the grammar """ + """Gets the start variable of the grammar.""" return self._start_variable @property def non_terminals(self) -> Set[Variable]: - """Get all the non-terminals in the grammar - - Returns - ---------- - terminals : iterable of any - The non-terminals used in the grammar - """ + """Gets all the nonterminals in the grammar.""" return {self.start_variable} | self._rules.non_terminals @property def terminals(self) -> Set[Terminal]: - """Get all the terminals in the grammar - - Returns - ---------- - terminals : iterable of any - The terminals used in the grammar - """ + """Gets all the terminals in the grammar.""" return self._rules.terminals def _duplication_processing(self, rule: DuplicationRule) \ -> Tuple[bool, bool]: - """Processes a duplication rule + """Processes a duplication rule. Parameters ---------- - rule : :class:`~pyformlang.indexed_grammar.DuplicationRule` - The duplication rule to process + rule: + The duplication rule to process. """ was_modified = False need_stop = False @@ -127,12 +114,12 @@ def _duplication_processing(self, rule: DuplicationRule) \ def _production_process(self, rule: ProductionRule) \ -> Tuple[bool, bool]: - """Processes a production rule + """Processes a production rule. Parameters ---------- - rule : :class:`~pyformlang.indexed_grammar.ProductionRule` - The production rule to process + rule: + The production rule to process. """ was_modified = False # f_rules contains the consumption rules associated with @@ -172,12 +159,11 @@ def _production_process(self, rule: ProductionRule) \ return was_modified, False def is_empty(self) -> bool: - """Checks whether the grammar generates a word or not + """Checks whether the grammar generates a word or not. Returns - ---------- - is_empty : bool - Whether the grammar is empty or not + ------- + Whether the grammar is empty or not. """ # To know when no more modification are done was_modified = True @@ -202,15 +188,15 @@ def is_empty(self) -> bool: return True def __bool__(self) -> bool: + """Checks whether the grammar is empty or not.""" return not self.is_empty() def get_reachable_non_terminals(self) -> Set[Variable]: - """ Get the reachable symbols + """Gets the reachable symbols in the grammar. Returns - ---------- - reachables : set of any - The reachable symbols from the start state + ------- + The reachable symbols from the start state. """ # Preprocess reachable_from: Dict[Variable, Set[CFGObject]] = {} @@ -250,12 +236,11 @@ def get_reachable_non_terminals(self) -> Set[Variable]: return reachables def get_generating_non_terminals(self) -> Set[Variable]: - """ Get the generating symbols + """Gets the generating symbols in the grammar. Returns - ---------- - generating : set of any - The generating symbols from the start state + ------- + The generating symbols from the start state. """ # Preprocess generating_from: Dict[Variable, Set[Variable]] = {} @@ -322,15 +307,14 @@ def _preprocess_rules_generating( to_process.append(left) def remove_useless_rules(self) -> "IndexedGrammar": - """ Remove useless rules in the grammar + """Removes useless rules in the grammar. - More precisely, we remove rules which do not contain only generating \ - or reachable non terminals. + More precisely, we remove rules which do not contain only generating + or reachable nonterminals. Returns - ---------- - i_grammar : :class:`~pyformlang.indexed_grammar.IndexedGrammar` - The indexed grammar which useless rules + ------- + The indexed grammar without useless rules. """ l_rules = [] generating = self.get_generating_non_terminals() @@ -365,28 +349,19 @@ def remove_useless_rules(self) -> "IndexedGrammar": return IndexedGrammar(rules) def intersection(self, other: FST) -> "IndexedGrammar": - """ Computes the intersection of the current indexed grammar with the \ - other object + """Computes the intersection of indexed grammar with the given FST. - Equivalent to - -------------- - >> indexed_grammar and regex + Equivalent to: + >>> indexed_grammar & fst Parameters ---------- - other : any - The object to intersect with + other: + The Finite State Transducer to intersect with. Returns - ---------- - i_grammar : :class:`~pyformlang.indexed_grammar.IndexedGrammar` - The indexed grammar which useless rules - - Raises - ------ - NotImplementedError - When trying to intersection with something else than a regular - expression or a finite automaton + ------- + The indexed grammar resulting in the intersection. """ new_rules: List[ReducedRule] = [EndRule("T", "epsilon")] self._extract_consumption_rules_intersection(other, new_rules) @@ -400,18 +375,16 @@ def intersection(self, other: FST) -> "IndexedGrammar": return IndexedGrammar(rules).remove_useless_rules() def __and__(self, other: FST) -> "IndexedGrammar": - """ Computes the intersection of the current indexed grammar with the - other object + """Computes the intersection of indexed grammar with the given FST. Parameters ---------- - other : any - The object to intersect with + other: + The Finite State Transducer to intersect with. Returns - ---------- - i_grammar : :class:`~pyformlang.indexed_grammar.IndexedGrammar` - The indexed grammar which useless rules + ------- + The indexed grammar resulting in the intersection. """ return self.intersection(other) diff --git a/pyformlang/indexed_grammar/production_rule.py b/pyformlang/indexed_grammar/production_rule.py index a8bfc47..83c7b22 100644 --- a/pyformlang/indexed_grammar/production_rule.py +++ b/pyformlang/indexed_grammar/production_rule.py @@ -1,5 +1,6 @@ -""" -Represents a production rule, i.e. a rule that pushed on the stack +"""Representation of a production rule. + +A rule that pushes on the stack. """ from typing import List, Set, Hashable, Any @@ -11,98 +12,67 @@ class ProductionRule(ReducedRule): - """Represents a production rule, i.e. a rule of the form: + """Representation of a production rule. + + It is a rule of form: A[sigma] -> B[r sigma] Parameters ---------- - left : any - The non-terminal on the left side of the rule, A here - right : any - The non-terminal on the right side of the rule, B here - prod : any - The terminal used in the rule, "r" here + left_term: + The non-terminal on the left side of the rule, "A" here. + right_term: + The non-terminal on the right side of the rule, "B" here. + production: + The terminal produced by the rule, "r" here. """ def __init__(self, left_term: Hashable, right_term: Hashable, production: Hashable) -> None: + """Initializes the production rule.""" self._left_term = to_variable(left_term) self._right_term = to_variable(right_term) self._production = to_terminal(production) @property def f_parameter(self) -> Terminal: + """Gets the symbol consumed by the rule.""" raise NotImplementedError @property def production(self) -> Terminal: - """Gets the terminal used in the production - - Returns - ---------- - production : any - The production used in this rule - """ + """Gets the symbol produced by the rule.""" return self._production @property def left_term(self) -> Variable: - """Gets the non-terminal on the left side of the rule - - Returns - ---------- - left_term : any - The left term of this rule - """ + """Gets a nonterminal on the left of the rule.""" return self._left_term @property def right_term(self) -> Variable: - """Gets the non-terminal on the right side of the rule - - Returns - ---------- - right_term : any - The right term used in this rule - """ + """Gets a nonterminal on the right of the rule.""" return self._right_term @property def right_terms(self) -> List[CFGObject]: - """Gives the non-terminals on the right of the rule - - Returns - --------- - right_terms : iterable of any - The right terms of the rule - """ + """Gets a list of right terms of the rule.""" return [self._right_term] @property def non_terminals(self) -> Set[Variable]: - """Gets the non-terminals used in the rule - - Returns - ---------- - non_terminals : any - The non terminals used in this rules - """ + """Gets the nonterminals used in the rule.""" return {self._left_term, self._right_term} @property def terminals(self) -> Set[Terminal]: - """Gets the terminals used in the rule - - Returns - ---------- - terminals : any - The terminals used in this rule - """ + """Gets the terminals used in the rule.""" return {self._production} def __eq__(self, other: Any) -> bool: + """Checks if the rule is equal to the given object.""" if not isinstance(other, ProductionRule): return False return other.left_term == self.left_term \ @@ -110,5 +80,5 @@ def __eq__(self, other: Any) -> bool: and other.production == self.production def __repr__(self) -> str: - """Gets the string representation of the rule""" + """Gets a string representation of the rule.""" return f"{self._left_term} -> {self._right_term} [ {self._production} ]" diff --git a/pyformlang/indexed_grammar/reduced_rule.py b/pyformlang/indexed_grammar/reduced_rule.py index 39c6c50..2136b7a 100644 --- a/pyformlang/indexed_grammar/reduced_rule.py +++ b/pyformlang/indexed_grammar/reduced_rule.py @@ -1,5 +1,6 @@ -""" -Representation of a reduced rule +"""Representation of a reduced rule of an indexed grammar. + +Rule of any of available forms. """ from typing import List, Set, Any @@ -9,8 +10,9 @@ class ReducedRule: - """Representation of all possible reduced forms. - They can be of four types : + """Representation of any of possible reduced forms. + + They can be of four types: * Consumption * Production * End @@ -20,87 +22,51 @@ class ReducedRule: @property @abstractmethod def f_parameter(self) -> Terminal: - """The f parameter - - Returns - ---------- - f : cfg.Terminal - The f parameter - """ + """Gets the symbol consumed by the rule.""" raise NotImplementedError @property @abstractmethod def production(self) -> Terminal: - """The production - - Returns - ---------- - right_terms : any - The production - """ + """Gets the symbol produced by the rule.""" raise NotImplementedError @property @abstractmethod def left_term(self) -> Variable: - """The left term - - Returns - ---------- - left_term : cfg.Variable - The left term of the rule - """ + """Gets a nonterminal on the left of the rule.""" raise NotImplementedError @property @abstractmethod def right_term(self) -> CFGObject: - """The unique right term - - Returns - ---------- - right_term : cfg.cfg_object.CFGObject - The unique right term of the rule - """ + """Gets the single right term of the rule.""" raise NotImplementedError @property @abstractmethod def right_terms(self) -> List[CFGObject]: - """The right terms - - Returns - ---------- - right_terms : list of cfg.cfg_object.CFGObject - The right terms of the rule - """ + """Gets a list of right terms of the rule.""" raise NotImplementedError @property @abstractmethod def non_terminals(self) -> Set[Variable]: - """Gets the non-terminals used in the rule - - terminals : set of cfg.Variable - The non-terminals used in the rule - """ + """Gets the nonterminals used in the rule.""" raise NotImplementedError @property @abstractmethod def terminals(self) -> Set[Terminal]: - """Gets the terminals used in the rule - - terminals : set of cfg.Terminal - The terminals used in the rule - """ + """Gets the terminals used in the rule.""" raise NotImplementedError @abstractmethod def __eq__(self, other: Any) -> bool: + """Checks if the rule is equal to the given object.""" raise NotImplementedError @abstractmethod def __repr__(self) -> str: + """Gets a string representation of the rule.""" raise NotImplementedError diff --git a/pyformlang/indexed_grammar/rule_ordering.py b/pyformlang/indexed_grammar/rule_ordering.py index 677c9f5..8c1110f 100644 --- a/pyformlang/indexed_grammar/rule_ordering.py +++ b/pyformlang/indexed_grammar/rule_ordering.py @@ -1,6 +1,4 @@ -""" -Representation of a way to order rules -""" +"""Representation of a way to order rules.""" from typing import List, Dict @@ -17,38 +15,42 @@ class RuleOrdering: - """A class to order rules in an indexed grammar + """A class to order rules in an indexed grammar. Parameters ---------- - rules : iterable of :class:`~pyformlang.indexed_grammar.ReducedRule` - The non consumption rules of the indexed grammar - conso_rules : dict of any to \ - :class:`~pyformlang.indexed_grammar.ConsumptionRule` - The consumption rules of the indexed grammar + rules: + The non consumption rules of the indexed grammar. + conso_rules: + The consumption rules of the indexed grammar. """ def __init__(self, rules: List[ReducedRule], conso_rules: Dict[Terminal, List[ConsumptionRule]]) -> None: + """Initializes the ordering of rules.""" self.rules = rules self.conso_rules = conso_rules def reverse(self) -> List[ReducedRule]: - """The reverser ordering, simply reverse the order. + """Gets the rules in reversed order. Returns - ---------- - new_rules : iterable of \ - :class:`~pyformlang.indexed_grammar.ReducedRule` - The reversed rules + ------- + The reversed list of rules. """ return self.rules[::1] def _get_graph(self) -> DiGraph: - """ Get the graph of the non-terminals in the rules. If there - there is a link between A and B (oriented), it means that modifying A - may modify B""" + """Gets the graph of the nonterminals in the rules. + + If there is a link between A and B (oriented), + it means that modifying A may modify B. + + Returns + ------- + A directed graph representing the rules. + """ di_graph = DiGraph() for rule in self.rules: if isinstance(rule, DuplicationRule): @@ -65,18 +67,16 @@ def _get_graph(self) -> DiGraph: return di_graph def order_by_core(self, reverse: bool = False) -> List[ReducedRule]: - """Order the rules using the core numbers + """Orders the rules using the core number. Parameters ---------- - reverse : bool - Boolean to know if we should reverse the order + reverse: + Whether to reverse the rule order. Returns - ---------- - new_rules : iterable of \ - :class:`~pyformlang.indexed_grammar.ReducedRule` - The rules ordered using core number + ------- + The rules ordered using core number. """ # Graph construction di_graph = self._get_graph() @@ -91,18 +91,16 @@ def order_by_core(self, reverse: bool = False) -> List[ReducedRule]: def order_by_arborescence(self, reverse: bool = True) \ -> List[ReducedRule]: - """Order the rules using the arborescence method. + """Orders the rules using the arborescence method. Parameters ---------- - reverse : bool - Boolean to know if we should reverse the order + reverse: + Whether to reverse the rule order. Returns - ---------- - new_rules : iterable of \ - :class:`~pyformlang.indexed_grammar.ReducedRule` - The rules ordered using core number + ------- + The list of ordered rules. """ di_graph = self._get_graph() arborescence = minimum_spanning_tree(di_graph.to_undirected()) @@ -131,33 +129,36 @@ def order_by_arborescence(self, reverse: bool = True) \ @staticmethod def _get_len_out(di_graph: DiGraph, rule: ReducedRule) -> int: - """Get the number of out edges of a rule (more exactly, the non \ - terminal at its left. + """Gets the number of out edges of a rule. + + More exactly, of the nonterminal at its left. Parameters ---------- - di_graph : DiGraph - A directed graph - rule : :class:`~pyformlang.indexed_grammar.ReducedRule` - The rule + di_graph: + A directed graph representing the rules. + rule: + A rule to get number of out edges of. + + Returns + ------- + A number of out edges of the rule. """ if rule.left_term in di_graph: return len(di_graph[rule.left_term]) return 0 def order_by_edges(self, reverse: bool = False) -> List[ReducedRule]: - """Order using the number of edges. + """Orders the rules using the number of edges. Parameters ---------- - reverse : bool - Boolean to know if we should reverse the order + reverse: + Whether to reverse the rule order. Returns - ---------- - new_rules : iterable of \ - :class:`~pyformlang.indexed_grammar.ReducedRule` - The rules ordered by number of edges + ------- + The rules ordered by number of edges. """ di_graph = self._get_graph() new_order = sorted(self.rules, key=lambda x: @@ -167,13 +168,11 @@ def order_by_edges(self, reverse: bool = False) -> List[ReducedRule]: return new_order def order_random(self) -> List[ReducedRule]: - """The random ordering + """Randomly orders the rules. Returns - ---------- - new_rules : iterable of \ - :class:`~pyformlang.indexed_grammar.ReducedRule` - The rules ordered at random + ------- + The rules in random order. """ shuffle(self.rules) return self.rules diff --git a/pyformlang/indexed_grammar/rules.py b/pyformlang/indexed_grammar/rules.py index f94aa85..7fd7db4 100644 --- a/pyformlang/indexed_grammar/rules.py +++ b/pyformlang/indexed_grammar/rules.py @@ -1,6 +1,4 @@ -""" -Representations of rules in a indexed grammar -""" +"""Representation of rules in the indexed grammar.""" from typing import Dict, List, Set, Tuple, Iterable, Hashable @@ -14,13 +12,13 @@ class Rules: - """Store a set of rules and manipulate them + """Class to store and manipulate indexed grammar rules. Parameters ---------- - rules : iterable of :class:`~pyformlang.indexed_grammar.ReducedRule` - A list of all the rules - optim : int + rules: + A list of all the rules. + optim: Optimization of the order of the rules 0 -> given order 1 -> reverse order @@ -34,6 +32,7 @@ class Rules: """ def __init__(self, rules: Iterable[ReducedRule], optim: int = 7) -> None: + """Initializes the rules of the indexed grammar.""" self._rules: List[ReducedRule] = [] self._consumption_rules: Dict[Terminal, List[ConsumptionRule]] = {} self._optim = optim @@ -67,61 +66,33 @@ def __init__(self, rules: Iterable[ReducedRule], optim: int = 7) -> None: @property def optim(self) -> int: - """Gets the optimization number - - Returns - ---------- - non_consumption_rules : int - The optimization number - """ + """Gets the optimization number.""" return self._optim @property def rules(self) -> List[ReducedRule]: - """Gets the non consumption rules - - Returns - ---------- - non_consumption_rules : iterable of \ - :class:`~pyformlang.indexed_grammar.ReducedRule` - The non consumption rules - """ + """Gets the non consumption rules.""" return self._rules @property def length(self) -> Tuple[int, int]: - """Get the total number of rules + """Get the total number of rules. Returns - --------- - number_rules : couple of int - A couple with first the number of non consumption rules and then\ - the number of consumption rules + ------- + A pair of the number of non consumption rules and the number \ + of consumption rules. """ return len(self._rules), len(self._consumption_rules.values()) @property def consumption_rules(self) -> Dict[Terminal, List[ConsumptionRule]]: - """Gets the consumption rules - - Returns - ---------- - consumption_rules : dict of any to iterable of \ - :class:`~pyformlang.indexed_grammar.ConsumptionRule` - A dictionary contains the consumption rules gathered by consumed \ - symbols - """ + """Gets a dictionary of consumption rules by the consumed symbol.""" return self._consumption_rules @property def non_terminals(self) -> Set[Variable]: - """Gets all the non-terminals used by all the rules - - Returns - ---------- - non_terminals : iterable of any - The non terminals used in the rule - """ + """Gets all the nonterminals used by all the rules.""" non_terminals = set() for rules in self._consumption_rules.values(): for rule in rules: @@ -132,13 +103,7 @@ def non_terminals(self) -> Set[Variable]: @property def terminals(self) -> Set[Terminal]: - """Gets all the terminals used by all the rules - - Returns - ---------- - terminals : iterable of any - The terminals used in the rules - """ + """Gets all the terminals used by all the rules.""" terminals = set() for rules in self._consumption_rules.values(): for rule in rules: @@ -151,17 +116,19 @@ def add_production(self, left: Hashable, right: Hashable, prod: Hashable) -> None: - """Add the production rule: + """Adds the production rule. + + A rule of form: left[sigma] -> right[prod sigma] Parameters - ----------- - left : any - The left non-terminal in the rule - right : any - The right non-terminal in the rule - prod : any - The production used in the rule + ---------- + left: + The left nonterminal of the rule. + right: + The right nonterminal of the rule. + prod: + The production used in the rule. """ left = to_variable(left) right = to_variable(right) @@ -172,17 +139,19 @@ def remove_production(self, left: Hashable, right: Hashable, prod: Hashable) -> None: - """Remove the production rule: + """Removes the production rule. + + A rule of form: left[sigma] -> right[prod sigma] Parameters - ----------- - left : any - The left non-terminal in the rule - right : any - The right non-terminal in the rule - prod : any - The production used in the rule + ---------- + left: + The left nonterminal of the rule. + right: + The right nonterminal of the rule. + prod: + The production used in the rule. """ left = to_variable(left) right = to_variable(right) diff --git a/pyformlang/indexed_grammar/utils.py b/pyformlang/indexed_grammar/utils.py index 2fce4df..8f4c6ef 100644 --- a/pyformlang/indexed_grammar/utils.py +++ b/pyformlang/indexed_grammar/utils.py @@ -1,4 +1,4 @@ -""" Utility for indexed grammars """ +"""Utility for indexed grammars.""" # pylint: disable=cell-var-from-loop @@ -8,16 +8,25 @@ def addrec_bis(l_sets: Iterable[Any], marked_left: Set[Any], marked_right: Set[Any]) -> bool: - """addrec_bis - Optimized version of addrec - :param l_sets: a list containing tuples (C, M) where: + """Optimized version of addrec. + + Parameters + ---------- + l_sets: + A list containing tuples (C, M) where: * C is a non-terminal on the left of a consumption rule - * M is the set of the marked set for the right non-terminal in the - production rule - :param marked_left: Sets which are marked for the non-terminal on the - left of the production rule - :param marked_right: Sets which are marked for the non-terminal on the - right of the production rule + * M is the set of the marked set for the right non-terminal + in the production rule + marked_left: + Sets which are marked for the non-terminal on the + left of the production rule. + marked_right: + Sets which are marked for the non-terminal on the + right of the production rule. + + Returns + ------- + Whether an element was actually marked. """ was_modified = False for marked in list(marked_right): @@ -30,16 +39,25 @@ def addrec_bis(l_sets: Iterable[Any], def addrec_ter(l_sets: List[Any], marked_left: Set[Any]) -> bool: - """addrec - Explores all possible combination of consumption rules to mark a + """addrec. + + Explores all possible combinations of consumption rules to mark a production rule. - :param l_sets: a list containing tuples (C, M) where: + + Parameters + ---------- + l_sets: + A list containing tuples (C, M) where: * C is a non-terminal on the left of a consumption rule * M is the set of the marked set for the right non-terminal in the - production rule - :param marked_left: Sets which are marked for the non-terminal on the - left of the production rule - :return Whether an element was actually marked + production rule + marked_left: + Sets which are marked for the non-terminal on the + left of the production rule. + + Returns + ------- + Whether an element was actually marked. """ # End condition, nothing left to process temp_in = [x[0] for x in l_sets] diff --git a/pyformlang/pda/pda.py b/pyformlang/pda/pda.py index ef4e451..9869641 100644 --- a/pyformlang/pda/pda.py +++ b/pyformlang/pda/pda.py @@ -304,7 +304,7 @@ def to_final_state(self) -> "PDA": Returns ------- - The new PDA which accepts by final state the language that was + The new PDA which accepts by final state the language that was \ accepted by empty stack. """ new_start = self.__get_next_free("#STARTTOFINAL#", @@ -345,7 +345,7 @@ def to_empty_stack(self) -> "PDA": Returns ------- - The new PDA which accepts by empty stack the language that was + The new PDA which accepts by empty stack the language that was \ accepted by final state. """ new_start = self.__get_next_free("#STARTEMPTYS#", @@ -593,7 +593,7 @@ def intersection(self, other: DeterministicFiniteAutomaton) -> "PDA": Returns ------- - The PDA resulting of the intersection. + The PDA resulting in the intersection. """ if not self.start_state or not other.start_state or other.is_empty(): return PDA() @@ -655,7 +655,7 @@ def __and__(self, other: DeterministicFiniteAutomaton) -> "PDA": Returns ------- - The PDA resulting of the intersection. + The PDA resulting in the intersection. """ return self.intersection(other) diff --git a/pyformlang/regular_expression/regex.py b/pyformlang/regular_expression/regex.py index 4d901fb..993779d 100644 --- a/pyformlang/regular_expression/regex.py +++ b/pyformlang/regular_expression/regex.py @@ -18,7 +18,7 @@ class Regex(RegexReader): Pyformlang implements the operators of textbooks, which deviate slightly from the operators in Python. For a representation closer to Python one, - please use :class:`~pyformlang.regular_expression.PythonRegex` + please use :class:`~pyformlang.regular_expression.PythonRegex`. * The concatenation can be represented either by a space or a dot (.) * The union is represented either by | or + @@ -510,8 +510,7 @@ def from_string(self, regex_str: str) -> "Regex": Returns ------- - regex: - The regex as a string. + The regex as a string. Examples -------- From 9e8be781d505f7e7a6099aac14f69bde6dca54cf Mon Sep 17 00:00:00 2001 From: bygu4 Date: Fri, 3 Jan 2025 16:29:32 +0300 Subject: [PATCH 18/21] update regex and FA code examples --- .../deterministic_finite_automaton.py | 26 ++++++ pyformlang/finite_automaton/epsilon_nfa.py | 88 +++++++++++++------ .../finite_automaton/finite_automaton.py | 68 ++++++++------ .../nondeterministic_finite_automaton.py | 26 ++++++ pyformlang/regular_expression/regex.py | 7 +- 5 files changed, 159 insertions(+), 56 deletions(-) diff --git a/pyformlang/finite_automaton/deterministic_finite_automaton.py b/pyformlang/finite_automaton/deterministic_finite_automaton.py index ca7bf98..fc6c296 100644 --- a/pyformlang/finite_automaton/deterministic_finite_automaton.py +++ b/pyformlang/finite_automaton/deterministic_finite_automaton.py @@ -207,6 +207,8 @@ def copy(self) -> "DeterministicFiniteAutomaton": >>> dfa_copy = dfa.copy() >>> dfa.is_equivalent_to(dfa_copy) True + >>> dfa_copy is dfa + False """ return self._copy_to(DeterministicFiniteAutomaton()) @@ -283,6 +285,18 @@ def from_epsilon_nfa(cls, enfa: EpsilonNFA) \ Returns ------- A deterministic automaton equivalent to `enfa`. + + Examples + -------- + >>> enfa = EpsilonNFA() + >>> enfa.add_transitions([(0, "epsilon", 1), (1, "a", 2), (1, "a", 3)]) + >>> enfa.add_start_state(0) + >>> enfa.add_final_state(2) + >>> dfa = DeterministicFiniteAutomaton.from_epsilon_nfa(enfa) + >>> dfa.is_deterministic() + True + >>> dfa.accepts("a") + True """ return cls._from_epsilon_nfa_internal(enfa, True) @@ -299,6 +313,18 @@ def from_nfa(cls, nfa: NondeterministicFiniteAutomaton) \ Returns ------- A deterministic automaton equivalent to `nfa`. + + Examples + -------- + >>> nfa = NondeterministicFiniteAutomaton() + >>> nfa.add_transitions([(0, "a", 1), (1, "b", 2), (1, "b", 3)]) + >>> nfa.add_start_state(0) + >>> nfa.add_final_state(2) + >>> dfa = DeterministicFiniteAutomaton.from_nfa(nfa) + >>> dfa.is_deterministic() + True + >>> dfa.accepts("ab") + True """ return cls._from_epsilon_nfa_internal(nfa, False) diff --git a/pyformlang/finite_automaton/epsilon_nfa.py b/pyformlang/finite_automaton/epsilon_nfa.py index c25e058..210ef3d 100644 --- a/pyformlang/finite_automaton/epsilon_nfa.py +++ b/pyformlang/finite_automaton/epsilon_nfa.py @@ -106,13 +106,12 @@ def accepts(self, word: Iterable[Hashable]) -> bool: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions( + [(0, "abc", 1), (0, "d", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa.accepts(["abc", "epsilon"]) True - >>> enfa.accepts(["epsilon"]) False """ @@ -141,12 +140,12 @@ def eclose_iterable(self, states: Iterable[Hashable]) -> Set[State]: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions( + [(0, "abc", 1), (0, "d", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa.eclose_iterable([0]) - {2} + {0, 2} """ states = [to_state(x) for x in states] res = set() @@ -169,12 +168,12 @@ def eclose(self, state: Hashable) -> Set[State]: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions( + [(0, "abc", 1), (0, "d", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa.eclose(0) - {2} + {0, 2} """ state = to_state(state) to_process = [state] @@ -198,8 +197,7 @@ def is_deterministic(self) -> bool: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "abc", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa.is_deterministic() @@ -219,13 +217,16 @@ def copy(self) -> "EpsilonNFA": Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "abc", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa_copy = enfa.copy() - >>> enfa.is_equivalent_to(enfa_copy) + >>> enfa.states == enfa_copy.states + True + >>> enfa_copy.accepts(["abc"]) True + >>> enfa_copy is enfa + False """ return self._copy_to(EpsilonNFA()) @@ -253,8 +254,8 @@ def from_networkx(cls, graph: MultiDiGraph) -> "EpsilonNFA": Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions( + [(0, "abc", 1), (0, "d", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> graph = enfa.to_networkx() @@ -288,14 +289,13 @@ def get_complement(self) -> "EpsilonNFA": Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions( + [(0, "abc", 1), (0, "d", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa_complement = enfa.get_complement() >>> enfa_complement.accepts(["epsilon"]) True - >>> enfa_complement.accepts(["abc"]) False """ @@ -346,8 +346,8 @@ def get_intersection(self, other: "EpsilonNFA") -> "EpsilonNFA": Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions( + [(0, "abc", 1), (0, "d", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa2 = EpsilonNFA() @@ -357,7 +357,6 @@ def get_intersection(self, other: "EpsilonNFA") -> "EpsilonNFA": >>> enfa_inter = enfa.get_intersection(enfa2) >>> enfa_inter.accepts(["abc"]) False - >>> enfa_inter.accepts(["d"]) True """ @@ -414,6 +413,18 @@ def get_union(self, other: "EpsilonNFA") -> "EpsilonNFA": Returns ------- The union of the two Epsilon NFAs. + + Examples + -------- + >>> enfa0 = Regex("ab").to_epsilon_nfa() + >>> enfa1 = Regex("c*").to_epsilon_nfa() + >>> union = enfa0.get_union(enfa1) + >>> union.accepts(["ab"]) + True + >>> union.accepts([]) + True + >>> union.accepts(["c", "c", "c"]) + True """ union = EpsilonNFA() self.__copy_transitions_marked(self, union, 0) @@ -458,6 +469,18 @@ def concatenate(self, other: "EpsilonNFA") -> "EpsilonNFA": Returns ------- The concatenation of the two Epsilon NFAs. + + Examples + -------- + >>> enfa0 = Regex("a").to_epsilon_nfa() + >>> enfa1 = Regex("b|c").to_epsilon_nfa() + >>> concatenation = enfa0.concatenate(enfa1) + >>> concatenation.accepts(["a", "b"]) + True + >>> concatenation.accepts(["a", "c"]) + True + >>> concatenation.accepts(["a"]) + False """ concatenation = EpsilonNFA() self.__copy_transitions_marked(self, concatenation, 0) @@ -505,8 +528,8 @@ def get_difference(self, other: "EpsilonNFA") -> "EpsilonNFA": Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions( + [(0, "abc", 1), (0, "d", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa2 = EpsilonNFA() @@ -516,7 +539,6 @@ def get_difference(self, other: "EpsilonNFA") -> "EpsilonNFA": >>> enfa_diff = enfa.get_difference(enfa2) >>> enfa_diff.accepts(["d"]) False - >>> enfa_diff.accepts(["abc"]) True """ @@ -587,6 +609,18 @@ def kleene_star(self) -> "EpsilonNFA": Returns ------- The kleene closure of current Epsilon NFA. + + Examples + -------- + >>> enfa = EpsilonNFA() + >>> enfa.add_transition(0, "a", 1) + >>> enfa.add_start_state(0) + >>> enfa.add_final_state(1) + >>> kleene_star = enfa.kleene_star() + >>> kleene_star.accepts([]) + True + >>> kleene_star.accepts(["a", "a"]) + True """ new_start = self.__get_new_state("Start") kleene_closure = EpsilonNFA(start_states={new_start}, @@ -608,8 +642,8 @@ def is_empty(self) -> bool: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions( + [(0, "abc", 1), (0, "d", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa.is_empty() diff --git a/pyformlang/finite_automaton/finite_automaton.py b/pyformlang/finite_automaton/finite_automaton.py index c0fbda8..18d75d4 100644 --- a/pyformlang/finite_automaton/finite_automaton.py +++ b/pyformlang/finite_automaton/finite_automaton.py @@ -114,8 +114,7 @@ def add_transitions(self, transitions_list: \ Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) """ temp = 0 for s_from, symb_by, s_to in transitions_list: @@ -164,8 +163,7 @@ def get_number_transitions(self) -> int: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.get_number_transitions() 3 """ @@ -186,8 +184,7 @@ def add_start_state(self, state: Hashable) -> int: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) """ state = to_state(state) @@ -210,8 +207,7 @@ def remove_start_state(self, state: Hashable) -> int: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.remove_start_state(0) """ @@ -236,8 +232,7 @@ def add_final_state(self, state: Hashable) -> int: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) """ @@ -261,8 +256,7 @@ def remove_final_state(self, state: Hashable) -> int: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa.remove_final_state(1) @@ -290,10 +284,9 @@ def __call__(self, s_from: Hashable, symb_by: Hashable) -> Set[State]: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) - >>> enfa(0, "abc") - [1] + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) + >>> enfa(0, "a") + {1} """ s_from = to_state(s_from) symb_by = to_symbol(symb_by) @@ -320,6 +313,13 @@ def get_transitions_from(self, s_from: Hashable) \ Yields ------ Pairs of transition symbol and destination state. + + Examples + -------- + >>> enfa = EpsilonNFA() + >>> enfa.add_transitions([(0, "a", 1), (0, "epsilon", 2)]) + >>> set(enfa.get_transitions_from(0)) + {("a", 1), ("epsilon", 2)} """ s_from = to_state(s_from) return self._transition_function.get_transitions_from(s_from) @@ -335,6 +335,13 @@ def get_next_states_from(self, s_from: Hashable) -> Set[State]: Returns ------- A set of next states defined by the transition function. + + Examples + -------- + >>> enfa = EpsilonNFA() + >>> enfa.add_transitions([(0, "a", 1), (0, "epsilon", 2)]) + >>> enfa.get_next_states_from(0) + {1, 2} """ s_from = to_state(s_from) return self._transition_function.get_next_states_from(s_from) @@ -354,8 +361,7 @@ def is_final_state(self, state: Hashable) -> bool: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa.is_final_state(1) @@ -419,8 +425,7 @@ def is_acyclic(self) -> bool: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa.is_acyclic() @@ -452,8 +457,7 @@ def to_networkx(self) -> MultiDiGraph: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> graph = enfa.to_networkx() @@ -499,8 +503,7 @@ def write_as_dot(self, filename: str) -> None: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa.write_as_dot("enfa.dot") @@ -534,6 +537,20 @@ def get_accepted_words(self, max_length: Optional[int] = None) \ Yields ------ Words accepted by current automaton. + + Examples + -------- + >>> enfa = EpsilonNFA() + >>> enfa.add_transitions([(0, "a", 1), (1, "b", 1)]) + >>> enfa.add_start_state(0) + >>> enfa.add_final_state(1) + >>> accepted_words = list(enfa.get_accepted_words(3)) + >>> ["a"] in accepted_words + True + >>> ["a", "b"] in accepted_words + True + >>> len(accepted_words) == 3 + True """ if max_length is not None and max_length < 0: return @@ -626,8 +643,7 @@ def to_dict(self) -> Dict[State, Dict[Symbol, Set[State]]]: Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "a", 1), (0, "b", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) >>> enfa_dict = enfa.to_dict() diff --git a/pyformlang/finite_automaton/nondeterministic_finite_automaton.py b/pyformlang/finite_automaton/nondeterministic_finite_automaton.py index 1cf11b0..7a63b17 100644 --- a/pyformlang/finite_automaton/nondeterministic_finite_automaton.py +++ b/pyformlang/finite_automaton/nondeterministic_finite_automaton.py @@ -134,6 +134,20 @@ def copy(self) -> "NondeterministicFiniteAutomaton": Returns ------- The copy of current finite automaton. + + Examples + -------- + >>> nfa = NondeterministicFiniteAutomaton() + >>> nfa.add_transitions([(0, "abc", 1), (0, "d", 2)]) + >>> nfa.add_start_state(0) + >>> nfa.add_final_state(1) + >>> nfa_copy = nfa.copy() + >>> nfa.states == nfa_copy.states + True + >>> nfa_copy.accepts(["abc"]) + True + >>> nfa_copy is nfa + False """ return self._copy_to(NondeterministicFiniteAutomaton()) @@ -150,6 +164,18 @@ def from_epsilon_nfa(cls, enfa: EpsilonNFA) \ Returns ------- An equivalent automaton without epsilon transitions. + + Examples + -------- + >>> enfa = EpsilonNFA() + >>> enfa.add_transitions([(0, "epsilon", 1), (1, "a", 2), (1, "a", 3)]) + >>> enfa.add_start_state(0) + >>> enfa.add_final_state(2) + >>> nfa = NondeterministicFiniteAutomaton.from_epsilon_nfa(enfa) + >>> nfa.is_deterministic() + False + >>> nfa.accepts("a") + True """ nfa = NondeterministicFiniteAutomaton() for state in enfa.start_states: diff --git a/pyformlang/regular_expression/regex.py b/pyformlang/regular_expression/regex.py index 993779d..d087df5 100644 --- a/pyformlang/regular_expression/regex.py +++ b/pyformlang/regular_expression/regex.py @@ -561,13 +561,14 @@ def from_finite_automaton(cls, automaton: FiniteAutomaton) -> "Regex": Examples -------- >>> enfa = EpsilonNFA() - >>> enfa.add_transitions([(0, "abc", 1), (0, "d", 1), \ - (0, "epsilon", 2)]) + >>> enfa.add_transitions([(0, "abc", 1), (0, "epsilon", 2)]) >>> enfa.add_start_state(0) >>> enfa.add_final_state(1) - >>> regex = enfa.to_regex() + >>> regex = Regex.from_finite_automaton(enfa) >>> regex.accepts(["abc"]) True + >>> regex.accepts([]) + False """ copies = [automaton.copy() for _ in automaton.final_states] final_states = list(automaton.final_states) From a1838b423a04560307e3ef76f39e8d70cbe94b6a Mon Sep 17 00:00:00 2001 From: bygu4 Date: Fri, 3 Jan 2025 17:24:44 +0300 Subject: [PATCH 19/21] add ruff to CI config --- .github/workflows/ci_extra.yml | 4 ++++ .github/workflows/ci_feature.yml | 4 ++++ .github/workflows/ci_master.yml | 4 ++++ pyrightconfig.json | 4 ++-- ruff.toml | 2 -- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_extra.yml b/.github/workflows/ci_extra.yml index 7309c12..6b5e97e 100644 --- a/.github/workflows/ci_extra.yml +++ b/.github/workflows/ci_extra.yml @@ -31,6 +31,10 @@ jobs: - name: Lint with pycodestyle run: | pycodestyle pyformlang || true + - name: Lint with ruff + run: | + ruff check pyformlang || true + - name: Check with pyright run: | pyright --stats pyformlang diff --git a/.github/workflows/ci_feature.yml b/.github/workflows/ci_feature.yml index edf0626..6f52fb2 100644 --- a/.github/workflows/ci_feature.yml +++ b/.github/workflows/ci_feature.yml @@ -35,6 +35,10 @@ jobs: - name: Lint with pycodestyle run: | pycodestyle pyformlang || true + - name: Lint with ruff + run: | + ruff check pyformlang || true + - name: Check with pyright run: | pyright --stats pyformlang diff --git a/.github/workflows/ci_master.yml b/.github/workflows/ci_master.yml index 2ab30a8..ae56b0d 100644 --- a/.github/workflows/ci_master.yml +++ b/.github/workflows/ci_master.yml @@ -34,6 +34,10 @@ jobs: - name: Lint with pycodestyle run: | pycodestyle pyformlang || true + - name: Lint with ruff + run: | + ruff check pyformlang || true + - name: Check with pyright run: | pyright --stats pyformlang diff --git a/pyrightconfig.json b/pyrightconfig.json index 7fd2a49..c0e01ce 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -2,7 +2,7 @@ "include": [ "pyformlang" ], - + "exclude": [ "**/node_modules", "**/__pycache__", @@ -12,7 +12,7 @@ "pythonVersion": "3.8", "pythonPlatform": "Linux", - + "strictListInference": true, "strictSetInference": true, "strictDictionaryInference": true, diff --git a/ruff.toml b/ruff.toml index 9faafcd..2080912 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,5 +1,3 @@ -include = ["pyformlang"] - exclude = [ ".bzr", ".direnv", From f4609be0682bb4c98682866d39438259b2b280ec Mon Sep 17 00:00:00 2001 From: bygu4 Date: Fri, 3 Jan 2025 17:30:24 +0300 Subject: [PATCH 20/21] update requirements --- doc/requirements.txt | 8 +++++--- requirements.txt | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index da306e3..533106a 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,6 +1,3 @@ -sphinx_rtd_theme -networkx -numpy pytest pytest-cov pytest-profiling @@ -8,8 +5,13 @@ numpydoc setuptools wheel twine +networkx +sphinx_rtd_theme +numpy pylint pycodestyle +ruff +pyright pydot pygments>=2.7.4 # not directly required, pinned by Snyk to avoid a vulnerability pylint>=2.7.0 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/requirements.txt b/requirements.txt index c65ce0c..533106a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ sphinx_rtd_theme numpy pylint pycodestyle +ruff pyright pydot pygments>=2.7.4 # not directly required, pinned by Snyk to avoid a vulnerability From eba4c9473a08cafe3814c17ecb99b287ce21635d Mon Sep 17 00:00:00 2001 From: bygu4 Date: Sat, 4 Jan 2025 14:09:11 +0300 Subject: [PATCH 21/21] include documented special methods with sphinx, correct some docstrings --- doc/conf.py | 3 +++ pyformlang/cfg/formal_grammar.py | 2 +- pyformlang/objects/base_epsilon.py | 2 +- pyformlang/objects/formal_object.py | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 3d44833..eb185f8 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -300,6 +300,9 @@ # Use type annotations for documenting the return type. napoleon_use_rtype = False +# Include documented special methods. +napoleon_include_special_with_doc = True + # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/pyformlang/cfg/formal_grammar.py b/pyformlang/cfg/formal_grammar.py index 5ee3958..38cf3ae 100644 --- a/pyformlang/cfg/formal_grammar.py +++ b/pyformlang/cfg/formal_grammar.py @@ -154,7 +154,7 @@ def to_text(self) -> str: def from_text( cls: Type[GrammarT], text: str, - start_symbol: Optional[Hashable] = Variable("S")) \ + start_symbol: Optional[Hashable] = "S") \ -> GrammarT: """Read a grammar from the given text. diff --git a/pyformlang/objects/base_epsilon.py b/pyformlang/objects/base_epsilon.py index 3037fd9..eb45b70 100644 --- a/pyformlang/objects/base_epsilon.py +++ b/pyformlang/objects/base_epsilon.py @@ -21,7 +21,7 @@ def __init__(self) -> None: super().__init__("epsilon") def __eq__(self, other: Any) -> bool: - """Check if the epsilon is equal to the given object.""" + """Checks if the epsilon is equal to the given object.""" return isinstance(other, BaseEpsilon) \ or not isinstance(other, FormalObject) and other in EPSILON_SYMBOLS diff --git a/pyformlang/objects/formal_object.py b/pyformlang/objects/formal_object.py index 82b426d..65244ef 100644 --- a/pyformlang/objects/formal_object.py +++ b/pyformlang/objects/formal_object.py @@ -36,7 +36,7 @@ def __eq__(self, other: Any) -> bool: return self._is_equal_to(other) and other._is_equal_to(self) def __hash__(self) -> int: - """Gets the hash current formal object.""" + """Gets the hash of current formal object.""" if self._hash is None: self._hash = hash(self._value) return self._hash