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 }