Skip to content

Commit 9510353

Browse files
feat: add custom functions
Co-authored-by: Magnus Holm <judofyr@gmail.com>
1 parent f8f928a commit 9510353

File tree

5 files changed

+75
-6
lines changed

5 files changed

+75
-6
lines changed

spec/02-syntax.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# Syntax
22

3-
A GROQ query is a string consisting of Unicode characters. The encoding of the query string is implementation-defined, but UTF-8 is the preferred choice. A query consist of a single {Expression}, with {WhiteSpace} and {Comment} allowed anywhere with no effect on the interpretation.
3+
A GROQ query is a string consisting of Unicode characters. The encoding of the query string is implementation-defined, but UTF-8 is the preferred choice.
44

55
SourceCharacter : "any Unicode character"
66

7+
A query consist of a single {Expression} optionally preceded by a list of {FuncDecl}, with {WhiteSpace} and {Comment} allowed anywhere with no effect on the interpretation.
8+
9+
Query : FuncDecl\* Expression
10+
711
## JSON Superset
812

913
GROQ's syntax is a superset of JSON, so any valid JSON value is a valid GROQ expression (that simply returns the given value). Below are a few examples of JSON values:
@@ -112,6 +116,7 @@ SimpleExpression :
112116
- ThisAttribute
113117
- Everything
114118
- Parent
119+
- Parameter
115120
- FuncCall
116121

117122
CompoundExpression :

spec/03-execution.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ A query context consists of:
4242
- the dataset
4343
- parameter values (map from {string} to {value})
4444
- the mode: either "normal" or "delta"
45+
- custom function definitions (map from {string} to {function})
4546

4647
If the mode is "delta" then the query context also has:
4748

@@ -55,6 +56,7 @@ A scope consists of:
5556
- a this value
5657
- an optional parent scope
5758
- a query context
59+
- a map of parameters
5860

5961
A root scope can be constructed from a query context, and a nested scope can be constructed from an existing scope.
6062

@@ -64,6 +66,7 @@ NewNestedScope(value, scope):
6466
- Set the this value of {newScope} to {value}.
6567
- Set the parent scope of {newScope} to {scope}.
6668
- Set the query context of {newScope} to the query context of {scope}.
69+
- Set the parameters of {newScope} to the parameter of {scope}.
6770
- Return {newScope}.
6871

6972
NewRootScope(context):
@@ -72,6 +75,7 @@ NewRootScope(context):
7275
- Set the this value of {newScope} to {null}.
7376
- Set the parent scope of {newScope} to {null}.
7477
- Set the query context of {newScope} to {context}.
78+
- Set the parameters to the parameters given to the query request.
7579
- Return {newScope}.
7680

7781
## Expression validation
@@ -83,6 +87,11 @@ Validate(expr):
8387
- Let {validator} be the validator of {expr}.
8488
- Execute the {validator}.
8589

