<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Segni Adeba</title>
    <description>The latest articles on DEV Community by Segni Adeba (@segniadebagodsson).</description>
    <link>https://dev.to/segniadebagodsson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1240065%2Fdf1aea0f-522f-4400-b563-db9a15765212.jpeg</url>
      <title>DEV Community: Segni Adeba</title>
      <link>https://dev.to/segniadebagodsson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/segniadebagodsson"/>
    <language>en</language>
    <item>
      <title>Building a Development Environment for Monkey: Part 3 - Solving an Advent of Code problem</title>
      <dc:creator>Segni Adeba</dc:creator>
      <pubDate>Sat, 24 Oct 2026 16:33:47 +0000</pubDate>
      <link>https://dev.to/segniadebagodsson/building-a-development-environment-for-monkey-part-3-solving-an-advent-of-code-problem-44gl</link>
      <guid>https://dev.to/segniadebagodsson/building-a-development-environment-for-monkey-part-3-solving-an-advent-of-code-problem-44gl</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;We are finally done with building the development environment for Monkey after Part 1 and Part 2 of our series, by &lt;a href="https://segni.hashnode.dev/building-a-development-environment-for-monkey-part-1-syntax-highlighting-with-tree-sitter" rel="noopener noreferrer"&gt;adding syntax highlighting with Tree-sitter&lt;/a&gt; and &lt;a href="https://segni.hashnode.dev/building-a-development-environment-for-monkey-part-2-lsp" rel="noopener noreferrer"&gt;building a Language Server for it from scratch in Go&lt;/a&gt; respectively. If you haven't already I suggest you check them out.&lt;/p&gt;

&lt;p&gt;In the final entry of the series we will put Monkey to "real" work, solving an AoC problem (&lt;a href="https://adventofcode.com/2024/day/1" rel="noopener noreferrer"&gt;2024 - Day 1&lt;/a&gt;). Attempting to solve this problem will quickly reveal how limited Monkey is, how the Tree-sitter corpus and all the LSP unit tests missed some bugs and how we had to improve upon the language by going back to our previous two projects. Finally, we will also look closely at the solution's performance.&lt;/p&gt;

&lt;p&gt;The following are some of the key findings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The LSP had a typed nil interface bug that broke all language features inside if blocks without an else block.&lt;/li&gt;
&lt;li&gt;Monkey's lexer didn't read escape characters correctly while reading/lexing string literals.&lt;/li&gt;
&lt;li&gt;Monkey is &lt;code&gt;~1000x&lt;/code&gt; slower than Go (2317 ms vs 2 ms).&lt;/li&gt;
&lt;li&gt;Monkey allocates &lt;code&gt;~2000x&lt;/code&gt; more memory than Go (232 MB vs 118 KB per op).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~83%&lt;/code&gt; of CPU time is inside the interpreter's &lt;code&gt;Eval&lt;/code&gt; loop.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~39%&lt;/code&gt; of all allocations come from creating a new &lt;code&gt;Environment&lt;/code&gt; per function call.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The AoC problem and solution
&lt;/h2&gt;

&lt;p&gt;For brevity, I won't dump the interesting story behind the problem itself here since AoC problems tend to have rather long mythical stories.&lt;/p&gt;

&lt;p&gt;The problem has two parts and they both have a similar input file: 1000 lines of two columns of numbers separated by three white-space characters. An example input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3   4
4   3
2   5
1   3
3   9
3   3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Part one
&lt;/h3&gt;

&lt;p&gt;The problem is stated as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pair up the smallest number in the left list with the smallest number in the right list, then the second-smallest left number with the second-smallest right number, and so on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within each pair, figure out how far apart the two numbers are; you'll need to add up all of those distances.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;naive algorithm&lt;/strong&gt; of my solution is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sort both columns in a non-decreasing order.&lt;/li&gt;
&lt;li&gt;Iterate over the two columns simultaneously, and in the iteration we sum up the absolute difference between each column for each line.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Part two
&lt;/h3&gt;

&lt;p&gt;The problem is stated as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This time, you'll need to figure out exactly how often each number from the left list appears in the right list. Calculate a total similarity score by adding up each number in the left list after multiplying it by the number of times that number appears in the right list.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The algorithm to my &lt;strong&gt;naive solution&lt;/strong&gt;: To find the similarity score, for each number in the first column, we count it in the second column and multiply that count with the number. The sum of all of these products will be the solution.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;efficient solution&lt;/strong&gt; would be to build a frequency map the numbers in the right column first and then using it to find the counts of the numbers in the first column with a cheap hash map lookup.&lt;/p&gt;

&lt;p&gt;We will stick to the naive &lt;code&gt;O(n^2)&lt;/code&gt; solution to put Monkey to more work, which will help us observe its flaws far better than if we went with the efficient solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monkey's limitations
&lt;/h2&gt;

&lt;p&gt;First and foremost we need to &lt;strong&gt;read the input file&lt;/strong&gt; and Monkey does not have a way to do that. We also need to do some string manipulations on the input text like &lt;strong&gt;splitting&lt;/strong&gt; and &lt;strong&gt;trimming&lt;/strong&gt;. Since the input is read as a string, we need a way to &lt;strong&gt;convert the string numbers into integers&lt;/strong&gt;. As we saw in our solution for part one, we also need a way to &lt;strong&gt;sort&lt;/strong&gt; an array of numbers. Other major issues include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;no for loops&lt;/strong&gt; - we have no keywords to iteratively run a code snippet or loop over iterable objects.&lt;/li&gt;
&lt;li&gt;no &lt;strong&gt;variable re-assignment&lt;/strong&gt; (only declaration) - which makes Monkey a "type-safe" language (when it comes to variables). But funnily enough not just limited to types, it's also "value-safe" because you cannot assign another value to an already declared variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Solutions to language limitations
&lt;/h3&gt;

&lt;p&gt;Some limitations can be worked around while some require changes to the interpreter itself. For example we can solve the following without any changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loops&lt;/strong&gt;: We can use recursion to loop however we like. It only comes at the disadvantage of having &lt;code&gt;O(n)&lt;/code&gt; space complexity. The bigger the loop, the deeper the recursion, the more stack memory used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variable assignment&lt;/strong&gt;: This can be solved by simply declaring new variables or, in our solutions, just passing them into functions as arguments to be updated through calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But when it comes to other limitations such as reading a file, string manipulations, ASCII to integer conversion or sorting, I resorted to just adding more built-in Monkey functions. In other words, doing them in Go, which may seem like cheating but implementing some of these things in Monkey is way more than what I'm currently willing to do!&lt;/p&gt;




&lt;h2&gt;
  
  
  Monkey updates: built-in functions and more
&lt;/h2&gt;

&lt;p&gt;The following are the new Monkey built-in functions I had to add in order solve this AoC problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  readFile
&lt;/h3&gt;

&lt;p&gt;This function accepts the path to the file as a string and returns the full string content of the file. It uses Go's &lt;code&gt;os.ReadFile&lt;/code&gt; function under the hood.&lt;/p&gt;

&lt;p&gt;The main take away from implementing this and struggling with why it was reporting "file doesn't exist" is that a process's CWD is wherever the shell was when the program is launched. Running &lt;code&gt;./bin/monkey&lt;/code&gt; from the project root and relative paths resolve against the root; &lt;code&gt;cd bin &amp;amp;&amp;amp; ./run&lt;/code&gt; and they resolve against &lt;code&gt;bin&lt;/code&gt;. The binary's location is irrelevant.&lt;/p&gt;

&lt;h3&gt;
  
  
  splitString
&lt;/h3&gt;

&lt;p&gt;This function accepts a string as its first argument and the separator string as the second and it returns an array of strings. It uses Go's &lt;code&gt;strings.Split&lt;/code&gt; function under the hood.&lt;/p&gt;

&lt;p&gt;Unfortunately this wasn't the end of it, check out the following code: &lt;code&gt;input.txt&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.monkey&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let input = readFile("input.txt");
let lines = splitString(input, "\n");

puts(lines)
// Expected: ["1", "2"]
// Actual  : ["1/n2"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I tried to split a file into lines using &lt;code&gt;splitString&lt;/code&gt;, the result was a single-element array containing the entire file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Updating Monkey's Lexer to read escape sequences properly
&lt;/h4&gt;

&lt;p&gt;The culprit turned out to be Monkey's Lexer, can you figure out what the issue is? &lt;code&gt;lexer.go&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="c"&gt;// readChar() reads one character at a time from the input source code&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Lexer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;readString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="c"&gt;// advances the reader's position by one from the initial opening " of a string&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'"'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// reached end of string or file&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our lexer did not handle escape sequences correctly, our second argument "\n" was lexed as two literal characters/bytes (&lt;code&gt;\&lt;/code&gt; and &lt;code&gt;n&lt;/code&gt;) rather than the newline byte &lt;code&gt;0x0A&lt;/code&gt; (&lt;code&gt;\n&lt;/code&gt;). This meant that &lt;code&gt;splitString&lt;/code&gt; was searching for a &lt;strong&gt;two-character sequence&lt;/strong&gt; that never appeared in the actual file content.&lt;/p&gt;

&lt;p&gt;The fix was to rewrite &lt;code&gt;readString&lt;/code&gt; in the lexer to decode escape sequences on the fly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;\n&lt;/code&gt; becomes a real newline&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\t&lt;/code&gt; a tab&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\\&lt;/code&gt; a backslash, and&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\"&lt;/code&gt; a quote&lt;/li&gt;
&lt;li&gt;Unknown escapes are kept literally so that no data is silently lost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;lexer.go&lt;/code&gt; (see &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lexer/lexer.go#L195" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Lexer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;readString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c"&gt;// end of string or file&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'"'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// normal case&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sc"&gt;'\\'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// different paths depending on the next char&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peekChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="sc"&gt;'n'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\t'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="sc"&gt;'r'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\r'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="sc"&gt;'"'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="sc"&gt;'\\'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\\'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\\'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// just writes \ by default&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Not only did we fix &lt;code&gt;readString&lt;/code&gt;, but we also added escaping quotes to Monkey's strings, which was not possible before.&lt;/p&gt;

&lt;h4&gt;
  
  
  Updating Monkey's Tree-sitter to handle escape sequences properly
&lt;/h4&gt;

&lt;p&gt;Monkey strings can properly use escape sequences after updating our Lexer but our Tree-sitter grammar does not.&lt;/p&gt;

&lt;p&gt;This is what a Monkey string with escaped quotes looked like before the fix, not so great:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9tdvm3oi15yuzpn0sviw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9tdvm3oi15yuzpn0sviw.png" alt=" " width="597" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at how our grammar describes strings: &lt;code&gt;grammar.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;"&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="c1"&gt;// no escapes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It handles any character except for quotes. Lets fix this by adding a new rule &lt;code&gt;escape_sequence&lt;/code&gt;: &lt;code&gt;grammar.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;escape_sequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// \n, \t, \\, \"&lt;/span&gt;
&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="nx"&gt;escape_sequence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\[&lt;/span&gt;&lt;span class="sr"&gt;ntr"&lt;/span&gt;&lt;span class="se"&gt;\\]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;It also turns out that there's a specific Neovim capture &lt;code&gt;@string.escape&lt;/code&gt; (check &lt;a href="https://neovim.io/doc/user/treesitter/#treesitter-highlight-groups" rel="noopener noreferrer"&gt;Neovim docs&lt;/a&gt;) to handle escaped characters specifically, lets use that in &lt;code&gt;highlights.scm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(escape_sequence) @string.escape
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrvnlived36zkkymvaiv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrvnlived36zkkymvaiv.png" alt=" " width="597" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Bellissima!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  atoi
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;atoi&lt;/code&gt; accepts a string as an argument and returns a number, it uses Go's &lt;code&gt;strconv.Atoi&lt;/code&gt; function under the cover.&lt;/p&gt;

&lt;h3&gt;
  
  
  sortInts
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;sortInts&lt;/code&gt; accepts an array of integers and returns it sorted in a non-decreasing order, it uses Go's &lt;code&gt;slices.Sort&lt;/code&gt; function.&lt;/p&gt;

&lt;h3&gt;
  
  
  trimSpace
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;trimSpace&lt;/code&gt; accepts a string and trims leading and trailing white space, returning the resulting string. It uses Go's &lt;code&gt;strings.TrimSpace&lt;/code&gt; internally.&lt;/p&gt;

&lt;h3&gt;
  
  
  trimSuffix
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;trimSuffix&lt;/code&gt; accepts a string and returns it without the provided trailing suffix string. It uses Go's &lt;code&gt;strings.TrimSuffix&lt;/code&gt; internally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating Tree-sitter to include our new built-in functions
&lt;/h3&gt;

&lt;p&gt;We should update &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/tree-sitter-monkey/queries/highlights.scm#L50" rel="noopener noreferrer"&gt;&lt;code&gt;highlights.scm&lt;/code&gt;&lt;/a&gt; to highlight our new functions as built-ins with the &lt;code&gt;@function.builtin&lt;/code&gt; capture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;((call_expression
  function: (identifier) @function.builtin
  (#any-of? @function.builtin "len" "first" "last" "rest" "push" "puts"
    "readFile" "splitString" "atoi" "sortInts" "trimSpace" "trimSuffix"))
  (#set! priority 130))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Yet another typed nil interface bug
&lt;/h2&gt;

&lt;p&gt;As I was implementing the solution, I encountered the most elusive bug I have faced in the series so far. Consider the following monkey code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if(true){
    puts("hello")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three language features stopped working inside and also after the if block, all triggering these features caused the server to panic.&lt;/p&gt;

&lt;p&gt;After inspecting the stack trace, the cause turned out to be the function &lt;code&gt;FindASTNode&lt;/code&gt;(&lt;code&gt;state.go&lt;/code&gt;) in the Monkey Language Server. &lt;code&gt;FindASTNode&lt;/code&gt; received an interface value whose data word was &lt;code&gt;0x0&lt;/code&gt; yet somehow survived my if &lt;code&gt;node == nil&lt;/code&gt; guard at the very start of the function.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IfExpression.Alternative&lt;/code&gt; is declared as &lt;code&gt;*ast.BlockStatement&lt;/code&gt;, and an &lt;code&gt;if&lt;/code&gt; expression without an &lt;code&gt;else&lt;/code&gt; leaves it as a nil pointer.&lt;/p&gt;

&lt;p&gt;Boxing a &lt;code&gt;(*ast.BlockStatement)(nil)&lt;/code&gt; into the &lt;code&gt;ast.Node&lt;/code&gt; interface produces a non-nil interface wrapping a nil pointer. &lt;code&gt;node == nil&lt;/code&gt; is false because the type word is set, the type switch still matches &lt;code&gt;*ast.BlockStatement&lt;/code&gt;, and the walker happily de-references the empty struct causing a panic.&lt;/p&gt;

&lt;p&gt;This exact mistake is mentioned in the &lt;a href="https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them" rel="noopener noreferrer"&gt;Go Mistakes and How to Avoid Them&lt;/a&gt; book under mistake #45, Returning a nil receiver. &lt;code&gt;state.go&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;FindASTNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// This fails&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BlockStatement&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Panic happens in this function, dereferencing the nil node&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;positionIsInRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Statements&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FindASTNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IfExpression&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FindASTNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FindASTNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Consequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// The issue, node.Alternative is nil&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;FindASTNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alternative&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following fix of changing the if expression case suffices (see &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L406" rel="noopener noreferrer"&gt;source&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IfExpression&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FindASTNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FindASTNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Consequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alternative&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;FindASTNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alternative&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also update our guard to be more capable using reflection as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;FindASTNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isNilNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;isNilNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNil&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Monkey solution
&lt;/h2&gt;

&lt;p&gt;All issues are now fixed, let's finally solve the AoC problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  reading the input
&lt;/h3&gt;

&lt;p&gt;We read the input similarly for both problems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let content = trimSuffix(readFile("input.txt"), "\n")
let lines = splitString(content, "\n")

let prep = fn(i) {
    if (i==len(lines)){
        return {
            "colOne": [],
            "colTwo": [],
        };
    }

    let res = prep(i+1);

    let splitLine = splitString(lines[i], "   ");
    return {
        "colOne": push(res["colOne"], atoi(splitLine[0])),
        "colTwo":push(res["colTwo"], atoi(splitLine[1])),
    };
}

let res = prep(0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;trimSuffix&lt;/code&gt; applied on the input because editors sometimes add &lt;code&gt;\n&lt;/code&gt; to the input text. &lt;code&gt;prep&lt;/code&gt; accepts an iteration index to recursively prepare two integer arrays from &lt;code&gt;lines&lt;/code&gt;, an array of strings where each string is a line from the input. The base case is the end of the input, when the iterator is equal to the number of lines.&lt;/p&gt;

&lt;p&gt;The result is a hash-map with two keys, they should be self-descriptive enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  part one
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/examples/AoC_2024/day1_part1.monkey" rel="noopener noreferrer"&gt;&lt;code&gt;part1.monkey&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// read input

let colOne = sortInts(res["colOne"]);
let colTwo = sortInts(res["colTwo"]);

let dayOne = fn(i, j){
    if (i==len(lines)) {
        return 0
    }

    let sum = dayOne(i+1, j+1);

    let diff = colOne[i] - colTwo[j];
    return sum + if(diff&amp;lt;0){ -1*diff }else{ diff };
}

puts(dayOne(0, 0))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by sorting both columns (integer arrays) with &lt;code&gt;sortInts&lt;/code&gt;. &lt;code&gt;dayOne&lt;/code&gt; accepts two iteration indexes and recursively sums up the absolute differences of numbers on each line. The base case is the end of the input; we return 0. Then the recursion stack unwinds calculating and returning the new sum starting from the end of the arrays.&lt;/p&gt;

&lt;p&gt;Running this on the main &lt;a href="https://adventofcode.com/2024/day/1/input" rel="noopener noreferrer"&gt;input&lt;/a&gt; returns: &lt;code&gt;2580760&lt;/code&gt;, which is the actual solution. &lt;strong&gt;It worked first try!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  part two
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/examples/AoC_2024/day1_part2.monkey" rel="noopener noreferrer"&gt;&lt;code&gt;part2.monkey&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// read input

let colOne = res["colOne"];
let colTwo = sortInts(res["colTwo"]);

let countNumInArray = fn(num, arr, i) {
    if (i == len(arr)){
        return 0;
    }

    if (num &amp;lt; arr[i]){
        return 0;
    }

    let res = countNumInArray(num, arr, i+1);
    return if (num == arr[i]){ res + 1 } else { res };
}

let dayTwo = fn(i){
    if (i == len(colOne)){
        return 0;
    }

    let sol = dayTwo(i+1);

    let num = colOne[i]
    let numCount = countNumInArray(num, colTwo, 0);

    return sol + (num * numCount);
}

puts(dayTwo(0))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The solution to the second part is a bit more involved compared to the first as it is for most AoC problems.&lt;/p&gt;

&lt;p&gt;We start by grabbing the two columns from the input and sorting the second one, this will help us prune our search by stopping the count when a candidate number in the second column becomes bigger than the number we are counting from the first.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;countNumInArray&lt;/code&gt; accepts a number, an array and an iteration index and recursively counts the number in the given array. The base case is the end of the input and it also has an early return to stop the count to continue past bigger numbers than the one we are counting.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dayTwo&lt;/code&gt; accepts an iteration index as input and recursively iterates over the first column of integers and each time, it counts the number at that index in the second column and returns the product of the number and its count in the second column.&lt;/p&gt;

&lt;p&gt;Running this on the main &lt;a href="https://adventofcode.com/2024/day/1/input" rel="noopener noreferrer"&gt;input&lt;/a&gt; again returns: &lt;code&gt;25358365&lt;/code&gt;, which is the &lt;strong&gt;correct&lt;/strong&gt; answer!&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Even though our solutions to each problem produced correct answers, their performances leave much to be desired. The poor performance is especially noticeable when testing the solutions with the main 1000-line input.&lt;/p&gt;

&lt;p&gt;In the following sections we will do some benchmarking to compare our Monkey solutions to their equivalent Go solutions. Afterwards, we will explore and try to diagnose where all the CPU time is spent by performing CPU profiling.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/examples/AoC_2024/go_soln/soln.go" rel="noopener noreferrer"&gt;Go solution&lt;/a&gt; is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fileBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fileBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SplitSeq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;splitLine&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repeat&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="n"&gt;num1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Atoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;splitLine&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Atoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;splitLine&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

        &lt;span class="n"&gt;col1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;col2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Part1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;soln&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;soln&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;soln&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Part2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Could've done this with a frequency map, again, but let's keep the solution as close as possible to the Monkey version.&lt;/span&gt;
    &lt;span class="n"&gt;slices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;soln&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;col1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;soln&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;soln&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the Go solution doesn't compromise by avoiding for loops in favor of recursion to try and close the gap to the Monkey solution. The one adjustment I made is in &lt;code&gt;Part2&lt;/code&gt;, where I sort &lt;code&gt;col2&lt;/code&gt; and do a linear scan instead of using a hash map - this keeps the algorithm closer to Monkey's approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Raw time comparison: Monkey vs Go solutions
&lt;/h3&gt;

&lt;p&gt;You can check out the benchmark test file at &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/examples/AoC_2024/benchmark/bench_test.go" rel="noopener noreferrer"&gt;&lt;code&gt;bench_test.go&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using the 1000-line input file, we use the following command to run our benchmark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-benchtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-benchtime&lt;/code&gt; flag allows us to set the duration of the benchmark, how long each benchmark should run.&lt;/p&gt;

&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goos: linux
goarch: amd64
pkg: github.com/SegniAT/monkey-language-interpreter/examples/AoC_2024/benchmark
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkGoDay1Part1-8     12189     1032764 ns/op     121160 B/op     2029 allocs/op
BenchmarkGoDay1Part2-8      6582     1965238 ns/op     121160 B/op     2029 allocs/op
BenchmarkMonkeyDay1Part1-8   273     46781362 ns/op    19007745 B/op   55037 allocs/op
BenchmarkMonkeyDay1Part2-8     5     2317095265 ns/op  243211913 B/op  4325964 allocs/op
PASS
ok      github.com/SegniAT/monkey-language-interpreter/examples/AoC_2024/benchmark  49.902s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Reading the Benchmark Output
&lt;/h4&gt;

&lt;p&gt;Before diving into the profiling data, let's establish a baseline. Below is the raw output from running the benchmarks. Each row represents one benchmark, and the columns break down the performance per operation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Benchmark Name &amp;amp;&lt;/strong&gt; &lt;code&gt;GOMAXPROCS&lt;/code&gt;&lt;strong&gt;:&lt;/strong&gt; The name of the benchmark, followed by the number of CPUs available to the Go runtime (e.g., -8 means 8 logical CPUs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterations:&lt;/strong&gt; The number of times the benchmark loop ran during the test duration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ns/op&lt;/code&gt;&lt;strong&gt;:&lt;/strong&gt; The average execution time per operation in nanoseconds. This is the primary measure of speed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;B/op&lt;/code&gt;&lt;strong&gt;:&lt;/strong&gt; The average number of bytes allocated per operation. This measures memory pressure.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allocs/op&lt;/code&gt;: The average number of distinct memory allocations per operation. This is the key metric for understanding GC pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  A quick look at the numbers and code complexity
&lt;/h4&gt;

&lt;p&gt;The following is the comparison for bench time of 10 seconds. &lt;strong&gt;Part One:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stat&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;Monkey&lt;/th&gt;
&lt;th&gt;Diff&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ops&lt;/td&gt;
&lt;td&gt;12,189&lt;/td&gt;
&lt;td&gt;273&lt;/td&gt;
&lt;td&gt;x44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;duration/op&lt;/td&gt;
&lt;td&gt;1 ms&lt;/td&gt;
&lt;td&gt;46 ms&lt;/td&gt;
&lt;td&gt;x46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;allocation size/op&lt;/td&gt;
&lt;td&gt;118 KB&lt;/td&gt;
&lt;td&gt;18,562 KB&lt;/td&gt;
&lt;td&gt;x157&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;allocation count/op&lt;/td&gt;
&lt;td&gt;2,029&lt;/td&gt;
&lt;td&gt;55,037&lt;/td&gt;
&lt;td&gt;x27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part Two:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stat&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;Monkey&lt;/th&gt;
&lt;th&gt;Diff&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ops&lt;/td&gt;
&lt;td&gt;6,582&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;x1,316&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;duration/op&lt;/td&gt;
&lt;td&gt;1.9 ms&lt;/td&gt;
&lt;td&gt;2,317 ms&lt;/td&gt;
&lt;td&gt;x1,219&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;allocation size/op&lt;/td&gt;
&lt;td&gt;118 KB&lt;/td&gt;
&lt;td&gt;237,512 KB&lt;/td&gt;
&lt;td&gt;x2,013&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;allocation count/op&lt;/td&gt;
&lt;td&gt;2,029&lt;/td&gt;
&lt;td&gt;4,325,964&lt;/td&gt;
&lt;td&gt;x2,132&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What could be causing such a stark difference, especially in memory usage and allocations? Let's take a closer look at the Monkey solution and look for hints.&lt;/p&gt;

&lt;p&gt;Let's start with &lt;code&gt;prep&lt;/code&gt; function since it's common to both part one and two.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let content = trimSuffix(readFile("input.txt"), "\n");
let lines = splitString(content, "\n"); // array of strings

let prep = fn(i) {
    if (i==len(lines)){
        return {
            "colOne": [],
            "colTwo": [],
        };
    }

    let res = prep(i+1);

    let splitLine = splitString(lines[i], "   ");
    return {
        "colOne": push(res["colOne"], atoi(splitLine[0])),
        "colTwo":push(res["colTwo"], atoi(splitLine[1])),
    };
}

let res = prep(0);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function is called with an argument of &lt;code&gt;0&lt;/code&gt; and recursively calls itself until the base case is hit. At the base case we return a new hash literal and then the call stack unwinds, this is where all the work is done.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;splitString&lt;/code&gt; creates a new array for each line, in our case 1000 times. The function then returns a new hash literal with two keys where for each key a &lt;code&gt;push&lt;/code&gt; function is called, which is not cheap since it copies the array, time complexity of &lt;code&gt;O(n)&lt;/code&gt;. The &lt;code&gt;push&lt;/code&gt; function makes our &lt;code&gt;prep&lt;/code&gt; function have a time and space complexity of &lt;code&gt;O(n^2)&lt;/code&gt;, compared to &lt;code&gt;O(n)&lt;/code&gt; time complexity of the Go version. This is also the main suspect for the high memory allocation because of the &lt;code&gt;n&lt;/code&gt; (count of lines) number of new hash literal creations.&lt;/p&gt;

&lt;p&gt;After getting the two columns we do sorting in both cases, which has the time complexity of &lt;code&gt;O(nlog(n))&lt;/code&gt; leaving &lt;code&gt;prep&lt;/code&gt; with &lt;code&gt;O(n^2)&lt;/code&gt; to still be our bottleneck.&lt;/p&gt;

&lt;p&gt;Coming to the actual solutions, part one is a linear recursive function that only does math. The space complexity would also be linear because of the call stack. Part two on the other hand has &lt;code&gt;O(n^2)&lt;/code&gt; time and space complexity.&lt;/p&gt;

&lt;p&gt;We can get an idea of what the performance bottlenecks are from looking at the code alone and doing time and space complexity analysis. But we must do profiling to really know where all the CPU time is spent and what causes the massive memory allocations. Is our algorithm too slow and inefficient or is the Monkey interpreter itself too much of an overhead?&lt;/p&gt;

&lt;p&gt;We do CPU and Memory profiling in the following section. Our goal is to only investigate, not optimize the solutions or the Interpreter.&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU Profiling to know where Monkey is CHOKING
&lt;/h3&gt;

&lt;p&gt;The benchmark function we use to profile our solution lives in at &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/examples/AoC_2024/benchmark/bench_test.go" rel="noopener noreferrer"&gt;&lt;code&gt;bench_test.go&lt;/code&gt;&lt;/a&gt;. We'll only benchmark the Monkey solution to part two since it's the most glaring when it comes to raw execution time and memory allocation numbers.&lt;/p&gt;

&lt;p&gt;We will use the following command to produce a CPU profile file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;BenchmarkMonkeyDay1Part2 &lt;span class="nt"&gt;-benchtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10s &lt;span class="nt"&gt;-cpuprofile&lt;/span&gt; /tmp/cpu.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs the following and creates a CPU profile file at &lt;code&gt;/tmp/cpu.out&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goos: linux
goarch: amd64
pkg: github.com/SegniAT/monkey-language-interpreter/examples/AoC_2024/benchmark
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkMonkeyDay1Part2-8   5   2548156523 ns/op   243326184 B/op   4325995 allocs/op
PASS
ok      github.com/SegniAT/monkey-language-interpreter/examples/AoC_2024/benchmark      13.110s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use the following command to see the results in our browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go tool pprof &lt;span class="nt"&gt;-http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;:8080 /tmp/cpu.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what the Flame Graph looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbkunp33lvi3f3vlllz8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbkunp33lvi3f3vlllz8.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;X-axis:&lt;/strong&gt; the total sample count (which represents CPU time). The width of a block is proportional to how much CPU time it consumed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Y-axis:&lt;/strong&gt; the call stack (depth).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As expected most of our time is spent evaluating the source code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;evaluator.Eval&lt;/code&gt;: 7.33 s, &lt;code&gt;51.4%&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;evaluator.applyFunction&lt;/code&gt;: 2.54 s, &lt;code&gt;17.8%&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;evaluator.evalBlockStatement&lt;/code&gt;: 2.03 s, &lt;code&gt;14.2%&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But these only account for &lt;code&gt;83.4%&lt;/code&gt; of the time spent, where is the rest?&lt;/p&gt;

&lt;p&gt;The rest of the time is when the GC runs in a separate goroutine, functions such as &lt;code&gt;runtime.gcBgMarkWorker&lt;/code&gt;, &lt;code&gt;runtime.bgsweep&lt;/code&gt; etc. We're using up so much memory that &lt;code&gt;20%&lt;/code&gt; of our program's run time is the GC doing work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The CPU profiler is a sampling tool that runs for the duration of the benchmark and captures all code that executes during that window, including setup. Our &lt;code&gt;b.ResetTimer()&lt;/code&gt; only affects the &lt;code&gt;ns/op&lt;/code&gt; and &lt;code&gt;allocs/op&lt;/code&gt; numbers (as we'll see in the Heap Profiling), not the profile's sampling window.&lt;/p&gt;

&lt;p&gt;Now let's take a look at the Flat views of the functions/nodes called.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The Flat View ("Where is the CPU?")
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Flat%&lt;/strong&gt; is the percentage of total CPU time spent directly inside the function itself.&lt;/p&gt;

&lt;p&gt;Command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Show only the top 20 functions sorted by flat time&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;go tool pprof &lt;span class="nt"&gt;-top&lt;/span&gt; &lt;span class="nt"&gt;-flat&lt;/span&gt; &lt;span class="nt"&gt;-nodecount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20 /tmp/cpu.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File: benchmark.test
Build ID: f8e079acc9de3bb17d615a24edff6fbc81f4736f
Type: cpu
Time: 2026-10-08 11:19:36 EAT
Duration: 12.75s, Total samples = 14270ms (111.93%)
Showing nodes accounting for 8460ms, 59.29% of 14270ms total
Dropped 201 nodes (cum &amp;lt;= 71.35ms)
Showing top 20 nodes out of 129
      flat  flat%   sum%        cum   cum%
    2090ms 14.65% 14.65%    11910ms 83.46%  github.com/SegniAT/monkey-language-interpreter/evaluator.Eval
     870ms  6.10% 20.74%      870ms  6.10%  aeshashbody
     740ms  5.19% 25.93%     1810ms 12.68%  internal/runtime/maps.(*Map).getWithoutKeySmallFastStr
     450ms  3.15% 29.08%     2850ms 19.97%  github.com/SegniAT/monkey-language-interpreter/object.(*Environment).Get
     450ms  3.15% 32.24%     2370ms 16.61%  runtime.mapaccess2_faststr
     360ms  2.52% 34.76%      360ms  2.52%  runtime.nextFreeFast (inline)
     330ms  2.31% 37.07%      360ms  2.52%  runtime.tryDeferToSpanScan
     310ms  2.17% 39.24%     1810ms 12.68%  runtime.mallocgcSmallScanNoHeader
     300ms  2.10% 41.35%    11900ms 83.39%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalBlockStatement
     290ms  2.03% 43.38%     2920ms 20.46%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalExpressions
     290ms  2.03% 45.41%     3310ms 23.20%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalIdentifier
     280ms  1.96% 47.37%      280ms  1.96%  runtime.memclrNoHeapPointers
     230ms  1.61% 48.98%      550ms  3.85%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalInfixExpression
     230ms  1.61% 50.60%      230ms  1.61%  runtime.memmove
     220ms  1.54% 52.14%      220ms  1.54%  runtime.procyieldAsm
     210ms  1.47% 53.61%      390ms  2.73%  github.com/SegniAT/monkey-language-interpreter/evaluator.isError (inline)
     210ms  1.47% 55.08%      210ms  1.47%  memeqbody
     210ms  1.47% 56.55%      270ms  1.89%  runtime.(*mspan).writeHeapBitsSmall
     210ms  1.47% 58.02%     2850ms 19.97%  runtime.mallocgc
     180ms  1.26% 59.29%      290ms  2.03%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalIntegerInfixExpression
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the top of the Flat output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;14.65%&lt;/code&gt; : &lt;code&gt;github.com/SegniAT/monkey-language-interpreter/evaluator.Eval&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;6.10%&lt;/code&gt; : &lt;code&gt;aeshashbody&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5.19%&lt;/code&gt; : &lt;code&gt;internal/runtime/maps.(*Map).getWithoutKeySmallFastStr&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3.15%&lt;/code&gt; : &lt;code&gt;github.com/SegniAT/monkey-language-interpreter/object.(*Environment).Get&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3.15%&lt;/code&gt; : &lt;code&gt;runtime.mapaccess2_faststr&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The single biggest cost is the &lt;code&gt;evaluator.Eval&lt;/code&gt; function itself, this is expected since we are bound to have a very high count of recursive calls to it and we spend considerable time inside that function even though it involves just a big switch statement.&lt;/p&gt;

&lt;p&gt;But if we look closely at the next four entries, they are all related to map look-ups.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aeshashbody&lt;/code&gt; is the CPU instruction Go uses to hash strings for map keys. It is eating &lt;code&gt;6.1%&lt;/code&gt; of our entire CPU time. The reason is the Monkey interpreter resolves variables by looking up strings (like &lt;code&gt;i&lt;/code&gt;, &lt;code&gt;lines&lt;/code&gt;, &lt;code&gt;colOne&lt;/code&gt;) in a &lt;code&gt;map[string]object.Object&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then, look at the memory functions: &lt;code&gt;nextFreeFast&lt;/code&gt;, &lt;code&gt;mallocgcSmallScanNoHeader&lt;/code&gt;, &lt;code&gt;memclrNoHeapPointers&lt;/code&gt;, &lt;code&gt;memmove&lt;/code&gt;. They collectively account for another &lt;code&gt;~7%&lt;/code&gt; of flat CPU time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CPU is spending a lot of time hashing strings for variable look-ups and frantically moving memory around for allocations.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. The Cumulative View ("Why is it slow?")
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Cum%&lt;/strong&gt; is the percentage of total CPU time spent in the function plus all functions it called.&lt;/p&gt;

&lt;p&gt;Command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Show only the top 20 functions sorted by cumulative time&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;go tool pprof &lt;span class="nt"&gt;-top&lt;/span&gt; &lt;span class="nt"&gt;-cum&lt;/span&gt; &lt;span class="nt"&gt;-nodecount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20 /tmp/cpu.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File: benchmark.test
Build ID: f8e079acc9de3bb17d615a24edff6fbc81f4736f
Type: cpu
Time: 2026-10-08 11:19:36 EAT
Duration: 12.75s, Total samples = 14.27s (111.93%)
Showing nodes accounting for 5.84s, 40.93% of 14.27s total
Dropped 201 nodes (cum &amp;lt;= 0.07s)
Showing top 20 nodes out of 129
      flat  flat%   sum%        cum   cum%
     2.09s 14.65% 14.65%     11.91s 83.46%  github.com/SegniAT/monkey-language-interpreter/evaluator.Eval
     0.09s  0.63% 15.28%     11.91
s 83.46%  github.com/SegniAT/monkey-language-interpreter/evaluator.applyFunction
     0.30s  2.10% 17.38%     11.90s 83.39%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalBlockStatement
     0.14s  0.98% 18.36%      6.02s 42.19%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalIfExpression
     0.29s  2.03% 20.39%      3.31s 23.20%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalIdentifier
     0.29s  2.03% 22.42%      2.92s 20.46%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalExpressions
     0.45s  3.15% 25.58%      2.85s 19.97%  github.com/SegniAT/monkey-language-interpreter/object.(*Environment).Get
     0.21s  1.47% 27.05%      2.85s 19.97%  runtime.mallocgc
         0     0% 27.05%      2.79s 19.55%  runtime.systemstack
     0.45s  3.15% 30.20%      2.37s 16.61%  runtime.mapaccess2_faststr
     0.11s  0.77% 30.97%      2.07s 14.51%  github.com/SegniAT/monkey-language-interpreter/evaluator.extendFunctionEnv (inline)
     0.74s  5.19% 36.16%      1.81s 12.68%  internal/runtime/maps.(*Map).getWithoutKeySmallFastStr
     0.31s  2.17% 38.33%      1.81s 12.68%  runtime.mallocgcSmallScanNoHeader
         0     0% 38.33%      1.75s 12.26%  runtime.gcBgMarkWorker
         0     0% 38.33%      1.67s 11.70%  runtime.gcBgMarkWorker.func2
     0.03s  0.21% 38.54%      1.67s 11.70%  runtime.gcDrain
     0.09s  0.63% 39.17%      1.49s 10.44%  runtime.newobject
     0.09s  0.63% 39.80%      1.47s 10.30%  github.com/SegniAT/monkey-language-interpreter/object.(*Environment).Set (inline)
     0.16s  1.12% 40.93%      1.37s  9.60%  runtime.mapassign_faststr
         0     0% 40.93%      0.93s  6.52%  runtime.gcDrainMarkWorkerDedicated (inline)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at these three numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;83.46%&lt;/code&gt; - &lt;code&gt;evaluator.Eval&lt;/code&gt;: This is expected since the entire program runs inside this recursive function.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;42.19%&lt;/code&gt; - &lt;code&gt;evaluator.evalIfExpression&lt;/code&gt;: This is pretty huge. Nearly half of your interpreter's total execution time is spent evaluating if expressions. In Monkey, &lt;code&gt;if&lt;/code&gt; is used for base cases and control flow. Every recursive call (&lt;code&gt;prep(i+1)&lt;/code&gt;, &lt;code&gt;partTwo(i+1)&lt;/code&gt;, &lt;code&gt;countNumInArray(...)&lt;/code&gt;) hits an if check. Millions of conditional jumps are being evaluated as AST nodes rather than machine instructions like in compiled languages such as Go.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;23.20%&lt;/code&gt; - &lt;code&gt;evaluator.evalIdentifier&lt;/code&gt;: Almost a quarter of all time is spent just finding variables. This calls &lt;code&gt;Environment.Get&lt;/code&gt; &lt;code&gt;19.97%&lt;/code&gt;), which does a map lookup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look at &lt;code&gt;runtime.mallocgc&lt;/code&gt; &lt;code&gt;19.97%&lt;/code&gt;) and &lt;code&gt;runtime.gcBgMarkWorker&lt;/code&gt; &lt;code&gt;12.26%&lt;/code&gt;). GC background workers alone account for &lt;code&gt;~20%&lt;/code&gt;; adding the allocator brings total memory-management CPU to &lt;code&gt;~32%&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Where are all the allocations and garbage coming from?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;extendFunctionEnv&lt;/code&gt; &lt;code&gt;14.51%&lt;/code&gt;): Every function call creates a new enclosed environment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;object.(*Environment).Set&lt;/code&gt; &lt;code&gt;10.30%&lt;/code&gt;): Every variable declaration or assignment updates a map.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NewEnclosedEnvironment&lt;/code&gt;: Creating a map for every single recursive call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interpreter is spending &lt;code&gt;32%&lt;/code&gt; of its time cleaning up the memory it allocated. This is because &lt;strong&gt;every recursive call creates a brand new environment map&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heap Profiling for confirmation
&lt;/h3&gt;

&lt;p&gt;Just from CPU Profiling above we were able to figure out that the GC is doing a lot of work and we were able to deduce the exact reason there's so much garbage for the GC to collect: mainly creating a new environment on each function call. Now let's confirm this with Heap Profiling.&lt;/p&gt;

&lt;p&gt;We use the &lt;code&gt;-memprofile&lt;/code&gt; flag to create the Heap Profile file.&lt;/p&gt;

&lt;p&gt;Command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go test -bench=BenchmarkMonkeyDay1Part2 -benchtime=10s -memprofile /tmp/mem.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goos: linux
goarch: amd64
pkg: github.com/SegniAT/monkey-language-interpreter/examples/AoC_2024/benchmark
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkMonkeyDay1Part2-8  4  2540705147 ns/op  243217254 B/op   4325988 allocs/op
PASS
ok      github.com/SegniAT/monkey-language-interpreter/examples/AoC_2024/benchmark      10.508s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use the following command to see the results in our browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go tool pprof &lt;span class="nt"&gt;-http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;:8080 /tmp/mem.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can take a look at the Flame Graph with sampling set to &lt;code&gt;alloc_objects&lt;/code&gt; under &lt;code&gt;SAMPLE&lt;/code&gt; since the GC deals with objects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr9nh6dtt2dlqplu5g7ft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr9nh6dtt2dlqplu5g7ft.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;X-axis:&lt;/strong&gt; the total allocated objects. The width of a block is proportional to how many objects are allocated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Y-axis:&lt;/strong&gt; the call stack (depth).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following are the functions that account for &lt;code&gt;~99%&lt;/code&gt; of the object allocations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;84.4%&lt;/code&gt; - &lt;code&gt;evaluator.Eval&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;7.7%&lt;/code&gt; - &lt;code&gt;evaluator.evalBlockStatement&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;6.5%&lt;/code&gt; - &lt;code&gt;evaluator.applyFunction&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are expected, let's scroll down and take a look at the rest of the graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4df9vq58t2qjfuomfpzm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4df9vq58t2qjfuomfpzm.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The suspects from the CPU profile - &lt;code&gt;NewEnclosedEnvironment&lt;/code&gt; and &lt;code&gt;Environment.Set&lt;/code&gt; - are back at the top of the alloc list. Let's see their numbers more closely below.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The Flat View (The Smoking Guns)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Flat%&lt;/strong&gt; tells us who is doing the allocating directly. This points the finger at the exact function and line of code. This is our "smoking gun."&lt;/p&gt;

&lt;p&gt;Command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go tool pprof &lt;span class="nt"&gt;-top&lt;/span&gt; &lt;span class="nt"&gt;-flat&lt;/span&gt; &lt;span class="nt"&gt;-sample_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;alloc_objects &lt;span class="nt"&gt;-nodecount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20 /tmp/mem.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File: benchmark.test
Build ID: f8e079acc9de3bb17d615a24edff6fbc81f4736f
Type: alloc_objects
Time: 2026-10-08 14:27:14 EAT
Showing nodes accounting for 15357595, 99.41% of 15448453 total
Dropped 29 nodes (cum &amp;lt;= 77242)
      flat  flat%   sum%        cum   cum%
   4150735 26.87% 26.87%    4150735 26.87%  github.com/SegniAT/monkey-language-interpreter/object.NewEnvironment (inline)
   4104337 26.57% 53.44%    6267057 40.57%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalExpressions
   2981932 19.30% 72.74%   15443004   100%  github.com/SegniAT/monkey-language-interpreter/evaluator.Eval
   1922917 12.45% 85.19%    1922917 12.45%  github.com/SegniAT/monkey-language-interpreter/object.(*Environment).Set (inline)
   1114129  7.21% 92.40%    1114129  7.21%  github.com/SegniAT/monkey-language-interpreter/evaluator.init.func1
   1081360  7.00% 99.40%    1081360  7.00%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalIntegerInfixExpression
      2185 0.014% 99.41%     136747  0.89%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalHashLiteral
         0     0% 99.41%   15443004   100%  github.com/SegniAT/monkey-language-interpreter/evaluator.applyFunction
         0     0% 99.41%   15443004   100%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalBlockStatement
         0     0% 99.41%    3309618 21.42%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalIfExpression
         0     0% 99.41%    1081360  7.00%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalInfixExpression
         0     0% 99.41%    6073652 39.32%  github.com/SegniAT/monkey-language-interpreter/evaluator.extendFunctionEnv (inline)
         0     0% 99.41%    4150735 26.87%  github.com/SegniAT/monkey-language-interpreter/object.NewEnclosedEnvironment (inline)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sorting by &lt;strong&gt;flat%&lt;/strong&gt; shows the top functions physically calling &lt;code&gt;mallocgc&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;26.87%&lt;/code&gt; - &lt;code&gt;object.NewEnvironment&lt;/code&gt;: 4.15 million objects!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;26.57%&lt;/code&gt; - &lt;code&gt;evalExpressions&lt;/code&gt;: 4.10 million objects!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;12.45%&lt;/code&gt; - &lt;code&gt;Environment.Set&lt;/code&gt;: 1.92 million objects!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;7.21%&lt;/code&gt; - &lt;code&gt;init.func1&lt;/code&gt;: 1.11 million objects!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;7.00%&lt;/code&gt; - &lt;code&gt;evalIntegerInfixExpression&lt;/code&gt;: 1.08 million objects!&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NewEnvironment&lt;/code&gt; is creating a new map for every single recursive function call. Because Part 2 has an &lt;code&gt;O(n^2)&lt;/code&gt; algorithm, it calls functions millions of times creating millions of maps.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;evalExpressions&lt;/code&gt; is allocating a slice every time it evaluates a list of arguments (like function arguments or array literal elements).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Environment.Set&lt;/code&gt; is allocating an object every time a variable is declared (like &lt;code&gt;let res = ...&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;init.func1&lt;/code&gt; is a builtin function - almost certainly Monkey's &lt;code&gt;push&lt;/code&gt; function allocating a new array on every single call. It's named &lt;code&gt;func1&lt;/code&gt; since it's an anonymous closure. It's under &lt;code&gt;init&lt;/code&gt; since we defined &lt;code&gt;builtins&lt;/code&gt; map globally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. The Cumulative View (The General Trend)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Cum%&lt;/strong&gt; tells us the total cost of a subsystem. It shows you how individual allocations roll up into a massive total. This is the "macro trend."&lt;/p&gt;

&lt;p&gt;Command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go tool pprof &lt;span class="nt"&gt;-top&lt;/span&gt; &lt;span class="nt"&gt;-cum&lt;/span&gt; &lt;span class="nt"&gt;-sample_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;alloc_objects &lt;span class="nt"&gt;-nodecount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20 /tmp/mem.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File: benchmark.test
Build ID: f8e079acc9de3bb17d615a24edff6fbc81f4736f
Type: alloc_objects
Time: 2026-10-08 14:27:14 EAT
Showing nodes accounting for 15357595, 99.41% of 15448453 total
Dropped 29 nodes (cum &amp;lt;= 77242)
      flat  flat%   sum%        cum   cum%
   2981932 19.30% 19.30%   15443004   100%  github.com/SegniAT/monkey-language-interpreter/evaluator.Eval
         0     0% 19.30%   15443004   100%  github.com/SegniAT/monkey-language-interpreter/evaluator.applyFunction
         0     0% 19.30%   15443004   100%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalBlockStatement
   4104337 26.57% 45.87%    6267057 40.57%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalExpressions
         0     0% 45.87%    6073652 39.32%  github.com/SegniAT/monkey-language-interpreter/evaluator.extendFunctionEnv (inline)
         0     0% 45.87%    4150735 26.87%  github.com/SegniAT/monkey-language-interpreter/object.NewEnclosedEnvironment (inline)
   4150735 26.87% 72.74%    4150735 26.87%  github.com/SegniAT/monkey-language-interpreter/object.NewEnvironment (inline)
         0     0% 72.74%    3309618 21.42%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalIfExpression
   1922917 12.45% 85.19%    1922917 12.45%  github.com/SegniAT/monkey-language-interpreter/object.(*Environment).Set (inline)
   1114129  7.21% 92.40%    1114129  7.21%  github.com/SegniAT/monkey-language-interpreter/evaluator.init.func1
         0     0% 92.40%    1081360  7.00%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalInfixExpression
   1081360  7.00% 99.40%    1081360  7.00%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalIntegerInfixExpression
      2185 0.014% 99.41%     136747  0.89%  github.com/SegniAT/monkey-language-interpreter/evaluator.evalHashLiteral
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sorting by &lt;strong&gt;cum%&lt;/strong&gt; shows the subsystems responsible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;40.57%&lt;/code&gt; - &lt;code&gt;evalExpressions&lt;/code&gt;: 6.26 million objects!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;39.32%&lt;/code&gt; - &lt;code&gt;extendFunctionEnv&lt;/code&gt;: 6.07 million objects!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;26.87%&lt;/code&gt; - &lt;code&gt;NewEnclosedEnvironment&lt;/code&gt;: 4.15 million objects!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;21.42%&lt;/code&gt; - &lt;code&gt;evalIfExpression&lt;/code&gt;: 3.30 million objects!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nearly &lt;code&gt;40%&lt;/code&gt; of all object allocations in the entire program come from one place: &lt;code&gt;extendFunctionEnv&lt;/code&gt;. This is the function that sets up the environment for a new function call. Because our Part 2 algorithm is heavily recursive, this function is called millions of times and every call creates a new environment map.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnosis: Interpreter Overhead vs. Language Design
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Interpreter overhead:&lt;/strong&gt; &lt;code&gt;~83%&lt;/code&gt; of time is spent in &lt;code&gt;Eval&lt;/code&gt; + &lt;code&gt;applyFunction&lt;/code&gt; + &lt;code&gt;evalBlockStatement&lt;/code&gt;. That's the cost of the AST tree-walking nature of the interpreter itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment churn:&lt;/strong&gt; &lt;code&gt;~39%&lt;/code&gt; of all allocations come from &lt;code&gt;extendFunctionEnv&lt;/code&gt;. That's a design choice of the interpreter: creating a fresh &lt;code&gt;Environment&lt;/code&gt; per call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Language design:&lt;/strong&gt; The lack of loops forces &lt;code&gt;O(n)&lt;/code&gt; recursion depth, which means &lt;code&gt;O(n)&lt;/code&gt; environments. The lack of mutable arrays turns &lt;code&gt;push&lt;/code&gt; into &lt;code&gt;O(n)&lt;/code&gt; copying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; It's both. The CPU profile shows the interpreter's tree-walking overhead dominates execution time (&lt;code&gt;~83%&lt;/code&gt; in &lt;code&gt;Eval&lt;/code&gt;). But the memory gap is a different story - the &lt;code&gt;2000x&lt;/code&gt; difference is driven by the language's design (no loops, immutable arrays) being amplified by the &lt;code&gt;O(n^2)&lt;/code&gt; algorithm. The interpreter overhead is the cost of running any Monkey program; the language design is what makes this specific program catastrophic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Even though we had to make a few improvements to the interpreter, like adding builtin functions which could be considered cheating, and fixing tricky bugs we were able to achieve our goal of solving an AoC problem with Monkey. We were able to navigate Monkey's limitations such as missing loop primitives and variable re-assignments to solve the two-part problem.&lt;/p&gt;

&lt;p&gt;We were able to accomplish our initial goal but there's a noticable performance problem we had discovered. Our Monkey solutions were &lt;code&gt;46x&lt;/code&gt; and &lt;code&gt;1,219x&lt;/code&gt; slower compared to their Go counterpart for part one and two of the AoC problem respectively.&lt;/p&gt;

&lt;p&gt;To know the exact reason why there's such big contrast between the two languages, we had to do CPU and Heap profiling. The story these profiling results told were that there's a huge memory/heap overhead (lots of allocations) and the CPU spends a lot of time allocating and cleaning up all the garbage. Due to the nature of our interpreter (AST tree-walker) and lack of loop primitives forcing us to resort to recursive Monkey function calls, it led to deep call stacks and millions of new environment creations per function call.&lt;/p&gt;

&lt;p&gt;Adding native builtins is how every production interpreter handles I/O and performance-critical paths - it's not cheating, it's the standard extension mechanism. But it does mean part of the answer comes from Go, not from Monkey.&lt;/p&gt;

&lt;p&gt;Monkey is barely capable on its own even for simple AoC problems let alone real problems. Yet it was more capable than I thought when I began this series creating a Tree-sitter highlighting and a Language Server after that. Capability is one thing but programs running at a reasonable efficiency is another, and Monkey is far from efficient.&lt;/p&gt;

&lt;p&gt;This shouldn't stop us from sitting back and appreciating what we have built. To conclude this 3-part series, I'll leave you with a quote from a wise king:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The end of a thing is better than its beginning; The patient in spirit is better than the proud in spirit. Ecclesiastes 7:8&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>adventofcode</category>
      <category>performance</category>
    </item>
    <item>
      <title>Building a Development Environment for Monkey: Part 2 - An LSP for Hover, Definition, Completion and Diagnostics</title>
      <dc:creator>Segni Adeba</dc:creator>
      <pubDate>Sat, 03 Oct 2026 18:04:58 +0000</pubDate>
      <link>https://dev.to/segniadebagodsson/building-a-development-environment-for-monkey-part-2-an-lsp-for-hover-definition-completion-3edp</link>
      <guid>https://dev.to/segniadebagodsson/building-a-development-environment-for-monkey-part-2-an-lsp-for-hover-definition-completion-3edp</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In the previous &lt;a href="https://segni.hashnode.dev/building-a-development-environment-for-monkey-part-1-syntax-highlighting-with-tree-sitter" rel="noopener noreferrer"&gt;blog&lt;/a&gt;, the first entry of the series, we taught editors what Monkey &lt;em&gt;looks like&lt;/em&gt; by adding syntax highlighting to Neovim using Tree-sitter. Syntax highlighting makes our source-code pretty but it only goes so far.&lt;/p&gt;

&lt;p&gt;The next planned step in building the development environment is teaching editors what Monkey &lt;em&gt;means&lt;/em&gt;, by introducing diagnostics, hover, go-to-definition and completion. That layer is the LSP.&lt;/p&gt;

&lt;p&gt;This blog covers the actual steps I took to build an extremely basic but extremely helpful version. It includes the walls I hit and how I had to go and extend the already existing interpreter to help me implement LSP features.&lt;/p&gt;

&lt;p&gt;You can find the full docs for Monkey and full LSP implementation in the &lt;a href="https://github.com/SegniAT/monkey-language-interpreter" rel="noopener noreferrer"&gt;Monkey repository&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the LSP actually is (and what we're building)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Language Server&lt;/strong&gt; is a server that provides language-specific functions that can communicate with development tooling, for example editors, over a protocol that enables inter-process communication. &lt;strong&gt;LSP (Language Server Protocol)&lt;/strong&gt; on the other hand standardizes the way editors communicate with Language Servers.&lt;/p&gt;

&lt;p&gt;Thanks to this protocol, we know exactly what editors send to Language Servers and the responses they expect to receive back.&lt;/p&gt;

&lt;p&gt;The messages the editor and server exchange looks like the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Length: ...\r\n
\r\n
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "textDocument/completion",
    "params": {
        ...
    }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All messages have two parts, separated by the &lt;code&gt;\r\n&lt;/code&gt; separator:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Headers&lt;/strong&gt;: consists header fields separated by &lt;code&gt;\r\n&lt;/code&gt;. Currently &lt;code&gt;Content-Length&lt;/code&gt; and &lt;code&gt;Content-Type&lt;/code&gt; headers are supported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content&lt;/strong&gt;: is the main body of the message which is just &lt;code&gt;json&lt;/code&gt;, specifically &lt;a href="https://www.jsonrpc.org/specification" rel="noopener noreferrer"&gt;JSON-RPC 2.0&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The messages that the Language Server receives from the editors can be of two types: &lt;em&gt;request&lt;/em&gt; and &lt;em&gt;notification&lt;/em&gt;. We can pick and choose what to handle and what to ignore. Some of them are absolutely necessary to handle.&lt;/p&gt;

&lt;p&gt;But before we talk about the specifics of the messages, let's actually read them first. That is handled by the &lt;code&gt;rpc&lt;/code&gt; package.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 - The RPC layer
&lt;/h2&gt;

&lt;p&gt;The RPC layer deals with reading and writing LSP messages from and to the LS clients, often editors. The LSP communication mostly happens using the &lt;code&gt;stdin&lt;/code&gt; and &lt;code&gt;stdout&lt;/code&gt; files, but not limited to it.&lt;/p&gt;

&lt;p&gt;Because of this, we will abstract functions using &lt;code&gt;io.Reader&lt;/code&gt; and &lt;code&gt;io.Writer&lt;/code&gt; interfaces from the standard lib. This will be very handy when it comes to unit testing as well. This practice is mentioned in &lt;a href="https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them" rel="noopener noreferrer"&gt;100 Go Mistakes and How to Avoid Them&lt;/a&gt; book under mistake #46, "Using a filename as a function input".&lt;/p&gt;

&lt;p&gt;The writer half of it is pretty simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;accept an argument of content which is a byte array (presumably JSON)&lt;/li&gt;
&lt;li&gt;frame it with the header section of the message that includes &lt;code&gt;Content-Length&lt;/code&gt; (the length of the byte array argument)&lt;/li&gt;
&lt;li&gt;write the whole message to our writer interface&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When it comes to reading, we can't just do &lt;code&gt;json.Decode(io.Reader)&lt;/code&gt;, an LSP message is not just JSON. It's framed as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Length: 42\r\n
\r\n
&amp;lt;42 bytes of JSON&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  First attempt: &lt;code&gt;bufio.Scanner&lt;/code&gt; with a custom &lt;code&gt;SplitFunc&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://youtu.be/YsdlcQoHqPY" rel="noopener noreferrer"&gt;Learn By Building: Language Server Protocol&lt;/a&gt;, the first attempt uses &lt;code&gt;bufio.Scanner&lt;/code&gt; and &lt;code&gt;SplitFunc&lt;/code&gt; to neatly read each of our messages from the input as tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;scanner&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advance&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sc"&gt;'\r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'\r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Content-Length: &amp;lt;number&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;contentLengthBytes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Length: "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;contentLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Atoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentLengthBytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;contentLength&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;totalLength&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;contentLength&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totalLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;totalLength&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation reads the input until a full message has arrived, which is decided by the &lt;code&gt;Split&lt;/code&gt; function, which advances depending on if the full message has been received.&lt;/p&gt;

&lt;p&gt;This worked fine - until it didn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;bufio.Scanner&lt;/code&gt; has a default token size limit of &lt;code&gt;64 KiB&lt;/code&gt;. Since an entire LSP message is one "token" under our &lt;code&gt;SplitFunc&lt;/code&gt;, a large message (say, a big &lt;code&gt;didOpen&lt;/code&gt; with a huge file) would blow up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bufio.Scanner: token too long
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shortcoming is mentioned in &lt;code&gt;go doc bufio.scanner&lt;/code&gt; as follows, it even suggests a solution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scanning stops unrecoverably at EOF, the first I/O error, or a token too large to fit in the Scanner.Buffer. When a scan stops, the reader may have advanced arbitrarily far past the last token. Programs that need more control over error handling or large tokens, or must run sequential scans on a reader, should use &lt;code&gt;bufio.Reader&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is possible to change the token size limit and the initial buffer size that are set by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;scanner&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// initial buffer&lt;/span&gt;
    &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c"&gt;// maximum token size: 1 MiB&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem with this approach is that we're just pulling those numbers out of nowhere. Let's move on to the solution suggested in the Go docs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution: &lt;code&gt;bufio.Reader&lt;/code&gt; + &lt;code&gt;io.ReadFull&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This solution allows us to first read the headers line by line using &lt;code&gt;bufio.Reader&lt;/code&gt;, which in-turn allows us to handle errors at a more granular level. After reading all the headers, we parse out our &lt;code&gt;Content-Length&lt;/code&gt;, then allocate a buffer with size that number and use &lt;code&gt;io.ReadFull&lt;/code&gt; to read the content part of the message until the buffer is full or an error occurs, then we return the content that we just read.&lt;/p&gt;

&lt;p&gt;The following is the implementation.&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/rpc/reader.go#L22" rel="noopener noreferrer"&gt;&lt;code&gt;rpc/reader.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Reader&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ReadMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;contentLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readHeaders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contentLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrUnexpectedEOF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrIncompleteMessage&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;readHeaders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Omitted for brevity, this function reads from the input line-by-line with '\n' as the delimiter, handling reading all the headers and handling errors in a more granular way, check source.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The folks at Microsoft implemented their own version in &lt;a href="https://github.com/microsoft/typescript-go/blob/main/internal/jsonrpc/baseproto.go" rel="noopener noreferrer"&gt;typescript-go&lt;/a&gt; in a very similar way to this.&lt;/p&gt;

&lt;p&gt;We now have a way of reading and writing LSP messages, but we don't exactly know what they look like, when we receive them, when or what we should respond with.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 - Protocol types (the boring part) and handlers
&lt;/h2&gt;

&lt;p&gt;The next work is basically converting Typescript types specified in the LSP spec into Go structs in the &lt;code&gt;protocol&lt;/code&gt; package. LSP is a big spec, so we will only include the minimum required types to achieve our goals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go's type system
&lt;/h3&gt;

&lt;p&gt;One of the more frustrating parts of implementing LSP in Go is that the language's type system doesn't always map neatly onto the shape of the specification. LSP makes extensive use of inheritance-like relationships, optional fields, and union types using TypeScript, while Go has no direct equivalent of TypeScript's &lt;code&gt;extends&lt;/code&gt; or tagged unions. Embedding and interfaces can approximate some of these relationships, but they don't always express the same structure.&lt;/p&gt;

&lt;p&gt;For example, my base request type contains only the fields common to every request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Message&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;     &lt;span class="kt"&gt;int64&lt;/span&gt;  &lt;span class="s"&gt;`json:"id"`&lt;/span&gt;
    &lt;span class="n"&gt;Method&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"method"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HoverRequest&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Request&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt; &lt;span class="n"&gt;HoverParams&lt;/span&gt; &lt;span class="s"&gt;`json:"params"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that &lt;code&gt;Request&lt;/code&gt; isn't really a complete LSP request on its own; its &lt;code&gt;params&lt;/code&gt; depend on which concrete request it represents. It doesn't even signal that there should be one more field named &lt;code&gt;params&lt;/code&gt;. In TypeScript, this relationship can be modeled more naturally through interfaces and extension. In Go, it becomes a collection of concrete types built around an incomplete base type, with the connection between them handled by the implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lifecycle message Initialize and server capabilities
&lt;/h3&gt;

&lt;p&gt;We start by defining &lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#requestMessage" rel="noopener noreferrer"&gt;Request Message&lt;/a&gt;, &lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#responseMessage" rel="noopener noreferrer"&gt;Response Message&lt;/a&gt; and &lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#notificationMessage" rel="noopener noreferrer"&gt;Notification Message&lt;/a&gt; types as structs. These comprise all types of messages exchanged between the client and the server, every other types extend these.&lt;/p&gt;

&lt;p&gt;The lifecycle messages, as mentioned in the &lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#lifeCycleMessages-side" rel="noopener noreferrer"&gt;spec&lt;/a&gt;, begin with a request of method &lt;code&gt;Initialize&lt;/code&gt; sent from the client to the server.&lt;/p&gt;

&lt;p&gt;This is the very first message that we expect to receive and it includes a &lt;code&gt;capabilities&lt;/code&gt; field which includes details of if and how the client supports features such as hover, completion etc. We are expected to return a response including the server information and also the &lt;code&gt;InitializeResult&lt;/code&gt; type, which includes its very own &lt;code&gt;capabilities&lt;/code&gt;, i.e capabilities the server supports or implements. In our case we return the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"textDocumentSync"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"hoverProvider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"definitionProvider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"completionProvider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"serverInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"monkey-lsp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With our response, we tell our client that we support the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Document sync&lt;/strong&gt; of type 1 (full document sync). This means that the client will send the full text document each time there's a change. Type 1 may seem inefficient compared to type 2  (incremental document sync) but let's save that more complicated task for future me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hover provider&lt;/strong&gt;: &lt;code&gt;true&lt;/code&gt;, this tells the client that we can handle a hover request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Definition provider&lt;/strong&gt;: &lt;code&gt;true&lt;/code&gt;, to tell the client that we can handle a definition request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completion provider&lt;/strong&gt; is set to an empty object because it has some optional configurations that we could set inside, which we won't bother with for now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server info&lt;/strong&gt; includes the name and the version of our Language Server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The client doesn't ask the server for something that the server has not specified in its capabilities. So, the client won't bombard our server with all types of requests.&lt;/p&gt;

&lt;p&gt;The rest is mechanical translation. We now know the &lt;strong&gt;requests&lt;/strong&gt; that we can expect from the client thanks to the server capabilities so we can define the request and response Go types for them by referring to the LSP spec. All the types are specified in the &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/tree/main/lsp/lsp/protocol" rel="noopener noreferrer"&gt;&lt;code&gt;protocol&lt;/code&gt; package&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Requests and Notifications
&lt;/h3&gt;

&lt;p&gt;Our server uses the &lt;code&gt;rpc&lt;/code&gt; package to first read the received message. It then figures out if the message is of a request or notification type depending if the message includes an ID, only requests include IDs. Then call their respective handlers depending on the method by passing content (and the ID in case of requests) to them. These handlers use the Go structs we defined above to read the incoming content of the message, do some work and then return a response using the proper LSP types.&lt;/p&gt;

&lt;p&gt;We can now make sense of the shapes of the data we receive and send. We are now done with the building blocks and we can move on to what to do with those nicely structured messages we receive in the following steps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 - State, Documents, and Diagnostics
&lt;/h2&gt;

&lt;p&gt;Before we can do anything with the document open in the editor, we need to know about its current state on the server. We do this in the &lt;code&gt;analysis&lt;/code&gt; package.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document sync
&lt;/h3&gt;

&lt;p&gt;Our server needs a way of knowing about the state of the current text document content in the editor. For this very reason, we have 3 helpful notifications for &lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_synchronization" rel="noopener noreferrer"&gt;document synchronization in the spec&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DidOpen&lt;/code&gt; is sent from client to server to signal a newly opened text document. It includes the following relevant fields to us: &lt;code&gt;uri&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt; and &lt;code&gt;text&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DidChange&lt;/code&gt; is sent from the client to the server to signal changes to a text document. It includes the following fields: &lt;code&gt;textDocument&lt;/code&gt; (identifier) and &lt;code&gt;contentChanges&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DidClose&lt;/code&gt; is sent from the client to the server when the document gets closed in the client. It will only include &lt;code&gt;textDocument&lt;/code&gt; (identifier).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will store the state of the opened text document in our editor buffers using the following structures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;analysis&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;URI&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

    &lt;span class="n"&gt;AST&lt;/span&gt;     &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parser&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// key is URI of the text document&lt;/span&gt;
    &lt;span class="n"&gt;Documents&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Document&lt;/code&gt; represents an opened text document with the information we receive in the &lt;code&gt;DidOpen&lt;/code&gt; notification. It also includes &lt;code&gt;AST&lt;/code&gt; and &lt;code&gt;parser&lt;/code&gt; field imported from our interpreter used to parse the &lt;code&gt;Content&lt;/code&gt;. The server will have a single &lt;code&gt;State&lt;/code&gt; object and is able store and process multiple open documents.&lt;/p&gt;

&lt;p&gt;The server updates it's state whenever it receives the three document sync notifications listed above.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DidOpen&lt;/code&gt; and &lt;code&gt;DidChange&lt;/code&gt; either create a new document or update an existing one identified by its &lt;code&gt;uri&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DidClose&lt;/code&gt; deletes a document from the server state using the given &lt;code&gt;uri&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Diagnostics
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;DidOpen&lt;/code&gt; and &lt;code&gt;DidChange&lt;/code&gt; notifications are good set of events on when to send some diagnostics back to the client, they could be errors, warnings, information etc. The server can publish a diagnostics notification to the client (see &lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_publishDiagnostics" rel="noopener noreferrer"&gt;spec&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Diagnostics notification the client expects has the following shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;PublishDiagnosticsParams&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;URI&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;`json:&lt;/span&gt;&lt;span class="s2"&gt;"uri"&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;Version&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;int&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;`json:&lt;/span&gt;&lt;span class="s2"&gt;"version"&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;Diagnostics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="err"&gt;Diagnostic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`json:&lt;/span&gt;&lt;span class="s2"&gt;"diagnostics"&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Diagnostic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;Range&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;Range&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;`json:&lt;/span&gt;&lt;span class="s2"&gt;"range"&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`start`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`end`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Position:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Character&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fields&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;Severity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;int&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;`json:&lt;/span&gt;&lt;span class="s2"&gt;"severity"&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0-4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Warning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Hint)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;Source&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`json:&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;Message&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`json:&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our original interpreter's parser is already capable of reporting syntax errors as strings as it parses our source-code. But as we have seen above, diagnostics are more encompassing than errors with more information than just a message.&lt;/p&gt;

&lt;p&gt;There's another problem though, diagnostics requires &lt;code&gt;Range&lt;/code&gt; which is used by the editor to show exactly where in the source code a specific diagnostic message applies by usually underlining the specified range with different colors depending on the severity, red for errors and yellow for warnings, for example.&lt;/p&gt;

&lt;p&gt;The Monkey parser reports errors faced in the parsing process as it's dealing with tokens, if the token is unexpected or incorrect, it adds an error to a list. It would be helpful to know the range of this problematic token for our LSP diagnostics implementation, but we have no way of knowing in our current implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extending Monkey tokens
&lt;/h3&gt;

&lt;p&gt;As the original interpreter is limited, we need to add starting and ending positions to all Monkey tokens. We also need a way to store diagnostics that includes the message, range and severity.&lt;/p&gt;

&lt;p&gt;The idea is to track and update where the current position of the lexer is on every byte read. Line number always increases when &lt;code&gt;\n&lt;/code&gt; is met, while character number increases on each byte read then resets when we hit &lt;code&gt;\n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/token/token.go#L60" rel="noopener noreferrer"&gt;&lt;code&gt;token/token.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt;    &lt;span class="n"&gt;TokenType&lt;/span&gt;
    &lt;span class="n"&gt;Literal&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Range&lt;/span&gt;   &lt;span class="n"&gt;Range&lt;/span&gt; &lt;span class="c"&gt;// New - includes Start &amp;amp; End, both include Line and Character number&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lexer/lexer.go#L9" rel="noopener noreferrer"&gt;&lt;code&gt;lexer/lexer.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Lexer&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;position&lt;/span&gt;     &lt;span class="kt"&gt;int&lt;/span&gt;  &lt;span class="c"&gt;// current position in input (points to current char)&lt;/span&gt;
    &lt;span class="n"&gt;readPosition&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;  &lt;span class="c"&gt;// current reading position in input (after current char)&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt;           &lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="c"&gt;// current char under examination&lt;/span&gt;

    &lt;span class="n"&gt;line&lt;/span&gt;      &lt;span class="kt"&gt;uint&lt;/span&gt; &lt;span class="c"&gt;// New - current line&lt;/span&gt;
    &lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt; &lt;span class="c"&gt;// New - current column&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Lexer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Lexer&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// we begin at 0 since we call readChar below, which advances our cursor and sets this to 1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Lexer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readPosition&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="c"&gt;// ASCII code for the "NUL" character&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readPosition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readPosition&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readPosition&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that our tokens each have their positions known, we can collect the diagnostics as follows during parsing:&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/token/token.go#L90" rel="noopener noreferrer"&gt;&lt;code&gt;token.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// New type&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Diagnostic&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Range&lt;/span&gt;    &lt;span class="n"&gt;Range&lt;/span&gt;
    &lt;span class="n"&gt;Message&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Severity&lt;/span&gt; &lt;span class="n"&gt;DiagnosticSeverity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/parser/parser.go#L41" rel="noopener noreferrer"&gt;&lt;code&gt;parser.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Parser&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;           &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lexer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lexer&lt;/span&gt;
    &lt;span class="n"&gt;diagnostics&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt; &lt;span class="c"&gt;// updated field&lt;/span&gt;

    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ...&lt;/span&gt;

&lt;span class="c"&gt;// An example of how we collect diagnostics&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;parseIntegerLiteral&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Expression&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;literal&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerLiteral&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;curToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;curToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"could not parse %q as integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;curToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnostics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnostics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;curToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;literal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;literal&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lines and Characters: 0 vs 1-based numbering
&lt;/h3&gt;

&lt;p&gt;There was a decision we needed to make above: LSP positions are 0-based. But editors usually show lines and characters as 1-based numbers. I opted to use 1-based numbering for the Lexer and the Language Server's state analyzer.&lt;br&gt;
We convert from 0 to 1-based numbers at the protocol boundary in &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/lsp/server/translate.go" rel="noopener noreferrer"&gt;&lt;code&gt;translate.go&lt;/code&gt;&lt;/a&gt; and convert from 1 to 0-based when it's the other way around. Every diagnostic, hover, and definition range passes through that same conversion.&lt;/p&gt;

&lt;p&gt;Syntax diagnostics are fairly easy. But hover, go-to-definition, and completion need to know what an identifier &lt;em&gt;means&lt;/em&gt; - which &lt;code&gt;let&lt;/code&gt; it refers to, whether it's a parameter or builtin.&lt;/p&gt;

&lt;p&gt;There is no way to answer that without building a small semantic model of the program.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 4 - Static analysis: symbols, scopes, and the features built on them (the fun part)
&lt;/h2&gt;

&lt;p&gt;Now that we have diagnostics, specifically errors, correctly being reported back to our editor and we can move on to implementing our three language features: hover, definition and completion.&lt;/p&gt;

&lt;p&gt;This step is without a doubt the most interesting and challenging part of the entire project for me, but at the end, also very rewarding.&lt;/p&gt;

&lt;p&gt;Let us look at what the shapes of requests and expected responses for each look like below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hover&lt;/strong&gt; (see &lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_hover" rel="noopener noreferrer"&gt;spec&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;REQUEST&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"textDocument/hover"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"textDocument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file:///tmp/main.monkey"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"character"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;RESPONSE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"contents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"markdown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"```

monkey&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;let x = 42;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;

```"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Neovim it is triggered with &lt;code&gt;K&lt;/code&gt; and it shows an information on whatever is being hovered over.&lt;br&gt;
We receive the document URI and exact position where the hover happens. We are expected to return a response with an optional content (markdown in our case) that has some information on the token under the cursor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Definition&lt;/strong&gt; (see &lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_definition" rel="noopener noreferrer"&gt;spec&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;REQUEST&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"textDocument/definition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"textDocument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file:///tmp/main.monkey"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"character"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;RESPONSE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file:///tmp/main.monkey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"character"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"character"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Neovim this is commonly triggered with &lt;code&gt;g + d&lt;/code&gt; and it moves the cursor to the position in the code-base where the identifier under the cursor is defined.&lt;br&gt;
We receive similar request params as Hover and we are expected to return an optional result including a file URI and a range (start and end position).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Completion&lt;/strong&gt; (see &lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_completion" rel="noopener noreferrer"&gt;spec&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;REQUEST&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"textDocument/completion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"textDocument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file:///tmp/main.monkey"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"character"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;RESPONSE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;optional&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foobar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This request is triggered by typing in our editor. The request's content is similar to the above two, except here the position we receive is of the cursor's so it's one character off to the right of the text we are typing. The response includes an optional &lt;code&gt;return&lt;/code&gt; field which includes a list of candidates to apply completion.&lt;/p&gt;

&lt;p&gt;Our server as a response to these requests has to answer: &lt;em&gt;what identifier is under the cursor?&lt;/em&gt;&lt;br&gt;
If your intuition is to first find the AST nodes at the positions we get from the requests, then you're spot on!&lt;/p&gt;

&lt;p&gt;But we immediately hit the wall here since our original interpreter's AST nodes do not have ranges defined to help us search the AST for what node exists at a specific position. We go back to our interpreter to extend it for the second time.&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding &lt;code&gt;Start()&lt;/code&gt; and &lt;code&gt;End()&lt;/code&gt; on every AST node
&lt;/h3&gt;

&lt;p&gt;Same trick as tokens, applied one level up. Every node already wraps a token or more so adding &lt;code&gt;Start()&lt;/code&gt; and &lt;code&gt;End()&lt;/code&gt; that return the token's range makes it trivial to walk the AST tree and find the smallest node containing a given position. The changes are as follows:&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/ast/ast.go#L9" rel="noopener noreferrer"&gt;&lt;code&gt;ast/ast.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;TokenLiteral&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="c"&gt;// New&lt;/span&gt;
    &lt;span class="n"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="c"&gt;// New&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding ranges to Monkey tokens earlier makes implementing these methods rather trivial. For example, If Expression AST nodes implement these as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;IfExpression&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Token&lt;/span&gt;       &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;
    &lt;span class="n"&gt;Condition&lt;/span&gt;   &lt;span class="n"&gt;Expression&lt;/span&gt;
    &lt;span class="n"&gt;Consequence&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BlockStatement&lt;/span&gt; &lt;span class="c"&gt;// BlockStatement implements Start() &amp;amp; End() itself&lt;/span&gt;
    &lt;span class="n"&gt;Alternative&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;BlockStatement&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ie&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;IfExpression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c"&gt;// New&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ie&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;IfExpression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// New&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alternative&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Alternative&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EndToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;End&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Consequence&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EndToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;End&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a way of grabbing AST nodes from a parsed source code given a position in it using &lt;em&gt;Depth First Search&lt;/em&gt; and the &lt;code&gt;Start()&lt;/code&gt; and &lt;code&gt;End()&lt;/code&gt; methods we just introduced.&lt;/p&gt;

&lt;p&gt;Let's move on with our implementations for the three language features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing our language features and the problems we face
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Hover
&lt;/h4&gt;

&lt;p&gt;We can handle this request depending on the node under the cursor as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;String Literal:&lt;/strong&gt; Show the length and size of the string in bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integer Literal:&lt;/strong&gt; Show the hexadecimal, octal and binary representation of the number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boolean Literal:&lt;/strong&gt; Show just the value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identifier:&lt;/strong&gt; Show the type (variable, parameter or built-in function), name, and the position where it is defined.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can implement the first three fairly simply, but the fourth one needs significantly more brain power to come up with a good enough solution for it. Consider the following scenario.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let num = 1;
puts(num);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; If we hover over &lt;code&gt;num&lt;/code&gt; on the second line, it should report back the position where the original is defined at, which we have no way of knowing currently.&lt;br&gt;
The obvious and naive solution would be to traverse the AST again and to find the closest definition of this identifier to the position where the hover happened, in order to respect Monkey scopes. But this is inefficient to do on every hover request, we can do better.&lt;/p&gt;
&lt;h4&gt;
  
  
  Definition
&lt;/h4&gt;

&lt;p&gt;We basically need to first find the node in the AST and if it's an Identifier node, we return the range where this identifier is defined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; We face the same exact problem as we mentioned above, we have no immediate way of knowing where this identifier is originally defined at in our source-code.&lt;/p&gt;
&lt;h4&gt;
  
  
  Completion
&lt;/h4&gt;

&lt;p&gt;Completion needs more work to implement, we do the following in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;get the full identifier that is in the process of being typed&lt;/li&gt;
&lt;li&gt;know the deepest scope or function enclosing this identifier&lt;/li&gt;
&lt;li&gt;build a list of candidate identifiers (identifiers that have current identifier as prefix) for the completion that are defined in the deepest scope enclosing the identifier and then unwind to go up the scopes up until the root scope where built-in functions and keywords are defined.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; The two problems above were only about finding and searching for the closest definition of an identifier, but here we have a problem an order of magnitude bigger: we need to find multiple identifiers, but also that are in scope of the identifier before the cursor.&lt;/p&gt;

&lt;p&gt;Before solving the problems listed above, we must do &lt;em&gt;static analysis&lt;/em&gt; to help us solve these issues more efficiently.&lt;/p&gt;
&lt;h3&gt;
  
  
  Static analysis, symbols, symbol tables and diagnostics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Static analysis&lt;/strong&gt; deals with inspecting the source code for semantic errors, style violations etc. In our case, we analyze our AST to collect our identifiers and all function scopes for quick access.&lt;/p&gt;

&lt;p&gt;This will help us solve the problems mentioned in the previous section efficiently, since the analysis just happens once on text document open or update it allows every hover, definition or completion use the results of the latest static analysis.&lt;/p&gt;

&lt;p&gt;We introduce a few more types in the &lt;code&gt;analysis&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/scope.go#L8" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/scope.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;analysis&lt;/span&gt;

&lt;span class="c"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;symbolType&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;variable&lt;/span&gt;  &lt;span class="n"&gt;symbolType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"variable"&lt;/span&gt;
    &lt;span class="n"&gt;parameter&lt;/span&gt; &lt;span class="n"&gt;symbolType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"parameter"&lt;/span&gt;
    &lt;span class="n"&gt;builtin&lt;/span&gt;   &lt;span class="n"&gt;symbolType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"builtin"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt;       &lt;span class="n"&gt;symbolType&lt;/span&gt;
    &lt;span class="n"&gt;Range&lt;/span&gt;      &lt;span class="n"&gt;Range&lt;/span&gt;
    &lt;span class="n"&gt;Identifier&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identifier&lt;/span&gt;
    &lt;span class="n"&gt;Used&lt;/span&gt;       &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;symbolTable&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Outer&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;symbolTable&lt;/span&gt;
    &lt;span class="n"&gt;Symbols&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;symbol&lt;/code&gt; represents all identifier definitions including variables, parameters and names of built-in functions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;symbolTable&lt;/code&gt; stores symbols, the keys are their names and the values are the symbols themselves. It also has &lt;code&gt;symbolTable.Outer&lt;/code&gt; field which will allow us to store symbols defined in the outer scope of the current symbol table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use the above to add some very important fields to &lt;code&gt;Document&lt;/code&gt; struct.&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L32" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/state.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Version, URI, Content, AST, parser ... old fields&lt;/span&gt;

    &lt;span class="n"&gt;rootScope&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;symbolTable&lt;/span&gt;                          &lt;span class="c"&gt;// New&lt;/span&gt;
    &lt;span class="n"&gt;identifierUses&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identifier&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;           &lt;span class="c"&gt;// New &lt;/span&gt;
    &lt;span class="n"&gt;functionScopes&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionLiteral&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;symbolTable&lt;/span&gt; &lt;span class="c"&gt;// New&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rootScope&lt;/code&gt; stores our initial/root symbol table initialized with symbols of built-in functions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;identifierUses&lt;/code&gt; stores a map of pointers of all identifier nodes to their respective symbols.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;functionScopes&lt;/code&gt; stores a map of pointers of all function literal nodes to their respective symbol tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now for the main player that makes sense of all the types we have mentioned above.&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/analyzer.go" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/analyzer.go&lt;/code&gt;&lt;/a&gt; (some sections are removed for brevity)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;analysis&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/SegniAT/monkey-language-interpreter/ast"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/SegniAT/monkey-language-interpreter/token"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// analyze builds the document's symbol table, resolves every identifier into&lt;/span&gt;
&lt;span class="c"&gt;// the Uses map, records every function scope in the Scopes map and returns&lt;/span&gt;
&lt;span class="c"&gt;// semantic diagnostics for undefined, redeclared and unused names.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootScope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newRootScope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifierUses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identifier&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;functionScopes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionLiteral&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;symbolTable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;diags&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;diags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;diags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unusedDiagnostics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;diags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;symbolTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diags&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// cases *ast.Program and *ast.BlockStatement:&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LetStatement&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;diags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;diags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"redeclaration of %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionLiteral&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;newChildScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;functionScopes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;diags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;diags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"redeclaration of %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// multipe cases removed for brevity&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identifier&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
            &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifierUses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;diags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;diags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"undefined variable: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// *ast.IntegerLiteral, *ast.Boolean and *ast.StringLiteral are leaves.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;*Document.analyze&lt;/code&gt; gets called on events &lt;code&gt;textDocument/didOpen&lt;/code&gt; and &lt;code&gt;textDocument/didChange&lt;/code&gt; in order to analyze the latest source-code and return it's diagnostics.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*symbolTable.define&lt;/code&gt; adds a new symbol to the symbol table and returns &lt;code&gt;true&lt;/code&gt; if it is re-declared in the same scope.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*symbolTable.lookup&lt;/code&gt; searches for a symbol by name in the proper scopes, starting from the current symbol table (scope) up to the root symbol table.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*Document.analyze&lt;/code&gt; initializes some fields and starts the tree walker that populates the newly added &lt;code&gt;Document&lt;/code&gt; fields and returns diagnostics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;*Document.visit&lt;/code&gt; is the main function that walks our AST and does several distinct actions depending on the type of the node it encounters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;*ast.LetStatement&lt;/code&gt;: we define a new symbol using the node's name. If it is a re-declaration we add a new error diagnostic to our global diagnostics list.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*ast.FunctionLiteral&lt;/code&gt;:

&lt;ol&gt;
&lt;li&gt;We first create a new child symbol table using the current as parent.&lt;/li&gt;
&lt;li&gt;We save this child scope's pointer into &lt;code&gt;*Document.functionScopes&lt;/code&gt; using the function literal AST node's pointer as key.&lt;/li&gt;
&lt;li&gt;We loop over the parameters and define symbols for each, if there's a re-declaration we add a new error diagnostic.&lt;/li&gt;
&lt;li&gt;We visit the function's body with our newly defined scope as input.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*ast.Identifier&lt;/code&gt;: We lookup if a symbol exists with similar name, if so, we store the identifier to &lt;code&gt;*Document.identifierUses&lt;/code&gt; using the node pointer as key and the found symbol pointer as a value. We also set &lt;code&gt;*symbol.Used&lt;/code&gt; to true, we will find this useful later. If no symbol is found, we add an error "undefined variable" diagnostic to our global diagnostics list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One last extra but useful thing is the method &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/analyzer.go#L124" rel="noopener noreferrer"&gt;&lt;code&gt;*Document.unusedDiagnostics&lt;/code&gt;&lt;/a&gt;called in &lt;code&gt;*Document.analyze&lt;/code&gt;, its purpose is straight-forward: it collects warning diagnostics by going through every single symbol defined in all scopes of the source-code for unused symbols.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Remember that we set a symbol's &lt;code&gt;Used&lt;/code&gt; field to true when we come across an identifier in &lt;code&gt;*Document.visit&lt;/code&gt; and if an identifier with the same name is found in the relevant scope/s.&lt;/p&gt;

&lt;p&gt;We now have done all the work to finally implement our language features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hover, definition and completion implementation
&lt;/h3&gt;

&lt;p&gt;Let's get straight to implementing our solutions now that we have overcome the latest blockers.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hover
&lt;/h4&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L93" rel="noopener noreferrer"&gt;analysis/state.go &lt;code&gt;*State.Hover()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We first find the node at the specified position and then handle the request depending on the type of the node. The problem was that in case of hovering over identifiers, we did not have a good way to know the position where it is defined at.&lt;/p&gt;

&lt;p&gt;We can now figure this out easily thanks to &lt;code&gt;*Document.identifierUses&lt;/code&gt;. We use it to look-up the node's symbol and get the range off of it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Definition
&lt;/h4&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L165" rel="noopener noreferrer"&gt;analysis/state.go &lt;code&gt;*State.Definition()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Definition, we first find the AST node at the given position and we make sure that it's of an identifier type or we return early. We then look-up it's symbol in &lt;code&gt;*Document.identifierUses&lt;/code&gt; and if we find a symbol we return it's range.&lt;/p&gt;

&lt;h4&gt;
  
  
  Completion
&lt;/h4&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L199" rel="noopener noreferrer"&gt;analysis/state.go &lt;code&gt;*State.Completion()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Completion does more work than the other two. After finding the exact identifier node, the next step is finding the &lt;strong&gt;deepest&lt;/strong&gt; function node that encloses this identifier.  From &lt;code&gt;*document.functionScopes&lt;/code&gt;, we can get that function's symbol table, which also includes a pointer to its outer scope's symbol table. For each level of eligible scope, we go through the unique names of identifiers registered and check if the identifier being typed is a prefix to them, if it is we add it as one option for completion.&lt;/p&gt;

&lt;p&gt;LSP has an option to differentiate what type of completion an item is, in our case we have three: &lt;code&gt;Variable&lt;/code&gt;, &lt;code&gt;Function&lt;/code&gt; and &lt;code&gt;Keyword&lt;/code&gt;.&lt;br&gt;
Most of our completion items will be of type &lt;code&gt;Variable&lt;/code&gt;, even functions are defined using a &lt;code&gt;let&lt;/code&gt; statement by assigning them to variables. Only built-in functions will have completion type &lt;code&gt;Function&lt;/code&gt; and &lt;code&gt;Keyword&lt;/code&gt; is for Monkey keywords such as &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt; etc.&lt;/p&gt;
&lt;h3&gt;
  
  
  A design trade-off I hit while writing the analyzer
&lt;/h3&gt;

&lt;p&gt;Our static analyzer has one flaw right now. For a &lt;code&gt;let&lt;/code&gt; statement, it registers a symbol with the variable's name &lt;em&gt;before&lt;/em&gt; visiting the right-hand side. This leads to failing to report "undefined variable" in the following case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let a = a;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose we fixed that by defining the symbol &lt;em&gt;after&lt;/em&gt; visiting the right-hand side. Now consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// fib not yet defined → error&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reporting an "undefined variable 'fib'" error here will hinder us from ever using recursion. Our interpreter can technically run the code without any problems but our Language Server will always report this as an error to our editor.&lt;/p&gt;

&lt;p&gt;I chose to go with the current approach, i.e defining before visiting the right-hand side, because recursion is a first-class Monkey feature and self-reference is a footgun the programmer will notice immediately. Worth calling out as an explicit trade-off.&lt;/p&gt;

&lt;p&gt;We are finally done with all the language features!&lt;/p&gt;




&lt;h2&gt;
  
  
  The &lt;code&gt;let&lt;/code&gt; crash (a Go interface gotcha)
&lt;/h2&gt;

&lt;p&gt;Excited, after implementing all the cool features of our Language Server I went right to testing. But I soon found out a bug that made my server crash.&lt;/p&gt;

&lt;p&gt;Opening a file containing or typing the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there ever is a &lt;code&gt;let&lt;/code&gt; keyword alone without an identifier in the source-code, it crashes the server. I went digging through the stack-traces and finally figured out the problem! It was the following function in Monkey's parser.&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/commit/e4d013e7556a39d7fc6a2b1809e5e46c617d068f#diff-e7aabff4498aa61c581fe6fd5c90c50da24b02b88f32d75238a5dc37edaaa794L124" rel="noopener noreferrer"&gt;&lt;code&gt;parser/parser.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;parseStatement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;curToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LET&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseLetStatement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETURN&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseReturnStatement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseExpressionStatement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;parseLetStatement&lt;/code&gt; returns &lt;code&gt;(*ast.LetStatement)(nil)&lt;/code&gt;, that pointer is wrapped in the &lt;code&gt;ast.Statement&lt;/code&gt; interface, and an interface holding a &lt;strong&gt;typed nil pointer is not nil&lt;/strong&gt;. Every downstream &lt;code&gt;if stmt != nil&lt;/code&gt; check succeeds, and the first field access panics.&lt;/p&gt;

&lt;p&gt;This exact mistake is mentioned in the &lt;a href="https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them" rel="noopener noreferrer"&gt;Go Mistakes and How to Avoid Them&lt;/a&gt; book under mistake #45, Returning a nil receiver.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;solution&lt;/strong&gt; is to return &lt;code&gt;nil&lt;/code&gt; explicitly at the call site. We return a &lt;em&gt;nil interface&lt;/em&gt;, not a &lt;em&gt;nil receiver converted into a non-nil interface&lt;/em&gt;.&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/parser/parser.go#L124" rel="noopener noreferrer"&gt;&lt;code&gt;parser/parser.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;parseStatement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;curToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LET&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseLetStatement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="c"&gt;// Explicitly returns untyped nil&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETURN&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseReturnStatement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseExpressionStatement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5 - Concurrency
&lt;/h2&gt;

&lt;p&gt;The main loop of our server currently is single-threaded. While a hover request is being served, no notifications are being read. A slow request made the editor feel laggy, even worse, a long completion while the user typed meant &lt;code&gt;didChange&lt;/code&gt; was queued behind it.&lt;/p&gt;

&lt;p&gt;The solution to this is to dispatch request handlers in goroutines:&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/lsp/server/server.go#L66" rel="noopener noreferrer"&gt;&lt;code&gt;server/server.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Spin up a goroutine for read-only requests&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reqID&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqMethod&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqContent&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c"&gt;// ... logs error&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;

        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reqID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqMethod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Run notifications synchronously to guarantee state mutation order&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solution however introduces a &lt;strong&gt;problem&lt;/strong&gt; of its own. Now &lt;code&gt;didChange&lt;/code&gt; notification (which mutates &lt;code&gt;AST&lt;/code&gt;, &lt;code&gt;identifierUses&lt;/code&gt;, &lt;code&gt;functionScopes&lt;/code&gt; fields on &lt;code&gt;Document&lt;/code&gt;) races against &lt;code&gt;hover&lt;/code&gt; request (which reads &lt;code&gt;identifierUses&lt;/code&gt;). Go's runtime map access detector could catch it, and could cause the server to fatally crash with a &lt;code&gt;concurrent map read and map write&lt;/code&gt; message.&lt;/p&gt;

&lt;p&gt;We must add a locking mechanism to our server's state to access and update these fields safely. We add a global &lt;code&gt;sync.RWMutex&lt;/code&gt; to &lt;code&gt;analysis.State&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L53" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/state.go State&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mu&lt;/span&gt;        &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;         &lt;span class="c"&gt;// New&lt;/span&gt;
    &lt;span class="n"&gt;Documents&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write-lock on &lt;code&gt;DidOpen&lt;/code&gt;, &lt;code&gt;DidChange&lt;/code&gt;, &lt;code&gt;DidClose&lt;/code&gt;.&lt;br&gt;
Sources: &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L62" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/state.go *State.DidOpen&lt;/code&gt;&lt;/a&gt;, &lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L73" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/state.go *State.DidChange&lt;/code&gt;&lt;/a&gt;, &lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L472" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/state.go *State.Close&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;DidOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// DidChange and Close are similar to the above.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read-lock on &lt;code&gt;Hover&lt;/code&gt;, &lt;code&gt;Definition&lt;/code&gt;, &lt;code&gt;Completion&lt;/code&gt;.&lt;br&gt;
Sources: &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L93" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/state.go *State.Hover&lt;/code&gt;&lt;/a&gt;, &lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L165" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/state.go *State.Definition&lt;/code&gt;&lt;/a&gt;, &lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/lsp/analysis/state.go#L199" rel="noopener noreferrer"&gt;&lt;code&gt;analysis/state.go *State.Completion&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Hover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Definition and Completion are similar to the above.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These additions solve the data race mentioned above.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6 - Hooking up our server to Neovim
&lt;/h2&gt;

&lt;p&gt;We should first add the monkey file type to Neovim, we have done the same in part 1 when adding our Tree-sitter to Neovim.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;monkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"monkey"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final step is to configure and enable our LSP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"monkey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"monkey-lsp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"/path-to-LSP-binary/monkey"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;filetypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"monkey"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"monkey"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;All packages are extensively unit tested to ensure reliability.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests for the RPC reader&lt;/li&gt;
&lt;li&gt;Analyzer tests: scopes, undefined vars, re-declaration, unused variables, closure capture.&lt;/li&gt;
&lt;li&gt;Manual testing in Neovim:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;K&lt;/code&gt; for hover&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gd&lt;/code&gt; for go-to-definition.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;CTRL + x&amp;gt;&amp;lt;CTRL + o&amp;gt;&lt;/code&gt; for completion.&lt;/li&gt;
&lt;li&gt;Typing &lt;code&gt;let&lt;/code&gt; alone to confirm the nil-interface crash is fixed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Showcase
&lt;/h2&gt;

&lt;p&gt;Let's now look at the final result of our hard work!&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnostics on text document open and change
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Re-declaration of variables
&lt;/h4&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/U-HExcfnnbw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Notice how variables are function scoped by the way the second &lt;code&gt;sum&lt;/code&gt; variable is not being reported as a re-declaration. Also note that this diagnosis also catches parameter re-declarations as well.&lt;/p&gt;

&lt;h4&gt;
  
  
  Undefined variables
&lt;/h4&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/FeampO2KuUE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;result&lt;/code&gt; is undefined in the above code snippet.&lt;/p&gt;

&lt;h4&gt;
  
  
  Unused variables
&lt;/h4&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/W7ApwroqFBQ" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We can use an underscore (&lt;code&gt;_&lt;/code&gt;) to ignore unused variables so that the LSP won't complain about them, inspired by Go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hover
&lt;/h3&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/VEQq2j2GzKA" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Hovering over a builtin function &lt;code&gt;puts&lt;/code&gt;, a string, a parameter, a variable, a number and a boolean shows different types of information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Definition
&lt;/h3&gt;

&lt;p&gt;This request is triggered with &lt;code&gt;&amp;lt;g&amp;gt;&amp;lt;r&amp;gt;&amp;lt;d&amp;gt;&lt;/code&gt; key combination in my current Neovim setup.&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/Vv35fzojxCg" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Completion
&lt;/h3&gt;

&lt;p&gt;The completion request is triggered automatically in my Neovim, but it can also be triggered manually with &lt;code&gt;&amp;lt;C-x&amp;gt;&amp;lt;C-o&amp;gt;&lt;/code&gt;.&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/rNlhJzq_-Eg" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Part 1 taught editors what Monkey &lt;em&gt;looks like&lt;/em&gt;. This post taught them what Monkey &lt;em&gt;means&lt;/em&gt;. Between the two, we went from syntax highlighting to hover, go-to-definition, completion, and diagnostics - all from scratch, in a language that had none of them two posts ago.&lt;/p&gt;

&lt;p&gt;The timeline this post actually followed: RPC framing, protocol types and handlers, document state and diagnostics, extending the interpreter with token ranges, adding AST node ranges, static analysis, the three language features, concurrency and locking, and finally wiring it all into Neovim.&lt;/p&gt;

&lt;p&gt;Two lessons that stuck with me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Editor features aren't a single feature.&lt;/strong&gt; Diagnostics looked trivial until I realized the parser had no way to point at a range in the file. Hover looked trivial until I realized the AST had no node positions. Everything looked fine until I made the server concurrent and noticed a possible data race. Each feature I added exposed a missing layer in the layer below it. None of those trips back to the interpreter were in the original plan. All of them were necessary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The protocol boundary is where conventions get translated.&lt;/strong&gt; LSP is 0-indexed; editors are 1-indexed. The protocol deals in wire types; the analyzer deals in &lt;code&gt;analysis.Hover&lt;/code&gt; and &lt;code&gt;analysis.Location&lt;/code&gt;. Every one of those conversions lives in exactly one place - &lt;code&gt;translate.go&lt;/code&gt;. It keeps the analyzer free of protocol concerns and the protocol layer free of analysis concerns, and it's the architectural decision I'd keep if I had to rewrite this from scratch.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What's next: Once Monkey has enough of a dev environment to be pleasant to write in, I want to see what it actually feels like to solve a real Advent of Code problem with it. That's Part 3.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/" rel="noopener noreferrer"&gt;LSP specification&lt;/a&gt; (3.18)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://segni.hashnode.dev/building-a-development-environment-for-monkey-part-1-syntax-highlighting-with-tree-sitter" rel="noopener noreferrer"&gt;Part 1: Tree-sitter grammar for Monkey&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/" rel="noopener noreferrer"&gt;&lt;code&gt;monkey-language-interpreter&lt;/code&gt; repository&lt;/a&gt; (includes LSP implementation in &lt;code&gt;/lsp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Go docs: &lt;code&gt;bufio.Reader&lt;/code&gt;, &lt;code&gt;io.ReadFull&lt;/code&gt;, &lt;code&gt;sync.RWMutex&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them" rel="noopener noreferrer"&gt;Go Mistakes and How to Avoid Them&lt;/a&gt; : the nil-interface gotcha, &lt;code&gt;io.Reader&lt;/code&gt;/&lt;code&gt;io.Writer&lt;/code&gt; abstraction&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>lsp</category>
      <category>interpreter</category>
      <category>go</category>
      <category>languageserverprotocol</category>
    </item>
    <item>
      <title>Building a development environment for Monkey: Part 1 - Syntax highlighting with Tree-sitter</title>
      <dc:creator>Segni Adeba</dc:creator>
      <pubDate>Sun, 20 Sep 2026 14:54:48 +0000</pubDate>
      <link>https://dev.to/segniadebagodsson/building-a-development-environment-for-monkey-part-1-syntax-highlighting-with-tree-sitter-39oj</link>
      <guid>https://dev.to/segniadebagodsson/building-a-development-environment-for-monkey-part-1-syntax-highlighting-with-tree-sitter-39oj</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;A few years ago on a quest of learning Go, I followed &lt;a href="https://interpreterbook.com/" rel="noopener noreferrer"&gt;Writing An Interpreter In Go&lt;/a&gt; and built Monkey, a small interpreted programming language. Recently, I came back to it with a different goal: I wanted to see how far I could take the language as an actual development environment.&lt;/p&gt;

&lt;p&gt;My plan is to build the tooling around Monkey step by step: first syntax highlighting, then LSP support, and eventually use the language to solve an Advent of Code problem.&lt;/p&gt;

&lt;p&gt;As we begin writing code in Monkey, we immediately notice that it lacks the editor features we have come to take for granted: syntax highlighting, diagnostics, navigation, completion, and more.&lt;br&gt;
There is a lot of machinery behind features we take for granted, and I wanted to understand it by building it. This post is the first step: teaching editors how to understand Monkey's syntax using Tree-sitter, primarily for highlighting purposes.&lt;/p&gt;

&lt;p&gt;Before building the tooling, we should first understand the language we are building it for. Monkey is small, but it has enough language features to build simple tooling around it that would teach us all the basics of parsing. The core functionalities and features in Monkey are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Types:&lt;/strong&gt; integers, booleans, strings, arrays, and hash maps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variables:&lt;/strong&gt; created using &lt;code&gt;let&lt;/code&gt; statements (e.g., &lt;code&gt;let foo = 1;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operators:&lt;/strong&gt; prefix (e.g., &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;!&lt;/code&gt;) and infix operators (e.g., &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;) to evaluate arithmetic and boolean expressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functions as First-Class Citizens:&lt;/strong&gt; Functions can be bound to names, passed as arguments, and returned from other functions, allowing for higher-order functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closures:&lt;/strong&gt; fully supports lexical closures, meaning functions can capture and retain access to the variables in which they were defined.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in Functions:&lt;/strong&gt; includes pre-defined utility functions, such as &lt;code&gt;len()&lt;/code&gt; (to get the length of strings or arrays), &lt;code&gt;puts()&lt;/code&gt; (for printing to standard output), &lt;code&gt;first()&lt;/code&gt;, &lt;code&gt;rest()&lt;/code&gt;, &lt;code&gt;last()&lt;/code&gt;, and &lt;code&gt;push()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the full docs of Monkey and full Tree-sitter implementation in the &lt;a href="https://github.com/SegniAT/monkey-language-interpreter" rel="noopener noreferrer"&gt;Monkey repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By the end of this post we'll have a Tree-sitter grammar that parses every construct in Monkey, tested against a corpus, and wired into Neovim.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Tree-sitter?
&lt;/h2&gt;

&lt;p&gt;I decided on using it instead of the approaches most editors used for syntax highlighting before Tree-sitter, such as regular expressions, custom parsers, and lexer-based systems, because Tree-sitter has become widely adopted for editor tooling and syntax-aware features.&lt;/p&gt;

&lt;p&gt;There are a few other reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error recovery&lt;/strong&gt;: Tree-sitter can parse incomplete or syntactically invalid code while still producing a useful syntax tree. This is important for editors because code is often temporarily broken while we are in the process of writing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A real syntax tree&lt;/strong&gt;: Instead of treating source code as a collection of patterns to match, Tree-sitter produces a syntax tree, which the CLI displays as S-expressions. Editors can use this structural information to provide more accurate, context-aware highlighting. For example, an identifier can be distinguished based on whether it represents a type, variable, function, or something else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And syntax highlighting is only one of the things we can build on top of a syntax tree. Once we have one, we can also implement features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incremental parsing&lt;/li&gt;
&lt;li&gt;Code folding&lt;/li&gt;
&lt;li&gt;Structural selection&lt;/li&gt;
&lt;li&gt;Code navigation&lt;/li&gt;
&lt;li&gt;Semantic highlighting&lt;/li&gt;
&lt;li&gt;LSP integration and more&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Project setup
&lt;/h2&gt;

&lt;p&gt;Before we start writing the grammar, let's get a minimal Tree-sitter project running. Since the goal of this post isn't to explain the installation process, I'll keep this section short and link to the official setup guide.&lt;/p&gt;

&lt;p&gt;Dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A JavaScript runtime&lt;/li&gt;
&lt;li&gt;A C compiler&lt;/li&gt;
&lt;li&gt;Tree-sitter CLI (0.26.9)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow the getting started &lt;a href="https://tree-sitter.github.io/tree-sitter/creating-parsers/1-getting-started.html#installation" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; to set up a similar project.&lt;/p&gt;


&lt;h2&gt;
  
  
  Building the grammar
&lt;/h2&gt;

&lt;p&gt;With the Tree-sitter project set up, the next step is to teach it what Monkey looks like. We already have a parser and AST that define the language's structure in the interpreter, so rather than designing the grammar from scratch, we'll translate those existing concepts into Tree-sitter rules.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Starting with a basic grammar
&lt;/h3&gt;
&lt;h4&gt;
  
  
  The grammar skeleton
&lt;/h4&gt;

&lt;p&gt;The following is the minimal skeleton of our grammar.&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/tree-sitter-monkey/grammar.js" rel="noopener noreferrer"&gt;&lt;code&gt;grammar.js&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * @file Monkey grammar for tree-sitter
 * @author SegniAT &amp;lt;se.segni.adeba@gmail.com&amp;gt;
 * @license MIT
 */&lt;/span&gt;

&lt;span class="c1"&gt;/// &amp;lt;reference types="tree-sitter-cli/dsl" /&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// @ts-check&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;grammar&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;monkey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;source_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_statement&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;_statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression_statement&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;expression_statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z_&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The grammar function is the most important part of this file. It contains the declarative schema for our language. The &lt;code&gt;name&lt;/code&gt; property is the name of the language we're writing the grammar for and the &lt;code&gt;rules&lt;/code&gt; property allows us to define rules using built-in functions listed in the &lt;a href="https://tree-sitter.github.io/tree-sitter/creating-parsers/2-the-grammar-dsl.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We build the grammar top-down, mirroring how the interpreter's AST (Abstract Syntax Tree) is organized. One important fact to know up front is that the &lt;strong&gt;start rule&lt;/strong&gt; for the grammar is the first property in the rules object. In the example above, that would correspond to &lt;code&gt;source_file&lt;/code&gt;, but it can be named anything.&lt;/p&gt;

&lt;p&gt;Every grammar rule is written as a JavaScript function that takes a parameter &lt;code&gt;$&lt;/code&gt;. The syntax &lt;code&gt;$.identifier&lt;/code&gt; is how you refer to another grammar symbol within a rule.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;monkey&lt;/code&gt; parser, a program is a struct that has a slice of statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ast/ast.go&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Program&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Statements&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Statement&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We replicated this in our grammar as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;source_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_statement&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// 'repeat(rule)' creates a rule that matches zero-or-more occurrences of a given rule.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have to define &lt;code&gt;_statement&lt;/code&gt;. In our Monkey parser, there are 3 types of Statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ast/ast.go&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Statement&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;
    &lt;span class="n"&gt;statementNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// 1. The LET statement &lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;LetStatement&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="c"&gt;// the token.LET token&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Identifier&lt;/span&gt;
    &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="n"&gt;Expression&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// 2. The RETURN statement&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ReturnStatement&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Token&lt;/span&gt;       &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="c"&gt;// the 'return' token&lt;/span&gt;
    &lt;span class="n"&gt;ReturnValue&lt;/span&gt; &lt;span class="n"&gt;Expression&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// 3. The ExpressionStatement statement&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ExpressionStatement&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Token&lt;/span&gt;      &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="c"&gt;// the first token of the expression&lt;/span&gt;
    &lt;span class="n"&gt;Expression&lt;/span&gt; &lt;span class="n"&gt;Expression&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our skeleton grammar we only define the Expression Statement for now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Starting a rule's name with an underscore causes the rule to be hidden in the syntax tree. This avoids depth and noise to the syntax tree.&lt;/span&gt;
&lt;span class="c1"&gt;// 'choice(rule1, rule2, ...)' function creates a rule that matches one of a set of possible rules.&lt;/span&gt;
&lt;span class="nx"&gt;_statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression_statement&lt;/span&gt;
      &lt;span class="c1"&gt;// We later add the LET and RETURN statements here.&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expression Statement is defined as the following in our grammar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// `seq(rule1, rule2, ...)` function creates a rule that matches any number of other rules, in order.&lt;/span&gt;
&lt;span class="nx"&gt;expression_statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// `optional(rule)` function creates a rule that matches zero or one occurrence of a given rule.&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An Expression Statement is just an Expression followed by an optional semicolon (semicolons are optional in Monkey).&lt;br&gt;
Let's look at Expression now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z_&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An expression can be an identifier or an integer here, we will add much more to our final version. We describe &lt;code&gt;identifier&lt;/code&gt; and &lt;code&gt;integer&lt;/code&gt; as regular expressions, an identifier can only include letters and underscores, while an integer only includes numbers.&lt;/p&gt;

&lt;p&gt;At this point we have enough grammar to parse something simple, so let's verify that Tree-sitter produces the tree we expect before adding more rules.&lt;/p&gt;

&lt;h4&gt;
  
  
  Testing the grammar
&lt;/h4&gt;

&lt;p&gt;Create &lt;code&gt;test/corpus/basics.txt&lt;/code&gt; with these two tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=================================
Expression statement (identifier)
=================================

foo

---

(source_file
  (expression_statement
    (identifier)))

==============================
Expression statement (integer)
==============================

42

---

(source_file
  (expression_statement
    (integer)))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's now generate a parser from the grammar and test it against the test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tree-sitter generate
tree-sitter &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;basics:
    1. ✓ Expression statement (identifier)
    2. ✓ Expression statement (integer)

Total parses: 2; successful parses: 2; failed parses: 0; success percentage: 100.00%; average speed: 593 bytes/ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our generated parser based on the grammar outputs an S-expression when parsing our target language source code, so we use that fact to write our tests. Let's look at the first tests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;name&lt;/strong&gt; of each test is written between two lines containing only = (equal sign) characters.&lt;/li&gt;
&lt;li&gt;Then the &lt;strong&gt;input source code&lt;/strong&gt; is written, followed by a line containing three or more - (dash) characters.&lt;/li&gt;
&lt;li&gt;Then, the &lt;strong&gt;expected output syntax tree&lt;/strong&gt; is written as an S-expression. The exact whitespace in the S-expression doesn't matter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As shown in the expected output, the root of our syntax tree is a named node called &lt;code&gt;source_file&lt;/code&gt;, which directly corresponds to our root rule defined in &lt;code&gt;grammar.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The next node we might expect would be &lt;code&gt;_statement&lt;/code&gt;, but the underscore makes it hidden. So we move on to its children, for now we just have &lt;code&gt;expression_statement&lt;/code&gt; which has either &lt;code&gt;identifier&lt;/code&gt; or &lt;code&gt;integer&lt;/code&gt; as its children. In our first test case, &lt;code&gt;foo&lt;/code&gt; will be identified as &lt;code&gt;identifier&lt;/code&gt;, but in the second &lt;code&gt;42&lt;/code&gt; is an &lt;code&gt;integer&lt;/code&gt; node.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Building out the grammar
&lt;/h3&gt;

&lt;p&gt;The basic grammar works, so now we can start filling in the pieces we left out of the skeleton.&lt;/p&gt;

&lt;h4&gt;
  
  
  Statements: &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;return&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Our interpreter has two more Statement types in addition to the already defined Expression Statement. We add them as the other two alternatives of &lt;code&gt;_statement&lt;/code&gt; rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// `field(name, rule)` function assigns a field name to the child node(s) matched by the given rule. We can use it to access specific children in the resulting syntax tree.&lt;/span&gt;
&lt;span class="nx"&gt;let_statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;let&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="nx"&gt;return_statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;return&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mirrors the interpreter's AST: the &lt;code&gt;LetStatement&lt;/code&gt; struct has a &lt;code&gt;Name&lt;/code&gt; and a &lt;code&gt;Value&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;With the remaining Statements in place, we can move on to the remaining Literals and Expressions. This is where the grammar starts getting more interesting. Monkey has several kinds of Literals and Expressions, and some Tree-sitter features become necessary to keep the resulting tree useful and less noisy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Literals and Expressions
&lt;/h4&gt;

&lt;p&gt;Some of the remaining Literals are as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// `token(rule)` function marks the given rule as producing only a single token. Tree-sitter's default is to treat each `String` or `RegExp` literal in the grammar as a separate token. We don't want 3 separate tokens here, just one.&lt;/span&gt;
&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;"&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="c1"&gt;// We don't allow '"' in strings since we cannot escape characters in Monkey at the time of writing this.&lt;/span&gt;
&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="c1"&gt;// ... function, hash, array literals&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Monkey grammar, the &lt;code&gt;expression&lt;/code&gt; rule is defined as a choice between many different kinds of Expressions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unary_expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;binary_expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paren_expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call_expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index_expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;if_expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;function_literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;array_literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash_literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Keyword extraction using &lt;code&gt;word&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Consider the following snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iffoo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tree-sitter would lex this source code as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;code&gt;if&lt;/code&gt; keyword &lt;/li&gt;
&lt;li&gt;an identifier &lt;code&gt;foo&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But &lt;code&gt;if&lt;/code&gt; should only be matched if it appears as a whole word, on its own.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;word&lt;/code&gt; property solves this problem. It tells Tree-sitter which rule represents the language's "word" (its identifier). This is what drives &lt;strong&gt;keyword extraction&lt;/strong&gt;: Tree-sitter scans the grammar for string literals that could collide with identifiers (&lt;code&gt;let&lt;/code&gt;, &lt;code&gt;fn&lt;/code&gt;, &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;, &lt;code&gt;return&lt;/code&gt;) and turns them into keywords that &lt;strong&gt;only match as whole words&lt;/strong&gt;.&lt;br&gt;
Add &lt;code&gt;word&lt;/code&gt; property as a grammar-level setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Abstract categories using &lt;code&gt;supertypes&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Some rules in your grammar will represent &lt;strong&gt;abstract categories&lt;/strong&gt; of syntax nodes, such as "expression", "type", or "declaration". These rules are often defined as simple choices between several other rules. As shown above, in our grammar, the &lt;code&gt;expression&lt;/code&gt; rule is a choice between 13 different rules.&lt;/p&gt;

&lt;p&gt;By default Tree-sitter will generate a visible node type for each of these abstract category rules, which can lead to unnecessarily deep and complex syntax trees. To avoid this you can add these abstract category rules to the grammar's &lt;code&gt;supertypes&lt;/code&gt; definition. Tree-sitter will then treat these rules as &lt;code&gt;supertypes&lt;/code&gt; and will not generate visible node types for them in the syntax tree.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;supertypes&lt;/code&gt; is a cleanup setting, we add it as a grammar-level setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;supertypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's the difference between &lt;code&gt;supertypes&lt;/code&gt; and hidden rules (using &lt;code&gt;_&lt;/code&gt; prefix)?&lt;/strong&gt;&lt;br&gt;
Both &lt;code&gt;supertypes&lt;/code&gt; and hidden rules are used to keep the final syntax tree cleaner, they are both hidden. However, they serve different purposes and are used in distinct ways.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;supertypes&lt;/code&gt; mark abstract categories as conceptual groups, so they don't appear as concrete nodes while hidden rules hide intermediate helper rules that exist only to structure the grammar, not to represent a meaningful language construct.&lt;/p&gt;

&lt;p&gt;A query can target a &lt;code&gt;supertype&lt;/code&gt;, and it will automatically match all of its sub-types. When it comes to hidden rules, since the node doesn't exist in the tree, you can't query for it.&lt;/p&gt;
&lt;h4&gt;
  
  
  Expression precedence (Pratt precedence)
&lt;/h4&gt;

&lt;p&gt;This is the most interesting part of our parser, because the interpreter's Pratt parser and Tree-sitter's GLR parser solve the same problem in completely different ways.&lt;/p&gt;

&lt;p&gt;Let's look at why we need to explicitly define precedence for rules that may cause conflict. Let's say our grammar has the following snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nl"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unary_expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;binary_expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="nx"&gt;unary_expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="nx"&gt;binary_expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This flat structure is highly ambiguous. If we try to generate a parser with the &lt;code&gt;tree-sitter generate&lt;/code&gt; command Tree-sitter gives us an error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Unresolved conflict for symbol sequence:

  '-'  _expression  •  '*'  …

Possible interpretations:

  1:  '-'  (binary_expression  _expression  •  '*'  _expression)
  2:  (unary_expression  '-'  _expression)  •  '*'  …

Possible resolutions:

  1:  Specify a higher precedence in `binary_expression` than in the other rules.
  2:  Specify a higher precedence in `unary_expression` than in the other rules.
  3:  Specify a left or right associativity in `unary_expression`
  4:  Add a conflict for these rules: `binary_expression` `unary_expression`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;•&lt;/code&gt; in the error message indicates where exactly during parsing the conflict occurs. For an expression like &lt;code&gt;-a * b&lt;/code&gt;, it's not clear whether the &lt;code&gt;-&lt;/code&gt; operator applies to the &lt;code&gt;a * b&lt;/code&gt; or just to the &lt;code&gt;a&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is where the &lt;code&gt;prec&lt;/code&gt; function comes into play. By wrapping a rule with &lt;code&gt;prec&lt;/code&gt;, we can indicate that certain sequence of symbols should bind to each other more tightly than others. For example, the &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;$.expression&lt;/code&gt; sequence in &lt;code&gt;unary_expression&lt;/code&gt; should bind more tightly than the &lt;code&gt;$.expression&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;$.expression&lt;/code&gt; sequence in &lt;code&gt;binary_expression&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our interpreter resolves operator precedence &lt;em&gt;in code&lt;/em&gt; at runtime, with a table of constants. They are numbered starting from 1 (&lt;code&gt;LOWEST&lt;/code&gt;) to 8 (&lt;code&gt;INDEX&lt;/code&gt;):&lt;br&gt;
&lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/parser/parser.go#L11" rel="noopener noreferrer"&gt;&lt;code&gt;parser/parser.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;iota&lt;/span&gt;
    &lt;span class="n"&gt;LOWEST&lt;/span&gt;
    &lt;span class="n"&gt;EQUALS&lt;/span&gt;      &lt;span class="c"&gt;// ==&lt;/span&gt;
    &lt;span class="n"&gt;LESSGREATER&lt;/span&gt; &lt;span class="c"&gt;// &amp;gt; or &amp;lt;&lt;/span&gt;
    &lt;span class="n"&gt;SUM&lt;/span&gt;         &lt;span class="c"&gt;// +                &lt;/span&gt;
    &lt;span class="n"&gt;PRODUCT&lt;/span&gt;     &lt;span class="c"&gt;// *&lt;/span&gt;
    &lt;span class="n"&gt;PREFIX&lt;/span&gt;      &lt;span class="c"&gt;// -X or !X&lt;/span&gt;
    &lt;span class="n"&gt;CALL&lt;/span&gt;        &lt;span class="c"&gt;// myFunction(X)&lt;/span&gt;
    &lt;span class="n"&gt;INDEX&lt;/span&gt;       &lt;span class="c"&gt;// array[index]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tree-sitter handles precedence &lt;em&gt;in the grammar&lt;/em&gt;, by attaching &lt;code&gt;prec&lt;/code&gt; and &lt;code&gt;prec.left&lt;/code&gt; to the rules themselves. The mapping is almost one-to-one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;Interpreter constant&lt;/th&gt;
&lt;th&gt;Tree-sitter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;==&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EQUALS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prec.left(1)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LESSGREATER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prec.left(2)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SUM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prec.left(3)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;*&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PRODUCT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prec.left(4)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;-x&lt;/code&gt;, &lt;code&gt;!x&lt;/code&gt; (prefix)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PREFIX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prec(5)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;f(x)&lt;/code&gt; (call)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CALL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prec.left(7)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;a[i]&lt;/code&gt; (index)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;INDEX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prec.left(8)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="c1"&gt;// `prec(number, rule)` function marks the given rule with a numerical precedence, which will be used to resolve LR(1) Conflicts (https://en.wikipedia.org/wiki/LR_parser#Conflicts_in_the_constructed_tables) at parser-generation time.&lt;/span&gt;
  &lt;span class="nl"&gt;unary_expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// `prec.left([number], rule)` function marks the given rule as left-associative (and optionally applies a numerical precedence).&lt;/span&gt;
    &lt;span class="nl"&gt;binary_expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;==&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
      &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;!=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
      &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
      &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
      &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
      &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
      &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
      &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;prec.left(n)&lt;/code&gt; means: this operator binds with precedence &lt;code&gt;n&lt;/code&gt; and is &lt;strong&gt;left-associative&lt;/strong&gt;, so &lt;code&gt;a - b - c&lt;/code&gt; parses as &lt;code&gt;(a - b) - c&lt;/code&gt;, exactly like the interpreter's loop, which keeps parsing while the next token's precedence is higher. Prefix operators use plain &lt;code&gt;prec(n)&lt;/code&gt;, because associativity doesn't apply to them.&lt;/p&gt;

&lt;p&gt;In the Pratt parser the constants only need to be &lt;em&gt;relatively&lt;/em&gt; ordered. Tree-sitter's numbers do the same job, so the two tables line up almost exactly.&lt;/p&gt;

&lt;p&gt;There is one &lt;strong&gt;main difference&lt;/strong&gt; between the two styles worth knowing: the Pratt parser decides how to continue looking forward from an expression, but the GLR parser explores &lt;em&gt;all&lt;/em&gt; parse branches in parallel and only uses precedence to resolve conflicts when they actually arise. That's why for example &lt;code&gt;choice&lt;/code&gt; order doesn't matter in Tree-sitter.&lt;/p&gt;

&lt;h4&gt;
  
  
  Calls, indexing, and the remaining constructs
&lt;/h4&gt;

&lt;p&gt;With operator precedence sorted out, we can finish the remaining Expressions. Calls and indexing are Infix Expressions with the highest binding as seen in the previous section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;call_expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arguments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argument_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)),&lt;/span&gt;

&lt;span class="nx"&gt;index_expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remaining constructs need no new concepts, they are combinations of the primitives we have already seen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_statement&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="nx"&gt;if_expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;if&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;condition&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;consequence&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;else&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alternative&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="nx"&gt;function_literal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parameters&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parameter_list&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="nx"&gt;hash_pair&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. The complete grammar
&lt;/h3&gt;

&lt;p&gt;At this point we've covered all the Tree-sitter concepts that required explanation. The remaining rules are mostly combinations of the same primitives, so we can jump directly to the complete grammar. The final &lt;code&gt;grammar.js&lt;/code&gt; is only &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/tree-sitter-monkey/grammar.js" rel="noopener noreferrer"&gt;~145 lines&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feeding it a complete program shows what we built. Create a file with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let x = 5;
let add = fn(a, b) { return a + b; };
let result = add(3, 4);
let arr = [1, 2, 3, 4];
let person = {"name": "Alice", "age": 30};
let age = person["age"];
if (age &amp;gt; 21) { let status = "adult"; } else { let status = "minor"; }
let makeAdder = fn(x) { return fn(y) { return x + y; }; };
let addFive = makeAdder(5);
addFive(10);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the command &lt;code&gt;tree-sitter parse [your file name]&lt;/code&gt; should output the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(source_file
  (let_statement name: (identifier) value: (integer))
  (let_statement
    name: (identifier)
    value: (function_literal
      parameters: (parameter_list
        name: (identifier)
        name: (identifier))
      body: (block
        (return_statement
          value: (binary_expression
            left: (identifier)
            right: (identifier))))))
  (let_statement
    name: (identifier)
    value: (call_expression
      function: (identifier)
      arguments: (argument_list
        (integer)
        (integer))))
  (let_statement
    name: (identifier)
    value: (array_literal
      (integer) (integer) (integer) (integer)))
  (let_statement
    name: (identifier)
    value: (hash_literal
      (hash_pair
        key: (string)
        value: (string))
      (hash_pair
        key: (string)
        value: (integer))))
  (let_statement
    name: (identifier)
    value: (index_expression
      object: (identifier)
      index: (string)))
  (expression_statement
    (if_expression
      condition: (binary_expression
        left: (identifier)
        right: (integer))
      consequence: (block
        (let_statement
          name: (identifier)
          value: (string)))
      alternative: (block
        (let_statement
          name: (identifier)
          value: (string)))))
  (let_statement
    name: (identifier)
    value: (call_expression
      function: (identifier)
      arguments: (argument_list
        (integer))))
  (expression_statement
    (call_expression
      function: (identifier)
      arguments: (argument_list
        (integer)))))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every construct shows up as a named node, with fields (&lt;code&gt;name:&lt;/code&gt;, &lt;code&gt;value:&lt;/code&gt;, &lt;code&gt;left:&lt;/code&gt;, &lt;code&gt;right:&lt;/code&gt;, &lt;code&gt;condition:&lt;/code&gt;, &lt;code&gt;consequence:&lt;/code&gt;, ...) carrying the same information the interpreter's AST carries in Go.&lt;/p&gt;

&lt;p&gt;The grammar is now complete, but that does not mean that the generated parser outputs the correct syntax tree. Validation is our next undertaking. &lt;/p&gt;




&lt;h2&gt;
  
  
  The corpus as executable specification
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tree-sitter generate&lt;/code&gt; compiles the grammar into a C parser, and &lt;code&gt;tree-sitter test&lt;/code&gt; runs the corpus (test cases). There are 70 test cases in the &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/tree/main/tree-sitter-monkey/test/corpus" rel="noopener noreferrer"&gt;project&lt;/a&gt; written alongside the grammar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total parses: 70; successful parses: 70; failed parses: 0; success percentage: 100.00%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The corpus is the spec.&lt;/strong&gt; Because each test includes an input source code and the expected output tree, it also serves as documentation of the language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error recovery&lt;/strong&gt;: Tree-sitter's superpower for editors is its behavior on broken code. Instead of stopping the parsing task, the parser emits an &lt;code&gt;ERROR&lt;/code&gt; node and continues its parsing. The tree stays mostly correct while you type. We test these cases in the corpus with the &lt;code&gt;:error&lt;/code&gt; directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;===========================
Integer as let name
:error
===========================

let 5 = x;

---

============================
Missing value
:error
============================

let x = ;

---

============================
Unterminated list
:error
============================

[1, 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests assert that the input contains a syntax error while still allowing Tree-sitter to produce a useful tree around it. This is why highlighting stays useful in an editor. Code is broken most of the time while we're writing it, and the parser still produces a useful tree around the errors.&lt;/p&gt;

&lt;p&gt;At this point we have a parser that can turn Monkey source code into a syntax tree, and we can test that parser against a corpus to verify its validity. But our original goal wasn't just to parse Monkey, we wanted an editor to understand it well enough to highlight it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Turning the parsed tree into highlighting
&lt;/h2&gt;

&lt;p&gt;To achieve our main goal of syntax highlighting, we need to write queries that match nodes in the syntax tree and assign them &lt;em&gt;captures&lt;/em&gt;, those are names that Neovim understands and maps to highlight groups it has already defined.&lt;/p&gt;

&lt;p&gt;You can find the list of standard captures supported by Neovim in their &lt;a href="https://neovim.io/doc/user/treesitter/#treesitter-highlight-groups" rel="noopener noreferrer"&gt;Tree-sitter docs&lt;/a&gt;. These include captures such as &lt;code&gt;@function&lt;/code&gt;, &lt;code&gt;@variable&lt;/code&gt;, &lt;code&gt;@keyword&lt;/code&gt;, and &lt;code&gt;@string&lt;/code&gt;, which are highlighted according to the current colorscheme.&lt;/p&gt;

&lt;p&gt;The queries assigned to captures live at &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/tree-sitter-monkey/queries/highlights.scm" rel="noopener noreferrer"&gt;&lt;code&gt;queries/highlights.scm&lt;/code&gt;&lt;/a&gt;. The ordering inside it matters, more specific patterns need to come before generic ones. But we will set priorities using &lt;code&gt;#set! priority N&lt;/code&gt; to be more explicit. Higher numbers win, which lets us keep related patterns grouped together instead of carefully ordering every rule.&lt;/p&gt;

&lt;p&gt;In our case, we have different types of functions and identifiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scheme"&gt;&lt;code&gt;&lt;span class="c1"&gt;; User-defined function calls&lt;/span&gt;
&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;call_expression&lt;/span&gt;
  &lt;span class="nv"&gt;function:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;@function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;set!&lt;/span&gt; &lt;span class="nv"&gt;priority&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;; Built-in function calls&lt;/span&gt;
&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;call_expression&lt;/span&gt;
  &lt;span class="nv"&gt;function:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;@function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;builtin&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;any-of?&lt;/span&gt; &lt;span class="nv"&gt;@function&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;builtin&lt;/span&gt; &lt;span class="s"&gt;"len"&lt;/span&gt; &lt;span class="s"&gt;"first"&lt;/span&gt; &lt;span class="s"&gt;"last"&lt;/span&gt; &lt;span class="s"&gt;"rest"&lt;/span&gt; &lt;span class="s"&gt;"push"&lt;/span&gt; &lt;span class="s"&gt;"puts"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;set!&lt;/span&gt; &lt;span class="nv"&gt;priority&lt;/span&gt; &lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;; Function parameters&lt;/span&gt;
&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;function_literal&lt;/span&gt;
  &lt;span class="nv"&gt;parameters:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parameter_list&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;@variable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;parameter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;set!&lt;/span&gt; &lt;span class="nv"&gt;priority&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;; Hash keys are properties (strings and bare identifiers)&lt;/span&gt;
&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;hash_pair&lt;/span&gt;
  &lt;span class="nv"&gt;key:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;@property&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;set!&lt;/span&gt; &lt;span class="nv"&gt;priority&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;hash_pair&lt;/span&gt;
  &lt;span class="nv"&gt;key:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;@property&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;set!&lt;/span&gt; &lt;span class="nv"&gt;priority&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;; All-caps identifiers are constants by convention, which is a convention from other languages - Monkey has no reassignment, so every variable is effectively constant.&lt;/span&gt;
&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;@constant&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;match?&lt;/span&gt; &lt;span class="nv"&gt;@constant&lt;/span&gt; &lt;span class="s"&gt;"^[A-Z][A-Z_]*$"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;set!&lt;/span&gt; &lt;span class="nv"&gt;priority&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;; more captures ...&lt;/span&gt;

&lt;span class="c1"&gt;; Catch-all: every other identifier is a variable&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;@variable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two &lt;a href="https://tree-sitter.github.io/tree-sitter/using-parsers/queries/3-predicates-and-directives.html" rel="noopener noreferrer"&gt;predicates&lt;/a&gt; do the heavy lifting here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#any-of?&lt;/code&gt;&lt;/strong&gt; checks the node's text against a list, that's how &lt;code&gt;len&lt;/code&gt; and other builtin functions become &lt;code&gt;@function.builtin&lt;/code&gt; while every other callee is &lt;code&gt;@function.call&lt;/code&gt;. Monkey has no named function definitions (functions assigned to a variable with &lt;code&gt;let&lt;/code&gt;), so &lt;code&gt;@function&lt;/code&gt; itself stays unused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#match?&lt;/code&gt;&lt;/strong&gt; applies a regex, all-caps identifiers become &lt;code&gt;@constant&lt;/code&gt; by convention, before the catch-all below turns everything else into &lt;code&gt;@variable&lt;/code&gt;. The Monkey language does not have the concept of constant variables, this is just for convention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest is a straightforward mapping of literals and keywords:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scheme"&gt;&lt;code&gt;&lt;span class="c1"&gt;; Keywords&lt;/span&gt;
&lt;span class="s"&gt;"let"&lt;/span&gt; &lt;span class="nv"&gt;@keyword&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"if"&lt;/span&gt;
  &lt;span class="s"&gt;"else"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;@keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;conditional&lt;/span&gt;

&lt;span class="s"&gt;"return"&lt;/span&gt; &lt;span class="nv"&gt;@keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;return&lt;/span&gt;

&lt;span class="s"&gt;"fn"&lt;/span&gt; &lt;span class="nv"&gt;@keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;function&lt;/span&gt;

&lt;span class="c1"&gt;; Literals&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="s"&gt;"false"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;@boolean&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;@number&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;@string&lt;/span&gt;

&lt;span class="c1"&gt;; ... more&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keywords and literals map directly, and the rest of &lt;a href="https://github.com/SegniAT/monkey-language-interpreter/blob/main/tree-sitter-monkey/queries/highlights.scm" rel="noopener noreferrer"&gt;&lt;code&gt;queries/highlights.scm&lt;/code&gt;&lt;/a&gt; covers operators, brackets, and delimiters (&lt;code&gt;,&lt;/code&gt;, &lt;code&gt;;&lt;/code&gt;, &lt;code&gt;:&lt;/code&gt;). Check out the file to explore all the captures. The full list is 18 captures which is tiny compared to languages like TypeScript or Rust for example.&lt;/p&gt;

&lt;p&gt;We now have both pieces we need to finalize our project: a parser that produces the tree and highlight queries that turn nodes into captures. The final step is connecting that grammar to Neovim so it knows when to use the parser for .monkey files.&lt;/p&gt;




&lt;h2&gt;
  
  
  Neovim integration and showcase
&lt;/h2&gt;

&lt;p&gt;Now comes our final task, i.e. editor integration. We will follow the &lt;a href="https://github.com/nvim-treesitter/nvim-treesitter/blob/main/README.md" rel="noopener noreferrer"&gt;nvim-treesitter README.md&lt;/a&gt;, "Adding custom languages" section. &lt;/p&gt;

&lt;p&gt;First, we need to let Neovim know that &lt;code&gt;.monkey&lt;/code&gt; files are a thing, in our configuration file we add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;monkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"monkey"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to register the parser with &lt;code&gt;nvim-treesitter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_autocmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'User'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'TSUpdate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nvim-treesitter.parsers'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;monkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;install_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://github.com/SegniAT/monkey-language-interpreter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tree-sitter-monkey'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tree-sitter-monkey/queries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part is the monorepo layout: our grammar lives &lt;em&gt;inside&lt;/em&gt; the interpreter's repository. nvim-treesitter downloads the repo tarball from &lt;code&gt;url&lt;/code&gt;, builds the grammar found at &lt;code&gt;location&lt;/code&gt;, and installs the queries found at &lt;code&gt;queries&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This works, but it isn't really ideal since the grammar is only one directory inside a much larger repository of the Monkey interpreter (and soon, the LSP as well)! So installing the parser means downloading the entire interpreter repository.&lt;/p&gt;

&lt;p&gt;After this, the update loop is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tree-sitter generate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; tree-sitter &lt;span class="nb"&gt;test&lt;/span&gt;   &lt;span class="c"&gt;# locally&lt;/span&gt;
git commit &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push                     &lt;span class="c"&gt;# to the repo&lt;/span&gt;
:TSUpdate monkey                           &lt;span class="c"&gt;# in Neovim&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's enough to get the whole pipeline working. We can now open a .monkey file in Neovim, have Tree-sitter parse it, apply our queries, and get syntax highlighting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdfp6f74ffdg3h76sl8zb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdfp6f74ffdg3h76sl8zb.png" alt="Monkey code before syntax highlighting" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; (&lt;code&gt;tokyonight-night&lt;/code&gt; theme)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feij9r6vdk2v1o1punn89.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feij9r6vdk2v1o1punn89.png" alt="Monkey code after syntax highlighting" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Beautiful!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A look at the generated tree by the parser using &lt;code&gt;:InspectTree&lt;/code&gt; in Neovim:&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/ezHMVNos348" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;A few queries on the generated tree using &lt;code&gt;:EditQuery&lt;/code&gt; in Neovim:&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/ycQNCGB2c9g" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;To add syntax highlighting and make our custom language prettier to look at and more convenient to work with, we had to go through a whole bunch of fun challenges: a hand-written grammar, a corpus that also serves as a spec, queries that map the tree to editor semantics, and finally editor integration.&lt;/p&gt;

&lt;p&gt;The biggest lesson: &lt;strong&gt;the grammar is an executable spec and the corpus is its test suite&lt;/strong&gt;. Together they make the language's actual behavior much harder to misunderstand than documentation alone.&lt;/p&gt;

&lt;p&gt;A second lesson: &lt;strong&gt;a toy language is still a precise language.&lt;/strong&gt; Monkey's grammar fits in ~145 lines, yet every rule had to be right: precedence levels, hidden rules, keyword extraction, fields, this is the same machinery that parses Go or Zig, just much smaller.&lt;/p&gt;

&lt;p&gt;But syntax highlighting is only the beginning. We can now tell the editor what a piece of code looks like, but not much about what it means. The next step is to teach the editor about Monkey itself: diagnostics, definitions, completion, and eventually the other features we expect from a modern development environment. That's where the LSP comes in, in our second entry the series.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://interpreterbook.com/" rel="noopener noreferrer"&gt;Writing An Interpreter In Go&lt;/a&gt; by Thorsten Ball&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="noopener noreferrer"&gt;Tree-sitter documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/SegniAT/monkey-language-interpreter" rel="noopener noreferrer"&gt;The Monkey interpreter + tree-sitter grammar repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/09-9LltqWLY" rel="noopener noreferrer"&gt;TJ DeVries - Tree-sitter explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/a1rC79DHpmY" rel="noopener noreferrer"&gt;Tree-sitter: a new parsing system for programming tools - GitHub Universe 2017&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>treesitter</category>
      <category>syntaxhighlighting</category>
      <category>customlanguage</category>
    </item>
  </channel>
</rss>
