nav_dugme codeBlog codeBlog
  • početna
  • Učionica
  • Saveti
  • Zanimljivosti
  • Kontakt

Tutorijal - Uklanjanje komentara iz programskog koda

Viber
zoom_plus zoom_minus

Uvod

Prošlim člankom, započeli smo mini-serijal članaka u kojima ćemo se (na ovaj ili onaj način) baviti obradom teksta (ili, ako želite da sve lepše zvuči, a pri tom je još i tačno - programskog koda).

U ovom članku, implementiraćemo Python skriptu za uklanjanje komentara iz programskog koda, prvenstvano za 'C-olike' programske jezike, kao što su C/C++, C#, Java, JavaScript i PHP.

Kažemo tako (uz napomenu), jer u slučaju jezika kao što su Python i SQL, u kojima su zastupljeni samo linijski komentari, pronalaženje (i uklanjanje) komentara je jednostavna operacija, dok je, kada su u pitanju jezici u kojima se pored linijskih komentara koriste i blok komentari, situacija ipak malo kompleksnija.

Takođe, ovaj članak će biti dobra prilika da se pobliže upoznamo sa konecptom vođenja računa o kontekstu čitanju koda (iako detaljniju razradu "zvanično" ostavljamo za sledeći članak) ....

Uklanjanje linijskih komentara i osnovna razmatranja pri pronalaženju komentara u tekstu

Za sam početak, dva pitanja: kako možemo ukloniti linijske komentare i da li postoji jednostavan obrazac po kome možemo prepoznavati obe vrste komentara (pri čemu je drugo pitanje od većeg značaja)?

Prepoznavanje linijskih komntara jeste jednostavno, a verujemo da iskusnijim čitaocima odmah pada na pamet da se obe vrste komentara mogu pronalaziti preko regularnih izraza, pri čemu se početni tekst može podeliti na tri (2 + 1) grupe tokena:

  • one koji počinju sa /* i završavaju se sa */ (blok komentari za C-olike jezike)
  • one koji počinju sa // (C/C++, C#, Java, JavaScript, PHP), # (Python), ili -- (SQL) i završavaju prelaskom u novi red n\ (linijski komentari)
  • sve "ostale" (kod koji nije pod komentarima)

.... ali - i dalje ostaje pitanje da li se posle toga komentari mogu jednostavno ukloniti iz liste.

Pogledajmo prvo kako stvari stoje sa linijskim komentarima.

Uklanjanje linijskih komentara

Donja skripta pronalazi tokene koji počinju sa // (ili #, ili --) i završavaju se sa \n i potom ih zamenjuje tokenom \n:

		
# ----- konfiguracija ---------------------------------------- #

datoteka_ulaz  = "naziv_datoteke"
datoteka_izlaz = "naziv_datoteke" # ista ili različita datoteka,
                                  # po vašem izboru
regex_clike    = "//.*\n"
regex_python   = "#.*\n"
regex_sql      = "--.*\n"

# ----- funkcije --------------------------------------------- #

def uklanjanje_linijskih komentara(tekst, regex):
	return tekst.replace(regex, "\n")

# ----- obrada ----------------------------------------------- #

f = open(datoteka_ulaz, "rb")
s = f.read().decode("utf-8")
f.close()

s = uklanjanje_linijskih_komentara(s, regex_clike)

f = open(datoteka_izlaz, "wb")
f.write(s)
f.close()
		
	
Slika 1. - Python skripta za uklanjanje linijskih komentara.

Bili smo praktični i u ovom (vrlo jednostavnom) slučaju, nismo pravili listu, već smo zamene obavili direktno.

(Moguće je da se već pitate i zašto smo onda uopšte i spominjali liste?!)

Postupak koji smo upravo razmotrili je krajnje adekvatan za zamenu linijskih komentara (što znači da je odgovor na prvo od dva pitanja sa početka potvrdan), ali, ako pokušamo da takav isti ("naivan") pristup primenimo za uklanjanje blok komentara, naići ćemo odmah na određene zamke ....

(Možda ne baš odmah, možda će na samom početku sve i dalje delovati lepo. Videćemo ....)

