Seikkailuja editorimaassa



Tilapäisessä mielenhäiriössä aloin taas toteuttaa pientä kirjastoa
C:llä. Ajattelin kokeilla samalla (olihan kyseessä puhdas
harrasteprojekti, ja näissä on hyvä aina kokeilla) namespace-ratkaisua
nimitörmäysten välttämiseksi.

No, aloin kirjoittaa:

#ifndef G
#define G(symbol) pieni_ ## symbol
#endif

typedef struct G(foobar) *G(foobar_t);
                         typedef ...

Ja cc-moden autoindent hajosi pyytämättä ja yllättäen jo viidennellä
rivillä.

Mitäs tässä tapahtui? No, C:n preprosessori mahdollistaa tietysti
kaikennäköisen huonosti motivoidun temppuilun, mutta eroon siitä ei
enää päästä, ja C:tä indentoivan järjestelmän on jollakin tavalla
elettävä preprosessorin olemassaolon kanssa. Vaikkakin se tekee
täydellisestä syntaktisesta analyysista vähintäänkin hyvin vaikeaa,
ehkä jopa mahdotonta (jos kaikkia relevantteja headereita ei
esimerkiksi pystytä jostakin syystä löytämään).

Tapaus on erityistapaus yleisemmästä ongelmaluokasta. Joskus ongelmaa
ei voida ratkaista täydellisesti, ja täydellisyyden yrittäminen johtaa
siihen, että ero toteutuneen ja tavoitetilan välillä ärsyttää
käyttäjää erityisesti. Voi olla kaikkien kannalta parempi etsiä
paikallinen optimi huomattavan yksinkertaistuksen keinoin.

Mietin tapausta C:n sisennys. Jos unohdetaan jäsentäminen C:n
sääntöjen mukaan, ja keksitään oma, yksinkertaisempi, täydellisesti
jäsennettävissä oleva syntaksi, josta C on vain
erityistapaus. (Lisäbonuksena tämän syntaksin erityistapauksia ovat
C++, Java ja Javascript, puhumattakaan sadoista vähemmän tunnetuista
kielistä.)

kieli ::= lauseke*
lauseke ::= atomi | "{" lauseke "}" | "(" lauseke ")" | "[" lauseke "]"
atomi ::= [^{}()[]] | merkkijono
merkkijono ::= """ [^"]* """

Ohjelmointikielen sisällön tulkinnan kannalta tämä syntaksi on
hyödytön, mutta emme tulkitse sisältöä, vaan sisennämme.

Jokaista riviä sisentäessä katsomme edellisen ei-tyhjän rivin
ensimmäistä ei-whitespacemerkkiä. Selvitetään siitä kuinka monien
sulkujen sisällä se on, ja monenteenko sarakkeeseen se on sisennetty.
Seuraavaksi katsomme monienko sulkujen sisällä sisennettävän rivin
ensimmäinen ei-whitespacemerkki on. Lisätään edellisen rivin
sisennyssarakkeeseen niin monta sisennysaskelta kuin sulkutasoilla on
eroa.

int foo(int x) {
    bartholomew(exp(1/(x+1), 2)*10*exp(1-(1/(x+1)), 2),
        + exp(1-(1/(x+1)), 2));
    while (quux(x + really_long,
            other_parameter)) {
        repeat_this(x);
    }
    return x;
}

Pieniä kauneusvirheitä. Varsinainen haitta on funktiomäärittelyn
parametrilistan jatkorivien sisennys samalle tasolle kuin funktion
leipäteksti. Parametrilista olisi parempi sisennettynä enemmän, jotta
nämä erottaisi toisistaan.

int quux(int x,
    int y) {
    return x + y;
}

Tämä voidaan korjata lisäämällä {:sta aina yksi taso, mutta (:sta ja
[:sta ensin kaksi tasoa ja seuraavasta samasta avaavasta merkistä vain
yksi taso (Eli (( tuottaa kolme tasoa ja ({( tuottaa viisi.)

Nyt esimerkit sisentyvät näin.

int foo(int x) {
    bartholomew(exp(1/(x+1), 2)*10*exp(1-(1/(x+1)), 2),
            + exp(1-(1/(x+1)), 2));
    while (quux(x + really_long,
                other_parameter)) {
        repeat_this(x);
    }
    return x;
}

int quux(int x,
        int y) {
    return x + y;
}

Tätä voidaan edelleen korjata huomioimalla, että jos edellisen rivin
viimeinen efektiivinen avaava sulje (eli jos rivi loppuu a(b()c,
viimeinen efektiivinen avaava sulje on tuo stringin ensimmäinen sulje)
ei ole rivin viimeinen ei-whitespacemerkki, sisennetään suoraan tuon
sulkeen jälkeiseen sarakkeeseen, eikä lasketakaan sisennyksiä
askeleina. Nyt saadaan:

int foo(int x) {
    bartholomew(exp(1/(x+1), 2)*10*exp(1-(1/(x+1)), 2),
                + exp(1-(1/(x+1)), 2));
    while (quux(x + really_long,
                other_parameter)) {
        repeat_this(x);
    }
    return x;
}

int quux(int x,
         int y) {
    return x + y;
}

Ylläesitettyä syntaksia ei varsinaisesti jäsennetä missään välissä,
sisennys perustuu vain sulkulaskentaan. Vain kommentit ja stringit
pitää jäsentää oikein. Kommenttien ja stringien rajoja ei voi edes
sotkea C-preprosessorilla, joten tämä ei palauta meille alkuperäistä
ongelmaa, jota lähdettiin korjaamaan.

C-like-mode:n jatkokehitys jatkuu. Älkää pidättäkö hengitystänne.

Tämän artikkelin on kirjoittanut Teemu Kalvas ja sitä ovat sittemmin muokanneet muut Codenton työntekijät.

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *