-
Notifications
You must be signed in to change notification settings - Fork 2
/
pipeline_parser.go
138 lines (115 loc) · 3.31 KB
/
pipeline_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
package main
import ()
// Any old piece of a pipeline.
type PipelineNode interface{}
// A command to run in the pipeline
type Command struct {
// The command name to run.
Command string
// The arguments to give it.
Arguments []string
}
// A set union operation (+)
type UnionNode struct {
Left PipelineNode
Right PipelineNode
}
// A set difference operation (-)
type DifferenceNode struct {
Left PipelineNode
Right PipelineNode
}
// Parse a given command at the given index of the token stream.
// Return the command node, and the new position in the token stream.
func parseCommand(tokens []token, idx int) (PipelineNode, int) {
switch tokens[idx].tokenType {
case plus_token:
panic("Unexpected plus")
case minus_token:
panic("Unexpected minus")
case pipe_token:
panic("Unexpected pipe")
case string_literal_token:
panic("expected: command")
}
cmd := Command{Command: tokens[idx].tokenValue}
for {
idx = idx + 1
if idx >= len(tokens) {
break
}
ct := tokens[idx]
if ct.tokenType == identifier_token || ct.tokenType == string_literal_token {
cmd.Arguments = append(cmd.Arguments, ct.tokenValue)
} else {
break
}
}
return cmd, idx
}
// Parse a set union operation (+) at the given index in the token stream.
// The left hand side has already been parsed.
// Return the union node, and the new position in the token stream.
func parsePlus(tokens []token, idx int, lhs PipelineNode) (PipelineNode, int) {
if tokens[idx].tokenType != plus_token {
panic("Unexpected non-plus token")
}
idx++
if idx >= len(tokens) {
panic("Unexpected lack of RHS")
}
rhs, idx := parseRecursively(tokens, idx)
return UnionNode{Left: lhs, Right: rhs}, idx
}
// Parse a set difference operation (-) at the given index in the token stream.
// The left hand side has already been parsed.
// Return the difference node, and the new position in the token stream.
func parseMinus(tokens []token, idx int, lhs PipelineNode) (PipelineNode, int) {
if tokens[idx].tokenType != minus_token {
panic("Unexpected non-minus token")
}
idx++
if idx >= len(tokens) {
panic("Unexpected lack of RHS")
}
rhs, idx := parseRecursively(tokens, idx)
return DifferenceNode{Left: lhs, Right: rhs}, idx
}
// Parse a node at the given index in the token stream.
// Return the node, and the new position in the token stream.
// Note that this may recurse (e.g. if an operator is found)
func parseRecursively(tokens []token, idx int) (PipelineNode, int) {
ret, idx := parseCommand(tokens, idx)
if idx < len(tokens) {
switch tokens[idx].tokenType {
case plus_token:
ret, idx = parsePlus(tokens, idx, ret)
return ret, idx
case minus_token:
ret, idx = parseMinus(tokens, idx, ret)
return ret, idx
case pipe_token:
idx += 1
return ret, idx
case identifier_token:
panic("Unexpected identifier_token")
case string_literal_token:
panic("Unexpected string_literal_token")
}
} else {
return ret, idx
}
panic("Unreachable")
}
// Parse a string pipeline into a slice of PipelineNodes.
// For instance, echo foo | cat will turn into two commands: echo (foo), and cat.
func parsePipeline(cmd string) []PipelineNode {
tokens := lex(cmd)
nodes := []PipelineNode{}
for idx := 0; idx < len(tokens); {
var ret PipelineNode
ret, idx = parseRecursively(tokens, idx)
nodes = append(nodes, ret)
}
return nodes
}