axel-0.0.13: src/Axel/Normalize.hs
{-# LANGUAGE ApplicativeDo #-}
module Axel.Normalize where
import Axel.Prelude
import Axel.AST
( CaseBlock(CaseBlock)
, DataDeclaration(DataDeclaration)
, Expression(ECaseBlock, EEmptySExpression, EFunctionApplication,
EIdentifier, ELambda, ELetBlock, ELiteral, EPatternBinding,
ERawExpression, ERecordDefinition, ERecordType)
, FunctionApplication(FunctionApplication)
, FunctionDefinition(FunctionDefinition)
, Identifier
, Import(ImportItem, ImportType)
, ImportSpecification(ImportAll, ImportOnly)
, Lambda(Lambda)
, LetBlock(LetBlock)
, Literal(LChar, LFloat, LInt, LString)
, MacroDefinition(MacroDefinition)
, MacroImport(MacroImport)
, NewtypeDeclaration(NewtypeDeclaration)
, PatternBinding(PatternBinding)
, Pragma(Pragma)
, QualifiedImport(QualifiedImport)
, RecordDefinition(RecordDefinition)
, RecordType(RecordType)
, RestrictedImport(RestrictedImport)
, SMStatement
, Statement(SDataDeclaration, SFunctionDefinition, SMacroDefinition,
SMacroImport, SModuleDeclaration, SNewtypeDeclaration, SPragma,
SQualifiedImport, SRawStatement, SRestrictedImport, STopLevel,
STypeSignature, STypeSynonym, STypeclassDefinition,
STypeclassInstance)
, TopLevel(TopLevel)
, TypeDefinition(ProperType, TypeConstructor)
, TypeSignature(TypeSignature)
, TypeSynonym(TypeSynonym)
, TypeclassDefinition(TypeclassDefinition)
, TypeclassInstance(TypeclassInstance)
)
import Axel.Eff ((:>>))
import Axel.Eff.Error (Error(NormalizeError), renderError, unsafeRunError)
import Axel.Parse (unhygenisizeIdentifier)
import qualified Axel.Parse.AST as Parse
( Expression(LiteralChar, LiteralFloat, LiteralInt, LiteralString,
SExpression, Symbol)
)
import qualified Axel.Sourcemap as SM (Expression)
import qualified Data.Text as T
import Effectful ((:>))
import qualified Effectful as Eff
import qualified Effectful.Error.Static as Eff
import qualified Effectful.Reader.Static as Eff
type ExprCtxt = [SM.Expression]
pushCtxt ::
(Eff.Reader [SM.Expression] :> effs)
=> SM.Expression
-> Eff.Eff effs a
-> Eff.Eff effs a
pushCtxt newCtxt = Eff.local (newCtxt :)
withExprCtxt :: Eff.Eff (Eff.Reader [SM.Expression] ': effs) a -> Eff.Eff effs a
withExprCtxt = Eff.runReader []
throwNormalizeError ::
('[ Eff.Error Error, Eff.Reader FilePath, Eff.Reader ExprCtxt] :>> effs)
=> Text
-> Eff.Eff effs a
throwNormalizeError msg = do
filePath <- Eff.ask @FilePath
exprCtxt <- Eff.ask @ExprCtxt
Eff.throwError $ NormalizeError filePath msg exprCtxt
normalizeExpression ::
('[ Eff.Error Error, Eff.Reader FilePath, Eff.Reader ExprCtxt] :>> effs)
=> SM.Expression
-> Eff.Eff effs (Expression (Maybe SM.Expression))
normalizeExpression expr@(Parse.LiteralChar _ char) =
pure $ ELiteral (LChar (Just expr) char)
normalizeExpression expr@(Parse.LiteralFloat _ float) =
pure $ ELiteral (LFloat (Just expr) float)
normalizeExpression expr@(Parse.LiteralInt _ int) =
pure $ ELiteral (LInt (Just expr) int)
normalizeExpression expr@(Parse.LiteralString _ string) =
pure $ ELiteral (LString (Just expr) (T.pack string))
normalizeExpression expr@(Parse.SExpression _ items) =
pushCtxt expr $
case items of
(Parse.Symbol sourceMetadata special):args ->
case unhygenisizeIdentifier special of
"case" ->
case args of
var:cases ->
let normalizedCases =
traverse
(\case
Parse.SExpression _ [pat, body] ->
(,) <$> normalizeExpression pat <*>
normalizeExpression body
x ->
pushCtxt x $
throwNormalizeError
"A `case` clause must be an s-expression containing: 1) a pattern and 2) a body.")
cases
in ECaseBlock <$>
(CaseBlock (Just expr) <$> normalizeExpression var <*>
normalizedCases)
_ ->
throwNormalizeError
"`case` takes an expression followed by `case` clauses."
"@" ->
case args of
[name, pattern'] ->
EPatternBinding <$>
(PatternBinding (Just expr) <$> normalizeExpression name <*>
normalizeExpression pattern')
_ -> throwNormalizeError "`@` takes: 1) a name and 2) a pattern."
"\\" ->
case args of
[Parse.SExpression _ (Parse.Symbol _ "list":args'), body] ->
let normalizedArguments = traverse normalizeExpression args'
in ELambda <$>
(Lambda (Just expr) <$> normalizedArguments <*>
normalizeExpression body)
_ ->
throwNormalizeError
"`\\` takes exactly two arguments: 1) an argument list and 2) a body."
"let" ->
case args of
[Parse.SExpression _ (Parse.Symbol _ "list":bindings), body] ->
let normalizedBindings =
traverse
(\case
Parse.SExpression _ [name, value] ->
(,) <$> normalizeExpression name <*>
normalizeExpression value
x ->
pushCtxt x $
throwNormalizeError
"Each `let` binding must be an s-expression containing: 1) a pattern and 2) a value!")
bindings
in ELetBlock <$>
(LetBlock (Just expr) <$> normalizedBindings <*>
normalizeExpression body)
_ ->
throwNormalizeError
"`let` takes exactly two arguments: 1) a bindings list and 2) a body."
"raw" ->
case args of
[rawSource] ->
let normalizedRawSource =
case rawSource of
Parse.LiteralString _ x -> pure x
x ->
pushCtxt x $
throwNormalizeError
"`raw` takes strings representing the code to inject directly."
in ERawExpression (Just expr) . T.pack <$> normalizedRawSource
_ ->
throwNormalizeError
"`raw` takes exactly one argument: a string literal."
"record" ->
let normalizedBindings =
traverse
(\x ->
normalizeExpression x >>= \case
EFunctionApplication (FunctionApplication _ (EIdentifier _ field) [val]) ->
pure (field, val)
_ ->
pushCtxt x $
throwNormalizeError
"Each `record` field setter must be an s-expression containing: 1) a field name and 2) a value!")
args
in ERecordDefinition <$>
(RecordDefinition (Just expr) <$> normalizedBindings)
"recordType" ->
let normalizedFields =
traverse
(\x ->
normalizeExpression x >>= \case
EFunctionApplication (FunctionApplication _ (EIdentifier _ field) [ty]) ->
pure (field, ty)
_ ->
pushCtxt x $
throwNormalizeError
"Each `recordType` field descriptor must be an s-expression containing: 1) a field name and 2) a type!")
args
in ERecordType <$> (RecordType (Just expr) <$> normalizedFields)
_ ->
EFunctionApplication <$>
(FunctionApplication (Just expr) <$>
normalizeExpression (Parse.Symbol sourceMetadata special) <*>
traverse normalizeExpression args)
fn:args ->
EFunctionApplication <$>
(FunctionApplication (Just expr) <$> normalizeExpression fn <*>
traverse normalizeExpression args)
[] -> pure $ EEmptySExpression (Just expr)
normalizeExpression expr@(Parse.Symbol _ symbol) =
pure $ EIdentifier (Just expr) (T.pack symbol)
normalizeFunctionDefinition ::
('[ Eff.Error Error, Eff.Reader FilePath, Eff.Reader ExprCtxt] :>> effs)
=> SM.Expression
-> Identifier
-> [SM.Expression]
-> SM.Expression
-> [SM.Expression]
-> Eff.Eff effs (FunctionDefinition (Maybe SM.Expression))
normalizeFunctionDefinition expr fnName arguments body whereDefs =
FunctionDefinition (Just expr) fnName <$>
traverse normalizeExpression arguments <*>
normalizeExpression body <*>
traverse
(\x ->
normalizeStatement x >>= \case
stmt@(SFunctionDefinition _) -> pure stmt
stmt@(STypeSignature _) -> pure stmt
_ ->
pushCtxt x $
throwNormalizeError
"Each `where` binding must be either a function definition or a type signature!")
whereDefs
normalizeStatement ::
('[ Eff.Error Error, Eff.Reader FilePath, Eff.Reader ExprCtxt] :>> effs)
=> SM.Expression
-> Eff.Eff effs SMStatement
normalizeStatement expr@(Parse.SExpression _ items) =
pushCtxt expr $
case items of
Parse.Symbol _ special:args ->
case unhygenisizeIdentifier special of
"::" ->
case args of
[Parse.Symbol _ fnName, Parse.SExpression _ (Parse.Symbol _ "list":constraints), typeDef] ->
STypeSignature <$>
(TypeSignature (Just expr) (T.pack fnName) <$>
traverse normalizeExpression constraints <*>
normalizeExpression typeDef)
_ ->
throwNormalizeError
"`::` takes three arguments: 1) a function name, 2) a list of constraints, and 3) a type."
"=" ->
case args of
fnSpecifier:body:whereDefs -> do
(fnName, arguments) <-
case fnSpecifier of
Parse.Symbol _ fnName -> pure (fnName, [])
Parse.SExpression _ (Parse.Symbol _ fnName:args') ->
pure (fnName, args')
_ ->
throwNormalizeError
"A function head specifier takes 1) a function name, followed by argument patterns."
SFunctionDefinition <$>
normalizeFunctionDefinition
expr
(T.pack fnName)
arguments
body
whereDefs
_ ->
throwNormalizeError
"`=` takes 1) a function head specifier 2) and an expression, followed by `where` bindings."
"begin" ->
let normalizedStmts = traverse normalizeStatement args
in STopLevel . TopLevel (Just expr) <$> normalizedStmts
"class" ->
case args of
Parse.SExpression _ (Parse.Symbol _ "list":classConstraints):className:sigs ->
let normalizedSigs =
traverse
(\x ->
normalizeStatement x >>= \case
STypeSignature tySig -> pure tySig
_ ->
pushCtxt x $
throwNormalizeError
"`class`'s body must contain type signatures!")
sigs
in STypeclassDefinition <$>
(TypeclassDefinition (Just expr) <$>
normalizeExpression className <*>
traverse normalizeExpression classConstraints <*>
normalizedSigs)
_ ->
throwNormalizeError
"`class` takes a constraint list, a type constructor, and type signatures!"
"data" ->
case args of
typeDef:rest ->
let (constructors, derivedConstraints) =
case last rest of
Parse.SExpression _ (Parse.Symbol _ "list":xs) ->
(init rest, xs)
_ -> (rest, [])
in SDataDeclaration <$>
(DataDeclaration (Just expr) <$>
(normalizeExpression typeDef >>= \case
EFunctionApplication typeConstructor ->
pure $ TypeConstructor (Just expr) typeConstructor
EIdentifier _ properType ->
pure $ ProperType (Just expr) properType
_ ->
pushCtxt typeDef $
throwNormalizeError "Invalid type constructor!") <*>
traverse normalizeExpression constructors <*>
traverse normalizeExpression derivedConstraints)
_ ->
throwNormalizeError
"`data` takes a type constructor followed by type constructors, optionally followed by a list of constraints to derive!"
"newtype" ->
case args of
typeDef:wrappedType:rest ->
let derivedConstraints =
case rest of
[] -> pure []
[Parse.SExpression _ (Parse.Symbol _ "list":xs)] ->
traverse normalizeExpression xs
_ ->
throwNormalizeError
"Invalid list of constraints to derive!"
in SNewtypeDeclaration <$>
(NewtypeDeclaration (Just expr) <$>
(normalizeExpression typeDef >>= \case
EFunctionApplication typeConstructor ->
pure $ TypeConstructor (Just expr) typeConstructor
EIdentifier _ properType ->
pure $ ProperType (Just expr) properType
_ ->
pushCtxt typeDef $
throwNormalizeError "Invalid type constructor!") <*>
normalizeExpression wrappedType <*>
derivedConstraints)
_ ->
throwNormalizeError
"`newtype` takes two or three arguments: 1) a type constructor, 2) a type, and optionally 3) a list of constraints to derive!"
"import" ->
case args of
[Parse.Symbol _ moduleName, importSpec] ->
SRestrictedImport <$>
(RestrictedImport (Just expr) (T.pack moduleName) <$>
normalizeImportSpec importSpec)
_ ->
throwNormalizeError
"`import` takes exactly two arguments: 1) a module name and 2) an import specification."
"importm" ->
case args of
[Parse.Symbol _ moduleName, macroImportSpec] ->
SMacroImport . MacroImport (Just expr) (T.pack moduleName) <$>
normalizeMacroImportSpec macroImportSpec
_ ->
throwNormalizeError
"`importm` takes exactly two arguments: 1) a module name and 2) a list of macro names."
"importq" ->
case args of
[Parse.Symbol _ moduleName, Parse.Symbol _ alias, importSpec] ->
SQualifiedImport <$>
(QualifiedImport (Just expr) (T.pack moduleName) (T.pack alias) <$>
normalizeImportSpec importSpec)
_ ->
throwNormalizeError
"`importq` takes exactly three arguments: 1) a module name, 2) a module name, and 3) an import specification."
"instance" ->
case args of
Parse.SExpression _ (Parse.Symbol _ "list":instanceConstraints):instanceName:defs ->
let normalizedDefs =
traverse
(\x ->
normalizeStatement x >>= \case
SFunctionDefinition fnDef -> pure fnDef
_ ->
pushCtxt x $
throwNormalizeError
"`instance`'s body must only contain function definitions!")
defs
in STypeclassInstance <$>
(TypeclassInstance (Just expr) <$>
normalizeExpression instanceName <*>
traverse normalizeExpression instanceConstraints <*>
normalizedDefs)
_ ->
throwNormalizeError
"`instance` takes a constraint list, an expression, and a list of function definitions!"
"pragma" ->
case args of
[Parse.LiteralString _ pragma] ->
pure $ SPragma (Pragma (Just expr) (T.pack pragma))
_ ->
throwNormalizeError
"`pragma` takes exactly one argument: a string literal."
"=macro" ->
case args of
Parse.Symbol _ macroName:argument:body:whereBindings ->
SMacroDefinition . MacroDefinition (Just expr) <$>
normalizeFunctionDefinition
expr
(T.pack macroName)
[argument]
body
whereBindings
_ ->
throwNormalizeError
"`=macro` takes 1) an identifier, 2) an argument list, 3) an expression, and 4) a list of statements."
"module" ->
case args of
[Parse.Symbol _ moduleName] ->
pure $ SModuleDeclaration (Just expr) (T.pack moduleName)
_ ->
throwNormalizeError
"`module` takes exactly one argument: a module name."
"raw" ->
case args of
[rawSource] ->
let normalizedRawSource =
case rawSource of
Parse.LiteralString _ x -> pure $ T.pack x
x ->
pushCtxt x $
throwNormalizeError
"`raw` takes strings representing the code to inject directly."
in SRawStatement (Just expr) <$> normalizedRawSource
_ ->
throwNormalizeError
"`raw` takes exactly one argument: a string literal."
"type" ->
case args of
[alias, def] ->
let normalizedAlias = normalizeExpression alias
normalizedDef = normalizeExpression def
in STypeSynonym <$>
(TypeSynonym (Just expr) <$> normalizedAlias <*> normalizedDef)
_ ->
throwNormalizeError
"`type` takes exactly two arguments: 1) a type constructor and 2) a type."
_ -> throwNormalizeError "Invalid statement!"
_ -> throwNormalizeError "Invalid statement!"
where
normalizeMacroImportSpec importSpec =
case importSpec of
Parse.SExpression _ (Parse.Symbol _ "list":macroImportList) ->
traverse
(\case
Parse.Symbol _ import' -> pure $ T.pack import'
x ->
pushCtxt x $
throwNormalizeError "A macro import must be an identifier!")
macroImportList
x ->
pushCtxt x $
throwNormalizeError
"`importm`'s import specification must be a list of identifiers (`all` is not valid for macro imports)!"
normalizeImportSpec importSpec =
case importSpec of
Parse.Symbol _ "all" -> pure $ ImportAll (Just expr)
Parse.SExpression _ (Parse.Symbol _ "list":importList) ->
normalizeImportList importList
x ->
pushCtxt x $
throwNormalizeError
"An import specification must be either `all` or a list of import items!"
normalizeImportList input =
ImportOnly (Just expr) <$>
traverse
(\item ->
pushCtxt item $
case item of
Parse.Symbol _ import' ->
pure $ ImportItem (Just expr) (T.pack import')
Parse.SExpression _ (Parse.Symbol _ type':imports) ->
let normalizedImports =
traverse
(\case
Parse.Symbol _ import' -> pure $ T.pack import'
x ->
pushCtxt x $
throwNormalizeError
"An import subitem must be an identifier!")
imports
in ImportType (Just expr) (T.pack type') <$> normalizedImports
_ ->
throwNormalizeError
"An import item must be either an identifier or an s-expression of identifiers!")
input
normalizeStatement expr =
pushCtxt expr $ throwNormalizeError "Invalid top-level form!"
unsafeNormalize ::
(SM.Expression -> Eff.Eff '[ Eff.Reader ExprCtxt, Eff.Reader FilePath, Eff.Error Error] a)
-> SM.Expression
-> a
unsafeNormalize f =
Eff.runPureEff .
unsafeRunError renderError . Eff.runReader (FilePath "") . withExprCtxt . f