-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.go
144 lines (124 loc) · 3.8 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// This package implements a parser and parse tree dumper for Dockerfiles.
package parser
import (
"bufio"
"fmt"
"io"
"regexp"
"strings"
"unicode"
)
// Node is a structure used to represent a parse tree.
//
// In the node there are three fields, Value, Next, and Children. Value is the
// current token's string value. Next is always the next non-child token, and
// children contains all the children. Here's an example:
//
// (value next (child child-next child-next-next) next-next)
//
// This data structure is frankly pretty lousy for handling complex languages,
// but lucky for us the Dockerfile isn't very complicated. This structure
// works a little more effectively than a "proper" parse tree for our needs.
//
type Node struct {
Value string // actual content
Next *Node // the next item in the current sexp
Children []*Node // the children of this sexp
Attributes map[string]bool // special attributes for this node
Original string // original line used before parsing
}
var (
dispatch map[string]func(string) (*Node, map[string]bool, error)
TOKEN_WHITESPACE = regexp.MustCompile(`[\t\v\f\r ]+`)
TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\[ \t]*$`)
TOKEN_COMMENT = regexp.MustCompile(`^#.*$`)
)
func init() {
// Dispatch Table. see line_parsers.go for the parse functions.
// The command is parsed and mapped to the line parser. The line parser
// recieves the arguments but not the command, and returns an AST after
// reformulating the arguments according to the rules in the parser
// functions. Errors are propogated up by Parse() and the resulting AST can
// be incorporated directly into the existing AST as a next.
dispatch = map[string]func(string) (*Node, map[string]bool, error){
"user": parseString,
"onbuild": parseSubCommand,
"workdir": parseString,
"env": parseEnv,
"maintainer": parseString,
"from": parseString,
"add": parseMaybeJSONToList,
"copy": parseMaybeJSONToList,
"run": parseMaybeJSON,
"cmd": parseMaybeJSON,
"entrypoint": parseMaybeJSON,
"expose": parseStringsWhitespaceDelimited,
"volume": parseMaybeJSONToList,
"insert": parseIgnore,
}
}
// parse a line and return the remainder.
func parseLine(line string) (string, *Node, error) {
if line = stripComments(line); line == "" {
return "", nil, nil
}
if TOKEN_LINE_CONTINUATION.MatchString(line) {
line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
return line, nil, nil
}
cmd, args, err := splitCommand(line)
if err != nil {
return "", nil, err
}
if len(args) == 0 {
return "", nil, fmt.Errorf("Instruction %q is empty; cannot continue", cmd)
}
node := &Node{}
node.Value = cmd
sexp, attrs, err := fullDispatch(cmd, args)
if err != nil {
return "", nil, err
}
node.Next = sexp
node.Attributes = attrs
node.Original = line
return "", node, nil
}
// The main parse routine. Handles an io.ReadWriteCloser and returns the root
// of the AST.
func Parse(rwc io.Reader) (*Node, error) {
root := &Node{}
scanner := bufio.NewScanner(rwc)
for scanner.Scan() {
scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
line, child, err := parseLine(scannedLine)
if err != nil {
return nil, err
}
if line != "" && child == nil {
for scanner.Scan() {
newline := scanner.Text()
if stripComments(strings.TrimSpace(newline)) == "" {
continue
}
line, child, err = parseLine(line + newline)
if err != nil {
return nil, err
}
if child != nil {
break
}
}
if child == nil && line != "" {
line, child, err = parseLine(line)
if err != nil {
return nil, err
}
}
}
if child != nil {
root.Children = append(root.Children, child)
}
}
return root, nil
}