| // 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 |
| } |