Kompilator - odsładzanie

29 Stycznia 2019

Odsładzanie (z ang. desugaring) to proces przetwarzania abstrakcyjnego drzewa składni, do pewnej prostszej (logicznie) formy, który być może w kodzie zajmowałaby za dużo miejsca.

Przykładami takich elementów języków programowania są np. pętle foreach.

foreach(var v in list)      var e = list.getEnumerator()
    doSomething(v);    <=>  while(e.MoveNext()) {
                                var v = e.Current;
                                doSomething(v);
                            }

Innym przykładem będzie lock (lub synchronized)

                  var temp = obj;
lock(obj) {       Monitor.Enter(temp);
    work();  <=>  try { work(); }
}                 finally { Monitor.Exit(temp); }

Czyli dajemy użytkownikom naszego języka pewną składnię, która ma tę samą siłę wyrazu co składnia bazowa, ale pozwala na pisanie mniejszej ilości kodu.

W moim kompilatorze języka Latte w module odsładzania przechodzę z drzewa AST wygenerowanego przez parser do drzewa AST napisanego przeze mnie. Miedzy innymi

if(cond)       if(cond)
    stmt;  =>     stmt;
               else ;

x++;       =>   x = x + 1;
x--;       =>   x = x - 1;

for(var x : array)    int n__x = 0;
    stmt;          => var a__x = array;
                      while(n__x < a__x.length) {
                          var x = a__x[n__x];
                          stmt;
                          n__x = n__x + 1;
                      }

Pozbywam się też różnych wariantów związanych z kolejnością działań algebraicznych na rzecz jednego BinaryOp.

Na etapie desugaringu przekształcam też znaki specjalne w stringach, czyli jak widzę dwa znaki \\n to wstawiam tam jeden znak LF. Podobnie dla \\t,\\\\,\\',\\" oraz \\ddd, gdzie d to cyfra.

Żeby użytkownik otrzymywał dobre informacje o błędach, to odsładzanie należy wykonać po sprawdzeniu typów. U mnie niestety dzieje się to odwrotnie i możemy znaleźć się w sytuacji, gdzie dostajemy dziwny błąd który ma sens w kontekście odsłodzonego kodu, ale nie bardzo w kontekście kodu użytkownika.

W szczególności

string s;
s++;

Przekształci się na

string s;
s = s + 1;

Co podczas sprawdzania typów przekształci się na

string s;
s = s.concat(intToString(1));

Co po propagacji stałych to

string s = null;
s = (null).concat(intToString(1));

A następnie użytkownik dostanie błąd

ERROR:
Expression is always null
    s++;
    ^ I nie jest super oczywiste o co chodzi.