Autor Zpráva
papula
Profil *
Dobrý den!
Potýkám se s následujícím problémem - načtu soubor pomocí file_get_contents() do stringové proměnné a následně bych rád s obsahem této proměnné pracoval. Tedy, abych byl přesnější, rád bych ho nějakým inteligentním způsobem prošel (regulární výrazy, zjevně) a zjistil, kolikrát se v tom načteném obsahu vyskytují slova, ze skupiny povolených slov. Konkrétní případ: rajce, okurka, banan, jablko jsou slova, jejichž výskyt chci zjistit nad daným souborem a soubor sám jako takový obsahuje ještě kupu jiného textu.

No a aby to nebylo tak snadné (a tady právě asi přichází nejpalčivější problém), nebudu počítat výskyt v komentářích (povolené formáty: //, /* */) a řetězcových literálech (tedy cokoliv uvozeno dvojitými uvozovkami), které se v rámci toho souboru mohou vyskytnout.

Našel jsem na netu napsanou funkci preg_match_count, která by počet výskytů měla asi vyřešit:
 function preg_match_count($pattern, $input) {
    if(preg_match_all($pattern, $input, $matches, PREG_PATTERN_ORDER)) {
         return count($matches[0]);
     }
     return 0;
 }

Za předpokladu, že daná funkce bude fungovat, potýkám se tedy pouze už jen s tím, jak takový regulární výraz napsat. To, že se zadaná klíčová slova nesmí vyskytovat v rámci komentářů mi činí problém.

Díky předem za odpovědi.
Keeehi
Profil
Nenápadá mě regulár, kterým by se to dalo udělat. Ovšem dokázal bych problém převést na jiný, s stejným výsledkem. Protože počet všech výskytů = počet povolených + počet nepovolaných => počet povolených = počet všech - počet nepovolených. Což je jednodušší úloha, ovšem výpočetně bude lehce složitější.
Nebo nejdříve pomocí preg_relace smazat všechny nepovolené kontexty a pak už jen spočítat všechny výskyty.
No a nebo si na celý problém napsat stavový automat který ten vstup rozparsuje a spočítá výskyty. Znamená to projít ručně znak po znaku celý řetězec, pamatovat si v jakém kontextu zrovna jsem a pokud ve správném, tak počítat výskyty.
papula
Profil *
Keeehi:
Díky za reakci a návrh na zjednodušení problému.
Dobré a žádoucí je, že výpočetní složitost zcela výjimečně nehraje velikou roli. Upřímně doufám, že se vyhnu stavovému automatu, toho jsem si na posledním projektu užil až až při tvorbě lexikálu pro kompilátor jistého jazyka.

Stále budu rád za jakoukoliv další reakci k tématu. :)
Keeehi
Profil
Ještě jak je to s escapováním? Protože to v případě regulárních výrazů určitě ztíží práci. Osobně mi ten automat přijde jako nejjednodušší ze všech možností.
papula
Profil *
Keeehi:
Vstup bude přeložitelný C kód s c99 standardem, takže všechno povoleno. Asi to nakonec fakt bude třeba.
Alphard
Profil
Chvíli si tady hraji s regulárem, který by dokázal vymazat stringy. Je to rychlé řešení pro inspiraci, před nasazením by se muselo ověřit.

$in = '
var = "nejaky text";
var = "nejaky \"text\" s exapovanim";
var = "\'nejaky\' \"text\"";
var = "nejaky " + var + " text";
var = "nejaky \\\\\\" text";
var = "nejaky \n text";
var = "nejaky \n text
        na vice radku";
';
echo $in;

$regexp = '~"(?:\\\\.|[^"\\\\])*"~s';

preg_match_all($regexp, $in, $m);
echo preg_replace($regexp, '', $in), PHP_EOL;
print_r($m[0]);

Doplněn regulár od [#9] Jan Tvrdík a upraven vstupní příklad. Byla tam ještě jiná chyba, chybně jsem si sám interpretoval escapování a podle toho chybně odladil regulár.
Keeehi
Profil
Alphard:
Ten regulár vypadá dobře. Podobně by měl jít zapsat i víceřádkový komentář. Ovšem pak se nastávejí komplikace, když se kříží.
/* " */ jablko /* " */
Když by se nejdříve odstranili stringy a pak komentáře, tak nám to jablko vypadne.
Alphard
Profil
Těch problémů je tam více, můj regulár bez dalších úprav třeba nezvládne konstrukci
ch = '"';
for (int i = 0; i<count; i++) ;
ch = '"';
Vyrobí z toho tohle
ch = '';
To znamená, že ještě před hledáním řetězců v uvozovkách by se musely smazat znaky v apostrofech.

Záleží na požadované přesnosti (tolerance chyb) a jak ten kód reálně vypadá. Obecný případ se bude regulárama řešit dost těžko. Hypoteticky i hledaný řetězec nacházející se v makru by se měl možná násobit počtem použití makra. 100% spolehlivé bude jen napsání parseru.
Jan Tvrdík
Profil
Alphard:
Regulární výraz pro řetězce vypadá trošku jinak $regexp = '~"(?:\\\\.|[^"\\\\])*"~';, ten tvůj selže, pokud na konci řetězce bude zpětné lomítko.

100% spolehlivé bude jen napsání parseru.
Spíš lexeru, parser netřeba.
Alphard
Profil
Jan Tvrdík:
Spíš lexeru, parser netřeba.
Já myslel i na uvedené případné vyhodnocení maker, tedy parser na této úrovni, ne parser kompletního C. Ale to byl jen takový nápad, jaké problémy by ještě mohly nastat, tazatel o tom vůbec nemluvil a možná by to tak ani nechtěl.
papula
Profil *
Keeehi, Alphard:
No a kdyby se to oddělávalo postupně? Nejprve blokové komentáře, pak řetězce? Pak by to mohlo regulárními výrazy jít, nebo se pletu? Jen jsem si tedy hned uvědomil další věc a to, že si nejsem jistý, jestli jde nějak efektivně napsat RV pro víceřádková makra.


Alphard:
Makra není třeba provádět, jen odstranit. Naštěstí.
Keeehi
Profil
papula:
No a kdyby se to oddělávalo postupně?
Nepůjde to. Problémová posloupnost existuje pro obě varianty. řetězce > komentáře jsou v [#7] a pro komentáře > řetězce je to třeba toto:
" /* " jablko " */ "
Kontext je extrémně důležitý a celkem špatně se v regulárech podchytává.
Jan Tvrdík
Profil
Keeehi:
Kontext je extrémně důležitý a celkem špatně se v regulárech podchytává.
Jde použít např. malinkatou knihovnu nette/tokenizer.

$tokenizer = new Nette\Utils\Tokenizer([
    'comment' => '/\*.*?\*/',
    'string' => '"(?:\\\\.|[^"\\\\])*"',
    'identifier' => '\w+',
    'whitespace' => '\s+',
]);

$tokens = $tokenizer->tokenize('" /* " jablko " */ "');
print_r($tokens);

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: