Skip to content

Commit defbcea

Browse files
committed
Introduce structured IR of MtExpr
The new IR (Intermediate Representation) introduces a Tree-like structure for the program AST in favour of the unstructured list. This avoids some additional work in the transpiler, as the transpiler no longer needs to figure out what the program AST actually means, at the cost of some additional complexity in the renamer. However, since the complexity has been moved from one point to the other, we end with a nice separation, keeping the transpiler simple and focusing on the individual transpiler, and tucking complexity away into the Renamer phase. We also improve the Simala backend slightly by utilising the new program AST.
1 parent 9120f8f commit defbcea

14 files changed

+544
-688
lines changed

lib/haskell/natural4/src/LS/Renamer.hs

+148-65
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,6 @@ scanTypeDeclName tracer mtexprs = do
409409
-- * A GIVETH can be referred to in other rules up the scope hierarchy
410410
-- * The head in DECIDE clauses can also be referred to by other rules in scope hierarchy
411411
-- * WHERE clauses are local to the rule
412-
--
413412
renameRules :: (Traversable f) => Tracer Log -> f Rule -> Renamer (f RnRule)
414413
renameRules tracer rules = do
415414
rulesWithLocalDefs <-
@@ -443,7 +442,7 @@ renameRule tracer [email protected]{} = do
443442
defaults <- assertEmptyList rule.defaults
444443
symtab <- assertEmptyList rule.symtab
445444
clauses <- traverse (renameHornClause tracer) rule.clauses
446-
name <- renameMultiTerm tracer rule.name
445+
name <- renameMultiTerm tracer RootExpression rule.name
447446
pure $
448447
Hornlike
449448
RnHornlike
@@ -504,7 +503,7 @@ renameTypeDeclName :: LS.RuleName -> Renamer RnRuleName
504503
renameTypeDeclName mtexprs = do
505504
mt <- assertSingletonMultiTerm mtexprs
506505
rnTyName <- lookupExistingName (NE.singleton mt) RnType
507-
pure [RnExprName rnTyName]
506+
pure $ RnExprName rnTyName
508507

509508
renameUpons ::
510509
Maybe LS.ParamText ->
@@ -574,36 +573,69 @@ renameGivenInlineEnumParamText params = do
574573
rnParams <- traverse renameEach params
575574
pure $ RnParamText rnParams
576575

576+
-- | Track what "Level" an expression has.
577+
-- This level merely tracks how deep we are in the program AST.
578+
-- If we are at the top-level, called 'RootExpression', we sometimes have to handle
579+
-- certain things differently. For example, if we encounter a '[LS.MultiTerm]' such as:
580+
--
581+
-- @
582+
-- [MTT "f", MTT "a", MTT "b"]
583+
-- @
584+
--
585+
-- Is this a function declaration (e.g., the @f a b@ part of a the haskell expression @f a b = a + b@)
586+
-- or is this a function application, where @f@ is applied to the variables @a@ and @b@?
587+
-- Without further context, impossible to tell, but we do want to be able to tell these two cases
588+
-- apart to simplify transpiler backends.
589+
-- Thus, we track whether we are at the root of the program AST.
590+
data ExprLevel
591+
= -- | We are at the root of an expression tree.
592+
RootExpression
593+
| -- | We are in some sub-expression of an expression tree.
594+
SubExpression
595+
deriving stock (Eq, Show, Ord, Enum, Bounded)
596+
597+
-- | Downgrade any 'ExprLevel' to a 'SubExpression'.
598+
-- Strictly, speaking, this doesn't need a function, but it reduces a few occurrences
599+
-- of random constants.
600+
subExpression :: ExprLevel -> ExprLevel
601+
subExpression _ = SubExpression
602+
577603
renameHornClause :: Tracer Log -> LS.HornClause2 -> Renamer RnHornClause
578604
renameHornClause tracer hc = do
579-
rnHead <- renameRelationalPredicate tracer hc.hHead
605+
rnHead <- renameRelationalPredicate tracer RootExpression hc.hHead
580606
rnBody <- traverse (renameBoolStruct tracer) hc.hBody
581607
pure $
582608
RnHornClause
583609
{ rnHcHead = rnHead
584610
, rnHcBody = rnBody
585611
}
586612

