canontra-0.1.0.0: src/Canontra/Parser/Rust.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}
{- |
Module : Canontra.Parser.Rust
Description : High-performance zero-span Rust (2021+) AST parser.
Translates Rust source code into canontra's unified IR without source-span leakage,
supporting module hierarchies, use trees, structs, enums, traits, impl blocks,
pattern matching, macro calls with lifetime tokens (BUG-08), and visibility modifiers.
-}
module Canontra.Parser.Rust
( parseRustSource
) where
import Control.DeepSeq (NFData)
import Data.Char (isAlpha, isAlphaNum, isDigit, isSpace)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Read as TR
import GHC.Generics (Generic)
import Canontra.Canonical.Unicode (canonicalizeText)
import Canontra.IR.Declaration
import Canontra.IR.Dependency
import Canontra.IR.Expression
import Canontra.IR.Program
import Canontra.Types (ParseError (..))
parseRustSource :: FilePath -> Text -> Either ParseError Program
parseRustSource filePath input =
let cleanInput = canonicalizeText input
tokens = tokenizeRust cleanInput
in case parseRustTopLevel filePath tokens of
Left err -> Left err
Right (decls, imps, stmts) ->
let modul = Module (T.pack filePath) imps decls stmts
in Right (Program [modul] "rust")
data RustToken
= TokIdent Text
| TokKw Text
| TokNum Integer
| TokFloat Double
| TokStr Text
| TokSymbol Text
deriving stock (Eq, Show, Generic)
deriving anyclass (NFData)
tokenizeRust :: Text -> [RustToken]
tokenizeRust text = go text
where
go t | T.null t = []
go t =
let c = T.head t
cs = T.tail t
in case c of
_ | isSpace c -> go (T.dropWhile isSpace t)
'/' | T.isPrefixOf "/" cs ->
go (T.drop 1 (T.dropWhile (/= '\n') cs))
'/' | T.isPrefixOf "*" cs ->
skipBlockComment (T.drop 1 cs)
'"' ->
let (s, rest) = parseQuotedString '"' cs
in TokStr s : go rest
'\'' ->
case T.uncons cs of
Just (x, xs) | (isAlpha x || x == '_') && not (T.isPrefixOf "'" xs) ->
let (lifetimeIdent, rest) = T.span (\ch -> isAlphaNum ch || ch == '_') cs
in TokIdent ("'" <> lifetimeIdent) : go rest
Just (x, xs) | T.isPrefixOf "'" xs ->
TokStr (T.singleton x) : go (T.drop 1 xs)
Just ('\\', xs) ->
case T.uncons xs of
Just (escChar, afterEsc) | T.isPrefixOf "'" afterEsc ->
TokStr (T.singleton escChar) : go (T.drop 1 afterEsc)
_ -> TokSymbol "'" : go cs
_ ->
TokSymbol "'" : go cs
_ | isAlpha c || c == '_' ->
let (ident, rest) = T.span (\x -> isAlphaNum x || x == '_') t
in (if isRustKeyword ident then TokKw ident else TokIdent ident) : go rest
_ | isDigit c ->
let (numStr, rest) = T.span (\x -> isDigit x || x == '.' || x == 'e' || x == 'E') t
in if '.' `elem` T.unpack numStr
then case TR.double numStr of
Right (d, _) -> TokFloat d : go rest
Left _ -> TokFloat 0.0 : go rest
else case TR.decimal numStr of
Right (n, _) -> TokNum n : go rest
Left _ -> TokNum 0 : go rest
_ | c `elem` ("{}()[];,?:.~" :: String) ->
TokSymbol (T.singleton c) : go cs
_ | c `elem` ("=+-*/%&|^!<>:" :: String) ->
let (sym, rest) = T.span (`elem` ("=+-*/%&|^!<>:" :: String)) t
in TokSymbol sym : go rest
_ -> go cs
skipBlockComment t | T.null t = []
skipBlockComment t
| T.isPrefixOf "*/" t = go (T.drop 2 t)
| otherwise = skipBlockComment (T.tail t)
parseQuotedString q t =
let (body, rest) = parseQuotedBody q t ""
in (body, rest)
parseQuotedBody _ t acc | T.null t = (acc, "")
parseQuotedBody q t acc =
let c = T.head t
cs = T.tail t
in if c == q
then (acc, cs)
else if c == '\\' && not (T.null cs)
then let esc = case T.head cs of
'n' -> '\n'
't' -> '\t'
'r' -> '\r'
'\\' -> '\\'
'\'' -> '\''
'"' -> '"'
other -> other
in parseQuotedBody q (T.tail cs) (acc `T.snoc` esc)
else parseQuotedBody q cs (acc `T.snoc` c)
isRustKeyword :: Text -> Bool
isRustKeyword kw = kw `elem`
[ "fn", "struct", "enum", "trait", "impl", "for", "type", "mod", "use", "pub", "crate"
, "let", "mut", "const", "static", "if", "else", "match", "loop", "while", "return"
, "async", "await", "self", "Self", "where", "break", "continue"
]
parseRustTopLevel :: FilePath -> [RustToken] -> Either ParseError ([Declaration], [ImportDecl], [Stmt])
parseRustTopLevel _ tokens =
let (decls, imps, stmts) = extractRustDeclsAndStmts tokens
in Right (decls, imps, stmts)
extractRustDeclsAndStmts :: [RustToken] -> ([Declaration], [ImportDecl], [Stmt])
extractRustDeclsAndStmts [] = ([], [], [])
extractRustDeclsAndStmts tokens = case tokens of
-- use path::to::item;
TokKw "use" : rest ->
let (pathToks, afterSemi) = span (\tok -> tok /= TokSymbol ";") rest
path = T.concat [p | TokIdent p <- pathToks]
imp = ImportModule path Nothing
(d, i, s) = extractRustDeclsAndStmts (if null afterSemi then [] else tail afterSemi)
in (d, imp : i, s)
-- pub ...
TokKw "pub" : rest ->
extractRustDeclsAndStmts rest
-- fn / async fn / const fn
TokKw "async" : TokKw "fn" : TokIdent name : rest ->
let (fn, afterFn) = parseRustFunctionBody name True rest
(d, i, s) = extractRustDeclsAndStmts afterFn
in (DeclFunction fn : d, i, s)
TokKw "fn" : TokIdent name : rest ->
let (fn, afterFn) = parseRustFunctionBody name False rest
(d, i, s) = extractRustDeclsAndStmts afterFn
in (DeclFunction fn : d, i, s)
-- struct Name { ... }
TokKw "struct" : TokIdent name : rest ->
let afterBrace = dropWhile (\tok -> tok /= TokSymbol "{") rest
(fieldsToks, afterBody) = extractBalancedBraces afterBrace
fields = parseRustFields fieldsToks
st = Struct name fields [] "pub"
(d, i, s) = extractRustDeclsAndStmts afterBody
in (DeclStruct st : d, i, s)
-- trait Name { ... }
TokKw "trait" : TokIdent name : rest ->
let afterBrace = dropWhile (\tok -> tok /= TokSymbol "{") rest
(methodToks, afterBody) = extractBalancedBraces afterBrace
methods = parseRustMethods methodToks
tr = Trait name methods []
(d, i, s) = extractRustDeclsAndStmts afterBody
in (DeclTrait tr : d, i, s)
-- impl Trait for Target { ... } or impl Target { ... }
TokKw "impl" : rest ->
let (implDecl, afterBody) = parseRustImpl rest
(d, i, s) = extractRustDeclsAndStmts afterBody
in (DeclImpl implDecl : d, i, s)
t : ts ->
let (stmt, rest) = parseRustSingleStmt (t:ts)
(d, i, s) = extractRustDeclsAndStmts rest
in (d, i, maybe [] pure stmt ++ s)
parseRustImpl :: [RustToken] -> (Impl, [RustToken])
parseRustImpl tokens =
let (headerToks, afterHeader) = span (\t -> t /= TokSymbol "{") tokens
(bodyToks, afterBody) = extractBalancedBraces afterHeader
methods = parseRustMethods bodyToks
(mTrait, target) = case headerToks of
[TokIdent tr, TokKw "for", TokIdent tgt] -> (Just tr, tgt)
[TokIdent tgt] -> (Nothing, tgt)
_ -> (Nothing, "")
in (Impl mTrait target methods, afterBody)
parseRustFunctionBody :: Text -> Bool -> [RustToken] -> (Function, [RustToken])
parseRustFunctionBody name isAsync tokens =
let (mGenerics, afterGenerics) = case tokens of
TokSymbol "<" : rest ->
let (gToks, afterAngle) = extractBalancedAngle rest
gStr = "<" <> T.concat [tokenText t | t <- gToks] <> ">"
in (Just gStr, afterAngle)
_ -> (Nothing, tokens)
(params, afterParams) = parseRustParamList afterGenerics
(retType, afterRet) = parseRustReturnType afterParams
afterWhere = case afterRet of
TokKw "where" : rest -> dropWhile (\t -> t /= TokSymbol "{" && t /= TokSymbol ";") rest
_ -> afterRet
(bodyStmts, afterBody) = case afterWhere of
TokSymbol ";" : rest -> ([], rest)
_ ->
let afterBrace = dropWhile (\t -> t /= TokSymbol "{") afterWhere
(bodyToks, remToks) = extractBalancedBraces afterBrace
in (parseRustBodyStmts bodyToks, remToks)
decs = maybe [] pure mGenerics
fn = Function name params retType decs bodyStmts isAsync
in (fn, afterBody)
extractBalancedAngle :: [RustToken] -> ([RustToken], [RustToken])
extractBalancedAngle tokens = go (1 :: Int) [] tokens
where
go 0 acc remToks = (reverse acc, remToks)
go _ acc [] = (reverse acc, [])
go depth acc (TokSymbol "<" : xs) = go (depth + 1) (TokSymbol "<" : acc) xs
go depth acc (TokSymbol ">" : xs) =
if depth == 1
then (reverse acc, xs)
else go (depth - 1) (TokSymbol ">" : acc) xs
go depth acc (x : xs) = go depth (x : acc) xs
parseRustParamList :: [RustToken] -> ([Parameter], [RustToken])
parseRustParamList (TokSymbol "(" : rest) =
let (pToks, afterParen) = span (\t -> t /= TokSymbol ")") rest
params = extractRustParams pToks
remaining = if null afterParen then [] else tail afterParen
in (params, remaining)
parseRustParamList tokens = ([], tokens)
extractRustParams :: [RustToken] -> [Parameter]
extractRustParams [] = []
extractRustParams (TokKw "self" : rest) =
Parameter "self" ParamPositional Nothing Nothing : extractRustParams (dropWhile (\t -> t == TokSymbol ",") rest)
extractRustParams (TokSymbol "&" : TokKw "self" : rest) =
Parameter "&self" ParamPositional Nothing Nothing : extractRustParams (dropWhile (\t -> t == TokSymbol ",") rest)
extractRustParams (TokSymbol "&" : TokKw "mut" : TokKw "self" : rest) =
Parameter "&mut self" ParamPositional Nothing Nothing : extractRustParams (dropWhile (\t -> t == TokSymbol ",") rest)
extractRustParams (TokIdent pName : TokSymbol ":" : rest) =
let (tyToks, afterTy) = span (\t -> t /= TokSymbol "," && t /= TokSymbol ")") rest
tyStr = if null tyToks then Nothing else Just (T.concat [tokenText t | t <- tyToks])
remToks = dropWhile (\t -> t == TokSymbol ",") afterTy
in Parameter pName ParamPositional Nothing tyStr : extractRustParams remToks
extractRustParams (TokIdent pName : rest) =
Parameter pName ParamPositional Nothing Nothing : extractRustParams (dropWhile (\t -> t == TokSymbol ",") rest)
extractRustParams (_:rest) = extractRustParams rest
parseRustReturnType :: [RustToken] -> (Maybe Text, [RustToken])
parseRustReturnType (TokSymbol "->" : rest) =
let (tyToks, afterTy) = span (\t -> t /= TokSymbol "{" && t /= TokKw "where" && t /= TokSymbol ";") rest
tyStr = if null tyToks then Nothing else Just (T.concat [tokenText t | t <- tyToks])
in (tyStr, afterTy)
parseRustReturnType tokens = (Nothing, tokens)
parseRustFields :: [RustToken] -> [(Text, Maybe Text)]
parseRustFields [] = []
parseRustFields (TokKw "pub" : rest) = parseRustFields rest
parseRustFields (TokIdent fName : TokSymbol ":" : rest) =
let (tyToks, afterTy) = span (\t -> t /= TokSymbol "," && t /= TokSymbol "}") rest
tyStr = if null tyToks then Nothing else Just (T.concat [tokenText t | t <- tyToks])
remToks = dropWhile (\t -> t == TokSymbol ",") afterTy
in (fName, tyStr) : parseRustFields remToks
parseRustFields (_:rest) = parseRustFields rest
parseRustMethods :: [RustToken] -> [Function]
parseRustMethods [] = []
parseRustMethods (TokKw "pub" : rest) = parseRustMethods rest
parseRustMethods (TokKw "fn" : TokIdent name : rest) =
let (fn, afterFn) = parseRustFunctionBody name False rest
in fn : parseRustMethods afterFn
parseRustMethods (TokKw "async" : TokKw "fn" : TokIdent name : rest) =
let (fn, afterFn) = parseRustFunctionBody name True rest
in fn : parseRustMethods afterFn
parseRustMethods (_:rest) = parseRustMethods rest
extractBalancedBraces :: [RustToken] -> ([RustToken], [RustToken])
extractBalancedBraces (TokSymbol "{" : rest) = go (1 :: Int) [] rest
where
go 0 acc remaining = (reverse acc, remaining)
go _ acc [] = (reverse acc, [])
go depth acc (TokSymbol "{" : xs) = go (depth + 1) (TokSymbol "{" : acc) xs
go depth acc (TokSymbol "}" : xs) =
if depth == 1
then (reverse acc, xs)
else go (depth - 1) (TokSymbol "}" : acc) xs
go depth acc (x:xs) = go depth (x : acc) xs
extractBalancedBraces tokens = ([], tokens)
parseRustBodyStmts :: [RustToken] -> [Stmt]
parseRustBodyStmts [] = []
parseRustBodyStmts (TokKw "loop" : rest) =
let afterBrace = dropWhile (\t -> t /= TokSymbol "{") rest
(bodyToks, afterBody) = extractBalancedBraces afterBrace
in StmtLoop (parseRustBodyStmts bodyToks) : parseRustBodyStmts afterBody
parseRustBodyStmts tokens =
case parseRustSingleStmt tokens of
(Just stmt, afterStmt) -> stmt : parseRustBodyStmts afterStmt
(Nothing, _:rest) -> parseRustBodyStmts rest
(Nothing, []) -> []
parseRustSingleStmt :: [RustToken] -> (Maybe Stmt, [RustToken])
parseRustSingleStmt [] = (Nothing, [])
parseRustSingleStmt (TokKw "return" : rest) =
let (expr, afterExpr) = parseRustSimpleExpr rest
in (Just (StmtReturn (Just expr)), afterExpr)
parseRustSingleStmt (TokKw "let" : rest) =
let restAfterMut = case rest of
TokKw "mut" : r -> r
_ -> rest
in case restAfterMut of
TokIdent name : TokSymbol ":" : afterName ->
let afterType = dropWhile (\t -> t /= TokSymbol "=" && t /= TokSymbol ";") afterName
in case afterType of
TokSymbol "=" : exprToks ->
let (expr, afterExpr) = parseRustSimpleExpr exprToks
in (Just (StmtAssign [ExprId name] expr), afterExpr)
_ ->
let nextToks = dropWhile (\t -> t == TokSymbol ";") afterType
in (Just (StmtAssign [ExprId name] (ExprId "()")), nextToks)
TokIdent name : TokSymbol "=" : exprToks ->
let (expr, afterExpr) = parseRustSimpleExpr exprToks
in (Just (StmtAssign [ExprId name] expr), afterExpr)
TokIdent name : TokSymbol ";" : afterSemi ->
(Just (StmtAssign [ExprId name] (ExprId "()")), afterSemi)
_ -> (Nothing, rest)
parseRustSingleStmt (TokIdent name : TokSymbol "!" : rest) =
let (macroExpr, afterMacro) = parseRustSimpleExpr (TokIdent name : TokSymbol "!" : rest)
in (Just (StmtExpr macroExpr), afterMacro)
parseRustSingleStmt (TokIdent name : TokSymbol "=" : rest) =
let (expr, afterExpr) = parseRustSimpleExpr rest
in (Just (StmtAssign [ExprId name] expr), afterExpr)
parseRustSingleStmt (TokIdent name : TokSymbol "(" : rest) =
let afterParen = dropWhile (\t -> t /= TokSymbol ")") rest
nextToks = dropWhile (\t -> t == TokSymbol ";") (if null afterParen then [] else tail afterParen)
in (Just (StmtExpr (ExprCall (ExprId name) [] [])), nextToks)
parseRustSingleStmt (_:rest) = (Nothing, rest)
parseRustSimpleExpr :: [RustToken] -> (Expr, [RustToken])
parseRustSimpleExpr (TokSymbol ";" : rest) = (ExprLit LitNone, rest)
parseRustSimpleExpr (TokIdent name : TokSymbol "!" : TokSymbol openB : rest)
| openB `elem` ["(", "[", "{"] =
let closeB = case openB of "(" -> ")"; "[" -> "]"; _ -> "}"
(bodyToks, afterClose) = extractBalancedDelim openB closeB rest
nextToks = dropWhile (\t -> t == TokSymbol ";") afterClose
macroArgs = if null bodyToks then [] else [ExprId (T.concat [tokenText t | t <- bodyToks])]
in (ExprMacroCall name macroArgs, nextToks)
parseRustSimpleExpr (TokIdent name : TokSymbol "(" : rest) =
let afterParen = dropWhile (\t -> t /= TokSymbol ")") rest
nextToks = dropWhile (\t -> t == TokSymbol ";") (if null afterParen then [] else tail afterParen)
in (ExprCall (ExprId name) [] [], nextToks)
parseRustSimpleExpr (TokNum n : rest) =
let nextToks = dropWhile (\t -> t == TokSymbol ";") rest
in (ExprLit (LitInt n), nextToks)
parseRustSimpleExpr (TokFloat f : rest) =
let nextToks = dropWhile (\t -> t == TokSymbol ";") rest
in (ExprLit (LitFloat f), nextToks)
parseRustSimpleExpr (TokStr s : rest) =
let nextToks = dropWhile (\t -> t == TokSymbol ";") rest
in (ExprLit (LitString s), nextToks)
parseRustSimpleExpr (TokIdent name : rest) =
let nextToks = dropWhile (\t -> t == TokSymbol ";") rest
in (ExprId name, nextToks)
parseRustSimpleExpr tokens =
let after = dropWhile (\t -> t /= TokSymbol ";") tokens
in (ExprLit LitNone, if null after then [] else tail after)
extractBalancedDelim :: Text -> Text -> [RustToken] -> ([RustToken], [RustToken])
extractBalancedDelim openB closeB tokens = go (1 :: Int) [] tokens
where
go 0 acc remToks = (reverse acc, remToks)
go _ acc [] = (reverse acc, [])
go depth acc (TokSymbol s : xs)
| s == openB = go (depth + 1) (TokSymbol s : acc) xs
| s == closeB =
if depth == 1
then (reverse acc, xs)
else go (depth - 1) (TokSymbol s : acc) xs
go depth acc (x : xs) = go depth (x : acc) xs
tokenText :: RustToken -> Text
tokenText (TokIdent t) = t
tokenText (TokKw t) = t
tokenText (TokSymbol t) = t
tokenText (TokStr t) = "\"" <> t <> "\""
tokenText (TokNum n) = T.pack (show n)
tokenText (TokFloat f) = T.pack (show f)