Az alkalmazás egy böngészőben futó számrendszer-átváltó, amely egy beírt számról megállapítja, hogy kettes, tízes vagy tizenhatos számrendszerben értelmezhető-e, majd átváltja a kiválasztott cél-számrendszerbe. Az átváltás mellett az oldal lépésről lépésre megmutatja, hogyan fut le a háttérben a JavaScript kód: melyik sor fut éppen, milyen értéket vesznek fel a változók, és hogyan vizsgálja a program a szám karaktereit.
A projekt keretrendszer és külső könyvtár nélkül, tiszta HTML, CSS és JavaScript felhasználásával készült. Az egyetlen külső függőség a dokumentációs oldalak ábráit rajzoló Mermaid könyvtár, amely csak a dokumentációban szerepel, magát az alkalmazást nem érinti.
| Fájl | Szerepe |
|---|---|
index.html | Az alkalmazás felülete: beviteli mező, legördülő menük, eredmény, karaktervizsgáló tábla, kódnézet és vezérlőgombok. |
script.js | A tényleges átváltási logika: nev(), jo(), valt(), atvalt(). |
megjelenites.js | A vizualizáció: kódkiírás, szintaxis-kiemelés, a kód műszerezése, a lépések rögzítése és lejátszása. |
style.css | A teljes oldal megjelenése. |
dokumentacio.html | Ez a dokumentáció. |
tesztek.html | A szoftvertesztelési dokumentáció (tesztesetek, állapotok, hibajegyek). |
.github/workflows/deploy.yml | A tárhelyre történő automatikus feltöltés beállítása. |
A két szkript betöltési sorrendje fontos: az index.html előbb a script.js-t, utána a megjelenites.js-t tölti be, mert a vizualizáció a script.js függvényeinek forráskódját olvassa ki.
1f és 1F ugyanaz), a szám előtti és utáni szóközöket a program levágja.Ha a szám több számrendszerben is értelmezhető (például 101), a program mindegyik értelmezés eredményét kiírja, és jelzi, hogy a forrás-számrendszer kiválasztásával a kétértelműség megszüntethető.
Egy b alapú helyiértékes számrendszerben a számjegyek értéke a pozíciójuktól függ: a jobbról számított k-adik helyen álló (0-tól számozva) d számjegy értéke d · bk. A szám értéke ezeknek az összege.
| Számrendszer | Alap | Megengedett számjegyek | Példa |
|---|---|---|---|
| Kettes (bináris) | 2 | 0 1 | 101 = 1·4 + 0·2 + 1·1 = 5 |
| Tízes (decimális) | 10 | 0–9 | 255 = 2·100 + 5·10 + 5·1 |
| Tizenhatos (hexadecimális) | 16 | 0–9, A–F (A=10 … F=15) | 1F = 1·16 + 15·1 = 31 |
Tízes számrendszerből b alapra a maradékos osztás módszerével lehet váltani: a számot addig osztjuk b-vel, amíg a hányados nulla nem lesz, a maradékokat pedig fordított sorrendben olvassuk össze. Például 31 kettes számrendszerbe:
| Osztás | Hányados | Maradék |
|---|---|---|
| 31 : 2 | 15 | 1 |
| 15 : 2 | 7 | 1 |
| 7 : 2 | 3 | 1 |
| 3 : 2 | 1 | 1 |
| 1 : 2 | 0 | 1 |
Az eredmény alulról felfelé olvasva 11111. A program ezt a műveletet nem kézzel valósítja meg, hanem a JavaScript beépített parseInt() és Number.prototype.toString() függvényeit használja (lásd 4.3).
A kettes számrendszer számjegyei a tízes számjegyeinek, a tízes számjegyek pedig a tizenhatos számjegyeinek részhalmazai. Ezért minden érvényes kettes szám egyben érvényes tízes és tizenhatos szám is, csak más értékkel. A 101 jelentése kettesben 5, tízesben 101, tizenhatosban 257. A beírt karaktersorozatból tehát önmagában nem dönthető el egyértelműen az alap, csak az, hogy melyik alapokban lehetséges. A program ezért nem „tippel”, hanem minden lehetséges értelmezést megmutat.
Források: [1], [2], [3]
script.js)A script.js négy függvényből áll. A kód egyszerű, egymásba ágyazott if szerkezetekkel és számlálós for ciklusokkal dolgozik, minden utasítás külön sorban áll. Ez a formázás nemcsak az olvashatóságot szolgálja, hanem a vizualizáció működésének feltétele is (lásd 5.3).
nev(alap)| Paraméter | Visszatérési érték |
|---|---|
alap: 2, 10 vagy 16 | A számrendszer magyar neve: "kettes", "tízes", "tizenhatos" |
Kizárólag a kiírt szövegek előállítására szolgál. Minden 2-től és 10-től eltérő értékre "tizenhatos"-t ad vissza, ami azért elegendő, mert a program csak ezt a három alapot használja.
jo(szam, alap) – karakterek érvényességének vizsgálata| Paraméter | Leírás |
|---|---|
szam | A vizsgált szöveg, már levágott szóközökkel és nagybetűsítve. |
alap | A vizsgált számrendszer alapja. |
| visszatérés | true, ha minden karakter megengedett az adott alapban, különben false. |
A függvény egy for ciklussal karakterenként halad végig a szövegen. Minden karakterre három feltételt vizsgál, de egyszerre mindig csak az aktuális alaphoz tartozó feltétel lehet igaz:
"0" vagy "1" lehet."0"-nál és nem lehet nagyobb "9"-nél."0"–"9" vagy az "A"–"F" tartományba kell esnie.Az első hibás karakternél a függvény azonnal false-szal visszatér (korai kilépés), a további karaktereket már nem nézi meg. Ha a ciklus hiba nélkül végigér, az eredmény true.
c < "0" összehasonlítás?c >= "A" && c <= "F" pontosan a 65–70 kódú karaktereket engedi át. Az ékezetes betűk (pl. Á) kódja ezeken kívül esik, ezért azokat a függvény helyesen elutasítja. [4], [5]
A futási idő a szöveg hosszával arányos (legfeljebb n lépés egy alapra, automatikus módban legfeljebb 3n). A 24 karakteres beviteli korlát miatt ez legfeljebb 72 karaktervizsgálatot jelent.
valt(szam, alap, cel) – az átváltásvar ertek = parseInt(szam, alap);
if (ertek > Number.MAX_SAFE_INTEGER) {
return "túl nagy szám";
}
return ertek.toString(cel).toUpperCase();
parseInt(szam, alap) a szöveget az adott alapú számként értelmezi és számmá alakítja. A második paraméter (radix) megadása kötelező gyakorlat, különben a böngésző maga próbálja kitalálni az alapot. [6]ertek.toString(cel) a számot a cél alapú szöveges alakra alakítja. A toString kisbetűs hexadecimális számjegyeket ad (ff), ezért a toUpperCase() egységesen nagybetűsít. [7]A JavaScript a számokat 64 bites lebegőpontos (IEEE 754 double) formátumban tárolja. Ebben a formátumban 253 − 1 = 9 007 199 254 740 991 a legnagyobb egész, amely és amely alatt minden egész pontosan ábrázolható; ez a Number.MAX_SAFE_INTEGER értéke. Efölött a szomszédos ábrázolható számok között már 2, 4, 8… távolság van, vagyis bizonyos egészeket a gép csak kerekítve tud tárolni, hibaüzenet nélkül. [8], [9]
A valt() ezért az átalakítás után ellenőrzi az értéket: ha nagyobb a biztonságos határnál, nem ad vissza esetleg hibás eredményt, hanem a „túl nagy szám” szöveget. A határérték pontos viselkedése a tesztdokumentációban ellenőrzésre került: 9007199254740991 még átváltható (1FFFFFFFFFFFFF), 9007199254740992 már „túl nagy szám”.
atvalt() – a vezérlő függvényEz a függvény fut le az Átváltás gomb megnyomásakor. Lépései:
document.getElementById() segítségével. A legördülő menük értéke szöveg, ezért parseInt()-tel számmá alakítja őket. [10]trim()-mel megtisztítja a szélső szóközöktől, és toUpperCase()-szel nagybetűsíti. [11]alap != 0) egyetlen jo() hívással ellenőriz, majd vagy átvált, vagy kiírja, hogy a szám nem az adott számrendszerbe tartozik.[2, 10, 16] tömbön halad végig. Minden érvényes alapnál elvégzi az átváltást, és közben a következő változókat vezeti:
| Változó | Jelentése |
|---|---|
db | Hány alapban érvényes a szám. |
elso | Az első érvényes értelmezés eredménye. |
talalt | Az első érvényes alap (ezt írja ki, ha csak egy találat van). |
egyforma | Igaz marad, amíg minden érvényes értelmezés ugyanazt az eredményt adja. |
szoveg | Az összes értelmezés soronkénti listája a kétértelmű esethez. |
db és az egyforma alapján négy kimenet lehetséges (ez a tesztdokumentáció döntési táblájának alapja):
db = 0: a szám egyik számrendszerben sem érvényes.db = 1: egyértelmű, a program megnevezi a felismert számrendszert.db > 1 és egyforma: például 0 vagy 1, ahol minden értelmezés ugyanazt adja, ezért egy eredmény elegendő.db > 1 és nem egyforma: kétértelmű, minden értelmezés megjelenik.innerHTML tulajdonságon keresztül kerül az oldalra (a sortörések miatt). A felhasználó által beírt szöveg azonban sosem kerül bele közvetlenül, csak a nev() szövegei és a valt() által előállított, kizárólag számjegyekből és A–F betűkből álló eredmények. Így HTML-kód beinjektálására nincs lehetőség. [12]
flowchart TD
classDef lepes fill:#2d2d2d,stroke:#555,color:#e0e0e0
classDef dontes fill:#1e1e1e,stroke:#64ffda,color:#ffffff
classDef kimenet fill:#121212,stroke:#888,color:#64ffda
A(["atvalt() indul"]):::lepes --> B["szam = trim + toUpperCase"]:::lepes
B --> C{"Üres a szam?"}:::dontes
C -- igen --> K0["Nem adtál meg számot!"]:::kimenet
C -- nem --> D{"Ki van választva alap?"}:::dontes
D -- igen --> E{"jo(szam, alap)"}:::dontes
E -- igaz --> K1["Eredmény: valt(szam, alap, cel)"]:::kimenet
E -- hamis --> K2["Ez nem ... szám!"]:::kimenet
D -- nem --> F["Következő alap a 2, 10, 16 listából"]:::lepes
F --> G{"jo(szam, alap)"}:::dontes
G -- igaz --> H["uj = valt(...), db növelése, szoveg bővítése"]:::lepes
G -- hamis --> I{"Van még alap?"}:::dontes
H --> I
I -- igen --> F
I -- nem --> J{"db és egyforma"}:::dontes
J -- "db = 0" --> K3["Nem kettes, tízes vagy tizenhatos"]:::kimenet
J -- "db = 1" --> K4["Felismert számrendszer és eredmény"]:::kimenet
J -- "több, egyforma" --> K5["Egyetlen közös eredmény"]:::kimenet
J -- "több, eltérő" --> K6["Minden értelmezés listája"]:::kimenet
megjelenites.js)A vizualizáció célja, hogy a felhasználó ne csak az eredményt lássa, hanem azt is, hogyan jut el hozzá a program. Az ötlet az oktatásban használt programvizualizációs eszközökből származik; a legismertebb ilyen a Python Tutor, amely a kód lépésenkénti végrehajtását és a változók állapotát jeleníti meg [13]. Az itt alkalmazott megoldás ennek egy jóval egyszerűbb, kizárólag erre az egy programra szabott változata.
A működés két fázisra bontható:
atvalt() függvényt, és minden sor előtt elmenti, hol tart és mik a változók értékei.
sequenceDiagram
actor F as Felhasználó
participant UI as Oldal (index.html)
participant M as megjelenites.js
participant A as Műszerezett atvalt
F->>UI: Átváltás gomb vagy Enter
UI->>M: inditas()
M->>A: felvesz() meghívja futtathato()
loop minden végrehajtott sor
A->>M: __lepes(sor, függvény, változók)
M->>M: rogzit() elmenti a lépést
end
A-->>M: végső eredmény
loop setTimeout időzítővel
M->>UI: sorMutat, valtozokMutat, tablaFrissit
end
M->>UI: befejez() kiírja az eredményt
var forras = nev.toString() + "\n\n" + jo.toString() + "\n\n" + valt.toString() + "\n\n" + atvalt.toString();
var sorok = forras.split("\n");
A JavaScriptben egy függvény toString() metódusa a függvény forráskódját adja vissza szövegként, eredeti formázással együtt [14]. Így a kódnézetben megjelenő kód nem egy kézzel bemásolt példány, hanem pontosan az, ami ténylegesen fut. Ha a script.js módosul, a kódnézet automatikusan követi. A szöveget a program soronként egy tömbbe bontja; a további feldolgozás erre a sorok tömbre épül.
szinez())A színezés két lépésben történik:
&, < és > karaktereket a megfelelő HTML entitásra cseréli. Az & cseréje szándékosan az első, különben a később beszúrt entitások & jelét is lecserélné. Erre azért van szükség, mert a kódban szerepel a < jel (pl. i < szam.length), amit a böngésző egyébként HTML-elem kezdetének venne. [12]| Csoport | Minta | Mit talál | CSS osztály |
|---|---|---|---|
| 1. | ("[^"]*") | Idézőjeles szöveg | str |
| 2. | \b(function|var|if|...)\b | Kulcsszavak, teljes szóként | kw |
| 3. | \b(\d+)\b | Számok | num |
| 4. | \b([a-zA-Z]+)(?=\() | Függvénynév, amit nyitó zárójel követ | fn |
String.prototype.replace() második paramétereként megadott függvény minden találatnál megkapja a csoportokat, és amelyik nem üres, az alapján dönti el a színt [15]. A 4. csoport lookahead (előretekintő) feltételt használ: a zárójelet csak megköveteli, de nem veszi bele a találatba [16]. Mivel a szövegeket az 1. csoport előbb „elkapja”, egy idézőjelen belüli kulcsszó nem kap kulcsszó-színt.
A kodKiir() függvény ezután minden sort egy kodsor osztályú div-be tesz, sorszámmal és egyedi azonosítóval (kodsor0, kodsor1, …), hogy a lejátszás során a sorok közvetlenül elérhetők legyenek.
valtozoLista() és muszerez())A műszerezés (instrumentation) azt jelenti, hogy a programba plusz utasításokat illesztünk, amelyek a működését nem változtatják meg, de információt gyűjtenek róla. Ezt a technikát használják például a kódlefedettség-mérő eszközök is, mint az Istanbul, amely a JavaScript kódba számlálókat szúr be, hogy mérje, mely sorok futottak le [17].
Változólista: a valtozoLista() minden függvény fejlécéből kiolvassa a paramétereket (a zárójelek közötti részt vesszőknél szétvágva), majd a függvény törzsében reguláris kifejezéssel (/var (\w+)/g) összegyűjti a deklarált változókat. Ebből egy objektum-literált készít szövegként, például a jo függvényre:
{szam: szam, alap: alap, i: i, c: c}
Beszúrási szabályok: a muszerez() soronként halad, és a sor típusa szerint szúrja be a __lepes(sorszám, "függvénynév", változók) hívást:
| Sor típusa | Beszúrás módja | Indoklás |
|---|---|---|
Üres sor, } | Nincs beszúrás | Nem végrehajtható sor. |
+-szal kezdődő sor | Nincs beszúrás | Egy előző sor folytatása (szövegösszefűzés), oda utasítás nem szúrható. |
function ..., } else { | A sor végére, a nyitó kapcsos zárójel után | Így a hívás a blokk első utasítása lesz. |
if (, } else if ( | A feltétel elejére, vesszővel elválasztva | A vessző operátor kiértékeli a hívást, de a feltétel értéke az utolsó kifejezés marad [18]. |
for ( | Az első ; után, a ciklusfeltétel elejére | Így a hívás minden ellenőrzéskor, azaz minden ismétlésnél lefut. |
| Minden más | A sor elejére, az eredeti behúzás megtartásával | Egyszerű utasítás előtti lépés. |
Példa a jo függvény első sorainak átalakítására (a változólistát az olvashatóság kedvéért {...} jelöli):
function jo(szam, alap) { __lepes(10, "jo", {...});
for (var i = 0; __lepes(11, "jo", {...}), i < szam.length; i++) {
__lepes(12, "jo", {...}); var c = szam[i];
if (__lepes(14, "jo", {...}), alap == 2 && c != "0" && c != "1") {
__lepes(15, "jo", {...}); return false;
i: i és c: c szerepel?var kulcsszóval deklarált változók deklarációját a JavaScript a függvény elejére „emeli” (hoisting), értékük a tényleges értékadásig undefined. Így a változólista már a függvény első sorában kiértékelhető, a vizualizáció pedig undefined értékként mutatja a még be nem állított változókat. [19]
A műszerezett kód futtathatóvá tétele:
futtathato = new Function("__lepes", muszerez() + "\nreturn atvalt;")(rogzit);
A Function konstruktor szövegből hoz létre függvényt [20]. Az itt létrehozott függvény egyetlen paramétere a __lepes, törzse pedig a négy műszerezett függvény, a végén egy return atvalt; utasítással. Ezt azonnal meghívva a rogzit függvénnyel, a __lepes név a belső függvények számára a rogzit-et jelenti (closure, lezárás [21]). Az eredmény a műszerezett atvalt, amely a saját, szintén műszerezett jo, valt és nev függvényeit hívja. Az eredeti, globális függvények érintetlenek maradnak.
A new Function szöveges kódot futtat, ezért ismeretlen forrásból származó szöveggel veszélyes lenne. Itt azonban a szöveg kizárólag az oldal saját, a toString()-gel kiolvasott kódjából készül, felhasználói bemenet nem kerül bele.
rogzit())Minden __lepes hívás egy objektumot tesz a lepesek tömbbe: { sor, fv, ertekek }. A változók értékéről másolat készül, a tömbökről a slice() segítségével, hogy a későbbi módosítások ne írják felül a már rögzített állapotot [22]. Védelmi okból 20 000 lépés felett a rögzítés kivételt dob, amit a felvesz() egy try...catch blokkban elkap, és üzenetet ír ki a végtelen vagy túl hosszú futás helyett [23]. A rögzítés közben a műszerezett atvalt ténylegesen beírja az eredményt az oldalba; a felvesz() ezt elmenti a vegsoEredmeny változóba, majd kiüríti a mezőt, hogy az eredmény csak a lejátszás megfelelő pontján jelenjen meg.
ciklus(), kovetkezo(), lepesMutat())A lejátszás nem egy hagyományos ciklus, hanem önmagát újraütemező setTimeout hívások sorozata [24]. Egy blokkoló ciklus megfagyasztaná a böngészőt, és a felhasználó nem látná a köztes állapotokat. Az időzítő minden lépés után a csúszkához tartozó késleltetéssel hívja meg újra a ciklus() függvényt:
| Csúszka | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| Késleltetés (ms) | 1100 | 750 | 500 | 350 | 240 | 160 | 100 | 60 | 30 | 10 |
Egy lépés megjelenítésekor a lepesMutat() három dolgot frissít:
sorMutat(): a futó sor aktiv, a már érintett sorok volt osztályt kapnak (classList [25]). Ha a sor kilóg a látható területből, a kódnézet scrollTo() segítségével, sima görgetéssel odaugrik [26].valtozokMutat(): kiírja az aktuális függvény változóit. Függvényenként megjegyzi az előző értékeket, és a megváltozott változót valtozott osztállyal kiemeli. A szöveges értékeket idézőjelben, a tömböket szögletes zárójelben, a DOM-elemeket <p#eredmeny> alakban jeleníti meg (formaz()).tablaFrissit(): lásd 5.6.A lejátszás állapotait (kezdő, fut, szünet, vége) és az ezek közötti átmeneteket a tesztdokumentáció állapotátmenet-táblája írja le részletesen.
tablaEpit(), tablaFrissit())Átváltáskor a tablaEpit() minden vizsgálandó alaphoz (automatikus módban mindháromhoz) létrehoz egy sort a document.createElement() segítségével [27]: egy címkét, karakterenként egy cellát és egy állapotjelvényt. A karakterek a textContent tulajdonságon keresztül kerülnek a cellákba, így a beírt szöveg HTML-ként sosem értelmeződik [28].
A tablaFrissit() csak a jo függvény lépéseire reagál, és a lépés alap változója alapján választja ki a megfelelő sort:
| A futó sor | Hatás a táblán |
|---|---|
function jo... | Minden cella alaphelyzetbe, jelvény: „vizsgálja...” |
Ciklus belseje, i = k | A 0…k−1. cella zöld pipa, a k. cella kiemelve (most) |
return false | Az i. cella piros X, jelvény: „nem érvényes” |
return true | Minden cella zöld pipa, jelvény: „érvényes” |
A gombok és az Enter billentyű az addEventListener() segítségével kapcsolódnak a megfelelő függvényekhez [29]. Az Enter lenyomását a keydown esemény key tulajdonságával ismeri fel a program [30].
| Esemény | Függvény |
|---|---|
| Átváltás gomb, Enter a szám mezőben | inditas() |
| Lejátszás / Szünet / Újra gomb | lejatszasValt() |
| Lépés gomb | egyLepes() |
| Végére gomb | vegere() |
Források: [13]–[30]
style.css)A felület sötét hátterű, egyszerű, „lapos” (flat) kialakítású: nincsenek színátmenetek, árnyékok és animációk, a hangsúlyt egyetlen kiemelőszín adja. A cél a jó olvashatóság és az, hogy a vizualizáció állapotai (aktív sor, megváltozott változó, érvényes vagy hibás karakter) egyértelműen elkülönüljenek.
| Szín | Felhasználás |
|---|---|
#121212 | Oldal háttere |
#1e1e1e | Kártyák, dobozok háttere |
#64ffda | Kiemelés: linkek, aktív cella, érvényes karakter, megváltozott változó |
#ff5252 | Hibás karakter, „nem érvényes” jelvény |
Az elrendezés Flexboxra épül [31]: a main elem két oszlopa (bal oldalon az átváltó és a tábla, jobb oldalon a kódnézet) a flex-wrap: wrap és a min-width beállítás miatt keskeny képernyőn automatikusan egymás alá kerül. A kódnézet és a változók monospace betűtípust használnak, hogy a kód oszlopai egymás alatt maradjanak. A betűtípusok a Google Fonts szolgáltatásból töltődnek be [32].
| Korlát | Magyarázat | Lehetséges megoldás |
|---|---|---|
| Legfeljebb 253 − 1 értékű számok | A Number típus pontossági határa (4.4). | A BigInt típus használata, amely tetszőleges méretű egészeket kezel [33]. |
| Csak nemnegatív egészek | Előjel és tizedesjel nem megengedett karakter. | Előjel külön kezelése, törtrész átváltása szorzásos módszerrel. |
| Csak 2, 10 és 16 alap | A jo() feltételei erre a háromra vannak megírva. | Általános vizsgálat: a karakter értéke kisebb legyen, mint az alap (2–36). |
| A műszerezés formázásfüggő | A muszerez() a sorok elejét vizsgálja, ezért feltételezi, hogy egy sorban egy utasítás áll, és az if ( / for ( alakot követi. | Valódi szintaktikai elemzés (parser) használata. |
| Rögzítés csak Átváltáskor | Ha a szám mező az Átváltás után módosul, de új Átváltás nem történik, a Lépés / Újra gomb a régi felvételt játssza le az új számhoz épített táblán (lásd tesztdokumentáció, BUG-03). | A mező változásakor a felvétel törlése. |
| Dokumentáció ábrái | A Mermaid könyvtár internetről töltődik be. | A könyvtár helyi másolatának használata. |
Számrendszerek, számábrázolás
DOM és alapvető JavaScript
Vizualizáció, műszerezés
Megjelenés, továbbfejlesztés