Skip to content

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.

  1. property

    input:

    {
        "cat": {
            "age": 1
        }
    }

    apath:

    cat.age
    

    result:

    1 
    

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.

InScopeExpression "," InScopeExpression ScopeExpression Expression

StepFunctionDefinition VariableAssignment OrdinaryExpression InScopeExpression

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)

  1. scope expression

    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

"func" Identifier "(" FormalParameterList ")" "=" OrdinaryExpression StepFunctionDefinition

Identifier FormalParameter "," Identifier FormalParameter FormalParameterList

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)

  1. reusable expressions

    input:

    {
        "cat": {
            "age": 1,
            "name": "jo"
        }
    }

    apath:

    func plus(k) = _ + $k,
    cat.age.plus(10)

    result:

    11 
    
  2. constructive function

    input:

    {
        "cat": {
            "age": 1,
            "name": "jo"
        }
    }

    apath:

    func callName(x) = { id: $x, '#call': name },
    cat.callName(2)

    result:

    { "id": 2, "#call": "jo" } 
    
  3. recursive function

    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

Identifier "=" OrdinaryExpression VariableAssignment

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)

  1. variable assignment

    input:

    {
        "cat": {
            "age": 1
        }
    }

    apath:

    toLive = 15 - cat.age,
    $toLive

    result:

    14 
    
  2. ... memoizing nodes

    input:

    { "cat": {
        "mom": {"age": 5},
        "age": 2 
    }}

    apath:

    cat.(child=_,
         mom.('gave birth at age '
              + (age - $child.age)))

    result:

    "gave birth at age 3" 
    
  3. ... joins

    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

ConditionalExpression ControlExpression LogicalANDExpression "or" LogicalANDExpression LogicalORExpression OrdinaryExpression

"if" "(" Expression ")" OrdinaryExpression OrdinaryExpression ConditionalExpression

The conditional consists of the condition-part, then-part, and optional alternative (else-part).(1)

  1. conditional

    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:

BooleanLiteral NumericLiteral StringLiteral NullLiteral Nil Literal Step "." Step Path PrimaryExpression

Paths & Steps

Step "." Step Path

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.

Subscript "?" "(" Expression ")" Filter BasicStep "[" Expression "]" Subscript Step

A step is a filter, a subscription, or solely a basic step (if none of the optional branches are chosen).

Filter

Subscript "?" "(" Expression ")" 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)

  1. filter (predicate)

    input:

    [{"age": 1}, {"age": 2}]
    

    apath:

    *?(age==1)
    

    result:

    { "age": 1 } 
    

Subscript

BasicStep "[" Expression "]" Subscript

Subscripts are reserved for arrays, and the index expression must evaluate to a number.(1)

  1. subscript

    input:

    {"names": ["jo", "lu"]}
    

    apath:

    names[1]
    

    result:

    "lu" 
    
Note / nested arrays

List of subscriptions are not directly supported. Nested array subscripts can be performed with (...[i])[j].

Basic Steps

Identifier "(" ArgumentList ")" StepFunctionCall Identifier NameLiteral RegularExpressionLiteral "prop" "(" Expression ")" Property "*" Children "(" ScopeExpression Expression ")" "self" "_" Self ObjectConstruction ArrayConstruction Construction "$" Identifier VariableReference BasicStep

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

! ReservedWord IdentifierName Identifier "`" BacktickStringCharacter "`" NameLiteral "/" RegularExpressionBody "/" RegularExpressionLiteral "prop" "(" ScopeExpression Expression ")" Property

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)

  1. property

    input:

    {
        "cat": {
            "age": 1
        }
    }

    apath:

    cat.age
    

    result:

    1 
    
  2. property literal

    input:

    {
        "cat": {
            "#key": 8,
            "#id": 7
        }
    }

    apath:

    cat.`#id`
    

    result:

    7 
    
  3. property regex

    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)

  1. dynamic property

    input:

    {
        "cat": {
            "age": 8,
            "p": "age"
        }
    }

    apath:

    cat.prop(p)
    

    result:

    8 
    

"self" "_" "and" "or" "not" "if" "nil" "none" "prop" "func" Keyword "null" NullLiteral "true" "false" BooleanLiteral "nil" Nil ReservedWord

Step Function Call

Identifier "(" ArgumentList ")" StepFunctionCall

OrdinaryExpression "," OrdinaryExpression ArgumentList

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

"*" Children

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)

  1. children (array)

    input:

    {"names": ["jo", "lu"]}
    

    apath:

    names.*
    

    result:

    "jo" , "lu" 
    
  2. self

    input:

    {
        "cat": {
            "age": 1,
            "name": "jo"
        }
    }

    apath:

    cat._
    

    result:

    { "age": 1, "name": "jo" } 
    

"self" "_" Self

Construction

"{" PropertyAssignmentList "}" ObjectConstruction "[" ElementList "]" ArrayConstruction Construction

The construction of objects and arrays conforms to JSON/javascript syntax with special enhancements.

Objects

"{" PropertyAssignment "," PropertyAssignment PropertyAssignmentList "}" ObjectConstruction

PropertyName ":" OrdinaryExpression "none" PropertyAssignment

Identifier StringLiteral "prop" "(" Expression ")" PropertyName

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)

  1. construction (object)

    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)

  1. ... dynamic property

    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)

  1. ... embedding

    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

"[" OrdinaryExpression "," OrdinaryExpression ElementList "]" ArrayConstruction

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)

  1. construction (array)

    input:

    {
        "cat": {
            "age": 1,
            "p": "name"
        }
    }

    apath:

    cat.[ 3, age ]
    

    result:

    [ 3, 1 ] 
    
  2. ... sequences

    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'       

  1. non-Terminals are hyper-linked if defined here. ↩