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  }