packages feed

ag-pictgen-1.0.0.0: src/PictGen.ag

MODULE {Main} {main} {}

--
-- Imports
--

imports
{
import Data.Foldable(toList)
import Data.Sequence(Seq)
import qualified Data.Sequence as Seq
import Data.Map(Map)
import qualified Data.Map as Map
import Data.Set(Set)
import qualified Data.Set as Set
import UU.Pretty
import UU.Scanner
import UU.Parsing
import UU.Scanner.GenTokenParser
import System.Environment
import Data.List
}


--
-- Source language
--

DATA ViewedGrammar | Gram
  name        : {String}
  hierarchies : {[[String]]}
  prods       : ProdViews

DATA Grammar | Gram
  name        : {String}
  view        : {String}
  prods       : Prods

TYPE ProdViews = [ProdView]
TYPE Prods     = [Prod]

DATA ProdView | Prod
  view : {String}
  prod : Prod

DATA Prod | Prod
  name     : {String}
  parent   : Nonterm
  children : Syms
  locals   : Attrs
  deps     : Deps

TYPE Syms = [Sym]

DATA Sym
  | Term
      name : {String}
      tp   : {String}
      tex  : {[String]}
  | Nont
      name : {String}
      tp   : Nonterm

DATA Nonterm | Nont
  tp  : {String}
  inh : Attrs
  syn : Attrs
  tex : {[String]}

TYPE Attrs = [Attr]

DATA Attr | Attr
  name   : {String}
  tp     : {String}
  tex    : {[String]}

TYPE Deps = [Dep]

DATA Dep | Dep
  source : {Ident}
  target : {Ident}
  hint   : {Maybe DirHint}
  tex    : {[String]}

DATA DirHint
  | ForceUp
  | ForceDown

DATA HorHint
  | LeftRight
  | RightLeft
  | Unknown

DATA Flow
  | Inh
  | Syn

DATA Ident
  | ChildAttr
      child : {String}
      flow  : {Flow}
      attr  : {String}
  | LocAttr
      attr  : {String}
  | LhsAttr
      flow  : {Flow}
      attr  : {String}
  | ChildVal
      child : {String}

DERIVING Ident Flow : Eq, Ord
DERIVING * : Show


SET AllAttr       = Attrs Attr
SET AllNonterm    = Nonterm AllAttr
SET AllSyms       = Syms Sym AllNonterm
SET AllDeps       = Deps Dep
SET AllProdViews  = ProdViews ProdView AllProds
SET AllProds      = Prods Prod AllSyms AllDeps
SET AllGram       = Grammar AllProds
SET AllViewedGram = ViewedGrammar AllProdViews

{
type Dim = Double

data UID
  = UidName  !String
  | UidNum   !Int
  | UidIdent !Ident
  deriving (Eq,Ord,Show)

half :: Dim -> Dim
half = (/2)

isSmall :: Dim -> Bool
isSmall d = d < 0.0001
}


--
-- Desugar
--

ATTR ViewedGrammar [ | | grammars : {Map String Grammar} ]
ATTR ProdViews ProdView [ | | prods USE {++} {[]} : {[(String, Prod)]} ]
ATTR AllProds [ | | copy : SELF ]

SEM ViewedGrammar
  | Gram
      loc.views    = flattenViews @hierarchies
      lhs.grammars = Map.mapWithKey (mergeForView @name @prods.prods) @loc.views

SEM ProdView
  | Prod
      lhs.prods = [(@view, @prod.copy)]

{
flattenViews :: [[String]] -> Map String (Set String)
flattenViews hierarchies
  = let views = nub $ concat hierarchies
        parents v acc [] = []
        parents v acc (x:xs)
          | v == x    = acc
          | otherwise = parents v (x:acc) xs
    in Map.fromList [ (v, Set.fromList (concatMap (parents v [v]) hierarchies)) | v <- views ]

mergeForView :: String -> [(String, Prod)] -> String -> Set String -> Grammar
mergeForView name prods view parents
  = Grammar_Gram name view prods'
  where
    prods' = [ p | (n,p) <- prods, n `Set.member` parents ]
}


