go/src/cmd/asm/internal/lex/input.go
Bryan C. Mills 2c92b2349a internal/buildcfg: extract logic specific to cmd/go
cmd/go/internal/cfg duplicates many of the fields of
internal/buildcfg, but initializes them from a Go environment file in
addition to the usual process environment.

internal/buildcfg doesn't (and shouldn't) know or care about that
environment file, but prior to this CL it exposed hooks for
cmd/go/internal/cfg to write data back to internal/buildcfg to
incorporate information from the file. It also produced quirky
GOEXPERIMENT strings when a non-trivial default was overridden,
seemingly so that 'go env' would produce those same quirky strings in
edge-cases where they are needed.

This change reverses that information flow: internal/buildcfg now
exports a structured type with methods — instead of top-level
functions communicating through global state — so that cmd/go can
utilize its marshaling and unmarshaling functionality without also
needing to write results back into buildcfg package state.

The quirks specific to 'go env' have been eliminated by distinguishing
between the raw GOEXPERIMENT value set by the user (which is what we
should report from 'go env') and the cleaned, canonical equivalent
(which is what we should use in the build cache key).

For #51461.

Change-Id: I4ef5b7c58b1fb3468497649a6d2fb6c19aa06c70
Reviewed-on: https://go-review.googlesource.com/c/go/+/393574
Trust: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 15:35:06 +00:00

502 lines
13 KiB
Go

// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lex
import (
"fmt"
"internal/buildcfg"
"os"
"path/filepath"
"strconv"
"strings"
"text/scanner"
"cmd/asm/internal/flags"
"cmd/internal/objabi"
"cmd/internal/src"
)
// Input is the main input: a stack of readers and some macro definitions.
// It also handles #include processing (by pushing onto the input stack)
// and parses and instantiates macro definitions.
type Input struct {
Stack
includes []string
beginningOfLine bool
ifdefStack []bool
macros map[string]*Macro
text string // Text of last token returned by Next.
peek bool
peekToken ScanToken
peekText string
}
// NewInput returns an Input from the given path.
func NewInput(name string) *Input {
return &Input{
// include directories: look in source dir, then -I directories.
includes: append([]string{filepath.Dir(name)}, flags.I...),
beginningOfLine: true,
macros: predefine(flags.D),
}
}
// predefine installs the macros set by the -D flag on the command line.
func predefine(defines flags.MultiFlag) map[string]*Macro {
macros := make(map[string]*Macro)
// Set macros for GOEXPERIMENTs so we can easily switch
// runtime assembly code based on them.
if *flags.CompilingRuntime {
for _, exp := range buildcfg.Experiment.Enabled() {
// Define macro.
name := "GOEXPERIMENT_" + exp
macros[name] = &Macro{
name: name,
args: nil,
tokens: Tokenize("1"),
}
}
}
for _, name := range defines {
value := "1"
i := strings.IndexRune(name, '=')
if i > 0 {
name, value = name[:i], name[i+1:]
}
tokens := Tokenize(name)
if len(tokens) != 1 || tokens[0].ScanToken != scanner.Ident {
fmt.Fprintf(os.Stderr, "asm: parsing -D: %q is not a valid identifier name\n", tokens[0])
flags.Usage()
}
macros[name] = &Macro{
name: name,
args: nil,
tokens: Tokenize(value),
}
}
return macros
}
var panicOnError bool // For testing.
func (in *Input) Error(args ...interface{}) {
if panicOnError {
panic(fmt.Errorf("%s:%d: %s", in.File(), in.Line(), fmt.Sprintln(args...)))
}
fmt.Fprintf(os.Stderr, "%s:%d: %s", in.File(), in.Line(), fmt.Sprintln(args...))
os.Exit(1)
}
// expectText is like Error but adds "got XXX" where XXX is a quoted representation of the most recent token.
func (in *Input) expectText(args ...interface{}) {
in.Error(append(args, "; got", strconv.Quote(in.Stack.Text()))...)
}
// enabled reports whether the input is enabled by an ifdef, or is at the top level.
func (in *Input) enabled() bool {
return len(in.ifdefStack) == 0 || in.ifdefStack[len(in.ifdefStack)-1]
}
func (in *Input) expectNewline(directive string) {
tok := in.Stack.Next()
if tok != '\n' {
in.expectText("expected newline after", directive)
}
}
func (in *Input) Next() ScanToken {
if in.peek {
in.peek = false
tok := in.peekToken
in.text = in.peekText
return tok
}
// If we cannot generate a token after 100 macro invocations, we're in trouble.
// The usual case is caught by Push, below, but be safe.
for nesting := 0; nesting < 100; {
tok := in.Stack.Next()
switch tok {
case '#':
if !in.beginningOfLine {
in.Error("'#' must be first item on line")
}
in.beginningOfLine = in.hash()
in.text = "#"
return '#'
case scanner.Ident:
// Is it a macro name?
name := in.Stack.Text()
macro := in.macros[name]
if macro != nil {
nesting++
in.invokeMacro(macro)
continue
}
fallthrough
default:
if tok == scanner.EOF && len(in.ifdefStack) > 0 {
// We're skipping text but have run out of input with no #endif.
in.Error("unclosed #ifdef or #ifndef")
}
in.beginningOfLine = tok == '\n'
if in.enabled() {
in.text = in.Stack.Text()
return tok
}
}
}
in.Error("recursive macro invocation")
return 0
}
func (in *Input) Text() string {
return in.text
}
// hash processes a # preprocessor directive. It reports whether it completes.
func (in *Input) hash() bool {
// We have a '#'; it must be followed by a known word (define, include, etc.).
tok := in.Stack.Next()
if tok != scanner.Ident {
in.expectText("expected identifier after '#'")
}
if !in.enabled() {
// Can only start including again if we are at #else or #endif but also
// need to keep track of nested #if[n]defs.
// We let #line through because it might affect errors.
switch in.Stack.Text() {
case "else", "endif", "ifdef", "ifndef", "line":
// Press on.
default:
return false
}
}
switch in.Stack.Text() {
case "define":
in.define()
case "else":
in.else_()
case "endif":
in.endif()
case "ifdef":
in.ifdef(true)
case "ifndef":
in.ifdef(false)
case "include":
in.include()
case "line":
in.line()
case "undef":
in.undef()
default:
in.Error("unexpected token after '#':", in.Stack.Text())
}
return true
}
// macroName returns the name for the macro being referenced.
func (in *Input) macroName() string {
// We use the Stack's input method; no macro processing at this stage.
tok := in.Stack.Next()
if tok != scanner.Ident {
in.expectText("expected identifier after # directive")
}
// Name is alphanumeric by definition.
return in.Stack.Text()
}
// #define processing.
func (in *Input) define() {
name := in.macroName()
args, tokens := in.macroDefinition(name)
in.defineMacro(name, args, tokens)
}
// defineMacro stores the macro definition in the Input.
func (in *Input) defineMacro(name string, args []string, tokens []Token) {
if in.macros[name] != nil {
in.Error("redefinition of macro:", name)
}
in.macros[name] = &Macro{
name: name,
args: args,
tokens: tokens,
}
}
// macroDefinition returns the list of formals and the tokens of the definition.
// The argument list is nil for no parens on the definition; otherwise a list of
// formal argument names.
func (in *Input) macroDefinition(name string) ([]string, []Token) {
prevCol := in.Stack.Col()
tok := in.Stack.Next()
if tok == '\n' || tok == scanner.EOF {
return nil, nil // No definition for macro
}
var args []string
// The C preprocessor treats
// #define A(x)
// and
// #define A (x)
// distinctly: the first is a macro with arguments, the second without.
// Distinguish these cases using the column number, since we don't
// see the space itself. Note that text/scanner reports the position at the
// end of the token. It's where you are now, and you just read this token.
if tok == '(' && in.Stack.Col() == prevCol+1 {
// Macro has arguments. Scan list of formals.
acceptArg := true
args = []string{} // Zero length but not nil.
Loop:
for {
tok = in.Stack.Next()
switch tok {
case ')':
tok = in.Stack.Next() // First token of macro definition.
break Loop
case ',':
if acceptArg {
in.Error("bad syntax in definition for macro:", name)
}
acceptArg = true
case scanner.Ident:
if !acceptArg {
in.Error("bad syntax in definition for macro:", name)
}
arg := in.Stack.Text()
if i := lookup(args, arg); i >= 0 {
in.Error("duplicate argument", arg, "in definition for macro:", name)
}
args = append(args, arg)
acceptArg = false
default:
in.Error("bad definition for macro:", name)
}
}
}
var tokens []Token
// Scan to newline. Backslashes escape newlines.
for tok != '\n' {
if tok == scanner.EOF {
in.Error("missing newline in definition for macro:", name)
}
if tok == '\\' {
tok = in.Stack.Next()
if tok != '\n' && tok != '\\' {
in.Error(`can only escape \ or \n in definition for macro:`, name)
}
}
tokens = append(tokens, Make(tok, in.Stack.Text()))
tok = in.Stack.Next()
}
return args, tokens
}
func lookup(args []string, arg string) int {
for i, a := range args {
if a == arg {
return i
}
}
return -1
}
// invokeMacro pushes onto the input Stack a Slice that holds the macro definition with the actual
// parameters substituted for the formals.
// Invoking a macro does not touch the PC/line history.
func (in *Input) invokeMacro(macro *Macro) {
// If the macro has no arguments, just substitute the text.
if macro.args == nil {
in.Push(NewSlice(in.Base(), in.Line(), macro.tokens))
return
}
tok := in.Stack.Next()
if tok != '(' {
// If the macro has arguments but is invoked without them, all we push is the macro name.
// First, put back the token.
in.peekToken = tok
in.peekText = in.text
in.peek = true
in.Push(NewSlice(in.Base(), in.Line(), []Token{Make(macroName, macro.name)}))
return
}
actuals := in.argsFor(macro)
var tokens []Token
for _, tok := range macro.tokens {
if tok.ScanToken != scanner.Ident {
tokens = append(tokens, tok)
continue
}
substitution := actuals[tok.text]
if substitution == nil {
tokens = append(tokens, tok)
continue
}
tokens = append(tokens, substitution...)
}
in.Push(NewSlice(in.Base(), in.Line(), tokens))
}
// argsFor returns a map from formal name to actual value for this argumented macro invocation.
// The opening parenthesis has been absorbed.
func (in *Input) argsFor(macro *Macro) map[string][]Token {
var args [][]Token
// One macro argument per iteration. Collect them all and check counts afterwards.
for argNum := 0; ; argNum++ {
tokens, tok := in.collectArgument(macro)
args = append(args, tokens)
if tok == ')' {
break
}
}
// Zero-argument macros are tricky.
if len(macro.args) == 0 && len(args) == 1 && args[0] == nil {
args = nil
} else if len(args) != len(macro.args) {
in.Error("wrong arg count for macro", macro.name)
}
argMap := make(map[string][]Token)
for i, arg := range args {
argMap[macro.args[i]] = arg
}
return argMap
}
// collectArgument returns the actual tokens for a single argument of a macro.
// It also returns the token that terminated the argument, which will always
// be either ',' or ')'. The starting '(' has been scanned.
func (in *Input) collectArgument(macro *Macro) ([]Token, ScanToken) {
nesting := 0
var tokens []Token
for {
tok := in.Stack.Next()
if tok == scanner.EOF || tok == '\n' {
in.Error("unterminated arg list invoking macro:", macro.name)
}
if nesting == 0 && (tok == ')' || tok == ',') {
return tokens, tok
}
if tok == '(' {
nesting++
}
if tok == ')' {
nesting--
}
tokens = append(tokens, Make(tok, in.Stack.Text()))
}
}
// #ifdef and #ifndef processing.
func (in *Input) ifdef(truth bool) {
name := in.macroName()
in.expectNewline("#if[n]def")
if !in.enabled() {
truth = false
} else if _, defined := in.macros[name]; !defined {
truth = !truth
}
in.ifdefStack = append(in.ifdefStack, truth)
}
// #else processing
func (in *Input) else_() {
in.expectNewline("#else")
if len(in.ifdefStack) == 0 {
in.Error("unmatched #else")
}
if len(in.ifdefStack) == 1 || in.ifdefStack[len(in.ifdefStack)-2] {
in.ifdefStack[len(in.ifdefStack)-1] = !in.ifdefStack[len(in.ifdefStack)-1]
}
}
// #endif processing.
func (in *Input) endif() {
in.expectNewline("#endif")
if len(in.ifdefStack) == 0 {
in.Error("unmatched #endif")
}
in.ifdefStack = in.ifdefStack[:len(in.ifdefStack)-1]
}
// #include processing.
func (in *Input) include() {
// Find and parse string.
tok := in.Stack.Next()
if tok != scanner.String {
in.expectText("expected string after #include")
}
name, err := strconv.Unquote(in.Stack.Text())
if err != nil {
in.Error("unquoting include file name: ", err)
}
in.expectNewline("#include")
// Push tokenizer for file onto stack.
fd, err := os.Open(name)
if err != nil {
for _, dir := range in.includes {
fd, err = os.Open(filepath.Join(dir, name))
if err == nil {
break
}
}
if err != nil {
in.Error("#include:", err)
}
}
in.Push(NewTokenizer(name, fd, fd))
}
// #line processing.
func (in *Input) line() {
// Only need to handle Plan 9 format: #line 337 "filename"
tok := in.Stack.Next()
if tok != scanner.Int {
in.expectText("expected line number after #line")
}
line, err := strconv.Atoi(in.Stack.Text())
if err != nil {
in.Error("error parsing #line (cannot happen):", err)
}
tok = in.Stack.Next()
if tok != scanner.String {
in.expectText("expected file name in #line")
}
file, err := strconv.Unquote(in.Stack.Text())
if err != nil {
in.Error("unquoting #line file name: ", err)
}
tok = in.Stack.Next()
if tok != '\n' {
in.Error("unexpected token at end of #line: ", tok)
}
pos := src.MakePos(in.Base(), uint(in.Line())+1, 1) // +1 because #line nnn means line nnn starts on next line
in.Stack.SetBase(src.NewLinePragmaBase(pos, file, objabi.AbsFile(objabi.WorkingDir(), file, *flags.TrimPath), uint(line), 1))
}
// #undef processing
func (in *Input) undef() {
name := in.macroName()
if in.macros[name] == nil {
in.Error("#undef for undefined macro:", name)
}
// Newline must be next.
tok := in.Stack.Next()
if tok != '\n' {
in.Error("syntax error in #undef for macro:", name)
}
delete(in.macros, name)
}
func (in *Input) Push(r TokenReader) {
if len(in.tr) > 100 {
in.Error("input recursion")
}
in.Stack.Push(r)
}
func (in *Input) Close() {
}