View on GitHub

Dexter's Blog

A Short Guide to Adding a Keyword to Go

13 Jan 2018

Go is a programming language.

Sometimes code looks cleaner when variable declarations are listed after code that uses them. Imagine writing Go in this style.

$ cat test.go
package main

import (
  "fmt"
  "strconv"
  )

func main() {
  fmt.Println("hi")
  fmt.Println(f(a))

  andwhere a = 1
  andwhere f = func(a int) string { return strconv.Itoa(a+1) }
}
$ ./bin/go run test.go
hi
2

Below are the modifications I made to the Go compiler.

--- a/src/cmd/compile/internal/syntax/tokens.go
+++ b/src/cmd/compile/internal/syntax/tokens.go
@@ -46,6 +46,7 @@ const (
        _Continue
        _Default
        _Defer
+       _Andwhere
        _Else
        _Fallthrough
        _For
@@ -118,6 +119,7 @@ var tokstrings = [...]string{
        _Continue:    "continue",
        _Default:     "default",
        _Defer:       "defer",
+       _Andwhere:    "andwhere",
        _Else:        "else",
        _Fallthrough: "fallthrough",
        _For:         "for",
--- a/src/cmd/compile/internal/syntax/nodes.go
+++ b/src/cmd/compile/internal/syntax/nodes.go
@@ -371,6 +371,11 @@ type (
                stmt
        }

+       Andwhere struct {
+               S *DeclStmt
+               stmt
+       }
+
        ReturnStmt struct {
                Results Expr // nil means no explicit return values
                stmt[
--- a/src/cmd/compile/internal/syntax/parser.go
+++ b/src/cmd/compile/internal/syntax/parser.go
@@ -2034,6 +2053,11 @@ func (p *parser) stmtOrNil() Stmt {
        case _Go, _Defer:
                return p.callStmt()

+       case _Andwhere:
+               s := new(Andwhere)
+               s.S = p.declStmt(p.varDecl)
+               return s
+
        case _Goto:
                s := new(BranchStmt)
                s.pos = p.pos()
@@ -542,9 +543,27 @@ func (p *parser) funcDeclOrNil() *FuncDecl {
        }
        f.Pragma = p.pragma

+       // Lift all Andwhere to the top
+       if f.Body != nil {
+               f.Body.List = lift_andwheres(f.Body.List)
+       }
+
        return f
 }

+func lift_andwheres(list []Stmt) []Stmt {
+       for i, x := range list {
+               switch x.(type) {
+               case *Andwhere:
+                       removed := append(list[:i], list[i+1:]...)
+                       modified := x.(*Andwhere).S
+                       prepended := append([]Stmt{modified}, removed...)
+                       return lift_andwheres(prepended)
+               }
+       }
+       return list
+}
+
 func (p *parser) funcBody() *BlockStmt {
        p.fnest++
        errcnt := p.errcnt