Sure, you can do it this way:
package main
import "fmt"
func main() {
fmt.Printf("Hello world.\n")
}
But that's no fun, is it?
"Give a clever engineer a straightforward problem and they'll add complexity until it's interesting enough to solve."Let's write a program that writes hello.go for us! That's right: write go, using go.
(Sure, you could just write a program that echos the above source text as a quoted string to stdout, but that's not much fun. We must add more complexity to make it interesting.)
The "go/ast" package is used by gofmt and gofix (and various other tools) to parse and manipulate go source code in the form of abstract syntax trees.
We're going to build a new go program from scratch, in go, so we're not really interested in parsing so much as constructing an AST and printing the result as human-readable source code.
Here's a skeleton that uses go/ast to construct an AST for a very minimal go program that compiles, but doesn't actually do anything:
package main
import (
"bytes"
"fmt"
"go/ast"
"go/printer"
"go/token"
)
func main() {
// Start with a file
f := &ast.File{
Name: &ast.Ident{
// The package name is "main"
Name: "main",
},
// Top-level declarations in this file:
Decls: []ast.Decl{
// A basic func declaration with no receiver:
&ast.FuncDecl{
Name: &ast.Ident{
// This func is named "main"
Name: "main",
},
// With an empty func type (no params, no returns)
Type: &ast.FuncType{},
// And an empty body.
Body: &ast.BlockStmt{},
},
},
}
fset := token.NewFileSet()
var buf bytes.Buffer
printer.Fprint(&buf, fset, f)
fmt.Printf("%s\n", buf.String())
}
Try it out on play.golang.org here. It produces the following:
package main
func main() {
}
Which does compile, but doesn't actually do anything. Let's add the next pieces: The import statement for "fmt" and the fmt.Printf statement that actually prints "Hello world."
To add the import statement, add a new element to f.Decls:
// Start an "import" declaration
&ast.GenDecl{
Tok: token.IMPORT,
Specs: []ast.Spec{
&ast.ImportSpec{
// With a string literal of "fmt"
Path: &ast.BasicLit{
Kind: token.STRING,
// Note the "" contained in ``
Value: `"fmt"`,
},
},
},
},
If you leave f.Decls as it is here and run it, it will produce the following:
package main
import "fmt"
func main() {
}
Which will fail to compile because "fmt" is unused. So let's use it by adding the fmt.Printf statement inside the body of main(). Change the empty Body: &ast.BlockStmt{} in the above skeleton to include some ast.Stmts:
Body: &ast.BlockStmt{
List: []ast.Stmt{
// Start a stand-alone expression statement
&ast.ExprStmt{
// Representing a function call to "fmt"
X: &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{
Name: "fmt",
},
// With a selector for Printf
Sel: &ast.Ident{
Name: "Printf",
},
},
// And a single-element arg list consisting of a string literal
Args: []ast.Expr{
&ast.BasicLit{
Kind: token.STRING,
Value: `"Hello world.\n"`,
},
},
},
},
},
},
This will finally produce a runnable hello world:
package main
import "fmt"
func main() {
fmt.Printf("Hello world.\n")
}
Try the final product out on play.golang.org here.
In conclusion, if you'd like to do some code generation with go, this might not be a bad place to start. You can explore other parts of go/ast by adding some extra function declarations with actual parameter lists, return values and even receiver types, and then calling them from main.
If I was really bored, I'd write a further iteration of this program that constructs itself :)
If I was really bored, I'd write a further iteration of this program that constructs itself :)