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
|
package main
import (
"bufio"
"fmt"
"io"
)
type toktype int
const (
tText toktype = iota
tNextCmd
)
type token struct {
Type toktype
Data string
Line int
}
func scan(r io.Reader, tokens chan<- token, errch chan<- error) {
emit := func(t toktype, d []byte, line int) {
if t == tText && len(d) == 0 {
return
}
tokens <- token{t, string(d), line}
}
err := func() error {
br := bufio.NewReader(r)
escaped := false
data := []byte{}
line := 1
for {
b, err := br.ReadByte()
switch err {
case nil:
case io.EOF:
return nil
default:
return ErrorAtLine{line, err}
}
if b == '\n' {
line++
}
if escaped {
data = append(data, b)
escaped = false
continue
}
switch b {
case '\\':
escaped = true
case ' ', '\t':
emit(tText, data, line)
data = data[:0]
case '\n':
emit(tText, data, line)
data = data[:0]
emit(tNextCmd, nil, line)
default:
data = append(data, b)
}
}
emit(tText, data, line)
emit(tNextCmd, nil, line)
return nil
}()
close(tokens)
errch <- err
}
type command struct {
Name string
Params []string
Line int
}
func parse(tokens <-chan token, cmds chan<- command) {
defer close(cmds)
startcmd := true
cmd := command{"", make([]string, 0), 0}
for tok := range tokens {
switch tok.Type {
case tText:
if startcmd {
cmd.Name = tok.Data
cmd.Line = tok.Line
startcmd = false
} else {
cmd.Params = append(cmd.Params, tok.Data)
}
case tNextCmd:
if !startcmd {
cmds <- cmd
cmd.Name = ""
cmd.Params = make([]string, 0)
startcmd = true
}
}
}
}
type cmdfunc func(params []string) error
var commands = map[string]cmdfunc{
"nop": func(_ []string) error { return nil },
}
func RegisterCommand(name string, f cmdfunc) {
commands[name] = f
}
type ErrorAtLine struct {
Line int
Err error
}
func (err ErrorAtLine) Error() string {
return fmt.Sprintf("%s (at line %d)", err.Err, err.Line)
}
type CommandNotFound string
func (c CommandNotFound) Error() string {
return fmt.Sprintf("Command \"%s\" not found", c)
}
func RunCommands(r io.Reader) error {
errch := make(chan error)
tokens := make(chan token)
cmds := make(chan command)
go scan(r, tokens, errch)
go parse(tokens, cmds)
for cmd := range cmds {
f, ok := commands[cmd.Name]
if !ok {
return ErrorAtLine{cmd.Line, CommandNotFound(cmd.Name)}
}
if err := f(cmd.Params); err != nil {
return ErrorAtLine{cmd.Line, err}
}
}
return <-errch
}
|