Je suis bloqué sur un problème d'écriture d'un analyseur syntaxique en Haskell et j'espère que quelqu'un pourra m'aider !
Il est un peu plus compliqué que mon analyseur habituel car il y a deux niveaux d'analyse. D'abord, la définition du langage est analysée dans un AST, puis cet AST est transformé en un autre analyseur qui analyse le langage réel.
J'ai bien avancé jusqu'à présent mais je suis bloqué sur l'implémentation de la récursion dans la définition du langage. Comme la définition du langage est transformée de l'AST en un analyseur syntaxique dans une fonction récursive, je n'arrive pas à comprendre comment elle peut s'appeler elle-même si elle n'existe pas encore.
J'ai un peu de mal à expliquer mon problème, alors peut-être qu'un exemple m'aidera.
La définition du langage peut indiquer qu'un langage se compose de trois mots-clés en séquence, puis d'une récursion optionnelle entre parenthèses.
A B C ($RECURSE)
Ce qui serait analysé dans un AST comme :
[Keyword "A", Keyword "B", Keyword "C", Optional (Many [Recurse])]
Le site Many
n'est pas vraiment nécessaire pour cet exemple, mais dans mon projet actuel, les blocs optionnels peuvent contenir plusieurs éléments de syntaxe, donc un fichier Optional
contiendrait un Many
con n éléments.
Je voudrais ensuite qu'il soit transformé en un analyseur syntaxique qui analyse des chaînes de caractères comme :
A B C
A B C (A B C)
A B C (A B C (A B C))
J'ai réduit mon projet à l'exemple le plus simple possible. Vous pouvez voir mon commentaire TODO où je suis coincé en essayant d'implémenter la récursion.
{-# LANGUAGE OverloadedStrings #-}
module Example
( runExample,
)
where
import Control.Applicative hiding (many, some)
import Data.Text (Text)
import Data.Void
import System.IO as SIO
import Text.Megaparsec hiding (State)
import Text.Megaparsec.Char (space1, string')
import qualified Text.Megaparsec.Char.Lexer as L
import Text.Megaparsec.Debug
import Text.Pretty.Simple (pPrint)
-- Types
type Parser = Parsec Void Text
data SyntaxAst = Keyword Text | Recurse | Optional SyntaxAst | Many [SyntaxAst]
-- Megaparsec Base Parsers
-- Space consumer - used by other parsers to ignore whitespace
sc :: Parser ()
sc =
L.space
space1
(L.skipLineComment "--")
(L.skipBlockComment "/*" "*/")
-- Runs a parser, then consumes any left over space with sc
lexeme :: Parser a -> Parser a
lexeme = L.lexeme sc
-- Parses a string, then consumes any left over space with sc
symbol :: Text -> Parser Text
symbol = L.symbol sc
-- Parses something between parentheses
inParens :: Parser a -> Parser a
inParens =
between
(symbol "(")
(symbol ")")
-- Transforms the AST into a parser
transformSyntaxExprToParser :: SyntaxAst -> Parser [Text]
transformSyntaxExprToParser (Many exprs) = dbg "Many" (createParser exprs)
transformSyntaxExprToParser (Keyword text) = dbg "Keyword" (pure <$> lexeme (string' text))
transformSyntaxExprToParser (Optional inner) = dbg "Optional" (option [] (try (inParens (transformSyntaxExprToParser inner))))
transformSyntaxExprToParser Recurse = dbg "Recurse" (pure ["TODO"]) -- TODO: How do I recurse here?
-- transformSyntaxExprToParser s Recurse = dbg "Recurse" (createParser s) -- Seems to work in the example, but in my actual application creates an infinite loop and freezes
-- Walks over the parser AST and convert it to a parser
createParser :: [SyntaxAst] -> Parser [Text]
createParser expressions =
do
foldr1 (liftA2 (<>)) (fmap transformSyntaxExprToParser expressions)
runExample :: IO ()
runExample = do
-- To make the example simple, lets cut out the language definition parsing and just define
-- it literally.
let languageParser = createParser [Keyword "A", Keyword "B", Keyword "C", Optional (Many [Recurse])]
let run p = runParser p "" "A B C (A B C (A B C))"
let result = run languageParser
case result of
Left bundle -> SIO.putStrLn (errorBundlePretty bundle)
Right xs -> pPrint xs
J'ai essayé quelques trucs :
- Transmettez l'AST original à l
transformSyntaxExprToParser
et appeler la fonctioncreateParser
lorsque leRecurse
est rencontré. Cela n'a pas fonctionné à cause des boucles infinies. - Utilisation de références mutables comme IORef/STRef pour passer une référence qui est mise à jour pour référencer le parseur final une fois la transformation terminée. Je n'ai pas réussi à trouver comment enfiler les monades IO/ST dans la fonction de transformation du parseur.
- Monades d'état. Je n'ai pas réussi à trouver comment faire passer une référence par la monade d'état.
J'espère que cela a du sens, faites-moi savoir si j'ai besoin d'en dire plus. Je peux aussi mettre en avant mon projet complet si cela peut aider.
Merci de nous lire !
Edit : J'ai apporté des modifications à mon exemple original pour démontrer le problème de la boucle infinie (en intégrant les excellentes suggestions de la réponse ci-dessous) à l'adresse suivante https://pastebin.com/DN0JJ9BA