angle-0.8.1.0: src/Angle/Parse/Parser/Internal.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-|
Module : Angle.Parse.Parser.Internal
Description : Module defining functions for parsing Angle code.
Copyright : Copyright (C) 2015 Ben Moon
License : GNU GPL, version 3
Maintainer : GuiltyDolphin@gmail.com
Stability : alpha
Uses parsers from "Angle.Parse.Token" and "Angle.Parse.Helpers", as well
as custom lexing functions to define all the necessary functions for
converting Angle source code into Haskell types.
-}
module Angle.Parse.Parser.Internal
( program
, evalParse
-- ** Literals
, lambda
, langLit
, litBool
, litInt
, litList
, litNull
, litRange
, litStr
-- ** Statements and structures
, langStruct
, singStmt
, stmt
-- ** Expressions
, expr
, exprFunCall
, langOp
) where
import Control.Applicative
import Control.Monad.State
import Angle.Parse.Helpers
import Angle.Parse.Token
import Angle.Types.Lang
-- | Single statement or a multi-statement.
--
-- See 'singleStmt' for the syntax of singular statments.
--
-- Multi-statements are declared by surrounding a series
-- of statements with braces.
--
-- For example: a series of statements that swaps the values
-- of two variables might be:
--
-- @
-- {
-- c = x;
-- x = y;
-- y = c;
-- }
-- @
--
-- Which would swap he values of @x@ and @y@, and the value produced by
-- the series of statements would be the value of the last (value of
-- @c@).
stmt :: Parser Stmt
stmt = (multiStmt <|> singleStmt) <?> "statement"
singleStmt :: Parser Stmt
singleStmt = do
many $ surrounded whitespace stmtComment
initPos <- liftM sourcePos get
res <- singStmt
endPos <- liftM sourcePos get
return SingleStmt
{ stmtSingStmt = res
, stmtSourcePos = SourceRef (initPos, endPos)
}
-- | Statement consisting of zero or more statements.
multiStmt :: Parser Stmt
multiStmt = MultiStmt <$> within tokMultiStmtStart tokMultiStmtEnd (many (tryParse stmt))
-- | Singular statement.
--
-- Takes the following forms:
--
-- [@return expr@] exits early from a function and produces the
-- value that @expr@ evaluates to.
--
-- [@break {expr}@] exits early from a loop, and produces the value
-- @expr@ if supplied, or the last value present in the loop.
--
-- [@continue@] skips the rest of the body of the current loop and
-- begins the next iteration.
--
-- [@ident = expr@] assigns the value of @expr@ to the variable
-- @ident@.
--
-- [@expr@] executes an expression and produces the value evaluated.
--
-- Or a structure may be used, see 'langStruct'.
singStmt :: Parser SingStmt
singStmt = stmtStruct
<|> stmtReturn <* singStmtEnd
<|> stmtRaise <* singStmtEnd
<|> stmtBreak <* singStmtEnd
<|> stmtAssignGlobal <* singStmtEnd
<|> stmtAssignNonLocal <* singStmtEnd
<|> stmtAssign <* singStmtEnd
<|> stmtExpr <* singStmtEnd
<?> "statement"
singStmtEnd :: Parser ()
singStmtEnd = surrounded whitespace (void (char ';')) <?> "end of statement"
-- | Variable assignment.
stmtAssign :: Parser SingStmt
stmtAssign = StmtAssign
<$> tryParse (langIdent <* tokAssign)
<*> expr
stmtAssignNonLocal :: Parser SingStmt
stmtAssignNonLocal = StmtAssignNonLocal
<$> tryParse (langIdent <* tokAssignNonLocal)
<*> expr
stmtAssignGlobal :: Parser SingStmt
stmtAssignGlobal = StmtAssignGlobal
<$> tryParse (langIdent <* tokAssignGlobal)
<*> expr
stmtBreak :: Parser SingStmt
stmtBreak = sBreak <|> sContinue
where sContinue = string "continue" >> return StmtBreak { breakValue=Nothing, breakContinue=True}
sBreak = string "break" >> (do
retV <- optional (tryParse (tokNSpaced *> expr))
return StmtBreak { breakValue=retV, breakContinue=False})
stmtComment :: Parser SingStmt
stmtComment = StmtComment
<$> (char '#' *> manyTill tokEndComment anyChar <* tokEndComment)
where
tokEndComment = void (char '\n')
<|> void tokEOF
stmtStruct :: Parser SingStmt
stmtStruct = liftM StmtStruct langStruct
stmtReturn :: Parser SingStmt
stmtReturn = liftM StmtReturn (string "return " *> expr)
<?> "return construct"
stmtExpr :: Parser SingStmt
stmtExpr = liftM StmtExpr expr
stmtRaise :: Parser SingStmt
stmtRaise = string "raise " >> liftM (StmtRaise . getLitKeyword) litKeyword
-- | Language structure.
--
-- Possible forms are:
--
-- [@for ident in expr do stmt@] loops over values produced by
-- @expr@, allowing them to be referenced in @stmt@ by the name
-- @ident@.
--
-- [@while expr do stmt@] while @expr@ evaluates to @true@, will
-- execute @stmt@ then repeat.
--
-- [@if expr then stmt1 {else stmt2}@] if @expr@ evaluates to @true@,
-- executes @stmt1@, otherwise will execute @stmt2@ if it exists, or
-- produce a null value.
--
-- [@unless expr stmt@] if @expr@ evaluates to @false@ then execute
-- @stmt@, otherwise produce a null value.
--
-- [@defun ident(args) stmt@] defines a function @ident@ that has
-- a parameter list @args@ and body @stmt@.
langStruct :: Parser LangStruct
langStruct = structFor
<|> structWhile
<|> structIf
<|> structUnless
<|> structDefun
<|> structTryCatch
<?> "language construct"
-- | For loop.
structFor :: Parser LangStruct
structFor = do
name <- string "for " *> langIdent
iter <- string " in " *> expr
body <- string " do " *> stmt
return $ StructFor name iter body
-- | While loop.
structWhile :: Parser LangStruct
structWhile = StructWhile
<$> (string "while " *> expr)
<*> (string " do " *> stmt)
-- | Conditional if statement.
--
-- of the form:
--
-- if EXPR then STMT {else STMT}
structIf :: Parser LangStruct
structIf = StructIf
<$> (string "if " *> expr)
<*> (string " then " *> stmt)
<*> optional (string "else " *> stmt)
-- | unless EXPR STMT
--
-- is equivalent to
--
-- if (NOT)EXPR then STMT
structUnless :: Parser LangStruct
structUnless = do
e <- string "unless " *> expr
tokNSpaced
s <- stmt
els <- optional $ string "else " *> stmt
return $ StructIf (ExprOp (SpecOp OpNot e)) s els
-- | Function definition.
structDefun :: Parser LangStruct
structDefun = StructDefun
<$> (string "defun " *> identName)
<*> (Lambda
<$> callList <* tokStmtBetween
<*> stmt <*> return Nothing)
-- | Exception handling.
structTryCatch :: Parser LangStruct
structTryCatch = do
string "try "
tryCode <- stmt
catchers <- many catchSec
return $ StructTryCatch tryCode catchers
where exceptionList = tokList $ sepWith tokEltSep (liftM getLitKeyword litKeyword)
singleE = liftM ((:[]) . getLitKeyword) litKeyword
catchSec = do
string "catch "
toCatch <- singleE <|> exceptionList
exceptCode <- stmt
return (toCatch, exceptCode)
-- | A program consists of a series of statements.
program :: Parser Stmt
program = liftM MultiStmt $ followed tokEOF (many stmt)
exprLit :: Parser Expr
exprLit = liftM ExprLit langLit
-- | Language literals.
langLit :: Parser LangLit
langLit = litStr
<|> litNull
<|> litChar
<|> tryParse litClosure
<|> tryParse litLambda
<|> litRange
<|> litBool
<|> litList
<|> tryParse litFloat
<|> litInt
<|> litKeyword
<?> "literal"
-- | A literal string.
litStr :: Parser LangLit
litStr = liftM LitStr tokString
-- | Denary integer.
litInt :: Parser LangLit
litInt = liftA LitInt tokInt <?> "integer literal"
-- | Floating-point literal.
litFloat :: Parser LangLit
litFloat = liftM LitFloat tokFloat
<?> "floating-point literal"
-- | Multi-type list.
litList :: Parser LangLit
litList = liftM LitList (tokList $ sepWith tokEltSep langLit)
<?> "list literal"
litKeyword :: Parser LangLit
litKeyword = liftM LitKeyword (char ':' >> identKeyword)
litLambda :: Parser LangLit
litLambda = liftM LitLambda lambda
litClosure :: Parser LangLit
litClosure = char '$' >> liftM LitClosure lambda
exprList :: Parser Expr
exprList = liftM ExprList (tokList $ sepWith tokEltSep expr)
exprRange :: Parser Expr
exprRange = parens $ do
from <- expr
string ".."
to <- optional expr
step <- optional . tryParse $ do
string ".."
expr
return $ ExprRange from to step
-- | Boolean literal.
--
-- litBool = true | false ;
litBool :: Parser LangLit
litBool = liftM LitBool (litTrue <|> litFalse)
<?> "boolean literal"
where litTrue = string "true" >> return True
litFalse = string "false" >> return False
litChar :: Parser LangLit
litChar = liftM LitChar tokChar
-- | Dotted range of values.
--
-- Ranges have a single mandatory part and two optional parts, in the
-- form @(start..[stop][..step])@.
--
-- Start is the value with which the range will begin. @(1..)@
-- represents a range starting with the integer 1 and enumerating
-- to infinity.
--
-- Stop is the value at which the range will stop enumerating when
-- it is equal to or higher than the specified value.
-- @(1..50)@ represents a range that can produce the numerical
-- values 1 through 50.
--
-- Step is the optional increment of the range, for non-integers, such
-- as characters, the range step is the positional difference between
-- the characters.
-- @(1..10..2)@ represents a range that will enumerate 1 through
-- 10, but incrementing by 2 on each enumeration.
--
-- The following are all valid forms of ranges:
--
-- [@(start..)@] range from start to maximum bound of type.
--
-- [@(start..stop)@] range from start to stop.
--
-- [@(start..stop..step)@] range from start to stop, incrementing by
-- step.
--
-- [@(start....step)@] range from start to maxmimum bound of type,
-- incrementing by step.
litRange :: Parser LangLit
litRange = tryParse $ parens $ do
from <- langLit
string ".."
to <- optional langLit
step <- optional . tryParse $ do
string ".."
langLit
return $ LitRange from to step
-- | Non-valued literal.
--
-- litNull = 'null' | '()' ;
litNull :: Parser LangLit
litNull = string "()" <|> string "null" >> return LitNull
-- | Expression.
expr :: Parser Expr
expr = ( tryParse exprLit
<|> exprList
<|> exprFunIdent
<|> tryParse exprOp
<|> tryParse exprRange
<|> exprFunCall
<|> exprIdent)
<?> "expression"
exprIdent :: Parser Expr
exprIdent = liftM ExprIdent langIdent <?> "identifier"
identName :: Parser LangIdent
identName = liftM LangIdent (tryParse $ ident False) <?> "identifier"
identKeyword :: Parser LangIdent
identKeyword = liftM LangIdent (tryParse $ ident True)
langIdent :: Parser LangIdent
langIdent = identName
funIdent :: Parser LangIdent
funIdent = char '$' *> identName
exprFunIdent :: Parser Expr
exprFunIdent = liftM ExprFunIdent funIdent
-- | Lambdas are unnamed functions which can be assigned to
-- identifiers or passed to functions when used in their literal
-- form.
--
-- @((x y) (+ x y);)@
--
-- References a lambda that performs basic addition.
lambda :: Parser Lambda
lambda = parens $ do
args <- callList <* tokNSpaced
body <- stmt
return $ Lambda args body Nothing
-- | Set of arguments for a function
arglist :: Parser [Expr]
arglist = parens (sepWith tokEltSep (expr <|> exprParamExpand))
exprParamExpand :: Parser Expr
exprParamExpand = liftM ExprParamExpand $ string ".." >> langIdent
-- | Function call.
--
-- Function calls consist of an identifier followed
-- by an argument list surrounded by parentheses (see 'arglist').
exprFunCall :: Parser Expr
exprFunCall = (do
asClass <- (char '@' >> return True) <|> return False
name <- tryParse (langIdent <* lookAhead (char '('))
args <- arglist
return $ ExprFunCall name asClass args) <?> "function call"
exprOp :: Parser Expr
exprOp = liftM ExprOp langOp
-- | Either a special operator or a multi-operator.
--
-- Special operators are prefix and have one operand, i.e., prefix
-- unary operators.
--
-- Multi-operators can take an arbitrary number of arguments (usually
-- at least one) but must be surrounded by parentheses.
langOp :: Parser LangOp
langOp = specOp <|> multiOp
makeOp :: Parser a -> Op -> Parser Op
makeOp sc op = sc >> return op
opAdd, opAnd, opConcat, opDiv, opEq,
opGreater, opGreaterEq, opLess, opLessEq,
opMult, opNeg, opNot, opOr, opSub, opExp
:: Parser Op
opAdd = makeOp (char '+') OpAdd
opAnd = makeOp (char '&') OpAnd
opConcat = makeOp (string "++") OpConcat
opDiv = makeOp (char '/') OpDiv
opEq = makeOp (string "==") OpEq
opGreater = makeOp (char '>') OpGreater
opGreaterEq = makeOp (string ">=") OpGreaterEq
opLess = makeOp (char '<') OpLess
opLessEq = makeOp (string "<=") OpLessEq
opMult = makeOp (char '*') OpMult
opNeg = makeOp (char '-') OpNeg
opNot = makeOp (char '^') OpNot
opOr = makeOp (char '|') OpOr
opSub = makeOp (char '-') OpSub
opExp = makeOp (string "**") OpExp
userOp :: Parser Op
userOp = liftM (UserOp . LangIdent) (some tokOpChar) <?> "operator"
-- | Special operators that can be used outside of
-- parentheses in a prefix form.
specOp :: Parser LangOp
specOp = choice (map preOp specOps) <?> "special operator"
specOps :: [Parser Op]
specOps = [opNeg, opNot]
preOp :: Parser Op -> Parser LangOp
preOp sc = do
op <- sc
opr <- expr
return $ SpecOp op opr
-- |Operators called within parentheses that may have
-- multiple operands
multiOp :: Parser LangOp
multiOp = parens (choice (map (tryParse . multOp) multiOps)
<|> multOp userOp) <?> "operator expression"
-- | List of all the MultiOp parsers.
multiOps :: [Parser Op]
multiOps = [ opAdd, opAnd, opConcat
, opExp
, opDiv, opEq
, opGreater, opGreaterEq
, opLess, opLessEq
, opMult, opOr
, opSub ]
multOp :: Parser Op -> Parser LangOp
multOp sc = MultiOp
<$> (sc <* tokSpace)
<*> sepWith (some tokWhitespace) expr
constrRef :: Parser ConstrRef
constrRef = char '@' >> liftM2 ConstrRef langIdent (optional arglist)
constrRefArgSig :: Parser ConstrRef
constrRefArgSig = tryParse (char ':' >> constrRef)
callList :: Parser ArgSig
callList = parens $ do
params <- sepWith tokEltSep argElt
catcher <- optional catchArg
return $ ArgSig params catcher
catchArg :: Parser CatchArg
catchArg = do
string ".."
n <- identName
constr <- optional constrRefArgSig
return $ CatchArg n constr
argElt :: Parser ArgElt
argElt = do
typ <- argSigType
name <- identName
cls <- optional constrRefArgSig
return $ ArgElt typ name cls
argSigType :: Parser AnnType
argSigType = char '$' *> return AnnFun
<|> char '!'*> return AnnLit
<|> return AnnAny