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  }