Perspectives Language Reference

This is the authoritive reference on the Perspectives Language. It is a work in progress. Currently, it is mostly about syntax. Semantical constraints will be added later. This is not the only source of information on PL:

Terminology

Table of Contents

[toc]

identifiers

All types are identified by qualified names. These are qualified names:

Name form Explanation
model://perspectives.domains#System A domain name must be qualified in a custom URI scheme (see below)
model:MyModel$MyCase A fully qualified case name.
model:MyModel$MyCase$MyRole A fully qualified role name.
model:MyModel$MyCase$MyRole$MyProperty A fully qualified property name.
pre:MyCase A prefixed name is a qualified name as long as we have a use statement for the prefix: use: pre for model:MyModel

Quite often the parser/compiler can guess the qualified name from just the local part of that name. If not, an error is signalled. The use...for statement may be used to abbreviate longer namespaces, e.g. use mrl for model:MyModel$MyCase$MyRole lets us identify the property given in the table as mrl:MyProperty.

If the parser finds multiple qualified names with the same local part, either fully qualify the offending name or prefix it with enough segments to make it unique. For example, given the roles:

model:MyModel$ACase$MyRole
model:MyModel$AnOtherCase$MyRole

the single segment MyRole does not uniquely identify either. However, ACase$MyRole does and the system does not complain.

Finally, each context and role act as namespace containers. Hence, qualified names can become quite long!

Custom URI scheme

Version 18 and later support a style of identifier that is based on a custom URI scheme, informally: model://authority/LocalModelName. Examples:

Name form Explanation
model://perspectives.domains#System The model System is qualified with the namespace model://perspectives.domains
model//social.perspectives.domains#AnotherModel$MyCase The namespace can have subnamespaces, just like DNS style domains can have subdomains

These URIs can be mapped onto URLs. We use that mapping to find the location of the machine readable version of the model file. The first example maps to https://perspectives.domains/models_perspectives_domains/System.json. The server at perspectives.domains will understand that as a request for the file System.json in the Couchdb database models_perspectives_domains.

Context declarations

There are four kinds of context. Each can be declared with a specific keyword. Each can house some, but not all, other context declarations.

Kind Explanation
domain A domain is the top level context of a model. A domain can contain roles and contexts, and use statements. The name of a domain must start with model:, as in model:TestDomain.
case A case revolves around a central concept (a role) and must contain the user roles that have a perspective on that concept
party A party is for the purpose of introducing user roles that can be referred from cases. Use party to structure an organisation. A party can contain other parties.
activity An activity is about doing things in a context with clear responsibilities. An activity may contain states.

Context declarations can only be nested inside other Context definitions, with limitations:

domain case party activity
domain x x x
case x x
party x
activity x

Public contexts

A context type may be constructed to have public instances. These are like web pages on the open internet: anyone with their address (and a MyContexts installation) can open them. Public instances are exclusively constructed through the mechanisme of a public role: this is a (fictitional) user role that 'lives' at a specific URL. See below.

Role declarations

There are five kinds of roles. Each can be declared with a specific keyword inside a context definition.

Kind Explanation
user A user role represents living persons that perform actions. Actions are collected in a perspective. A user role can only be filled by another user role.
thing A thing role has no perspective. It represents everything that is not a user. A thing role can only be filled by another thing role.
context A context role is bound to the External Role of a context. It represents a context (instance) within another context (instance). A context role can only be filled by another context role, or by an external role.
external An external role is a role that represents a context. An external role cannot be filled.
public A calculated user role whose perspective on the context is published on a server on the internet and thus is available to all

Use these keywords to create Roles. Restrictions apply on filling roles:

  1. user and thing roles can only be filled with the same kind;
  2. a context role can be filled by a context role or an external role (but in the expressions it looks as if one fills it with a Context type)
  3. external roles cannot be filled.

Role declarations:

Expression Explanation
user Driver Declare a User role with default attributes: not mandatory, functional.
user Driver (mandatory, relational) Declare a User role with explicit attributes.
user Driver filledBy: Person User role with default attributes, filled with the Person role.
user SystemUser filledBy: None SystemUser is the root user and is never filled with another role
user Parent filledBy: Father, Mother User role filled by either of two roles.
user Driver (mandatory) filledBy: Person User role with attributes, filled with the Person role.
user Driver filledBy: Person + LicenseHolder User role that requires its fillers to have two types simultaneously. One role provides personal details, the other the drivers license
thing UnfinishedRides = filter Rides with not Finished Calculate a role with an expression.
thing ListOfDocuments = callExternal "docNamesFromCouchdb" returns Document A call to a (fictitious) API function that results in instances of the type Document. We call a role that is created in this way a computed role.
thing Account (unlinked) An unlinked role is not registered within the context itself. There is no semantic difference with an ordinary role; but the representation of its context doesn't refer to it
public Visitor at "https://perspectives.domains/cw_servers_and_repositories/" = sys:Me A public user role that defaults to the system user (so, anyone), whose context- and role instances can be found at the given url. Access is supposed to be free (i.e. no credentials should be needed to access the instances)

The syntax for the public role declaration is as follows:

public <RoleName> at <expression-with-stringvalue> = <expression-with-userrole-value>

In other words, the location where the resources for the public perspective are stored, results from an expression and so can be calculated.

State

Roles and Contexts may have state definitions (note: a state is not a type that can be instantiated, like a context or role. Yet state terminology can be used to describe states). States are declared using a name and an expression with a Boolean value:

