dsl.remarkup
No OneTemporary

File Metadata

Created
Fri, Mar 13, 7:21 PM

dsl.remarkup

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 instructionsnumbers, strings, identifiers
Relational and logic operatorse.g. x=="true", x!=0, operator and
if, switch statementsStatement expansion allowed
loops statementsStatement expansion allowed
call statementFunction calls, Partial function call interpretation
index operatore.g. x= [1, 2, 3]. y= x[0], info= {planet="Earth"}. x= info["planet"]
list operatorse.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.

IMPORTANT: As of now, expansion defined for if, switch, loop fold statements and for functions.

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.

NOTE: In example, an arbitrary undefined type undef is used in order to be 100% sure that no compilation occurs, while interpretation pays no attention to a types for now.

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 recursionAnalysis is able to correctly find out whether function involving direct recursion(in which a function calls itself) is subject to interpretation
Indirect recursionAs 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:

InferenceInfers is it possible to interpret expression based on known decisions for its arguments
UnificationAssigns appropriate decision w.r.t. to an previously inferred expectations and developer-provided annotations

Event Timeline