Skip to content

Commit

Permalink
Enable to define custom predicates on decisions
Browse files Browse the repository at this point in the history
The patch enables to address the decision points of the generator.
Having these addresses, custom model implementations have the
power to redefine them according to the expected input format.
  • Loading branch information
renatahodovan committed Mar 9, 2020
1 parent 3f7abef commit fdffe7a
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 68 deletions.
110 changes: 55 additions & 55 deletions examples/fuzzer/HTMLGenerator.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions grammarinator/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

from .cooldown_model import CooldownModel
from .default_model import DefaultModel
from .dispatching_model import DispatchingModel


__all__ = [
'CooldownModel',
'DefaultModel',
'DispatchingModel',
]
10 changes: 5 additions & 5 deletions grammarinator/model/cooldown_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ def __init__(self, model, weights=None, cooldown=1.0):
self.weights = weights or dict()
self.cooldown = cooldown

def choice(self, name, choices):
i = self.model.choice(name, [w * self.weights.get((name, i), 1) for i, w in enumerate(choices)])
self.weights[(name, i)] = self.weights.get((name, i), 1) * self.cooldown
def choice(self, node, idx, choices):
i = self.model.choice(node, idx, [w * self.weights.get((node.name, i), 1) for i, w in enumerate(choices)])
self.weights[(node.name, i)] = self.weights.get((node.name, i), 1) * self.cooldown
return i

def quantify(self, min, max):
yield from self.model.quantify(min=min, max=max)
def quantify(self, node, idx, min, max):
yield from self.model.quantify(node, idx, min, max)
4 changes: 2 additions & 2 deletions grammarinator/model/default_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class DefaultModel(object):
def random_decision():
return bool(random.getrandbits(1))

def choice(self, name, choices):
def choice(self, node, idx, choices):
# assert sum(choices) > 0, 'Sum of choices is zero.'
max_item = max(choices)
choices = [i / max_item for i in choices]
Expand All @@ -26,7 +26,7 @@ def choice(self, name, choices):
upto += w
raise AssertionError('Shouldn\'t get here.')

def quantify(self, min, max):
def quantify(self, node, idx, min, max):
cnt = 0
for _ in range(min):
yield
Expand Down
19 changes: 19 additions & 0 deletions grammarinator/model/dispatching_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) 2020 Renata Hodovan, Akos Kiss.
#
# Licensed under the BSD 3-Clause License
# <LICENSE.rst or https://opensource.org/licenses/BSD-3-Clause>.
# This file may not be copied, modified, or distributed except
# according to those terms.

from .default_model import DefaultModel


class DispatchingModel(DefaultModel):

def choice(self, node, idx, choices):
name = 'choice_' + node.name
return (getattr(self, name) if hasattr(self, name) else super().choice)(node, idx, choices)

def quantify(self, node, idx, min, max):
name = 'quantify_' + node.name
yield from (getattr(self, name) if hasattr(self, name) else super().quantify)(node, idx, min, max)
19 changes: 13 additions & 6 deletions grammarinator/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ def __init__(self, antlr_parser_cls, actions):
self.indent_level = 0
self.charset_idx = 0
self.code_id = 0
self.alt_id = 0
self.quant_id = 0

self.graph = GrammarGraph()

Expand Down Expand Up @@ -325,14 +327,15 @@ def generate_grammar(self, node):

with self.indent():
for rule in generator_rules:
self.generator_body += self.generate_single(rule, None)
self.generator_body += self.generate_single(rule)

if node.grammarType().PARSER() or not (node.grammarType().LEXER() or node.grammarType().PARSER()):
with self.indent():
self.generator_body += self.line('default_rule = {name}\n'.format(name=generator_rules[0].RULE_REF()))

def generate_single(self, node, parent_id):
def generate_single(self, node, parent_id=None):
if isinstance(node, (self.antlr_parser_cls.ParserRuleSpecContext, self.antlr_parser_cls.LexerRuleSpecContext)):
self.alt_id, self.quant_id = 0, 0
parser_rule = isinstance(node, self.antlr_parser_cls.ParserRuleSpecContext)
node_type = UnparserRule if parser_rule else UnlexerRule
rule_name = str(node.RULE_REF() if parser_rule else node.TOKEN_REF())
Expand Down Expand Up @@ -392,9 +395,13 @@ def generate_single(self, node, parent_id):

conditions = [(self.new_code_id('cond'), self.find_conditions(child)) for child in children]
self.code_chunks.update(conditions)
result = self.line('choice = self.model.choice({alt_name!r}, [0 if {{{alt_name}}}[i] > self.max_depth else w for i, w in enumerate([{weights}])])'

result = self.line('choice = self.model.choice(current, {idx}, [0 if {{{alt_name}}}[i] > self.max_depth else w for i, w in enumerate([{weights}])])'
.format(alt_name=alt_name,
idx=self.alt_id,
weights=', '.join('{{{cond_id}}}'.format(cond_id=cond_id) for cond_id, _ in conditions)))
self.alt_id += 1

for i, child in enumerate(children):
alternative_name = '{alt_name}_{idx}'.format(alt_name=alt_name, idx=i)
self.graph.add_node(AlternativeNode(id=alternative_name))
Expand All @@ -403,6 +410,7 @@ def generate_single(self, node, parent_id):
result += self.line('{if_kw} choice == {idx}:'.format(if_kw='if' if i == 0 else 'elif', idx=i))
with self.indent():
result += self.generate_single(child, alternative_name) or self.line('pass')

return result

if isinstance(node, self.antlr_parser_cls.LabeledAltContext) and node.identifier():
Expand Down Expand Up @@ -458,7 +466,6 @@ def generate_single(self, node, parent_id):
return self.generate_single(node.children[0], parent_id)

suffix = str(suffix.children[0])

if suffix in ['?', '*']:
quant_name = self.new_code_id('quant')
self.graph.add_node(QuantifierNode(id=quant_name))
Expand All @@ -468,8 +475,8 @@ def generate_single(self, node, parent_id):
quant_args = {'?': 'min=0, max=1', '*': 'min=0, max=inf', '+': 'min=1, max=inf'}[suffix]
result = self.line('if self.max_depth >= {min_depth}:'.format(min_depth='0' if suffix == '+' else '{{{name}}}'.format(name=parent_id)))
with self.indent():
result += self.line('for _ in self.model.quantify({args}):'.format(args=quant_args))

result += self.line('for _ in self.model.quantify(current, {idx}, {args}):'.format(idx=self.quant_id, args=quant_args))
self.quant_id += 1
with self.indent():
result += self.generate_single(node.children[0], parent_id)
result += '\n'
Expand Down

0 comments on commit fdffe7a

Please sign in to comment.