case MyContext
  state SomeState = <expression>

States can be nested inside other states.

The use of states is threefold:

on entry, on exit

Notifications and automatic effects are defined in the lexical scope of an on entry or on exit clause.

domain Parties
  case Party
    user Guest
      property FirstName (String)
      property Accept (Boolean)
      state Accepted = Accept
        on entry
          notify Organizer
            “{FirstName} has accepted.”
    user Organizer

A notification consists of a user role that the notification is meant for, and a string that may contain expressions in accolades. The expressions are evaluated with respect to a resource defined by the current state of the sentence; here, that is the state Guest$Accepted. As this is a state of a user role, the state is subject state, fixing the resource we apply the expression to Guest.

We can also embed an automatic effect in a state transition. An automatic effect is just a list of assignment statements (see the paragraph Assignment statements below).

domain Parties
  case Party
    user Guest
      property FirstName (String)
      property Accept (Boolean)
      state Accepted = Accept
        on entry
          do for Organizer
            -- Some relevant action expressed as assignment statements
    user Organizer

The fragment do for may be followed by a relative or absolute name. However, only user roles of the current context can be referred to. In order to have a role external to the context execute the action, create a calculated role in the current context.

There are three forms of the on entry clause:

on entry
on entry of subject state
on entry of subject state S

Instead of subject, we can use object and context as well. By using of subject state we change the current state to subject state. Lexical analysis of the text will reveal the relevant user role.

The same variations hold for on exit.

in state

The in state clause may be used to describe a perspective that only holds in the given state. For example:

domain Parties
  case Party
    user Guest
      perspective on WishList
        in object state Ready
          view AllProperties verbs (Consult)

This model fragment gives Guest the means to consult all properties of the WishList, provided it is in state Ready. The in state clause refers to object state, here. This object state is specified by the perspective on WishList clause, so it is about the WishList. Instead, we might have specified subject state by using the clause of subject state, thereby referring to states of Guest. Finally, use of context state to refer to states of the enclosing context.

Please note that this is not the only way to model this perspective. We could have given the perspective in the lexical context of WishList itself:

domain Parties
  case Party
    thing WishList
      state Ready = ...
        perspective of Guest
          view AllProperties verbs (Consult)

You are free to choose either approach, whatever is more legible or understandable.

These are the variations of in state:

in state
in state S
in subject state
in subject state S
in object state
in object state S
in context state
in context state S

State and Aspects

The states of Aspects of a context- or role type just add to its own states. There is no provision, however, to

Timing facets

An automatic action is carried out immediately upon state entry. That is, as soon as a state holds for a context or role, any automatic actions are carried out on behalf of their respective users.

However, it is possible to delay that action:

      state Accepted = Accept
        on entry
          do for Organizer after 2 Minutes
            -- Some relevant action expressed as assignment statements

The keyword delay must be followed by an integer value and a time constant type: Milliseconds, Seconds, Minutes, Hours, Days.

More interestingly, it is possible to have an action repeated :

      state Accepted = Accept
        on entry
          do for Organizer after 2 Minutes every 20 Seconds
            -- Some relevant action expressed as assignment statements

The keyword every must be to the right of the (optional) after clause.

We can also have a cap to the number of times:

      state Accepted = Accept
        on entry
          do for Organizer after 2 Minutes every 20 Seconds maximally 5 times
            -- Some relevant action expressed as assignment statements

