{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverlappingInstances #-} {-| Module : Angle.Types.Lang Description : Basic types that make up the Angle language. Copyright : Copyright (C) 2015 Ben Moon License : GNU GPL, version 3 Maintainer : GuiltyDolphin@gmail.com Stability : experimental * LangLit is a container for the various literal values. * LangType represents the types of these values. * LangIdent represents names (identifiers). * Stmt wraps SingStmts. * SingStmt represents the statements in Angle. = Statements in Angle In Angle, statements are split into two main types (see 'Stmt'), singular statements and multi-statements. Singular statements are represented by 'SingStmt' and are what the programmer uses to achieve tasks; multi-statements are multiple singular statements grouped together such that the effective value of the statements is the same as the last statement in the group. == Singular statements Statements are separated by semi-colons and allow the programmer to perform actions. [@assignment@] this allows the programmer to give a name to values and functions, so that he may refer to them later on in the code. [@comments@] comments have no functional value, but serve as a means to document or explain parts of code. [@structures@] see "Angle.Types.Lang#structures" [@expressions@] see 'Expr' [@return@] allows the programmer to exit a function early and use the provided value as the function's value. [@break and continue@] for use during loops: break exits the loop immediately, whereas continue starts the next iteration of the loop, skipping the rest of the loop body. [@raise@] allows the user to throw exceptions. == Language structures #structures# In Angle, there exist language structures for performing certain tasks. [@for loops@ : 'StructFor'] a looping structure that iterates over the elements of an enumerable type and allows each to be used within the body individually. [@while loops@ : 'StructWhile'] executes the body while some condition holds. [@if statement@ : 'StructIf'] categorized as a structure for convenience, consists of three parts: a condition, a body of code that will execute if the condition holds, and an optional body that will execute if the condition does not hold. [@function definitions@ : 'StructDefun'] allows the assignment of previously non-existant lambda bodies to a name. [@try catch@ : 'StructTryCatch'] allows basic handling of exceptions. -} module Angle.Types.Lang ( Expr(..) , LangOp(..) , Op(..) -- * Fundamental types , LangLit(..) , isNull , LangIdent(..) -- * Advanced types , Stmt(..) , SingStmt(..) , LangStruct(..) -- ** Angle types , LangType(..) , typeOf , ArgSig(..) , CatchArg(..) , ShowSyn(..) , SourceRef(..) , startRef , ConstrRef(..) , AnnType(..) , typeAnnOf , ArgElt(..) , Lambda(..) , Scope , enumType , allType ) where import Numeric (showFFloat) import System.IO (Handle) import Angle.Scanner (SourcePos, beginningOfFile) import Angle.Types.Scope (GenScope) type Scope = GenScope LangIdent LangLit Lambda -- | Wraps statements to allow for positional tracking as well -- as multiple statements grouped together. data Stmt = SingleStmt { stmtSingStmt :: SingStmt , stmtSourcePos :: SourceRef } -- ^ Any language construct that -- performs some action or evaluation. | MultiStmt [Stmt] -- ^ Many statements, allowing -- a series of statements to be -- executed one after another, -- discarding intermediate -- results. deriving (Show) -- | Statements are equal if their contents are equal, -- the position may differ. instance Eq Stmt where (SingleStmt x _) == (SingleStmt y _) = x == y (MultiStmt xs) == (MultiStmt ys) = xs == ys _ == _ = False -- | Positional reference to a section of source code. newtype SourceRef = SourceRef { getSourceRef :: (SourcePos, SourcePos) } deriving (Show, Eq) -- | The initial `SourceRef' - starting and ending at -- the beginning of the file. startRef :: SourceRef startRef = SourceRef (beginningOfFile, beginningOfFile) -- | Interface for types that can have a string representation -- in the language. class ShowSyn a where -- | Convert the value to a string representation that -- would produce the exact same result if lexed. showSyn :: a -> String instance ShowSyn Stmt where showSyn (SingleStmt x _) = showSyn x showSyn (MultiStmt xs) = "{\n" ++ showRest ++ "}\n" where showRest = unlines $ map (" "++) $ lines $ concatMap showSyn xs instance ShowSyn SingStmt where showSyn (StmtAssign n e) = concat [showSyn n, " = ", showSyn e, ";\n"] showSyn (StmtAssignNonLocal n e) = concat [showSyn n, " |= ", showSyn e, ";\n"] showSyn (StmtAssignGlobal n e) = concat [showSyn n, " ||= ", showSyn e, ";\n"] showSyn (StmtStruct x) = showSyn x showSyn (StmtExpr e) = showSyn e ++ ";\n" showSyn (StmtComment x) = "#" ++ x ++ "\n" showSyn (StmtReturn x) = "return " ++ showSyn x ++ ";\n" showSyn (StmtBreak x False) = "break" ++ retVal ++ ";\n" where retVal = maybe "" ((" "++) . showSyn) x showSyn (StmtBreak Nothing True) = "continue;\n" showSyn (StmtRaise e) = "raise " ++ showSyn e ++ ";\n" showSyn (StmtBreak _ _) = error "showSyn: StmtBreak not a valid combination!" -- | A single statement. data SingStmt = StmtAssign LangIdent Expr | StmtAssignNonLocal LangIdent Expr | StmtAssignGlobal LangIdent Expr | StmtComment String -- ^ Comment which is - for all intents and purposes - ignored by the parser. | StmtStruct LangStruct | StmtExpr Expr -- ^ Expression. Evaluates to a literal. | StmtReturn Expr | StmtBreak { breakValue :: Maybe Expr , breakContinue :: Bool } | StmtRaise LangIdent deriving (Show, Eq) -- | Specialised language constructs. data LangStruct = StructFor LangIdent Expr Stmt | StructWhile Expr Stmt | StructIf Expr Stmt (Maybe Stmt) | StructDefun LangIdent Lambda | StructTryCatch Stmt [([LangIdent], Stmt)] deriving (Show, Eq) instance ShowSyn LangStruct where showSyn (StructFor n e s) = concat [ "for ", showSyn n , " in ", showSyn e , " do ", showSyn s] showSyn (StructWhile e s) = concat ["while ", showSyn e, " do ", showSyn s] showSyn (StructIf e s els) = concat [ "if " , showSyn e , " then ", showSyn s] ++ case els of Nothing -> "" Just x -> " else " ++ showSyn x showSyn (StructDefun n c) = concat ["defun ", showSyn n, showLambdaFun c] showSyn (StructTryCatch s es) = "try " ++ showSyn s ++ concatMap showCatch es where showCatch (toCatch, b) = "\ncatch " ++ es' toCatch ++ showSyn b es' toCatch = if length toCatch == 1 then showSyn . LitKeyword $ head toCatch else showSyn . LitList . map LitKeyword $ toCatch showSynSep :: ShowSyn a => String -> String -> String -> [a] -> String showSynSep start end _ [] = start ++ end showSynSep start end sep xs = start ++ concatMap ((++sep) . showSyn) (init xs) ++ showSyn (last xs) ++ end showSynArgs :: (ShowSyn a) => [a] -> String showSynArgs = showSynSep "(" ")" ", " showSynOpList :: (ShowSyn a) => [a] -> String showSynOpList = showSynSep " " ")" " " -- | Lambdas consist of two parts: the parameter list and the body. -- -- The parameter list describes the possible forms with which the -- lambda can be invoked. -- -- The body is the code that is executed upon successful invokation. data Lambda = Lambda { lambdaArgs :: ArgSig , lambdaBody :: Stmt , lambdaScope :: Maybe Scope } deriving (Show, Eq) instance ShowSyn Lambda where showSyn (Lambda args body@(SingleStmt _ _) _) = concat ["(", showSyn args, " ", init $ showSyn body, ")"] showSyn (Lambda args body _) = concat ["(", showSyn args, " ", showSyn body, ")"] -- | An argument signature. data ArgSig = ArgSig { stdArgs :: [ArgElt] -- ^ Standard positional arguments. , catchAllArg :: Maybe CatchArg -- ^ Argument that catches any remaining arguments after the positional arguments have been filled. } deriving (Show, Eq) data CatchArg = CatchArg { catchArgName :: LangIdent , catchArgConstr :: Maybe ConstrRef } deriving (Show, Eq) instance ShowSyn CatchArg where showSyn (CatchArg { catchArgName = n, catchArgConstr = c }) = showSyn n ++ case c of Just constr -> ':' : showSyn constr Nothing -> "" -- | A single element of a parameter list, allows enforcing of -- correct annotation types and parameter constraints. data ArgElt = ArgElt { argEltType :: AnnType , argEltName :: LangIdent , argEltConstr :: Maybe ConstrRef } deriving (Show, Eq) instance ShowSyn ArgElt where showSyn (ArgElt {argEltType=typ , argEltName=name , argEltConstr=constr }) = case typ of AnnFun -> "$" AnnLit -> "!" AnnAny -> "" ++ showSyn name ++ case constr of Just c -> ':' : showSyn c Nothing -> "" -- | Name referencing a function to be used as a parameter constraint. -- -- Parameter-constraints perform run-time checks on arguments -- passed to a function. -- -- For example, if a function of the form @foo(x:\@largeInt)@ -- is called with some value @y@, the value of @y@ will be -- evaluated and then passed to the function @largeInt@. -- @largeInt@ should then return a boolean value stating whether -- the value passed satisfies it's criteria. Failure to return -- a boolean value will result in a fatal error. -- If @largeInt@ returns @true@, then the function proceeds as normal, -- otherwise an error is thrown. -- -- Functions to be used as parameter-constraints must satisfy the following: -- -- * Must be able to take 1 value on its own. -- -- * Must return a true or false value when used as a constraint. data ConstrRef = ConstrRef { getConstrRef :: LangIdent , constrRefArgs :: Maybe [Expr] } deriving (Show, Eq) instance ShowSyn ConstrRef where showSyn (ConstrRef {getConstrRef = name, constrRefArgs=a}) = '@' : showSyn name ++ showRefArgs where showRefArgs = maybe "" showSynArgs a -- | Possible parameter restrictions provided in definition annotation. -- -- As an example, in the function @foo(x)@, there -- is no restriction on what @x@ is passed. However, in the -- function @bar($x)@, @x@ must be a function if passed to @bar@, -- if it is anything else an error will occur. data AnnType = AnnFun | AnnLit | AnnAny deriving (Eq) instance Show AnnType where show AnnFun = "function" show AnnLit = "literal" show AnnAny = "any" -- | Represents the basic types that can be used in Angle. -- -- The reason for the types being contained within one datatype -- is to allow untyped expressions to exist in Angle, including -- multi-type lists. -- -- See 'ConstrRef' for a means of providing run-time parameter -- constraints in Angle. data LangLit = LitStr String -- ^ Strings. | LitInt Int -- ^ Integers, support at least the range -2^29 to 2^29-1. | LitFloat Double -- ^ Double-precision floating point value. | LitList [LangLit] -- ^ List of literal values. Values may be of different types. | LitBool Bool -- ^ Boolean value. | LitChar Char -- ^ Character literals, these cannot be specified by the programmer and are used internally -- by Angle. | LitRange LangLit (Maybe LangLit) (Maybe LangLit) | LitNull -- ^ Implicit value -- returned from any expression -- that fails to return a value -- explicitly. | LitLambda Lambda -- ^ A function without a name. | LitClosure Lambda | LitKeyword { getLitKeyword :: LangIdent } | LitHandle Handle deriving (Show, Eq) instance ShowSyn LangLit where showSyn (LitStr x) = show x showSyn (LitChar x) = show x showSyn (LitInt x) = show x showSyn (LitFloat x) = showFFloat Nothing x "" showSyn (LitList xs) = showSynList xs where showSynList = showSynSep "[" "]" ", " showSyn (LitBool x) = if x then "true" else "false" showSyn (LitRange x y z) = showRange where showRange = concat [ "(" , showSyn x , ".." , maybe "" showSyn y , maybe "" ((".." ++) . showSyn) z , ")" ] showSyn LitNull = "null" showSyn (LitLambda x) = showSyn x showSyn (LitClosure x) = showSyn x showSyn (LitKeyword x) = ':' : showSyn x showSyn (LitHandle h) = show h -- | The types of the values that can be used in Angle. -- -- The separation of the value containers ('LangLit') and -- type representations allows untyped expressions to be -- attained more easily. data LangType = LTStr | LTChar | LTInt | LTFloat | LTList | LTBool | LTRange | LTNull | LTKeyword | LTLambda | LTHandle deriving (Eq) -- | Function for determining the type of a literal. typeOf :: LangLit -> LangType typeOf (LitStr _) = LTStr typeOf (LitChar _) = LTChar typeOf (LitInt _) = LTInt typeOf (LitFloat _) = LTFloat typeOf (LitList _) = LTList typeOf (LitBool _) = LTBool typeOf (LitRange{}) = LTRange typeOf LitNull = LTNull typeOf (LitLambda{}) = LTLambda typeOf (LitClosure{}) = LTLambda typeOf (LitKeyword _) = LTKeyword typeOf (LitHandle _) = LTHandle -- | Determine the required annotation restriction of a -- particular literal. See 'AnnType' for more information. typeAnnOf :: LangLit -> AnnType typeAnnOf (LitLambda{}) = AnnFun typeAnnOf _ = AnnLit instance Show LangType where show LTList = "list" show LTBool = "boolean" show LTStr = "string" show LTInt = "integer" show LTFloat = "float" show LTNull = "null" show LTRange = "range" show LTLambda = "function" show LTKeyword = "keyword" show LTChar = "char" show LTHandle = "handle" -- | Expressions must be evaluable to some literal, although -- in some cases they may evaluate to the null literal. data Expr = ExprIdent LangIdent -- ^ 'LangIdent' when representing a variable. | ExprFunIdent LangIdent -- ^ 'LangIdent' when representing a function. | ExprLit LangLit -- ^ Expression wrapping a literal value. | ExprFunCall LangIdent Bool [Expr] | ExprLambdaCall Lambda [Expr] | ExprOp LangOp | ExprList [Expr] -- ^ An unevaluated list (see 'LitList'). | ExprRange Expr (Maybe Expr) (Maybe Expr) -- ^ An unevaluated range (see 'LitRange'). | ExprParamExpand LangIdent -- ^ Special form of expression that represents a -- catch parameter. deriving (Show, Eq) instance ShowSyn Expr where showSyn (ExprIdent x) = showSyn x showSyn (ExprLit x) = showSyn x showSyn (ExprFunCall n asClass es) = (if asClass then "@" else "") ++ showSyn n ++ showSynArgs es showSyn (ExprOp x) = showSyn x showSyn (ExprFunIdent x) = "$" ++ showSyn x showSyn (ExprList xs) = showSynList xs where showSynList = showSynSep "[" "]" ", " showSyn (ExprRange{}) = error "showSyn - cannot show unevaluated range" showSyn (ExprLambdaCall x xs) = showSyn (LitLambda x) ++ " : (" ++ showSynArgs xs ++ ")" showSyn (ExprParamExpand x) = ".." ++ showSyn x -- | Represents names that can be assigned values. -- -- Each name can contain one literal value and one lambda (function). -- When a name is being resolved, context and constraints expressed -- by the programmer determine whether a name will resolve to the -- contained value or lambda. newtype LangIdent = LangIdent { getIdent :: String } deriving (Show, Eq, Ord) instance ShowSyn LangIdent where showSyn = getIdent instance ShowSyn ArgSig where showSyn (ArgSig args catchArg) = showSynSep "(" (case catchArg of Nothing -> ")" Just x -> concat [ if not (null args) then ", .." else ".." , showSyn x , ")"]) ", " args -- | Two forms of operator exist in Angle: data LangOp = SpecOp Op Expr -- ^ Special operators that can only be used in prefix -- and act upon a single expresson. | MultiOp Op [Expr] -- ^ Multi-operators that can take multiple values but -- must be enclosed within parentheses. deriving (Show, Eq) instance ShowSyn LangOp where showSyn (SpecOp o e) = showSyn o ++ showSyn e showSyn (MultiOp o es) = concat ["(", showSyn o, showSynOpList es] -- | Builtin operators. data Op = OpAdd | OpAnd -- ^ Logical AND. | OpConcat | OpDiv | OpEq -- ^ Check equality. | OpGreater | OpGreaterEq | OpLess | OpLessEq | OpExp | OpMult | OpNeg | OpNot -- ^ Logical NOT. | OpOr -- ^ Logical OR. | OpSub | UserOp LangIdent deriving (Show, Eq) instance ShowSyn Op where showSyn OpAdd = "+" showSyn OpAnd = "&" showSyn OpConcat = "++" showSyn OpDiv = "/" showSyn OpEq = "==" showSyn OpGreater = ">" showSyn OpGreaterEq = ">=" showSyn OpLess = "<" showSyn OpLessEq = "<=" showSyn OpMult = "*" showSyn OpNeg = "-" showSyn OpNot = "^" showSyn OpOr = "|" showSyn OpSub = "-" showSyn OpExp = "**" showSyn (UserOp x) = showSyn x showLambdaFun :: Lambda -> String showLambdaFun (Lambda {lambdaArgs=args, lambdaBody=body}) = showSyn args ++ " " ++ showSyn body -- | True when passed the nullary literal. isNull :: LangLit -> Bool isNull LitNull = True isNull _ = False -- | True if the type can be enumerated in Angle. enumType :: LangLit -> Bool enumType (LitInt _) = True enumType (LitChar _) = True enumType (LitFloat _) = True enumType _ = False -- | True if all values in list have the specified type. allType :: LangType -> [LangLit] -> Bool allType t = all ((==t) . typeOf)