{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Project 2 - Grammars" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "import fuzzingbook_utils" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: coverage in /opt/conda/lib/python3.6/site-packages (4.5.2)\r\n", "Requirement already satisfied: gcovr in /opt/conda/lib/python3.6/site-packages (4.1)\r\n", "Requirement already satisfied: jinja2 in /opt/conda/lib/python3.6/site-packages (from gcovr) (2.10)\r\n", "Requirement already satisfied: MarkupSafe>=0.23 in /opt/conda/lib/python3.6/site-packages (from jinja2->gcovr) (1.0)\r\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": 22, "metadata": {}, "outputs": [], "source": [ "from data.regex.regex_3 import _regex_core\n", "from data.regex.regex_3 import regex" ] }, { "cell_type": "code", "execution_count": 23, "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": 24, "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": 25, "metadata": {}, "outputs": [], "source": [ "from GrammarFuzzer import GrammarFuzzer, display_tree" ] }, { "cell_type": "code", "execution_count": 26, "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": 27, "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": 28, "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": 29, "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": 30, "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": 31, "metadata": {}, "outputs": [], "source": [ "from GrammarCoverageFuzzer import GrammarCoverageFuzzer\n", "from Grammars import is_valid_grammar, def_used_nonterminals\n", "import random, time\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", " #time_spent < 3 hrs = 10800 sec\n", " start_time = time.time()\n", " time_spent = 0\n", " while (time_spent <= 10800) and (len(inputs) <= 1000) and (len(f.max_expansion_coverage() - f.expansion_coverage()) > 0):\n", " inputs.append(f.fuzz())\n", " end_time = time.time()\n", " time_spent = end_time - start_time\n", "\n", " #print(\"set(inputs): \", set(inputs))\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": 32, "metadata": {}, "outputs": [], "source": [ "def ensure_all_valid(pattern, inputs):\n", " valid = True\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]))\n", " valid = False\n", "\n", " return valid\n" ] }, { "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": 33, "metadata": {}, "outputs": [], "source": [ "from coverage import Coverage\n", "import os" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "def run_with_py_coverage(pattern, inp):\n", " cwd = os.getcwd()\n", " if cwd.split(\"/\")[-2] != \"data\" and cwd.split(\"/\")[-1] != \"regex\": \n", " target_dir = os.path.join(cwd,\"data\", \"regex\")\n", " else: \n", " target_dir = cwd\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": 35, "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": 36, "metadata": {}, "outputs": [], "source": [ "def replace_all_special_chars(inp):\n", " #print(\"inp: \", inp)\n", " special_char_repl = {\"\\\\\": \"\\\\\\\\\\\\\\\\\", \"\\n\": \"\\\\\\n\", \"\\r\": \"\\\\\\r\", \\\n", " \"\\'\": \"\\\\\\'\", '\"':'\\\\\"', '`':'\\\\`'}\n", " #\"\\\\\": \"\\\\\\\\\\\\\"\n", "\n", " for key in special_char_repl:\n", " inp = inp.replace(key, special_char_repl[key])\n", " #print(\"replaced inp: \", inp)\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": 37, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "\n", "def run_with_c_coverage(pattern, inp):\n", " cwd = os.getcwd()\n", " if cwd.split(\"/\")[-2] != \"data\" and cwd.split(\"/\")[-1] != \"regex\":\n", " target_dir = os.path.join(cwd , \"data\", \"regex\")\n", " else: \n", " target_dir = cwd\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": 38, "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": null, "metadata": { "code_folding": [] }, "outputs": [], "source": [] }, { "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": 40, "metadata": {}, "outputs": [], "source": [ "old_simple = [[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]]\n", "\n", "new_simple =[[r'^\\s+$', 278, 1099],[r'^$', 157, 820], [r'/b(a|e|i|o|u)t/', 303, 1171], [r'^The$', 241, 988], \\\n", " [r'of despair$', 170, 998], [r'^abc$', 241, 988], [r'grammar$', 170, 999], [r'hi|hello$', 272, 1088], \\\n", " [r'ab+$', 207, 1153], [r'ab?$', 207, 1140], [r'a?b+$', 284, 1202], [r'(b|cd)ef$', 389, 1294], \\\n", " [r'a(bc)*$', 273, 1439], [r'(a|b)*c$', 398, 1482]]\n", "\n", "simple_regex = old_simple + new_simple\n", "\n", "old_complex = [[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", "\n", "new_complex = [[r'(|<.+\\W>)$', 423, 1752], [r'\\d{2}[ ]?\\d{3}$', 356, 1100], \\\n", " [r'\\d{3}-\\d{4}$', 330, 1173], [r'[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z][ ]?\\d[ABCEGHJ-NPRSTV-Z]\\d$', 342, 1160], \\\n", " [r'\\d{5}([ -]\\d{4})?$', 430, 1470], [r'\\d{5}$', 299, 1072], [r'(/[*]+)((\\w| )*)([*]+/)$', 424, 1637], \\\n", " [r'^((\\w| )+)/([^/]+)$', 473, 1618], [r'^[A-PR-WY][1-9]\\d\\s?\\d{4}[1-9]$', 390, 1201], \\\n", " [r'(^\\d{5}(-\\d{4})?)|(^[ABCEGHJKLMNPRSTVXY]{1}\\d{1}[A-Z]{1} *\\d{1}[A-Z]{1}\\d{1})$', 524, 1587], \\\n", " [r'^([1-9]|0[1-9]|[12][0-9]|3[01])\\D([1-9]|0[1-9]|1[012])\\D(19[0-9][0-9]|20[0-9][0-9])$', 455, 1366], \\\n", " [r'^3[47][0-9]{13}$', 375, 1244], [r'^(\\w|,|\\s|-)+[.][A-Za-z]{3}$', 520, 1673], \\\n", " [r'^([a-z][a-z0-9-]+([.]|-*[.]))+[a-z]{2,6}$', 494, 1763], \\\n", " [r'^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$', 530, 1891], \\\n", " [r'^(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])[.](\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5]){3}$', 487, 1723], \\\n", " [r'^((\\w| )*[a-z])((\\w| )*[A-Z])((\\w| )*\\d)(\\w| ){6,12}$', 482, 1640], [r'^[a-z0-9_-]{6,18}$', 361, 1149], \\\n", " [r'^([a-z0-9_.+-]+)@([0-9a-z.-]+)[.]([a-z.]{2,6})$', 444, 1571], [r'^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+[.][a-zA-Z]{2,6})*$', 431, 1670], \\\n", " [r'^([a-z0-9_.-]+)@([0-9a-z.-]+)[.]([a-z.]{2,6})$', 444, 1571],[r'^[a-z0-9_-]{3,16}$', 361, 1149], \\\n", " [r'^(19|20)\\d{2}$', 465, 1415],[r'^[+]?([0-9]|\\s)+[(]?([0-9]|\\s){10,}$', 475, 1697], \\\n", " [r'^[+]?([0-9]|\\s){3,}$', 475, 1638],[r'^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$', 401, 1324], \\\n", " [r'^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$', 496, 1468],[r'^#?([a-f0-9]{6}|[a-f0-9]{3})$', 496, 1468], \\\n", " [r'^[a-z0-9-]+$', 336, 1147], [r'^[+-<>., ]+$', 336, 1151], \\\n", " [r'^([01]?[0-9]|2[0-3]):[0-5][0-9]$', 462, 1584],[r'^(0?[1-9]|[12][0-9]|3[01])([ /-])(0?[1-9]|1[012])2([0-9][0-9][0-9][0-9])(([ -])([0-1]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5]?[0-9])?$', 465, 1786], \\\n", " [r'^-?\\d*([.]\\d+)?$', 364, 1451], [r'^\\d*([.]\\d+)?$', 363, 1430], [r'^\\d*[.]\\d+$', 359, 1241], \\\n", " [r'^-?\\d*[.]?\\d+$', 353, 1284], [r'^-\\d*[.]?\\d+$', 331, 1310],[r'^\\d*[.]?\\d+$', 352, 1262], \\\n", " [r'^(\\w| )+[.](png|jpg|jpeg|gif|webp)$', 501, 1639],[r'[-]?[0-9]+[,.]?[0-9]*([/][0-9]+[,.]?[0-9]*)*$', 394, 1549], \\\n", " [r'^-?\\d+$', 317, 1161], [r'^-\\d+$', 294, 1189], [r'^\\d+$', 278, 1097] , [r'^([0-9]{3}|)$', 0, 0] \n", " ]\n", "\n", "\n", "complex_regex = old_complex + new_complex\n", "\n", "old_bonus = [[r'\\babc\\b$', 258, 865], [r'abc\\B', 177, 987], [r'\\babc\\B', 247, 928]]\n", "new_bonus = [[r'\\b\\d{13,16}\\b$', 303, 1063],[r'\\bon\\w+=\\S+((\\w| )*>)$', 436, 1263]]\n", "bonus_regex = old_bonus + new_bonus\n", "\n", "\n", "old_secret_set_of_regex = old_simple + old_complex + old_bonus\n", "new_secret_set_of_regex = new_simple + new_complex + new_bonus\n", "\n", "\n", "\n", "secret_set_of_regex = old_secret_set_of_regex + new_secret_set_of_regex \n" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "code_folding": [] }, "outputs": [], "source": [ "from multiprocessing import Process\n", "import traceback\n", "\n", "def run_with_limited_time(func, args, kwargs, time):\n", " p = Process(target=func, args=args, kwargs=kwargs)\n", " p.start()\n", " p.join(time)\n", " if p.is_alive():\n", " p.terminate()\n", " return False\n", "\n", " return True" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "code_folding": [] }, "outputs": [], "source": [ "to_process = []\n", "for pattern, py_min_coverage, c_min_coverage in secret_set_of_regex:\n", " try:\n", " if run_with_limited_time(regex_to_CFG, (pattern,), {}, 60):\n", " grammar = regex_to_CFG(pattern)\n", " to_process.append([pattern, py_min_coverage, c_min_coverage, grammar])\n", " except Exception as e:\n", " e.__traceback__ = None\n", " traceback.clear_frames(e.__traceback__)\n", " pass\n", " " ] }, { "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": 43, "metadata": { "code_folding": [] }, "outputs": [], "source": [ "def evaluate(grammar, pattern, py_min_coverage, c_min_coverage):\n", " py_all_coverage, c_max_coverage = 0, 0\n", " py_percent, c_percent = \"0\", \"0\"\n", " \n", " try:\n", " inputs = generate_inputs(grammar)\n", " if ensure_all_valid(pattern, inputs):\n", " py_all_coverage, c_max_coverage, py_percent, c_percent = population_coverage(pattern, inputs) \n", " except Exception as e:\n", " e.__traceback__ = None\n", " traceback.clear_frames(e.__traceback__)\n", " pass\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": 44, "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(tuple([str(y) for y in tuple(range(1, len(secret_set_of_regex) + 1))]))\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": 45, "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", " passed_regexes = set()\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", " if py_all_coverage >= py_min_coverage and c_max_coverage >= c_min_coverage:\n", " passed_regexes.add(pattern)\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(secret_set_of_regex), ((score*100)/len(secret_set_of_regex))))\n", " if failed_regexes:\n", " print(\"The following regexes failed the coverage baseline: \", failed_regexes)\n", " \n", " return failed_regexes, passed_regexes" ] }, { "cell_type": "code", "execution_count": 46, "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% 1633 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-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$ 168 6% 879 6% 1\n", "\\S$ 168 6% 879 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", "^\\s+$ 278 10% 1099 8% 1\n", "^$ 157 5% 820 6% 1\n", "/b(a|e|i|o|u)t/ 303 11% 1171 8% 1\n", "^The$ 241 8% 988 7% 1\n", "of despair$ 170 6% 998 7% 1\n", "^abc$ 241 8% 988 7% 1\n", "grammar$ 170 6% 999 7% 1\n", "hi|hello$ 272 9% 1088 8% 1\n", "ab+$ 207 7% 1153 8% 1\n", "ab?$ 207 7% 1140 8% 1\n", "a?b+$ 284 10% 1202 8% 1\n", "(b|cd)ef$ 389 14% 1294 9% 1\n", "a(bc)*$ 273 9% 1439 10% 1\n", "(a|b)*c$ 398 14% 1482 10% 1\n", "(|<.+\\W>)$ 423 15% 1752 12% 1\n", "\\d{2}[ ]?\\d{3}$ 356 12% 1100 8% 1\n", "\\d{3}-\\d{4}$ 330 12% 1173 8% 1\n", "[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z][ ]?\\d[ABCEGHJ-NPRSTV-Z]\\d$ 342 12% 1160 8% 1\n", "\\d{5}([ -]\\d{4})?$ 430 15% 1470 10% 1\n", "\\d{5}$ 299 10% 1072 7% 1\n", "(/[*]+)((\\w| )*)([*]+/)$ 424 15% 1637 12% 1\n", "^((\\w| )+)/([^/]+)$ 473 17% 1618 11% 1\n", "^[A-PR-WY][1-9]\\d\\s?\\d{4}[1-9]$ 390 14% 1201 8% 1\n", "(^\\d{5}(-\\d{4})?)|(^[ABCEGHJKLMNPRSTVXY]{1}\\d{1}[A-Z]{1} *\\d{1}[A-Z]{1}\\d{1})$ 524 19% 1587 11% 1\n", "^([1-9]|0[1-9]|[12][0-9]|3[01])\\D([1-9]|0[1-9]|1[012])\\D(19[0-9][0-9]|20[0-9][0-9])$ 455 16% 1366 10% 1\n", "^3[47][0-9]{13}$ 375 13% 1244 9% 1\n", "^(\\w|,|\\s|-)+[.][A-Za-z]{3}$ 520 18% 1673 12% 1\n", "^([a-z][a-z0-9-]+([.]|-*[.]))+[a-z]{2,6}$ 494 18% 1763 12% 1\n", "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$ 0 0 0 0 0\n", "^(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])[.](\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5]){3}$ 487 17% 1723 12% 1\n", "^((\\w| )*[a-z])((\\w| )*[A-Z])((\\w| )*\\d)(\\w| ){6,12}$ 482 17% 1640 12% 1\n", "^[a-z0-9_-]{6,18}$ 361 13% 1149 8% 1\n", "^([a-z0-9_.+-]+)@([0-9a-z.-]+)[.]([a-z.]{2,6})$ 444 16% 1571 11% 1\n", "^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+[.][a-zA-Z]{2,6})*$ 431 15% 1670 12% 1\n", "^([a-z0-9_.-]+)@([0-9a-z.-]+)[.]([a-z.]{2,6})$ 444 16% 1571 11% 1\n", "^[a-z0-9_-]{3,16}$ 361 13% 1149 8% 1\n", "^(19|20)\\d{2}$ 465 16% 1415 10% 1\n", "^[+]?([0-9]|\\s)+[(]?([0-9]|\\s){10,}$ 0 0 0 0 0\n", "^[+]?([0-9]|\\s){3,}$ 0 0 0 0 0\n", "^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$ 401 14% 1324 9% 1\n", "^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$ 496 18% 1468 10% 1\n", "^#?([a-f0-9]{6}|[a-f0-9]{3})$ 496 18% 1468 10% 1\n", "^[+-<>., ]+$ 336 12% 1151 8% 1\n", "^([01]?[0-9]|2[0-3]):[0-5][0-9]$ 462 16% 1513 11% 0\n", "^(0?[1-9]|[12][0-9]|3[01])([ /-])(0?[1-9]|1[012])2([0-9][0-9][0-9][0-9])(([ -])([0-1]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5]?[0-9])?$ 465 16% 1786 13% 1\n", "^-?\\d*([.]\\d+)?$ 364 13% 1451 10% 1\n", "^\\d*([.]\\d+)?$ 363 13% 1430 10% 1\n", "^\\d*[.]\\d+$ 359 13% 1241 9% 1\n", "^-?\\d*[.]?\\d+$ 353 12% 1284 9% 1\n", "^-\\d*[.]?\\d+$ 331 12% 1302 9% 0\n", "^\\d*[.]?\\d+$ 352 12% 1262 9% 1\n", "^(\\w| )+[.](png|jpg|jpeg|gif|webp)$ 501 18% 1639 12% 1\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[-]?[0-9]+[,.]?[0-9]*([/][0-9]+[,.]?[0-9]*)*$ 394 14% 1549 11% 1\n", "^-?\\d+$ 317 11% 1161 8% 1\n", "^-\\d+$ 294 10% 1189 8% 1\n", "^\\d+$ 278 10% 1097 8% 1\n", "^([0-9]{3}|)$ 412 15% 1334 9% 1\n", "\\b\\d{13,16}\\b$ 303 11% 1063 7% 1\n", "\\bon\\w+=\\S+((\\w| )*>)$ 436 15% 1263 9% 1\n" ] }, { "data": { "image/png": "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OhtvvHFf+wuYAQAAAIApt2LFijz84Q/P/PnzU1WDLmdWaq3l1ltvzYoVK7Ltttv2dQwjMgAAAACAKXfXXXdl8803Fy4PUFVl8803X68ucgEzAAAAADAQwuXBW9/XQMAMAAAAAEBfBjaDuaqekOQTSR6X5L4kJ7XWTqyqRydZkmR+kmuSvLS19svqReknJjkwyZ1Jjmitfa871iuTvL079Ltba6dO5bkAAAAAAOvnpJM27PEWLhx/m4022ig777xzVq5cme233z6nnnpqHvrQh4667TXXXJP//M//zGGHHZYkOeWUU3LhhRfmwx/+8IYsO0ly9tln5x3veEfuuOOOtNZy0EEH5f3vf/+E9990003zq1/9aoPXNZpBdjCvTPJnrbXtkzw9yeuqaockxyb5emttuyRf724nyfOTbNd9LEzykSTpAul3Jnlakj2TvLOqHjWVJwIAAAAATD8PechDsmzZsixfvjybbLJJ/umf/mmt215zzTX5zGc+M+k1LV++PEcffXQ+9alP5bLLLsvy5cvz27/925P+uP0aWMDcWrthVQdya+32JJcl2SrJwUlWdSCfmuSF3ecHJ/lE6/luks2qassk+yf5WmvtF621Xyb5WpIDpvBUAAAAAIBpbq+99sqVV16Zd7zjHTnxxBNXrx933HH50Ic+lGOPPTb/8R//kd122y0f/OAHkyTXX399DjjggGy33XZ5y1vesnqf0047LTvvvHN22mmnvPWtb129vummm+a4447Lrrvumqc//em58cYbH1DHe9/73hx33HH53d/93STJnDlz8qd/+qdJkmuvvTb77bdfdtlll+y333756U9/miT5yU9+kmc84xnZY4898o53vON+x3vf+96XPfbYI7vsskve+c53bqBn6zeGYgZzVc1P8pQk/5Xksa21G5JeCJ1ki26zrZJcN2K3Fd3a2tZHe5yFVXVhVV148803b8hTAAAAAACmqZUrV+bss8/OzjvvnKOOOiqnntrrf73vvvty+umn5/DDD8/xxx+fvfbaK8uWLcub3vSmJMmyZcuyZMmSXHLJJVmyZEmuu+66XH/99XnrW9+ab3zjG1m2bFmWLl2aL3zhC0mSO+64I09/+tNz8cUXZ++9985HP/rRB9SyfPny7L777qPWefTRR+cVr3hFfvCDH+Twww/PG97whiTJG9/4xrz2ta/N0qVL87jHPW719uecc06uuOKKXHDBBVm2bFkuuuiinHfeeRv0uRt4wFxVmyb5lyTHtNb+Z6xNR1lrY6w/cLG1k1prC1prC+bNm7fuxQIAAAAAM8avf/3r7LbbblmwYEG22WabHHXUUZk/f34233zzfP/7388555yTpzzlKdl8881H3X+//fbLIx/5yMydOzc77LBDrr322ixdujT77rtv5s2blzlz5uTwww9fHepusskmOeigg5Iku+++e6655pp1qvf8889fPQP65S9/eb797W8nSb7zne/k0EMPXb2+yjnnnLP6HJ761KfmRz/6Ua644op1eszxDOwif0lSVRunFy5/urX2uW75xqrasrV2QzcC46ZufUWSJ4zYfesk13fr+66x/q3JrBsAAAAAmP5WzWBe06tf/eqccsop+fnPf54jjzxyrfs/+MEPXv35RhttlJUrV6a1UXtfkyQbb7xxqup+269pxx13zEUXXZRdd9113PpXHWvNz1dpreVtb3tbXvOa14x7rH4NrIO5emf8sSSXtdY+MOKuLyV5Zff5K5N8ccT6K6rn6Ulu60Zo/FuS51XVo7qL+z2vWwMAAAAAWGeHHHJIvvrVr2bp0qXZf//9kyQPf/jDc/vtt4+779Oe9rSce+65ueWWW3LvvffmtNNOyz777DPhx37zm9+cxYsX58c//nGS3piOD3ygF58+85nPzOmnn54k+fSnP53f+73fS5I861nPut/6Kvvvv39OPvnk/OpXv0qS/OxnP8tNN92UDWmQHczPSvLyJJdU1aofEyxKcnySM6rqqCQ/TfKS7r6zkhyY5MokdyZ5VZK01n5RVX+dZGm33btaa7+YmlMAAAAAADaEhQsHXcFvbLLJJnn2s5+dzTbbLBtttFGSZJdddsmcOXOy66675ogjjsijHvWoUffdcsst8zd/8zd59rOfndZaDjzwwBx88METfuxddtklJ5xwQg499NDceeedqaq84AUvSJJ86EMfypFHHpn3ve99mTdvXj7+8Y8nSU488cQcdthhOfHEE/OiF71o9bGe97zn5bLLLssznvGMJL2LDH7qU5/KFlts8cAH7lON1bI9ky1YsKBdeOGFgy4DAAAAAGalyy67LNtvv/2gyxjVfffdl6c+9an57Gc/m+22227Q5Uy60V6LqrqotbZgvH0HfpE/AAAAAIBhcemll+ZJT3pS9ttvv1kRLq+vgV7kDwAAAABgmOywww65+uqrB13GtKGDGQAAAACAvgiYAQAAAADoi4AZAAAAAIC+CJgBAAAAAOiLi/wBAAAAAIN30kkb9ngLF467yc9//vMcc8wxWbp0aR784Adn/vz5OeGEE/LkJz+5r+3W5ogjjshBBx2UF7/4xX2dyjDTwQwAAAAAzDqttRxyyCHZd999c9VVV+XSSy/N4sWLc+ONN/a13WwlYAYAAAAAZp1vfvOb2XjjjfMnf/Inq9d222237LXXXhPerrWWN7/5zdlpp52y8847Z8mSJUl6ofTRRx+dHXbYIS94wQty0003rd73oosuyj777JPdd989+++/f2644YZJPtPJZUQGAAAAADDrLF++PLvvvvt6bfe5z30uy5Yty8UXX5xbbrkle+yxR/bee++cf/75ufzyy3PJJZfkxhtvzA477JAjjzwy99xzT17/+tfni1/8YubNm5clS5bkuOOOy8knn7yhT2/KCJgBAAAAAPrw7W9/O4ceemg22mijPPaxj80+++yTpUuX5rzzzlu9/vjHPz7Pec5zkiSXX355li9fnuc+97lJknvvvTdbbrnlIE9hvQmYAQAAGNOiRYuSJIsXLx5wJQCw4ey4444588wz12u71tpa96uqUbffcccdc/7550+80CFnBjMAAAAAMOs85znPyd13352PfvSjq9eWLl2ac889d8Lb7b333lmyZEnuvffe3HzzzTnvvPOy5557Zu+9987pp5+ee++9NzfccEO++c1vJkl+53d+JzfffPPqgPmee+7JD3/4wyk428mjgxkAAAAAGLyFC6f04aoqn//853PMMcfk+OOPz9y5czN//vyccMIJE95u1bzlXXfdNVWV9773vXnc4x6XQw45JN/4xjey884758lPfnL22WefJMkmm2ySM888M294wxty2223ZeXKlTnmmGOy4447Tum5b0g1Vhv3TLZgwYJ24YUXDroMAACAoWdEBgCT4bLLLsv2228/6DLI6K9FVV3UWlsw3r5GZAAAAAAA0BcBMwAAAAAAfREwAwAAAAADMVvH9w6T9X0NBMwAAAAAwJSbO3dubr31ViHzALXWcuutt2bu3Ll9H2POBqwHAAAAAGBCtt5666xYsSI333zzoEuZ1ebOnZutt9667/0FzAAAAADAlNt4442z7bbbDroM1pMRGQAA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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Score: 81/86\n", "Score Percentage: 94.18604651162791%\n", "The following regexes failed the coverage baseline: [['^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$', 'Py'], ['^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$', 'C'], ['^[+]?([0-9]|\\\\s)+[(]?([0-9]|\\\\s){10,}$', 'Py'], ['^[+]?([0-9]|\\\\s)+[(]?([0-9]|\\\\s){10,}$', 'C'], ['^[+]?([0-9]|\\\\s){3,}$', 'Py'], ['^[+]?([0-9]|\\\\s){3,}$', 'C'], ['^([01]?[0-9]|2[0-3]):[0-5][0-9]$', 'C'], ['^-\\\\d*[.]?\\\\d+$', 'C']]\n" ] } ], "source": [ "failed_regex, passed_regex = compute_results()" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Detailed report of the implementation's score for each category:\n", "\tSimple Regex: 23/23\n", "\tComplex Regex: 53/58\n", "\tBonus Regex: 5/5\n" ] } ], "source": [ "total_simple = len(simple_regex)\n", "total_complex = len(complex_regex)\n", "total_bonus = len(bonus_regex)\n", "\n", "num_passed_simple, num_passed_complex, num_passed_bonus = 0,0,0\n", "\n", "def getRegexPatterns(eval_regex):\n", " res = set()\n", " for elem in eval_regex:\n", " res.add(str(elem[0]))\n", " return res\n", "\n", "simple_patterns = getRegexPatterns(simple_regex)\n", "complex_patterns = getRegexPatterns(complex_regex)\n", "bonus_patterns = getRegexPatterns(bonus_regex)\n", "\n", "for item in passed_regex:\n", " if item in simple_patterns:\n", " num_passed_simple += 1\n", " continue\n", " if item in complex_patterns:\n", " num_passed_complex += 1\n", " continue\n", " if item in bonus_patterns:\n", " num_passed_bonus += 1\n", " continue\n", " \n", "print(\"Detailed report of the implementation's score for each category:\")\n", "print(\"\\tSimple Regex: {0}/{1}\".format(num_passed_simple, total_simple))\n", "print(\"\\tComplex Regex: {0}/{1}\".format(num_passed_complex, total_complex))\n", "print(\"\\tBonus Regex: {0}/{1}\".format(num_passed_bonus, total_bonus))\n" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "failed_regex: {'^-\\\\d*[.]?\\\\d+$', '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])[.]){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$', '^([01]?[0-9]|2[0-3]):[0-5][0-9]$', '^[+]?([0-9]|\\\\s)+[(]?([0-9]|\\\\s){10,}$', '^[+]?([0-9]|\\\\s){3,}$'}\n" ] } ], "source": [ "print(\"failed_regex: \", getRegexPatterns(failed_regex))" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "set_unattempted_regex: set()\n" ] } ], "source": [ "set_unattempted_regex = getRegexPatterns(secret_set_of_regex) - getRegexPatterns(failed_regex) - passed_regex\n", "print(\"set_unattempted_regex: \", set_unattempted_regex)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "# failed_regex: 5\n", "# set_unattempted_regex: 0\n" ] } ], "source": [ "print(\"# failed_regex: \", len(getRegexPatterns(failed_regex)))\n", "print(\"# set_unattempted_regex: \", len(set_unattempted_regex))" ] } ], "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": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "178px" }, "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 }