This is your task, I have provided both the instructions and the scaffold, implement it:

instructions:

please help me implement this correctly:
we will use Haskell to develop a transpiler that converts Markdown strings into HyperText Markup Language (HTML). This task involves parsing Markdown syntax and generating corresponding HTML output. A web page is provided, in which Markdown will be sent through an HTML-based websocket connection to a Haskell backend server, the Haskell server will need to convert this Markdown into the corresponding HTML and return it back to the website. A skeleton code was provided which will handle the basic communication between the web page and your assignment code. You are encouraged to utilise materials covered in previous weeks, including solutions for tutorial questions, to aid in the development of your transpiler. You must reference or cite ideas and code constructs obtained from external sources, as well as anything else you might find in your independent research, for this assignment. The assignment is split up into Part A (parsing), Part B (pretty printing) and Part C (extras). However, we do recommend completing Part A/Part B in tandem. The language you will parse will be based on the Markdown specification, however with additional restrictions to reduce ambiguity. It is important that you read the requirements of each exercise carefully to avoid unnecessary work. Goals / Learning Outcomes The purpose of this assignment is to highlight and apply the skills you have learned to a practical exercise (parsing): ● Use functional programming and parsing effectively ● Understand and be able to use key functional programming principles (higher order functions, pure functions, immutable data structures, abstractions) ● Apply Haskell and FP techniques to parse non-trivial Markdown text Scope of assignment You are only required to parse an expression into the necessary data types and convert the result to an HTML string such that it can be rendered using an existing interpreter. You will not be required to render the Markdown or HTML strings.Exercises (24 marks) These exercises provide a structured approach for creating the beginnings of a transpiler. ● Part A (12 marks): Parsing Markdown strings ● Part B (6 marks): Conversion between Markdown and HTML ● Part C (6 marks): Adding extra functionality to the webpage. ● (Extension) Part D Part E: extensions for bonus marks! You must parse the input into an intermediary representation (ADT) such as an Abstract Syntax Tree to receive marks. This will allow easy conversion between your ADT and HTML. You must add deriving Show to your ADT and all custom types your ADT contains. (Note that the skeleton code already has deriving Show on the ADT type for you, which you must not remove.) You must not override this default Show instance as this will help us test your code. Your Assignment.hs file must export the following functions: ● markdownParser :: Parser ADT ● convertADTHTML :: ADT -> String Example Scripts For each of these exercises, there will be a series of provided Markdown files. By running stack test, it will try to parse the Markdown and save the output to a folder. This will generate HTML which you can manually view for correctness in a browser. During marking, we will be running your transpiler on more complex examples than the provided example scripts, therefore, it is important you devise your own test cases to ensure your parser is valid on more complex Markdown. It will also aim to produce a git diff, which is the difference between your output and the expected output. However, this requires installing the git command line tool. So, ensure that it is installed. Furthermore, the more recommended way to test your code will be to use npm run dev in combination with stack run main can be used to run the webpage with a live editor, running your code in real-time.Part A: (12 marks): Parsing Markdown The first part of this task, requires you to parse a markdown string into an Algebraic Data Type (ADT). This requires you to define your own Algebraic Data Type and define a series of functions that parse everything in the requirements. Consider that you will need to convert the result to HTML and therefore, your ADT should have enough information to assist you in converting to HTML. Aside - Text Modifiers (2 marks) There are six different modifiers for inline text, which can change the way a markdown string will be rendered. You do not have to worry about any escape characters. All text modifiers will need to be strictly non-empty. ● Italic Text: Specified by a single underscore character, _. For example, italics ● Bold Text: Specified by a set of two asterisks, **, around a word. For example, bold ● Strikethrough: Specified by two tilde characters, ~~, around a word. For example, strikethrough ● Link: Users can include a link to an external page using link text. For example, click here. You do not need to consider links inside links. ● Inline Code: Users can include code in the middle of sentences, using a backtick character, . For example, there is code` here ● Footnotes: Users can indicate a footnote with [^ℤ
], where ℤ

= {1,2,3,…}, i.e., any positive integer. For example, 1, 2 and so forth. Note that you do not need to validate any sort of ordering on these numbers, e.g., the markdown may only contain one footnote [^10]. You also do not need to validate that the footnote comes with an appropriate reference (see Footnote References). ○ Note that there must not be any whitespace inside the [ and ]. For example, 1, 2, and [ ^3] are all not valid footnotes. You do not need to consider text with nested modifiers, such as bold and italics. Unless specified otherwise, the text inside the modifiers can include any amount of whitespace (excluding new lines). For example, _ italics _, **bold **, ~~ strikethrough~~, inline code, and link text, and [link text] (example.com) are all valid.Images (0.5 marks) An image is specified with three parts:
The Alt Text is the alternative text for the image, which is displayed if the image fails to load or for accessibility purposes.
The URL is the URL or path to the image file. This can be a web URL or a local file path. The URL cannot contain any whitespace.
The Caption Text is the caption for the image. Alt Text The alternative text, caption text, and URL should not consider the text modifiers. An image must be at the beginning of a line, and the exclamation mark (!) character may be preceded by zero or more (non-newline) whitespace characters. There must be at least one non-newline whitespace character between the URL and the caption text. For example, ![Alt Text](URL"Caption Text") is not a valid image. There must not be any spaces after the ! and before the [. Footnote References (0.5 marks) Similarly to footnotes, footnote references consist of at the beginning of a line: ● zero or more (non-newline) whitespace characters, followed by ● [^], where ℤ
= {1,2,3,…}, i.e., any positive integer, followed by ● a colon (:), followed by ● some text. Note that this text will not include the text modifiers. Leading whitespace before the text should be ignored.
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
This heading has a space before the hash
Note that because at least one non-newline whitespace character is required, this is not a valid heading: #Heading 1. Also, because the line must start with the hash characters (or whitespace), the following is not a valid heading: abc # Heading. Alternatively, Heading 1 and Heading 2 can be specified with an alternative syntax (shown below). On the line below the text, add at least 2 equals sign (=) characters for heading level 1 or at least 2 dash (-) characters for heading level 2. The line below the text must not contain any other characters. There is no alternative syntax for any other heading levels. The heading text, equals sign (=) characters and dash (-) characers may be preceded by zero or more (non-newline) whitespace characters. Alternative Heading 1
Heading level 2
A heading 1 with a space in front of it==== Importantly, headings may include any of the previously mentioned text modifiers, for example, a heading can be bolded, by surrounding it with a double asterisk.

Bolded Heading 1
Blockquotes (1 mark) To create a block quote in Markdown, you use the greater than symbol (>) at the beginning of a line followed by the text you want to quote. The greater than symbol (>) may be preceded by zero or more (non-newline) whitespace characters before it on the same line. You can also include multiple lines of text within the same block quote by starting each consecutive line with the greater than symbol (>). Leading whitespace after the greater than symbol (>) and before the text should be ignored. The text inside the block quote may have text modifiers. You do not need to consider nested blo代 写FIT2102、Java/Python 代做程序编程语言ck quotes. For example:

This is a block quote. It can span multiple lines. This has a space before > and is also a block quote Code (1 mark) A code block in Markdown starts with three backticks (```) on a line by themselves, followed by an optional language identifier. The code block ends with another three backticks on a line by themselves. The code block should not consider the text modifiers. The first three backticks may have zero or more (non-newline) whitespace characters preceding it. An example code block is:

