Olen toteuttanut MySQL:n ja PHP:n avulla uutisenjulkaisujärjestelmän. Tarkoitukseni olisi kehittää järjestelmästä sellainen, että siinä voidaan käyttää jollain tavoin sen omaa koodikieltä. Koodin syntaksi kuuluisi jotenkin näin (muistuttaa hieman PHP:tä):
hinta 20 € ja sisarukset 15 €. {(login)? [url=lomake/321]Ilmottaudu tästä[/url] : Kirjaudu sisään ilmottautuaksesi!}
Skriptin tulisi hakea ennen sivun latautumista (oletetaan tiedon olevan PHP:ssä $logged
-muuttujassa), onko käyttäjä kirjautunut sisään. Mikäli on, näkisi hän tekstin "Ilmottaudu tästä". Myöhemmässä vaiheessa kehittelisin muitakin vaihtoehtoja tarkistettavaksi, kuin tuon login-arvon.
Projekti voi kuulostaa ehkä, että mitä h*******ä olen tekemässä, mutta tarkoitukseni olisi luoda siis pieni simppeli koodikieli. Onhan Wikipediassakin mallineohjelmointikieli jne. Mahdotonta?
P.S. Putkassa voisi olla koodin lihavointia, kursivointia ja alleviivausta varten omat tagit [kl], [kk], [ku].
Petja kirjoitti:
Onhan Wikipediassakin mallineohjelmointikieli jne. Mahdotonta?
Ei se mahdotonta ole.
Petja kirjoitti:
Projekti voi kuulostaa ehkä, että mitä h*******ä olen tekemässä, mutta tarkoitukseni olisi luoda siis pieni simppeli koodikieli.
Juuri siltä se kuulostaa. Jos minulla olisi pakottava tarve luoda oma ohjelmointikieli, tekisin ainakin ensiksi assembly-tyylisen ja yksinkertaisen tulkattavan kielen, jolloin parserista ei välttämättä tulisi hirveän monimutkaista. Mikäli kielessä ei tarvitse olla funktiota tai aliohjelmia, pääsee vielä helpommalla.
Projektin järkevyydestä en sano mitään...
Edit: Muokkasit viestiä lähetyksen jälkeen. Uusimmasta versiosta saa sellaisen käsityksen, että tarkoituksena tehdä kieli, jossa ei ole kuin yksinkertainen if-lause. Silloin projekti on huomattavasti järkevämpi.
Esitteletkö sinä tässä oman aivoriihesi tulosta, vai haluatko että me koodaisimme sinulle tuon kyseisen härpäkkeen?
Itse ottaisin tässä tapauksessa pohjaksi BBC-koodin ja lähtisin laajentamaan siitä samaa syntaksia mukaillen. BBC on suht standardi tapa käyttää tekstin muotoiluita esim. keskustelupalstoilla, joten se on käyttäjille intuitiivinen tapa. Miettisin samalla sitäkin, miten suuren vaivan tuottaa kirjoittaa parseri/tulkki kyseiselle koodikielelle.
-tossu- kirjoitti:
tarkoituksena tehdä kieli, jossa ei ole kuin yksinkertainen if-lause
Juu näin on.
The Alchemist kirjoitti:
Itse ottaisin tässä tapauksessa pohjaksi BBC-koodin
Vaikka esimerkiksi tämän pohjalta:
https://www.ohjelmointiputka.net/koodivinkit/
Voisi toki tätäkin projektia jatkaa BBCodella käytettäväksi, kuten sanoit. Tyylin näin:
[if login][url=lomake/321]Ilmottaudu tästä[/url][else]Kirjaudu sisään ilmottautuaksesi[/if]
Hankalinta tässä on tuo parserin teko.
Näyttää vähän turhan hankalalta.
Itse käyttäisin ehkä tällaisia rakenteita:
[if (clause) (cmd1) (cmd2)] [if (clause1) (cmd1) ([if (clause2) (cmd2) (cmd3)]) ] [case (x=1: cmd1) (x=2: cmd2) (x=3: cmd3) (def: cmd4)] *** esim [if (login) ([url=lomake/321]) (Kirjaudu sisään)]
Nuo iffit eivät tietenkään skaalaudu kovin hyvin useita ehtoja käsittäville ketjuille, mutta liekö yksinkertaista CMS:ää tarvitsekaan sellaisille optimoida?
Mitenkäs tuota parseria lähtisi tekemään? Mistä aloitan?
Vastaan tulee kuitenkin nuo säännölliset lausekkeet, joista en ymmärrä sitten pöläystäkään, edes putkan asiantuntevien oppaiden avulla.
The Alchemist kirjoitti:
Nuo iffit eivät tietenkään skaalaudu kovin hyvin useita ehtoja käsittäville ketjuille, mutta liekö yksinkertaista CMS:ää tarvitsekaan sellaisille optimoida?
Ei tarvitse, kyseessä on vain pieni systeemi.
En suosittelisi The Alchemistin lähestymistapaa, koska sille on jopa vaikeampi tehdä parseri. Mitä vähemmän erilaisia rakenteita kielessä on, sitä helpompi kieli on parsia. Jos kielessäsi on joka tapauksessa hakasulkuihin kirjoitettavia tageja, on yksinkertaisinta toteuttaa myös if-lause niillä.
Tavallinen tagiparseri on ihan helppo tehdä, ja onkin suorastaan ihme, että kaiken maailman BBCode-viritelmät ovat niin surkeita. Etsit vain tekstistä aina seuraavan hakasulun, lisäät hakasulkua edeltävän osan nykyiseen elementtiin tekstinä ja lisäät tai poistat tageja pinosta hakasulkujen sisällön mukaan. PHP:llä voisi lähestyä ongelmaa tähän tyyliin:
function muotoile($teksti) { $tagit = array(new Juuritagi()); while (preg_match('/(.*?)[[]([^][\\n]*?)[]](.*)/s', $teksti, $osat)) { end($tagit)->lisaa($osat[1]); hoida_tagi($tagit, $osat[2]); $teksti = $osat[3]; } if (count($tagit) > 1) { throw new Parserivirhe("Tageja jäi sulkematta!"); } end($tagit)->lisaa($teksti); return end($tagit)->muotoile(); } function hoida_tagi(&$tagit, $tagi) { // $tagi = "if ehto", "else", "/if" tms. // Tutki, että tagi on kelvollinen, ja muokkaa tagipinoa. }
Putkassa on (nykyään) tällä periaatteella toimiva muotoilu, tosin kooditagit ja eräät historialliset ominaisuudet mutkistavat tilannetta hieman.
parseri.php kirjoitti:
Fatal error: Class 'Juuritagi' not found in parseri.php on line 5
Otin tekstin (perustuen The Alchemistin viestiin) ja ajoin sen muotoile -funktion läpi. Funktion hoida_tagi sisään kirjoitin:
print $tagi;
En ymmärtänyt ihan koodia kokonaan, joten laittaisitko hieman lisää kommentteja? - Joo, olen tumpelo.
Kirjoitinpa aikani kuluksi parserin tuolle omalle, huonoksi povatulle syntaksilleni.
BBC-koodin parsiminen regexillä on väitetysti mahdotonta, joten käytin itse iteroivaa ja rekursiivista menetelmää.
P.S. testClause() on vain nopea purkka ehtojen parsimisen testaamiseksi.
<?php class ExtendedBBC { private $vars; function __construct() { $this->vars = array(); $this->vars['clause'] = true; $this->vars['clause1'] = false; $this->vars['clause2'] = false; $this->vars['login'] = false; $this->vars['$x'] = 2; } function parse($str) { $buffer = ''; $j = 0; $i = strpos($str, '['); if ($i === false) return $str; while ($i !== false) { preg_match('/\w+/', $str, $tag, 0, $i+1); $tag = $tag[0]; $b = substr($str, $j, $i-$j); switch ($tag) { case 'if': list($clause, $cmd1, $cmd2) = $this->parseBlock($str, $i); if (isset($this->vars[$clause])) $b .= $this->parse( $this->vars[$clause] ? $cmd1 : $cmd2 ); else { try { $b .= $this->parse( $this->testClause($clause) ? $cmd1 : $cmd2 ); } catch (Exception $e) { $b .= sprintf('DEBUG: Invalid clause "%s"<br/>', $clause); } } break; case 'case': $data = $this->parseBlock($str, $i); $ok = false; $def = null; try { foreach ($data as $case) { $args = explode(':', $case, 2); if (count($args) == 1) { $def = $args[0]; continue; } if ($ok = $this->testClause($args[0])) { $b .= $this->parse(trim($args[1])); break; } } } catch (Exception $e) { $b .= sprintf('DEBUG: Syntax error in case clause: %s', $clause); } if (!$ok && $def) $b .= $this->parse($def); break; default: // Match is not a valid XBBC tag, so we want to print it instead of parsing it. // The tag itself is actually printed on next round, but we need to print its opening tag first. $b .= '['; } $buffer .= $b; $j = $i+1; $i = strpos($str, '[', $j); } $buffer .= substr($str, $j); return $buffer; } // Notice that both args are REFERENCES! private function parseBlock(&$str, &$i) { $bi = $i; $len = strlen($str); $data = array(); $word = ''; $state = 0; $nest = 0; while ($i < $len) { if ($state == 1) { if (!$nest && $str[$i] == ')') { $data[] = $word; $word = ''; $state = 0; } else $word .= $str[$i]; if ($str[$i] == '(') $nest++; elseif ($nest && $str[$i] == ')') $nest--; } else { if ($str[$i] == '(') $state = 1; elseif ($str[$i] == ']') return $data; } $i++; } $i = $bi; throw new Exception('Invalid block!'); } private function testClause($clause) { if (preg_match('/^(.*?)\s*((?:<=)|(?:>=)|[<>!=])\s*(.*?)$/', trim($clause), $m)) { list($foo, $l, $op, $r) = $m; if ($l[0] == '$') $l = isset($this->vars[$l]) ? $this->vars[$l] : null; if ($r[0] == '$') $r = isset($this->vars[$r]) ? $this->vars[$r] : null; switch ($op) { case '<': return $l < $r; case '>': return $l > $r; case '=': return $l == $r; case '!': return $l != $r; case '<=': return $l <= $r; case '>=': return $l >= $r; } } throw new Exception('Invalid clause!'); } } $bbc = new ExtendedBBC(); ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Extended BBCode</title> </head> <body> <form action="<?php print $_SERVER['PHP_SELF'];?>" method="get"> <fieldset> <textarea name="bbcode" cols="100" rows="15"><?php if (!empty($_GET['bbcode'])) print $_GET['bbcode']; ?></textarea> <button type="submit">Parse</button> </fieldset> </form> <div id="parseResult"> <pre> <?php if (!empty($_GET['bbcode'])) print $bbc->parse($_GET['bbcode']); ?> </pre> </div> </body> </html>
Esimerkki XBBC-koodista:
(Copy-pastea kokonaan.)
Demoan tässä laajennettua BBC-koodia. [if (clause) (cmd1) (cmd2)] [if (clause1) (cmd1) ([if (clause2) (cmd2) (Siika sanoo moi(hei))])] [if (5 >= 3) (greater than) (not greater than)] Switch-case: [case ($x=1: cmd1) ($x=2: cmd2) ($x=3: cmd3) (cmd4)] [testi 'ok'] *** esim [if (login) ([url=lomake/321]) (Kirjaudu sisään)] foo::bar
Petja kirjoitti:
P.S. Putkassa voisi olla koodin lihavointia, kursivointia ja alleviivausta varten omat tagit...
Miksi ihmeessä koodia pitäisi lihavoida, kursivoida tai alleviivata? Sitä paitsi kooditagin suurin tarkoitushan on, että mitkään merkinnät (kuten tyypillinen [i]) eivät maagisesti muutu toisiksi.
Petja kirjoitti:
En ymmärtänyt ihan koodia kokonaan, joten laittaisitko hieman lisää kommentteja? - Joo, olen tumpelo.
Mitä kohtaa koodista et ymmärtänyt – vai oliko ongelma koko idean ymmärtämisessä? Se on ihan tavallista PHP:tä ja tekee juuri sen, mitä edeltävässä tekstissä selostin.
Petja kirjoitti:
Funktion hoida_tagi sisään kirjoitin:
print $tagi;
Kuten sanoin, hoida_tagi-funktiossa pitää muokata $tagit-muuttujaa: avaustagin kohdalla pitää lisätä siihen uusi tagi (esimerkiksi new IfTagi($ehto)
), ja sulkutagin kohdalla pitää poistaa kyseinen tagi. Lisäksi pitää tietenkin aina tarkistaa, että löytynyt tagi edes on sallittu. Jotkin tagit (kuten else) vaativat mutkikkaampia toimia, esimerkiksi if-tagin automaattisen sulkemisen ja uuden ElseTagi-olion liittämisen edelliseen IfTagi-olioon.
Petja kirjoitti:
Fatal error: Class 'Juuritagi' not found in parseri.php on line 5
Kai nyt tyhmempikin tajuaa, ettei tuollaista luokkaa voi olla valmiiksi olemassa. Jos et osaa toteuttaa sitä, ehkä sinun ei pitäisi haaveilla oman kielen tekemisestä. (Jos kirjoittaisin tuollaisen luokan sinulle, olisiko kieli enää sinun tekemäsi?)
The Alchemist kirjoitti:
BBC-koodin parsiminen regexillä on väitetysti mahdotonta, joten käytin itse iteroivaa ja rekursiivista menetelmää.
Ehkä ymmärsit väitteen väärin. Tietenkään sisäkkäisiä tageja ei voi käsitellä oikein yhdellä lausekkeella, ja juuri se onkin amatöörien viritelmien yleisin ongelma. Siitä ei missään tapauksessa seuraa, etteikö seuraavaa tagia voisi silti etsiä säännöllisellä lausekkeella, kuten tuossa minun koodissani. Lisäksi tarvitaan se iteraatio tai rekursio.
Tarvitseeko jokainen "uutinen" oman skriptin, vai olisiko järkevämpää kovakoodata jokin valmis sääntö?
Tällöin jokainen uutinen olisi ihan perus html-sisältöä, ja esim. kirjautumattomille näytettettäisiin x kpl merkkejä (esim. Eka kappale).
Tällöin itse uutisen kirjoituskin hoituisi ilman outoa skriptikieltä ilman ongelmia.
Metabolix,
koodia voisi esimerkiksi korostaa lihavoimalla. Jos haluat jonkun tietyn osan koodista huomattavaksi. Kursiivi ja alleviivaus nyt eivät olisikaan niin tärkeitä.
Lisäksi sanottakoon vielä, että olen pikku koodari, eikä kaikenmaailman syvällisemmät asiat ole tullut vielä tässä vaiheessa mieleen.
En sinun olettanut koko koodia kirjoittavan, mutta tosin jos kommentteja voisi lisätä asiaa helpottamaan.
Lebe80 kirjoitti:
Tällöin jokainen uutinen olisi ihan perus html-sisältöä, ja esim. kirjautumattomille näytettettäisiin x kpl merkkejä (esim. Eka kappale).
Sitten eräänä kauniina päivänä kun haluaisin kirjoittaa kaksi kappaletta, vain toinen niistä näkyisi. Sen lisäksi jos haluaa tehdä muitakin ehtoja kuin vain kirjautuneet/ei kirjautuneet.
Edelleenkin, voit tehdä vaikkapa wordpressin tyylillä tietyn jakajaelementin, josta näytettävä alue katkaistaan. Pointti oli se, ettei itse uutiseen tulisi mitään ihme koodikieltä. Tai voithan testata tietenkin kirjoittamalla aluksi 20 uutista ja laskea kauanko itse uutisen kirjoittamiseen menee ja kauanko uutisen skriptien kirjoittamiseen menee aikaa.
Niin, mutta...
Petja kirjoitti:
Sen lisäksi jos haluaa tehdä muitakin ehtoja kuin vain kirjautuneet/ei kirjautuneet.
No mitä ehtoja sinne tulee?
no voisihan olla vaikka eri käyttäjätasoille suunnattuja uutisia, mutta silloinkin olisi varmaan fiksuinta tehdä logiikka scriptiin, jolloin uutinen itsessään olisi samanlainen, mutta se kenelle uutinen näkyy ei ole uutisen päätettävissä. Tällöin tietokannassa olisi kenttä 'oikeustaso', joka käyttäjällä tulee olla riittävän suurena.
Niin, ja tällöin uutisen ehdot asetetaan, kuten muissakin julkkareissa, ihan uutisen omista asetuksista valitsemalla, eikä ehtolauseita kirjoittamalla uutisen joukkoon.
Lebe80 kirjoitti:
No mitä ehtoja sinne tulee?
Sitä en tiedä mitä, mutta esimerkiksi
[if (logged) ([if (reg1) (Olet jo ilmottautunut!) ([url=lomake/321]Ilmottaudu tästä![/url])]) (Sinun tulee kirjautua sisään ilmottautuaksesi tapahtumaan!)]
Helppo (muttei kovin tyylikäs) tapa tehdä toiminnallisuutta olisi muotoilla ensin uutinen nätisti, korvata sitten määrätyt rakenteet tunnetulla PHP-koodilla ja ajaa lopuksi koko homma eval-funktion läpi.
<?php $teksti = "A [login] B [/] C"; $teksti = htmlspecialchars($teksti); // ym. muotoilut $teksti = str_replace("[login]", "<?php if (on_kirjauduttu()) { ?>", $teksti); $teksti = str_replace("[/]", "<?php } ?>", $teksti); eval("?>". $teksti);
Toinen mahdollisuus olisi tehdä sisältö XHTML:llä ja lisätä ehdot esimerkiksi elementtien attribuuteiksi, jolloin parseriksi kelpaisi mikä tahansa XML-kirjasto ja ehdot voisi käydä tutkimassa (ja tietenkin poistamassa ennen tulostusta) DOM-puusta yksinkertaisella rekursiolla. Ehto voisi näyttää tältä:
<p condition="login"> Ilmoittaudu heti! </p>
En itse käyttäisi funktiota eval() yhtään mihinkään. Se on niin tautisen hidas. Niin ovat tosin regexejä raiskaavat parseritkin.
Ensiksi taas kehottaisin Petjaa tutkimaan, miten muissa julkkareissa hommat on toteutettu. Itse ainakin pitäisi tämmöisiä artikkelin asetuksesta tehtäviä valmiita valintoja paljon loogisempana ja ei niin virhealttiita kuin tuommoinen "koodin kirjoitus" artikkelin joukkoon. Lisäksi, joudut kuitenkin ennalta tekemään ehtosi php:n lähdekoodiin, joten miksi turhaan kirjoitushetkellä pitäisi muistaa syntaksi, kun voisit valita ehdot suoraan asetuksista.
Kaikki julkaisuajat, julkaisun päättymiset yms. käyttäjätasokohtaiset löytyisivät kauniisti listattuna esim. artikkelin kirjoituskentän lähettyviltä. Kaikenlaisia ajax-virityksiä hyödyntäen kirjoitushetkellä ei olisi edes kaikkien asetusten pakko näkyä kerralla, vaan niitä voisi availla vaikka ihan eri "välilehdistä" tai haitarivalinnoista, jotta voisi täyttää/valita vain tarvittaviin kenttiin tiedot.
Kiitos ja kumarrus:
En aluksi huomannut The Alchemistin viestiä.
Kirjoitit koodin kokonaan, enhän minä tässä mitään oppinut, mutta kiitoksia kuitenkin!
Pääsen nyt poistamaan tyhjiä rivejä ja kommentteja, joita minun on ylivoimaisen vaikea sietää!
Lebe80 kirjoitti:
ja ei niin virhealttiita kuin tuommoinen "koodin kirjoitus" artikkelin joukkoon.
Jaahas... No hyviä perustelujakin?
Eihän kukaan enää mihinkään <textarea>:aan muutenkaan kirjoita mitään. Jokaisessa CMS:ssä ja blogialustassa pitää olla Web 2.0 -härpäke. Se voi lennosta parsia BBC-koodit kuten muunkin muotoilun. Noille if-elseille voi myös tehdä syntaksin tarkastamisen.
Asiasta en tiedä tarkemmin, mutta eikös moinen toimi vaan uusimmilla selaimilla. Skriptiä käytetään useammalta koneelta ja tiedän varsin hyvin, että selaimet ovat jotain FX3.5 tai IE7 (huippujännää).
Koodiin vielä kommentoiden, tämä on aika hauska juttu:
[if ($x > 3) (greater than) (not greater than)]
Että voit vertailla arvoja (jotka tulevat sieltä syvemmältä) keskenään.
Aihe on jo aika vanha, joten et voi enää vastata siihen.