587-
renameRelationalPredicate :: Tracer Log -> LS.RelationalPredicate -> Renamer RnRelationalPredicate
588-
renameRelationalPredicate tracer = \case
613+
renameRelationalPredicate :: Tracer Log -> ExprLevel -> LS.RelationalPredicate -> Renamer RnRelationalPredicate
614+
renameRelationalPredicate tracer exprLvl = \case
589615
LS.RPParamText pText ->
590616
throwError $ UnsupportedRPParamText pText
591-
LS.RPMT mt -> RnRelationalTerm <$> renameMultiTerm tracer mt
617+
LS.RPMT mt -> RnRelationalTerm <$> renameMultiTerm tracer exprLvl mt
592618
LS.RPConstraint lhs relationalPredicate rhs -> do
593-
rnLhs <- renameMultiTerm tracer lhs
594-
rnRhs <- renameMultiTerm tracer rhs
619+
rnLhs <- renameMultiTerm tracer exprLvl lhs
620+
rnRhs <- renameMultiTerm tracer (subExpression exprLvl) rhs
595621
pure $ RnConstraint rnLhs relationalPredicate rnRhs
596622
LS.RPBoolStructR lhs relationalPredicate rhs -> do
597-
rnLhs <- renameMultiTerm tracer lhs
623+
rnLhs <- renameMultiTerm tracer exprLvl lhs
598624
rnRhs <- renameBoolStruct tracer rhs
599625
pure $ RnBoolStructR rnLhs relationalPredicate rnRhs
600-
LS.RPnary relationalPredicate rhs -> do
601-
rnRhs <- traverse (renameRelationalPredicate tracer) rhs
602-
pure $ RnNary relationalPredicate rnRhs
626+
LS.RPnary relationalPredicate [] -> pure $ RnNary relationalPredicate []
627+
LS.RPnary relationalPredicate (lhs : rhs) -> do
628+
-- We have to handle the first element explicitly and differently.
629+
-- See 'scanDecideHeadClause', which explains why.
630+
rnLhs <- renameRelationalPredicate tracer exprLvl lhs
631+
rnRhs <- traverse (renameRelationalPredicate tracer $ subExpression exprLvl) rhs
632+
pure $ RnNary relationalPredicate (rnLhs : rnRhs)
603633