Šta se dešava ako pokušamo da primenimo naivan pristup za uklanjanje blok komentara

Uzmimo kao primer sledeći C kod ....

		
/*
	Blok komentar
*/

#include<stdio.h>

void main() // Glavna funkcija
{
	printf("Dobar dan! :)");
}
		
	
Slika 2. - Primer C koda za testiranje skripte.

Ako definišemo regularni izraz koji pronalazi obe vrste komentara ....

		
regex_clike = "//.*\n||\/\*[\s\S]*?\*\/"
		
	
Slika 3. - Regularni izraz za izdvajanje komentara.

.... i primenimo ga u naredbi replace (kao što smo radili u prvoj skripti za uklanjanje linijskih komentara), skripta će na gornjem primeru C koda svoj posao (i ovog puta) obaviti adekvatno.

Ali, šta ako se prebacimo na C++ (što, samo po sebi nije od prevelikog začaja) i malo promenimo kod (što jeste od značaja)?

		
#include<iostream>
using namespace std;

/* ----- Komentar ----- */

int main()
{	
	string s = "/* ----- Komentar ----- */";
}
		
	
Slika 4. - Primer komplikovanijeg C++ koda, koij može "prevariti" skriptu koju smo prethodno napisali.

Jasno je da će obe niske /* ----- Komentar ----- */ (gde je, u kontekstu primera, samo prva niska zapravo komentar, a druga deo niske) - biti uklonjene ....

		
#include<iostream>
using namespace std;



int main()
{	
	string s = "";
}
		
	
Slika 5. - Rezultat izvršavanja prethodne skripte - vidimo da su uklonjeni, i komentari, i delovi niski koji "liče" na komentar.

.... što praktično obesmišljava naredbu dodele u 8. redu (naredba, sama po sebi, jeste u redu, ali, to nije naredba koju smo hteli da izvršimo)!

Nije teško zaključiti da problem nastaje usled toga što program ne vodi računa o kontekstu čitanja tokena (odnosno - tokena), to jest, o tome da li se tokeni nalaze unutar komentara, niski i sl. (ili izvan).

Sa konceptom (ne)proznavanja konteksta sreli smo se još u prethdnom članku, pa ćemo ovoga puta detaljnije razraditi pomenute ideje.

Odgovor na drugo od dva pitanja koja smo postavili na početku je dakle - odričan. Vidimo da ne postoji (skroz) jednostavan obrazac po kome možemo prepoznavati blok komentare i jasno je da je potreban drugačiji pristup.

Srećom, algoritam za prepoznavanje blok komentara nije posebno komplikovan ....

Uklanjanje blok komentara

Korišćenje regularnih izraza je i dalje (krajnje) adekvatan pristup za skriptu koja polako dobija konačni oblik, ali, moramo biti precizniji i moramo kreirati mehanizam koji prepoznaje kontekst u kome se token koji trenutno razmatramo pojavljuje.

(Takođe, trebaće nam i liste. Zato smo ih spominjali od početka (kada nam još uvek nisu trebale). Tada nam nisu trebale, ali - sada će nam trebati.)

U prepoznavanju konteksta, pomoći će nam (zapravo prilično jednostavna) pravila jezika, pri čemu znamo sledeće:

  • linijski komentari počinju sa // i završavaju se prelaskom u sledeći red
  • blok komentari počinju sa /* i završavaju se sa */
  • niske počinju i završavaju se sa " (mogu naravno počinjati i sa ' (apostrof), kao i ` (backtick), ali ćemo se u ovom članku, zarad jednostavnosti, zadržati samo na navodnicima)
  • znak " (navodnik), ne može se samostalno pojaviti unutar niske (koja počinje i završava navodnicima), već isključivo kao deo escape sekvence: \"

Dakle, svi tokeni koji se pojave posle tokena /* predstavljaju deo komentara (sve do pojave tokena */), kao i svi tokeni posle tokena // (sve do pojave tokena \n).

To znači da je naš osnovni zadatak da (preko regularnih izraza), u tekstu pronađemo sledeće tokene: //, \n, /*, */ i " (kao što smo već pominjali, inače se za niske mogu - a praktično i moraju - koristiti i tokeni ' (apostrof) i `(backtick)).

