-
Notifications
You must be signed in to change notification settings - Fork 67
/
api.go
143 lines (132 loc) · 3.46 KB
/
api.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
package parser
import (
"fmt"
"os"
"strings"
)
// ParseZed calls ConcatSource followed by Parse. If Parse fails, it calls
// ImproveError.
func ParseZed(filenames []string, src string) (interface{}, error) {
src, srcInfo, err := ConcatSource(filenames, src)
if err != nil {
return nil, err
}
p, err := Parse("", []byte(src))
if err != nil {
return nil, ImproveError(err, src, srcInfo)
}
return p, nil
}
// SourceInfo holds source file offsets.
type SourceInfo struct {
filename string
start int
end int
}
// ConcatSource concatenates the source files in filenames followed by src,
// returning the result and a corresponding slice of SourceInfos.
func ConcatSource(filenames []string, src string) (string, []SourceInfo, error) {
var b strings.Builder
var sis []SourceInfo
for _, f := range filenames {
bb, err := os.ReadFile(f)
if err != nil {
return "", nil, err
}
start := b.Len()
b.Write(bb)
sis = append(sis, SourceInfo{f, start, b.Len()})
b.WriteByte('\n')
}
start := b.Len()
b.WriteString(src)
sis = append(sis, SourceInfo{"", start, b.Len()})
if b.Len() == 0 {
return "*", nil, nil
}
return b.String(), sis, nil
}
// ImproveError tries to improve an error from Parse. err is the error. src is
// the source code for which Parse return err. If src came from ConcatSource,
// sis is the corresponding slice of SourceInfo; otherwise, sis is nil.
func ImproveError(err error, src string, sis []SourceInfo) error {
el, ok := err.(errList)
if !ok || len(el) != 1 {
return err
}
pe, ok := el[0].(*parserError)
if !ok {
return err
}
return NewError(src, sis, pe.pos.offset)
}
// Error is a parse error with nice formatting. It includes the source code
// line containing the error.
type Error struct {
Offset int // offset into original source code
filename string // omitted from formatting if ""
LineNum int // zero-based; omitted from formatting if negative
line string // contains no newlines
Column int // zero-based
}
// NewError returns an Error. src is the source code containing the error. If
// src came from ConcatSource, sis is the corresponding slice of SourceInfo;
// otherwise, src is nil. offset is the offset of the error within src.
func NewError(src string, sis []SourceInfo, offset int) error {
var filename string
for _, si := range sis {
if offset < si.end {
filename = si.filename
offset -= si.start
src = src[si.start:si.end]
break
}
}
lineNum := -1
if filename != "" || strings.Count(src, "\n") > 0 {
lineNum = strings.Count(src[:offset], "\n")
}
column := offset
if i := strings.LastIndexByte(src[:offset], '\n'); i != -1 {
column -= i + 1
src = src[i+1:]
}
if i := strings.IndexByte(src, '\n'); i != -1 {
src = src[:i]
}
return &Error{
Offset: offset,
LineNum: lineNum,
Column: column,
filename: filename,
line: src,
}
}
func (e *Error) Error() string {
var b strings.Builder
b.WriteString("error parsing Zed ")
if e.filename != "" {
fmt.Fprintf(&b, "in %s ", e.filename)
}
b.WriteString("at ")
if e.LineNum >= 0 {
fmt.Fprintf(&b, "line %d, ", e.LineNum+1)
}
fmt.Fprintf(&b, "column %d:\n", e.Column+1)
b.WriteString(e.ParseErrorContext())
return b.String()
}
func (e *Error) ParseErrorContext() string {
var b strings.Builder
b.WriteString(e.line + "\n")
for k := 0; k < e.Column; k++ {
if k >= e.Column-4 && k != e.Column-1 {
b.WriteByte('=')
} else {
b.WriteByte(' ')
}
}
b.WriteByte('^')
b.WriteString(" ===")
return b.String()
}