{-# LANGUAGE MagicHash #-}
module Hmssql.Query.Lexer
( Fragment (..)
, lexPlaceholders
, isVariableChar
) where
import Data.Char (GeneralCategory (..), generalCategory, isDigit)
import qualified Data.Text as T
import Data.Text.Internal (Text (..))
import Hmssql.Internal.Derived
data Fragment
= Static !Text
| Placeholder !Int
instance Show Fragment where
showsPrec :: Int -> Fragment -> ShowS
showsPrec Int
p = \case
Static Text
a -> Int -> Addr# -> Text -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"Static"# Text
a
Placeholder Int
a -> Int -> Addr# -> Int -> ShowS
forall a. Show a => Int -> Addr# -> a -> ShowS
showCon1 Int
p Addr#
"Placeholder"# Int
a
{-# OPAQUE showsPrec #-}
instance Eq Fragment where
Fragment
x == :: Fragment -> Fragment -> Bool
== Fragment
y = case (Fragment
x, Fragment
y) of
(Static Text
a, Static Text
a') -> Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
eq Text
a Text
a'
(Placeholder Int
a, Placeholder Int
a') -> Int
a Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
a'
(Fragment, Fragment)
_ -> Bool
False
{-# OPAQUE (==) #-}
lexPlaceholders :: Text -> [Fragment]
lexPlaceholders :: Text -> [Fragment]
lexPlaceholders whole :: Text
whole@(Text Array
arr Int
off0 Int
len0) = Int -> [(Int, Int, Int)] -> [Fragment]
fragments Int
off0 (Maybe Char -> Text -> [(Int, Int, Int)]
go Maybe Char
forall a. Maybe a
Nothing Text
whole)
where
fragments :: Int -> [(Int, Int, Int)] -> [Fragment]
fragments Int
from = \case
[] -> Int -> Int -> [Fragment]
static Int
from (Int
off0 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
len0)
(Int
start, Int
end, Int
n) : [(Int, Int, Int)]
ps -> Int -> Int -> [Fragment]
static Int
from Int
start [Fragment] -> [Fragment] -> [Fragment]
forall a. [a] -> [a] -> [a]
++ Int -> Fragment
Placeholder Int
n Fragment -> [Fragment] -> [Fragment]
forall a. a -> [a] -> [a]
: Int -> [(Int, Int, Int)] -> [Fragment]
fragments Int
end [(Int, Int, Int)]
ps
static :: Int -> Int -> [Fragment]
static Int
a Int
b = [Text -> Fragment
Static (Array -> Int -> Int -> Text
Text Array
arr Int
a (Int
b Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
a)) | Int
b Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
a]
go :: Maybe Char -> Text -> [(Int, Int, Int)]
go :: Maybe Char -> Text -> [(Int, Int, Int)]
go Maybe Char
prev Text
t
| Text -> Bool
T.null Text
t = []
| Bool
otherwise =
let (Text
plain, Text
rest) = (Char -> Bool) -> Text -> (Text, Text)
T.span Char -> Bool
ordinary Text
t
prev' :: Maybe Char
prev' = if Text -> Bool
T.null Text
plain then Maybe Char
prev else Char -> Maybe Char
forall a. a -> Maybe a
Just (HasCallStack => Text -> Char
Text -> Char
T.last Text
plain)
in Maybe Char -> Text -> [(Int, Int, Int)]
special Maybe Char
prev' Text
rest
special :: Maybe Char -> Text -> [(Int, Int, Int)]
special Maybe Char
prev Text
t = case Text -> Maybe (Char, Text)
T.uncons Text
t of
Maybe (Char, Text)
Nothing -> []
Just (Char
c, Text
rest) -> case Char
c of
Char
'\'' -> Char -> Text -> [(Int, Int, Int)]
skipQuoted Char
'\'' Text
rest
Char
'"' -> Char -> Text -> [(Int, Int, Int)]
skipQuoted Char
'"' Text
rest
Char
'[' -> Char -> Text -> [(Int, Int, Int)]
skipQuoted Char
']' Text
rest
Char
'-' | Text
"--" Text -> Text -> Bool
`T.isPrefixOf` Text
t -> Maybe Char -> Text -> [(Int, Int, Int)]
go (Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'-') ((Char -> Bool) -> Text -> Text
T.dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'\n') Text
t)
Char
'/' | Text
"/*" Text -> Text -> Bool
`T.isPrefixOf` Text
t -> Maybe Char -> Text -> [(Int, Int, Int)]
go (Char -> Maybe Char
forall a. a -> Maybe a
Just Char
'/') (Int -> Text -> Text
blockComment (Int
1 :: Int) (Int -> Text -> Text
T.drop Int
2 Text
t))
Char
'@' | Maybe Char -> Bool
placeholderMayStart Maybe Char
prev
, Just (Char
p, Text
afterP) <- Text -> Maybe (Char, Text)
T.uncons Text
rest
, Char
p Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'p' Bool -> Bool -> Bool
|| Char
p Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'P'
, (Text
digits, Text
after) <- (Char -> Bool) -> Text -> (Text, Text)
T.span Char -> Bool
isAsciiDigit Text
afterP
, Bool -> Bool
not (Text -> Bool
T.null Text
digits), Text -> Int
T.length Text
digits Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
9
, Bool -> (Char -> Bool) -> Maybe Char -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
True (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isVariableChar) ((Char, Text) -> Char
forall a b. (a, b) -> a
fst ((Char, Text) -> Char) -> Maybe (Char, Text) -> Maybe Char
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Maybe (Char, Text)
T.uncons Text
after)
-> (Text -> Int
offset Text
t, Text -> Int
end Text
digits, (Int -> Char -> Int) -> Int -> Text -> Int
forall a. (a -> Char -> a) -> a -> Text -> a
T.foldl' (\Int
n Char
d -> Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
10 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Char -> Int
forall a. Enum a => a -> Int
fromEnum Char
d Int -> Int -> Int
forall a. Num a => a -> a -> a
- Char -> Int
forall a. Enum a => a -> Int
fromEnum Char
'0') Int
0 Text
digits)
(Int, Int, Int) -> [(Int, Int, Int)] -> [(Int, Int, Int)]
forall a. a -> [a] -> [a]
: Maybe Char -> Text -> [(Int, Int, Int)]
go (Char -> Maybe Char
forall a. a -> Maybe a
Just (HasCallStack => Text -> Char
Text -> Char
T.last Text
digits)) Text
after
Char
_ -> Maybe Char -> Text -> [(Int, Int, Int)]
go (Char -> Maybe Char
forall a. a -> Maybe a
Just Char
c) Text
rest
offset :: Text -> Int
offset (Text Array
_ Int
o Int
_) = Int
o
end :: Text -> Int
end (Text Array
_ Int
o Int
l) = Int
o Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
l
ordinary :: Char -> Bool
ordinary Char
c = Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'\'' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'"' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'[' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'-' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'/' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'@'
skipQuoted :: Char -> Text -> [(Int, Int, Int)]
skipQuoted Char
close = Bool -> Text -> [(Int, Int, Int)]
region Bool
False
where
region :: Bool -> Text -> [(Int, Int, Int)]
region Bool
doubled Text
t =
let (Text
body, Text
rest) = (Char -> Bool) -> Text -> (Text, Text)
T.break (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
close) Text
t
in case Text -> Maybe (Char, Text)
T.uncons Text
rest of
Maybe (Char, Text)
Nothing -> Maybe Char -> Text -> [(Int, Int, Int)]
go (Char -> Maybe Char
forall a. a -> Maybe a
Just (Bool -> Text -> Char
lastOf Bool
doubled Text
body)) Text
rest
Just (Char
_, Text
afterClose)
| Just (Char
c, Text
afterDouble) <- Text -> Maybe (Char, Text)
T.uncons Text
afterClose, Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
close -> Bool -> Text -> [(Int, Int, Int)]
region Bool
True Text
afterDouble
| Bool
otherwise -> Maybe Char -> Text -> [(Int, Int, Int)]
go (Char -> Maybe Char
forall a. a -> Maybe a
Just Char
close) Text
afterClose
lastOf :: Bool -> Text -> Char
lastOf Bool
doubled Text
body
| Bool -> Bool
not (Text -> Bool
T.null Text
body) = HasCallStack => Text -> Char
Text -> Char
T.last Text
body
| Bool
doubled = Char
close
| Bool
otherwise = Char
opening
opening :: Char
opening = case Char
close of
Char
']' -> Char
'['
Char
q -> Char
q
blockComment :: Int -> Text -> Text
blockComment :: Int -> Text -> Text
blockComment Int
0 Text
rest = Text
rest
blockComment Int
d Text
rest
| Text -> Bool
T.null Text
rest = Text
rest
| Text
"/*" Text -> Text -> Bool
`T.isPrefixOf` Text
rest = Int -> Text -> Text
blockComment (Int
d Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (Int -> Text -> Text
T.drop Int
2 Text
rest)
| Text
"*/" Text -> Text -> Bool
`T.isPrefixOf` Text
rest = Int -> Text -> Text
blockComment (Int
d Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) (Int -> Text -> Text
T.drop Int
2 Text
rest)
| Bool
otherwise = Int -> Text -> Text
blockComment Int
d ((Char -> Bool) -> Text -> Text
T.dropWhile (\Char
c -> Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'/' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'*') (Int -> Text -> Text
T.drop Int
1 Text
rest))
placeholderMayStart :: Maybe Char -> Bool
placeholderMayStart = \case
Maybe Char
Nothing -> Bool
True
Just Char
c -> Bool -> Bool
not (Char -> Bool
isVariableChar Char
c)
isAsciiDigit :: Char -> Bool
isAsciiDigit :: Char -> Bool
isAsciiDigit Char
c = Char -> Bool
isDigit Char
c Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
'9'
isVariableChar :: Char -> Bool
isVariableChar :: Char -> Bool
isVariableChar Char
c = Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
'\xFFFF' Bool -> Bool -> Bool
&& (Char
c Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (String
"@#$_" :: String) Bool -> Bool -> Bool
|| case Char -> GeneralCategory
generalCategory Char
c of
GeneralCategory
UppercaseLetter -> Bool
True
GeneralCategory
LowercaseLetter -> Bool
True
GeneralCategory
TitlecaseLetter -> Bool
True
GeneralCategory
ModifierLetter -> Bool
True
GeneralCategory
OtherLetter -> Bool
True
GeneralCategory
NonSpacingMark -> Bool
True
GeneralCategory
SpacingCombiningMark -> Bool
True
GeneralCategory
DecimalNumber -> Bool
True
GeneralCategory
LetterNumber -> Bool
True
GeneralCategory
_ -> Bool
False)