-
-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add parseutil.FileReader to track source filenames and line numbers.
- Loading branch information
Showing
3 changed files
with
204 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Package parseutil contains various utilities for parsing GoAWK source code. | ||
package parseutil | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
) | ||
|
||
// FileReader serves two purposes: | ||
// 1. read input sources and join them into a single source (slice of bytes) | ||
// 2. track the lines counts of each input source | ||
type FileReader struct { | ||
files []file | ||
source bytes.Buffer | ||
} | ||
|
||
type file struct { | ||
path string | ||
lines int | ||
} | ||
|
||
// AddFile adds a single source file. | ||
func (fr *FileReader) AddFile(path string, source io.Reader) error { | ||
curLen := fr.source.Len() | ||
_, err := fr.source.ReadFrom(source) | ||
if err != nil { | ||
return err | ||
} | ||
if !bytes.HasSuffix(fr.source.Bytes(), []byte("\n")) { | ||
// Append newline to file in case it doesn't end with one | ||
fr.source.WriteByte('\n') | ||
} | ||
content := fr.source.Bytes()[curLen:] | ||
lines := bytes.Count(content, []byte("\n")) | ||
fr.files = append(fr.files, file{path, lines}) | ||
return nil | ||
} | ||
|
||
// FileLine resolves an overall line number from the concatenated source code | ||
// to the local line number in that source file (identified by path). | ||
func (fr *FileReader) FileLine(line int) (path string, fileLine int) { | ||
startLine := 1 | ||
for _, f := range fr.files { | ||
if line >= startLine && line < startLine+f.lines { | ||
return f.path, line - startLine + 1 | ||
} | ||
startLine += f.lines | ||
} | ||
return "", 0 | ||
} | ||
|
||
// Source returns the concatenated source code from all files added. | ||
func (fr *FileReader) Source() []byte { | ||
return fr.source.Bytes() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package parseutil_test | ||
|
||
import ( | ||
. "github.com/benhoyt/goawk/internal/parseutil" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
type testFile struct{ name, source string } | ||
|
||
type test struct { | ||
name string | ||
// input: | ||
files []testFile | ||
line int | ||
// expected: | ||
path string | ||
fileLine int | ||
} | ||
|
||
func TestFileReader(t *testing.T) { | ||
fileSetNoNewline := []testFile{ | ||
{"file1", `BEGIN { | ||
print f(1) | ||
}`}, | ||
{"file2", `function f(x) { | ||
print x | ||
}`}, | ||
} | ||
fileSetWithNewline := []testFile{ | ||
{"file1", `BEGIN { | ||
print f(1) | ||
} | ||
`}, | ||
{"file2", `function f(x) { | ||
print x | ||
} | ||
`}, | ||
} | ||
tests := []test{ | ||
{ | ||
"TestInFirstFile", | ||
fileSetNoNewline, | ||
2, | ||
"file1", | ||
2, | ||
}, | ||
{ | ||
"TestInSecondFile", | ||
fileSetNoNewline, | ||
5, | ||
"file2", | ||
2, | ||
}, | ||
{ | ||
"TestInFirstFileWithNewline", | ||
fileSetWithNewline, | ||
2, | ||
"file1", | ||
2, | ||
}, | ||
{ | ||
"TestInSecondFileWithNewline", | ||
fileSetWithNewline, | ||
5, | ||
"file2", | ||
2, | ||
}, | ||
{ | ||
"TestOutside", | ||
fileSetNoNewline, | ||
100, | ||
"", | ||
0, | ||
}, | ||
{ | ||
"TestOutsideNegative", | ||
fileSetNoNewline, | ||
-100, | ||
"", | ||
0, | ||
}, | ||
{ | ||
"TestNoFiles", | ||
[]testFile{}, | ||
1, | ||
"", | ||
0, | ||
}, | ||
{ | ||
"TestZeroLenFiles", | ||
[]testFile{ | ||
{"file1", ""}, | ||
}, | ||
1, | ||
"file1", | ||
1, | ||
}, | ||
{ | ||
"TestZeroLenFiles1", | ||
[]testFile{ | ||
{"file1", ""}, | ||
}, | ||
2, | ||
"", | ||
0, | ||
}, | ||
} | ||
|
||
for _, tst := range tests { | ||
t.Run(tst.name, func(t *testing.T) { | ||
|
||
fr := &FileReader{} | ||
|
||
for _, file := range tst.files { | ||
if nil != fr.AddFile(file.name, strings.NewReader(file.source)) { | ||
panic("should not happen") | ||
} | ||
} | ||
|
||
path, fileLine := fr.FileLine(tst.line) | ||
if path != tst.path { | ||
t.Errorf("expected path: %v, got: %v", tst.path, path) | ||
} | ||
if fileLine != tst.fileLine { | ||
t.Errorf("expected fileLine: %v, got: %v", tst.fileLine, fileLine) | ||
} | ||
|
||
// test result source | ||
source := string(fr.Source()) | ||
for _, file := range tst.files { | ||
if strings.Index(source, file.source) < 0 { | ||
t.Errorf("Source() is incorrect") | ||
} | ||
} | ||
}) | ||
} | ||
} |