Documentation
¶
Overview ¶
Package combinator defines generator functions for creating parser combinators
Index ¶
- func Any(name string, parsers ...parsley.Parser) *parser.NamedFunc
- func Choice(name string, parsers ...parsley.Parser) *parser.NamedFunc
- func Memoize(p parsley.Parser) *parser.NamedFunc
- func Optional(p parsley.Parser) parser.Func
- func Single(p parsley.Parser) parser.Func
- type Recursive
- func Many(p parsley.Parser) *Recursive
- func Many1(p parsley.Parser) *Recursive
- func NewRecursive(token string, name func() string, returnSingle bool, ...) *Recursive
- func Sentence(p parsley.Parser) *Recursive
- func SepBy(valueP parsley.Parser, sepP parsley.Parser) *Recursive
- func SepBy1(valueP parsley.Parser, sepP parsley.Parser) *Recursive
- func SepByOrValue(valueP parsley.Parser, sepP parsley.Parser) *Recursive
- func SepByOrValue1(valueP parsley.Parser, sepP parsley.Parser) *Recursive
- func Seq(token string, name string, parsers ...parsley.Parser) *Recursive
- func SeqTry(token string, name string, parsers ...parsley.Parser) *Recursive
- func SeqTryOrValue(token string, name string, parsers ...parsley.Parser) *Recursive
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Any ¶
Any tries all the given parsers independently and merges the results
Example ¶
Let's define a parser which accepts integer or float numbers. The parser would return with all matches, so both 1 and 1.23.
package main
import (
"fmt"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/data"
"github.com/opsidian/parsley/parser"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/parsley/parsleyfakes"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
// Let's define a parser which accepts integer or float numbers.
// The parser would return with all matches, so both 1 and 1.23.
func main() {
p := combinator.Any("number",
terminal.Integer(),
terminal.Float(),
)
r := text.NewReader(text.NewFile("example.file", []byte("1.23")))
ctx := parsley.NewContext(r)
value, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value, value)
}
var _ = Describe("Any", func() {
var (
p *parser.NamedFunc
r *parsleyfakes.FakeReader
parsers []parsley.Parser
p1, p2 *parsleyfakes.FakeParser
leftRecCtx data.IntMap
pos parsley.Pos
cp, p1CP, p2CP data.IntSet
res, p1Res, p2Res parsley.Node
n1, n2 *parsleyfakes.FakeNode
ctx *parsley.Context
)
BeforeEach(func() {
r = &parsleyfakes.FakeReader{}
ctx = parsley.NewContext(r)
p1 = &parsleyfakes.FakeParser{}
p1.NameReturns("p1")
p2 = &parsleyfakes.FakeParser{}
p2.NameReturns("p2")
leftRecCtx = data.EmptyIntMap
parsers = []parsley.Parser{p1, p2}
pos = parsley.Pos(1)
n1 = &parsleyfakes.FakeNode{}
n1.TokenReturns("n1")
n2 = &parsleyfakes.FakeNode{}
n2.TokenReturns("n2")
p1CP = data.EmptyIntSet
p2CP = data.EmptyIntSet
p1Res = nil
p2Res = nil
n1 = nil
n2 = nil
})
JustBeforeEach(func() {
p1.ParseReturnsOnCall(0, p1Res, p1CP)
p2.ParseReturnsOnCall(0, p2Res, p2CP)
p = combinator.Any("test", parsers...)
res, cp = p.Parse(ctx, leftRecCtx, pos)
})
Context("when no parsers are given", func() {
It("should panic", func() {
Expect(func() { combinator.Any("test") }).To(Panic())
})
})
Context("when there is only one parser", func() {
BeforeEach(func() {
parsers = []parsley.Parser{p1}
p1CP = data.NewIntSet(1)
p1Res = n1
})
It("should return the result of that parser", func() {
Expect(cp).To(Equal(p1CP))
Expect(res).To(Equal(p1Res))
Expect(p1.ParseCallCount()).To(Equal(1))
passedCtx, passedLeftRecCtx, passedPos := p1.ParseArgsForCall(0)
Expect(passedCtx).To(BeEquivalentTo(ctx))
Expect(passedLeftRecCtx).To(BeEquivalentTo(leftRecCtx))
Expect(passedPos).To(Equal(pos))
})
})
Context("with multiple parsers", func() {
BeforeEach(func() {
parsers = []parsley.Parser{p1, p2}
p1CP = data.NewIntSet(1)
p2CP = data.NewIntSet(2)
})
It("should call all parsers", func() {
Expect(p1.ParseCallCount()).To(Equal(1))
passedCtx, passedLeftRecCtx, passedPos := p1.ParseArgsForCall(0)
Expect(passedCtx).To(BeEquivalentTo(ctx))
Expect(passedLeftRecCtx).To(BeEquivalentTo(leftRecCtx))
Expect(passedPos).To(Equal(pos))
Expect(p2.ParseCallCount()).To(Equal(1))
passedCtx, passedLeftRecCtx, passedPos = p2.ParseArgsForCall(0)
Expect(passedCtx).To(BeEquivalentTo(ctx))
Expect(passedLeftRecCtx).To(BeEquivalentTo(leftRecCtx))
Expect(passedPos).To(Equal(pos))
})
It("should merge the curtailing parsers", func() {
Expect(cp).To(Equal(p1CP.Union(p2CP)))
})
Context("when no parsers match", func() {
It("should return nil", func() {
Expect(res).To(BeNil())
})
})
Context("when one parser matches", func() {
BeforeEach(func() {
p1Res = n1
})
It("should return the result of that parser", func() {
Expect(res).To(Equal(p1Res))
})
})
Context("when multiple parsers match", func() {
BeforeEach(func() {
p1Res = n1
p2Res = n2
})
It("should return with all results", func() {
Expect(res).To(Equal(ast.NodeList([]parsley.Node{n1, n2})))
})
})
})
})
Output: float64 1.23
func Choice ¶
Choice tries to apply the given parsers until one of them succeeds
Example ¶
Let's define a parser which accepts integer or float numbers. The parser would return only the first match so in this case we have to put the float parser first.
package main
import (
"fmt"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/data"
"github.com/opsidian/parsley/parser"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/parsley/parsleyfakes"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
// Let's define a parser which accepts integer or float numbers.
// The parser would return only the first match so in this case we have to put the float parser first.
func main() {
p := combinator.Choice("number",
terminal.Float(),
terminal.Integer(),
)
r := text.NewReader(text.NewFile("example.file", []byte("1.23")))
ctx := parsley.NewContext(r)
value, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value, value)
}
var _ = Describe("Choice", func() {
var (
p *parser.NamedFunc
r *parsleyfakes.FakeReader
parsers []parsley.Parser
p1, p2 *parsleyfakes.FakeParser
leftRecCtx data.IntMap
pos parsley.Pos
cp, p1CP, p2CP data.IntSet
res, p1Res, p2Res parsley.Node
n1, n2 *parsleyfakes.FakeNode
ctx *parsley.Context
)
BeforeEach(func() {
r = &parsleyfakes.FakeReader{}
ctx = parsley.NewContext(r)
p1 = &parsleyfakes.FakeParser{}
p1.NameReturns("p1")
p2 = &parsleyfakes.FakeParser{}
p2.NameReturns("p2")
leftRecCtx = data.EmptyIntMap
parsers = []parsley.Parser{p1, p2}
pos = parsley.Pos(1)
n1 = &parsleyfakes.FakeNode{}
n1.TokenReturns("n1")
n2 = &parsleyfakes.FakeNode{}
n2.TokenReturns("n2")
p1CP = data.EmptyIntSet
p2CP = data.EmptyIntSet
p1Res = nil
p2Res = nil
n1 = nil
n2 = nil
})
JustBeforeEach(func() {
p1.ParseReturnsOnCall(0, p1Res, p1CP)
p2.ParseReturnsOnCall(0, p2Res, p2CP)
p = combinator.Choice("test", parsers...)
res, cp = p.Parse(ctx, leftRecCtx, pos)
})
Context("when no parsers are given", func() {
It("should panic", func() {
Expect(func() { combinator.Choice("test") }).To(Panic())
})
})
Context("when there is only one parser", func() {
BeforeEach(func() {
parsers = []parsley.Parser{p1}
p1CP = data.NewIntSet(1)
p1Res = n1
})
It("should return the result of that parser", func() {
Expect(cp).To(Equal(p1CP))
Expect(res).To(Equal(p1Res))
Expect(p1.ParseCallCount()).To(Equal(1))
passedCtx, passedLeftRecCtx, passedPos := p1.ParseArgsForCall(0)
Expect(passedCtx).To(BeEquivalentTo(ctx))
Expect(passedLeftRecCtx).To(BeEquivalentTo(leftRecCtx))
Expect(passedPos).To(Equal(pos))
})
})
Context("with multiple parsers", func() {
BeforeEach(func() {
parsers = []parsley.Parser{p1, p2}
p1CP = data.NewIntSet(1)
p2CP = data.NewIntSet(2)
})
It("should merge the curtailing parsers", func() {
Expect(cp).To(Equal(p1CP.Union(p2CP)))
})
Context("when no parsers match", func() {
It("should return nil", func() {
Expect(res).To(BeNil())
})
It("should call all parsers", func() {
Expect(p1.ParseCallCount()).To(Equal(1))
passedCtx, passedLeftRecCtx, passedPos := p1.ParseArgsForCall(0)
Expect(passedCtx).To(BeEquivalentTo(ctx))
Expect(passedLeftRecCtx).To(BeEquivalentTo(leftRecCtx))
Expect(passedPos).To(Equal(pos))
Expect(p2.ParseCallCount()).To(Equal(1))
passedCtx, passedLeftRecCtx, passedPos = p2.ParseArgsForCall(0)
Expect(passedCtx).To(BeEquivalentTo(ctx))
Expect(passedLeftRecCtx).To(BeEquivalentTo(leftRecCtx))
Expect(passedPos).To(Equal(pos))
})
})
Context("when one parser matches", func() {
BeforeEach(func() {
p1Res = n1
})
It("should return the result of that parser", func() {
Expect(res).To(Equal(p1Res))
})
It("should not call the remaining parsers", func() {
Expect(p2.ParseCallCount()).To(Equal(0))
})
})
})
})
Output: float64 1.23
func Memoize ¶
Memoize handles result cache and curtailing left recursion
Example ¶
Let's define a left-recursive language where we need to curtail left-recursion and also cache previous parser matches with Memoize. Grammar: S -> A, A -> a | Ab
package main
import (
"fmt"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/parser"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
func main() {
concat := ast.InterpreterFunc(func(ctx interface{}, nodes []parsley.Node) (interface{}, parsley.Error) {
var res string
for _, node := range nodes {
val, _ := node.Value(ctx)
if runeVal, ok := val.(rune); ok {
res += string(runeVal)
} else {
res += val.(string)
}
}
return res, nil
})
var p parser.NamedFunc
p = *combinator.Memoize(combinator.Any("a or ab",
terminal.Rune('a'),
combinator.Seq("AB", "", &p, terminal.Rune('b')).Bind(concat),
))
f := text.NewFile("example.file", []byte("abbbbbbbb"))
r := text.NewReader(f)
s := combinator.Sentence(&p)
ctx := parsley.NewContext(r)
value, _ := parsley.Evaluate(ctx, s, nil)
fmt.Printf("%T %v\n", value, value)
}
Output: string abbbbbbbb
func Optional ¶
Optional returns the parser's matches and an empty match
Example ¶
Let's define a parser which accepts "a", an optional "b" and a "c" character. The optional parser will result in a nil node so in the interpreter we have to handle that.
package main
import (
"fmt"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
func main() {
concat := ast.InterpreterFunc(func(ctx interface{}, nodes []parsley.Node) (interface{}, parsley.Error) {
var res string
for _, node := range nodes {
if node != nil {
val, _ := node.Value(ctx)
if val != nil {
res += string(val.(rune))
}
}
}
return res, nil
})
p := combinator.Seq("AB", "",
terminal.Rune('a'),
combinator.Optional(terminal.Rune('b')),
terminal.Rune('c'),
).Bind(concat)
r := text.NewReader(text.NewFile("example.file", []byte("ac")))
ctx := parsley.NewContext(r)
value, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value, value)
}
Output: string ac
Types ¶
type Recursive ¶ added in v0.6.0
type Recursive struct {
// contains filtered or unexported fields
}
Recursive is a recursive and-type combinator
func Many ¶
Many applies the parser zero or more times
Example ¶
Let's define a parser which accepts any number of "a" characters
package main
import (
"fmt"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
func main() {
concat := ast.InterpreterFunc(func(ctx interface{}, nodes []parsley.Node) (interface{}, parsley.Error) {
var res string
for _, node := range nodes {
val, _ := node.Value(ctx)
res += string(val.(rune))
}
return res, nil
})
p := combinator.Many(terminal.Rune('a')).Bind(concat)
r := text.NewReader(text.NewFile("example.file", []byte("aaaaa")))
ctx := parsley.NewContext(r)
value, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value, value)
}
Output: string aaaaa
func NewRecursive ¶ added in v0.6.0
func NewRecursive(token string, name func() string, returnSingle bool, parserLookUp func(int) parsley.Parser, lenCheck func(int) bool) *Recursive
NewRecursive creates a new recursive instance
func SepBy ¶
SepBy applies the given value parser zero or more times separated by the separator parser
Example ¶
Let's define a simple language where you define an integer array. The language would be left recursive, but using SepBy we can avoid this. The grammar is: S -> [I(,I)*], I -> any integer
package main
import (
"fmt"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/ast/interpreter"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
func main() {
arr := ast.InterpreterFunc(func(ctx interface{}, nodes []parsley.Node) (interface{}, parsley.Error) {
var res []int64
for i := 0; i < len(nodes); i += 2 {
val, _ := nodes[i].Value(ctx)
res = append(res, val.(int64))
}
return res, nil
})
intList := combinator.SepBy(terminal.Integer(), terminal.Rune(',')).Bind(arr)
p := combinator.Seq("ARR", "array",
terminal.Rune('['),
intList,
terminal.Rune(']'),
).Bind(interpreter.Select(1))
r := text.NewReader(text.NewFile("example.file", []byte("[]")))
ctx := parsley.NewContext(r)
value1, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value1, value1)
r = text.NewReader(text.NewFile("example.file", []byte("[1,2,3]")))
ctx = parsley.NewContext(r)
value2, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value2, value2)
}
Output: []int64 [] []int64 [1 2 3]
func SepBy1 ¶
SepBy1 applies the given value parser one or more times separated by the separator parser
Example ¶
Let's define a simple language where you can add integer numbers. The language would be left recursive, but using SepBy1 we can avoid this. The grammar is: S -> I(+I)*, I -> any integer The "<empty>" result will never be returned as the SepBy1 doesn't match zero p occurrences.
package main
import (
"fmt"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
func main() {
interpreter := ast.InterpreterFunc(func(ctx interface{}, nodes []parsley.Node) (interface{}, parsley.Error) {
if len(nodes) == 0 {
return "<empty>", nil
}
var sum int64
for i := 0; i < len(nodes); i += 2 {
val, _ := nodes[i].Value(ctx)
sum += val.(int64)
}
return sum, nil
})
p := combinator.SepBy1(terminal.Integer(), terminal.Rune('+')).Bind(interpreter)
r := text.NewReader(text.NewFile("example.file", []byte("")))
ctx := parsley.NewContext(r)
value1, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value1, value1)
r = text.NewReader(text.NewFile("example.file", []byte("1")))
ctx = parsley.NewContext(r)
value2, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value2, value2)
r = text.NewReader(text.NewFile("example.file", []byte("1+2+3")))
ctx = parsley.NewContext(r)
value3, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value3, value3)
}
Output: <nil> <nil> int64 1 int64 6
func SepByOrValue ¶
SepByOrValue applies the given value parser zero or more times separated by the separator parser If there is only one value then the value node will be returned
Example ¶
Let's define a simple language where you concatenate chars. The language would be left recursive, but using SepBy we can avoid this. The grammar is: S -> [V(+V)*], V -> any char In the second call the value of the char node will be returned directly and the interpreter is not used.
package main
import (
"fmt"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
func main() {
concat := ast.InterpreterFunc(func(ctx interface{}, nodes []parsley.Node) (interface{}, parsley.Error) {
var res string
for i := 0; i < len(nodes); i += 2 {
val, _ := nodes[i].Value(ctx)
res = res + string(val.(rune))
}
return res, nil
})
p := combinator.SepByOrValue(terminal.Char(), terminal.Rune('+')).Bind(concat)
r := text.NewReader(text.NewFile("example.file", []byte(`'a'+'b'`)))
ctx := parsley.NewContext(r)
value1, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value1, value1)
r = text.NewReader(text.NewFile("example.file", []byte("'a'")))
ctx = parsley.NewContext(r)
value2, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value2, value2)
}
Output: string ab int32 97
func SepByOrValue1 ¶
SepByOrValue1 applies the given value parser one or more times separated by the separator parser If there is only one value then the value node will be returned
func Seq ¶
Seq tries to apply all parsers after each other matching effectively a sequence of tokens and returns with all combinations of the results. Only matches are returned where all parsers were applied successfully.
Example ¶
Let's define a parser which accepts "a", "b", "c" characters in order.
package main
import (
"fmt"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
func main() {
concat := ast.InterpreterFunc(func(ctx interface{}, nodes []parsley.Node) (interface{}, parsley.Error) {
var res string
for _, node := range nodes {
val, _ := node.Value(ctx)
res += string(val.(rune))
}
return res, nil
})
p := combinator.Seq("ABC", "",
terminal.Rune('a'),
terminal.Rune('b'),
terminal.Rune('c'),
).Bind(concat)
r := text.NewReader(text.NewFile("example.file", []byte("abc")))
ctx := parsley.NewContext(r)
value, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value, value)
}
Output: string abc
func SeqTry ¶
SeqTry tries to apply all parsers after each other matching effectively the longest possible sequences of tokens and returns with all combinations of the results. It needs to match the first parser at least
Example ¶
Let's define a parser which accepts any prefix of the "abc" string.
package main
import (
"fmt"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
func main() {
concat := ast.InterpreterFunc(func(ctx interface{}, nodes []parsley.Node) (interface{}, parsley.Error) {
var res string
for _, node := range nodes {
val, _ := node.Value(ctx)
res += string(val.(rune))
}
return res, nil
})
p := combinator.SeqTry("ABC", "",
terminal.Rune('a'),
terminal.Rune('b'),
terminal.Rune('c'),
).Bind(concat)
r := text.NewReader(text.NewFile("example.file", []byte("ab")))
ctx := parsley.NewContext(r)
value, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value, value)
}
Output: string ab
func SeqTryOrValue ¶ added in v0.8.1
SeqTryOrValue tries to apply all parsers after each other matching effectively the longest possible sequences of tokens and returns with all combinations of the results. It needs to match the first parser at least. If only the first parser matches it will return the result of the first parser directly.
Example ¶
When using the SeqTryOrValue the result will be the value of node 'a' and the interpreter is not used.
package main
import (
"fmt"
"github.com/opsidian/parsley/ast"
"github.com/opsidian/parsley/combinator"
"github.com/opsidian/parsley/parsley"
"github.com/opsidian/parsley/text"
"github.com/opsidian/parsley/text/terminal"
)
func main() {
concat := ast.InterpreterFunc(func(ctx interface{}, nodes []parsley.Node) (interface{}, parsley.Error) {
var res string
for _, node := range nodes {
val, _ := node.Value(ctx)
res += string(val.(rune))
}
return res, nil
})
p := combinator.SeqTryOrValue("ABC", "",
terminal.Rune('a'),
terminal.Rune('b'),
terminal.Rune('c'),
).Bind(concat)
r := text.NewReader(text.NewFile("example.file", []byte("a")))
ctx := parsley.NewContext(r)
value, _ := parsley.Evaluate(ctx, combinator.Sentence(p), nil)
fmt.Printf("%T %v\n", value, value)
}
Output: int32 97
func (*Recursive) Bind ¶ added in v0.6.0
func (rp *Recursive) Bind(interpreter parsley.Interpreter) *Recursive
Bind binds the given interpreter