90+
## Query validation
91+
92+
A query is validated by first validating the custom functions and then [validating the expression](#sec-Expression-validation).
93+
Custom functions are validated by validating the function bodies.
94+
8695
## Expression evaluation
8796

8897
An expression is evaluated in a scope. You must successfully validate an expression before you attempt to evaluate it. Every expression type has their own evaluator function in their respective section in this specification (e.g. the evaluator of {ParenthesisExpression} is {EvaluateParenthesis()}).

spec/06-simple-expressions.md

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,34 @@ EvaluateParent(scope):
7575
- Decrease {level} by one.
7676
- Return the this value of {currentScope}.
7777

78+
## Parameter expression
79+
80+
A parameter expression returns the value of a parameter
81+
82+
```example
83+
*[_type == $type]
84+
~~~~~
85+
```
86+
87+
Parameter : `$` Identifier
88+
89+
EvaluateParameter(scope):
90+
91+
- Let {name} be the string value of the {Identifier}.
92+
- Return the value of the parameter in {scope}.
93+
94+
ValidateParameter():
95+
96+
- Let {name} be the string value of the {Identifier}.
97+
- If the parameter doesn't exist in the current validation context:
98+
- Report an error.
99+
78100
## Function call expression
79101

80102
GROQ comes with a set of built-in functions which provides additional features. See the ["Functions"](#sec-Functions) for available functions and their namespaces.
81103

104+
Custom GROQ functions can be defined to extend or override the built-in function set. See [Custom functions](12-custom-functions.md) for details.
105+
82106
```example
83107
*{"score": round(score, 2)}
84108
~~~~~~~~~~~~~~~
@@ -106,17 +130,31 @@ EvaluateFuncCall(scope):
106130
- For each {Expression} in {FuncCallArgs}:
107131
- Let {argumentNode} be the {Expression}.
108132
- Append {argumentNode} to {args}.
109-
- Let {func} be the function defined under the name {name} in either {namespace} namespace if provided, or the `global` namespace.
110-
- Return the result of {func(args, scope)}.
133+
- If the query context of {scope} has a function defined with the given {name} and {namespace}:
134+
- Let {funcBody} be body of the custom function.
135+
- Let {context} be the query context of {scope}.
136+
- Let {newScope} be the result of {NewRootScope(context)}.
137+
- For each {param} in the parameter list of the custom function:
138+
- Let {argNode} be next {Expression} in {FuncCallArgs}.
139+
- Let {arg} be the result of {Evaluate(argNode, scope)}.
140+
- Set the parameter named {param} in {newScope} to {arg}.
141+
- Return the result of {Evaluate(funcBody, newScope)}
142+
- Otherwise:
143+
- Let {func} be the function defined under the name {name} in either {namespace} namespace if provided, or the `global` namespace.
144+
- Return the result of {func(args, scope)}.
111145

112146
ValidateFuncCall():
113147

148+
- Let {name} be the string value of the {FuncIdentifier}.
114149
- Let {namespace} be the string value of the {FuncNamespace}.
150+
- Let {args} be an array of the {Expression}s in {FuncCallArgs}.
151+
- If there's a custom function defined with the given {name} and {namespace}:
152+
- For each {arg} in {args}:
153+
- Execute {Validate(arg)}.
154+
- Stop.
115155
- If there is no namespace named {namespace}:
116156
- Stop and report an error.
117-
- Let {name} be the string value of the {FuncIdentifier}.
118157
- If there is no function named {name} defined in either {namespace} namespace if provided, or the `global` namespace:
119158
- Stop and report an error.
120-
- Let {args} be an array of the {Expression}s in {FuncCallArgs}.
121159
- Let {validator} be the validator for the function under the name {name}.
122160
- Execute {validator(args)}.

spec/12-custom-functions.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Custom Functions
2+
3+
Custom functions are reusable sets of query substructures that allow for the modular composition of GROQ queries.
4+
5+
## Function Definition
6+
7+
Functions MUST be defined in the beginning of a query using the delimiter `;`.
8+
A function definition MUST start with the keyword `fn`.
9+
All custom defined functions MUST live in a namespace.
10+
11+
FuncDecl : `fn` FuncNamespace FuncIdentifier FuncParams `=` FuncBody `;`
12+
13+
## Function invocation
14+
15+
FuncParams : `(` Param `)`

spec/GROQ.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ _Current Working Draft_
55
This is the specification for GROQ (**G**raph-**R**elational **O**bject **Q**ueries), a query language and execution engine made at Sanity, Inc, for filtering and projecting JSON documents. The work started in 2015. The development of this open standard started in 2019.
66

77
GROQ is authored by [Alexander Staubo](https://twitter.com/purefiction) and [Simen Svale Skogsrud](https://twitter.com/svale).
8-
Additional follow up work by [Erik Grinaker](https://twitter.com/erikgrinaker), [Magnus Holm](https://twitter.com/judofyr), [Radhe](https://github.com/j33ty), [Israel Roldan](https://github.com/israelroldan), [Sindre Gulseth](https://github.com/sgulseth), [Matt Craig](https://github.com/codebymatt), [Espen Hovlandsdal](https://github.com/rexxars).
8+
Additional follow up work by [Erik Grinaker](https://twitter.com/erikgrinaker), [Magnus Holm](https://twitter.com/judofyr), [Radhe](https://github.com/j33ty), [Israel Roldan](https://github.com/israelroldan), [Sindre Gulseth](https://github.com/sgulseth), [Matt Craig](https://github.com/codebymatt), [Espen Hovlandsdal](https://github.com/rexxars), [Tonina Zhelyazkova](https://github.com/tzhelyazkova).
99

1010
This specification should be considered _work in progress_ until the first release.
1111

@@ -62,3 +62,5 @@ Conformance requirements expressed as algorithms can be fulfilled by an implemen
6262
# [Vendor functions](13-vendor-functions.md)
6363

6464
# [Extensions](14-extensions.md)
65+
66+
# [Custom functions](12-custom-functions.md)

0 commit comments

Comments
 (0)