{ "info": { "author": "Jeroen van der Heijden", "author_email": "jeroen@transceptor.technology", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Linguistic" ], "description": "Python 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/transceptor-technology/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\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/transceptor-technology/jsleri): JavaScript parser\n- [libcleri](https://github.com/transceptor-technology/libcleri): C parser\n- [goleri](https://github.com/transceptor-technology/goleri): Go parser\n- [jleri](https://github.com/transceptor-technology/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) # => True\nprint(my_grammar.parse('bye \"Iris\"').is_valid) # => False\nprint(my_grammar.parse('bye \"Iris\"').as_str()) # => 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 \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 \n#include \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/transceptor-technology/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) # => 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) # => 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 \"siri\" bye \"siri\"')\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\\\" hi \\\"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 type of [Element](#elements) (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 because of 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 # => 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 # => 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 # => 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 # => 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 # => True\nmy_grammar.parse('bye \"Iris\"').is_valid # => 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 # => 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 # => 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 # => True\nmy_grammar.parse('hi').is_valid # => 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>Warning: A reference is not protected against testing the same position in\n>a string. This could potentially lead to an infinite loop.\n>For example:\n>```python\n>r = Ref()\n>r = Optional(r) # DON'T DO THIS\n>```\n>Use [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 # => 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>Note: Use a [Ref](#ref) when possible.\n>A `Prio` element is required when the same position in a string is potentially\n>checked 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 # => True\n```", "description_content_type": "text/markdown", "docs_url": null, "download_url": "https://github.com/transceptor-technology/pyleri/tarball/1.3.2", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/transceptor-technology/pyleri", "keywords": "parser,grammar,autocompletion", "license": "", "maintainer": "", "maintainer_email": "", "name": "pyleri", "package_url": "https://pypi.org/project/pyleri/", "platform": "", "project_url": "https://pypi.org/project/pyleri/", "project_urls": { "Download": "https://github.com/transceptor-technology/pyleri/tarball/1.3.2", "Homepage": "https://github.com/transceptor-technology/pyleri" }, "release_url": "https://pypi.org/project/pyleri/1.3.2/", "requires_dist": null, "requires_python": "", "summary": "Python Left-Right Parser", "version": "1.3.2" }, "last_serial": 4426888, "releases": { "1.0.6": [ { "comment_text": "", "digests": { "md5": "9b12f740b61a966cbdcc1b4438a0e0fc", "sha256": "4cf183780b28adeabea8d50ecd9100a7db153e977ac60b21757ef14f2f3ddfb6" }, "downloads": -1, "filename": "pyleri-1.0.6.tar.gz", "has_sig": false, "md5_digest": "9b12f740b61a966cbdcc1b4438a0e0fc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8148, "upload_time": "2016-02-18T20:47:02", "url": "https://files.pythonhosted.org/packages/81/d9/c7aa2fb425fa90b4b2ec38c472af578604adb4b78753f787add37a7c9627/pyleri-1.0.6.tar.gz" } ], "1.0.8": [ { "comment_text": "", "digests": { "md5": "f70d4d9e480d31df8c0eb7bb1bf26753", "sha256": "217e1e35679b2d068d4122a765473bc4f69e0c568c4c1a7e6a0d7424ce948029" }, "downloads": -1, "filename": "pyleri-1.0.8.tar.gz", "has_sig": false, "md5_digest": "f70d4d9e480d31df8c0eb7bb1bf26753", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8754, "upload_time": "2016-02-19T09:29:31", "url": "https://files.pythonhosted.org/packages/51/85/475036997113e871e727c00081e2b6f1284d9f6da6bda6f937b2d045b77c/pyleri-1.0.8.tar.gz" } ], "1.0.9": [ { "comment_text": "", "digests": { "md5": "4a26b02ea49bd48e3ca219c2424807fd", "sha256": "74ada29882d30656b9c51b88507be6f3da89656345773a47e1236e369630c2bd" }, "downloads": -1, "filename": "pyleri-1.0.9.tar.gz", "has_sig": false, "md5_digest": "4a26b02ea49bd48e3ca219c2424807fd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9947, "upload_time": "2016-05-09T10:24:02", "url": "https://files.pythonhosted.org/packages/ce/b0/8f492e0c87a8bc0019f7d057eb59a5fe029531385e035f8ff4c271e628d7/pyleri-1.0.9.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "5ff43e1478651b7f4b339eb41a81bafb", "sha256": "c5814a50a25c3203875d7efd8327de5ec3f41921db959d5a2c3247528de73e1b" }, "downloads": -1, "filename": "pyleri-1.1.0.tar.gz", "has_sig": false, "md5_digest": "5ff43e1478651b7f4b339eb41a81bafb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10909, "upload_time": "2016-05-20T08:40:47", "url": "https://files.pythonhosted.org/packages/b7/38/aa0a1882c9f2e15eab930ebc9e23141f9633db274d1e71ba477f15373cef/pyleri-1.1.0.tar.gz" } ], "1.1.2": [ { "comment_text": "", "digests": { "md5": "060afe89c89c84d74200442f77681a67", "sha256": "998054506b96c3886f29e5f7484c3964c0ce1a69e3dc1f8b6fc10f83f43814a0" }, "downloads": -1, "filename": "pyleri-1.1.2.tar.gz", "has_sig": false, "md5_digest": "060afe89c89c84d74200442f77681a67", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11176, "upload_time": "2016-05-23T08:12:37", "url": "https://files.pythonhosted.org/packages/eb/db/d659cbb8872bf189ebf8623ff028902c163b9875f6c864778b507c7a6ea0/pyleri-1.1.2.tar.gz" } ], "1.1.3": [ { "comment_text": "", "digests": { "md5": "637c3f9dc5b340d9a8ddc7871649bb40", "sha256": "1bcc2f10b23459510566a9eb8df64cce898eb14ef50d0db2faf7e04fa2ab76ca" }, "downloads": -1, "filename": "pyleri-1.1.3.tar.gz", "has_sig": false, "md5_digest": "637c3f9dc5b340d9a8ddc7871649bb40", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11145, "upload_time": "2016-10-24T12:10:47", "url": "https://files.pythonhosted.org/packages/7c/f4/a0d5f682f6b306f418a1a70bb7d7ee311108473f3e29c3853f055b0a6112/pyleri-1.1.3.tar.gz" } ], "1.1.4": [ { "comment_text": "", "digests": { "md5": "f5188a942c86388a93d187020804b920", "sha256": "43b336be6346d8909a01199aaae483e4b6ee19f8a27f9799cd5503ff92738d96" }, "downloads": -1, "filename": "pyleri-1.1.4.tar.gz", "has_sig": false, "md5_digest": "f5188a942c86388a93d187020804b920", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11144, "upload_time": "2016-10-25T07:36:10", "url": "https://files.pythonhosted.org/packages/b7/69/ec97d1bcf48aea5a036646371cee9cccb33cf236feee041cbec400f4a5f9/pyleri-1.1.4.tar.gz" } ], "1.1.5": [ { "comment_text": "", "digests": { "md5": "8cda55009717586ded39e3ff0971e5f0", "sha256": "853b9d15154a057af01588102ea62d3fb4e002188834f80ccc5b2fe50b23460f" }, "downloads": -1, "filename": "pyleri-1.1.5.tar.gz", "has_sig": false, "md5_digest": "8cda55009717586ded39e3ff0971e5f0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11819, "upload_time": "2017-03-13T20:42:03", "url": "https://files.pythonhosted.org/packages/e0/31/b8b2751f43fb5f6a4e8c7018588d084bb868f416d3a5c92fa2b9f87d86fb/pyleri-1.1.5.tar.gz" } ], "1.1.6": [ { "comment_text": "", "digests": { "md5": "d7ef15d0102859ee3d19871c42120d8c", "sha256": "90a7f7f000376f0f6f8d99f49b33ec9c7ed6e2100809440e22db96c6dd6b0c14" }, "downloads": -1, "filename": "pyleri-1.1.6.tar.gz", "has_sig": false, "md5_digest": "d7ef15d0102859ee3d19871c42120d8c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11767, "upload_time": "2017-06-20T20:15:35", "url": "https://files.pythonhosted.org/packages/4d/6a/a2ddf35f8df3d8d69ff8b119c05de43786330ae654aca8b489d7ff77ded1/pyleri-1.1.6.tar.gz" } ], "1.1.7": [ { "comment_text": "", "digests": { "md5": "ae0fcfc1963dff73987f84f9fef2402b", "sha256": "59ceecc7b672421e97910792023bb834840dc8d6877f8866fe0d96672f076e3b" }, "downloads": -1, "filename": "pyleri-1.1.7.tar.gz", "has_sig": false, "md5_digest": "ae0fcfc1963dff73987f84f9fef2402b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11895, "upload_time": "2017-06-22T13:16:42", "url": "https://files.pythonhosted.org/packages/84/fe/90535de1cbf433330c849c31fa87d986c96d801fff0babd80b0c58ecb240/pyleri-1.1.7.tar.gz" } ], "1.1.8": [ { "comment_text": "", "digests": { "md5": "a095f1303a8680a5c674e7ae2154bd6d", "sha256": "f6713c0dd5298185f51340747643c0e781577169483af6714729254c4bba7018" }, "downloads": -1, "filename": "pyleri-1.1.8.tar.gz", "has_sig": false, "md5_digest": "a095f1303a8680a5c674e7ae2154bd6d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11856, "upload_time": "2017-06-23T08:26:59", "url": "https://files.pythonhosted.org/packages/f6/56/f7791f63375fe8fcf121179d4c07adf0dd492921086026560110b24e8668/pyleri-1.1.8.tar.gz" } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "ee06d764c244a305bcea48246515cd2d", "sha256": "b29c8f40c12d96a7925a0b2ced625bfbd630eb1bb8dcc3a6a28792531e076bf1" }, "downloads": -1, "filename": "pyleri-1.2.0.tar.gz", "has_sig": false, "md5_digest": "ee06d764c244a305bcea48246515cd2d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18725, "upload_time": "2018-07-05T14:28:10", "url": "https://files.pythonhosted.org/packages/85/9c/9e911e405104276cff3fca9b64cd6180f745d2373b8f359b0cef468f4e00/pyleri-1.2.0.tar.gz" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "fb434d31ca25623c8cf0c389767826d1", "sha256": "3a2ef69b7a4b37c113b90efaad466b74112e2f9631f724bb6bb2cf539d8e634d" }, "downloads": -1, "filename": "pyleri-1.2.1.tar.gz", "has_sig": false, "md5_digest": "fb434d31ca25623c8cf0c389767826d1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22314, "upload_time": "2018-07-05T15:58:34", "url": "https://files.pythonhosted.org/packages/08/e6/18eacf653edcfc9e8fe570053526b0202ca0cae3325c7e9c1838f7553c34/pyleri-1.2.1.tar.gz" } ], "1.2.2": [ { "comment_text": "", "digests": { "md5": "584e5b9f25d2a0b632a158f71ac769f3", "sha256": "5b539f342892bb55d2c6ebc9c9d4507570af33743d43a237c9315243d3b1963b" }, "downloads": -1, "filename": "pyleri-1.2.2.tar.gz", "has_sig": false, "md5_digest": "584e5b9f25d2a0b632a158f71ac769f3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22913, "upload_time": "2018-07-09T11:19:14", "url": "https://files.pythonhosted.org/packages/a4/ca/49265a8ed8d72807ee9af12e4ea87ae1013c3bafb17bf3dbe616cb159785/pyleri-1.2.2.tar.gz" } ], "1.3.0": [ { "comment_text": "", "digests": { "md5": "caadd8dbb0ba9638262ae4ba8def5b83", "sha256": "c5a3d225adfde5df78a045c0bce9fee8d89c1bd7a7702788e60ca7a2c774ca12" }, "downloads": -1, "filename": "pyleri-1.3.0.tar.gz", "has_sig": false, "md5_digest": "caadd8dbb0ba9638262ae4ba8def5b83", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34444, "upload_time": "2018-10-15T14:26:59", "url": "https://files.pythonhosted.org/packages/8a/f4/659a10e896befffc2221b4aa4ed2c98d00111d9a53484c70839235945e9c/pyleri-1.3.0.tar.gz" } ], "1.3.1": [ { "comment_text": "", "digests": { "md5": "dc980b84fe4e8ce28d6b9c952e38ff5a", "sha256": "ca15682a429d295d75930c43243dc7a1262a6c9aa52c02be1d9be2e20440ed7a" }, "downloads": -1, "filename": "pyleri-1.3.1.tar.gz", "has_sig": false, "md5_digest": "dc980b84fe4e8ce28d6b9c952e38ff5a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34498, "upload_time": "2018-10-25T20:02:26", "url": "https://files.pythonhosted.org/packages/23/d0/a6b4ec63d472b871675ffe295d024fd49a361d86387a4142a6560be462ed/pyleri-1.3.1.tar.gz" } ], "1.3.2": [ { "comment_text": "", "digests": { "md5": "819f65aed0a331f8f3b8ad7a67c7a156", "sha256": "f893dd2989feaa27ead244c0887e45aa831df408eeb7d19ecfe30358fbfabe69" }, "downloads": -1, "filename": "pyleri-1.3.2.tar.gz", "has_sig": false, "md5_digest": "819f65aed0a331f8f3b8ad7a67c7a156", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34527, "upload_time": "2018-10-29T10:01:20", "url": "https://files.pythonhosted.org/packages/49/17/3395056129b8090564f00f914640c665eb1206136708b09d5ed3ceb5d898/pyleri-1.3.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "819f65aed0a331f8f3b8ad7a67c7a156", "sha256": "f893dd2989feaa27ead244c0887e45aa831df408eeb7d19ecfe30358fbfabe69" }, "downloads": -1, "filename": "pyleri-1.3.2.tar.gz", "has_sig": false, "md5_digest": "819f65aed0a331f8f3b8ad7a67c7a156", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34527, "upload_time": "2018-10-29T10:01:20", "url": "https://files.pythonhosted.org/packages/49/17/3395056129b8090564f00f914640c665eb1206136708b09d5ed3ceb5d898/pyleri-1.3.2.tar.gz" } ] }