imports{
import UHA_Utils
import UHA_Syntax 
import UHA_Range
import OperatorTable
import Char
import Utils
import Messages
import Data.FiniteMap
}

INCLUDE "../syntax/UHA_Syntax.ag"

{

data ResolveError = 
    Ambiguous Assoc Name Assoc Name

instance HasMessage ResolveError where
    getRanges (Ambiguous _ n1 _ n2) = 
        [ getNameRange n1, getNameRange n2 ]
    
    getMessage (Ambiguous assoc1 op1 assoc2 op2) = 
        let 
            assocString AssocRight = "right-associative"
            assocString AssocLeft  = "left-associative"
            assocString AssocNone  = "non-associative"

            firstLine =
                    "Ambiguous use of "
                ++  assocString (assoc1)
                ++  " operator "
                ++  show op1
            secondLine = 
                    " with "
                ++  assocString (assoc2)
                ++  " operator "
                ++  show op2
        in [ MessageOneLiner ( MessageString $ firstLine ++ secondLine ) ]
        
resolveOperators :: OperatorTable -> Module -> (Module, [ResolveError])
resolveOperators opTable m = 
    let (errs, newModule) = sem_Module m opTable []
    in (newModule, errs)

expression opTable e = -- !!! errors ignored
    let (errs, newE) = sem_Expression e opTable [] 
    in newE

operatorsFromModule :: Module -> OperatorTable
operatorsFromModule m =
    listToFM (concatMap declToOps (collectInfixdecls m))
  where
    declToOps (Declaration_Fixity _ f (MaybeInt_Just p) os) = 
        [ (o, (p, fixityToAssoc f)) | o <- os ]
    fixityToAssoc f = case f of
        Fixity_Infixl _ -> AssocLeft
        Fixity_Infixr _ -> AssocRight
        Fixity_Infix  _ -> AssocNone

collectInfixdecls :: Module -> [Declaration]
collectInfixdecls 
    (Module_Module _ _ _ (Body_Body _ _ ds)) = filter isInfixdecl ds
    where
        isInfixdecl (Declaration_Fixity _ _ _ _) = True
        isInfixdecl _ = False
}

ATTR Module Body Declarations Declaration FunctionBindings RightHandSide
     FunctionBinding LeftHandSide MaybeDeclarations GuardedExpressions
     GuardedExpression Expression Patterns Pattern Alternatives Alternative
     Qualifiers Qualifier Statements Statement MaybeExpression Expressions
     RecordExpressionBinding RecordPatternBinding RecordPatternBindings
     RecordExpressionBindings
    [ opTable : OperatorTable 
    | resolveErrors : { [ResolveError] } 
    | 
    ]                

SEM Pattern 
    | List lhs.resolveErrors = @errs ++ @patterns.resolveErrors
           loc . (self, errs) = 
                    case @range of
                        Range_Range Position_Unknown Position_Unknown -> 
                            resolvePattern @lhs.opTable @patterns.self
                        _ -> 
                            (Pattern_List @range.self @patterns.self, [])
           
SEM Expression
    | List lhs . resolveErrors = @errs ++ @expressions.resolveErrors
           loc . (self, errs) = 
                    case @range of
                        Range_Range Position_Unknown Position_Unknown -> 
                            resolveExpression @lhs.opTable @expressions.self
                        _ -> (Expression_List @range.self @expressions.self, [])
            
