packages feed

qute-syntax-0.1.0: src/Language/QBE.hs

-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>
--
-- SPDX-License-Identifier: GPL-3.0-only

-- | This module provides top-level definitions for representing programs
-- written in the [QBE](https://c9x.me/compile/) intermediate representation.
module Language.QBE
  ( Program,
    Definition (..),
    globalFuncs,
    Language.QBE.parse,
    ExecError (..),
    parseAndFind,
  )
where

import Control.Monad.Catch (Exception, MonadThrow, throwM)
import Data.List (find)
import Data.Maybe (mapMaybe)
import Language.QBE.Parser (dataDef, fileDef, funcDef, skipInitComments, typeDef)
import Language.QBE.Types (DataDef, FuncDef, GlobalIdent, TypeDef, fName)
import Text.ParserCombinators.Parsec
  ( ParseError,
    Parser,
    SourceName,
    choice,
    eof,
    many,
    parse,
    try,
  )

-- | A QBE program consists of a sequence of definitions. Four types of objects
-- can be defined: aggregate types, data, functions, and debugging information.
--
-- See also: The corresponding section of the [QBE specification](https://c9x.me/compile/doc/il-v1.2.html#Definitions).
data Definition
  = -- | Definition of data (e.g. a string).
    DefData DataDef
  | -- | Definition of an aggregate data type.
    DefType TypeDef
  | -- | Definition of a function.
    DefFunc FuncDef
  | -- | Definition of a debug file.
    DefFile String
  deriving (Eq, Show)

parseDef :: Parser Definition
parseDef =
  choice
    [ DefType <$> typeDef,
      -- Need to try funcDef as both funcDef and
      -- dataDef start with a linkage definition.
      --
      -- TODO: Try parsing linkage then funcDef <|> dataDef.
      DefData <$> try dataDef,
      DefFunc <$> funcDef,
      DefFile <$> fileDef
    ]

-- | A parsed QBE program, represented as a list of 'Definition' values.
type Program = [Definition]

-- | Wrapper to parse a QBE program using 'Text.ParserCombinators.Parsec.parse'.
parse :: SourceName -> String -> Either ParseError Program
parse =
  Text.ParserCombinators.Parsec.parse
    (skipInitComments *> many parseDef <* eof)

-- | Utility function to obtain all functions defined in a QBE 'Program'.
globalFuncs :: Program -> [FuncDef]
globalFuncs = mapMaybe globalFuncs'
  where
    globalFuncs' :: Definition -> Maybe FuncDef
    globalFuncs' (DefFunc f) = Just f
    globalFuncs' _ = Nothing

------------------------------------------------------------------------

-- | Custom 'Exception' used for error handling in 'parseAndFind'.
data ExecError
  = -- | The input is not a valid QBE program.
    ESyntaxError ParseError
  | -- | The given entry function is not defined in the QBE program.
    EUnknownEntry GlobalIdent
  deriving (Show)

instance Exception ExecError

-- | Utility function for the common task of parsing an input as a QBE
-- 'Program' and, within that program, finding the entry function. If the
-- function doesn't exist or a the input is invalid a 'ExecError' exception
-- is thrown.
parseAndFind ::
  (MonadThrow m) =>
  GlobalIdent ->
  String ->
  m (Program, FuncDef)
parseAndFind entryIdent input = do
  prog <- case Language.QBE.parse "" input of -- TODO: file name
    Right rt -> pure rt
    Left err -> throwM $ ESyntaxError err

  let funcs = globalFuncs prog
  func <- case find (\f -> fName f == entryIdent) funcs of
    Just x -> pure x
    Nothing -> throwM $ EUnknownEntry entryIdent

  pure (prog, func)