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.