--
-- Distributing unique numbers
--

ATTR AllSyms [ | guid : Int | ]
SEM Prod | Prod
  loc.luid    = 1
  parent.guid = @loc.luid + 1

SEM Attr | Attr
  loc.luid = @lhs.guid
  lhs.guid = @lhs.guid + 1

SEM Sym
  | Term
      loc.luid = @lhs.guid
      lhs.guid = @lhs.guid + 1
  | Nont
      loc.luid = @lhs.guid
      tp.guid  = @lhs.guid + 1

--
-- Constructing unique identifiers
--

ATTR Nonterm Attrs Attr [ mkAttrIdent : {Flow -> String -> Ident} | | ]
ATTR Attrs Attr [ flow : Flow | | ]

SEM Sym | Term Nont
  loc.ident = Ident_ChildVal @name
  loc.uid   = UidIdent @loc.ident

SEM Sym | Nont
  tp.mkAttrIdent = Ident_ChildAttr @name

SEM Prod | Prod
  loc.uid            = UidName "lhs"
  parent.mkAttrIdent = Ident_LhsAttr
  locals.flow        = Flow_Syn
  locals.mkAttrIdent = const Ident_LocAttr

SEM Nonterm | Nont
  inh.flow = Flow_Inh
  syn.flow = Flow_Syn

SEM Attr | Attr
  loc.ident = @lhs.mkAttrIdent @lhs.flow @name
  loc.uid   = UidIdent @loc.ident


--
-- Name of Nont
--

{
-- | Combine parent name and own name of Nont
cmbNontNm :: String -> String -> String
cmbNontNm ctxtNm tp = ctxtNm ++ ":" ++ tp
}

ATTR Nonterm [ ctxtNm: String | | ]
SEM Prod
  | Prod
      parent.ctxtNm  = @name

SEM Sym
  | Nont
      tp.ctxtNm  	 = ""

--
-- Width calculation
--

ATTR AllSyms [ | | width : Dim ]
SEM Prod | Prod
  loc.totalWidth = @parent.width `max` @children.width `max` @locals.width

SEM Syms
  | Cons
      loc.sepWidth   = defaultSymSepWidth
      loc.totalWidth = consWidth @hd.width @tl.width @loc.sepWidth
      lhs.width      = @loc.totalWidth
  | Nil
      lhs.width = 0

SEM Sym  | Term
  loc.width = defaultSymWidth

SEM Nonterm | Nont
  loc.width      = ((fromIntegral $ length (cmbNontNm @lhs.ctxtNm @tp)) * defaultSymWidth) / 3
  loc.sepWidth   = defaultAttrSepWidth
  loc.innerWidth = @loc.width + 2 * @loc.sepWidth
  loc.totalWidth = @loc.innerWidth + @inh.width + @syn.width
  lhs.width      = @loc.totalWidth

SEM Attrs
  | Cons
      loc.sepWidth   = defaultAttrSepWidth
      loc.totalWidth = consWidth @hd.width @tl.width @loc.sepWidth
      lhs.width      = @loc.totalWidth
  | Nil
      lhs.width = 0

SEM Attr | Attr
  loc.width = defaultAttrWidth

{
consWidth :: Dim -> Dim -> Dim -> Dim
consWidth hd tl defltSep = sepWidth + hd + tl where
  sepWidth | isSmall tl = 0
           | otherwise  = defltSep

defaultSymSepWidth :: Dim
defaultSymSepWidth = 2

defaultSymWidth :: Dim
defaultSymWidth = 1

defaultAttrSepWidth :: Dim
defaultAttrSepWidth = 0.8

defaultAttrWidth :: Dim
defaultAttrWidth = 1
}


--
-- Horizontal coordinate distribution
-- (center position)
--

ATTR AllSyms [ x : Dim | | ]

SEM Prod | Prod
  parent.x   = left 0 @loc.totalWidth @parent.width
  locals.x   = left 0 @loc.totalWidth @locals.width
  children.x = left 0 @loc.totalWidth @children.width

