1. 程式人生 > >LIRQ — Language Integrated Reflection Queries

LIRQ — Language Integrated Reflection Queries

What is LIRQ?

Queries can yield the following entities:

  • identifiers,
  • local variables,
  • function parameters,
  • arrays,
  • collections,
  • fields of classes,
  • classes,
  • instances of classes,
  • interfaces,
  • enumerations, and
  • functions (methods).

A query consists of one or more conditions written within curly braces and separated by Boolean operations:

  • comma , for conjunction
  • vertical bar | for disjunction
  • exclamation mark ! for negation.

In this post, I will assume that queries are embedded into Java/C#, though the concept itself does not depend on a particular language.

Value, name and type queries

For an identifier x:

  • query { &x } yields its value,
  • query { @x } yields string “x”, and
  • query { #x } yields type of x , which can be used in declarations:
int x;{ #x } y; // int y;

Primitive queries

For each entity mentioned above, there is a corresponding query ({var}, {class}, {field}, and so on) that yields all such entities. For example, query {var} will yield all

variables. To yield a non-empty result, a query should contain at least one primitive condition.

Regular expressions for names

Query {'v*'} yields all identifiers with names starting with symbol “v”. Queries can be used in qualified names, too:

person.{'a?e'}

Type constraints

Query {var, of type int} yields all local integer variables. It can be used in an assignment statement:

{var, of type int} = 0;

Constraints

Query

{ var, of type int, (that >= 0 | that <= 10) }

yields all local integer variables whose value is in range 0..10.

Keyword that refers to an yielded result of a query. Negation can also be used within that expressions.

Queries &that , @that and #that are considered primitive queries. For example, {var, @that} yields a collection of names of all variables.

Query variables

Query

{ var <T>, of type int, ( <T> >= 0 | <T> <= 10 ) }

is equivalent to query

{ var, of type int, (that >= 0 | that <= 10) }

given above, but uses a query variable T that refers to yielded result. Variable names are enclosed in angle brackets (remark: this syntax has nothing to do with generics).

Query variables can also be used for types and essentially all other entities, for example:

{var <X>, of type <Y>, <Y> is subtype of int}

Functions

Query {function <F>() returns <R>} yields all functions without arguments visible in the current scope.

Desired parameters can be requested by using regular expressions-like syntax:

  • ? denotes any parameter,
  • * denotes 0 or more parameters,
  • + denotes 1 or more parameters,
  • int denotes an integer parameter, and so on.

Query {function <F>(?, int, *) returns string} yields all string functions whose second argument is of type integer.

Qualifiers

Query {class <T>, <T> extends MyClass} yields all classes that extend MyClass.

Part ... extends ... of this query is called a qualifier. Other qualifiers include:

  • is abstract
  • is static
  • ... implements ...
  • ... inherits ...
  • is subtype of ...
  • has ... (used to specify that a class has a certain field or method),
  • and so on.

Declared entities

Qualifier declared ... allows distinguishing between an yielded result and a condition in a query. For example, query

{class <B>, <B> extends <A>, class <A>}

is invalid because it has two primitive conditions ( class <B> and class <A>). However, query

{class <B>, <B> extends <A>, declared class <A>}

is valid and yields all subclasses of all classes.

Instances

Statement

{instance of Person}.age = 0;

assigns value 0 to field age of all instances of class Person. Depending on how semantics of queries is defined, instances may either refer to all declared instances of a class or to all instances existing during runtime.

Loops

Queries can be used in for-each loops:

for x in {var x, of type int} {  x = 0;}

Scopes

Query {field, of type int, in declared class MyClass} yields all integer fields in class MyClass. Keyword declared can be omitted in in conditions.

Query {in function(int, int) returns <R>, var} yields all local variables in all functions (from the current scope) with two integer arguments.

Nested queries

Query

{in {function(int, int) returns <R>, in class MyClass}, var}

differs from the previous one in that it only considers methods of class MyClass.

Visibility modifiers

Queries can be used to define custom visibility modifiers.

class A {   modifier children = {class <T>, <T> extends A};   [children] int x; // only visible in subclasses of A   ...