diff options
| author | adelon <22380201+adelon@users.noreply.github.com> | 2026-08-06 17:54:00 +0200 |
|---|---|---|
| committer | adelon <22380201+adelon@users.noreply.github.com> | 2026-08-06 17:54:00 +0200 |
| commit | 82328890108bae64b372b8d58620ebc62699de76 (patch) | |
| tree | 575404c6b425c19259c0ded296f1c8ffb7ff0e2b /source/Felix/Syntax/Adapt.hs | |
| parent | 1a25421c2a168d420581358c8733fcd8f36f379b (diff) | |
Diffstat (limited to 'source/Felix/Syntax/Adapt.hs')
| -rw-r--r-- | source/Felix/Syntax/Adapt.hs | 928 |
1 files changed, 928 insertions, 0 deletions
diff --git a/source/Felix/Syntax/Adapt.hs b/source/Felix/Syntax/Adapt.hs new file mode 100644 index 0000000..eb0cb6c --- /dev/null +++ b/source/Felix/Syntax/Adapt.hs @@ -0,0 +1,928 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE ApplicativeDo #-} +{-# LANGUAGE RecordWildCards #-} + +module Felix.Syntax.Adapt + ( FunctionPatternError(..) + , LexicalScanError(..) + , ScannedLexicalItem(..) + , scannedItemMarker + , canonicalScannedItem + , SyntaxMaterializationError(..) + , materializeSyntaxDelta + , scanChunk + ) where + +import Base +import Felix.Syntax.Abstract +import Felix.Syntax.Interface +import Felix.Syntax.Lexicon +import Felix.Report.Location + +import Control.Monad (foldM) +import Data.Bifunctor qualified as Bifunctor +import Data.Map.Strict qualified as Map +import Data.Maybe (catMaybes) +import Data.Set qualified as Set +import Data.Sequence qualified as Seq +import Data.Text qualified as Text +import Numeric.Natural (Natural) +import Text.Regex.Applicative qualified as RE +import Text.Regex.Applicative (RE) + +data FunctionPatternError + = FunctionPatternEmpty + | FunctionPatternBareVariable + | FunctionPatternReservedApplication + deriving (Show, Eq) + +data LexicalScanError + = MalformedLexicalEnvironment !Location !Text + | InvalidFunctionPattern !Location !FunctionPatternError + | MalformedDatatype !Location !Text + deriving (Eq) + +instance Show LexicalScanError where + show = \case + MalformedLexicalEnvironment location environment -> + "could not find a lexical pattern in " + <> Text.unpack environment + <> " at " + <> prettyLocation location + InvalidFunctionPattern location problem -> + functionPatternErrorMessage problem + <> " at " + <> prettyLocation location + MalformedDatatype location problem -> + "malformed datatype declaration at " + <> prettyLocation location + <> ": " + <> Text.unpack problem + +scanChunk + :: [Located Token] + -> Either LexicalScanError [Located ScannedLexicalItem] +scanChunk ltoks = + case ltoks of + first@Located{startPos = pos, unLocated = BeginEnv "definition"} : _ -> + locateAt first + <$> (matchOrErr (definition pos) "definition" pos >>= id) + first@Located{startPos = pos, unLocated = BeginEnv "signature"} : _ -> + locateAt first + <$> (matchOrErr (signatureExtension pos) "signature" pos >>= id) + first@Located{startPos = pos, unLocated = BeginEnv "abbreviation"} : _ -> + locateAt first + <$> (matchOrErr (abbreviation pos) "abbreviation" pos >>= id) + first@Located{startPos = pos, unLocated = BeginEnv "struct"} : _ -> + locateStructItems first ltoks + <$> matchOrErr structRE "struct definition" pos + first@Located{startPos = pos, unLocated = BeginEnv "inductive"} : _ -> + locateAt first + <$> (matchOrErr (inductive pos) "inductive definition" pos >>= id) + Located{startPos = pos, unLocated = BeginEnv "datatype"} : _ -> + scanDatatypeChunk pos ltoks + _ -> + Right [] + where + toks = unLocated <$> ltoks + + matchOrErr + :: RE Token a + -> Text + -> Location + -> Either LexicalScanError a + matchOrErr re environment location = + case RE.match re toks of + Nothing -> + Left + (MalformedLexicalEnvironment + location + environment) + Just result -> + Right result + + locateAt first items = + [item <$ first | item <- items] + +data ScannedLexicalItem + = ScanAdj LexicalPhrase Marker + | ScanFun LexicalPhrase Marker + | ScanNoun LexicalPhrase Marker + | ScanStructNoun LexicalPhrase Marker + | ScanVerb LexicalPhrase Marker + | ScanRelationSymbol Token ParameterArity Marker + | ScanFunctionSymbol Pattern Marker + | ScanPrefixPredicate PrefixPredicate Marker + | ScanStructOp Text -- we an use the command text as export name. + deriving (Show, Eq, Ord) + +scannedItemMarker :: ScannedLexicalItem -> Marker +scannedItemMarker = \case + ScanAdj _item marker -> + marker + ScanFun _item marker -> + marker + ScanNoun _item marker -> + marker + ScanStructNoun _item marker -> + marker + ScanVerb _item marker -> + marker + ScanRelationSymbol _token _arity marker -> + marker + ScanFunctionSymbol _pattern marker -> + marker + ScanPrefixPredicate _predicate marker -> + marker + ScanStructOp commandText -> + Marker commandText + +canonicalScannedItem + :: Fixity + -> ScannedLexicalItem + -> CanonicalLexicalEntry +canonicalScannedItem fixity = \case + ScanAdj phrase marker + | isAdjR phrase -> + CanonicalRightAdjective + (patternFromHoley phrase) + marker + | otherwise -> + CanonicalLeftAdjective + (patternFromHoley phrase) + marker + ScanFun phrase marker -> + canonicalSgPl + CanonicalFunctionPhrase + (guessNounPlural phrase) + marker + ScanNoun phrase marker -> + canonicalSgPl + CanonicalNoun + (guessNounPlural phrase) + marker + ScanStructNoun phrase marker -> + canonicalSgPl + CanonicalStructureNoun + (guessNounPlural phrase) + marker + ScanVerb phrase marker -> + canonicalSgPl + CanonicalVerb + (guessVerbPlural phrase) + marker + ScanRelationSymbol token arity marker -> + CanonicalRelation token arity marker + ScanFunctionSymbol pat marker -> + CanonicalExpressionFunction + (if pat == tupleSurfacePattern + then mixfixPattern PairSymbol + else pat) + marker + fixity + ScanPrefixPredicate + (PrefixPredicate commandText arity) + marker -> + CanonicalPrefixPredicate + commandText + (fromIntegral arity) + marker + ScanStructOp commandText -> + CanonicalStructureOperation commandText + where + canonicalSgPl constructor phrases marker = + constructor + (patternFromHoley (sg phrases)) + (patternFromHoley (pl phrases)) + marker + +data SyntaxMaterializationError + = MaterializedSyntaxCollision !CanonicalSyntaxCollision + | PrefixPredicateArityOutOfRange !Natural + deriving (Show, Eq) + +materializeSyntaxDelta + :: CanonicalSyntaxDelta + -> Either SyntaxMaterializationError Lexicon +materializeSyntaxDelta delta = do + combined <- + Bifunctor.first MaterializedSyntaxCollision + (canonicalSyntaxDelta + (fixedBaseSyntaxEntries + <> canonicalSyntaxDeltaEntries delta)) + foldM + insertCanonicalEntry + builtins + [ entry + | entry <- canonicalSyntaxDeltaEntries combined + , entry `Set.notMember` fixedEntries + ] + where + fixedEntries = + Set.fromList fixedBaseSyntaxEntries + +insertCanonicalEntry + :: Lexicon + -> CanonicalLexicalEntry + -> Either SyntaxMaterializationError Lexicon +insertCanonicalEntry lexicon@Lexicon{..} entry = do + extended <- case entry of + CanonicalLeftAdjective pat marker -> + pure + lexicon + { lexiconAdjLs = + lexicalItem pat marker : lexiconAdjLs + } + CanonicalRightAdjective pat marker -> + pure + lexicon + { lexiconAdjRs = + lexicalItem pat marker : lexiconAdjRs + } + CanonicalFunctionPhrase singular plural marker -> + pure + lexicon + { lexiconFuns = + lexicalItemSgPl singular plural marker + : lexiconFuns + } + CanonicalNoun singular plural marker -> + pure + lexicon + { lexiconNouns = + lexicalItemSgPl singular plural marker + : lexiconNouns + } + CanonicalStructureNoun singular plural marker -> + pure + lexicon + { lexiconStructNouns = + lexicalItemSgPl singular plural marker + : lexiconStructNouns + } + CanonicalVerb singular plural marker -> + pure + lexicon + { lexiconVerbs = + lexicalItemSgPl singular plural marker + : lexiconVerbs + } + CanonicalRelation token arity marker -> + pure + lexicon + { lexiconRelationSymbols = + RelationSymbol token arity marker + : lexiconRelationSymbols + } + CanonicalExpressionFunction + pat + marker + (Fixity associativity level) -> + pure + lexicon + { lexiconMixfixTable = + Seq.adjust + (Map.insert + pat + (MixfixItem + pat + marker + associativity)) + (fromIntegral + (mixfixLevelValue level)) + lexiconMixfixTable + } + CanonicalPrefixPredicate commandText arity marker -> do + runtimeArity <- + if arity <= fromIntegral (maxBound :: Int) + then pure (fromIntegral arity) + else + Left + (PrefixPredicateArityOutOfRange arity) + pure + lexicon + { lexiconPrefixPredicates = + (PrefixPredicate commandText runtimeArity, marker) + : lexiconPrefixPredicates + } + CanonicalStructureOperation commandText -> + pure + lexicon + { lexiconStructFun = + StructSymbol commandText : lexiconStructFun + } + pure extended + where + lexicalItem pat marker = + mkLexicalItem (patternToHoley pat) marker + + lexicalItemSgPl singular plural marker = + mkLexicalItemSgPl + (SgPl + (patternToHoley singular) + (patternToHoley plural)) + marker + +skipUntilNextLexicalEnv :: RE Token [Token] +skipUntilNextLexicalEnv = many (RE.psym otherToken) + where + otherToken tok = tok /= BeginEnv "definition" && tok /= BeginEnv "struct" && tok /= BeginEnv "abbreviation" + +notEndOfLexicalEnvToken :: RE Token Token +notEndOfLexicalEnvToken = RE.psym innerToken + where + innerToken tok = tok /= EndEnv "definition" && tok /= EndEnv "struct" && tok /= EndEnv "abbreviation" + +notEndOfSignatureSentenceToken :: RE Token Token +notEndOfSignatureSentenceToken = RE.psym \case + Symbol "." -> False + EndEnv "signature" -> False + _ -> True + +definition + :: Location + -> RE Token (Either LexicalScanError [ScannedLexicalItem]) +definition location = do + RE.sym (BeginEnv "definition") + RE.few notEndOfLexicalEnvToken + m <- labelRE + RE.few RE.anySym + lexicalItem <- headRE location + RE.few RE.anySym + RE.sym (EndEnv "definition") + skipUntilNextLexicalEnv + pure ((: []) <$> lexicalItem m) + +abbreviation + :: Location + -> RE Token (Either LexicalScanError [ScannedLexicalItem]) +abbreviation location = do + RE.sym (BeginEnv "abbreviation") + RE.few RE.anySym + m <- labelRE + RE.few RE.anySym + lexicalItem <- headRE location + RE.few RE.anySym + RE.sym (EndEnv "abbreviation") + skipUntilNextLexicalEnv + pure ((: []) <$> lexicalItem m) + +signatureExtension + :: Location + -> RE Token (Either LexicalScanError [ScannedLexicalItem]) +signatureExtension location = do + RE.sym (BeginEnv "signature") + RE.few notEndOfLexicalEnvToken + m <- labelRE + RE.few RE.anySym + lexicalItem <- sigHeadRE location + RE.few notEndOfSignatureSentenceToken + RE.sym (Symbol ".") + RE.sym (EndEnv "signature") + skipUntilNextLexicalEnv + pure ((: []) <$> lexicalItem m) + +labelRE :: RE Token Marker +labelRE = RE.msym \case + Label m -> Just (Marker m) + _ -> Nothing + +-- | 'RE' that matches the head of a definition. +headRE + :: Location + -> RE Token (Marker -> Either LexicalScanError ScannedLexicalItem) +-- Note that @<|>@ is left biased for 'RE', so we can just +-- place 'adj' before 'verb' and do not have to worry about +-- overlapping patterns. +headRE location = + pureScan ScanNoun <$> nounRE + <|> pureScan ScanAdj <$> adjRE + <|> pureScan ScanVerb <$> verbRE + <|> pureScan ScanFun <$> funRE + <|> relationScan <$> relationSymbolRE + <|> functionScan <$> functionSymbolRE location + <|> pureScan ScanPrefixPredicate <$> prefixPredicate + where + pureScan constructor value marker = + Right (constructor value marker) + + relationScan (token, arity) marker = + Right (ScanRelationSymbol token arity marker) + + functionScan patternResult marker = + (`ScanFunctionSymbol` marker) <$> patternResult + +sigHeadRE + :: Location + -> RE Token (Marker -> Either LexicalScanError ScannedLexicalItem) +sigHeadRE location = + asum + [ signatureHeadRE form + | form <- concreteSignatureHeadForms + ] + where + signatureHeadRE = \case + AdjectiveSignatureHead -> + pureScan ScanAdj <$> sigAdjectiveRE + SymbolicSignatureHead -> + functionScan <$> sigFunctionSymbolRE location + + pureScan constructor value marker = + Right (constructor value marker) + + functionScan patternResult marker = + (`ScanFunctionSymbol` marker) <$> patternResult + +sigAdjectiveRE :: RE Token LexicalPhrase +sigAdjectiveRE = + toLexicalPhrase + <$> ( math var + *> RE.sym (Word "can") + *> RE.sym (Word "be") + *> RE.some + (RE.psym isLexicalPhraseToken <|> math var) + ) + +sigFunctionSymbolRE :: Location -> RE Token (Either LexicalScanError Pattern) +sigFunctionSymbolRE location = do + RE.sym (BeginEnv "math") + toks <- RE.few nonDefinitionKeyword + RE.sym (EndEnv "math") + pure (makeFunctionSymbol location toks) + +inductive + :: Location + -> RE Token (Either LexicalScanError [ScannedLexicalItem]) +inductive location = do + RE.sym (BeginEnv "inductive") + RE.few notEndOfLexicalEnvToken + m <- labelRE + RE.few RE.anySym + lexicalItem <- functionSymbolInductive location + RE.few RE.anySym + RE.sym (EndEnv "inductive") + skipUntilNextLexicalEnv + pure (((: []) . (`ScanFunctionSymbol` m)) <$> lexicalItem) + +scanDatatypeChunk + :: Location + -> [Located Token] + -> Either LexicalScanError [Located ScannedLexicalItem] +scanDatatypeChunk = datatypeLexicalItems + +datatypeLexicalItems + :: Location + -> [Located Token] + -> Either LexicalScanError [Located ScannedLexicalItem] +datatypeLexicalItems environmentLocation toks = do + marker <- requireDatatype + environmentLocation + "missing declaration label" + (findDatatypeLabel toks) + datatypeHeadToks <- requireDatatype + environmentLocation + "missing datatype head" + (findDatatypeHead toks) + constructorToks <- requireDatatype + environmentLocation + "missing constructor enumeration" + (findDatatypeConstructors toks) + let datatypeLocation = + maybe environmentLocation startPos (listToMaybe datatypeHeadToks) + datatypePattern <- makeFunctionSymbol + datatypeLocation + (unLocated <$> datatypeHeadToks) + constructorItems <- traverse makeConstructor constructorToks + pure + ((ScanFunctionSymbol datatypePattern marker + <$ locationTemplate datatypeLocation toks) + : constructorItems) + where + makeConstructor (itemLocation, raw) = do + constructorToks <- requireDatatype + itemLocation + "constructor item has no symbolic declaration" + (itemConstructorToks raw) + let stripped = stripOuterParens constructorToks + markerToken <- constructorMarker itemLocation stripped + constructorPattern <- makeFunctionSymbol + (startPos markerToken) + (unLocated <$> stripped) + pure + (ScanFunctionSymbol + constructorPattern + (markerFromToken (unLocated markerToken)) + <$ markerToken) + +requireDatatype + :: Location + -> Text + -> Maybe a + -> Either LexicalScanError a +requireDatatype location problem = + maybe (Left (MalformedDatatype location problem)) Right + +locationTemplate :: Location -> [Located Token] -> Located Token +locationTemplate location = \case + token : _ -> + token{startPos = location} + [] -> + impossible "datatype scanner has no environment token" + +findDatatypeLabel :: [Located Token] -> Maybe Marker +findDatatypeLabel = \case + [] -> Nothing + Located{unLocated = Label m} : _ -> Just (Marker m) + _ : toks -> findDatatypeLabel toks + +findDatatypeHead :: [Located Token] -> Maybe [Located Token] +findDatatypeHead toks = do + afterLabel <- tailMay =<< dropUntil (isLabel . unLocated) toks + defineToks <- dropUntil ((== Word "define") . unLocated) afterLabel + case defineToks of + _define : Located{unLocated = BeginEnv "math"} : rest -> + takeUntilToken (EndEnv "math") rest + _ -> Nothing + +findDatatypeConstructors + :: [Located Token] + -> Maybe [(Location, [Located Token])] +findDatatypeConstructors toks = do + afterEnumerate <- tailMay + =<< dropUntil ((== BeginEnv "enumerate") . unLocated) toks + enumerateBody <- takeUntilToken (EndEnv "enumerate") afterEnumerate + let items = splitDatatypeItems enumerateBody + guard (not (null items)) + pure items + +splitDatatypeItems + :: [Located Token] + -> [(Location, [Located Token])] +splitDatatypeItems = \case + [] -> + [] + Located{startPos = itemLocation, unLocated = Command "item"} : rest -> + let (item, remaining) = + break ((== Command "item") . unLocated) rest + in (itemLocation, item) : splitDatatypeItems remaining + _ : rest -> + splitDatatypeItems rest + +itemConstructorToks :: [Located Token] -> Maybe [Located Token] +itemConstructorToks toks = do + afterMath <- tailMay =<< dropUntil ((== BeginEnv "math") . unLocated) toks + mathBody <- takeUntilToken (EndEnv "math") afterMath + takeUntilToken (Command "in") mathBody + +constructorMarker + :: Location + -> [Located Token] + -> Either LexicalScanError (Located Token) +constructorMarker fallback = + maybe + (Left + (MalformedDatatype + fallback + "constructor has no marker-bearing head token")) + Right + . find (isConstructorMarkerToken . unLocated) + +isConstructorMarkerToken :: Token -> Bool +isConstructorMarkerToken = \case + Word _ -> True + Symbol _ -> True + Command _ -> True + Integer _ -> True + _ -> False + +stripOuterParens :: [Located Token] -> [Located Token] +stripOuterParens toks + | hasOuterParens toks = case toks of + Located{unLocated = ParenL} : rest -> case reverse rest of + Located{unLocated = ParenR} : innerRev -> reverse innerRev + _ -> toks + _ -> toks + | otherwise = toks + +hasOuterParens :: [Located Token] -> Bool +hasOuterParens = \case + Located{unLocated = ParenL} : rest -> go (1 :: Int) rest + _ -> False + where + go _ [] = False + go depth [Located{unLocated = ParenR}] = depth == 1 + go depth (Located{unLocated = ParenL} : rest) = + go (depth + 1) rest + go depth (Located{unLocated = ParenR} : rest) + | depth <= 0 = False + | otherwise = go (depth - 1) rest + go depth (_ : rest) = go depth rest + +isLabel :: Token -> Bool +isLabel = \case + Label _ -> True + _ -> False + +dropUntil :: (a -> Bool) -> [a] -> Maybe [a] +dropUntil predicate = \case + [] -> Nothing + xs@(x : rest) + | predicate x -> Just xs + | otherwise -> dropUntil predicate rest + +takeUntilToken :: Token -> [Located Token] -> Maybe [Located Token] +takeUntilToken stop = \case + [] -> Nothing + x : xs + | unLocated x == stop -> Just [] + | otherwise -> (x :) <$> takeUntilToken stop xs + +tailMay :: [a] -> Maybe [a] +tailMay = \case + [] -> Nothing + _ : xs -> Just xs + +structRE :: RE Token [ScannedLexicalItem] +structRE = do + RE.sym (BeginEnv "struct") + RE.few RE.anySym + m <- labelRE + RE.few RE.anySym + lexicalItem <- ScanStructNoun . toLexicalPhrase <$> (an *> structPat <* math var) + RE.few RE.anySym + lexicalItems <- structOps <|> pure [] + RE.sym (EndEnv "struct") + skipUntilNextLexicalEnv + pure (lexicalItem m : lexicalItems) + +structOps :: RE Token [ScannedLexicalItem] +structOps = do + RE.sym (BeginEnv "enumerate") + lexicalItems <- many structOp + RE.sym (EndEnv "enumerate") + RE.few RE.anySym + pure lexicalItems + +structOp :: RE Token ScannedLexicalItem +structOp = do + RE.sym (Command "item") + op <- math command + pure (ScanStructOp op) + +locateStructItems + :: Located Token + -> [Located Token] + -> [ScannedLexicalItem] + -> [Located ScannedLexicalItem] +locateStructItems environmentToken toks = \case + [] -> + [] + structureNoun : operations -> + (structureNoun <$ environmentToken) + : zipWith locateOperation operations operationTokens + where + operationTokens = + structOperationTokens toks + <> repeat environmentToken + + locateOperation operation token = + operation <$ token + +structOperationTokens :: [Located Token] -> [Located Token] +structOperationTokens = \case + Located{unLocated = Command "item"} + : Located{unLocated = BeginEnv "math"} + : commandToken@Located{unLocated = Command _} + : Located{unLocated = EndEnv "math"} + : rest -> + commandToken : structOperationTokens rest + _ : rest -> + structOperationTokens rest + [] -> + [] + +nounRE :: RE Token LexicalPhrase +nounRE = toLexicalPhrase <$> (math var *> is *> an *> patRE <* iff) + +adjRE :: RE Token LexicalPhrase +adjRE = toLexicalPhrase <$> (math var *> is *> patRE <* iff) + +verbRE :: RE Token LexicalPhrase +verbRE = toLexicalPhrase <$> (math var *> patRE <* iff) + +funRE :: RE Token LexicalPhrase +funRE = toLexicalPhrase <$> (the *> patRE <* (is <|> comma)) + +relationSymbolRE :: RE Token (Token, ParameterArity) +relationSymbolRE = do + beginMath + var + rel <- symbol + k <- params + var + endMath + iff + pure (rel, k) + where + params :: RE Token ParameterArity + params = do + vars <- many (RE.sym InvisibleBraceL *> var <* RE.sym InvisibleBraceR) + pure (parameterArityOf vars) + +functionSymbolRE + :: Location + -> RE Token (Either LexicalScanError Pattern) +functionSymbolRE location = do + RE.sym (BeginEnv "math") + toks <- RE.few nonDefinitionKeyword + RE.sym (Symbol "=") + pure (makeFunctionSymbol location toks) + +makeFunctionSymbol + :: Location + -> [Token] + -> Either LexicalScanError Pattern +makeFunctionSymbol location = \case + [] -> + Left (InvalidFunctionPattern location FunctionPatternEmpty) + [Variable _] -> + Left (InvalidFunctionPattern location FunctionPatternBareVariable) + [Variable _, ParenL, Variable _, ParenR] -> + Left + (InvalidFunctionPattern + location + FunctionPatternReservedApplication) + toks -> + Right (patternFromHoley (fromToken <$> toks)) + where + fromToken = \case + Variable _ -> Nothing -- Variables become slots. + tok -> Just tok -- Everything else is part of the pattern. + +functionPatternErrorMessage :: FunctionPatternError -> String +functionPatternErrorMessage = \case + FunctionPatternEmpty -> + "malformed function pattern: no pattern" + FunctionPatternBareVariable -> + "malformed function pattern: a bare variable would cause infinite left recursion" + FunctionPatternReservedApplication -> + "malformed function pattern: _(_) is reserved for set-theoretic function application" + +functionSymbolInductive + :: Location + -> RE Token (Either LexicalScanError Pattern) +functionSymbolInductive location = do + RE.sym (BeginEnv "math") + toks <- RE.few nonDefinitionKeyword + RE.sym (Command "subseteq") + pure (makeFunctionSymbol location toks) + +prefixPredicate :: RE Token PrefixPredicate +prefixPredicate = math prfx <* iff + where + prfx = do + r <- command + args <- many (RE.sym InvisibleBraceL *> var <* RE.sym InvisibleBraceR) + pure (PrefixPredicate r (length args)) + + +command :: RE Token Text +command = RE.msym \case + Command cmd -> Just cmd + _ -> Nothing + +var :: RE Token Token +var = RE.psym isVar + + +nonDefinitionKeyword :: RE Token Token +nonDefinitionKeyword = RE.psym (`notElem` keywords) + where + keywords = + [ Word "if" + , Word "iff" + , Symbol "=" + , Command "iff" + , BeginEnv "math" + , EndEnv "math" + ] + + +patRE :: RE Token [Token] +patRE = many (RE.psym isLexicalPhraseToken <|> math var) + +structPat :: RE Token [Token] +structPat = many (RE.psym isLexicalPhraseToken) + +beginMath, endMath :: RE Token () +beginMath = void (RE.sym (BeginEnv "math")) +endMath = void (RE.sym (EndEnv "math")) + +math :: RE Token a -> RE Token a +math re = beginMath *> re <* endMath + +-- | We allow /conditional perfection/: the first /@if@/ in a definition is interpreted as /@iff@/. +iff :: RE Token () +iff = void (RE.sym (Word "if")) -- Using @void@ is faster (only requires recognition). + <|> void (RE.sym (Word "iff")) + <|> void (RE.string [Word "if", Word "and", Word "only", Word "if"]) + <|> void (RE.sym (Word "denote")) + <|> void (RE.sym (Word "stand") *> RE.sym (Word "for")) +{-# INLINE iff #-} + +an :: RE Token () +an = void (RE.sym (Word "a")) + <|> void (RE.sym (Word "an")) +{-# INLINE an #-} + +is :: RE Token () +is = void (RE.sym (Word "is") <|> RE.sym (Word "denotes")) +{-# INLINE is #-} + +the :: RE Token () +the = void (RE.sym (Word "the")) +{-# INLINE the #-} + +comma :: RE Token () +comma = void (RE.sym (Symbol ",")) +{-# INLINE comma #-} + + +isVar :: Token -> Bool +isVar = \case + Variable _ -> True + _token -> False + +isLexicalPhraseToken :: Token -> Bool +isLexicalPhraseToken = \case + Word w -> w `Set.notMember` keywords + -- + -- Simple commands (outside of math-mode) are allowed. This is useful + -- for defining lexical phrases containing symbolic expressions such as + -- `X is \Ttwo{}`, where `\Ttwo` is a macro that expands to `T_2`. + -- We also allow these macros to take arguments, hence the need to + -- allow grouping delimiters. They can also be used to escape the end + -- of the command for correct spacing, as in the above example. + -- + Command _cmd -> True + InvisibleBraceL -> True + InvisibleBraceR -> True + -- + -- No other tokens may occur in lexical phrases. In particular, no `_dot` + -- token may occur, limiting the lexical phrase to a single sentence. + -- Commas occurring in variable lists should be placed + -- within the math environment. Thus `$a,b$ are coprime iff`, + -- not `$a$,`$b$` are coprime iff`. + -- + _token -> False + where + keywords = Set.fromList ["a", "an", "is", "are", "if", "iff", "denote", "stand", "let"] + + +toLexicalPhrase :: [Token] -> LexicalPhrase +toLexicalPhrase toks = component <$> toks + where + component = \case + Variable _ -> Nothing + tok -> Just tok + + +symbol :: RE Token Token +symbol = RE.msym $ \tok -> case tok of + Command _ -> Just tok + Symbol _ -> Just tok + _tok -> Nothing + + +-- | Basic paradigms for pluralizations of nominals. +guessNounPlural :: LexicalPhrase -> SgPl LexicalPhrase +guessNounPlural item = SgPl item (pluralize item) + where + pluralize :: LexicalPhrase -> LexicalPhrase + pluralize = \case + Just (Word w) : pat'@(Just w' : _) | isPreposition w' -> Just (Word (Text.snoc w 's')) : pat' + tok : Just (Word w) : pat'@(Just w' : _) | isPreposition w' -> tok : Just (Word (Text.snoc w 's')) : pat' + tok1 : tok2 : Just (Word w) : pat'@(Just w' : _) | isPreposition w' -> tok1 : tok2 : Just (Word (Text.snoc w 's')) : pat' + [Just (Word w)] -> [Just (Word (Text.snoc w 's'))] + [tok, Just (Word w)] -> [tok, Just (Word (Text.snoc w 's'))] + [tok, tok', Just (Word w)] -> [tok, tok', Just (Word (Text.snoc w 's'))] + pat' -> pat' + +guessVerbPlural :: LexicalPhrase -> SgPl LexicalPhrase +guessVerbPlural item = SgPl item itemPl + where + itemPl = case item of + Just (Word v) : rest -> case Text.unsnoc v of + Just (v', 's') -> Just (Word v') : rest + _ -> item + _ -> item + +isAdjR :: LexicalPhrase -> Bool +isAdjR item = containsPreposition item || containsSlot item + where + containsPreposition, containsSlot :: LexicalPhrase -> Bool + containsPreposition = any isPreposition . catMaybes + containsSlot = (Nothing `elem`) + +isPreposition :: Token -> Bool +isPreposition w = Set.member w (Set.map Word prepositions) |
