πŸ”„ Exercise 3: Multiple “go” Keywords - Parser Enhancement

In this exercise, you’ll modify the Go parser to accept multiple consecutive “go” keywords for starting goroutines! πŸš€ This will teach you how to enhance parser logic to handle repetitive syntax patterns while maintaining the same semantic behavior.

🎯 Learning Objectives

By the end of this exercise, you will:

πŸ” Step 1: Navigate to the Parser

cd go/src/cmd/compile/internal/syntax

πŸ”‘ Understanding the Current Parser Logic

Let’s examine how the parser currently handles the “go” statement in parser.go. Look around line 2675:

// go/src/cmd/compile/internal/syntax/parser.go:2673-2676
...
return s

case _Go, _Defer:
    return p.callStmt()
...

The parser recognizes _Go token and immediately calls p.callStmt() to handle the goroutine creation.

Find the callStmt() method in parser.go at line 977. This is where we’ll add our multiple “go” logic:

// go/src/cmd/compile/internal/syntax/parser.go:976-985
// callStmt parses call-like statements that can be preceded by 'defer' and 'go'.
func (p *parser) callStmt() *CallStmt {
    if trace {
        defer p.trace("callStmt")()
    }

    s := new(CallStmt)
    s.pos = p.pos()
    s.Tok = p.tok // _Defer or _Go
    p.next()
    ...
}

The key line is s.Tok = p.tok which captures whether this is a “defer” or “go” statement, followed by p.next() which consumes the token.

Step 2: Add Multiple “go” Support

We need to modify the callStmt() method to consume multiple consecutive “go” tokens while preserving the same semantic meaning.

Edit parser.go:

Find line 985 where p.next() is called and add our multiple “go” logic right after it:

// go/src/cmd/compile/internal/syntax/parser.go:982-990
s := new(CallStmt)
s.pos = p.pos()
s.Tok = p.tok // _Defer or _Go
p.next()

// Allow multiple consecutive "go" keywords (go go go ...)
if s.Tok == _Go {
    for p.tok == _Go {
        p.next()
    }
}

...

πŸ”§ Understanding the Code Change

Step 3: Rebuild the Compiler

Now let’s rebuild the Go toolchain with our changes:

cd ../../../  # back to go/src
./make.bash

If there are any compilation errors, review your changes and fix them.

Step 4: Test Multiple “go” Keywords

Create a test program to verify our multiple “go” syntax works:

mkdir -p /tmp/multiple-go-test
cd /tmp/multiple-go-test

Create a test.go file:

package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    fmt.Printf("Hello from %s!\n", name)
}

func main() {
    fmt.Println("Testing multiple go keywords...")

    // Test regular single go
    go sayHello("single go")

    // Test double go
    go go sayHello("double go")

    // Test triple go
    go go go sayHello("triple go")

    // Test quadruple go
    go go go go sayHello("quadruple go")

    // Wait a bit to see output
    time.Sleep(100 * time.Millisecond)
    fmt.Println("All done!")
}

Execute the test program with your custom Go:

/path/to/workshop/go/bin/go run test.go

You should see output like: ✨

Testing multiple go keywords...
Hello from single go!
Hello from double go!
Hello from triple go!
Hello from quadruple go!
All done!

Step 5: Run Parser Tests

Let’s make sure we didn’t break the parser:

cd /path/to/workshop/go/src
../bin/go test cmd/compile/internal/syntax -short

Understanding What We Did

  1. Parser Enhancement: Modified callStmt() to handle multiple consecutive “go” tokens
  2. Token Consumption: Added a loop to consume additional “go” tokens after the first one
  3. Semantic Preservation: Multiple “go” keywords still create exactly one goroutine
  4. Targeted Change: Only affects “go” statements, not “defer” statements

πŸŽ“ What We Learned

πŸ’‘ Extension Ideas

Try these additional modifications: πŸš€

  1. βž• Add similar support for “defer defer defer” (more challenging!)
  2. βž• Add a maximum limit (e.g., max 5 consecutive “go” keywords)
  3. πŸ“Š Track how many “go” keywords were used for debugging
  4. 🎨 Make the multiple keywords affect goroutine priority

➑️ Next Steps

Excellent work! πŸŽ‰ You’ve successfully enhanced Go’s parser to handle repetitive syntax patterns.

In Exercise 4: Compiler Inlining Parameters, we’ll shift focus to explore how Go’s compiler optimization works, learning to tune inlining parameters for binary size control.

Cleanup

To restore the original Go source:

cd /path/to/workshop/go/src/cmd/compile/internal/syntax
git checkout parser.go
cd ../../../
./make.bash  # Rebuild with original code

Summary

Multiple “go” keywords now work for starting goroutines:

// These are all equivalent and create exactly one goroutine:
go myFunction()
go go myFunction()
go go go myFunction() 
go go go go myFunction()

// The parser consumes all consecutive "go" tokens
// but the semantic behavior remains the same!

This exercise demonstrated how parser-level modifications can add expressive syntactic sugar while preserving the underlying language semantics! πŸš€βœ¨


Continue to Exercise 4 or return to the main workshop