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