zwirn-0.2.2.0: src/zwirn-lang/Zwirn/Language/Rotate.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
module Zwirn.Language.Rotate
( runRotate,
runRotateUnsafe,
RotationError (..),
)
where
{-
Rotate.hs - syntax tree rotation, code adapted from
https://gist.github.com/heitor-lassarote/b20d6da0a9042d31e439befb8c236a4e
Copyright (C) 2023, Martin Gius
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this library. If not, see <http://www.gnu.org/licenses/>.
-}
import Control.Monad.Except
import Control.Monad.Identity
import Zwirn.Language.Location
import Zwirn.Language.Simple
import Zwirn.Language.Syntax
ops :: [Declaration]
ops =
[ ("(*)", Fixity LeftA 9),
("(/)", Fixity LeftA 9),
("(.)", Fixity RightA 9),
("(+)", Fixity LeftA 6),
("(-)", Fixity LeftA 6),
("($)", Fixity RightA 0),
("($:)", Fixity RightA 0),
("(&:)", Fixity RightA 0),
("($!)", Fixity RightA 0),
("(#!)", Fixity RightA 0),
("($|)", Fixity RightA 0),
("(|$)", Fixity RightA 0),
("(#)", Fixity RightA 3),
("(&)", Fixity RightA 2),
("(++)", Fixity RightA 5),
-- arithmetic
("(|-)", Fixity LeftA 6),
("(-|)", Fixity LeftA 6),
("(|+)", Fixity LeftA 6),
("(+|)", Fixity LeftA 6),
("(|*)", Fixity LeftA 7),
("(*|)", Fixity LeftA 7),
("(//)", Fixity LeftA 7),
("(|/)", Fixity LeftA 7),
("(/|)", Fixity LeftA 7),
-- ord
("(<=)", Fixity NonA 4),
("(>=)", Fixity NonA 4),
("(&&)", Fixity RightA 3),
("(||)", Fixity RightA 3),
("(==)", Fixity NonA 4)
]
defaultFixity :: Fixity
defaultFixity = Fixity LeftA 8
newtype RotationError = RotationError SrcLoc
instance Show RotationError where
show (RotationError (SrcLoc p)) = "Could not resolve operator precedence at location " ++ show p
show (RotationError NoLoc) = "Could not resolve operator precedence"
type Rotate a = ExceptT RotationError Identity a
-- | Describes which action the rotation algorithm should use.
data Rotation
= -- | Fail due to the mixing of incompatible operators.
Fail
| -- | Keep the tree as it is.
Keep
| -- | Balance the tree to the left.
Rotate
runRotate :: LocSimpleTerm -> Either RotationError LocSimpleTerm
runRotate t = runIdentity $ runExceptT $ rotate t
runRotateUnsafe :: LocSimpleTerm -> LocSimpleTerm
runRotateUnsafe t = case runRotate t of
Left err -> error $ show err
Right r -> r
-- | The Happy parser is written in a way so that it will always create a right-balanced AST.
-- We compare the operators and indicate how to rotate the tree.
shouldRotate :: Fixity -> Fixity -> Rotation
shouldRotate (Fixity a p) (Fixity a' p') = case compare p p' of
LT -> Keep
EQ -> case (a, a') of
(LeftA, LeftA) -> Rotate
(RightA, RightA) -> Keep
(_, _) -> Fail
GT -> Rotate
-- | Rebalances the tree to respect the associativity and precedence of the
-- parsed operators.
-- Not very efficient, but enough for demonstration purposes.
findOp :: LocVar -> Rotate Fixity
findOp (Located _ o) = case lookup o ops of
Just d -> return d
Nothing -> return defaultFixity
rotate :: LocSimpleTerm -> Rotate LocSimpleTerm
rotate (Located p1 (SInfix l op r)) = do
-- Rotating the left side is unneeded since this grammar is very simple.
-- This is because trees are always right-balanced and the left side is
-- always an atom.
lRotated <- rotate l
rRotated <- rotate r
case rRotated of
(Located p2 (SInfix l' op' r')) -> do
opDec <- findOp op
opDec' <- findOp op'
case shouldRotate opDec opDec' of
Fail -> throwError (RotationError $ lLoc op)
Keep -> return $ Located p1 $ SInfix lRotated op rRotated
Rotate -> return $ Located p2 $ SInfix (Located p1 $ SInfix lRotated op l') op' r'
_ -> return $ Located p1 $ SInfix lRotated op rRotated
rotate (Located p (SApp l r)) = do
lRotated <- rotate l
rRotated <- rotate r
return $ Located p $ SApp lRotated rRotated
rotate (Located p e@(SVar _)) = return $ Located p e
rotate (Located p e@(SText _)) = return $ Located p e
rotate (Located p e@(SNum _)) = return $ Located p e
rotate (Located p SRest) = return $ Located p SRest
rotate (Located p (SSeq ts)) = fmap (Located p . SSeq) (mapM rotate ts)
rotate (Located p (SStack ts)) = fmap (Located p . SStack) (mapM rotate ts)
rotate (Located p (SChoice n ts)) = fmap (Located p . SChoice n) (mapM rotate ts)
rotate (Located p (SLambda vs t)) = Located p . SLambda vs <$> rotate t
rotate (Located p (SBracket t)) = fmap (Located p . SBracket) (rotate t)