packages feed

morley-1.19.2: src/Morley/Michelson/Parser/Value.hs

-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

-- | Parsing of untyped Michelson values.

module Morley.Michelson.Parser.Value
  ( value'

  -- * For tests
  , stringLiteral
  , bytesLiteral
  , intLiteral
  ) where

import Prelude hiding (many, note, try)

import Data.Char qualified as Char
import Text.Hex qualified as Hex

import Text.Megaparsec (anySingle, choice, customFailure, label, manyTill, satisfy, takeWhileP, try)
import Text.Megaparsec.Char (char, string)
import Text.Megaparsec.Char.Lexer qualified as L

import Morley.Michelson.Macro (ParsedOp, ParsedSeq(..), ParsedValue)
import Morley.Michelson.Parser.Error
import Morley.Michelson.Parser.Helpers
import Morley.Michelson.Parser.Lexer
import Morley.Michelson.Parser.Types (Parser)
import Morley.Michelson.Text (isMChar, mkMText)
import Morley.Michelson.Untyped qualified as U

-- | Parse untyped 'ParsedValue'. Take instruction parser as argument
-- to avoid cyclic dependencies between modules, hence ' in its name.
value' :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
value' opsParser = parensOrTuple opsParser <|> valueInnerWithoutParens opsParser

parensOrTuple :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
parensOrTuple opsParser = parens $ value' opsParser

valueInnerWithoutParens :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
valueInnerWithoutParens opsParser = label "value" $ choice $
  [ stringLiteral, bytesLiteral, intLiteral, unitValue
  , trueValue, falseValue, pairValueCore opsParser, leftValue opsParser
  , rightValue opsParser, someValue opsParser, noneValue, nilValue
  , seqOrLambda opsParser, mapValue opsParser, lambdaRecValue opsParser
  ]

seqOrLambda :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
seqOrLambda opsParser = try (lambdaValue opsParser) <|> seqValue opsParser

stringLiteral :: Parser ParsedValue
stringLiteral = lexeme $ U.ValueString . unsafe . mkMText . toText <$> do
  _ <- try $ string "\""
  manyTill validChar (string "\"")
  where
      validChar :: Parser Char
      validChar = choice
        [ strEscape
        , satisfy (\x -> x /= '"' && isMChar x)
        , anySingle >>= stringLiteralFailure . InvalidChar
        ]

      strEscape :: Parser Char
      strEscape = try (char '\\') >> esc
        where
          esc = choice
            [ char '\\'
            , char '"'
            , char 'n' $> '\n'
            , anySingle >>= stringLiteralFailure . InvalidEscapeSequence
            ]
      stringLiteralFailure = customFailure . StringLiteralException

-- It is safe not to use `try` here because bytesLiteral is the only
-- thing that starts from 0x (at least for now)
bytesLiteral :: Parser (U.Value' f op)
bytesLiteral = lexeme $ do
  string "0x"
  hexdigits <- takeWhileP Nothing Char.isHexDigit
  let mBytes = Hex.decodeHex hexdigits
  maybe
    (customFailure OddNumberBytesException)
    (return . U.ValueBytes . U.InternalByteString)
    mBytes

intLiteral :: Parser (U.Value' f op)
intLiteral = lexeme $ try $ U.ValueInt <$> L.signed pass L.decimal

unitValue :: Parser ParsedValue
unitValue = word "Unit" U.ValueUnit

trueValue :: Parser ParsedValue
trueValue = word "True" U.ValueTrue

falseValue :: Parser ParsedValue
falseValue = word "False" U.ValueFalse

pairValueCore :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
pairValueCore opsParser = symbol1 "Pair" *> pairInner
  where
    pairInner = U.ValuePair
      <$> value' opsParser
      <*> (foldr1 U.ValuePair <$> some' (value' opsParser))

leftValue :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
leftValue opsParser = word "Left" U.ValueLeft <*> value' opsParser

rightValue :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
rightValue opsParser = word "Right" U.ValueRight <*> value' opsParser

someValue :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
someValue opsParser = word "Some" U.ValueSome <*> value' opsParser

noneValue :: Parser ParsedValue
noneValue = word "None" U.ValueNone

nilValue :: Parser ParsedValue
nilValue = U.ValueNil <$ (try $ braces pass)

lambdaValue :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
lambdaValue opsParser = opsParser <&> \case
  PSSequence [] -> U.ValueNil
  ops -> U.ValueLambda ops

lambdaRecValue :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
lambdaRecValue opsParser = word "Lambda_rec" U.ValueLamRec <*> opsParser

seqValue :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
seqValue opsParser =
  U.ValueSeq <$> (try $ braces $ sepEndBy1 (value' opsParser) semicolon)

eltValue :: Parser (ParsedSeq ParsedOp) -> Parser (U.Elt ParsedSeq ParsedOp)
eltValue opsParser = word "Elt" U.Elt <*> value' opsParser <*> value' opsParser

mapValue :: Parser (ParsedSeq ParsedOp) -> Parser ParsedValue
mapValue opsParser =
  U.ValueMap <$> (try $ braces $ sepEndBy1 (eltValue opsParser) semicolon)