/
input.go
145 lines (133 loc) · 4.26 KB
/
input.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
// input.go -- regular expression input
package rx
import (
"bufio"
"flag"
"fmt"
"io"
"log"
"os"
"regexp"
"strings"
)
// Globals set as a side effect of loading input
var (
InputRegExCount int // number of expressions successfully loaded
InputErrorCount int // number of unacceptable expressions rejected
)
// A RegExParsed is a single parsed regular expression.
// If Tree is not nil then the expression was parsed as valid.
// If Tree is nil and Err is not, Err is a parsing error.
// If Tree is nil and Err is nil, the struct can represent a comment.
// Meta includes all immediately preceding metadata lines.
type RegExParsed struct {
Expr string // input string
Tree Node // parse tree
Err error // parse error
Meta map[string]string // metadata list
}
// RegExParsed.IsExpr returns true if the struct represents an expression,
// not a comment, whether or not it is valid.
func (rxp *RegExParsed) IsExpr() bool {
return rxp.Tree != nil || rxp.Err != nil
}
// RegExParsed.ShowMeta prints the expression's metadata intelligently.
func (rxp *RegExParsed) ShowMeta(f io.Writer, indent string) {
if rxp.Meta != nil {
for _, k := range KeyList(rxp.Meta) {
for _, s := range strings.Split(rxp.Meta[k], "\n") {
fmt.Fprintf(f, "%s#%s: %s\n", indent, k, s)
}
}
}
}
// LoadExpressions reads a file and parses the expressions found.
// A filename of "" or "-" reads from standard input. Any file error is fatal.
// See LoadFromScanner for details.
func LoadExpressions(fname string, f func(*RegExParsed)) []*RegExParsed {
return LoadFromScanner(MkScanner(fname), f)
}
// LoadFromScanner reads and parses expressions from a bufio.Scanner.
//
// Empty lines and lines beginning with '#' are treated as comments.
// If non-nil, the function f is called for each non-metadata line read.
// The returned array contains only successfully parsed expressions.
//
// Metadata from comments matching the pattern "^#\w+:" is accumulated and
// returned with the next non-metadata line (whether comment or expr).
//
// The globals InputRegExCount and InputExprErrors are set by this function.
func LoadFromScanner(efile *bufio.Scanner, f func(*RegExParsed)) []*RegExParsed {
mpat := regexp.MustCompile(`^#(\w+): *(.*)`)
elist := make([]*RegExParsed, 0)
meta := make(map[string]string)
InputRegExCount = 0
InputErrorCount = 0
for efile.Scan() {
line := efile.Text()
e := &RegExParsed{Expr: line}
if IsComment(line) {
r := mpat.FindStringSubmatch(line)
if r != nil { // if recognized metadata format
addMeta(meta, r[1], r[2]) // accumulate metadata
continue // and don't call
} else {
e.Meta = meta // return accumulation
}
} else {
e.Tree, e.Err = Parse(line) // parse input
if e.Tree != nil { // if okay
elist = append(elist, e) // save parse tree
InputRegExCount++ // count success
} else {
InputErrorCount++ // else count error
}
e.Meta = meta // accumulated metadata
}
if f != nil {
f(e)
}
meta = make(map[string]string) // reset meta collection
}
CkErr(efile.Err())
return elist
}
// addMeta grows the metadata, concatenating with \n if the key is a duplicate.
func addMeta(meta map[string]string, key string, val string) {
if meta[key] == "" {
meta[key] = val
} else {
meta[key] = meta[key] + "\n" + val
}
}
// OneInputFile returns the name of the input file from the command line.
// The program is aborted with an error message if multiple arguments appear.
// If no arguments are present, "-" is returned to represent standard input.
func OneInputFile() string {
flag.Parse() // in case not already called
args := flag.Args()
switch len(args) {
case 0:
return "-"
case 1:
return args[0]
default:
log.Fatal("too many arguments")
}
return "" //NOTREACHED
}
// MkScanner creates a Scanner for reading from a file, aborting on error.
// A filename of "-" reads standard input.
func MkScanner(fname string) *bufio.Scanner { // return scanner for file
if fname == "" || fname == "-" {
return bufio.NewScanner(os.Stdin)
} else {
f, err := os.Open(fname)
CkErr(err)
return bufio.NewScanner(f)
}
}
// IsComment returns true if a line begins with '#' or is empty.
func IsComment(s string) bool {
return len(s) == 0 || s[0] == '#'
}