packages feed

phino-0.0.0.53: src/CST.hs

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}

-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT
-- This module represents concrete syntax tree for phi-calculus program
module CST where

import AST
import Data.List (intercalate)
import Data.Maybe (isJust)
import Misc

data LCB = LCB | BIG_LCB
  deriving (Eq, Show)

data RCB = RCB | BIG_RCB
  deriving (Eq, Show)

data LSB = LSB | LSB'
  deriving (Eq, Show)

data RSB = RSB | RSB'
  deriving (Eq, Show)

data COMMA = COMMA | NO_COMMA
  deriving (Eq, Show)

data ARROW = ARROW | ARROW'
  deriving (Eq, Show)

data DASHED_ARROW = DASHED_ARROW
  deriving (Eq, Show)

data VOID = EMPTY | QUESTION
  deriving (Eq, Show)

data PHI = PHI | AT
  deriving (Eq, Show)

data RHO = RHO | CARET
  deriving (Eq, Show)

data DELTA = DELTA
  deriving (Eq, Show)

data XI = XI | DOLLAR
  deriving (Eq, Show)

data LAMBDA = LAMBDA
  deriving (Eq, Show)

data GLOBAL = Φ | Q
  deriving (Eq, Show)

data DEF_PACKAGE = Φ̇ | QQ
  deriving (Eq, Show)

data TERMINATION = DEAD | T
  deriving (Eq, Show)

data SPACE = SPACE
  deriving (Eq, Show)

data EOL = EOL | NO_EOL
  deriving (Eq, Show)

data BYTES
  = BT_EMPTY
  | BT_ONE String
  | BT_MANY [String]
  | BT_META META
  deriving (Eq, Show)

data META
  = MT_EXPRESSION {rest :: String}
  | MT_EXPRESSION' {rest :: String}
  | MT_ATTRIBUTE {rest :: String}
  | MT_ATTRIBUTE' {rest :: String}
  | MT_BINDING {rest :: String}
  | MT_BINDING' {rest :: String}
  | MT_BYTES {rest :: String}
  | MT_BYTES' {rest :: String}
  | MT_TAIL {rest :: String}
  | MT_FUNCTION {rest :: String}
  deriving (Eq, Show)

data TAB
  = TAB {indent :: Int}
  | TAB'
  | NO_TAB
  deriving (Eq, Show)

data ALPHA = ALPHA | ALPHA'
  deriving (Eq, Show)

data PROGRAM
  = PR_SWEET {lcb :: LCB, expr :: EXPRESSION, rcb :: RCB}
  | PR_SALTY {global :: GLOBAL, arrow :: ARROW, expr :: EXPRESSION}
  deriving (Eq, Show)

data PAIR
  = PA_TAU {attr :: ATTRIBUTE, arrow :: ARROW, expr :: EXPRESSION}
  | PA_FORMATION {attr :: ATTRIBUTE, voids :: [ATTRIBUTE], arrow :: ARROW, expr :: EXPRESSION}
  | PA_VOID {attr :: ATTRIBUTE, arrow :: ARROW, void :: VOID}
  | PA_LAMBDA {func :: String}
  | PA_LAMBDA' {func :: String} -- ASCII version of PA_LAMBDA
  | PA_META_LAMBDA {meta :: META}
  | PA_META_LAMBDA' {meta :: META} -- ASCII version of PA_META_LAMBDA'
  | PA_DELTA {bytes :: BYTES}
  | PA_DELTA' {bytes :: BYTES} -- ASCII version of PA_DELTA
  | PA_META_DELTA {meta :: META}
  | PA_META_DELTA' {meta :: META} -- ASCII version of PA_META_DELTA
  deriving (Eq, Show)

newtype APP_BINDING = APP_BINDING {pair :: PAIR}
  deriving (Eq, Show)

data BINDING
  = BI_PAIR {pair :: PAIR, bindings :: BINDINGS, tab :: TAB}
  | BI_EMPTY {tab :: TAB}
  | BI_META {meta :: META, bindings :: BINDINGS, tab :: TAB}
  deriving (Eq, Show)

data BINDINGS
  = BDS_PAIR {eol :: EOL, tab :: TAB, pair :: PAIR, bindings :: BINDINGS}
  | BDS_EMPTY {tab :: TAB}
  | BDS_META {eol :: EOL, tab :: TAB, meta :: META, bindings :: BINDINGS}
  deriving (Eq, Show)

-- Arguments for application with default α attributes
-- which are not necessary to be printed
data APP_ARG = APP_ARG {expr :: EXPRESSION, args :: APP_ARGS}
  deriving (Eq, Show)

data APP_ARGS
  = AAS_EXPR {eol :: EOL, tab :: TAB, expr :: EXPRESSION, args :: APP_ARGS}
  | AAS_EMPTY
  deriving (Eq, Show)