Ovoga puta, podelićemo ulazni tekst u listu tokena, u kojoj je svaki element:

  • neki od navedenih tokena (ili)
  • tekst koji se nalazi između dva tokena iz gore navedene grupe (ili, između početka ili kraja niske i nekog od navedenih tokena)

Preko novog regularnog izraza ....

		
import re

regex_clike = "(//|\n|/*|*/|\")"

def rastavljanje_teksta(tekst, regex):
	lista = re.split(regex, tekst)
	return lista
		
	
Slika 6. - Regularni izraz za pronalaženje najvažnijih tokena.

.... dobijamo sledeću listu (primer C++ koda koji smo prethodno koristili):

		
#include<iostream>
-----------------------------
using namespace std;
-----------------------------
/*
-----------------------------
 ----- Komentar ----- 
-----------------------------
*/
-----------------------------
int main()
{	
	string s =
-----------------------------
"
-----------------------------
/*
-----------------------------
 ----- Komentar ----- 
-----------------------------
*/
-----------------------------
"
-----------------------------
;
-----------------------------
}
-----------------------------
		
	
Slika 7. - Lista koja se dobija kao rezultat rastavljanja teksta preko prethodnog regularnog izraza.

Da bismo bolje razumeli kako treba protumačiti ovakvu listu, pogledajmo nekoliko jednostavnijih primera ....

Uklanjanje komentara 01
Slika 8. - Primeri za uklanjanje .

Izdvojeni su i ostali tokeni (operatori, white space znakovi i sl), što smo učinili da bi primeri bili zanimljiviji, ali, princip prepoznavanja niski i komentara se ne menja.

U prvom primeru ....

Uklanjanje komentara 02
Slika 9. - Primer prepoznavanja niske - korak 1.

.... prvo se pojavljuju se dva tokena koja ne menjaju kontekst čitanja.

Treći token ....

Uklanjanje komentara 03
Slika 10. - Primer prepoznavanja niske - korak 2.

.... uvodi čitač u režim prepoznavanja niski, što znači da će svi tokeni

Uklanjanje komentara 04
Slika 11. - Primer prepoznavanja niske - korak 3.

.... sve do pojave sledećeg tokena "\"" ....

Uklanjanje komentara 05
Slika 12. - Primer prepoznavanja niske - korak 4.

.... biti prepoznati kao deo niske.

Ove informacije (inače) možemo iskoristiti da (po želji), saržaj niske grupišemo u jedan token:

Uklanjanje komentara 06
Slika 13. - Primer prepoznavanja niske - korak 5.

.... ili da celu nisku smestimo u jedan token:

Uklanjanje komentara 07
Slika 14. - Primer prepoznavanja niske - korak 6.

U našim primerima, ostavićemo (ipak) tokene u originalnom rasporedu.

Spajanje tokena nije ni od kakvog značaja za skriptu koju pravimo, jer će tokeni koji bi inače bili spajani - jednostavnobiti zanemareni (odnosno, neće biti kopirani u izlaznu datoteku).

Uklanjanje komentara 08
Slika 15. - Primer prepoznavanja niske - korak 7.

U drugom primeru, prvi token od značaje je token "/*", koji uvodi čitač u režim prepoznavanja komentara ( u nekoj drugoj implementaciji, kao što smo već videli, to bi mogao biti i režim spajanja komentara).

Uklanjanje komentara 09
Slika 16. - Primer prepoznavanja komentara - korak 1.

To znači da će svi tokeni do pojave tokena "*/" (uključujući i taj token, biti prepoznati kao komentar).

Uklanjanje komentara 10
Slika 17. - Primer prepoznavanja komentara - korak 2.

(U ovom primeru, ostatak naredbe je algebarski izraz sa promenljivom.)

Uklanjanje komentara 11
Slika 18. - Primer prepoznavanja komentara - korak 3.

U sledećem primeru, koji predstavlja složeniju situaciji (kada se među tokenima pojavljuju, i tokeni koji otvaraju / zatvaraju komentare, i tokeni koji otvaraju / zatvaraju niske), prvi token od značaja je token "/*", koji uvodi čitač u režim prepoznavanja komentara, što znači da se dolazeći tokeni tretiraju kao komentari ....

