Python API Reference#
Fandango provides a simple API for Python programs to
Note
This API will be extended over time.
Installing the API#
When you install Fandango, the Python Fandango API is installed as well.
The Fandango class#
The Fandango API comes as a class, named Fandango.
To use it, write
from fandango import Fandango
The Fandango constructor allows reading in a .fan specification, either from an (open) file, a string, or a list of strings or files.
class Fandango(fan_files: str | IO | List[str | IO],
constraints: List[str] = None, *,
start_symbol: Optional[str] = None,
use_cache: bool = True,
use_stdlib: bool = True,
logging_level: Optional[int] = None)
Create a Fandango object.
fan_filesis the Fandango specification to load. This is eithera string holding a
.fanspecification; oran (open)
.fanfile; ora list of strings or files.
constraints, if given, is a list of additional constraints (as strings).start_symbolis the start symbol to use (default:<start>).use_cachecan be set toFalseto avoid loading the input from cache.use_stdlibcan be set toFalseto avoid loading the standard library.includes: A list of directories to search for include files before the Fandango spec locations.logging_levelcontrols the logging output. It can be set to any of the values in the Python logging module, such aslogging.DEBUGorlogging.INFO. Default islogging.WARNING.
Warning
Be aware that .fan files can contain Python code that is executed when loaded. This code can execute arbitrary commands.
Warning
Code in the .fan spec cannot access identifiers from the API-calling code or vice versa. However, as both are executed in the same Python interpreter, there is a risk that loaded .fan code may bypass these restrictions and gain access to the API-calling code.
Caution
Only load .fan files you trust.
Fandango() can raise a number of exceptions, including
FandangoSyntaxErrorif the.faninput has syntax errors. The exception attributesline,column, andmsghold the line, column, and error message.FandangoValueErrorif the.faninput has consistency errors.
The exception class FandangoError is the superclass of these exceptions.
The fuzz() method#
On a Fandango object, use the fuzz() method to produce outputs from the loaded specification.
fuzz(extra_constraints: Optional[List[str]] = None, **settings)
-> List[DerivationTree])
Create outputs from the specification, as a list of derivation trees.
extra_constraints: if given, use this list of strings as additional constraintssettings: pass extra values to control the fuzzer algorithm. These includepopulation_size: int: set the population size (default: 100).desired_solutions: int: set the number of desired solutions.initial_population: List[Union[DerivationTree, str]]: set the initial population.max_generations: int: set the maximum number of generations (default: 500).warnings_are_errors: boolcan be set to True to raise an exception on warnings.best_effort: boolcan be set to True to return the population even if it does not satisfy the constraints.
The fuzz() method returns a list of DerivationTree objects. These are typically converted into Python data types (typically using str() or bytes()) to be used in standard Python functions.
fuzz() can raise a number of exceptions, including
FandangoFailedErrorif the algorithm failed andwarnings_are_errorsis set.FandangoValueErrorif algorithm settings are invalid.FandangoParseErrorif a generator value could not be parsed.
The exception class FandangoError is the superclass of these exceptions.
The parse() method#
On a Fandango object, use the parse() method to parse an input using the loaded specification.
parse(word: str | bytes | DerivationTree, *, prefix: bool = False, **settings)
-> Generator[DerivationTree, None, None]
Parse a word; return a generator for derivation trees.
word: the word to be parsed. This can be a string, a byte string, or a derivation tree.prefix: if True, allow incomplete inputs that form a prefix of the inputs accepted by the grammar.settings: additional settings for the parser.There are no additional user-facing settings for the parser at this point.
The return value is a generator of derivation trees - if the .fan grammar is ambiguous, a single word may be parsed into different trees.
To iterate over all trees parsed, use a construct such as
for tree in fan.parse(word):
... # do something with the tree
parse() can raise exceptions, notably
FandangoParseErrorif parsing fails. The attributepositionof the exception indicates the position inwordat which the syntax error was detected.[1]
Note
At this point, the parse() method does not check whether constraints are satisfied.
API Usage Examples#
Fuzzing from a .fan Spec#
Let us read and produce inputs from persons-faker.fan discussed in the section on generators and fakers:
from fandango import Fandango
# Read in a .fan spec from a file
with open('persons-faker.fan') as persons:
fan = Fandango(persons)
for tree in fan.fuzz(desired_solutions=10):
print(str(tree))
Thomas Garcia,2228
Mariah Todd,53
Ian Hunt,0
Megan Moore,3
William Stone,6
Nicole Tran,926
Christopher Watkins,5887
Megan Schroeder,6091
James Barker,944
Lynn Davila,2991
Fuzzing from a .fan String#
We can also read a .fan spec from a string. This also demonstrates the usage of the logging_level parameter.
from fandango import Fandango
import logging
# Read in a .fan spec from a string
spec = """
<start> ::= ('a' | 'b' | 'c')+
where str(<start>) != 'd'
"""
fan = Fandango(spec, logging_level=logging.INFO)
for tree in fan.fuzz(desired_solutions=10):
print(str(tree))
fandango:INFO: <string>: saving spec to cache /home/runner/.cache/fandango/44ea58c934e9a64b9aa5648257f8147645cb7b07293c2ec9e81c8b065aac1f44.pickle
fandango:INFO: Redefining <start>
fandango:INFO: ---------- Initializing base population ----------
fandango:INFO: ---------- Initializing FANDANGO algorithm ----------
fandango:INFO: ---------- Done initializing base population ----------
fandango:INFO: ---------- Generating ----------
fandango:INFO: Generating (additional) initial population (size: 100)...
cccb
bca
baac
cccbb
acaa
baba
ccb
cac
bbc
caa
Parsing an Input#
This example illustrates usage of the parse() method.
from fandango import Fandango
spec = """
<start> ::= ('a' | 'b' | 'c')+
where str(<start>) != 'd'
"""
fan = Fandango(spec)
word = 'abc'
for tree in fan.parse(word):
print(f"tree = {repr(str(tree))}")
print(tree.to_grammar())
tree = 'abc'
<start> ::= 'a' 'b' 'c' # Position 0x0000 (0); abc
Use the DerivationTree functions to convert and traverse the resulting trees.
Parsing an Incomplete Input#
This example illustrates how to parse a prefix ('ab') for a grammar that expects a final d letter.
from fandango import Fandango
spec = """
<start> ::= ('a' | 'b' | 'c')+ 'd'
where str(<start>) != 'd'
"""
fan = Fandango(spec)
word = 'ab'
for tree in fan.parse(word, prefix=True):
print(f"tree = {repr(str(tree))}")
print(tree.to_grammar())
tree = 'ab'
<start> ::= 'a' 'b' # Position 0x0000 (0); ab
Without prefix=True, parsing would fail:
from fandango import Fandango
spec = """
<start> ::= ('a' | 'b' | 'c')+ 'd'
where str(<start>) != 'd'
"""
fan = Fandango(spec)
word = 'ab'
fan.parse(word)
<generator object Fandango.parse at 0x7f715099b780>
Handling Parsing Errors#
This example illustrates handling of parse() errors.
from fandango import Fandango, FandangoParseError
spec = """
<start> ::= ('a' | 'b' | 'c')+
where str(<start>) != 'd'
"""
fan = Fandango(spec)
invalid_word = 'abcdef'
try:
fan.parse(invalid_word)
except FandangoParseError as exc:
error_position = exc.position
print("Syntax error at", repr(invalid_word[error_position]))