/
parser.go
235 lines (213 loc) · 5.96 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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package parser
import (
"bufio"
"bytes"
"github.com/vron/sem/parser/adr"
"io"
"io/ioutil"
"strings"
)
// Constants used to describe what type of a command something is
const (
c_error = iota
C_adr
C_a
C_c
C_i
C_d
C_s
C_m
C_t
C_pipeIn
C_pipeOut
C_pipe
C_bang
C_x
C_y
C_g
C_v
C_k
)
// Build up a map so that we can go from command to it's string representation
// etc, all so that we can pretty print
var cmdInfo map[uint]int
func init() {
cmdInfo = make(map[uint]int, len(commands))
for i, v := range commands {
cmdInfo[v.cmdType] = i
}
}
// List of all the commands that we support
var commands = []struct {
text string
takesText bool
takesReg bool
takesRegSub bool
takesAdr bool
takesUnix bool
takesCmd bool
cmdType uint
desc string
}{
{"a", true, false, false, false, false, false, C_a, "Append text after dot"},
{"c", true, false, false, false, false, false, C_c, "Change text in dot"},
{"i", true, false, false, false, false, false, C_i, "Insert text before dot"},
{"d", false, false, false, false, false, false, C_d, "Delete text in dot"},
{"s", false, false, true, false, false, false, C_s, "Substitute text in dot"},
{"m", false, false, false, true, false, false, C_m, "Move text in dot after address"},
{"t", false, false, false, true, false, false, C_t, "Copy text in dot after address"},
{"<", false, false, false, false, true, false, C_pipeIn, "Replace dot by command"},
{">", false, false, false, false, true, false, C_pipeOut, "Send dot to command"},
{"|", false, false, false, false, true, false, C_pipe, "Send to and replace dot by command"},
{"!", false, false, false, false, true, false, C_bang, "Run the command"},
{"x", false, true, false, false, false, true, C_x, "For each math, set dot, run command"},
{"y", false, true, false, false, false, true, C_y, "Between matches, set dot, run command"},
{"g", false, true, false, false, false, true, C_g, "If it matches, run command"},
{"v", false, true, false, false, false, true, C_v, "If it doesnt match, run command"},
{"k", false, false, false, false, false, false, C_k, "Store address in dot"},
}
type Command struct {
Type uint // The type of command, given by the constants
err error // The error that occured if this is a error command
Text string // Text field of the command
Sub string // Second text filed, or substitute field of command
Cmds []Command // List of subcommands
Adr adr.Node // The adress structure if this is a address command or takes an address
}
// Tries to parse the given slice of bytes as commands, if an error is encountered
// it will return a slice of all fully parsed commands and an error. Any partly
// parsed command is discarded
func Parse(b []byte) ([]Command, error) {
return parse(b)
}
// As Parse but takes a string
func ParseString(s string) ([]Command, error) {
return parse([]byte(s))
}
// As Parse but for a reader
func ParseReader(r io.Reader) ([]Command, error) {
b, e := ioutil.ReadAll(r)
if e != nil && e != io.EOF {
return nil, e
}
return parse(b)
}
// As Parse but uses it to propagate put one command at a time at the returned
// channels (blocking). If an error is encountered an error is propagated on
// the error channel and this function returns. However, all commands on cmd
// should be handled before error
func ParseLive(r *bufio.Reader) (chan Command, chan error) {
cm, er := make(chan Command), make(chan error)
go func() {
l := lexer{r, make(chan *Command)}
go l.run()
for {
v := <-l.items
if v == nil {
return
}
// Check so the command is not an error
if v.Type == c_error {
println("ERR")
if v.err != io.EOF {
er <- v.err
return
}
} else {
cm <- *v
}
}
}()
return cm, er
}
func parse(b []byte) ([]Command, error) {
ba := bytes.NewBuffer(b)
buf := bufio.NewReader(ba)
l := lexer{buf, make(chan *Command)}
go l.run()
// Get the output until EOF
cmds := []Command{}
var e error
for {
v := <-l.items
if v == nil {
break
}
// Check so the command is not an error
if v.Type == c_error {
if v.err != io.EOF {
e = v.err
}
break
} else {
cmds = append(cmds, *v)
}
}
return cmds, e
}
func (c *Command) String() string {
// This thing will recursively print the command so we better create a buffer to
// write to..
buf := bytes.NewBuffer(nil)
c.recString(buf, true)
return buf.String()
}
func (c *Command) recString(w io.Writer, terminate bool) {
// Get the info of what I am
id, ok := cmdInfo[c.Type]
if !ok {
if c.Type != C_adr {
panic("Unimplemented")
}
// So this is an adr, format it nicely by calling into the adr package
io.WriteString(w, c.Adr.String())
io.WriteString(w, " ")
return
}
desc := commands[id]
io.WriteString(w, desc.text)
if desc.takesText {
sep, str, _ := getSep(c.Text, "")
io.WriteString(w, sep)
io.WriteString(w, str)
io.WriteString(w, sep)
}
if desc.takesRegSub {
sep, a, b := getSep(c.Text, c.Sub)
io.WriteString(w, sep)
io.WriteString(w, a)
io.WriteString(w, sep)
io.WriteString(w, b)
io.WriteString(w, sep)
}
if desc.takesAdr {
io.WriteString(w, " ")
io.WriteString(w, c.Adr.String())
}
if desc.takesUnix {
panic("TODO")
}
if desc.takesCmd {
io.WriteString(w, " ")
c.Cmds[0].recString(w, false)
}
if terminate {
io.WriteString(w, "\n")
}
}
// Priority order for how separator is choosen, try first first etc.
// if none of them can be used without escaping the first is choosen
// and all instances are escaped
var seps = []string{"/", "|", "-", "'", " "}
// Returns the best choice of separator for the given strings, as well
// as the input string escaped so that it is pretty! (sep, escaped a, escaped b)
func getSep(a, b string) (string, string, string) {
for _, v := range seps {
if !strings.Contains(a, v) && !strings.Contains(b, v) {
return v, a, b
}
}
// No match found, then escape each occurance
return seps[0], strings.Replace(a, seps[0], `\`+seps[0], -1),
strings.Replace(b, seps[0], `\`+seps[0], -1)
}