haskell - semantico - Ejemplos de analizador completo con parsec?
analizador sintactico jflex (3)
El libro Write Yourself a Scheme in 48 Hours es un excelente resumen en profundidad y tutorial de la funcionalidad de Parsec. Lo guía a través de todo con ejemplos en profundidad, y al final ha implementado una parte bastante significativa del esquema en un intérprete de parsec.
Estoy intentando hacer un analizador para un lenguaje funcional simple, un poco como Caml, pero parece que estoy atascado con las cosas más simples.
Así que me gustaría saber si hay algunos ejemplos más completos de analizadores parsec
, algo que va más allá de "así es como se analizan 2 + 3". Especialmente las llamadas de función en términos y similares.
Y he leído "Escríbele un esquema", pero la sintaxis del esquema es bastante simple y no ayuda mucho para aprender.
El mayor número de problemas que tengo es cómo usar try
, <|>
y choice
correctamente, porque realmente no entiendo por qué Parsec nunca analiza a(6)
como una llamada de función usando este analizador:
expr = choice [number, call, ident]
number = liftM Number float <?> "Number"
ident = liftM Identifier identifier <?> "Identifier"
call = do
name <- identifier
args <- parens $ commaSep expr
return $ FuncCall name args
<?> "Function call"
EDITAR Agregado un código para completar, aunque en realidad no es lo que pedí:
AST.hs
module AST where
data AST
= Number Double
| Identifier String
| Operation BinOp AST AST
| FuncCall String [AST]
deriving (Show, Eq)
data BinOp = Plus | Minus | Mul | Div
deriving (Show, Eq, Enum)
Lexer.hs
module Lexer (
identifier, reserved, operator, reservedOp, charLiteral, stringLiteral,
natural, integer, float, naturalOrFloat, decimal, hexadecimal, octal,
symbol, lexeme, whiteSpace, parens, braces, angles, brackets, semi,
comma, colon, dot, semiSep, semiSep1, commaSep, commaSep1
) where
import Text.Parsec
import qualified Text.Parsec.Token as P
import Text.Parsec.Language (haskellStyle)
lexer = P.makeTokenParser haskellStyle
identifier = P.identifier lexer
reserved = P.reserved lexer
operator = P.operator lexer
reservedOp = P.reservedOp lexer
charLiteral = P.charLiteral lexer
stringLiteral = P.stringLiteral lexer
natural = P.natural lexer
integer = P.integer lexer
float = P.float lexer
naturalOrFloat = P.naturalOrFloat lexer
decimal = P.decimal lexer
hexadecimal = P.hexadecimal lexer
octal = P.octal lexer
symbol = P.symbol lexer
lexeme = P.lexeme lexer
whiteSpace = P.whiteSpace lexer
parens = P.parens lexer
braces = P.braces lexer
angles = P.angles lexer
brackets = P.brackets lexer
semi = P.semi lexer
comma = P.comma lexer
colon = P.colon lexer
dot = P.dot lexer
semiSep = P.semiSep lexer
semiSep1 = P.semiSep1 lexer
commaSep = P.commaSep lexer
commaSep1 = P.commaSep1 lexer
Parser.hs
module Parser where
import Control.Monad (liftM)
import Text.Parsec
import Text.Parsec.String (Parser)
import Lexer
import AST
expr = number <|> callOrIdent
number = liftM Number float <?> "Number"
callOrIdent = do
name <- identifier
liftM (FuncCall name) (parens $ commaSep expr) <|> return (Identifier name)
Escribí una serie de ejemplos sobre cómo analizar los números romanos con parsec. Es bastante básico, pero a ti oa otros recién llegados les puede resultar útil:
Hmm
*Expr> parse expr "" "a(6)"
Right (FuncCall "a" [Number 6.0])
Esa parte me funciona después de rellenar las piezas faltantes.
Edición: llené las piezas faltantes escribiendo mi propio analizador float
, que podría analizar literales enteros. El analizador float
de Text.Parsec.Token
por otro lado, solo analiza literales con una parte fraccionada o un exponente, por lo que no pudo analizar el "6".
Sin embargo,
*Expr> parse expr "" "variable"
Left (line 1, column 9):
unexpected end of input
expecting "("
cuando la llamada falla después de haber analizado un identificador, esa parte de la entrada se consume, por lo tanto, no se intenta identificar y el análisis general falla. Puede a) hacer que try call
en la lista de opciones de expr
, de modo que la llamada falle sin consumir datos, o b) escribir un analizador callOrIdent para usar en expr
, por ejemplo
callOrIdent = do
name <- identifier
liftM (FuncCall name) (parens $ commaSep expr) <|> return (Identifier name)
lo que evita try
y por lo tanto puede rendir mejor.