Skip to content
This repository has been archived by the owner on Mar 21, 2021. It is now read-only.

Commit

Permalink
Allow filter/take/count to be "free-standing"
Browse files Browse the repository at this point in the history
  • Loading branch information
andreypopp committed Oct 12, 2020
1 parent 9c6f14f commit f6cdfd6
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 39 deletions.
24 changes: 7 additions & 17 deletions README.md
Expand Up @@ -52,21 +52,11 @@ Syntax Sugar
The ``select`` query combinator is so common that there's **selection** syntax
sugar available:

QUERY_PARENT { NAME: QUERY... }
{ NAME: QUERY... }

This desugars into:

select(QUERY_PARENT, NAME: QUERY...)

Common query combinators like `filter`, `sort`, `take`, `count` (and many
others) operate on a "primary query" (query which is used as a basis for
filtering, sorting, ...). For this case there's a **method-call** syntax sugar:

QUERY_ARG.COMB(QUERY_EXTRA_ARG...)

This desugars into:

COMB(QUERY_ARG, QUERY_EXTRA_ARG...)
select(NAME: QUERY...)

Python EDSL API
---------------
Expand All @@ -86,18 +76,18 @@ naturally like:
q.region.name

Another syntax for composition enables to compose two queries built
independently:
independently (it's the same composition syntax but because Python syntax
doesn't allow us to reuse `.` as an operator we have `>>` here):

q.region >> q.name

To apply a query combinator one does:

q.count(q.region)
q.filter(q.name = q.val('AFRICA'))

Alternatively a query combinator can be used as a method (in this case it
receives the left hand side query as its first argument):
Another example where a query combinator is composed with another query:

q.region.count()
q.region.count() # same as q.region >> q.count()

Another example with `select` combinator:

Expand Down
22 changes: 9 additions & 13 deletions qc0/bind.py
Expand Up @@ -189,7 +189,9 @@ def Nav_to_op(syn: Nav, parent: Op):
parent, expr=parent, transform=transform, scope=next_scope
)
elif isinstance(parent.scope, EmptyScope): # pragma: no cover
assert False, f"Unable to lookup {syn.name} in empty scope" # pragma: no cover
assert (
False
), f"Unable to lookup {syn.name} in empty scope" # pragma: no cover
else:
assert False # pragma: no cover

Expand All @@ -203,20 +205,15 @@ def Select_to_op(syn: Select, parent: Op):
@to_op.register
def Apply_to_op(syn: Apply, parent: Op):
if syn.name in {"count", "exists", "sum"}:
assert (
len(syn.args) == 1
), f"{syn.name}(...): expected a single argument"
arg = syn.args[0]
parent = to_op(arg, parent)
assert len(syn.args) == 0, f"{syn.name}(...): expected no arguments"
assert isinstance(parent, Pipe), f"{syn.name}(...): requires a pipe"
assert (
parent.card >= Cardinality.SEQ
), "{syn.name}(...): expected a sequence of items"
return ExprAggregatePipe.wrap(parent, pipe=parent, func=syn.name)
elif syn.name == "take":
assert len(syn.args) == 2, "take(...): expected exactly two arguments"
arg, take = syn.args
parent = to_op(arg, parent)
assert len(syn.args) == 1, "take(...): expected a single argument"
take = syn.args[0]
assert isinstance(parent, Pipe), "take(...): requires a pipe"
return PipeTake.wrap(parent, pipe=parent, take=take)
elif syn.name in {
Expand Down Expand Up @@ -247,10 +244,9 @@ def Apply_to_op(syn: Apply, parent: Op):
)
elif syn.name == "filter":
assert (
len(syn.args) == 2
), "filter(...): expected exactly two arguments"
arg, expr = syn.args
parent = to_op(arg, parent)
len(syn.args) == 1
), "filter(...): expected a single argument"
expr = syn.args[0]
assert isinstance(parent, Pipe), "filter(...): requires a pipe"
expr = to_op(
expr,
Expand Down
16 changes: 7 additions & 9 deletions qc0/syn.py
Expand Up @@ -150,18 +150,16 @@ def __rshift__(self, o: Q):
return self.__class__(Compose(a=self.syn, b=o.syn))

def __call__(self, *args):
assert (
isinstance(self.syn, Compose)
and isinstance(self.syn.b, Nav)
or isinstance(self.syn, Nav)
)
args = tuple(arg.syn if isinstance(arg, Q) else arg for arg in args)
if isinstance(self.syn, Compose):
if isinstance(self.syn, Nav):
name = self.syn.name
return self.__class__(Apply(name=name, args=args))
elif isinstance(self.syn, Compose) and isinstance(self.syn.b, Nav):
parent = self.syn.a
name = self.syn.b.name
args = (self.syn.a,) + args
return self.__class__(Compose(parent, Apply(name=name, args=args)))
else:
name = self.syn.name
return self.__class__(Apply(name=name, args=args))
assert False, "SyntaxError: cannot do call here"


q = Q(None)
Expand Down

0 comments on commit f6cdfd6

Please sign in to comment.