haskell 带有拼写检查的解析器
haskell parser with spell-checking
为了更多地了解 Haskell(尤其是 Monads),我正在尝试构建一个拼写检查器。我的目标是能够浏览 LaTeX 文档并对不在字典列表中的单词执行某些操作。
我已经编写了解析器(字符串到 AST),我将其代码粘贴在下面。它基本上 returns 将 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
您可以使用 Parsec 的 getPosition
操作来获取输入流中的当前位置。然后您可以将其存储在您的 AST 类型中(即将其更改为
data TexFile = Items [(SourcePos, TexTerm)]
)
你的基本问题是你丢弃了关于
白色 space 在文件中。如果您将白色 space 记录为另一个 TextTerm
您可以 a) 从 TexFile 重建文件内容,并且 b) 知道
每个 TextTerm 出现在哪一行。
所以一种方法是为 TexTerm
添加一个 WhiteSpace
构造函数:
data TexTerm = Comment String
| ...
| WhiteSpace String
现在,当您遍历 AST 时,您可以通过计算每个 WhiteSpace
构造函数中换行符的数量来确定每个构造位于哪一行。
但是,这会使您的解析器复杂化,因为您正在使用 lexeme
跳过白色 space。如果您需要做的只是对 TeX 文档进行拼写检查,
我建议使用更简单的数据结构的 "tag-soup" 方法:
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 输入标记化。
为了更多地了解 Haskell(尤其是 Monads),我正在尝试构建一个拼写检查器。我的目标是能够浏览 LaTeX 文档并对不在字典列表中的单词执行某些操作。
我已经编写了解析器(字符串到 AST),我将其代码粘贴在下面。它基本上 returns 将 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
您可以使用 Parsec 的 getPosition
操作来获取输入流中的当前位置。然后您可以将其存储在您的 AST 类型中(即将其更改为
data TexFile = Items [(SourcePos, TexTerm)]
)
你的基本问题是你丢弃了关于 白色 space 在文件中。如果您将白色 space 记录为另一个 TextTerm 您可以 a) 从 TexFile 重建文件内容,并且 b) 知道 每个 TextTerm 出现在哪一行。
所以一种方法是为 TexTerm
添加一个 WhiteSpace
构造函数:
data TexTerm = Comment String
| ...
| WhiteSpace String
现在,当您遍历 AST 时,您可以通过计算每个 WhiteSpace
构造函数中换行符的数量来确定每个构造位于哪一行。
但是,这会使您的解析器复杂化,因为您正在使用 lexeme
跳过白色 space。如果您需要做的只是对 TeX 文档进行拼写检查,
我建议使用更简单的数据结构的 "tag-soup" 方法:
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 输入标记化。