github.com/jhump/protoreflect@v1.16.0/desc/protoparse/ast/file.go (about) 1 package ast 2 3 import "fmt" 4 5 // FileDeclNode is a placeholder interface for AST nodes that represent files. 6 // This allows NoSourceNode to be used in place of *FileNode for some usages. 7 type FileDeclNode interface { 8 Node 9 GetSyntax() Node 10 } 11 12 var _ FileDeclNode = (*FileNode)(nil) 13 var _ FileDeclNode = NoSourceNode{} 14 15 // FileNode is the root of the AST hierarchy. It represents an entire 16 // protobuf source file. 17 type FileNode struct { 18 compositeNode 19 Syntax *SyntaxNode // nil if file has no syntax declaration 20 Decls []FileElement 21 22 // TODO: add Edition *EditionNode 23 24 // Any comments that follow the last token in the file. 25 FinalComments []Comment 26 // Any whitespace at the end of the file (after the last token or 27 // last comment in the file). 28 FinalWhitespace string 29 } 30 31 // NewFileNode creates a new *FileNode. The syntax parameter is optional. If it 32 // is absent, it means the file had no syntax declaration. 33 // 34 // This function panics if the concrete type of any element of decls is not 35 // from this package. 36 func NewFileNode(syntax *SyntaxNode, decls []FileElement) *FileNode { 37 numChildren := len(decls) 38 if syntax != nil { 39 numChildren++ 40 } 41 children := make([]Node, 0, numChildren) 42 if syntax != nil { 43 children = append(children, syntax) 44 } 45 for _, decl := range decls { 46 children = append(children, decl) 47 } 48 49 for _, decl := range decls { 50 switch decl := decl.(type) { 51 case *PackageNode, *ImportNode, *OptionNode, *MessageNode, 52 *EnumNode, *ExtendNode, *ServiceNode, *EmptyDeclNode: 53 default: 54 panic(fmt.Sprintf("invalid FileElement type: %T", decl)) 55 } 56 } 57 58 return &FileNode{ 59 compositeNode: compositeNode{ 60 children: children, 61 }, 62 Syntax: syntax, 63 Decls: decls, 64 } 65 } 66 67 func NewEmptyFileNode(filename string) *FileNode { 68 return &FileNode{ 69 compositeNode: compositeNode{ 70 children: []Node{NewNoSourceNode(filename)}, 71 }, 72 } 73 } 74 75 func (f *FileNode) GetSyntax() Node { 76 return f.Syntax 77 } 78 79 // FileElement is an interface implemented by all AST nodes that are 80 // allowed as top-level declarations in the file. 81 type FileElement interface { 82 Node 83 fileElement() 84 } 85 86 var _ FileElement = (*ImportNode)(nil) 87 var _ FileElement = (*PackageNode)(nil) 88 var _ FileElement = (*OptionNode)(nil) 89 var _ FileElement = (*MessageNode)(nil) 90 var _ FileElement = (*EnumNode)(nil) 91 var _ FileElement = (*ExtendNode)(nil) 92 var _ FileElement = (*ServiceNode)(nil) 93 var _ FileElement = (*EmptyDeclNode)(nil) 94 95 // SyntaxNode represents a syntax declaration, which if present must be 96 // the first non-comment content. Example: 97 // 98 // syntax = "proto2"; 99 // 100 // Files that don't have a syntax node are assumed to use proto2 syntax. 101 type SyntaxNode struct { 102 compositeNode 103 Keyword *KeywordNode 104 Equals *RuneNode 105 Syntax StringValueNode 106 Semicolon *RuneNode 107 } 108 109 // NewSyntaxNode creates a new *SyntaxNode. All four arguments must be non-nil: 110 // - keyword: The token corresponding to the "syntax" keyword. 111 // - equals: The token corresponding to the "=" rune. 112 // - syntax: The actual syntax value, e.g. "proto2" or "proto3". 113 // - semicolon: The token corresponding to the ";" rune that ends the declaration. 114 func NewSyntaxNode(keyword *KeywordNode, equals *RuneNode, syntax StringValueNode, semicolon *RuneNode) *SyntaxNode { 115 if keyword == nil { 116 panic("keyword is nil") 117 } 118 if equals == nil { 119 panic("equals is nil") 120 } 121 if syntax == nil { 122 panic("syntax is nil") 123 } 124 if semicolon == nil { 125 panic("semicolon is nil") 126 } 127 children := []Node{keyword, equals, syntax, semicolon} 128 return &SyntaxNode{ 129 compositeNode: compositeNode{ 130 children: children, 131 }, 132 Keyword: keyword, 133 Equals: equals, 134 Syntax: syntax, 135 Semicolon: semicolon, 136 } 137 } 138 139 // ImportNode represents an import statement. Example: 140 // 141 // import "google/protobuf/empty.proto"; 142 type ImportNode struct { 143 compositeNode 144 Keyword *KeywordNode 145 // Optional; if present indicates this is a public import 146 Public *KeywordNode 147 // Optional; if present indicates this is a weak import 148 Weak *KeywordNode 149 Name StringValueNode 150 Semicolon *RuneNode 151 } 152 153 // NewImportNode creates a new *ImportNode. The public and weak arguments are optional 154 // and only one or the other (or neither) may be specified, not both. When public is 155 // non-nil, it indicates the "public" keyword in the import statement and means this is 156 // a public import. When weak is non-nil, it indicates the "weak" keyword in the import 157 // statement and means this is a weak import. When both are nil, this is a normal import. 158 // The other arguments must be non-nil: 159 // - keyword: The token corresponding to the "import" keyword. 160 // - public: The token corresponding to the optional "public" keyword. 161 // - weak: The token corresponding to the optional "weak" keyword. 162 // - name: The actual imported file name. 163 // - semicolon: The token corresponding to the ";" rune that ends the declaration. 164 func NewImportNode(keyword *KeywordNode, public *KeywordNode, weak *KeywordNode, name StringValueNode, semicolon *RuneNode) *ImportNode { 165 if keyword == nil { 166 panic("keyword is nil") 167 } 168 if name == nil { 169 panic("name is nil") 170 } 171 if semicolon == nil { 172 panic("semicolon is nil") 173 } 174 numChildren := 3 175 if public != nil || weak != nil { 176 numChildren++ 177 } 178 children := make([]Node, 0, numChildren) 179 children = append(children, keyword) 180 if public != nil { 181 children = append(children, public) 182 } else if weak != nil { 183 children = append(children, weak) 184 } 185 children = append(children, name, semicolon) 186 187 return &ImportNode{ 188 compositeNode: compositeNode{ 189 children: children, 190 }, 191 Keyword: keyword, 192 Public: public, 193 Weak: weak, 194 Name: name, 195 Semicolon: semicolon, 196 } 197 } 198 199 func (*ImportNode) fileElement() {} 200 201 // PackageNode represents a package declaration. Example: 202 // 203 // package foobar.com; 204 type PackageNode struct { 205 compositeNode 206 Keyword *KeywordNode 207 Name IdentValueNode 208 Semicolon *RuneNode 209 } 210 211 func (*PackageNode) fileElement() {} 212 213 // NewPackageNode creates a new *PackageNode. All three arguments must be non-nil: 214 // - keyword: The token corresponding to the "package" keyword. 215 // - name: The package name declared for the file. 216 // - semicolon: The token corresponding to the ";" rune that ends the declaration. 217 func NewPackageNode(keyword *KeywordNode, name IdentValueNode, semicolon *RuneNode) *PackageNode { 218 if keyword == nil { 219 panic("keyword is nil") 220 } 221 if name == nil { 222 panic("name is nil") 223 } 224 if semicolon == nil { 225 panic("semicolon is nil") 226 } 227 children := []Node{keyword, name, semicolon} 228 return &PackageNode{ 229 compositeNode: compositeNode{ 230 children: children, 231 }, 232 Keyword: keyword, 233 Name: name, 234 Semicolon: semicolon, 235 } 236 }