Name | lark-dynamic JSON |
Version |
1.1.4
JSON |
| download |
home_page | |
Summary | Grammar generator for Lark parsing toolkit |
upload_time | 2023-05-20 02:24:54 |
maintainer | |
docs_url | None |
author | Dmitry Gritsenko |
requires_python | >=3.7,<4.0 |
license | MIT |
keywords |
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
Dynamic grammar generator for [Lark parsing toolkit](https://github.com/lark-parser/lark)
# Why?
While writing EBNF by hand is a preferred approach for most use cases (because of rich syntax), it's only viable for static grammars.
If your grammar is variable (e.g. based on some kind of configuration), it should be built with code on the fly.
As per [lark#439](https://github.com/lark-parser/lark/issues/439), no API for grammar exists, so building a string is only solution (it's pretty fast though).
**This README implies you are familiar with Lark and Lark EBNF.**
# Basic usage
```python
from lark_dynamic import *
g = Grammar()
g.DIGIT = g.NONZERO | "0"
g.NONZERO = Range("1", "9")
g.INTEGER = makeBoolVariable(
"zero_leading_numbers",
true=Many(g.DIGIT),
false=(g.NONZERO, Some(g.DIGIT))
)
g.FLOAT[2] = Group(g.INTEGER, ".", Some(g.DIGIT)) | (".", Many(g.DIGIT))
g.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)
print(g.generate(zero_leading_numbers=False))
print(g.generate(zero_leading_numbers=True))
```
Which outputs:
*zero_leading_numbers=False*:
```
DIGIT: NONZERO | "0"
NONZERO: "1".."9"
INTEGER: ( NONZERO ( DIGIT )* )
FLOAT.2: ( INTEGER "." ( DIGIT )* ) | ( "." ( DIGIT )+ )
number: INTEGER -> integer | FLOAT -> float
```
*zero_leading_numbers=True*
```
DIGIT: NONZERO | "0"
NONZERO: "1".."9"
INTEGER: ( DIGIT )+
FLOAT.2: ( INTEGER "." ( DIGIT )* ) | ( "." ( DIGIT )+ )
number: INTEGER -> integer | FLOAT -> float
```
# Getting started
Install with:
`python -m pip install lark-dynamic`
To create a grammar, you need to create `Grammar` object:
```python
from lark_dynamic import * # it is recommended to create grammar in a separate file, so * import is fine
g = Grammar() # short name is convenient
```
Then write rules and terminals:
```python
# this is a terminal (uppercase)
g.NUMBER = RegExp(r'[0-9]+')
g.SIGN = Literal("+") | "-" # first (or second) string needs to be wrapped into a Literal to support |
g.test = Maybe(g.SIGN), Many(g.TEST)
```
# Reference
All code snippets use this setup:
```python
from lark_dynamic import *
g = Grammar()
```
## Grammar
Main object, stores rules, terminals, templates and directives
To create a rule, you can use a shorthand:
```python
g.any_lowercase_identifier = ... # rule contents here
```
Same for terminal:
```python
g.ANY_UPPERCASE_IDENTIFIER = ... # terminal contents here
```
To create a template (`name{arg1, arg2} = ...`) you need to use `[]`:
```python
g.template_name[g.arg1, g.arg2] = ... # template contents here
```
Directives have different syntaxes, so no special shorthand is present:
```python
g.make_directive("name", "directive contents here")
```
To build grammar, you need to call `.generate(**context)`:
```python
g.generate(some_variable=True) # variables are discussed below
```
## Variable
Since the purpose of the module is to create grammars dynamically, this class exists.
It takes a function, which accepts one argument (context) - a dictionary of everything passed to `.generate()`.
Function sould return anything renderable (e.g. a token or a tuple of tokens):
```python
def integer_term(context):
if context.get("zero_leading_numbers"):
return Many(g.DIGIT)
return g.NONZERO, Some(g.DIGIT)
g.DIGIT = g.NONZERO | "0"
g.NONZERO = Range("1", "9")
g.INTEGER = Variable(integer_term)
g.FLOAT[2] = Group(g.INTEGER, ".", Some(g.DIGIT)) | (".", Many(g.DIGIT))
g.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)
```
*(this is an example from Basic usage section rewritten to use Variable)*
### BoolVariable and makeBoolVariable
Common use case for Variable is to render different content based on a boolean key (such as in Basic usage example).
`BoolVariable` makes it more convenient:
```python
def integer_term(zln):
if zln:
return Many(g.DIGIT)
return g.NONZERO, Some(g.DIGIT)
g.DIGIT = g.NONZERO | "0"
g.NONZERO = Range("1", "9")
g.INTEGER = BoolVariable(integer_term, key="zero_leading_numbers")
g.FLOAT[2] = Group(g.INTEGER, ".", Some(g.DIGIT)) | (".", Many(g.DIGIT))
g.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)
```
`makeBoolVariable` goes even further, providing arguments for `True` and `False` values:
```python
g.DIGIT = g.NONZERO | "0"
g.NONZERO = Range("1", "9")
g.INTEGER = makeBoolVariable(
"zero_leading_numbers",
true=Many(g.DIGIT),
false=(g.NONZERO, Some(g.DIGIT))
)
g.FLOAT[2] = Group(g.INTEGER, ".", Some(g.DIGIT)) | (".", Many(g.DIGIT))
g.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)
```
## Literal
Used for literal strings:
```python
g.hello = Literal("Hello"), Literal("World")
```
yields:
```
hello: "Hello" "World"
```
In most cases you can just use a string. Explicit `Literal(...)` is needed to support `|` (see [Combinators/Option](#option-usage-rules))
### Using regexp flags on Literal
```python
g.hello = Literal("Hello").si, Literal("World").i
```
yields:
```
hello: "Hello"si "World"i
```
## Combinators
### Maybe (aka QuestionMark)
Corresponds to `?` combinator in Lark:
```python
g.hello = "Hello", Maybe(","), "World", QuestionMark("!") # QuestionMark is just an alias for Maybe
```
yields:
```
hello: "Hello" (",")? "World" ("!")?
```
### Some (aka Star)
Corresponds to `*` combinator in Lark:
```python
g.hello = "Hello", Maybe(","), "World", Some("!")
```
yields:
```
hello: "Hello" (",")? "World" ("!")*
```
### Many (aka Plus)
Corresponds to `+` combinator in Lark:
```python
g.POSITION = Literal("two number 9s") | "a number 9 large" | "a number 6 with extra dip" | "a number 7" | "two number 45s" | "one with cheese" | "a large soda"
g.order = "I'll have ", g.position, Many(", ", g.position), Maybe(", and ", g.position)
g.position = g.POSITION
```
yields:
```
POSITION: "two number 9s" | "a number 9 large" | "a number 6 with extra dip" | "a number 7" | "two number 45s" | "one with cheese" | "a large soda"
order: "I'll have " position ( ", " position )+ ( ", and " position )?
position: POSITION
```
### Optional (aka Brackets)
Corresponds to `[...]` combinator in Lark:
```python
g.hello = "Hello", Optional(","), "World", ["!"]
```
yields:
```
hello: "Hello" [","] "World" ["!"]
```
Can also be written as `[...]` (as a list, basically), but be aware of [`|` usage rules (below)](#option-usage-rules)
### Option
Corresponds to `|` combinator in Lark:
```python
g.hello = "Hello", Maybe(","), (Option("World", "dlroW"), ), Some("!") # note Option is placed inside a tuple, (explained below)
```
yields:
```
hello: "Hello" (",")? ("World" | "dlroW") ("!")*
```
Can also be written as `|`:
```python
g.hello = "Hello", Maybe(","), (Literal("World") | "dlroW", ), Some("!") # note Literal (explained below)
```
#### Option usage rules
**`|` priority in Lark is higher than in Python:**
`Maybe(","), Option("World", "dlroW"), Maybe("!")`
yields
`(",")? "World" | "dlroW" ("!")?`
which, in turn, is equal to
`(","? "World") | ("dlroW" "!"?)`
You should wrap option with any other combinator (e.g. `Group()` or tuple. Remember that `(x)` is not a tuple) or just use `OptionG`.
The reason why default `Option` doesn't do this is that wrapping top-level option in parens would break alias creation.
**`|` operator would not work with python built-in types (str, list or tuple)**
You need at least one `Token` (rule, combinator, literal etc.) on either side of `|` for it to work.
### Group (aka Parens)
Corresponds to `(...)` combinator in Lark:
```python
g.hello = "Hello", Group(Maybe(","), "World"), (Some("!"), )
```
yields:
```
hello: "Hello" ((",")? "World") (("!")*)
```
Can also be written as `(...)` (as a tuple, basically), but be aware of [`|` usage rules](#option-usage-rules)
`Some`, `Many` and `Maybe` also will render in parenthesis by default to avoid combinator collision (e.g. to avoid rendering `(("!")+)?` as `"!"+?` which is invalid).
### SomeSeparated and ManySeparated
Correspond to `content (sep, content)*` and `content (sep, content)+` patterns, respectively:
```python
g.POSITION = Literal("two number 9s") | "a number 9 large" | "a number 6 with extra dip" | "a number 7" | "two number 45s" | "one with cheese" | "a large soda"
g._COMMA = ",", Maybe(g.WS)
g.WS = " "
g.order = "I'll have ", SomeSeparated(g._COMMA, g.position), Maybe(g._COMMA, "and ", g.position)
g.position = g.POSITION
```
yields:
```
POSITION: "two number 9s" | "a number 9 large" | "a number 6 with extra dip" | "a number 7" | "two number 45s" | "one with cheese" | "a large soda"
_COMMA: "," ( WS )?
WS: " "
order: "I'll have " (position ( _COMMA position )*) ( _COMMA "and " position )?
position: POSITION
```
## RegExp
Corresponds to Lark regexp syntax (`/content/flags`):
```python
g.WORD = RegExp(r"\w+") # flags are provided as a second argument (e.g. RegExp(r"\w+", "is"))
g.some_phrase = g.WORD, Maybe(","), g.WORD, Some("!")
```
yields:
```
WORD: /\w+/
some_phrase: WORD ( "," )? WORD ( "!" )*
```
## Repeat
Corresponds to Lark repeat syntax: `item ~ n` and `item ~ n..m`:
```python
g.three_lemons = Repeat("Lemon", 3)
g.three_to_five_apples = Repeat("Lemon", [3, 5])
```
yields:
```
three_lemons: ( "Lemon" ) ~ 3
three_to_five_apples: ( "Lemon" ) ~ 3..5
```
## Prerendered
If you want to insert a prerendered content into a rule/terminal/template, you can use `Prerendered`:
```python
g.hello = "Hello", Prerendered('((",")? "World")'), Some("!")
```
yields:
```
hello: "Hello" ((",")? "World") ("!")*
```
### Empty
Sometimes (when using Variable) you'll need to render nothing.
It can be achieved with `Prerendered("")`, or its alias, `Empty`
## Modifiers
Rule and terminal modifiers (such as `?` and `!`) can be used with `Modifier` class (**works only on top level of rule/terminal**):
```python
g.parens = Modifier.INLINE_SINGLE("(", g.word, ")")
g.word = g.WORD
g.WORD = RegExp(r"\w+")
```
yields:
```
WORD: /\w+/
?parens: "(" word ")"
word: WORD
```
# Priority
To use terminal/rule priority, you can use `[priority]` notation (similar to templates, but with a number):
```python
g.DIGIT = g.NONZERO | "0"
g.NONZERO = Range("1", "9")
g.INTEGER = g.NONZERO, Some(g.DIGIT)
g.FLOAT[2] = Group(g.INTEGER, ".", Some(g.DIGIT)) | (".", Many(g.DIGIT))
g.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)
```
yields:
```
DIGIT: NONZERO | "0"
NONZERO: "1".."9"
INTEGER: NONZERO ( DIGIT )*
FLOAT.2: ( INTEGER "." ( DIGIT )* ) | ( "." ( DIGIT )+ )
number: INTEGER -> integer | FLOAT -> float
```
# Advanced usage
`Grammar` object can be edited after its creation.
First, you need to get a grammar wrapper: `wrapper = grammar.use_wrapper()`
## Get a definition
```python
g = Grammar()
g.HELLO = "Hello"
wrapper = g.use_wrapper()
print(wrapper.get_def("HELLO"))
# prints
# RuleDef(
# "Hello"
# )
```
## Replace definition contents
```python
g = Grammar()
g.HELLO = "Hello"
wrapper = g.use_wrapper()
wrapper.replace("HELLO", "World")
print(wrapper.get_def("HELLO"))
# prints
# RuleDef(
# "World"
# )
```
## Edit definition
```python
g = Grammar()
g.HELLO = "Hello"
wrapper = g.use_wrapper()
wrapper.edit("HELLO", priority=10)
print(wrapper.get_def("HELLO"))
# prints
# RuleDef(
# "World"
# )
```
## Extend definition
```python
g = Grammar()
g.HELLO = "Hello"
wrapper = g.use_wrapper()
wrapper.extend("HELLO", "World")
print(wrapper.get_def("HELLO"))
# prints
# RuleDef(
# Option(
# "Hello"
# "World"
# )
# )
```
## Getting all definitions / directives
You can get dictionaries with rules, terminals, templates, or a list of all directives with a wrapper:
```python
g = Grammar()
wrapper = g.use_wrapper()
wrapper.rules
wrapper.terminals
wrapper.templates
wrapper.directives
```
## Why a wrapper?
Because grammar object itself is used to create definitions with arbitrary names. Creating methods with common names would easily create a problem:
```python
g = Grammar()
g.edit = "EDIT", Some("blah")
g.save = "SAVE", Some(Option("lorem", "ipsum", "dolor", "sit", "amet"))
g.command = Option(g.edit, g.save) # g.edit used here as a rule, not method
```
Raw data
{
"_id": null,
"home_page": "",
"name": "lark-dynamic",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.7,<4.0",
"maintainer_email": "",
"keywords": "",
"author": "Dmitry Gritsenko",
"author_email": "k01419q45@ya.ru",
"download_url": "https://files.pythonhosted.org/packages/1c/fc/3b176c20097aa97a310733fb330be1c790149204071fa69a94ee10c727d2/lark_dynamic-1.1.4.tar.gz",
"platform": null,
"description": "Dynamic grammar generator for [Lark parsing toolkit](https://github.com/lark-parser/lark)\n\n# Why?\n\nWhile writing EBNF by hand is a preferred approach for most use cases (because of rich syntax), it's only viable for static grammars. \nIf your grammar is variable (e.g. based on some kind of configuration), it should be built with code on the fly. \nAs per [lark#439](https://github.com/lark-parser/lark/issues/439), no API for grammar exists, so building a string is only solution (it's pretty fast though).\n\n**This README implies you are familiar with Lark and Lark EBNF.** \n\n# Basic usage\n\n```python\nfrom lark_dynamic import *\n\ng = Grammar()\n\ng.DIGIT = g.NONZERO | \"0\"\ng.NONZERO = Range(\"1\", \"9\")\ng.INTEGER = makeBoolVariable(\n \"zero_leading_numbers\", \n true=Many(g.DIGIT), \n false=(g.NONZERO, Some(g.DIGIT))\n)\ng.FLOAT[2] = Group(g.INTEGER, \".\", Some(g.DIGIT)) | (\".\", Many(g.DIGIT))\n\ng.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)\n\nprint(g.generate(zero_leading_numbers=False))\nprint(g.generate(zero_leading_numbers=True))\n```\n\nWhich outputs: \n*zero_leading_numbers=False*: \n```\nDIGIT: NONZERO | \"0\"\nNONZERO: \"1\"..\"9\"\nINTEGER: ( NONZERO ( DIGIT )* )\nFLOAT.2: ( INTEGER \".\" ( DIGIT )* ) | ( \".\" ( DIGIT )+ )\n\nnumber: INTEGER -> integer | FLOAT -> float\n```\n\n*zero_leading_numbers=True* \n```\nDIGIT: NONZERO | \"0\"\nNONZERO: \"1\"..\"9\"\nINTEGER: ( DIGIT )+\nFLOAT.2: ( INTEGER \".\" ( DIGIT )* ) | ( \".\" ( DIGIT )+ )\n\nnumber: INTEGER -> integer | FLOAT -> float\n```\n\n# Getting started\n\nInstall with:\n\n`python -m pip install lark-dynamic`\n\nTo create a grammar, you need to create `Grammar` object:\n```python\nfrom lark_dynamic import * # it is recommended to create grammar in a separate file, so * import is fine\n\ng = Grammar() # short name is convenient\n```\n\nThen write rules and terminals:\n\n```python\n# this is a terminal (uppercase)\ng.NUMBER = RegExp(r'[0-9]+')\ng.SIGN = Literal(\"+\") | \"-\" # first (or second) string needs to be wrapped into a Literal to support |\ng.test = Maybe(g.SIGN), Many(g.TEST)\n```\n\n# Reference\n\nAll code snippets use this setup:\n```python\nfrom lark_dynamic import *\ng = Grammar()\n```\n\n## Grammar\n\nMain object, stores rules, terminals, templates and directives\n\nTo create a rule, you can use a shorthand:\n\n```python\ng.any_lowercase_identifier = ... # rule contents here\n```\n\nSame for terminal:\n\n```python\ng.ANY_UPPERCASE_IDENTIFIER = ... # terminal contents here\n```\n\nTo create a template (`name{arg1, arg2} = ...`) you need to use `[]`:\n\n```python\ng.template_name[g.arg1, g.arg2] = ... # template contents here\n```\n\nDirectives have different syntaxes, so no special shorthand is present:\n\n```python\ng.make_directive(\"name\", \"directive contents here\")\n```\n\nTo build grammar, you need to call `.generate(**context)`:\n\n```python\ng.generate(some_variable=True) # variables are discussed below\n```\n\n## Variable\n\nSince the purpose of the module is to create grammars dynamically, this class exists. \nIt takes a function, which accepts one argument (context) - a dictionary of everything passed to `.generate()`. \nFunction sould return anything renderable (e.g. a token or a tuple of tokens):\n\n```python\ndef integer_term(context):\n if context.get(\"zero_leading_numbers\"):\n return Many(g.DIGIT)\n return g.NONZERO, Some(g.DIGIT)\n\ng.DIGIT = g.NONZERO | \"0\"\ng.NONZERO = Range(\"1\", \"9\")\ng.INTEGER = Variable(integer_term)\ng.FLOAT[2] = Group(g.INTEGER, \".\", Some(g.DIGIT)) | (\".\", Many(g.DIGIT))\n\ng.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)\n```\n*(this is an example from Basic usage section rewritten to use Variable)*\n\n### BoolVariable and makeBoolVariable\n\nCommon use case for Variable is to render different content based on a boolean key (such as in Basic usage example). \n`BoolVariable` makes it more convenient:\n\n```python\ndef integer_term(zln):\n if zln:\n return Many(g.DIGIT)\n return g.NONZERO, Some(g.DIGIT)\n\ng.DIGIT = g.NONZERO | \"0\"\ng.NONZERO = Range(\"1\", \"9\")\ng.INTEGER = BoolVariable(integer_term, key=\"zero_leading_numbers\")\ng.FLOAT[2] = Group(g.INTEGER, \".\", Some(g.DIGIT)) | (\".\", Many(g.DIGIT))\n\ng.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)\n```\n\n`makeBoolVariable` goes even further, providing arguments for `True` and `False` values:\n\n```python\ng.DIGIT = g.NONZERO | \"0\"\ng.NONZERO = Range(\"1\", \"9\")\ng.INTEGER = makeBoolVariable(\n \"zero_leading_numbers\", \n true=Many(g.DIGIT), \n false=(g.NONZERO, Some(g.DIGIT))\n)\ng.FLOAT[2] = Group(g.INTEGER, \".\", Some(g.DIGIT)) | (\".\", Many(g.DIGIT))\n\ng.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)\n```\n\n## Literal\n\nUsed for literal strings:\n\n```python\ng.hello = Literal(\"Hello\"), Literal(\"World\")\n```\nyields:\n\n```\nhello: \"Hello\" \"World\"\n```\n\nIn most cases you can just use a string. Explicit `Literal(...)` is needed to support `|` (see [Combinators/Option](#option-usage-rules))\n\n### Using regexp flags on Literal\n\n```python\ng.hello = Literal(\"Hello\").si, Literal(\"World\").i\n```\nyields:\n```\nhello: \"Hello\"si \"World\"i\n```\n\n## Combinators\n\n### Maybe (aka QuestionMark)\n\nCorresponds to `?` combinator in Lark:\n\n```python\ng.hello = \"Hello\", Maybe(\",\"), \"World\", QuestionMark(\"!\") # QuestionMark is just an alias for Maybe\n```\nyields:\n\n```\nhello: \"Hello\" (\",\")? \"World\" (\"!\")?\n```\n\n### Some (aka Star)\n\nCorresponds to `*` combinator in Lark:\n\n```python\ng.hello = \"Hello\", Maybe(\",\"), \"World\", Some(\"!\")\n```\nyields:\n\n```\nhello: \"Hello\" (\",\")? \"World\" (\"!\")*\n```\n\n### Many (aka Plus)\n\nCorresponds to `+` combinator in Lark:\n\n```python\ng.POSITION = Literal(\"two number 9s\") | \"a number 9 large\" | \"a number 6 with extra dip\" | \"a number 7\" | \"two number 45s\" | \"one with cheese\" | \"a large soda\"\n\ng.order = \"I'll have \", g.position, Many(\", \", g.position), Maybe(\", and \", g.position)\ng.position = g.POSITION\n```\nyields:\n\n```\nPOSITION: \"two number 9s\" | \"a number 9 large\" | \"a number 6 with extra dip\" | \"a number 7\" | \"two number 45s\" | \"one with cheese\" | \"a large soda\"\n\norder: \"I'll have \" position ( \", \" position )+ ( \", and \" position )?\nposition: POSITION\n```\n\n### Optional (aka Brackets)\n\nCorresponds to `[...]` combinator in Lark:\n\n```python\ng.hello = \"Hello\", Optional(\",\"), \"World\", [\"!\"]\n```\nyields:\n\n```\nhello: \"Hello\" [\",\"] \"World\" [\"!\"]\n```\n\nCan also be written as `[...]` (as a list, basically), but be aware of [`|` usage rules (below)](#option-usage-rules)\n\n### Option \n\nCorresponds to `|` combinator in Lark:\n\n```python\ng.hello = \"Hello\", Maybe(\",\"), (Option(\"World\", \"dlroW\"), ), Some(\"!\") # note Option is placed inside a tuple, (explained below)\n```\nyields:\n\n```\nhello: \"Hello\" (\",\")? (\"World\" | \"dlroW\") (\"!\")*\n```\n\nCan also be written as `|`:\n```python\ng.hello = \"Hello\", Maybe(\",\"), (Literal(\"World\") | \"dlroW\", ), Some(\"!\") # note Literal (explained below)\n```\n\n#### Option usage rules\n\n**`|` priority in Lark is higher than in Python:**\n\n`Maybe(\",\"), Option(\"World\", \"dlroW\"), Maybe(\"!\")` \nyields \n`(\",\")? \"World\" | \"dlroW\" (\"!\")?` \nwhich, in turn, is equal to \n`(\",\"? \"World\") | (\"dlroW\" \"!\"?)` \n\nYou should wrap option with any other combinator (e.g. `Group()` or tuple. Remember that `(x)` is not a tuple) or just use `OptionG`. \nThe reason why default `Option` doesn't do this is that wrapping top-level option in parens would break alias creation.\n\n**`|` operator would not work with python built-in types (str, list or tuple)**\n\nYou need at least one `Token` (rule, combinator, literal etc.) on either side of `|` for it to work.\n\n### Group (aka Parens)\n\nCorresponds to `(...)` combinator in Lark:\n\n```python\ng.hello = \"Hello\", Group(Maybe(\",\"), \"World\"), (Some(\"!\"), )\n```\nyields:\n\n```\nhello: \"Hello\" ((\",\")? \"World\") ((\"!\")*)\n```\n\nCan also be written as `(...)` (as a tuple, basically), but be aware of [`|` usage rules](#option-usage-rules) \n`Some`, `Many` and `Maybe` also will render in parenthesis by default to avoid combinator collision (e.g. to avoid rendering `((\"!\")+)?` as `\"!\"+?` which is invalid).\n\n### SomeSeparated and ManySeparated\n\nCorrespond to `content (sep, content)*` and `content (sep, content)+` patterns, respectively:\n\n```python\ng.POSITION = Literal(\"two number 9s\") | \"a number 9 large\" | \"a number 6 with extra dip\" | \"a number 7\" | \"two number 45s\" | \"one with cheese\" | \"a large soda\"\ng._COMMA = \",\", Maybe(g.WS)\ng.WS = \" \"\n\ng.order = \"I'll have \", SomeSeparated(g._COMMA, g.position), Maybe(g._COMMA, \"and \", g.position)\ng.position = g.POSITION\n```\nyields:\n\n```\nPOSITION: \"two number 9s\" | \"a number 9 large\" | \"a number 6 with extra dip\" | \"a number 7\" | \"two number 45s\" | \"one with cheese\" | \"a large soda\"\n_COMMA: \",\" ( WS )?\nWS: \" \"\n\norder: \"I'll have \" (position ( _COMMA position )*) ( _COMMA \"and \" position )?\nposition: POSITION\n```\n\n## RegExp\n\nCorresponds to Lark regexp syntax (`/content/flags`):\n\n```python\ng.WORD = RegExp(r\"\\w+\") # flags are provided as a second argument (e.g. RegExp(r\"\\w+\", \"is\")) \ng.some_phrase = g.WORD, Maybe(\",\"), g.WORD, Some(\"!\")\n```\nyields:\n```\nWORD: /\\w+/\n\nsome_phrase: WORD ( \",\" )? WORD ( \"!\" )*\n```\n\n## Repeat\n\nCorresponds to Lark repeat syntax: `item ~ n` and `item ~ n..m`:\n\n```python\ng.three_lemons = Repeat(\"Lemon\", 3)\ng.three_to_five_apples = Repeat(\"Lemon\", [3, 5])\n```\nyields:\n```\nthree_lemons: ( \"Lemon\" ) ~ 3\nthree_to_five_apples: ( \"Lemon\" ) ~ 3..5\n```\n\n## Prerendered\n\nIf you want to insert a prerendered content into a rule/terminal/template, you can use `Prerendered`:\n\n```python\ng.hello = \"Hello\", Prerendered('((\",\")? \"World\")'), Some(\"!\")\n```\nyields:\n\n```\nhello: \"Hello\" ((\",\")? \"World\") (\"!\")*\n```\n\n### Empty\n\nSometimes (when using Variable) you'll need to render nothing. \nIt can be achieved with `Prerendered(\"\")`, or its alias, `Empty`\n\n## Modifiers\n\nRule and terminal modifiers (such as `?` and `!`) can be used with `Modifier` class (**works only on top level of rule/terminal**):\n\n```python\ng.parens = Modifier.INLINE_SINGLE(\"(\", g.word, \")\")\ng.word = g.WORD\ng.WORD = RegExp(r\"\\w+\")\n```\nyields:\n```\nWORD: /\\w+/\n\n?parens: \"(\" word \")\"\nword: WORD\n```\n\n# Priority\n\nTo use terminal/rule priority, you can use `[priority]` notation (similar to templates, but with a number):\n\n```python\ng.DIGIT = g.NONZERO | \"0\"\ng.NONZERO = Range(\"1\", \"9\")\ng.INTEGER = g.NONZERO, Some(g.DIGIT)\ng.FLOAT[2] = Group(g.INTEGER, \".\", Some(g.DIGIT)) | (\".\", Many(g.DIGIT))\n\ng.number = Alias.integer(g.INTEGER) | Alias.float(g.FLOAT)\n```\nyields:\n```\nDIGIT: NONZERO | \"0\"\nNONZERO: \"1\"..\"9\"\nINTEGER: NONZERO ( DIGIT )*\nFLOAT.2: ( INTEGER \".\" ( DIGIT )* ) | ( \".\" ( DIGIT )+ )\n\nnumber: INTEGER -> integer | FLOAT -> float\n```\n\n\n# Advanced usage\n\n`Grammar` object can be edited after its creation.\n\nFirst, you need to get a grammar wrapper: `wrapper = grammar.use_wrapper()`\n\n\n## Get a definition\n\n\n```python\n\ng = Grammar()\n\n\ng.HELLO = \"Hello\"\n\nwrapper = g.use_wrapper()\n\nprint(wrapper.get_def(\"HELLO\")) \n\n# prints\n# RuleDef(\n# \"Hello\"\n# )\n\n``` \n\n## Replace definition contents\n\n\n```python\n\ng = Grammar()\n\ng.HELLO = \"Hello\"\n\nwrapper = g.use_wrapper()\nwrapper.replace(\"HELLO\", \"World\")\n\n\nprint(wrapper.get_def(\"HELLO\")) \n\n# prints\n# RuleDef(\n# \"World\"\n# )\n\n``` \n\n## Edit definition\n\n\n```python\n\ng = Grammar()\n\ng.HELLO = \"Hello\"\n\nwrapper = g.use_wrapper()\nwrapper.edit(\"HELLO\", priority=10)\n\n\nprint(wrapper.get_def(\"HELLO\")) \n\n# prints\n# RuleDef(\n# \"World\"\n# )\n\n``` \n\n## Extend definition\n\n\n```python\n\ng = Grammar()\n\ng.HELLO = \"Hello\"\n\nwrapper = g.use_wrapper()\nwrapper.extend(\"HELLO\", \"World\")\n\n\nprint(wrapper.get_def(\"HELLO\")) \n\n# prints\n# RuleDef(\n# Option(\n# \"Hello\"\n# \"World\"\n# )\n# )\n\n``` \n\n## Getting all definitions / directives\n\nYou can get dictionaries with rules, terminals, templates, or a list of all directives with a wrapper:\n\n```python\n\ng = Grammar()\nwrapper = g.use_wrapper()\n\nwrapper.rules\nwrapper.terminals\nwrapper.templates\nwrapper.directives\n```\n\n\n## Why a wrapper?\n\nBecause grammar object itself is used to create definitions with arbitrary names. Creating methods with common names would easily create a problem:\n\n\n```python\ng = Grammar()\n\ng.edit = \"EDIT\", Some(\"blah\")\ng.save = \"SAVE\", Some(Option(\"lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\"))\n\ng.command = Option(g.edit, g.save) # g.edit used here as a rule, not method\n\n```\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Grammar generator for Lark parsing toolkit",
"version": "1.1.4",
"project_urls": null,
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "a886a5c38120525dbd4cc9ed6d7764cb650ca736f779a411e235dfd440231972",
"md5": "ad42059746ffd6a9881d1df790401f34",
"sha256": "251c75c0ee946067ec5ac8a9ffe524b2b492b6079cb467672da3b836eb9892f6"
},
"downloads": -1,
"filename": "lark_dynamic-1.1.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ad42059746ffd6a9881d1df790401f34",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7,<4.0",
"size": 13290,
"upload_time": "2023-05-20T02:24:53",
"upload_time_iso_8601": "2023-05-20T02:24:53.059766Z",
"url": "https://files.pythonhosted.org/packages/a8/86/a5c38120525dbd4cc9ed6d7764cb650ca736f779a411e235dfd440231972/lark_dynamic-1.1.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "1cfc3b176c20097aa97a310733fb330be1c790149204071fa69a94ee10c727d2",
"md5": "f09f791a5309866f09cbf954ec277faa",
"sha256": "dfae34a906e699c13250a0a69ceb47c6f51f9de9aaa1c39a24ec5ed325c531cb"
},
"downloads": -1,
"filename": "lark_dynamic-1.1.4.tar.gz",
"has_sig": false,
"md5_digest": "f09f791a5309866f09cbf954ec277faa",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7,<4.0",
"size": 13419,
"upload_time": "2023-05-20T02:24:54",
"upload_time_iso_8601": "2023-05-20T02:24:54.889836Z",
"url": "https://files.pythonhosted.org/packages/1c/fc/3b176c20097aa97a310733fb330be1c790149204071fa69a94ee10c727d2/lark_dynamic-1.1.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-05-20 02:24:54",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "lark-dynamic"
}