go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers-sdk/v1/lr/lr.go (about) 1 // copyright: 2019, Dominik Richter and Christoph Hartmann 2 // author: Dominik Richter 3 // author: Christoph Hartmann 4 5 package lr 6 7 import ( 8 "io" 9 "strings" 10 "text/scanner" 11 12 "github.com/alecthomas/participle" 13 "github.com/alecthomas/participle/lexer" 14 ) 15 16 // Int number type 17 type Int int64 18 19 // Float number type 20 type Float float64 21 22 // Bool for true/false 23 type Bool bool 24 25 // Capture a Bool type for participle 26 func (b *Bool) Capture(values []string) error { 27 *b = values[0] == "true" 28 return nil 29 } 30 31 type Map map[string]string 32 33 func (m *Map) Capture(values []string) error { 34 if len(values) == 0 { 35 return nil 36 } 37 38 if *m == nil { 39 *m = map[string]string{} 40 } 41 (*m)[values[0]] = values[2] 42 return nil 43 } 44 45 type Alias struct { 46 Definition SimpleType `@@` 47 Type SimpleType `'=' @@` 48 } 49 50 // LR are MQL resources parsed into an AST 51 // nolint: govet 52 type LR struct { 53 Comments []string `{ @Comment }` 54 Imports []string `{ "import" @String }` 55 Options Map `{ "option" @(Ident '=' String) }` 56 Aliases []Alias `{ "alias" @@ }` 57 Resources []*Resource `{ @@ }` 58 imports map[string]map[string]struct{} 59 packPaths map[string]string 60 aliases map[string]*Resource 61 } 62 63 // Resource in LR 64 // nolint: govet 65 type Resource struct { 66 Comments []string `{ @Comment }` 67 IsPrivate bool `@"private"?` 68 IsExtension bool `@"extend"?` 69 ID string `@Ident { @'.' @Ident }` 70 Defaults string ` ( '@' "defaults" '(' @String ')' )? ` 71 ListType *SimplListType `[ '{' [ @@ ]` 72 Body *ResourceDef `@@ '}' ]` 73 title string 74 desc string 75 } 76 77 // nolint: govet 78 type Type struct { 79 MapType *MapType `( @@ |` 80 ListType *ListType ` @@ |` 81 SimpleType *SimpleType ` @@ )` 82 } 83 84 // nolint: govet 85 type SimplListType struct { 86 Type SimpleType `'[' ']' @@` 87 Args *FieldArgs `[ '(' @@ ')' ]` 88 } 89 90 // nolint: govet 91 type ListType struct { 92 Type Type `'[' ']' @@` 93 } 94 95 // nolint: govet 96 type MapType struct { 97 Key SimpleType `'map' '[' @@ ` 98 Value Type `']' @@` 99 } 100 101 // nolint: govet 102 type SimpleType struct { 103 Type string `@Ident { @'.' @Ident }` 104 } 105 106 // ResourceDef carrying the definition of the resource 107 // nolint: govet 108 type ResourceDef struct { 109 Fields []*Field `{ @@ }` 110 } 111 112 // ResourceDef carrying the definition of the field 113 // nolint: govet 114 type Field struct { 115 Comments []string `{ @Comment }` 116 Init *Init `( @@ ` 117 Embeddable *Embeddable `| @@` 118 BasicField *BasicField `| @@ )?` 119 } 120 121 // Init field definition 122 // nolint: govet 123 type Init struct { 124 Args []TypedArg `'init' '(' @@ { ',' @@ } ')'` 125 } 126 127 // TypedArg is an argument with a type 128 // nolint: govet 129 type TypedArg struct { 130 ID string `@Ident` 131 Optional bool `@'?'?` 132 Type Type ` @@` 133 } 134 135 // Basic field definition of a resource 136 // nolint: govet 137 type BasicField struct { 138 ID string `@Ident?` 139 Args *FieldArgs `[ '(' @@ ')' ]` 140 Type Type `[ @@ ]` 141 isEmbedded bool 142 } 143 144 // Field definition of a embeddable field resource 145 // nolint: govet 146 type Embeddable struct { 147 Type string `"embed" @Ident { @'.' @Ident }` 148 Alias *string `("as" @Ident)?` 149 } 150 151 // Args list of arguments 152 // nolint: govet 153 type FieldArgs struct { 154 List []SimpleType `[ @@ { ',' @@ } ]` 155 } 156 157 // LEXER 158 159 type lrLexer struct{} 160 161 func (l *lrLexer) Lex(r io.Reader) (lexer.Lexer, error) { 162 var scannerObj scanner.Scanner 163 lexerObj := lexer.LexWithScanner(r, &scannerObj) 164 scannerObj.Mode ^= scanner.SkipComments 165 return lexerObj, nil 166 } 167 168 func (l *lrLexer) Symbols() map[string]rune { 169 return map[string]rune{ 170 "EOF": scanner.EOF, 171 "Char": scanner.Char, 172 "Ident": scanner.Ident, 173 "Int": scanner.Int, 174 "Float": scanner.Float, 175 "String": scanner.String, 176 "RawString": scanner.RawString, 177 "Comment": scanner.Comment, 178 } 179 } 180 181 func (r *Resource) GetInitFields() []*Init { 182 inits := []*Init{} 183 for _, f := range r.Body.Fields { 184 if f.Init != nil { 185 inits = append(inits, f.Init) 186 } 187 } 188 return inits 189 } 190 191 func SanitizeComments(raw []string) []string { 192 todoStart := -1 193 for i := range raw { 194 if raw[i] != "" { 195 raw[i] = strings.Trim(raw[i][2:], " \t\n") 196 } 197 if todoStart == -1 && strings.HasPrefix(raw[i], "TODO") { 198 todoStart = i 199 } 200 } 201 if todoStart != -1 { 202 raw = raw[0:todoStart] 203 } 204 return raw 205 } 206 207 func extractTitleAndDescription(raw []string) (string, string) { 208 if len(raw) == 0 { 209 return "", "" 210 } 211 title, rest := raw[0], raw[1:] 212 213 desc := strings.Join(rest, " ") 214 215 return title, desc 216 } 217 218 // Parse the input leise string to an AST 219 func Parse(input string) (*LR, error) { 220 res := &LR{} 221 222 var lexer lrLexer 223 parser := participle.MustBuild(&LR{}, 224 participle.Lexer(&lexer), 225 ) 226 227 err := parser.Parse(strings.NewReader(input), res) 228 229 // clean up the parsed results 230 for i := range res.Resources { 231 resource := res.Resources[i] 232 233 resource.Comments = SanitizeComments(resource.Comments) 234 resource.title, resource.desc = extractTitleAndDescription(resource.Comments) 235 resource.Comments = nil 236 237 // List types have an implicit list field 238 if resource.ListType != nil { 239 t := resource.ListType.Type.Type 240 args := resource.ListType.Args 241 242 // args of nil tell the compiler that this field needs to be pre-populated 243 // however for list we don't have this logic, it is always computed 244 if args == nil { 245 args = &FieldArgs{} 246 } 247 248 field := &BasicField{ 249 ID: "list", 250 Args: args, 251 Type: Type{ListType: &ListType{Type: Type{SimpleType: &SimpleType{t}}}}, 252 } 253 254 resource.Body.Fields = append(resource.Body.Fields, &Field{BasicField: field}) 255 } 256 257 if resource.Body == nil { 258 continue 259 } 260 if len(resource.Body.Fields) == 0 { 261 continue 262 } 263 264 // eliminate fields that are comment-only (no ID) 265 arr := resource.Body.Fields 266 ptr := len(arr) 267 for j := 0; j < ptr; j++ { 268 if arr[j].BasicField == nil && arr[j].Embeddable == nil && arr[j].Init == nil { 269 arr[j], arr[ptr-1] = arr[ptr-1], arr[j] 270 ptr-- 271 } 272 } 273 if ptr < len(arr) { 274 resource.Body.Fields = arr[:ptr] 275 } 276 277 for i, f := range resource.Body.Fields { 278 if f.Embeddable == nil { 279 continue 280 } 281 var name string 282 if f.Embeddable.Alias != nil { 283 name = *f.Embeddable.Alias 284 } else { 285 // use the first part of the type name as a id, i.e. os for os.any 286 // this wont work if there're are multiple embedded resources without aliases that share the same package, i.e os.any and os.base 287 name = strings.Split(f.Embeddable.Type, ".")[0] 288 } 289 newField := &Field{ 290 Comments: f.Comments, 291 BasicField: &BasicField{ 292 ID: name, 293 Type: Type{SimpleType: &SimpleType{f.Embeddable.Type}}, 294 Args: &FieldArgs{}, 295 isEmbedded: true, 296 }, 297 } 298 resource.Body.Fields[i] = newField 299 } 300 301 } 302 303 return res, err 304 }