SEM Syms | Cons
  hd.x = @lhs.x
  tl.x = @lhs.x + @loc.totalWidth - @tl.width

SEM Nonterm | Nont
  inh.x = @lhs.x
  syn.x = @lhs.x + @loc.totalWidth - @syn.width
  loc.x = left (@lhs.x + @inh.width) @loc.innerWidth 0

SEM Attrs | Cons
  hd.x = @lhs.x
  tl.x = @lhs.x + @loc.totalWidth - @tl.width

SEM Attr | Attr
  loc.x = left @lhs.x @loc.width 0

{
left :: Dim -> Dim -> Dim -> Dim
left x total width
  = x + (half (total - width))
}


--
-- Collect uids of attributes
--

ATTR Attrs Attr [ | | uids USE {Seq.><} {Seq.empty} : {Seq UID} ]
SEM Attr | Attr
  lhs.uids = Seq.singleton @loc.uid

ATTR Nonterm [ | | inhUids : {Seq UID} synUids : {Seq UID} ]
SEM Nonterm | Nont
  lhs.inhUids = @inh.uids
  lhs.synUids = @syn.uids

--
-- The nonterminal name
--

ATTR Nonterm [ | | nt : String ]
SEM Nonterm | Nont
  lhs.nt = @tp


--
-- UID dictionary construction
--

{
type UIDDict = Map Ident UID
}

ATTR AllSyms [ | | dictCol USE {`Map.union`} {Map.empty} : UIDDict ]
ATTR AllDeps [ dict : UIDDict | | ]

SEM Sym | Term
  lhs.dictCol = Map.singleton @loc.ident @loc.uid

SEM Attr | Attr
  lhs.dictCol = Map.singleton @loc.ident @loc.uid

SEM Prod | Prod
  loc.dict = @parent.dictCol `Map.union` @children.dictCol `Map.union` @locals.dictCol

--
-- The order of children
--

ATTR Syms Sym [ | rank : Int | collRanks USE {`Map.union`} {Map.empty} : {Map String Int} ]
ATTR AllDeps [ childRanks : {Map String Int} | | ]

SEM Prod | Prod
  children.rank  = 1
  loc.childRanks = @children.collRanks

SEM Sym
  | Term Nont
      loc.rank = @lhs.rank
      lhs.rank = 1 + @lhs.rank
      lhs.collRanks = Map.singleton @name @loc.rank


--
-- Command synthesis
--

ATTR AllGram [ | | cmds USE {Seq.><} {Seq.empty} : Cmds ]
ATTR Nonterm [ build : PartCmd | | ]
ATTR AllNonterm [ buildAttr : AttrCmd | | ]

SEM Prod | Prod
  parent.build     = \x tp -> Cmd_Parent "lhs" @loc.uid x (cmbNontNm @name tp)
  parent.buildAttr = Cmd_AttrParent
  locals.buildAttr = Cmd_AttrLocal
  loc.inhEdges     = mkEdges (@parent.inhUids Seq.>< Seq.singleton @loc.uid)
  loc.synEdges     = mkEdges (Seq.singleton @loc.uid Seq.>< @parent.synUids)
  loc.beginCmds    = Seq.singleton $ Cmd_BeginProd @name @loc.uid @parent.nt @loc.totalWidth
  loc.endCmds      = Seq.singleton $ Cmd_EndProd @name @loc.uid @parent.nt @loc.totalWidth
  lhs.cmds         = @loc.beginCmds
                     Seq.>< @parent.cmds
                     Seq.>< @children.cmds
                     Seq.>< @locals.cmds
                     Seq.>< Seq.fromList @loc.inhEdges Seq.>< Seq.fromList @loc.synEdges
                     Seq.>< @deps.cmds
                     Seq.>< @loc.endCmds

SEM Sym | Nont
  tp.build     = Cmd_ChildNonTerm @name @loc.uid
  tp.buildAttr = Cmd_AttrChild
  loc.inhEdges = mkEdges (@tp.inhUids Seq.>< Seq.singleton @loc.uid)
  loc.synEdges = mkEdges (Seq.singleton @loc.uid Seq.>< @tp.synUids)
  lhs.cmds     = @tp.cmds Seq.>< Seq.fromList @loc.inhEdges Seq.>< Seq.fromList @loc.synEdges

