Document cuts
This commit is contained in:
parent
af542eff9e
commit
a0e66b1c46
@ -45,13 +45,12 @@ func Any(parsers ...Parserish) Parser {
|
|||||||
for _, parser := range parserfied {
|
for _, parser := range parserfied {
|
||||||
node := parser(ps)
|
node := parser(ps)
|
||||||
if ps.Errored() {
|
if ps.Errored() {
|
||||||
if ps.Cut > startpos {
|
|
||||||
longestError = ps.Error
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if ps.Error.pos > longestError.pos {
|
if ps.Error.pos > longestError.pos {
|
||||||
longestError = ps.Error
|
longestError = ps.Error
|
||||||
}
|
}
|
||||||
|
if ps.Cut > startpos {
|
||||||
|
break
|
||||||
|
}
|
||||||
ps.Recover()
|
ps.Recover()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -91,7 +90,7 @@ func manyImpl(min int, op Parserish, sep ...Parserish) Parser {
|
|||||||
for {
|
for {
|
||||||
node := opParser(ps)
|
node := opParser(ps)
|
||||||
if ps.Errored() {
|
if ps.Errored() {
|
||||||
if len(result.Child) < min {
|
if len(result.Child) < min || ps.Cut > ps.Pos {
|
||||||
ps.Pos = startpos
|
ps.Pos = startpos
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -116,8 +115,9 @@ func Maybe(parser Parserish) Parser {
|
|||||||
parserfied := Parsify(parser)
|
parserfied := Parsify(parser)
|
||||||
|
|
||||||
return NewParser("Maybe()", func(ps *State) Result {
|
return NewParser("Maybe()", func(ps *State) Result {
|
||||||
|
startpos := ps.Pos
|
||||||
node := parserfied(ps)
|
node := parserfied(ps)
|
||||||
if ps.Errored() {
|
if ps.Errored() && ps.Cut <= startpos {
|
||||||
ps.Recover()
|
ps.Recover()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,12 +163,23 @@ func TestBind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCut(t *testing.T) {
|
func TestCut(t *testing.T) {
|
||||||
// does backtracking happen anywhere else?
|
|
||||||
t.Run("test any", func(t *testing.T) {
|
t.Run("test any", func(t *testing.T) {
|
||||||
_, ps := runParser("var world", Any(Seq("var", Cut, "hello"), "var world"))
|
_, ps := runParser("var world", Any(Seq("var", Cut, "hello"), "var world"))
|
||||||
require.Equal(t, "offset 4: expected hello", ps.Error.Error())
|
require.Equal(t, "offset 4: expected hello", ps.Error.Error())
|
||||||
require.Equal(t, 0, ps.Pos)
|
require.Equal(t, 0, ps.Pos)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("test many", func(t *testing.T) {
|
||||||
|
_, ps := runParser("hello <world", Many(Any(Seq("<", Cut, Chars("a-z"), ">"), Chars("a-z"))))
|
||||||
|
require.Equal(t, "offset 12: expected >", ps.Error.Error())
|
||||||
|
require.Equal(t, 0, ps.Pos)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test maybe", func(t *testing.T) {
|
||||||
|
_, ps := runParser("var", Maybe(Seq("var", Cut, "hello")))
|
||||||
|
require.Equal(t, "offset 3: expected hello", ps.Error.Error())
|
||||||
|
require.Equal(t, 0, ps.Pos)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMerge(t *testing.T) {
|
func TestMerge(t *testing.T) {
|
||||||
|
@ -32,7 +32,7 @@ type debugParser struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dp *debugParser) Name() string {
|
func (dp *debugParser) Name() string {
|
||||||
if len(activeParsers) > 2 && activeParsers[len(activeParsers)-2].Var == dp.Var {
|
if len(activeParsers) > 1 && activeParsers[len(activeParsers)-2].Var == dp.Var {
|
||||||
return dp.Match
|
return dp.Match
|
||||||
}
|
}
|
||||||
return dp.Var
|
return dp.Var
|
||||||
|
24
examples_test.go
Normal file
24
examples_test.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package goparsify_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
. "github.com/vektah/goparsify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleCuts() {
|
||||||
|
// without a cut if the close tag is left out the parser will backtrack and ignore the rest of the string
|
||||||
|
alpha := Chars("a-z")
|
||||||
|
nocut := Many(Any(Seq("<", alpha, ">"), alpha))
|
||||||
|
_, err := Run(nocut, "asdf <foo")
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
|
||||||
|
// with a cut, once we see the open tag we know there must be a close tag that matches it, so the parser will error
|
||||||
|
cut := Many(Any(Seq("<", Cut, alpha, ">"), alpha))
|
||||||
|
_, err = Run(cut, "asdf <foo")
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// left unparsed: <foo
|
||||||
|
// offset 9: expected >
|
||||||
|
}
|
@ -42,17 +42,17 @@ var (
|
|||||||
return Result{Result: attr}
|
return Result{Result: attr}
|
||||||
})
|
})
|
||||||
|
|
||||||
tstart = Seq("<", Cut, identifier, attrs, ">")
|
tstart = Seq("<", identifier, Cut, attrs, ">")
|
||||||
tend = Seq("</", Cut, identifier, ">")
|
tend = Seq("</", Cut, identifier, ">")
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
tag = Map(Seq(tstart, elements, tend), func(node Result) Result {
|
tag = Map(Seq(tstart, Cut, elements, tend), func(node Result) Result {
|
||||||
openTag := node.Child[0]
|
openTag := node.Child[0]
|
||||||
return Result{Result: Tag{
|
return Result{Result: Tag{
|
||||||
Name: openTag.Child[2].Token,
|
Name: openTag.Child[1].Token,
|
||||||
Attributes: openTag.Child[3].Result.(map[string]string),
|
Attributes: openTag.Child[3].Result.(map[string]string),
|
||||||
Body: node.Child[1].Result.([]interface{}),
|
Body: node.Child[2].Result.([]interface{}),
|
||||||
}}
|
}}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
23
readme.md
23
readme.md
@ -7,9 +7,9 @@ A parser-combinator library for building easy to test, read and maintain parsers
|
|||||||
I dont have many benchmarks set up yet, but the json parser is very promising. Nearly keeping up with the stdlib for raw speed:
|
I dont have many benchmarks set up yet, but the json parser is very promising. Nearly keeping up with the stdlib for raw speed:
|
||||||
```
|
```
|
||||||
$ go test -bench=. -benchtime=2s -benchmem ./json
|
$ go test -bench=. -benchtime=2s -benchmem ./json
|
||||||
BenchmarkUnmarshalParsec-8 50000 71447 ns/op 50464 B/op 1318 allocs/op
|
BenchmarkUnmarshalParsec-8 20000 65682 ns/op 50460 B/op 1318 allocs/op
|
||||||
BenchmarkUnmarshalParsify-8 50000 56414 ns/op 43887 B/op 334 allocs/op
|
BenchmarkUnmarshalParsify-8 30000 51292 ns/op 45104 B/op 334 allocs/op
|
||||||
BenchmarkUnmarshalStdlib-8 50000 50187 ns/op 13949 B/op 262 allocs/op
|
BenchmarkUnmarshalStdlib-8 30000 46522 ns/op 13953 B/op 262 allocs/op
|
||||||
PASS
|
PASS
|
||||||
ok github.com/vektah/goparsify/json 10.840s
|
ok github.com/vektah/goparsify/json 10.840s
|
||||||
```
|
```
|
||||||
@ -198,6 +198,23 @@ func init() {
|
|||||||
|
|
||||||
Take a look at [calc](calc/calc.go) for a full example.
|
Take a look at [calc](calc/calc.go) for a full example.
|
||||||
|
|
||||||
|
### preventing backtracking with cuts
|
||||||
|
A cut is a marker that prevents backtracking past the point it was set. This greatly improves error messages when used correctly:
|
||||||
|
```go
|
||||||
|
alpha := Chars("a-z")
|
||||||
|
|
||||||
|
// without a cut if the close tag is left out the parser will backtrack and ignore the rest of the string
|
||||||
|
nocut := Many(Any(Seq("<", alpha, ">"), alpha))
|
||||||
|
_, err := Run(nocut, "asdf <foo")
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
// Outputs: left unparsed: <foo
|
||||||
|
|
||||||
|
// with a cut, once we see the open tag we know there must be a close tag that matches it, so the parser will error
|
||||||
|
cut := Many(Any(Seq("<", Cut, alpha, ">"), alpha))
|
||||||
|
_, err = Run(cut, "asdf <foo")
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
// Outputs: offset 9: expected >
|
||||||
|
```
|
||||||
|
|
||||||
### prior art
|
### prior art
|
||||||
|
|
||||||
|
2
state.go
2
state.go
@ -92,7 +92,7 @@ func (s *State) Get() string {
|
|||||||
|
|
||||||
// Preview of the the next x characters
|
// Preview of the the next x characters
|
||||||
func (s *State) Preview(x int) string {
|
func (s *State) Preview(x int) string {
|
||||||
if s.Pos > len(s.Input) {
|
if s.Pos >= len(s.Input) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if len(s.Input)-s.Pos >= x {
|
if len(s.Input)-s.Pos >= x {
|
||||||
|
@ -43,3 +43,9 @@ func TestState_Errors(t *testing.T) {
|
|||||||
require.Equal(t, 2, ps.Error.Pos())
|
require.Equal(t, 2, ps.Error.Pos())
|
||||||
require.True(t, ps.Errored())
|
require.True(t, ps.Errored())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestState_Preview(t *testing.T) {
|
||||||
|
require.Equal(t, "", NewState("").Preview(10))
|
||||||
|
require.Equal(t, "asdf", NewState("asdf").Preview(10))
|
||||||
|
require.Equal(t, "asdfasdfas", NewState("asdfasdfasdf").Preview(10))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user