604634
renameBoolStruct :: Tracer Log -> LS.BoolStructR -> Renamer RnBoolStructR
605635
renameBoolStruct tracer = \case
606-
AA.Leaf p -> AA.Leaf <$> renameRelationalPredicate tracer p
636+
-- No expression in a 'BoolStructR' can be a 'RootExpression' expression.
637+
-- Thus, we hardcode 'SubExpression' here.
638+
AA.Leaf p -> AA.Leaf <$> renameRelationalPredicate tracer SubExpression p
607639
AA.All lbl cs -> do
608640
rnBoolStruct <- traverse (renameBoolStruct tracer) cs
609641
pure $ AA.All lbl rnBoolStruct
@@ -634,8 +666,17 @@ renameBoolStruct tracer = \case
634666
-- For example, @[MTT "x", MTT "f"]@ will be changed @[MTT "f", MTT "x"]@,
635667
-- if and only if @"f"@ is a known function variable in scope with associated
636668
-- arity information.
637-
renameMultiTerm :: Tracer Log -> LS.MultiTerm -> Renamer RnMultiTerm
638-
renameMultiTerm tracer multiTerms = do
669+
--
670+
-- At last, we perform an additional translation which turns the list representation
671+
-- of 'LS.MultiTerm' into a proper AST with dedicated constructors for function
672+
-- application, record projection and variables.
673+
-- We use the 'ExprLevel' to resolve an 'LS.MultiTerm' to either a function
674+
-- declaration or a function application. If the Renamer is currently renaming
675+
-- a root expression, for example the head of a `DECIDE` clause, then we may be
676+
-- introducing a function declaration.
677+
-- Otherwise, we know there can only be function applications.
678+
renameMultiTerm :: Tracer Log -> ExprLevel -> LS.MultiTerm -> Renamer RnExpr
679+
renameMultiTerm tracer exprLvl multiTerms = do
639680
(reversedRnMultiTerms, ctx) <-
640681
foldM
641682
( \(results, state) mt -> do
@@ -646,57 +687,94 @@ renameMultiTerm tracer multiTerms = do
646687
multiTerms
647688
let
648689
rnMultiTerms = reverse reversedRnMultiTerms
649-
fixFixity ctx rnMultiTerms
690+
multiTermsFixed <- fixFixity ctx rnMultiTerms
691+
692+
case multiTermsFixed of
693+
[expr]
694+
| Just nameOrLitOrBuiltin <- isNameOrLitOrBuiltin expr -> pure nameOrLitOrBuiltin
695+
(varName : attrs)
696+
| Just (name, sels) <- isProjection varName attrs -> pure $ RnProjection name sels
697+
(varName : args)
698+
| Just (name, argExprs) <- isFunctionApp varName args
699+
, mustBeFunctionApplication ->
700+
pure $ RnFunApp name argExprs
701+
(varName : args)
702+
| Just (name, argExprs) <- isFunctionDecl varName args
703+
, mustBeFunctionDeclaration ->
704+
pure $ RnFunDecl name argExprs
705+
exprs -> throwError $ UnknownTermStructure exprs
650706
where
651-
-- Fixing the arity of a function requires us rewrite infix and postfix
652-
-- notation to a prefix notation.
653-
--
654-
-- To rewrite a function application, we first gather the 'FuncInfo' to
655-
-- find the declared arity of the function. Say the arity of the function @f@ is
656-
-- given by the tuple @(p, q)@ where @p@ is the number of arguments before the
657-
-- function name and @q@ is the number of arguments after the function name.
658-
-- This captures functions applied in prefix, infix and postfix notation.
659-
-- Then, we find the index of the function name as it occurs in the 'LS.MultiTerm'
660-
-- and take @p@ elements from the back of the list of @[LS.MTExpr]@ that occur before
661-
-- the function, which we name @ps@, and take @q@ elements from the list of
662-
-- @[LS.MTExpr]@ that occur after the function name, called @qs@.
663-
--
664-
-- Finally, we replace the function application by @[f] ++ ps ++ qs@.
665-
fixFixity ctx rnMultiTerms = case ctx.multiTermContextFunctionCall of
666-
Nothing -> pure rnMultiTerms
667-
Just fnName -> do
668-
funcInfo <- lookupExistingFunction fnName
669-
let
670-
(preNum, postNum) = funcInfo ^. funcArity
671-
(lhs, fnExpr, rhs) <- findFunctionApplication fnName rnMultiTerms
672-
(leftNonArgs, leftArgs) <- processLhs fnName preNum lhs
673-
(rightNonArgs, rightArgs) <- processRhs fnName postNum rhs
674-
pure $ reverse leftNonArgs <> [fnExpr] <> leftArgs <> rightArgs <> rightNonArgs
707+
mustBeFunctionApplication = exprLvl == SubExpression
708+
mustBeFunctionDeclaration = not mustBeFunctionApplication
709+
710+
isProjection var sels = do
711+
varName <- isVariableName var
712+
selNames <- traverse isSelectorName sels
713+
Just (varName, selNames)
714+
715+
isFunctionApp var args = do
716+
varName <- isFunctionName var
717+
Just (varName, args)
675718

676-
findFunctionApplication fnName rnMultiTerms = do
719+
isFunctionDecl var args = do
720+
varName <- isFunctionName var
721+
argNames <- traverse isVariableName args
722+
Just (varName, argNames)
723+
724+
-- | Fixing the arity of a function requires us rewrite infix and postfix
725+
-- notation to a prefix notation.
726+
--
727+
-- To rewrite a function application, we first gather the 'FuncInfo' to
728+
-- find the declared arity of the function. Say the arity of the function @f@ is
729+
-- given by the tuple @(p, q)@ where @p@ is the number of arguments before the
730+
-- function name and @q@ is the number of arguments after the function name.
731+
-- This captures functions applied in prefix, infix and postfix notation.
732+
-- Then, we find the index of the function name as it occurs in the 'LS.MultiTerm'
733+
-- and take @p@ elements from the back of the list of @[LS.MTExpr]@ that occur before
734+
-- the function, which we name @ps@, and take @q@ elements from the list of
735+
-- @[LS.MTExpr]@ that occur after the function name, called @qs@.
736+
--
737+
-- Finally, we replace the function application by @[f] ++ ps ++ qs@.
738+
fixFixity :: MultiTermContext -> [RnExpr] -> Renamer [RnExpr]
739+
fixFixity ctx rnMultiTerms = case ctx.multiTermContextFunctionCall of
740+
Nothing -> pure rnMultiTerms
741+
Just fnName -> do
742+
funcInfo <- lookupExistingFunction fnName
677743
let
678-
(preArgs, postArgsWithName) = List.break (== (RnExprName fnName)) rnMultiTerms
679-
case postArgsWithName of
680-
[] -> throwError $ FixArityFunctionNotFound fnName rnMultiTerms
681-
(fnExpr : postArgs) -> pure (preArgs, fnExpr, postArgs)
682-
683-
processLhs name n lhs = do
684-
case safeSplitAt n (reverse lhs) of
685-
Nothing ->
686-
throwError $ ArityErrorLeft n name lhs
687-
Just (args, nonArgs) -> pure (reverse nonArgs, reverse args)
688-
689-
processRhs name n rhs = do
690-
case safeSplitAt n rhs of
691-
Nothing ->
692-
throwError $ ArityErrorRight n name rhs
693-
Just (nonArgs, args) -> pure (nonArgs, args)
694-
695-
initialMultiTermContext =
696-
MultiTermContext
697-
{ multiTermContextInSelector = False
698-
, multiTermContextFunctionCall = Nothing
699-
}
744+
(preNum, postNum) = funcInfo ^. funcArity
745+
(lhs, fnExpr, rhs) <- findFunctionApplication fnName rnMultiTerms
746+
(leftNonArgs, leftArgs) <- processLhs fnName preNum lhs
747+
(rightNonArgs, rightArgs) <- processRhs fnName postNum rhs
748+
pure $ reverse leftNonArgs <> [fnExpr] <> leftArgs <> rightArgs <> rightNonArgs
749+
750+
findFunctionApplication :: RnName -> [RnExpr] -> Renamer ([RnExpr], RnExpr, [RnExpr])
751+
findFunctionApplication fnName rnMultiTerms = do
752+
let
753+
(preArgs, postArgsWithName) = List.break (== (RnExprName fnName)) rnMultiTerms
754+
case postArgsWithName of
755+
[] -> throwError $ FixArityFunctionNotFound fnName rnMultiTerms
756+
(fnExpr : postArgs) -> pure (preArgs, fnExpr, postArgs)
757+
758+
processLhs :: RnName -> Int -> [RnExpr] -> Renamer ([RnExpr], [RnExpr])
759+
processLhs name n lhs = do
760+
case safeSplitAt n (reverse lhs) of
761+
Nothing ->
762+
throwError $ ArityErrorLeft n name lhs
763+
Just (args, nonArgs) -> pure (reverse nonArgs, reverse args)
764+
765+
processRhs :: RnName -> Int -> [RnExpr] -> Renamer ([RnExpr], [RnExpr])
766+
processRhs name n rhs = do
767+
case safeSplitAt n rhs of
768+
Nothing ->
769+
throwError $ ArityErrorRight n name rhs
770+
Just (nonArgs, args) -> pure (nonArgs, args)
771+
772+
initialMultiTermContext :: MultiTermContext
773+
initialMultiTermContext =
774+
MultiTermContext
775+
{ multiTermContextInSelector = False
776+
, multiTermContextFunctionCall = Nothing
777+
}
700778

701779
-- | Rename a single 'LS.MTExpr' to a 'RnExpr'.
702780
renameMultiTermExpression :: Tracer Log -> MultiTermContext -> LS.MTExpr -> Renamer (RnExpr, MultiTermContext)
@@ -754,6 +832,7 @@ renameMultiTermExpression tracer ctx = \case
754832
where
755833
-- There is no doubt this is a text literal, if it is enclosed in quotes.
756834
-- Strips away the quotes.
835+
-- TODO: this is lossy, we can never rebuild the exact AST with this.
757836
isTextLiteral t = do
758837
('"', t') <- uncons t
759838
(t'', '"') <- unsnoc t'
@@ -775,6 +854,7 @@ data RenamerError
775854
| UnexpectedRnNameNotFound RnName
776855
| InsertNameUnexpectedType RnNameType RnNameType
777856
| LookupOrInsertNameUnexpectedType RnNameType RnNameType
857+
| UnknownTermStructure [RnExpr]
778858
| AssertErr AssertionError
779859
deriving (Show, Eq, Ord)
780860

@@ -836,6 +916,8 @@ renderRenamerError = \case
836916
<> Text.pack (show actual)
837917
<> " but expected: "
838918
<> Text.pack (show expected)
919+
UnknownTermStructure terms ->
920+
"Renamed the terms " <> Text.pack (show terms) <> " but failed to determine the program structure."
839921
AssertErr err -> renderAssertionError err
840922

841923
renderAssertionError :: AssertionError -> Text.Text
@@ -976,7 +1058,8 @@ recordScopeTable act = do
9761058
prevUnique <- use scUniqueSupply
9771059
a <- act
9781060
scTable <- use scScopeTable
979-
let scTableWithNewNames = filterScopeTable (\_ name -> name.rnUniqueId >= prevUnique) scTable
1061+
let
1062+
scTableWithNewNames = filterScopeTable (\_ name -> name.rnUniqueId >= prevUnique) scTable
9801063
pure (a, scTableWithNewNames)
9811064

9821065
recordScopeTable_ :: Renamer a -> Renamer ScopeTable

0 commit comments

Comments
 (0)