Derniers messages sur Zeste de Savoirhttps://zestedesavoir.com/forums/2019-09-03T20:10:24+02:00Les derniers messages parus sur le forum de Zeste de Savoir.Faire du "look-ahead" avec OcamlLex, message #2082092019-09-03T20:10:24+02:00Ksass`Peuk/@Ksass%60Peukhttps://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208209<p>De retour après avoir mis en place une solution qui marche bien. On a finalement dû opter pour une modification du lexer, mais aussi, plus intéressant, quelques petites modifs dans le parseur.</p>
<p>La solution consistant à se placer entre le lexeur et le parseur n’était finalement pas trop pratique. Elle nécessitait trop de temps de calcul pour une fonctionnalité qui ne va servir que très ponctuellement. Aussi on a ajouté une règle dans le lexer pour générer un token <code>SPECIAL_ELSE</code> lorsque l’on recontrait les mots clés en question. Et au passage on a eu quelques petits cas particulier rigolos à gérer.</p>
<p>Un point plus intéressant, c’est ce qu’on a fait dans le parseur pour régler le conflit, qui apparaissait toujours avec le nouveau token <img src="/static/smileys/smile.png" alt=":)" class="smiley"> (mais le nouveau token nous a quand même bien arranger). Pour aider le parseur, on a utilisé le mot-clé <code>%prec</code> pour choisir le cas le plus pertinent. En gros, à la place des règles que j’ai mis plus haut, on a maintenant quelque chose comme:</p>
<div class="hljs-code-div"><div class="hljs-line-numbers"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div><pre><code class="hljs language-ocaml">/* operator precedence */
%nonassoc <span class="hljs-type">IF_NO_ELSE</span>
%nonassoc <span class="hljs-type">SPECIAL_ELSE_NO_ELSE</span>
%nonassoc <span class="hljs-type">ELSE</span> <span class="hljs-type">SPECIAL_ELSE</span>
else_part:
/* empty */ { ... }
%prec <span class="hljs-type">IF_NO_ELSE</span> /* <span class="hljs-type">To</span> attach the next <span class="hljs-keyword">else</span> <span class="hljs-keyword">to</span> the current <span class="hljs-keyword">if</span> */
| <span class="hljs-type">ELSE</span> inst { ... }
| <span class="hljs-type">SPECIAL_ELSE</span> inst <span class="hljs-type">CLOSE_SPECIAL</span> { ... }
%prec <span class="hljs-type">SPECIAL_ELSE_NO_ELSE</span> /* <span class="hljs-type">To</span> force the normal <span class="hljs-keyword">else</span> <span class="hljs-keyword">to</span> be attached <span class="hljs-keyword">to</span> the current <span class="hljs-keyword">if</span> */
| <span class="hljs-type">SPECIAL_ELSE</span> inst <span class="hljs-type">CLOSE_SPECIAL</span> <span class="hljs-type">ELSE</span> inst
{ error }
inst:
| <span class="hljs-type">IF</span> exp inst else_part{ ... }
| <span class="hljs-type">OPEN_SPECIAL</span> inst <span class="hljs-type">CLOSE_SPECIAL</span> { ... }
...
</code></pre></div>
<p>Ce qui permet de régler les conflits et de détecter les cas non valides de manière relativement simple. (Le dernier cas peut, peut-être, paraître bizarre mais c’est probablement dû à un détail dans le fonctionnement de nos règles, donc si c’est le cas, il faut juste retenir le coup du <code>%prec</code> qui résout le premier conflit).</p>
<p>Merci en tout cas <img src="/static/smileys/smile.png" alt=":)" class="smiley"></p>Faire du "look-ahead" avec OcamlLex, message #2081162019-09-02T17:15:15+02:00Ksass`Peuk/@Ksass%60Peukhttps://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208116<blockquote>
<p>Oui, parce que ces commentaires sont reconnaissables par une regex simple du genre <code>//[^\n]*\n</code> (tu n’as pas besoin d’une règle dédiée, qui sert en général plutôt pour parser des commentaires <code>/* ... */</code> imbriqués, qui ne sont plus réguliers).</p>
<p>Tu peux donc juste écrire une règle comme <code>open_special (blank|comment)* else</code> pour trouver les <code>ELSE_SPECIAL</code>.</p>
</blockquote>
<p>Ok, je comprends mieux.</p>
<blockquote>
<p>Si tu as des commentaires plus compliqués, tu peux aussi t’en sortir avec des règles dédiées, mais je ne pense pas que tu puisses faire beaucoup plus lisible qu’avec un post-traitement (autant éliminer au maximum ce qui n’est pas régulier — donc les commentaires — avant de modifier le flux). </p>
</blockquote>
<p>Il faut surtout que je regarde si on y conserve des choses. Mais ça m’éclaire déjà pas mal. Merci.</p>
<blockquote>
<p>Tu dois passer à Menhir si tu en as la possibilité, mais en l’occurrence ça ne change pas grand chose.</p>
</blockquote>
<p>Cette décision là n’est pas de mon ressort pour le coup.</p>
<figure><blockquote>
<p>Comment appelles-tu ton parser ? </p>
<p>[…]</p>
<p>Ici, au lieu d’utiliser <code>Lexer.regle_principale</code> comme fournisseur de token, on utiliserait le wrapper <code>fix_lexer</code>, qui a le même comportement sauf qu’il modifie la sortie en cas de <code>open_special else</code>. S’il existe déjà des fonctions de ce genre dans ton code, tu peux sans doute les regrouper dans un module intermédiaire de traitement de flux. Note quand même que, selon ton code, avoir un gros amas de fonctions comme ça n’est pas forcément la meilleure façon de faire — mais on ne peut pas dire grand chose de plus sans connaître le reste.</p>
</blockquote><figcaption><a href="https://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208114">Eusèbe</a></figcaption></figure>
<p>Je m’étais pas posé la question, j’avoue <img src="/static/smileys/rouge.png" alt=":honte:" class="smiley"> .</p>
<p>Je vais déjà essayer de me brancher là dedans et faire quelques tests de performances pour voir si je ne mets pas à trop à mal le temps de parsing. Merci <img src="/static/smileys/smile.png" alt=":)" class="smiley"> .</p>Faire du "look-ahead" avec OcamlLex, message #2081142019-09-02T17:01:15+02:00Eusèbe/@Eus%C3%A8behttps://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208114<blockquote>
<blockquote>
<p>Est-ce que tu ne peux pas juste avoir une règle <code>open_special else</code> dans ton lexer, qui serait prioritaire sur la règle qui lexe <code>open_special</code> parce qu’elle matche quelque chose de plus long ? Si tu n’as que des blancs et que tes commentaires sont "jusqu’à la fin de la ligne", ça s’écrit assez bien.</p>
</blockquote>
<p>Juste pour être sûr de bien comprendre, est-ce que</p>
<div class="hljs-code-div"><div class="hljs-line-numbers"><span></span><span></span><span></span><span></span></div><pre><code class="hljs language-actionscript">open_special
<span class="hljs-comment">// truc</span>
<span class="hljs-comment">// machin</span>
<span class="hljs-keyword">else</span>
</code></pre></div>
<p>serait parsable ? J’ai l’impression que c’est ce que tu dis mais je ne suis pas sûr de voir comment dans ce cas je dois écrire la ligne en question (pour le coup, je ne connais pas très bien le fonctionnement des outils de parsing). Parce que ce serait clairement le plus facile pour moi en l’état je pense.</p>
</blockquote>
<p>Oui, parce que ces commentaires sont reconnaissables par une regex simple du genre <code>//[^\n]*\n</code> (tu n’as pas besoin d’une règle dédiée, qui sert en général plutôt pour parser des commentaires <code>/* ... */</code> imbriqués, qui ne sont plus réguliers).</p>
<p>Tu peux donc juste écrire une règle comme <code>open_special (blank|comment)* else</code> pour trouver les <code>ELSE_SPECIAL</code>.</p>
<p>Si tu as des commentaires plus compliqués, tu peux aussi t’en sortir avec des règles dédiées, mais je ne pense pas que tu puisses faire beaucoup plus lisible qu’avec un post-traitement (autant éliminer au maximum ce qui n’est pas régulier — donc les commentaires — avant de modifier le flux). </p>
<blockquote>
<figure><blockquote>
<p>…., tu peux avoir un intermédiaire entre le flux de tokens qu’il génère et ton parser qui transforme simplement "OPEN_SPECIAL ELSE" en "ELSE_SPECIAL". Puisque Menhir prend en paramètre une fonction de type <code>Lexing.lexbuf -> token</code>, il suffit a priori d’écrire une fonction qui, pour renvoyer ce token, appelle ton lexer et l’appelle une deuxième fois lorsqu’elle tombe sur <code>OPEN_SPECIAL</code>. Quelque chose de ce genre (pas testé) :</p>
<p>[code]</p>
</blockquote><figcaption><a href="https://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208110">Eusèbe</a></figcaption></figure>
<p>C’est ce que j’ai commencé à explorer après avoir envoyé mon post (même si pour ma part, je suis avec OcamlYacc et pas Menhir, mais j’ai vu des bouts de code qui semblent faire ce genre de chose dans le parser actuel). </p>
</blockquote>
<p>Tu dois passer à Menhir si tu en as la possibilité, mais en l’occurrence ça ne change pas grand chose.</p>
<figure><blockquote>
<p>Il faut juste que je trouve où m’insérer dans le processus de parsing parce que sur ce point là, j’ai assez fortement simplifié, l’ensemble est plus dispersé que ce que j’ai écris plus haut. Même si ce n’est pas insurmontable, ça pourrait être pénible.</p>
</blockquote><figcaption><a href="https://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208111">Ksass`Peuk</a></figcaption></figure>
<p>Comment appelles-tu ton parser ? Ocamlyacc transforme tes règles principales (qui renvoient, disons, un <code>Ast.t</code>) en fonctions de type <code>(Lexing.lexbuf -> token) -> lexbuf -> Ast.t</code>. De son côté, ocamllex transforme tes règles en fonctions de type <code>Lexing.lexbuf -> token</code>. Appelées sur un <code>lexbuf</code>, elles lisent ce qu’il faut (et retirent ces caractères du <code>lexbuf</code>) avant de renvoyer le prochain <code>token</code>. </p>
<p>Dans une configuration simple, comme les types ont le bon goût de s’emboîter, on écrit donc quelque chose comme <code>fun filename -> Parser.regle_principale Lexer.regle_principale (Lexing.from_channel @@ open_in filename)</code> pour obtenir une fonction de type <code>string (* filename *) -> Ast.t</code>.</p>
<p>Ici, au lieu d’utiliser <code>Lexer.regle_principale</code> comme fournisseur de token, on utiliserait le wrapper <code>fix_lexer</code>, qui a le même comportement sauf qu’il modifie la sortie en cas de <code>open_special else</code>. S’il existe déjà des fonctions de ce genre dans ton code, tu peux sans doute les regrouper dans un module intermédiaire de traitement de flux. Note quand même que, selon ton code, avoir un gros amas de fonctions comme ça n’est pas forcément la meilleure façon de faire — mais on ne peut pas dire grand chose de plus sans connaître le reste.</p>Faire du "look-ahead" avec OcamlLex, message #2081112019-09-02T16:42:45+02:00Ksass`Peuk/@Ksass%60Peukhttps://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208111<blockquote>
<p>Est-ce que tu ne peux pas juste avoir une règle <code>open_special else</code> dans ton lexer, qui serait prioritaire sur la règle qui lexe <code>open_special</code> parce qu’elle matche quelque chose de plus long ? Si tu n’as que des blancs et que tes commentaires sont "jusqu’à la fin de la ligne", ça s’écrit assez bien.</p>
</blockquote>
<p>Juste pour être sûr de bien comprendre, est-ce que</p>
<div class="hljs-code-div"><div class="hljs-line-numbers"><span></span><span></span><span></span><span></span></div><pre><code class="hljs language-actionscript">open_special
<span class="hljs-comment">// truc</span>
<span class="hljs-comment">// machin</span>
<span class="hljs-keyword">else</span>
</code></pre></div>
<p>serait parsable ? J’ai l’impression que c’est ce que tu dis mais je ne suis pas sûr de voir comment dans ce cas je dois écrire la ligne en question (pour le coup, je ne connais pas très bien le fonctionnement des outils de parsing). Parce que ce serait clairement le plus facile pour moi en l’état je pense.</p>
<p>Hum. Encore que, il faut que je regarde ce que l’on conserve dans les commentaires.</p>
<figure><blockquote>
<p>…., tu peux avoir un intermédiaire entre le flux de tokens qu’il génère et ton parser qui transforme simplement "OPEN_SPECIAL ELSE" en "ELSE_SPECIAL". Puisque Menhir prend en paramètre une fonction de type <code>Lexing.lexbuf -> token</code>, il suffit a priori d’écrire une fonction qui, pour renvoyer ce token, appelle ton lexer et l’appelle une deuxième fois lorsqu’elle tombe sur <code>OPEN_SPECIAL</code>. Quelque chose de ce genre (pas testé) :</p>
<p>[code]</p>
</blockquote><figcaption><a href="https://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208110">Eusèbe</a></figcaption></figure>
<p>C’est ce que j’ai commencé à explorer après avoir envoyé mon post (même si pour ma part, je suis avec OcamlYacc et pas Menhir, mais j’ai vu des bouts de code qui semblent faire ce genre de chose dans le parser actuel). Il faut juste que je trouve où m’insérer dans le processus de parsing parce que sur ce point là, j’ai assez fortement simplifié, l’ensemble est plus dispersé que ce que j’ai écris plus haut. Même si ce n’est pas insurmontable, ça pourrait être pénible.</p>Faire du "look-ahead" avec OcamlLex, message #2081102019-09-02T16:31:54+02:00Eusèbe/@Eus%C3%A8behttps://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208110<p>(Le problème arrive quand tu es dans l’état <code>IF exp inst . OPEN_SPECIAL</code>, sans le <code>ELSE</code>)</p>
<p>Est-ce que tu ne peux pas juste avoir une règle <code>open_special else</code> dans ton lexer, qui serait prioritaire sur la règle qui lexe <code>open_special</code> parce qu’elle matche quelque chose de plus long ? Si tu n’as que des blancs et que tes commentaires sont "jusqu’à la fin de la ligne", ça s’écrit assez bien.</p>
<p>Sinon, plutôt que d’essayer de faire le malin sur le buffer du lexer (ça peut sans doute marcher, ça n’est certainement pas une bonne idée), tu peux avoir un intermédiaire entre le flux de tokens qu’il génère et ton parser qui transforme simplement "OPEN_SPECIAL ELSE" en "ELSE_SPECIAL". Puisque Menhir prend en paramètre une fonction de type <code>Lexing.lexbuf -> token</code>, il suffit a priori d’écrire une fonction qui, pour renvoyer ce token, appelle ton lexer et l’appelle une deuxième fois lorsqu’elle tombe sur <code>OPEN_SPECIAL</code>. Quelque chose de ce genre (pas testé) :</p>
<div class="hljs-code-div"><div class="hljs-line-numbers"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div><pre><code class="hljs language-ocaml"><span class="hljs-keyword">let</span> fix_lexer =
<span class="hljs-keyword">let</span> buffer = <span class="hljs-built_in">ref</span> <span class="hljs-type">None</span> <span class="hljs-keyword">in</span>
<span class="hljs-keyword">fun</span> lexbuf ->
<span class="hljs-keyword">match</span> !buffer <span class="hljs-keyword">with</span>
| <span class="hljs-type">Some</span> not_else_token -> not_else_token
| <span class="hljs-type">None</span> ->
<span class="hljs-keyword">let</span> token = <span class="hljs-type">Lexer</span>.lexer lexbuf <span class="hljs-keyword">in</span>
<span class="hljs-keyword">if</span> token = <span class="hljs-type">OPEN_SPECIAL</span>
<span class="hljs-keyword">then</span>
<span class="hljs-keyword">let</span> token' = <span class="hljs-type">Lexer</span>.lexer lexbuf <span class="hljs-keyword">in</span>
<span class="hljs-keyword">if</span> token' = <span class="hljs-type">ELSE</span>
<span class="hljs-keyword">then</span> <span class="hljs-type">ELSE_SPECIAL</span>
<span class="hljs-keyword">else</span> <span class="hljs-keyword">begin</span>
buffer := <span class="hljs-type">Some</span> token';
<span class="hljs-type">OPEN_SPECIAL</span>
<span class="hljs-keyword">end</span>
</code></pre></div>Faire du "look-ahead" avec OcamlLex, message #2081062019-09-02T15:10:03+02:00Ksass`Peuk/@Ksass%60Peukhttps://zestedesavoir.com/forums/sujet/12948/faire-du-look-ahead-avec-ocamllex/?page=1#p208106<p>Salut,</p>
<p>Je dois parser une grammaire qui malheureusement a, en l’état, un conflit de shift reduce. Et je ne peux pas faire grand chose pour changer la grammaire en question. J’ai essayé de simplifier au maximum (et j’espère n’avoir pas trop simplifié).</p>
<p>En gros, j’ai des règles qui ont une forme comme:</p>
<div class="hljs-code-div"><div class="hljs-line-numbers"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div><pre><code class="hljs language-routeros">list_inst:
{ [] }
| inst list_inst { <span class="hljs-variable">$1</span> :: <span class="hljs-variable">$2</span> }
inst:
| <span class="hljs-keyword">IF</span> exp inst { <span class="hljs-built_in">..</span>. }
| <span class="hljs-keyword">IF</span> exp inst <span class="hljs-keyword">ELSE</span> inst { <span class="hljs-built_in">..</span>. }
| <span class="hljs-keyword">IF</span> exp inst OPEN_SPECIAL <span class="hljs-keyword">ELSE</span> inst CLOSE_SPECIAL { <span class="hljs-built_in">..</span>. }
| OPEN_SPECIAL inst CLOSE_SPECIAL { <span class="hljs-built_in">..</span>. }
<span class="hljs-built_in">..</span>.
</code></pre></div>
<p>Le problème, c’est que si je suis dans un état:</p>
<div class="hljs-code-div"><div class="hljs-line-numbers"><span></span></div><pre><code class="hljs language-autoit"><span class="hljs-keyword">IF</span> <span class="hljs-built_in">exp</span> inst <span class="hljs-keyword">ELSE</span> . OPEN_SPECIAL
</code></pre></div>
<p>Le parseur ne peut pas savoir si je suis en train d’activer la troisième règle de <code>inst</code> ou si je suis en train d’activer la quatrième règle, depuis la règle <code>list_inst</code>. L’idéal dans ce cas serait de pouvoir changer la grammaire mais je n’en ai pas vraiment la liberté.</p>
<p>Du coup, je pensais utiliser le lexer pour aller jeter un oeil un peu en avant histoire de regarder ce que j’y trouve. Avec dans l’idée de dire "si je tombe sur ELSE, plutôt que mettre un token OPEN_SPECIAL, je met un token OPEN_SPECIAL_ELSE", ce qui règlerait le problème de la grammaire.</p>
<p>Du coup dans le lexer, j’ai tenté un truc <em>super élégant</em> à base de:</p>
<div class="hljs-code-div"><div class="hljs-line-numbers"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div><pre><code class="hljs language-ceylon">| <span class="hljs-string">"open_special"</span> {
<span class="hljs-keyword">let</span> pos = lexbuf.Lexing.lex<span class="hljs-number">_</span>curr<span class="hljs-number">_p</span>os <span class="hljs-keyword">in</span>
look<span class="hljs-number">_f</span>or<span class="hljs-number">_</span><span class="hljs-keyword">else</span> lexbuf ;
lexbuf.Lexing.lex<span class="hljs-number">_</span>curr<span class="hljs-number">_p</span>os <- pos ;
OPEN<span class="hljs-number">_</span>SPECIAL
}
and look<span class="hljs-number">_f</span>or<span class="hljs-number">_</span><span class="hljs-keyword">else</span> = parse
| blank { look<span class="hljs-number">_f</span>or<span class="hljs-number">_</span><span class="hljs-keyword">else</span> lexbuf }
| <span class="hljs-string">"else"</span> { next<span class="hljs-number">_</span><span class="hljs-keyword">else</span><span class="hljs-number">_</span>special := true }
| <span class="hljs-string">"//"</span> { do<span class="hljs-number">_</span>lex<span class="hljs-number">_</span>comment lexbuf }
| <span class="hljs-number">_</span> { () }
</code></pre></div>
<p>Juste pour voir si déjà je pouvais faire mon backtracking tranquillement, sans même tenter d’insérer quoi que ce soit pour l’instant. Mais ça explose (sur une opération <code>refill_buff</code>). Alors avant d’investiguer plus (parce que le programme en question n’est pas un petit morceau), je voudrais déjà savoir si cette idée à la moindre chance de fonctionner ou si quelque chose d’intrinsèque à OcamlLex (dans la manière dont il gère le buffer par exemple) l’empêche complètement.</p>