SEM Sym | Term
  lhs.cmds = Seq.singleton $ Cmd_ChildTerm @name @loc.uid @lhs.x @tp @tex

SEM Nonterm | Nont
  lhs.cmds = (Seq.singleton $ @lhs.build @loc.x @tp @tex) Seq.>< @inh.cmds Seq.>< @syn.cmds

SEM Attr | Attr
  lhs.cmds = Seq.singleton $ @lhs.buildAttr @name @loc.uid @loc.x @tp @tex

SEM Dep | Dep
  loc.uidSource  = Map.findWithDefault (error $ "not in map: " ++ show @source) @source @lhs.dict
  loc.uidTarget  = Map.findWithDefault (error $ "not in map: " ++ show @target) @target @lhs.dict
  (loc.hintSource, loc.hintDest) = selectHints @source @target @hint
  loc.hintHor = selectHorHint @lhs.childRanks @source @target
  lhs.cmds = Seq.singleton $ Cmd_DepLine @loc.uidSource @loc.uidTarget @loc.hintSource @loc.hintDest @loc.hintHor @tex

{
mkEdges :: Seq UID -> [Cmd]
mkEdges us
  = zipWith Cmd_SibLine uids (tail uids)
  where uids = toList us

-- Gives an indication if the line will be from right to left or from left to right
selectHorHint :: Map String Int -> Ident -> Ident -> HorHint
selectHorHint order (Ident_ChildAttr a flowA _) (Ident_ChildAttr b flowB _)  = case Map.lookup a order of
  Nothing    -> HorHint_Unknown
  Just rankA -> case Map.lookup b order of
    Nothing  -> HorHint_Unknown
    Just rankB | rankA == rankB -> case (flowA, flowB) of
                                     (Flow_Inh, Flow_Inh) -> HorHint_Unknown
                                     (Flow_Syn, Flow_Syn) -> HorHint_Unknown
                                     (Flow_Inh, Flow_Syn) -> HorHint_LeftRight
                                     (Flow_Syn, Flow_Inh) -> HorHint_RightLeft
               | rankA > rankB  -> HorHint_RightLeft
               | otherwise      -> HorHint_LeftRight
selectHorHint order _ _ = HorHint_Unknown

-- Gives an indication if the arrow will arrive upward or downward on an attribute box
selectHints :: Ident -> Ident -> Maybe DirHint -> (DirHint, DirHint)
selectHints _                       _                       (Just h) = (h, h)
selectHints (Ident_LocAttr _)       (Ident_LocAttr _)       _        = (DirHint_ForceUp, DirHint_ForceUp)
selectHints (Ident_LocAttr _)       (Ident_LhsAttr _ _)     _        = (DirHint_ForceUp, DirHint_ForceDown)
selectHints (Ident_LhsAttr _ _)     (Ident_LocAttr _)       _        = (DirHint_ForceDown, DirHint_ForceUp)
selectHints (Ident_LocAttr _)       _                       _        = (DirHint_ForceDown, DirHint_ForceUp)
selectHints _                       (Ident_LocAttr _)       _        = (DirHint_ForceUp, DirHint_ForceDown)
selectHints (Ident_ChildAttr _ _ _) (Ident_ChildAttr _ _ _) _        = error "Dep from child attr to child attr: a hint is required."
selectHints (Ident_ChildAttr _ _ _) (Ident_LhsAttr _ _)     _        = (DirHint_ForceUp, DirHint_ForceDown)
selectHints (Ident_ChildAttr _ _ _) (Ident_ChildVal _)      _        = error "Child-values cannot have a dependency on an attribute."
selectHints (Ident_LhsAttr _ _)     (Ident_ChildAttr _ _ _) _        = (DirHint_ForceDown, DirHint_ForceUp)
selectHints (Ident_LhsAttr _ _)     (Ident_LhsAttr _ _)     _        = error "Dep from lhs attr to lhs attr: a hint is required."
selectHints (Ident_LhsAttr _ _)     (Ident_ChildVal _)      _        = error "Child-values cannot have a dependency on an attribute."
selectHints (Ident_ChildVal _)      (Ident_ChildAttr _ _ _) _        = (DirHint_ForceDown, DirHint_ForceDown)
selectHints (Ident_ChildVal _)      (Ident_LhsAttr _ _)     _        = (DirHint_ForceUp, DirHint_ForceDown)
selectHints (Ident_ChildVal _)      (Ident_ChildVal _)      _        = error "Child-values cannot have a dependency on another child-value."
}




