Синтакса (програмски језици)

Из Википедије, слободне енциклопедије
Синтаксни преглед и увучен стил текста помажу програмерима у препознавању елемената изворног кода. У овом примеру је коришћен синтаксни преглед у боји написан у Пајтону.

У рачунарству, синтакса програмског језика представља низ правила која дефинишу комбинацију симбола за које се сматра да дају исправно структуиран документ или фрагмент у траженом програмском језику. Ово се односи на програмске језике, где документ представља изворни код, али и на језике за обележавање, где документ представља податак. Синтакса програмског језика дефинише своју површинску форму.[1] Текстуално базирани програмски језици су базирани на распореду карактера, док су визуелни програмски језици базирани на просторном распореду и вези између симбола (које могу бити текстуалне или графичке природе) абстрактно. За документ који има неисправну синтаксу каже се да има синтаксну грешку.

Синтакса (форма) представља супротност семантици. Код обраде програмских језика, обрада семантике генерално долази након обраде синтаксе, али у неким случајевима је неопходна прво обрада семантике за обраду синтаксе. У комапјлеру, синтаксна анализа обухвата предњи крај, док семантичка анализа обухвата задњи крај (и средњи крај, ако представља карактеристичан део).

Нивои синтаксе[уреди]

Синтаксе програмских језика су генерално подељене у три нивоа:

  • Речи – лексички ниво, одређује како ће изгледати форма знакова;
  • Фразе – граматички ниво, генерално гледано, одређује фразну форму знакова;
  • Садржај – одређује шта објекти или променљиве раде, такође одређује да ли су оне исправне, итд.

Овакво разликовање доводи до модуларитета, дозвољавајући да се сваки ниво обради одвојено, и углавном независно. Прво лексер претвара линеаран распоред карактера у линеарни распоред знакова; ова процедура је позната као "лексичка анализа". Затим парсер претвара линеарни распоред знакова у хијерархијско синтаксно стабло; ова процедура је позната као "рашчлањивање" у генералном смислу. Затим контекстуална анализа открива имена и проверава типове. Овакав модуларитет је понекад могућ, али у многим програмским језицима ранији корак зависи од каснијег корака, нпр. лексер хак у C-у зато што организација знакова зависи од контекста. Чак и у оваквим ситуацијама, синтаксна анализа је често виђена као апроксимација за овакав идеални модел.

Рашчлањивање се може поделити у два дела: дрво извођења или "конкретно синтаксно стабло", део који је одређен граматиком, али је генерално гледано превише комплексан за практичну употребу, и абстрактно синтаксно дрво (АСД), које упрошћава ово у употребљиву форму. АСД кораци и кораци семантичке анализе се могу сматрати формом семантичкке анализе, као што је додавање значења и интерпетације синтакси, или алтернативно као нефорамлна, мануелна имплементација синтаксних правила која би се тешко или непријатно описала тј. имплементирала.

Нивои углавном одговарају нивоима у хијерархији Чомског. Речи су у регуларним језицима, наведене у лексичкој граматици, која је граматика "Типа-3", дате као регуларни изрази. Фразе су у контекстуално слободним језицима (КСЈ) , или генерално гледано у детерминистички контекстно слободним језицима (ДКСЈ), наведене у фразама структуре граматике, која је "Типа-2", дате као правила у Бакус-Науровој форми (БНФ). Фразе граматике су чешће специфиране у многим ограниченим граматика него у контекстно слободној граматици, како би се лакше анализирале; док се ЛР рашчлањивач може анализирази ДФЦЛ у линеарном времену, једноставни ЛАЛР рашчлањивач и чак једноставнији ЛЛ рашчлањивач су више ефикаснији од ЛР-а, али могу једино анализирази граматику која има ограничена правила. Контекстуална структура се може описати уз помоћ контекстно-сензитивне граматике, и аутоматски анализирати помоћу атрибута граматике (иако се овај корак генерално гледано обавља ручно, преко правила имена резолуције и проверавања типа) и имплементирати преко симболичке табеле која садржи имена и типове.


