Files
LocalAI/pkg/functions/peg/parser_test.go
Ettore Di Giacinto 59108fbe32 feat: add distributed mode (#9124)
* feat: add distributed mode (experimental)

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix data races, mutexes, transactions

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactorings

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fixups

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix events and tool stream in agent chat

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* use ginkgo

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(cron): compute correctly time boundaries avoiding re-triggering

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* enhancements, refactorings

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* do not flood of healthy checks

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* do not list obvious backends as text backends

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* tests fixups

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Drop redundant healthcheck

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* enhancements, refactorings

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-03-30 00:47:27 +02:00

778 lines
23 KiB
Go

package peg_test
import (
"github.com/mudler/LocalAI/pkg/functions/peg"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func extractTags(ast *peg.AstArena, result *peg.ParseResult) map[string]string {
tags := make(map[string]string)
ast.VisitResult(result, func(node *peg.AstNode) {
if node.Tag != "" {
tags[node.Tag] = node.Text
}
})
return tags
}
var _ = Describe("PEG Parser", func() {
Context("LiteralParser", func() {
It("succeeds on exact match", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Literal("hello")
})
ctx := peg.NewParseContext("hello world", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.Start).To(Equal(0))
Expect(r.End).To(Equal(5))
})
It("fails on mismatch", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Literal("hello")
})
ctx := peg.NewParseContext("world", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Fail))
})
It("returns NeedMoreInput in partial mode", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Literal("hello")
})
ctx := peg.NewParseContext("hel", true)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.NeedMoreInput))
})
It("fails on partial input when not in partial mode", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Literal("hello")
})
ctx := peg.NewParseContext("hel", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Fail))
})
})
Context("SequenceParser", func() {
It("matches full sequence", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("hello"), b.Literal(" "), b.Literal("world"))
})
ctx := peg.NewParseContext("hello world", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(11))
})
It("fails midway", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("hello"), b.Literal("X"))
})
ctx := peg.NewParseContext("hello world", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Fail))
})
It("returns NeedMoreInput at boundary in partial mode", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("hello"), b.Literal(" world"))
})
ctx := peg.NewParseContext("hello wo", true)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.NeedMoreInput))
})
})
Context("ChoiceParser", func() {
It("matches first alternative", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Choice(b.Literal("hello"), b.Literal("world"))
})
ctx := peg.NewParseContext("hello", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(5))
})
It("matches second alternative", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Choice(b.Literal("hello"), b.Literal("world"))
})
ctx := peg.NewParseContext("world", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(5))
})
It("fails when all alternatives fail", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Choice(b.Literal("hello"), b.Literal("world"))
})
ctx := peg.NewParseContext("foo", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Fail))
})
})
Context("RepetitionParser", func() {
It("handles zero or more matches", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.ZeroOrMore(b.Literal("ab"))
})
ctx := peg.NewParseContext("ababab", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(6))
ctx2 := peg.NewParseContext("xyz", false)
r2 := arena.Parse(ctx2)
Expect(r2.Type).To(Equal(peg.Success))
Expect(r2.End).To(Equal(0))
})
It("handles one or more matches", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.OneOrMore(b.Literal("ab"))
})
ctx := peg.NewParseContext("ababab", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(6))
ctx2 := peg.NewParseContext("xyz", false)
r2 := arena.Parse(ctx2)
Expect(r2.Type).To(Equal(peg.Fail))
})
It("handles optional matches", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Optional(b.Literal("hello")), b.Literal("world"))
})
ctx := peg.NewParseContext("helloworld", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(10))
ctx2 := peg.NewParseContext("world", false)
r2 := arena.Parse(ctx2)
Expect(r2.Type).To(Equal(peg.Success))
Expect(r2.End).To(Equal(5))
})
It("respects bounded repetition", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Repeat(b.Literal("a"), 2, 4)
})
ctx := peg.NewParseContext("aaaaa", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(4))
ctx2 := peg.NewParseContext("a", false)
r2 := arena.Parse(ctx2)
Expect(r2.Type).To(Equal(peg.Fail))
})
})
Context("Lookahead", func() {
It("succeeds with positive lookahead", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Peek(b.Literal("hello")), b.Literal("hello"))
})
ctx := peg.NewParseContext("hello", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(5))
})
It("fails with positive lookahead mismatch", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Peek(b.Literal("world")), b.Literal("hello"))
})
ctx := peg.NewParseContext("hello", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Fail))
})
It("succeeds with negative lookahead", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Negate(b.Literal("world")), b.Literal("hello"))
})
ctx := peg.NewParseContext("hello", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(5))
})
It("fails with negative lookahead match", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Negate(b.Literal("hello")), b.Literal("hello"))
})
ctx := peg.NewParseContext("hello", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Fail))
})
})
Context("UntilParser", func() {
It("consumes until single delimiter", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Until("<end>"), b.Literal("<end>"))
})
ctx := peg.NewParseContext("content<end>", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(12))
})
It("consumes until first of multiple delimiters", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.UntilOneOf("<a>", "<b>")
})
ctx := peg.NewParseContext("content<b>more", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(7))
})
It("consumes rest of input", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Rest()
})
ctx := peg.NewParseContext("everything", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(10))
})
It("returns NeedMoreInput in partial mode", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Until("<end>")
})
ctx := peg.NewParseContext("content", true)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.NeedMoreInput))
})
})
Context("JSONParser", func() {
It("parses objects", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.JSON()
})
ctx := peg.NewParseContext(`{"key": "value", "num": 42}`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(27))
})
It("parses arrays", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.JSON()
})
ctx := peg.NewParseContext(`[1, "two", true, null]`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(22))
})
It("parses strings with escapes", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.JSON()
})
ctx := peg.NewParseContext(`"hello \"world\""`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(17))
})
It("parses numbers", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.JSON()
})
ctx := peg.NewParseContext(`-123.45e10`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(10))
})
It("parses booleans", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.JSON()
})
ctx := peg.NewParseContext(`true`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(4))
})
It("parses null", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.JSON()
})
ctx := peg.NewParseContext(`null`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(4))
})
It("parses nested structures", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.JSON()
})
input := `{"a": [1, {"b": true}], "c": null}`
ctx := peg.NewParseContext(input, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(len(input)))
})
})
Context("Tag extraction", func() {
It("extracts basic tags", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(
b.Tag("greeting", b.Until(" ")),
b.Literal(" "),
b.Tag("name", b.Rest()),
)
})
ctx := peg.NewParseContext("Hello World", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
tags := extractTags(&ctx.Ast, &r)
Expect(tags["greeting"]).To(Equal("Hello"))
Expect(tags["name"]).To(Equal("World"))
})
It("extracts structured tags", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(
b.Tag("header", b.Until("\n")),
b.Literal("\n"),
b.Tag("body", b.Rest()),
)
})
ctx := peg.NewParseContext("Title\nBody content here", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
tags := extractTags(&ctx.Ast, &r)
Expect(tags["header"]).To(Equal("Title"))
Expect(tags["body"]).To(Equal("Body content here"))
})
It("overwrites duplicate tags", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(
b.Tag("item", b.Until(",")),
b.Literal(","),
b.Tag("item", b.Rest()),
)
})
ctx := peg.NewParseContext("first,second", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
tags := extractTags(&ctx.Ast, &r)
Expect(tags["item"]).To(Equal("second"))
})
It("returns empty map when no tags", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Rest()
})
ctx := peg.NewParseContext("Hello", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
tags := extractTags(&ctx.Ast, &r)
Expect(tags).To(HaveLen(0))
})
})
Context("Rule and Ref", func() {
It("handles named rules", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
word := b.Rule("word", b.Chars("[a-z]", 1, -1))
return b.Seq(word, b.Literal(" "), word)
})
ctx := peg.NewParseContext("hello world", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(11))
})
It("handles forward references", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
ref := b.Ref("greeting")
b.Rule("greeting", b.Literal("hello"))
return ref
})
ctx := peg.NewParseContext("hello", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(5))
})
})
Context("AtomicParser", func() {
It("suppresses partial AST nodes", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Atomic(b.Tag("test", b.Literal("hello world")))
})
ctx := peg.NewParseContext("hello", true)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.NeedMoreInput))
Expect(r.Nodes).To(HaveLen(0))
})
})
Context("Start and End parsers", func() {
It("matches at start of input", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Start(), b.Literal("hello"))
})
ctx := peg.NewParseContext("hello", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
})
It("matches at end of input", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("hello"), b.End())
})
ctx := peg.NewParseContext("hello", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
ctx2 := peg.NewParseContext("hello world", false)
r2 := arena.Parse(ctx2)
Expect(r2.Type).To(Equal(peg.Fail))
})
})
Context("Partial parsing", func() {
It("extracts tags during partial parse", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(
b.Tag("prefix", b.Until(":")),
b.Literal(":"),
b.Tag("value", b.Rest()),
)
})
ctx := peg.NewParseContext("key:val", true)
r := arena.Parse(ctx)
Expect(r.Type).NotTo(Equal(peg.Fail))
tags := extractTags(&ctx.Ast, &r)
Expect(tags["prefix"]).To(Equal("key"))
})
})
Context("ParseAnywhere", func() {
It("finds pattern in middle of input", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(
b.Choice(b.Literal("{"), b.Literal(":")),
b.Space(),
b.Literal("\""),
b.Atomic(b.Literal("fun_name")),
)
})
input := `This is a very long jinja template string... <tool_call>{ "fun_name" : { "arg" : 1 }</tool_call>`
found := false
for i := range len(input) {
ctx := peg.NewParseContext(input, false)
r := arena.ParseFrom(ctx, i)
if r.Type == peg.Success {
found = true
break
}
}
Expect(found).To(BeTrue())
})
It("fails when pattern is not found", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(
b.Choice(b.Literal("{"), b.Literal(":")),
b.Space(),
b.Literal("\""),
b.Atomic(b.Literal("fun_name")),
)
})
input := `This is a very long jinja template string... <tool_call><fun=fun_name><arg name=arg>1</arg></tool_call>`
found := false
for i := range len(input) {
ctx := peg.NewParseContext(input, false)
r := arena.ParseFrom(ctx, i)
if r.Type == peg.Success {
found = true
break
}
}
Expect(found).To(BeFalse())
})
})
Context("CharsParser", func() {
It("matches lowercase letters", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Chars("[a-z]", 1, -1)
})
ctx := peg.NewParseContext("hello123", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(5))
})
It("matches negated character class", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Chars("[^0-9]", 1, -1)
})
ctx := peg.NewParseContext("hello123", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(5))
})
})
Context("JSONStringParser", func() {
It("parses basic strings", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("\""), b.JSONString(), b.Literal("\""))
})
ctx := peg.NewParseContext(`"hello world"`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(13))
})
It("parses strings with escapes", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("\""), b.JSONString(), b.Literal("\""))
})
ctx := peg.NewParseContext(`"hello \"world\""`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(17))
})
It("parses strings with unicode escapes", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("\""), b.JSONString(), b.Literal("\""))
})
ctx := peg.NewParseContext(`"hello \u0041"`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(14))
})
})
Context("SpaceParser", func() {
It("matches whitespace", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("a"), b.Space(), b.Literal("b"))
})
ctx := peg.NewParseContext("a b", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(5))
})
It("matches zero whitespace", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("a"), b.Space(), b.Literal("b"))
})
ctx := peg.NewParseContext("ab", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(2))
})
})
Context("PythonDictStringParser", func() {
It("parses basic single-quoted strings", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("'"), b.PythonDictString(), b.Literal("'"))
})
ctx := peg.NewParseContext("'hello world'", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
Expect(r.End).To(Equal(13))
})
It("handles escaped single quotes", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("'"), b.PythonDictString(), b.Literal("'"))
})
ctx := peg.NewParseContext(`'it\'s fine'`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
})
It("handles double quotes inside single-quoted strings", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("'"), b.PythonDictString(), b.Literal("'"))
})
ctx := peg.NewParseContext(`'He said "hi"'`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
})
})
Context("peg.ParseCharClassChar", func() {
It("parses \\x hex escape", func() {
r, n := peg.ParseCharClassChar(`\x41`, 0)
Expect(r).To(Equal('A'))
Expect(n).To(Equal(4))
})
It("parses \\u unicode escape", func() {
r, n := peg.ParseCharClassChar(`\u0041`, 0)
Expect(r).To(Equal('A'))
Expect(n).To(Equal(6))
})
It("parses \\U unicode escape", func() {
r, n := peg.ParseCharClassChar(`\U00000041`, 0)
Expect(r).To(Equal('A'))
Expect(n).To(Equal(10))
})
It("falls back on invalid hex", func() {
r, n := peg.ParseCharClassChar(`\xZZ`, 0)
Expect(r).To(Equal('x'))
Expect(n).To(Equal(2))
})
})
Context("ParseAnywhere method", func() {
It("finds pattern in middle of input", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Seq(b.Literal("needle"), b.Tag("after", b.Until(".")))
})
ctx := peg.NewParseContext("some hay needle found.", false)
r := arena.ParseAnywhere(ctx)
Expect(r.Type).To(Equal(peg.Success))
tags := extractTags(&ctx.Ast, &r)
Expect(tags["after"]).To(Equal(" found"))
})
It("finds function tag with name", func() {
haystack := "\n<tool_call>\n<function=foofoo>\n<parameter=first>\nXXXX\n</parameter>\n<parameter=second>\nYYYY\n</parameter>\n</function>\n</tool_call>\n"
needle := "foofoo"
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Tag("fun_marker", b.Choice(
b.Seq(
b.Tag("fun_pre", b.Seq(b.Literal("<"), b.UntilOneOf(">", needle))),
b.Literal(needle),
b.Tag("fun_post", b.Seq(
b.Seq(b.Negate(b.Seq(b.Space(), b.Literal("<"))), b.Until(">"), b.Literal(">")),
)),
b.Space(),
),
b.Seq(
b.Tag("fun_pre", b.Seq(b.Literal("["), b.UntilOneOf("]", needle))),
b.Literal(needle),
b.Tag("fun_post", b.Seq(
b.Negate(b.Seq(b.Space(), b.Seq(b.Literal("["), b.Until("]"), b.Literal("]")))),
b.Space(),
)),
),
))
})
ctx := peg.NewParseContext(haystack, false)
r := arena.ParseAnywhere(ctx)
Expect(r.Type).To(Equal(peg.Success))
tags := extractTags(&ctx.Ast, &r)
Expect(tags["fun_pre"]).To(Equal("<function="))
Expect(tags["fun_post"]).To(Equal(">"))
})
})
Context("LazyRule", func() {
It("handles recursive JSON-like structures", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
b.LazyRule("value", func() peg.ParserID {
str := b.Seq(b.Literal("\""), b.JSONString(), b.Literal("\""))
arr := b.Seq(
b.Literal("["), b.Space(),
b.Ref("value"),
b.ZeroOrMore(b.Seq(b.Space(), b.Literal(","), b.Space(), b.Ref("value"))),
b.Space(), b.Literal("]"),
)
return b.Choice(str, arr)
})
return b.Ref("value")
})
ctx := peg.NewParseContext(`["hello",["world","nested"]]`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
})
It("parses python dicts", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.PythonDict()
})
ctx := peg.NewParseContext(`{'key': 'value', 'num': 42}`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
})
It("parses nested python values", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.PythonValue()
})
ctx := peg.NewParseContext(`{'outer': {'inner': [1, 2, 'three']}}`, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
})
It("parses python booleans and None", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.PythonValue()
})
for _, input := range []string{"True", "False", "None"} {
ctx := peg.NewParseContext(input, false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
}
})
})
Context("Marker", func() {
It("matches angle brackets", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Marker()
})
ctx := peg.NewParseContext("<tool_call>", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
})
It("matches square brackets", func() {
arena := peg.BuildPegParser(func(b *peg.Builder) peg.ParserID {
return b.Marker()
})
ctx := peg.NewParseContext("[TOOL]", false)
r := arena.Parse(ctx)
Expect(r.Type).To(Equal(peg.Success))
})
})
})