--
-- Target language
--

{
type Cmds = Seq.Seq Cmd
type PartCmd = Dim -> String -> [String] -> Cmd
type AttrCmd = String -> UID -> Dim -> String -> [String] -> Cmd
}

TYPE CmdList = [Cmd]

DATA Cmd
  | (BeginProd EndProd)
      nm    : {String}
      uid   : {UID}
      nt    : {String}
      width : {Dim}
  | Parent
      nm  : {String}
      uid : {UID}
      x   : {Dim}
      tp  : {String}
      tex : {[String]}
  | (AttrParent AttrChild AttrLocal)
      nm  : {String}
      uid : {UID}
      x   : {Dim}
      tp  : {String}
      tex : {[String]}
  | (ChildNonTerm ChildTerm)
      nm  : {String}
      uid : {UID}
      x   : {Dim}
      tp  : {String}
      tex : {[String]}
  | SibLine
      uidA : {UID}
      uidB : {UID}
  | DepLine
      uidA  : {UID}
      uidB  : {UID}
      hintA : {DirHint}
      hintB : {DirHint}
      hint  : {HorHint}
      tex   : {[String]}

SET AllCmds = CmdList Cmd


--
-- Wrappers
--

WRAPPER ViewedGrammar Grammar CmdList


--
-- PP
--

ATTR AllCmds [ | | pp USE {>-<} {empty} : {PP_Doc} ]

SEM Cmd
  | BeginProd
      lhs.pp = texcmd "agbeginprod" [@nm, mkId @uid, @nt, show @width] []
  | EndProd
      lhs.pp = texcmd "agendprod" [@nm, mkId @uid, @nt, show @width] []
  | Parent
      lhs.pp = texcmd "agparent" [@nm, mkId @uid, show @x, @tp] @tex
  | AttrParent
      lhs.pp = texcmd "agparattr" [@nm, mkId @uid, show @x, @tp] @tex
  | AttrChild
      lhs.pp = texcmd "agchldattr" [@nm, mkId @uid, show @x, @tp] @tex
  | AttrLocal
      lhs.pp = texcmd "aglocattr" [@nm, mkId @uid, show @x, @tp] @tex
  | ChildNonTerm
      lhs.pp = texcmd "agchldnonterm" [@nm, mkId @uid, show @x, @tp] @tex
  | ChildTerm
      lhs.pp = texcmd "agchldterm" [@nm, mkId @uid, show @x, @tp] @tex
  | SibLine
      lhs.pp = texcmd "agsibline" [mkId @uidA, mkId @uidB] []
  | DepLine
      loc.texname = case (@hintA, @hintB) of
                      (DirHint_ForceUp, DirHint_ForceUp)     -> "agdeplineupup"
                      (DirHint_ForceUp, DirHint_ForceDown)   -> "agdeplineupdown"
                      (DirHint_ForceDown, DirHint_ForceUp)   -> "agdeplinedownup"
                      (DirHint_ForceDown, DirHint_ForceDown) -> "agdeplinedowndown"
      loc.hor     = case @hint of
                      HorHint_Unknown   -> "horunknown"
                      HorHint_LeftRight -> "horleftright"
                      HorHint_RightLeft -> "horrightleft"
      lhs.pp = texcmd @loc.texname [mkId @uidA, mkId @uidB, @loc.hor] @tex

