{ "cells": [ { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "# Railroad Diagrams\n", "\n", "The code in this notebook helps with drawing syntax-diagrams. It is a (slightly customized) copy of the [excellent library from Tab Atkins jr.](https://github.com/tabatkins/railroad-diagrams), which unfortunately is not available as a Python package." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "**Prerequisites**\n", "\n", "This notebook needs some understanding on advanced concepts in Python and Graphics, notably \n", " * classes\n", " * the Python `with` statement\n", " * Scalable Vector Graphics" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Excursion: Railroad diagrams implementation" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:31:27.644176Z", "iopub.status.busy": "2024-01-18T17:31:27.643741Z", "iopub.status.idle": "2024-01-18T17:31:27.699593Z", "shell.execute_reply": "2024-01-18T17:31:27.699091Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import bookutils.setup" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:31:27.701762Z", "iopub.status.busy": "2024-01-18T17:31:27.701583Z", "iopub.status.idle": "2024-01-18T17:31:27.703323Z", "shell.execute_reply": "2024-01-18T17:31:27.703017Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import re\n", "import io" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.705116Z", "iopub.status.busy": "2024-01-18T17:31:27.704988Z", "iopub.status.idle": "2024-01-18T17:31:27.707636Z", "shell.execute_reply": "2024-01-18T17:31:27.707337Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class C:\n", " # Display constants\n", " DEBUG = False # if true, writes some debug information into attributes\n", " VS = 8 # minimum vertical separation between things. For a 3px stroke, must be at least 4\n", " AR = 10 # radius of arcs\n", " DIAGRAM_CLASS = 'railroad-diagram' # class to put on the root \n", " # is the stroke width an odd (1px, 3px, etc) pixel length?\n", " STROKE_ODD_PIXEL_LENGTH = True\n", " # how to align items when they have extra space. left/right/center\n", " INTERNAL_ALIGNMENT = 'center'\n", " # width of each monospace character. play until you find the right value\n", " # for your font\n", " CHAR_WIDTH = 8.5\n", " COMMENT_CHAR_WIDTH = 7 # comments are in smaller text by default\n", "\n", " DEFAULT_STYLE = '''\\\n", " svg.railroad-diagram {\n", " }\n", " svg.railroad-diagram path {\n", " stroke-width:3;\n", " stroke:black;\n", " fill:white;\n", " }\n", " svg.railroad-diagram text {\n", " font:14px \"Fira Mono\", monospace;\n", " text-anchor:middle;\n", " }\n", " svg.railroad-diagram text.label{\n", " text-anchor:start;\n", " }\n", " svg.railroad-diagram text.comment{\n", " font:italic 12px \"Fira Mono\", monospace;\n", " }\n", " svg.railroad-diagram rect{\n", " stroke-width:2;\n", " stroke:black;\n", " fill:mistyrose;\n", " }\n", "'''" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.709288Z", "iopub.status.busy": "2024-01-18T17:31:27.709165Z", "iopub.status.idle": "2024-01-18T17:31:27.711115Z", "shell.execute_reply": "2024-01-18T17:31:27.710830Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def e(text):\n", " text = re.sub(r\"&\", '&', str(text))\n", " text = re.sub(r\"<\", '<', str(text))\n", " text = re.sub(r\">\", '>', str(text))\n", " return str(text)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.712742Z", "iopub.status.busy": "2024-01-18T17:31:27.712626Z", "iopub.status.idle": "2024-01-18T17:31:27.714632Z", "shell.execute_reply": "2024-01-18T17:31:27.714373Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def determineGaps(outer, inner):\n", " diff = outer - inner\n", " if C.INTERNAL_ALIGNMENT == 'left':\n", " return 0, diff\n", " elif C.INTERNAL_ALIGNMENT == 'right':\n", " return diff, 0\n", " else:\n", " return diff / 2, diff / 2" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.716213Z", "iopub.status.busy": "2024-01-18T17:31:27.716107Z", "iopub.status.idle": "2024-01-18T17:31:27.717995Z", "shell.execute_reply": "2024-01-18T17:31:27.717733Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def doubleenumerate(seq):\n", " length = len(list(seq))\n", " for i, item in enumerate(seq):\n", " yield i, i - length, item" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.719609Z", "iopub.status.busy": "2024-01-18T17:31:27.719508Z", "iopub.status.idle": "2024-01-18T17:31:27.721310Z", "shell.execute_reply": "2024-01-18T17:31:27.721071Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def addDebug(el):\n", " if not C.DEBUG:\n", " return\n", " el.attrs['data-x'] = \"{0} w:{1} h:{2}/{3}/{4}\".format(\n", " type(el).__name__, el.width, el.up, el.height, el.down)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.722746Z", "iopub.status.busy": "2024-01-18T17:31:27.722644Z", "iopub.status.idle": "2024-01-18T17:31:27.726197Z", "shell.execute_reply": "2024-01-18T17:31:27.725938Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class DiagramItem:\n", " def __init__(self, name, attrs=None, text=None):\n", " self.name = name\n", " # up = distance it projects above the entry line\n", " # height = distance between the entry/exit lines\n", " # down = distance it projects below the exit line\n", " self.height = 0\n", " self.attrs = attrs or {}\n", " self.children = [text] if text else []\n", " self.needsSpace = False\n", "\n", " def format(self, x, y, width):\n", " raise NotImplementedError # Virtual\n", "\n", " def addTo(self, parent):\n", " parent.children.append(self)\n", " return self\n", "\n", " def writeSvg(self, write):\n", " write(u'<{0}'.format(self.name))\n", " for name, value in sorted(self.attrs.items()):\n", " write(u' {0}=\"{1}\"'.format(name, e(value)))\n", " write(u'>')\n", " if self.name in [\"g\", \"svg\"]:\n", " write(u'\\n')\n", " for child in self.children:\n", " if isinstance(child, DiagramItem):\n", " child.writeSvg(write)\n", " else:\n", " write(e(child))\n", " write(u''.format(self.name))\n", "\n", " def __eq__(self, other):\n", " return isinstance(self, type(\n", " other)) and self.__dict__ == other.__dict__\n", "\n", " def __ne__(self, other):\n", " return not (self == other)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.727669Z", "iopub.status.busy": "2024-01-18T17:31:27.727588Z", "iopub.status.idle": "2024-01-18T17:31:27.734679Z", "shell.execute_reply": "2024-01-18T17:31:27.734422Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Path(DiagramItem):\n", " def __init__(self, x, y):\n", " self.x = x\n", " self.y = y\n", " DiagramItem.__init__(self, 'path', {'d': 'M%s %s' % (x, y)})\n", "\n", " def m(self, x, y):\n", " self.attrs['d'] += 'm{0} {1}'.format(x, y)\n", " return self\n", "\n", " def ll(self, x, y): # was l(), which violates PEP8 -- AZ\n", " self.attrs['d'] += 'l{0} {1}'.format(x, y)\n", " return self\n", "\n", " def h(self, val):\n", " self.attrs['d'] += 'h{0}'.format(val)\n", " return self\n", "\n", " def right(self, val):\n", " return self.h(max(0, val))\n", "\n", " def left(self, val):\n", " return self.h(-max(0, val))\n", "\n", " def v(self, val):\n", " self.attrs['d'] += 'v{0}'.format(val)\n", " return self\n", "\n", " def down(self, val):\n", " return self.v(max(0, val))\n", "\n", " def up(self, val):\n", " return self.v(-max(0, val))\n", "\n", " def arc_8(self, start, dir):\n", " # 1/8 of a circle\n", " arc = C.AR\n", " s2 = 1 / math.sqrt(2) * arc\n", " s2inv = (arc - s2)\n", " path = \"a {0} {0} 0 0 {1} \".format(arc, \"1\" if dir == 'cw' else \"0\")\n", " sd = start + dir\n", " if sd == 'ncw':\n", " offset = [s2, s2inv]\n", " elif sd == 'necw':\n", " offset = [s2inv, s2]\n", " elif sd == 'ecw':\n", " offset = [-s2inv, s2]\n", " elif sd == 'secw':\n", " offset = [-s2, s2inv]\n", " elif sd == 'scw':\n", " offset = [-s2, -s2inv]\n", " elif sd == 'swcw':\n", " offset = [-s2inv, -s2]\n", " elif sd == 'wcw':\n", " offset = [s2inv, -s2]\n", " elif sd == 'nwcw':\n", " offset = [s2, -s2inv]\n", " elif sd == 'nccw':\n", " offset = [-s2, s2inv]\n", " elif sd == 'nwccw':\n", " offset = [-s2inv, s2]\n", " elif sd == 'wccw':\n", " offset = [s2inv, s2]\n", " elif sd == 'swccw':\n", " offset = [s2, s2inv]\n", " elif sd == 'sccw':\n", " offset = [s2, -s2inv]\n", " elif sd == 'seccw':\n", " offset = [s2inv, -s2]\n", " elif sd == 'eccw':\n", " offset = [-s2inv, -s2]\n", " elif sd == 'neccw':\n", " offset = [-s2, -s2inv]\n", "\n", " path += \" \".join(str(x) for x in offset)\n", " self.attrs['d'] += path\n", " return self\n", "\n", " def arc(self, sweep):\n", " x = C.AR\n", " y = C.AR\n", " if sweep[0] == 'e' or sweep[1] == 'w':\n", " x *= -1\n", " if sweep[0] == 's' or sweep[1] == 'n':\n", " y *= -1\n", " cw = 1 if sweep == 'ne' or sweep == 'es' or sweep == 'sw' or sweep == 'wn' else 0\n", " self.attrs['d'] += 'a{0} {0} 0 0 {1} {2} {3}'.format(C.AR, cw, x, y)\n", " return self\n", "\n", " def format(self):\n", " self.attrs['d'] += 'h.5'\n", " return self\n", "\n", " def __repr__(self):\n", " return 'Path(%r, %r)' % (self.x, self.y)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.736167Z", "iopub.status.busy": "2024-01-18T17:31:27.736085Z", "iopub.status.idle": "2024-01-18T17:31:27.737779Z", "shell.execute_reply": "2024-01-18T17:31:27.737536Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def wrapString(value):\n", " return value if isinstance(value, DiagramItem) else Terminal(value)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.739253Z", "iopub.status.busy": "2024-01-18T17:31:27.739168Z", "iopub.status.idle": "2024-01-18T17:31:27.741383Z", "shell.execute_reply": "2024-01-18T17:31:27.741145Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Style(DiagramItem):\n", " def __init__(self, css):\n", " self.name = 'style'\n", " self.css = css\n", " self.height = 0\n", " self.width = 0\n", " self.needsSpace = False\n", "\n", " def __repr__(self):\n", " return 'Style(%r)' % css\n", "\n", " def format(self, x, y, width):\n", " return self\n", "\n", " def writeSvg(self, write):\n", " # Write included stylesheet as CDATA. See\n", " # https:#developer.mozilla.org/en-US/docs/Web/SVG/Element/style\n", " cdata = u'/* */\\n{css}\\n/* */\\n'.format(css=self.css)\n", " write(u''.format(cdata=cdata))" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.742944Z", "iopub.status.busy": "2024-01-18T17:31:27.742866Z", "iopub.status.idle": "2024-01-18T17:31:27.762336Z", "shell.execute_reply": "2024-01-18T17:31:27.762040Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Diagram(DiagramItem):\n", " def __init__(self, *items, **kwargs):\n", " # Accepts a type=[simple|complex] kwarg\n", " DiagramItem.__init__(\n", " self, 'svg', {'class': C.DIAGRAM_CLASS, 'xmlns': \"http://www.w3.org/2000/svg\"})\n", " self.type = kwargs.get(\"type\", \"simple\")\n", " self.items = [wrapString(item) for item in items]\n", " if items and not isinstance(items[0], Start):\n", " self.items.insert(0, Start(self.type))\n", " if items and not isinstance(items[-1], End):\n", " self.items.append(End(self.type))\n", " self.css = kwargs.get(\"css\", C.DEFAULT_STYLE)\n", " if self.css:\n", " self.items.insert(0, Style(self.css))\n", " self.up = 0\n", " self.down = 0\n", " self.height = 0\n", " self.width = 0\n", " for item in self.items:\n", " if isinstance(item, Style):\n", " continue\n", " self.width += item.width + (20 if item.needsSpace else 0)\n", " self.up = max(self.up, item.up - self.height)\n", " self.height += item.height\n", " self.down = max(self.down - item.height, item.down)\n", " if self.items[0].needsSpace:\n", " self.width -= 10\n", " if self.items[-1].needsSpace:\n", " self.width -= 10\n", " self.formatted = False\n", "\n", " def __repr__(self):\n", " if self.css:\n", " items = ', '.join(map(repr, self.items[2:-1]))\n", " else:\n", " items = ', '.join(map(repr, self.items[1:-1]))\n", " pieces = [] if not items else [items]\n", " if self.css != C.DEFAULT_STYLE:\n", " pieces.append('css=%r' % self.css)\n", " if self.type != 'simple':\n", " pieces.append('type=%r' % self.type)\n", " return 'Diagram(%s)' % ', '.join(pieces)\n", "\n", " def format(self, paddingTop=20, paddingRight=None,\n", " paddingBottom=None, paddingLeft=None):\n", " if paddingRight is None:\n", " paddingRight = paddingTop\n", " if paddingBottom is None:\n", " paddingBottom = paddingTop\n", " if paddingLeft is None:\n", " paddingLeft = paddingRight\n", " x = paddingLeft\n", " y = paddingTop + self.up\n", " g = DiagramItem('g')\n", " if C.STROKE_ODD_PIXEL_LENGTH:\n", " g.attrs['transform'] = 'translate(.5 .5)'\n", " for item in self.items:\n", " if item.needsSpace:\n", " Path(x, y).h(10).addTo(g)\n", " x += 10\n", " item.format(x, y, item.width).addTo(g)\n", " x += item.width\n", " y += item.height\n", " if item.needsSpace:\n", " Path(x, y).h(10).addTo(g)\n", " x += 10\n", " self.attrs['width'] = self.width + paddingLeft + paddingRight\n", " self.attrs['height'] = self.up + self.height + \\\n", " self.down + paddingTop + paddingBottom\n", " self.attrs['viewBox'] = \"0 0 {width} {height}\".format(**self.attrs)\n", " g.addTo(self)\n", " self.formatted = True\n", " return self\n", "\n", " def writeSvg(self, write):\n", " if not self.formatted:\n", " self.format()\n", " return DiagramItem.writeSvg(self, write)\n", "\n", " def parseCSSGrammar(self, text):\n", " token_patterns = {\n", " 'keyword': r\"[\\w-]+\\(?\",\n", " 'type': r\"<[\\w-]+(\\(\\))?>\",\n", " 'char': r\"[/,()]\",\n", " 'literal': r\"'(.)'\",\n", " 'openbracket': r\"\\[\",\n", " 'closebracket': r\"\\]\",\n", " 'closebracketbang': r\"\\]!\",\n", " 'bar': r\"\\|\",\n", " 'doublebar': r\"\\|\\|\",\n", " 'doubleand': r\"&&\",\n", " 'multstar': r\"\\*\",\n", " 'multplus': r\"\\+\",\n", " 'multhash': r\"#\",\n", " 'multnum1': r\"{\\s*(\\d+)\\s*}\",\n", " 'multnum2': r\"{\\s*(\\d+)\\s*,\\s*(\\d*)\\s*}\",\n", " 'multhashnum1': r\"#{\\s*(\\d+)\\s*}\",\n", " 'multhashnum2': r\"{\\s*(\\d+)\\s*,\\s*(\\d*)\\s*}\"\n", " }" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.763924Z", "iopub.status.busy": "2024-01-18T17:31:27.763836Z", "iopub.status.idle": "2024-01-18T17:31:27.768226Z", "shell.execute_reply": "2024-01-18T17:31:27.767938Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Sequence(DiagramItem):\n", " def __init__(self, *items):\n", " DiagramItem.__init__(self, 'g')\n", " self.items = [wrapString(item) for item in items]\n", " self.needsSpace = True\n", " self.up = 0\n", " self.down = 0\n", " self.height = 0\n", " self.width = 0\n", " for item in self.items:\n", " self.width += item.width + (20 if item.needsSpace else 0)\n", " self.up = max(self.up, item.up - self.height)\n", " self.height += item.height\n", " self.down = max(self.down - item.height, item.down)\n", " if self.items[0].needsSpace:\n", " self.width -= 10\n", " if self.items[-1].needsSpace:\n", " self.width -= 10\n", " addDebug(self)\n", "\n", " def __repr__(self):\n", " items = ', '.join(map(repr, self.items))\n", " return 'Sequence(%s)' % items\n", "\n", " def format(self, x, y, width):\n", " leftGap, rightGap = determineGaps(width, self.width)\n", " Path(x, y).h(leftGap).addTo(self)\n", " Path(x + leftGap + self.width, y + self.height).h(rightGap).addTo(self)\n", " x += leftGap\n", " for i, item in enumerate(self.items):\n", " if item.needsSpace and i > 0:\n", " Path(x, y).h(10).addTo(self)\n", " x += 10\n", " item.format(x, y, item.width).addTo(self)\n", " x += item.width\n", " y += item.height\n", " if item.needsSpace and i < len(self.items) - 1:\n", " Path(x, y).h(10).addTo(self)\n", " x += 10\n", " return self" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.769758Z", "iopub.status.busy": "2024-01-18T17:31:27.769669Z", "iopub.status.idle": "2024-01-18T17:31:27.775179Z", "shell.execute_reply": "2024-01-18T17:31:27.774936Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Stack(DiagramItem):\n", " def __init__(self, *items):\n", " DiagramItem.__init__(self, 'g')\n", " self.items = [wrapString(item) for item in items]\n", " self.needsSpace = True\n", " self.width = max(item.width + (20 if item.needsSpace else 0)\n", " for item in self.items)\n", " # pretty sure that space calc is totes wrong\n", " if len(self.items) > 1:\n", " self.width += C.AR * 2\n", " self.up = self.items[0].up\n", " self.down = self.items[-1].down\n", " self.height = 0\n", " last = len(self.items) - 1\n", " for i, item in enumerate(self.items):\n", " self.height += item.height\n", " if i > 0:\n", " self.height += max(C.AR * 2, item.up + C.VS)\n", " if i < last:\n", " self.height += max(C.AR * 2, item.down + C.VS)\n", " addDebug(self)\n", "\n", " def __repr__(self):\n", " items = ', '.join(repr(item) for item in self.items)\n", " return 'Stack(%s)' % items\n", "\n", " def format(self, x, y, width):\n", " leftGap, rightGap = determineGaps(width, self.width)\n", " Path(x, y).h(leftGap).addTo(self)\n", " x += leftGap\n", " xInitial = x\n", " if len(self.items) > 1:\n", " Path(x, y).h(C.AR).addTo(self)\n", " x += C.AR\n", " innerWidth = self.width - C.AR * 2\n", " else:\n", " innerWidth = self.width\n", " for i, item in enumerate(self.items):\n", " item.format(x, y, innerWidth).addTo(self)\n", " x += innerWidth\n", " y += item.height\n", " if i != len(self.items) - 1:\n", " (Path(x, y)\n", " .arc('ne').down(max(0, item.down + C.VS - C.AR * 2))\n", " .arc('es').left(innerWidth)\n", " .arc('nw').down(max(0, self.items[i + 1].up + C.VS - C.AR * 2))\n", " .arc('ws').addTo(self))\n", " y += max(item.down + C.VS, C.AR * 2) + \\\n", " max(self.items[i + 1].up + C.VS, C.AR * 2)\n", " x = xInitial + C.AR\n", " if len(self.items) > 1:\n", " Path(x, y).h(C.AR).addTo(self)\n", " x += C.AR\n", " Path(x, y).h(rightGap).addTo(self)\n", " return self" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.776676Z", "iopub.status.busy": "2024-01-18T17:31:27.776592Z", "iopub.status.idle": "2024-01-18T17:31:27.785690Z", "shell.execute_reply": "2024-01-18T17:31:27.785432Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class OptionalSequence(DiagramItem):\n", " def __new__(cls, *items):\n", " if len(items) <= 1:\n", " return Sequence(*items)\n", " else:\n", " return super(OptionalSequence, cls).__new__(cls)\n", "\n", " def __init__(self, *items):\n", " DiagramItem.__init__(self, 'g')\n", " self.items = [wrapString(item) for item in items]\n", " self.needsSpace = False\n", " self.width = 0\n", " self.up = 0\n", " self.height = sum(item.height for item in self.items)\n", " self.down = self.items[0].down\n", " heightSoFar = 0\n", " for i, item in enumerate(self.items):\n", " self.up = max(self.up, max(C.AR * 2, item.up + C.VS) - heightSoFar)\n", " heightSoFar += item.height\n", " if i > 0:\n", " self.down = max(self.height + self.down, heightSoFar\n", " + max(C.AR * 2, item.down + C.VS)) - self.height\n", " itemWidth = item.width + (20 if item.needsSpace else 0)\n", " if i == 0:\n", " self.width += C.AR + max(itemWidth, C.AR)\n", " else:\n", " self.width += C.AR * 2 + max(itemWidth, C.AR) + C.AR\n", " addDebug(self)\n", "\n", " def __repr__(self):\n", " items = ', '.join(repr(item) for item in self.items)\n", " return 'OptionalSequence(%s)' % items\n", "\n", " def format(self, x, y, width):\n", " leftGap, rightGap = determineGaps(width, self.width)\n", " Path(x, y).right(leftGap).addTo(self)\n", " Path(x + leftGap + self.width, y\n", " + self.height).right(rightGap).addTo(self)\n", " x += leftGap\n", " upperLineY = y - self.up\n", " last = len(self.items) - 1\n", " for i, item in enumerate(self.items):\n", " itemSpace = 10 if item.needsSpace else 0\n", " itemWidth = item.width + itemSpace\n", " if i == 0:\n", " # Upper skip\n", " (Path(x, y)\n", " .arc('se')\n", " .up(y - upperLineY - C.AR * 2)\n", " .arc('wn')\n", " .right(itemWidth - C.AR)\n", " .arc('ne')\n", " .down(y + item.height - upperLineY - C.AR * 2)\n", " .arc('ws')\n", " .addTo(self))\n", " # Straight line\n", " (Path(x, y)\n", " .right(itemSpace + C.AR)\n", " .addTo(self))\n", " item.format(x + itemSpace + C.AR, y, item.width).addTo(self)\n", " x += itemWidth + C.AR\n", " y += item.height\n", " elif i < last:\n", " # Upper skip\n", " (Path(x, upperLineY)\n", " .right(C.AR * 2 + max(itemWidth, C.AR) + C.AR)\n", " .arc('ne')\n", " .down(y - upperLineY + item.height - C.AR * 2)\n", " .arc('ws')\n", " .addTo(self))\n", " # Straight line\n", " (Path(x, y)\n", " .right(C.AR * 2)\n", " .addTo(self))\n", " item.format(x + C.AR * 2, y, item.width).addTo(self)\n", " (Path(x + item.width + C.AR * 2, y + item.height)\n", " .right(itemSpace + C.AR)\n", " .addTo(self))\n", " # Lower skip\n", " (Path(x, y)\n", " .arc('ne')\n", " .down(item.height + max(item.down + C.VS, C.AR * 2) - C.AR * 2)\n", " .arc('ws')\n", " .right(itemWidth - C.AR)\n", " .arc('se')\n", " .up(item.down + C.VS - C.AR * 2)\n", " .arc('wn')\n", " .addTo(self))\n", " x += C.AR * 2 + max(itemWidth, C.AR) + C.AR\n", " y += item.height\n", " else:\n", " # Straight line\n", " (Path(x, y)\n", " .right(C.AR * 2)\n", " .addTo(self))\n", " item.format(x + C.AR * 2, y, item.width).addTo(self)\n", " (Path(x + C.AR * 2 + item.width, y + item.height)\n", " .right(itemSpace + C.AR)\n", " .addTo(self))\n", " # Lower skip\n", " (Path(x, y)\n", " .arc('ne')\n", " .down(item.height + max(item.down + C.VS, C.AR * 2) - C.AR * 2)\n", " .arc('ws')\n", " .right(itemWidth - C.AR)\n", " .arc('se')\n", " .up(item.down + C.VS - C.AR * 2)\n", " .arc('wn')\n", " .addTo(self))\n", " return self" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.787208Z", "iopub.status.busy": "2024-01-18T17:31:27.787122Z", "iopub.status.idle": "2024-01-18T17:31:27.794734Z", "shell.execute_reply": "2024-01-18T17:31:27.794479Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class AlternatingSequence(DiagramItem):\n", " def __new__(cls, *items):\n", " if len(items) == 2:\n", " return super(AlternatingSequence, cls).__new__(cls)\n", " else:\n", " raise Exception(\n", " \"AlternatingSequence takes exactly two arguments got \" + len(items))\n", "\n", " def __init__(self, *items):\n", " DiagramItem.__init__(self, 'g')\n", " self.items = [wrapString(item) for item in items]\n", " self.needsSpace = False\n", "\n", " arc = C.AR\n", " vert = C.VS\n", " first = self.items[0]\n", " second = self.items[1]\n", "\n", " arcX = 1 / math.sqrt(2) * arc * 2\n", " arcY = (1 - 1 / math.sqrt(2)) * arc * 2\n", " crossY = max(arc, vert)\n", " crossX = (crossY - arcY) + arcX\n", "\n", " firstOut = max(arc + arc, crossY / 2 + arc + arc,\n", " crossY / 2 + vert + first.down)\n", " self.up = firstOut + first.height + first.up\n", "\n", " secondIn = max(arc + arc, crossY / 2 + arc + arc,\n", " crossY / 2 + vert + second.up)\n", " self.down = secondIn + second.height + second.down\n", "\n", " self.height = 0\n", "\n", " firstWidth = (20 if first.needsSpace else 0) + first.width\n", " secondWidth = (20 if second.needsSpace else 0) + second.width\n", " self.width = 2 * arc + max(firstWidth, crossX, secondWidth) + 2 * arc\n", " addDebug(self)\n", "\n", " def __repr__(self):\n", " items = ', '.join(repr(item) for item in self.items)\n", " return 'AlternatingSequence(%s)' % items\n", "\n", " def format(self, x, y, width):\n", " arc = C.AR\n", " gaps = determineGaps(width, self.width)\n", " Path(x, y).right(gaps[0]).addTo(self)\n", " x += gaps[0]\n", " Path(x + self.width, y).right(gaps[1]).addTo(self)\n", " # bounding box\n", " # Path(x+gaps[0], y).up(self.up).right(self.width).down(self.up+self.down).left(self.width).up(self.down).addTo(self)\n", " first = self.items[0]\n", " second = self.items[1]\n", "\n", " # top\n", " firstIn = self.up - first.up\n", " firstOut = self.up - first.up - first.height\n", " Path(x, y).arc('se').up(firstIn - 2 * arc).arc('wn').addTo(self)\n", " first.format(\n", " x\n", " + 2\n", " * arc,\n", " y\n", " - firstIn,\n", " self.width\n", " - 4\n", " * arc).addTo(self)\n", " Path(x + self.width - 2 * arc, y\n", " - firstOut).arc('ne').down(firstOut - 2 * arc).arc('ws').addTo(self)\n", "\n", " # bottom\n", " secondIn = self.down - second.down - second.height\n", " secondOut = self.down - second.down\n", " Path(x, y).arc('ne').down(secondIn - 2 * arc).arc('ws').addTo(self)\n", " second.format(\n", " x\n", " + 2\n", " * arc,\n", " y\n", " + secondIn,\n", " self.width\n", " - 4\n", " * arc).addTo(self)\n", " Path(x + self.width - 2 * arc, y\n", " + secondOut).arc('se').up(secondOut - 2 * arc).arc('wn').addTo(self)\n", "\n", " # crossover\n", " arcX = 1 / Math.sqrt(2) * arc * 2\n", " arcY = (1 - 1 / Math.sqrt(2)) * arc * 2\n", " crossY = max(arc, C.VS)\n", " crossX = (crossY - arcY) + arcX\n", " crossBar = (self.width - 4 * arc - crossX) / 2\n", " (Path(x + arc, y - crossY / 2 - arc).arc('ws').right(crossBar)\n", " .arc_8('n', 'cw').ll(crossX - arcX, crossY - arcY).arc_8('sw', 'ccw')\n", " .right(crossBar).arc('ne').addTo(self))\n", " (Path(x + arc, y + crossY / 2 + arc).arc('wn').right(crossBar)\n", " .arc_8('s', 'ccw').ll(crossX - arcX, -(crossY - arcY)).arc_8('nw', 'cw')\n", " .right(crossBar).arc('se').addTo(self))\n", "\n", " return self" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.796177Z", "iopub.status.busy": "2024-01-18T17:31:27.796095Z", "iopub.status.idle": "2024-01-18T17:31:27.803552Z", "shell.execute_reply": "2024-01-18T17:31:27.803292Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Choice(DiagramItem):\n", " def __init__(self, default, *items):\n", " DiagramItem.__init__(self, 'g')\n", " assert default < len(items)\n", " self.default = default\n", " self.items = [wrapString(item) for item in items]\n", " self.width = C.AR * 4 + max(item.width for item in self.items)\n", " self.up = self.items[0].up\n", " self.down = self.items[-1].down\n", " self.height = self.items[default].height\n", " for i, item in enumerate(self.items):\n", " if i in [default - 1, default + 1]:\n", " arcs = C.AR * 2\n", " else:\n", " arcs = C.AR\n", " if i < default:\n", " self.up += max(arcs, item.height + item.down\n", " + C.VS + self.items[i + 1].up)\n", " elif i == default:\n", " continue\n", " else:\n", " self.down += max(arcs, item.up + C.VS\n", " + self.items[i - 1].down + self.items[i - 1].height)\n", " # already counted in self.height\n", " self.down -= self.items[default].height\n", " addDebug(self)\n", "\n", " def __repr__(self):\n", " items = ', '.join(repr(item) for item in self.items)\n", " return 'Choice(%r, %s)' % (self.default, items)\n", "\n", " def format(self, x, y, width):\n", " leftGap, rightGap = determineGaps(width, self.width)\n", "\n", " # Hook up the two sides if self is narrower than its stated width.\n", " Path(x, y).h(leftGap).addTo(self)\n", " Path(x + leftGap + self.width, y + self.height).h(rightGap).addTo(self)\n", " x += leftGap\n", "\n", " innerWidth = self.width - C.AR * 4\n", " default = self.items[self.default]\n", "\n", " # Do the elements that curve above\n", " above = self.items[:self.default][::-1]\n", " if above:\n", " distanceFromY = max(\n", " C.AR * 2,\n", " default.up\n", " + C.VS\n", " + above[0].down\n", " + above[0].height)\n", " for i, ni, item in doubleenumerate(above):\n", " Path(x, y).arc('se').up(distanceFromY\n", " - C.AR * 2).arc('wn').addTo(self)\n", " item.format(x + C.AR * 2, y - distanceFromY,\n", " innerWidth).addTo(self)\n", " Path(x + C.AR * 2 + innerWidth, y - distanceFromY + item.height).arc('ne') \\\n", " .down(distanceFromY - item.height + default.height - C.AR * 2).arc('ws').addTo(self)\n", " if ni < -1:\n", " distanceFromY += max(\n", " C.AR,\n", " item.up\n", " + C.VS\n", " + above[i + 1].down\n", " + above[i + 1].height)\n", "\n", " # Do the straight-line path.\n", " Path(x, y).right(C.AR * 2).addTo(self)\n", " self.items[self.default].format(\n", " x + C.AR * 2, y, innerWidth).addTo(self)\n", " Path(x + C.AR * 2 + innerWidth, y\n", " + self.height).right(C.AR * 2).addTo(self)\n", "\n", " # Do the elements that curve below\n", " below = self.items[self.default + 1:]\n", " if below:\n", " distanceFromY = max(\n", " C.AR * 2,\n", " default.height\n", " + default.down\n", " + C.VS\n", " + below[0].up)\n", " for i, item in enumerate(below):\n", " Path(x, y).arc('ne').down(\n", " distanceFromY - C.AR * 2).arc('ws').addTo(self)\n", " item.format(x + C.AR * 2, y + distanceFromY,\n", " innerWidth).addTo(self)\n", " Path(x + C.AR * 2 + innerWidth, y + distanceFromY + item.height).arc('se') \\\n", " .up(distanceFromY - C.AR * 2 + item.height - default.height).arc('wn').addTo(self)\n", " distanceFromY += max(\n", " C.AR,\n", " item.height\n", " + item.down\n", " + C.VS\n", " + (below[i + 1].up if i + 1 < len(below) else 0))\n", " return self" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.805018Z", "iopub.status.busy": "2024-01-18T17:31:27.804935Z", "iopub.status.idle": "2024-01-18T17:31:27.814244Z", "shell.execute_reply": "2024-01-18T17:31:27.813976Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class MultipleChoice(DiagramItem):\n", " def __init__(self, default, type, *items):\n", " DiagramItem.__init__(self, 'g')\n", " assert 0 <= default < len(items)\n", " assert type in [\"any\", \"all\"]\n", " self.default = default\n", " self.type = type\n", " self.needsSpace = True\n", " self.items = [wrapString(item) for item in items]\n", " self.innerWidth = max(item.width for item in self.items)\n", " self.width = 30 + C.AR + self.innerWidth + C.AR + 20\n", " self.up = self.items[0].up\n", " self.down = self.items[-1].down\n", " self.height = self.items[default].height\n", " for i, item in enumerate(self.items):\n", " if i in [default - 1, default + 1]:\n", " minimum = 10 + C.AR\n", " else:\n", " minimum = C.AR\n", " if i < default:\n", " self.up += max(minimum, item.height\n", " + item.down + C.VS + self.items[i + 1].up)\n", " elif i == default:\n", " continue\n", " else:\n", " self.down += max(minimum, item.up + C.VS\n", " + self.items[i - 1].down + self.items[i - 1].height)\n", " # already counted in self.height\n", " self.down -= self.items[default].height\n", " addDebug(self)\n", "\n", " def __repr__(self):\n", " items = ', '.join(map(repr, self.items))\n", " return 'MultipleChoice(%r, %r, %s)' % (self.default, self.type, items)\n", "\n", " def format(self, x, y, width):\n", " leftGap, rightGap = determineGaps(width, self.width)\n", "\n", " # Hook up the two sides if self is narrower than its stated width.\n", " Path(x, y).h(leftGap).addTo(self)\n", " Path(x + leftGap + self.width, y + self.height).h(rightGap).addTo(self)\n", " x += leftGap\n", "\n", " default = self.items[self.default]\n", "\n", " # Do the elements that curve above\n", " above = self.items[:self.default][::-1]\n", " if above:\n", " distanceFromY = max(\n", " 10 + C.AR,\n", " default.up\n", " + C.VS\n", " + above[0].down\n", " + above[0].height)\n", " for i, ni, item in doubleenumerate(above):\n", " (Path(x + 30, y)\n", " .up(distanceFromY - C.AR)\n", " .arc('wn')\n", " .addTo(self))\n", " item.format(x + 30 + C.AR, y - distanceFromY,\n", " self.innerWidth).addTo(self)\n", " (Path(x + 30 + C.AR + self.innerWidth, y - distanceFromY + item.height)\n", " .arc('ne')\n", " .down(distanceFromY - item.height + default.height - C.AR - 10)\n", " .addTo(self))\n", " if ni < -1:\n", " distanceFromY += max(\n", " C.AR,\n", " item.up\n", " + C.VS\n", " + above[i + 1].down\n", " + above[i + 1].height)\n", "\n", " # Do the straight-line path.\n", " Path(x + 30, y).right(C.AR).addTo(self)\n", " self.items[self.default].format(\n", " x + 30 + C.AR, y, self.innerWidth).addTo(self)\n", " Path(x + 30 + C.AR + self.innerWidth, y\n", " + self.height).right(C.AR).addTo(self)\n", "\n", " # Do the elements that curve below\n", " below = self.items[self.default + 1:]\n", " if below:\n", " distanceFromY = max(\n", " 10 + C.AR,\n", " default.height\n", " + default.down\n", " + C.VS\n", " + below[0].up)\n", " for i, item in enumerate(below):\n", " (Path(x + 30, y)\n", " .down(distanceFromY - C.AR)\n", " .arc('ws')\n", " .addTo(self))\n", " item.format(x + 30 + C.AR, y + distanceFromY,\n", " self.innerWidth).addTo(self)\n", " (Path(x + 30 + C.AR + self.innerWidth, y + distanceFromY + item.height)\n", " .arc('se')\n", " .up(distanceFromY - C.AR + item.height - default.height - 10)\n", " .addTo(self))\n", " distanceFromY += max(\n", " C.AR,\n", " item.height\n", " + item.down\n", " + C.VS\n", " + (below[i + 1].up if i + 1 < len(below) else 0))\n", " text = DiagramItem('g', attrs={\"class\": \"diagram-text\"}).addTo(self)\n", " DiagramItem('title', text=\"take one or more branches, once each, in any order\" if self.type\n", " == \"any\" else \"take all branches, once each, in any order\").addTo(text)\n", " DiagramItem('path', attrs={\n", " \"d\": \"M {x} {y} h -26 a 4 4 0 0 0 -4 4 v 12 a 4 4 0 0 0 4 4 h 26 z\".format(x=x + 30, y=y - 10),\n", " \"class\": \"diagram-text\"\n", " }).addTo(text)\n", " DiagramItem('text', text=\"1+\" if self.type == \"any\" else \"all\", attrs={\n", " \"x\": x + 15,\n", " \"y\": y + 4,\n", " \"class\": \"diagram-text\"\n", " }).addTo(text)\n", " DiagramItem('path', attrs={\n", " \"d\": \"M {x} {y} h 16 a 4 4 0 0 1 4 4 v 12 a 4 4 0 0 1 -4 4 h -16 z\".format(x=x + self.width - 20, y=y - 10),\n", " \"class\": \"diagram-text\"\n", " }).addTo(text)\n", " DiagramItem('text', text=u\"↺\", attrs={\n", " \"x\": x + self.width - 10,\n", " \"y\": y + 4,\n", " \"class\": \"diagram-arrow\"\n", " }).addTo(text)\n", " return self" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.815851Z", "iopub.status.busy": "2024-01-18T17:31:27.815765Z", "iopub.status.idle": "2024-01-18T17:31:27.824523Z", "shell.execute_reply": "2024-01-18T17:31:27.824281Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class HorizontalChoice(DiagramItem):\n", " def __new__(cls, *items):\n", " if len(items) <= 1:\n", " return Sequence(*items)\n", " else:\n", " return super(HorizontalChoice, cls).__new__(cls)\n", "\n", " def __init__(self, *items):\n", " DiagramItem.__init__(self, 'g')\n", " self.items = [wrapString(item) for item in items]\n", " allButLast = self.items[:-1]\n", " middles = self.items[1:-1]\n", " first = self.items[0]\n", " last = self.items[-1]\n", " self.needsSpace = False\n", "\n", " self.width = (C.AR # starting track\n", " + C.AR * 2 * (len(self.items) - 1) # inbetween tracks\n", " + sum(x.width + (20 if x.needsSpace else 0)\n", " for x in self.items) # items\n", " # needs space to curve up\n", " + (C.AR if last.height > 0 else 0)\n", " + C.AR) # ending track\n", "\n", " # Always exits at entrance height\n", " self.height = 0\n", "\n", " # All but the last have a track running above them\n", " self._upperTrack = max(\n", " C.AR * 2,\n", " C.VS,\n", " max(x.up for x in allButLast) + C.VS\n", " )\n", " self.up = max(self._upperTrack, last.up)\n", "\n", " # All but the first have a track running below them\n", " # Last either straight-lines or curves up, so has different calculation\n", " self._lowerTrack = max(\n", " C.VS,\n", " max(x.height + max(x.down + C.VS, C.AR * 2)\n", " for x in middles) if middles else 0,\n", " last.height + last.down + C.VS\n", " )\n", " if first.height < self._lowerTrack:\n", " # Make sure there's at least 2*C.AR room between first exit and\n", " # lower track\n", " self._lowerTrack = max(self._lowerTrack, first.height + C.AR * 2)\n", " self.down = max(self._lowerTrack, first.height + first.down)\n", "\n", " addDebug(self)\n", "\n", " def format(self, x, y, width):\n", " # Hook up the two sides if self is narrower than its stated width.\n", " leftGap, rightGap = determineGaps(width, self.width)\n", " Path(x, y).h(leftGap).addTo(self)\n", " Path(x + leftGap + self.width, y + self.height).h(rightGap).addTo(self)\n", " x += leftGap\n", "\n", " first = self.items[0]\n", " last = self.items[-1]\n", "\n", " # upper track\n", " upperSpan = (sum(x.width + (20 if x.needsSpace else 0) for x in self.items[:-1])\n", " + (len(self.items) - 2) * C.AR * 2\n", " - C.AR)\n", " (Path(x, y)\n", " .arc('se')\n", " .up(self._upperTrack - C.AR * 2)\n", " .arc('wn')\n", " .h(upperSpan)\n", " .addTo(self))\n", "\n", " # lower track\n", " lowerSpan = (sum(x.width + (20 if x.needsSpace else 0) for x in self.items[1:])\n", " + (len(self.items) - 2) * C.AR * 2\n", " + (C.AR if last.height > 0 else 0)\n", " - C.AR)\n", " lowerStart = x + C.AR + first.width + \\\n", " (20 if first.needsSpace else 0) + C.AR * 2\n", " (Path(lowerStart, y + self._lowerTrack)\n", " .h(lowerSpan)\n", " .arc('se')\n", " .up(self._lowerTrack - C.AR * 2)\n", " .arc('wn')\n", " .addTo(self))\n", "\n", " # Items\n", " for [i, item] in enumerate(self.items):\n", " # input track\n", " if i == 0:\n", " (Path(x, y)\n", " .h(C.AR)\n", " .addTo(self))\n", " x += C.AR\n", " else:\n", " (Path(x, y - self._upperTrack)\n", " .arc('ne')\n", " .v(self._upperTrack - C.AR * 2)\n", " .arc('ws')\n", " .addTo(self))\n", " x += C.AR * 2\n", "\n", " # item\n", " itemWidth = item.width + (20 if item.needsSpace else 0)\n", " item.format(x, y, itemWidth).addTo(self)\n", " x += itemWidth\n", "\n", " # output track\n", " if i == len(self.items) - 1:\n", " if item.height == 0:\n", " (Path(x, y)\n", " .h(C.AR)\n", " .addTo(self))\n", " else:\n", " (Path(x, y + item.height)\n", " .arc('se')\n", " .addTo(self))\n", " elif i == 0 and item.height > self._lowerTrack:\n", " # Needs to arc up to meet the lower track, not down.\n", " if item.height - self._lowerTrack >= C.AR * 2:\n", " (Path(x, y + item.height)\n", " .arc('se')\n", " .v(self._lowerTrack - item.height + C.AR * 2)\n", " .arc('wn')\n", " .addTo(self))\n", " else:\n", " # Not enough space to fit two arcs\n", " # so just bail and draw a straight line for now.\n", " (Path(x, y + item.height)\n", " .ll(C.AR * 2, self._lowerTrack - item.height)\n", " .addTo(self))\n", " else:\n", " (Path(x, y + item.height)\n", " .arc('ne')\n", " .v(self._lowerTrack - item.height - C.AR * 2)\n", " .arc('ws')\n", " .addTo(self))\n", " return self" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.826088Z", "iopub.status.busy": "2024-01-18T17:31:27.826002Z", "iopub.status.idle": "2024-01-18T17:31:27.827720Z", "shell.execute_reply": "2024-01-18T17:31:27.827480Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def Optional(item, skip=False):\n", " return Choice(0 if skip else 1, Skip(), item)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.829241Z", "iopub.status.busy": "2024-01-18T17:31:27.829154Z", "iopub.status.idle": "2024-01-18T17:31:27.833616Z", "shell.execute_reply": "2024-01-18T17:31:27.833378Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class OneOrMore(DiagramItem):\n", " def __init__(self, item, repeat=None):\n", " DiagramItem.__init__(self, 'g')\n", " repeat = repeat or Skip()\n", " self.item = wrapString(item)\n", " self.rep = wrapString(repeat)\n", " self.width = max(self.item.width, self.rep.width) + C.AR * 2\n", " self.height = self.item.height\n", " self.up = self.item.up\n", " self.down = max(\n", " C.AR * 2,\n", " self.item.down + C.VS + self.rep.up + self.rep.height + self.rep.down)\n", " self.needsSpace = True\n", " addDebug(self)\n", "\n", " def format(self, x, y, width):\n", " leftGap, rightGap = determineGaps(width, self.width)\n", "\n", " # Hook up the two sides if self is narrower than its stated width.\n", " Path(x, y).h(leftGap).addTo(self)\n", " Path(x + leftGap + self.width, y + self.height).h(rightGap).addTo(self)\n", " x += leftGap\n", "\n", " # Draw item\n", " Path(x, y).right(C.AR).addTo(self)\n", " self.item.format(x + C.AR, y, self.width - C.AR * 2).addTo(self)\n", " Path(x + self.width - C.AR, y + self.height).right(C.AR).addTo(self)\n", "\n", " # Draw repeat arc\n", " distanceFromY = max(C.AR * 2, self.item.height\n", " + self.item.down + C.VS + self.rep.up)\n", " Path(x + C.AR, y).arc('nw').down(distanceFromY - C.AR * 2) \\\n", " .arc('ws').addTo(self)\n", " self.rep.format(x + C.AR, y + distanceFromY,\n", " self.width - C.AR * 2).addTo(self)\n", " Path(x + self.width - C.AR, y + distanceFromY + self.rep.height).arc('se') \\\n", " .up(distanceFromY - C.AR * 2 + self.rep.height - self.item.height).arc('en').addTo(self)\n", "\n", " return self\n", "\n", " def __repr__(self):\n", " return 'OneOrMore(%r, repeat=%r)' % (self.item, self.rep)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.835023Z", "iopub.status.busy": "2024-01-18T17:31:27.834942Z", "iopub.status.idle": "2024-01-18T17:31:27.836645Z", "shell.execute_reply": "2024-01-18T17:31:27.836407Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def ZeroOrMore(item, repeat=None, skip=False):\n", " result = Optional(OneOrMore(item, repeat), skip)\n", " return result" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.838112Z", "iopub.status.busy": "2024-01-18T17:31:27.838020Z", "iopub.status.idle": "2024-01-18T17:31:27.841320Z", "shell.execute_reply": "2024-01-18T17:31:27.841078Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Start(DiagramItem):\n", " def __init__(self, type=\"simple\", label=None):\n", " DiagramItem.__init__(self, 'g')\n", " if label:\n", " self.width = max(20, len(label) * C.CHAR_WIDTH + 10)\n", " else:\n", " self.width = 20\n", " self.up = 10\n", " self.down = 10\n", " self.type = type\n", " self.label = label\n", " addDebug(self)\n", "\n", " def format(self, x, y, _width):\n", " path = Path(x, y - 10)\n", " if self.type == \"complex\":\n", " path.down(20).m(0, -10).right(self.width).addTo(self)\n", " else:\n", " path.down(20).m(10, -20).down(20).m(-10,\n", " - 10).right(self.width).addTo(self)\n", " if self.label:\n", " DiagramItem('text', attrs={\n", " \"x\": x, \"y\": y - 15, \"style\": \"text-anchor:start\"}, text=self.label).addTo(self)\n", " return self\n", "\n", " def __repr__(self):\n", " return 'Start(type=%r, label=%r)' % (self.type, self.label)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.842769Z", "iopub.status.busy": "2024-01-18T17:31:27.842688Z", "iopub.status.idle": "2024-01-18T17:31:27.845202Z", "shell.execute_reply": "2024-01-18T17:31:27.844969Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class End(DiagramItem):\n", " def __init__(self, type=\"simple\"):\n", " DiagramItem.__init__(self, 'path')\n", " self.width = 20\n", " self.up = 10\n", " self.down = 10\n", " self.type = type\n", " addDebug(self)\n", "\n", " def format(self, x, y, _width):\n", " if self.type == \"simple\":\n", " self.attrs['d'] = 'M {0} {1} h 20 m -10 -10 v 20 m 10 -20 v 20'.format(\n", " x, y)\n", " elif self.type == \"complex\":\n", " self.attrs['d'] = 'M {0} {1} h 20 m 0 -10 v 20'\n", " return self\n", "\n", " def __repr__(self):\n", " return 'End(type=%r)' % self.type" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.846648Z", "iopub.status.busy": "2024-01-18T17:31:27.846571Z", "iopub.status.idle": "2024-01-18T17:31:27.850164Z", "shell.execute_reply": "2024-01-18T17:31:27.849933Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Terminal(DiagramItem):\n", " def __init__(self, text, href=None, title=None):\n", " DiagramItem.__init__(self, 'g', {'class': 'terminal'})\n", " self.text = text\n", " self.href = href\n", " self.title = title\n", " self.width = len(text) * C.CHAR_WIDTH + 20\n", " self.up = 11\n", " self.down = 11\n", " self.needsSpace = True\n", " addDebug(self)\n", "\n", " def __repr__(self):\n", " return 'Terminal(%r, href=%r, title=%r)' % (\n", " self.text, self.href, self.title)\n", "\n", " def format(self, x, y, width):\n", " leftGap, rightGap = determineGaps(width, self.width)\n", "\n", " # Hook up the two sides if self is narrower than its stated width.\n", " Path(x, y).h(leftGap).addTo(self)\n", " Path(x + leftGap + self.width, y).h(rightGap).addTo(self)\n", "\n", " DiagramItem('rect', {'x': x + leftGap, 'y': y - 11, 'width': self.width,\n", " 'height': self.up + self.down, 'rx': 10, 'ry': 10}).addTo(self)\n", " text = DiagramItem('text', {'x': x + width / 2, 'y': y + 4}, self.text)\n", " if self.href is not None:\n", " a = DiagramItem('a', {'xlink:href': self.href}, text).addTo(self)\n", " text.addTo(a)\n", " else:\n", " text.addTo(self)\n", " if self.title is not None:\n", " DiagramItem('title', {}, self.title).addTo(self)\n", " return self" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.851573Z", "iopub.status.busy": "2024-01-18T17:31:27.851497Z", "iopub.status.idle": "2024-01-18T17:31:27.855203Z", "shell.execute_reply": "2024-01-18T17:31:27.854914Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class NonTerminal(DiagramItem):\n", " def __init__(self, text, href=None, title=None):\n", " DiagramItem.__init__(self, 'g', {'class': 'non-terminal'})\n", " self.text = text\n", " self.href = href\n", " self.title = title\n", " self.width = len(text) * C.CHAR_WIDTH + 20\n", " self.up = 11\n", " self.down = 11\n", " self.needsSpace = True\n", " addDebug(self)\n", "\n", " def __repr__(self):\n", " return 'NonTerminal(%r, href=%r, title=%r)' % (\n", " self.text, self.href, self.title)\n", "\n", " def format(self, x, y, width):\n", " leftGap, rightGap = determineGaps(width, self.width)\n", "\n", " # Hook up the two sides if self is narrower than its stated width.\n", " Path(x, y).h(leftGap).addTo(self)\n", " Path(x + leftGap + self.width, y).h(rightGap).addTo(self)\n", "\n", " DiagramItem('rect', {'x': x + leftGap, 'y': y - 11, 'width': self.width,\n", " 'height': self.up + self.down}).addTo(self)\n", " text = DiagramItem('text', {'x': x + width / 2, 'y': y + 4}, self.text)\n", " if self.href is not None:\n", " a = DiagramItem('a', {'xlink:href': self.href}, text).addTo(self)\n", " text.addTo(a)\n", " else:\n", " text.addTo(self)\n", " if self.title is not None:\n", " DiagramItem('title', {}, self.title).addTo(self)\n", " return self" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.856754Z", "iopub.status.busy": "2024-01-18T17:31:27.856668Z", "iopub.status.idle": "2024-01-18T17:31:27.860100Z", "shell.execute_reply": "2024-01-18T17:31:27.859863Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Comment(DiagramItem):\n", " def __init__(self, text, href=None, title=None):\n", " DiagramItem.__init__(self, 'g')\n", " self.text = text\n", " self.href = href\n", " self.title = title\n", " self.width = len(text) * C.COMMENT_CHAR_WIDTH + 10\n", " self.up = 11\n", " self.down = 11\n", " self.needsSpace = True\n", " addDebug(self)\n", "\n", " def __repr__(self):\n", " return 'Comment(%r, href=%r, title=%r)' % (\n", " self.text, self.href, self.title)\n", "\n", " def format(self, x, y, width):\n", " leftGap, rightGap = determineGaps(width, self.width)\n", "\n", " # Hook up the two sides if self is narrower than its stated width.\n", " Path(x, y).h(leftGap).addTo(self)\n", " Path(x + leftGap + self.width, y).h(rightGap).addTo(self)\n", "\n", " text = DiagramItem(\n", " 'text', {'x': x + width / 2, 'y': y + 5, 'class': 'comment'}, self.text)\n", " if self.href is not None:\n", " a = DiagramItem('a', {'xlink:href': self.href}, text).addTo(self)\n", " text.addTo(a)\n", " else:\n", " text.addTo(self)\n", " if self.title is not None:\n", " DiagramItem('title', {}, self.title).addTo(self)\n", " return self" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.861542Z", "iopub.status.busy": "2024-01-18T17:31:27.861460Z", "iopub.status.idle": "2024-01-18T17:31:27.863552Z", "shell.execute_reply": "2024-01-18T17:31:27.863330Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Skip(DiagramItem):\n", " def __init__(self):\n", " DiagramItem.__init__(self, 'g')\n", " self.width = 0\n", " self.up = 0\n", " self.down = 0\n", " addDebug(self)\n", "\n", " def format(self, x, y, width):\n", " Path(x, y).right(width).addTo(self)\n", " return self\n", "\n", " def __repr__(self):\n", " return 'Skip()'" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "button": false, "code_folding": [], "execution": { "iopub.execute_input": "2024-01-18T17:31:27.865065Z", "iopub.status.busy": "2024-01-18T17:31:27.864982Z", "iopub.status.idle": "2024-01-18T17:31:27.866799Z", "shell.execute_reply": "2024-01-18T17:31:27.866562Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def show_diagram(graph, log=False):\n", " with io.StringIO() as f:\n", " d = Diagram(graph)\n", " if log:\n", " print(d)\n", " d.writeSvg(f.write)\n", " mysvg = f.getvalue()\n", " return mysvg" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## End of Excursion" ] } ], "metadata": { "ipub": { "bibliography": "fuzzingbook.bib", "toc": true }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.10.2" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true }, "toc-autonumbering": false, "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": 4 }