Kirjautuminen

Haku

Tehtävät

Keskustelu: Nettisivujen teko: Oma "koodikieli" uutisenjulkaisujärjestelmään

Sivun loppuun

Petja [02.07.2011 11:44:53]

#

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].

-tossu- [02.07.2011 12:00:55]

#

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.

The Alchemist [02.07.2011 12:01:20]

#

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.

Petja [02.07.2011 12:05:01]

#

-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/25254-php-bbcode

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.

The Alchemist [02.07.2011 12:25:36]

#

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?

Petja [02.07.2011 12:28:43]

#

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.

Metabolix [02.07.2011 12:49:10]

#

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.

Petja [02.07.2011 13:19:27]

#

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.

The Alchemist [02.07.2011 14:08:21]

#

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

Metabolix [02.07.2011 15:57:46]

#

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.

Lebe80 [02.07.2011 17:30:28]

#

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.

Petja [02.07.2011 17:42:46]

#

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.

Lebe80 [02.07.2011 17:58:14]

#

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.

Petja [02.07.2011 18:07:36]

#

Niin, mutta...

Petja kirjoitti:

Sen lisäksi jos haluaa tehdä muitakin ehtoja kuin vain kirjautuneet/ei kirjautuneet.

Lebe80 [02.07.2011 18:11:14]

#

No mitä ehtoja sinne tulee?

Teuro [02.07.2011 18:19:44]

#

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.

Lebe80 [02.07.2011 18:29:45]

#

Niin, ja tällöin uutisen ehdot asetetaan, kuten muissakin julkkareissa, ihan uutisen omista asetuksista valitsemalla, eikä ehtolauseita kirjoittamalla uutisen joukkoon.

Petja [02.07.2011 21:01:48]

#

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!)]

Metabolix [02.07.2011 21:18:05]

#

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>

The Alchemist [02.07.2011 21:31:49]

#

En itse käyttäisi funktiota eval() yhtään mihinkään. Se on niin tautisen hidas. Niin ovat tosin regexejä raiskaavat parseritkin.

Lebe80 [02.07.2011 21:36:34]

#

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.

Petja [05.07.2011 07:12:27]

#

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?

The Alchemist [05.07.2011 07:50:16]

#

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.

Petja [05.07.2011 07:59:06]

#

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.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta