Jak zwykle parsowane są komentarze?

31

Jak ogólnie traktuje się komentarze w językach programowania i znacznikach? Piszę parser dla jakiegoś niestandardowego języka znaczników i chcę przestrzegać zasady najmniejszego zaskoczenia , więc staram się ustalić ogólną konwencję.

Na przykład, czy komentarz osadzony w tokenie powinien „zakłócać” token, czy nie? Zasadniczo jest coś takiego:

Sys/* comment */tem.out.println()

ważny?

Ponadto, jeśli język jest wrażliwy na nowe wiersze, a komentarz obejmuje nowy wiersz, czy należy wziąć pod uwagę nowy wiersz, czy nie?

stuff stuff /* this is comment
this is still comment */more stuff 

być traktowanym jak

stuff stuff more stuff

lub

stuff stuff
more stuff

?

Wiem, co robi kilka konkretnych języków, nie szukam też opinii, ale szukam odpowiedzi na pytanie: czy istnieje ogólny konsensus, czego można się spodziewać po narzutach i nowych liniach?


Mój szczególny kontekst to znaczniki typu wiki.

Sanki
źródło
Czy nowa linia istnieje w komentarzu? Dlaczego miałoby być traktowane inaczej niż jakakolwiek inna postać w komentarzu?
1
@Snowman istnieje taka perspektywa, ale z drugiej strony, jeśli token „x” ma specjalne znaczenie, jeśli jest to pierwszy token w linii i wydaje się, że jest to pierwszy token w linii zarówno dla osoby patrzącej na źródło, jak i na odczyt parsera linia po linii. Wydaje się, że to dylemat, więc zadałem pytanie.
Sled
4
Musiałem zrobić to dokładnie zgodnie ze specyfikacją jakiś czas temu i uznałem dokumentację gcc za doskonały zasób. Istnieje kilka dziwnych przypadków narożnych, których mogłeś nie wziąć pod uwagę.
Karl Bielefeldt

Odpowiedzi:

40

Zazwyczaj komentarze są skanowane (i odrzucane) w ramach procesu tokenizacji, ale przed analizą. Komentarz działa jak separator tokenów, nawet jeśli nie ma wokół niego białych znaków.

Jak zauważyłeś, specyfikacja C wyraźnie stwierdza, że ​​komentarze są zastępowane pojedynczą spacją. Jest to jednak tylko specyfikacja, ponieważ parser w świecie rzeczywistym niczego nie zastąpi, a jedynie skanuje i odrzuca komentarz w taki sam sposób, jak skanuje i odrzuca białe znaki. Ale wyjaśnia w prosty sposób, że komentarz rozdziela tokeny w taki sam sposób, jak spacja.

Treść komentarzy jest ignorowana, więc łamanie wiersza w komentarzach wielowierszowych nie ma wpływu. Języki wrażliwe na podział wiersza (Python i Visual Basic) zwykle nie mają komentarzy wielowierszowych, ale JavaScript jest jednym wyjątkiem. Na przykład:

return /*
       */ 17

Jest równa

return 17

nie

return
17

Komentarze jednowierszowe zachowują podział wiersza, tj

return // single line comment
    17

jest równa

return
17

nie

return 17

Ponieważ komentarze są skanowane, ale nie analizowane, zwykle nie zagnieżdżają się. Więc

 /*  /* nested comment */ */

to błąd składniowy, ponieważ komentarz jest otwierany przez pierwszy /*i zamykany przez pierwszy*/

