| // Copyright 2026 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 specgen | |
| import ( | |
| "fmt" | |
| "strings" | |
| ) | |
| // ParagraphKind classifies a doc comment paragraph. | |
| type ParagraphKind int | |
| const ( | |
| // SpecOwned paragraphs describe the behavioral semantics of an operation. | |
| SpecOwned ParagraphKind = iota | |
| // ImplementationNote paragraphs describe target-specific implementation | |
| // details (such as assembly instructions, required CPU features, emulation | |
| // notes, etc.). | |
| // | |
| // All implementation note paragraphs start with one of a fixed set of | |
| // prefixes (see [HasNotePrefix]). | |
| ImplementationNote | |
| // DirectiveComment paragraphs represent compiler or tool directives (e.g. | |
| // //go:noescape). | |
| DirectiveComment | |
| ) | |
| func (k ParagraphKind) String() string { | |
| switch k { | |
| case SpecOwned: | |
| return "spec-owned paragraph" | |
| case ImplementationNote: | |
| return "implementation note" | |
| case DirectiveComment: | |
| return "directive comment" | |
| default: | |
| return fmt.Sprintf("ParagraphKind(%d)", k) | |
| } | |
| } | |
| // Paragraph is a doc comment paragraph with its classification. | |
| type Paragraph struct { | |
| Kind ParagraphKind | |
| Text string // No leading "//" or trailing \n | |
| } | |
| // HasNotePrefix reports whether text starts with an implementation note prefix. | |
| // The text should NOT include any leading "//". | |
| func HasNotePrefix(text string) bool { | |
| if strings.HasPrefix(text, "Asm:") || | |
| strings.HasPrefix(text, "CPU Feature:") || | |
| strings.HasPrefix(text, "Deprecated:") || | |
| strings.HasPrefix(text, "Performance:") || | |
| strings.HasPrefix(text, "Note:") { | |
| return true | |
| } | |
| // "Emulated" notes are a little bit of a mess. They don't usually have any | |
| // content, so there's no colon. (go.dev/issue/81568) | |
| if strings.HasPrefix(text, "Emulated,") || text == "Emulated" || strings.HasPrefix(text, "Emulated\n") || strings.HasPrefix(text, "Emulated:") { | |
| return true | |
| } | |
| return false | |
| } | |
| // parseSpecDoc splits expanded spec doc text (without comment markers) into | |
| // paragraphs. | |
| // | |
| // It reports errors to ctx if any paragraph in text is not "spec-owned". The | |
| // spec docs are merged with implementation notes and directive comments in | |
| // generated source. In order for this to be idempotent, we must only inject | |
| // spec text into source code that will still be recognized as spec text when we | |
| // need to strip it back out. | |
| func parseSpecDoc(ctx context, text string) []Paragraph { | |
| text = strings.Trim(text, "\n") | |
| var out []Paragraph | |
| for para := range strings.SplitSeq(text, "\n\n") { | |
| // Spec docs structurally cannot contain directive comments. | |
| if HasNotePrefix(para) { | |
| ctx.errorf("spec doc contains implementation note: %s", strings.TrimSpace(para)) | |
| } else { | |
| out = append(out, Paragraph{SpecOwned, para}) | |
| } | |
| } | |
| return out | |
| } | |
| // FormatComment formats a slice of paragraphs into standard Go comment text. | |
| // The result has "//"-prefixed lines and ends with "\n" unless the whole result | |
| // is empty. | |
| // | |
| // FormatComment checks that [SpecOwned] paragraphs must appear strictly before | |
| // [ImplementationNote] paragraphs, which must appear strictly before | |
| // [DirectiveComment]s. If the ordering rule is violated, it returns an error. | |
| func FormatComment(paras []Paragraph) (string, error) { | |
| if len(paras) == 0 { | |
| return "", nil | |
| } | |
| lastKind := SpecOwned | |
| for _, p := range paras { | |
| if p.Kind < lastKind { | |
| return "", fmt.Errorf("doc comment ordering violation: %s appeared after %s", p.Kind, lastKind) | |
| } | |
| lastKind = p.Kind | |
| } | |
| var buf strings.Builder | |
| inDirectives := false | |
| for i, p := range paras { | |
| if i > 0 && !inDirectives { | |
| buf.WriteString("//\n") | |
| } | |
| if p.Kind == DirectiveComment { | |
| buf.WriteString("//") | |
| buf.WriteString(p.Text) | |
| buf.WriteByte('\n') | |
| // Directives don't have paragraph separators | |
| inDirectives = true | |
| } else { | |
| for line := range strings.SplitSeq(p.Text, "\n") { | |
| if strings.HasPrefix(line, "\t") { | |
| buf.WriteString("//") | |
| } else { | |
| buf.WriteString("// ") | |
| } | |
| buf.WriteString(line) | |
| buf.WriteByte('\n') | |
| } | |
| } | |
| } | |
| return buf.String(), nil | |
| } |