{-# LANGUAGE DeriveAnyClass #-}
module Syntax where

import Prologue
import Data.Mergeable
import GHC.Generics
import Test.QuickCheck hiding (Fixed)
import Data.Aeson

-- | A node in an abstract syntax tree.
--
-- 'a' is the type of leaves in the syntax tree, typically 'Text', but possibly some datatype representing different leaves more precisely.
-- 'f' is the type representing another level of the tree, e.g. the children of branches. Often 'Cofree', 'Free' or similar.
data Syntax a f
  -- | A terminal syntax node, e.g. an identifier, or atomic literal.
  = Leaf a
  -- | An ordered branch of child nodes, expected to be variadic in the grammar, e.g. a list of statements or uncurried function parameters.
  | Indexed [f]
  -- | An ordered branch of child nodes, expected to be of fixed length in the grammar, e.g. a binary operator & its operands.
  | Fixed [f]
  -- | A function call has an identifier where f is a (Leaf a) and a list of arguments.
  | FunctionCall f [f]
  -- | A ternary has a condition, a true case and a false case
  | Ternary { ternaryCondition :: f, ternaryCases :: [f] }
  -- | An anonymous function has a list of expressions and params.
  | AnonymousFunction { params :: [f], expressions :: [f] }
  -- | A function has a list of expressions.
  | Function { id :: f, params :: [f], expressions :: [f] }
  -- | An assignment has an identifier where f can be a member access, and the value is another syntax element (function call, leaf, etc.)
  | Assignment { assignmentId :: f, value :: f }
  -- | An operator assignment represents expressions with operators like math (e.g x += 1) or conditional (e.g. x ||= 1) assignment.
  | OperatorAssignment f f
  -- | A member access contains a syntax, and another syntax that identifies a property or value in the first syntax.
  -- | e.g. in Javascript x.y represents a member access syntax.
  | MemberAccess { memberId :: f, property :: f }
  -- | A method call consisting of its target, the method name, and the parameters passed to the method.
  -- | e.g. in Javascript console.log('hello') represents a method call.
  | MethodCall { targetId :: f, methodId :: f, methodParams :: [f] }
  -- | An operator can be applied to a list of syntaxes.
  | Operator [f]
  -- | A variable declaration. e.g. var foo;
  | VarDecl f
  -- | A variable assignment in a variable declaration. var foo = bar;
  | VarAssignment { varId :: f, varValue :: f }
  -- | A subscript access contains a syntax, and another syntax that indefies a property or value in the first syntax.
  -- | e.g. in Javascript x["y"] represents a subscript access syntax.
  | SubscriptAccess { subscriptId :: f, subscriptElement :: f }
  | Switch { switchExpr :: (Maybe f), cases :: [f] }
  | Case { caseExpr :: f, caseStatements :: [f] }
  -- | A default case in a switch statement.
  | Default [f]
  | Select { cases :: [f] }
  | Object { objectTy :: Maybe f, keyValues :: [f] }
  -- | A pair in an Object. e.g. foo: bar or foo => bar
  | Pair f f
  -- | A comment.
  | Comment a
  -- | A term preceded or followed by any number of comments.
  | Commented [f] (Maybe f)
  | Error [f]
  -- | A for statement has a list of expressions to setup the iteration and then a list of expressions in the body.
  | For [f] [f]
  | DoWhile { doWhileBody :: f, doWhileExpr :: f }
  | While { whileExpr :: f, whileBody :: [f] }
  | Return [f]
  | Throw f
  | Constructor f
  -- | TODO: Is it a problem that in Ruby, this pattern can work for method def too?
  | Try { tryBegin :: [f], catchRescue :: [f], beginElse :: Maybe f, finallyEnsure :: Maybe f }
  -- | An array literal with list of children.
  | Array (Maybe f) [f]
  -- | A class with an identifier, superclass, and a list of definitions.
  | Class f (Maybe f) [f]
  -- | A method definition with an identifier, params, and a list of expressions.
  | Method f [f] [f]
  -- | An if statement with an expression and maybe more expression clauses.
  | If f [f]
  -- | A module with an identifier, and a list of syntaxes.
  | Module { moduleId:: f, moduleBody :: [f] }
  | Import f [f]
  | Export (Maybe f) [f]
  | Yield [f]
  -- | A negation of a single expression.
  | Negate f
  -- | A rescue block has a list of arguments to rescue and a list of expressions.
  | Rescue [f] [f]
  | Go f
  | Defer f
  | TypeAssertion f f
  | TypeConversion f f
  -- | A struct with an optional type.
  | Struct (Maybe f) [f]
  | Break f
  | Continue f
  -- | A block statement has an ordered branch of child nodes, e.g. BEGIN {...} or END {...} in Ruby/Perl.
  | BlockStatement [f]
  -- | A parameter declaration with an optional type.
  | ParameterDecl (Maybe f) f
  -- | A type declaration.
  | TypeDecl f f
  deriving (Eq, Foldable, Functor, Generic, Generic1, Mergeable, Ord, Show, Traversable, ToJSON)


-- Instances

instance (Arbitrary leaf, Arbitrary f) => Arbitrary (Syntax leaf f) where
  arbitrary = sized (syntaxOfSize (`resize` arbitrary) )

  shrink = genericShrink

syntaxOfSize :: Arbitrary leaf => (Int -> Gen f) -> Int -> Gen (Syntax leaf f)
syntaxOfSize recur n | n <= 1 = oneof $ (Leaf <$> arbitrary) : branchGeneratorsOfSize n
                     | otherwise = oneof $ branchGeneratorsOfSize n
  where branchGeneratorsOfSize n =
          [ Indexed <$> childrenOfSize (pred n)
          , Fixed <$> childrenOfSize (pred n)
          ]
        childrenOfSize n | n <= 0 = pure []
        childrenOfSize n = do
          m <- choose (1, n)
          first <- recur m
          rest <- childrenOfSize (n - m)
          pure $! first : rest