Jikka-5.0.11.1: src/Jikka/Python/Parse/Alex.x
{
-- vim: filetype=haskell
{-# LANGUAGE FlexibleContexts #-}
-- |
-- Module : Jikka.Core.Parse.Alex
-- Description : tokenizes the code of the standard Python with Alex.
-- Copyright : (c) Kimiyuki Onaka, 2020
-- License : Apache License 2.0
-- Maintainer : kimiyuki95@gmail.com
-- Stability : experimental
-- Portability : portable
--
-- * TODO: tokenize float literals
-- * TODO: tokenize string literals
module Jikka.Python.Parse.Alex
( run
) where
import Data.Char (chr, isHexDigit, isOctDigit)
import Jikka.Common.Error
import Jikka.Common.Location
import Jikka.Common.Parse.JoinLines (joinLinesWithParens, removeEmptyLines)
import Jikka.Common.Parse.OffsideRule (insertIndents)
import Jikka.Python.Parse.Token
}
%wrapper "monad"
$space = [\ ]
$tab = [\t]
$alpha = [A-Z a-z]
$alnum = [0-9 A-Z a-z]
$doublequote = ["]
$backslash = [\\]
@nl = "\n" | "\r\n"
$digit = [0-9]
$nonzerodigit = [1-9]
$bindigit = [0-1]
$octdigit = [0-7]
$hexdigit = [0-9a-fA-F]
$shortstringchar_single = [^ \\ \r \n ']
$shortstringchar_double = [^ \\ \r \n ']
@stringescapeseq = $backslash .
tokens :-
$space + ;
"#" .* ;
$backslash @nl ;
@nl { tok Newline }
[\n\r] { tok Newline }
"None" { tok None }
"True" { tok (Bool True) }
"False" { tok (Bool False) }
"0" ("_" ? "0") * { tok' parseInt }
$nonzerodigit ("_" ? $digit) * { tok' parseInt }
"0" [bB] ("_" ? $bindigit) + { tok' parseInt }
"0" [oO] ("_" ? $octdigit) + { tok' parseInt }
"0" [xX] ("_" ? $hexdigit) + { tok' parseInt }
"'" ($shortstringchar_single | @stringescapeseq) * "'" { tok'' parseString }
$doublequote ($shortstringchar_double | @stringescapeseq) * $doublequote { tok'' parseString }
"def" { tok Def }
"if" { tok If }
"elif" { tok Elif }
"else" { tok Else }
"for" { tok For }
"in" { tok In }
"assert" { tok Assert }
"return" { tok Return }
"lambda" { tok Lambda }
-- punctuations
"->" { tok Arrow }
":" { tok Colon }
";" { tok Semicolon }
"," { tok Comma }
"." { tok Dot }
"=" { tok Equal }
"_" { tok Underscore }
-- parens
"{" { tok OpenBrace }
"[" { tok OpenBracket }
"(" { tok OpenParen }
"}" { tok CloseBrace }
"]" { tok CloseBracket }
")" { tok CloseParen }
-- special operators
"-" { tok MinusOp }
"*" { tok MulOp }
"**" { tok PowOp }
-- expr operators
"+" { tok PlusOp }
"//" { tok (DivModOp FloorDiv) }
"/" { tok (DivModOp Div) }
"%" { tok (DivModOp FloorMod) }
"&" { tok BitAndOp }
"|" { tok BitOrOp }
"^" { tok BitXorOp }
"~" { tok BitNotOp }
"<<" { tok BitLShiftOp }
">>" { tok BitRShiftOp }
">" { tok (CmpOp GreaterThan) }
"<" { tok (CmpOp LessThan) }
"<=" { tok (CmpOp LessEqual) }
">=" { tok (CmpOp GreaterEqual) }
"==" { tok (CmpOp DoubleEqual) }
"!=" { tok (CmpOp NotEqual) }
"and" { tok AndOp }
"or" { tok OrOp }
"not" { tok NotOp }
"@" { tok AtOp }
":=" { tok WalrusOp }
-- assignment operators
"+=" { tok (AugOp AugAdd) }
"-=" { tok (AugOp AugSub) }
"*=" { tok (AugOp AugMul) }
"@=" { tok (AugOp AugAt) }
"/=" { tok (AugOp AugDiv) }
"//=" { tok (AugOp AugFloorDiv) }
"%=" { tok (AugOp AugFloorMod) }
"%^=" { tok (AugOp AugCeilMod) }
"**=" { tok (AugOp AugPow) }
"<<=" { tok (AugOp AugBitRShift) }
">>=" { tok (AugOp AugBitLShift) }
"&=" { tok (AugOp AugBitAnd) }
"^=" { tok (AugOp AugBitXor) }
"|=" { tok (AugOp AugBitOr) }
-- additional operators
"/^" { tok (DivModOp CeilDiv) }
"%^" { tok (DivModOp CeilMod) }
"<?" { tok MinOp }
">?" { tok MaxOp }
"implies" { tok ImpliesOp }
"/^=" { tok (AugOp AugCeilDiv) }
"<?=" { tok (AugOp AugMin) }
">?=" { tok (AugOp AugMax) }
-- Python reserved
"as" { tok As }
"async" { tok Async }
"await" { tok Await }
"break" { tok Break }
"class" { tok Class }
"continue" { tok Continue }
"del" { tok Del }
"except" { tok Except }
"finally" { tok Finally }
"from" { tok From }
"global" { tok Global }
"import" { tok Import }
"is" { tok Is }
"nonlocal" { tok Nonlocal }
"pass" { tok Pass }
"raise" { tok Raise }
"try" { tok Try }
"while" { tok While }
"with" { tok With }
"yield" { tok Yield }
-- identifier
($alpha | _) ($alnum | _) * { tok' Ident }
-- catch error
. { skip' }
{
type Token'' = Either Error Token'
alexEOF :: Alex (Maybe Token'')
alexEOF = return Nothing
tok'' :: (Loc -> String -> Token'') -> AlexAction (Maybe Token'')
tok'' f (AlexPn _ line column, _, _, s) n = return . Just $ f loc (take n s) where
loc = Loc
{ line = line
, column = column
, width = n
}
tok' :: (String -> Token) -> AlexAction (Maybe Token'')
tok' f = tok'' (\loc s -> Right (WithLoc loc (f s)))
tok :: Token -> AlexAction (Maybe Token'')
tok token = tok' (const token)
parseInt :: String -> Token
parseInt s' = Int $ case filter (/= '_') s' of
'0' : 'b' : s -> foldl (\acc c -> acc * 2 + read [c]) 0 (reverse s)
'0' : 'B' : s -> foldl (\acc c -> acc * 2 + read [c]) 0 (reverse s)
s@('0' : 'o' : _) -> read s
s@('0' : 'O' : _) -> read s
s@('0' : 'x' : _) -> read s
s@('0' : 'X' : _) -> read s
s -> read s
parseString :: Loc -> String -> Token''
parseString loc s = WithLoc loc . String <$> go (tail (init s)) where
go "" = Right ""
go ('\\' : s) = case s of
[] -> throwInternalErrorAt loc "invalid escape sequence"
'a' : s -> ('\a' :) <$> go s
'b' : s -> ('\b' :) <$> go s
'f' : s -> ('\f' :) <$> go s
'n' : s -> ('\n' :) <$> go s
'r' : s -> ('\r' :) <$> go s
't' : s -> ('\t' :) <$> go s
'v' : s -> ('\v' :) <$> go s
o1 : o2 : o3 : s | isOctDigit o1 && isOctDigit o2 && isOctDigit o3 -> (chr (read ("0o" ++ [o1, o2, o3])) :) <$> go s
o1 : o2 : s | isOctDigit o1 && isOctDigit o2 -> (chr (read ("0o" ++ [o1, o2])) :) <$> go s
o1 : s | isOctDigit o1 -> (chr (read ("0o" ++ [o1])) :) <$> go s
'x' : h1 : h2 : s | isHexDigit h1 && isHexDigit h2 -> (chr (read ("0x" ++ [h1, h2])) :) <$> go s
'x' : _ -> throwLexicalErrorAt loc "truncated \\xXX escape"
c : s -> (c :) <$> go s
go (c : s) = (c :) <$> go s
skip' :: AlexAction (Maybe Token'')
skip' (AlexPn _ line column, _, _, s) n = return (Just (Left err)) where
loc = Loc line column n
msg = show (take n s) ++ " is not a acceptable character"
err = lexicalErrorAt loc msg
unfoldM :: Monad m => m (Maybe a) -> m [a]
unfoldM f = do
x <- f
case x of
Nothing -> return []
Just x -> (x :) <$> unfoldM f
run :: MonadError Error m => String -> m [Token']
run input = wrapError' "Jikka.Python.Parse.Alex failed" $ do
tokens <- case runAlex input (unfoldM alexMonadScan) of
Left err -> throwInternalError $ "Alex says: " ++ err
Right tokens -> return tokens
tokens <- reportErrors tokens
tokens <- joinLinesWithParens (`elem` [OpenParen, OpenBracket, OpenBrace]) (`elem` [CloseParen, CloseBracket, CloseBrace]) (== Newline) tokens
tokens <- return $ removeEmptyLines (== Newline) tokens
tokens <- insertIndents Indent Dedent (== Newline) tokens
return tokens
}