The keywords maximally and times must be to the right of after but have to come before the optional maximum number of repetitions. Notice that we need to add a clause every: otherwise, all repetitions would be carried out immediately after one another (the compiler sees to it that you don't forget it).

Finally, we can limit the repetition by providing an until clause:

      state Accepted = Accept
        on entry
          do for Organizer after 2 Minutes until 5 Minutes every 20 Seconds
            -- Some relevant action expressed as assignment statements

The until is to be read as: until the given time has passed since the state became valid. Summing up, we have the following syntax:

    [after N <Time constant>] [until M <Time constant>] [every P <Time constant> [maximally Q times]]

Notice how all clauses are optional, but must come in the order as given; and notice that the maximally clause is optional, but must be used together with every.

Property

Property declarations:

Declaration Explanation
property MyProperty A default property: a String range, it is functional and not mandatory.
property MyProperty (mandatory, relational, Boolean) A property with defined attributes and range
property PlannedDuration = Destination >> Planned – Origin >> Planned Calculate a property with an expression.
property P (functional) = binder SomeRole >> SomeProp Property is known to be functional despite potentially relational binder step.

Range

A property must be declared with a valid range, or it will be a String. The table below gives the valid range types and the form of their literals in model text:

Range type Literal form
String "This is a string literal"
Date '2022-03-11'
Time "hh:mm:ss" and "hh:mm:ss.sss"
DateTime 'YYYY-MM-DDTHH:mm:ss.sssZ', but time may be left out
Boolean true
Number 123
Email john@doe.com
File _
Year, Month, Week, Day, Hour, Minute, Second, MilliSecond Duration type values must be written as integers
MarkDown <<This is a **markdown** literal.>>

See this page for the supported time format. See this page for the supported date-time format.

Note that there is no literal form for a File property. However, we do have an assignment statement to create files (create file..., see below).

Constraining facets

Apart from the mandatory constraint, a number of other so-called constraining facets may be specified with the property. They are inspired on the XML specification: see Constraining Facets.

Constraint Explanation Applies to
minLength Input may not be shorter than given integer Number
maxLength Input may not be longer than given integer Number
enumeration Only the given values are valid String, Date, Number, Email
pattern String form of the input must match a regular expression String, Date, Number, Email, the mime type of File
maxInclusive Upper bound for the value Date, Number
minInclusive Lower bound for the value Date, Number

Examples

property Text (mandatory, String)
    minLength = 100
    maxLength = 200
    
property ADateTime (DateTime)
    minInclusive = '2022-04-15'

property ANumber (Number)
    minInclusive = 10
    maxInclusive = 80

property WeekDay (String)
    enumeration = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")

property Appel (String)
    pattern = ".*appel.*" "Any word with the string `appel` in it."

property Source (File)
  pattern = "text/arc" "Only .arc files (Perspectives Language source files) are allowed, so use `text//arc."

The pattern must be enclosed in strings. Furthermore, observe the following:

E.g.:

  pattern "https://\\w+\\$" "An URL that ends on a $ sign"

Aspect

Adding an aspect uses the same syntax for contexts and roles. Here is how to add an Aspect to a context:

case MyContext
  aspect SomeOtherContext

Once an aspect has been added to a context, its roles may be added to the enriched context (notice the use of qualified names):

case MyContext
  ...
  aspect user pre:SomeUserRole
  aspect thing model:SomeModel$SomeThingRole

NOTE: when referring to a role that has been added as an aspect to a context, one should use the fully qualified name (or the prefixed name)!

  user SomeUser
    -- This will fail:
    perspective on SomeThingRole
    -- But this will succeed:
    perspective on model:SomeModel$SomeThingRole

We can also add an Aspect to an existing role, thereby adding all its properties to that role:

case MyContext
  ...
  thing MyRole
    aspect SomeOtherRole

It is then possible to map an Aspect property onto a property of the role:

case MyContext
  ...
  thing MyRole
    aspect SomeOtherRole
      where 
        AspectProperty is replaced by Property

Whenever an aspect context is added to a receiving context, its external role is added automatically to the external rol of the receiving context. In other words, there is no need for:

case MyContext
  aspect SomeOtherContext
  external
    aspect SomeOtherContext$External

If an Aspect is relational (allows multiple instances), the enriched role will be relational as well.

If an Aspect is mandatory, the enriched role will be mandatory as well. This cannot be overridden.

Note: aspect identifiers must be fully qualified.

Restrictions

Certain restrictions apply:

Perspective

Perspective declaration:

  user SomeRole
    perspective on SomeThing

There is no need to qualify the role name if it is in the same context as the user role (unless the perspective object is added as an aspect role to the context, see above).

A perspective declaration can be followed by role- and property verbs, to flesh out what the user role can do:

  user SomeRole
    perspective on SomeThing
      only (Fill, Remove)
      props (SomeProperty, AnotherProperty) verbs (SetPropertyValue)

The role verbs are Remove, Delete, Create, CreateAndFill, Fill, Unbind, RemoveFiller, Move. Apart from the keyword only, one can use except, whatever is more compact (however: notice that except does not forbid the excepted verbs. It is just a convenient way to express a list of allowed verbs. This is important w.r.t. aspects, as a user role aspect causes the users' perspectives to be enriched by the aspect user and this may add role verbs to the final list that are in an exclude clause in one of the combined perspectives!). To include all role verbs, use all roleverbs.

perspective on SomeThing
  all roleverbs

To include no role verbs, just don't include a role verb specification:

perspective on SomeThing

The property verbs are: RemovePropertyValue, DeleteProperty, AddPropertyValue, SetPropertyValue, Consult. Note that property verbs are given per set of properties:

  props (SomeProperty, AnotherProperty) verbs (SetPropertyValue)

or per view (omit the verbs keyword in that case)

  view AView (SetPropertyValue)

To allow all property verbs with a view, just omit a specification of property verbs:

  view aview

Finally, to allow all role verbs, and all property verbs for all properties:

    perspective on SomeThing
      defaults

Subject and object of a Perspective

First, notice that a perspective must be both for some subject (a user role) and on some object (any other role: a thing, context, user or external role). By embedding a perspective in a user role (as we did in the examples above), the subject is given implicitly. By using perspective of we can specify the subject role explicitly. So, for example, in the context of some thing role, by using perspective of we again have the combination of object and subject.

  thing SomeThing
    perspective of SomeRole

equals:

  user SomeRole
    perspective on SomeThing

Other components of a Perspective

Other clauses we can include in the scope of a perspective:

The first three options introduce the means to qualify what follows in terms of state or state transitions. We have discussed Embedding perspectives in the previous paragraph. The Perspectives Language parser and compiler will not allow you to nest two perspective on or perspective of clauses; however, you can nest one in the other.

An action is like an automatic action. But whereas the latter occurs on state transitions, the former must be executed by the end user. This obviously requires some user interface element such as a button or menu action; however, that is beyond the scope of the language specification.

selfonly

Imagine a party with a role Guests, modelled with a Boolean property that indicates whether a Guest has accepted the invitation or not. In order to be able to change that property, the role Guests must have a perspective on itself. However, each Guest should be able to change just his own property value. This we model with the modifier selfonly.

Verbs in Perspectives

As modifications sent by peer installations are checked against the model, it is important to know how to endow a user with an adequate perspective, so they will not be blocked by the receiver. This is especially important for automatic actions. The table below links an assignment operator with the verb required for peers to accept the modification.

Modification Assignment operator Required verb
Remove a single role instance remove Remove
Remove all role instances of a type delete Delete
Add a role instance to a context instance create role Create
Create a role instance and fill it with a new context create context CreateAndFill
Fill a role instance bind Fill
Remove a role as filler from all roles (optionally of a type) unbind RemoveFiller
Move a role instance to another context instance move Move
Add a property value += AddPropertyValue
Remove a property value -= RemovePropertyValue
Delete a property (remove all values) delete property DeleteProperty
Replace a property's values by a value = AddPropertyValue and RemovePropertyValue

Lexical scope: embedding constructs

A type definition consists of a type declaration line, followed by an indented block that gives the type’s details (we might call this block the body of the type definition text). Similarly, many other syntactical constructs, like perspective or in state, consist of a line of text followed by a body. The table below gives, for nine such bodies, how they may be nested inside each other. Not all nestings are arbitrarily deep: only two perspectives can be nested, for example, and one must be a perspective on while the other is a perspective of.

Right: contained context role state perspective in state state transition action notification automatic action screen
Below: containing
context yes yes yes
role yes yes yes yes yes user role only
state yes yes yes yes
perspective yes yes yes yes
in state yes yes
state transition yes yes
action
notification
automatic action
screen

Note that there can be further restrictions. For example, an action may be embedded in a user role, but not in other roles.

Use the table to work out what a particular construct can be nested in. This gives rise to a tree. For a state transition, for example, the tree is given partially below:

Combine this with the table in the section on the standard variables currentcontext, currentactor, notifieduser and origin to work out whether a particular variable is available in a syntactical location. For example, this model fragment:

case C
    user U
    thing R
        perspective of U
            on entry
                do
                    <statement>

<statement> can have currentcontext, currentactor and origin.

In contrast, in:

case C
    thing R
        property P = <expression>

we can only use currentcontext and origin in <expression>.

Assignment

(Automatic) Actions are expressed as assignments. An <assignment> comes in two forms:

A letA expression:

letA
  varname1 <- <expression>
  varname2 <- <expression>
  in
    <assignment>

Notice: variable names must be lowercase!

A letE or letA expression lets you bind variables to the result of expressions. However, the letA allows the part that gives the value of a variable to be one of two assignment statements, too: create role and create context. This makes it possible to create an instance and modify it immediately.

letA
    newroleinstance <- create role <SomeType>
    varname2 <- <expression>
  in
    <SomeNumericProp> = 10 for newroleinstance

create context returns the role instance in the embedding context that is filled with the external role of the new context instance.

letA
  trans <- create context Transaction bound to Transactions
in
  bind sys:Me to Creditor in trans >> binding >> context

Assignment Statements

Assignment statements change the instances of a role in a context. The first group of assignments creates or deletes role instances or fills them with another role. The second group changes the values its properties have.

NOTE: the text Assignment has a more in-depth treatment of the subject.

Assignment Required Verb Explanation
remove role <role-expression> Remove Select the role instances to remove, in any context
remove context <role-expression> Remove Select the role instances to remove, in any context. They must be instances of a context role type. Their bound contexts will be removed, too.
create role RoleType [in <contextExpression>] Create Create a new instance of the role and attach it to the current context, or to the context selected with the optional <contextExpression>.
move <roleExpression> [to <contextExpression>] Move Move the selected role instances to the current context, or the selected context. Notice that both contexts have to have the same type!
delete role roleType [from <context-expression>] Delete Delete all selected instances of the RoleType, from the selected contexts.
delete context bound to roleType [from <context-expression>] Delete Delete all selected instances of the context RoleType and the contexts bound by them, from the selected contexts.
bind <binding> to RoleType [in <contextExpression>] Fill To fill a role with another role, use bind (when A fills B, we say that B is bound to A. A is the binding; B is the binder). RoleType resolves to the current context or the selected context. New instances of RoleType are created as binders.
bind_ <binding> to <binder> Fill Instead of creating new instances, this syntax allows you to select an (empty) binder and fill it with the binding. Notice that both queries must select a single instance!
unbind <binding> [from RoleType] RemoveFiller Select bindings - that is, the roles that are bound - and release them from their binders. When RoleType is given, just release them from binders of that Type.
unbind_ <binding> from <binder> RemoveFiller Select a single binding and and release it from the selected single binder.
create context ContextType [named localName] [bound to RoleType] [in <contextExpression>] CreateAndFill Create a new instance of ContextType and optionally bind it in a new instance of RoleType, by default in the current context and optionally in the context instances selected by <contextExpression. Also, optionally provide an identifier for the context instance (without a storage scheme such as "def:" etc.). Useful for public contexts. If no RoleType has been provided, the context will be created as a root context.
create_ context ContextType [named localName] bound to <roleExpression> CreateAndFill Create a new instance of ContextType for every role instance selected by <roleExpression and bind them. Also, optionally provide a name for the context instance (without a storage scheme such as "def:" etc.). Useful for public contexts; overrides symbolic public store names.

Property assignments. This is how to change the values of a property for some role. The assignment statements below change the roles in the current object set, unless one or more role is selected with <role-expression>. The current object set is determined by the object of the perspective.

Assignment Required Verb Explanation
SomePropertyType =+ <value-expression [for <role-expression] AddPropertyValue Add a value to the list of values for SomePropertyType. It is an error if the type of <value-expression is not equal to the range of SomePropertyType. Changes the current object set, or another role if <role-expression> is given.
SomePropertyType =- <value-expression [for <role-expression] RemovePropertyValue Remove the value from the list of values for SomePropertyType. It is an error if the type of <value-expression is not equal to the range of SomePropertyType.
SomePropertyType = <value-expression [for <role-expression] AddPropertyValue and RemovePropertyValue Completely replace all values of SomePropertyType with the values returned by <value-expression. It is an error if the type of <value-expression is not equal to the range of SomePropertyType.
delete property SomePropertyType [for <role-expression] DeleteProperty Completely remove all values of SomePropertyType.
create file <Name> as <MimeType> in PropertyType [for <RoleExpression>] <Contents> SetPropertyValue (note Contents should be on the next line) Create a file with name Name, and mime type MimeType and give property PropertyType that value. The contents of the file are Contents. Notice that this will only work for text mime types.

Creating Context and Role instances

Creating a context is different from creating a role. It is not possible to create a role instance without immediately inserting it into a context. Similarly, no context should be created without binding it into some other context. This is enforced by the only context creation operators, create context and create_ ontext.

External functions

Perspectives has a mechanism to extend the language with external functions. These functions should be thought of as external with respect to the core language. Such functions are defined in namespaces and added through modules that are compiled with the PDR. One can call them through the use of two keywords:

For example, this ContextRole declaration:

context Modellen = callExternal cdb:Models() returns: Model$External

adds a Role that is computed by the function Models in the module identified by the (standard) prefix cdb: model:Couchdb. As another example, consider this statement in the then-part of a rule:

callEffect cdb:AddModelToLocalStore( origin >> binding >> Url )

This external function loads a model file from an Url and adds it to the local store of model files in Couchdb.

The general form for calling these functions and statements is qualifiedName( <expression>* ), where each argument must be provided by ordinary Perspectives expressions (as detailed below).

View

A view is merely a list of properties.

view MyView (SomeProperty, AnotherProperty)

It is an error if SomeProperty and AnotherProperty are not the identifiers of Properties.

Expressions (queries)

Expressions (also called 'queries') represent a collection of role- or context instances. An expression can be used as a way to calculate a role or property. we do not support calculated contexts. Apart from indexed contexts, every context is only accessible through a context role in another context. It is, of course, entirely possible to calculate a collection of external roles.

Expressions have a highly recursive structure. They are built from three primary forms: (assignments are not expressions!):

Here is the full expression syntax:

expression = simpleExpr | unaryExpr | binaryExpr | letE
simpleExpr = 
    identifier 
    | value 
    | variable 
    | >>= sequenceFunction 
    | ‘this’ 
    | ‘binding’ 
    | ‘binder’ 
    | ‘context’ 
    | ‘extern’ 
    | 'origin' 
    | 'currentcontext'
    | 'currentactor'
    | 'notifieduser'
value = number | Boolean | string | date
variable = lowerCase+
sequenceFunction = ‘sum’ | ‘product’ | ‘count’ | ‘minimum’ | ‘maximum’
unaryExpr = 
    ‘not’ expr 
    | ‘exists’ expression 
    | 'available' expression 
    | 'filledBy' expression 
    | 'fills' expression
binaryExpr = 
    ‘filter’ expression ‘with’ expression 
    | expression operator expression
operator = 
    '>>' | '==' | '<' | '>' | '<=' | '>=' 
    | 'and' | 'or' 
  | 'union' | 'intersection' | 'orElse'
    | '+' | '-' | '*' | '/' 
    | 'filledBy' | 'fills'
letE = ‘letE’ binding+ ‘in’ expression
binding = variable ‘<-‘ expression

Operator Precedence

Operator precedence is ruled by weights for each operator. Parentheses can be used to override these precedences. An operator with higher precedence prevails of an operator with lower precedence. Hence,

not SomeBoolean and not AnotherBoolean

will be interpreted as:

not (SomeBoolean and not AnotherBoolean)

which is usually not what you expect. instead, write:

(not SomeBoolean) and not AnotherBoolean
Operator Precedence
>> 9
union, intersection, orElse 8
>>= 7
* 6
/ 5
+, - 4
==, >, <. <=, >= 3
filledBy as infix operator 3
matches 3
and, or 2
not, exists, available, filledBy, fills 1
filter 0

Some expression examples:

SomeContextRole >> binding >> context >> AnotherRole

SomeRole >>= sum

SomeRole >>= sum / SomeRole >> count

not (ABooleanProperty and AnotherBooleanProperty)

filter SomeRole with not exists APropertyOnThatRole

filter SomeRole >> binding with BooleanPropertyOnTheBinding

filter SomeRole with filledBy AnotherRole

filter SomeRole with not fills sys:Me

ThisRole >> Date == ThatRole >> Date

SomeNumericProperty * 2

letA
  a <- SomeRole >> binding
  b <- AnotherRole >> binding
in
  unbind a from SomeRole
  unbind b from AnotherRole
  bind a in AnotherRole
  bind b in SomeRole

Some operators explained

Most operators speak for themselves; however, we describe a few less intuitive ones.

>> (compose)

The operator >> is used to compose a path. We also call a path a query, as it computes context- or role instances from a starting point (always a context or a role: see below). A path/query thus is a function. The term compose derives from the fact that role and property names and a number of standard functions such as context (move from a role instance to its context) can be composed into larger functions with >>. For example:

|binding >> context >> SomeRole

selects from its starting point - which must be a context role - its binding, then the context of that binding and then all instances of the type SomeRole

union and intersection

A union B means: the union of the results of queries A and B, while A intersection B means: just return those values that are both an A, and a B. Equivalently, A both B means the instances that are in the intersection of the results of applying A and applying B.

orElse

A orElse B means: the result of A, if any. However, when A results in an empty sequence, return the results of B (notice that these may be empty, too, in which case the result of the entire expression is the empty sequence).

>>= (sequence)

The sequence operator >>= is used to apply a function to all of the results of a query. It should be followed by a function name: SomeRole >> SomeNumericProperty >>= sum will result in a single number summing all values of the property SomeNumericProperty over all instances of SomeRole. Other functions are product, minimum, maximum, count and first.

exists and available

The exists operator takes, like not, just a single argument.

exists binding

yields a Boolean value indicating whether the role instance that this expression is applied to, has, in fact, a binding (is filled).

available tests whether the role instances that it is applied to, are, in fact, available in the storage locations used by the PDR. The difference is subtle. The PDR operates on the assumption that it can exchange any instance identifier for the actual instance, any time. available works around that assumption: you can test whether it holds for the given identifiers. This is rather an edge case.

filledBy and fills

A filledBy B means that A is filled with B, or, perhaps confusingly, B fills A. The binding of A is B. A fills B means the inverse: it is true when B is filled with A: in other words, if the binding of B is A (A fills B).

Both filledBy and fills can be used as a binary operator, and as a unary operator (what we might call a predicate).

  filter SomeRole with filledBy sys:Me

The above expression returns all instances of SomeRole that are (ultimately) filled by sys:Me (that is, for each PDR installation, the 'own' user). Here filledBy is used as a predicate.

In contrast, in

filter Repositories with IsPublic or binding >> context >> Admin filledBy currentsubject

filledBy is used as a binary operator: the left side of the expression is binding >> context >> Admin, while the right side is just currentsubject.

this

this is the identity function: it just returns the value that is passed as argument. The following expression is always true:

letE
    startingpoint <- this
in
    startingpoint == origin

<, >, <=, => (inequalities)

These comparison operators are defined on Strings, Numbers, Booleans and DateTimes.

binding in <context type>

Sometimes, one wants to make sure a query moves to fillers that originate in a specific type of context. A likely case is one where the restriction on a filler of a particular role type has been expressed in terms of a role type that is used as an Aspect. Let's say we require a Driver and that this role type has been used as an aspect of roles in both a Boat and a Plane context. The first would be a Captain and the second a Pilot. Now we want to make sure that in our query, when we move from a role to its filler, we only end up with Pilots. We would then say:

binding in Plane

The resulting sequence would hold role instances of Pilot only.

Operator precedence explained

Let's examine a familiar example. The expression

1 + 2 * 3

should be understood as

1 + (2 * 3)

yielding 7, instead of

(1 + 2) * 3

yielding 9. The simple rule is: at any moment, group operands around the operator with the highest precedence first.

What if there are ties?

1 + 2 + 3

can be understood in two ways:

(1 + 2) + 3
1 + (2 + 3)

Now, for addition, it does not matter. Not so for subtraction:

3 - 2 - 1

can be

(3 - 2) - 1 = 0

or as

3 - (2 - 1) = 2

So, order matters. In Perspectives, we choose the second grouping. It is called right-associative: we associate terms from the right to the left.

Now for a Perspectives Language example:

filter Repositories with IsPublic or binding >> context >> Admin filledBy currentsubject

The operators in this example, with their precedence, are:

operator precedence
filter ... with 0
or 2
>> 9
filledBy 3

Starting with the >> operator (and applying right associativity) we get:

filter Repositories with IsPublic or (binding >> (context >> Admin)) filledBy currentsubject

the next powerful operator is filledBy:

filter Repositories with IsPublic or ((binding >> (context >> Admin)) filledBy currentsubject)

then comes or:

filter Repositories with (IsPublic or ((binding >> (context >> Admin)) filledBy currentsubject))

and then we're done, as we've only got filter left. Filter is a bit difficult to see. It may be easier if you rewrite it like this:

Repositories filterwith (IsPublic or ((binding >> (context >> Admin)) filledBy currentsubject))

Let's examine another case:

filter context >> Versions with VersionNumber == origin >> LastVersion >> Url

Operator == has precedence 3. First we apply >>:

filter (context >> Versions) with VersionNumber == (origin >> (LastVersion >> Url))

then comes ==:

filter (context >> Versions) with (VersionNumber == (origin >> (LastVersion >> Url)))

and we're done. However, now we have an expression that tries to compare a VersionNumber to a Url. Without knowing the definitions for these properties, we can guess that this is wrong and indeed it is. We need to add parenthesis to the original expression:

(filter context >> Versions with VersionNumber == origin >> LastVersion) >> Url

Now we get:

(filter (context >> Versions) with (VersionNumber == (origin >> LastVersion))) >> Url

so we take the Url of the filtered Versions (the one with the latest version number).

Literal values

Strings, Numbers, Booleans and DateTime values are written as follows:

Value expression Explanation
"a string" A string should be enclosed in quotes. Any character can be inside a string, that is not a double quote, a backslash or an ampersand. Escape character sequences are allowed, too, but they are not documented here. We refer to the source code of Text.Parsing.Parser.Token
true, false These are the entire population of the Boolean type.
123 Number representation has yet to be decided on. Right now only integers can be used.
'2019-10-04', '2011-10-10T14:48:00' Date and DateTime expressions are parsed by Javascript Date.parse(), enclosed in single quotes. See Date Time String Format for more details.

Public instance literals

To a limited extent, it is possible to include role- or context instances as literals in a model text:

Value expression Explanation
publicrole pub:someUrl#someIdentifier A way to write a literal role instance value (exclusively for public roles).
publiccontext pub:someUrl#someIdentifier A way to write a literal context instance value (exclusively for public contexts).

This allows us to refer in a model to public resources. The original use case was when we needed to introduce the base repository for perspectives.domains in the System model. A repository is described by a context instance; we needed to have that base repository present in every installation (for other reasons, this is no longer the way we introduce that repository in an installation).

Indexed individuals

Another way to refer to a role- or context instance in a model text is by way of them being indexed.

A modeller can declare a type to be indexed. Such a type can only have a single instance, but these instances may be unique for each user. In order to reference them in a model text irrespective of the specific installation (or user), one uses the indexed name. MySystem is an example. Actually, an indexed name should be fully qualified. The usual practice is to use prefixes, as in sys:MySystem. Even though any context- or role type may be declared indexed, the modeller is adviced to restrict this practice. E.g. a root context (usually an 'app' or the starting context of a particular domain), that need not itself be embedded in another context, should be declared indexed (so it can be reached through its indexed name).

An indexed individual can simply be referred to in a model text by just using its name (an indexed type name by itself is an expression that yields its unique instance for each installation).

Instances calculated from an expression

Finally, we have a way to refer to a role- or context individual by way of an arbitray String-valued expression.

contextinstance (QualifiedContextType) SomeProperty

returns the context instance of type QualifiedContextType that is identified by the String value that is the result of the expression SomeProperty. We need to provide the type of the instance in the model text; otherwise the compiler cannot perform its checks.

IMPORTANT: the context type must be qualified (possibly with a prefix). The same holds for the role type.

Similarly we have roleinstance.

Use of expressions

Expressions occur in a few places in a model expressed in the Perspectives Language:

An expression can be thought of as a function. In the paragraphs below we explain what these functions are applied to. This follows from lexical analysis of a Perspectives source text, so we first explain a number of concepts used in this analysis. We also introduce four standard variables that can be used inside expressions.

Type level queries

Expressions have instance values. That is, they represent collections of instances (role- or context) in runtime. Type reflection is a powerful mechanism in a language. The Perspectives Language has limited support for type level queries. We have queries that have a collection of role types as result (we call the type of such a query RoleKind). Similarly, we have queries that have a collection of context types as result (RoleKind).

The operators role and context

The operator role allows us to switch a query to the type level.

[role model://perspectives.domains#System$Invitation$Invitee]

is a query whose result is the type Invitee. It is a RoleKind-valued query. The operator context works similarly.

NOTE: Do not confuse this use of context with the function context that takes a role instance as input and produces its context instance as output! The square brackets are essential to make the difference

The predicate specialisesRoleType

This operator allows us to filter a collection of types by just keeping types that specialise a certain type, like in this fragment taken from the System model:

filter origin >> context >> contextType >> roleTypes with specialisesRoleType model://perspectives.domains#System$Invitation$Invitee)

Notice that specialisesRoleType assumes its literal argument is on the type level.

The indexedName keyword

Related to the above is the following type reflection. Whereas an indexed name in a model text is a way to include a reference to a role- or context instance, we can also work in the other direction by requesting the string value of the chosen indexed name from an instance. The keyword: indexedName is by itself an expression step. Given an instance, it returns the indexed name for its type (if any exists). For example:

  sys:Me >> context >> indexedName

returns "model://perspectives.domains#System$MySystem". It is a String-valued expression, but one that reflects a type level constant.

Current state

At each point in the source text of a model (at each lexical position) some kind of state holds. Here is how current state is determined:

  1. A context definition sets the current state to the Root state of that context (context state).
  2. A role definition sets the current state to the Root state of that role (subject state for user roles, object state for other roles ).
  3. A state definition sets the current state for its body. Obviously, for a state of a context type, the current state will be context state; for a user role it will be subject state and for any other role it will be object state.
  4. The in state X clause sets the current state to its substate X. When a state type (in object state, in subject state, in context state) is specified, the current state is set to the substate X of the current object, current subject or current context respectively.
  5. The on entry and on exit clauses do by themselves not change the current state, but specify a state transition for the current state. When of and a state name is specified, the state transition is for the substate of the current state. Alternatively, you may think that state name changes the current state for the lexical position of on entry X and on exit X to substate X of the current state.
  6. The on entry and on exit clauses can be augmented with three parts:
    • of object state, optionally extended with a state name. It sets the current state to the Root state (or the named state) of the current object;
    • of subject state, idem, for the current subject;
    • of context state, idem, for the current context.

See the document Understanding a Perspective Model Text for an in-depth treatment of this subject.

Current context, current object, current subject

In the lexical analysis of a Perspectives source text, we use three concepts.

A context type is the current context in its body, up till a nested context type.

The bodies of the following constructs define the current object:

Finally, the current subject holds in the bodies of:

standard variables

The previous paragraph introduced concepts for lexical analysis. Related, but not similar, are four standard variables that may be used inside expressions without providing a value for them:

Standard variable Value in runtime
currentcontext Is bound to an instance of the lexical current context type.
origin Is bound to the context or role instance that the expression is applied to.
currentactor Is bound to the user role instance for whom an action is executed automatically or who executes the action by hand
notifieduser Is bound to the user role instance who is notified.

What expressions are applied to

We now have the conceptual tools to explain what expressions are applied to. The standard variables currentcontext and origin can be used in every expression. The table shows the usage of the other two.

Expressions in Applied to standard variables other than origin and currentcontext
The definition of a calculated role Current context
The definition of a calculated property Current object
The condition of a context state Current context
The condition of a role state Current object
The object of a perspective Current context
Expressions in do The resource whose state is transitioned into or out of currentactor
Expressions in notify The resource whose state is transitioned into or out of notifieduser
Expressions in action in a lexical position with a current object Current object currentactor
Expressions in action otherwise Current context currentactor

Screen

A screen is a lexical construct to describe a screen as it should be generated in runtime. In essence, a screen is a series of tabs where each tab is composed of widgets that can be arranged in rows and columns. Currently we have three widgets: the form (for a functional role), the table (for a relational or multi-role) and a markdown widget.

It is up to you whether you start your tab with rows or with columns. However, columns should be embedded in rows and rows in columns: you cannot have rows in rows or columns in columns. For example:

    user Tester filledBy sys:PerspectivesSystem$User
      indexed com:Tester
      perspective on TestRole
        props (Text, Bool, ADateTime, ANumber) verbs (Consult, SetPropertyValue)
        only (Remove, Create)
      perspective on TestTable
        defaults
      screen "Test screen"
        tab "Test"
          row
            column
              form TestRole
                props (Text, WeekDay) verbs (Consult)
                only (Create)
              table "MyTable" TestTable
                view Limited verbs (Consult)
                only (Remove)
        tab "Another tab" default
          row
            ...

This model fragment gives us a user role Tester with a perspective on two roles. It also describes a screen for Tester, for those perspectives. Notice that a screen must be described within a user role definition and it is just for that user role. The screen has a single tab. It arranges two widgets below each other in a column. Somewhat superfluosly it puts that column in a single row.

Notice how for both widgets the fragment is quite detailed as to

As a matter of fact, one can use the very same syntax used for describing these details for a perspective, in a widget.

Also notice the second tab with the marker "Another tab". It has the default keyword and this signifies that on opening the screen, this tab should be opened by default. Without a default tab, none of them will be openend.

The compiler checks the following constraints concerning a screen specification:

The verb- and property specification of a screen may only serve to further restrict the possibilities given by the perspective a user has on the role that underlies the widet.

MarkDown

The markdown construct is for rendering MarkDown source as HTML (the source should conform to the Common Mark standard). There are three lexical variants.

MarkDown literal

      screen "Embedded (constant) MarkDown"
        row
          markdown <## Markdown! 
                    The source of this text is included in the Perspectives model.
                   >

The first form consists of a MarkDown literal value in the model. It should be enclosed between '<' and '>' angular brackets. It is rendered on screen without any frills. The rendering may be conditional as in the next example:

      screen "Some Conditional MarkDown"
        row
          markdown <## Markdown! 
                    The source of this text is included in the Perspectives model.
                   >
            when ARole >> ShowIt
  thing ARole
    property ShowIt (Boolean)

Here, the MarkDown text is rendered only when the property ShowIt of ARole evaluates to true. Notice that the condition is evaluated with respect to the current context.

MarkDown expression

Instead of embedding a MarkDown string in the model, we can retrieve it from a property with the range MarkDown:

      screen "MarkDown Expression"
        row
          markdown ARole >> MD
            when ARole >> ShowIt
  thing ARole
    property MD (MarkDown)
    property ShowIt (Boolean)

This allows a user with a sufficient perspective to enter MarkDown text in a form. A user with a Consult perspective on the property MD will just see the rendered MarkDown.

MarkDown perspective

Notice that, with the MarkDown expression, in order to be able to edit it, the user must have a defined perspective whose object is the role that has the MarkDown property (and at least SetPropertyValue should be specified in the verbs section for that property).

A more straightforward form is this:

      screen "MarkDown Perspective"
        row
            markdown ARole
              props (MD) verbs (Consult, SetPropertyValue)
  thing ARole
    property MD (MarkDown)

Here, we clearly tie the markdown component to a specific role and property and we set the verbs specifically for this rendering. However, we still have to provide a sufficient perspective for the user role for this to work.

MarkDown extensions for Perspectives

We have defined two extensions that can be used in the markdown expressions. The first is the link that allows the end user to navigate from the page displaying the rendered MarkDown to another context:

A link allows the end user to move to <<link: def:#re61moqm93|move to another context>>.

Here, the string "def:#re61moqm93" is the identifier of a context role or of an embedded context role. One obtains such an identifier by clicking the card that represents the role and pressing ctrl-c. It can then simply be pasted into the markdown text.

The second is an action. Actions can be defined for a user in a context, or for a user on a particular role type. A so-called context action will be applied to the context instance that is displayed on screen; a role action will be applied to the selected role instance. We currently only support context actions in MarkDown. Here is an example:

Press the button to create a new instance of the role Building <<action: CreateBuilding|New Building>>.

Notice that the action must already be modelled in the users' perspective! The markdown instruction above does not create it, it merely provides a complementary way of invoking it from within the readable text that is rendered from the MarkDown source text. The name of the action (like CreateBuilding in the example) should be exactly the name as it appears in the model.


Copyright 2019-2021 Joop Ringelberg and Cor Baars