Move sexp into query
This commit is contained in:
parent
a6cf432313
commit
09943c3eb6
7 changed files with 545 additions and 545 deletions
273
query/sexp/parser.go
Normal file
273
query/sexp/parser.go
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
// Copyright 2014 The Cayley Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sexp
|
||||
|
||||
import (
|
||||
"github.com/badgerodon/peg"
|
||||
|
||||
"github.com/google/cayley/graph"
|
||||
"github.com/google/cayley/graph/iterator"
|
||||
"github.com/google/cayley/quad"
|
||||
)
|
||||
|
||||
func BuildIteratorTreeForQuery(ts graph.TripleStore, query string) graph.Iterator {
|
||||
tree := parseQuery(query)
|
||||
return buildIteratorTree(tree, ts)
|
||||
}
|
||||
|
||||
func ParseString(input string) string {
|
||||
return parseQuery(input).String()
|
||||
}
|
||||
|
||||
func parseQuery(input string) *peg.ExpressionTree {
|
||||
parser := peg.NewParser()
|
||||
|
||||
start := parser.NonTerminal("Start")
|
||||
whitespace := parser.NonTerminal("Whitespace")
|
||||
quotedString := parser.NonTerminal("QuotedString")
|
||||
rootConstraint := parser.NonTerminal("RootConstraint")
|
||||
|
||||
constraint := parser.NonTerminal("Constraint")
|
||||
colonIdentifier := parser.NonTerminal("ColonIdentifier")
|
||||
variable := parser.NonTerminal("Variable")
|
||||
identifier := parser.NonTerminal("Identifier")
|
||||
fixedNode := parser.NonTerminal("FixedNode")
|
||||
nodeIdent := parser.NonTerminal("NodeIdentifier")
|
||||
predIdent := parser.NonTerminal("PredIdentifier")
|
||||
reverse := parser.NonTerminal("Reverse")
|
||||
predKeyword := parser.NonTerminal("PredicateKeyword")
|
||||
optional := parser.NonTerminal("OptionalKeyword")
|
||||
|
||||
start.Expression = rootConstraint
|
||||
|
||||
whitespace.Expression = parser.OneOrMore(
|
||||
parser.OrderedChoice(
|
||||
parser.Terminal(' '),
|
||||
parser.Terminal('\t'),
|
||||
parser.Terminal('\n'),
|
||||
parser.Terminal('\r'),
|
||||
),
|
||||
)
|
||||
|
||||
quotedString.Expression = parser.Sequence(
|
||||
parser.Terminal('"'),
|
||||
parser.OneOrMore(
|
||||
parser.OrderedChoice(
|
||||
parser.Range('0', '9'),
|
||||
parser.Range('a', 'z'),
|
||||
parser.Range('A', 'Z'),
|
||||
parser.Terminal('_'),
|
||||
parser.Terminal('/'),
|
||||
parser.Terminal(':'),
|
||||
parser.Terminal(' '),
|
||||
parser.Terminal('\''),
|
||||
),
|
||||
),
|
||||
parser.Terminal('"'),
|
||||
)
|
||||
|
||||
predKeyword.Expression = parser.OrderedChoice(
|
||||
optional,
|
||||
)
|
||||
|
||||
optional.Expression = parser.Sequence(
|
||||
parser.Terminal('o'),
|
||||
parser.Terminal('p'),
|
||||
parser.Terminal('t'),
|
||||
parser.Terminal('i'),
|
||||
parser.Terminal('o'),
|
||||
parser.Terminal('n'),
|
||||
parser.Terminal('a'),
|
||||
parser.Terminal('l'),
|
||||
)
|
||||
|
||||
identifier.Expression = parser.OneOrMore(
|
||||
parser.OrderedChoice(
|
||||
parser.Range('0', '9'),
|
||||
parser.Range('a', 'z'),
|
||||
parser.Range('A', 'Z'),
|
||||
parser.Terminal('_'),
|
||||
parser.Terminal('.'),
|
||||
parser.Terminal('/'),
|
||||
parser.Terminal(':'),
|
||||
parser.Terminal('#'),
|
||||
),
|
||||
)
|
||||
|
||||
reverse.Expression = parser.Terminal('!')
|
||||
|
||||
variable.Expression = parser.Sequence(
|
||||
parser.Terminal('$'),
|
||||
identifier,
|
||||
)
|
||||
|
||||
colonIdentifier.Expression = parser.Sequence(
|
||||
parser.Terminal(':'),
|
||||
identifier,
|
||||
)
|
||||
|
||||
fixedNode.Expression = parser.OrderedChoice(
|
||||
colonIdentifier,
|
||||
quotedString,
|
||||
)
|
||||
|
||||
nodeIdent.Expression = parser.OrderedChoice(
|
||||
variable,
|
||||
fixedNode,
|
||||
)
|
||||
|
||||
predIdent.Expression = parser.Sequence(
|
||||
parser.Optional(reverse),
|
||||
parser.OrderedChoice(
|
||||
nodeIdent,
|
||||
constraint,
|
||||
),
|
||||
)
|
||||
|
||||
constraint.Expression = parser.Sequence(
|
||||
parser.Terminal('('),
|
||||
parser.Optional(whitespace),
|
||||
predIdent,
|
||||
parser.Optional(whitespace),
|
||||
parser.Optional(predKeyword),
|
||||
parser.Optional(whitespace),
|
||||
parser.OrderedChoice(
|
||||
nodeIdent,
|
||||
rootConstraint,
|
||||
),
|
||||
parser.Optional(whitespace),
|
||||
parser.Terminal(')'),
|
||||
)
|
||||
|
||||
rootConstraint.Expression = parser.Sequence(
|
||||
parser.Terminal('('),
|
||||
parser.Optional(whitespace),
|
||||
nodeIdent,
|
||||
parser.Optional(whitespace),
|
||||
parser.ZeroOrMore(parser.Sequence(
|
||||
constraint,
|
||||
parser.Optional(whitespace),
|
||||
)),
|
||||
parser.Terminal(')'),
|
||||
)
|
||||
|
||||
tree := parser.Parse(input)
|
||||
return tree
|
||||
}
|
||||
|
||||
func getIdentString(tree *peg.ExpressionTree) string {
|
||||
out := ""
|
||||
if len(tree.Children) > 0 {
|
||||
for _, child := range tree.Children {
|
||||
out += getIdentString(child)
|
||||
}
|
||||
} else {
|
||||
if tree.Value != '"' {
|
||||
out += string(tree.Value)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func buildIteratorTree(tree *peg.ExpressionTree, ts graph.TripleStore) graph.Iterator {
|
||||
switch tree.Name {
|
||||
case "Start":
|
||||
return buildIteratorTree(tree.Children[0], ts)
|
||||
case "NodeIdentifier":
|
||||
var out graph.Iterator
|
||||
nodeID := getIdentString(tree)
|
||||
if tree.Children[0].Name == "Variable" {
|
||||
allIt := ts.NodesAllIterator()
|
||||
allIt.Tagger().Add(nodeID)
|
||||
out = allIt
|
||||
} else {
|
||||
n := nodeID
|
||||
if tree.Children[0].Children[0].Name == "ColonIdentifier" {
|
||||
n = nodeID[1:]
|
||||
}
|
||||
fixed := ts.FixedIterator()
|
||||
fixed.Add(ts.ValueOf(n))
|
||||
out = fixed
|
||||
}
|
||||
return out
|
||||
case "PredIdentifier":
|
||||
i := 0
|
||||
if tree.Children[0].Name == "Reverse" {
|
||||
//Taken care of below
|
||||
i++
|
||||
}
|
||||
it := buildIteratorTree(tree.Children[i], ts)
|
||||
lto := iterator.NewLinksTo(ts, it, quad.Predicate)
|
||||
return lto
|
||||
case "RootConstraint":
|
||||
constraintCount := 0
|
||||
and := iterator.NewAnd()
|
||||
for _, c := range tree.Children {
|
||||
switch c.Name {
|
||||
case "NodeIdentifier":
|
||||
fallthrough
|
||||
case "Constraint":
|
||||
it := buildIteratorTree(c, ts)
|
||||
and.AddSubIterator(it)
|
||||
constraintCount++
|
||||
continue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return and
|
||||
case "Constraint":
|
||||
var hasa *iterator.HasA
|
||||
topLevelDir := quad.Subject
|
||||
subItDir := quad.Object
|
||||
subAnd := iterator.NewAnd()
|
||||
isOptional := false
|
||||
for _, c := range tree.Children {
|
||||
switch c.Name {
|
||||
case "PredIdentifier":
|
||||
if c.Children[0].Name == "Reverse" {
|
||||
topLevelDir = quad.Object
|
||||
subItDir = quad.Subject
|
||||
}
|
||||
it := buildIteratorTree(c, ts)
|
||||
subAnd.AddSubIterator(it)
|
||||
continue
|
||||
case "PredicateKeyword":
|
||||
switch c.Children[0].Name {
|
||||
case "OptionalKeyword":
|
||||
isOptional = true
|
||||
}
|
||||
case "NodeIdentifier":
|
||||
fallthrough
|
||||
case "RootConstraint":
|
||||
it := buildIteratorTree(c, ts)
|
||||
l := iterator.NewLinksTo(ts, it, subItDir)
|
||||
subAnd.AddSubIterator(l)
|
||||
continue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
hasa = iterator.NewHasA(ts, subAnd, topLevelDir)
|
||||
if isOptional {
|
||||
optional := iterator.NewOptional(hasa)
|
||||
return optional
|
||||
}
|
||||
return hasa
|
||||
default:
|
||||
return &iterator.Null{}
|
||||
}
|
||||
panic("Not reached")
|
||||
}
|
||||
149
query/sexp/parser_test.go
Normal file
149
query/sexp/parser_test.go
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2014 The Cayley Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sexp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/cayley/graph"
|
||||
"github.com/google/cayley/quad"
|
||||
|
||||
_ "github.com/google/cayley/graph/memstore"
|
||||
)
|
||||
|
||||
func TestBadParse(t *testing.T) {
|
||||
str := ParseString("()")
|
||||
if str != "" {
|
||||
t.Errorf("Unexpected parse result, got:%q", str)
|
||||
}
|
||||
}
|
||||
|
||||
var testQueries = []struct {
|
||||
message string
|
||||
add *quad.Quad
|
||||
query string
|
||||
typ graph.Type
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
message: "get a single triple linkage",
|
||||
add: &quad.Quad{"i", "can", "win", ""},
|
||||
query: "($a (:can \"win\"))",
|
||||
typ: graph.And,
|
||||
expect: "i",
|
||||
},
|
||||
{
|
||||
message: "get a single triple linkage",
|
||||
add: &quad.Quad{"i", "can", "win", ""},
|
||||
query: "(\"i\" (:can $a))",
|
||||
typ: graph.And,
|
||||
expect: "i",
|
||||
},
|
||||
}
|
||||
|
||||
func TestMemstoreBackedSexp(t *testing.T) {
|
||||
ts, _ := graph.NewTripleStore("memstore", "", nil)
|
||||
it := BuildIteratorTreeForQuery(ts, "()")
|
||||
if it.Type() != graph.Null {
|
||||
t.Errorf(`Incorrect type for empty query, got:%q expect: "null"`, it.Type())
|
||||
}
|
||||
for _, test := range testQueries {
|
||||
if test.add != nil {
|
||||
ts.AddTriple(test.add)
|
||||
}
|
||||
it := BuildIteratorTreeForQuery(ts, test.query)
|
||||
if it.Type() != test.typ {
|
||||
t.Errorf("Incorrect type for %s, got:%q expect %q", test.message, it.Type(), test.expect)
|
||||
}
|
||||
got, ok := graph.Next(it)
|
||||
if !ok {
|
||||
t.Errorf("Failed to %s", test.message)
|
||||
}
|
||||
if expect := ts.ValueOf(test.expect); got != expect {
|
||||
t.Errorf("Incorrect result for %s, got:%v expect %v", test.message, got, expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeConstraintParse(t *testing.T) {
|
||||
ts, _ := graph.NewTripleStore("memstore", "", nil)
|
||||
ts.AddTriple(&quad.Quad{"i", "like", "food", ""})
|
||||
ts.AddTriple(&quad.Quad{"food", "is", "good", ""})
|
||||
query := "(\"i\"\n" +
|
||||
"(:like\n" +
|
||||
"($a (:is :good))))"
|
||||
it := BuildIteratorTreeForQuery(ts, query)
|
||||
if it.Type() != graph.And {
|
||||
t.Error("Odd iterator tree. Got: %s", it.DebugString(0))
|
||||
}
|
||||
out, ok := graph.Next(it)
|
||||
if !ok {
|
||||
t.Error("Got no results")
|
||||
}
|
||||
if out != ts.ValueOf("i") {
|
||||
t.Errorf("Got %d, expected %d", out, ts.ValueOf("i"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeConstraintTagParse(t *testing.T) {
|
||||
ts, _ := graph.NewTripleStore("memstore", "", nil)
|
||||
ts.AddTriple(&quad.Quad{"i", "like", "food", ""})
|
||||
ts.AddTriple(&quad.Quad{"food", "is", "good", ""})
|
||||
query := "(\"i\"\n" +
|
||||
"(:like\n" +
|
||||
"($a (:is :good))))"
|
||||
it := BuildIteratorTreeForQuery(ts, query)
|
||||
_, ok := graph.Next(it)
|
||||
if !ok {
|
||||
t.Error("Got no results")
|
||||
}
|
||||
tags := make(map[string]graph.Value)
|
||||
it.TagResults(tags)
|
||||
if ts.NameOf(tags["$a"]) != "food" {
|
||||
t.Errorf("Got %s, expected food", ts.NameOf(tags["$a"]))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMultipleConstraintParse(t *testing.T) {
|
||||
ts, _ := graph.NewTripleStore("memstore", "", nil)
|
||||
for _, tv := range []*quad.Quad{
|
||||
{"i", "like", "food", ""},
|
||||
{"i", "like", "beer", ""},
|
||||
{"you", "like", "beer", ""},
|
||||
} {
|
||||
ts.AddTriple(tv)
|
||||
}
|
||||
query := `(
|
||||
$a
|
||||
(:like :beer)
|
||||
(:like "food")
|
||||
)`
|
||||
it := BuildIteratorTreeForQuery(ts, query)
|
||||
if it.Type() != graph.And {
|
||||
t.Error("Odd iterator tree. Got: %s", it.DebugString(0))
|
||||
}
|
||||
out, ok := graph.Next(it)
|
||||
if !ok {
|
||||
t.Error("Got no results")
|
||||
}
|
||||
if out != ts.ValueOf("i") {
|
||||
t.Errorf("Got %d, expected %d", out, ts.ValueOf("i"))
|
||||
}
|
||||
_, ok = graph.Next(it)
|
||||
if ok {
|
||||
t.Error("Too many results")
|
||||
}
|
||||
}
|
||||
122
query/sexp/session.go
Normal file
122
query/sexp/session.go
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2014 The Cayley Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sexp
|
||||
|
||||
// Defines a running session of the sexp query language.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/google/cayley/graph"
|
||||
"github.com/google/cayley/query"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
ts graph.TripleStore
|
||||
debug bool
|
||||
}
|
||||
|
||||
func NewSession(inputTripleStore graph.TripleStore) *Session {
|
||||
var s Session
|
||||
s.ts = inputTripleStore
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *Session) ToggleDebug() {
|
||||
s.debug = !s.debug
|
||||
}
|
||||
|
||||
func (s *Session) InputParses(input string) (query.ParseResult, error) {
|
||||
var parenDepth int
|
||||
for i, x := range input {
|
||||
if x == '(' {
|
||||
parenDepth++
|
||||
}
|
||||
if x == ')' {
|
||||
parenDepth--
|
||||
if parenDepth < 0 {
|
||||
min := 0
|
||||
if (i - 10) > min {
|
||||
min = i - 10
|
||||
}
|
||||
return query.ParseFail, errors.New(fmt.Sprintf("Too many close parens at char %d: %s", i, input[min:i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
if parenDepth > 0 {
|
||||
return query.ParseMore, nil
|
||||
}
|
||||
if len(ParseString(input)) > 0 {
|
||||
return query.Parsed, nil
|
||||
}
|
||||
return query.ParseFail, errors.New("Invalid Syntax")
|
||||
}
|
||||
|
||||
func (s *Session) ExecInput(input string, out chan interface{}, limit int) {
|
||||
it := BuildIteratorTreeForQuery(s.ts, input)
|
||||
newIt, changed := it.Optimize()
|
||||
if changed {
|
||||
it = newIt
|
||||
}
|
||||
|
||||
if s.debug {
|
||||
fmt.Println(it.DebugString(0))
|
||||
}
|
||||
nResults := 0
|
||||
for {
|
||||
_, ok := graph.Next(it)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
tags := make(map[string]graph.Value)
|
||||
it.TagResults(tags)
|
||||
out <- &tags
|
||||
nResults++
|
||||
if nResults > limit && limit != -1 {
|
||||
break
|
||||
}
|
||||
for it.NextResult() == true {
|
||||
tags := make(map[string]graph.Value)
|
||||
it.TagResults(tags)
|
||||
out <- &tags
|
||||
nResults++
|
||||
if nResults > limit && limit != -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
close(out)
|
||||
}
|
||||
|
||||
func (s *Session) ToText(result interface{}) string {
|
||||
out := fmt.Sprintln("****")
|
||||
tags := result.(map[string]graph.Value)
|
||||
tagKeys := make([]string, len(tags))
|
||||
i := 0
|
||||
for k := range tags {
|
||||
tagKeys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(tagKeys)
|
||||
for _, k := range tagKeys {
|
||||
if k == "$_" {
|
||||
continue
|
||||
}
|
||||
out += fmt.Sprintf("%s : %s\n", k, s.ts.NameOf(tags[k]))
|
||||
}
|
||||
return out
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue