summaryrefslogtreecommitdiff
path: root/source/Syntax/Token.hs
diff options
context:
space:
mode:
Diffstat (limited to 'source/Syntax/Token.hs')
-rw-r--r--source/Syntax/Token.hs633
1 files changed, 0 insertions, 633 deletions
diff --git a/source/Syntax/Token.hs b/source/Syntax/Token.hs
deleted file mode 100644
index b16379b..0000000
--- a/source/Syntax/Token.hs
+++ /dev/null
@@ -1,633 +0,0 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-
--- |
--- This module defines the lexer and its associated data types.
--- The lexer takes `Text` as input and produces a stream of tokens
--- annotated with positional information. This information is bundled
--- together with the original raw input for producing error messages.
---
--- The lexer perfoms some normalizations to make describing the grammar easier.
--- Words outside of math environments are case-folded. Some commands are analysed
--- as variable tokens and are equivalent to their respective unicode variants
--- (α, β, γ, ..., 𝔸, 𝔹, ℂ, ...). Similarly, @\\begin{...}@ and @\\end{...}@ commands
--- are each parsed as single tokens.
---
-module Syntax.Token
- ( Token(..)
- , VariableDisplay(..)
- , VariableSuffix(..)
- , displayVariable
- , renderVariableText
- , tokToString
- , tokToText
- , TokStream(..)
- , Located(..)
- , runLexer
- , gatherImports
- ) where
-
-
-import Base hiding (many)
-
-import Report.Location
-
-import Control.DeepSeq (NFData)
-import Control.Monad.Combinators
-import Control.Monad.State.Strict
-import Data.List.NonEmpty qualified as NonEmpty
-import Data.Text qualified as Text
-import Prettyprinter (Pretty(..))
-import Text.Megaparsec hiding (Token, Label, label)
-import Text.Megaparsec.Char qualified as Char
-import Text.Megaparsec.Char.Lexer qualified as Lexer
-import Tptp.UnsortedFirstOrder (isAsciiLetter, isAsciiAlphaNumOrUnderscore)
-
-
-runLexer :: FileId -> String -> Text -> Either (ParseErrorBundle Text Void) ([FilePath], [[Located Token]])
-runLexer fileId file raw = runParser (evalStateT document (initLexerState fileId)) file raw
-
-
-type Lexer = StateT LexerState (Parsec Void Text)
-
-
-data LexerState = LexerState
- { frames :: !(NonEmpty Frame)
- , currentFileId :: !FileId
- } deriving (Show, Eq)
-
-data Frame
- = TopText
- | MathFrame !Text
- | TextFrame !Int
- deriving (Show, Eq)
-
-initLexerState :: FileId -> LexerState
-initLexerState fileId = LexerState (TopText :| []) fileId
-
-topFrameOf :: LexerState -> Frame
-topFrameOf LexerState{frames = frame :| _} = frame
-
-topFrame :: Lexer Frame
-topFrame = gets topFrameOf
-
-pushFrame :: Frame -> LexerState -> LexerState
-pushFrame frame st@LexerState{frames = top :| rest} =
- st{frames = frame :| (top : rest)}
-
-popFrame :: LexerState -> LexerState
-popFrame st@LexerState{frames = _ :| []} = st
-popFrame st@LexerState{frames = _ :| (top : rest)} =
- st{frames = top :| rest}
-
-modifyTopFrame :: (Frame -> Frame) -> LexerState -> LexerState
-modifyTopFrame f st@LexerState{frames = top :| rest} =
- st{frames = f top :| rest}
-
--- Token recognizers only emit tokens; this is the single place that changes
--- lexical context.
-advance :: Token -> LexerState -> LexerState
-advance tok st =
- case (topFrameOf st, tok) of
- (TopText, BeginEnv "math") ->
- pushFrame (MathFrame "math") st
- (TextFrame{}, BeginEnv "math") ->
- pushFrame (MathFrame "math") st
- (TopText, BeginEnv "align*") ->
- pushFrame (MathFrame "align*") st
- (TextFrame{}, BeginEnv "align*") ->
- pushFrame (MathFrame "align*") st
- (MathFrame env, EndEnv env')
- | env == env' ->
- popFrame st
- (MathFrame{}, BeginEnv "text") ->
- pushFrame (TextFrame 1) st
- (TextFrame n, InvisibleBraceL) ->
- modifyTopFrame (const (TextFrame (n + 1))) st
- (TextFrame n, InvisibleBraceR)
- | n > 1 ->
- modifyTopFrame (const (TextFrame (n - 1))) st
- (TextFrame 1, EndEnv "text") ->
- popFrame st
- _ ->
- st
-
--- |
--- A token stream as input stream for a parser. Contains the raw input
--- before tokenization as 'Text' for showing error messages.
---
-data TokStream = TokStream
- { rawInput :: !Text
- , unTokStream :: ![[Located Token]]
- } deriving (Show, Eq)
-
-instance Semigroup TokStream where
- TokStream raw1 toks1 <> TokStream raw2 toks2 = TokStream (raw1 <> raw2) (toks1 <> toks2)
-
-instance Monoid TokStream where
- mempty = TokStream mempty mempty
-
--- | A LaTeX token.
--- Invisible delimiters 'InvisibleBraceL' and 'InvisibleBraceR' are
--- unescaped braces used for grouping in TEX (@{@),
--- visibles braces are escaped braces (@\\{@).
-data Token
- = Word !Text
- | Variable !Text
- | Symbol !Text
- | Integer !Int
- | Command !Text
- | Label Text -- ^ A /@\\label{...}@/ command (case-sensitive).
- | Ref (NonEmpty Text) -- ^ A /@\\ref{...}@/ command (case-sensitive).
- | BeginEnv !Text
- | EndEnv !Text
- | ParenL | ParenR
- | BracketL | BracketR
- | VisibleBraceL | VisibleBraceR
- | InvisibleBraceL | InvisibleBraceR
- deriving (Show, Eq, Ord, Generic, Hashable, NFData)
-
-instance IsString Token where
- fromString w = Word (Text.pack w)
-
-data VariableDisplay = VariableDisplay
- { variableBaseText :: !Text
- , variableSuffix :: !(Maybe VariableSuffix)
- } deriving (Show, Eq, Ord)
-
-data VariableSuffix
- = VariableSubscript !Text
- | VariableTicks !Int
- deriving (Show, Eq, Ord)
-
-displayVariable :: Text -> VariableDisplay
-displayVariable rawName =
- case splitVariableBase rawName of
- Nothing ->
- VariableDisplay rawName Nothing
- Just (baseText, suffixText) ->
- VariableDisplay baseText (displayVariableSuffix suffixText)
-
-renderVariableText :: Text -> Text
-renderVariableText rawName =
- case displayVariable rawName of
- VariableDisplay baseText Nothing ->
- baseText
- VariableDisplay baseText (Just (VariableTicks n)) ->
- baseText <> Text.replicate n "'"
- VariableDisplay baseText (Just (VariableSubscript subscriptText)) ->
- baseText <> renderSubscriptText subscriptText
-
-splitVariableBase :: Text -> Maybe (Text, Text)
-splitVariableBase rawName =
- matchBlackboardBase rawName <|> matchGreekBase rawName <|> matchSingleLetterBase rawName
-
-matchBlackboardBase :: Text -> Maybe (Text, Text)
-matchBlackboardBase rawName = do
- suffixText <- Text.stripPrefix "bb" rawName
- case Text.uncons suffixText of
- Just (upper, rest)
- | 'A' <= upper && upper <= 'Z' ->
- Just ("bb" <> Text.singleton upper, rest)
- _ ->
- Nothing
-
-matchGreekBase :: Text -> Maybe (Text, Text)
-matchGreekBase rawName =
- asum
- [ (\suffixText -> (rendered, suffixText)) <$> Text.stripPrefix prefix rawName
- | (prefix, rendered) <- greekVariables
- ]
-
-matchSingleLetterBase :: Text -> Maybe (Text, Text)
-matchSingleLetterBase rawName = do
- (baseChar, suffixText) <- Text.uncons rawName
- pure (Text.singleton baseChar, suffixText)
-
-displayVariableSuffix :: Text -> Maybe VariableSuffix
-displayVariableSuffix suffixText
- | Text.null suffixText =
- Nothing
- | Text.all (== '_') suffixText =
- Just (VariableTicks (Text.length suffixText))
- | otherwise =
- Just (VariableSubscript (Text.replace "_" "'" suffixText))
-
-renderSubscriptText :: Text -> Text
-renderSubscriptText subscriptText
- | Text.length subscriptText == 1 =
- "_" <> subscriptText
- | otherwise =
- "_{" <> subscriptText <> "}"
-
-greekVariables :: [(Text, Text)]
-greekVariables =
- [ ("alpha", "α"), ("beta", "β"), ("gamma", "γ"), ("delta", "δ")
- , ("epsilon", "ε"), ("zeta", "ζ"), ("eta", "η"), ("theta", "θ")
- , ("iota", "ι"), ("kappa", "κ"), ("lambda", "λ"), ("mu", "μ")
- , ("nu", "ν"), ("xi", "ξ"), ("pi", "π"), ("rho", "ρ"), ("sigma", "σ")
- , ("tau", "τ"), ("upsilon", "υ"), ("phi", "φ"), ("chi", "χ")
- , ("psi", "ψ"), ("omega", "ω")
- , ("Gamma", "Γ"), ("Delta", "Δ"), ("Theta", "Θ"), ("Lambda", "Λ")
- , ("Xi", "Ξ"), ("Pi", "Π"), ("Sigma", "Σ"), ("Upsilon", "Υ")
- , ("Phi", "Φ"), ("Psi", "Ψ"), ("Omega", "Ω")
- ]
-
-tokToText :: Token -> Text
-tokToText = \case
- Word w -> w
- Variable v -> renderVariableText v
- Symbol s -> s
- Integer n -> Text.pack (show n)
- Command cmd -> Text.cons '\\' cmd
- Label m -> "\\label{" <> m <> "}"
- Ref ms -> "\\ref{" <> Text.intercalate ", " (toList ms) <> "}"
- BeginEnv "math" -> "$"
- EndEnv "math" -> "$"
- BeginEnv env -> "\\begin{" <> env <> "}"
- EndEnv env -> "\\end{" <> env <> "}"
- ParenL -> "("
- ParenR -> ")"
- BracketL -> "["
- BracketR -> "]"
- VisibleBraceL -> "\\{"
- VisibleBraceR -> "\\}"
- InvisibleBraceL -> "{"
- InvisibleBraceR -> "}"
-
-tokToString :: Token -> String
-tokToString = Text.unpack . tokToText
-
-instance Pretty Token where
- pretty = \case
- Word w -> pretty w
- Variable v -> pretty (renderVariableText v)
- Symbol s -> pretty s
- Integer n -> pretty n
- Command cmd -> "\\" <> pretty cmd
- Label m -> "\\label{" <> pretty m <> "}"
- Ref m -> "\\ref{" <> pretty m <> "}"
- BeginEnv env -> "\\begin{" <> pretty env <> "}"
- EndEnv env -> "\\end{" <> pretty env <> "}"
- ParenL -> "("
- ParenR -> ")"
- BracketL -> "["
- BracketR -> "]"
- VisibleBraceL -> "\\{"
- VisibleBraceR -> "\\}"
- InvisibleBraceL -> "{"
- InvisibleBraceR -> "}"
-
-
-data Located a = Located
- { startPos :: !Location
- , unLocated :: !a
- , postWhitespace :: Whitespace
- } deriving (Show, Functor)
-
-data Whitespace = NoSpace | Space deriving (Show)
-
-collapseWhitespace :: [Whitespace] -> Whitespace
-collapseWhitespace = \case
- Space : _ -> Space
- NoSpace : ws -> collapseWhitespace ws
- [] -> NoSpace
-
-instance Eq a => Eq (Located a) where (==) = (==) `on` unLocated
-instance Ord a => Ord (Located a) where compare = compare `on` unLocated
-
-
-document :: Lexer ([FilePath], [[Located Token]])
-document = do
- is <- importBlock
- es <- many environment
- eof
- return (unLocated <$> is, es)
-
-
-importBlock :: Lexer [Located FilePath]
-importBlock = do
- void (skipManyTill skipChar importLineOrBeginEnvOrEof)
- many importLine
- where
- -- When skipping to the import block, we first need to try parsing whitespace to properly handle comments and avoid picking up a commented import line at the start of the import block.
- skipChar, importLineOrBeginEnvOrEof :: Lexer ()
- skipChar = comment <|> void anySingle
- importLineOrBeginEnvOrEof =
- lookAhead
- (void (Char.string "\\import{")
- <|> void beginToplevelEnvironment)
- <|> eof
-
- importLine :: Lexer (Located FilePath) = lexeme do
- Char.string "\\import{"
- path <- some (satisfy isTheoryNameChar)
- Char.char '}'
- pure path
-
- isTheoryNameChar :: Char -> Bool
- isTheoryNameChar c =
- c /= '}' && c /= '\n' && c /= '\r' && c /= '\0'
-
--- | Scan only the leading import block. Source-graph construction uses this
--- authority-free pass before parsing modules under their composed syntax.
-gatherImports
- :: FileId
- -> String
- -> Text
- -> Either (ParseErrorBundle Text Void) [Located FilePath]
-gatherImports fileId file =
- runParser (evalStateT importBlock (initLexerState fileId)) file
-
-
-beginToplevelEnvironment :: Lexer (Located Text)
-beginToplevelEnvironment = lexeme do
- Char.string "\\begin{"
- env :: Text <- asum (Char.string <$> ["definition", "theorem", "lemma", "axiom", "proof", "corollary", "proposition", "claim", "abbreviation", "datatype", "inductive", "signature", "struct"])
- Char.char '}'
- pure env
-
--- | Parses tokens, switching tokenizing frames when encountering math and text environments.
-environment :: Lexer [Located Token]
-environment = do
- env <- skipManyTill (comment <|> void anySingle) beginToplevelEnvironment
- lts <- go (unLocated env) id
- pure ((BeginEnv <$> env) : lts)
- where
- go env f = do
- frame <- topFrame
- r <- optional (nextTokenFor frame)
- case r of
- Nothing ->
- pure (f [])
- Just t@Located{unLocated = EndEnv env'}
- | frame == TopText && env == env' ->
- pure (f [t])
- Just t -> do
- modify' (advance (unLocated t))
- go env (f . (t:))
-{-# INLINE environment #-}
-
-nextTokenFor :: Frame -> Lexer (Located Token)
-nextTokenFor = \case
- TopText -> normalToken
- MathFrame{} -> mathToken
- TextFrame n -> textToken n
-
--- | Parses a single normal-mode token.
-normalToken :: Lexer (Located Token)
-normalToken =
- word <|> symbol <|> beginMath <|> beginAlign <|> subEnvironment <|> opening <|> closing <|> label <|> ref <|> end <|> command
-
--- | Parses a single math mode token.
-mathToken :: Lexer (Located Token)
-mathToken =
- var <|> symbol <|> number <|> beginCases <|> endAlign <|> endCases <|> opening <|> closing <|> beginText <|> beginExplanation <|> endMath <|> command
-
-beginText :: Lexer (Located Token)
-beginText = lexeme do
- Char.string "\\text{" <|> Char.string "\\textbox{"
- pure (BeginEnv "text")
-
--- | Same as text modulo spacing, so we treat it synonymously
-beginExplanation :: Lexer (Located Token)
-beginExplanation = lexeme do
- Char.string "\\explanation{"
- pure (BeginEnv "text")
-
-subEnvironment :: Lexer (Located Token)
-subEnvironment = beginOrEnd ["enumerate", "subproof", "byCase"]
- where
- beginOrEnd envs = asum [beginEnv env <|> endEnv env | env <- envs]
- beginEnv env = lexeme do
- Char.string ("\\begin{" <> env <> "}")
- pure (BeginEnv env)
- endEnv env = lexeme do
- Char.string ("\\end{" <> env <> "}")
- pure (EndEnv env)
-
--- | Normal mode embedded into math mode via @\text{...}@.
-textToken :: Int -> Lexer (Located Token)
-textToken n = word <|> symbol <|> textEnd <|> beginMath <|> beginAlign <|> opening' <|> closing' <|> ref <|> command
- where
- textEnd = lexeme do
- guard (n == 1)
- Char.char '}'
- pure (EndEnv "text")
-
- opening' = lexeme (group <|> optional (Char.string "\\left") *> (brace <|> paren <|> bracket))
- where
- brace = VisibleBraceL <$ lexeme (Char.string "\\{")
- group = InvisibleBraceL <$ lexeme (Char.char '{')
- paren = ParenL <$ lexeme (Char.char '(')
- bracket = BracketL <$ lexeme (Char.char '[')
-
- closing' = lexeme (group <|> optional (Char.string "\\right") *> (brace <|> paren <|> bracket))
- where
- brace = VisibleBraceR <$ lexeme (Char.string "\\}")
- group = InvisibleBraceR <$ lexeme (Char.char '}')
- paren = ParenR <$ lexeme (Char.char ')')
- bracket = BracketR <$ lexeme (Char.char ']')
-
-
--- | Parses a single begin math token.
-beginMath :: Lexer (Located Token)
-beginMath = lexeme do
- Char.string "\\(" <|> Char.string "\\[" <|> Char.string "$"
- pure (BeginEnv "math")
-
-beginAlign :: Lexer (Located Token)
-beginAlign = lexeme do
- Char.string "\\begin{align*}"
- pure (BeginEnv "align*")
-
-beginCases :: Lexer (Located Token)
-beginCases = lexeme do
- Char.string "\\begin{cases}"
- pure (BeginEnv "cases")
-
--- | Parses a single end math token.
-endMath :: Lexer (Located Token)
-endMath = lexeme do
- Char.string "\\)" <|> Char.string "\\]" <|> Char.string "$"
- pure (EndEnv "math")
-
-endAlign :: Lexer (Located Token)
-endAlign = lexeme do
- Char.string "\\end{align*}"
- pure (EndEnv "align*")
-
-endCases :: Lexer (Located Token)
-endCases = lexeme do
- Char.string "\\end{cases}"
- pure (EndEnv "cases")
-
-
--- | Parses the end of an environment.
--- Commits only after having seen "\end{".
-end :: Lexer (Located Token)
-end = lexeme do
- notFollowedBy (Char.string "\\end{cases}")
- Char.string "\\end{"
- env <- some (Char.letterChar <|> Char.char '*')
- Char.char '}'
- pure (EndEnv (Text.pack env))
-
-
-
--- | Parses a word. Words are returned casefolded, since we want to ignore their case later on.
-word :: Lexer (Located Token)
-word = lexeme do
- w <- some (Char.letterChar <|> Char.char '\'' <|> Char.char '-')
- let t = Word (Text.toCaseFold (Text.pack w))
- pure t
-
-number :: Lexer (Located Token)
-number = lexeme $ Integer <$> Lexer.decimal
-
-
-var :: Lexer (Located Token)
-var = lexeme (fmap Variable var')
- where
- var' = do
- alphabeticPart <- letter <|> bb <|> greek
- variationPart <- subscript <|> ticked <|> pure ""
- pure (alphabeticPart <> variationPart)
-
- subscript :: Lexer Text
- subscript = do
- Char.char '_'
- unbraced <|> braced <|> text
- where
- unbraced = Text.singleton <$> Char.alphaNumChar
- braced = Text.pack <$> (Char.char '{' *> some (Char.alphaNumChar <|> tick) <* Char.char '}')
- text = Char.string "\\text" *> braced -- for rendering the subscript in roman type
-
- -- A bit of a hack to fit the TPTP format.
- tick :: Lexer Char
- tick = '_' <$ Char.char '\''
-
- ticked :: Lexer Text
- ticked = do
- ticks <- some tick
- pure (Text.pack ticks)
-
- letter :: Lexer Text
- letter = fmap Text.singleton Char.letterChar
-
- greek :: Lexer Text
- greek = try do
- Char.char '\\'
- l <- symbolParser greeks
- notFollowedBy Char.letterChar
- pure l
-
- greeks :: [Text]
- greeks =
- [ "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta"
- , "iota", "kappa", "lambda", "mu", "nu", "xi", "pi", "rho", "sigma"
- , "tau", "upsilon", "phi", "chi", "psi", "omega"
- , "Gamma", "Delta", "Theta", "Lambda", "Xi", "Pi", "Sigma", "Upsilon"
- , "Phi", "Psi", "Omega"
- ]
-
- bb :: Lexer Text
- bb = do
- Char.string "\\mathbb{"
- l <- symbolParser bbs
- Char.char '}'
- pure $ "bb" <> l
-
- bbs :: [Text]
- bbs = Text.singleton <$> ['A'..'Z']
-
-
- symbolParser :: [Text] -> Lexer Text
- symbolParser symbols = asum (fmap Char.string symbols)
-
-
-symbol :: Lexer (Located Token)
-symbol = lexeme do
- symb <- some (satisfy (`elem` symbols))
- pure (Symbol (Text.pack symb))
- where
- symbols :: [Char]
- symbols = ".,:;!?@=≠+-/|^><≤≥*&≈⊂⊃⊆⊇∈“”‘’"
-
--- | Parses a TEX-style command.
-command :: Lexer (Located Token)
-command = lexeme do
- Char.char '\\'
- cmd <- some Char.letterChar
- pure (Command (Text.pack cmd))
-
--- | Parses a label command and extracts its marker.
-label :: Lexer (Located Token)
-label = lexeme do
- Char.string "\\label{"
- m <- marker
- Char.char '}'
- pure (Label m)
-
--- | Parses a label command and extracts its marker.
-ref :: Lexer (Located Token)
-ref = lexeme do
- -- @\\cref@ is from @cleveref@ and @\\hyperref@ is from @hyperref@
- cmd <- Char.string "\\ref{" <|> Char.string "\\cref{" <|> Char.string "\\hyperref["
- ms <- NonEmpty.fromList <$> marker `sepBy1` Char.char ','
- case cmd of
- "\\hyperref[" -> Char.string "]{" *> some (satisfy (/= '}')) *> Char.char '}' *> pure (Ref ms)
- _ -> Char.char '}' *> pure (Ref ms)
-
-marker :: Lexer Text
-marker = do
- c <- satisfy isAsciiLetter
- cs <- takeWhileP Nothing isAsciiAlphaNumOrUnderscore
- pure (Text.cons c cs)
-
--- | Parses an opening delimiter.
-opening :: Lexer (Located Token)
-opening = lexeme (group <|> optional (Char.string "\\left") *> (paren <|> brace <|> bracket))
- where
- brace = VisibleBraceL <$ lexeme (Char.string "\\{")
- group = InvisibleBraceL <$ lexeme (Char.char '{')
- paren = ParenL <$ lexeme (Char.char '(')
- bracket = BracketL <$ lexeme (Char.char '[')
-
--- | Parses a closing delimiter.
-closing :: Lexer (Located Token)
-closing = lexeme (group <|> optional (Char.string "\\right") *> (paren <|> brace <|> bracket))
- where
- brace = VisibleBraceR <$ lexeme (Char.string "\\}")
- group = InvisibleBraceR <$ lexeme (Char.char '}')
- paren = ParenR <$ lexeme (Char.char ')')
- bracket = BracketR <$ lexeme (Char.char ']')
-
--- | Turns a Lexer into one that tracks the source position of the token
--- and consumes trailing whitespace.
-lexeme :: Lexer a -> Lexer (Located a)
-lexeme p = do
- fileId <- gets currentFileId
- start <- getSourcePos
- location <-
- either
- (fail . show)
- pure
- (fromSourcePosChecked fileId start)
- t <- p
- w <- whitespace
- pure (Located location t w)
-
-space :: Lexer Whitespace
-space = Space <$ (Char.char ' ' <|> Char.char '\n' <|> Char.char '\r')
- <|> Space <$ (Char.string "\\ " <|> Char.string "\\\\" <|> Char.string "\\!" <|> Char.string "\\," <|> Char.string "\\:" <|> Char.string "\\;" <|> Char.string "\\;")
-
-whitespace :: Lexer Whitespace
-whitespace = do
- ws <- many (spaces <|> NoSpace <$ comment)
- pure (collapseWhitespace ws)
- where
- spaces = collapseWhitespace <$> some space
-
-comment :: Lexer ()
-comment = Lexer.skipLineComment "%"