A context-sensitive and stochastic Lindenmayer System grammar parser. Parses strings of tokens using a set of production rules. Unlike traditional parsing, the rules are applied on the _entire_ string of tokens from left to right _before_ returning to the first symbol in the string.
| 45 | |
| 46 | |
| 47 | class LSystemGrammar: |
| 48 | """A context-sensitive and stochastic Lindenmayer System grammar parser. |
| 49 | |
| 50 | Parses strings of tokens using a set of production rules. |
| 51 | Unlike traditional parsing, the rules are applied on the _entire_ string of tokens from left to |
| 52 | right _before_ returning to the first symbol in the string. |
| 53 | |
| 54 | Example: |
| 55 | Given the rules 'a -> ab', 'b -> a', and the starting axiom 'a', |
| 56 | |
| 57 | 1st iteration: a -> ab |
| 58 | 2nd iteration: ab -> aba (apply 'a -> ab' on the first token, then 'b -> a' on the second). |
| 59 | 3rd iteration: aba -> abaab |
| 60 | |
| 61 | The rules may be context-sensitive, and may have at most one token of context to the left, |
| 62 | right, or both directions. |
| 63 | Some tokens may optionally be ignored when considering context. |
| 64 | |
| 65 | The rules may be stochastic, with more than one possible replacement for a given token. |
| 66 | The probabilities for each token can be specified. |
| 67 | If multiple replacements for the same token are parsed, but probabilities are not given, we |
| 68 | assume uniform probability. |
| 69 | |
| 70 | The left and right contexts are single tokens. |
| 71 | The probability may be None, or a float in (0, 1]. |
| 72 | |
| 73 | There may be multiple rules for the same token. |
| 74 | If, after considering context, there are multiple matching rules, |
| 75 | one will be picked randomly with the probability distribution specified in the rule definitions. |
| 76 | """ |
| 77 | |
| 78 | def __init__( |
| 79 | self, |
| 80 | rules: MultiDict[TokenName, RuleMapping], |
| 81 | ignore: Set[TokenName] = None, |
| 82 | seed: int = None, |
| 83 | ): |
| 84 | """Initialize a Lindenmayer-System grammar parser with the given rules. |
| 85 | |
| 86 | :param rules: A set of production rules. A mapping of token -> replacements. |
| 87 | """ |
| 88 | self.ignore: Set[TokenName] = ignore if ignore is not None else set() |
| 89 | self.rules: MultiDict[TokenName, RuleMapping] = rules |
| 90 | |
| 91 | self.seed = seed if seed is not None else random.randint(0, 2**32 - 1) |
| 92 | np.random.seed(self.seed) |
| 93 | logger.info(f"Using random seed: {self.seed}") |
| 94 | |
| 95 | def pick_rule(self, rules: List[RuleMapping], token, left_ctx, right_ctx) -> RuleMapping: |
| 96 | """Pick the right rule based off the probability values or the parametric condition.""" |
| 97 | # If there's not choice, no need to make it a random choice. |
| 98 | if len(rules) == 1: |
| 99 | return rules[0] |
| 100 | |
| 101 | for rule in rules: |
| 102 | if rule.probability is None: |
| 103 | return rule |
| 104 |
no outgoing calls