------------------------------------------------------------------
-- |
-- Module      :  Language.JSMW.Arith
-- Copyright   :  (c) Dmitry Golubovsky, 2009
-- License     :  BSD-style
-- 
-- Maintainer  :  golubovsky@gmail.com
-- Stability   :  experimental
-- Portability :  portable
-- 
--
--
-- Encoding of Javascript arithmetic and mathematic operations.
------------------------------------------------------------------

module Language.JSMW.Arith (
 -- * Arithmetic operators
 -- $arith
 -- $comp
  lt, le, gt, ge, eq, ne, (===), (=/=),
 -- * Logical operators
 -- $logic
  land, lor
 -- * Tests for being a number
 ,isNAN
 -- * Primitive conversions
 ,toString
 ,parseInt
 ,parseFloat) where

import BrownPLT.JavaScript
import Data.DOM
import Language.JSMW.Monad

-- $arith Instance of the 'Num' class can be defined for Javascript expressions
-- provided that their type annotation also belongs to the 'Num' class. Therefore
-- it is possible to overload the '+', '-', etc. operators so that using them
-- in JSMW code would result in correct Javascript code:
--
-- @'number' 5 - ('number' 1 - 'number' 4)@ 
--
-- results in
--
-- @(5.0 - (1.0 - 4.0))@.
--
-- Also, addition is defined for String expressions, so this is possible:
--
-- @'alert' (g + 'string' \" Greater than 3\")@
--
-- Unlike the methods of the 'Num' class, comparison operators (members of the 'Ord'
-- class) cannot be overloaded in suitable manner. For Javascript expressions,
-- the following infix operators are provided, with the same fixity as the corresponding
-- Haskell operators.

arithBin op x y = let et = exprType x in ParenExpr et (InfixExpr et op x y)

arithPfx op x = let et = exprType x in ParenExpr et (PrefixExpr et op x)

arithFun fn x = let et = exprType x in ParenExpr et (CallExpr et (VarRef et $ Id et fn) [x])

boolBin op x y = (arithBin op x y) /\ (undefined :: Bool)

instance Num (Expression Double) where
  (+) = arithBin OpAdd
  (-) = arithBin OpSub
  (*) = arithBin OpMul
  negate = arithPfx PrefixMinus
  abs = arithFun "Math.abs"
  signum = arithFun "(function(x){return (x<0)?(-1):(x>0)?1:0})"
  fromInteger n = NumLit (undefined :: a) (fromIntegral n)

instance Num (Expression String) where
  (+) = arithBin OpAdd
  (*) x y = error "(*) undefined on strings"
  abs x = error "abs undefined on strings"
  signum x = error "signum undefined on strings"
  fromInteger = string . show

instance Fractional (Expression Double) where
  (/) = arithBin OpDiv
  fromRational n = NumLit (undefined :: a) (fromRational n)

lt :: Expression a -> Expression a -> Expression Bool
le :: Expression a -> Expression a -> Expression Bool
gt :: Expression a -> Expression a -> Expression Bool
ge :: Expression a -> Expression a -> Expression Bool
eq :: Expression a -> Expression a -> Expression Bool
ne :: Expression a -> Expression a -> Expression Bool
(===) :: Expression a -> Expression a -> Expression Bool
(=/=) :: Expression a -> Expression a -> Expression Bool

lt = boolBin OpLT
le = boolBin OpLEq
gt = boolBin OpGT
ge = boolBin OpGEq
eq = boolBin OpEq
ne = boolBin OpNEq

(===) = boolBin OpStrictEq
(=/=) = boolBin OpStrictNEq

infix 4 `lt`, `le`, `gt`, `ge`, `eq`, `ne`, ===, =/=

-- $logic Similarly, logical operators for Javascript expressions are provided.

land :: Expression Bool -> Expression Bool -> Expression Bool
lor  :: Expression Bool -> Expression Bool -> Expression Bool

land = arithBin OpLAnd
lor = arithBin OpLOr

infixr 3 `land`
infixr 2 `lor`

-- | Test if a given value is a number.

isNAN :: Expression Double -> JSMW e (Expression Bool)

isNAN n = do
  let dt = undefined :: Bool
  nx <- once =<< return n
  once =<< return (CallExpr dt (VarRef dt (Id dt "isNaN")) [nx /\ dt])

-- | Obtain a string representation of an arbitrary Javascript expression.

toString :: Expression a -> JSMW e (Expression String)

toString x = do
  vx <- once =<< return x
  once =<< return (CallExpr "" (DotRef "" (vx /\ "") (Id "" "toString")) [])

-- | Parse an integer.

parseInt :: Expression String -> Expression Double -> JSMW e (Expression Double)

parseInt s r = do
  let dt = undefined :: Double
  str <- once =<< return s
  rdx <- once =<< return r
  once =<< return (CallExpr dt (VarRef dt (Id dt "parseInt")) [str /\ dt, rdx /\ dt])

-- | Parse a floating point number.

parseFloat :: Expression String -> JSMW e (Expression Double)

parseFloat s = do
  let dt = undefined :: Double
  str <- once =<< return s
  once =<< return (CallExpr dt (VarRef dt (Id dt "parseFloat")) [str /\ dt])