Алати су написали да је лексер аутоматски генерисан из лексичке спецификације у регуларни израз а рашчлањивач из фразе граматике написан у БНФ-у: ово омогућава коришћење декларативног програмирања, уместо процедуралног или функционалног програмирања. Значајан пример је Лекс-Yacc пар. Овај пар аутоматски продукује конкретно синтаксно стабло; аутор рашчлањивача мора ручно да напише код и да објасни како га је претворио у абстрактно синтаксно стабло. Контекстуална анализа је углавном имплементирана ручно. Упркос постојању ових аутоматских алата, рашчлањавање се обично уноси ручно, из неколико разлога - можда фразна структура није контекстуално слободна, или алтернативна имплементација побољшава перфорамнсе или постоји нека грешка, или се граматика врло лако може изменити. Рашчлањивачи су често писани у функционалним програмским језицима, као што је Хаскел, у језицима за скриптовање, као што је Пајтон или Перл, или у C или C++.


Примери грешака[уреди]

Као пример, узећемо, (add 1 1) који је синтатички исправан Lisp-ов програм (претпостављајући да 'add' функција постоји, иначе би програм био неисправан), који додаје 1 и 1. Међутим, следећи код је неиспаван:

(_ 1 1)    lexical error: '_' is not valid
(add 1 1   parsing error: missing closing ')'
(add 1 x)  name error: 'x' is not bound

Приметно је да лексер не може да идентификује грешку - све што он зна је да након продуковања знака ЛЕВЕ_ЗАГРАДЕ, '(' остатак програма је неисправан, јер не постоји реч која креће са '_'. У овој фази рашчлањивања, рашчлањивач индетификује "листу"  правила производње због '(' знака, и на тај начин се може добити порука која указује на грешку; генерално гледано, она може бити двосмислена. У фази садржаја, симбол 'х' постоји у синтаксном стаблу, али није дефинисан, па саимим тим анализа контекста може дати специфичну грешку.

У строго типским прогргамским језицима, типске грешке су такође форме синтаксних грешака које се обично одређују у фази контекстуалне анализе, и ово представља снагу снажног куцања. На пример, следећи пример има неисправну синтаксу Пајтоновог кода (зато што је ово штампарска грешка, тј. тип који се може утврдити у фази рашчлањивања):

'a' + 1

... јер сабира стринг са бројем. Ово се може детектовати у фази анализе ако постоје одвојена правила за "стринг + стринг" и "број + број", али ово ће чешће бити анализирано од стране генералног правила као што је "Књижевно или индетификовано + књижевно или идентификовано" и на тај начин ће грешка бити детектована у фази контекстуалне анализе, где се налази типска провера. У неким случајевима провера није могућа, и овакве синтаксне грешке се могу открити само при покретању програма


У слабим типским језицима, где се тип може одредити у периоду покретања програма, јављају се типске грешке уместо семантичких грешака, и као што је речено, могу се открити тек при покретању програма. Следећи Пајтонов код:

a + b

је двосислен, и иако овај код има исправну синтаксу у периоду анализе, валидност синтаксе се може утврдити само при покретању кода, зато што променљиве немају тип у Пајтону, већ их само вредности имају.

Дефиниција синтаксе[уреди]

Синтакса текстуалних програмских језика се обинчно дефинише коришћењем комбинације регуларних израза (за лексичку структуру) и Бакус-Наурове форме (за граматичку структуру) за идуктивно одређивање синтаксних категорија (нетерминали) и терминалских симбола. Синтаксне категорије су дефинисане правилима која се називају продукције, које специфирају вредност која припада синтаксној категорији.[1] Терминалски симболи су конкретни карактери или стрингови карактера (на пример кључна реч као што је  дефиниши, ако, дозволи, или поништити) од којих су синтаксно исправни програми направљени.

Програмски језик мора имати више различитих и једнаких граматика, као што су регуларни једнаки изрази (на лексичким нивоима), или различита правила анализирања која генеришу исти језик. Користећи ширу категорију граматика, као што је ЛР граматика, дозвољено је поређење мањих или једноставнијих граматика са више ограничених категорија, као што је ЛЛ граматика, која захтева дужу граматику са више правила. Различита али једнака фраза граматике даје различита стабла анализирања, иако је основни језик (низ исправних докумената) исти.


Пример: Lisp[уреди]

Испод је приказана једноставна граматика, дефинисана користећи нотацију регуларних израза и Бакус-Наурове форме. Она описује Lisp-ову синтаксу, која дефинише продукте синтатичких категорија израза (expression), атома (atom), броја (number), симбола (symbol), и листе (list):

expression ::= atom | list
atom ::= number | symbol 
number ::= [+-]?['0'-'9']+
symbol ::= ['A'-'Z''a'-'z'].*
list ::= '(' expression* ')'

Ова граматика дефинише следеће:

  • израз је или атом или листа;
  • атом је број или симбол;
  • број представља јединствен низ од једне или више дигиталних јединица, обично следи након операција као што су плус или минус;
  • симбол представља писмо од нула или неколико карактера (без спејс простора); и
  • листа представља идентични пар заграда, која садржи неколико израза у себи, мада може бити и празна.

Децимални бројеви, карактери малих и великих слова, и заграде представљају симболе терминала.

Следе примери добро формираних знакова у овој граматици : '12345', '()', '(a b c232 (1))'

Комплексна граматика[уреди]

Граматика мора спецификовати програмски језик који се може класификовати према својој позицији у хијерархији Чомског. Фразна граматика већине програмских језика се може спецификовати користећи "Тип-2" граматику, тј. они су контекстно слободне граматике,[2] иако је целокупни садржај синтаксе осетљив (због декларације променљивих и оквира програма), па отуда следи "Тип-1". Међутим, постоје изузеци, па је за неке програмске језике фразна граматика "Типа-0" (Тјуринг-комплетни програми).

У неким језицима као што су на пример Перл и Lisp, спецификацију (или имплементацију) програмског језика омогућава конструкција која се извршава током фазе рашчлањавања. Осим тога, ови програмски језици имају конструкторе који дозвољавају програмеру да промни ток понашања рашлчањивања. Ова комбинација ефективно замагљује разлике између рашчлањивања и извршавања, и чини анализу синтаксне анализе неодлучивим проблемом у поменутим програмским језицима, мислећи на то да се фаза рашчлањавања можда није завршила. На пирмер, у Перлу је могуће извршавати код током фазе рашчлањивања користећи BEGIN функцију, па тако прототипови Перлове функције могу мењати синтаксу интерпретације, а некада могу мењати и целокупну исправност синтаксе целог кода.[3] Генерално гледано, ово значи да "Само Перл може анализирати Перл" (зато што код мора да се изврши током фазе рашчлањавања, и може да измени граматику), или другачије речено "чак и Перл не може анализирати Перл" (зато што је непредвидив). Слично, Lisp макрои који се уводе defmacro синтаксом, који се такође извршавају током рашчлањивања, мислећи на то да Lisp компајлер мора имати представљен цео Lisp-ов систем. Супротно, C макрои представљају замену за стрингове, и не захтевају извршавање кода.[4][5]

Синтакса у односу на семантику[уреди]

Синтакса програмског језика описује форму исправности програма, али не даје никакве информације о значењу програма или разултатима извршавања програма. Значење дате комбинације симбола зависи од семантике (било да је формална и тешко кодирана у референтној имплементацији), тј. семантика управља значењем комбинације симбола. Нису сви синтатички исправни програми и семантички исправни. Многи синтатички исправни програми су ипак лоше формирани, и зависе од правила програмског језика; и могу (у зависности од спецификације програма и врсте имплементације) да дају резултат као грешку у преводу или извршавању. У посебним случајевима, такви програми могу да испоље недефинисано понашање. Чак и када је програм добро дефинисан и осмишљен у оквиру језика, ипак може имати супротно значење од оног значења које је програмер хтео да програм има.

Коришћењем природног језика као један пример, можда неће бити могуће доделити значење граматички коректним реченицама или реченицама које су можда неисправне:

  • "Безбојне зелене идеје брже спавају" је граматички добро формирана реченица али нема неко посебно значење.
  • "Џон је ожењени нежења." је граматички исправна реченца али не може бити тачна.

Следећи C код је синтатички исправан, али обавља операцију која није семантички дефинисана (зато што је "п (p)" нулти показивач, а операције "p->real" и "p->im" немају значење):

 complex *p = NULL;
 complex abs_p = sqrt (p->real * p->real + p->im * p->im);

Још више поједностављено:

 int x;
 printf("%d", x);

је синтатички исправан, али семантички неисправан код, јер користи неозначену променљиву.

Види још[уреди]

Брзо поређење синтакси у различитим програмским језицима. Можете погледати листу "Здраво, свете!". Ево неколико примера:

Референце[уреди]

  1. 1,0 1,1 Friedman, Daniel P.; Mitchell Wand; Christopher T. Haynes (1992).
  2. Michael Sipser (1997).
  3. The following discussions give examples:
  4. "An Introduction to Common Lisp Macros".
  5. "The Common Lisp Cookbook - Macros and Backquote".

Спољашње везе[уреди]