main :: IO ()
main = do
putStrLn "Never gonna give you up"
putStrLn "Never gonna let you down"
putStrLn "Never gonna run around and desert you"
Never gonna let you cryNever gonna say goodbye
Never gonna tell a lie and hurt you
Ordered Lists (2 marks) An ordered list consists of at least one ordered list item separated by exactly 1 new line character. An ordered list item starts with a positive number at the beginning of a line, a . (full stop) character, and at least one whitespace character (excluding new lines). An ordered list must start with the number 1, and any number after that can appear. You do not have to consider any other numbering system or an unordered list. Ordered lists may contain sublists, where there will be exactly 4 spaces before each ordered list item. Each sublist must also start with the number 1. Similar to previous sections, list items may also contain text modifiers.

Item 1
Sub Item 1
Sub Item 2
Sub Item 3
Bolded Item 2
Item 3
Item 4 You do not have to handle unordered lists. Ordered lists must not have any whitespace before the number, unless it is the 4 spaces of indentation for a sublist. For example:
This is an ordered list
This is not an ordered list (starts with 2 spaces) Tables (3 marks) To create a table in Markdown, you use pipes (|) to separate columns and at least three dashes (-) between each column to separate the header row from the content rows. Each column may contain varying amounts of dashes. Each row is written on a separate line. The beginning and ending pipes (|) are compulsory. Each row must have the same amount of columns. Each cell may also contain text with the text modifiers. Leading and trailing whitespace before and after the text in each cell should be ignored. Each row in the table may be preceded by zero or more (non-newline) whitespace characters.| Tables | Are | Cool | | ------------- | ------------- | ----- | | here | is | data | | here | is | data | | here | is also | bolded data | | also | part of the | table | Part B: (6 marks): HTML Conversion The second part of this task requires you to convert your ADT into a HTML representation. The resulting HTML file must be formatted such that it is indented with 4 spaces at the correct level to reflect the tree structure of HTML, ensuring that the HTML is valid and correctly renders the provided markdown. You do not need to indent the text modifiers, but other nested objects should be indented correctly. All HTML generated must be a self-contained webpage, i.e., including the following information, placing all generated HTML within the tags.
Test

GENERATED CONTENT GOES HERE

As a reference for the conversion between markdown and HTML, here will be listed the conversion of all of the examples from above. Text Modifiers (1 mark) ● Italics: italics ● Bold: bold ● Strikethrough: strikethrough ● Link: link text ● Inline Code: code● Footnotes: 1. It is important that you follow this convention precisely, where 1 is the number specified with the footnote, to ensure the footnotes work. Images (0.5 marks) The image must be in an image tag, with the appropriate attributes filled.

