github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/erlang/erlang_parser.go (about)

     1  package erlang
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  
     9  	"github.com/anchore/syft/syft/internal/parsing"
    10  )
    11  
    12  type erlangNode struct {
    13  	value interface{}
    14  }
    15  
    16  var errSkipComments = errors.New("")
    17  
    18  func (e erlangNode) Slice() []erlangNode {
    19  	out, ok := e.value.([]erlangNode)
    20  	if ok {
    21  		return out
    22  	}
    23  	return []erlangNode{}
    24  }
    25  
    26  func (e erlangNode) String() string {
    27  	out, ok := e.value.(string)
    28  	if ok {
    29  		return out
    30  	}
    31  	return ""
    32  }
    33  
    34  func (e erlangNode) Get(index int) erlangNode {
    35  	s := e.Slice()
    36  	if len(s) > index {
    37  		return s[index]
    38  	}
    39  	return erlangNode{}
    40  }
    41  
    42  func node(value interface{}) erlangNode {
    43  	return erlangNode{
    44  		value: value,
    45  	}
    46  }
    47  
    48  // parseErlang basic parser for erlang, used by rebar.lock
    49  func parseErlang(reader io.Reader) (erlangNode, error) {
    50  	data, err := io.ReadAll(reader)
    51  	if err != nil {
    52  		return node(nil), err
    53  	}
    54  
    55  	out := erlangNode{
    56  		value: []erlangNode{},
    57  	}
    58  
    59  	i := 0
    60  	for i < len(data) {
    61  		item, err := parseErlangBlock(data, &i)
    62  		if err == errSkipComments {
    63  			parsing.SkipWhitespace(data, &i)
    64  			continue
    65  		}
    66  		if err != nil {
    67  			return node(nil), fmt.Errorf("%w\n%s", err, parsing.PrintError(data, i))
    68  		}
    69  
    70  		parsing.SkipWhitespace(data, &i)
    71  
    72  		if i, ok := item.value.(string); ok && i == "." {
    73  			continue
    74  		}
    75  
    76  		out.value = append(out.value.([]erlangNode), item)
    77  	}
    78  	return out, nil
    79  }
    80  
    81  func parseErlangBlock(data []byte, i *int) (erlangNode, error) {
    82  	block, err := parseErlangNode(data, i)
    83  	if err != nil {
    84  		return node(nil), err
    85  	}
    86  
    87  	parsing.SkipWhitespace(data, i)
    88  	*i++ // skip the trailing .
    89  	return block, nil
    90  }
    91  
    92  func parseErlangNode(data []byte, i *int) (erlangNode, error) {
    93  	parsing.SkipWhitespace(data, i)
    94  	c := data[*i]
    95  	switch c {
    96  	case '[', '{':
    97  		offset := *i + 1
    98  		parsing.SkipWhitespace(data, &offset)
    99  		c2 := data[offset]
   100  
   101  		// Add support for empty lists
   102  		if (c == '[' && c2 == ']') || (c == '{' && c2 == '}') {
   103  			*i = offset + 1
   104  			return node(nil), nil
   105  		}
   106  
   107  		return parseErlangList(data, i)
   108  	case '"':
   109  		fallthrough
   110  	case '\'':
   111  		return parseErlangString(data, i)
   112  	case '<':
   113  		return parseErlangAngleString(data, i)
   114  	case '%':
   115  		parseErlangComment(data, i)
   116  		return node(nil), errSkipComments
   117  	}
   118  
   119  	if parsing.IsLiteral(c) {
   120  		return parseErlangLiteral(data, i)
   121  	}
   122  
   123  	return erlangNode{}, fmt.Errorf("invalid literal character: %s", string(c))
   124  }
   125  
   126  func parseErlangLiteral(data []byte, i *int) (erlangNode, error) {
   127  	var buf bytes.Buffer
   128  	for *i < len(data) {
   129  		c := data[*i]
   130  		if parsing.IsLiteral(c) {
   131  			buf.WriteByte(c)
   132  		} else {
   133  			break
   134  		}
   135  		*i++
   136  	}
   137  	return node(buf.String()), nil
   138  }
   139  
   140  func parseErlangAngleString(data []byte, i *int) (erlangNode, error) {
   141  	*i += 2
   142  	out, err := parseErlangString(data, i)
   143  	*i += 2
   144  	return out, err
   145  }
   146  
   147  func parseErlangString(data []byte, i *int) (erlangNode, error) {
   148  	delim := data[*i]
   149  	*i++
   150  	var buf bytes.Buffer
   151  	for *i < len(data) {
   152  		c := data[*i]
   153  		if c == delim {
   154  			*i++
   155  			return node(buf.String()), nil
   156  		}
   157  		if c == '\\' {
   158  			*i++
   159  			if len(data) >= *i {
   160  				return node(nil), fmt.Errorf("invalid escape without closed string at %d", *i)
   161  			}
   162  			c = data[*i]
   163  		}
   164  		buf.WriteByte(c)
   165  		*i++
   166  	}
   167  	return node(nil), fmt.Errorf("unterminated string at %d", *i)
   168  }
   169  
   170  func parseErlangList(data []byte, i *int) (erlangNode, error) {
   171  	*i++
   172  	out := erlangNode{
   173  		value: []erlangNode{},
   174  	}
   175  	for *i < len(data) {
   176  		item, err := parseErlangNode(data, i)
   177  		if err != nil {
   178  			if err == errSkipComments {
   179  				parsing.SkipWhitespace(data, i)
   180  				continue
   181  			}
   182  			return node(nil), err
   183  		}
   184  		out.value = append(out.value.([]erlangNode), item)
   185  		parsing.SkipWhitespace(data, i)
   186  		c := data[*i]
   187  		switch c {
   188  		case ',':
   189  			*i++
   190  			continue
   191  		case '%':
   192  			// Starts a new comment node
   193  			continue
   194  		case ']', '}':
   195  			*i++
   196  			return out, nil
   197  		default:
   198  			return node(nil), fmt.Errorf("unexpected character: %s", string(c))
   199  		}
   200  	}
   201  	return out, nil
   202  }
   203  
   204  func parseErlangComment(data []byte, i *int) {
   205  	for *i < len(data) {
   206  		c := data[*i]
   207  
   208  		*i++
   209  
   210  		// Rest of a line is a comment. Deals with CR, LF and CR/LF
   211  		if c == '\n' {
   212  			break
   213  		} else if c == '\r' && data[*i] == '\n' {
   214  			*i++
   215  			break
   216  		}
   217  	}
   218  }