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 ofx
, 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
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 ...