Eli nyt on ollut muutamissa edellisissä topiceissa puhetta funktioparsereista yms. sellaisista "ohjelmointikielen" tekoon liittyvistä asioista. Itsellänikin on BASIC-tyyppinen ohjelmointikieli suunnittelussa. Nyt tarvitsisin vain neuvoja muuttujien hallintaan ja operaattorien tekemiseen perustietotyypeille.
Eli tilanne on nyt tämä: tulkki lukee tavukoodia. Se saapuu kohtaan, johon on merkitty operaattori. Tulkki lukee tavukoodista operaattorin tyypin (olkoon vaikka * operaattori). Tämän jälkeen tulkki lukee muuttujien tyypit (vasen operandi: int, oikea operandi: float). Nytpä kuuluu kysymykseni: miten saisin toteutettua operaattorit niin, että muuttujien operaattorit voidaan hoitaa vain käyttämällä tavukoodista luettuja arvoja? Millä tavalla muuttujat kannattaisi säilöä muistiin ja millä tavalla hakea sieltä pelkkien luettujen int-tyyppisten lukujen perusteella.
Nykyinen systeemini käyttää hyväksi funktio-osoittimia ja mallifunktioita. Ensiksi on vektori scopeista. Kun tullaan uuteen scopeen, luodaan taulukko dynaamisesti käyttäen tavukoodista saatua tietoa muuttujien määrästä scopessa (kääntäjä hoitaa tämän). Muuttujat luodaan dynaamisesti ja ne säilötään void* -osoittimilla tämän vektorin taulukoihin. Operaattorit ovat seuraavassa kolmiulotteisessa funktio-osoitintaulkossa:
Functor<void*, FunctionData&> OPERATORS [OPERATOR_ID] [LEFT_OPERAND_DATATYPE] [RIGHT_OPERAND_DATATYPE]; // FunctionData-struktuurin sisältö: struct FunctionData { void* right; int rightType; void* left; int leftType; };
Operaattorit lisätään taulukkoon seuraavalla tavalla:
template<typename A, typename B> void* opMul(FunctionData& d) { // void* allocateVariable (int type, void* value) return allocateVariable (d.leftType, &(*((A*)d.left) + *((B*)d.right))); } OPERATORS [opMulID] [typeInt] [typeInt] = Functor( opMul<int,int> ); OPERATORS [opMulID] [typeInt] [typeShort] = Functor( opMul<int,short int> ); // jne..
Systeemini toimii hyvin, mutta siinä on yksi suuri heikkous: EXE:n koko kasvaa aivan julmasti. Perusoperaattoreilla koko on jo 200-300 kt, joten siksi yritänkin keksiä helpompaa ja elegantinpaa tapaa hoitaa nämä operaattorit. Ehdotukset olisivat tarpeen... Kiitos. :)
En nyt ihan ymmärtänyt, miksi taulukkoon on säilötty erityyppisten arvojen laskutoimituksia. Ja mitä ovat "pelkät luetut int-tyyppiset luvut"? Mitä kaikkia tietoja tavukoodissa on, kun pitää suorittaa vaikka kertolasku?
Siis:
- jokaisella perustietotyypillä on oma id (int = 1, uint = 2, short = 3, float = 4..)
- jokaisella operaattorilla on oma id ( + = 1, - = 2 = = 3, * = 4, / = 5.. )
- jokaisella eri komennolla on oma uniikki id:nsä (jotta tiedetään, mitä missäkin kohtaa koodia pitää tehdä (mitä seuraavat tavut koodissa tulevat tekemään)).
Otetaan esimerkiksi tuo kertolasku:
2 tavua: komennon nimi - operaattori
1 tavu: operaattori - kertolasku
1 tavu: vasemman operandin tyyppi (int,uint,short...) - int
1 tavu: oikean operandin tyyppi " - float
2 tavua: vasemman operandin muuttujan löytämiseen tarvittavat tiedot
2 tavua: oikean " " "
Tiedetään siis nyt että:
- kyseessä on kertolasku
- vasen operandi on int
- oikea operandi on float
- olemme saaneet tulkilta vasemman ja oikean operandin muuttujat (kummatkin ovat void*), joiden arvoilla laskutoimitus voidaan tehdä
Nyt pitäisi hoitaa itse kertolasku ja palauttaa paluuarvo pinoon (joka on tietysti float).
Kannattaisiko hoitaa tuo muuttujasysteemi jollain aivan eri tavalla, jotta voisi käyttää jotain parempaa tapaa operaattorien toteutukseen?
Onnistuisiko semmoinen, että muuttaisit heti aluksi molemmat operandit samantyyppisiksi eli paluuarvon tyyppisiksi? Näin pääset eroon yhdestä ulottuvuudesta. Mutta en kyllä keksi, miten sen voisi kiertää, että jonnekin täytyy kirjoittaa auki kaikki laskutoimitukset kaikilla tyypeillä. Satojen kilotavujen koodi kuulostaa kuitenkin tosi suurelta, kuinkahan paljon nuo C++:n hienoudet kasvattavat koodin kokoa?
Tuo ei toimi, sillä jos minulla on muuttujat määriteltyinä void* tyyppisiksi, tulee minun silti määrittää ne ensiksi _tyyppi_* tyyppisiksi, ennenkuin voin castata ne paluuarvon tyyppisiksi. Yksi vaihtoehto olisi tietysti käyttää vain kahta perustietotyyppiä: int ja double. Tämä on mielestäni kuitenkin liian rajoittunutta... Tietysti voisin tehdä operaattorit tuolla tavalla malleilla, mutta exen kooksi tulisi n. 500 kt, joka ei todellakaan ole hyvä juttu... Siksi niitä rakentavia neuvoja kaivattaisiinkin. :D
Ja tuo satojen kilotavujen koodi ei kuulosta ollenkaan isolta kun ajattelee, että tietotyyppejä on 8 ja operaattoreita noin 10. Se tekee yhteensä noin raa'asti laskettuna 8*8*10 = 640 mallifunktiota... :/
Auttaisiko, jos ajattelisit operaattoriesi olevan ns. curried, eli argumenteiltaan ketjutettuja funktioita? Selitän.
Normaalisti (määritelmän kannalta) + on funktio, joka ottaa kaksi argumenttia ja palauttaa yhden arvon. Funktiomuodossa se kirjoitettaisiin plus(x,y).
Ketjutetussa muodossa plus olisi funktio, joka ottaa yhden argumentin, ja palauttaa funktion, joka ottaa yhden argumentin. Vasta tämä jälkimmäinen funktio summaa ne arvot.
Siis plus (x) palauttaa yhden argumentin nimettömän funktion. Jos sitä nimetöntä funktiota kutsutaan arvolla y, se palauttaa summan x + y.
Nyt kutsu plus (x,y) olisi muotoa plus(x)(y). Kuitenkaan näillä muodoilla ei ole mitään merkitystä, koska tämä tapahtuisi ohjelman sisällä, eikä näkyisi käyttäjälle.
Seuraavaksi rakennat haluamistasi lukutyypeistä tornin. Siinä ylempänä on sellainen lukutyyppi, joksi alempana oleva pitää korottaa, että niillä voi operoida. Siispä short pitää muuttaa floatiksi, että ne voi summata (tai kertoa tms.). Float taas pitää korottaa kompleksiluvuksi lisäämällä nollakertoiminen imaginaariosa, jos se halutaan summata kompleksiluvun kanssa. Tähän väliin sopivat loogisesti int ja double, mutta etumerkilliset ja etumerkittömät luvut ovat vaikeampia. Ehkä voit kieltää etumerkittömät aluksi, että pääset tekemään jotain toimivaa.
Nyt yleisesti pätee, että jos sinulla on ketjutettu funktio, jonka sisällä on tyyppiä a oleva argumentti, pitää se korottaa tyypiksi b, jos seuraava argumentti on tyyppiä b ylempänä tornissa. Jos taas b on alempana, se pitää korottaa a:n tyyppiseksi.
Näin tyyppien muunnokset ja testaukset saisi ehkä lokalisoitua yhteen paikkaan. Tarvitse vain luokan osittain määritellyille funktioille (ne joissa on yksi argumentti jo "sisällä"), joiden argumenttia voi tarvittaessa korottaa. Perit siitä kaikki operaattorifunktorisi, joten vältät koodin toistoa.
En ole miettinyt tätä loppuun vielä. Palaan ehkä asiaan. Kysy selvennystä, jos olin liian hämärä.
Tässäpä olisi yksi artikkeli tyyppien esityksistä dynaamisissa kielissä. Se on vähän vanha, mutta perustekniikat ovat yhä samat.
http://citeseer.ist.psu.edu/
PDF ja muut saatavilla tuolta sivulta.
Aivan! Tuossa on ideaa! Kiitos!
Kokeilen ja testailen tuota tapaa ja ilmoita tuloksista. :)
Aihe on jo aika vanha, joten et voi enää vastata siihen.