pulumi/pkg/codegen/hcl2/syntax/parser.go
Pat Gavlin 900379bd38
Add an HCL2 static typechecker and semantic model. (#4087)
These changes add a package for type checking and modeling HCL2
configurations. It is made up of three primary components:

1. A static type system
2. A semantic representation of HCL2 expressions and a binder from HCL2
   native syntax to this representation
3. A semantic representation of HCL2 structural elements and binders
   from HCL2 native syntax to this representation.

The type system is described in the "Extended Types" section of the
specification. The semantic representations of expressions and
structural elements are documented in their implementations.
2020-03-18 09:28:57 -07:00

95 lines
3.3 KiB
Go

// Copyright 2016-2020, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package syntax
import (
"io"
"io/ioutil"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)
// File represents a single parsed HCL2 source file.
type File struct {
Name string // The name of the file.
Body *hclsyntax.Body // The body of the parsed file.
Bytes []byte // The raw bytes of the source file.
Tokens TokenMap // A map from syntax nodes to token information.
}
// Parser is a parser for HCL2 source files.
type Parser struct {
Files []*File // The parsed files.
Diagnostics hcl.Diagnostics // The diagnostics, if any, produced during parsing.
tokens tokenMap // A map from syntax nodes to token information.
}
// NewParser creates a new HCL2 parser.
func NewParser() *Parser {
return &Parser{tokens: tokenMap{}}
}
// ParseFile attempts to parse the contents of the given io.Reader as HCL2. If parsing fails, any diagnostics generated
// will be added to the parser's diagnostics.
func (p *Parser) ParseFile(r io.Reader, filename string) error {
src, err := ioutil.ReadAll(r)
if err != nil {
return err
}
hclFile, diags := hclsyntax.ParseConfig(src, filename, hcl.Pos{})
if !diags.HasErrors() {
tokens, _ := hclsyntax.LexConfig(src, filename, hcl.Pos{})
mapTokens(tokens, filename, hclFile.Body.(*hclsyntax.Body), hclFile.Bytes, p.tokens)
}
p.Files = append(p.Files, &File{
Name: filename,
Body: hclFile.Body.(*hclsyntax.Body),
Bytes: hclFile.Bytes,
Tokens: p.tokens,
})
p.Diagnostics = append(p.Diagnostics, diags...)
return nil
}
// NewDiagnosticWriter creates a new diagnostic writer for the files parsed by the parser.
func (p *Parser) NewDiagnosticWriter(w io.Writer, width uint, color bool) hcl.DiagnosticWriter {
return NewDiagnosticWriter(w, p.Files, width, color)
}
// NewDiagnosticWriter creates a new diagnostic writer for the given list of HCL2 files.
func NewDiagnosticWriter(w io.Writer, files []*File, width uint, color bool) hcl.DiagnosticWriter {
fileMap := map[string]*hcl.File{}
for _, f := range files {
fileMap[f.Name] = &hcl.File{Body: f.Body, Bytes: f.Bytes}
}
return hcl.NewDiagnosticTextWriter(w, fileMap, width, color)
}
// ParseExpression attempts to parse the given string as an HCL2 expression.
func ParseExpression(expression, filename string, start hcl.Pos) (hclsyntax.Expression, TokenMap, hcl.Diagnostics) {
source := []byte(expression)
hclExpression, diagnostics := hclsyntax.ParseExpression(source, filename, start)
if diagnostics.HasErrors() {
return nil, nil, diagnostics
}
tokens := tokenMap{}
hclTokens, _ := hclsyntax.LexExpression(source, filename, start)
mapTokens(hclTokens, filename, hclExpression, source, tokens)
return hclExpression, tokens, diagnostics
}