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 }