// Copyright 2019 The CC Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //TODO https://todo.sr.ht/~mcf/cc-issues/34 //TODO http://mcpp.sourceforge.net/ "Provides a validation suite to test C/C++ preprocessor's conformance and quality comprehensively." //go:generate rm -f lexer.go //go:generate golex -o lexer.go lexer.l //go:generate rm -f ast.go //go:generate yy -o /dev/null -position -astImport "\"fmt\"\n\n\"modernc.org/token\"" -prettyString PrettyString -kind Case -noListKind -noPrivateHelpers -forceOptPos parser.yy //go:generate stringer -output stringer.go -linecomment -type=Kind,Linkage //go:generate sh -c "go test -run ^Example |fe" // Package cc is a C99 compiler front end (Work in progress). // // Installation // // To install/update cc/v3 invoke: // // $ go get [-u] modernc.org/cc/v3 // // Online documentation // // See https://godoc.org/modernc.org/cc/v3. // // Status // // Most of the functionality is now working. // // Supported platforms // // The code is known to work on Darwin, Linux and Windows, but the supported // features may vary. // // Links // // Referenced from elsewhere: // // [0]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf // [1]: https://www.spinellis.gr/blog/20060626/cpp.algo.pdf // [2]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf // [3]: http://gallium.inria.fr/~fpottier/publis/jourdan-fpottier-2016.pdf // [4]: https://gcc.gnu.org/onlinedocs/gcc-8.3.0/gcc/Attribute-Syntax.html#Attribute-Syntax package cc // import "modernc.org/cc/v3" import ( "fmt" goscanner "go/scanner" gotoken "go/token" "hash/maphash" "io" "math" "os" "os/exec" "reflect" "regexp" "runtime" "sort" "strconv" "strings" "sync" "sync/atomic" "modernc.org/strutil" "modernc.org/token" ) const ( scopeParent StringID = -iota - 1 scopeSkip ) var ( _ Pragma = (*pragma)(nil) cache = newPPCache() dict = newDictionary() dictStrings [math.MaxUint8 + 1]string noPos token.Position debugIncludePaths bool debugWorkingDir bool isTesting bool isTestingMingw bool idPtrdiffT = dict.sid("ptrdiff_t") idSizeT = dict.sid("size_t") idWCharT = dict.sid("wchar_t") token4Pool = sync.Pool{New: func() interface{} { r := make([]token4, 0); return &r }} //DONE benchmrk tuned capacity tokenPool = sync.Pool{New: func() interface{} { r := make([]Token, 0); return &r }} //DONE benchmrk tuned capacity printHooks = strutil.PrettyPrintHooks{ reflect.TypeOf(Token{}): func(f strutil.Formatter, v interface{}, prefix, suffix string) { t := v.(Token) if (t == Token{}) { return } f.Format(prefix) r := t.Rune if p := t.Position(); p.IsValid() { f.Format("%v: ", p) } s := tokName(r) if x := s[0]; x >= '0' && x <= '9' { s = strconv.QuoteRune(r) } f.Format("%s", s) if s := t.Value.String(); len(s) != 0 { f.Format(" %q", s) } f.Format(suffix) }, reflect.TypeOf((*operand)(nil)): func(f strutil.Formatter, v interface{}, prefix, suffix string) { op := v.(*operand) f.Format(prefix) f.Format("[%v %T(%[2]v)]", op.Type(), op.Value()) f.Format(suffix) }, } ) func todo(s string, args ...interface{}) string { //TODO- switch { case s == "": s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...) default: s = fmt.Sprintf(s, args...) } pc, fn, fl, _ := runtime.Caller(1) f := runtime.FuncForPC(pc) var fns string if f != nil { fns = f.Name() if x := strings.LastIndex(fns, "."); x > 0 { fns = fns[x+1:] } } r := fmt.Sprintf("%s:%d:%s: TODOTODO %s", fn, fl, fns, s) //TODOOK fmt.Fprintf(os.Stdout, "%s\n", r) os.Stdout.Sync() return r } func trc(s string, args ...interface{}) string { //TODO- switch { case s == "": s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...) default: s = fmt.Sprintf(s, args...) } _, fn, fl, _ := runtime.Caller(1) r := fmt.Sprintf("%s:%d: TRC %s", fn, fl, s) fmt.Fprintf(os.Stdout, "%s\n", r) os.Stdout.Sync() return r } func origin(skip int) string { pc, fn, fl, _ := runtime.Caller(skip) f := runtime.FuncForPC(pc) var fns string if f != nil { fns = f.Name() if x := strings.LastIndex(fns, "."); x > 0 { fns = fns[x+1:] } } return fmt.Sprintf("%s:%d:%s", fn, fl, fns) } // String returns a StringID for a given value. func String(s string) StringID { return dict.sid(s) } // Linkage represents identifier linkage. // // [0]6.2.2: An identifier declared in different scopes or in the same scope // more than once can be made to refer to the same object or function by a // process called linkage. There are three kinds of linkage: External, // Internal, and None. type Linkage int // StorageClass determines storage duration. // // [0]6.2.4: An object has a storage duration that determines its lifetime. // There are three storage durations: Static, Automatic, and Allocated. type StorageClass int // Pragma defines behavior of the object passed to Config.PragmaHandler. type Pragma interface { Error(msg string, args ...interface{}) // Report error. MaxAligment() int // Returns the current maximum alignment. May return zero. MaxInitialAligment() int // Support #pragma pack(). Returns the maximum alignment in effect at start. May return zero. PopMacro(string) PushMacro(string) SetAlignment(n int) // Support #pragma pack(n) } type pragma struct { tok cppToken c *cpp } func (p *pragma) Error(msg string, args ...interface{}) { p.c.err(p.tok, msg, args...) } func (p *pragma) MaxAligment() int { return p.c.ctx.maxAlign } func (p *pragma) MaxInitialAligment() int { return p.c.ctx.maxAlign0 } func (p *pragma) SetAlignment(n int) { if n <= 0 { p.Error("%T.SetAlignment(%d): invalid argument", p, n) return } p.c.ctx.maxAlign = n } func (p *pragma) PushMacro(nm string) { id := dict.sid(nm) if p.c.macroStack == nil { p.c.macroStack = map[StringID][]*Macro{} } if m := p.c.macros[id]; m != nil { p.c.macroStack[id] = append(p.c.macroStack[id], p.c.macros[id]) } } func (p *pragma) PopMacro(nm string) { id := dict.sid(nm) a := p.c.macroStack[id] if n := len(a); n != 0 { p.c.macros[id] = a[n-1] p.c.macroStack[id] = a[:n-1] } } // PrettyString returns a formatted representation of things produced by this package. func PrettyString(v interface{}) string { return strutil.PrettyString(v, "", "", printHooks) } // StringID is a process-unique string numeric identifier. Its zero value // represents an empty string. type StringID int32 // String implements fmt.Stringer. func (n StringID) String() (r string) { if n < 256 { return dictStrings[byte(n)] } dict.mu.RLock() r = dict.strings[n] dict.mu.RUnlock() return r } // Node is implemented by Token and all AST nodes. type Node interface { Position() token.Position } type noder struct{} func (noder) Position() token.Position { panic(internalError()) } // Scope maps identifiers to definitions. type Scope map[StringID][]Node func (s *Scope) new() (r Scope) { if *s == nil { *s = Scope{} } r = Scope{scopeParent: []Node{struct { noder Scope }{Scope: *s}}} return r } func (s *Scope) declare(nm StringID, n Node) { sc := *s if sc == nil { *s = map[StringID][]Node{nm: {n}} // t := "" // if x, ok := n.(*Declarator); ok && x.IsTypedefName { // t = ", typedefname" // } // dbg("declared %s%s at %v in scope %p", nm, t, n.Position(), *s) return } switch x := n.(type) { case *Declarator, *StructDeclarator, *LabeledStatement, *BlockItem: // nop case *StructOrUnionSpecifier, *EnumSpecifier, *Enumerator: for { if _, ok := sc[scopeSkip]; !ok { break } sc = sc.Parent() } default: panic(todo("%T", x)) } sc[nm] = append(sc[nm], n) // t := "" // if x, ok := n.(*Declarator); ok && x.IsTypedefName { // t = ", typedefname" // } // dbg("declared %s%s at %v in scope %p", nm, t, n.Position(), sc) } // Parent returns s's outer scope, if any. func (s Scope) Parent() Scope { if s == nil { return nil } if x, ok := s[scopeParent]; ok { return x[0].(struct { noder Scope }).Scope } return nil } func (s *Scope) typedef(nm StringID, tok Token) *Declarator { seq := tok.seq for s := *s; s != nil; s = s.Parent() { for _, v := range s[nm] { switch x := v.(type) { case *Declarator: if !x.isVisible(seq) { continue } if x.IsTypedefName { return x } return nil case *Enumerator: return nil case *EnumSpecifier, *StructOrUnionSpecifier, *StructDeclarator: // nop default: panic(internalError()) } } } return nil } func (s *Scope) declarator(nm StringID, tok Token) *Declarator { seq := tok.seq for s := *s; s != nil; s = s.Parent() { defs := s[nm] for _, v := range defs { switch x := v.(type) { case *Declarator: if !x.isVisible(seq) { continue } for _, v := range defs { if x, ok := v.(*Declarator); ok { t := x.Type() if t != nil && t.Kind() == Function { if x.fnDef { return x } continue } if t != nil && !x.Type().IsIncomplete() { return x } } } return x case *Enumerator: return nil case *EnumSpecifier, *StructOrUnionSpecifier, *StructDeclarator: // nop default: panic(internalError()) } } } return nil } func (s *Scope) enumerator(nm StringID, tok Token) *Enumerator { seq := tok.seq for s := *s; s != nil; s = s.Parent() { for _, v := range s[nm] { switch x := v.(type) { case *Declarator: if !x.isVisible(seq) { continue } return nil case *Enumerator: return x case *EnumSpecifier, *StructOrUnionSpecifier, *StructDeclarator: // nop default: panic(internalError()) } } } return nil } // Config3 amends behavior of translation phases 1 to 3. type Config3 struct { // If IgnoreInclude is not nil, its MatchString method will be called by the // preprocessor with the argument any include directive expands to. If the call // evaluates to is true the include directive will be ignored completely. IgnoreInclude *regexp.Regexp // Name of a macro to use instead of FD_ZERO. // // Note: Temporary solution will be removed/replaced ReplaceMacroFdZero string // Name of a macro to use instead of TCL_DEFAULT_DOUBLE_ROUNDING. // // Note: Temporary solution will be removed/replaced ReplaceMacroTclDefaultDoubleRounding string // Name of a macro to use instead of TCL_DEFAULT_DOUBLE_ROUNDING. Note: Temporrary solution will be removed/replaced // Name of a macro to use instead of TCL_IEEE_DOUBLE_ROUNDING. // // Note: Temporary solution will be removed/replaced ReplaceMacroTclIeeeDoubleRounding string WorkingDir string // Overrides os.Getwd if non empty. Filesystem Filesystem // Overrides filesystem access if not empty. MaxSourceLine int // Zero: Scanner will use default buffer. Non zero: Scanner will use max(default buffer size, MaxSourceLine). // DisableBuiltinResolution disables resolution of undefined identifiers such // that eg. abort, becomes the same as __builtin_abort, prototype of which is // expected to be provided by one of the sources passed to Parse, Preprocess or // Translate. DisableBuiltinResolution bool DisableTrigraphs bool // GCC ignores them unless -trigraphs is used: https://gcc.gnu.org/onlinedocs/cpp/Initial-processing.html GCCStructs bool // Assume __attribute__(gcc_struct) applied to structs by default. //TODO MSStructs bool // Assume __attribute__(ms_struct) applied to structs by default. NoFieldAndBitfieldOverlap bool // Only bitfields can be grouped together. PreserveOnlyLastNonBlankSeparator bool // If PreserveWhiteSpace is true, keep only the last white space, do not combine PreserveWhiteSpace bool // Including also comments. RejectElseExtraTokens bool // Pedantic: do not silently accept "#else foo". RejectEndifExtraTokens bool // Pedantic: do not silently accept "#endif foo". RejectFinalBackslash bool // Pedantic: do not silently accept "foo\\\n". RejectFunctionMacroEmptyReplacementList bool // Pedantic: do not silently accept "#define foo(bar)\n". RejectIfdefExtraTokens bool // Pedantic: do not silently accept "#ifdef foo bar". RejectIfndefExtraTokens bool // Pedantic: do not silently accept "#ifndef foo bar". RejectIncludeNext bool // Pedantic: do not silently accept "#include_next". RejectInvalidVariadicMacros bool // Pedantic: do not silently accept "#define foo(bar...)". Standard allows only #define foo(bar, ...) RejectLineExtraTokens bool // Pedantic: do not silently accept "#line 1234 \"foo.c\" bar". RejectMissingFinalNewline bool // Pedantic: do not silently accept "foo\nbar". RejectUndefExtraTokens bool // Pedantic: do not silently accept "#undef foo bar". UnsignedEnums bool // GCC compatibility: enums with no negative values will have unsigned type. } type SharedFunctionDefinitions struct { M map[*FunctionDefinition]struct{} m map[sharedFunctionDefinitionKey]*FunctionDefinition //TODO hash maphash.Hash } type sharedFunctionDefinitionKey struct { pos StringID nm StringID hash uint64 } // Config amends behavior of translation phase 4 and above. Instances of Config // are not mutated by this package and it's safe to share/reuse them. // // The *Config passed to Parse or Translate should not be mutated afterwards. type Config struct { Config3 ABI ABI PragmaHandler func(Pragma, []Token) // Called on pragmas, other than #pragma STDC ..., if non nil // SharedFunctionDefinitions collects function definitions having the // same position and definition. This can happen, for example, when a // function is defined in a header file included multiple times. Either // within a single translation unit or across translation units. In the // later case just supply the same SharedFunctionDefinitions in Config // when translating/parsing each translation unit. SharedFunctionDefinitions *SharedFunctionDefinitions MaxErrors int // 0: default (10), < 0: unlimited, n: n. CheckExternInlineFnBodies bool // Translate will consider extern inline function bodies. DebugIncludePaths bool // Output to stderr. DebugWorkingDir bool // Output to stderr. DoNotTypecheckAsm bool EnableAssignmentCompatibilityChecking bool // No such checks performed up to v3.31.0. Currently only partially implemented. InjectTracingCode bool // Output to stderr. LongDoubleIsDouble bool PreprocessOnly bool RejectAnonymousFields bool // Pedantic: do not silently accept "struct{int;}". RejectCaseRange bool // Pedantic: do not silently accept "case 'a'...'z':". RejectEmptyCompositeLiterals bool // Pedantic: do not silently accept "foo = (T){}". RejectEmptyDeclarations bool // Pedantic: do not silently accept "int foo(){};". RejectEmptyFields bool // Pedantic: do not silently accept "struct {int a;;} foo;". RejectEmptyInitializerList bool // Pedantic: do not silently accept "foo f = {};". RejectEmptyStructDeclaration bool // Pedantic: do not silently accept "struct{; int i}". RejectEmptyStructs bool // Pedantic: do not silently accept "struct foo {};". RejectIncompatibleMacroRedef bool // Pedantic: do not silently accept "#define MIN(A,B) ...\n#define MIN(a,b) ...\n" etc. RejectLabelValues bool // Pedantic: do not silently accept "foo: bar(); void *ptr = &&foo;" or "goto *ptr". RejectLateBinding bool // Pedantic: do not silently accept void f() { g(); } void g() {} RejectMissingConditionalExpr bool // Pedantic: do not silently accept "foo = bar ? : baz;". RejectMissingDeclarationSpecifiers bool // Pedantic: do not silently accept "main() {}". RejectMissingFinalStructFieldSemicolon bool // Pedantic: do not silently accept "struct{int i; int j}". RejectNestedFunctionDefinitions bool // Pedantic: do not silently accept nested function definitons. RejectParamSemicolon bool // Pedantic: do not silently accept "int f(int a; int b)". RejectStatementExpressions bool // Pedantic: do not silently accept "i = ({foo();})". RejectTypeof bool // Pedantic: do not silently accept "typeof foo" or "typeof(bar*)". RejectUninitializedDeclarators bool // Reject int f() { int j; return j; } TrackAssignments bool // Collect a list of LHS declarators a declarator is used in RHS or as an function argument. doNotSanityCheckComplexTypes bool // Testing only fakeIncludes bool // Testing only. ignoreErrors bool // Testing only. ignoreIncludes bool // Testing only. ignoreUndefinedIdentifiers bool // Testing only. } type context struct { ast *AST breakCtx Node breaks int casePromote Type cases []*LabeledStatement // switch cfg *Config checkFn *FunctionDefinition closure map[StringID]struct{} continues int enums map[StringID]Operand //TODO putting this in alphabetical order within the struct causes crashes in VirtualBox/386 ??? goscanner.ErrorList includePaths []string intBits int intMaxWidth int64 // Set if the preprocessor saw __INTMAX_WIDTH__. keywords map[StringID]rune maxAlign int // If non zero: maximum alignment of members of structures (other than zero-width bitfields). maxAlign0 int maxErrors int mode mode modes []mode mu sync.Mutex ptrdiffT Type readDelta int sizeT Type structTypes map[StringID]Type structs map[StructInfo]struct{} switches int sysIncludePaths []string tuSize0 int64 // Sum of sizes of processed inputs tuSources0 int32 // Number of processed inputs wcharT Type capture bool evalIdentError bool } func newContext(cfg *Config) *context { maxErrors := cfg.MaxErrors if maxErrors == 0 { maxErrors = 10 } return &context{ cfg: cfg, enums: map[StringID]Operand{}, keywords: keywords, maxErrors: maxErrors, structTypes: map[StringID]Type{}, structs: map[StructInfo]struct{}{}, } } func (c *context) tuSizeAdd(n int64) { atomic.AddInt64(&c.tuSize0, n) } func (c *context) tuSize() int64 { return atomic.LoadInt64(&c.tuSize0) } func (c *context) tuSourcesAdd(n int32) { atomic.AddInt32(&c.tuSources0, n) } func (c *context) tuSources() int { return int(atomic.LoadInt32(&c.tuSources0)) } func (c *context) stddef(nm StringID, s Scope, tok Token) Type { if d := s.typedef(nm, tok); d != nil { if t := d.Type(); t != nil && t.Kind() != Invalid { return t } } c.errNode(&tok, "front-end: undefined: %s", nm) return noType } func (c *context) assignmentCompatibilityErrorCond(n Node, a, b Type) (stop bool) { if !c.cfg.EnableAssignmentCompatibilityChecking { return } return c.errNode(n, "invalid type combination of conditional operator: %v and %v", a, b) } func (c *context) assignmentCompatibilityError(n Node, lhs, rhs Type) (stop bool) { if !c.cfg.EnableAssignmentCompatibilityChecking { return } return c.errNode(n, "cannot use %v as type %v in assignment", rhs, lhs) } func (c *context) errNode(n Node, msg string, args ...interface{}) (stop bool) { return c.err(n.Position(), msg, args...) } func (c *context) err(pos token.Position, msg string, args ...interface{}) (stop bool) { // dbg("FAIL "+msg, args...) //fmt.Printf("FAIL "+msg+"\n", args...) if c.cfg.ignoreErrors { return false } s := fmt.Sprintf(msg, args...) c.mu.Lock() max := c.maxErrors switch { case max < 0 || max > len(c.ErrorList): c.ErrorList.Add(gotoken.Position(pos), s) default: stop = true } c.mu.Unlock() return stop } func (c *context) errs(list goscanner.ErrorList) (stop bool) { c.mu.Lock() defer c.mu.Unlock() max := c.maxErrors for _, v := range list { switch { case max < 0 || max > len(c.ErrorList): c.ErrorList = append(c.ErrorList, v) default: return true } } return false } func (c *context) Err() error { c.mu.Lock() switch x := c.ErrorList.Err().(type) { case goscanner.ErrorList: x = append(goscanner.ErrorList(nil), x...) c.mu.Unlock() var lpos gotoken.Position w := 0 for _, v := range x { if lpos.Filename != "" { if v.Pos.Filename == lpos.Filename && v.Pos.Line == lpos.Line { continue } } x[w] = v w++ lpos = v.Pos } x = x[:w] sort.Slice(x, func(i, j int) bool { a := x[i] b := x[j] if !a.Pos.IsValid() && b.Pos.IsValid() { return true } if a.Pos.IsValid() && !b.Pos.IsValid() { return false } if a.Pos.Filename < b.Pos.Filename { return true } if a.Pos.Filename > b.Pos.Filename { return false } if a.Pos.Line < b.Pos.Line { return true } if a.Pos.Line > b.Pos.Line { return false } return a.Pos.Column < b.Pos.Column }) a := make([]string, 0, len(x)) for _, v := range x { a = append(a, v.Error()) } return fmt.Errorf("%s", strings.Join(a, "\n")) default: c.mu.Unlock() return x } } func (c *context) not(n Node, mode mode) { if c.mode&mode != 0 { switch mode { case mIntConstExpr: c.errNode(n, "invalid integer constant expression") default: panic(internalError()) } } } func (c *context) push(mode mode) { c.modes = append(c.modes, c.mode) c.mode = mode } func (c *context) pop() { n := len(c.modes) c.mode = c.modes[n-1] c.modes = c.modes[:n-1] } func (c *context) statFile(name string, sys bool) (os.FileInfo, error) { fs := c.cfg.Config3.Filesystem if fs == nil { fs = LocalFS() } return fs.Stat(name, sys) } func (c *context) openFile(name string, sys bool) (io.ReadCloser, error) { fs := c.cfg.Config3.Filesystem if fs == nil { fs = LocalFS() } return fs.Open(name, sys) } // HostConfig returns the system C preprocessor/compiler configuration, or an // error, if any. The configuration is obtained by running the command named // by the cpp argumnent or "cpp" when it's empty. For the predefined macros // list the '-dM' options is added. For the include paths lists, the option // '-v' is added and the output is parsed to extract the "..." include and // <...> include paths. To add any other options to cpp, list them in opts. // // The function relies on a POSIX/GCC compatible C preprocessor installed. // Execution of HostConfig is not free, so caching of the results is // recommended. func HostConfig(cpp string, opts ...string) (predefined string, includePaths, sysIncludePaths []string, err error) { if cpp == "" { cpp = "cpp" } args := append(append([]string{"-dM"}, opts...), os.DevNull) pre, err := exec.Command(cpp, args...).Output() if err != nil { return "", nil, nil, err } args = append(append([]string{"-v"}, opts...), os.DevNull) out, err := exec.Command(cpp, args...).CombinedOutput() if err != nil { return "", nil, nil, err } sep := "\n" if env("GOOS", runtime.GOOS) == "windows" { sep = "\r\n" } a := strings.Split(string(out), sep) for i := 0; i < len(a); { switch a[i] { case "#include \"...\" search starts here:": loop: for i = i + 1; i < len(a); { switch v := a[i]; { case strings.HasPrefix(v, "#") || v == "End of search list.": break loop default: includePaths = append(includePaths, strings.TrimSpace(v)) i++ } } case "#include <...> search starts here:": for i = i + 1; i < len(a); { switch v := a[i]; { case strings.HasPrefix(v, "#") || v == "End of search list.": return string(pre), includePaths, sysIncludePaths, nil default: sysIncludePaths = append(sysIncludePaths, strings.TrimSpace(v)) i++ } } default: i++ } } return "", nil, nil, fmt.Errorf("failed parsing %s -v output", cpp) } func env(key, val string) string { if s := os.Getenv(key); s != "" { return s } return val } // Token is a grammar terminal. type Token struct { Rune rune // ';' or IDENTIFIER etc. Sep StringID // If Config3.PreserveWhiteSpace is in effect: All preceding white space combined, including comments. Value StringID // ";" or "foo" etc. Src StringID file *tokenFile macro StringID pos int32 seq int32 } // Seq returns t's sequential number. // // Comparing positions as in 'before', 'after' is complicated as tokens in a // translation unit usually come from more than one source file. Macro // expansion further complicates that. The solution is sequentially numbering // the tokens as they are finally seen by the parser, so the usual arithmetic // '<', '>' operators can be used for that purpose. func (t Token) Seq() int { return int(t.seq) } // Macro returns the name of a macro that expanded to this token, if any. func (t *Token) Macro() StringID { return t.macro } // String implements fmt.Stringer. func (t Token) String() string { return t.Value.String() } // Position implements Node. func (t *Token) Position() (r token.Position) { if t.pos != 0 && t.file != nil { r = t.file.PositionFor(token.Pos(t.pos), true) } return r } func tokStr(toks interface{}, sep string) string { var b strings.Builder switch x := toks.(type) { case []token3: for i, v := range x { if i != 0 { b.WriteString(sep) } b.WriteString(v.String()) } case []token4: for i, v := range x { if i != 0 { b.WriteString(sep) } b.WriteString(v.String()) } case []cppToken: for i, v := range x { if i != 0 { b.WriteString(sep) } b.WriteString(v.String()) } case []Token: for i, v := range x { if i != 0 { b.WriteString(sep) } b.WriteString(v.String()) } default: panic(internalError()) } return b.String() } func internalError() int { panic(fmt.Errorf("%v: internal error", origin(2))) } func internalErrorf(s string, args ...interface{}) int { s = fmt.Sprintf(s, args) panic(fmt.Errorf("%v: %s", origin(2), s)) } func detectMingw(s string) bool { return strings.Contains(s, "#define __MINGW") } func nodeSource(n ...Node) (r string) { if len(n) == 0 { return "" } var a []*Token for _, v := range n { Inspect(v, func(n Node, _ bool) bool { if x, ok := n.(*Token); ok && x.Seq() != 0 { a = append(a, x) } return true }) } sort.Slice(a, func(i, j int) bool { return a[i].Seq() < a[j].Seq() }) w := 0 seq := -1 for _, v := range a { if n := v.Seq(); n != seq { seq = n a[w] = v w++ } } a = a[:w] var b strings.Builder for _, v := range a { b.WriteString(v.Sep.String()) b.WriteString(v.Src.String()) } return b.String() }