为了学习更多Haskell(尤其是Monads),我正在尝试构建一个拼写检查器。我的目标是能够浏览一个LaTeX文档并对不在字典列表中的单词做一些事情。
我已经编写了解析器(字符串到AST),我将在下面粘贴代码。它基本上返回了相关部分(文本,公式,命令等)的LaTeX源。我想知道如何构建一个程序,使得在列表中找不到的每个单词上,我们都会询问用户要替换哪个单词。
(我们真正关心的是LaTeX,我们的部分源代码是文本,必须进行拼写检查,其他部分是公式而不是简单的英语)
让我用一些期望行为的例子更清楚地解释一下(为简单公式介于$ HERE IS THE FORMULA $
之间)
来源:
This is my frst file and here
we have a formula: $\forall x \quad x$
期望的行为:
In file 'first.tex' at line 1: 'frst' unknown
1 This is my **frst** file and here
2 we have a formula: $\forall x \quad x$
Action [Add word to dictionary / Change word]?
主要的问题是,在我解析了文件之后,我留下了一个AST,并且没有更多对行的引用,所以我无法像上面的例子一样显示它们。
解析器的代码:
import System.Environment
import Text.Parsec (ParseError)
import Text.Parsec.String (Parser, parseFromFile)
import Text.Parsec.String.Parsec (try)
import Text.Parsec.String.Char (oneOf, char, digit, string, letter, satisfy, noneOf, anyChar)
import Text.Parsec.String.Combinator (many1, choice, chainl1, between, count, option, optionMaybe, optional, manyTill, eof, lookAhead)
import Control.Applicative ((<$>), (<*>), (<*), (*>), (<|>), many, (<$))
import Control.Monad (void, ap, mzero)
import Data.Char (isLetter, isDigit)
import FunctionsAndTypesForParsing
data TexFile = Items [TexTerm]
deriving (Eq, Show)
data TexTerm = Comment String
| Formula String
| Command String [TexFile]
| Text String
| Block TexFile
deriving (Eq, Show)
-- We get the AST as output
texFile :: Parser TexFile
texFile = Items <$> (many texTerm) <* (optional (try $ eof))
texTerm :: Parser TexTerm
texTerm = lexeme $ (try comment <|> text <|> formula <|> command <|> block)
whitespace :: Parser ()
whitespace = void $ try $ oneOf " \n\t"
lexeme :: Parser a -> Parser a
lexeme p = p <* (many $ whitespace)
comment :: Parser TexTerm
comment = Comment <$> between (string "%") (string "\n") (many $ noneOf "\n")
formula :: Parser TexTerm
formula = Formula <$> (try singledollar <|> doubledollar <|> equation <|> align)
where
singledollar = between (string "$") (string "$") (many1 $ noneOf "$")
doubledollar = between (string "$$") (string "$$") (many1 $ noneOf "$$")
equation = try $ between (try $ string "\\begin{equation}") (string "\\end{equation}") (manyTill anyChar (lookAhead $ try $ string "\\end{equation}"))
align = try $ between (try $ string "\\begin{align*}") (string "\\end{align*}") (manyTill anyChar (lookAhead $ try $ string "\\end{align*}"))
command :: Parser TexTerm
command = Command <$> com <*> (many arg)
where
com = char '\\' *> (manyTill (try letter <|> oneOf "*") (lookAhead $ try $ oneOf "[{ \\\n\t"))
arg = (try (between (string "{") (string "}") texFile)
<|> (between (string "[") (string "]") texFile)
)
text :: Parser TexTerm
text = Text <$> many1 textualchars
where
textualchars = try letter <|> digit <|> oneOf " \n\t\r,.*:;-<>#@()`_!'?"
block :: Parser TexTerm
block = Block <$> between (string "{") (string "}") texFile
答案 0 :(得分:2)
您可以使用Parsec的getPosition
操作来获取输入流中的当前位置。然后,您可以将其存储在AST类型中(即将其更改为
data TexFile = Items [(SourcePos, TexTerm)]
)
答案 1 :(得分:1)
您的基本问题是您丢失了相关信息 文件中的空白区域。如果您将空白区域记录为另一个TexTerm 你可以a)从TexFile重建文件内容,b)知道 每个TexTerm出现在哪一行。
因此,一种方法是为WhiteSpace
添加TexTerm
构造函数:
data TexTerm = Comment String
| ...
| WhiteSpace String
现在,当您遍历AST时,您可以通过计算每个WhiteSpace
构造函数中的换行符数来确定每个构造所在的行。
但是,由于您使用lexeme
跳过空白区域,这会使解析器复杂化。如果您需要做的只是拼写检查TeX文档,
我会建议一个&#34; tag-soup&#34;使用更简单的数据结构的方法:
type TexFile = [TexTerm]
data TeXTerm = Comment String
| Formula String
| Command String -- e.g. \someCommand
| Text String
| Sym String -- e.g. Sym "{" or Sym "}"
| WhiteSpace String -- e.g. WhiteSpace "\n"
请注意,TeXFile
和TexTerm
是平的 - 非递归的 - 数据结构。我们只是简单地标记TeX输入。