data EXPRESSION
  = EX_GLOBAL {global :: GLOBAL}
  | EX_DEF_PACKAGE {pckg :: DEF_PACKAGE} -- sugar for Q.org.eolang
  | EX_XI {xi :: XI}
  | EX_ATTR {attr :: ATTRIBUTE} -- sugar for $.x -> just x
  | EX_TERMINATION {termination :: TERMINATION}
  | EX_FORMATION {lsb :: LSB, eol :: EOL, tab :: TAB, binding :: BINDING, eol' :: EOL, tab' :: TAB, rsb :: RSB}
  | EX_DISPATCH {expr :: EXPRESSION, attr :: ATTRIBUTE}
  | EX_APPLICATION {expr :: EXPRESSION, eol :: EOL, tab :: TAB, tau :: APP_BINDING, eol' :: EOL, tab' :: TAB, indent :: Int} -- e(a1 -> e1)
  | EX_APPLICATION_TAUS {expr :: EXPRESSION, eol :: EOL, tab :: TAB, taus :: BINDING, eol' :: EOL, tab' :: TAB, indent :: Int} -- e(a1 -> e1)(a2 -> e2)(...)
  | EX_APPLICATION_EXPRS {expr :: EXPRESSION, eol :: EOL, tab :: TAB, args :: APP_ARG, eol' :: EOL, tab' :: TAB, indent :: Int} -- e(e1, e2, ...)
  | EX_STRING {str :: String, tab :: TAB, rhos :: [Binding]}
  | EX_NUMBER {num :: Either Int Double, tab :: TAB, rhos :: [Binding]}
  | EX_META {meta :: META}
  | EX_META_TAIL {expr :: EXPRESSION, meta :: META}
  | EX_PHI_MEET {idx :: Int, expr :: EXPRESSION}
  | EX_PHI_AGAIN {idx :: Int, expr :: EXPRESSION}
  deriving (Eq, Show)

data ATTRIBUTE
  = AT_LABEL {label :: String}
  | AT_ALPHA {alpha :: ALPHA, idx :: Int}
  | AT_RHO {rho :: RHO}
  | AT_PHI {phi :: PHI}
  | AT_LAMBDA {lambda :: LAMBDA}
  | AT_DELTA {delta :: DELTA}
  | AT_META {meta :: META}
  deriving (Eq, Show)

programToCST :: Program -> PROGRAM
programToCST prog = toCST prog 0 EOL

expressionToCST :: Expression -> EXPRESSION
expressionToCST expr = toCST expr 0 EOL

-- This class is used to convert AST to CST
-- CST is created with sugar and unicode
-- All further transformations much consider that
class ToCST a b where
  toCST :: a -> Int -> EOL -> b

instance ToCST Program PROGRAM where
  toCST (Program expr) tabs eol = PR_SWEET LCB (toCST expr tabs eol) RCB

