github.com/cayleygraph/cayley@v0.7.7/query/sexp/parser.go (about)

     1  // Copyright 2014 The Cayley Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sexp
    16  
    17  import (
    18  	"github.com/badgerodon/peg"
    19  
    20  	"github.com/cayleygraph/cayley/graph"
    21  	"github.com/cayleygraph/cayley/graph/iterator"
    22  	"github.com/cayleygraph/cayley/graph/shape"
    23  	"github.com/cayleygraph/quad"
    24  )
    25  
    26  func BuildIteratorTreeForQuery(qs graph.QuadStore, query string) graph.Iterator {
    27  	s, err := BuildShape(query)
    28  	if err != nil {
    29  		return iterator.NewError(err)
    30  	}
    31  	return shape.BuildIterator(qs, s)
    32  }
    33  
    34  func BuildShape(query string) (shape.Shape, error) {
    35  	tree := parseQuery(query)
    36  	s, _ := buildShape(tree)
    37  	s, _ = shape.Optimize(s, nil)
    38  	return s, nil
    39  }
    40  
    41  func ParseString(input string) string {
    42  	return parseQuery(input).String()
    43  }
    44  
    45  func newParser() *peg.Parser {
    46  	parser := peg.NewParser()
    47  
    48  	start := parser.NonTerminal("Start")
    49  	whitespace := parser.NonTerminal("Whitespace")
    50  	quotedString := parser.NonTerminal("QuotedString")
    51  	rootConstraint := parser.NonTerminal("RootConstraint")
    52  
    53  	constraint := parser.NonTerminal("Constraint")
    54  	colonIdentifier := parser.NonTerminal("ColonIdentifier")
    55  	variable := parser.NonTerminal("Variable")
    56  	identifier := parser.NonTerminal("Identifier")
    57  	fixedNode := parser.NonTerminal("FixedNode")
    58  	nodeIdent := parser.NonTerminal("NodeIdentifier")
    59  	predIdent := parser.NonTerminal("PredIdentifier")
    60  	reverse := parser.NonTerminal("Reverse")
    61  	predKeyword := parser.NonTerminal("PredicateKeyword")
    62  	optional := parser.NonTerminal("OptionalKeyword")
    63  
    64  	start.Expression = rootConstraint
    65  
    66  	whitespace.Expression = parser.OneOrMore(
    67  		parser.OrderedChoice(
    68  			parser.Terminal(' '),
    69  			parser.Terminal('\t'),
    70  			parser.Terminal('\n'),
    71  			parser.Terminal('\r'),
    72  		),
    73  	)
    74  
    75  	quotedString.Expression = parser.Sequence(
    76  		parser.Terminal('"'),
    77  		parser.OneOrMore(
    78  			parser.OrderedChoice(
    79  				parser.Range('0', '9'),
    80  				parser.Range('a', 'z'),
    81  				parser.Range('A', 'Z'),
    82  				parser.Terminal('_'),
    83  				parser.Terminal('/'),
    84  				parser.Terminal(':'),
    85  				parser.Terminal(' '),
    86  				parser.Terminal('\''),
    87  			),
    88  		),
    89  		parser.Terminal('"'),
    90  	)
    91  
    92  	predKeyword.Expression = parser.OrderedChoice(
    93  		optional,
    94  	)
    95  
    96  	optional.Expression = parser.Sequence(
    97  		parser.Terminal('o'),
    98  		parser.Terminal('p'),
    99  		parser.Terminal('t'),
   100  		parser.Terminal('i'),
   101  		parser.Terminal('o'),
   102  		parser.Terminal('n'),
   103  		parser.Terminal('a'),
   104  		parser.Terminal('l'),
   105  	)
   106  
   107  	identifier.Expression = parser.OneOrMore(
   108  		parser.OrderedChoice(
   109  			parser.Range('0', '9'),
   110  			parser.Range('a', 'z'),
   111  			parser.Range('A', 'Z'),
   112  			parser.Terminal('_'),
   113  			parser.Terminal('.'),
   114  			parser.Terminal('/'),
   115  			parser.Terminal(':'),
   116  			parser.Terminal('#'),
   117  		),
   118  	)
   119  
   120  	reverse.Expression = parser.Terminal('!')
   121  
   122  	variable.Expression = parser.Sequence(
   123  		parser.Terminal('$'),
   124  		identifier,
   125  	)
   126  
   127  	colonIdentifier.Expression = parser.Sequence(
   128  		parser.Terminal(':'),
   129  		identifier,
   130  	)
   131  
   132  	fixedNode.Expression = parser.OrderedChoice(
   133  		colonIdentifier,
   134  		quotedString,
   135  	)
   136  
   137  	nodeIdent.Expression = parser.OrderedChoice(
   138  		variable,
   139  		fixedNode,
   140  	)
   141  
   142  	predIdent.Expression = parser.Sequence(
   143  		parser.Optional(reverse),
   144  		parser.OrderedChoice(
   145  			nodeIdent,
   146  			constraint,
   147  		),
   148  	)
   149  
   150  	constraint.Expression = parser.Sequence(
   151  		parser.Terminal('('),
   152  		parser.Optional(whitespace),
   153  		predIdent,
   154  		parser.Optional(whitespace),
   155  		parser.Optional(predKeyword),
   156  		parser.Optional(whitespace),
   157  		parser.OrderedChoice(
   158  			nodeIdent,
   159  			rootConstraint,
   160  		),
   161  		parser.Optional(whitespace),
   162  		parser.Terminal(')'),
   163  	)
   164  
   165  	rootConstraint.Expression = parser.Sequence(
   166  		parser.Terminal('('),
   167  		parser.Optional(whitespace),
   168  		nodeIdent,
   169  		parser.Optional(whitespace),
   170  		parser.ZeroOrMore(parser.Sequence(
   171  			constraint,
   172  			parser.Optional(whitespace),
   173  		)),
   174  		parser.Terminal(')'),
   175  	)
   176  	return parser
   177  }
   178  
   179  func parseQuery(input string) *peg.ExpressionTree {
   180  	return newParser().Parse(input)
   181  }
   182  
   183  func getIdentString(tree *peg.ExpressionTree) string {
   184  	out := ""
   185  	if len(tree.Children) > 0 {
   186  		for _, child := range tree.Children {
   187  			out += getIdentString(child)
   188  		}
   189  	} else {
   190  		if tree.Value != '"' {
   191  			out += string(tree.Value)
   192  		}
   193  	}
   194  	return out
   195  }
   196  
   197  func lookup(s string) shape.Shape {
   198  	return shape.Lookup{quad.StringToValue(s)}
   199  }
   200  
   201  func buildShape(tree *peg.ExpressionTree) (_ shape.Shape, opt bool) {
   202  	switch tree.Name {
   203  	case "Start":
   204  		return buildShape(tree.Children[0])
   205  	case "NodeIdentifier":
   206  		var out shape.Shape
   207  		nodeID := getIdentString(tree)
   208  		if tree.Children[0].Name == "Variable" {
   209  			out = shape.Save{
   210  				From: shape.AllNodes{},
   211  				Tags: []string{nodeID},
   212  			}
   213  		} else {
   214  			n := nodeID
   215  			if tree.Children[0].Children[0].Name == "ColonIdentifier" {
   216  				n = nodeID[1:]
   217  			}
   218  			out = lookup(n)
   219  		}
   220  		return out, false
   221  	case "PredIdentifier":
   222  		i := 0
   223  		if tree.Children[0].Name == "Reverse" {
   224  			//Taken care of below
   225  			i++
   226  		}
   227  		it, _ := buildShape(tree.Children[i])
   228  		return shape.Quads{
   229  			{Dir: quad.Predicate, Values: it},
   230  		}, false
   231  	case "RootConstraint":
   232  		var and shape.IntersectOpt
   233  		for _, c := range tree.Children {
   234  			switch c.Name {
   235  			case "NodeIdentifier":
   236  				fallthrough
   237  			case "Constraint":
   238  				it, opt := buildShape(c)
   239  				if opt {
   240  					and.AddOptional(it)
   241  				} else {
   242  					and.Add(it)
   243  				}
   244  				continue
   245  			default:
   246  				continue
   247  			}
   248  		}
   249  		return and, false
   250  	case "Constraint":
   251  		topLevelDir := quad.Subject
   252  		subItDir := quad.Object
   253  		var subAnd shape.IntersectOpt
   254  		isOptional := false
   255  		for _, c := range tree.Children {
   256  			switch c.Name {
   257  			case "PredIdentifier":
   258  				if c.Children[0].Name == "Reverse" {
   259  					topLevelDir = quad.Object
   260  					subItDir = quad.Subject
   261  				}
   262  				it, opt := buildShape(c)
   263  				if opt {
   264  					subAnd.AddOptional(it)
   265  				} else {
   266  					subAnd.Add(it)
   267  				}
   268  				continue
   269  			case "PredicateKeyword":
   270  				switch c.Children[0].Name {
   271  				case "OptionalKeyword":
   272  					isOptional = true
   273  				}
   274  			case "NodeIdentifier":
   275  				fallthrough
   276  			case "RootConstraint":
   277  				it, opt := buildShape(c)
   278  				l := shape.Quads{
   279  					{Dir: subItDir, Values: it},
   280  				}
   281  				if opt {
   282  					subAnd.AddOptional(l)
   283  				} else {
   284  					subAnd.Add(l)
   285  				}
   286  				continue
   287  			default:
   288  				continue
   289  			}
   290  		}
   291  		return shape.NodesFrom{
   292  			Dir:   topLevelDir,
   293  			Quads: subAnd,
   294  		}, isOptional
   295  	default:
   296  		return shape.Null{}, false
   297  	}
   298  }