Uklanjanje komentara 12
Slika 19. - Primer pravilnog tumačenja komentara koji sadrži nisku - korak 1.

Može se postaviti pitanje, da li token "\"", koji je prethodno imao poseban značaj ....

Uklanjanje komentara 13
Slika 20. - Primer pravilnog tumačenja komentara koji sadrži nisku - korak 2.

.... ima poseban značaj i u ovakvoj situaciji, ali, odgovor je - ne - i token "\"" se sada prepoznaje (samo) kao deo niske.

Uklanjanje komentara 14
Slika 21. - Primer pravilnog tumačenja komentara koji sadrži nisku - korak 3.

U slučaju da određeni token (u ovoj situaciji je to bio token "/*") izvede čitač iz osnovnog režima, svi tokeni koji inače imaju posebno značenje, postaju obični tokeni i jedini token od značaja je token koji vraća čitač u osnovni režim (u ovom slučaju, token "*/").

Uklanjanje komentara 15
Slika 22. - Primer pravilnog tumačenja komentara koji sadrži nisku - korak 4.

U trećem primeru, token "\"" uvodi čitač u režim perpoznavanja niske ....

Uklanjanje komentara 16
Slika 23. - Primer pravilnog tumačenja niske koja sadrži komentar - korak 1.

To (ponovo) znači da će određeni token ....

Uklanjanje komentara 17
Slika 24. - Primer pravilnog tumačenja niske koja sadrži komentar - korak 2.

.... koji u osnovnom režimu ima specijalno značenje, biti prepoznat shodno trenutnom režimu čitača (ovoga puta, kao deo niske).

Uklanjanje komentara 18
Slika 25. - Primer pravilnog tumačenja niske koja sadrži komentar - korak 3.

.... što važi i za prepostale tokene (dok drugi token "\"" ne zatvori nisku).

Uklanjanje komentara 19
Slika 26. - Primer pravilnog tumačenja niske koja sadrži komentar - korak 4.

Implementacija

Ostaje da sve što smo videli pretočimo u funkcionalnu skriptu. Koristićemo ideje koje smo razmotrili na pređašnjim primerima, s tom razlikom što smo u primerima komentare označavali, a u skripti ćemo ih uklanjati (to jest, deo teksta koji je prepoznat kao komentar, jednostavno neće biti kopiran u izlaznu datoteku)

Prvo ćemo precizno definisati pravila za premeštanje tokena.

Pravila za premeštanje tokena

  • preko steka ćemo voditi računa o kontekstu
  • kontekst 0 - token koji se čita je van komentara i niski
  • kontekst 1 - token je deo linijskog komentara
  • kontekst 2 - token je deo blok komentara
  • kontekst 3 - token je deo niske
  • proći ćemo redom kroz listu tokena
  • tokeni "//", "\n", "/*", "*/" i "\"" (ovoga puta znak navoda, a ne escape sekvenca za znak navoda), menjaju stanje steka, ali se svi osim znaka navoda uklanjaju iz liste
  • ostali tokeni prate kontekst
  • ako je kontekst 0 ili 3, biće ostavljeni
  • ako je kontekst 1 ili 2, biće uklonjeni (odnosno, neće biti kopirani)

Na kraju, napisaćemo i Python skriptu za uklanjanje programskog koda:

Python skripta za uklanjanje komentara iz programskog koda

		
# ----- konfiguracija ---------------------------------------- #

import re

datoteka_ulaz  = "naziv_datoteke"
datoteka_izlaz = "naziv_datoteke" # ista ili različita datoteka,
                                  # po vašem izboru
regex_clike  = "(//|\n|/*|*/|\")"
regex_python = "#.*\n"
regex_sql    = "--.*\n"

# ----- funkcije --------------------------------------------- #

def rastavljanje_teksta(tekst, regex):
	lista = re.split(regex, tekst)
	return lista

