For Haskell, there are libraries, e.g. this one:haskell-tools-ast: Haskell AST for efficient tooling
Maybe I can use such a library with some conversions?
For Haskell, there are libraries, e.g. this one:haskell-tools-ast: Haskell AST for efficient tooling
Maybe I can use such a library with some conversions?
Interested in how you would plan to use this data if you could get it. I know that some tool vendors use this for SAST scanning.
I do Daml metaprogramming, meaning rewriting and generating code.
The ideal starting point for this is when all imports are qualified, because in this case we don’t have to rely on external data (implicit imports) to handle the code and don’t have to bother about different import syntaxes.
I wrote a script for converting all imports into qualified imports, but it’s not perfect. E.g. it cannot distinguish between an imported function name and a local variable, if the token is the same (see the “Qualified name in binding position” error message).
I can extract some information about the package using the DAML LF API Type Signature package, but that only contains information about the record and variant types, nothing about functions and classes.
So I want to refine my script and my guess is that the AST of the source code would help (by AST I mean not the parse tree, this is the source of my understanding about the difference between the AST and the parse tree: Abstract syntax tree - Wikipedia).
There isn’t any tooling for Daml that gives you an AST unfortunately.
Like @cocreature says, there is no tooling for this at the moment. But, to throw in my two cents:
There are a few different “ASTs” for Daml - there is the AST which represents loosely the Haskell to which Daml code desugars, and then there is the AST representing the underlying executable lambda calculus that we use (called Daml-LF). Depending on what you want, one of these might be better. There is no AST for Daml code proper, all Daml code is pretty much parsed into a Haskell AST directly.
I assume for the qualified imports use case, the Haskell AST would be closest to what you needed.
As a pointer if you wanted to implement something like this yourself, there’s a good deal of prior work in GitHub - digital-asset/daml: The Daml smart contract language and GitHub - digital-asset/daml-ghcide: daml fork of haskell/ghcide, ultimately relying on GitHub - digital-asset/ghc: Fork of GHC (https://gitlab.haskell.org/ghc/ghc.git).
To understand the most basic form of parsing, inside the daml
repo, in compiler/damlc/daml-desugar/src/DA/Daml/Desugar.hs
there is a function desugar
which takes source, parses it, and pretty prints the parsed result. Refer to execDesugar
and cmdDesugar
in compiler/damlc/lib/DA/Cli/Damlc.hs
to see how it is executed.
To understand later phases (typechecking, LF generation) a decent entry point would be execBuild
in compiler/damlc/lib/DA/Cli/Damlc.hs
and buildDar
in compiler/damlc/daml-compiler/src/DA/Daml/Compiler/Dar.hs
. That’s how daml build
does dar generation.
From this point on we use rules that we’ve defined in a build system called Shake. buildDar
uses our GeneratePackage
rule - this dispatches to GenerateDalf
, when then goes to GenerateRawDalf
, which finally calls generateCore
(the GenerateCore
rule) from the daml-ghcide
repo. Staying within the daml-ghcide
repo, generateCore
uses the Typecheck
rule (produces TypecheckedModule
), which uses the GetParsedModule
rule (produces ParsedModule
). The TypecheckedModule
contains a lot more information about names because it runs the renaming phase - depending on what you need, the TypecheckedModule
or ParsedModule
could be appropriate.
Keep in mind this is all internally-facing code - we don’t guarantee it will remain stable.
Thank you, Dylan, I will check these out. I’ve just seen the Deep Dive into the Daml Compiler video, these links will lead me into an even deeper dive into the topic.
Hey Dylan,
I tried using the desugar function in order to get the Haskell AST and the Haskell code.
However, when trying to translate a simple whitelistedRegistry contract which has less than 40 lines in Daml and only 3 choices (changeOwner, SetWhitelisted and isWhitelisted) I got the following Haskell code:
module WhitelistedRegistry where
import (implicit) qualified DA.Internal.Record
import (implicit) qualified GHC.Types
import (implicit) qualified DA.Internal.Desugar
import (implicit) DA.Internal.RebindableSyntax
data GHC.Types.DamlTemplate => WhitelistedRegistry
= WhitelistedRegistry {owner : Party, whitelisted : [Party]}
deriving (DA.Internal.Desugar.Eq, DA.Internal.Desugar.Show)
instance DA.Internal.Record.GetField “owner” WhitelistedRegistry Party where
getField
= DA.Internal.Record.getFieldPrim @"owner" @WhitelistedRegistry @Party
instance DA.Internal.Record.SetField “owner” WhitelistedRegistry Party where
setField
= DA.Internal.Record.setFieldPrim @"owner" @WhitelistedRegistry @Party
instance DA.Internal.Record.GetField “whitelisted” WhitelistedRegistry [Party] where
getField
= DA.Internal.Record.getFieldPrim @"whitelisted" @WhitelistedRegistry @\[Party\]
instance DA.Internal.Record.SetField “whitelisted” WhitelistedRegistry [Party] where
setField
= DA.Internal.Record.setFieldPrim @"whitelisted" @WhitelistedRegistry @\[Party\]
data ChangeOwner
= ChangeOwner {newOwner : Party}
deriving (DA.Internal.Desugar.Eq, DA.Internal.Desugar.Show)
instance DA.Internal.Record.GetField “newOwner” ChangeOwner Party where
getField
= DA.Internal.Record.getFieldPrim @"newOwner" @ChangeOwner @Party
instance DA.Internal.Record.SetField “newOwner” ChangeOwner Party where
setField
= DA.Internal.Record.setFieldPrim @"newOwner" @ChangeOwner @Party
data SetWhitelisted
= SetWhitelisted {addr : Party, isWhitelisted : Bool}
deriving (DA.Internal.Desugar.Eq, DA.Internal.Desugar.Show)
instance DA.Internal.Record.GetField “addr” SetWhitelisted Party where
getField
= DA.Internal.Record.getFieldPrim @"addr" @SetWhitelisted @Party
instance DA.Internal.Record.SetField “addr” SetWhitelisted Party where
setField
= DA.Internal.Record.setFieldPrim @"addr" @SetWhitelisted @Party
instance DA.Internal.Record.GetField “isWhitelisted” SetWhitelisted Bool where
getField
= DA.Internal.Record.getFieldPrim @"isWhitelisted" @SetWhitelisted @Bool
instance DA.Internal.Record.SetField “isWhitelisted” SetWhitelisted Bool where
setField
= DA.Internal.Record.setFieldPrim @"isWhitelisted" @SetWhitelisted @Bool
data IsWhitelisted
= IsWhitelisted {addr : Party, caller : Party}
deriving (DA.Internal.Desugar.Eq, DA.Internal.Desugar.Show)
instance DA.Internal.Record.GetField “addr” IsWhitelisted Party where
getField
= DA.Internal.Record.getFieldPrim @"addr" @IsWhitelisted @Party
instance DA.Internal.Record.SetField “addr” IsWhitelisted Party where
setField
= DA.Internal.Record.setFieldPrim @"addr" @IsWhitelisted @Party
instance DA.Internal.Record.GetField “caller” IsWhitelisted Party where
getField
= DA.Internal.Record.getFieldPrim @"caller" @IsWhitelisted @Party
instance DA.Internal.Record.SetField “caller” IsWhitelisted Party where
setField
= DA.Internal.Record.setFieldPrim @"caller" @IsWhitelisted @Party
instance DA.Internal.Desugar.HasSignatory WhitelistedRegistry where
signatory this@WhitelistedRegistry {..}
= DA.Internal.Desugar.toParties (owner) where \_ = this
instance DA.Internal.Desugar.HasObserver WhitelistedRegistry where
observer this@WhitelistedRegistry {..}
= DA.Internal.Desugar.toParties (whitelisted) where \_ = this
instance DA.Internal.Desugar.HasEnsure WhitelistedRegistry where
ensure this@WhitelistedRegistry {..}
= DA.Internal.Desugar.True where \_ = this
instance DA.Internal.Desugar.HasArchive WhitelistedRegistry where
archive cid
= DA.Internal.Desugar.exercise cid DA.Internal.Desugar.Archive where \_ = cid
instance DA.Internal.Desugar.HasCreate WhitelistedRegistry where
create = GHC.Types.primitive @“UCreate”
instance DA.Internal.Desugar.HasFetch WhitelistedRegistry where
fetch = GHC.Types.primitive @“UFetch”
instance DA.Internal.Desugar.HasToAnyTemplate WhitelistedRegistry where
_toAnyTemplate = GHC.Types.primitive @“EToAnyTemplate”
instance DA.Internal.Desugar.HasFromAnyTemplate WhitelistedRegistry where
_fromAnyTemplate = GHC.Types.primitive @“EFromAnyTemplate”
instance DA.Internal.Desugar.HasTemplateTypeRep WhitelistedRegistry where
_templateTypeRep = GHC.Types.primitive @“ETemplateTypeRep”
instance DA.Internal.Desugar.HasIsInterfaceType WhitelistedRegistry where
_isInterfaceType _ = DA.Internal.Desugar.False
instance DA.Internal.Desugar.HasExercise WhitelistedRegistry DA.Internal.Desugar.Archive (()) where
exercise = GHC.Types.primitive @“UExercise”
instance DA.Internal.Desugar.HasToAnyChoice WhitelistedRegistry DA.Internal.Desugar.Archive (()) where
_toAnyChoice = GHC.Types.primitive @“EToAnyChoice”
instance DA.Internal.Desugar.HasFromAnyChoice WhitelistedRegistry DA.Internal.Desugar.Archive (()) where
_fromAnyChoice = GHC.Types.primitive @“EFromAnyChoice”
instance DA.Internal.Desugar.HasChoiceController WhitelistedRegistry DA.Internal.Desugar.Archive where
_choiceController = GHC.Types.primitive @“EChoiceController”
instance DA.Internal.Desugar.HasChoiceObserver WhitelistedRegistry DA.Internal.Desugar.Archive where
_choiceObserver = GHC.Types.primitive @“EChoiceObserver”
instance DA.Internal.Desugar.HasExercise WhitelistedRegistry ChangeOwner (ContractId WhitelistedRegistry) where
exercise = GHC.Types.primitive @“UExercise”
instance DA.Internal.Desugar.HasToAnyChoice WhitelistedRegistry ChangeOwner (ContractId WhitelistedRegistry) where
_toAnyChoice = GHC.Types.primitive @“EToAnyChoice”
instance DA.Internal.Desugar.HasFromAnyChoice WhitelistedRegistry ChangeOwner (ContractId WhitelistedRegistry) where
_fromAnyChoice = GHC.Types.primitive @“EFromAnyChoice”
instance DA.Internal.Desugar.HasChoiceController WhitelistedRegistry ChangeOwner where
_choiceController = GHC.Types.primitive @“EChoiceController”
instance DA.Internal.Desugar.HasChoiceObserver WhitelistedRegistry ChangeOwner where
_choiceObserver = GHC.Types.primitive @“EChoiceObserver”
instance DA.Internal.Desugar.HasExercise WhitelistedRegistry SetWhitelisted (ContractId WhitelistedRegistry) where
exercise = GHC.Types.primitive @“UExercise”
instance DA.Internal.Desugar.HasToAnyChoice WhitelistedRegistry SetWhitelisted (ContractId WhitelistedRegistry) where
_toAnyChoice = GHC.Types.primitive @“EToAnyChoice”
instance DA.Internal.Desugar.HasFromAnyChoice WhitelistedRegistry SetWhitelisted (ContractId WhitelistedRegistry) where
_fromAnyChoice = GHC.Types.primitive @“EFromAnyChoice”
instance DA.Internal.Desugar.HasChoiceController WhitelistedRegistry SetWhitelisted where
_choiceController = GHC.Types.primitive @“EChoiceController”
instance DA.Internal.Desugar.HasChoiceObserver WhitelistedRegistry SetWhitelisted where
_choiceObserver = GHC.Types.primitive @“EChoiceObserver”
instance DA.Internal.Desugar.HasExercise WhitelistedRegistry IsWhitelisted (Bool) where
exercise = GHC.Types.primitive @“UExercise”
instance DA.Internal.Desugar.HasToAnyChoice WhitelistedRegistry IsWhitelisted (Bool) where
_toAnyChoice = GHC.Types.primitive @“EToAnyChoice”
instance DA.Internal.Desugar.HasFromAnyChoice WhitelistedRegistry IsWhitelisted (Bool) where
_fromAnyChoice = GHC.Types.primitive @“EFromAnyChoice”
instance DA.Internal.Desugar.HasChoiceController WhitelistedRegistry IsWhitelisted where
_choiceController = GHC.Types.primitive @“EChoiceController”
instance DA.Internal.Desugar.HasChoiceObserver WhitelistedRegistry IsWhitelisted where
_choiceObserver = GHC.Types.primitive @“EChoiceObserver”
_choice$_WhitelistedRegistry$Archive :
(DA.Internal.Desugar.Consuming WhitelistedRegistry,
WhitelistedRegistry
→ DA.Internal.Desugar.Archive → [DA.Internal.Desugar.Party],
DA.Internal.Desugar.Optional (WhitelistedRegistry
-> DA.Internal.Desugar.Archive -> \[DA.Internal.Desugar.Party\]),
DA.Internal.Desugar.Optional (WhitelistedRegistry
-> DA.Internal.Desugar.Archive -> \[DA.Internal.Desugar.Party\]),
DA.Internal.Desugar.ContractId WhitelistedRegistry
→ WhitelistedRegistry
-> DA.Internal.Desugar.Archive -> DA.Internal.Desugar.Update (()))
_choice$_WhitelistedRegistry$Archive
= (DA.Internal.Desugar.Consuming,
\\ this \_ -> DA.Internal.Desugar.signatory this, DA.Internal.Desugar.None, DA.Internal.Desugar.None, \\ \_ \_ \_ -> pure ())
_choice$_WhitelistedRegistry$ChangeOwner :
(DA.Internal.Desugar.Consuming WhitelistedRegistry,
WhitelistedRegistry → ChangeOwner → [DA.Internal.Desugar.Party],
DA.Internal.Desugar.Optional (WhitelistedRegistry
-> ChangeOwner -> \[DA.Internal.Desugar.Party\]),
DA.Internal.Desugar.Optional (WhitelistedRegistry
-> ChangeOwner -> \[DA.Internal.Desugar.Party\]),
DA.Internal.Desugar.ContractId WhitelistedRegistry
→ WhitelistedRegistry
-> ChangeOwner -> DA.Internal.Desugar.Update (ContractId WhitelistedRegistry))
_choice$_WhitelistedRegistry$ChangeOwner
= (DA.Internal.Desugar.Consuming,
\\ this@WhitelistedRegistry {..} -> DA.Internal.Desugar.bypassReduceLambda \\ arg@ChangeOwner {..} -> let \_ = this in let \_ = arg in DA.Internal.Desugar.toParties (owner), DA.Internal.Desugar.None, DA.Internal.Desugar.None, \\ self this@WhitelistedRegistry {..} -> DA.Internal.Desugar.bypassReduceLambda \\ arg@ChangeOwner {..} -> let \_ = self in let \_ = this in let \_ = arg in do create this {owner = newOwner})
_choice$_WhitelistedRegistry$SetWhitelisted :
(DA.Internal.Desugar.Consuming WhitelistedRegistry,
WhitelistedRegistry
→ SetWhitelisted → [DA.Internal.Desugar.Party],
DA.Internal.Desugar.Optional (WhitelistedRegistry
-> SetWhitelisted -> \[DA.Internal.Desugar.Party\]),
DA.Internal.Desugar.Optional (WhitelistedRegistry
-> SetWhitelisted -> \[DA.Internal.Desugar.Party\]),
DA.Internal.Desugar.ContractId WhitelistedRegistry
→ WhitelistedRegistry
-> SetWhitelisted -> DA.Internal.Desugar.Update (ContractId WhitelistedRegistry))
_choice$_WhitelistedRegistry$SetWhitelisted
= (DA.Internal.Desugar.Consuming,
\\ this@WhitelistedRegistry {..} -> DA.Internal.Desugar.bypassReduceLambda \\ arg@SetWhitelisted {..} -> let \_ = this in let \_ = arg in DA.Internal.Desugar.toParties (owner), DA.Internal.Desugar.None, DA.Internal.Desugar.None, \\ self this@WhitelistedRegistry {..} -> DA.Internal.Desugar.bypassReduceLambda \\ arg@SetWhitelisted {..} -> let \_ = self in let \_ = this in let \_ = arg in do let newWhitelist = if isWhitelisted then addr :: filter (\\ p -> p /= addr) whitelisted else filter (\\ p -> p /= addr) whitelisted create this {whitelisted = newWhitelist})
_choice$_WhitelistedRegistry$IsWhitelisted :
(DA.Internal.Desugar.NonConsuming WhitelistedRegistry,
WhitelistedRegistry
→ IsWhitelisted → [DA.Internal.Desugar.Party],
DA.Internal.Desugar.Optional (WhitelistedRegistry
-> IsWhitelisted -> \[DA.Internal.Desugar.Party\]),
DA.Internal.Desugar.Optional (WhitelistedRegistry
-> IsWhitelisted -> \[DA.Internal.Desugar.Party\]),
DA.Internal.Desugar.ContractId WhitelistedRegistry
→ WhitelistedRegistry
-> IsWhitelisted -> DA.Internal.Desugar.Update (Bool))
_choice$_WhitelistedRegistry$IsWhitelisted
= (DA.Internal.Desugar.NonConsuming,
\\ this@WhitelistedRegistry {..} -> DA.Internal.Desugar.bypassReduceLambda \\ arg@IsWhitelisted {..} -> let \_ = this in let \_ = arg in DA.Internal.Desugar.toParties (caller), DA.Internal.Desugar.None, DA.Internal.Desugar.None, \\ self this@WhitelistedRegistry {..} -> DA.Internal.Desugar.bypassReduceLambda \\ arg@IsWhitelisted {..} -> let \_ = self in let \_ = this in let \_ = arg in do return (elem addr whitelisted))
Do you know why I didn’t get proper Haskell code?
Thanks in advance