Aoide-1.0.0.0: Composition/Parser.hs
{-|
Parse the custom file format for scores.
-}
module Composition.Parser (parse) where
import Composition.Errors
import Composition.Notes
import Composition.Score
import Composition.Time
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.Set
import Data.Tuple
import Parser.Files
import Parser.Locations
import Parser.Parser
import Parser.Utilities
import Text.Read
data Char_class =
Delimiter_char Token |
Invalid_char |
Letter_char Char |
Minus_char |
Nonzero_nat_char Char |
Newline_char |
Quote_char |
Whitespace_char |
Zero_char
type Parser = Parser' Token Error
data Token =
Clef_and_stave_token |
Clef_name_name_token |
Clef_name_value_token Clef_name |
Clef_token |
Clefs_and_staves_token |
Dot_token |
Eq_token |
Header_field_token Header_field |
Header_token |
Initial_position_token |
Instrument_clefs_and_staves_token |
Instrument_name_token |
Key_token |
Left_curly_bracket_token |
Left_square_bracket_token |
MIDI_instrument_token |
Negative_int_token Int |
Note_name_token Note_name |
Part_token |
Parts_token |
Percussion_clef_token |
Pitched_clef_token |
Pitched_token |
Positive_int_token Int |
Right_curly_bracket_token |
Right_square_bracket_token |
Score_token |
Slash_token |
Stave_groups_token |
Stave_token |
Tempo_token |
Text_token String |
Tie_token |
Time_name_token |
Time_value_token |
Title_token |
Transposition_token |
Unpitched_note_token |
Unpitched_token |
Velocity_token |
Zero_token
type Tokeniser = Tokeniser' Char_class Token Error
deriving instance Eq Char_class
deriving instance Eq Token
deriving instance Ord Token
deriving instance Show Char_class
deriving instance Show Token
classify_char :: Char -> Char_class
classify_char c =
case c of
'\n' -> Newline_char
' ' -> Whitespace_char
_ | elem c "!#$%&'()*,:;<>?@^_`|~" || isLetter c -> Letter_char c
'"' -> Quote_char
_ | elem c (fst <$> delimiters) -> Delimiter_char (fromJust (List.lookup c delimiters))
'-' -> Minus_char
'0' -> Zero_char
_ | isDigit c && c /= '0' -> Nonzero_nat_char c
'=' -> Delimiter_char Eq_token
_ -> Invalid_char
clef_name_token :: Token -> Maybe Clef_name
clef_name_token token =
case token of
Clef_name_value_token clef_name -> Just clef_name
_ -> Nothing
construct_basic_length :: Int -> Either (Location -> Error) Basic_length
construct_basic_length len =
case denominator_to_basic_length len of
Nothing -> Left Invalid_note_length
Just len' -> Right len'
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
Just header' -> Right header'
construct_key :: [Note_name] -> Either (Location -> Error) (Set Note_name)
construct_key key =
case construct_set key of
Nothing -> Left Duplicate_note_names_in_key_signature
Just key' -> Right key'
construct_notes :: [Note note_type] -> Either (Location -> Error) (Notes note_type)
construct_notes notes =
case construct_set notes of
Nothing -> Left Duplicate_notes
Just notes' -> Right (Notes (notes'))
construct_word :: String -> Either (Location -> Error) Token
construct_word word =
(case construct_clef_name <|> construct_header_field <|> construct_keyword <|> construct_note_name' of
Nothing -> Left (Invalid_word word)
Just token -> Right token) where
construct_clef_name :: Maybe Token
construct_clef_name = Clef_name_value_token <$> readMaybe word
construct_header_field :: Maybe Token
construct_header_field = Header_field_token <$> readMaybe word
construct_keyword :: Maybe Token
construct_keyword =
case word of
"Clef_and_stave" -> Just Clef_and_stave_token
"Instrument_clefs_and_staves" -> Just Instrument_clefs_and_staves_token
"Part" -> Just Part_token
"Percussion_clef" -> Just Percussion_clef_token
"Pitched" -> Just Pitched_token
"Pitched_clef" -> Just Pitched_clef_token
"Score" -> Just Score_token
"Time" -> Just Time_value_token
"Unpitched" -> Just Unpitched_token
"clef" -> Just Clef_token
"clef_name" -> Just Clef_name_name_token
"clefs_and_staves" -> Just Clefs_and_staves_token
"header" -> Just Header_token
"initial_position" -> Just Initial_position_token
"instrument_name" -> Just Instrument_name_token
"key" -> Just Key_token
"midi_instrument" -> Just MIDI_instrument_token
"parts" -> Just Parts_token
"stave" -> Just Stave_token
"stave_groups" -> Just Stave_groups_token
"tempo" -> Just Tempo_token
"time" -> Just Time_name_token
"title" -> Just Title_token
"transposition" -> Just Transposition_token
"velocity" -> Just Velocity_token
"x" -> Just Unpitched_note_token
_ -> Nothing
construct_note_name' :: Maybe Token
construct_note_name' =
case word of
"" -> Nothing
natural_note_name : accidental ->
do
natural_note_name' <- read_natural_note_name natural_note_name
accidental' <- read_accidental accidental
Note_name_token <$> construct_note_name natural_note_name' accidental'
convert_midi_instrument :: Int -> Either (Location -> Error) MIDI_instrument
convert_midi_instrument midi_instrument =
do
check (Is_out_of_range_with_location MIDI_instrument_code_IOOR) (between 0 127 midi_instrument)
Right (fromIntegral midi_instrument)
convert_time_numerator_factor :: Int -> Either (Location -> Error) Time_numerator_factor
convert_time_numerator_factor time_numerator_factor =
case int_to_time_numerator_factor time_numerator_factor of
Nothing -> Left Invalid_time_numerator_factor
Just time_numerator_factor' -> Right time_numerator_factor'
convert_velocity :: Int -> Either (Location -> Error) Velocity
convert_velocity velocity =
do
check Velocity_out_of_range (between 0 (fromIntegral max_velocity) velocity)
Right (fromIntegral velocity)
delimiter_char :: Char_class -> Maybe Token
delimiter_char char_class =
case char_class of
Delimiter_char token -> Just token
_ -> Nothing
delimiters :: [(Char, Token)]
delimiters =
[
('+', Tie_token),
('.', Dot_token),
('/', Slash_token),
('=', Eq_token),
('[', Left_square_bracket_token),
(']', Right_square_bracket_token),
('{', Left_curly_bracket_token),
('}', Right_curly_bracket_token)]
header_field_token :: Token -> Maybe Header_field
header_field_token token =
case token of
Header_field_token header_field -> Just header_field
_ -> Nothing
letter_char :: Char_class -> Maybe Char
letter_char char_class =
case char_class of
Letter_char c -> Just c
_ -> Nothing
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
negative_int_token :: Token -> Maybe Int
negative_int_token token =
case token of
Negative_int_token i -> Just i
_ -> Nothing
next_location :: Char_class -> Location -> Location
next_location char_class =
case char_class of
Newline_char -> next_line
_ -> next_char
nonzero_nat_char :: Char_class -> Maybe Char
nonzero_nat_char char_class =
case char_class of
Nonzero_nat_char c -> Just c
_ -> Nothing
note_name_token :: Token -> Maybe Note_name
note_name_token token =
case token of
Note_name_token note_name -> Just note_name
_ -> Nothing
parse :: File_path -> ExceptT Error IO Score
parse file_path =
do
score <- read_file "aoi" readFile File_error file_path
except (fromJust (parse' classify_char next_location tokenise parse_score Parse_error score))
parse_basic_length :: Parser Basic_length
parse_basic_length = fmap_filter_parser construct_basic_length parse_positive_int
parse_clef_name :: Parser Clef_name
parse_clef_name = parse_token' clef_name_token
parse_curly_brackets :: Parser t -> Parser t
parse_curly_brackets = parse_brackets Left_curly_bracket_token Right_curly_bracket_token
parse_dot :: Parser Dot
parse_dot =
do
parse_token Dot_token
return Dot
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_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_initial_position :: Parser Initial_position
parse_initial_position = parse_zero_initial_position <+> parse_nonzero_initial_position
parse_instrument_clefs_and_staves :: Parser (Pitched_or_unpitched Instrument_clefs_and_staves)
parse_instrument_clefs_and_staves =
(
Pitched <$> parse_instrument_clefs_and_staves' Branch_pitched <+>
Unpitched <$> parse_instrument_clefs_and_staves' Branch_unpitched)
parse_instrument_clefs_and_staves' :: forall note_type . Branch note_type -> Parser (Instrument_clefs_and_staves note_type)
parse_instrument_clefs_and_staves' branch =
do
parse_pitched_or_unpitched
parse_struct
Instrument_clefs_and_staves_token
(do
instrument_name <- parse_field Instrument_name_token parse_text
midi_instrument <- parse_field MIDI_instrument_token parse_midi_instrument
velocity <- parse_field Velocity_token parse_velocity
clefs_and_staves <- parse_field Clefs_and_staves_token (parse_list' parse_clef_and_stave)
return (Instrument_clefs_and_staves {instrument_name, midi_instrument, velocity, clefs_and_staves})) where
parse_clef :: Parser (Clef note_type)
parse_clef =
case branch of
Branch_pitched -> parse_pitched_clef
Branch_unpitched -> parse_unpitched_clef
parse_clef_and_stave :: Parser (Clef_and_stave note_type)
parse_clef_and_stave =
parse_struct
Clef_and_stave_token
(do
clef <- parse_field Clef_token parse_clef
stave <- parse_field Stave_token parse_stave
return (Clef_and_stave {clef, stave}))
parse_event :: Parser (Event note_type)
parse_event = parse_event' <+> parse_triplet
parse_event' :: Parser (Event note_type)
parse_event' = Event <$> parse_notes <*> parse_length
parse_events :: Parser [Event note_type]
parse_events = parse_list' parse_event
parse_note :: Parser (Note note_type)
parse_note =
case branch of
Branch_pitched -> parse_pitched_note
Branch_unpitched -> parse_unpitched_note
parse_notes :: Parser (Notes note_type)
parse_notes = parse_notes' <+> parse_tie
parse_notes' :: Parser (Notes note_type)
parse_notes' = fmap_filter_parser construct_notes (parse_list' parse_note)
parse_one_track :: Parser (Stave note_type)
parse_one_track = One_track <$> parse_events
parse_pitched_or_unpitched :: Parser ()
parse_pitched_or_unpitched = parse_token' pitched_or_unpitched_token
parse_stave :: Parser (Stave note_type)
parse_stave = parse_square_brackets (parse_one_track <+> parse_two_tracks)
parse_triplet :: Parser (Event note_type)
parse_triplet = Triplet <$> parse_events
parse_two_tracks :: Parser (Stave note_type)
parse_two_tracks = Two_tracks <$> parse_events <*> parse_events
pitched_or_unpitched_token :: Token -> Maybe ()
pitched_or_unpitched_token token =
case (branch, token) of
(Branch_pitched, Pitched_token) -> Just ()
(Branch_unpitched, Unpitched_token) -> Just ()
_ -> Nothing
parse_int :: Parser Int
parse_int = parse_negative_int <+> parse_nat
parse_key :: Parser Key
parse_key = fmap_filter_parser construct_key (parse_list' parse_note_name)
parse_length :: Parser Length
parse_length = Length <$> parse_basic_length <*> parse_many parse_dot
parse_list' :: Parser t -> Parser [t]
parse_list' parse_t = parse_square_brackets (parse_many parse_t)
parse_midi_instrument :: Parser MIDI_instrument
parse_midi_instrument = fmap_filter_parser convert_midi_instrument parse_nat
parse_nat :: Parser Int
parse_nat = parse_zero <+> parse_positive_int
parse_negative_int :: Parser Int
parse_negative_int = parse_token' negative_int_token
parse_nonzero_initial_position :: Parser Initial_position
parse_nonzero_initial_position =
do
num <- parse_positive_int
parse_slash
den <- parse_basic_length
return (Initial_position num den)
parse_note_name :: Parser Note_name
parse_note_name = parse_token' note_name_token
parse_part :: Parser Part
parse_part =
parse_struct
Part_token
(do
title <- parse_field Title_token parse_text
key <- parse_field Key_token parse_key
time <- parse_field Time_name_token parse_time
initial_position <- parse_field Initial_position_token parse_initial_position
tempo <- parse_field Tempo_token parse_positive_int
stave_groups <- parse_field Stave_groups_token (parse_list' (parse_list' parse_instrument_clefs_and_staves))
return (Part {title, key, time, initial_position, tempo, stave_groups}))
parse_pitched_clef :: Parser (Clef Pitched)
parse_pitched_clef =
parse_struct
Pitched_clef_token
(do
clef_name <- parse_field Clef_name_name_token parse_clef_name
transposition <- parse_field Transposition_token parse_int
return (Pitched_clef {clef_name, transposition}))
parse_pitched_note :: Parser (Note Pitched)
parse_pitched_note = flip Pitched_note <$> parse_note_name <*> parse_int
parse_positive_int :: Parser Int
parse_positive_int = parse_token' positive_int_token
parse_score :: Parser Score
parse_score =
parse_struct
Score_token
(do
title <- parse_field Title_token parse_text
header <- parse_field Header_token parse_header
parts <- parse_field Parts_token (parse_list' parse_part)
return (Score {title, header, parts}))
parse_slash :: Parser ()
parse_slash = parse_token Slash_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 t -> Parser t
parse_struct name parse_fields =
do
parse_token name
parse_curly_brackets parse_fields
parse_text :: Parser String
parse_text = parse_token' text_token
parse_tie :: Parser (Notes note_type)
parse_tie =
do
parse_token Tie_token
return Tie
parse_time :: Parser Time
parse_time =
do
parse_token Time_value_token
num <- parse_list' parse_time_numerator_factor
den <- parse_basic_length
return (Time num den)
parse_time_numerator_factor :: Parser Time_numerator_factor
parse_time_numerator_factor = fmap_filter_parser convert_time_numerator_factor parse_positive_int
parse_unpitched_clef :: Parser (Clef Unpitched)
parse_unpitched_clef =
do
parse_token Percussion_clef_token
return Percussion_clef
parse_unpitched_note :: Parser (Note Unpitched)
parse_unpitched_note =
do
parse_token Unpitched_note_token
return Unpitched_note
parse_velocity :: Parser Velocity
parse_velocity = fmap_filter_parser convert_velocity parse_nat
parse_zero :: Parser Int
parse_zero =
do
parse_token Zero_token
return 0
parse_zero_initial_position :: Parser Initial_position
parse_zero_initial_position =
do
_ <- parse_zero
return (Initial_position 0 Whole)
positive_int_token :: Token -> Maybe Int
positive_int_token token =
case token of
Positive_int_token i -> Just i
_ -> Nothing
read_accidental :: String -> Maybe Accidental
read_accidental accidental = List.lookup accidental (swap <$> accidentals)
read_natural_note_name :: Char -> Maybe Natural_note_name
read_natural_note_name natural_note_name = readMaybe (natural_note_name : "_natural")
text_char :: Char_class -> Maybe Char
text_char char_class =
case char_class of
Delimiter_char token -> List.lookup token (swap <$> delimiters)
Invalid_char -> Nothing
Letter_char c -> Just c
Minus_char -> Just '-'
Nonzero_nat_char c -> Just c
Newline_char -> Nothing
Quote_char -> Nothing
Whitespace_char -> Just ' '
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_int <+> tokenise_newline <+> tokenise_text <+> tokenise_whitespace <+> tokenise_word
tokenise_delimiter :: Tokeniser ()
tokenise_delimiter = add_token (parse_token' delimiter_char)
tokenise_int :: Tokeniser ()
tokenise_int = add_token (tokenise_negative_int <+> tokenise_zero <+> tokenise_positive_int)
tokenise_negative_int :: Tokeniser Token
tokenise_negative_int =
do
parse_token Minus_char
i <- tokenise_positive_int'
return (Negative_int_token (negate i))
tokenise_newline :: Tokeniser ()
tokenise_newline = parse_token Newline_char
tokenise_positive_int :: Tokeniser Token
tokenise_positive_int = Positive_int_token <$> tokenise_positive_int'
tokenise_positive_int' :: Tokeniser Int
tokenise_positive_int' = read <$> ((:) <$> parse_token' nonzero_nat_char <*> parse_many (parse_token' nat_char))
tokenise_text :: Tokeniser ()
tokenise_text = add_token (Text_token <$> tokenise_quotes (parse_many (parse_token' text_char)))
tokenise_quotes :: Tokeniser t -> Tokeniser t
tokenise_quotes = parse_brackets Quote_char Quote_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' letter_char)))
tokenise_zero :: Tokeniser Token
tokenise_zero =
do
parse_token Zero_char
return Zero_token