def obrada_pojedinacnog_tokena(token, stek, lista, nova_lista):
	kontekst  = stek[len(stek) - 1]
	if token == "": return

	if token == "/*":
		if kontekst == 0:
			stek.append(1)
			return
	
	if token == "*/":
		if kontekst == 1:
			stek.pop()
			return
	
	if token == "//":
		if kontekst == 0:
			stek.append(2)
			return
	
	if token == "\n" or token == "\r":
		if kontekst == 2:
			stek.pop()
			nova_lista.append(token)

	if token == "\"":
		if kontekst == 0: stek.append(3)
		if kontekst == 3: stek.pop()
	
	if kontekst == 0 or kontekst == 3:
		nova_lista.append(token)

def obrada_liste_tokena(lista):
	nova_lista = []
	stek       = [ 0 ]
	
	for token in lista:
		obrada_pojedinacnog_tokena(token, stek, lista, nova_lista)
	
	return nova_lista

def formatiranje_teksta(lista):
	s = ""
	for token in lista:
		s = s + token

	return s

# ----- obrada ----------------------------------------------- #

f = open(datoteka_ulaz, "rb")
s = f.read().decode("utf-8")
f.close()

lista = rastavljanje_teksta(s, regex_clike)
lista = obrada_liste_tokene(lista)
tekst = 

f = open(datoteka_izlaz, "wb")
f.write(s)
f.close()
		
	
Slika 27. - Kompletirana Python skripta za uklanjanje komentara iz programskog koda.

Ovoga puta, ako skripti damo na obradu C kodove koje smo prethodno koristili, biće uklonjene samo niske koje predstavljaju komentar, a ne i one koje samo "liče" na komentare.

Ideje za unapređenje skripte

Verujemo da, ako ste se zainteresovali za obradu teksta, nećete imati manjak ideja za samostalno isprobavanje, ali, spomenućemo 'najočigledniju' ideju.

Uklanjanjem linijskog komentara koji nije bio "slepljen" za početak reda (već je bio odvojen jednim ili više razmaka / tabova), ostaće whits space, a uklanjanje blok komentara (koji su se prostirali u više redova) ostavljaće prazne redove.

Ostavljamo vama, našim čitaocima, da samostalno implementirate jednu takvu jednostavnu (ali, ni izdaleka "banalnu") skriptu koja će rešiti navedene probleme ....

Autor članka Nikola Vukićević Za web portal www.codeblog.rs
Napomena: Tekstovi, slike, web aplikacije i svi ostali sadržaji na sajtu www.codeblog.rs (osim u slučajevima gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta www.codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog pismenog odobrenja autora.
©2021. Sva prava zadržana.
Viber
početna Početna > Članci > Tutorijal - Uklanjanje komentara iz programskog koda

Info & povezani članci

Info

trejler_sat Datum objave: 18.09.2021.

trejler_dokument Jezici: Python

trejler_teg_narandzasti Težina: 7/10

Povezani članci

Binarno stablo pretrage Visinski balansirano (AVL) stablo Dinamičko programiranje Objektno orijentisano programiranje Klase složenosti algoritama - (O notacija) Strukture podataka AVL Stablo - Implementacija - 1. deo - Osnovna struktura AVL Stablo - Implementacija - 2. deo - Pretraga Shunting Yard - Implementacija - 1. deo - Prevođenje izraza iz infiksnog u postfiksni zapis AVL Stablo - Implementacija - 3. deo - Obilazak stabla AVL Stablo - Implementacija - 4. deo - Dodavanje čvorova Stablo izraza
Preuzmite PDF verziju
On two occasions, I have been asked [by members of Parliament], 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?' I am not able to rightly apprehend the kind of confusion of ideas that could provoke such a question.
Charles Babbage
codeBlog codeBlog
Projekat posvećen popularizaciji kulture i veštine programiranja među mladim programerima.
Napomena: Tekstovi i slike na sajtu www.codeblog.rs (osim u slučajevima, gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta www.codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog odobrenja autora.
© 2021. Sva prava zadržana.
Facebook - logo
Instagram - logo
LinkedIn - logo
Twitter - logo
E-mail
Naslovna
   •
Uslovi korišćenja
   •
Obaveštenja
   •
FAQ
   •
Kontakt