-
Notifications
You must be signed in to change notification settings - Fork 1
/
file_test.go
120 lines (113 loc) · 2.81 KB
/
file_test.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
package lex_test
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
"github.com/db47h/lex"
"golang.org/x/text/width"
)
// This example shows how one could use File.GetLineBytes to display nicely
// formatted error messages.
// For the example's sake, we use a dummy lexer that errors on digits, newlines
// and EOF.
//
func ExampleFile_GetLineBytes() {
const (
tokEOF = iota
tokAny
)
expectLT := func(s *lex.State) lex.StateFn {
// digits are followed by a < in order to test proper Seek operation in input.
if s.Next() != '<' {
panic("seek to original pos failed")
}
return nil
}
input := "#〄 - Hello 世界 1<\ndéjà vu 2<"
f := lex.NewFile("INPUT", strings.NewReader(input))
l := lex.NewLexer(f, func(s *lex.State) lex.StateFn {
switch r := s.Next(); r {
case lex.EOF:
s.Errorf(s.Pos(), "some error @EOF")
s.Emit(s.Pos(), tokEOF, nil)
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
s.Errorf(s.Pos(), "digit")
return expectLT
case '\n':
s.Errorf(s.Pos(), "newline")
default:
s.Emit(s.Pos(), tokAny, r)
}
return nil
})
for {
tok, p, v := l.Lex()
if tok == tokEOF {
break
}
if tok == lex.Error {
reportError(f, p, v.(error).Error())
}
}
// The following output will display correctly only with monospaced fonts
// and a UTF-8 locale. The caret alignment will also be off with some fonts
// like Fira Code and East Asian characters.
// Output:
// INPUT:1:23: error digit
// |#〄 - Hello 世界 1<
// | ^
// INPUT:1:25: error newline
// |#〄 - Hello 世界 1<
// | ^
// INPUT:2:11: error digit
// |déjà vu 2<
// | ^
// INPUT:2:13: error some error @EOF
// |déjà vu 2<
// | ^
}
// reportError reports a lexing error in the form:
//
// file:line:col: error description
// source line where the error occurred followed by a line with a carret at the position of the error.
// ^
func reportError(f *lex.File, p int, msg string) {
pos := f.Position(p)
fmt.Printf("%s: error %s\n", pos, msg)
l, err := f.GetLineBytes(p)
if err != nil {
return
}
b := pos.Column - 1
if b > len(l) {
b = len(l)
}
fmt.Printf("|%s\n", l)
fmt.Printf("|%*c^\n", getWidth(l[:b]), ' ')
// or make it red!
// fmt.Printf("|%*c\x1b[31m^\x1b[0m\n", getWidth(l[:b]), ' ')
}
// getWidth computes the width in text cells of a given byte slice.
// (supposing rendering with a UTF-8 locale and monospaced font)
//
func getWidth(l []byte) int {
w := 0
for i := 0; i < len(l); {
r, s := utf8.DecodeRune(l[i:])
i += s
if !unicode.IsGraphic(r) {
continue
}
p := width.LookupRune(r)
switch p.Kind() {
case width.EastAsianFullwidth, width.EastAsianWide:
w += 2
case width.EastAsianAmbiguous:
w += 1 // depends on user locale. 2 if locale is CJK, 1 otherwise.
default:
w += 1
}
}
return w
}