Skip to content
This repository has been archived by the owner on Jun 18, 2023. It is now read-only.

Commit

Permalink
feat: var tracking while generating layouts and the LayoutM monad
Browse files Browse the repository at this point in the history
  • Loading branch information
prescientmoon committed Sep 25, 2020
1 parent 0a540f2 commit 90b36d0
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 251 deletions.
1 change: 1 addition & 0 deletions spago.dhall
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{ name = "lunarflow"
, dependencies =
[ "console"
, "debug"
, "effect"
, "free"
, "generics-rep"
Expand Down
182 changes: 38 additions & 144 deletions src/Ast.purs
Original file line number Diff line number Diff line change
@@ -1,195 +1,89 @@
module Lunarflow.Ast
( AstChunk(..)
, Ast(..)
( Ast(..)
, RawExpression
, Expression
, IndexedAst
, GroupedExpression
, WithId
, WithIndex
, IndexedLambdaData
, GroupedLambdaData
, IndexedVarData
, AstTerm
, groupExpression
, indexExpression
, mkAst
, withDebrujinIndices
, printDeBrujin
, _term
) where

import Prelude
import Control.Monad.Reader (ReaderT, Reader, asks, local, runReader, runReaderT)
import Control.Monad.State (State, evalState, modify)
import Control.Monad.Reader (Reader, asks, local, runReader)
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show (genericShow)
import Data.Lens (Lens')
import Data.Lens.Iso.Newtype (_Newtype)
import Data.Lens.Record (prop)
import Data.List as List
import Data.Maybe (Maybe(..), maybe)
import Data.Newtype (class Newtype)
import Data.Symbol (SProxy(..))
import Prim.Row as Row
import Record as Record
import Data.Maybe (Maybe(..))

-- | The meat and potatoes of representing an expression.
-- |
-- | - a represents the type we use as children.
-- | - l represents the type lambdas carry around.
-- | - c represents the type calls carry around.
-- | - v represents the type variables carry around.
-- |
-- | Thre reason we take an argument for a is so we don't have to
-- | take an extra type argumet and write `Ast v c l r` on each occurence.
data AstChunk v c l a
= Call c a a
| Lambda l a
data Ast v c l
= Call c (Ast v c l) (Ast v c l)
| Lambda l (Ast v c l)
| Var v

derive instance genericAstChunk :: Generic (AstChunk v c l a) _
derive instance genericAst :: Generic (Ast v c l) _

instance showAstChunk :: (Show c, Show l, Show a, Show v) => Show (AstChunk v c l a) where
show = genericShow

-- | AstChunk with asts as children
type AstTerm v c l r
= AstChunk v c l (Ast v c l r)

-- | Generic Ast type
-- |
-- | This is extensible so we don't have to create a different dsl
-- | each time we want to augument a tree with new information.
newtype Ast v c l r
= Ast
{ term :: AstTerm v c l r
| r
}

derive instance genericAst :: Generic (Ast v c l r) _

instance showAst :: (Show l, Show v, Show c) => Show (Ast v c l r) where
show (Ast { term }) = genericShow term

derive instance newtypeAst :: Newtype (Ast v c l r) _

-- | SProxy for the term prop of asts.
termProxy :: SProxy "term"
termProxy = SProxy

_term :: forall c l r v. Lens' (Ast v c l r) (AstTerm v c l r)
_term = _Newtype <<< prop termProxy

-- | Helper for packing an ast
mkAst :: forall v r l c. Row.Lacks "term" r => AstTerm v c l r -> Record r -> Ast v c l r
mkAst inner = Ast <<< Record.insert termProxy inner
instance showAst :: (Show c, Show l, Show v) => Show (Ast v c l) where
show a = genericShow a

-- | Basic lambda calculus expressions
type RawExpression
= Ast String Unit String ()

-- | We use this type so we keep access to the name of the variables for documentation purpouses.
type IndexedVarData r
= { | WithIndex ( name :: String | r ) }
= Ast String Unit String

-- | Lambda calculus expression using de brujin indices.
type Expression
= Ast (IndexedVarData ()) Unit String ()

-- | Basic extensible record for stuff which has an unique id represented as an int.
type WithId r
= ( id :: Int | r )
= Ast Int Unit String

type WithIndex r
= ( index :: Int | r )

-- | Data carried around by lambdas.
-- | The argumentId is an unique code variables
-- | can use to reference that argument (basically removeing the need for shadowing).
-- | Thre reason we do this instead of changing names to indices is for ease of generating the layouts.
-- I made this into a separate type because I use it in the GroupedExpression type as well
type IndexedLambdaData r
= { argumentName :: String, argumentId :: Int | r }

-- | Indexed lambda calculus expressions
type IndexedAst
= Ast (IndexedVarData ()) Unit (IndexedLambdaData ())
(WithId ())

-- | Add de brujin indices to a lambda calculus expression.
withDebrujinIndices :: RawExpression -> Expression
withDebrujinIndices = flip runReader List.Nil <<< go
where
go :: RawExpression -> Reader (List.List String) Expression
go (Ast { term }) =
flip mkAst {}
<$> case term of
Call _ func argument -> Call unit <$> go func <*> go argument
Lambda name body -> Lambda name <$> (local (List.Cons name) $ go body)
Var name -> do
maybeIndex <- asks $ List.findIndex (eq name)
pure case maybeIndex of
Just index -> Var { index, name }
Nothing -> Var { name, index: -1 }

-- | Add incidces for all nodes in an ast
indexExpression :: Expression -> IndexedAst
indexExpression = flip evalState 0 <<< flip runReaderT List.Nil <<< go
where
-- | Internal version of fromExpression which runs in an actual monad
go :: Expression -> ReaderT (List.List Int) (State Int) IndexedAst
go (Ast { term }) = do
indexed <- case term of
Call _ func argument -> Call unit <$> go func <*> go argument
Lambda name body -> do
argumentId <- getId
Lambda { argumentName: name, argumentId } <$> local (List.Cons argumentId) (go body)
Var data' -> pure $ Var data'
id <- case term of
Var { index } -> asks (flip List.index index) >>= maybe getId pure
_ -> getId
pure
$ Ast
{ id
, term: indexed
}

-- | Helper for getting a new unique id
getId = modify ((+) 1)
go = case _ of
Call _ func argument -> Call unit <$> go func <*> go argument
Lambda name body -> Lambda name <$> (local (List.Cons name) $ go body)
Var name -> do
maybeIndex <- asks $ List.findIndex (eq name)
pure case maybeIndex of
Just index -> Var index
Nothing -> Var (-1)

-- | Data for visually sugared grouped lambdas
type GroupedLambdaData r
= ( arguments :: List.List (IndexedLambdaData r) )
= ( arguments :: List.List { name :: String | r } )

-- | Ast which doesn't allow consecutive lambdas.
type GroupedExpression
= Ast (IndexedVarData ()) Unit { | GroupedLambdaData () } (WithId ())
= Ast Int Unit { | GroupedLambdaData () }

-- | Group multiple consecutive lambdas into 1 as visual sugar.
-- | This is the textual equivalent of suagring \f. \a. \b. f b a into \f a b. f b a
groupExpression :: IndexedAst -> GroupedExpression
groupExpression ast@(Ast astData) = Ast $ astData { term = term }
where
term = case astData.term of
Lambda lambdaData body -> case nestedTerm of
Lambda { arguments } nestedBody -> Lambda { arguments: List.Cons lambdaData arguments } nestedBody
_ -> Lambda { arguments: pure lambdaData } nestedAst
where
nestedAst@(Ast { term: nestedTerm }) = groupExpression body
Call _ func arg -> Call unit (groupExpression func) (groupExpression arg)
Var a -> Var a
groupExpression :: Expression -> GroupedExpression
groupExpression = case _ of
Lambda lambdaData body -> case nested of
Lambda { arguments } nestedBody -> Lambda { arguments: List.Cons { name: lambdaData } arguments } nested
_ -> Lambda { arguments: pure { name: lambdaData } } nested
where
nested = groupExpression body
Call _ func arg -> Call unit (groupExpression func) (groupExpression arg)
Var a -> Var a

-- Pretty printing stuff:
-- | Check if an ast chunk needs to be wrapped in parenthesis for printing
needsParenthesis :: forall v c l r. Row.Lacks "term" r => Boolean -> Ast v c l r -> Boolean
needsParenthesis left (Ast { term }) = go term
where
go (Lambda _ _) = left

go (Call _ _ _) = not left

go _ = false
needsParenthesis :: forall v c l. Boolean -> Ast v c l -> Boolean
needsParenthesis left = case _ of
(Lambda _ _) -> left
(Call _ _ _) -> not left
_ -> false

-- | Add parenthesis around a string
withParenthesis :: String -> String
Expand All @@ -206,9 +100,9 @@ lambdaChar :: String
lambdaChar = "λ"

-- | Print an expression which uses de brujin indices.
printDeBrujin :: forall v c l r. Row.Lacks "term" r => Ast ({ | WithIndex v }) c l r -> String
printDeBrujin (Ast { term }) = case term of
Var { index } -> show index
printDeBrujin :: forall c l. Ast Int c l -> String
printDeBrujin = case _ of
Var index -> show index
Lambda _ body -> lambdaChar <> printDeBrujin body
Call _ func arg ->
parenthesiseWhen (needsParenthesis true func) func'
Expand Down
Loading

0 comments on commit 90b36d0

Please sign in to comment.