cmlzaGk - Pythonhttps://cmlzagk.github.io/2021-12-15T01:52:00-08:00Structural Pattern Matching in Python2021-12-15T01:42:00-08:002021-12-15T01:52:00-08:00Rishi Makertag:cmlzagk.github.io,2021-12-15:/structural-pattern-matching.html<p>I discuss a new feature of Python 3.10 - Structural Pattern Matching</p><p>Python 3.10 was released in October 2021. </p> <p>A major highlight of the release is a new language feature called Structural Pattern Matching. </p> <p>Structural Pattern Matching was made famous in Scala but is found explicitly or implicitly in many languages.</p> <p>It is a key paradigm of modern programming. </p> <p>In this blog, I will share my thoughts on this feature. </p> <p>I will cover why it is important, and how to approach it, but this is not a tutorial of the feature. </p> <p>I encourage reading <a href="https://www.python.org/dev/peps/pep-0636/">PEP 636</a>, which is a complete tutorial of the feature IMO. </p> <h2>Links</h2> <p><a href="https://www.python.org/dev/peps/pep-0636/">PEP 636</a> Structural Pattern Matching: Tutorial</p> <p><a href="https://www.python.org/dev/peps/pep-0634/">PEP 634</a> Structural Pattern Matching: Specification</p> <p><a href="https://www.python.org/dev/peps/pep-0635/">PEP 635</a> Structural Pattern Matching: Motivation and Rationale</p> <h2>Contents</h2> <ul> <li>What is Structural Pattern Matching?</li> <li>Motivation and Applications</li> <li>Pattern is not an expression</li> <li>Performance </li> <li>Conclusion</li> </ul> <h2>What is Structural Pattern Matching?</h2> <p>Structural Pattern Matching is a programming technique that takes a Subject and a set of Patterns and performs a pattern test on structure and state. </p> <p>A successful pattern match results in execution of a block of code corresponding to the Pattern. </p> <p>In typical usage, a side effect of pattern matching is name-binding, which live in enclosing scope. </p> <p>Python 3.10 implements this by introduction of a new statement called the “match” statement. Typical usage is as follows</p> <div class="highlight"><pre><span></span><span class="n">match</span> <span class="n">subject_expr</span><span class="p">:</span> <span class="n">case</span> <span class="n">pat1</span><span class="p">:</span> <span class="n">do_something</span><span class="p">()</span> <span class="n">case</span> <span class="n">pat2</span><span class="p">:</span> <span class="n">do_something_else</span><span class="p">()</span> </pre></div> <p>In effect, match statement is a replacement for something like </p> <div class="highlight"><pre><span></span><span class="k">if</span> <span class="n">matches</span><span class="p">(</span><span class="n">expr1</span><span class="p">,</span> <span class="n">pat1</span><span class="p">)</span> <span class="p">:</span> <span class="n">do_something</span><span class="p">()</span> <span class="k">elif</span> <span class="n">matches</span><span class="p">(</span><span class="n">expr2</span><span class="p">,</span> <span class="n">pat2</span><span class="p">):</span> <span class="n">do_something_else</span><span class="p">()</span> </pre></div> <p>I will use “match statement" interchangeably with “Structural Pattern Matching” for the remaining sections of the blog. </p> <h2>Motivation and Applications</h2> <p>The match statement is usable in places where different blocks of code need to be executed based on the structure or state of the data. This means that it can be used in a lot of different places. </p> <p>The motivation to use a match statement is to simplify the code and make it more readable. </p> <p>The Subject is a regular python expression which can resolve to most sequences, dictionary or an object. Patterns can test for a match of length, type, specific index, key, attribute values, inheritance etc.</p> <p>As a use case, a typical json handler checks data for states or properties and performs corresponding actions.</p> <p>With match pattern, a handler for json data becomes very simple. </p> <div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">process_media_blob</span><span class="p">(</span><span class="n">jsondata</span><span class="p">):</span> <span class="n">match</span> <span class="n">jsondata</span><span class="p">:</span> <span class="n">case</span> <span class="p">{</span><span class="s1">&#39;type&#39;</span><span class="p">:</span><span class="s1">&#39;audio&#39;</span><span class="p">,</span> <span class="s1">&#39;format&#39;</span> <span class="p">:</span><span class="s1">&#39;mp3&#39;</span><span class="p">,</span> <span class="s1">&#39;data&#39;</span><span class="p">:</span> <span class="n">data</span><span class="p">}:</span> <span class="n">render_mp3</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="n">case</span> <span class="p">{</span><span class="s1">&#39;type&#39;</span><span class="p">:</span><span class="s1">&#39;video&#39;</span><span class="p">,</span> <span class="s1">&#39;format&#39;</span> <span class="p">:</span> <span class="p">(</span><span class="s1">&#39;webm&#39;</span> <span class="o">|</span> <span class="s1">&#39;mkv&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">videoformat</span><span class="p">,</span> <span class="s1">&#39;data&#39;</span><span class="p">:</span> <span class="n">data</span><span class="p">}:</span> <span class="n">render_video</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">videoformat</span><span class="p">)</span> <span class="n">case</span> <span class="n">_</span><span class="p">:</span> <span class="k">raise</span> <span class="n">MediaInputException</span><span class="p">(</span><span class="s1">&#39;Usage: .... &#39;</span><span class="p">)</span> </pre></div> <p>The above match statement performed the necessary tests for presence of keys and their expected values elegantly. </p> <p>It also introduced ‘data’ and ‘format’ values to the scope namespace as ‘data’ and ‘videoformat’ respectively. </p> <p>The above is an example when the subject is a dictionary. <a href="https://www.python.org/dev/peps/pep-0636/">PEP 636</a> exhausts use cases for data-structures like list, class, tuples etc. </p> <h2>Pattern is not an expression</h2> <p>An important thing to remember is that a Pattern is not a Python expression, even though under certain situations it looks exactly like a Python expression. </p> <p>A Pattern has completely different grammar rules from an expression. </p> <p>As an example below, <code>Point(x=0, y=1)</code> as an expression instantiates an object. The same as a Pattern statement is a rule check.</p> <div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Point</span><span class="p">:</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="n">p</span> <span class="o">=</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="n">match</span> <span class="n">p</span><span class="p">:</span> <span class="n">case</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span> <span class="k">print</span><span class="p">(</span><span class="s1">&#39;p is an object of class Point with attributes x as 0 and y as 1&#39;</span><span class="p">)</span> <span class="n">case</span> <span class="n">_</span><span class="p">:</span> <span class="k">print</span><span class="p">(</span><span class="s1">&#39; p is something else&#39;</span><span class="p">)</span> </pre></div> <h2>Performance</h2> <p>The match clause is functionally equivalent to “if .. elif .. “ tests. </p> <p>Even for potentially problematic patterns like sequences, constraints are in place to keep processing fast. </p> <p>E.g., The greedy operator ‘*’ in sequence patterns can only be used once in a pattern. </p> <p>The Pattern is a language specific rule and not a regular expression. </p> <p>Hence performance is equivalent to the earlier alternate patterns of "if .. elif ..". </p> <h2>Conclusion</h2> <p>Structural Pattern Matching is a very exciting addition to the Python language. </p> <p>Scala has already shown why this is a very popular and modern style of programming. </p> <p>Portions of python language already use the match clause to simplify existing code. </p> <p>Match statement is a language feature. Understanding it is not just useful for writing elegant code but also to read future Python code. </p>Scripting Matrix Rain On Good Old Terminals2021-12-04T05:47:00-08:002021-12-04T05:47:00-08:00Rishi Makertag:cmlzagk.github.io,2021-12-04:/matrix-rain.html<p>Matrix Rain</p><p>Matrix 4 release date is set for Dec 22. Matrix is one of the most influential movies of all time.</p> <p>A feature of the movie was the Matrix Rain.</p> <p>I went retro this week and scripted the Matrix Rain console application. </p> <p>In this blog I will go over how to create matrix rain on a terminal. </p> <h2>Links</h2> <p>The GIT <a href="https://github.com/cmlzaGk/mtrixrain/">repository</a> of the scripts. </p> <p>A <a href="https://www.youtube.com/watch?v=uJwc8n0OnQE">Youtube</a> of the matrix rain built using on Windows cmd prompt using the approach described here.</p> <h2>Contents</h2> <ul> <li>What is Matrix Rain?</li> <li>Approach</li> <li>Character Set</li> <li>Flickering Rain</li> <li>Enable VT100 mode on Windows 10+</li> <li>The Curses Library</li> <li>Conclusion</li> </ul> <h2>What is Matrix Rain?</h2> <p><a href="https://en.wikipedia.org/wiki/Matrix_digital_rain">Matrix Rain</a> is a visualization of characters falling downwards on a console in the form of rain. </p> <h2>Approach</h2> <p>The approach is simple </p> <ul> <li>Prepare a buffer</li> <li>Repeat forever<ul> <li>Clear the screen</li> <li>Render the buffer</li> <li>Sleep for some time (refresh rate)</li> <li>Update the buffer so that all horizontal lines move downwards</li> </ul> </li> </ul> <p>This works for most part, except for some challenges that I will take about, as well as how to overcome them.</p> <h2>Character Set</h2> <p>The choice of Unicode characters and font-family for a terminal-based matrix rain is very important. Three conditions need to be met. </p> <p>The font-family should be Mono-spaced, all characters should be printable on the terminal, and the terminal should be able to render the characters fast.</p> <p>Mono-spaced means that the letters occupy same amount of horizontal space. </p> <p>If this property is not true, then the lines that we print will be scattered, and there will not be an appearance of a vertical flow.</p> <p>The default font "Consolas" on Windows terminal is Mono-spaced and meets the requirement. </p> <p>I did a manual test to check which Unicode ranges can be displayed on the screen.</p> <p>I built this range manually because I noticed that I could not rely isprintable() method of string in python. </p> <p>e.g. My console was unable to print 0x16ef, but isprintable() returned True. </p> <div class="highlight"><pre><span></span><span class="c1">### https://stackoverflow.com/questions/1477294/generate-random-utf-8-string-in-python</span> <span class="n">include_ranges</span> <span class="o">=</span> <span class="p">[</span> <span class="p">(</span> <span class="mh">0x0023</span><span class="p">,</span> <span class="mh">0x0026</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x0028</span><span class="p">,</span> <span class="mh">0x007E</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x00A1</span><span class="p">,</span> <span class="mh">0x00AC</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x00AE</span><span class="p">,</span> <span class="mh">0x00FF</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x0100</span><span class="p">,</span> <span class="mh">0x017F</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x0180</span><span class="p">,</span> <span class="mh">0x024F</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x2C60</span><span class="p">,</span> <span class="mh">0x2C7F</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x16A0</span><span class="p">,</span> <span class="mh">0x16F0</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x0370</span><span class="p">,</span> <span class="mh">0x0377</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x037A</span><span class="p">,</span> <span class="mh">0x037E</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x0384</span><span class="p">,</span> <span class="mh">0x038A</span> <span class="p">),</span> <span class="p">(</span> <span class="mh">0x038C</span><span class="p">,</span> <span class="mh">0x038C</span> <span class="p">),</span> <span class="p">]</span> <span class="c1"># Used in manual testing</span> <span class="k">for</span> <span class="n">current_range</span> <span class="ow">in</span> <span class="n">include_ranges</span> <span class="p">:</span> <span class="k">for</span> <span class="n">code_point</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">current_range</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">current_range</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span> <span class="k">print</span><span class="p">(</span><span class="s1">&#39;0x{:4x} {} {}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">code_point</span><span class="p">,</span> <span class="nb">chr</span><span class="p">(</span><span class="n">code_point</span><span class="p">),</span> <span class="nb">chr</span><span class="p">(</span><span class="n">code_point</span><span class="p">)</span><span class="o">.</span><span class="n">isprintable</span><span class="p">()))</span> <span class="c1"># Once include_ranges is determined</span> <span class="n">ALPHABETS</span> <span class="o">=</span> <span class="p">[</span> <span class="nb">chr</span><span class="p">(</span><span class="n">code_point</span><span class="p">)</span> <span class="k">for</span> <span class="n">current_range</span> <span class="ow">in</span> <span class="n">include_ranges</span> <span class="k">for</span> <span class="n">code_point</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">current_range</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">current_range</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> </pre></div> <p>The above will also test whether a character takes a long time to render on the screen.</p> <p>Unicode contains a lot of rich characters and if your range picks up such a character that takes time to render, you will not get the rain effect.</p> <p>"Consolas" and the range I published should do the trick for Windows Command prompt.</p> <h2>Flickering Rain</h2> <p>The first goal was to script using dependencies from just the standard python library. With that restriction in place, the interesting part was how to clear the terminal.</p> <p>The first approach is to invoke a shell call, and call 'cls' on windows, and that is not a bad start.</p> <p><img alt="Alt Text" src="https://cmlzagk.github.io/images/flickering_matrix.gif"></p> <p>As can be seen, we have achieved a rain effect, but this level of flickering is unacceptable.</p> <p>This flicker occurs because of the time it takes to do an inter-process call, clear and then display our content from another process.</p> <p>We have to do better.</p> <h2>Enable VT100 mode on Windows 10+</h2> <p>The flicker occurs because of the delay within the refresh because clear screen happens in another process. </p> <p>There is no standard API to clear the screen. However, starting <a href="https://superuser.com/questions/413073/windows-console-with-ansi-colors-handling/1050078#1050078">Windows 10</a>, console got support for VT100 escape sequences that can be sent to the console.</p> <p>It can be enabled using system libraries, however I enabled it by setting a DWORD 'VirtualTerminalLevel' on registry key 'HKEY_CURRENT_USER\Console' to 0x1. </p> <p>The following code can test if the console understands VT100 escape sequencing. </p> <div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">sys</span> <span class="c1"># send the escape sequence to clear the screen for VT100 compatible console</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s1">&#39;This should not be visible</span><span class="se">\033</span><span class="s1">[H</span><span class="se">\033</span><span class="s1">[JThis should be first line of the screen&#39;</span><span class="p">)</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span> </pre></div> <p>The flicker stops after replacing the 'cls' call with VT100 escape sequences. </p> <p><img alt="Alt Text" src="https://cmlzagk.github.io/images/vt100_matrix.gif"></p> <p>VT100 controls also allows us to enter color codes, which completes the rendering, because we need that green matrix effect. </p> <p><img alt="Alt Text" src="https://cmlzagk.github.io/images/vt100_color_matrix.gif"></p> <h2>The Curses Library</h2> <p>The limitations of scripting using only the standard library can be summarized as below:</p> <ul> <li>There is no standard method of determining the width and height of the terminal.</li> <li>Terminal specific codes (e.g. VT100) need to be embedded to avoid flickering.</li> <li>The buffer that gets printed on the screen needs to be organized manually.</li> </ul> <p>It would be good if there was a library that provided a consistent interface and hid the porting implementation from the developer.</p> <p>The Curses library does exactly that. Curses is not shipped with the standard Python distribution on Windows. </p> <p>However, there are third party Windows Curses libraries that can be installed using pip. </p> <p>The overall approach does not change with Curses, except rendering. </p> <p>With Curses, I did something different - I decided to do async rendering and have vertical channels get drained asynchronously too. </p> <p>The output of the Curses implementation is on <a href="https://www.youtube.com/watch?v=uJwc8n0OnQE">Youtube</a></p> <h2>Conclusion</h2> <p>Console programming still has applications particularly in non-graphical systems that still exist.</p> <p>Naturally, there are differences in considerations, restrictions and limitations from what developing even in Simple GUI systems.</p> <p>Matrix Rain on terminal is a fun problem to learn the techniques. It is simple enough to solve and can have satisfying results. </p> <p>See you at the theatres. </p>How To Parse Like A Compiler - Part I2021-03-02T05:47:00-08:002021-03-02T05:47:00-08:00Rishi Makertag:cmlzagk.github.io,2021-03-02:/python-parse-compiler-part1.html<p>Python Lark Part 1</p><p>This week I helped a friend parse a file consisting of Poker hand histories. Instead of creating a hand parser, I wrote a small grammar and used a Python module called Lark to parse the file into a syntax tree. An integration like this used to be tedious in the past, but modern languages allow you to develop such a solution rather quickly.</p> <p>The aim of this blog is to highlight how to use context free grammars as an alternative to hand parsers. In part I, I will focus on parsing and the next part will be on transformation.</p> <p>This is a rather deep topic and I am by no means a language expert. I do not intend to cover theories of compilers, languages, parsers or syntax trees, but I will leave some practical references for those interested. I think that those with a basic understanding of context free grammars and Python can pick this solution, or you can read this again after doing a quick refresher on these topics.</p> <h2>Summary</h2> <ul> <li>Problem Statement</li> <li>Lark</li> <li>Context Free Grammar</li> <li>Earley vs LALR</li> <li>Building our grammar</li> <li>Moving to LALR</li> <li>Conclusion</li> </ul> <p>For reference the entire project can be found <a href="https://github.com/cmlzaGk/samplecodes/tree/main/parselikeacompiler1">here</a></p> <h2>Problem Statement</h2> <p>We will attempt to parse a poker hand history file.</p> <p>Each hand begins with a handnumber and is seperated by other hands by an empty newline.</p> <p>A hand consists of the complete state information of the game. The players, their positions, starting stacks, actions they took on each poker street, followed by the results.</p> <p>I hand-created a <a href="https://github.com/cmlzaGk/samplecodes/blob/main/parselikeacompiler1/sample.txt">sample file</a> for this blog consisting of two hands.</p> <p>The actual file that I parsed consisted a few hundred hands from one game.</p> <p>Online Poker players can download these files from the sites they play in. Each site could have its own format and there are many paid Software online for parsing these files into databases.</p> <h2>Lark</h2> <p>Lark is a python module that needs be installed using PIP.</p> <p>As always, I recommend creating a virtual python environment before installing any module to keep your global module space on your machine clean.</p> <div class="highlight"><pre><span></span>C:<span class="se">\~</span>&gt;mkdir samplelark C:<span class="se">\~</span>&gt;cd samplelark C:<span class="se">\~\s</span>amplelark&gt;python -m venv venv C:<span class="se">\~\s</span>amplelark&gt;.<span class="se">\v</span>env<span class="se">\S</span>cripts<span class="se">\a</span>ctivate <span class="o">(</span>venv<span class="o">)</span> C:<span class="se">\U</span>sers<span class="se">\r</span>ishim<span class="se">\s</span>amplelark&gt;pip install lark Collecting lark Using cached https://files.pythonhosted.org/packages/69/8b/9418c0d24df0e8261c24e4b7218e80369f175c68eb3dfa119bc89d53f7fc/lark-0.11.1-py2.py3-none-any.whl Installing collected packages: lark Successfully installed lark-0.11.1 You are using pip version <span class="m">19</span>.0.3, however version <span class="m">21</span>.0.1 is available. You should consider upgrading via the <span class="s1">&#39;python -m pip install --upgrade pip&#39;</span> command. <span class="o">(</span>venv<span class="o">)</span> C:<span class="se">\~\s</span>amplelark&gt; </pre></div> <p>To understand how to use Lark, lets attempt to parse a nested list of words.</p> <p>We will apply a simple grammar - a list consists of a list of single word or list, or it consists words and lists seperated by comma.</p> <p>In different words, a list consists of a single word or list, followed by zero or more sequences of comma and a single list or word.</p> <p>More formally</p> <div class="highlight"><pre><span></span>list -&gt; [listelem (&quot;,&quot; listelem)*] listelem: list|WORD </pre></div> <p>A complete Lark based parser looks like this:</p> <div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">lark</span> <span class="kn">import</span> <span class="n">Lark</span> <span class="n">cfg</span> <span class="o">=</span> <span class="sa">r</span><span class="s1">&#39;&#39;&#39;</span> <span class="s1"> list: &quot;[&quot; listelem (&quot;,&quot; listelem)* &quot;]&quot;</span> <span class="s1"> listelem: list|WORD</span> <span class="s1"> </span><span class="si">%i</span><span class="s1">mport common.WS</span> <span class="s1"> </span><span class="si">%i</span><span class="s1">gnore WS</span> <span class="s1"> </span><span class="si">%i</span><span class="s1">mport common.WORD</span> <span class="s1">&#39;&#39;&#39;</span> <span class="n">list_parser</span> <span class="o">=</span> <span class="n">Lark</span><span class="p">(</span><span class="n">cfg</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="s1">&#39;list&#39;</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">list_parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s1">&#39;[test, me, [I, am, nested, [no, kidding]]]&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">pretty</span><span class="p">())</span> </pre></div> <p>The parse() function above returned a syntax-tree.</p> <p>The syntax tree is the parsed output, which we will explore in part II. In this part, we will just print a pretty output of the tree.</p> <p>The output indicates, we were able to succesfully parse the word and capture all the terminals and non terminals that we defined.</p> <div class="highlight"><pre><span></span><span class="o">(</span>venv<span class="o">)</span> C:<span class="se">\~\S</span>amplelark&gt;python nestedlist.py list listelem <span class="nb">test</span> listelem me listelem list listelem I listelem am listelem nested listelem list listelem no listelem kidding </pre></div> <p>This is the core principle of cfg parsing and allows us to cleanly implement a parser.</p> <h2>Context Free Grammar</h2> <p>A Context Free Grammar (CFG) consists of four things:</p> <ol> <li> <p>Terminals : The terminals make up the actual content of a sentence. "[", "," , WORD are all terminals in our example. Note that WORD is in implemented as a regex and we could have defined it ourselves instead of using LARK predefinitions. We will use our own regex Terminals in the actual solution.</p> </li> <li> <p>NonTerminals : Phrase or a clause in grammar. eg. list, listelem</p> </li> <li> <p>Production Rules : A mapping between NonTerminals and a set of Terminals and NonTerminals. eg. listelem: list|WORD is a production rule.</p> </li> <li> <p>Start Elem: A special NonTerminal. This is essentially the rule we are trying to parse an input into.</p> </li> </ol> <p>The language is Context Free because its parsing does not depend on the actual value of a terminal.</p> <p>It is not possible to create an XML parser using a CFG if the parser attempts to match the correctness of start element tag and end element tag. We can create a rule in CFG where a start<X> and end element</Y> exist and are well nested, but we cannot guarentee X == Y.</p> <p>Such would be possible with a context sensitive grammar. In reality, CFG is still used for XML, but matching of X and Y is left to the next stage, the transformer (or the compiler).</p> <h2>Parsers</h2> <p>The first stage of parsing is lexical analyzer where the input is converted into tokens, however the actual parsing is performed by the parser.</p> <p>There are two main types of parsers supported by Lark - Earley and LALR.</p> <p>Earley can parse any grammar that we can write in CFG because it performs backtracking. Essentially, if a rule does not match, it can backtrack and use the next available rule.</p> <p>Such operation is clearly very expensive and is infact cubic in complexity. Unless performance is a major consideration, use Earley to begin with. It is infact default parser for Lark.</p> <p>If you are able to construct grammar in a manner where a parser can deterministicly make a decision on which rule to use next, you could use the LALR parser.</p> <p>LALR parses in linear time and fails fast.</p> <p>In a nutshell, LALR is a shift-reduce parser and that can parse a language does not have any conflicts between shifting and reducing.</p> <p>I will give an example of such a conflict later.</p> <p>By definition, Earley can parse more languages than LALR, but IMO most parsing problems in day to day life can be parsed using LALR.</p> <p>Lark will help you understand which particular construct of your grammar creates a conflict, and if you are able to modify your grammar, you should move to LALR.</p> <h2>Building a Grammar</h2> <p>For reference the sample input file can be found <a href="https://github.com/cmlzaGk/samplecodes/blob/main/parselikeacompiler1/sample.txt">here</a></p> <p>I started building the parser intitutively to begin with by reading the file.</p> <p>I parsed one hand first, section by section, expanding my grammar.</p> <p>The roadblock here were the statements begining with player names because the player names could be one or more words.</p> <div class="highlight"><pre><span></span>foobar checks Mr Foo bets 12 </pre></div> <p>This meant the lexical scanner will not be able to identify the player. If I used a rule such as a "oneword|twowords" to identify the player, the lexer will not be able to disambiguate between 'foobar checks' or 'Mr. foo'.</p> <p>Eventually, I decided that since I was parsing games with static list of player names, I will just include player name as grammar terminals.</p> <p>This allows me to be context free.</p> <p>I can also create a pre-parser that will generate the player names as terminals before I start the main parser, so I was not very concerned about broader application of this approach.</p> <p>As I started parsing more and more hands in the file, I encountered new conditions which I incorporated into my grammar and my final main rule took a form that was able to parse the entire file.</p> <div class="highlight"><pre><span></span> handhistory: handdesc when table seats posts preflop [flop] [turn] [river] [boards] summary winner [shows] </pre></div> <p>For reference the earley parser implementation can be found <a href="https://github.com/cmlzaGk/samplecodes/blob/main/parselikeacompiler1/hhparser.py">here</a></p> <h2>Moving to LALR</h2> <p>The above implementation was Earley and was visibly slow. I decided to move my parser to LALR.</p> <p>It is pretty straight-forward in Lark to move to LALR. I just needed to pass the parameter parser='lalr' to Lark constructor.</p> <p>However, my parser broke immediately, and I noticed that asking the lexer to ignore whitespace meant that new statements were not easily recognized.</p> <p>I conclude that Earley was doing a lot of backtracking between rules because of ignoring whitespace. I made the parser strict about whitespace and I was able to parse most of the structures.</p> <p>I ran into a conflict resolution with my Non Terminal called 'shows'.</p> <div class="highlight"><pre><span></span> handhistory: handdesc when table seats posts preflop [flop] [turn] [river] [boards] summary winner [shows] shows: showdown* </pre></div> <p>This directive happens when a player shows a hand. Square brackets in terminals indicate that the NonTerminal appears zero or one times.</p> <p>The asterix symbol in the NonTerminal is a regular regex symbol indicating zero or more times.</p> <p>Hence when there were no occurrences of shows, the parser didn't know which production rule to use, because both the rules matched.</p> <p>An LALR parser needs to know with certainty whether to shift the square bracket rule or to reduce with the second production.</p> <p>I resolved this by restructuring shows to have one or more occurences.</p> <p>Hence 'shows' is only reduced when there is actually a showdown directive. If there is no showdown, a shift is performed.</p> <p>The complete LALR parser implementation can be found <a href="https://github.com/cmlzaGk/samplecodes/blob/main/parselikeacompiler1/hhparser_lalr.py">here</a></p> <h2>Conclusion</h2> <p>At the end the complete parser was very quick to create and I was able to develop it organically and intituively.</p> <p>I will admit that it is difficult to understand error conditions during building, and it requires a fair bit of step by step parsing.</p> <p>The overall process is very quick because at the end of the day, the code is concise and quite frankly easier to understand</p> <p>I have used this in parsing poker ranges and poker hand histories.</p> <p>In the next part, I will explain the transformation technique. Afterall, the canonical use of parsing is in converting data from one form to another.</p>Python Project Layout For Flask And Everything2019-09-03T10:20:00-07:002019-09-06T11:30:00-07:00Rishi Makertag:cmlzagk.github.io,2019-09-03:/python-project-layout.html<p>Python Project Layouts.</p><p>I will walk through a sample web application built using python, flask and docker. The application invokes a specialized math function in a library co-developed during application development. </p> <p>The aim of this blog is to highlight some project structure best practices, that I learnt and used in my own application. </p> <p>For those not familiar with flask, flask is a small web framework that implements the python WSGI interface.</p> <p>WSGI is an application level contract to develop web applications in python. The other side of the contract is implemented by WSGI webservers like UWSGI. </p> <p>By no means, these practices are limited to a flask application, and could be applied everywhere.</p> <h2>Summary</h2> <p>I will highlight the following areas using a sample project which can be found <a href="https://github.com/cmlzaGk/sampleflask">here</a></p> <ul> <li>Project layout.</li> <li>setup.py</li> <li>requirements.txt</li> <li>__init__.py</li> <li>Local Deployment</li> <li>Intra-package and inter-package references.</li> <li>Class factories</li> <li>Test cases and code coverage</li> <li>DockerFiles</li> </ul> <h2>Project Layout</h2> <p>The project layout looks like this.</p> <div class="highlight"><pre><span></span>. ├── docker-compose.yml ├── Dockerfile ├── Dockerfile.unittests ├── requirements.txt ├── run_coverage.sh ├── uwsgi.ini └──-bettermath-lib ├── setup.py └── bettermathlib ├── better_random.py ├── __init__.py └── bettermathlib_tests ├── tests_bettermathlib.py ├── __init__.py └── randomweb-app ├── setup.py └── randomwebapp_tests ├── tests_basic.py ├── tests_client.py ├── __init__.py └── randomweb_app ├── config.py ├── create_app.py ├── flask_app.py ├── random_creator.py ├── __init__.py └── main ├── views.py ├── __init__.py └── static ├── about.txt └── templates ├── base.html └── betterrandom.html </pre></div> <p>A distribution is a folder that can be installed and moved around anywhere. bettermath-lib and randomweb-app are the self-contained distributions.</p> <p>A package is a directory contains other packages or modules and is also called "Import Package" because its importable.</p> <p>The file __init__.py is a special file that is executed when a package is imported in the runtime.</p> <p>Here bettermathlib, bettermathlib_tests are packages inside the distribution bettermath-lib. randomweb_app and randomwebapp_tests are packages inside the distribution randomweb-app, and finally main is a package inside the package randomweb_app.</p> <h2>setup.py</h2> <p>setup.py is part of setuptools and defines properties of a distribution.</p> <div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">setuptools</span> <span class="kn">import</span> <span class="n">setup</span><span class="p">,</span> <span class="n">find_packages</span> <span class="n">setup</span><span class="p">(</span> <span class="n">name</span><span class="o">=</span><span class="s1">&#39;randomweb-app&#39;</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="s1">&#39;1.0&#39;</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s1">&#39;Random Webapp&#39;</span><span class="p">,</span> <span class="n">author</span><span class="o">=</span><span class="s1">&#39;Fishy Baker&#39;</span><span class="p">,</span> <span class="n">author_email</span><span class="o">=</span><span class="s1">&#39;fishybaker@hotmail.com&#39;</span><span class="p">,</span> <span class="n">packages</span><span class="o">=</span><span class="n">find_packages</span><span class="p">(),</span> <span class="n">install_requires</span> <span class="o">=</span> <span class="p">[</span> <span class="s1">&#39;Flask &gt;= 1.1.1&#39;</span><span class="p">,</span> <span class="s1">&#39;Flask-Bootstrap &gt;= 3.3.7.1&#39;</span><span class="p">,</span> <span class="s1">&#39;Flask-WTF &gt;= 0.14.2&#39;</span><span class="p">,</span> <span class="s1">&#39;jsonschema &gt;= 3.0.1&#39;</span><span class="p">,</span> <span class="s1">&#39;bettermath-lib &gt;= 1.0&#39;</span> <span class="p">],</span> <span class="n">include_package_data</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">package_data</span><span class="o">=</span><span class="p">{</span><span class="s1">&#39;randomweb_app&#39;</span><span class="p">:</span> <span class="p">[</span><span class="s1">&#39;templates/*&#39;</span><span class="p">,</span> <span class="s1">&#39;static/*&#39;</span><span class="p">]}</span> <span class="p">)</span> </pre></div> <p>There are three main parts of setup.py</p> <div class="highlight"><pre><span></span> <span class="n">packages</span><span class="o">=</span><span class="n">find_packages</span><span class="p">(),</span> </pre></div> <p>find_packages is a neat helper to automatically include all packages inside randomweb-app as part of the distributable. The other option would be to create a static python list with a list of packages.</p> <div class="highlight"><pre><span></span> <span class="n">include_package_data</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">package_data</span><span class="o">=</span><span class="p">{</span><span class="s1">&#39;randomweb_app&#39;</span><span class="p">:</span> <span class="p">[</span><span class="s1">&#39;templates/*&#39;</span><span class="p">,</span> <span class="s1">&#39;static/*&#39;</span><span class="p">]}</span> </pre></div> <p>The third importand section is install_requires. It is typically a list of minimum versions of other distributions that your package has a dependency on.</p> <p>The canonical way to obtain this list is via <code>pip freeze</code> command.</p> <p>The catch is that we will not use this list in our application environment.</p> <p>The consumers of your distribution should ideally be free to choose the latest and greatest stable versions, and ensure that their environment is tested.</p> <h2>requirements.txt</h2> <p>requirements.txt is the list of dependencies that the application is tested with. In the production environment, you will want to install specific versions of those dependencies.</p> <p>requirements.txt is not part of the distribution. That role is fullfilled by setup.py's install_requires.</p> <p>Hence requirements.txt lives at a folder level higher than the distributions.</p> <div class="highlight"><pre><span></span>pip freeze &gt; requirements.txt </pre></div> <p>The typical production deployment should execute a variant of pip install with -r requirements.txt.</p> <div class="highlight"><pre><span></span>pip install -r requirements.txt </pre></div> <h2>__init__.py</h2> <p>__init__.py plays a crucial role in namespacing the package's contents appropriately.</p> <p>The class BetterRandom is in module better_random.py. So a consumer of my distribution would have to import bettermathlib.better_random to access BetterRandom.</p> <p>By directly importing BetterRandom in __init__.py we bring BetterRandom to bettermathlib namespace, and then "import bettermathlib" is a much better import style than "import bettermathlib.better_random"</p> <div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">.better_random</span> <span class="kn">import</span> <span class="n">BetterRandom</span> </pre></div> <h2>Local Deployment</h2> <p>Pip has an option -e, called the editable option, using which pip will reference the local source paths in the deployment index.</p> <div class="highlight"><pre><span></span>pip install --no-deps -e bettermath-lib/ pip install --no-deps -e randomweb-app/ </pre></div> <p>After this, both the packages are self-contained and installed in the dev environment. We can continue to make code changes without re-installing the packages.</p> <p>We also want to exclude these distributions in the applications requirements.txt in case we need to regenerate requirements.txt.</p> <div class="highlight"><pre><span></span>pip freeze --exclude-editable &gt; requirements.txt </pre></div> <p>For co-developed distributions we will call pip seperately for these distributions.</p> <h2>Intra-package and inter package references.</h2> <p>References is now where we will see the advantage of keeping packages distributable come in.</p> <p>For referencing modules within a package, we will use the intra-package references using the . notation.</p> <p>We should be ok taking dependecies on where the relative paths of modules within a package are.</p> <div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">..random_creator</span> <span class="kn">import</span> <span class="n">random_int</span> </pre></div> <p>For inter-package references, we can now use absolute references without worrying about where the actual code for the package resides.</p> <div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">bettermathlib</span> <span class="kn">import</span> <span class="n">BetterRandom</span> </pre></div> <h2>Class Factories</h2> <p>This section is flask-app sepcific, but the principle applies everywhere.</p> <p>This application is a flask micro-service, and does not need a "main" anywhere. Services like uwsgi and flask commandline expect to find a callable object in a module.</p> <p>I created a module called flask_app.py inside randomwebapp which creates a global app object using a class factory which lives inside another module create_app.py.</p> <p>Hence the module we want to define in uwsgi.ini becomes randomwebapp.flask_app.</p> <div class="highlight"><pre><span></span><span class="na">module</span> <span class="o">=</span> <span class="s">randomweb_app.flask_app</span> <span class="na">callable</span> <span class="o">=</span> <span class="s">app</span> </pre></div> <p>The class factory function create_app.create_app does flask initializaiton based on the passed configuration. This is now the only user object we want to expose in my package's namespace, as it will be useful in testing.</p> <h2>Test cases and code coverage</h2> <p>With distributed packages, there are a few options for testing. We cannot rely on test-discovery, as we want my tests to be deployable.</p> <p>We can exploit the __init__.py again to bring all test classes directly into the namespace of the test package.</p> <div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">.tests_bettermathlib</span> <span class="kn">import</span> <span class="n">BetterMathTestCases</span> </pre></div> <p>Now unittests can be run via the unittest module. We can choose to run both the test packages together or seperately.</p> <div class="highlight"><pre><span></span>python -m unittest randomwebapp_tests bettermathlib_tests </pre></div> <p>For Flask we can use a flask command-line-interface property to explicitly define all the test packages that verify this application.</p> <div class="highlight"><pre><span></span><span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s1">&#39;FLASK_CONFIG&#39;</span><span class="p">)</span> <span class="ow">or</span> <span class="s1">&#39;default&#39;</span><span class="p">)</span> <span class="nd">@app.cli.command</span><span class="p">()</span> <span class="k">def</span> <span class="nf">test</span><span class="p">():</span> <span class="sd">&quot;&quot;&quot;Run the unit tests.&quot;&quot;&quot;</span> <span class="kn">import</span> <span class="nn">unittest</span> <span class="n">testmodules</span> <span class="o">=</span> <span class="p">[</span> <span class="s1">&#39;bettermathlib_tests&#39;</span><span class="p">,</span> <span class="s1">&#39;randomwebapp_tests&#39;</span><span class="p">,</span> <span class="p">]</span> <span class="n">suite</span> <span class="o">=</span> <span class="n">unittest</span><span class="o">.</span><span class="n">TestSuite</span><span class="p">()</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">testmodules</span><span class="p">:</span> <span class="n">suite</span><span class="o">.</span><span class="n">addTest</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">defaultTestLoader</span><span class="o">.</span><span class="n">loadTestsFromName</span><span class="p">(</span><span class="n">t</span><span class="p">))</span> <span class="n">unittest</span><span class="o">.</span><span class="n">TextTestRunner</span><span class="p">(</span><span class="n">verbosity</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span> </pre></div> <p>Now flask tests starts working for me -</p> <div class="highlight"><pre><span></span><span class="o">(</span>venv<span class="o">)</span> ...&gt;flask <span class="nb">test</span> test_better_random <span class="o">(</span>bettermathlib_tests.tests_bettermathlib.BetterMathTestCases<span class="o">)</span> ... ok test_app_exists <span class="o">(</span>randomwebapp_tests.tests_basic.BasicsTestCase<span class="o">)</span> ... ok test_app_is_testing <span class="o">(</span>randomwebapp_tests.tests_basic.BasicsTestCase<span class="o">)</span> ... ok test_home_page <span class="o">(</span>randomwebapp_tests.tests_client.FlaskClientTestCase<span class="o">)</span> ... ok ---------------------------------------------------------------------- Ran <span class="m">4</span> tests in <span class="m">0</span>.036s OK </pre></div> <p>and code coverage</p> <div class="highlight"><pre><span></span>coverage run -m flask <span class="nb">test</span> coverage report </pre></div> <h2>DockerFiles</h2> <p>There are two DockerFiles one for production and one for unit-testing. The production docker files installs the packages non-editable.</p> <div class="highlight"><pre><span></span>RUN python3 -m pip install -r requirements.txt RUN python3 -m pip install --no-deps bettermath-lib/ RUN python3 -m pip install --no-deps randomweb-app/ </pre></div> <p>The unittest takes the production docker image and uninstalls the two packages and re-installs them editable. This allows us to retrieve code-coverage results from a container execution.</p> <div class="highlight"><pre><span></span>RUN python3 -m pip uninstall -y bettermath-lib RUN python3 -m pip uninstall -y randomweb-app RUN python3 -m pip install coverage RUN python3 -m pip install --no-deps -e bettermath-lib/ RUN python3 -m pip install --no-deps -e randomweb-app/ </pre></div> <p>Thank you. The complete sample project is <a href="https://github.com/cmlzaGk/sampleflask">here</a>.</p>