packages feed

statechart-0.0.0: src/Rhapsody.hs

module Rhapsody
  ( Record (..)
  , Field  (..)
  , parseRhapsody
  , records
  , values
  , value
  , containerRecords
  , ident
  , body
  ) where

import Text.ParserCombinators.Poly.Plain

data Record = Record String [Field]  deriving (Show, Eq)
data Field
  = Value       String [String]
  | RecordNamed String [Record]
  | RecordAnon          Record
  deriving (Show, Eq)

-- | Given a record and a field name, returns a subfield of records.  Null if not found.
records :: String -> Record -> [Record]
records name (Record _ fields) = concat [ recs | RecordNamed n recs <- fields, n == name ]

-- | Given a record and a field name, returns a subfield of values.  Null if not found.
values :: String -> Record -> [String]
values name (Record _ fields) = concat [ vals | Value n vals <- fields, n == name ]

-- | Returns a field with a single value, or an empty string if the field does not exist.
value :: String -> Record -> String
value name r = case values name r of
  [] -> ""
  a : _ -> a

-- | Returns a list of records packaged in an IRPYRawContainer.
containerRecords :: String -> Record -> [Record]
containerRecords name rec = case records name rec of
  [] -> []
  a : _ -> records "value" a
 
-- | A unique identifier field.
ident :: String -> Record -> String
ident name a = case values name a of
  [_, a] -> a
  _ -> ""

-- | Extract the _body string from triggers, guards, and actions.
body :: String -> Record -> String
body name rec = case records name rec of
  [] -> ""
  a : _ -> value "_body" a



-- Lexing.

data Token
  = Eq
  | Dash
  | SemiColon
  | BraceLeft
  | BraceRight
  | String String
  deriving (Show, Eq)

lexer :: String -> [Token]
lexer = tokens . unlines . tail . lines . decomment
  where

  tokens :: String -> [Token]
  tokens "" = []
  tokens a = case a of
    a : b | elem a " \r\n\t" -> tokens b
    '{' : b -> BraceLeft  : tokens b
    '}' : b -> BraceRight : tokens b
    '=' : b -> Eq         : tokens b
    '-' : ' ' : b -> Dash       : tokens b
    ';' : b -> SemiColon  : tokens b
    '"' : b -> String str : tokens rest where (str, rest) = string b
    '#' : b -> tokens $ dropWhile (/= '\n') b
    a -> String (takeWhile (flip notElem " \r\n\t;") a) : tokens (dropWhile (flip notElem " \r\n\t;") a)

  string :: String -> (String, String)
  string a = case a of
    [] -> error "Ending string quote not found."
    '"'  : b     -> ("", b)
    '\\' : a : b -> (a : a', b') where (a', b') = string b
    a : b        -> (a : a', b') where (a', b') = string b

decomment :: String -> String
decomment = decomment' 0
  where
  decomment' :: Int -> String -> String
  decomment' n "" | n == 0    = ""
                  | otherwise = error "reached end of file without closing comment"
  decomment' n ('(':'*':a) = "  " ++ decomment' (n + 1) a
  decomment' n ('*':')':a) | n > 0  = "  " ++ decomment' (n - 1) a
                           | otherwise = error "unexpected closing comment"
  decomment' n (a:b) = (if n > 0 && a /= '\n' then ' ' else a) : decomment' n b



-- Parsing.

parseRhapsody :: String -> Record
parseRhapsody a = case runParser (record `discard` eof) $ lexer a of
  (Left msg, remaining) -> error $ msg ++ "\nremaining tokens: " ++ show (take 30 $ remaining) ++ " ..."
  (Right a, []) -> a
  (Right _, remaining) -> error $ "parsed, but with remaining tokens: " ++ show remaining

type Rhap a = Parser Token a

tok :: Token -> Rhap ()
tok a = satisfy (== a) >> return ()

string :: Rhap String
string = do
  a <- satisfy (\ a -> case a of { String _ -> True; _ -> False })
  case a of
    String s -> return s
    _ -> undefined

record :: Rhap Record
record = do { tok BraceLeft; name <- string; fields <- many field; tok BraceRight; return $ Record name fields }

field :: Rhap Field
field = oneOf
  [ do { tok Dash; name <- string; tok Eq; v <- many string; tok SemiColon; return $ Value name v }
  , do { tok Dash; name <- string; tok Eq; r <- many record; return $ RecordNamed name r }
  , do { r <- record; return $ RecordAnon r }
  ]