-- ----------------------------------------------------------------------
-- | This module contains the code for parsing the ASCII output from
-- Quipper, into a GatePlus.
-- This program is heavily based on, and heavily borrows from the QCLParser.

module Quipper.Libraries.QuipperASCIIParser.Parse where

import Quipper.Internal.Circuit
import Data.IntMap hiding (map)

import Text.ParserCombinators.ReadP
import Data.Char (isDigit)
import qualified Data.Map as Map

-- ----------------------------------------------------------------------
-- | A type for gates, plus other circuit output, empty lines, and subroutine defs.
data GatePlus = G Gate [(Wire,Wiretype)]
              | I [(Wire,Wiretype)]
              | O [(Wire,Wiretype)]
              | EmptyLine
              | CommentLine String
              | SubroutineName String
              | SubroutineShape String
              | Controllable ControllableFlag
 deriving Show

-- ----------------------------------------------------------------------
-- * Parsing

-- | Parse a string literal. 
string_literal :: ReadP String
string_literal = do
  char '"'
  s <- string_with_escaped_chars ""
  char '"'
  return s
 where
  string_with_escaped_chars :: String -> ReadP String
  string_with_escaped_chars input = do
   s <- munch (\x -> x /= '\\' && x /= '"')
   let s'  = input ++ s
   rest <- look
   case rest of
    '"' : _ -> return s'
    '\\' : _ -> do
      e <- escaped_char
      string_with_escaped_chars (s' ++ [e])
    _ -> do
      pfail

-- | Parse an escaped character, such as @\0@, @\n@, @\\@, etc.
escaped_char :: ReadP Char
escaped_char = do
  char '\\'
  c <- get
  return (Map.findWithDefault c c map)
  where
    -- The official escape codes. We allow any other escaped character
    -- to denote itself. We do not permit "\&" to denote the empty string.
    map = Map.fromList [
      ('0', '\0'),
      ('a', '\a'),
      ('b', '\b'),
      ('f', '\f'),
      ('n', '\n'),
      ('r', '\r'),
      ('t', '\t'),
      ('v', '\v'),
      ('"', '\"'),
      ('\'', '\''),
      ('\\', '\\')
      ]

-- | Parse a signless integer. We avoid the usual trick ('readS_to_P'
-- 'reads'), because this introduces backtracking errors.
int :: ReadP Int
int = do
  s <- munch1 isDigit
  return $ (read s :: Int)

-- | Parse a floating point number. We avoid the usual trick
-- ('readS_to_P' 'reads'), because this introduces backtracking
-- errors.
double :: ReadP Double
double = do
  (s, _) <- gather parse_double
  return $ (read s :: Double)
  where
    parse_double = do
      option '+' (char '+' +++ char '-')
      munch isDigit
      optional (char '.' >> munch1 isDigit)
      optional (char 'e' >> option '+' (char '+' +++ char '-') >> int)
      
-- | Parse a comma separated list.
commalist :: ReadP a -> ReadP [a]
commalist elt = sepBy elt (skipSpaces >> char ',' >> skipSpaces)

-- | Parse a control structure.
control :: ReadP (Signed Wire,Wiretype)
control = do
  val <- choice [char '+',char '-']
  skipSpaces
  w <- int
  c <- option 'q' (char 'c')
  let wt = case c of
            'c' -> Cbit
            _ -> Qbit
  return (Signed w (val == '+'),wt)

-- | Parse a list of controls.
controls :: ReadP ([Signed Wire],[(Wire,Wiretype)])
controls = do
  skipSpaces
  string "with controls=["
  cwts <- commalist control
  char ']'
  let cs = map fst cwts
  let wts = map (\(Signed w _,wt) -> (w,wt)) cwts
  return (cs,wts)

-- | Parse a 'Wiretype'.
wiretype :: ReadP Wiretype
wiretype = do
  c <- choice [string "Qbit",string "Cbit"]
  case c of
    "Qbit" -> return Qbit
    "Cbit" -> return Cbit
    _ -> error "The impossible has happened"

-- | Parse a wire and its type.
wire :: ReadP (Int,Wiretype)
wire = do
  skipSpaces
  w <- int
  char ':'
  t <- wiretype
  return (w,t)

-- | Parse a list of input/output wires and types.
wires :: ReadP [(Int,Wiretype)]
wires = do
  skipSpaces
  ws <- commalist wire
  return ws

-- | Parse the string \"none\", 
-- returning an empty list of input/output wires and types.
none :: ReadP [(Int,Wiretype)]
none = do
  skipSpaces
  string "none"
  return []

-- | Consume an optional \"*\". Return 'True' if consumed, and 'False'
-- otherwise.
inversechar :: ReadP Bool  
inversechar = do
  c <- option '+' (char '*')
  return (c == '*')

-- | Consume a label.
label' :: ReadP (Int,String)
label' = do
  w <- int
  char ':'
  lab <- string_literal
  return (w,lab)

-- | Consumer any character other than \')\', \']\', or \',\'.
labelchar :: ReadP String
labelchar = do
  c <- satisfy (\x -> not (x `elem` ")],"))
  return [c]

-- | Consume an index of the form [...].
labelbracket :: ReadP String
labelbracket = do
  char '['
  s <- munch (\x -> not (x `elem` "[]"))
  char ']'
  return ("[" ++ s ++ "]")

-- | Consume a list of labels.
labels :: ReadP [(Int,String)]
labels = do
    skipSpaces
    char '('
    ls <- commalist label'
    char ')'
    return ls

-- | Consume a 'BoxId' followed by a \']\' character.
{-
ascii_of_boxid (BoxId name shape) = show name ++ ", shape " ++ show shape
-}
box_id :: ReadP BoxId
box_id = do
   name <- string_literal
   skipSpaces
   char ','
   skipSpaces
   string "shape"
   skipSpaces
   shape <- string_literal
   return (BoxId name shape)

-- | Consume an optional 'NoControlFlag', returning 'False' if it isn't present.
nocontrolflag :: ReadP NoControlFlag
nocontrolflag = do
 skipSpaces
 ncf <- option "" (string "with nocontrol")
 return (ncf == "with nocontrol")

-- | Parse a single line of ASCII output into a 'Gate'. This function
-- needs to be kept in line with Quipper's
-- 'Quipper.Internal.Printing.ascii_render_gate' function.
ascii_line :: ReadP GatePlus
ascii_line = choice [
    do -- Inputs: w:Type, w:Type ...
    string "Inputs:"
    ws <- choice [wires,none]
    eof
    return (I ws),
    do -- Outputs: w:Type, w:Type ...
    string "Outputs:"
    ws <- choice [wires,none]
    eof
    return (O ws),
    do -- An empty line can be parsed as an EmptyLine
    skipSpaces
    eof
    return EmptyLine,
    do -- "A comment in a circuit is any line beginnning with a # character"
    -- The comment is stored for later use as a CommentLine
    skipSpaces
    char '#'
    skipSpaces
    comment <- manyTill (satisfy (\_ -> True)) eof
    eof
    return (CommentLine comment),
    do -- Subroutine: "name" (keeping unquoted version for backward comp.)
    string "Subroutine: "
    name <- string_literal +++ manyTill (satisfy (\_ -> True)) eof
    eof
    return (SubroutineName name),
    do -- Shape: "shape" (keeping unquoted version for backward comp.)
    string "Shape: "
    shape <- string_literal +++ manyTill (satisfy (\_ -> True)) eof
    eof
    return (SubroutineShape shape),
    do -- Controllable: yes/no/classically
    string "Controllable: "
    val_string <- choice [string "yes",string "no", string "classically"]
    let val = case val_string of
               "yes" -> AllCtl
               "no" -> NoCtl
               "classically" -> OnlyClassicalCtl
               _ -> error "The impossible happened"
    eof
    return (Controllable val),
{-
For backward compatibility:
ascii_render_gate (QNot w c ncf) = 
  "QNot(" ++ show w ++ ")" 
  ++ ascii_render_controls wtm c 
  ++ ascii_render_nocontrolflag ncf
-}
    do 
    string "QNot("
    w <- int
    char ')'
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    return (G (QGate "not" False [w] [] cs ncf) wts),
{-
For backward compatibility:
ascii_render_gate (QMultinot ws c ncf) = 
  "QMultinot(" ++ string_of_list "" ", " "" "" show ws ++ ")" 
  ++ ascii_render_controls wtm c 
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QMultinot("
    ws <- commalist int
    char ')'
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    return (G (QGate "multinot" False ws [] cs ncf) wts),    
{-
For backward compatibility:
ascii_render_gate (QHad w c ncf) = 
  "QHad(" ++ show w ++ ")" 
  ++ ascii_render_controls wtm c 
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QHad("
    w <- int
    char ')'
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    return (G (QGate "H" False [w] [] cs ncf) wts),
{-
For backward compatibility:
ascii_render_gate (QSwap w1 w2 c ncf) = 
  "QSwap(" ++ show w1 ++ "," ++ show w2 ++ ")" 
  ++ ascii_render_controls wtm c 
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QSwap("
    w1 <- int
    char ','
    w2 <- int
    char ')'
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    return (G (QGate "swap" False [w1, w2] [] cs ncf) wts),
{-
For backward compatibility:
ascii_render_gate (QW w1 w2 c ncf) = 
  "QW(" ++ show w1 ++ "," ++ show w2 ++ ")" 
  ++ ascii_render_controls wtm c 
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QW("
    w1 <- int
    char ','
    w2 <- int
    char ')'
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    return (G (QGate "W" False [w1, w2] [] cs ncf) wts),
{-
ascii_render_gate (GPhase t ws c ncf) = 
  "GPhase() with t=" ++ show t 
  ++ ascii_render_controls wtm c 
  ++ ascii_render_nocontrolflag ncf
  ++ string_of_list " with anchors=[" ", " "]" "" show ws
-}
    do
    string "GPhase() with t="
    t <- double
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    ws <- option [] (do
      skipSpaces
      string "with anchors=["
      ws <- commalist int
      char ']'
      return ws )
    eof
    return (G (GPhase t ws cs ncf) wts),
{-
ascii_render_gate (QGate name inv ws1 ws2 c ncf) = 
  "QGate[" ++ show name ++ "]" 
  ++ optional inv "*" 
  ++ (string_of_list "(" "," ")" "()" show ws1)
  ++ (string_of_list "; [" "," "]" "" show ws2)
  ++ ascii_render_controls wtm c
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QGate["
    name <- string_literal
    char ']'
    inv <- inversechar
    char '('
    ws1 <- commalist int
    char ')'
    ws2 <- option [] (do
      string "; ["
      ws <- commalist int
      char ']'
      return ws )
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    return (G (QGate name inv ws1 ws2 cs ncf) wts),   
{-
ascii_render_gate (QRot name inv theta ws1 ws2 c ncf) = 
  "QRot[" ++ show name ++ "," ++ (show theta) ++ "]" 
  ++ optional inv "*"
  ++ (string_of_list "(" "," ")" "()" show ws1)
  ++ (string_of_list "; [" "," "]" "" show ws2)
  ++ ascii_render_controls wtm c
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QRot["
    name <- string_literal
    char ','
    theta <- double
    char ']'
    inv <- inversechar
    char '('
    ws1 <- commalist int
    char ')'
    ws2 <- option [] ( do
      string "; ["
      ws <- commalist int
      char ']'
      return ws )
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    return (G (QRot name inv theta ws1 ws2 cs ncf) wts),
{-
ascii_render_gate (CNot w c ncf) = 
  "CNot(" ++ show w ++ ")" 
  ++ ascii_render_controls wtm c
  ++ ascii_render_nocontrolflag ncf
-}
    do 
    string "CNot("
    w <- int
    char ')'
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    return (G (CNot w cs ncf) wts),
{-
ascii_render_gate (CGate n w c ncf) = 
  "CGate[" ++ show n ++ "]" ++ (string_of_list "(" "," ")" "()" show (w:c))
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "CGate["
    name <- string_literal
    string "]("
    ws <- commalist int
    char ')'
    ncf <- nocontrolflag
    eof
    return (G (CGate name (head ws) (tail ws) ncf) []),
{-
ascii_render_gate (CGateInv n w c ncf) = 
  "CGate[" ++ show n ++ "]" ++ "*" ++ (string_of_list "(" "," ")" "()" show (w:c))
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "CGate["
    name <- string_literal
    string "]*("
    ws <- commalist int
    char ')'
    ncf <- nocontrolflag
    eof
    return (G (CGateInv name (head ws) (tail ws) ncf) []),
{-
ascii_render_gate (CSwap w1 w2 c ncf) = 
  "CSwap(" ++ show w1 ++ "," ++ show w2 ++ ")" 
  ++ ascii_render_controls wtm c
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "CSwap("
    w1 <- int
    char ','
    w2 <- int
    char ')'
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    return (G (CSwap w1 w2 cs ncf) wts),
{-
ascii_render_gate (QPrep w ncf) = 
  "QPrep(" ++ show w ++ ")"
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QPrep("
    w <- int
    char ')'
    ncf <- nocontrolflag
    eof
    return (G (QPrep w ncf) []),
{-
ascii_render_gate (QUnprep w ncf) = 
  "QUnprep(" ++ show w ++ ")"
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QUnprep("
    w <- int
    char ')'
    ncf <- nocontrolflag
    eof
    return (G (QUnprep w ncf) []),
{-
ascii_render_gate (QInit b w ncf) = 
  "QInit" ++ (if b then "1" else "0") ++ "(" ++ show w ++ ")"
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QInit"
    val_char <- choice [char '0',char '1']
    let val = val_char == '1'
    char '('
    w <- int
    char ')'
    ncf <- nocontrolflag
    eof
    return (G (QInit val w ncf) []),
{-
ascii_render_gate (CInit b w ncf) = 
  "CInit" ++ (if b then "1" else "0") ++ "(" ++ show w ++ ")"
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "CInit"
    val_char <- choice [char '0',char '1']
    let val = val_char == '1'
    char '('
    w <- int
    char ')'
    ncf <- nocontrolflag
    eof
    return (G (CInit val w ncf) []),
{-
ascii_render_gate (QTerm b w ncf) = 
  "QTerm" ++ (if b then "1" else "0") ++ "(" ++ show w ++ ")"
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "QTerm"
    val_char <- choice [char '0',char '1']
    let val = val_char == '1'
    char '('
    w <- int
    char ')'
    ncf <- nocontrolflag
    eof
    return (G (QTerm val w ncf) []),
{-
ascii_render_gate (CTerm b w ncf) = 
  "CTerm" ++ (if b then "1" else "0") ++ "(" ++ show w ++ ")"
  ++ ascii_render_nocontrolflag ncf
-}
    do
    string "CTerm"
    val_char <- choice [char '0',char '1']
    let val = val_char == '1'
    char '('
    w <- int
    char ')'
    ncf <- nocontrolflag
    eof
    return (G (CTerm val w ncf) []),
{-
ascii_render_gate (QMeas w) = 
  "QMeas(" ++ show w ++ ")"
-}
    do
    string "QMeas("
    w <- int
    char ')'
    eof
    return (G (QMeas w) []),
{-
ascii_render_gate (QDiscard w) = 
  "QDiscard(" ++ show w ++ ")"
-}
    do
    string "QDiscard("
    w <- int
    char ')'
    eof
    return (G (QDiscard w) []),
{-
ascii_render_gate (CDiscard w) = 
  "CDiscard(" ++ show w ++ ")"
-}
    do
    string "CDiscard("
    w <- int
    char ')'
    eof
    return (G (CDiscard w) []),
{-
ascii_render_gate (DTerm b w) = 
  "DTerm" ++ (if b then "1" else "0") ++ "(" ++ show w ++ ")"
-}
    do
    string "DTerm"
    vc <- char '0' +++ char '1'
    let b = case vc of
             '0' -> False
             '1' -> True
             _ -> error "The impossible has happend" 
    char '('
    w <- int
    char ')'
    eof
    return (G (DTerm b w) []),
{-
ascii_render_gate (Subroutine boxid inv ws1 a1 ws2 a2 c ncf scf) = 
  "Subroutine" ++ show_rep ++ "[" ++ show (string_of_boxid boxid) ++ "]"
  ++ optional inv "*"
  ++ " "
  ++ (string_of_list "(" "," ")" "()" show ws1)
  ++ (string_of_list " -> (" "," ")" "()" show ws2)
  ++ ascii_render_controls wtm c
  ++ ascii_render_nocontrolflag ncf
  where
    show_rep = if rep == RepeatFlag 1 then "" else "(x" ++ show rep ++ ")"
-}
-- The parser returns a subroutine with dummy arities, and controllable flag,
-- and repeat flag, as
-- this information is not in a subroutine line in the ASCII output. 
-- The information is added when the GatePlus is evaluated, as the first phase of
-- parsing will have collected the information. 
    do
    string "Subroutine"
    reps <- option 1 $ do
     string "(x"
     r <- int
     char ')'
     return (toInteger r)
    char '['
    boxid <- box_id
    char ']'
    inv <- inversechar
    string " ("
    ws1 <- commalist int
    string ") -> ("
    ws2 <- commalist int
    char ')'
    (cs,wts) <- option ([],[]) controls
    ncf <- nocontrolflag
    eof
    let dummy = error "attempted evaluation of a dummy value"
    return (G (Subroutine boxid inv ws1 dummy ws2 dummy cs ncf dummy (RepeatFlag reps)) wts),   
{-
ascii_render_gate (Comment s inv ws) = 
  "Comment[" ++ show s ++ "]" 
  ++ optional inv "*"
  ++ (string_of_list "(" ", " ")" "()" (\(w,s) -> show w ++ ":" ++ show s) ws)
-}
    do
    string "Comment"
    skipSpaces
    char '['
    comment <- string_literal
    char ']'
    inv <- inversechar
    ls <- labels
    eof
    return (G (Comment comment inv ls) [])
  ]

-- | The overall parsing function, reading a line of ASCII output, and
-- producing a 'GatePlus'.
parse_ascii_line :: String -> Maybe GatePlus
parse_ascii_line s =
  case readP_to_S ascii_line s of
    (h, _):_ -> Just h
    _ -> Nothing
  where
    readline = do
      skipSpaces
      l <- ascii_line
      skipSpaces
      eof
      return l