github.com/beauknowssoftware/makehcl@v0.0.0-20200322000747-1b9bb1e1c008/internal/parse2/parser.go (about)

     1  package parse2
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/hashicorp/hcl/v2/hclparse"
     8  )
     9  
    10  const (
    11  	defaultFilename = "make.hcl"
    12  )
    13  
    14  type Parser struct {
    15  	Options    Options
    16  	hclParser  *hclparse.Parser
    17  	attributes []attribute
    18  	Definition
    19  }
    20  
    21  func (p *Parser) init() {
    22  	if p.Options.Filename == "" {
    23  		p.Options.Filename = defaultFilename
    24  	}
    25  
    26  	p.hclParser = hclparse.NewParser()
    27  }
    28  
    29  func (p *Parser) readFile(filename string) (*File, hcl.Diagnostics) {
    30  	hf, diag := p.hclParser.ParseHCLFile(filename)
    31  
    32  	f := p.addFile(filename, hf)
    33  
    34  	return f, diag
    35  }
    36  
    37  func (p *Parser) enumerateImportBlocks(ctx *hcl.EvalContext) hcl.Diagnostics {
    38  	var result hcl.Diagnostics
    39  
    40  	unprocessedFiles := []string{p.Options.Filename}
    41  
    42  	for len(unprocessedFiles) > 0 {
    43  		filename := unprocessedFiles[0]
    44  		unprocessedFiles = unprocessedFiles[1:]
    45  
    46  		if _, hasReadFile := p.Files[filename]; hasReadFile {
    47  			continue
    48  		}
    49  
    50  		f, diag := p.readFile(filename)
    51  		if diag.HasErrors() {
    52  			result = result.Extend(diag)
    53  		}
    54  
    55  		diag = f.enumerateImportBlocks(ctx)
    56  		if diag.HasErrors() {
    57  			result = result.Extend(diag)
    58  		}
    59  
    60  		newFilenames := make([]string, 0, len(f.ImportBlocks))
    61  
    62  		for _, imp := range f.ImportBlocks {
    63  			if imp.File == nil {
    64  				continue
    65  			}
    66  
    67  			newFilenames = append(newFilenames, imp.File.Value)
    68  		}
    69  
    70  		unprocessedFiles = append(unprocessedFiles, newFilenames...)
    71  	}
    72  
    73  	return result
    74  }
    75  
    76  func (p *Parser) enumerateAttributes() hcl.Diagnostics {
    77  	var result hcl.Diagnostics
    78  
    79  	gs := variableScope{}
    80  
    81  	for _, f := range p.Files {
    82  		diag := f.enumerateAttributes(&gs)
    83  		if diag.HasErrors() {
    84  			result = result.Extend(diag)
    85  		}
    86  
    87  		p.attributes = append(p.attributes, f.attributes...)
    88  	}
    89  
    90  	return result
    91  }
    92  
    93  func (p *Parser) fillAttributes(ctx *hcl.EvalContext) hcl.Diagnostics {
    94  	var result hcl.Diagnostics
    95  
    96  	s := attributeSorter{attributes: p.attributes}
    97  	s.sort()
    98  
    99  	for _, a := range s.sorted {
   100  		diag := a.fill(ctx)
   101  		if diag.HasErrors() {
   102  			result = result.Extend(diag)
   103  		}
   104  	}
   105  
   106  	return result
   107  }
   108  
   109  type importCycleDetector struct {
   110  	visited  map[string]bool
   111  	visiting map[string]bool
   112  	files    map[string]*File
   113  }
   114  
   115  func (d importCycleDetector) findImportCycles(filename string) hcl.Diagnostics {
   116  	if d.visited[filename] {
   117  		return nil
   118  	}
   119  
   120  	var result hcl.Diagnostics
   121  
   122  	d.visiting[filename] = true
   123  
   124  	f := d.files[filename]
   125  	for _, imp := range f.ImportBlocks {
   126  		if imp.File == nil {
   127  			continue
   128  		}
   129  
   130  		if d.visiting[imp.File.Value] {
   131  			diag := hcl.Diagnostic{
   132  				Summary:     "Import cycle detected",
   133  				Detail:      fmt.Sprintf("Cycle occurred when importing %v", imp.File.Value),
   134  				Severity:    hcl.DiagError,
   135  				Subject:     &imp.File.attribute.Range,
   136  				Expression:  imp.File.attribute.Expr,
   137  				EvalContext: imp.File.ctx,
   138  			}
   139  
   140  			result = result.Append(&diag)
   141  		} else {
   142  			diag := d.findImportCycles(imp.File.Value)
   143  			result = result.Extend(diag)
   144  		}
   145  	}
   146  
   147  	d.visited[filename] = true
   148  	d.visiting[filename] = false
   149  
   150  	return result
   151  }
   152  
   153  func (p Parser) findImportCycles() (result hcl.Diagnostics) {
   154  	d := importCycleDetector{
   155  		visited:  make(map[string]bool),
   156  		visiting: make(map[string]bool),
   157  		files:    p.Files,
   158  	}
   159  
   160  	return d.findImportCycles(p.Options.Filename)
   161  }
   162  
   163  func (p *Parser) Parse() hcl.Diagnostics {
   164  	p.init()
   165  
   166  	if diag := p.enumerateImportBlocks(nil); diag.HasErrors() {
   167  		return diag
   168  	}
   169  
   170  	if p.Options.StopAfterStage == StopAfterImports {
   171  		return nil
   172  	}
   173  
   174  	if diag := p.findImportCycles(); diag.HasErrors() {
   175  		return diag
   176  	}
   177  
   178  	if diag := p.enumerateAttributes(); diag.HasErrors() {
   179  		return diag
   180  	}
   181  
   182  	if diag := p.fillAttributes(nil); diag.HasErrors() {
   183  		return diag
   184  	}
   185  
   186  	return nil
   187  }