#import "layout.typst": project, indent-par #import "@preview/i-figured:0.1.0" #import "@preview/tablex:0.0.6": tablex, rowspanx, colspanx, cellx #import "utils.typst": * #show: project.with( university: "Latvijas Universitāte", faculty: "Eksakto zinātņu un tehnoloģiju fakultāte", title: [Atmiņas kartīšu lietotne ar domēna specifiskas valodas izmantošanu\ Kvalifikācijas darbs], authors: ( "Jorens Štekeļs, js21283", ), advisor: "prof. Dr. sc. comp. Laila Niedrīte", date: "Rīga 2025", ) #set heading(numbering: none) #pagebreak(weak: true) = Apzīmējumu saraksts #par( first-line-indent: 0cm, [ *Iezīmēšanas valoda (angl. markup)* -- dokumentu formatēšanas sistēma, kas izmanto birkas un citus simbolus, lai noteiktu dokumenta struktūru un izkārtojumu. To izmanto, lai organizētu informāciju un kontrolētu dokumenta izskatu; *Markdown* -- vienkārša un populāra iezīmēšanas valoda, kas galvenokārt paredzēta teksta dokumentu (īpaši tīmekļa lapu) formatēšanai; *Transpilēšana* -- teksta pārvēršana programmas datu struktūrās vai objektos, kas paredzēta kā starpposms starp dažādiem programmēšanas valodas līmeņiem; *Detranspilēšana* -- programmas objektu atgriešana atpakaļ noteiktā teksta formātā vai valodā, saglabājot objekta struktūru un īpašības; *Norāde* -- mainīgais vai datu struktūra, kas satur citu objektu vai vērtību adresi, ļaujot piekļūt šiem objektiem netieši; *Nulles objekts* -- objekts, kas norāda uz tukšu vai neinicializētu stāvokli, piemēram, lai apzīmētu, ka objekts nesatur derīgu vērtību vai ir tukšs; *Vienkāršais teksts* -- teksta dati, kas attēlo simbolus bez grafiskām vai citām struktūrām, piemēram, attēliem vai formatētiem elementiem, un ir viegli lasāms jebkurā teksta redaktorā; *Metadati* -- dati, kas apraksta citus datus, sniedzot informāciju par to īpašībām, piemēram, saturu, izcelsmi un struktūru; *CSV* -- formāts, kas satur vērtības, kas ir atdalīti ar komatiem; *Parsēšana* -- TODO; *Buferis* -- TODO; ], ) #pagebreak(weak: true) = Ievads == Nolūks #indent-par([ Šī darba nolūks ir specificēt lietotnes programmatūras prasības un aprakstīt tās projektējumu, kas tiek aprakstīta un izstrādāta kvalifikācijas darba ietvaros. ]) == Darbības sfēra #indent-par([ // Kas ir atmiņas kartes Atmiņas kartītes (angl. flashcards) ir izplatīts veids, kā skolēni, studenti un citi cilvēki, kas mācās vai studē, iegaumē materiālu. Šādu kartīšu pamatā parasti ir priekša un aizmugure. Priekšā atrodas jautājums vai uzdevums vai cita informācija, kas uzstāda mentālu vai praktisku uzdevumu, iespējams iekļaujot arī mājieni. Aizmugurē atrodas atbildes teksts, kas ar sevi atbild vai parāda pareizu atbildi uz priekšā uzstādītu jautājumu vai uzdevumu. ]) // Mācīšanās process un spaced-repetition Mācīšanās nolūkiem tiek veidotas vairākas atmiņas kartītes uz līdzīgu tēmu, veidojas kartīšu kaudzes. Mācīšanas seansā, tiek vērtēts, cik grūti vai viegli jautājumi vai uzdevumi ir attiecīgi atbildami vai atrisināmi. Balstoties uz grūtību vai iespēju atbildēt, kartītes tiek grupētas noteiktās grupās, kas, savukārt, nosaka intervālu, pēc kura kartīte figurēs nākamā mācīšanās seansā. Piemēram, grupas - ļoti viegli, vidēji, grūti, neizdevās -, kam atbilst 5 dienas, 3 dienas, 1 diena, 0 dienas attiecīgi. Balstoties uz subjektīvu piepūli, atbildot uz jautājumu vai risinot uzdevumu, tiek izvēlēta atbilsoša intervālu grupa, kas nosaka laiku pirms nākamās reizes, kad šī kartīte būs izmantota. Šī mācīšanās metode ir atkārtošana ar intervāliem. Mācīšanās metode vai līdzīgas metodes var uzlabot atcerēšanās līmeni pēc mācīšanās (#link()[Alison Voice and Arran Stirton, 2020]). // Stuff mdemory does Veidojot atmiņas kartītes manuāli tās var pazaudēt, var kļūdīties, sagatavojot to tekstu. Tāpēc ir iespējams kartītes veidot un uzturēt, izmantojot tīmekļa vai lokālo lietotni. Aprakstāmās programmatūras mērķis ir vienkāršot atmiņas kartīšu veidošanu ar iespēju veidot, mainīt un glabāt atmiņas kartītes vienkāršā tekstā un mācījies izmantojot atkārtošanu ar intervāliem. Atmiņas kartītes ir glabātas teksta failos un glabājas direktorijā, kas pilnībā paliek lietotāja pārvaldē. Vienkārša teksta kartīšu glabāšana paredz, ka lietotājs var brīvi pārvietot un mainīt kartīšu saturu, turpinot izmantot programmatūras mācīšanās funkcionalitāti. Uzsvars ir uz pilnīgu atmiņas kartīšu glabāšanas pilnīgu caurspīdīgumu - kartītes glabājas bez slēptas informācijas. Vienkārša teksta izmantošana bez slēptas informācijas atbrīvo lietotāju no atkarības no šīs programmatūras ar iespēju pārslēgties uz citu risinājumu ar dažām teksta izmaiņām. // @Improve: add some text?; // The name Programmatūras produkta nosaukums ir "Mdemory", kas ir darināts nosaukums no "MD" (iezīmēšanas valodas Markdown abreviatūra) un "memory" (angl. atmiņa), kas parāda produkta saistību ar atmiņu. == Saistība ar citiem dokumentiem #indent-par([ Programmatūras prasībus specifikācijas ir izstrādāta, ievērojot LVS 68:1996 "Programmatūras prasību specifikācijas ceļvedis" standarta un LVS 72:1996 "Ieteicamā prakse programmatūras projektējuma aprakstīšanai" standarta prasības. ]) == Pārskats // apraksts: Ievads #indent-par([ Ievadā tiek definēts nolūks darbības sfēra, aprakstīta galvenā programmatūras produkta būtība un mērķi, saistītie dokumenti. ]) Vispārējā apraksta sadaļā tiek aprakstīts esošais stāvoklis, pasūtītājs, produkta perspektīva, galvenās darījumprasības, pieņēmumi un atkarības. Programmatūras prasību specifikācijas sadaļā tiek aprakstīts risinājumā izmantots karītes formāts un ar to saistītas datu struktūras, risinājuma datu plūsmas, funkcijas un risinājuma nefunkcionālās prasības. // @Complete: apraksts -- Programmatūras projektējuma apraksts // @Complete: other things #pagebreak(weak: true) #set heading(numbering: "1.1.") = Vispārējais apraksts == Esošā stāvokļa apraksts // @TODO: go over the thing #indent-par([ "Mdemory" lietotne piedāvā vairāku specializēto atmiņas kartīšu veidu funkcionalitāti un pilnīgu vienkārša teksta atmiņas kartīšu definēšanu un pārvāldību. Vairāki esošie risinājumi piedāvā daļu no minētām iespējām, bet gan nepiedāvā abas iespējas. ]) // Quizlet Daži risinājumi iekļauj vairākus specializētus jautājumu veidus, kā grupēšanas jautājumi, vairāku atbilžu jautājumi un secības jautājumi. Tādu risinājumu piemērs ir #link()["Quizlet"] lietotne. Quizlet importa funkcionalitāte realizē vienkāršu importu, kas visbiežāk ir izmantots ar mērķi pārnest atmiņas kartītes vienas lietotnes ietvaros, nevis veidot kartītes šajā formātā. Quizlet vienkārša teksta imports ir realizēts, izmantojot CSV formātu. // org-drill Diezgan tuvs pēc funckionalitātes mērķiem ir "Emacs" teksta redaktora papildinājums "org-drill". Tas piedāvā vienkārša teksta atmiņas kartīšu veidošanu un glabāšanu ar vienkāršu tekstu. Lietotojot "ord-drill", ir nepieciešams izmantot "Emacs" teksta redaktoru. Programmas funkcionalitāte ir domāta pieredzējošākiem lietotājiem. Risinājums liek uzsvaru uz vienkāršām kartītēm, kas neiekļauj specializētus kartīšu jautājumu veidus (#link()[Paul Sexton]). // @Improve: should I add date here? // # Org-mode functions // Simple: Basic question and answer. // Cloze Deletion: Creates fill-in-the-blank cards where specific terms are // masked and must be recalled. // Multicloze: Allows multiple cloze deletions within the same card, which is // ideal for complex information. Visu jautājumu importa funkcija parasti nav pieejama esošos risinājumos. Pašlaik neviens no esošiem risinājumiem neatbalsta specializēto atmiņas kartīšu veidu importu. "Org-mode" dod iespēju glabāt kartītes vienkāršā teksta failos, taču tas fokusējas uz vienkāršumu ar klasisku kartīšu veidu bez specializētiem jautājumu veidiem. Lielākā daļa risinājumu glabā kartītes savā, pielāgotā, slēptā formātā, kas apgrūtina kartīšu pārvaldību un pārnešanu. == Pasūtītājs #indent-par([ Programmatūras produkts tiek izstrādāts pēc darba autora iniciatīvas kvalifikācijas darba ietvaros. ]) == Produkta perspektīva #indent-par([ Risinājums ir patstāvīga darbvirsmas lietotne. Lietotnes izstrādē ir izmantotas lietotnes saskarnes bibliotēkas un programmēšanas valodas standarta bibliotēka. Risinājums lasīs un izmainīs teksta failus no lietotāja norādītās direktorijas. ]) == Risinājuma lietotāji // TODO Spelling #indent-par([ Risinājumam ir viena lietotāju grupa, kam ir pieejamas visas risinājuma iespējas un funkcijas. Risinājuma augsta līmeņa datu plūsmas ir attēlota 0. līmeņa datu plūsmas diagrammā (DPD; skat. @fig:dpd0 attēls). Lietotāju galvenā datu apmaiņa ir atmiņas kartīšu dati un dati par saskarni ar kartītēm. DPD ir iekļautas trīs datu glabātuves: failu glabātuve, pagaidu kartīšu glabātuve un konfigurācijas glabātuve. ]) Failu glabātuve ir vieta, kur tiek glabātas kartītes vienkārša teskta formātā. Tas ir direktorijs ar noteikta formāta failiem (skat. attēlu @fig:conceptual-er un tabulas 2.1-2.6). Šo glabātuvi pārvalda lietotājs -- brīvi pievieno, modificē un pārstrukturē direktorijā esošos failus un apakšdirektorijus. Pagaidu kartīšu glabātuve glabā apskatāmās kartītes un visus izmainītos atmiņas failiem atbilstošus kartīšu sarakstus. Glabātuve ir spēkā risinājuma programmas darba laikā. Sākot darbu ar risinājumu, kartītes tiek ielādētas no failu glabātuves pagaidu glabātuvē. Darbu beidzot vai pēc lietotāja izvēles, kartītes tiek saglabātas failos, no kuriem katītes tika ielādētas. Izmaiņas pagaidu glabātuves datos notiek ar risinājuma saskarni. Konfigurācijas glabātuve ir fails(/-i), kuros tiek glabātas konfigurācijas vērtības, ko uzstāda un saglabā lietotājs. Konfigurācijas glabātuvi pārvalda lietotājs -- ir iespējams mainīt konfigurācijas faila saturu ar nosacījumu, ka faila formāts paliek korekts un ievadītās vertības ir korektas un ir pieejamā vērtību diapazonā. #figure( caption: "0.līmeņa datu plūsmas diagramma", placement: auto, image("img/DPD0.svg"), ) == Darījumprasības + Kartīšu failu apstrāde, izmantojot definēto domēna specifikas valodu; + Kartīšu saraksta veidošana; + Definēto jautājumu veidu tekstuāla atspoguļošana; + Definēto jautājumu veidu interaktīva mācīšanas funkcija; + Mācīšanās ar atkārtošanu ar intervāliem; + Kartīšu pievienošana, modifikācija un dzēšana sarakstā; + Vairāku kartīšu failu apstrāde direktorijā; + Kartīšu saglabāšana no saraksta. == Vispārējie ierobežojumi + Risinājums nepieslēdzas internetam; + Risinājums ir lokāla darbvirsmas programma; + Atmiņas kartītes un kofigurācija ir glabāta vienkārša teksta failos, ko lietotājs var modificēt pilnībā; == Pieņēmumi un atkarības + Lietotnes var tikt uzbūvēta darbam uz Windows vai Linux sistēmām, uz kurām ir pieejamas risinājumā izmantotas saskarnes bibliotēkas; + Lietotājs atbild par atmiņas kartīšu failu glabāšanu; #pagebreak(weak: true) = Programmatūras prasību specifikācija // Šajā sadaļā tiks aprakstīts TODO?? == Konceptuālais entitāšu-relāciju modelis // TODO spelling Risinājuma glabātie dati iekļauj atmiņas kartīšu un konfigurācijas datus, kas tiek glabāti vienkāršos failos. Risinājums neizmantos datubāzi datu glabāšanai. Risinājumā entitātes ir attēlotas konceptuālā entitāšu-relāciju modelī (skat. attēlu @fig:conceptual-er). Atmiņas kartītes tiek organizētas noteikta paplašinājuma failos, kas satur kartītes noteiktā formātā. Kartītes ir dažādu veidu jautājumi, kas ietver sākuma -- jautājuma tekstu un vairākus saistītus jautājuma atbildes elementus. Ir atbildes jautājumi, kas satur vairākus vienlīdzīgus elementus, un ir grupas jautājumi, kas satur vairākas grupas ar vairākiem tai piederošiem elementiem. Atbildes elementiem piemīt veids, katrs atšķiras ar to, kā elements sākas, ko nosaka sākuma simboli. Grupas elementi sastāv no teksta un pieder kādai grupai, kam ir nosaukums. Modelī ir iekļauti metadati, kā pēdējās mācīšanās laiks, jautājuma pārtraukuma laiks un faila nosaukums. Pēdējās mācīšanās laiks tiek izmantots intervālu metodē, lai reģistrētu laiku un pielietot intervālu metodi turpmākās mācību reizēs. Jautājuma pārtraukuma laiks nosaka, cik ilgam laikam jāpaiet, lai jautājums atkal būtu iekļauts intervālu metodes mācīšanās. Faila nosaukums tiek izmantots kā kartīšu saraksta nosaukums -- to var izmanot, lai nosauktu failā esošās kartītes, piemēram, to tēmu vai nolūku. Papildus datetime TODO #figure( caption: "Konceptuālais entitāšu-relāciju modelis", placement: auto, image("img/conceptualER.svg"), ) == Funkcionālās prasības #let ref_df01 = [(skat. tabulu 2.1)] #let ref_df02 = [(skat. tabulu 2.2)] #let ref_df03 = [(skat. tabulu 2.3)] #let ref_df04 = [(skat. tabulu 2.4)] #let ref_df05 = [(skat. tabulu 2.5)] #let ref_df06 = [(skat. tabulu 2.6)] #let ref_ds01 = [(skat. tabulu 2.7)] #let ref_ds02 = [(skat. tabulu 2.8)] #let ref_ds03 = [(skat. tabulu 2.9)] #let ref_ds04 = [(skat. tabulu 2.10)] #let ref_pr01 = [(skat. tabulu 2.12)] #let ref_pr02 = [(skat. tabulu 2.13)] #let ref_ab01 = [(skat. tabulu 2.14)] #let ref_ab02 = [(skat. tabulu 2.15)] #let ref_ab03 = [(skat. tabulu 2.16)] #let ref_ab04 = [(skat. tabulu 2.17)] #let ref_ab05 = [(skat. tabulu 2.18)] #let ref_ab06 = [(skat. tabulu 2.19)] #let ref_ab07 = [(skat. tabulu 2.20)] #let ref_ab08 = [(skat. tabulu 2.21)] #let ref_ma01 = [(skat. tabulu 2.22)] #let ref_ma02 = [(skat. tabulu 2.23)] #let ref_ma03 = [(skat. tabulu 2.24)] #let ref_ma04 = [(skat. tabulu 2.25)] #let ref_ma05 = [(skat. tabulu 2.26)] #let ref_kf01 = [(skat. tabulu 2.37)] #let ref_kf02 = [(skat. tabulu 2.38)] #let ref_kf03 = [(skat. tabulu 2.39)] #let questionSource = [ simbolu virkne, kas satur jautājumus formātā, kas atbilst datu formātiem #link()[DF02], #link()[DF03], #link()[DF04], #link()[DF05] (skat. tabulas 2.2.-2.5.) ]; #let questionList = [saraksts ar objektiem, kas atbilst datu struktūrai #link()[DS01] #ref_ds01]; #let question = [objekts, kas atbilst datu struktūrai #link()[DS01] #ref_ds01]; #let pageList = [saraksts ar objektiem, kas atbilst datu struktūrai #link()[DS02] #ref_ds02]; #let datetime = [simbola virkne formātā, kas atbilst datu struktūrai #link()[DS03] #ref_ds03]; #let uint = [vesels pozitīvs skaitlis]; #let mdemFilePath = [simbolu virkne, kas atbilst failam ar noteitu paplašinājumu]; #let listWithCorrectFlags = [Izvēlēto atbilžu simbolu virkņu saraksts ar pareizuma karodziņu vērtībām]; #let configValue = [viens no: skaitlis, simbolu virkne, karodziņš]; #let changeIndicator = [Saskarnē parādās indikators, ka atmiņas kartīšu fails tika izmainīts]; === Augsta līmeņa kartīšu valodas specifikācija #indent-par([ Šajā apakšsadaļā tiks aprakstīta domēna specifiska valodas formāta vienības, kas ir sastāvdaļas no valodas, kas ir izmantota risinājumā, definējot, glabājot un rediģējot atmiņas kartītes. Formāti ir aprakstīti tabulās 2.1.-2.6. ]) ==== Kartīšu veidi #format-table( "Jautājuma sākums", "DF01", [ Kartīšu veidošanai vienkāršā tekstā tiek izmantota vienkārša domēna specifiska valoda. Ir 4 jautājumu veidi, kuru struktūra ir līdzīga ar dažām atšķirībām. Katram jautājumam ir sākums, kam var būt skaitlis, kas apzīmē intervālu stundās, pēc kura tas būs atkārtoti izmantots. Priekša ir sākas ar "-" un beidzas ar ">". ], [ ``` - Ka sauc Latvijas galvaspilsētu? > - [12.5] Ka sauc Igaunijas galvaspilsētu? > ``` ] ) #format-table( "Vienkāršs atbilžu jautājums", "DF02", [ Vienkāršs atbilžu jautājums ir jautājums, kam ir viena vai vairākas pareizas atbildes, katra atbilde sākas ar "-". ], [ ```markdown - Ka sauc Lietuvas galvas pilsētu? > - Viļņa - Nosauciet Baltijas valstis > - Latvija - Igaunija - Lietuva ``` ] ) #format-table( "Izvēles jautājums", "DF03", [ Izvēles jautājums ir jautājums, kam ir viens vai vairākas pareizas atbildes, pareizās atbildes sākas ar "+", nepareizās -- ar "-". ], [ ```markdown - Kuras no valstīm ir Baltijas valstis > + Latvija - Rumānija + Lietuva - Spānija ``` ] ) #format-table( "Secības jautājums", "DF04", [ Secības jautājums -- jautājums ar vairākām vērtībām, kam ir noteikta secība, vērtības sākas ar "-^". ], [ ``` - Sakārtojiet zinātniskās metodes soļus pareizā secībā > -^ Uzdod jautājumu -^ Izvirza hipotēzi -^ Veic eksperimentu -^ Analizē datus ``` ] ) #format-table( "Grupēšanas jautājums", "DF05", [ Grupēšanas jautājums -- jautājums, kur ir vairākas grupas, kurām pieder nulle vai vairāki elementi. Grupas sākas ar "-" un beidzas ar ":". Grupas elementi sākas ar "-". ], [ ``` - Savienojiet planētas ar to īpašībām > - Zeme: - Ir dzīve - Jupiters: - Lielākā planēta - Ir gredzeni - Venēra: - Merkurs: - Mazākā planēta - Saturns: - Ir gredzeni - Neptūns: - Tālākā no Saules - Ir gredzeni ``` ] ) // @TODO: replace every jautājums -> kartīte except where its really needed. #format-table( "Kartītes faila pirmteksts", "DF06", [ Kartītes pirmteksta failā var tikt iekļauti vairāki dažādu veidu jautājumi. Sākumā var būt datuma un laika simbolu virkne, kas atbilst datu struktūrai #link()[DS02] #ref_ds02. ], [ ``` 21.10.2024 12:54 - Savienojiet dzīvniekus ar to īpašībām > - Kaķis: - Murrā - Suns: - Ir uzticīgs - Draudzīgs - Zivs: - Varde: - Dzīvo gan ūdenī, gan uz sauszemes - Zirgs: - Ir ātrs - Putns: - Spēj lidot - Dzīvo kokā - Sakārtojiet cilvēka dzīves posmus pareizā secībā > -^ Bērnība -^ Pusaudža gadi -^ Pieaugšana -^ Vecumdienas ``` ] ) === Formātu un datu struktūru specifikācija #indent-par([ Šajā apakšsadaļā tiks apskatīti ievades un izvades vērtību formāti, kas tiks izmantoti funkciju aprakstos. Formāti ir aprakstīti tabulās 2.7.-2.9. ]) #io-table( "Kartīšu datu struktūra", "DS01", [ Jautājumu datu struktūra satur informāciju par atmiņas kartīti ar papildus informāciju, kas atbils noteiktam jautājuma veidam. Pamatā jautājumam ir: - Teksts -- simbola virkne; - Ilgums, cik kartīte nebūs izmantota atkal intervālu metodē -- pozitīvs daļu skaitlis; Papildus informācija iedalās vairākos veidos. + Vairāku elementu veids: - Veids -- uzskaitījums, kurš no - vairāku variantu, secības, vienkāršs - jautājums ir; - Saraksts ar objektiem, kas sastāv no simbolu virknes un korektuma karodziņu; + Grupas veids: - Saraksts ar objektiem, kas sastāv no: - Nosaukuma -- simbolu virkne; - Elementiem -- saraksts ar simbolu virknēm. ], ) #io-table( "Lappuses datu struktūra", "DS02", [ Lappuse ir ir saraksts ar referencēm uz vairākiem jautājuma objektiem, kam ir noteikts lappuses skaitlis. // TODO: elaborate more ], ) #io-table( "Datuma un laika simbolu virkne", "DS03", [ Datuma un laika simbolu virknē tiek izmantots sekojošs formāts - [dienas_numurs].[mēneša_numurs].[gads] [stunda]:[minūte] -, piemēram, "13.05.2024 12:02". ], ) #io-table( "Transpilēšanas kļūdas datu struktūra", "DS04", [ Datu struktūra satur kļūdas aprakstu, kas notika transpilēšanas laikā. Papildus ir pievienots rindas numurs un kolonnas numurs. ], ) === Funkciju sadalījums moduļos #indent-par([ Risinājuma moduļi ar to saistītām datu plūsmām un datu glabātuvēm ir attēlots 1\. līmeņa DPD (skat. @fig:dpd1 attēls). Funkciju sadalījums moduļos ir apraksīts sadalījuma tabulā (skat. @tbl:function_table tabula). ]) #figure( caption: "1. līmeņa datu plūsmas diagramma", placement: auto, image("img/DPD1.svg"), ) #pagebreak(weak: true) #figure( caption: "Funkciju sadalījums pa moduļiem", kind: table, tablex( columns: 3, /* --- header --- */ [*Modulis*], [*Funkcija*], [*Identifikators*], /* -------------- */ rowspanx(8)[Atmiņas bāzes modulis], [Kartīšu faila ielāde], [#link()[AB01]], [Kartīšu saraksta lappušu izveidošana], [#link()[AB02]], [Atmiņas bāzes lappuses iegūšana], [#link()[AB03]], [Kartīšu pievienošana], [#link()[AB04]], [Kartītes pirmteksta iegūšana], [#link()[AB05]], // @Fix: rediģēšana everywhere [Kartītes rediģēšana], [#link()[AB06]], [Kartītes dzēšana], [#link()[AB07]], [Kartītes faila saglabāšana], [#link()[AB08]], rowspanx(5)[Mācīšanās modulis], [Nākamās kartītes noteikšana], [#link()[MA01]], [Izvēles kartītes pārbaude], [#link()[MA02]], [Kārtošanas kartītes pārbaude], [#link()[MA03]], [Grupēšanas kartītes pārbaude], [#link()[MA04]], [Intervāla grupas atbildes sniegšana], [#link()[MA05]], // un meta datu saglabāšana rowspanx(3)[Konfigurācijas modulis], [Konfigurācijas vērtības iegūšana], [#link()[KF01]], [Konfigurācijas vērtību iegūšana], [#link()[KF02]], [Konfigurācijas vērtības izmaiņa], [#link()[KF03]], ), ) === Kopīgas procedūras #indent-par([ Vairākas prasībās definētās funkcijas izmanto kopīgas procedūras, kas tiek definētas tabulās 2.12. un 2.13. ]) #procedure-table( "Teksta jautājumu transpilēšana", "PR01", [ Procedūra apstrādā simbolu virkni, iegūstot jautājumu datu struktūru objektus. Ir sagaidāms, ka simbolu virkne atbilst specificētai valodai. Ja simbolu virkne valodai neatbilst, tiek izvadīta kļūda. Simbolu virkne tiek apstrādāta vairākos posmos. ], [ + Jautājumu pirmteksts -- #questionSource\; ], [ + Tiek nolasīti simboli, sagrupējot to tekstvienībās; + Tiek veikta virspusēja gramatikas pārbaude; + Ja tiek sastapta tekstvienība, kas nav sagaidāma kādā kontekstā, beidz apstrādi, izvadē pievienojot kļūdu ar tās pozīciju; + Tiek nolasīti jautājumi, nosakot to veidu pēc to struktūras: + Jautājuma sākums; + Veidojot jautājumus tiek pielasīts arī intervāla laiks, ja tāds ir; + Pēc jautājuma sākuma ir viens vai vairākas atbildes jeb apakšpunkti; + Vairāki apakšpunkti ar "-" -- atbilžu jautājums; + Vairāki apakšpunkti ar "+" -- izvēles jautājums; + Vairāki apakšpunkti ar "-^" -- secības jautājums; + Vairāki apakšpunkti, kas beidzas ar ":", kam seko apakšpunkti ar "-" (iespējams tos nenorādīt); + Jautājumu veidi ir nosakāmi bez tiešas vieda norādīšanas; + Ja nolasīšanas laikā parādās nesagaidīta tekstvienība, beidz apstrādi, izvadē pievienojot kļūdu ar tās pozīciju; ], [ + Saraksts ar jautājumiem -- #questionList\; + Kļūda ar tekstu un tās pozīciju saturā -- paziņojuma objekts, kas atbilst datu struktūrai #link()[DS04] #ref_ds04\; ], ) #procedure-table( "Teksta jautājumu detranspilēšana", "PR02", [ Procedūra apstrādā jautājumu sarakstu un izveido tam atbilstošu simbolu virkni, kas atbilst specificētai valodai. Papildus sākumā tiek pievienots datums un laiks, ja tas tiek padots, kas ir daļa no metadatiem. ], [ + Jautājumu saraksts -- #questionList\; + Datums un laiks -- #datetime\; ], [ + Iegūst konfigurācijas vērtības, kas nosaka cik garš ir tabulācijas simbols un kāds ir maksimāls rindiņas platums pirms tiek veidots jaunās rindas simbols; + Ja datums un laiks ir padots, pievieno datumu un laiku atbilstoši formātam; + Katram jautājumam veic sekojošo: + Pievieno jautājuma sākumu ar intervāla skaitli, ja tāds ir; + Pievieno apakšpunktus atbilstoši tā veidam; + Vairāki apakšpunkti ar "-" -- atbilžu jautājums; + Vairāki apakšpunkti ar "+" -- izvēles jautājums; + Vairāki apakšpunkti ar "-^" -- izvēles jautājums; + Vairāki apakšpunkti, kas beidzas ar ":", kam seko apakšpunkti ar "-"; + Veidojot jautājumus ievēro iegūtās konfigurācijas vērtības; ], [ + Jautājumu pirmeksts -- #questionSource\; ], ) === Atmiņas bāzes modulis #indent-par([ Atmiņas bāze ir direktorijs ar atmiņas kartīšu failiem. Modulis atbild par mijiedarbību ar atmiņas šiem failiem un atmiņas kartīšu prezentēšanu. Šī moduļa funkcijas ir izmantotas atmiņu kartīšu saraksta funkcionalitātei. Funkcijas mijiedarbojas ar pagaidu glabātuvē esošām atmiņas kartītēm un failu glabātuves failiem, kas satur atmiņu kartīšu informāciju. Ar funkcijām saistītas datu plūsmas ir attēlotas moduļa 2. līmeņa DPD (skat. @fig:abdpd2 attēlu). Moduļa funkcijas tiek definētas tabulās 2.14.-2.21. ]) #figure( caption: "Atmiņas bāzes moduļa 2.līmeņa datu plūsmas diagramma", placement: auto, image("img/ABDPD2.svg"), ) #pagebreak(weak:true) #function-table( "Kartīšu faila ielāde", "AB01", [ Funkcija apstrādā faila saturu. Iegūstot objektus ar jautājumu dati un ar to saistītiem metadatiem. Ieejas failam jāatbilst valodas specifikācijai. ], [ + Faila atrašanās ceļš -- #mdemFilePath\; ], [ + Faila saturs tiek ielādēts atmiņā: + Ja faila ielāde ir neveiksmīga, parāda 1. paziņojumu. + Tiek nolasīts datums un laiks no faila sākuma, ja tāds eksistē: + Ja datuma un laika formāts nav korekts, parāda 2. paziņojumu ar atbilstošu iemeslu un kļūdas pazīmi, beidz apstrādi. + Tiek iegūti jautājuma objekti, izmantojot procedūru #link()[PR01] #ref_pr01\; + Ja radās kļūda, parāda 2. paziņojumu ar kļūdas informāciju, beidz apstrādi. + Ja kādā no apstrādes posmiem radās kļūda, parāda 2. paziņojumu ar atbilstošu iemeslu un kļūdas pozīciju, beidz apstrādi. ], [ + Saraksts ar jautājumiem -- #questionList\; + Faila reģistrēts datums un laiks -- #datetime\; ], [ + Ielādes kļūda: neizdevās atvērt failu; + Ielādes kļūda: [kļūdas iemesls] [kļūdas rinda]:[kļūdas kolonna]; ], ) #pagebreak(weak:true) #function-table( "Kartīšu saraksta lappušu izveidošana", "AB02", [ Funkcija izveido vairākas lappuses, sadalot atmiņā esošus jautājumu objektus intervālos. Lappušu izmērs ir noteikts ar konfigurācijas parametrus. ], [ + Jautājumu saraksts -- #questionList\; ], [ + No konfigurācijas tiek iegūts jautājumu skaits lappusē, un rādāmo lappušu pogu skaits; + Jautājumi tiek sadalīti lappusēs, veidojot lappušu objektus, katrā tiek ievietotas references uz jautājumiem. Lappušu pogu skaits atbilst konfigurācijas vērtībai; ], [ + Lappušu saraksts -- #pageList\; ], [ Funkcijai nav paziņojumu. ], ) #function-table( "Atmiņas bāzes lappuses iegūšana", "AB03", [ Funkcija iegūst jautājumu objektu intervālu no pagaidu glabātuves. ], [ + Lappuses numurs -- #uint\; ], [ + No pagaidu atmiņas kartīšu glabātuves tiek iegūtas atmiņas kartīšu lappuses; + Tiek meklēts lappušu objekts ar norādīto numuru; + Ja lappuse tiek atrasta, saistītie atmiņas kartīšu objekti tiek atgriezti; + Ja tāda lappuse netiek atrasta, parāda 1. paziņojumu; ], [ + Jautājumu saraksts -- #questionList\; + Datums un laiks -- #datetime\; // @Maybe: Add 'metadata' into DPD (datums un laiks); ], [ + Pieprasītā lappuse neeksistē; ], ) // @TODO: for all things that save someth somewhere, say that in the Izvade #function-table( "Kartīšu pievienošana", "AB04", [ Funkcija pieņem lietotāja ievadīto tekstu un pārveido to jautājumu objektos, kas pievieno tos jau esošiem jautājumiem. ], [ + Kartītes pirmteksts -- #questionSource\; ], [ + Ja ievadīts teksts, parāda 1. paziņojumu, beidz apstrādi; + Apstrādā simbolu virkni, izmantojot procedūru #link()[PR01] #ref_pr01\; + Ja tiek izvadīta kļūda, parāda 2. paziņojumu ar kļūdas informāciju, beidz apstrādi; ], [ Izvades jautājuma objekts tiek atjaunots atbilstošam ierakstu jautājumu pagaidu glabātuvē. #changeIndicator\. + Jautājumu saraksts -- #questionList\; ], [ + Nav ievadīts neviens jautājums; + Ielādes kļūda: [kļūdas iemesls] [kļūdas rinda]:[kļūdas kolonna]; ], ) #function-table( "Kartītes pirmteksta iegūšana", "AB05", [ Funkcija iegūst kartītes pirmtekstu, kas atbilst valodas specifikācijai. Funkcija ir specializēts procedūras izmantošanas gadījums, kurā tiek detranspilēts tikai viens jautājums. Funkcija ir izmantota rediģēšanas iespējai. ], [ + Jautājums -- #question\; ], [ + Izmantojot procedūru #link()[PR01] #ref_pr01, jautājums tiek detranspilēts pirmtekstā\; ], [ + Kartītes pirmteksts -- #questionSource\; ], [ Funkcijai nav paziņojumu. Saskarnē parādās indikators, ka atmiņas kartīšu fails tika izmainīts. ], ) #function-table( "Kartītes rediģēšana", "AB06", [ Funkcija izmaina esošo jautājumu, pēc to pirmteksta, kas atbilst valodas specifikācijai. ], [ + Norāde uz jautājumu -- #question\; + Jautājuma pirmteksts -- #questionSource\; ], [ + Ja ievades teksts ir tukšs, parāda 1. paziņojumu, beidz apstrādi; + Izmantojot procedūru #link()[PR01] #ref_pr01, tiek iegūts jautājuma objekts no sniegtā pirmteksta; + Ja tiek izvadīta kļūda, parāda 1. paziņojumu ar atbilstošu kļūdas informāciju, beidz apstrādi; + Padotais jautājuma objekts tiek atjaunots ar iegūta objekta datiem; ], [ Izvades jautājuma objekts tiek atjaunots atbilstošam ierakstu jautājumu pagaidu glabātuvē. #changeIndicator\. + Izmainīts jautājums -- #question\; ], [ + Nav ievadīts neviens jautājums; + Rediģēšanas kļūda: [kļūdas iemesls] [kļūdas rinda]:[kļūdas kolonna]; ], ) #function-table( "Kartītes dzēšana", "AB07", [ Funkcija izdēš kartīti no pagaidu glabātuves. ], [ + Norāde uz jautājumu -- #question\; ], [ + Jautājums tiek sameklēts un izdzēsts; ], [ Izvades jautājuma objekts tiek atjaunots atbilstošam ierakstu jautājumu pagaidu glabātuvē. #changeIndicator\. ], [ Funkcijai nav paziņojumu. ], ) #function-table( "Kartītes faila saglabāšana", "AB08", [ Funkcija saglabā kartīšu jautājumus un metadatus tam atbilstošā failā, pārveidojot tos valodai atbilstošā formātā. ], [ + Faila atrašanās ceļš -- #mdemFilePath\; + Jautājumu saraksts -- #questionList\; + Datums un laiks -- #datetime\; ], [ + Iegūst pirmtekstu padotajiem objektiem, izmantojot procedūru #link()[PR02] #ref_pr02\; + Ja tiek izvadīta kļūda, parāda 2. paziņojumu ar kļūdas informāciju, beidz apstrādi; + Izveido vai atver failu padotā ceļā; + Ja atvēršana vai izveidošana neizdevās, parāda 1. paziņojumu; + Pievienot iegūto saturu failam un saglabā to; ], [ Fails tiks atjaunots failu sistēmā. Saskarnē pazūd indikators, ka atmiņas kartīšu fails tika izmainīts. + Fails ar kartītes informāciju, kas atbilst datu formātam #link()[DF06] #ref_df06\; ], [ + Neizdevās atvērt vai izveidot atmiņas kartīšu failu; + Rediģēšanas kļūda: [kļūdas iemesls] [kļūdas rinda]:[kļūdas kolonna]; ], ) === Mācīšanās modulis #indent-par([ Mācīšanās risinājuma kontekstā ir kartīšu prezentēšana ar interaktīvu atbildes funkcionalitāti, atbilstoši atmiņas kartītes tipam. Šī moduļa funkcijas ir izmantotas mācīšanās loga funkcionalitātei. Funkcijas ietver darbības ar pagaidu glabātuvē esošām atmiņas kartītēm. Ar funkcijām saistītas datu plūsmas ir attēlotas moduļa 2. līmeņa DPD (skat. @fig:madpd2 attēlu). Moduļa funkcijas tiek definētas tabulās 2.22.-2.26. ]) #figure( caption: "Mācīšanās moduļa 2.līmeņa datu plūsmas diagramma", placement: auto, image("img/MADPD2.svg"), ) #pagebreak(weak: true) #function-table( "Nākamās kartītes noteikšana", "MA01", [ Funkcija nosaka nākamo kartīti, kas tiks parādīta lietotājam mācīšanas procesā. Kartīšu secību nosaka mācīšanās algoritms. ], [ + Norāde uz pašreizējo jautājums -- #question\; + Algoritms -- viens no vērtībām: "fizisks", "nejaušs", "intervālu"\; ], [ + Ja pagaidu kartīšu glabātuvē nav nevienas kartītes, beidz apstrādi, neatgriežot kartīti; + Ja padotais algoritms ir "fizisks"; + Ja pašreizējā kartīte ir padota, izvēlās nākamo kartīti starp pagaidu kartīšu glabātuves; + Citādi izvēlas pirmo kartīti no pagaidu kartīšu glabātuves; + Ja padotais algoritms ir "nejaušs"; + Izvēlas nākamo kartīti nejaušā kārtībā starp pagaidu kartīšu glabātuves, izņemot pašžreizējo jautājumu; + Ja padotais algoritms ir "intervālu"; + No pagaidu kartīšu glabātuves izvēlas tikai to, kur nav norādīts intervāls vai intervāls ir 0, izņemot pašžreizējo jautājumu; + No šī saraksta izvēlas nākamo kartīti nejaušā kārtībā; ], [ // Add null explanation + Nākamās kartītes objekts (iespējams nulles objekts) -- #question\; ], [ Funkcijai nav paziņojumu. ], ) #function-table( "Izvēles kartītes pārbaude", "MA02", [ Veic jautājuma atbildes pārbaudi izvēles kartītes jautājuma veidam. ], [ + Izvēlēto atbilžu simbolu virkņu saraksts ar pazīmi, vai atbilde ir izvēlēta; + Norāde uz izvēles kartītes objektu -- #question; ], [ + Izskata abus sarakstus paralēli; + Ja atbildes un jautājuma objektu izvēles elementa pazīmes sakrīt (piemēram, "pareizs" un "pareizs"); + Atbilžu elementam uzstāda pareizuma pazīmi kā "pareizs"; + Citādi uzstāda atbildes pareizuma atbildi kā "nepareizs"; ], [ + #listWithCorrectFlags\; ], [ Funkcijai nav paziņojumu. ], ) #function-table( "Kārtošanas kartītes pārbaude", "MA03", [ Veic jautājuma atbildes pārbaudi kārtošanas kartītes jautājuma veidam. ], [ + Atbilžu simbolu virkņu saraksts; + Norāde uz kārtošanas kartītes objektu -- #question; ], [ + Izskata abus sarakstus paralēli; + Ja atbildes un jautājuma objektu izvēles elementa sakrīt, atbilžu elementam uzstāda pareizuma pazīmi kā "pareizs"; + Citādi uzstāda atbildes pareizuma atbildi kā "nepareizs"; ], [ + #listWithCorrectFlags\; ], [ Funkcijai nav paziņojumu. ], ) #function-table( "Grupēšanas kartītes pārbaude", "MA04", [ Veic jautājuma atbildes pārbaudi grupēšanas kartītes jautājuma veidam. ], [ + Negrupēto elementu simbolu virkņu saraksts; + Grupētu simbolu virkņu sarakstu saraksts; + Norāde uz grupēšanas kartītes objektu -- #question; ], [ + Katram nesagrupētam elementam uzstāda pareizuma pazīmi uz "nepareizs"; + Katru grupai atbilžu un jautājuma objekta saraksta grupu izskata paralēli; + Elementiem, kas sakrīt abos sarakstos, atbilžu sarakstā uzstāda pareizuma pazīmi uz "pareizs"; + Elementiem, kas nav jautājuma atbildes grupā pareizuma pazīmi uz "nepareizs"; ], [ + Negrupēto elementu simbolu virkņu saraksts ar pareizuma karodziņu vērtībām; + Grupētu simbolu virkņu sarakstu saraksts ar pareizuma karodziņu vērtībām; ], [ Funkcijai nav paziņojumu. ], ) #function-table( "Intervāla grupas atbildes sniegšana", "MA05", [ Izmaina jautājuma objektu atbilstoši izvēlētam grūtības vai izpildījuma vērtējumam veicot kartītes atbildi. Atbilstošie intervāli tiek iegūti no konfigurācijas. ], [ + Intervāla atbilde -- viens no: "nav atbildēts", "grūti", "vidēji", "viegli", "ļoti viegli"; + Jautājums -- #question\; ], [ + No konfigurācijas glabātuves tiek iegūts intervāls, kas atbilst sniegtai atbildei; + Jautājumam tiek uzstādīts intervāls; ], [ + Izmainīts jautājums -- #question\; ], [ Funkcijai nav paziņojumu. ], ) === Konfigurācijas modulis #indent-par([ Konfigurācijas modulis nodrošina parametru apstrādi, ko var personalizēt saistībā ar mācīšanās procesu, prezentāciju uc. Šī moduļa funkcijas ir izmantotas konfigurējot vērtības, kas tiek glabātas konfigurācijas failā un tiek izmantotas risinājuma funkcionalitātei piemīt konfigurējamas vērtības. Ar funkcijām saistītas datu plūsmas ir attēlotas moduļa 2. līmeņa DPD (skat. @fig:kfdpd2 attēlu). Moduļa funkcijas tiek definētas tabulās 2.27.-2.29. ]) #figure( caption: "Konfigurācijas moduļa 2.līmeņa datu plūsmas diagramma", placement: auto, image("img/KFDPD2.svg"), ) #function-table( "Konfigurācijas vērtības iegūšana", "KF01", [ Funkcijas iegūst vienu definēto konfigurācijas vērtību. ], [ + Vērtības atslēga -- simbolu virkne; ], [ + Ja konfigurācija vēl nebija ielādēta, tiek ielādēts konfigurācijas fails; + Ielādētā konfigurācijā tiek sameklēts konfigurācijas vērības ieraksts; ], [ + Konfigurācijas vērtība -- #configValue\; ], [ + Konfigurācijas fails nav korekts, nevar nolasīt konfigurāciju; ], ) #function-table( "Konfigurācijas vērtību iegūšana", "KF02", [ Funkcija iegūst visas definētās konfigurācijas vērtības. ], [ + Vērtības atslēga -- simbolu virkne; ], [ + Ja konfigurācija vēl nebija ielādēta, tiek ielādēts konfigurācijas fails; + Ielādētā konfigurācijā tiek iegūtas vērtības; ], [ + Konfigurācijas vērtību vārdnīca -- konfigurācijas vērtības atslēga uz #configValue\; ], [ + Konfigurācijas fails nav korekts, nevar nolasīt konfigurāciju; ], ) #function-table( "Konfigurācijas vērtības izmaiņa", "KF03", [ Funkcija izmaina vērtību no definētām konfigurācijas vērtībām. ], [ + Vērtības atslēga -- simbolu virkne; + Konfigurācijas vērtība -- #configValue\; ], [ + Ja konfigurācija vēl nebija ielādēta, tiek ielādēts konfigurācijas fails; + Konfigurācijas vērtība ar ievadīto atslēgu tiek izmainītu uz ievadīto konfigurācijas vērtību; ], [ + Izmainīta konfigurācijas faila saturs; ], [ + Konfigurācijas fails nav korekts, nevar rediģēt konfigurāciju; ], ) == Nefunkcionālās prasības Šajā sadaļā tiks apskatītas risinājuma nefunkcionālās prasības. === Veikspējas prasības Uz dinamisko veikspēju ir sekojošas prasības: + Failu transpilēšanas ātrums ir vismaz 50000 rindas sekundē; + Failu detranspilēšanas (saglabāšanas) ātrums ir vismaz 50000 rindas sekundē; === Atribūti ==== Lietojamība Uz risinājuma lietojamību ir sekojošas prasības: + Visiem datiem jābūt maināmiem un redzamiem no vienkārša teksta failiem; + Bieži izmantojamām darbībām jābūt atbilstošiem tastatūras taustiņu īsceļiem; // @Complete: add some additional points ==== Pirmkoda īpašības un uzturamība Pirmkodam jābūt izstrādātam ar sekojošiem nosacījumiem: + Risinājuma pirmkods ir realizēts galvenokārt procedurālā stilā; + Transpilatora funkcionalitātes testu pārklājums ir 90%; ==== Pārnesamība #indent-par([ Uz pārnesamību risinājumam ir sekojoša prasība: kartīšu failiem ir jābūt pārnesamiem pārkopējot visu direktoriju uz citu mašīnu. Nosacījums saglabā atgriezenisko savietojamību vienas galvenās versijas ietvaros. ]) ==== Drošība Uz drošību risinājumam ir sekojošas prasības: + Risinājuma pirmkods ir iekļauts kopā ar izpildāmo failu; + Risinājums nemodificē un nelasa failus, izņemot izmantotās bibliotēkas, failu glabātuvi, standarta konfigurācijas vietu; #pagebreak(weak: true) = Programmatūras projektējuma apraksts == Daļējs funkciju projektējums #figure( caption: "Leksiskās analīzes aktivitātes diagramma", placement: none, image("img/lexer_activity.svg"), ) #figure( caption: "Parsēšanas aktivitātes diagramma", placement: none, image("img/parsing_activity.svg"), ) #figure( caption: "Atbildes parsēšanas aktivitātes diagramma", placement: none, image("img/parsing_answer_activity.svg"), ) == Saskarnes projektējums #figure( caption: "Kartīšu saraksta skats", placement: none, image("img/screens/mdem_list.png"), ) #figure( caption: "Atbildes jautājuma skats", placement: auto, image("img/screens/answer_question.png"), ) #figure( caption: "Izvēles jautājuma skats", placement: auto, image("img/screens/multiple_answer_question.png"), ) #figure( caption: "Secības jautājuma skats", placement: auto, image("img/screens/order_question.png"), ) #figure( caption: "Grupēšanas jautājuma skats", placement: auto, image("img/screens/group_question.png"), ) #figure( caption: "Konfigurācijas skats", placement: none, image("img/screens/config.png"), ) #figure( caption: "Apmācības skats", placement: none, image("img/screens/help.png"), ) #pagebreak(weak: true) = Testēšanas dokumentācija #pagebreak(weak: true) = Projekta organizācija #pagebreak(weak: true) = Kvalitātes nodrošināšana #pagebreak(weak: true) = Konfigurācijas pārvaldība #pagebreak(weak: true) = Darbietiplības novērtējums #pagebreak(weak: true) = Secinājumi #pagebreak(weak: true) #heading(numbering: none, "Izmantotā literatūra un avoti") + #hyperlink-source( "Alison Voice and Arran Stirton", [Spaced Repetition: towards more effective learning in STEM (2020)], "https://web.archive.org/web/20171019211402/https://www.loc.gov/standards/datetime/ISO_DIS%208601-1.pdf", std.datetime( year: 2024, month: 10, day: 12, ), ) + #hyperlink-source( "Institūcija \"Latvijas standarts\"", [LVS 68:1996 "Programmatūras prasību specifikācijas ceļvedis". 1996. marts], "", none ) + #hyperlink-source( "Institūcija \"Latvijas standarts\"", [LVS 72:1996 "Ieteicamā prakse programmatūras projektējuma aprakstīšanai". 1996, marts], "", none ) + #hyperlink-source( "Paul Sexton", [Org-drill.el – flashcards and spaced repetition for org-mode], "https://orgmode.org/worg/org-contrib/org-drill.html", std.datetime( year: 2024, month: 10, day: 21, ), ) + #hyperlink-source( "", [What are flashcard sets?], "https://help.quizlet.com/hc/en-us/articles/360032006352-What-are-flashcard-sets", std.datetime( year: 2024, month: 10, day: 21, ), ) + TODO UML 2.5. spec #pagebreak(weak: true) #heading(numbering: none, "Pielikumi") // TODO caption ```cpp std::vector tokens; std::vector buffer; int32_t row; int32_t column; int32_t previousRow; int32_t previousColumn; bool textStarted = false; bool identifierStarted = false; bool sof; /* * TODO */ void trimString(std::string &str, std::string trimChars) { // Noņem kreisās puses simbolus. int padSize = 0; bool pad = false; for (size_t i = 0; i < str.size(); ++i) { for (size_t k = 0; k < trimChars.size(); ++k) { if (str[i] == trimChars[k]) { padSize++; pad = true; break; } } if (!pad) { break; } pad = false; } if (padSize > 0) { str.erase(0, padSize); } // Noņem labās puses simbolus. padSize = 0; pad = false; for (size_t i = str.size(); i-- > 0;) { for (size_t k = 0; k < trimChars.size(); ++k) { if (str[i] == trimChars[k]) { padSize++; pad = true; break; } } if (!pad) { break; } pad = false; } if (padSize > 0) { str.erase(str.end() - padSize, str.end()); } } /* * Izveido tekstvienību, iegūstot to no bufera beigām. * Ja buferī ir teksta vienība pirms tekstvienības, pievieno to pirms beigu * tekstvienības. */ void tokenWithBuffer( TokenType ttype, size_t tokenLen, TokenType textType ) { std::string token(buffer.end() - tokenLen, buffer.end()); if (buffer.size() > tokenLen) { std::string prevFragment(buffer.begin(), buffer.end() - tokenLen); trimString(prevFragment, " \n\t"); if (prevFragment.length() > 0) { tokens.push_back(Token{ textType, prevFragment, previousRow, previousColumn }); } } buffer.clear(); tokens.push_back(Token{ ttype, token, row, column }); previousRow = row; previousColumn = column; buffer.clear(); } /* * Pārveido simbolu virkni tekstvienību sarakstā. * */ Result> tokenizeMdem(const std::string& content) { row = 1; column = 1; previousRow = 1; previousColumn = 1; textStarted = false; tokens.clear(); buffer.clear(); // Beidz, ja satur tikai tukšumus vai neko. if (content.find_first_not_of(" \n\t") == std::string::npos) { return {tokens, ""}; } for (size_t i = 0; i < content.size(); ++i) { char c = content[i]; // Apstrādā īpašos simbolus un tekstu. if (c == '\n') { row += 1; column = 0; } if (c == '\\') { i += 1; if (i < content.size()) { buffer.push_back(content[i]); } continue; } else { buffer.push_back(c); } if (!textStarted) { if (c == '\n') { previousRow += 1; previousColumn = 1; } else if (c == ' ') { previousColumn += 1; } else if (c == '\t') { previousColumn += 4; } else { textStarted = true; } } // Emitē tekstvienības. switch (c) { case '[': { tokenWithBuffer( TokenType::CooldownStart, 1, TokenType::TextFragment ); previousRow = row; previousColumn = column; textStarted = false; identifierStarted = true; } break; case ']': { if (!identifierStarted) { return { tokens, "Cannot end identifier if it is not started", tokens[i].row, tokens[i].column }; } tokenWithBuffer( TokenType::CooldownEnd, 1, TokenType::Cooldown ); previousRow = row; previousColumn = column; textStarted = false; identifierStarted = false; } break; case '-': { tokenWithBuffer( TokenType::ElementDashStart, 1, TokenType::TextFragment ); previousRow = row; previousColumn = column; textStarted = false; } break; case '^': { tokenWithBuffer( TokenType::ElementOrderModifier, 1, TokenType::TextFragment ); previousRow = row; previousColumn = column; textStarted = false; } break; case ':': { tokenWithBuffer( TokenType::MatchGroupEnd, 1, TokenType::TextFragment ); previousRow = row; previousColumn = column; textStarted = false; } break; case '>': { tokenWithBuffer( TokenType::QuestionEnd, 1, TokenType::TextFragment ); previousRow = row; previousColumn = column; textStarted = false; } break; case '+': { tokenWithBuffer( TokenType::ElementPlusStart, 1, TokenType::TextFragment ); previousRow = row; previousColumn = column; textStarted = false; } break; } column += 1; } // Pievieno beigu simbolu, lai atvieglotu parsēšanu. tokenWithBuffer( TokenType::EndOfFile, 0, TokenType::TextFragment ); if (debug) { std::cout << "SECTION: Lexer output:\n"; std::cout << std::format("Token count: {}", tokens.size()) << std::endl; for (const Token& token : tokens) { std::cout << token.toString(); } std::cout << "SECTION END: Lexer output\n"; } return {tokens, ""}; } std::string Token::toString(const TokenType* ttype) { switch (*ttype) { case TokenType::TextFragment: return "text fragment"; case TokenType::QuestionEnd: return "question end symbol"; case TokenType::MatchGroupEnd: return "match group end"; case TokenType::ElementDashStart: return "dash element start"; case TokenType::ElementOrderModifier: return "order element modifier"; case TokenType::ElementPlusStart: return "plus element start"; case TokenType::Cooldown: return "cooldown"; case TokenType::CooldownStart: return "start of cooldown"; case TokenType::CooldownEnd: return "end of cooldown"; case TokenType::StartOfFile: return "start of the file"; case TokenType::EndOfFile: return "end of file"; default: return "unrecognized token"; } } ``` // TODO caption ```cpp typedef std::map> TokenAutomata; TokenAutomata *automata = nullptr; /* * Tekstvienību secības pārejas, kas nosaka, kādā secībā tekstvienības var būt. * */ void initParserAutomata() { automata = new TokenAutomata; (*automata)[TokenType::TextFragment] = { TokenType::QuestionEnd, TokenType::ElementDashStart, TokenType::ElementPlusStart, TokenType::MatchGroupEnd, TokenType::EndOfFile, }; (*automata)[TokenType::MatchGroupEnd] = { TokenType::ElementDashStart }; (*automata)[TokenType::QuestionEnd] = { TokenType::ElementDashStart, TokenType::ElementPlusStart }; (*automata)[TokenType::ElementDashStart] = { TokenType::CooldownStart, TokenType::TextFragment, TokenType::ElementOrderModifier }; (*automata)[TokenType::ElementOrderModifier] = { TokenType::TextFragment }; (*automata)[TokenType::ElementPlusStart] = { TokenType::TextFragment }; (*automata)[TokenType::Cooldown] = { TokenType::CooldownEnd, }; (*automata)[TokenType::CooldownStart] = { TokenType::Cooldown }; (*automata)[TokenType::CooldownEnd] = { TokenType::TextFragment }; (*automata)[TokenType::StartOfFile] = { TokenType::TextFragment, TokenType::ElementDashStart, TokenType::EndOfFile }; (*automata)[TokenType::EndOfFile] = {}; } /* * Pārbauda, vai vai tekstvienību sarakstu akceptē atbilst atbilst valodas * automāts. * */ Result ValidateGrammar(const std::vector& tokens) { if (!automata) { initParserAutomata(); } for (size_t i = 0; i < tokens.size() - 1; ++i) { Token token = tokens[i]; Token nextToken = tokens[i + 1]; if ( std::find( (*automata)[token.tokenType].begin(), (*automata)[token.tokenType].end(), nextToken.tokenType ) == (*automata)[token.tokenType].end()) { auto capitalize = [](const std::string& str) { if (str.empty()) return str; std::string result = str; result[0] = std::towupper(result[0]); return result; }; return { .error=std::format( "Invalid token sequence: {} cannot precede {}", std::string(capitalize(Token::toString(&token.tokenType))), std::string(capitalize(Token::toString(&nextToken.tokenType))) ), .row=token.row, .column=token.column }; } } return {}; } // @Fix: Prevent duplicate group names and questions in ordered question (to // simplify checking in practice). Result parseQuestions(const std::vector& tokens) { auto questions = std::vector(); time_t time = 0; auto makeResult = [&questions, &time](std::string error, Token token) -> Result { return { { questions, time }, error, token.row, token.column }; }; if (tokens.size() == 0) { return makeResult("", Token()); } auto result = ValidateGrammar(tokens); if (result.error.length() > 0) { return makeResult( result.error, Token{.row=result.row, .column=result.column} ); } std::string section; size_t i = 0; if (debug) { std::cout << "SECTION: Parser output:\n"; } auto isInBounds = [tokens](size_t i) { return i < tokens.size() && tokens[i].tokenType != TokenType::EndOfFile; }; if (isInBounds(i) && tokens[i].tokenType == TokenType::TextFragment) { try { auto parseToUTCTime = [](const std::string datetime, std::string format) { std::tm tm = {}; std::istringstream ss(datetime); ss >> std::get_time(&tm, format.c_str()); if (ss.fail()) { throw std::runtime_error("Failed to parse datetime string"); } std::time_t time = timegm(&tm); return time; }; time = parseToUTCTime(tokens[i].content.c_str(), "%d.%m.%Y %H:%M"); } catch (std::exception e) { return makeResult( std::format("cannot parse the time - {}", e.what()), tokens[i] ); } i++; } while (i < tokens.size()) { if (tokens[i].tokenType == TokenType::ElementDashStart) { std::string questionText; std::vector questionElements; double cooldown; bool isOrderQuestion = false; bool isGroupQuestion = false; bool isPlusQuestion = false; // Start element parsing & add to the offset. if (isInBounds(i + 1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) { return makeResult( "cannot have order modifier ('^') in the question definition", tokens[i + 1] ); } if (isInBounds(i + 1) && tokens[i + 1].tokenType == TokenType::CooldownStart) { try { cooldown = std::stod(tokens[i + 2].content); } catch (std::exception e) { return makeResult( "error parsing cooldown", tokens[i + 1] ); } questionText = tokens[i + 4].content; i += 6; } else { cooldown = 0; questionText = tokens[i + 1].content; i += 3; } // Parse elements of a question. while (isInBounds(i)) { // Check question end. if (isInBounds(i + 3) && tokens[i].tokenType == TokenType::ElementDashStart) { // Distance to the possible question end. size_t offset; if (tokens[i + 1].tokenType == TokenType::ElementOrderModifier) { offset = tokens[i + 2].tokenType == TokenType::CooldownStart ? 6 : 3; } else { offset = tokens[i + 1].tokenType == TokenType::CooldownStart ? 5 : 2; } if (isInBounds(i + offset) && tokens[i + offset].tokenType == TokenType::QuestionEnd) { break; } if (offset == 5 && tokens[i + 5].tokenType != TokenType::QuestionEnd) { // Cannot place the identifier on the ordinary element. return makeResult( "Invalid identifier placement", tokens[i] ); } } // Determine element type. bool isDash; bool isGroup = false; bool isOrder = false; if (tokens[i].tokenType == TokenType::ElementDashStart) { isDash = true; } else { isDash = false; isPlusQuestion = true; } if (isInBounds(i+1) && tokens[i + 1].tokenType == TokenType::ElementOrderModifier) { isOrder = true; isOrderQuestion = true; if (!isDash) { return makeResult( "order questions can only be used with dashes ('-')", tokens[i] ); } if (isGroupQuestion) { return makeResult( "question with groups cannot be ordered ('-^' and ':')", tokens[i] ); } if (isInBounds(i + 3) && tokens[i + 3].tokenType == TokenType::MatchGroupEnd) { return makeResult( "cannot have groups in order question('-^' and ':')", tokens[i] ); } } if (isInBounds(i + 2) && tokens[i + 2].tokenType == TokenType::MatchGroupEnd) { isGroup = true; isGroupQuestion = true; if (!isDash) { return makeResult( "group questions can only be used with dashes ('-')", tokens[i] ); } } QuestionElement questionElement; questionElement.isDash = isDash; questionElement.isGroup = isGroup; if (isOrder) { questionElement.content = tokens[i + 2].content; } else { questionElement.content = tokens[i + 1].content; } questionElements.push_back(questionElement); size_t offset = 2; if (isOrder) { offset += 1; } if (isGroup) { offset += 1; } i += offset; } if (questionElements.size() > 0) { if (isGroupQuestion) { auto *question = new GroupQuestion(); question->cooldown = cooldown; question->questionText = questionText; question->section = section; int32_t k = -1; for (size_t i = 0; i < questionElements.size(); ++i) { auto questionElement = questionElements[i]; if (questionElement.isGroup) { ++k; auto group = Group(); group.name = cleanContent(questionElement.content); question->groups.push_back(group); } else { if (k >= 0) { question->groups[k].elements.push_back( cleanContent( questionElement.content ) ); } } } questions.push_back(question); if (debug) { std::cout << question->toString() << "\n"; } } else { auto *question = new MultiElementQuestion(); question->cooldown = cooldown; question->questionText = cleanContent(questionText); question->section = section; for (const auto& elem : questionElements) { Choice choice; choice.answer = cleanContent(elem.content); choice.isCorrect = !elem.isDash; question->choices.push_back(choice); } questions.push_back(question); if (isPlusQuestion) { question->type = MultiElementType::MultiChoice; } else if (isOrderQuestion) { question->type = MultiElementType::Order; } else { question->type = MultiElementType::Regular; } if (debug) { std::cout << question->toString() << "\n"; } } } } else if (tokens[i].tokenType == TokenType::EndOfFile) { if (debug) { std::cout << "File terminated: EndOfFile\n"; } break; } else { return makeResult( "Unexpected token encountered", tokens[i] ); } } if (debug) { std::cout << "SECTION END: Parser output:\n"; } return makeResult( "", Token() ); } std::string MultiElementQuestion::toString() const { std::stringstream ss; for (const auto& choice : choices) { char opener; if (type == MultiElementType::Order) { opener = '^'; } else if (choice.isCorrect) { opener = '+'; } else { opener = '-'; } ss << opener << " " << choice.answer << "; "; } return std::format( "\nsection:{}\nid:{}\n{}\n{}", section, cooldown, questionText, ss.str() ); } std::string GroupQuestion::toString() const { std::stringstream ss; for (auto group: groups) { ss << group.name << ": "; for (auto el: group.elements) { ss << el << ", "; } ss << "; "; } return std::format( "\nsection:{}\nid:{}\n{}\n{}", section, cooldown, questionText, ss.str() ); } ```