From 4cddf83904057fbe93632374ddfc45bf045e8526 Mon Sep 17 00:00:00 2001 From: Chloe Date: Mon, 27 Apr 2020 17:35:53 +1000 Subject: [PATCH] implement Scanner for Many and Branch (#69) * feat: add MergeScanners function to merge multiple same-src Scanners * feat: implement Scanner function for Many and Branch * chore: bold filename text --- ast/node_string.go | 42 +++++++++++++++++++++-- ast/node_test.go | 78 ++++++++++++++++++++++++++++++++++++++++++ parser/scanner.go | 39 +++++++++++++++++++-- parser/scanner_test.go | 29 ++++++++++++++++ 4 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 ast/node_test.go diff --git a/ast/node_string.go b/ast/node_string.go index 4de6f58..e5f0258 100644 --- a/ast/node_string.go +++ b/ast/node_string.go @@ -115,7 +115,20 @@ func (c One) Scanner() parser.Scanner { } func (c Many) Scanner() parser.Scanner { - panic("Scanner() not valid for Many") + childrenScanners := make([]parser.Scanner, 0, len(c)) + for _, n := range c { + childrenScanners = append(childrenScanners, n.Scanner()) + } + + if len(childrenScanners) > 0 { + manyScanner, err := parser.MergeScanners(childrenScanners...) + if err != nil { + panic(err) + } + return manyScanner + } + + return parser.Scanner{} } func (c Extra) Scanner() parser.Scanner { @@ -130,5 +143,30 @@ func (n Branch) Scanner() parser.Scanner { if len(n) == 1 && n.oneChild() != nil { return n.oneChild().Scanner() } - panic("Scanner() not valid for Branch") + + scanners := make([]parser.Scanner, 0) + for childrenName, ch := range n { + if !strings.HasPrefix(childrenName, "@") { + switch c := ch.(type) { + case One: + if s := c.Node.Scanner(); !s.IsNil() { + scanners = append(scanners, s) + } + case Many: + if s := c.Scanner(); !s.IsNil() { + scanners = append(scanners, s) + } + } + } + } + + if len(scanners) > 0 { + branchScanner, err := parser.MergeScanners(scanners...) + if err != nil { + panic(err) + } + return branchScanner + } + + return parser.Scanner{} } diff --git a/ast/node_test.go b/ast/node_test.go new file mode 100644 index 0000000..55b2066 --- /dev/null +++ b/ast/node_test.go @@ -0,0 +1,78 @@ +package ast + +import ( + "testing" + + "github.com/arr-ai/wbnf/parser" + "github.com/stretchr/testify/assert" +) + +func TestBranchScanner(t *testing.T) { + str := "one\ntwo\nthree\nfour" + + assertBranchScanner(t, parser.NewScannerAt(str, 0, 4), Branch{ + "foo": One{ + Node: Leaf(*parser.NewScannerAt(str, 0, 4)), + }, + }) + assertBranchScanner(t, parser.NewScannerAt(str, 0, 6), Branch{ + "foo": One{ + Node: Leaf(*parser.NewScannerAt(str, 0, 4)), + }, + "bar": One{ + Node: Leaf(*parser.NewScannerAt(str, 5, 1)), + }, + }) + assertBranchScanner(t, parser.NewScannerAt(str, 0, 11), Branch{ + "foo": One{ + Node: Leaf(*parser.NewScannerAt(str, 0, 4)), + }, + "bar": One{ + Node: Leaf(*parser.NewScannerAt(str, 10, 1)), + }, + }) + assertBranchScanner(t, parser.NewScannerAt(str, 0, 17), Branch{ + "foo": Many{ + Leaf(*parser.NewScannerAt(str, 0, 4)), + Leaf(*parser.NewScannerAt(str, 7, 10)), + }, + }) + assertBranchScanner(t, parser.NewScannerAt(str, 0, 17), Branch{ + "foo": Many{ + Leaf(*parser.NewScannerAt(str, 0, 4)), + Leaf(*parser.NewScannerAt(str, 7, 10)), + }, + "bar": Many{ + Leaf(*parser.NewScannerAt(str, 0, 4)), + Leaf(*parser.NewScannerAt(str, 7, 1)), + }, + }) + assertBranchScanner(t, parser.NewScannerAt(str, 0, 17), Branch{ + "foo": Many{ + Leaf(*parser.NewScannerAt(str, 0, 4)), + Leaf(*parser.NewScannerAt(str, 7, 10)), + }, + "bar": One{ + Node: Leaf(*parser.NewScannerAt(str, 5, 1)), + }, + }) + + assertBranchScanner(t, &parser.Scanner{}, Branch{}) + assertBranchScanner(t, &parser.Scanner{}, Branch{ + "foo": Many{}, + }) + assertBranchScanner(t, &parser.Scanner{}, Branch{ + "foo": Many{}, + "bar": Many{}, + }) + assertBranchScanner(t, parser.NewScannerAt(str, 5, 1), Branch{ + "foo": Many{}, + "bar": One{ + Node: Leaf(*parser.NewScannerAt(str, 5, 1)), + }, + }) +} + +func assertBranchScanner(t *testing.T, s *parser.Scanner, b Branch) { + assert.Equal(t, *s, b.Scanner()) +} diff --git a/parser/scanner.go b/parser/scanner.go index 2bedd8a..5df17b7 100644 --- a/parser/scanner.go +++ b/parser/scanner.go @@ -1,6 +1,7 @@ package parser import ( + "errors" "fmt" "regexp" "strings" @@ -55,6 +56,10 @@ func (s Scanner) String() string { return s.slice() } +func (s Scanner) IsNil() bool { + return s.src == nil +} + func (s Scanner) Format(state fmt.State, c rune) { if c == 'q' { _, _ = fmt.Fprintf(state, "%q", s.slice()) @@ -65,7 +70,7 @@ func (s Scanner) Format(state fmt.State, c rune) { var ( NoLimit = -1 - DefaultLimit = 3 + DefaultLimit = 1 ) func (s Scanner) Context(limitLines int) string { @@ -85,7 +90,7 @@ func (s Scanner) Context(limitLines int) string { } } - return fmt.Sprintf("\n%s:%d:%d:\n\n%s\033[1;31m%s\033[0m%s", + return fmt.Sprintf("\n\033[1;37m%s:%d:%d:\033[0m\n%s\033[1;31m%s\033[0m%s", s.Filename(), lineno, colno, @@ -118,6 +123,36 @@ func (s Scanner) Skip(i int) *Scanner { return &Scanner{s.src, s.sliceStart + i, s.sliceLength - i} } +func MergeScanners(items ...Scanner) (Scanner, error) { + if len(items) == 0 { + return Scanner{}, errors.New("needs at least one scanner") + } + if len(items) == 1 { + return items[0], nil + } + + l, r := items[0].sliceStart, items[0].sliceStart+items[0].sliceLength + src := items[0].src + + for _, v := range items[1:] { + if v.src != src { + return Scanner{}, fmt.Errorf("scanners' sources are not the same: %s vs %s", src, v.src) + } + if v.sliceStart < l { + l = v.sliceStart + } + if v.sliceStart+v.sliceLength > r { + r = v.sliceStart + v.sliceLength + } + } + + return Scanner{ + src: src, + sliceStart: l, + sliceLength: r - l, + }, nil +} + // Eat returns a scanner containing the next i bytes and advances s past them. func (s *Scanner) Eat(i int, eaten *Scanner) *Scanner { eaten.src = s.src diff --git a/parser/scanner_test.go b/parser/scanner_test.go index ad81b55..176e611 100644 --- a/parser/scanner_test.go +++ b/parser/scanner_test.go @@ -1,6 +1,7 @@ package parser import ( + "errors" "testing" "github.com/stretchr/testify/assert" @@ -36,3 +37,31 @@ func assertLineColumn(t *testing.T, scanner *Scanner, line, column int) { assert.Equal(t, line, l) assert.Equal(t, column, c) } + +func TestScannerMerge(t *testing.T) { + str := "one\ntwo\nthree\nfour" + src := stringSource{origin: str} + + assertMergedScanner(t, src, 0, 5, []Scanner{*NewScannerAt(str, 0, 5)}) + assertMergedScanner(t, src, 0, len(str), []Scanner{*NewScanner(str), *NewScanner(str)}) + assertMergedScanner(t, src, 0, len(str), []Scanner{*NewScanner(str), *NewScannerAt(str, 0, 1)}) + assertMergedScanner(t, src, 0, 11, []Scanner{*NewScannerAt(str, 0, 1), *NewScannerAt(str, 5, 6)}) + assertMergedScanner(t, src, 0, 11, []Scanner{*NewScannerAt(str, 0, 1), *NewScannerAt(str, 3, 1), *NewScannerAt(str, 5, 6)}) + assertMergedScanner(t, src, 0, 6, []Scanner{*NewScannerAt(str, 0, 1), *NewScannerAt(str, 0, 4), *NewScannerAt(str, 0, 6)}) + + assertMergedScannerErr(t, errors.New("needs at least one scanner"), []Scanner{}) + assertMergedScannerErr(t, errors.New("scanners' sources are not the same: {one\ntwo\nthree\nfour } vs {another src }"), []Scanner{*NewScanner(str), *NewScanner("another src")}) +} + +func assertMergedScanner(t *testing.T, src source, offset, length int, items []Scanner) { + s, err := MergeScanners(items...) + assert.NoError(t, err) + assert.Equal(t, src, s.src) + assert.Equal(t, offset, s.sliceStart) + assert.Equal(t, length, s.sliceLength) +} + +func assertMergedScannerErr(t *testing.T, err error, items []Scanner) { + _, e := MergeScanners(items...) + assert.Equal(t, err, e) +}