instance ToCST Expression EXPRESSION where
  toCST ExGlobal _ _ = EX_GLOBAL Φ
  toCST ExThis _ _ = EX_XI XI
  toCST (ExMeta mt) _ _ = EX_META (MT_EXPRESSION (tail mt))
  toCST (ExMetaTail expr mt) tabs eol = EX_META_TAIL (toCST expr tabs eol) (MT_TAIL (tail mt))
  toCST ExTermination _ _ = EX_TERMINATION DEAD
  toCST (ExFormation [BiVoid AtRho]) _ eol = toCST (ExFormation []) 0 eol
  toCST (ExFormation []) _ _ = EX_FORMATION LSB NO_EOL NO_TAB (BI_EMPTY NO_TAB) NO_EOL NO_TAB RSB
  toCST (ExPhiMeet idx expr) tabs eol = EX_PHI_MEET idx (toCST expr tabs eol)
  toCST (ExPhiAgain idx expr) tabs eol = EX_PHI_AGAIN idx (toCST expr tabs eol)
  toCST (ExFormation bds) tabs eol =
    let next = tabs + 1
        bds' = toCST (withoutLastVoidRho bds) next eol :: BINDING
     in EX_FORMATION
          LSB
          EOL
          (TAB next)
          bds'
          EOL
          (TAB tabs)
          RSB
    where
      withoutLastVoidRho :: [Binding] -> [Binding]
      withoutLastVoidRho [] = []
      withoutLastVoidRho [BiVoid AtRho] = []
      withoutLastVoidRho (bd : [BiVoid AtRho]) = [bd]
      withoutLastVoidRho (bd : bds') = bd : withoutLastVoidRho bds'
  toCST (DataString bts) tabs _ = EX_STRING (btsToStr bts) (TAB tabs) []
  toCST (DataNumber bts) tabs _ = EX_NUMBER (btsToNum bts) (TAB tabs) []
  toCST (ExDispatch (ExDispatch ExGlobal (AtLabel "org")) (AtLabel "eolang")) _ _ = EX_DEF_PACKAGE Φ̇
  toCST (ExDispatch ExThis attr) tabs eol = EX_ATTR (toCST attr tabs eol)
  toCST (ExDispatch expr attr) tabs eol = EX_DISPATCH (toCST expr tabs eol) (toCST attr tabs eol)
  -- Since we convert AST to CST in sweet notation, here we're trying to get rid of unnecessary rho bindings
  -- in primitives (more details here: https://github.com/objectionary/phino/issues/451)
  -- If we find something similar to:
  -- `QQ.number(~0 -> QQ.bytes(...), ^ -> ..., ^ -> ...)`
  -- We remove unnecessary rho bindings and save them to EX_STRING or EX_NUMBER so they can be successfully
  -- converted to salty notation without losing information.
  -- In the end we just get CST with data primitive which is printed correctly.
  -- If given application is not such primitive - we just convert it to one of the applications:
  -- 1. either with pure expression with arguments, which means there are incremented only alpha bindings
  -- 2. or with just bindings
  toCST app@(ExApplication exp tau) tabs eol =
    let (expr, taus, exprs) = complexApplication app
        expr' = toCST expr tabs eol :: EXPRESSION
        next = tabs + 1
        (taus', rhos) = withoutRhosInPrimitives expr taus
        obj = ExApplication expr (head taus')
     in if length taus' == 1 && isJust (matchDataObject obj)
          then applicationToPrimitive obj tabs rhos
          else
            if null exprs
              then
                let eol' = inlinedEOL (not (hasEOL taus))
                 in EX_APPLICATION_TAUS
                      expr'
                      eol'
                      (tabOfEOL eol' next)
                      (toCST taus next eol' :: BINDING)
                      eol'
                      (tabOfEOL eol' tabs)
                      next
              else
                let eol' = inlinedEOL (not (hasEOL exprs))
                 in EX_APPLICATION_EXPRS
                      expr'
                      eol'
                      (tabOfEOL eol' next)
                      (toCST exprs next eol')
                      eol'
                      (tabOfEOL eol' tabs)
                      next
    where
      primitives :: [String]
      primitives = ["number", "string"]
      withoutRhosInPrimitives :: Expression -> [Binding] -> ([Binding], [Binding])
      withoutRhosInPrimitives _ [] = ([], [])
      withoutRhosInPrimitives obj@(BaseObject label) bds@(rho@(BiTau AtRho _) : rest)
        | label `elem` primitives =
            let (bds', rhos) = withoutRhosInPrimitives obj rest
             in (bds', rho : rhos)
        | otherwise = (bds, [])
      withoutRhosInPrimitives obj@(BaseObject label) bds@(bd : rest)
        | label `elem` primitives =
            let (bds', rhos) = withoutRhosInPrimitives obj rest
             in (bd : bds', rhos)
        | otherwise = (bds, [])
      withoutRhosInPrimitives _ bds = (bds, [])
      applicationToPrimitive :: Expression -> Int -> [Binding] -> EXPRESSION
      applicationToPrimitive (DataNumber bts) tabs = EX_NUMBER (btsToNum bts) (TAB tabs)
      applicationToPrimitive (DataString bts) tabs = EX_STRING (btsToStr bts) (TAB tabs)
      -- Here we unroll nested application sequence into flat structure
      -- The returned tuple consists of:
      -- 1. deepest start expression
      -- 2. list of tau bindings which are applied to start expression
      -- 3. list of expressions which are applied to start expression with default
      --    alpha attributes (~0 -> e1, ~1 -> e2, ...)
      complexApplication :: Expression -> (Expression, [Binding], [Expression])
      complexApplication expr =
        let (expr', taus', exprs') = complexApplication' expr
         in (expr', reverse taus', reverse exprs')
        where
          complexApplication' :: Expression -> (Expression, [Binding], [Expression])
          complexApplication' (ExApplication (ExApplication expr tau) tau') =
            let (before, taus, exprs) = complexApplication' (ExApplication expr tau)
                taus' = tau' : taus
             in if null exprs
                  then (before, taus', [])
                  else case tau' of
                    BiTau (AtAlpha idx) expr' ->
                      if idx == fromIntegral (length exprs)
                        then (before, taus', expr' : exprs)
                        else (before, taus', [])
                    _ -> (before, taus', [])
          complexApplication' (ExApplication expr (BiTau (AtAlpha 0) expr')) = (expr, [BiTau (AtAlpha 0) expr'], [expr'])
          complexApplication' (ExApplication expr tau) = (expr, [tau], [])

instance ToCST [Expression] APP_ARG where
  toCST (expr : exprs) tabs eol = APP_ARG (toCST expr tabs eol) (toCST exprs tabs eol)

instance ToCST [Expression] APP_ARGS where
  toCST [] _ _ = AAS_EMPTY
  toCST (expr : exprs) tabs eol = AAS_EXPR eol (tabOfEOL eol tabs) (toCST expr tabs eol) (toCST exprs tabs eol)

instance ToCST [Binding] BINDING where
  toCST [] tabs _ = BI_EMPTY (TAB tabs)
  toCST (BiMeta mt : bds) tabs eol = BI_META (MT_BINDING (tail mt)) (toCST bds tabs eol) (tabOfEOL eol tabs)
  toCST (bd : bds) tabs eol = BI_PAIR (toCST bd tabs eol) (toCST bds tabs eol) (tabOfEOL eol tabs)

instance ToCST [Binding] BINDINGS where
  toCST [] tabs _ = BDS_EMPTY (TAB tabs)
  toCST (BiMeta mt : bds) tabs eol = BDS_META eol (tabOfEOL eol tabs) (MT_BINDING (tail mt)) (toCST bds tabs eol)
  toCST (bd : bds) tabs eol = BDS_PAIR eol (tabOfEOL eol tabs) (toCST bd tabs eol) (toCST bds tabs eol)

instance ToCST Binding PAIR where
  toCST (BiTau attr exp@(ExFormation bds)) tabs eol =
    let voids' = voids bds
        attr' = toCST attr tabs eol
     in if null voids'
          then PA_TAU attr' ARROW (toCST exp tabs eol)
          else
            let (_voids, _bds) = if length voids' == length bds && last voids' == AtRho then (init voids', []) else (voids', drop (length voids') bds)
             in PA_FORMATION
                  attr'
                  (map (\at -> toCST at tabs eol) _voids)
                  ARROW
                  (toCST (ExFormation _bds) tabs eol)
    where
      voids :: [Binding] -> [Attribute]
      voids [] = []
      voids (bd : bds) = case bd of
        BiVoid attr -> attr : voids bds
        _ -> []
  toCST (BiTau attr exp) tabs eol = PA_TAU (toCST attr tabs eol) ARROW (toCST exp tabs eol)
  toCST (BiVoid attr) tabs eol = PA_VOID (toCST attr tabs eol) ARROW EMPTY
  toCST (BiDelta bts) tabs eol = PA_DELTA (toCST bts tabs eol)
  toCST (BiLambda func) _ _ = PA_LAMBDA func
  toCST (BiMetaLambda mt) _ _ = PA_META_LAMBDA (MT_FUNCTION (tail mt))

instance ToCST Binding APP_BINDING where
  toCST bd@(BiTau _ _) tabs eol = APP_BINDING (toCST bd tabs eol :: PAIR)

instance ToCST Bytes BYTES where
  toCST BtEmpty _ _ = BT_EMPTY
  toCST (BtOne byte) _ _ = BT_ONE byte
  toCST (BtMany bts) _ _ = BT_MANY bts
  toCST (BtMeta mt) _ _ = BT_META (MT_BYTES (tail mt))

instance ToCST Attribute ATTRIBUTE where
  toCST (AtLabel label) _ _ = AT_LABEL label
  toCST (AtAlpha idx) _ _ = AT_ALPHA ALPHA idx
  toCST AtPhi _ _ = AT_PHI PHI
  toCST AtRho _ _ = AT_RHO RHO
  toCST AtDelta _ _ = AT_DELTA DELTA
  toCST AtLambda _ _ = AT_LAMBDA LAMBDA
  toCST (AtMeta mt) _ _ = AT_META (MT_ATTRIBUTE (tail mt))

inlinedEOL :: Bool -> EOL
inlinedEOL True = NO_EOL
inlinedEOL False = EOL

tabOfEOL :: EOL -> Int -> TAB
tabOfEOL EOL indent = TAB indent
tabOfEOL NO_EOL _ = TAB'

class HasEOL a where
  hasEOL :: a -> Bool

instance HasEOL [Binding] where
  hasEOL [] = False
  hasEOL (bd : rest) = hasEOL bd || hasEOL rest

instance HasEOL Binding where
  hasEOL (BiTau _ expr) = hasEOL expr
  hasEOL bd = False

instance HasEOL [Expression] where
  hasEOL [] = False
  hasEOL (expr : rest) = hasEOL expr || hasEOL rest

instance HasEOL Expression where
  hasEOL (ExFormation []) = False
  hasEOL (ExFormation _) = True
  hasEOL (DataNumber _) = False
  hasEOL (DataString _) = False
  hasEOL (BaseObject _) = False
  hasEOL (ExDispatch expr _) = hasEOL expr
  hasEOL (ExApplication expr tau) = hasEOL expr || hasEOL tau
  hasEOL expr = False