{            
type State expr = 
    ( [Name] -- operator stack
    , [expr] -- expression/pattern stack
    , [ResolveError]
    )

resolveExpression :: OperatorTable -> [Expression] -> (Expression, [ResolveError])
resolveExpression opTable es = resolve opTable es (getOp, applyMinus, applyBinary) ([], [], []) 
  where
    getOp (Expression_Variable (Range_Range Position_Unknown Position_Unknown) n) = Just n
    getOp (Expression_Constructor (Range_Range Position_Unknown Position_Unknown) n) = Just n
    getOp _ = Nothing
    
    applyMinus :: Name -> Expression -> Expression
    applyMinus n e
        | n == intUnaryMinusName =
            Expression_Negate      (mergeRanges (getNameRange n) (getExprRange e)) e
        | n == floatUnaryMinusName = 
            Expression_NegateFloat (mergeRanges (getNameRange n) (getExprRange e)) e
        | otherwise = internalError 
            "ResolveOperators.hs" "resolveExpression.applyMinus" "unknown unary operator"        
            
    applyBinary :: Name -> Expression -> Expression -> Expression
    applyBinary n e1 e2 =
        Expression_InfixApplication 
            (mergeRanges (getExprRange e1) (getExprRange e2)) 
            (MaybeExpression_Just e1) 
            ((if isConstructor n then Expression_Constructor else Expression_Variable) (getNameRange n) n)
            (MaybeExpression_Just e2)
        
resolvePattern :: OperatorTable -> [Pattern] -> (Pattern, [ResolveError])
resolvePattern opTable ps = resolve opTable ps (getOp, applyMinus, applyBinary) ([], [], []) 
  where
    getOp (Pattern_Variable (Range_Range Position_Unknown Position_Unknown) n) = Just n
    getOp _ = Nothing
    
    applyMinus :: Name -> Pattern -> Pattern
    applyMinus n p@(Pattern_Literal r l) 
        | n == intUnaryMinusName =
            Pattern_Negate (mergeRanges (getNameRange n) r) l
        | n == floatUnaryMinusName = 
            Pattern_NegateFloat (mergeRanges (getNameRange n) r) l            
        | otherwise = internalError 
                "ResolveOperators.hs" "resolvePattern.applyMinus" "unknown unary operator"        
    applyMinus n _ =
        internalError "ResolveOperators" "resolvePattern" "in patterns unary minus is only allowed in front of literals"         
        
    applyBinary :: Name -> Pattern -> Pattern -> Pattern
    applyBinary n p1 p2 =
        Pattern_InfixConstructor 
            (mergeRanges (getPatRange p1) (getPatRange p2)) 
            p1 n p2

resolve :: 
    OperatorTable -> 
    [expr] -> 
    ( expr -> Maybe Name -- get operator name (if it is one)
    , Name -> expr -> expr -- apply minus
    , Name -> expr -> expr -> expr -- apply binary
    ) 
    -> State expr -> (expr, [ResolveError])
resolve opTable exprs fs@(getOp, applyMinus, applyBinary) state = 
    case exprs of 
        [] -> cleanup state
        (expr:exprs) ->
            let newState = 
                    case getOp expr of
                        Nothing   -> pushExpr expr state
                        Just name -> pushOp opTable name state
            in
                resolve opTable exprs fs newState
  where
--    popOp :: State expr -> State expr
    popOp (op:ops, exprs, errs) 
        | isUnary op =
            case exprs of
                (expr:rest) -> (ops, applyMinus op expr : rest, errs)
                _ -> internalError "ResolveOperators" "popOp" "1"
        | otherwise =
            case exprs of
                (expr2:expr1:rest) -> (ops, applyBinary op expr1 expr2 : rest, errs)
                _ -> internalError "ResolveOperators" "popOp" "2"
--    pushOp :: Name -> State expr -> State expr
    pushOp opTable op state@(top:ops, exprs, errs) =
        case strongerOp opTable top op of
            Left True -> pushOp opTable op (popOp state)
            Left False -> (op:top:ops, exprs, errs)
            Right err -> (op:top:ops, exprs, err : errs) -- arbitrary choice
    pushOp _ op ([], exprs, errs) =
        ([op], exprs, errs)
--    cleanup :: State expr -> expr
    cleanup state@(_:_, _, _)       = cleanup (popOp state)
    cleanup state@(_, [expr], errs) = (expr, errs)
    cleanup _ = internalError "ResolveOperators" "cleanup" "invalid state"
    

pushExpr :: expr -> State expr -> State expr
pushExpr expr (ops, exprs, errs) =
    (ops, expr:exprs, errs)
                
strongerOp :: OperatorTable -> Name -> Name -> Either Bool ResolveError
strongerOp opTable op1 op2
    | isBinary op1 && isBinary op2 =
        if prio1 == prio2 then
            if assoc1 == AssocLeft && assoc2 == AssocLeft then
                Left True
            else if assoc1 == AssocRight && assoc2 == AssocRight then
                Left False
            else
                Right (Ambiguous assoc1 op1 assoc2 op2)
        else
            Left (prio1 > prio2)
    | isUnary  op1 && isBinary op2 = 
        Left (prio1 >= prio2)
    | isUnary  op2 = 
        Left False
    | otherwise = internalError "ResolveOperators" "strongerOp" "unable to determine which operator binds stronger"
  where
    assoc1 = assoc opTable op1
    assoc2 = assoc opTable op2
    prio1 = prio opTable op1
    prio2 = prio opTable op2

isUnary :: Name -> Bool
isUnary name = name `elem` [ intUnaryMinusName, floatUnaryMinusName ]

isBinary :: Name -> Bool
isBinary = not . isUnary

}
