Skip to content

Commit f835edd

Browse files
committed
convert source to a struct and fix minor issue displaying error
1 parent eeb1b8b commit f835edd

File tree

5 files changed

+66
-71
lines changed

5 files changed

+66
-71
lines changed

file/error.go

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package file
33
import (
44
"fmt"
55
"strings"
6-
"unicode/utf8"
76
)
87

98
type Error struct {
@@ -19,43 +18,49 @@ func (e *Error) Error() string {
1918
return e.format()
2019
}
2120

21+
var tabReplacer = strings.NewReplacer("\t", " ")
22+
2223
func (e *Error) Bind(source Source) *Error {
24+
src := source.String()
25+
2326
e.Line = 1
24-
for i, r := range source {
25-
if i == e.From {
27+
var runeCount, lineStart, lineOffset int
28+
for i, r := range src {
29+
if runeCount == e.From {
2630
break
2731
}
2832
if r == '\n' {
33+
lineStart = i
2934
e.Line++
3035
e.Column = 0
36+
lineOffset = 0
3137
} else {
3238
e.Column++
3339
}
40+
runeCount++
41+
lineOffset++
42+
}
43+
44+
lineEnd := lineStart + strings.IndexByte(src[lineStart:], '\n')
45+
if lineEnd < lineStart {
46+
lineEnd = len(src)
47+
}
48+
if lineStart == lineEnd {
49+
return e
3450
}
35-
if snippet, found := source.Snippet(e.Line); found {
36-
snippet := strings.Replace(snippet, "\t", " ", -1)
37-
srcLine := "\n | " + snippet
38-
var bytes = []byte(snippet)
39-
var indLine = "\n | "
40-
for i := 0; i < e.Column && len(bytes) > 0; i++ {
41-
_, sz := utf8.DecodeRune(bytes)
42-
bytes = bytes[sz:]
43-
if sz > 1 {
44-
goto noind
45-
} else {
46-
indLine += "."
47-
}
48-
}
49-
if _, sz := utf8.DecodeRune(bytes); sz > 1 {
50-
goto noind
51-
} else {
52-
indLine += "^"
53-
}
54-
srcLine += indLine
5551

56-
noind:
57-
e.Snippet = srcLine
52+
const prefix = "\n | "
53+
line := src[lineStart:lineEnd]
54+
snippet := new(strings.Builder)
55+
snippet.Grow(2*len(prefix) + len(line) + lineOffset + 1)
56+
snippet.WriteString(prefix)
57+
tabReplacer.WriteString(snippet, line)
58+
snippet.WriteString(prefix)
59+
for i := 0; i < lineOffset; i++ {
60+
snippet.WriteByte('.')
5861
}
62+
snippet.WriteByte('^')
63+
e.Snippet = snippet.String()
5964
return e
6065
}
6166

file/source.go

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,36 @@
11
package file
22

3-
import (
4-
"strings"
5-
"unicode/utf8"
6-
)
3+
import "strings"
74

8-
type Source []rune
5+
type Source struct {
6+
raw string
7+
}
98

109
func NewSource(contents string) Source {
11-
return []rune(contents)
10+
return Source{
11+
raw: contents,
12+
}
1213
}
1314

1415
func (s Source) String() string {
15-
return string(s)
16+
return s.raw
1617
}
1718

1819
func (s Source) Snippet(line int) (string, bool) {
19-
if s == nil {
20+
if s.raw == "" {
2021
return "", false
2122
}
22-
lines := strings.Split(string(s), "\n")
23-
lineOffsets := make([]int, len(lines))
24-
var offset int
25-
for i, line := range lines {
26-
offset = offset + utf8.RuneCountInString(line) + 1
27-
lineOffsets[i] = offset
28-
}
29-
charStart, found := getLineOffset(lineOffsets, line)
30-
if !found || len(s) == 0 {
31-
return "", false
23+
var start int
24+
for i := 1; i < line; i++ {
25+
pos := strings.IndexByte(s.raw[start:], '\n')
26+
if pos < 0 {
27+
return "", false
28+
}
29+
start += pos + 1
3230
}
33-
charEnd, found := getLineOffset(lineOffsets, line+1)
34-
if found {
35-
return string(s[charStart : charEnd-1]), true
36-
}
37-
return string(s[charStart:]), true
38-
}
39-
40-
func getLineOffset(lineOffsets []int, line int) (int, bool) {
41-
if line == 1 {
42-
return 0, true
43-
} else if line > 1 && line <= len(lineOffsets) {
44-
offset := lineOffsets[line-2]
45-
return offset, true
31+
end := start + strings.IndexByte(s.raw[start:], '\n')
32+
if end < start {
33+
end = len(s.raw)
4634
}
47-
return -1, false
35+
return s.raw[start:end], true
4836
}

parser/lexer/lexer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
func Lex(source file.Source) ([]Token, error) {
1111
l := &lexer{
12-
source: source,
12+
source: []rune(source.String()),
1313
tokens: make([]Token, 0),
1414
start: 0,
1515
end: 0,
@@ -28,7 +28,7 @@ func Lex(source file.Source) ([]Token, error) {
2828
}
2929

3030
type lexer struct {
31-
source file.Source
31+
source []rune
3232
tokens []Token
3333
start, end int
3434
err *file.Error

parser/lexer/lexer_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ literal not terminated (1:10)
335335
früh ♥︎
336336
unrecognized character: U+2665 '♥' (1:6)
337337
| früh ♥︎
338+
| .....^
338339
`
339340

340341
func TestLex_error(t *testing.T) {

vm/vm_test.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99
"time"
1010

11+
"github.com/expr-lang/expr/file"
1112
"github.com/expr-lang/expr/internal/testify/require"
1213

1314
"github.com/expr-lang/expr"
@@ -609,10 +610,10 @@ func TestVM_DirectCallOpcodes(t *testing.T) {
609610
for _, tt := range tests {
610611
t.Run(tt.name, func(t *testing.T) {
611612
program := vm.NewProgram(
612-
nil, // source
613-
nil, // node
614-
nil, // locations
615-
0, // variables
613+
file.Source{}, // source
614+
nil, // node
615+
nil, // locations
616+
0, // variables
616617
tt.consts,
617618
tt.bytecode,
618619
tt.args,
@@ -735,10 +736,10 @@ func TestVM_IndexAndCountOperations(t *testing.T) {
735736
for _, tt := range tests {
736737
t.Run(tt.name, func(t *testing.T) {
737738
program := vm.NewProgram(
738-
nil, // source
739-
nil, // node
740-
nil, // locations
741-
0, // variables
739+
file.Source{}, // source
740+
nil, // node
741+
nil, // locations
742+
0, // variables
742743
tt.consts,
743744
tt.bytecode,
744745
tt.args,
@@ -1176,10 +1177,10 @@ func TestVM_DirectBasicOpcodes(t *testing.T) {
11761177
for _, tt := range tests {
11771178
t.Run(tt.name, func(t *testing.T) {
11781179
program := vm.NewProgram(
1179-
nil, // source
1180-
nil, // node
1181-
nil, // locations
1182-
0, // variables
1180+
file.Source{}, // source
1181+
nil, // node
1182+
nil, // locations
1183+
0, // variables
11831184
tt.consts,
11841185
tt.bytecode,
11851186
tt.args,

0 commit comments

Comments
 (0)