Grammar-based Description
apath is an expression language, so the start rule is Expression. The grammar will be described top-down1. Icon references the related cheat-sheet part and icon (1) shows an example popup. Furthermore, icon
shows the semantics of the language element. See also basic definitions.
Expressions
Every (sub-)expression in apath is evaluated to a sequence of nodes and the result of the entire expression is the composition of the evaluations of the individual sub-expressions. We say that an evaluation has no solution if the corresponding sequence is empty.
The grammar starts with consecutive expressions, delimited by comma. In this case, its value is the value of the last one. It is mostly used for side effects, e.g., logging or variable assignment.(1)
-
input:
{
"cat": {
"age": 1,
"name": "jo"
}
}apath:
cat.(a = age, name + $a)result:
"jo1"
Note / Grammar
The usual enclosing parenthesis for a ScopeExpression are introduced at the appropriate points of use within the grammar.
Step Functions
Step functions support the reuse of functionality and the building of custom language elements. Step functions are divided in functions written in apath itself (described here) or written in javascript.
A function definition is introduced with the func keyword, followed by a parameter list and the expression it should return. The context of the specified expression is the one that was valid when the function was called. Parameters are referenced in the same way as variables.(1) Frequently used constructions can be reused(2), and complex functions can be defined with local variables and access to global variables.(3)
-
input:
{
"cat": {
"age": 1,
"name": "jo"
}
}apath:
func plus(k) = _ + $k,
cat.age.plus(10)result:
11 -
input:
{
"cat": {
"age": 1,
"name": "jo"
}
}apath:
func callName(x) = { id: $x, '#call': name },
cat.callName(2)result:
{ "id": 2, "#call": "jo" } -
input:
{
"cat": {
"age": 1,
"name": "jo"
}
}apath:
to = 5,
func recIncr(x) = (
y = $x + 1,
if ($y == $to) $y recIncr($y)),
cat.recIncr(age)result:
5
Note / Body
For simple functions, the expression can be specified directly in the function body. For more complex ones, we recommend using parenthesis (ScopeExpression in basic step).
Variables
Variables enables the reuse of values. They are declared implicitly the first time they are assigned. Their scope is the ScopeExpression in which they were declared.(1) As well as being used to store simple values, they can be used to memorize nodes that are referenced (with the $ symbol) in subsequent paths.(2) Finally, entire sequences can also be saved, enabling you to realize joins.(3)
-
input:
{
"cat": {
"age": 1
}
}apath:
toLive = 15 - cat.age,
$toLiveresult:
14 -
input:
{ "cat": {
"mom": {"age": 5},
"age": 2
}}apath:
cat.(child=_,
mom.('gave birth at age '
+ (age - $child.age)))result:
"gave birth at age 3" -
input:
{
"cats": ["jo", "lu"],
"dogs": ["bay", "jo"]
}apath:
cats = cats.*, dogs = dogs.*,
$cats.(cat=_,
$dogs ?(_ != $cat).
($cat + ' loves ' + _))result:
"jo loves bay" , "lu loves bay" , "lu loves jo"
Note / Grammar
The dollar symbol $ is only used for referencing variables. Within the assignment expression or in the parameters of a function declaration, it is omitted for reasons of better readability, thus following the design of some scripting languages (e.g., shell scripting). This is because it is clearly decidable in the places of the assignment statement, whereas in referencing it could collide with a property name, for instance.
Ordinary Expressions
The conditional consists of the condition-part, then-part, and optional alternative (else-part).(1)
-
input:
{
"cat": {
"age": 1,
"name": "jo"
}
}apath:
cat.(if (age < 10) age 'old')result:
1
Tip / else-part
As in some other functional languages (e.g. clojure and scheme), conditionals are without the else keyword. For complex parts, parsing unambiguousness, and better readability you can use parenthesized expressions, e. g., if (A) (a, b) (c + 1). Only very simple ones could skip them.
Basic elements, starting from LogicalORExpression, follow the usual logical and relational syntax of programming languages. apath mostly reuses javascript syntax, adopted from the javascript-PEG-grammar with slight changes.
The keywords and/or/not (instead of &&, ||, !) are used and equality/relational expressions (==, !=, <=, >=, <, >) are defined as usual, see compact grammar for details. Also, as usual, the atomic parts are primary expressions, and they now contain paths:
Paths & Steps
Paths consist of 'dot'-separated steps. Every step yields a sequence of nodes that are passed one by one to the subsequent step. This passed node is called context node. The solution of a path is the yielded sequence of the last step. If one step yields an empty sequence, no further steps are performed, and the solution of the whole path is the empty sequence.
A step is a filter, a subscription, or solely a basic step (if none of the optional branches are chosen).
Filter
By means of a filter, the sequence retrieved by Subscript can be restricted to a subsequence of nodes. For every node in the original sequence, the filter expression is evaluated, and the result is passed to the test function (see test). If the test yields true, then the node is contained in the sub-sequence.(1)
Subscript
Subscripts are reserved for arrays, and the index expression must evaluate to a number.(1)
Note / nested arrays
List of subscriptions are not directly supported. Nested array subscripts can be performed with (...[i])[j].
Basic Steps
A basic step is a step function call, a property value selection, all children, a parenthesized expression, the context node itself, or a variable reference. Furthermore, objects and arrays can be created.
Selection
Properties
Property values of an object can be selected by an identifier that must not be a reserved word.(1) If the property name is not a regular identifier (e.g. #1), a so-called name literal can be used.(2) If a regex is given, all properties with names conforming to it will be taken.(3)
-
input:
{
"cat": {
"age": 1
}
}apath:
cat.ageresult:
1 -
input:
{
"cat": {
"#key": 8,
"#id": 7
}
}apath:
cat.`#id`result:
7 -
input:
{
"cat": {
"#key": 8,
"#id": 7
}
}apath:
cat./#.*/result:
8 , 7
Dynamic property names can be realized with the help of the prop keyword. In this case, the evaluation of the argument must be a string.(1)
Step Function Call
Step function calls can take arguments and yield a sequence defined by an apath function or javascript. Whenever they are invoked, the current context node is passed and used as usual within the body of the function.
Children & Self
To retrieve all property values of an object or all elements of an array, the children construct can be used.(1) To access the context node, use the following: (2)
-
input:
{"names": ["jo", "lu"]}apath:
names.*result:
"jo" , "lu" -
input:
{
"cat": {
"age": 1,
"name": "jo"
}
}apath:
cat._result:
{ "age": 1, "name": "jo" }
Construction
The construction of objects and arrays conforms to JSON/javascript syntax with special enhancements.
Objects
As usual, an object is built with a list of property assignments. The property value (right-hand side of an assignment) must be a singleton or empty. If it is empty, no assignment is performed. If the value is none then the property will be undefined.(1)
-
input:
{
"cat": {
"age": 1,
"name": "jo"
}
}apath:
cat.{ id: 2, '#call': name }result:
{ "id": 2, "#call": "jo" }
Note / property value
If the sequence has more than one element, the value is not automatically an array as in other path languages, instead an error is thrown. If array construction is intended, the right-hand side can use the array constructor [...].
The property name can be at least an identifier or string -- we adopt the relaxed definition from javascript instead of JSON.
If a property name shall be dynamically created, the prop keyword has to be used. In this case, the evaluation of the argument must be empty or a string. If empty, no assignment is performed, otherwise the string is used as the property name.(1)
-
input:
{
"cat": {
"age": 1,
"p": "name"
}
}apath:
cat.{ prop(p): 'jo' }result:
{ "name": "jo" }
So-called embeddings can be realized by omitting the property name. In this case, the value of the expression can be a sequence with more than one element. The properties of every node in this sequence, that has to be an object, are embedded in the constructed object, possibly overwriting previous properties.(1)
-
input:
{
"cat": {
"age": 1,
"name": "jo",
"id": 3
}
}apath:
cat.{ _, age: 3, id: none }result:
{ "age": 3, "name": "jo" }
Tip / object embeddings
By this an extension or modification of objects can be easily done, e. g., the expression pattern {_, ...} extends or modifies the context node ('_') by subsequent assignments.
Arrays
The array constructor takes a list of values as consecutive array elements.(1) If one value is not a singleton, all its elements will be embedded.(2)
-
input:
{
"cat": {
"age": 1,
"p": "name"
}
}apath:
cat.[ 3, age ]result:
[ 3, 1 ] -
input:
{
"cat": {
"names": ["jo", "miau"]
}
}apath:
cat.[ names.*, 'lu' ]result:
[ "jo", "miau", "lu" ]
Compact EBNF Grammar
Main EBNF rules in PEG (Parsing Expression Grammars) style.
//-
//------------------------ Expressions ------------------------
//-
Expression = ScopeExpression
ScopeExpression =
InScopeExpression (',' InScopeExpression)*
InScopeExpression =
StepFunctionDefinition
/ VariableAssignment
/ OrdinaryExpression
StepFunctionDefinition =
'func' Identifier '(' FormalParameterList? ')' '=' OrdinaryExpression
FormalParameterList =
FormalParameter ("," FormalParameter)*
FormalParameter = Identifier
VariableAssignment =
Identifier '=' OrdinaryExpression
OrdinaryExpression =
ControlExpression
/ LogicalORExpression
ControlExpression =
ConditionalExpression
ConditionalExpression =
'if' '(' Expression ')' OrdinaryExpression OrdinaryExpression?
VariableReference =
"$"Identifier
LogicalORExpression =
LogicalANDExpression ('or' LogicalANDExpression)*
LogicalANDExpression =
EqualityExpression ('and' EqualityExpression)*
EqualityExpression =
RelationalExpression (EqualityOperator RelationalExpression)*
EqualityOperator =
'=='
/ '!='
RelationalExpression =
AdditiveExpression (RelationalOperator AdditiveExpression)*
RelationalOperator =
"<="
/ ">="
/ "<"
/ ">"
AdditiveExpression =
MultiplicativeExpression (AdditiveOperator MultiplicativeExpression)*
AdditiveOperator =
'+'
/ '-'
MultiplicativeExpression =
ExponentiationExpression (MultiplicativeOperator ExponentiationExpression)*
MultiplicativeOperator =
'*'
/ '/'
/ '%'
ExponentiationExpression =
UnaryExpression ('**' ExponentiationExpression)*
UnaryExpression =
PrimaryExpression
/ UnaryOperator UnaryExpression
UnaryOperator =
'+'
/ '-'
/ 'not'
PrimaryExpression =
Literal
/ Path
//-
//------------------------ Paths & Steps ------------------------
//-
Path =
Step ('.' Step)*
Step =
(Filter / Subscript)
Filter =
Subscript ('?' '(' Expression ')')?
Subscript =
BasicStep ('[' Expression ']')?
BasicStep =
StepFunctionCall / Property / Children
/ '(' Expression ')'
/ Self
/ Construction
/ VariableReference
Property =
Identifier
/ NameLiteral
/ RegularExpressionLiteral
/ 'prop' '(' Expression ')'
Children =
'*'
Self =
('self' / '_')
StepFunctionCall =
Identifier '(' ArgumentList? ')'
ArgumentList =
OrdinaryExpression (',' OrdinaryExpression)*
//-
//------------------------ Construction ------------------------
//-
Construction =
ObjectConstruction
/ ArrayConstruction
ObjectConstruction =
'{' PropertyAssignmentList? '}'
PropertyAssignmentList =
PropertyAssignment (',' PropertyAssignment)*
PropertyAssignment =
(PropertyName ':')? (OrdinaryExpression / 'none')
PropertyName =
Identifier
/ StringLiteral
/ 'prop' '(' Expression ')'
ArrayConstruction =
'[' ElementList? ']'
ElementList =
OrdinaryExpression (',' OrdinaryExpression)*
//-
//------------------------------------------------
//-
Identifier =
!ReservedWord IdentifierName
ReservedWord
= Keyword
/ NullLiteral
/ BooleanLiteral
/ Nil
Keyword
= 'self'
/ '_'
/ 'and'
/ 'or'
/ 'not'
/ 'if'
/ 'nil'
/ 'none'
/ 'prop'
/ 'func'
-
non-Terminals are hyper-linked if defined here. ↩