{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Project 2 - Grammars" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import fuzzingbook_utils" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: coverage in /opt/conda/lib/python3.6/site-packages (4.5.2)\n", "Requirement already satisfied: gcovr in /opt/conda/lib/python3.6/site-packages (4.1)\n", "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.6/site-packages (from gcovr) (2.10)\n", "Requirement already satisfied: MarkupSafe>=0.23 in /opt/conda/lib/python3.6/site-packages (from jinja2->gcovr) (1.0)\n" ] } ], "source": [ "!pip install coverage gcovr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction\n", "\n", "Testing regular expression (regex) parsers is a challenging task. A simple regex, such as `a?(b|c)+d*`, requires dozens of inputs to cover all possibilities.\n", "\n", "To test regex, we use the python [regex](https://pypi.org/project/regex/) module (provided in the `data/regex` directory of this project). We use the source code of the `regex` module so that we can compute the coverage of the module. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "from data.regex.regex_3 import _regex_core\n", "from data.regex.regex_3 import regex" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a matches a?(b|c)+d*$: False\n", "b matches a?(b|c)+d*$: True\n" ] } ], "source": [ "pattern = 'a?(b|c)+d*$'\n", "print('a matches %s: %s' % (pattern, regex.match(pattern, 'a') != None))\n", "print('b matches %s: %s' % (pattern, regex.match(pattern, 'ab') != None))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An efficient approach to test regex is to use grammars. Regex are instances of regular languages - a subset of context-free grammar (CFG) - and, thus, can be tested with the techniques presented in this course.\n", "\n", "Assume the following grammar, which represents the `a?(b|c)+d*` regular expression." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "example_grammar = {'': [''],\n", " '': ['a', ''],\n", " '': ['b', 'c'],\n", " '': ['b', 'c', ''],\n", " '': ['d', ''],\n", " '': [''],\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Reusing the techniques from the lecture we expand and visualize it." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "from GrammarFuzzer import GrammarFuzzer, display_tree" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "\n", "0\n", "<start>\n", "\n", "\n", "\n", "1\n", "<A>\n", "\n", "\n", "\n", "0->1\n", "\n", "\n", "\n", "\n", "\n", "3\n", "<BC>\n", "\n", "\n", "\n", "0->3\n", "\n", "\n", "\n", "\n", "\n", "8\n", "<D>\n", "\n", "\n", "\n", "0->8\n", "\n", "\n", "\n", "\n", "\n", "2\n", "<empty>\n", "\n", "\n", "\n", "1->2\n", "\n", "\n", "\n", "\n", "\n", "4\n", "b\n", "\n", "\n", "\n", "3->4\n", "\n", "\n", "\n", "\n", "\n", "5\n", "<BC2>\n", "\n", "\n", "\n", "3->5\n", "\n", "\n", "\n", "\n", "\n", "6\n", "b\n", "\n", "\n", "\n", "5->6\n", "\n", "\n", "\n", "\n", "\n", "7\n", "<BC2>\n", "\n", "\n", "\n", "5->7\n", "\n", "\n", "\n", "\n", "\n", "9\n", "d\n", "\n", "\n", "\n", "8->9\n", "\n", "\n", "\n", "\n", "\n", "10\n", "<D>\n", "\n", "\n", "\n", "8->10\n", "\n", "\n", "\n", "\n", "\n", "11\n", "d\n", "\n", "\n", "\n", "10->11\n", "\n", "\n", "\n", "\n", "\n", "12\n", "<D>\n", "\n", "\n", "\n", "10->12\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def as_tree(grammar):\n", " f = GrammarFuzzer(grammar)\n", " \n", " derivation_tree = f.init_tree()\n", " for i in range(6):\n", " derivation_tree = f.expand_tree_once(derivation_tree) \n", " display_tree(derivation_tree)\n", "\n", "as_tree(example_grammar)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And we can use it to produce valid input values" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ccbbcbbb matches a?(b|c)+d*$: True\n", "abbbbd matches a?(b|c)+d*$: True\n", "c matches a?(b|c)+d*$: True\n", "bbc matches a?(b|c)+d*$: True\n", "bbcbbccbcbcc matches a?(b|c)+d*$: True\n" ] } ], "source": [ "def test_input(grammar):\n", " f = GrammarFuzzer(grammar)\n", " input_value = f.fuzz()\n", " print('%s matches %s: %s' % (input_value, pattern, regex.match(pattern, input_value) != None))\n", "\n", "for i in range(5): \n", " test_input(example_grammar)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Objective\n", "\n", "The goal of this project is to _implement an algorithm that constructs a CFG from an arbitrary regular expression. Then, use the techniques learned in the lecture (i.e. efficient grammar-based fuzzing) to automatically expand your grammar and to automatically produce a set of inputs that match the given regular expression, covering as many grammar productions as possible._" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Regex specification\n", "\n", "In this project, we restrict the regex specification into its simpler constructions and grouped then into 3 categories: _basic syntax, complex syntax and bonus_ as follows:\n", "\n", "| Category | Components |\n", "|----------|-----------|\n", "| Basic Syntax | Anchors `^` and `$`
Groupings `()`
Whitespace `\\s = [ \\t\\n\\r\\f\\v]`
Quantifiers `*`, `+`, `?`
concatenation
OR operator | | \n", "| Complex syntax | Character classes `[0-9]`, `[a-z]`, `[A-Z]`, `[0-9A-Za-z]`
Special character classes `\\d = [0-9]`, `\\w = [0-9a-zA-Z_]`
Bounded quantifier `{}`
Set Negation `[^]` (requires look-ahead)
`\\D = [^0-9]`
`\\W = [^0-9a-zA-Z_]`
`\\S = [^\\s]` | \n", "| Bonus | Boundaries `\\b` and `\\B` | |\n", "\n", "In your implementation, ignore any non-listed regex elements, such as flags, back references, other types of look-ahead, look-behind and greedy/lazy match.\n", "\n", "I have also updated the project accordingly, you can `git pull` to obtain the \n", "latest version of the project.\n", "\n", "We use the [regex parser](https://pypi.org/project/regex/), version 2.4.151, instead of Python's native `re`, because `re` is just a wrapper for a C library. Confirm regex version using: " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2.4.151\n" ] } ], "source": [ "print(regex.__version__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Regex samples for development\n", "\n", "Ensure your implementation works on the following regex examples, and covers the provided minimum required coverage for parsing a represententative input for each regex:\n", "\n", "| Category | Examples | Min. Cov. LOC _regex_core.py | Min. Cov. LOC *.c|\n", "|--------------|----------|------|------|\n", "| Basic Syntax | `^ab$` | 241 (8%) | 988 (7%)| \n", "| Basic Syntax | `b$` | 170 (6%) | 949 (6%) |\n", "| Basic Syntax | `ab*$` | 207 (7%) | 1142 (8%) |\n", "| Basic Syntax | a|b`$` | 308 (11%) | 1024 (7%) |\n", "| Basic Syntax | a(b|c)`$` | 321 (11%) | 1198 (8%)|\n", "| Basic Syntax | (a|b)\\s`$` | 334 (12%) | 1131 (8%) |\n", "| Basic Syntax | ^a*(b+|c)d?`$` | 413 (15%) | 1442 (10%) |\n", "| Basic Syntax | ^a*(b+|c)d?\\se`$` | 455 (16%) | 1632 (11%) |\n", "| Basic Syntax | ^a+(b*|c)d?\\se`$` | 426 (15%) | 1564 (11%) |\n", "| Complex Syntax | `^[0-9]+\\s[a-z]*\\s?[A-Z]+$` | 303 (11%) | 1228 (9%) |\n", "| Complex Syntax | `\\w$` | 169 (6%) | 878 (6%) | \n", "| Complex Syntax | `\\d$` | 169 (6%) | 875 (6%) | \n", "| Complex Syntax | (a|b){2,5}$ | 401 (14%) | 1380 (10%)| \n", "| Complex Syntax | `[0-9a-zA-Z]$` | 252 (9%) | 902 (6%) |\n", "| Complex Syntax | `[0-9]$` | 222 (8%) | 852 (6%) |\n", "| Complex Syntax | `[a-z]$` | 222 (8%) | 852 (6%) |\n", "| Complex Syntax | `[^a-zA-Z]$` | 213 (7%)| 879 (6%) |\n", "| Complex Syntax | `[^0-9]$` | 178 (6%) | 825 (6%) | \n", "| Complex Syntax | `\\D$` | 168 (6%) | 877 (6%) |\n", "| Complex Syntax | `\\W$` | 122 (4%) | 852 (6%) |\n", "| Complex Syntax | `\\S$` | 122 (4%) | 852 (6%) |\n", "| Complex Syntax | `[a-zA-Z]\\s\\w+\\s\\d\\s$` | 324 (11%) | 1295 (9%) |\n", "| Complex Syntax | `a(bc){2,5}$` | 297 (10%) | 1472 (10%) |\n", "| Bonus | `\\babc\\b$` | 258 (9%) | 865 (6%) |\n", "| Bonus | `abc\\B` | 177 (6%) | 987 (7%) |\n", "| Bonus | `\\babc\\B` | 247 (9%) | 928 (6%) |\n", " \n", "\n", "Note that the code coverage during the execution of the import statement (`import regex`) is unacccounted for, since this module was executed before the collection of coverage data began. We also do not track or evaluate the coverage of the python code (`regex.py`), since it is always the same for`regex.match` method call.\n", "\n", "Coverage data for Python (i.e. `_regex_core.py`) were obtained using a python utility for measuring code coverage of Python programs called [Coverage.py](https://coverage.readthedocs.io/en/v4.5.x/), version 4.5.2. Confirm your Coverage.py version:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Coverage.py, version 4.5.2 with C extension\r\n", "Documentation at https://coverage.readthedocs.io\r\n" ] } ], "source": [ "!coverage --version" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Coverage data for the C extensions of regex (i.e. _regex.c and _regex_unicode.c) were obtained using the python utility for GNU gcov utility [Gcovr](https://gcovr.com/), version 4.1. You can confirm your Gcovr version using:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "gcovr 4.1\r\n", "\r\n", "Copyright 2013-2018 the gcovr authors\r\n", "Copyright 2013 Sandia Corporation\r\n", "Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,\r\n", "the U.S. Government retains certain rights in this software.\r\n" ] } ], "source": [ "!gcovr --version" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tips\n", "For more information on Python regex matching read the documentation of Python3 [re](https://docs.python.org/3/library/re.html).\n", "\n", "To test your implementation with sample regex inputs, use the following python libraries that generate matching strings for any regex: [rstr](https://bitbucket.org/leapfrogdevelopment/rstr) & [exrex](https://github.com/asciimoo/exrex)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Evaluation\n", "\n", "Your generated test inputs will be executed on [the fuzzingbook server](https://fuzzingbook.cispa.saarland/), based on the amount of executed methods and lines of code (LOC). \n", "\n", "__Note: if you are developing locally, use the source code of version 2018.11.07 of the [regex parser](https://pypi.org/project/regex/), however test on [the fuzzingbook server](https://fuzzingbook.cispa.saarland/) before submission.__\n", " \n", "Your implementation will be tested with a _secret_ set of regex, encompassing all previously specified syntax categories.\n", "\n", "## Grading framework\n", "\n", "For grading:\n", " * __To pass__, your implementation should automatically construct a grammar for an arbitrary set of regular expressions encompassing the basic syntax in the Regex Specification. __Points__ will be assigned based on the coverage achieved by the generated test inputs.\n", " * __To obtain extra points__, your implementation should support the construction of the grammar for all or some of the complex syntax specification. __Points__ will be assigned based on the number of features you cover, as well as on the coverage achieved by your generated test inputs.\n", " * __Bonus Points__ will be awarded to implementation which support the bonus specification (boundaries).\n", "\n", "\n", "## Auxiliary functions\n", "\n", "The following functions are available to assist your implementation and tests:\n", "\n", "### Generating Sample inputs from your grammar\n", "\n", "Using the GrammarCoverageFuzzer, produce unique set of inputs from your grammar:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "from GrammarCoverageFuzzer import GrammarCoverageFuzzer\n", "from Grammars import is_valid_grammar, def_used_nonterminals\n", "import random\n", "\n", "def generate_inputs(grammar):\n", " assert is_valid_grammar(grammar)\n", " start_seed, end_seed = 2000, 2005\n", " inputs = []\n", " for seed in range(start_seed, end_seed):\n", " random.seed(seed)\n", " f = GrammarCoverageFuzzer(grammar)\n", " while (len(f.max_expansion_coverage() - f.expansion_coverage()) > 0):\n", " inputs.append(f.fuzz()) \n", " return set(inputs)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ensuring all inputs are valid\n", "\n", "Your grammar should produce __only__ valid inputs. To ensure that all inputs your grammar can produce are valid you can use:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "def ensure_all_valid(pattern, inputs):\n", " res = [[inp, regex.match(pattern, inp) != None] for inp in inputs]\n", " \n", " failures = list(filter(lambda x: not x[1], res))\n", " \n", " if len(failures) > 0:\n", " raise ValueError(\"Invalid inputs: \" + str([x[0] for x in failures]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Python: Obtaining code coverage for an input\n", "\n", "You can use the `run_with_py_coverage` function to obtain the python code covered by each input generated by your grammar." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "from coverage import Coverage\n", "import os" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "def run_with_py_coverage(pattern, inp):\n", " cwd = os.getcwd()\n", " target_dir = os.path.join(cwd,\"data\", \"regex\")\n", " os.chdir(target_dir)\n", " \n", " regex._cache = {}\n", " cov = Coverage(include=[\"*.py\"])\n", " cov.start()\n", " regex.match(pattern, str(inp))\n", " cov.stop()\n", " cov.save()\n", " results = cov.analysis2(\"regex_3/_regex_core.py\")\n", " covered = set(results[1]) - set(results[3])\n", "\n", " os.chdir(cwd)\n", " return covered, len(set(results[1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### C Extensions: Obtaining code coverage for an input\n", "We implement helper function to execute bash commands:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "import subprocess\n", "\n", "def execute_bash_command(command):\n", " process = subprocess.Popen(command, shell=True,\n", " stdout=subprocess.PIPE, \n", " stderr=subprocess.PIPE)\n", " out, error = process.communicate()\n", " error = error.decode(\"utf-8\").strip() \n", " if error:\n", " raise ValueError(\"bash command error: '{0}',\\n during execution of command: \\n '{1}': \".format(error, command ))\n", " res = out.decode(\"utf-8\").strip()\n", " return res\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We implement the function `replace_all_special_chars()` to escape all special chars in bash command." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "def replace_all_special_chars(inp):\n", " special_char_repl = {\"\\\\\": \"\\\\\\\\\\\\\", \"\\n\": \"\\\\\\n\", \"\\r\": \"\\\\\\r\", \\\n", " \"\\'\": \"\\\\\\'\", '\"':'\\\\\"', '`':'\\\\`'}\n", "\n", " for key in special_char_repl:\n", " inp = inp.replace(key, special_char_repl[key])\n", " \n", " return inp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We implement the function `run_with_c_coverage()` to obtain coverage for the C classes in the regex module. \n" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "def run_with_c_coverage(pattern, inp):\n", " cwd = os.getcwd()\n", " target_dir = os.path.join(cwd , \"data\", \"regex\")\n", " if os.getcwd() != target_dir and os.path.isdir(target_dir):\n", " os.chdir(target_dir) \n", " \n", " #remove old build\n", " build_dir = os.path.join(os.getcwd(), \"build\")\n", " if os.path.isdir(build_dir):\n", " rm_command = \"rm -r \" + build_dir \n", " execute_bash_command(rm_command)\n", " \n", " #re-build regex \n", " c_flags = 'CFLAGS=\"-fprofile-arcs -ftest-coverage -fPIC -g -O0\" ' \n", " build_instr = 'python setup.py build_ext --inplace > /dev/null'\n", " build_command = c_flags + build_instr\n", " execute_bash_command(build_command)\n", "\n", " #run regex match test\n", " run_test_command = \"\"\"python -c \"from regex_3 import _regex_core;\\\n", " from regex_3 import regex;pattern = '{0}'; inp = '{1}';\\\n", " regex._cache = {{}}; regex.match(pattern, str(inp))\" \"\"\".\\\n", " format(pattern, replace_all_special_chars(inp))\n", "\n", " execute_bash_command(run_test_command) \n", "\n", " #obtain coverage of c extensions\n", " gcovr_instr = 'gcovr -r . '\n", " grep_TOTAL = 'grep -n \"TOTAL\" '\n", " c_cov_command = gcovr_instr + \"| \" + grep_TOTAL \n", " cov_out = execute_bash_command(c_cov_command) \n", " coverage = cov_out.split()[2]\n", " percent_coverage = cov_out.split()[3]\n", "\n", " os.chdir(cwd)\n", " return coverage, percent_coverage" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Obtain both Python and C coverage for a population of inputs \n", "For all your inputs, use `population_coverage` to obtain the overall cummulative coverage of Python code and the maximum coverage for C Code." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "def population_coverage(pattern, population):\n", " py_all_coverage = set()\n", " c_max_coverage = 0\n", " c_max_percent = 0\n", " py_max_percent = \"\"\n", "\n", " for inp in population:\n", " #obtain all covered LOC for Python code\n", " py_covered, py_length = run_with_py_coverage(pattern, inp)\n", " py_all_coverage |= py_covered\n", "\n", " #obtain max coverage for C code\n", " c_covered, percent_covered = run_with_c_coverage(pattern, inp)\n", " if c_max_coverage < int(c_covered):\n", " c_max_coverage = int(c_covered)\n", " c_max_percent = percent_covered\n", " \n", " py_max_percent = str(int(len(py_all_coverage)/py_length * 100)) + \"%\"\n", " \n", " return len(py_all_coverage), c_max_coverage, py_max_percent, c_max_percent" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Your code\n", "Write your code here to create a grammar out of an arbitrary regular expression. " ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "code_folding": [] }, "outputs": [], "source": [ "\n", "import string\n", "from collections import defaultdict\n", "\n", "grammar = defaultdict(list)\n", "\n", "#write your code below\n", "\n", "#regex to context free grammar conversion\n", "def regex_to_CFG(regex_pattern):\n", " \n", " return grammar\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Evaluation code\n", "\n", "The following code will be used to run your evaluation.\n", "\n", "__Note that the set of regular expression which will be used for evaluation may be different from those provided as example.__" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "secret_set_of_regex = [[r'^ab$', 241, 988], [r'b$', 170, 949],[r'ab*$', 207, 1142],\\\n", " [r'a|b$',308, 1024], [r'a(b|c)$', 321, 1198], [r'(a|b)\\s$',334, 1131],\\\n", " [r'^a*(b+|c)d?$', 413, 1442],[r'^a*(b+|c)d?\\se$', 455, 1632],\\\n", " [r'^a+(b*|c)d?\\se$',426, 1564], [r'^[0-9]+\\s[a-z]*\\s?[A-Z]+$', 303, 1228],\\\n", " [r'\\w$', 169, 878], [r'\\d$', 169, 875], [r'(a|b){2,5}$', 401, 1380],\\\n", " [r'[0-9a-zA-Z]$', 252, 902], [r'[0-9]$', 222, 852], [r'[a-z]$', 222, 852],\\\n", " [r'[^a-zA-Z]', 213, 879], [r'[^0-9]', 178, 825],\\\n", " [r'\\D$', 168, 877], [r'\\W', 122, 852] , [r'\\S', 122, 852],\\\n", " [r'[a-zA-Z]\\s\\w+\\s\\d\\s$', 324, 1295], [r'a(bc){2,5}$', 297, 1472],\\\n", " [r'\\babc\\b$', 258, 865], [r'abc\\B', 177, 987], [r'\\babc\\B', 247, 928]]" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "to_process = []\n", "for pattern, py_min_coverage, c_min_coverage in secret_set_of_regex:\n", " grammar = regex_to_CFG(pattern)\n", " to_process.append([pattern, py_min_coverage, c_min_coverage, grammar])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Each regex will be evaluated individually, against a minimum precomputed coverage.\n", "Plot coverage result and indicate errors. " ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "code_folding": [] }, "outputs": [], "source": [ "def evaluate(grammar, pattern, py_min_coverage, c_min_coverage):\n", " \n", " inputs = generate_inputs(grammar)\n", " ensure_all_valid(pattern, inputs)\n", " \n", " py_all_coverage, c_max_coverage, py_percent, c_percent = population_coverage(pattern, inputs) \n", " \n", " return py_all_coverage >= py_min_coverage and c_max_coverage >= c_min_coverage, py_all_coverage, c_max_coverage, py_percent, c_percent" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plot coverage result and indicate errors. " ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "def plot_bar_chart(py_cov_tuple, c_cov_tuple, py_err, c_err, num_grammars):\n", " \n", " fig, ax = plt.subplots(figsize=(20,10))\n", " index = np.arange(num_grammars)\n", " bar_width = 0.4\n", " opacity = 0.4\n", " error_config = {'ecolor': '0.3'}\n", "\n", " rects1 = ax.bar(index, py_cov_tuple, bar_width,\n", " alpha=opacity, color='b', yerr=py_err,\n", " error_kw=error_config,\n", " label='Python Code')\n", "\n", " rects2 = ax.bar(index + bar_width, c_cov_tuple, bar_width,\n", " alpha=opacity, color='r', yerr=c_err,\n", " error_kw=error_config,\n", " label='C Code')\n", "\n", " ax.set_xlabel('Regex')\n", " ax.set_ylabel('Coverage (LOC)')\n", " ax.set_title('Coverage of Regex by Language (Python & C)')\n", " ax.set_xticks(index + bar_width / 2)\n", " ax.set_xticklabels(('1', '2', '3', '4', '5', '6', '7', '8', '9', '10',\\\n", " '11', '12', '13', '14', '15', '16', '17', '18', '19', '20',\\\n", " '21', '22', '23', '24', '25', '26'))\n", " ax.legend()\n", "\n", " fig.tight_layout()\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Final result will be computed accordingly:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "def compute_results():\n", " overall_result, py_cov_list, c_cov_list, py_err, c_err, evaluation_list, failed_regexes = [], [], [], [], [], [], []\n", " \n", " dash = '-' * 98\n", " print(dash)\n", " print('{:<28s}{:>12s}{:>12s}{:>12s}{:>12s}{:>18s}'.format(\"Pattern\", \"Py_cov (LOC)\", \"Py_cov (%)\", \"C_cov (LOC)\", \"C_cov (%)\", \"Pass(1)/Fail(0)\"))\n", " print(dash)\n", "\n", " \n", " for pattern, py_min_coverage, c_min_coverage, grammar in to_process: \n", " evaluation_result, py_all_coverage, c_max_coverage, py_percent, c_percent = evaluate(grammar, pattern, py_min_coverage, c_min_coverage)\n", "\n", " py_cov_list.append(py_all_coverage)\n", " c_cov_list.append(c_max_coverage)\n", " evaluation_list.append(evaluation_result)\n", " if py_all_coverage < py_min_coverage:\n", " py_err.append((py_all_coverage - py_min_coverage ))\n", " failed_regexes.append([pattern, \"Py\"])\n", " else:\n", " py_err.append(0)\n", " if c_max_coverage < c_min_coverage:\n", " c_err.append((c_max_coverage - c_min_coverage ))\n", " failed_regexes.append([pattern, \"C\"])\n", " else:\n", " c_err.append(0)\n", "\n", " print('{:<28s}{:>12d}{:>12s}{:>12d}{:>12s}{:>12b}'.format(pattern, py_all_coverage, py_percent, c_max_coverage, c_percent, c_max_coverage >= c_min_coverage and py_all_coverage >= py_min_coverage))\n", "\n", " overall_result.append([pattern, evaluation_result])\n", "\n", " plot_bar_chart(tuple(py_cov_list), tuple(c_cov_list), tuple(py_err), tuple(c_err), len(to_process))\n", " score = sum(evaluation_list)\n", " print(\"Score: {0}/{1}\\nScore Percentage: {2}%\".format(score,len(evaluation_list), ((score*100)/len(evaluation_list))))\n", " if failed_regexes:\n", " print(\"The following regexes failed the coverage baseline: \", failed_regexes)\n" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--------------------------------------------------------------------------------------------------\n", "Pattern Py_cov (LOC) Py_cov (%) C_cov (LOC) C_cov (%) Pass(1)/Fail(0)\n", "--------------------------------------------------------------------------------------------------\n", "^ab$ 241 8% 988 7% 1\n", "b$ 170 6% 949 6% 1\n", "ab*$ 207 7% 1142 8% 1\n", "a|b$ 308 11% 1024 7% 1\n", "a(b|c)$ 321 11% 1198 8% 1\n", "(a|b)\\s$ 334 12% 1131 8% 1\n", "^a*(b+|c)d?$ 413 15% 1442 10% 1\n", "^a*(b+|c)d?\\se$ 455 16% 1632 11% 1\n", "^a+(b*|c)d?\\se$ 426 15% 1564 11% 1\n", "^[0-9]+\\s[a-z]*\\s?[A-Z]+$ 303 11% 1228 9% 1\n", "\\w$ 169 6% 878 6% 1\n", "\\d$ 169 6% 875 6% 1\n", "(a|b){2,5}$ 401 14% 1380 10% 1\n", "[0-9a-zA-Z]$ 252 9% 902 6% 1\n", "[0-9]$ 222 8% 852 6% 1\n", "[a-z]$ 222 8% 852 6% 1\n", "[^a-zA-Z] 213 7% 879 6% 1\n", "[^0-9] 178 6% 825 6% 1\n", "\\D$ 168 6% 877 6% 1\n", "\\W 122 4% 852 6% 1\n", "\\S 122 4% 852 6% 1\n", "[a-zA-Z]\\s\\w+\\s\\d\\s$ 324 11% 1295 9% 1\n", "a(bc){2,5}$ 297 10% 1472 10% 1\n", "\\babc\\b$ 258 9% 865 6% 1\n", "abc\\B 177 6% 987 7% 1\n", "\\babc\\B 247 9% 928 6% 1\n" ] }, { "data": { "image/png": "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1l577V73C5gBAAAAgGm3bNmyPOEJT8j8+fNTVTNdzhqptZbbb789y5Yty+abb97rDCMyAAAAAIBpd88992TDDTcULs+gqsqGG274qLrIBcwAAAAAwIwQLs+8R/s7EDADAAAAANCLGcwAAAAAwIxbuHByzxsaGn/PWmutle222y7Lly/PVlttlZNOOinrrrvuiHuvu+66/Pd//3cOPvjgJMmJJ56YCy+8MB//+Mcns+wkyZlnnpn3ve99ueuuu9Jay/7775+PfOQjE75/vfXWyy9+8YtJr2skOpgBAAAAgDXS4x73uCxZsiRLly7N7Nmz84//+I+j7r3uuuvyz//8z1Ne09KlS3PEEUfkc5/7XK644oosXbo0z3jGM6b8cfsSMAMAAAAAa7zdd989V199dd73vvfl+OOPf3D96KOPzgknnJB3vetd+a//+q/suOOO+djHPpYkufHGG7Pffvtliy22yDve8Y4H7zn55JOz3XbbZdttt8073/nOB9fXW2+9HH300dlhhx2y66675uabb35YHR/60Idy9NFH57d+67eSJLNmzcqf/MmfJEmuv/767L333tl+++2z995750c/+lGS5Ic//GF222237LLLLnnf+973kPM+/OEPZ5dddsn222+fY445ZpJerV8TMAMAAAAAa7Tly5fnzDPPzHbbbZfDDz88J510UpLkgQceyCmnnJJDDjkkxx57bHbfffcsWbIkb3vb25IkS5YsyamnnppLL700p556am644YbceOONeec735lvfOMbWbJkSRYvXpwvf/nLSZK77roru+66ay6++OLsscce+eQnP/mwWpYuXZqdd955xDqPOOKIvPa1r80ll1ySQw45JEceeWSS5K1vfWve9KY3ZfHixXnqU5/64P6zzjorV111VS644IIsWbIkF110Uc4777xJfe0EzAAAAADAGumXv/xldtxxxyxYsCDz5s3L4Ycfnvnz52fDDTfM9773vZx11ll5znOekw033HDE+/fee+886UlPypw5c7L11lvn+uuvz+LFi7PXXntl7ty5mTVrVg455JAHQ93Zs2dn//33T5LsvPPOue666x5Rveeff/6DM6Bf85rX5Fvf+laS5Nvf/nYOOuigB9dXOOussx58DjvttFO+//3v56qrrnpEjzkeH/IHAAAAAKyRVsxgXtnrX//6nHjiifnJT36Sww47bNT711lnnQe/X2uttbJ8+fK01kbdv/baa6eqHrJ/Zdtss00uuuii7LDDDuPWv+Kslb9fobWWd7/73XnDG94w7ll96WAGAAAAABjmwAMPzL/9279l8eLF2XfffZMkT3jCE/Lzn/983Huf97zn5dxzz81tt92W+++/PyeffHL23HPPCT/229/+9nzgAx/ID37wgySDMR0f/ehHkyTPf/7zc8oppyRJPv/5z+e3f/u3kyQveMELHrK+wr777ptFixblF7/4RZLkxz/+cW655ZYJ1zIROpgBAAAAgBk3NDTTFfza7Nmz88IXvjDrr79+1lprrSTJ9ttvn1mzZmWHHXbI6173umywwQYj3rvJJpvkr//6r/PCF74wrbW89KUvzQEHHDDhx95+++1z3HHH5aCDDsrdd9+dqsrLXvayJMkJJ5yQww47LB/+8Iczd+7cfPrTn06SHH/88Tn44INz/PHH5xWveMWDZ+2zzz654oorsttuuyUZfMjg5z73uWy88ca9XpeR1Fgt26urBQsWtAsvvHCmywAAAAAARnHFFVdkq622mukyRvTAAw9kp512yhe+8IVsscUWM13OlBvpd1FVF7XWFox3rxEZAAAAAACdyy+/PM961rOy9957rxHh8qNlRAYAAAAAQGfrrbfOtddeO9NlrDZ0MAMAAAAA0IuAGQAAAACAXgTMAAAAAAD0ImAGAAAAAKAXH/IHAAAAAMy8hQsn97yhoXG3/OQnP8lRRx2VxYsXZ5111sn8+fNz3HHHZcstt+y1bzSve93rsv/+++eVr3xlr6eyKtPBDAAAAACscVprOfDAA7PXXnvlmmuuyeWXX54PfOADufnmm3vtW1MJmAEAAACANc4555yTtddeO2984xsfXNtxxx2z++67T3hfay1vf/vbs+2222a77bbLqaeemmQQSh9xxBHZeuut87KXvSy33HLLg/dedNFF2XPPPbPzzjtn3333zU033TTFz3RqGZEBAAAAAKxxli5dmp133vlR7fviF7+YJUuW5OKLL85tt92WXXbZJXvssUfOP//8XHnllbn00ktz8803Z+utt85hhx2W++67L295y1vyla98JXPnzs2pp56ao48+OosWLZrspzdtBMwAAAAAAD1861vfykEHHZS11lorT3nKU7Lnnntm8eLFOe+88x5cf9rTnpYXvehFSZIrr7wyS5cuzYtf/OIkyf33359NNtlkJp/CoyZgBgAAAADWONtss01OP/30R7WvtTbqfVU14v5tttkm559//sQLXcUJmAFgqkzmJyBP4NOPAQAAmLgXvehFec973pNPfvKT+eM//uMkyeLFi3P33Xdnzz33nNC+PfbYI5/4xCdy6KGH5o477sh5552XD3/4w1m+fHk+8YlP5LWvfW1uueWWnHPOOTn44IPz7Gc/O7feemvOP//87Lbbbrnvvvvygx/8INtss82MvAaTQcAMAAAAAMy8aW6sqap86UtfylFHHZVjjz02c+bMyfz583PcccdNeN+Kecs77LBDqiof+tCH8tSnPjUHHnhgvvGNb2S77bbLlltu+WBgPXv27Jx++uk58sgjc+edd2b58uU56qijVuuAucZq415dLViwoF144YUzXQYAazodzAAAAKO64oorstVWW810GWTk30VVXdRaWzDevb8xZVUBAAAAAPCYJmAGAAAAAKAXATMAAAAAMCMei+N7VzeP9ncgYAYAAAAApt2cOXNy++23C5lnUGstt99+e+bMmdP7jFmTWA8AAAAAwIRsttlmWbZsWW699daZLmWNNmfOnGy22Wa97xcwAwAAAADTbu21187mm28+02XwKE3ZiIyqWlRVt1TV0pXW31JVV1bVZVX1oWHr766qq7tr+w5b369bu7qq3jVV9QIAAAAA8MhMZQfziUk+nuQzKxaq6oVJDkiyfWvt3qrauFvfOsmrk2yT5GlJ/qOqtuxu+7skL06yLMniqvpqa+3yKawbAAAAAIAJmLKAubV2XlXNX2n5TUmOba3d2+25pVs/IMkp3foPq+rqJM/trl3dWrs2SarqlG6vgBkAAAAAYIZN2YiMUWyZZPeq+p+qOreqdunWN01yw7B9y7q10dYBAAAAAJhh0/0hf7OSbJBk1yS7JDmtqp6RpEbY2zJyAN5GOriqhpIMJcm8efMmpVgAAAAAAEY33R3My5J8sQ1ckOSBJBt1608ftm+zJDeOsf4wrbWFrbUFrbUFc+fOnZLiAQAAAAD4tekOmL+c5EVJ0n2I3+wktyX5apJXV9U6VbV5ki2SXJBkcZItqmrzqpqdwQcBfnWaawYAAAAAYARTNiKjqk5OsleSjapqWZJjkixKsqiqlib5VZJDW2styWVVdVoGH963PMmbW2v3d+cckeTfk6yVZFFr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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Score: 26/26\n", "Score Percentage: 100.0%\n" ] } ], "source": [ "compute_results()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.6" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }