packages feed

descript-lang-0.2.0.0: src/Descript/Lex/Parse.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Descript.Lex.Parse
  ( parse_
  , parse
  ) where

import Prelude hiding (lex)
import Descript.Lex.Data
import Descript.Misc
import Text.Megaparsec hiding (parse)
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
import Data.Ratio
import Core.Data.List
import Data.Text (Text)
import qualified Data.Text as Text

type Parser a = Parsec RangedError Text a
type RParser a = Parser (a Range)

parse_ :: ParseAction () (RangeStream Lex)
parse_ file () = parse file

parse :: SFile -> ParseResult (RangeStream Lex)
parse = runFirstParser lexemes

lexemes :: Parser (RangeStream Lex)
lexemes = stripSeps <$> (white *> many lexeme)

lexeme :: RParser Lex
lexeme
    = LexPunc <$> punc
  <|> LexSymbol <$> symbol
  <|> LexPrim <$> prim
  <?> "lexeme"

punc :: RParser Punc
punc
    = ranged puncFullRange
  <|> DeclModule <$> rword "module"
  <|> DeclImport <$> rword "import"
  <?> "punctuation"

puncFullRange :: Parser (an -> Punc an)
puncFullRange
    = PhaseSep <$ genPuncSep "---"
  <|> Period <$ genPuncSuffix "."
  <|> Colon <$ genPuncInfix ":"
  <|> Question <$ genPuncSuffix "?"
  <|> Union <$ genPuncInfix "|"
  <|> PathFwd <$ genPuncInfix ">"
  <|> PathBwd <$ genPuncInfix "<"
  <|> ArrowEqFwd <$ genPuncInfix "=>"
  <|> OpenBracket <$ genPuncPrefix "["
  <|> CloseBracket <$ genPuncSuffix "]"
  <|> OpenBrace <$ genPuncPrefix "{"
  <|> CloseBrace <$ genPuncSuffix "}"
  <|> Sep <$ sep

symbol :: RParser Symbol
symbol = genLexeme symbol'

prim :: RParser Prim
prim = genLexeme prim'

symbol' :: Parser (an -> Symbol an)
symbol' = Symbol <$@> symbolStr <?> "symbol"

prim' :: Parser (an -> Prim an)
prim'
    = PrimNumber <$@> number
  <|> PrimText <$@> primText
  <?> "primitive"

symbolStr :: Parser String
symbolStr = (:) <$> symStartChar <*> many symNextChar

symStartChar :: Parser Char
symStartChar = letterChar <|> char '_' <|> char '#'

symNextChar :: Parser Char
symNextChar = alphaNumChar <|> char '_'

primText :: Parser Text
primText = Text.pack <$> (char '"' *> manyTill L.charLiteral (char '"')) <?> "string"

unsignedNumber :: Parser Rational
unsignedNumber
    = try (toRational @Double <$> L.float)
  <|> try (string "0x") *> (toRational @Integer <$> L.hexadecimal)
  <|> try (string "0o") *> (toRational @Integer <$> L.octal)
  <|> (%) <$> L.decimal <*> (char '/' *> L.decimal <|> pure 1)
  <?> "unsigned number"

number :: Parser Rational
number
    = char '-' *> (negate <$> unsignedNumber)
  <|> unsignedNumber
  <?> "number"

sep :: Parser ()
sep  = genPuncPrefix ","
   <|> genPuncPrefix "\n"
   <?> "separator"

rword :: Text -> Parser Range
rword word
  = expRanged_ (Text.length word) $ genPuncPrefix (word `Text.snoc` ' ')

genPuncInfix :: Text -> Parser ()
genPuncInfix lit = () <$ L.symbol white lit

genPuncPrefix :: Text -> Parser ()
genPuncPrefix = genPuncInfix

genPuncSuffix :: Text -> Parser ()
genPuncSuffix lit = () <$ try (white *> L.symbol whiteNoSep lit)

genPuncSep :: Text -> Parser ()
genPuncSep lit = () <$ L.symbol whiteNoSep lit

genLexeme :: Parser (Range -> a) -> Parser a
genLexeme = L.lexeme whiteNoSep . ranged

spaceNoSep :: Parser ()
spaceNoSep = () <$ oneOf [' ', '\t']

whiteNoSep :: Parser ()
whiteNoSep = L.space spaceNoSep lineComment blockComment

white :: Parser ()
white = L.space space1 lineComment blockComment

lineComment :: Parser ()
lineComment = L.skipLineComment "//"

blockComment :: Parser ()
blockComment = L.skipBlockComment "/*" "*/"

stripSeps :: RangeStream Lex -> RangeStream Lex
stripSeps = strip $ (== LexPunc (Sep ())) . remAnns