Footnote References (0.5 marks) A footnote reference must be encased in a tag, and have the appropriately numbered id. My reference. Another reference. The 2 spaces after the colon should be ignored space before the [ Free Text (0.5 marks) Every line of free text must be encased in tags. You do not need to consider how to handle newlines.

Here is some markdown

More lines here

Text Headings (0.5 marks) Where, the number after the h, contains the level of the heading, for example, in heading level 1: Heading 1 Heading 2 Heading 3 Heading 4 Heading 5 Heading 6 This heading has a space before the hash Alternative Heading 1Heading level 2 A heading 1 with a space in front of it Blockquotes (0.5 marks) Each blockquote must be encased by , while each line within the blockquote must be encased with a tag.

This is a block quote.

It can span multiple lines.

Code (0.5 marks) The code block must be encased in both the and the tags. If there is a language identifier (e.g., haskell), it must be included within the class attribute, prefixed by language-. Otherwise, there should not be any class attribute. The newlines and code indentation must remain. main :: IO () main = do putStrLn "Never gonna give you up" putStrLn "Never gonna let you down" putStrLn "Never gonna run around and desert you"

Never gonna let you cry Never gonna say goodbye Never gonna tell a lie and hurt you

Ordered Lists (1 marks) Ordered lists must begin and end with the tag, and each list item must begin and end with the opening/closing tag.

Item 1

Sub Item 1 Sub Item 2 Sub Item 3

Bolded Item 2 Item 3 Item 4

Tables (1 mark) The HTML convention for representing tables involves using the , , , and elements. represents the entire table, represents a row within the table, represents a header cell within a table row, used for the header row, and represents a data cell within a table row, used for the content rows. You may optionally include and tags. Either of these outupts is acceptable:

Tables Are Cool

here is data

here is data

here is also bolded data

also part of the table

Tables Are Cool

here is data

here is data

here is also bolded data

also part of the table

Part C (6 marks): Adding extra functionality to the webpage This task involves changing the webpage to include extra capabilities allowing a more feature-full UI. You will not be marked on the layout, or ease of use of features, as long as they are clearly visible to your marker, e.g., a button should be clearly visible on the screen. This task will involve some light additions to both the HTML page and TypeScript code. This will likely involve creating an observable stream for the data, merging it into the subscription stream, and sending the information to the Haskell backend. The communicated information between the Haskell backend and the webpage will need to be updated to include additional information that the user wants the engine to achieve. ● A button must be added to the webpage for saving, where the converted HTML is saved using Haskell. The user does not need to be prompted for a file name, and the HTML should be saved according to the current time, formatted in ISO 8601 format for the current date and time: YYYY-MM-DDTHH:MM:SS. The function getTime is provided which will provide you this time in an IO String format. ● A separate input box, to allow the user to change the title of the page, instead of the default Converted HTML.Part D (up to 6 bonus marks): Extension Implement anything that is interesting, impressive, or otherwise “shows off” your understanding of Haskell, Functional Programming, and/or Parsing. To achieve the maximum amount of bonus marks, the feature should be similar in complexity to Part C (6 marks): The bonus marks only apply to this assignment, and the final mark for this assignment is capped at 30 marks (100%). This means you cannot score more than 30 marks or 100%. Some suggestions for extensions of varying complexity and difficulty: ● Markdown validation ○ E.g., enforce all table columns have the same width ● Correct BNF for the Markdown you are parsing in report (worth 2 marks) ○ For any part of the parser which is not context-free, you may simplify the parsing rules to be context-free. ● Further extensions to the webpage for extra features, using RxJS ● Parse nested text modifiers, such as bold and italics and click here ● Parse further parts of the markdown specification which make use of interesting parsers, which you have not used in other parts of the assignment. ● Comprehensive test cases over the parser and pretty printing ○ Warning: It is super hard to be comprehensive, stay away unless you love testing. (Choosing one of the simpler suggestions to implement may not receive the maximum available marks).Report (2 marks) You are required to provide a report in PDF format of max. 600 words (markers will not mark beyond this word limit). Descriptions of extensions can use up to 200 words per extension feature. Make sure to summarise the intention of the code, and highlight the interesting parts and difficulties you encountered. Focus on the "why" not the "how". Additionally, just posting screenshots of code is heavily discouraged, unless it contains something of particular importance. Remember, markers will be looking at your code alongside your report, so we do not need to see your code twice. Importantly, this report must include a description of why and how parser combinators helped you complete the parsing. In summary, your report should include the following sections: ● Design of the code (including data structures) ○ High-level description of approach ○ High-level structure of code ○ Code architecture choices ● Parsing ○ Usage of parser combinators ○ Choices made in creating parsers and parser combinators ○ How parsers and parser combinators were constructed using the Functor, Applicative, and Monad typeclasses ● Functional Programming (focusing on the why) ○ Small modular functions ○ Composing small functions together ○ Declarative style (including point free style) ● Haskell Language Features Used (focusing on the why) ○ Typeclasses and Custom Types ○ Higher order functions, fmap, apply, bind ○ Function composition ● Description of Extensions (if applicable) ○ What you intended to implement ○ What you did implement ○ What is cool/interesting/complex about it ○ This may include using Haskell features that are not covered in course content There is some overlap between the sections. You should avoid repeating descriptions or ideas in the report.Code Quality (4 marks) Code quality will relate more to how understandable your code is. You must have readable and functional code, commented when necessary. Readable code means that you keep your lines at a reasonable length (>=) for the sake of using the Monad when it is not needed will not count as "effective usage." Most importantly, code that does not utilise Haskell's language features, and that attempts to code in a more imperative style, will not be awarded high marks. Minimum Requirements: An estimate of a passing grade will be parsing up to and including code blocks, but not lists or tables, where the difficulty and the marks step up. However, this will need to be accompanied by high code quality and a good report. A higher mark will require parsing of the more difficult data structures, and modifications of the HTML page.Changelog ● Add note that text modifiers must be non-empty ● Add note about BNF can simplify parser, if and only if the parser is not context free. ● 18 Sep: Remove the requirement to parse nested text modifiers and instead make that an extension ● 18 Sep: Fix issue in scaffold where frontend would show output HTML with a leading and trailing quote ● 20 Sep: Changed “Abstract Data Type” to “Algebraic Data Type” (under Part A) ● 24 Sep: Clarify that URLs in images should not consider text modifiers ● 25 Sep: Clarify that there should be no spaces after ! and before [ in images ● 25 Sep: Clarify whitespace rules for images, footnote references, headings, blockquotes, code blocks, and tables ● 25 Sep: Specify how to convert a code block with no language identifier to HTML ● 28 Sep: Allow optionally including and when rendering tables ● 29 Sep: Fix indentation in ordered list HTML output


Scaffold:

assignment.hs:

module Assignment (markdownParser, convertADTHTML) where


import           Data.Time.Clock  (getCurrentTime)
import           Data.Time.Format (defaultTimeLocale, formatTime)
import           Instances        (Parser (..))
import           Parser           ()

data ADT = Empty
  -- Your ADT **must** derive Show.
  deriving (Show, Eq)

markdownParser :: Parser ADT
markdownParser = pure Empty

getTime :: IO String
getTime = formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" <$> getCurrentTime

convertADTHTML :: ADT -> String
convertADTHTML Empty = "IMPLEMENT_THIS"


Parser.hs:

{-# OPTIONS_GHC -Wno-missing-export-lists #-}

-- | Implementation of a parser-combinator.
module Parser where

import           Control.Applicative
import           Data.Char           (isAlpha, isDigit, isLower, isSpace,
                                      isUpper)
import           Instances           (ParseError (..), ParseResult (..),
                                      Parser (..), readInt)

-- $setup
-- >>> import Instances (isErrorResult, parse)


-- | -------------------------------------------------
-- | --------------- Core parsers --------------------
-- | -------------------------------------------------

-- | Return a parser that always fails with the given error.
--
-- >>> isErrorResult (parse (failed UnexpectedEof) "abc")
-- True
failed :: ParseError -> Parser a
failed = Parser . const . Error

-- | Produces a parser that always fails with 'UnexpectedChar' using the given
-- character.
unexpectedCharParser :: Char -> Parser a
unexpectedCharParser = Parser . const . Error . UnexpectedChar

-- | Return a parser that succeeds with a character off the input or fails with
-- an error if the input is empty.
--
-- >>> parse char "abc"
-- Result >bc< 'a'
--
-- >>> isErrorResult (parse char "")
-- True
char :: Parser Char
char = Parser f
  where
    f ""       = Error UnexpectedEof
    f (x : xs) = Result xs x

-- | Parse numbers as int until non-digit

---- >>> parse int "abc"
-- Result >bc< 'a'
--
-- >>> isErrorResult (parse int "")
-- True
--
-- >>> isErrorResult (parse int "a")
-- True
int :: Parser Int
int = Parser f
  where
    -- This is okay because the case statement is small
    f "" = Error UnexpectedEof
    f x = case readInt x of
      Just (v, rest) -> Result rest v
      Nothing        -> Error $ UnexpectedChar (head x)

-- | Write a parser that asserts that there is no remaining input.
--
-- >>> parse eof ""
-- Result >< ()
--
-- >>> isErrorResult (parse eof "abc")
-- True
eof :: Parser ()
eof = Parser f
  where
    f "" = Result "" ()
    f x  = Error $ ExpectedEof x

-- | -------------------------------------------------
-- | --------------- Satisfy parsers -----------------
-- | -------------------------------------------------
-- | All of these parsers use the `satisfy` parser!
-- | Return a parser that produces a character but fails if:
--
--   * the input is empty; or
--
--   * the character does not satisfy the given predicate.
--
-- >>> parse (satisfy isUpper) "Abc"
-- Result >bc< 'A'
--
-- >>> isErrorResult (parse (satisfy isUpper) "abc")
-- True
satisfy :: (Char -> Bool) -> Parser Char
satisfy f = char >>= f'
  where
    -- This is okay because guards are small
    f' c
      | f c = pure c
      | otherwise = unexpectedCharParser c

-- | Return a parser that produces the given character but fails if:
--
--   * the input is empty; or
--
--   * the produced character is not equal to the given character.
--
-- >>> parse (is 'c') "c"
-- Result >< 'c'
--
-- >>> isErrorResult (parse (is 'c') "")
-- True
--
-- >>> isErrorResult (parse (is 'c') "b")
-- True
is :: Char -> Parser Char
is = satisfy . (==)

-- | Return a parser that produces any character but fails if:
--
--   * the input is empty; or
--
--   * the produced character is equal to the given character.
--
-- >>> parse (isNot 'c') "b"
-- Result >< 'b'
--
-- >>> isErrorResult (parse (isNot 'c') "")
-- True
--
-- >>> isErrorResult (parse (isNot 'c') "c")
-- True
isNot :: Char -> Parser Char
isNot = satisfy . (/=)

-- | Write a function that parses one of the characters in the given string.
--
-- /Hint/: What does `elem` do? What are its parameters?
--
-- >>> parse (oneof "abc") "bcdef"
-- Result >cdef< 'b'
--
-- >>> isErrorResult (parse (oneof "abc") "def")
-- True
oneof :: String -> Parser Char
oneof = satisfy . flip elem

-- | Write a function that parses any character, but fails if it is in the
-- given string.
--
-- /Hint/: What does `notElem` do? What are its parameters?
--
-- >>> parse (noneof "bcd") "abc"
-- Result >bc< 'a'
--
-- >>> isErrorResult (parse (noneof "abcd") "abc")
-- True
noneof :: String -> Parser Char
noneof = satisfy . flip notElem

-- | Return a parser that produces a character between '0' and '9' but fails if
--
--   * the input is empty; or
--
--   * the produced character is not a digit.
--
-- /Hint/: Use the 'isDigit' function
digit :: Parser Char
digit = satisfy isDigit

-- | Return a parser that produces a space character but fails if
--
--   * the input is empty; or
--
--   * the produced character is not a space.
--
-- /Hint/: Use the 'isSpace' function
space :: Parser Char
space = satisfy isSpace

-- | Return a parser that produces a lower-case character but fails if:
--
--   * the input is empty; or
--
--   * the produced character is not lower-case.
--
-- /Hint/: Use the 'isLower' function
lower :: Parser Char
lower = satisfy isLower

-- | Return a parser that produces an upper-case character but fails if:
--
--   * the input is empty; or
--
--   * the produced character is not upper-case.
--
-- /Hint/: Use the 'isUpper' function
upper :: Parser Char
upper = satisfy isUpper

-- | Return a parser that produces an alpha character but fails if:
--
--   * the input is empty; or
--
--   * the produced character is not alpha.
--
-- /Hint/: Use the 'isAlpha' function
alpha :: Parser Char
alpha = satisfy isAlpha

-- | Write a parser that will parse zero or more spaces (including newlines)
--
-- /Hint/: Remember the `space` parser!
--
-- >>> parse spaces " abc"
-- Result >abc< " "
--
-- >>> parse spaces "abc"
-- Result >abc< ""
spaces :: Parser String
spaces = many space

-- | Return a parser that produces one or more space characters (consuming
-- until the first non-space) but fails if:
--
--   * the input is empty; or
--
--   * the first produced character is not a space.
--
-- /Hint/: Remember the `space` parser!
--
-- >>> parse spaces1 " abc"
-- Result >abc< " "
--
-- >>> isErrorResult $ parse spaces1 "abc"
-- True
spaces1 :: Parser String
spaces1 = some space


-- | Write a parser that will parse zero or more spaces (not including newlines)
--
-- The possible whitespace characters: \t, \r, \f, \v, and a space character.
--
-- >>> parse inlineSpace " abc"
-- Result >abc< " "
--
-- >>> parse inlineSpace "abc"
-- Result >abc< ""
inlineSpace :: Parser String
inlineSpace = many (oneof "\t\r\f\v ")

-- | Write a function that parses the given string (fails otherwise).
--
-- /Hint/: Use 'is' and 'traverse'.
--
-- >>> parse (string "abc") "abcdef"
-- Result >def< "abc"
--
-- >>> isErrorResult (parse (string "abc") "bcdef")
-- True
string :: String -> Parser String
string = traverse is

-- | -------------------------------------------------
-- | --------------- Token parsers -------------------
-- | -------------------------------------------------

-- | Write a function that applies the given parser, then parses 0 or more
-- spaces, then produces the result of the original parser.
--
-- /Hint/: You can use the Monad instance or Applicatives
--
-- >>> parse (tok (is 'a')) "a bc"
-- Result >bc< 'a'
--
-- >>> parse (tok (is 'a')) "abc"
-- Result >bc< 'a'
tok :: Parser a -> Parser a
tok = (<* spaces)

-- tok p = do
--   r <- p
--   spaces
--   pure r

-- | Write a function that parses the given char followed by 0 or more spaces.
--
-- /Hint/: Remember the `is` parser
--
-- >>> parse (charTok 'a') "abc"
-- Result >bc< 'a'
--
-- >>> isErrorResult (parse (charTok 'a') "dabc")
-- True
charTok :: Char -> Parser Char
charTok = tok . is

-- | Write a parser that parses a comma ',' followed by 0 or more spaces.
--
-- /Hint/: We just implemented `charTok`
--
-- >>> parse commaTok ",123"
-- Result >123< ','
--
-- >>> isErrorResult( parse commaTok "1,23")
-- True
commaTok :: Parser Char
commaTok = charTok ','

-- | Write a function that parses the given string, followed by 0 or more
-- spaces.
--
-- /Hint/: Remember the `string` parser
--
-- >>> parse (stringTok "abc") "abc  "
-- Result >< "abc"
--
-- >>> isErrorResult (parse (stringTok "abc") "bc  ")
-- True
stringTok :: String -> Parser String
stringTok = tok . string


Instances.hs:

{-# LANGUAGE InstanceSigs #-}

module Instances (ParseResult (Result, Error), Parser (..), readInt, ParseError(..), isErrorResult) where

import           Control.Applicative (Alternative (..))

-- $setup
-- >>> let p = \n -> Parser (\x -> Result x n)
-- >>> let add = \n -> \m -> Parser (\x -> Result x (n+m))

data ParseError
  = UnexpectedEof
  | ExpectedEof Input
  | UnexpectedChar Char
  | UnexpectedString String
  deriving (Eq, Show)

data ParseResult a
  = Error ParseError
  | Result Input a
  deriving (Eq)

type Input = String

newtype Parser a = Parser {parse :: Input -> ParseResult a}

-- Result Instances

instance Show a => Show (ParseResult a) where
  show :: ParseResult a -> String
  show (Result i a) = "Result >" ++ i ++ "< " ++ show a
  show (Error UnexpectedEof) = "Unexpected end of stream"
  show (Error (UnexpectedChar c)) = "Unexpected character: " ++ show [c]
  show (Error (UnexpectedString s)) = "Unexpected string: " ++ show s
  show (Error (ExpectedEof i)) =
    "Expected end of stream, but got >" ++ show i ++ "<"

instance Functor ParseResult where
  fmap :: (a -> b) -> ParseResult a -> ParseResult b
  fmap f (Result i a) = Result i (f a)
  fmap _ (Error e)    = Error e

-- Parser Instances

-- | Functor instance for a parser
--
-- >>> parse ((+1) <$> Parser (`Result` 1)) "hello"
-- Result >hello< 2

--  >>> parse ((+1) <$> Parser (const (Error UnexpectedEof))) "hello"
-- Unexpected end of stream
instance Functor Parser where
  fmap :: (a -> b) -> Parser a -> Parser b
  fmap f (Parser p) = Parser (fmap f . p)

-- | Implement this before Applicative Parser!
--
-- >>> parse (p 1 >>= add 2) ""
-- Result >< 3
--
-- prop> \i j -> parse (p i >>= add j) "" == Result "" (i + j)
instance Monad Parser where
  (>>=) :: Parser a -> (a -> Parser b) -> Parser b
  (>>=) (Parser p) f = Parser $ \i -> case p i of
    Result rest x -> parse (f x) rest
    Error e       -> Error e

-- |
--
-- >>> parse (Parser (`Result` (+1)) <*> Parser (`Result` 1)) "hello"
-- Result >hello< 2
--
-- >>> parse (pure (+1) <*> Parser (`Result` 1)) "hello"
-- Result >hello< 2
--
-- >>> parse (pure (+1) <*> (pure 1 :: Parser Int)) "hello"
-- Result >hello< 2
instance Applicative Parser where
  -- creates a Parser that always succeeds with the given input
  pure :: a -> Parser a
  pure = Parser . flip Result

  -- or
  (<*>) :: Parser (a -> b) -> Parser a -> Parser b
  (<*>) p q = p >>= (<$> q)

-- |
--
-- 'Alternative' is used for parsers that support choice (alternative) and
-- failure (empty). In the context of this 'Parser' type:
--
-- 1. 'empty' represents a parser that always fails
--
-- 2. '<|>' combines two parsers. It tries the first parser on the input,
-- and if it fails it tries the second parser.
--
-- see https://tgdwyer.github.io/haskell3/#alternative
--
-- >>> parse (pure 1 <|> empty) "world, hello!"
-- Result >world, hello!< 1
-- >>> parse (empty <|> pure 1) "world"
-- Result >world< 1
instance Alternative Parser where
  empty :: Parser a
  empty = Parser $ const (Error UnexpectedEof)

  (<|>) :: Parser a -> Parser a -> Parser a
  (<|>) pa pb = Parser $ \x -> case parse pa x of
    Error _ -> parse pb x
    r       -> r

-- Support functions

isErrorResult :: ParseResult a -> Bool
isErrorResult (Error _) = True
isErrorResult _         = False

readInt :: String -> Maybe (Int, String)
readInt s = case reads s of
  [(x, rest)] -> Just (x, rest)
  _           -> Nothing



Main.hs:

{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

-- import Assignment (markdownParser)

import           Assignment              (convertADTHTML, markdownParser)
import           Data.Aeson              (object, (.=))
import           Data.Aeson.Key          (fromString)
import           Data.Text.Lazy          (Text, pack, unpack)
import           Data.Text.Lazy.Encoding (decodeUtf8)
import           Instances               (ParseResult (Result), parse)
import           Web.Scotty              (ActionM, body, json, post, scotty)

getResult :: ParseResult a -> (a -> String) -> String
getResult (Result _ a) f = f a
getResult _ _            = ""

-- Magic code to convert key, value pairs to JSON to send back to the server
jsonResponse :: [(String, String)] -> ActionM ()
jsonResponse pairs =
  json $ object [fromString key .= ((pack value) :: Text) | (key, value) <- pairs]


main :: IO ()
main = scotty 3000 $ do
  post "/api/convertMD" $ do
    requestBody <- body
    -- Convert the raw request body from ByteString to Text
    let requestBodyText = decodeUtf8 requestBody
        -- Convert the Text to String
        str = unpack requestBodyText
        -- Parse the Markdown string using 'markdownParser' and apply 'convertAllHTML'
        converted_html = getResult (parse markdownParser str) convertADTHTML

    -- Respond with the converted HTML as JSON
    jsonResponse [("html", converted_html)]


Spec.hs:

import           Assignment           (convertADTHTML, markdownParser)
import           Control.Exception    (IOException, try)
import           Control.Monad        (mapM_, (>>), (>>=))
import           Data.List            (isPrefixOf)
import           Instances            (ParseResult (Error, Result), parse)
import           System.FilePath      (joinPath, replaceExtension,
                                       splitDirectories, splitFileName,
                                       takeFileName, (</>))

import           System.Directory     (createDirectoryIfMissing)
import           System.FilePath.Glob (glob)
import           System.IO            (IOMode (WriteMode), withFile)
import           System.IO.Error      (isDoesNotExistError)
import           System.Process       (StdStream (UseHandle), createProcess,
                                       proc, std_out, waitForProcess)

-- Function to replace a substring with another substring in a string
replaceSubstring :: String -> String -> String -> String
replaceSubstring _ _ [] = []
replaceSubstring from to str@(x : xs)
  | from `isPrefixOf` str = to ++ replaceSubstring from to (drop (length from) str)
  | otherwise = x : replaceSubstring from to xs

processFile :: FilePath -> FilePath -> IO ()
processFile inputFile outputFile  = do
  putStrLn $ "Starting " ++ inputFile
  result <- try (readFile inputFile) :: IO (Either IOException String)
  case result of
    Left e
      | isDoesNotExistError e -> putStrLn $ "File not found: " ++ inputFile
      | otherwise -> putStrLn $ "Error reading file " ++ inputFile ++ ": " ++ show e
    Right contents -> do
      let parsed = parse markdownParser contents
      case parsed of
        Result _ adt -> do
          let prettyOutput = convertADTHTML adt
          writeFile outputFile prettyOutput
          putStrLn $ "Processed: " ++ inputFile
        _ -> putStrLn $ "Invalid ParseResult for file " ++ inputFile

mkFolderIfNotExists :: FilePath -> IO ()
mkFolderIfNotExists = createDirectoryIfMissing True


gitDiffNoIndex :: FilePath -> FilePath -> IO ()
gitDiffNoIndex file1 file2 = do
    mkFolderIfNotExists "./examples/diff_output"
    let outputFile = genOutputName "diff_output" "diff" file1

    withFile outputFile WriteMode $ \handle -> do
      -- Create a process for git diff --no-index
      (_, _, _, processHandle) <- createProcess (proc "git" ["--no-pager", "diff", "--no-index", "--no-ext-diff", file1, file2])
                                                   { std_out = UseHandle handle }


      -- Wait for the process to complete but ignore the exit code
      _ <- waitForProcess processHandle
      return ()

genOutputName :: FilePath -> String -> String -> FilePath
genOutputName outputFolder extension file = replaceExtension (replaceFolderAtIndex file 1 outputFolder) extension

replaceFolderAtIndex :: FilePath -> Int -> String -> FilePath
replaceFolderAtIndex filePath index newFolder = joinPath (replaceAtIndex index newFolder (splitDirectories dirPath)) </> fileName
  where
    -- Split the file path into directory and file components
    dirPath = fst $ splitFileName filePath
    fileName = takeFileName filePath

    -- Replace the folder at the specified index using zipWith
    replaceAtIndex :: Int -> String -> [FilePath] -> [FilePath]
    replaceAtIndex idx newVal = zipWith (\i folder -> if i == idx then newVal else folder) [0..]

checkADTShow :: IO ()
checkADTShow = do
  let markdownStr = "test **bold**"
  putStrLn "Testing ADT's Show instance:"
  putStrLn $ "Input string: " ++ markdownStr
  putStrLn $ case parse markdownParser markdownStr of
    Error _      -> "Parse error"
    Result _ adt -> "Result: " ++ show adt

main :: IO ()
main = do
  checkADTShow

  putStrLn ""
  putStrLn "Testing example inputs:"
  files <- glob "examples/input/*.md"
  let outputFiles = map (genOutputName "output" "html") files
  let expectedOutput = map (genOutputName "expected_output" "html") files

  mapM_ (uncurry processFile) (zip files outputFiles)
  mapM_ (uncurry gitDiffNoIndex) (zip expectedOutput outputFiles)





Below are the Html, css and js files:

Main.ts:

import { fromEvent, merge } from "rxjs";
import { map, mergeScan, first } from "rxjs/operators";
import { ajax } from "rxjs/ajax";
import { type Observable } from "rxjs";
import { State } from "./types";

import hljs from "highlight.js/lib/core";

import javascript from "highlight.js/lib/languages/javascript";
import python from "highlight.js/lib/languages/python";
import haskell from "highlight.js/lib/languages/haskell";

// Load the languages from the unit for syntax highlighting!
hljs.registerLanguage("javascript", javascript);
hljs.registerLanguage("python", python);
hljs.registerLanguage("haskell", haskell);

const markdownInput = document.getElementById(
    "markdown-input",
) as HTMLTextAreaElement;
const checkbox = document.querySelector('input[name="checkbox"]')!;

type Action = (_: State) => State;

const resetState: Action = (s) => {
    return { ...s, save: false };
};

const compose =
    <T, U>(g: (_: T) => U) =>
    <V>(f: (_: U) => V) =>
    (t: T): V =>
        f(g(t));

// Create an Observable for keyboard input events
const input$: Observable<Action> = fromEvent<KeyboardEvent>(
    markdownInput,
    "input",
).pipe(
    map((event) => (event.target as HTMLInputElement).value),
    map((value) => (s) => ({ ...s, markdown: value })),
);

const checkboxStream$: Observable<Action> = fromEvent(checkbox, "change").pipe(
    map((event) => (event.target as HTMLInputElement).checked),
    map((value) => (s) => ({ ...s, renderHTML: value })),
);

function getHTML(s: State): Observable<State> {
    // Get the HTML as a stream
    return ajax<{ html: string }>({
        url: "/api/convertMD",
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
        },
        body: s.markdown,
    }).pipe(
        map((response) => response.response), // Extracting the response data
        map((data) => {
            return {
                ...s,
                HTML: data.html,
            };
        }),
        first(),
    );
}

const initialState: State = {
    markdown: "",
    HTML: "",
    renderHTML: true,
    save: false,
};

function main() {
    // Subscribe to the input Observable to listen for changes
    const subscription = merge(input$, checkboxStream$)
        .pipe(
            map((reducer: Action) => {
                // Reset Some variables in the state in every tick
                const newReducer = compose(reducer)(resetState);
                return newReducer;
            }),
            mergeScan((acc: State, reducer: Action) => {
                const newState = reducer(acc);
                // getHTML returns an observable of length one
                // so we `scan` and merge the result of getHTML in to our stream
                return getHTML(newState);
            }, initialState),
        )
        .subscribe((value) => {
            const htmlOutput = document.getElementById("html-output");
            if (htmlOutput) {
                htmlOutput.innerHTML = "";
                htmlOutput.textContent = "";
                if (value.renderHTML) {
                    const highlight =
                        '<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.3.1/styles/default.min.css" />';
                    htmlOutput.innerHTML = highlight + value.HTML;
                    // Magic code to add code highlighting
                    const blocks = htmlOutput.querySelectorAll("pre code");
                    blocks.forEach((block) =>
                        hljs.highlightElement(block as HTMLElement),
                    );
                } else {
                    htmlOutput.textContent = value.HTML;
                }
            }
        });
}
if (typeof window !== "undefined") {
    window.onload = function () {
        main();
    };
}


types.ts:

export type State = Readonly<{
    markdown: string;
    HTML: string;
    renderHTML: boolean;
    save: false;
}>;


style.css:

body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
}

header,
footer {
    background-color: #333;
    color: #fff;
    padding: 10px;
    text-align: center;
}

main {
    padding: 20px;
}

h1,
h2 {
    margin-bottom: 10px;
}

#container {
    display: flex;
    flex-direction: row;
    /* Ensure the sections are laid out horizontally */
    justify-content: space-between;
}

textarea {
    flex: 1;
    /* Take up available space equally */
    height: 200px;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
    resize: vertical;
}

#html-output {
    flex: 1;
    /* Take up available space equally */
    margin-left: 10px;
    /* Add some space between input and output */
    border: 1px solid #ccc;
    border-radius: 5px;
    padding: 10px;
    min-height: 100px;
}

footer {
    margin-top: 20px;
    background-color: #333;
    color: #fff;
    padding: 10px;
    text-align: center;
    position: fixed;
    bottom: 0;
    width: 100%;
}

index.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Markdown to HTML Converter</title>
    <link rel="stylesheet" href="/style.css">
</head>

<body>
    <header>
        <h1>Markdown to HTML Converter</h1>
    </header>

    <main>
        <div id="container" style="display: flex; width: 100%;">
            <section id="input-section" style="flex: 1; padding-right: 10px;">
                <h2>Enter Markdown:</h2>
                <textarea id="markdown-input" placeholder="Enter your Markdown here..." style="width: 100%;"></textarea>
            </section>
            <section id="output-section" style="flex: 1; padding-left: 10px;">
                <h2>HTML Output:</h2>
                <div id="html-output"></div>
                <label><input type="checkbox" name="checkbox" checked>Render HTML</label>
            </section>
        </div>
    </main>
    <footer>
        <p>Developed by Your Name</p>
    </footer>
    <script src="./src/main.ts" type="module"></script>
</body>

</html>