{"id":33138696,"url":"https://github.com/cesbit/pyleri","last_synced_at":"2026-02-05T16:34:36.506Z","repository":{"id":42987813,"uuid":"42098822","full_name":"cesbit/pyleri","owner":"cesbit","description":"Python Parser","archived":false,"fork":false,"pushed_at":"2025-11-15T08:31:09.000Z","size":309,"stargazers_count":122,"open_issues_count":3,"forks_count":13,"subscribers_count":7,"default_branch":"master","last_synced_at":"2026-01-24T03:38:48.932Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cesbit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"cesbit"}},"created_at":"2015-09-08T08:08:28.000Z","updated_at":"2025-11-29T20:11:11.000Z","dependencies_parsed_at":"2024-01-06T12:02:56.108Z","dependency_job_id":"4804945d-a0bd-4a98-b6c4-60a40a3b43ac","html_url":"https://github.com/cesbit/pyleri","commit_stats":{"total_commits":188,"total_committers":11,"mean_commits":17.09090909090909,"dds":0.5425531914893618,"last_synced_commit":"4694185c11c1230fa8e57c8eb2ac4794c91e4a94"},"previous_names":["transceptor-technology/pyleri"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/cesbit/pyleri","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesbit%2Fpyleri","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesbit%2Fpyleri/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesbit%2Fpyleri/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesbit%2Fpyleri/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cesbit","download_url":"https://codeload.github.com/cesbit/pyleri/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesbit%2Fpyleri/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29125878,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-05T14:05:12.718Z","status":"ssl_error","status_checked_at":"2026-02-05T14:03:53.078Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-11-15T11:00:46.744Z","updated_at":"2026-02-05T16:34:36.500Z","avatar_url":"https://github.com/cesbit.png","language":"Python","funding_links":["https://github.com/sponsors/cesbit"],"categories":["Tools"],"sub_categories":[],"readme":"[![CI](https://github.com/cesbit/pyleri/workflows/CI/badge.svg)](https://github.com/cesbit/pyleri/actions)\n[![Release Version](https://img.shields.io/github/release/cesbit/pyleri)](https://github.com/cesbit/pyleri/releases)\n[![Github stargazers](https://img.shields.io/github/stars/cesbit/pyleri)](https://github.com/cesbit/pyleri/stargazers)\n\nPython Left-Right Parser\n========================\nPyleri is an easy-to-use parser created for [SiriDB](http://siridb.net/). We first used [lrparsing](http://lrparsing.sourceforge.net/doc/html/) and wrote [jsleri](https://github.com/cesbit/jsleri) for auto-completion and suggestions in our web console. Later we found small issues within the `lrparsing` module and also had difficulties keeping the language the same in all projects. That is when we decided to create Pyleri which can export a created grammar to JavaScript, C, Python, Go and Java.\n\nGabriele Tomassetti [wrote a tutorial](https://tomassetti.me/pyleri-tutorial/) about the pyleri library.\n\n---------------------------------------\n  * [Related projects](#related-projects)\n  * [Installation](#installation)\n  * [Quick usage](#quick-usage)\n  * [Grammar](#grammar)\n    * [Grammar.parse()](#parse)\n    * [Grammar.export_js()](#export_js)\n    * [Grammar.export_c()](#export_c)\n    * [Grammar.export_go()](#export_go)\n    * [Grammar.export_java()](#export_java)\n    * [Grammar.export_py()](#export_py)\n  * [Result](#result)\n    * [is_valid](#is_valid)\n    * [Position](#position)\n    * [Tree](#tree)\n    * [Expecting](#expecting)\n  * [Elements](#elements)\n    * [Keyword](#keyword)\n    * [Regex](#regex)\n    * [Token](#token)\n    * [Tokens](#tokens)\n    * [Sequence](#sequence)\n    * [Choice](#choice)\n    * [Repeat](#repeat)\n    * [List](#list)\n    * [Optional](#optional)\n    * [Ref](#ref)\n    * [Prio](#prio)\n\n\n---------------------------------------\n## Related projects\n- [jsleri](https://github.com/cesbit/jsleri): JavaScript parser\n- [libcleri](https://github.com/cesbit/libcleri): C parser\n- [goleri](https://github.com/cesbit/goleri): Go parser\n- [jleri](https://github.com/cesbit/jleri): Java parser\n\n## Installation\nThe easiest way is to use PyPI:\n\n    sudo pip3 install pyleri\n\n## Quick usage\n```python\n# Imports, note that we skip the imports in other examples...\nfrom pyleri import (\n    Grammar,\n    Keyword,\n    Regex,\n    Sequence)\n\n# Create a Grammar Class to define your language\nclass MyGrammar(Grammar):\n    r_name = Regex('(?:\"(?:[^\"]*)\")+')\n    k_hi = Keyword('hi')\n    START = Sequence(k_hi, r_name)\n\n# Compile your grammar by creating an instance of the Grammar Class.\nmy_grammar = MyGrammar()\n\n# Use the compiled grammar to parse 'strings'\nprint(my_grammar.parse('hi \"Iris\"').is_valid) # =\u003e True\nprint(my_grammar.parse('bye \"Iris\"').is_valid) # =\u003e False\nprint(my_grammar.parse('bye \"Iris\"').as_str()) # =\u003e error at position 0, expecting: hi\n```\n\n## Grammar\nWhen writing a grammar you should subclass Grammar. A Grammar expects at least a `START` property so the parser knows where to start parsing. Grammar has some default properties which can be overwritten like `RE_KEYWORDS`, which will be explained later. Grammar also has a parse method: `parse()`, and a few export methods: [export_js()](#export_js), [export_c()](#export_c), [export_py()](#export_py), [export_go()](#export_go) and [export_java()](#export_java) which are explained below.\n\n\n### parse\nsyntax:\n```python\nGrammar().parse(string)\n```\nThe `parse()` method returns a result object which has the following properties that are further explained in [Result](#result):\n- `expecting`\n- `is_valid`\n- `pos`\n- `tree`\n\n\n### export_js\nsyntax:\n```python\nGrammar().export_js(\n    js_module_name='jsleri',\n    js_template=Grammar.JS_TEMPLATE,\n    js_indent=' ' * 4)\n```\nOptional keyword arguments:\n- `js_module_name`: Name of the JavaScript module. (default: 'jsleri')\n- `js_template`: Template String used for the export. You might want to look at the default string which can be found at Grammar.JS_TEMPLATE.\n- `js_indent`: indentation used in the JavaScript file. (default: 4 spaces)\n\nFor example when using our Quick usage grammar, this is the output when running `my_grammar.export_js()`:\n```javascript\n/* jshint newcap: false */\n\n/*\n * This grammar is generated using the Grammar.export_js() method and\n * should be used with the jsleri JavaScript module.\n *\n * Source class: MyGrammar\n * Created at: 2015-11-04 10:06:06\n */\n\n'use strict';\n\n(function (\n            Regex,\n            Sequence,\n            Keyword,\n            Grammar\n        ) {\n    var r_name = Regex('^(?:\"(?:[^\"]*)\")+');\n    var k_hi = Keyword('hi');\n    var START = Sequence(\n        k_hi,\n        r_name\n    );\n\n    window.MyGrammar = Grammar(START, '^\\w+');\n\n})(\n    window.jsleri.Regex,\n    window.jsleri.Sequence,\n    window.jsleri.Keyword,\n    window.jsleri.Grammar\n);\n```\n\n### export_c\nsyntax:\n```python\nGrammar().export_c(\n    target=Grammar.C_TARGET,\n    c_indent=' ' * 4)\n```\nOptional keyword arguments:\n- `target`: Name of the c module. (default: 'grammar')\n- `c_indent`: indentation used in the c files. (default: 4 spaces)\n\nThe return value is a tuple containing the source (c) file and header (h) file.\n\nFor example when using our Quick usage grammar, this is the output when running `my_grammar.export_c()`:\n```c\n/*\n * grammar.c\n *\n * This grammar is generated using the Grammar.export_c() method and\n * should be used with the libcleri module.\n *\n * Source class: MyGrammar\n * Created at: 2016-05-09 12:16:49\n */\n\n#include \"grammar.h\"\n#include \u003cstdio.h\u003e\n\n#define CLERI_CASE_SENSITIVE 0\n#define CLERI_CASE_INSENSITIVE 1\n\n#define CLERI_FIRST_MATCH 0\n#define CLERI_MOST_GREEDY 1\n\ncleri_grammar_t * compile_grammar(void)\n{\n    cleri_t * r_name = cleri_regex(CLERI_GID_R_NAME, \"^(?:\\\"(?:[^\\\"]*)\\\")+\");\n    cleri_t * k_hi = cleri_keyword(CLERI_GID_K_HI, \"hi\", CLERI_CASE_INSENSITIVE);\n    cleri_t * START = cleri_sequence(\n        CLERI_GID_START,\n        2,\n        k_hi,\n        r_name\n    );\n\n    cleri_grammar_t * grammar = cleri_grammar(START, \"^\\\\w+\");\n\n    return grammar;\n}\n```\nand the header file...\n```c\n/*\n * grammar.h\n *\n * This grammar is generated using the Grammar.export_c() method and\n * should be used with the libcleri module.\n *\n * Source class: MyGrammar\n * Created at: 2016-05-09 12:16:49\n */\n#ifndef CLERI_EXPORT_GRAMMAR_H_\n#define CLERI_EXPORT_GRAMMAR_H_\n\n#include \u003cgrammar.h\u003e\n#include \u003ccleri/cleri.h\u003e\n\ncleri_grammar_t * compile_grammar(void);\n\nenum cleri_grammar_ids {\n    CLERI_NONE,   // used for objects with no name\n    CLERI_GID_K_HI,\n    CLERI_GID_R_NAME,\n    CLERI_GID_START,\n    CLERI_END // can be used to get the enum length\n};\n\n#endif /* CLERI_EXPORT_GRAMMAR_H_ */\n\n```\n### export_go\nsyntax:\n```python\nGrammar().export_go(\n    go_template=Grammar.GO_TEMPLATE,\n    go_indent='\\t',\n    go_package='grammar')\n```\nOptional keyword arguments:\n- `go_template`: Template String used for the export. You might want to look at the default string which can be found at Grammar.GO_TEMPLATE.\n- `go_indent`: indentation used in the Go file. (default: one tab)\n- `go_package`: Name of the go package. (default: 'grammar')\n\nFor example when using our Quick usage grammar, this is the output when running `my_grammar.export_go()`:\n```go\npackage grammar\n\n// This grammar is generated using the Grammar.export_go() method and\n// should be used with the goleri module.\n//\n// Source class: MyGrammar\n// Created at: 2017-03-14 19:07:09\n\nimport (\n        \"regexp\"\n\n        \"github.com/cesbit/goleri\"\n)\n\n// Element indentifiers\nconst (\n        NoGid = iota\n        GidKHi = iota\n        GidRName = iota\n        GidSTART = iota\n)\n\n// MyGrammar returns a compiled goleri grammar.\nfunc MyGrammar() *goleri.Grammar {\n        rName := goleri.NewRegex(GidRName, regexp.MustCompile(`^(?:\"(?:[^\"]*)\")+`))\n        kHi := goleri.NewKeyword(GidKHi, \"hi\", false)\n        START := goleri.NewSequence(\n                GidSTART,\n                kHi,\n                rName,\n        )\n        return goleri.NewGrammar(START, regexp.MustCompile(`^\\w+`))\n}\n```\n### export_java\nsyntax:\n```python\nGrammar().export_java(\n    java_template=Grammar.JAVA_TEMPLATE,\n    java_indent=' ' * 4,\n    java_package=None,\n    is_public=True)\n```\nOptional keyword arguments:\n- `java_template`: Template String used for the export. You might want to look at the default string which can be found at Grammar.JAVA_TEMPLATE.\n- `java_indent`: indentation used in the Java file. (default: four spaces)\n- `java_package`: Name of the Java package or None when no package is specified. (default: None)\n- `is_public`: Class and constructor are defined as public when True, else they will be defined as package private.\n\nFor example when using our Quick usage grammar, this is the output when running `my_grammar.export_java()`:\n```java\n/**\n * This grammar is generated using the Grammar.export_java() method and\n * should be used with the jleri module.\n *\n * Source class: MyGrammar\n * Created at: 2018-07-04 12:12:34\n */\n\nimport jleri.Grammar;\nimport jleri.Element;\nimport jleri.Sequence;\nimport jleri.Regex;\nimport jleri.Keyword;\n\npublic class MyGrammar extends Grammar {\n    enum Ids {\n        K_HI,\n        R_NAME,\n        START\n    }\n\n    private static final Element R_NAME = new Regex(Ids.R_NAME, \"^(?:\\\"(?:[^\\\"]*)\\\")+\");\n    private static final Element K_HI = new Keyword(Ids.K_HI, \"hi\", false);\n    private static final Element START = new Sequence(\n        Ids.START,\n        K_HI,\n        R_NAME\n    );\n\n    public MyGrammar() {\n        super(START, \"^\\\\w+\");\n    }\n}\n```\n### export_py\nsyntax:\n```python\nGrammar().export_py(\n    py_module_name='pyleri',\n    py_template=Grammar.PY_TEMPLATE,\n    py_indent=' ' * 4)\n```\nOptional keyword arguments:\n- `py_module_name`: Name of the Pyleri Module. (default: 'pyleri')\n- `py_template`: Template String used for the export. You might want to look at the default string which can be found at Grammar.PY_TEMPLATE.\n- `py_indent`: indentation used in the Python file. (default: 4 spaces)\n\nFor example when using our Quick usage grammar, this is the output when running `my_grammar.export_py()`:\n```python\n\"\"\"\n This grammar is generated using the Grammar.export_py() method and\n should be used with the pyleri python module.\n\n Source class: MyGrammar\n Created at: 2017-03-14 19:14:51\n\"\"\"\nimport re\nfrom pyleri import Sequence\nfrom pyleri import Keyword\nfrom pyleri import Grammar\nfrom pyleri import Regex\n\nclass MyGrammar(Grammar):\n\n    RE_KEYWORDS = re.compile('^\\\\w+')\n    r_name = Regex('^(?:\"(?:[^\"]*)\")+')\n    k_hi = Keyword('hi')\n    START = Sequence(\n        k_hi,\n        r_name\n    )\n```\n\n## Result\nThe result of the `parse()` method contains 4 properties that will be explained next. A function `as_str(translate=None)` is also available which will\nshow the result as a string. The `translate` argument should be a function which accepts an element as argument. This function can be used to\nreturn custom strings for certain elements. If the return value of `translate` is `None` then the function will fall try to generate a string value. If\nthe return value is an empty string, the value will be ignored.\n\nExample of translate functions:\n```python\n# In case a translation function returns an empty string, no text is used\ndef translate(elem):\n    return ''  # as a result you get something like: 'error at position x'\n\n# Text may be returned based on gid\ndef translate(elem):\n    if elem is some_elem:\n        return 'A'   # something like: error at position x, expecting: A\n    elif elem is other_elem:\n        return ''    # other_elem will be ignored\n    else:\n        return None  # normal parsing\n\n# A translate function can be used as follow:\nprint(my_grammar.parse('some string').as_str(translate=translate))\n```\n\n### is_valid\n`is_valid` returns a boolean value, `True` when the given string is valid according to the given grammar, `False` when not valid.\n\nLet us take the example from Quick usage.\n```python\nres = my_grammar.parse('bye \"Iris\"')\nprint(res.is_valid) # =\u003e False\n```\n\n### Position\n`pos` returns the position where the parser had to stop. (when `is_valid` is `True` this value will be equal to the length of the given string with `str.rstrip()` applied)\n\nLet us take the example from Quick usage.\n```python\nresult = my_grammar.parse('hi Iris')\nprint(res.is_valid, result.pos) # =\u003e False, 3\n```\n\n### Tree\n`tree` contains the parse tree. Even when `is_valid` is `False` the parse tree is returned but will only contain results as far as parsing has succeeded. The tree is the root node which can include several `children` nodes. The structure will be further clarified in the following example which explains a way of visualizing the parse tree.\n\nExample:\n```python\nimport json\nfrom pyleri import Choice\nfrom pyleri import Grammar\nfrom pyleri import Keyword\nfrom pyleri import Regex\nfrom pyleri import Repeat\nfrom pyleri import Sequence\n\n\n# Create a Grammar Class to define your language\nclass MyGrammar(Grammar):\n    r_name = Regex('(?:\"(?:[^\"]*)\")+')\n    k_hi = Keyword('hi')\n    k_bye = Keyword('bye')\n    START = Repeat(Sequence(Choice(k_hi, k_bye), r_name))\n\n\n# Returns properties of a node object as a dictionary:\ndef node_props(node, children):\n    return {\n        'start': node.start,\n        'end': node.end,\n        'name': node.element.name if hasattr(node.element, 'name') else None,\n        'element': node.element.__class__.__name__,\n        'string': node.string,\n        'children': children}\n\n\n# Recursive method to get the children of a node object:\ndef get_children(children):\n    return [node_props(c, get_children(c.children)) for c in children]\n\n\n# View the parse tree:\ndef view_parse_tree(res):\n    start = res.tree.children[0] \\\n        if res.tree.children else res.tree\n    return node_props(start, get_children(start.children))\n\n\nif __name__ == '__main__':\n    # Compile your grammar by creating an instance of the Grammar Class:\n    my_grammar = MyGrammar()\n    res = my_grammar.parse('hi \"pyleri\" bye \"pyleri\"')\n    # The parse tree is visualized as a JSON object:\n    print(json.dumps(view_parse_tree(res), indent=2))\n```\n\nPart of the output is shown below.\n\n```json\n\n    {\n    \"start\": 0,\n    \"end\": 23,\n    \"name\": \"START\",\n    \"element\": \"Repeat\",\n    \"string\": \"hi \\\"pyleri\\\" bye \\\"pyleri\\\"\",\n    \"children\": [\n        {\n        \"start\": 0,\n        \"end\": 11,\n        \"name\": null,\n        \"element\": \"Sequence\",\n        \"string\": \"hi \\\"pyleri\\\"\",\n        \"children\": [\n            {\n            \"start\": 0,\n            \"end\": 2,\n            \"name\": null,\n            \"element\": \"Choice\",\n            \"string\": \"hi\",\n            \"children\": [\n                {\n                \"start\": 0,\n                \"end\": 2,\n                \"name\": \"k_hi\",\n                \"element\": \"Keyword\",\n                \"string\": \"hi\",\n                \"children\": []\n                }\n            ]\n            },\n            {\n            \"start\": 3,\n            \"end\": 11,\n            \"name\": \"r_name\",\n            \"element\": \"Regex\",\n            \"string\": \"\\\"pyleri\\\"\",\n            \"children\": []\n            }\n\n            \"...\"\n            \"...\"\n\n\n```\nA node contains 5 properties that will be explained next:\n\n- `start` property returns the start of the node object.\n- `end` property returns the end of the  node object.\n- `element` returns the [Element](#elements)'s type (e.g. Repeat, Sequence, Keyword, etc.). An element can be assigned to a variable; for instance in the example above `Keyword('hi')` was assigned to `k_hi`. With `element.name` the assigned name `k_hi` will be returned. Note that it is not a given that an element is named; in our example `Sequence` was not assigned, thus in this case the element has no attribute `name`.\n- `string` returns the string that is parsed.\n- `children` can return a node object containing deeper layered nodes provided that there are any. In our example the root node has an element type `Repeat()`, starts at 0 and ends at 24, and it has two `children`. These children are node objects that have both an element type `Sequence`, start at 0 and 12 respectively, and so on.\n\n\n### Expecting\n`expecting` returns a Python set() containing elements which pyleri expects at `pos`. Even if `is_valid` is true there might be elements in this set, for example when an `Optional()` element could be added to the string. \"Expecting\" is useful if you want to implement things like auto-completion, syntax error handling, auto-syntax-correction etc. The following example will illustrate a way of implementation.\n\nExample:\n```python\nimport re\nimport random\nfrom pyleri import Choice\nfrom pyleri import Grammar\nfrom pyleri import Keyword\nfrom pyleri import Repeat\nfrom pyleri import Sequence\nfrom pyleri import end_of_statement\n\n\n# Create a Grammar Class to define your language.\nclass MyGrammar(Grammar):\n    RE_KEYWORDS = re.compile(r'\\S+')\n    r_name = Keyword('\"pyleri\"')\n    k_hi = Keyword('hi')\n    k_bye = Keyword('bye')\n    START = Repeat(Sequence(Choice(k_hi, k_bye), r_name), mi=2)\n\n\n# Print the expected elements as a indented and numbered list.\ndef print_expecting(node_expecting, string_expecting):\n    for loop, e in enumerate(node_expecting):\n        string_expecting = '{}\\n\\t({}) {}'.format(string_expecting, loop, e)\n    print(string_expecting)\n\n\n# Complete a string until it is valid according to the grammar.\ndef auto_correction(string, my_grammar):\n    node = my_grammar.parse(string)\n    print('\\nParsed string: {}'.format(node.tree.string))\n\n    if node.is_valid:\n        string_expecting = 'String is valid. \\nExpected: '\n        print_expecting(node.expecting, string_expecting)\n\n    else:\n        string_expecting = 'String is NOT valid.\\nExpected: ' \\\n            if not node.pos \\\n            else 'String is NOT valid. \\nAfter \"{}\" expected: '.format(\n                                                  node.tree.string[:node.pos])\n        print_expecting(node.expecting, string_expecting)\n\n        selected = random.choice(list(node.expecting))\n        string = '{} {}'.format(node.tree.string[:node.pos],\n                                selected\n                                if selected\n                                is not end_of_statement else '')\n\n        auto_correction(string, my_grammar)\n\n\nif __name__ == '__main__':\n    # Compile your grammar by creating an instance of the Grammar Class.\n    my_grammar = MyGrammar()\n    string = 'hello \"pyleri\"'\n    auto_correction(string, my_grammar)\n\n```\n\nOutput:\n```\nParsed string: hello \"pyleri\"\nString is NOT valid.\nExpected:\n        (1) hi\n        (2) bye\n\nParsed string:  bye\nString is NOT valid.\nAfter \" bye\" expected:\n        (1) \"pyleri\"\n\nParsed string:  bye \"pyleri\"\nString is NOT valid.\nAfter \" bye \"pyleri\"\" expected:\n        (1) hi\n        (2) bye\n\nParsed string:  bye \"pyleri\" hi\nString is NOT valid.\nAfter \" bye \"pyleri\" hi\" expected:\n        (1) \"pyleri\"\n\nParsed string:  bye \"pyleri\" hi \"pyleri\"\nString is valid.\nExpected:\n        (1) hi\n        (2) bye\n\n```\nIn the above example we parsed an invalid string according to the grammar class. The `auto-correction()` method that we built for this example combines all properties from the `parse()` to create a valid string. The output shows every recursion of the `auto-correction()` method and prints successively the set of expected elements. It takes one randomly and adds it to the string. When the string corresponds to the grammar, the property `is_valid` will return `True`. Notably the `expecting` property still contains elements even if the `is_valid` returned `True`. The reason in this example is due to the [Repeat](#repeat) element.\n\n## Elements\nPyleri has several elements which are all subclasses of [Element](#element) and can be used to create a grammar.\n\n### Keyword\nsyntax:\n```python\nKeyword(keyword, ign_case=False)\n```\nThe parser needs to match the keyword which is just a string. When matching keywords we need to tell the parser what characters are allowed in keywords. By default Pyleri uses `^\\w+` which is both in Python and JavaScript equal to `^[A-Za-z0-9_]+`. We can overwrite the default by setting `RE_KEYWORDS` in the grammar. Keyword() accepts one keyword argument `ign_case` to tell the parser if we should match case insensitive.\n\nExample:\n\n```python\nclass TicTacToe(Grammar):\n    # Let's allow keywords with alphabetic characters and dashes.\n    RE_KEYWORDS = re.compile('^[A-Za-z-]+')\n\n    START = Keyword('tic-tac-toe', ign_case=True)\n\nttt_grammar = TicTacToe()\nttt_grammar.parse('Tic-Tac-Toe').is_valid  # =\u003e True\n```\n\n### Regex\nsyntax:\n```python\nRegex(pattern, flags=0)\n```\nThe parser compiles a regular expression using the `re` module. The current version of pyleri has only support for the `re.IGNORECASE` flag.\nSee the [Quick usage](#quick-usage) example for how to use `Regex`.\n\n### Token\nsyntax:\n```python\nToken(token)\n```\nA token can be one or more characters and is usually used to match operators like `+`, `-`, `//` and so on. When we parse a string object where pyleri expects an element, it will automatically be converted to a `Token()` object.\n\nExample:\n```python\nclass Ni(Grammar):\n    t_dash = Token('-')\n    # We could just write delimiter='-' because\n    # any string will be converted to Token()\n    START = List(Keyword('ni'), delimiter=t_dash)\n\nni = Ni()\nni.parse('ni-ni-ni-ni-ni').is_valid  # =\u003e True\n```\n\n### Tokens\nsyntax:\n```python\nTokens(tokens)\n```\nCan be used to register multiple tokens at once. The `tokens` argument should be a string with tokens separated by spaces. If given tokens are different in size the parser will try to match the longest tokens first.\n\nExample:\n```python\nclass Ni(Grammar):\n    tks = Tokens('+ - !=')\n    START = List(Keyword('ni'), delimiter=tks)\n\nni = Ni()\nni.parse('ni + ni != ni - ni').is_valid  # =\u003e True\n```\n\n### Sequence\nsyntax:\n```python\nSequence(element, element, ...)\n```\nThe parser needs to match each element in a sequence.\n\nExample:\n```python\nclass TicTacToe(Grammar):\n    START = Sequence(Keyword('Tic'), Keyword('Tac'), Keyword('Toe'))\n\nttt_grammar = TicTacToe()\nttt_grammar.parse('Tic Tac Toe').is_valid  # =\u003e True\n```\n\n### Choice\nsyntax:\n```python\nChoice(element, element, ..., most_greedy=True)\n```\nThe parser needs to choose between one of the given elements. Choice accepts one keyword argument `most_greedy` which is `True` by default. When `most_greedy` is set to `False` the parser will stop at the first match. When `True` the parser will try each element and returns the longest match. Setting `most_greedy` to `False` can provide some extra performance. Note that the parser will try to match each element in the exact same order they are parsed to Choice.\n\nExample: let us use `Choice` to modify the Quick usage example to allow the string 'bye \"Iris\"'\n```python\nclass MyGrammar(Grammar):\n    r_name = Regex('(?:\"(?:[^\"]*)\")+')\n    k_hi = Keyword('hi')\n    k_bye = Keyword('bye')\n    START = Sequence(Choice(k_hi, k_bye), r_name)\n\nmy_grammar = MyGrammar()\nmy_grammar.parse('hi \"Iris\"').is_valid  # =\u003e True\nmy_grammar.parse('bye \"Iris\"').is_valid  # =\u003e True\n```\n\n### Repeat\nsyntax:\n```python\nRepeat(element, mi=0, ma=None)\n```\nThe parser needs at least `mi` elements and at most `ma` elements. When `ma` is set to `None` we allow unlimited number of elements. `mi` can be any integer value equal or higher than 0 but not larger then `ma`.\n\nExample:\n```python\nclass Ni(Grammar):\n    START = Repeat(Keyword('ni'))\n\nni = Ni()\nni.parse('ni ni ni ni ni').is_valid  # =\u003e True\n```\n\nIt is not allowed to bind a name to the same element twice and Repeat(element, 1, 1) is a common solution to bind the element a second (or more) time(s).\n\nFor example consider the following:\n```python\nclass MyGrammar(Grammar):\n    r_name = Regex('(?:\"(?:[^\"]*)\")+')\n\n    # Raises a SyntaxError because we try to bind a second time.\n    r_address = r_name # WRONG\n\n    # Instead use Repeat\n    r_address = Repeat(r_name, 1, 1) # RIGHT\n```\n\n### List\nsyntax:\n```python\nList(element, delimiter=',', mi=0, ma=None, opt=False)\n```\nList is like Repeat but with a delimiter. A comma is used as default delimiter but any element is allowed. When a string is used as delimiter it will be converted to a `Token` element. `mi` and `ma` work exactly like with Repeat. An optional keyword argument `opt` can be set to `True` to allow the list to end with a delimiter. By default this is set to `False` which means the list has to end with an element.\n\nExample:\n```python\nclass Ni(Grammar):\n    START = List(Keyword('ni'))\n\nni = Ni()\nni.parse('ni, ni, ni, ni, ni').is_valid  # =\u003e True\n```\n\n### Optional\nsyntax:\n```python\nOptional(element)\n```\nThe parser looks for an optional element. It is like using `Repeat(element, 0, 1)` but we encourage to use `Optional` since it is more readable. (and slightly faster)\n\nExample:\n```python\nclass MyGrammar(Grammar):\n    r_name = Regex('(?:\"(?:[^\"]*)\")+')\n    k_hi = Keyword('hi')\n    START = Sequence(k_hi, Optional(r_name))\n\nmy_grammar = MyGrammar()\nmy_grammar.parse('hi \"Iris\"').is_valid  # =\u003e True\nmy_grammar.parse('hi').is_valid  # =\u003e True\n```\n\n### Ref\nsyntax:\n```python\nRef()\n```\nThe grammar can make a forward reference to make recursion possible. In the example below we create a forward reference to START but note that\na reference to any element can be made.\n\n\u003eWarning: A reference is not protected against testing the same position in\n\u003ea string. This could potentially lead to an infinite loop.\n\u003eFor example:\n\u003e```python\n\u003er = Ref()\n\u003er = Optional(r)  # DON'T DO THIS\n\u003e```\n\u003eUse [Prio](#prio) if such recursive construction is required.\n\nExample:\n```python\nclass NestedNi(Grammar):\n    START = Ref()\n    ni_item = Choice(Keyword('ni'), START)\n    START = Sequence('[', List(ni_item), ']')\n\nnested_ni = NestedNi()\nnested_ni.parse('[ni, ni, [ni, [], [ni, ni]]]').is_valid  # =\u003e True\n```\n\n### Prio\nsyntax:\n```python\nPrio(element, element, ...)\n```\nChoose the first match from the prio elements and allow `THIS` for recursive operations. With `THIS` we point to the `Prio` element. Probably the example below explains how `Prio` and `THIS` can be used.\n\n\u003eNote: Use a [Ref](#ref) when possible.\n\u003eA `Prio` element is required when the same position in a string is potentially\n\u003echecked more than once.\n\nExample:\n```python\nclass Ni(Grammar):\n    k_ni = Keyword('ni')\n    START = Prio(\n        k_ni,\n        # '(' and ')' are automatically converted to Token('(') and Token(')')\n        Sequence('(', THIS, ')'),\n        Sequence(THIS, Keyword('or'), THIS),\n        Sequence(THIS, Keyword('and'), THIS))\n\nni = Ni()\nni.parse('(ni or ni) and (ni or ni)').is_valid  # =\u003e True\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesbit%2Fpyleri","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcesbit%2Fpyleri","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesbit%2Fpyleri/lists"}