github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/erlang/erlang_parser.go (about)

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