| | |
| | |
| | |
| |
|
| | package lex |
| |
|
| | import ( |
| | "fmt" |
| | "os" |
| | "path/filepath" |
| | "slices" |
| | "strconv" |
| | "strings" |
| | "text/scanner" |
| |
|
| | "cmd/asm/internal/flags" |
| | "cmd/internal/objabi" |
| | "cmd/internal/src" |
| | ) |
| |
|
| | |
| | |
| | |
| | type Input struct { |
| | Stack |
| | includes []string |
| | beginningOfLine bool |
| | ifdefStack []bool |
| | macros map[string]*Macro |
| | text string |
| | peek bool |
| | peekToken ScanToken |
| | peekText string |
| | } |
| |
|
| | |
| | func NewInput(name string) *Input { |
| | return &Input{ |
| | |
| | includes: append([]string{filepath.Dir(name)}, flags.I...), |
| | beginningOfLine: true, |
| | macros: predefine(flags.D), |
| | } |
| | } |
| |
|
| | |
| | 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 |
| | } |
| |
|
| | var panicOnError bool |
| |
|
| | func (in *Input) Error(args ...any) { |
| | 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) |
| | } |
| |
|
| | |
| | func (in *Input) expectText(args ...any) { |
| | in.Error(append(args, "; got", strconv.Quote(in.Stack.Text()))...) |
| | } |
| |
|
| | |
| | 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 |
| | } |
| | |
| | |
| | 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: |
| | |
| | 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 { |
| | |
| | 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 |
| | } |
| |
|
| | |
| | func (in *Input) hash() bool { |
| | |
| | tok := in.Stack.Next() |
| | if tok != scanner.Ident { |
| | in.expectText("expected identifier after '#'") |
| | } |
| | if !in.enabled() { |
| | |
| | |
| | |
| | switch in.Stack.Text() { |
| | case "else", "endif", "ifdef", "ifndef", "line": |
| | |
| | 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 |
| | } |
| |
|
| | |
| | func (in *Input) macroName() string { |
| | |
| | tok := in.Stack.Next() |
| | if tok != scanner.Ident { |
| | in.expectText("expected identifier after # directive") |
| | } |
| | |
| | return in.Stack.Text() |
| | } |
| |
|
| | |
| | func (in *Input) define() { |
| | name := in.macroName() |
| | args, tokens := in.macroDefinition(name) |
| | in.defineMacro(name, args, tokens) |
| | } |
| |
|
| | |
| | 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, |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | 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 |
| | } |
| | var args []string |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if tok == '(' && in.Stack.Col() == prevCol+1 { |
| | |
| | acceptArg := true |
| | args = []string{} |
| | Loop: |
| | for { |
| | tok = in.Stack.Next() |
| | switch tok { |
| | case ')': |
| | tok = in.Stack.Next() |
| | 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 slices.Contains(args, arg) { |
| | 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 |
| | |
| | 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 (in *Input) invokeMacro(macro *Macro) { |
| | |
| | if macro.args == nil { |
| | in.Push(NewSlice(in.Base(), in.Line(), macro.tokens)) |
| | return |
| | } |
| | tok := in.Stack.Next() |
| | if tok != '(' { |
| | |
| | |
| | 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)) |
| | } |
| |
|
| | |
| | |
| | func (in *Input) argsFor(macro *Macro) map[string][]Token { |
| | var args [][]Token |
| | |
| | for argNum := 0; ; argNum++ { |
| | tokens, tok := in.collectArgument(macro) |
| | args = append(args, tokens) |
| | if tok == ')' { |
| | break |
| | } |
| | } |
| | |
| | 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 |
| | } |
| |
|
| | |
| | |
| | |
| | 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())) |
| | } |
| | } |
| |
|
| | |
| | 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) |
| | } |
| |
|
| | |
| | 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] |
| | } |
| | } |
| |
|
| | |
| | func (in *Input) endif() { |
| | in.expectNewline("#endif") |
| | if len(in.ifdefStack) == 0 { |
| | in.Error("unmatched #endif") |
| | } |
| | in.ifdefStack = in.ifdefStack[:len(in.ifdefStack)-1] |
| | } |
| |
|
| | |
| | func (in *Input) include() { |
| | |
| | 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") |
| | |
| | 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)) |
| | } |
| |
|
| | |
| | func (in *Input) line() { |
| | |
| | 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) |
| | in.Stack.SetBase(src.NewLinePragmaBase(pos, file, objabi.AbsFile(objabi.WorkingDir(), file, *flags.TrimPath), uint(line), 1)) |
| | } |
| |
|
| | |
| | func (in *Input) undef() { |
| | name := in.macroName() |
| | if in.macros[name] == nil { |
| | in.Error("#undef for undefined macro:", name) |
| | } |
| | |
| | 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() { |
| | } |
| |
|