-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright 2014 2015 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE

-- a parser for a numeric expression.
module Graphics.Implicit.ExtOpenScad.Parser.Expr(expr0) where

import Prelude (Char, Maybe(Nothing, Just), fmap, ($), (.), (>>), return, Bool(True, False), read, (++), (*), (**), (/), id, foldl, map, foldl1, unzip, tail, zipWith3)

-- the datatype representing the graininess of our world.
import Graphics.Implicit.Definitions ()

-- The parsec parsing library.
import Text.ParserCombinators.Parsec (GenParser, string, many1, digit, char, many, noneOf, sepBy, sepBy1, optionMaybe, try)

import Graphics.Implicit.ExtOpenScad.Definitions (Expr(Var, LitE, ListE, (:$)), OVal(ONum, OString, OBool, OUndefined), collector) 

import Graphics.Implicit.ExtOpenScad.Parser.Util (variableSymb, (?:), (*<|>), genSpace, padString)

variable :: GenParser Char st Expr
variable = fmap Var variableSymb

literal :: GenParser Char st Expr
literal = ("literal" ?:) $
    "boolean" ?: do
        b  <-      (string "true"  >> return True )
              *<|> (string "false" >> return False)
        return . LitE $ OBool b
    -- FIXME: this is a hack, implement something like exprN to replace this?
    *<|> "number" ?: (
         do
            a <- many1 digit
            _ <- char 'e'
            b <- many1 digit
            return . LitE $ ONum (((read a) * (10 ** (read b))) :: )
        *<|>  do
            a <- many1 digit
            _ <- char '.'
            b <- many digit
            _ <- char 'e'
            c <- many1 digit
            return . LitE $ ONum ((read (a ++ "." ++ b) * (10 ** (read c))) :: )
        *<|>  do
            a <- many1 digit
            _ <- char '.'
            b <- many digit
            _ <- char 'e'
            _ <- char '+'
            c <- many1 digit
            return . LitE $ ONum ((read (a ++ "." ++ b) * (10 ** (read c))) :: )
        *<|>  do
            a <- many1 digit
            _ <- char '.'
            b <- many digit
            _ <- char 'e'
            _ <- char '-'
            c <- many1 digit
            return . LitE $ ONum ((read (a ++ "." ++ b) / (10 ** (read c))) :: )
        *<|>  do
            a <- many1 digit
            _ <- char 'e'
            _ <- char '-'
            b <- many1 digit
            return . LitE $ ONum (((read a) / (10 ** (read b))) :: )
        *<|>  do
            a <- many1 digit
            _ <- char '.'
            b <- many digit
            return . LitE $ ONum (read (a ++ "." ++ b) :: )
        *<|>  do
            a <- many1 digit
            return . LitE $ ONum (read a :: )
        )
     *<|> "string" ?: do
        _ <- string "\""
        strlit <-  many $ (string "\\\"" >> return '\"')
                     *<|> (string "\\n" >> return '\n')
                     *<|> (string "\\r" >> return '\r')
                     *<|> (string "\\t" >> return '\t')
                     *<|> (string "\\\\" >> return '\\')
                      -- FIXME: no \u unicode support?
                     *<|> noneOf "\"\n"
        _ <- string "\""
        return . LitE $ OString strlit

-- We represent the priority or 'fixity' of different types of expressions
-- by the ExprIdx argument, with A0 as the highest.

expr0 :: GenParser Char st Expr
expr0 = exprN A0

-- what state in the expression parser tree we are inside of.
data ExprIdx = A0 | A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12

exprN :: ExprIdx -> GenParser Char st Expr

exprN A12 =
         literal
    *<|> variable
    *<|> "bracketed expression" ?: do
        -- eg. ( 1 + 5 )
        _ <- string "("
        expr <- expr0
        _ <- string ")"
        return expr
    *<|> "vector/list" ?: (
        do
            -- eg. [ 3, a, a+1, b, a*b ]
            _ <- string "["
            exprs <- sepBy expr0 (char ',' )
            _ <- string "]"
            return $ ListE exprs
        *<|> do
            -- eg. ( 1,2,3 )
            _ <- string "("
            exprs <- sepBy expr0 (char ',' )
            _ <- string ")"
            return $ ListE exprs
        )
    *<|> "vector/list generator" ?: do
        -- eg.  [ a : 1 : a + 10 ]
        _ <- string "["
        exprs <- sepBy expr0 (char ':' )
        _ <- string "]"
        return $ collector "list_gen" exprs

