packages feed

Aoide-2.0.0.0: Main.hs

module Main (main) where
  import Composition.Errors
  import Composition.Lilypond
  import Composition.MIDI
  import Composition.Keyboard
  import Composition.Parser
  import Composition.Score
  import Composition.Write
  import Control.Applicative
  import Control.Monad.Except
  import Control.Monad.Trans.Except
  import Data.Char
  import Data.Functor
  import Data.List as List
  import Data.Map.Strict
  import Data.Maybe
  import Data.Tuple
  import Parser.Files
  import Parser.Locations
  import Parser.Parser
  import Parser.Utilities
  import System.Environment
  import Text.Read
  data Char_class =
    Delimiter_char Token |
    File_path_char Char |
    Invalid_char |
    Nonzero_nat_char Char |
    Quote_char |
    Text_char Char |
    Whitespace_char |
    Word_char Char |
    Zero_char
  data Command =
    Keyboard_command {
      playables :: File_path,
      source :: File_path,
      header :: Map Header_field String,
      midi_instrument :: MIDI_instrument,
      destination :: File_path} |
    Lilypond_command {source :: File_path, destination :: File_path} |
    MIDI_command {source :: File_path, destination :: File_path}
  type Parser = Parser' Token Error
  data Token =
    Destination_token |
    Eq_token |
    File_path_token String |
    Header_field_token Header_field |
    Header_token |
    Keyboard_token |
    Left_curly_bracket_token |
    Left_square_bracket_token |
    Lilypond_token |
    MIDI_instrument_token |
    MIDI_token |
    Nat_token Int |
    Playables_token |
    Right_curly_bracket_token |
    Right_square_bracket_token |
    Source_token |
    Text_token String
  type Tokeniser = Tokeniser' Char_class Token Error
  deriving instance Eq Char_class
  deriving instance Eq Token
  deriving instance Show Char_class
  deriving instance Show Command
  deriving instance Show Token
  classify_char :: Char -> Char_class
  classify_char c =
    case c of
      ' ' -> Whitespace_char
      _ | elem c "!#$%&'()*+-,:;<>?@^`|~" -> Text_char c
      '"' -> Quote_char
      _ | elem c "'_" || isLetter c -> Word_char c
      _ | elem c "./" -> File_path_char c
      '0' -> Zero_char
      _ | isDigit c && c /= '0' -> Nonzero_nat_char c
      _ | elem c (fst <$> delimiters) -> Delimiter_char (fromJust (List.lookup c delimiters))
      _ -> Invalid_char
  construct_header :: [(Header_field, String)] -> Either (Location -> Error) (Map Header_field String)
  construct_header header =
    case construct_map header of
      Nothing -> Left (\ _ -> Duplicate_header_fields Nothing)
      Just header' -> Right header'
  construct_word :: String -> Either (Location -> Error) Token
  construct_word word =
    (case construct_header_field <|> construct_keyword of
      Nothing -> Left (\ _ -> Invalid_word word Nothing)
      Just token -> Right token) where
    construct_header_field :: Maybe Token
    construct_header_field = Header_field_token <$> readMaybe word
    construct_keyword :: Maybe Token
    construct_keyword =
      case word of
        "Keyboard" -> Just Keyboard_token
        "Lilypond" -> Just Lilypond_token
        "MIDI" -> Just MIDI_token
        "destination" -> Just Destination_token
        "header" -> Just Header_token
        "midi_instrument" -> Just MIDI_instrument_token
        "playables" -> Just Playables_token
        "source" -> Just Source_token
        _ -> Nothing
  convert_midi_instrument :: Int -> Either (Location -> Error) MIDI_instrument
  convert_midi_instrument midi_instrument =
    do
      check (\ _ -> MIDI_instrument_code_is_out_of_range_command midi_instrument) (between 0 127 midi_instrument)
      Right (fromIntegral midi_instrument)
  delimiter_char :: Char_class -> Maybe Token
  delimiter_char char_class =
    case char_class of
      Delimiter_char token -> Just token
      _ -> Nothing
  delimiters :: [(Char, Token)]
  delimiters =
    [
      ('=', Eq_token),
      ('[', Left_square_bracket_token),
      (']', Right_square_bracket_token),
      ('{', Left_curly_bracket_token),
      ('}', Right_curly_bracket_token)]
  file_path_char :: Char_class -> Maybe Char
  file_path_char char_class =
    case char_class of
      File_path_char c -> Just c
      Nonzero_nat_char c -> Just c
      Word_char c -> Just c
      Zero_char -> Just '0'
      _ -> Nothing
  file_path_token :: Token -> Maybe String
  file_path_token token =
    case token of
      File_path_token file_path -> Just file_path
      _ -> Nothing
  header_field_token :: Token -> Maybe Header_field
  header_field_token token =
    case token of
      Header_field_token header_field -> Just header_field
      _ -> Nothing
  main :: IO ()
  main =
    do
      args <- getArgs
      result <- runExceptT (main' (intercalate " " args))
      case result of
        Left err -> putStrLn (write_error err)
        Right () -> return ()
  main' :: String -> ExceptT Error IO ()
  main' command =
    (do
      command' <- except parse_command
      case command' of
        Keyboard_command {playables = playables_file_path, source, header, midi_instrument, destination} ->
          do
            playables <- parse_playables playables_file_path
            original_score <- parse source
            keyboard_score <- except (reduction playables header midi_instrument original_score)
            write destination keyboard_score
        Lilypond_command {source, destination} ->
          do
            score <- parse source
            lilypond score destination
        MIDI_command {source, destination} ->
          do
            score <- parse source
            midi score destination) where
    parse_command :: Either Error Command
    parse_command =
      fromJust (parse' classify_char (return next_char) tokenise parse_command' (\ _ -> Parse_error_command) command)
  nat_char :: Char_class -> Maybe Char
  nat_char char_class =
    case char_class of
      Zero_char -> Just '0'
      Nonzero_nat_char c -> Just c
      _ -> Nothing
  nat_token :: Token -> Maybe Int
  nat_token token =
    case token of
      Nat_token i -> Just i
      _ -> Nothing
  nonzero_nat_char :: Char_class -> Maybe Char
  nonzero_nat_char char_class =
    case char_class of
      Nonzero_nat_char c -> Just c
      _ -> Nothing
  parse_command' :: Parser Command
  parse_command' = parse_keyboard <+> parse_lilypond <+> parse_midi
  parse_curly_brackets :: Parser t -> Parser t
  parse_curly_brackets = parse_brackets Left_curly_bracket_token Right_curly_bracket_token
  parse_eq :: Parser ()
  parse_eq = parse_token Eq_token
  parse_field :: Token -> Parser t -> Parser t
  parse_field name parse_t =
    do
      parse_token name
      parse_eq
      parse_t
  parse_file_path :: String -> Parser File_path
  parse_file_path ext = fmap_filter_parser (parse_file_path' (\ err _ -> File_error err) ext) (parse_token' file_path_token)
  parse_header :: Parser (Map Header_field String)
  parse_header = fmap_filter_parser construct_header (parse_list' parse_header_field)
  parse_header_field :: Parser (Header_field, String)
  parse_header_field =
    do
      name <- parse_header_field_name
      parse_eq
      value <- parse_text
      return (name, value)
  parse_header_field_name :: Parser Header_field
  parse_header_field_name = parse_token' header_field_token
  parse_keyboard :: Parser Command
  parse_keyboard =
    parse_struct
      Keyboard_token
      (do
        playables <- parse_field Playables_token (parse_file_path "key")
        source <- parse_field Source_token (parse_file_path "aoi")
        header <- parse_field Header_token parse_header
        midi_instrument <- parse_field MIDI_instrument_token parse_midi_instrument
        destination <- parse_field Destination_token (parse_file_path "aoi")
        return (Keyboard_command {playables, source, header, midi_instrument, destination}))
  parse_lilypond :: Parser Command
  parse_lilypond =
    parse_struct
      Lilypond_token
      (do
        source <- parse_field Source_token (parse_file_path "aoi")
        destination <- parse_field Destination_token (parse_file_path "ly")
        return (Lilypond_command {source, destination}))
  parse_list' :: Parser t -> Parser [t]
  parse_list' parse_t = parse_square_brackets (parse_many parse_t)
  parse_midi :: Parser Command
  parse_midi =
    parse_struct
      MIDI_token
      (do
        source <- parse_field Source_token (parse_file_path "aoi")
        destination <- parse_field Destination_token (parse_file_path "mid")
        return (MIDI_command {source, destination}))
  parse_midi_instrument :: Parser MIDI_instrument
  parse_midi_instrument = fmap_filter_parser convert_midi_instrument parse_nat
  parse_nat :: Parser Int
  parse_nat = parse_token' nat_token
  parse_square_brackets :: Parser t -> Parser t
  parse_square_brackets = parse_brackets Left_square_bracket_token Right_square_bracket_token
  parse_struct :: Token -> Parser Command -> Parser Command
  parse_struct name parse_fields =
    do
      parse_token name
      parse_curly_brackets parse_fields
  parse_text :: Parser String
  parse_text = parse_token' text_token
  text_char :: Char_class -> Maybe Char
  text_char char_class =
    case char_class of
      Delimiter_char token -> List.lookup token (swap <$> delimiters)
      File_path_char c -> Just c
      Invalid_char -> Nothing
      Nonzero_nat_char c -> Just c
      Quote_char -> Nothing
      Text_char c -> Just c
      Whitespace_char -> Just ' '
      Word_char c -> Just c
      Zero_char -> Just '0'
  text_token :: Token -> Maybe String
  text_token token =
    case token of
      Text_token text -> Just text
      _ -> Nothing
  tokenise :: Tokeniser ()
  tokenise = void (parse_many tokenise_1)
  tokenise_1 :: Tokeniser ()
  tokenise_1 =
    tokenise_delimiter <+> ((tokenise_nat <+> tokenise_word) <+ tokenise_file_path) <+> tokenise_text <+> tokenise_whitespace
  tokenise_delimiter :: Tokeniser ()
  tokenise_delimiter = add_token (parse_token' delimiter_char)
  tokenise_file_path :: Tokeniser ()
  tokenise_file_path = add_token (File_path_token <$> parse_some (parse_token' file_path_char))
  tokenise_nat :: Tokeniser ()
  tokenise_nat = add_token (Nat_token <$> (tokenise_zero <+> tokenise_positive_int))
  tokenise_positive_int :: Tokeniser Int
  tokenise_positive_int = read <$> ((:) <$> parse_token' nonzero_nat_char <*> parse_many (parse_token' nat_char))
  tokenise_quotes :: Tokeniser t -> Tokeniser t
  tokenise_quotes = parse_brackets Quote_char Quote_char
  tokenise_text :: Tokeniser ()
  tokenise_text = add_token (Text_token <$> tokenise_quotes (parse_many (parse_token' text_char)))
  tokenise_whitespace :: Tokeniser ()
  tokenise_whitespace = parse_token Whitespace_char
  tokenise_word :: Tokeniser ()
  tokenise_word = add_token (fmap_filter_parser construct_word (parse_some (parse_token' word_char)))
  tokenise_zero :: Tokeniser Int
  tokenise_zero =
    do
      parse_token Zero_char
      return 0
  word_char :: Char_class -> Maybe Char
  word_char char_class =
    case char_class of
      Word_char c -> Just c
      _ -> Nothing
  -- | Writing errors.
  write_error :: Error -> String
  write_error err =
    case err of
      An_event_crosses_the_bar_line -> "An event crosses the bar line."
      Conflicting_accidentals_in_key_signature -> "Conflicting accidentals in key signature."
      Duplicate_header_fields location -> "Duplicate header fields" <-> write_maybe_location location <> "."
      Duplicate_keys location -> "Duplicate keys at" <-> write_location location <> "."
      Duplicate_note_names_in_key_signature location ->
        "Duplicate note name in key signature at" <-> write_location location <> "."
      Duplicate_notes location -> "Duplicate notes at" <-> write_location location <> "."
      Empty_triplet -> "Empty triplet."
      Event_or_part_length_is_out_of_range_for_MIDI -> "Event or part length is out of range for MIDI."
      File_error err' ->
        case err' of
          Failed_to_find_file file_path -> "Failed to find file" <-> file_path <> "."
          Invalid_file_path -> "Invalid file path."
          Unexpected_extension ext -> "Unexpected extension ." <> ext <> "."
      Initial_position_is_out_of_range -> "Initial position is out of range."
      Key_is_out_of_range key location -> "Key" <-> show key <-> "at" <-> write_location location <-> "is out of range."
      Invalid_character_in_text -> "Invalid character in text."
      Invalid_note_length len location -> "Invalid note length" <-> show len <-> "at" <-> write_location location <> "."
      Invalid_note_name natural_note_name accidental location ->
        (
          "Invalid note name" <->
          write_natural_note_name natural_note_name <>
          write_accidental accidental <->
          "at" <->
          write_location location <>
          ".")
      Invalid_time_numerator_factor time_numerator_factor location ->
        "Invalid time numerator factor" <-> show time_numerator_factor <-> "at" <-> write_location location <> "."
      Invalid_word word location -> "Invalid word" <-> word <-> write_maybe_location location <> "."
      Is_out_of_range_with_location typ location ->
        write_is_out_of_range_type typ <-> "at" <-> write_location location <-> "is out of range."
      Is_out_of_range_without_location typ -> write_is_out_of_range_type typ <-> "is out of range."
      MIDI_instrument_code_is_out_of_range_command instrument ->
        "MIDI instrument code" <-> show instrument <-> "in the command is out of range."
      MIDI_instrument_code_is_out_of_range_data instrument ->
        "MIDI instrument code" <-> show instrument <-> "is out of range."
      MIDI_instrument_code_is_out_of_range_score instrument location ->
        "MIDI instrument code" <-> show instrument <-> "in the score at" <-> write_location location <-> "is out of range."
      Non_positive_note_length -> "Non-positive note length."
      Note_is_out_of_range_for_MIDI note -> "Note" <-> write_note note <-> "is out of range for MIDI."
      Note_length_denominator_contains_factors_other_than_2_and_3 ->
        "Note length denominator contains factors other than 2 and 3."
      Parse_error_command -> "Parse error in the command."
      Parse_error_playables location -> "Parse error in the playables file at" <-> write_location location <> "."
      Parse_error_score location -> "Parse error in the score at" <-> write_location location <> "."
      Track_ends_with_an_incomplete_triplet -> "Track ends with an incomplete triplet."
      Track_length_mismatch -> "Track length mismatch."
      Velocity_out_of_range location -> "Velocity out of range at " <> write_location location <> "."
  write_is_out_of_range_type :: Is_out_of_range_type -> String
  write_is_out_of_range_type typ =
    case typ of
      Note_length_denominator_IOOR -> "Note length denominator"
      Tempo_IOOR -> "Tempo"
      The_least_common_denominator_of_note_lengths_IOOR -> "The least common denominator of note lengths"
      The_number_of_pitched_tracks_IOOR -> "The number of pitched tracks"
      The_number_of_tracks_IOOR -> "The number of tracks"
      Track_length_IOOR -> "Track length"
      Velocity_IOOR -> "Velocity"
  write_maybe_location :: Maybe Location -> String
  write_maybe_location maybe_location =
    case maybe_location of
      Nothing -> "in the command"
      Just location -> "at" <-> write_location location