packages feed

jsonnet-0.1.0.0: src/Language/Jsonnet/Desugar.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeSynonymInstances #-}

module Language.Jsonnet.Desugar (desugar) where

import Data.Fix as F
import Data.List.NonEmpty (NonEmpty (..), fromList, toList)
import qualified Data.List.NonEmpty as NE
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Language.Jsonnet.Annotate
import Language.Jsonnet.Common
import Language.Jsonnet.Core
import Language.Jsonnet.Parser.SrcSpan
import Language.Jsonnet.Syntax
import Unbound.Generics.LocallyNameless

class Desugarer a where
  desugar :: a -> Core

instance Desugarer (Ann ExprF ()) where
  desugar = foldFix go . zipWithOutermost
    where
      go (AnnF f (_, b)) = alg b f

instance Desugarer (Ann ExprF SrcSpan) where
  desugar = foldFix go . zipWithOutermost
    where
      go (AnnF f (a, b)) = alg b $ CLoc a <$> f

-- annotate nodes with a boolean denoting outermost objects
zipWithOutermost :: Ann ExprF a -> Ann ExprF (a, Bool)
zipWithOutermost = annZip . inherit go False
  where
    go (Fix (AnnF (EObj {}) _)) False = (True, True)
    go (Fix (AnnF (EObj {}) _)) True = (False, True)
    go _ x = (False, x)

alg outermost = \case
  ELit l -> CLit l
  EIdent i -> CVar (s2n i)
  EFun ps e -> mkFun ps e
  EApply e es -> CApp e es
  ELocal bnds e -> mkLet bnds e
  -- operator % is overloaded for both modulo and string formatting
  EBinOp (Arith Mod) e1 e2 ->
    stdFunc "mod" (Args [Pos e1, Pos e2] Lazy)
  EBinOp (Comp Eq) e1 e2 ->
    stdFunc "equals" (Args [Pos e1, Pos e2] Lazy)
  EBinOp (Comp Ne) e1 e2 ->
    CUnyOp LNot (stdFunc "equals" (Args [Pos e1, Pos e2] Lazy))
  EBinOp op e1 e2 -> CBinOp op e1 e2
  EUnyOp op e -> CUnyOp op e
  EIfElse c t e -> CIfElse c t e
  EIf c t -> CIfElse c t (CLit Null)
  EArr e -> CArr e
  EObj {..} -> mkObj outermost locals fields
  ELookup e1 e2 -> CLookup e1 e2
  EIndex e1 e2 -> CLookup e1 e2
  EErr e -> CErr e
  EAssert e -> mkAssert e
  ESlice {..} -> mkSlice expr start end step
  EArrComp {expr, comp} -> mkArrComp expr comp
  EObjComp {field, comp, locals} -> mkObjComp field comp locals

mkSlice expr start end step =
  stdFunc
    "slice"
    ( Args
        [ Pos expr,
          Pos $ maybeNull start,
          Pos $ maybeNull end,
          Pos $ maybeNull step
        ]
        Lazy
    )
  where
    maybeNull = fromMaybe (CLit Null)

mkObj outermost locals fields =
  --mkLet (("self", CObj fields) :| bnds) self
  case bnds of
    [] -> fs
    xs -> mkLet (NE.fromList xs) fs
  where
    bnds =
      if outermost
        then (("$", fs) : locals)
        else locals
    fs = CObj (mkKeyValue <$> fields)

mkAssert :: Assert Core -> Core
mkAssert (Assert c m e) =
  CIfElse
    c
    e
    ( CErr $
        fromMaybe
          (CLit $ String "Assertion failed")
          m
    )

mkArrComp :: Core -> NonEmpty (CompSpec Core) -> Core
mkArrComp expr comp = foldr f (CArr [expr]) comp
  where
    f CompSpec {..} e =
      CComp (ArrC (bind (s2n var) (e, ifspec))) forspec

mkKeyValue :: Field Core -> KeyValue Core
mkKeyValue Field {..} = KeyValue key (Hideable value' visibility)
  where
    value' =
      if override
        then
          CIfElse
            (CBinOp In key super)
            (CBinOp (Arith Add) (CLookup super key) value)
            value
        else value
    super = CVar $ s2n "super"

mkObjComp (Field {..}) comp locals =
  CComp (ObjC (bind (s2n "arr") (kv', Nothing))) arrComp
  where
    kv' =
      mkKeyValue
        ( Field
            { key = key',
              value = value',
              visibility,
              override
            }
        )
    bnds = NE.zip (fmap var comp) xs
    key' = mkLet bnds key
    value' = case locals of
      [] -> mkLet bnds value
      -- we need to nest the let bindings due to the impl.
      xs -> mkLet bnds $ mkLet (NE.fromList xs) value
    xs = CLookup (CVar $ s2n "arr") . CLit . Number . fromIntegral <$> [0 ..]
    arrComp = mkArrComp arr comp
    arr = CArr $ NE.toList $ CVar . s2n . var <$> comp

stdFunc :: Text -> Args Core -> Core
stdFunc f =
  CApp
    ( CLookup
        (CVar "std")
        (CLit $ String f)
    )

mkFun ps e =
  CFun $
    Fun $
      bind
        ( rec $
            fmap
              ( \(n, a) ->
                  (s2n n, Embed a)
              )
              ps
        )
        e

mkLet bnds e =
  CLet $
    Let $
      bind
        ( rec $
            toList
              ( fmap
                  ( \(n, a) ->
                      (s2n n, Embed a)
                  )
                  bnds
              )
        )
        e