exprN A11 =
    do
        obj <- exprN A12
        _ <- genSpace
        mods <- many1 (
            "function application" ?: do
                _ <- padString "("
                args <- sepBy expr0 (padString ",")
                _ <- padString ")"
                return $ \f -> f :$ args
            *<|> "list indexing" ?: do
                _ <- padString "["
                i <- expr0
                _ <- padString "]"
                return $ \l -> Var "index" :$ [l, i]
            *<|> "list splicing" ?: do
                _ <- padString "["
                start <- optionMaybe expr0
                _ <- padString ":"
                end   <- optionMaybe expr0
                _ <- padString "]"
                return $ case (start, end) of
                    (Nothing, Nothing) -> id
                    (Just s,  Nothing)  -> \l -> Var "splice" :$ [l, s, LitE OUndefined ]
                    (Nothing, Just e )  -> \l -> Var "splice" :$ [l, LitE $ ONum 0, e]
                    (Just s,  Just e )  -> \l -> Var "splice" :$ [l, s, e]
            )
        return $ foldl (\a b -> b a) obj mods
    *<|> exprN A12

-- match a leading (+) or (-) operator.
exprN A10 =
    "negation" ?: do
        _ <- padString "-"
        expr <- exprN A11
        return $ Var "negate" :$ [expr]
    *<|> do
        _ <- padString "+"
        exprN A11
    *<|> exprN A11

-- match power-of (^) operator.
exprN A9 =
    "exponentiation" ?: do
        a <- exprN A10
        _ <- padString "^"
        b <- exprN A9
        return $ Var "^" :$ [a,b]
    *<|> exprN A10

-- match sequences of multiplication and division.
exprN A8 =
    "multiplication/division" ?: do
        -- outer list is multiplication, inner division.
        -- eg. "1*2*3/4/5*6*7/8"
        --     [[1],[2],[3,4,5],[6],[7,8]]
        exprs <- sepBy1
            (sepBy1 (exprN A9) (try $ padString "/" ))
            (try $ padString "*" )
        let div' a b = Var "/" :$ [a, b]
        return . collector "*" $ map (foldl1 div') exprs
    *<|> exprN A9

-- match remainder (%) operator.
exprN A7 =
    "modulo" ?: do
        exprs <- sepBy1 (exprN  A8) (try $ padString "%")
        let mod' a b = Var "%" :$ [a, b]
        return $ foldl1 mod' exprs
    *<|> exprN A8

-- match string addition (++) operator.
exprN A6 =
    "append" ?: do
        exprs <- sepBy1 (exprN A7) (try $ padString "++")
        return $ collector "++" exprs
    *<|> exprN A7

-- match sequences of addition and subtraction.
exprN A5 =
    "addition/subtraction" ?: do
        -- Similar to multiply & divide
        -- eg. "1+2+3-4-5+6-7"
        --     [[1],[2],[3,4,5],[6,7]]
        exprs <- sepBy1
            (sepBy1 (exprN A6) (try $ padString "-" ))
            (try $ padString "+" )
        let sub a b = Var "-" :$ [a, b]
        return . collector "+" $ map (foldl1 sub) exprs
    *<|> exprN A6

-- match comparison operators.
exprN A4 =
    do
        firstExpr <- exprN A5
        otherComparisonsExpr <- many $ do
            comparisonSymb <-
                     padString "=="
                *<|> padString "!="
                *<|> padString ">="
                *<|> padString "<="
                *<|> padString ">"
                *<|> padString "<"
            expr <- exprN A5
            return (Var comparisonSymb, expr)
        let
            (comparisons, otherExprs) = unzip otherComparisonsExpr
            exprs = firstExpr:otherExprs
        return $ case comparisons of
            []  -> firstExpr
            [x] -> x :$ exprs
            _   -> collector "all" $ zipWith3 (\c e1 e2 -> c :$ [e1,e2]) comparisons exprs (tail exprs)
    *<|> exprN A5

-- match the logical negation operator.
exprN A3 =
    "logical-not" ?: do
        _ <- padString "!"
        a <- exprN A4
        return $ Var "!" :$ [a]
    *<|> exprN A4

-- match the logical And and Or (&&,||) operators.
exprN A2 =
    "logical and/or" ?: do
        a <- exprN A3
        symb <-      padString "&&"
                *<|> padString "||"
        b <- exprN A2
        return $ Var symb :$ [a,b]
    *<|> exprN A3

-- match the ternary (1?2:3) operator.
exprN A1 =
    "ternary" ?: do
        a <- exprN A2
        _ <- padString "?"
        b <- exprN A1
        _ <- padString ":"
        c <- exprN A1
        return $ Var "?" :$ [a,b,c]
    *<|> exprN A2

-- Match and throw away any white space around an expression.
exprN A0 =
    do
        _ <- genSpace
        expr <- exprN A1
        _ <- genSpace
        return expr
    *<|> exprN A1