language-python-0.5.6: src/Language/Python/Common/StringEscape.hs
{-# OPTIONS #-}
-----------------------------------------------------------------------------
-- |
-- Module : Language.Python.Common.StringEscape
-- Copyright : (c) 2009 Bernie Pope
-- License : BSD-style
-- Maintainer : bjpop@csse.unimelb.edu.au
-- Stability : experimental
-- Portability : ghc
--
-- Conversion to/from escaped characters in strings. Note: currently does not
-- support escaped Unicode character names.
--
-- See:
--
-- * Version 2.6 <http://docs.python.org/2.6/reference/lexical_analysis.html#string-literals>
--
-- * Version 3.1 <http://docs.python.org/3.1/reference/lexical_analysis.html#string-and-bytes-literals>
-----------------------------------------------------------------------------
module Language.Python.Common.StringEscape (
-- * String conversion.
unescapeString,
unescapeRawString,
-- * Digits allowed in octal and hex representation.
octalDigits,
hexDigits) where
import Numeric (readHex, readOct)
-- | Convert escaped sequences of characters into /real/ characters in a normal Python string.
-- XXX does not handle escaped unicode literals
unescapeString :: String -> String
unescapeString ('\\':'\\':cs) = '\\' : unescapeString cs -- Backslash (\)
unescapeString ('\\':'\'':cs) = '\'' : unescapeString cs -- Single quote (')
unescapeString ('\\':'"':cs) = '"' : unescapeString cs -- Double quote (")
unescapeString ('\\':'a':cs) = '\a' : unescapeString cs -- ASCII Bell (BEL)
unescapeString ('\\':'b':cs) = '\b' : unescapeString cs -- ASCII Backspace (BS)
unescapeString ('\\':'f':cs) = '\f' : unescapeString cs -- ASCII Formfeed (FF)
unescapeString ('\\':'n':cs) = '\n' : unescapeString cs -- ASCII Linefeed (LF)
unescapeString ('\\':'r':cs) = '\r' : unescapeString cs -- ASCII Carriage Return (CR)
unescapeString ('\\':'t':cs) = '\t' : unescapeString cs -- ASCII Horizontal Tab (TAB)
unescapeString ('\\':'v':cs) = '\v' : unescapeString cs -- ASCII Vertical Tab (VT)
unescapeString ('\\':'\n':cs) = unescapeString cs -- line continuation
unescapeString ('\\':rest@(o:_))
| o `elem` octalDigits = unescapeNumeric 3 octalDigits (fst . head . readOct) rest
unescapeString ('\\':'x':rest@(h:_))
| h `elem` hexDigits = unescapeNumeric 2 hexDigits (fst . head . readHex) rest
unescapeString (c:cs) = c : unescapeString cs
unescapeString [] = []
{-
-- | This function is a placeholder for unescaping characters in raw strings.
-- The Python documentation explicitly says that
-- "When an 'r' or 'R' prefix is present, a character following a backslash is included
-- in the string without change, and all backslashes are left in the string."
-- However it also says that When an 'r' or 'R' prefix is used in conjunction with
-- a 'u' or 'U' prefix, then the \uXXXX and \UXXXXXXXX escape sequences are processed
-- while all other backslashes are left in the string. Currently the function is the identity
-- but it ought to process unicode escape sequences.
-}
-- XXX does not handle escaped unicode literals
unescapeRawString :: String -> String
unescapeRawString = id
{-
-- | Convert escaped sequences of characters into /real/ characters in a raw Python string.
-- Note: despite their name, Python raw strings do allow a small set of character escapings,
-- namely the single and double quote characters and the line continuation marker.
unescapeRawString ('\\':'\'':cs) = '\'' : unescapeRawString cs -- Single quote (')
unescapeRawString ('\\':'"':cs) = '"' : unescapeRawString cs -- Double quote (")
unescapeRawString ('\\':'\n':cs) = unescapeRawString cs -- line continuation
unescapeRawString (c:cs) = c : unescapeRawString cs
unescapeRawString [] = []
-}
{-
This is a bit complicated because Python allows between 1 and 3 octal
characters after the \, and 1 and 2 hex characters after a \x.
-}
unescapeNumeric :: Int -> String -> (String -> Int) -> String -> String
unescapeNumeric n numericDigits readNumeric str
= loop n [] str
where
loop _ acc [] = [numericToChar acc]
loop 0 acc rest
= numericToChar acc : unescapeString rest
loop n acc (c:cs)
| c `elem` numericDigits = loop (n-1) (c:acc) cs
| otherwise = numericToChar acc : unescapeString (c:cs)
numericToChar :: String -> Char
numericToChar = toEnum . readNumeric . reverse
octalDigits, hexDigits :: String
-- | The set of valid octal digits in Python.
octalDigits = "01234567"
-- | The set of valid hex digits in Python.
hexDigits = "0123456789abcdef"