parfact 0.2.4
"Heavily flavored, humanized, in-memory factory API for non-tokenized parsing at runtime."
License: MIT
Requires Python: >=3.0
Keywords: parsing,parser

# PyParFact
ParFact is an acronym of *Parser Factory* meant to sound like an impure "perfect."

As its fully spelled out name suggests, ParFact is a functional API to build parsers within Python. Unlike a [Parser Generator](https://web.mit.edu/6.005/www/fa15/classes/18-parser-generators/) like [pyleri](https://pypi.org/project/pyleri/), ParFact does not generate Python code from a grammar featuring keywords. It does not use a lexer to extract a sequence of tokens from the source text. Instead, it exposes an API you may use to define rules of your custom parser. This parser then, according to your rules, builds the AST on the fly, without the use of tokens.

# Example
Please note that ParFact is designed to be unbiased. Resulting is a rather bulky & verbose API. A simple & biased, albeit more restrictive interface is in the works.

```python
from parfact import charset
import parfact

parse = parfact.Parser(parfact.any({
    'redirect': parfact.sequence(parfact.string('#redirect', case_sensitive=False), parfact.ws(), parfact.chars.until(charset.vws()), terminal=True),
    'headline': parfact.sequence(parfact.char('#'), parfact.ws(), parfact.chars.until(charset.vws()), terminal=True),
    'link':     parfact.sequence(parfact.char('['), parfact.chars.until('|'), parfact.chars.until(']'), terminal=True),
}))
parse('#Redirect')
parse('# I\'m a headline!\n')
parse('[I\'m a link!|Kinda...]')
```

# Patterns
*ParFact*'s API is composed of various patterns and callback builders. A pattern represents a partition of the state machine. When "plugging" a pattern, its corresponding state machine is directly referenced. Be wary of proper state machine design.

## String Pattern
The `parfact.string` pattern directly matches a sequence of characters, with optional case-insensitivity.

**Example**
```python
import parfact

parfact.string('#redirect', case_sensitive=False)
```

## Char/s Patterns
*ParFact* supports matching any single or string of Unicode characters. It is accomplished through the `char` and `chars` patterns. These take one or more `charset`s or strings representing such.

**Example**
```python
import parfact

parfact.char('a-z', 'A-Z') # matches a single Roman letter, lower case or upper case.
parfact.chars('a-z', 'A-Z') # matches at least one Roman letter, lower case or upper case.
```

Matched characters are captured non-recursively. One may intercept such a character through the `.onmatch(callback: Callable[[Pattern, str], None])` method.

## Digit/s Patterns
Direct derivatives of the `char` and `chars` patterns specialized for Arabic numerals (`'0-9'`). Match callback receives the parsed integer directly.

## Letter/s Patterns
Direct derivatives of the `char` and `chars` patterns using `charset.letters()` - i.e. every character in Unicode which is part of any 'L' category. See [Unicode Character Categories | FileFormat.info](https://www.fileformat.info/info/unicode/category/index.htm) for more information.

## nl/ws Patterns
Specialized patterns to match (without capturing) horizontal & vertical whitespaces in Unicode. These include more than just ASCII whitespaces. See [Whitespace (Unicode) | Wikipedia](https://en.wikipedia.org/wiki/Template:Whitespace_(Unicode)) for more information.

## any Pattern
The *any* pattern effectively resembles a branch of patterns. One employs it to specify alternative paths through the state machine. The top level of a programming language would consist of a single *any* pattern with many options. When an option is matched, calls `onmatch` callback with pattern's associated name tag.

**Signature**
```python
any(patterns: Dict[str, Pattern], **kwargs)
```

Create a new `any` pattern where each pattern is associated a unique tag.

```python
any.equivalent(name: str, *patterns: Pattern, **kwargs)
```

Create a new `any` pattern where each pattern is associated with the same tag (`name`). This is useful for patterns which are synonymous to one another. `kwargs` is passed directly to the constructor.

**Example**
```python
import parfact

parfact.any({
    'annotation': sequence(char('@'), chars(charset.letters(), '_'), nl()),
    'signature': sequence(string('function'), ws(), chars(charset.letters(), '_'), char('('), chars.until(')'), char(')'), nl())
})
```

## Sequence Pattern
*Sequence* pattern sequentially connects specified sub-patterns through `SuccessorMixin`'s `next(pattern)` method. It turns a recursive definition of patterns into a linear one, for somewhat improved simplicity.

**Signature**
```python
sequence(*patterns: Pattern, **kwargs)
```

# Global Configuration
For convenience, some commonly used pattern configurations can be set globally.

## Case Sensitivity
Whenever a letter is matched, both lower-case and upper-case characters of the same letter are accepted.

**Signature**
```python
parfact.case_sensitive(True|False)
```

## Simple Whitespaces
Whenever a whitespace is matched, any (horizontal) whitespace is matched. This includes the unicode character category for whitespaces additionally including tabulation.

**Signature**
```python
parfact.use_simple_whitespaces(True|False)
```

# Custom Patterns
ParFact provides numerous general purpose & flexible patterns. Nonetheless, one might find themselves in need of custom patterns. Be aware that patterns are a rather involved abstraction requiring advanced understanding of the underlying low-level system.

Guidelines to consider:

1. Patterns should not return to root. Prefer introducing a new interim state.
2. Patterns should call `self._cb_onenter` and `self._cb_onexit` callbacks appropriately. `Pattern` base provides `openter` and `opexit` transition callbacks respectively.
3. Patterns should set states to terminal where appropriate. This may include a succeeding pattern's root state, hence guideline #1.

ParFact currently provides three base classes:

## Pattern Base
`Pattern` is an abstract base which all custom patterns *should* derive. Specifically, one should implement `Pattern.build(self, root: State)`. In doing so, one should adhere to the above guidelines.

This base class provides basic entry & exit handling & transition callbacks. The callbacks must be manually hooked into states during `build(root)`.

## MatchingPattern Base
A `Pattern` derived utility exposing additional callbacks & transition callbacks for capturing matched characters. Non-recursive. Attach `self._op_match` to capture input and `self.opmatch` to trigger `onmatch` callbacks.

## SuccessorMixin
Mixin to handle a single successor to the pattern. Compiles it where applicable. Call `self.successor(terminal = False)` when linking transition.

# License
MIT License

Copyright (c) 2021 Kiruse

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.