<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ast on Roberto Selbach</title><link>https://rselbach.com/tags/ast/</link><description>Recent content in Ast on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Tue, 22 Jan 2013 13:48:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/ast/index.xml" rel="self" type="application/rss+xml"/><item><title>gofmt is an AST program</title><link>https://rselbach.com/gofmt-is-an-ast-program/</link><pubDate>Tue, 22 Jan 2013 13:48:00 +0000</pubDate><guid>https://rselbach.com/gofmt-is-an-ast-program/</guid><description>&lt;p&gt;I wanted to rename a field across a pile of Go files. My first impulse was a regular expression. My second, considerably healthier impulse was to remember that Go ships with a parser.&lt;/p&gt;
&lt;p&gt;Source code is text, but edits usually concern syntax. The &lt;code&gt;go/parser&lt;/code&gt; package reads source into nodes from &lt;code&gt;go/ast&lt;/code&gt;. An identifier is an &lt;code&gt;*ast.Ident&lt;/code&gt;; a function declaration is an &lt;code&gt;*ast.FuncDecl&lt;/code&gt;; an expression has structure beyond where its parentheses happened to land. Once the source is a tree, a tool can ask precise questions instead of hoping a comment does not look executable.&lt;/p&gt;
&lt;p&gt;Here is a tiny program that lists function names:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;go/ast&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;go/parser&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#e6db74"&gt;&amp;#34;go/token&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;fset&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;token&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewFileSet&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;parser&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ParseFile&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;fset&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;sample.go&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		panic(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;ast&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Inspect&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ast&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Node&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.(&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ast&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;FuncDecl&lt;/span&gt;); &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Name&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Name&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;token.FileSet&lt;/code&gt; connects tree positions to files, lines, and columns. It looks like administrative furniture until an error must say exactly where it found something. Then it becomes the only chair in the room.&lt;/p&gt;
&lt;p&gt;After changing a tree, &lt;code&gt;go/printer&lt;/code&gt; can write Go source in the standard layout. This is the important lesson of &lt;code&gt;gofmt&lt;/code&gt;: parse syntax, then print it consistently. It is not lining up text with an especially ambitious set of regular expressions.&lt;/p&gt;
&lt;p&gt;Comments make transformations subtler. Parsing with &lt;code&gt;parser.ParseComments&lt;/code&gt; retains comment groups, but moving nodes while preserving the intended comment association still requires care. Imports are another edge. A rename may change whether an import is used, and blindly printing the tree does not make semantic mistakes disappear.&lt;/p&gt;
&lt;p&gt;There is also a limit to what the AST alone knows. The syntax tree can tell me that &lt;code&gt;x.Name&lt;/code&gt; is a selector expression. It cannot, by itself, tell me which package or type supplied &lt;code&gt;Name&lt;/code&gt;. That needs type information. For a local mechanical transformation with an unambiguous syntactic shape, the AST is often enough. For a semantic rename, it may not be.&lt;/p&gt;
&lt;p&gt;My field change ended up being a dozen lines around &lt;code&gt;ast.Inspect&lt;/code&gt;, followed by formatting and a review of the diff. That was more code than a one-line substitution and much less code than repairing a one-line substitution after it renamed examples, comments, and an unrelated field with the same spelling.&lt;/p&gt;
&lt;p&gt;I used to think &lt;code&gt;gofmt&lt;/code&gt; was mainly a successful ceasefire in the whitespace wars. It also shows the value of shipping language tools as ordinary libraries: the compiler is not the only program allowed to understand Go source.&lt;/p&gt;</description></item></channel></rss>