packages feed

dawgdic-1.0.0: app/Main.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Main where

import Control.Monad (forM_, when)
import Data.Char (ord)
import qualified Data.Vector as Vector
import Options.Applicative
import Text.Read (readMaybe)

import qualified Data.DAWG.Internal.DAWGBuilder as DB
import Data.DAWG.Completer (keyToString, start, next)
import qualified Data.DAWG.Completer as C
import qualified Data.DAWG.DAWG as DAWG
import qualified Data.DAWG.Dictionary as Dict
import qualified Data.DAWG.Guide as Guide

main :: IO ()
main = execParser (info (commands <**> helper) idm) >>= run

-- ** CLI

data WithGuide = WithGuide | WithRankedGuide

data Opts = Opts
  { lexiconPath :: FilePath
  , tabSeparator :: Bool
  , withGuide :: Maybe WithGuide
  , dictionaryPath :: FilePath
  }

data Command = Build Opts | Find Opts

commands :: Parser Command
commands = subparser
  (command "build" (info buildOpts (progDesc "Build dictionary from lexicon"))
  <> (command "find" (info findOpts (progDesc "Find words in dictionary"))))
  where
    buildOpts = Build <$> opts
    findOpts = Find <$> opts
    opts = Opts
      <$> strOption
        (  long "lexicon"
        <> short 'l'
        <> metavar "FILE"
        <> help "Lexicon path")
      <*> switch
        (  long "tab-separated"
        <> short 't'
        <> help "Use tab as separator")
      <*> optional (guideOpts <|> rankedGuideOpts)
      <*> strOption
        (  long "dictionary"
        <> short 'd'
        <> metavar "FILE"
        <> help "Dictionary path")
    guideOpts = flag' WithGuide
      (long "guide" <> short 'g' <> help "Build dictionary with guide")
    rankedGuideOpts = flag' WithRankedGuide
      (long "ranked-guide" <> short 'r' <> help "Build dictionary with ranked-guide")
      
run :: Command -> IO ()
run = \case
  Build Opts{..} -> do
    contents <- readFile lexiconPath

    let check !n
          | n < 0 = 0
          | otherwise = n

        toKeyValue !line =
          let (w, strVal) = break (== '\t') line
              mVal = readMaybe . drop 1 $ strVal
          in (w, check <$> mVal)

        makeLexicon = if tabSeparator
          then fmap toKeyValue
          else fmap (\x -> (x, Nothing))

        lexicon = makeLexicon (lines contents)

    db <- DB.new
    forM_ lexicon \(w, mVal)  -> do
      DB.insert (Vector.fromList w) mVal db
    dawg <- DAWG.freeze db
    dict <- Dict.build' dawg

    case withGuide of
      Nothing -> Dict.write dictionaryPath dict
      Just WithGuide -> do
        guide <- Guide.build' dawg dict
        Dict.write dictionaryPath dict
        Guide.write ("guide." <> dictionaryPath) guide
      Just WithRankedGuide -> error "not implemented yet"
    
  Find Opts{..} -> do
    contents <- readFile lexiconPath
    case withGuide of
      Nothing -> do
        let go _dictIx _word [] _dict = pure ()
            go dictIx w ((k, ix) : rest) d = do
              case Dict.followChar k dictIx d of
                Nothing -> pure ()
                Just nextDictIx -> do
                  when (Dict.hasValue nextDictIx d) do
                    putStr $ concat
                      [ " ", take (ix + 1) w, " = ", show $ Dict.value nextDictIx d, ";" ]
                  go nextDictIx w rest d

        dict <- Dict.read dictionaryPath
        forM_ (lines contents) \l -> do
          putStr $ concat [l, ":"]
          let queries = zip ((fromIntegral . ord) <$> l) [0 .. length l - 1]
          go Dict.root l queries dict
          putStr "\n"

      Just WithGuide -> do
        let goNext w c = case next c of
              Nothing -> pure ()
              Just !nc -> do
                putStr $ concat
                  [ " ", w, keyToString nc, " = ", show $ C.value nc ]
                goNext w nc

            go _dictIx _fullWord [] _guide = pure ()
            go dictIx fw w g = do
              let d = Guide.guideDictionary g
              case Dict.followPrefixLength w (fromIntegral $ length w) dictIx d of
                Nothing -> pure ()
                Just nextDictIx -> do
                  let !nc = start nextDictIx "" g
                  goNext fw nc
                  go nextDictIx fw w g
        dict <- Dict.read dictionaryPath
        guide <- Guide.read $ "guide." <> dictionaryPath
        forM_ (lines contents) \l -> do
          putStr $ concat [l, ":"]
          go Dict.root l l guide
          putStr "\n"
        
      Just WithRankedGuide -> error "not implemented yet"