<?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 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>
