packages feed

preql-0.4: src/Preql/QuasiQuoter/Syntax/Lex.x

{
module Preql.QuasiQuoter.Syntax.Lex where

import Data.Text (Text)
import Data.Word (Word)
import Prelude hiding (LT, GT, lex)

import qualified Data.Text as T

}

%wrapper "monadUserState"

$unicodeIds = $printable # [$white \,\.\;\'\"\(\)\<\>=\+\-\^\!@]
$firstLetter = $unicodeIds # [0-9_\$]
$quoted = $printable # [\']
$digit = [0-9]
$haskell = $printable # [\}]
$a = [aA]
$b = [bB]
$c = [cC]
$d = [dD]
$e = [eE]
$f = [fF]
$g = [gG]
$h = [hH]
$i = [iI]
$j = [jJ]
$k = [kK]
$l = [lL]
$m = [mM]
$n = [nN]
$o = [oO]
$p = [pP]
$q = [qQ]
$r = [rR]
$s = [sS]
$t = [tT]
$u = [uU]
$v = [vV]
$w = [wW]
$x = [xX]
$y = [yY]
$z = [zZ]

tokens :-

    $white+            ;

    $a $l $l  { lex ALL }
    $a $n $d { lex AND }
    $a $s $c { lex ASC }
    $a $s { lex AS }
    $b $y { lex BY }
    $c $a $s $e { lex CASE }
    $c $o $a $l $e $s $c $e { lex COALESCE }
    $c $r $o $s $s  { lex CROSS }
    $d $e $l $e $t $e { lex DELETE_P }
    $d $e $s $c { lex DESC }
    $d $i $s $t $i $n $c $t { lex DISTINCT }
    $e $l $s $e { lex ELSE }
    $e $n $d { lex END_P }
    $e $s $c $a $p $e  { lex ESCAPE }
    $e $x $c $e $p $t { lex EXCEPT }
    $f $a $l $s $e { lex FALSE_P }
    $f $i $l $t $e $r { lex FILTER }
    $f $i $r $s $t { lex First }
    $f $o $r { lex FOR }
    $f $r $o $m { lex FROM }
    $f $u $l $l { lex FULL }
    $g $r $e $a $t $e $s $t { lex GREATEST }
    $g $r $o $u $p { lex GROUP_P }
    $h $a $v $i $n $g { lex HAVING }
    $i $l $i $k $e { lex ILIKE }
    $i $n $n $e $r { lex INNER_P }
    $i $n $s $e $r $t { lex INSERT }
    $i $n $t $e $r $s $e $c $t  { lex INTERSECT }
    $i $n $t $o { lex INTO }
    $i $s $n $u $l $l { lex ISNULL }
    $i $s { lex IS }
    $j $o $i $n { lex JOIN }
    $k $e $y { lex KEY }
    $l $a $s $t { lex LAST }
    $l $e $a $s $t { lex LEAST }
    $l $e $f $t { lex LEFT }
    $l $i $k $e { lex LIKE }
    $l $i $m $i $t { lex LIMIT }
    $l $o $c $k $e $d { lex LOCKED }
    $m $a $t $e $r $i $a $l $i $z $e $d { lex MATERIALIZED }
    $n $a $t $u $r $a $l { lex NATURAL }
    $n $o $t $n $u $l $l { lex NOTNULL }
    $n $o $t { lex NOT }
    $n $o $w $a $i $t { lex NOWAIT }
    $n $o { lex NO }
    $n $u $l $l $s { lex Nulls }
    $n $u $l $l { lex NULL_P }
    $o $f $f $s $e $t { lex OFFSET }
    $o $f { lex OF }
    $o $n { lex ON }
    $o $p $e $r $a $t $o $r { lex OPERATOR }
    $o $r $d $e $r { lex ORDER }
    $o $r { lex OR }
    $o $v $e $r { lex OVER }
    $p $a $r $t $i $t $i $o $n { lex PARTITION }
    $r $e $c $u $r $s $i $v $e { lex RECURSIVE }
    $r $i $g $h $t { lex RIGHT }
    $s $e $l $e $c $t { lex SELECT }
    $s $e $t { lex SET }
    $s $h $a $r $e { lex SHARE }
    $s $i $m $i $l $a $r { lex SIMILAR }
    $s $k $i $p { lex SKIP }
    $t $a $b $l $e { lex TABLE }
    $t $h $e $n { lex THEN }
    $t $o { lex TO }
    $t $r $u $e { lex TRUE_P }
    $u $n $i $o $n { lex UNION }
    $u $p $d $a $t $e { lex UPDATE }
    $u $s $i $n $g { lex USING }
    $v $a $l $u $e $s { lex VALUES }
    $w $h $e $n { lex WHEN }
    $w $h $e $r $e { lex WHERE }
    $w $i $t $h $i $n { lex WITHIN }
    $w $i $t $h { lex WITH }

    "(" { lex LParen }
    "," { lex Comma }
    ")" { lex RParen }
    "." { lex Dot }
    "*" { lex Mul }
    "/" { lex Div }
    "%" { lex Mod }
    "+" { lex Add }
    "-" { lex Sub }
    "^" { lex Exponent }
    "<>" { lex NotEquals }
    "!=" { lex NotEquals }
    "<" { lex LT }
    "<=" { lex LTE }
    ">" { lex GT }
    ">=" { lex GTE }
    "=" { lex Equals }
    ":=" {lex COLON_EQUALS }
    "=>" { lex EQUALS_GREATER }

    [\'] ("''" | $quoted)* [\'] { lex' (String . T.pack . unquoteString) }
    $firstLetter $unicodeIds* { lex' (Name . T.pack) }

    $digit+ { lex' (Iconst . read) } -- positive only?
    $digit+ ("." $digit+) { lex' (Fconst . read) }
    $digit+ ("." $digit+)? ($e "-"? $digit+) { lex' (Fconst . read) }

    "$" $digit+ { lex' (NumberedParam . read . tail) }
    "${" $haskell+ "}" { lex' (HaskellParam . T.pack . init . drop 2) }
    ";" { lex Semicolon }

{

data LocToken = LocToken
     { loc :: AlexPosn
     , unLoc :: Token
     } deriving Show

-- commented out PascallCase, where I've added CAPS
data Token = -- Delete | Select | Insert
    Nulls | First
    -- TODO rename Name -> Ident to match bison
    | Name Text | String Text | Iconst Word | Fconst Double
    | NumberedParam Word | HaskellParam Text
    | LParen | RParen | Comma
    | Mul | Div | Add | Sub | Mod | Exponent
    | Equals | NotEquals | LT | LTE | GT | GTE
    | Dot | Semicolon | EOF
    | COLON_EQUALS | EQUALS_GREATER
    -- all the keywords, from bison
    | ABORT_P | AUTHORIZATION | BETWEEN | ABSOLUTE_P | ACCESS | ACTION | ADD_P
    | ADMIN | AFTER | AGGREGATE | ALL | ALSO | ALTER | ALWAYS | ANALYSE | ANALYZE | AND
    | ANY | ARRAY | AS | ASC | ASSERTION | ASSIGNMENT | ASYMMETRIC | AT | ATTACH
    | ATTRIBUTE | BACKWARD | BEFORE | BEGIN_P | BIGINT | BINARY | BIT | BOOLEAN_P
    | BOTH | BY | CACHE | CALL | CALLED | CASCADE | CASCADED | CASE | CAST | CATALOG_P
    | CHAIN | CHARACTER | CHARACTERISTICS | CHAR_P | CHECK | CHECKPOINT | CLASS
    | CLOSE | CLUSTER | COALESCE | COLLATE | COLLATION | COLUMN | COLUMNS | COMMENT
    | COMMENTS | COMMIT | COMMITTED | CONCURRENTLY | CONFIGURATION | CONFLICT
    | CONNECTION | CONSTRAINT | CONSTRAINTS | CONTENT_P | CONTINUE_P
    | CONVERSION_P | COPY | COST | CREATE | CROSS | CSV | CUBE | CURRENT_CATALOG
    | CURRENT_DATE | CURRENT_P | CURRENT_ROLE | CURRENT_SCHEMA | CURRENT_TIME
    | CURRENT_TIMESTAMP | CURRENT_USER | CURSOR | CYCLE | DATABASE | DATA_P
    | DAY_P | DEALLOCATE | DEC | DECIMAL_P | DECLARE | DEFAULT | DEFAULTS
    | DEFERRABLE | DEFERRED | DEFINER | DELETE_P | DELIMITER | DELIMITERS
    | DEPENDS | DESC | DETACH | DICTIONARY | DISABLE_P | DISCARD | DISTINCT | DO
    | DOCUMENT_P | DOMAIN_P | DOUBLE_P | DROP | EACH | ELSE | ENABLE_P | ENCODING
    | ENCRYPTED | END_P | ENUM_P | ESCAPE | EVENT | EXCEPT | EXCLUDE | EXCLUDING
    | EXCLUSIVE | EXECUTE | EXISTS | EXPLAIN | EXTENSION | EXTERNAL | EXTRACT
    | FALSE_P | FAMILY | FETCH | FILTER | FIRST_P | FLOAT_P | FOLLOWING | FOR | FORCE
    | FOREIGN | FORWARD | FREEZE | FROM | FULL | FUNCTION | FUNCTIONS | GENERATED
    | GLOBAL | GRANT | GRANTED | GREATEST | GROUPING | GROUPS | GROUP_P | HANDLER
    | HAVING | HEADER_P | HOLD | HOUR_P | IDENTITY_P | IF_P | ILIKE | IMMEDIATE
    | IMMUTABLE | IMPLICIT_P | IMPORT_P | INCLUDE | INCLUDING | INCREMENT | INDEX
    | INDEXES | INHERIT | INHERITS | INITIALLY | INLINE_P | INNER_P | INOUT
    | INPUT_P | INSENSITIVE | INSERT | INSTEAD | INTEGER | INTERSECT | INTERVAL
    | INTO | INT_P | INVOKER | IN_P | IS | ISNULL | ISOLATION | JOIN | KEY | LABEL
    | LANGUAGE | LARGE_P | LAST | LATERAL_P | LEADING | LEAKPROOF | LEAST | LEFT
    | LEVEL | LIKE | LIMIT | LISTEN | LOAD | LOCAL | LOCALTIME | LOCALTIMESTAMP
    | LOCATION | LOCKED | LOCK_P | LOGGED | MAPPING | MATCH | MATERIALIZED
    | MAXVALUE | METHOD | MINUTE_P | MINVALUE | MODE | MONTH_P | MOVE | NAMES
    | NAME_P | NATIONAL | NATURAL | NCHAR | NEW | NEXT | NO | NONE | NOT | NOTHING
    | NOTIFY | NOTNULL | NOWAIT | NULLIF | NULLS_P | NULL_P | NUMERIC | OBJECT_P | OF
    | OFF | OFFSET | OIDS | OLD | ON | ONLY | OPERATOR | OPTION | OPTIONS | OR | ORDER
    | ORDINALITY | OTHERS | OUTER_P | OUT_P | OVER | OVERLAPS | OVERLAY
    | OVERRIDING | OWNED | OWNER | PARALLEL | PARSER | PARTIAL | PARTITION
    | PASSING | PASSWORD | PLACING | PLANS | POLICY | POSITION | PRECEDING
    | PRECISION | PREPARE | PREPARED | PRESERVE | PRIMARY | PRIOR | PRIVILEGES
    | PROCEDURAL | PROCEDURE | PROCEDURES | PROGRAM | PUBLICATION | QUOTE | RANGE
    | READ | REAL | REASSIGN | RECHECK | RECURSIVE | REF | REFERENCES | REFERENCING
    | REFRESH | REINDEX | RELATIVE_P | RELEASE | RENAME | REPEATABLE | REPLACE
    | REPLICA | RESET | RESTART | RESTRICT | RETURNING | RETURNS | REVOKE | RIGHT
    | ROLE | ROLLBACK | ROLLUP | ROUTINE | ROUTINES | ROW | ROWS | RULE | SAVEPOINT
    | SCHEMA | SCHEMAS | SCROLL | SEARCH | SECOND_P | SECURITY | SELECT | SEQUENCE
    | SEQUENCES | SERIALIZABLE | SERVER | SESSION | SESSION_USER | SET | SETOF
    | SETS | SHARE | SHOW | SIMILAR | SIMPLE | SKIP | SMALLINT | SNAPSHOT | SOME
    | SQL_P | STABLE | STANDALONE_P | START | STATEMENT | STATISTICS | STDIN
    | STDOUT | STORAGE | STORED | STRICT_P | STRIP_P | SUBSCRIPTION | SUBSTRING
    | SUPPORT | SYMMETRIC | SYSID | SYSTEM_P | TABLE | TABLES | TABLESAMPLE
    | TABLESPACE | TEMP | TEMPLATE | TEMPORARY | TEXT_P | THEN | TIES | TIME
    | TIMESTAMP | TO | TRAILING | TRANSACTION | TRANSFORM | TREAT | TRIGGER | TRIM
    | TRUE_P | TRUNCATE | TRUSTED | TYPES_P | TYPE_P | UNBOUNDED | UNCOMMITTED
    | UNENCRYPTED | UNION | UNIQUE | UNKNOWN | UNLISTEN | UNLOGGED | UNTIL | UPDATE
    | USER | USING | VACUUM | VALID | VALIDATE | VALIDATOR | VALUES | VALUE_P
    | VARCHAR | VARIADIC | VARYING | VERBOSE | VERSION_P | VIEW | VIEWS
    | VOLATILE | WHEN | WHERE | WHITESPACE_P | WINDOW | WITH | WITHIN
    | WITHOUT | WORK | WRAPPER | WRITE | XMLATTRIBUTES | XMLCONCAT
    | XMLELEMENT | XMLEXISTS | XMLFOREST | XMLNAMESPACES | XMLPARSE
    | XMLPI | XMLROOT | XMLSERIALIZE | XMLTABLE | XML_P | YEAR_P
    | YES_P | ZONE

     deriving (Show, Read, Eq, Ord)

/* from https://github.com/dagit/happy-plus-alex/blob/master/src/Lexer.x */

-- To improve error messages, We keep the path of the file we are
-- lexing in our own state.
data AlexUserState = AlexUserState { filePath :: FilePath }

alexInitUserState :: AlexUserState
alexInitUserState = AlexUserState "<unknown>"

getFilePath :: Alex FilePath
getFilePath = filePath <$> alexGetUserState

setFilePath :: FilePath -> Alex ()
setFilePath = alexSetUserState . AlexUserState

-- For nice parser error messages.
unLex :: Token -> String
unLex t = case t of
    Name n -> T.unpack n 
    String s -> T.unpack s
    Iconst n -> show n
    Fconst n -> show n
    NumberedParam i -> '$' : show i
    HaskellParam s -> "${" ++ T.unpack s ++ "}"
    LParen  -> "("
    RParen  -> ")"
    Comma -> ","
    Mul -> "*"
    Div -> "/"
    Add -> "+"
    Sub -> "-"
    Exponent -> "^"
    ISNULL -> "IsNull"
    NOTNULL -> "NotNull"
    Equals -> "="
    NotEquals -> "!="
    Semicolon -> ";"
    EOF -> "<EOF>"
    _ -> show t

-- | remove single quotes, and '' escape sequences
unquoteString :: String -> String
unquoteString ('\'' : str) = go str
  where
    go  ('\'' : '\'' : rest) = '\'' : go rest
    go ['\''] = ""
    go [] = error "string did not end with a '"
    go ('\'' : _rest) = error "unescaped ' in middle"
    go (c : rest)  = c : go rest
unquoteString _ = error "string did not begin with a '"

alexEOF :: Alex LocToken
alexEOF = do
  (p,_,_,_) <- alexGetInput
  return $ LocToken p EOF

-- Unfortunately, we have to extract the matching bit of string ourselves...
lex' :: (String -> Token) -> AlexAction LocToken
lex' f = \(p,_,_,s) i -> return $ LocToken p (f (take i s))

-- For constructing tokens that do not depend on the input
lex :: Token -> AlexAction LocToken
lex = lex' . const

-- We rewrite alexMonadScan' to delegate to alexError' when lexing fails
-- (the default implementation just returns an error message).
alexMonadScan' :: Alex LocToken
alexMonadScan' = do
  inp <- alexGetInput
  sc <- alexGetStartCode
  case alexScan inp sc of
    AlexEOF -> alexEOF
    AlexError (p, _, _, s) ->
        alexErrorPosn p ("lexical error at character '" ++ take 1 s ++ "'")
    AlexSkip  inp' _len -> do
        alexSetInput inp'
        alexMonadScan'
    AlexToken inp' len action -> do
        alexSetInput inp'
        action (ignorePendingBytes inp) len

-- Signal an error, including a commonly accepted source code position.
alexErrorPosn :: AlexPosn -> String -> Alex a
alexErrorPosn (AlexPn _ l c) msg = do
  fp <- getFilePath
  alexError (fp ++ ":" ++ show l ++ ":" ++ show c ++ ": " ++ msg)

instance MonadFail Alex where
    fail = alexError

-- A variant of runAlex, keeping track of the path of the file we are lexing.
runAlexWithFilepath :: Alex a -> FilePath -> String -> Either String a
runAlexWithFilepath a fp input = runAlex input (setFilePath fp >> a)

lexAll :: Alex [LocToken]
lexAll = do
    t <- alexMonadScan
    case unLoc t of
        EOF -> return [t]
        _ -> fmap (t :) lexAll

testLex' :: String -> Either String [LocToken]
testLex' s = runAlex s lexAll

testLex :: String -> Either String [Token]
testLex s = map unLoc <$> testLex' s

}