{
mkId :: UID -> String
mkId uid = case uid of
  UidName nm -> nm
  UidNum u   -> "s" ++ show u
  UidIdent i -> ident2LaTeX i

ident2LaTeX :: Ident -> String
ident2LaTeX i = case i of
  Ident_ChildAttr child flow attr -> attr2LaTeX child flow attr
  Ident_LocAttr attr              -> attr2LaTeX "loc" Flow_Syn attr
  Ident_LhsAttr flow attr         -> attr2LaTeX "lhs" flow attr
  Ident_ChildVal child            -> child

attr2LaTeX :: String -> Flow -> String -> String
attr2LaTeX child flow attr = child ++ flow2LaTeX flow ++ attr

flow2LaTeX :: Flow -> String
flow2LaTeX Flow_Inh = "I"
flow2LaTeX Flow_Syn = "S"

optStyle :: Maybe String -> [String]
optStyle (Just s) = [s]
optStyle Nothing  = []

texcmd :: String -> [String] -> [String] -> PP_Doc
texcmd nm ps os
  = "\\" >|< nm >|< hlist (map (pp_brackets . pp) os) >|< pp_braces (pp $ length os) >|< hlist (map (pp_braces . pp) ps) >#< "%"
}


--
-- Pipeline
--

{
transform :: ViewedGrammar -> String
transform
  = phasePPString
  . vlist
  . Map.elems
  . Map.mapWithKey (\v -> phaseCreateTexMacro v . phaseCmdListPP . phaseCmdSeqCmdList . phaseGrammarCmdSeq)
  . phaseDesugar
  where
    phaseDesugar :: ViewedGrammar -> Map String Grammar
    phaseDesugar grammar
      = let inh = Inh_ViewedGrammar {}
            sem = sem_ViewedGrammar grammar
            syn = wrap_ViewedGrammar sem inh
        in grammars_Syn_ViewedGrammar syn

    phaseGrammarCmdSeq :: Grammar -> Cmds
    phaseGrammarCmdSeq grammar
      = let inh = Inh_Grammar {}
            sem = sem_Grammar grammar
            syn = wrap_Grammar sem inh
        in cmds_Syn_Grammar syn

    phaseCmdSeqCmdList :: Cmds -> CmdList
    phaseCmdSeqCmdList = toList

    phaseCmdListPP :: CmdList -> PP_Doc
    phaseCmdListPP list
      = let inh = Inh_CmdList {}
            sem = sem_CmdList list
            syn = wrap_CmdList sem inh
         in pp_Syn_CmdList syn

    phaseCreateTexMacro :: String -> PP_Doc -> PP_Doc
    phaseCreateTexMacro v d
      = d
      -- disabled for now:
      {-
      "\\newcommand{\\tikz." >|< v >|< "}" >#< "{ %"
      >-< " " >#< d
      >-< "}"
      -}

    phasePPString :: PP_Doc -> String
    phasePPString doc
      = disp doc 10000 ""


scanPPDFile :: FilePath -> IO [Token]
scanPPDFile
  = let keywords    = ["grammar", "hierarchy", "view", "prod", "attrs", "inh", "syn", "children", "nonterm", "term", "locals", "flows", "lhs", "loc", "downwards", "upwards", "in", "out", "style"]
        keywordsops = ["->", ".", ":"]
        opchars     = "->.:"
    in scanFile keywords keywordsops "" opchars


pUpperId :: Parser Token String
pUpperId = pConid <|>  pString

pLowerId :: Parser Token String
pLowerId = pVarid <|> pString

pGrammar :: Parser Token ViewedGrammar
pGrammar
  = opt (ViewedGrammar_Gram
          <$  pKey "grammar" <*> pUpperId
          <*> pList pHierarchy
        )
        (ViewedGrammar_Gram "Nont" [["View"]])  -- backwards compatibility
  <*> pList pProd

pHierarchy :: Parser Token [String]
pHierarchy
  = pKey "hierarchy" *> pList pUpperId

pProd :: Parser Token ProdView
pProd
  = (\n t c v (i,s) syms locs deps -> ProdView_Prod v (Prod_Prod n (Nonterm_Nont t i s c) syms locs deps))
  <$  pKey "prod" <*> pUpperId <* pKey ":" <*> pUpperId
  <*> pTex
  <*> opt (pKey "view" *> pUpperId) "View"  -- backwards compatibility
  <*> pAttrs
  <*> pSyms
  <*> pLocals
  <*> pDeps

pTex :: Parser Token [String]
pTex = pList_gr pTexBlock <?> "tex blocks"

pTexBlock :: Parser Token String
pTexBlock = pString <?> "a tex block"

pAttrs :: Parser Token (Attrs, Attrs)
pAttrs
  = (,) <$ pKey "attrs" <*> pList (pAttr "inh") <*> pList (pAttr "syn") <?> "attribute decls"

pAttr :: String -> Parser Token Attr
pAttr key
  = Attr_Attr <$ pKey key <*> pLowerId <* pKey ":" <*> pUpperId <*> pTex <?> (key ++ " decl")

pSyms :: Parser Token Syms
pSyms
  = pKey "children" *> pList pRhsSym <?> "children decls"

pLocals :: Parser Token Attrs
pLocals
  = ((pKey "locals" *> pList ((\nm -> Attr_Attr nm "LOC") <$ pKey "loc" <*> pLowerId <*> pTex)) `opt` []) <?> "local decls"

pRhsSym :: Parser Token Sym
pRhsSym
  =   Sym_Term <$ pKey "term" <*> pLowerId <* pKey ":" <*> pUpperId <*> pTex
  <|> (\n t c (i,s) -> Sym_Nont n (Nonterm_Nont t i s c)) <$ pKey "nonterm" <*> pLowerId <* pKey ":" <*> pUpperId <*> pTex <*> pAttrs
  <?> "(non)term decls"

pDeps :: Parser Token Deps
pDeps
  = pKey "flows" *> pList pDep <?> "dependencies"

pDep :: Parser Token Dep
pDep = Dep_Dep <$> pAttrOcc <* pKey "->" <*> pAttrOcc <*> opt (Just <$> pHint) Nothing <*> pTex <?> "dependency"

pAttrOcc :: Parser Token Ident
pAttrOcc  =  (\fl f a -> f fl a) <$> pFlow <* pKey "." <*> pChildOcc <* pKey "." <*> pLowerId
         <|> pChildOcc <* pKey "." <*> pFlowOld <* pKey "." <*> pLowerId
         <|> Ident_ChildVal <$> pLowerId
         <|> Ident_LocAttr <$ pKey "loc" <* pKey "." <*> pLowerId
         <?> "attribute occurrence"

pChildOcc :: Parser Token (Flow -> String -> Ident)
pChildOcc
   =   Ident_LhsAttr <$ pKey "lhs"
  <|>  Ident_ChildAttr <$> pLowerId
  <?> "child occurrence"

pHint :: Parser Token DirHint
pHint =   DirHint_ForceUp   <$ pKey "upwards"
      <|> DirHint_ForceDown <$ pKey "downwards"
      <?> "dir hints"

pFlow :: Parser Token Flow
pFlow =   Flow_Inh <$ pKey "inh"
      <|> Flow_Syn <$ pKey "syn"
      <?> "inh/syn"

pFlowOld :: Parser Token Flow
pFlowOld
      =   Flow_Inh <$ pKey "in"
      <|> Flow_Syn <$ pKey "out"
      <?> "in/out"


compile :: FilePath -> FilePath -> IO ()
compile source dest
  = do tokens <- scanPPDFile source
       gram   <- parseIOMessage show pGrammar tokens
       let pict = transform gram
       writeFile dest pict


main :: IO ()
main
  = do args <- getArgs
       if (length args /= 2)
        then putStrLn "usage: ag-pictgen <source> <dest>"
        else let [source,dest] = args
             in compile source dest
}


--
-- Additional type signatures
--

SEM *
  | *
    loc.innerWidth : {Dim}
    loc.width : {Dim}
    loc.totalWidth : {Dim}
    loc.uid : {UID}