blob: a193649fee7842acfa5242261b52b8ce5e963830 [file] [log] [blame]
// 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"
"os"
"path/filepath"
"strconv"
"strings"
"text/scanner"
"cmd/asm/internal/flags"
"cmd/internal/obj"
)
// 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
}
// NewInput returns a
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)
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
}
func (in *Input) Error(args ...interface{}) {
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.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 {
for {
tok := in.Stack.Next()
switch tok {
case '#':
if !in.beginningOfLine {
in.Error("'#' must be first item on line")
}
in.beginningOfLine = in.hash()
case scanner.Ident:
// Is it a macro name?
name := in.Stack.Text()
macro := in.macros[name]
if macro != nil {
in.invokeMacro(macro)
continue
}
fallthrough
default:
in.beginningOfLine = tok == '\n'
if in.enabled() {
return tok
}
}
}
in.Error("recursive macro invocation")
return 0
}
// hash processes a # preprocessor directive. It returns true iff 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.
// We let #line through because it might affect errors.
switch in.Text() {
case "else", "endif", "line":
// Press on.
default:
return false
}
}
switch in.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 identifier after '#':", in.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.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) {
tok := in.Stack.Next()
if tok == '\n' || tok == scanner.EOF {
in.Error("no definition for macro:", name)
}
var args []string
if tok == '(' {
// 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 == '\\' {
tok = in.Stack.Next()
if tok != '\n' && tok != '\\' {
in.Error(`can only escape \ or \n in definition for macro:`, name)
}
if tok == '\n' { // backslash-newline is discarded
tok = in.Stack.Next()
continue
}
}
tokens = append(tokens, Make(tok, in.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) {
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.File(), in.Line(), tokens))
}
// argsFor returns a map from formal name to actual value for this macro invocation.
func (in *Input) argsFor(macro *Macro) map[string][]Token {
if macro.args == nil {
return nil
}
tok := in.Stack.Next()
if tok != '(' {
in.Error("missing arguments for invocation of macro:", macro.name)
}
var tokens []Token
args := make(map[string][]Token)
argNum := 0
for {
tok = in.Stack.Next()
switch tok {
case scanner.EOF, '\n':
in.Error("unterminated arg list invoking macro:", macro.name)
case ',', ')':
if argNum >= len(macro.args) {
in.Error("too many arguments for macro:", macro.name)
}
if len(macro.args) == 0 && argNum == 0 && len(tokens) == 0 {
// Zero-argument macro invoked with no arguments.
return args
}
args[macro.args[argNum]] = tokens
tokens = nil
argNum++
if tok == ')' {
if argNum != len(macro.args) {
in.Error("too few arguments for macro:", macro.name)
}
return args
}
default:
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 _, 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")
}
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.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)
}
obj.Linklinehist(linkCtxt, histLine, file, line)
in.Stack.SetPos(line, file)
}
// #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() {
}