JacquesB
źródło
3
W większości języków komentarze w linii ( /* like this */) są uważane za równe pojedynczej spacji, a komentarze zakończone EOL ( // like this) w pustej linii.
9000
@JacquesB, więc myślę o traktowaniu komentarzy jako zastąpionych w całości ze źródła jako przestrzeni o zerowej szerokości , która wydaje się być równoważna z tym, co sugerujesz.
Sled
1
@artb zwykła spacja powinna dobrze działać i leży na stronie kodowej ASCII.
John Dvorak
@ JanDvorak spacja wpłynie na wygląd i usunie zrozumienie i jest bliższa semantyce „komentarza tak naprawdę nie ma”. Podstawowym wyjściem renderującym będzie HTML, więc w moim przypadku ASCII nie jest tak problematyczne, jak przeglądarki obsługują Unicode. To powiedziawszy, uważam, że standard C nakazuje zastąpienie komentarzy pojedynczą spacją.
Sled
1
Niektóre języki, zwłaszcza Racket, mają zagnieżdżone komentarze wieloliniowe: (define x #| this is #| a sub-comment |# the main comment |# 3) xplony 3.
wchargin
9

Aby odpowiedzieć na pytanie:

czy istnieje ogólna zgoda, czego na ogół oczekuje się od marży?

Powiedziałbym, że nikt nie spodziewałby się, że komentarz osadzony w tokenie będzie zgodny z prawem.

Zasadniczo komentarze należy traktować tak samo jak białe znaki. Każde miejsce, które może mieć obce białe znaki, powinno mieć również możliwość umieszczenia komentarza. Jedynym wyjątkiem byłyby ciągi znaków:

trace("Hello /*world*/") // should print Hello /*world*/

Wspieranie komentarzy wewnątrz łańcuchów byłoby dziwne, a ucieczka przed nimi byłaby nużąca!

Connor Clark
źródło
2
Nigdy nie myślałem o ciągach, to dobry przypadek na krawędzi. Moja obecna myśl polegała na prostym wyrażeniu regularnym między początkiem a końcem komentarza i zastąpieniu go pojedynczym odstępem. To by zadziałało na twoją sprawę.
Sled
3
+1 za ten fragment o ucieczce łańcuchów. Chociaż w twoim przykładzie ogólnie spodziewam się, że zostanie wydrukowany, Hello /* world*/!zamiast tłumić ograniczniki komentarzy. Witamy także w Programistach!
8bittree
1
Dzięki 8bittree! I to właśnie miałem na myśli. Co ciekawe, muszę też uciec przed ** w mojej odpowiedzi ....
Connor Clark,
2
@ArtB w ogóle „parsowanie przez podstawienie” staje się bardzo trudne z przypadkami na krawędziach i interakcją z innymi funkcjami, i najlepiej tego unikać od samego początku.
hobbs
7

W językach niewrażliwych na spacje ignorowane znaki (tj. Białe spacje lub te, które są częścią komentarza) ograniczają tokeny.

Na przykład Sys temsą dwa tokeny, podczas gdy Systemjeden. Przydatność tego może być bardziej widoczna, jeśli porównasz, new Foo()a newFoo()jeden z nich zbuduje instancję, Foopodczas gdy inne wywołania newFoo.

Komentarze mogą odgrywać taką samą rolę jak ciąg białych znaków, np. new/**/Foo()Działa tak samo jak new Foo(). Oczywiście może to być bardziej skomplikowane, np new /**/ /**/ Foo().

Technicznie powinno być możliwe zezwolenie na komentarze w ramach identyfikatorów, ale wątpię, aby było to szczególnie praktyczne.

A co z językami wrażliwymi na białe znaki?

Przychodzi mi na myśl Python i ma bardzo prostą odpowiedź: brak komentarzy blokowych. Zaczynasz komentarz od, #a następnie parser działa dokładnie tak, jakby reszta linii nie istniała, ale była tylko nową linią.

W przeciwieństwie do tego, Jade pozwala na komentarze do bloku , gdzie blok kończy się, gdy wrócisz do tego samego poziomu wcięcia. Przykład:

body
  //-
    As much text as you want
    can go here.
  p this is no longer part of the comment

Więc w tym królestwie nie powiedziałbym, że można powiedzieć, jak zwykle się sprawy załatwiają. Wydaje się, że wspólną cechą jest to, że komentarz zawsze kończy się końcem wiersza, co oznacza, że ​​wszystkie komentarze działają dokładnie tak samo jak nowe wiersze.

back2dos
źródło
Hmm, nowa linia jest prawdziwym problemem, ponieważ do komentarzy używamy składni HTML \ XML, więc będzie wieloliniowa.
Sled
3
@ArtB Jeśli używasz składni HTML / XML, rozsądne może być po prostu ich zachowanie.
8bittree
1
@ 8bittree ma sens, powinienem o tym pomyśleć. Zostawię to pytanie, ponieważ jest bardziej przydatne w ten sposób.
Sled
3

W przeszłości komentarze zamieniałem w pojedynczy token w ramach analizy leksykalnej. To samo dotyczy ciągów znaków. Stamtąd życie jest łatwe.

W konkretnym przypadku ostatniego kompilatora, który zbudowałem, reguła Escape jest przekazywana do procedury analizy najwyższego poziomu. Reguła zmiany znaczenia jest używana do obsługi tokenów, takich jak tokeny komentarza zgodne z podstawową gramatyką. Ogólnie rzecz biorąc, te żetony zostały odrzucone.

Konsekwencją zrobienia tego w ten sposób jest to, że w przykładzie zamieszczonym z komentarzem pośrodku identyfikatora identyfikator nie byłby pojedynczym identyfikatorem - jest to oczekiwane zachowanie we wszystkich językach (z pamięci), z którymi pracowałem .

Przypadek komentarza w ciągu powinien być domyślnie rozpatrzony przez analizę leksykalną. Reguły do ​​obsługi ciągu nie są zainteresowane komentarzami, dlatego komentarz jest traktowany jako treść ciągu. To samo dotyczy ciągu (lub literału cytowanego) w komentarzu - ciąg jest częścią komentarza, który jest jawnie pojedynczym tokenem; zasady przetwarzania komentarza nie są zainteresowane ciągami.

Mam nadzieję, że to ma sens / pomaga.

użytkownik202190
źródło
Więc jeśli masz kod, taki jak console.log(/*a comment containing "quotes" is possible*/ "and a string containing /*slash-star, star-slash*/ is possible"), gdzie w komentarzu są cytaty i składnia komentarza w łańcuchu, to skąd lexer wiedziałby, aby poprawnie go tokenizować? Czy możesz zredagować swoją odpowiedź, podając ogólny opis tych przypadków?
chharvey
1

To zależy od celu twojego parsera. Jeśli napiszesz analizator składni, aby zbudować drzewo analizy do kompilacji, wówczas komentarz nie ma wartości semantycznej oprócz potencjalnie oddzielających tokenów (np. Metoda / komentarz / (/ komentarz /)). W tym przypadku jest traktowane jak spacje.

Jeśli twój parser jest częścią transpilatora tłumaczącego jeden język źródłowy na inny język źródłowy lub jeśli twój parser jest preprocesorem pobierającym jednostkę kompilacyjną w języku źródłowym, analizującym go, modyfikującym go i zapisującym zmodyfikowaną wersję z powrotem w tym samym języku źródłowym, komentarze jak wszystko inne staje się bardzo ważne.

Również jeśli masz meta informacje w komentarzach, a szczególnie zależy ci na komentarzach, takich jak przy generowaniu dokumentacji API, tak jak robi to JavaDoc, komentarze są nagle bardzo ważne.

Tutaj komentarze są często dołączane do samych tokenów. Jeśli znajdziesz komentarz, dołącz go jako komentarz tokena. Ponieważ token może mieć wiele tokenów przed i po, ponownie zależy od celu obsługi tych komentarzy.

Pomysł dodawania adnotacji do tokenów nieskomentujących za pomocą komentarzy polega na całkowitym usunięciu komentarzy z gramatyki.

Kiedy już masz parsowane drzewo, niektóre AST zaczynają rozpakowywać komentarze reprezentujące każdy token według własnego elementu AST, ale są dołączone do innego elementu AST oprócz zwykłego związku zawierającego. Dobrym pomysłem jest sprawdzenie wszystkich implementacji parsera / AST dla języków źródłowych dostępnych w otwartym środowisku IDE.

Jedną z bardzo dobrych implementacji jest infrastruktura kompilatora Eclipse dla języka Java. Zachowują komentarze podczas tokenizacji i reprezentują komentarze w AST - o ile pamiętam. Ponadto ta implementacja parsera / AST zachowuje formatowanie.

Martin Kersten
źródło