cirilisp/doc/rad.tex

1320 lines
100 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\documentclass[a4paper]{article}
% biber
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[serbianc]{babel}
\usepackage{textalpha}
\usepackage{wrapfig}
\usepackage{textcase}
\usepackage{comment}
\usepackage{tocloft}
\usepackage{graphicx}
\usepackage{fancyhdr}
\usepackage[backend=biber]{biblatex}
\addbibresource{lit.bib}
\usepackage{setspace}
\usepackage{float}
\usepackage{multirow}
\usepackage{array}
\author{Петар Каприш}
\title{Ћирилисп: програмски језик на српској ћирилици - дизајн и имплементација}
\pagestyle{fancy}
\makeatletter
\lhead{\tiny{\@title}}
\rhead{\tiny{\@author, IV-10}}
\fancypagestyle{Predgovor}
{
\fancyfoot[C]{\thepage}
}
\begin{document}
\begin{titlepage}
\centering
{\Large
\textbf{Гимназија ,,Јован Јовановић Змај''}
Нови Сад
}
\vspace*{5cm}
{\LARGE
\textbf{Матурски рад из програмирања}
\vspace*{0.5cm}
\begin{spacing}{0.8}
\MakeUppercase{\textbf{\@title}}
\end{spacing}
}
\vspace*{8.9cm}
{\LARGE
Професор ментор:
\hfill
Ученик:
\vspace*{0.3cm}
Марко Савић
\hfill
{\@author} IV-10
}
\vspace*{\fill}
{\large Нови Сад, {\@date}}
\makeatother
\end{titlepage}
\addtocounter{page}{1}
\section*{Предговор}
\thispagestyle{Predgovor}
Овај рад се бави концепцијом и имплементацијом Ћирилисп програмског језика, минималистичког Лисп дијалекта осмишљеног да се користи на српском језику са ћириличком тастатуром.
Моја главна мотивација за овај рад јесте моје опште интересовање за дизајн и имплементацију програмских језика и нарочито за дизајн дијалеката Лиспа. Иако је ова тема помало преопширна за опсег матурског рада одлучио сам да радим на њој јер ми је такође пружала изазов.
Желео бих да се захвалим свом ментору Марку Савићу за све савете и препоруке којима ми је помогао у изради рада и дизајну програма, као и за детаљно прегледање свег кода и овог самог рада.
\newpage
\tableofcontents
\newpage
\section*{Увод}
\addcontentsline{toc}{section}{\protect\numberline{}Увод}
Ћирилисп је програмски језик, дијалекат Лиспа, осмишљен да се његов код пише на српској ћирилици. Има многа својства која би се очекивала од типичног Лисп дијалекта, укључујући типичне методе манипулације листама, лексички опсег променљивих, комплетан макро систем, којим се може проширити синтакса језика за жељену употребу, као и способност за разломачку аритметику.
У сврху овог матурског рада, направљена је имплементација овог језика, као интерпретер написан у C-у, који између осталог може покренути код у датом језику као извршиву датотеку у било којој Посикс сагласној љусци.
Овај рад се може уопштено поделити на два дела. Први и други одељак представљају део који се бави описивањем Лиспа и Ћирилиспа, не захтева посебно предзнање Лиспа за разумевање, мада се претпоставља разумевање неких основних појмова у рачунарству. Други део представљају одељци \ref{Code} и надаље који објашњавају унутрашњи рад Ћирилисповог интерпретера и захтевају основно знање C-а за разумевање, будући да је у њему интерпретер испрограмиран.
Интерпретер захтева C компајлер сагласан са C99 стандардом, или више, као и стандардну бибилиотеку сагласну са POSIX стандардом 2001. или касније. До сада је једино тестиран са ГНУ C компајлером 6.3.0 на Девуан ГНУ/Линуксу. MinGW на Виндоузу је потврђен да не садржи довољно комплетну C библиотеку да би компајлирао Ћирилиспов интерпретер.
Уколико имате копију изворног кода овог програма, са окружењем способним да га покрене, један \texttt{make install} би требало да компајлира и инсталира цео програм.
Са инсталираним интерпретером можете покренути Ћирилиспово ЧПШП окружење командом \texttt{cirilisp}. Уколико желите да интерпретирате код било којег већ написаног .ћ фајла, можете то учинити командом \texttt{cirilisp -q <име-фајла>}. За покретање оваквог Ћирилисп фајла попут извршивог програма, он мора да започне уз шабенг \texttt{\#!/usr/local/bin/cirilisp -q}, као и да има дозволе за извршавање (што му можете омогућити уз \texttt{chmod} команду), такав програм можете покренути уз просто \texttt{./<име-фајла>}.
Уколико желите да компајлирате овај \LaTeX{} документ, потребна вам је \texttt{texlive} инсталација уз \texttt{biber} и све пакете које документ користи (за шта можете прочитати његов изворни код).
(Напомена: \texttt{babel-serbianc} мора бити верзије строго веће од \texttt{2.2}, у супротном се дешава конфликт са \texttt{biblatex} пакетом)
\newpage
\section{Кратак увод у Лисп програмски језик}
Лисп је један од најстаријих програмских језика који је и данас у општој примени. Познат је по својој једноставној и једноличној синтакси, аутоматским системима за управљање меморијом, и моћним макроима који могу проширити његову већ изражајну синтаксу. Управо због своје екстензибилности, и изражајне моћи основа његовог дизајна, Лисп је један од ретких програмских језика који поседује сопствене дијалекте\parencite{graham02}. Постоје стотине Лисп дијалеката, сваки са другачијом сврхом и философијом дизајна. Од више функционалних и декларативних, као Ским или Кложур, до практичнијих и опште-наменских, као Комон Лисп и, па и обласно-специфичних као Емакс-Лисп.
\subsection{Листе и синтакса}
Лисп је акроним за List Processing (прерађивање листа) што указује на најосновнију, и најбитнију сложену структуру података која је доступна у овом језику, наиме листа. Листа се гради помоћу конс ћелија, структура које поседују два показивача (или два сама објекта у зависности од имплементације) који могу показати на било какав податак. Ови показивачи се респективно зову CAR и CDR (Contents of the address part of the register number и Contents of the decrement part of the register number) (ова имена су заснована на облику машинске речи на IBM-704 серији рачунара где је Лисп првобитно имплементиран, данашњи Лиспови их зову исто као и првобитни, али сама имена немају више посебно значење). Листа се уз њих дефинише као скуп конс ћелија где CAR сваке показује на одређени податак, члан листе, а CDR показује на следећу конс ћелију која чини исто као и претходна, све до задње, која показује на посебни тип објекта најчешће зван нил објекат.\parencite{recursive}
\begin{figure}[h!]
\includegraphics[width=\linewidth]{list.png}
\caption{Приказ листе}
\label{list1}
\end{figure}
Слика \ref{list1} приказује листу која би се текстуално могла представити као симболички израз \texttt{(1 . (2 . (3 . ())))} или у скраћеној нотацији симболичких израза, \texttt{(1 2 3)}. Овиме се може видети да \textit{car} и \textit{cdr} функције, када су примењене на валидну листу, враћају првог члана листе и остатак листе респективно.
Лиспова синтакса је хомоиконична, другим речима он није само способан да прерађује листе, већ је и сам његов код сачињен од листа. Ово чини саму синтаксу језика довољно једноставном да се опише једним примером позива функцијем, који у Лиспу увек бивају следећег облика: \texttt{(функција арг1 арг2 арг3 ...)}, овако изгледа готово свака операција или функција извршена у самом језику, то наравно значи да су неки, нама познати изрази као \texttt{1 / (3 + 4 * 5)}, претворени у, на први изглед, необичне \texttt{(/ 1 (+ 3 (* 4 5)))}. Међутим, управо та чудна униформност читаве синтаксе која није присутна у већини других програмских језика је оно што Лиспу омогућава толику изражајну моћ.
\subsection{REPL\label{REPL}}
Један од централних појмова у свакој имплементацији Лиспа јесте такозвани ,,Read, Evaluate, Print Loop'', у овој имплементацији познато као ЧПШП. ЧПШП је најосновнији начин интеракције са већином Лисп система, у питању је командно-линијски интерфејс у којем корисник текстуално уноси програм као низ симболичких израза, који у ,,бесконачној'' петљи учитава симболичке изразе а затим их евалуира (процењује им коначну вредност), и на крају их штампа. На пример горепоменути израз:
\begin{verbatim}
(/ 1 (+ 3 (* 4 5)))
\end{verbatim}
би био учитан кроз \textit{read} функцију, која би га парсирала и претворила у листу, коју би потом \textit{eval} функција проценила да има резултат
\begin{verbatim}
1/23
\end{verbatim}
(већина имплементација природно подржава разломачку аритметику), након чега би \textit{print} штампала дати број на екрану или терминалу.
Будући да \texttt{eval} функција покушава да евалуира сваку дату непразну листу као позив на функцију која је описана првим чланом листе, а сваки симбол као референцу на неку променљиву, веома често је пожељно назначити \texttt{eval} функцији да дати израз не желимо да она евалуира, ово се у већини дијалеката чини помоћу \texttt{quote} специјалне форме. На пример уколико напишемо израз као \texttt{(1 2 3)}, највероватније желимо да на неки начин користимо листу са члановима 1, 2 и 3, а не да позовемо функцију \texttt{1} са аргументима \texttt{2} и \texttt{3}, дакле написаћемо га као:
\begin{verbatim}
(quote (1 2 3))
\end{verbatim}
Пошто се ова форма изузетно често користи чешће се виђа скраћени облик исте:
\begin{verbatim}
'(1 2 3)
'simbol
\end{verbatim}
\subsection{Функције}
Лисп није нужно функционалан језик и његове функције углавном могу да стварају ,,нуспроизводе'' над остатком програма. Међутим многи дијалекти фаворизују функционалан стил. Функције се врло често дефинишу путем ,,ламбда'' израза, који су у већини дијалеката форме:
\begin{verbatim}
(lambda (arg1 arg2 ...) expr1 expr2 ...)
\end{verbatim}
Овакви ламбда изрази се потом евалуирају и као резултат стварају ,,функција објекат'', који се потом може користити у другим изразима и као коначну вредност враћа евалуирани задњи израз у својој дефиницији (\texttt{exprn}), на пример, израз који би означавао функцију која враћа квадрат унесеног броја би изгледала овако.
\begin{verbatim}
(lambda (x) (* x x))
\end{verbatim}
Док би израз који такву функцију заиста примењује на број (нпр. 5) изгледао овако.
\begin{verbatim}
((lambda (x) (* x x)) 5)
\end{verbatim}
Овај израз би унесен у REPL вратио вредност \texttt{25}.
Пошто мноштво анонимних функција није претерано лепо за гледати у коду, и овако дефинисане анонимне функције немају погодан начин да врше рекурзију, већина функција у Лиспу се дефинише везивањем оваквог ламбда израза за име променљиве тј. симбол. Следећи изрази именују и примењују функцију за квадрирање бројева.
\begin{verbatim}
(defun sqr (x) (* x x))
(sqr 6)
\end{verbatim}
(Овај пример је записан у Комон Лиспу)
Да би се показала рекурзивна могућност овако дефинисаних функција, дат је пример Акерманове функције написан у Скиму. Ским користи благо другачију форму за дефинисање функција и других променљивих.
\begin{verbatim}
(define (ack m n)
(cond
((= m 0) (+ n 1))
((and (> m 0) (= n 0)) (ack (- m 1) 1))
((and (> m 0) (> n 0)) (ack (- m 1) (ack m (- n 1))))))
\end{verbatim}
Уз \texttt{cond} се врши контрола тока, у зависности од тога који од предиката (првих израза у задња три реда) први буде испуњен, само једна (или ниједна) грана тока (други изрази у иста три реда) ће бити евалуирана\parencite{sicp}. \texttt{cond} је специјална форма, што значи да се не понаша као уобичајна функција (штавише, у већини дијалеката није функција никакве врсте), то значи да се њени аргументи не евалуирају очекиваним редоследом, на пример у функцијама \texttt{+} \texttt{*} и \texttt{/}, у примеру у одељку \ref{REPL}, сваки подизраз датог израза се евалуира пре него што се сама функција примени, у \texttt{cond}-овом случају, ово би било проблем, јер би то значило да се свака могућа грана евалуира, што не желимо од условног израза.
\section{Кратак увод у Ћирилисп}
Ћирилисп је минималистички, претежно функционални дијалекат Лиспа, који је дизајниран и имплементиран у сврху овог матурског рада. Његово главно дефинишуће својство јесте што за саставне карактере токена, уместо енглеских латиничних слова из ASCII табеле користи Уникод UTF-8 карактере који описују слова која припадају српској ћирилици.
Интерпретер који служи као имплементација овог језика се може покренути на већ постојећем фајлу или унутар ЧПШ петље, описане у под-одељку \ref{REPL}, начин на који се интерпретер може покренути на оба описана начина је објашњен у одељку Увод.
Ћирилисп садржи неколико основних типова података које корисник може да користи у својим програмима: \texttt{нил}, \texttt{конс}, \texttt{ћелија}, \texttt{број} (реални и разломачки), \texttt{симбол}, \texttt{процедура}, \texttt{булски}, \texttt{ниска}, \texttt{карактер}, и \texttt{грешка}.
Такође су дефинисани ,,крај фајла'' и ,,неодређен'' типови објеката, међутим корисник нема приступ њима и искључиво их користи \texttt{read} функција да би сигнализирала различите случајеве читања израза.
\subsection{Променљиве}
У Ћирилиспу се променљиве дефинишу на ,,Скимолик'' начин уз процедуру \texttt{опиши}, такође су променљиве искључиво лексичког опсега, као и у Скиму. Ово је пример просте дефиниције променљиве:
\begin{verbatim}
(опиши џ (* 37 1))
\end{verbatim}
Током дефиниције, други аргумент \texttt{опиши} процедуре се евалуира, и коначна вредност се везује за променљиву са именом првом аргумента (џ) у локалном опсегу.
Процедуре се могу дефиницати путем ламбда израза на сличан начин. Међутим чешће је (и читљивије) користити другу форму \texttt{опиши}, израза:
\begin{verbatim}
(опиши (квадрат џ) (* џ џ))
\end{verbatim}
Овај израз сама \texttt{опиши} функција преобраћује у:
\begin{verbatim}
(опиши квадрат (ламбда (џ) (* џ џ)))
\end{verbatim}
\subsection{Синтакса описана Read функцијом}\label{readsyntax}
Read функција (кориснику Ћирилиспа доступна као \texttt{читај}) прихвата унесени Ћирилисп израз и парсира га.
Следећи синтаксни елементи су уносиви од стране корисника у интерпретер:
\begin{enumerate}
\item Бројеви, који се могу замислити као састављени од низа цифара, или два низа цифара, између којих је разломачка црта или запета:
\texttt{43}, \texttt{2/73}, \texttt{43,4938}, ...
\item Симболи, који се састоје од низа саставних карактера, или почињу са избегнутим карактером и садрже \underline{било какав} карактер до следећег избегнутог карактера:
\texttt{имесимбола}, \texttt{име-><симбола/\_\_?}, \texttt{|СимБОЛ са разМАКОМ и ВЕЛИКИМ и малим словима|}, \texttt{|latinični simbol sa razmakom|},\fontencoding{T1} \texttt{|σύμβολο|}\fontencoding{T2A}.
\item Коментари, који или почињу уз семиколон или шабенг\texttt{(\#!)}, и трају до краја датог реда или почињу уз, \texttt{\#|} секвенцу и трају док се не наиђе на супротну \texttt{|\#} секвенцу карактера:
\begin{verbatim}
; ово је линијски коментар
#! ово је такође линијски коментар
#|ово је вишелинијски
коментар|#
\end{verbatim}
\item Булски објекти: \texttt{\#и} (истинито) и \texttt{\#л} (лажно)
\item Карактери: \texttt{\#\textbackslashа}, \texttt{\#\textbackslashновиред}, ...
\item Ниске: \texttt{"\textbackslash"Ово\textbackslash" је ниска, два наводника у овој ниски су избегнути, тако да и наводници могу да се нађу у нискама."}
\item Листе, конс ћелије и ,,окрњене'' листе:
\begin{verbatim}
(ово је листа)
(конс . ћелија)
(ово је окрњена . листа)
\end{verbatim}
\item ,,навод'' оператер, који спречава евалуацију израза на који је примењен:
\begin{verbatim}
'(ова листа се неће евалуирати)
`(неће ни ова)
\end{verbatim}
\end{enumerate}
\subsection{Стандардне процедуре језика}
Већ су поменуте \texttt{опиши} и \texttt{ламбда} специјалне форме, којима се граде и везују имена за процедуре које корисник описује, међутим језик би био бескористан да не поседује сопствене, предефинисане функције за различите сврхе. Скуп функција који је узет за стандардне је претежно одабран по узору на Скимов R\textsuperscript{5}RS(\textcite{r5rs}) стандардни скуп функција, услед тога што овај језик има веома минималистичку филозофију дизајна у том погледу (или је барем имао имао за време овог стандарда).
\subsubsection{Типови}
Неке од најбитнијих врста јесу типски предикати, другим речима функције које враћају булеан \texttt{\#и} или \texttt{\#л} у зависности од тога да ли је једини аргумент датог типа или не, језик пружа следеће предикате типова: \texttt{листа?}, \texttt{број?}, \texttt{конс?}, \texttt{нил?}, \texttt{ниска?}, \texttt{реалан?}, \texttt{разломак?}, \texttt{процедура?}, \texttt{симбол?}, \texttt{цео-број?}.
\begin{verbatim}
Ћ> (листа? 5)
Ћ> (цео-број? 5)
\end{verbatim}
\subsubsection{Аритметика}
Постоји неколико функција које омогућавају извршавање рачуна над бројевима, све основне аритметичке операције су понуђене: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}.
\begin{verbatim}
Ћ> (+ (* 3 5 4) 9)
69
Ћ> (/ (+ 6 7) (- 150 94))
13/56
Ћ> (+ (* 20 20) 20 0,666)
420,666
\end{verbatim}
Такође постоје и функције за преобраћивање рационалног (тачног) броја, у реални (нетачни), и обратно.
\begin{verbatim}
Ћ> (тачно->нетачно 555/333)
1,666667
Ћ> (нетачно->тачно 12,250)
49/4
\end{verbatim}
На рационалним бројевима се такође могу применити функције \texttt{бројилац} и \texttt{именилац}.
\begin{verbatim}
Ћ> (бројилац 88/14) ; разломци се скраћују, улаз је 44/7
44
Ћ> (именилац 1312) ; сваки цео број је само разломак са имениоцем 1
1
\end{verbatim}
Коначно, постоје функције за добијање маскимума и минимума из листе бројева.
\begin{verbatim}
Ћ> (макс 3 5 2 512 3)
512
Ћ> (мин 85 99 105 110 110)
85
\end{verbatim}
\subsubsection{Прерада листа}
Као и сваки дијалекат Лиспа, Ћирилисп поседује процедуре у сврси прераде листа и конс ћелија. \texttt{сар} (садржај адресног дела броја регистра) и \texttt{сдр} (садржај декрементног дела броја регистра) су важне процедуре за анализирање ових структура.
\begin{verbatim}
Ћ> (сдр '(а . б))
б
Ћ> (сар '("један" један 1))
"један"
Ћ> (сдр '("један" један 1))
(један 1)
Ћ> (сдр '(једини-члан))
()
\end{verbatim}
Конструкција конс ћелија се може вршити преко уграђеног \texttt{.} оператора током учитавања кода. Међутим постоји и процедура која конструише ћелије: \texttt{конс}.
\begin{verbatim}
Ћ> (конс 'а 'б)
(а . б)
\end{verbatim}
Конструкција листе се може вршити путем \texttt{листа} процедуре:
\begin{verbatim}
Ћ> (листа 1 3 5 4)
(1 3 5 4)
\end{verbatim}
Дужина листе се може добити путем \texttt{дужина} процедуре:
\begin{verbatim}
Ћ> (дужина '(1 4 9 16 25))
5
Ћ> (дужина ())
0
\end{verbatim}
Две листе се могу надовезати уз \texttt{надовежи}:
\begin{verbatim}
Ћ> (надовежи '(1 3 5) '(један три пет))
(1 3 5 један три пет)
\end{verbatim}
\subsubsection{Логика и контрола тока}\label{Logika}
Ћирилисп поседује неколико логичких оператора и начина усмеравања тока евалуације, један од најзначајних је \texttt{ако}, \texttt{ако} је специјална форма која узима 2 или 3 аргумента, први је предикат, који се евалуира, уколико је његова коначна вредност \texttt{\#л}, евалуира се трећи аргумент (уколико он постоји) и враћа његова вредност (или нил уколико он не постоји), уколико предикат даје вредност \texttt{\#и}, или \underline{било коју другу вредност}, евалуира се други аргумент и враћа се његова вредност.
\begin{verbatim}
Ћ> (опиши џ 5)
џ
Ћ> (ако (> џ 0) "џ је позитивно" "џ је непозитивно")
је позитивно"
\end{verbatim}
Уз ово, ,,\texttt{ако}'' више посдећа на тернарни оператер (\texttt{?:}), код C-оликих језика, него на \texttt{if} исказ код истих. Међутим с обзиром на то да Лиспови често фаворизују функционални стил, ово је и очекивано. За више конвенционално гранање исказа постоји \texttt{услов} специјална форма.
\begin{verbatim}
Ћ> (услов
((> џ 0) "џ је позитивно")
((= џ 0) "џ је нула")
(#и "џ је негативно"))
\end{verbatim}
Што се тиче логичких операција, имплементирани су најосновније, \texttt{није}, \texttt{и} и \texttt{или}.
\begin{verbatim}
Ћ> (није (= 3 4))
Ћ> (и (= 8 8) (= 5 6))
Ћ> (или (= 8 (+ 4 4)) (> 3 2) (= 5 6))
\end{verbatim}
\texttt{и} и \texttt{или} су специјалне форме, које се служе ,,лењом'' евалуацијом. Другим речима \texttt{и} се може замислити да редом евалуира своје арументе, и уколико наиђе на неки који враћа \texttt{\#л}, зауставља евалуацију и сам враћа \texttt{\#л}, уколико дође до краја листе аргумената, враћа вредност задњег члана. \texttt{или} ради на сличан али супротан начин, он се зауставља када наиђе на први нелажни предикат, и враћа \texttt{\#л} уколико дође до краја листе аргумената. \texttt{ако}, је сам довољан да се све ове логичке форме и форме контроле тока имплементирају и ,,испод хаубе'' су све оне само макрои и процедуре, засноване на \texttt{ако} форми.
\subsubsection{Процедуре}\label{Procedures}
\texttt{ламбда} и \texttt{опиши} су већ описани као методи дефинисања процедура и променљивих.
Међутим постоје и још неке занимљиве методе за рад са процедурама.
\texttt{примени} је процедура чији је први аргумент друга процедура, а други аргумент листа. \texttt{примени} као резултат враћа вредност коју би унесена процедура вратила у нормалном позиву.
\begin{verbatim}
Ћ> (опиши (сума-квадрата џ ж)
(+ (* џ џ) (* ж ж)))
сума-квадрата
Ћ> (опиши аргументи '(10 11))
аргументи
Ћ> (примени сума-квадрата аргументи)
221
\end{verbatim}
\texttt{мапирај} је сличан али примењује процедуру на сваки члан листе појединачно
\begin{verbatim}
Ћ> (опиши (квадрат џ) (* џ џ))
квадрат
Ћ> (мапирај квадрат '(1 2 3 4 5))
(1 4 9 16 25)
\end{verbatim}
Ћирилисп поседује и \texttt{нека} форму, пандан \texttt{let} форми у већини других дијалеката, ово је начин дефинисања анонимне процедуре, уз везивање аргумената и истотренутно извршавање.
\begin{verbatim}
Ћ> (нека ((џ 3) (ж 4))
(+ (* џ џ) (* ж ж)))
25
\end{verbatim}
Коначно, постоји специјална група процедура које су познате као специјалне форме, већ су помињане у ранијим одељцима, у питању су процедуре (за шта их већина других дијалеката не узима), које могу да избегну нормални редослед евалуације, што је врло често пожељно, можда сте се запитали како имплементирати своју специјалну форму и Ћирилиспу, а он управо има средства која можете користити за то, неке специјалне форме нису макрои, већ су уграђени део интерпретера, међутим уз њих можете имплементирати своје специјалне форме путем макроа.
Макрои су посебно обележене процедуре, које интерпретер третира другачије од осталих, за разлику од других процедура, које \texttt{eval}, процедура евалуира тако што прво евалуира њихове аргументе а потом примени процедуру на готове аргументе, макрои се евалуирају тако што се њихови аргументи \underline{не}евалуирају, већ се она одмах примењује (ова фаза се зове проширење макроа), након тога, производи се проширени израз, који се евалуира поново, да би се добио коначни израз. процедура \texttt{опиши-складњу} (складња је синоним за синтаксу, будући да макрои служе за проширење синтаксе), се користи за дефиницију нових макроа. Као пример дата је дефиниција специјалне форме \texttt{и} у Ћирилисповој стандардној библиотеци
\begin{verbatim}
(опиши-складњу (и . предикати)
(ако (нил? предикати) #и
(ако (нил? (сдр предикати))
(сар предикати)
(листа 'ако (сар предикати)
(примени и (сдр предикати))
#л))))
\end{verbatim}
Оваква дефиниција може испрва изгледати конфузно, међутим постаје јаснија када се примени на једноставан пример коришћења као онај који је задат у одељку \ref{Logika}.
\begin{verbatim}
(и (= 8 (+ 4 4)) (> 3 2) (= 5 6))
\end{verbatim}
Када се макро проширење изврши над овим изразом добија се нови израз
\begin{verbatim}
(ако (= 8 (+ 4 4))
(ако (> 3 2)
(= 5 6)
#л)
#л)
\end{verbatim}
Овде видимо да овај макро као резултат враћа низ угњеждених \texttt{ако} форми, које враћају нетачно уколико иједан од предиката не буде тачан, али тачно уколико су сви до задњег тачни.
\subsection{Поређење и једнакости}
Већ смо без именовања увели оператере за поређење \texttt{>} \texttt{<} и \texttt{=}. Они се могу примењивати искључиво на бројевима, и задаће грешку у типовима уколико се примене на небројеве. Сваки од њих враћа \texttt{\#и} уколико је листа бројева који су аргументи строго: опадајућа, растућа или константа, такође постоје и \texttt{>=} и \texttt{<=} процедуре које су тачне за нерастуће и неопадајуће улазе респективно.
\begin{verbatim}
Ћ> (>= 5 4 4 3 2)
Ћ> (>= 5 4 4 3 4)
\end{verbatim}
Међутим ови оператори служе исључиво за бројеве, њима се не могу поредити други Лисп објекти као ниске, листе, конс ћелије, симболи, итд. За то постоје друга два оператора, \texttt{јед?} и \texttt{једнаки?}.
\texttt{јед?} правилно пореди само ,,просте'' објекте, другим речима све типове објеката осим конс ћелија и процедура, уколико је нека конс ћелија или процедура дата за аргумент, аутоматски се враћа \texttt{\#л}. \texttt{једнаки?} правилно евалуира и једнакост сложених објеката и конс ћелија, али процедуре није могуће правилно поредити за једнакост у било којем смислу.
\begin{verbatim}
Ћ> (јед? 'симбол1 'симбол2)
Ћ> (јед? 'симбол1 'симбол1)
Ћ> (једнаки? '(1 2 3) '(1 . (2 . (3 . ()))))
Ћ> (једнаки? '(а . б) '(а . в))
\end{verbatim}
\subsubsection{Ниске и карактери}
Карактери у Ћирилиспу су имплементирани као Уникод карактери (ширине 32 бита на већини модерних рачунара), док су ниске имплементиране као UTF-8 енкодирани низови вишебајтских секвенци, све стандардне процедуре везане за рад са нискама урачунавају ту чињеницу и корисник не мора да размишља о представљању карактера које се дешава у самој меморији коју ниска заузима, све док његов терминал правилно сарађује са Уникод карактерима (јер ће у супротном случају имати потешкоћа да покрене сам програм).
Уз помоћ стандардних функција корисник може свезати, генерисати и одредити дужину ниски, па и добити појединачне карактере из њих.
\begin{verbatim}
Ћ> (свежи-ниске "Упркос " "бивању " "само ...")
"Упркос бивању само ..."
Ћ> (направи-ниску # 24)
"љљљљљљљљљљљљљљљљљљљљљљљљ"
Ћ> (дужина-ниске "дужина")
6
Ћ> (дужина-ниске "")
0
Ћ> (карактер "Минори воли хорор-филмове" 0)
#\М
Ћ> (карактер "Индустријска револуција и њене последице..." 30)
#\размак
\end{verbatim}
\subsubsection{Улаз и излаз}
Ћирилиспова стандардна библиотека има само мали број улазно-излазних процедура, међутим оне су довољно комплетне да покрију велики број случајева коришћења.
\begin{verbatim}
; код ћирилисп програма
(опиши џ (читај))
(штампај (надовежи '(танос кола) џ))
; терминал корисника горњег програма:
(1 2 3) ; корисников унос
(танос кола 1 2 3) ; излаз који програм штампа
\end{verbatim}
\texttt{читај} и \texttt{штампај} процедуре учитавају или штампају један Ћирилисп објекат на исти начин на који би интерпретер сам то учинио. (овај пример није дат у ЧПШП већ као пример Ћирилисп скрипте која би се могла написати и покренути, разлог је то да ове функције нису ни намењене да се користе у ЧПШП окружењу)
\texttt{прикажи} функција се користи за штампање ниски или карактера на излаз без посебног представљања (без наводника и тараба-секвенце)
Као често потребна погодност, додата је и процедура \texttt{новиред}, која штампа нови ред на терминалу.
\begin{verbatim}
; програм
(прикажи "Ниска")
(новиред)
(прикажи "Висок")
(прикажи #\а)
; терминал
Ниска
Висока
\end{verbatim}
\subsubsection{Евалуација}
Већ су поменуте форме за избегавање евалуације у другим дијалектима, и користили смо исту у Ћирилиспу без много објашњења, право име ове специјалне форме у Ћирилиспу је \texttt{навод} (скраћено од наводник), и као и у другим дијалектима, \texttt{read} функција има посебну скраћену форму позива на ову процедуру.
\begin{verbatim}
Ћ> (навод (1 2 3))
(1 2 3)
Ћ> '(1 2 3)
(1 2 3)
\end{verbatim}
Још једна битна процедура уколико програмер жели да нпр. улази у процедуру коју је дефинисао буду одређеног типа, или да буду форматирани на одређен начин, и жели да пријави грешку уколико ти услови нису испуњени јесте баци форма, која враћа грешку у облику ниске која садржи детаље грешке, све процедуре које овај језик поседује могу да ,,баце'' грешку под одређеним условима, на овај начин корисник може да дефинише сопствене процедуре које чине исто.
\begin{verbatim}
Ћ> (опиши (не-дај-ми-стрингу-осам а)
(ако (јед? а "осам")
(баци "Захтевао сам дијаметрално супротно од\
онога што си учинио")
"Све је у реду"))
Ћ> (не-дај-ми-стрингу-осам "седам")
"Све је у реду"
Ћ> (не-дај-ми-стрингу-осам "осам")
ГРЕШКА: Захтевао сам дијаметрално супротно од онога што си учинио
\end{verbatim}
\section{Организација кода у програму}\label{Code}
Сви одељци овог рада су до ове тачке описивали принципе дизајна као и правилно коришћење Ћирилисп програмског језика, остатак рада се бави спровођењем овако дизајнираног Лисп дијалекта у дело. Остатак рада не намерава да изложи читав код програма (он је свакако отворено доступан на адреси \url{https://gitlab.com/tvrdosrz/cirilisp}), већ да опише принципе рада интерпретера.
Интерпретер за Ћирилисп, који је и његова једина постојећа имплементација, је написан у C-у, уз део стандардне библиотеке који је написан у самом Ћирилиспу.
Код овог програма је подељен на неколико фајлова, чије појединачне сврхе ћемо размотрити.
\begin{itemize}
\item util.c и util.h, који садрже основне C функције које омогућавају читавом остатку програма да врше основне операције над објектима сваког типа, као и да раде са лексичким окружењима, сами Ћирилисп објекти и окружења су путем C-ових типова дефинисани управо овде.
\item internals.c и internals.h садрже C код који дефинише велику већину Ћирилиспових функција, наиме дефинишу оне које се могу звати ,,уграђене'' или ,,примитивне'', функције које нису дефинисане Ћирилисп кодом, користивши друге функције, већ просто коришћењем C кода који сам манипулише листама аргумената и прерађује их. Овакав начин дефинисања функција уме да буде незахвалан и да се исходи у коду у којем свака функције врши мноштво веома сличних провера над својим улазом, које сложене процедуре (оне које се дефинишу коришћењем самог језика) не морају да чине, јер су већ уздигнуте на ниво апстракције остатка језика, што олакшава њихово писање, управо услед овога, остатак стандардних функције је описан у ...
\item инит.ћ: Ћирилисп фајлу који садржи код за многе стандардне процедуре и макрое који су захтевани у језику. Овај код интерпретер прво учитава, пре свега другог што чини, и тражи га у стандардној локацији /usr/local/lib/cirilisp/инит.ћ, због овога је битно инсталирати, а не само компајлирати програм, јер се тек током инсталације инит.ћ заиста копира на ово место, и уколико интерпретер не нађе библиотеку на очекиваном месту, одбиће да се покрене.
\item read.c и read.h: омогућавају програму да учитава изразе и испарсира их, описивши Read функцију.
\item eval.c и eval.h: описују Eval и apply функције које служе да би интерпретер могао извршити унесени Ћирилисп код.
\item print.c и print.h: штампање израза, уз помоћ функција овог и претходна два предмета на листи се Ћирилиспов ЧПШП спроводи у дело
\item cirilisp.c: главни део програма, користи процедуре и променљиве свих осталих датотека да би се иницијализовао и дефинисао све стандардне процедуре језика, потом прерадио све командне аргументе и (уколико није одређено да се то не учини) започео ЧПШП.
\end{itemize}
Свеукупно, ове датотеке (заједно са Makefile рецептом који је коришћен за спровођење компилације програма) чине 2979 линија кода (1885 значајних линија), и сачињавају потпуни садржај овог матурског рада (изузев овог самог документа).
\subsection{Дефиниције вредности и окружења}
Као што је већ поменуто, у util.h се налазе дефиниције објеката података који се налазе у овом језику, спроведени у дело путем C-структура и унија, такође су ту и дефинисане структуре лексичких окружења која су веома важне за евалуацију израза у језику.
Објекти су меморијска представа израза, која је дефинисана на следећи начин:
\begin{verbatim}
typedef enum
{
nilObject,
unspecifiedObject,
consObject,
numberObject,
symbolObject,
procedureObject,
boolObject,
stringObject,
charObject,
errorObject,
EOFObject
} dataType;
struct object
{
dataType type;
union
{
char *err;
char *symbol;
char *string;
wchar_t character;
cons *consCell;
number num;
procedure *proc;
int boolean;
} value;
};
\end{verbatim}
Неки типови су дефинисани као само показивачи на већ постојећи тип података у C-у попут булских типова, симбола и карактера, док су други имплементирани у сопственим структурама, као бројеви, конс ћелије и процедуре, следе дефиниције управо тих типова података:
\begin{verbatim}
struct number
{
numType type;
union
{
long double real;
struct
{
long long int numerator;
long long int denominator;
} fraction;
} value;
};
struct cons
{
object car;
object cdr;
};
struct procedure
{
procType type;
int isSpecialForm;
union
{
struct
{
object (*pointer)(object);
} builtin;
struct
{
object args;
object body;
env environment;
} compound;
} value;
};
\end{verbatim}
Бројеви су дефинисани кроз унију ,,реалних'' и ,,разломачких'' бројева, где су први представљени кроз C-ов \texttt{long double} тип података, док су други представљени уз структуру у којој је описан бројилац и именилац.
Конс ћелије су само једноставна структура која у себи садржи два објекта: \texttt{сар} и \texttt{сдр}.
Процедуре се могу сагледати као да постоје у 4 категорије, на основу два критеријума, да ли су сложене или уграђене (што одређује пребројиви тип \texttt{procType}), и да ли су специјалне форме или не (што одређује булеан \texttt{isSpecialForm}). \texttt{Eval} функција ће их третирати другачије у зависности од њихове категорије (више о томе у одељку \ref{Eval}), уколико су примитивне тј. ургађене, њихова дефиниција се састоји од показивача на C функцију уз помоћ које су дефинисани, уколико су сложене, њихова дефиниција се састоји од три дела, листе аргумената, листе наредби (тело процедуре) и окружења дефиниције процедуре.
Ове структуре су се често мењале током развоја програма, а приступање њиховим члановима има тежњу да постане нечитљиви хаос. На пример, само да бисте приступили бројиоцу разломка који је други члан неке листе направили бисте израз као:
\begin{verbatim}
lista.value.consCell->cdr.value.consCell->car.value.number.numerator
\end{verbatim}
Управо због ова два разлога написани су C макрои који омогућавају да се претерано дугачак низ приступа изнад изрази краћим и елегантнијим:
\begin{verbatim}
NUM_NUMER(CAR(CDR(lista)))
\end{verbatim}
Окружења су још један битан део онога што чини извршавање Ћирилисп процедура могућим, Ћирилиспов модел евалуације је налик Скимовом моделу окружења. Окружења садрже све дефиниције променљивих које су доступне у датом тренутку, окружења су у стварности показивачи на структуру звану рам, ова структура садржи табелу променљивих и родитељско окружење (показивач на родитељски рам).
\begin{verbatim}
struct entry
{
char *name;
object value;
struct entry *left;
struct entry *right;
};
struct frame
{
entry *table;
env enclosing;
};
typedef frame *env;
\end{verbatim}
Као што се види у дефиницији \texttt{entry} структуре, табела променљивих је дефинисана као бинарно стабло претраге, разлог зашто је изабрана ова структура је то да се уз веома просту дефиницију (наиме 4 реда у коду горе) и са веома једноставним функцијама за рад са истом, добија веома брза структура са просечном сложеношћу претраге од $O(log(n))$.
\begin{verbatim}
entry **findEntry(entry **current, char *symbol)
{
int cond;
if (*current == NULL)
{
return current;
}
else if ((cond = strcmp(symbol, (*current)->name)) < 0)
{
return findEntry(&(*current)->left, symbol);
}
else if (cond > 0)
{
return findEntry(&(*current)->right, symbol);
}
else
{
return current;
}
}
object referVariable(char *symbol, env currentEnv)
{
entry **e = findEntry(&currentEnv->table, symbol);
if (*e == NULL)
{
if (currentEnv->enclosing == NULL)
{
SIGERR(unrecognizedSymbolError);
}
else
{
return referVariable(symbol,
currentEnv->enclosing);
}
}
else
{
return copyObject((*e)->value);
}
}
\end{verbatim}
Претрага вредности променљиве унутар неког окружења се врши на веома једноставан начин. Прво се име променљиве претражује у табели тог окружења бинарном претрагом, уколико још није нађен, тражи се у родитељском окружењу, где се исти процес претраге понавља, док се не дође до глобалног окружења (које нема родитеља тј. његово родитељско окружење је \texttt{NULL}).
Лексички опсег унутар сложених процедура се постиже тако што се током дефиниције сваке сложене процедуре (нпр. помоћу ламбда израза) осим приписивања листе аргумената и тела извршавања такође приписује окружење, као што се видело у коду који дефинише процедуру. Ово окружење је управо оно у којем је дефинисана сама процедура. Када се ова процедура потом изврши, она ствара сопствено окружење, ово окружење преузима ,,окружење дефиниције'' као свог родитеља (више о овоме у одељку \ref{Apply}).
\section{\texttt{Eval} функција}\label{Eval}
Оно што чини извршавање било каквог Лисп кода могућим јесте процес евалуације, где се улазни израз трансформише у излазни. \texttt{Eval} функција узима улазни објекат (тј. израз) и окружење у чијем контексту се израз евалуира, а потом враћа нови израз као резултат, сходно томе, овако изгледа њен прототип:
\begin{verbatim}
object Eval(object input, env currentEnv);
\end{verbatim}
Ово је код \texttt{Eval} процедуре:
\begin{verbatim}
object Eval(object input, env currentEnv)
{
object result;
if (TYPE(input) == symbolObject)
{
result = referVariable(SYM(input), currentEnv);
}
else if (TYPE(input) == consObject)
{
if (!properList(input))
{
deleteObject(input);
SIGERR(improperListError);
}
else
{
int regularEvalOrder = 1;
CAR(input) = Eval(CAR(input), currentEnv);
if (TYPE(CAR(input)) == errorObject)
{
result = copyObject(CAR(input));
deleteObject(input);
CPYERR(ERR(result));
}
if (TYPE(CAR(input)) == procedureObject &&
PROC_SPECIAL(CAR(input)))
{
regularEvalOrder = 0;
}
object *currentCell = &CDR(input);
int noErrors = 1;
if (regularEvalOrder)
{
while (TYPE(*currentCell) != nilObject)
{
CAR(*currentCell) =
Eval(CAR(*currentCell),
currentEnv);
if (TYPE(CAR(*currentCell)) ==
errorObject)
{
noErrors = 0;
object err = copyObject(
CAR(*currentCell));
deleteObject(input);
CPYERR(ERR(err));
break;
}
currentCell = &CDR(*currentCell);
}
}
if (noErrors)
{
result = apply(CAR(input), CDR(input),
currentEnv);
}
if (TYPE(CAR(input)) == procedureObject &&
PROC_SPECIAL(CAR(input)) &&
PROC_TYPE(CAR(input)) == compoundProc)
{
result = Eval(result, currentEnv);
}
}
}
else
{
result = copyObject(input);
}
deleteObject(input);
return result;
}
\end{verbatim}
\texttt{Eval} се може сагледати као да поседује 4 могућности евалуације у зависности од улазног објекта:
\begin{enumerate}
\item Објекат је симбол који се вероватно односи на неку променљиву, претражи променљиву и врати је као резултат евалуације.
\item Објекат је конс ћелија која није правилна листа. Врати грешку јер се једино правилне листе могу евалуирати.
\item Објекат је конс ћелија која јесте правилна листа, евалуирај први члан да би одредио да ли је у питању примењива процедура (и која је категорија процедуре), на основу тога, евалуирај (или немој) остатак листе који чине аргументи. Након што су све нужне евалуације спроведене, примени први члан листе на остатак листе.
\item Објекат је самоевалуирајуће врсте, врати неизмењену копију објекта.
\end{enumerate}
Као што се у коду функције види и као што је поменуто у претходној листи, \texttt{Eval} мора прво да евалуира први члан листе одвојено од осталих да би проценио да ли је пожељно евалуирати остатак листе или не, јер уколико је први члан тј. процедура која се примењује специјална форма евалуација остатка листе је потпуно непожељна, већ је поменуто да Ћирилисп процедуре могу поделити у 4 категорије и да их \texttt{Eval} третира другачије у односу на њихову категорију, таблица \ref{ProcTable} показује и како.
\begin{table}[H]
\caption{Начини евалуације у зависности од типа процедуре} \label{ProcTable}
\begin{center}
\begin{tabular}{ | l | l | m{4.5cm} | }
\hline
& Регуларна & Специјална \\ \hline
Уграђена & \multirow{2}{4.5cm}{Редом евалуирај сваки аргумент у листи, потом примени процедуру на
аргументе.} & Одмах примени процедуру, дозволи јој да сама евалуира аргументе својим редоследом. \\ \cline{1-1}\cline{3-3}
Сложена & & Одмах примени процедуру (макро-проширење) потом евалуирај произведен израз поново. \\ \hline
\end{tabular}
\end{center}
\end{table}
\subsection{Примена процедура помоћу \texttt{apply}}\label{Apply}
Једном када је одлучено да ли су аргументи евалуирани или не, битно је применити саму процедуру на њих. Овај посао извршава \texttt{apply} функција, којој и сам корисник може приступити помоћу \texttt{примени} процедуре поменуте у одељку \ref{Procedures}. Овако она изгледа:
\begin{verbatim}
object apply(object procedure, object parameters, env currentEnv)
{
object result;
if (TYPE(procedure) != procedureObject)
{
SIGERR(notApplicableError);
}
if (PROC_TYPE(procedure) == builtinProc)
{
object(*f)() = PROC_BUILTIN(procedure);
if (f == defineInt || f == lambdaInt || f == ifInt ||
f == applyInt)
{
result = f(parameters, currentEnv);
}
else
{
result = f(parameters);
}
return result;
}
if (++currentRecursionDepth > maxRecursionDepth)
{
--currentRecursionDepth;
SIGERR(maxRecursionDepthError);
}
object args = copyObject(PROC_COMP_ARGS(procedure));
object body = copyObject(PROC_COMP_BODY(procedure));
env definitionEnv = PROC_COMP_ENV(procedure);
env procEnv = createEnvironment(definitionEnv);
if (!bindArgs(parameters, args, procEnv))
{
deleteObject(args);
deleteObject(body);
removeEnvironment(procEnv);
SIGERR(argumentNumberError);
}
object *currentSubProc = &body;
while (TYPE(*currentSubProc) != nilObject)
{
CAR(*currentSubProc) = Eval(CAR(*currentSubProc), procEnv);
if (TYPE(CDR(*currentSubProc)) == nilObject)
{
result = copyObject(CAR(*currentSubProc));
}
currentSubProc = &CDR(*currentSubProc);
}
deleteObject(args);
deleteObject(body);
removeEnvironment(procEnv);
--currentRecursionDepth;
return result;
}
\end{verbatim}
Као што се види \texttt{apply} различито примењује примитивне и сложене процедуре.
Уколико је примитивна, она просто на листу аргумената примењује C функцију која ,,испод хаубе'' представља Ћирилисп процедуру (неке специјалне форме добијају и окружење као додатни аргумент, јер морају да саме врше евалуацију, а за евалуацију је потребно постојање неког окружења).
Међутим уколико је процедура сложена, и сама примена постаје ,,сложенија'', у случају сложене процедуре, прво је потребно означити да је дубина рекурзије повећана, ово служи да би се ограничила количина меморије као и број меморијских блокова које програм заузима, што смањује шансе да ,,пукне'' неочекивано услед великих трошења меморије током мноштва рекурзивних позива. Разлог зашто је ово нужно је чињеница да примена сложене процедуре извршава велики број захтева за расподелу меморије, уколико се врше многоструке примене путем \texttt{apply}-а (најчешће током рекузије велике дубине), овај процес може да изазове немогућу расподелу меморије и тиме грешку у сегментацији и неизбежно ,,пуцање'' програма.
Након што је утврђено да није пређена максимална дубина рекурзије за програм, преузимају се аргументи и тело функције, ради каснијег извршавања, потом се ствара ново окружење у којем је окружење где је процедура почетно дефинисана наметнуто као родитељско, након чега се аргументи везују за вредности путем функције \texttt{bindArgs}. Након овога се сваки израз у телу редом евалуира и задњи се враћа као вредност читаве процедуре.
\subsection{Примитивне процедуре}
Код примитивних процедура чини скоро трећину читавог кода програма. Сачињен је од десетина дефиниција C функција које све врше сличне провере за валидности аргумената. Услед овога, анализа истих би постала репетитивна изузетно брзо, међутим битно је испитати пар ових функција да би се видело како оне генерално морају да изгледају. Испитајмо једну функцију која је дефинисана као стандардна форма, и једну која је дефинисана као специјална, \texttt{ако} и \texttt{сдр}.
\begin{verbatim}
object ifInt(object parameters, env currentEnv)
{
object predicate, result;
switch (listLength(parameters))
{
case 2:
predicate = Eval(copyObject(CAR(parameters)),
currentEnv);
if (TYPE(predicate) == errorObject)
{
CPYERR(ERR(predicate));
}
if (TYPE(predicate) == boolObject &&
BOOL(predicate) == 0)
{
TYPE(result) = nilObject;
}
else
{
result = Eval(copyObject(
CAR(CDR(parameters))),
currentEnv);
}
if (TYPE(result) == errorObject)
{
CPYERR(ERR(result));
}
break;
case 3:
predicate = Eval(copyObject(CAR(parameters)),
currentEnv);
if (TYPE(predicate) == errorObject)
{
CPYERR(ERR(predicate));
}
if (TYPE(predicate) == boolObject &&
BOOL(predicate) == 0)
{
result = Eval(copyObject(CAR(CDR(CDR(parameters)))),
currentEnv);
}
else
{
result = Eval(copyObject(
CAR(CDR(parameters))),
currentEnv);
}
if (TYPE(result) == errorObject)
{
CPYERR(ERR(result));
}
break;
default:
SIGERR(argumentNumberError);
}
return result;
}
object cellElement(object parameters, int part)
/* враћа car или cdr (сар или сдр) датог конс објекта у зависности од тога да
* ли је part 0 или нешто друго (1) */
{
if (listLength(parameters) != 1)
{
SIGERR(argumentNumberError);
}
if (TYPE(CAR(parameters)) != consObject)
{
SIGERR(typeError);
}
return !part ? copyObject(CAR(CAR(parameters))) :
copyObject(CDR(CAR(parameters)));
}
object cdrInt(object parameters)
{
return cellElement(parameters, 1);
}
\end{verbatim}
Оба типа функције захтевају неки облик провере над својом листом аргумената, да би било сигурно да је листа дозвољене дужине, \texttt{ако} може да има само 2 или 3 аргумента, а \texttt{сдр} може да има само 1. Међутим очигледно је да \texttt{ако} мора да врши и неке ствари које \texttt{сдр} не мора, делом зато што је сдр релативно проста процедура, али и делом зато што је \texttt{ако} специјална форма, што значи да она сама мора да врши евалуацију својих аргумената, провери да ли је дата евалуација уродила грешком и уколико јесте, пријави је.
\section{\texttt{Read} функција}
\texttt{Read} је једна од битнијих и сложенијих функција коришћених у овом програму, њена сврха је очигледно да прихата и парсира изразе од стране корисника. Она има сопствени алгоритам учитавања помоћу којег се парсирање врши. (Овај алгоритам је претежно дизајниран по узору на Комон Лиспов стандардни алгоритам за \texttt{read} функцију, онакав какав је описан у књизи \textcite{steele84}).
Следе типови карактера које \texttt{read} функција распознаје:
Размаци: <space>, <form-feed>, <newline>, <carriage return>, <horizontal tab>, <vertical tab>.
Саставни карактери: а, б, в, г, д, ђ, е, ж, з, и, ј, к, л, љ, м, н, њ, о, п, р, с, т, ћ, у, ф, х, ц, ч, џ, ш, А, Б, В, Г, Д, Ђ, Е, Ж, З, И, Ј, К, Л, Љ, М, Н, Њ, О, П, Р, С, Т, Ћ, У, Ф, Х, Ц, Ч, Џ, Ш, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, !, \$, \&, *, +, -, ., /, <, =, >, ?, @, \^{}, \textbackslash, \_, \~{}.
Макро-карактери: ", \#, ', (, ), ;, `.
Избегнути карактер: |.
Алгоритам учитавања:
\begin{enumerate}
\item Одштампај промпт који назначава кориснику да је програм спреман да унесе израз (у ЧПШП окружењу ово је уобичајно "Ћ> ")
\item \label{start}Учитај карактер, док је карактер ,,размак'', игнориши га и учитај следећи. Уколико није, одреди правилну радњу у зависности од типова карактера учитаног, она ће бити у једном од корака од \ref{firsttype} до \ref{lasttype}.
\item \label{firsttype}Ако је достигнут крај фајла, врати ,,крај фајла објекат'' као своју вредност.
\item Ако је карактер макро-карактер, изврши функцију макро-карактера. (функције макро-карактера могу да саме учитавају карактере са улаза, или чак да рекурзивно позивају \texttt{read} функцију), уколико функција врати ,,неодређен објекат'' (уколико је учитан коментар) врати се на корак \ref{start}.
\item \label{tokenchar}Ако је карактер саставан или избегнут, врати га назад на улаз и почни да учитаваш токен, скоком на корак \ref{begintoken}.
\item \label{lasttype}Ако карактер није ниједан од могућих типова, пријави грешку.
\item \label{begintoken}Сада се започиње учитавање токена, први учитан карактер (поменут у кораку \ref{tokenchar}) се додаје у токен, уколико је тај карактер саставан, започети обичан токен скоком на корак \ref{regtoken}, уколико је избегнут, започети проширени токен скоком на корак \ref{extoken}.
\item \label{regtoken}Сада се учитава обичан токен, уноси се карактер, уколико је унесени карактер саставан, додати га у токен (као мало слово ако је унесено велико) и поновити овај корак, уколико није вратити га на улаз и скочити на корак \ref{endtoken}
\item \label{extoken}Сада се учитава продужени токен, уноси се карактер, уколико је карактер избегнут, скочити на корак 9, уколико је крај фајла, пријавити грешку, у супротном, додати га у токен и поновити овај корак.
\item \label{endtoken}Завршено је учитавање токена, проверити да ли је дати токен валидан број, ако јесте преобратити га у објекат тог типа, ако није преобратити га у симбол.
\end{enumerate}
Овај читав алгоритам је практично извршен у следећем коду:
\begin{verbatim}
object Read(char *prompt, FILE *stream)
{
printf("%s", prompt);
wint_t c;
object result;
while (iswspace(c = scanwc(stream)))
;
if (c == WEOF)
{
TYPE(result) = EOFObject;
}
else if (isMacroC(c))
{
result = macroFunction(c, stream);
}
else if (isEscape(c) || isConstituent(c))
{
unscanwc(c, stream);
result = getToken(stream);
}
else
{
SIGERR(invalidCharacterError);
}
if (TYPE(result) == unspecifiedObject)
{
return Read("", stream);
}
else
{
return result;
}
}
object getToken(FILE *stream)
{
object result;
wchar_t *buffer = getBuffer();
wint_t c;
int i = 0;
c = scanwc(stream);
buffer[0] = towlower(c);
if (isEscape(c))
{
while ((c = scanwc(stream)) != WEOF && !isEscape(c))
{
if (i + 2 >= bufferSize)
{
increaseBuffer();
}
buffer[++i] = c;
}
buffer[++i] = c;
buffer[++i] = L'\0';
if (c == WEOF)
{
SIGERR(unexpectedEOFError);
}
}
else
{
while (isConstituent(c = scanwc(stream)))
{
if (i + 1 >= bufferSize)
{
increaseBuffer();
}
buffer[++i] = towlower(c);
}
unscanwc(c, stream);
buffer[++i] = L'\0';
}
int n = wcstombs(NULL, buffer, 0) + 1;
char *s = malloc(n * sizeof(char));
wcstombs(s, buffer, n);
regex_t regNumberFrac, regNumberReal;
regcomp(&regNumberFrac, "^[-+]?[[:digit:]]+(/[[:digit:]]+)?$",
REG_EXTENDED);
regcomp(&regNumberReal, "^[-+]?[[:digit:]]*,[[:digit:]]+$",
REG_EXTENDED);
const int nmatches = 1;
regmatch_t a[nmatches];
if (!regexec(&regNumberFrac, s, nmatches, a, 0))
{
TYPE(result) = numberObject;
NUM_TYPE(result) = fractionNum;
NUM_NUMER(result) = atoll(s);
char *tmp;
NUM_DENOM(result) = (tmp = strchr(s, '/')) == NULL ?
1 : atoll(tmp + 1);
result = shortenFractionNum(result);
}
else if (!regexec(&regNumberReal, s, nmatches, a, 0))
{
TYPE(result) = numberObject;
NUM_TYPE(result) = realNum;
NUM_REAL(result) = strtold(s, NULL);
}
else
{
TYPE(result) = symbolObject;
SYM(result) = malloc((strlen(s) + 1) * sizeof(char));
strcpy(SYM(result), s);
}
regfree(&regNumberFrac);
regfree(&regNumberReal);
return result;
}
\end{verbatim}
\subsection{Макро карактери}
Макро карактери су посебна група карактера за коју Read функција извршава одређену функцију када их учита и потом враћа њихов резултат, у стварности, сви ови карактери немају засебне функције већ постоји једна функција (у горњем коду искоришћена \texttt{macroFunction}) која извршава различите ствари у зависности од тога који карактер је узет као макро карактер.
\begin{itemize}
\item ; : учитавај и игнориши карактере до краја реда или фајла, врати неодређен објекат (служи за учитавања коментара)
\item ( : започни учитавање листе итеративним позивањем \texttt{Read} функције док се не учита супротни ,,)'', када је листа учитана, уколико је предзадњи токен \texttt{.}, претвори листу у жељену окрњену листу
\item ) : пријави грешку за неспарену заграду
\item ' или ` : учитај нови израз и створи израз облика \texttt{(навод учитани-израз)}
\item " : учитавај ниску до следећег неизбегнутог "{ }карактера.
\item \# Учитај следећи карактер, и врати ,,диспеч'' функцију на основу истог као резултат
\subitem л или Л : врати као вредност булеан \texttt{\#л}
\subitem и или И : врати као вредност булеан \texttt{\#и}
\subitem \ : учитај следећи карактер или ниску која представља карактер, и врати вредност типа карактер која одговара унесеном (\texttt{\#\textbackslashк} или \texttt{\#\textbackslash{}новиред})
\subitem | : учитавај у игнориши карактере све до супротне \texttt{|\#} секвенце, врати неодређен објекат (учитавање коментара)
\subitem ! : учитавај у игнориши карактере све до краја реда или фајла (коментар ,,шабенг'' облика, коришћен да се Ћирилисп фајл учини извршивим)
\end{itemize}
\section{Главни део програма}
Све функције наведене у претходним одељцима омогућавају интерпретеру да извршава валидан Ћирилисп код. Међутим и оне саме се једино могу покренути услед чињенице да су позване од стране главне функције у програму, овај одељак излаже и описује код \texttt{cirilisp.c} датотеке, у којој се налази главна функција, као и још неке важне помоћне функције.
\begin{verbatim}
extern int eofStatus;
int load(char *pathname)
{
FILE *stream;
if ((stream = fopen(pathname, "r")) == NULL)
{
return 0;
}
object exp;
while (TYPE(exp = Eval(Read("", stream), globalEnv)) !=
EOFObject)
{
if (TYPE(exp) == errorObject)
{
Print(exp);
}
else
{
deleteObject(exp);
}
}
eofStatus = 0;
fclose(stream);
return 1;
}
\end{verbatim}
Прва важна функција је \texttt{load}, она отвара постојећи Ћирилисп фајл, и учитава низ израза из њега, док не дође до краја када \texttt{Read} функција иначе обележава да је крај достигнут помоћу глобалне променљиве \texttt{eofStatus}, овде \texttt{load} враћа променљиву на почетну вредност да би учитавање следећег фајла могло да се настави неометано.
Пре него што се изврши учитавање фајлова и/или ЧПШП, потребно је иницијализовати програм, овај посао извршава \texttt{init} функција.
\begin{verbatim}
void init()
{
if (setlocale(LC_ALL, "sr_RS.utf8") == NULL)
{
fprintf(stderr, "lokal programa se nije mogao podesiti na\
\"sr_RS.utf8\", proverite da li ste ga osposobili na vasem sistemu\n");
exit(0);
}
/* Омогућава библиотекама коришћеним у интерпретеру да протумаче српску
* ћирилицу */
globalEnv = createEnvironment(NULL);
addSymbolInternal("листа?", &listQInt, 0);
addSymbolInternal("број?", &numberQInt, 0);
addSymbolInternal("<", &lessInt, 0);
addSymbolInternal("/", &divideInt, 0);
addSymbolInternal("+", &addInt, 0);
addSymbolInternal("=", &eqNumInt, 0);
addSymbolInternal(">", &greaterInt, 0);
addSymbolInternal("-", &subtractInt, 0);
addSymbolInternal("*", &multiplyInt, 0);
addSymbolInternal("ако", &ifInt, 1);
addSymbolInternal("баци", &throwInt, 0);
addSymbolInternal("јед?", &eqvQInt, 0);
addSymbolInternal("булски?", &boolQInt, 0);
addSymbolInternal("бројилац", &numeratorInt, 0);
addSymbolInternal("дужина-ниске", &strLengthInt, 0);
addSymbolInternal("именилац", &denominatorInt, 0);
addSymbolInternal("конс", &consInt, 0);
addSymbolInternal("карактер", &charInt, 0);
addSymbolInternal("карактер?", &charQInt, 0);
addSymbolInternal("конс?", &consQInt, 0);
addSymbolInternal("ламбда", &lambdaInt, 1);
addSymbolInternal("прикажи", &displayInt, 0);
addSymbolInternal("нил?", &nilQInt, 0);
addSymbolInternal("направи-ниску", &makeStrInt, 0);
addSymbolInternal("надовежи", &appendInt, 0);
addSymbolInternal("навод", &quoteInt, 1);
addSymbolInternal("нетачно->тачно", &inexactToExactInt, 0);
addSymbolInternal("опиши", &defineInt, 1);
addSymbolInternal("опиши-складњу", &defineMacroInt, 1);
addSymbolInternal("почни", &beginInt, 0);
addSymbolInternal("ниска?", &stringQInt, 0);
addSymbolInternal("сар", &carInt, 0);
addSymbolInternal("разломак?", &fractionQInt, 0);
addSymbolInternal("процедура?", &procedureQInt, 0);
addSymbolInternal("примени", &applyInt, 0);
addSymbolInternal("реалан?", &realQInt, 0);
addSymbolInternal("тачно->нетачно", &exactToInexactInt, 0);
addSymbolInternal("сдр", &cdrInt, 0);
addSymbolInternal("свежи-ниске", &catInt, 0);
addSymbolInternal("симбол?", &symbolQInt, 0);
addSymbolInternal("читај", &readInt, 0);
addSymbolInternal("штампај", &printInt, 0);
if (!load(DESTDIR "/usr/local/lib/cirilisp/инит.ћ"))
{
fprintf(stderr, "Није пронађена стандардна ЋИРЛИСП библиотека\
\nПрограм се није могао правилно покренути\n");
exit(3);
}
}
\end{verbatim}
Већина ове функције је само везивање примитивних процедура описаних у internals.c за имена у глобалном окружењу. После тога се у очекиваној локацији тражи стандардна Ћирилисп библиотека (инит.ћ)
Коначно можемо описати главну функцију:
\begin{verbatim}
int main(int argc, char **argv)
{
init();
int quitFlag = 0, opt;
while ((opt = getopt(argc, argv, "qvh")) != -1)
{
switch (opt)
{
case 'q':
quitFlag = 1;
break;
case 'v':
printf("Верзија: " VERSION "\n");
exit(0);
break;
case 'h':
printf(help);
exit(0);
break;
default:
fprintf(stderr, "Непозната командна опција");
exit(1);
}
}
while (argv[optind] != NULL)
{
if (!load(argv[optind]))
{
fprintf(stderr, "Није било могуће отворити фајл %s.\n\
Проверите да ли дати фајл заиста постоји\n", argv[optind]);
exit(2);
}
++optind;
}
if (quitFlag)
{
exit(0);
}
printf("Добродошли у ЋИРИЛИСП ЧПШП окружење, верзија: " VERSION "\n");
while (Print(Eval(Read("Ћ> ", stdin), globalEnv)))
;
printf("\nДостигнут крај улазног тока.\nЗбогом и дођите нам опет!\n");
return 0;
}
\end{verbatim}
У њој се \texttt{init} позива пре него што се било шта друго учини, потом се анализирају командни аргументи које је интерпретер примио, и помоћу \texttt{load}-а се учитавају фајлови описани у тим аргументима.
Након, тога (ако није опцијом \texttt{-q} назначено супротно), започиње се ЧПШП у којем корисник може уносити свој код путем командне линије.
Овиме је приведена крају ова детаљна анализа Ћирилисповог кода.
\newpage
\section*{Закључак}
\addcontentsline{toc}{section}{\protect\numberline{}Закључак}
Овај матурски рад је створен у (успешно спроведеној) намери да се створи потпуни и радећи програмски језик у којем је уобичајно писати команде на српској ћирилици, иако је овај језик веома минималан, довољно је комплетан да се може користити за програмирање у веома уском опсегу сврха.
Постоје многи елементи језика које сам желео имплементирати али које није било могуће постићи за ограничено време рада, као комплетна елиминација репних позива (што би омогућило прављење бесконачних петљи у овом језику), употпуњенији и дружељубивији систем за сигнализирање грешака, и још многи елементи.
Међутим, крај овог матурског рада не значи крај Ћирилиспа, готово сигурно ћу наставити рад на овом интерпретеру у слободно време, и веома је могуће да ће ускоро и сам овај рад (који описује Ћирилисп версију 0,9), престати да буде примењив јер ће језик проширити своје способности и могућности.
Уколико вас је као читаоца овај рад инспирисао да пробате да научите Лисп дијалекат, постоји велики број њих који су вам доступни, као Ским, Комон Лисп, Рекет, Емакс Лисп и други. Свака особа може наћи дијалекат који им се највише свиђа. Неки од добрих водича које бих лично препоручио су(у зависности од дијалекта који одаберете): \textcite{sicp} за Ским, \textcite{steele84} за Комон Лисп и \textcite{chassel09} за Емакс Лисп.
\newpage
\section*{Прилог}
\addcontentsline{toc}{section}{\protect\numberline{}Прилог}
Код Ћирилисповог интерпретера је доступан на гит складишту \url{https://gitlab.com/tvrdosrz/cirilisp}. Овај рад описује стање кода за датум 3. март 2019, стога уколико ово читате у каснијем датуму и желите да испитате код какав је описан овде, препоручујем да прегледате историју ,,комитова'' на Ћирилисповом складишту.
\newpage
\renewcommand\refname{Литература}
\printbibliography
\addcontentsline{toc}{section}{\protect\numberline{}Литература}
\listoffigures
\addcontentsline{toc}{section}{\protect\numberline{}Слике}
\newpage
\section*{Биографија матуранта}
\addcontentsline{toc}{section}{\protect\numberline{}Биографија Матуранта}
Ја сам Петар Каприш, ученик гимназије ,,Јован Јовановић Змај'', рођен 11. јула 2019.
\begin{wrapfigure}{r}{0.5\textwidth}
\begin{center}
\includegraphics[width=0.5\linewidth]{../../IBI_7206.JPG}
\end{center}
\caption{Моја слика}
\end{wrapfigure}
Главна интересовања су ми: рачунарство, музика и интернет супкултуре.
Похађао сам основну школу ,,Жарко Зрењанин'' у Зрењанину, где сам првобитно стекао интересовање за математику и рачунарство, у петом разреду сам први пут почео да се бавим програмирањем у C++-у, након тога сам у школској додатној настави почео учити Паскал и уз та два језика сам често ишао на такмичења из програмирања.
Након тога сам почео да истражујем о рачунарству преко интернета, помоћу разних ресурса: веб-сајтова, имеџ-бордова и других онлајн заједница; где је моје интересовање почело да се развија ка езотеричнијим странама рачунарства. Тако сам стекао интересовање за системско програмирање, слободан софтвер, теорију програмских језика и многе друге области рачунарских наука.
Одлучио сам да имплементирам лично дизајниран (али ни у којем смислу оригиналан) програмски језик као свој матурски рад да бих испитао своје способности за програмирање и дизајн софтвера, као и знање о теорији програмских језика и надам се да сам овим радом успео и да их докажем.
\newpage
\noindent
{\Large Датум предаје матурског рада: \noindent\rule{3cm}{0.4pt}
\\[1cm]
Комисија:
\\[0.4cm]
\-\hspace{1.2cm}Председник\hfill{}\noindent\rule{3cm}{0.4pt}\-\hspace{4cm}
\\[0.4cm]
\-\hspace{1.2cm}Испитивач\hfill{}\noindent\rule{3cm}{0.4pt}\-\hspace{4cm}
\\[0.4cm]
\-\hspace{1.2cm}Члан\hfill{}\noindent\rule{3cm}{0.4pt}\-\hspace{4cm}
\\[1.2cm]
Коментар:
\fbox{
\begin{minipage}[t]
{0.7\linewidth}\hfill\vspace{5cm}
\end{minipage}
}
\\[1.2cm]
Датум одбране:\noindent\rule{3cm}{0.4pt}\hfill Оцена \noindent\rule{2.1cm}{0.4pt} (\noindent\rule{0.6cm}{0.4pt})
}
\end{document}