File Metadata
- Created
- Fri, Mar 13, 7:21 PM
Domain specific languages
DSL allows to express various concepts in very lapidary and concise form. Xreate recognizes and acknowledges very successful and beneficial DSL usage in certain areas, primarily to express queries and configs, to name a few.
Taking this into account Xreate offers DSL concept by employing interpretation.
Interpretation
Interpretation stands for pre evaluation of program parts that are known at compile time.
main= function ::int;entry
{
x= "a"::string.
y= if (x=="b")::string; interpretation(force)
{1}
else {0}.
y
}In this example, identifier y has annotation interpretation(force) to enable compile-time interpretation for y.
Consequenlty, function result is 0 with neither memory allocations for string variable x nor any computation at runtime.
There are two annotations reserved to interfere with interpretation process:
| interpretation(force) | Force compiler to apply interpretation for annotated expression, function, function argument |
| interpretation(suppress) | Disable interpretation for annotated expression |
Eligible Operations
| Atomic instructions | numbers, strings, identifiers |
| Relational and logic operators | e.g. x=="true", x!=0, operator and |
| if, switch statements | Statement expansion allowed |
| loops statements | Statement expansion allowed |
| call statement | Function calls, Partial function call interpretation |
| index operator | e.g. x= [1, 2, 3]. y= x[0], info= {planet="Earth"}. x= info["planet"] |
| list operators | e.g. x= [1..10], x=["day", "night"] |
Expansion
Expansion stands for partial simplification or elimination of interpretable parts of certain statements.
test = function(x:: int) :: int; entry {
comm= "inc":: string; interpretation(force).
y= if (comm == "inc") {x + 1} else {x}
y
}In this example, computation of y depends on comm and x. Annotated identifier comm should be interpretated but identifier x is unknown at compile-time.
Thus, in order to satisfy opposite requierments expansion is enabled, i.e. due to possibility of condition interpretation, statement if is expanded to a just one of the child blocks,
x+1 in this example.
Below more complex example of loop expansion:
main = function(x:: int):: int; entry {
commands = ["inc", "double", "dec"]:: [string]; interpretation(force).
loop fold(commands->comm::string, x->operand):: int{
switch(comm):: int
case ("inc"){
operand + 1
}
case ("dec"){
operand - 1
}
case ("double"){
operand * 2
}
}
}We force compiler to interpret variable commands. Consequenlty, compiler has no choice but to expand loop in order to get rid of commands. Result could be presented as:
main = function(x:: int):: int; entry {
x(1) = x + 1.
x(2) = x(1) * 2.
x(3) = x(2) - 1.
x(3)
}In other words, this mimics famous loop unrolling technique by putting several copies of the loop body in a row, each one for every item of a list of commands.
Function call interpretation
Whole function body could be subject of interpretation.
unwrap= function(data:: undef, keys:: undef):: undef; interpretation(force){
loop fold(keys->key:: string, data->a):: undef {
a[key]
}
}
start= function::num; entry{
result= unwrap(
{
a = {
b =
{
c = "needle"
}
}
}, ["a", "b", "c"])::undef.
result == "needle"
}Here, compiler is informed by respective annotation to interpret unwrap. Moreover, all arguments of unwrap are fully defined at compile-time and compiler simply replaces function call by calculated value.
In simple cases interpretation analysis could determine that function is subject of interpretation on its own with no annotation hints provided.
unwrap= function(data:: undef, keys:: undef):: undef {
loop fold(keys->key:: string, data->a):: undef {
a[key]
}
}
start= function::num; entry{
result= unwrap(
{
a = {
b =
{
c = "needle"
}
}
}, ["a", "b", "c"])::undef; interpretation(force).
result == "needle"
}Only difference from example above is lack of annotation hint for unwrap. Developer requires interpretation of result and compiler accepts such code since analysis find out
that unwrap is possible to interpret.
There are, hovewer, more compilcated cases for analysis:
| Direct recursion | Analysis is able to correctly find out whether function involving direct recursion(in which a function calls itself) is subject to interpretation |
| Indirect recursion | As of now for processing of indirect recursion( in which a function is called not by itself but by another function) analysis rely on manually provided annotation hints |
Partial function call interpretation
More compilcated case is when some of the function arguments are subject to interpretation and other to a compilation - partial function interpretation.
evaluate= function(argument:: num, code:: string; interpretation(force)):: num {
switch(code)::int
case ("inc") {argument + 1}
case ("dec") {argument - 1}
case ("double") {argument * 2}
case default {argument}
}
main = function:: int; entry {
commands= ["inc", "double", "dec"]:: [string]; interpretation(force).
loop fold(commands->comm::string, 10->operand):: int{
evaluate(operand, comm)
}
}Here function evaluate is subject to a partial interpretation. To enable partial interpretation for function some of its arguments should be annotated with interpretation(force).
Compiler finds all the distinctive interpretable arguments combinations the given function called with and produces function specializations, each for every finded distinctive arguments combination.
For this example three different evaluate specializations are produced as below:
evaluate1= function(argument:: num):: num {
argument + 1
}
evaluate2= function(argument:: num):: num {
argument * 2
}
evaluate3= function(argument:: num):: num {
argument - 1
}
main= function:: int; entry {
operand= 10:: int.
operand(1)= evaluate1(operand).
operand(2)= evaluate2(operand(1)).
operand(3)= evaluate3(operand(2)).
operand(3)
}TODO: find correct term in supercomilation for partial interpretation
TODO: distinctive combinations w.r.t. equivalence relation
TODO: mention oppostion between cloning/ additional arguments
On interpretation analysis
Analysis follows classical type reconstruction algorithms to determine what expressions are subject to an interpretation and check correctness of reconstruction w.r.t. developer-provided annotations. Analysis consists of two general parts:
| Inference | Infers is it possible to interpret expression based on known decisions for its arguments |
| Unification | Assigns appropriate decision w.r.t. to an previously inferred expectations and developer-provided annotations |