为什么这个词法分析器不解析这个输入?

Why is this lexer not parsing this input?

我想对以下代码示例进行 lex:

prop levelBasedAlerter uni { a b } \I -> 
  levelBasedAlerter a                           
    | a > I ->                                   
      b: "ALERT: %a"                            

这应该是

Prop
Var "levelBasedAlerter"
Uni
PortSpecS " { a b }"
Lam
Var "I"
PatternMatchEnd
Indent 2
Var "levelBasedAlerter"
Var "a"
Indent 4
PatternGuard
Var "a"
Var ">"
Var "I" 
PatternMatchEnd
Indent 6
Var "b"
DefinedByCol
StringLit "Alert: %a"

然而,我的 alex 词法分析器在第一行遇到 \ 时出错(在 \ 后面有和没有 space)。

为什么会这样? 词法分析器:

{
{-# LANGUAGE DeriveDataTypeable #-}
module Lexer where
import Data.Typeable
import Data.Data
import Data.List
import Data.List.Split
import Data.Char
import Debug.Trace
import Prelude hiding (lex)
import Control.Monad (liftM)
}

%wrapper "posn"

$digit            = 0-9
@string           = (. # [\" \] )

$alpha            = [a-zA-Z]
@real             = ($digit+ \. | $digit * \. $digit +)
@boolLit          = ("True"|"False")
@alphaNum         = ($alpha|$digit)+
$bracketsOpen     = [\(\[\{]
$bracketsClose    = [\)\]\}]
$brackets         = [ $bracketsOpen $bracketsClose]
@identifier       = [^ : ! = \ \ " $brackets]+
@commaOrSpace     = (\,\ * | \ +)
@scopedIdentifier = @identifier(\.@identifier)+
@globalKeyword    = (prop|mesh|let|omni|uni|let|using|module|import|where)
@port             = (@identifier:\ *)?@identifier
@portSpec         = ((@identifier|@scopedIdentifier):)?
                    " "*
                        \{\ * @port
                            (@commaOrSpace @port)*
                        " "*\} 
@deepPortSpec     = ((@identifier|@scopedIdentifier):)?
                    " "*
                        \{\ * @identifier: (. # \})+ \} 
@indent           = \n[\t\ ]+

tokens :-
    @indent         { \_ s -> Indent $ length s }
    $white+         ;
    "--".*          ;
    @globalKeyword  { \_ keyword -> getTokenOf keyword }
    $digit+         { \_ s -> IntL (read s) }
    @real+          { \_ s -> DoubleL (read s) }
    @boolLit        { \_ s -> BoolL (read s) }
    \" @string \"   { \_ s -> StringLit (tail . init $ s) }
    @portSpec       { \_ s -> PortSpecS s } 
    @deepPortSpec   { \_ s -> DeepPortSpecS s }
    ":"             { \_ s -> DefinedByCol }
    ","             { \_ s -> Comma }
    "!"             { \_ s -> Negate }
    "=="            { \_ s -> Eq }
    "="             { \_ s -> LetAssOp }
    "~>"            { \_ s -> Wire }
    "->"            { \_ s -> PatternMatchEnd }
    $bracketsOpen   { \_ s -> BracO s}
    $bracketsClose  { \_ s -> BracC s}
    "||"            { \_ s -> Or }
    "|"             { \_ s -> PatternGuard}
    "!!"            { \_ s -> AccessPort }
    "\"            { \_ s -> Lam }

    @scopedIdentifier {\_ s -> ScopedVar s }
    @identifier     { \_ s -> Var s }

{

clean :: String -> String
clean s = reverse $ rmWs $ reverse $ rmWs s
    where rmWs = dropWhile (\c -> c ==' ' || c == '\t')

traceThis :: (Show a) => a -> a
traceThis a = trace ("DEBUG: " ++ show a) a

data Token
           = Prop
           | Mesh
           | Module
           | Import
           | Where
           | Var String
           | BracO String
           | BracC String
           | Comma
           | Eq
           | PatternGuard
           | Or
           | ScopedVar String 
           | Omni
           | Uni
           | PortSpecS String
           | DeepPortSpecS String
           | DefinedByCol                       -- ':' after definitions
           | Indent Int
           | PatternMatchEnd                    -- '->' after PM
           | Negate    
           | Let 
           | LetAssOp                           -- '=' in let x = ...
           | Wire
           | AccessPort
           | Using
           | Lam
           | StringLit String
           | IntL Int
           | DoubleL Double
           | BoolL Bool
           | EOF 
    deriving (Eq,Show,Data)

getTokenOf :: String -> Token
getTokenOf s = fromConstr 
             $ head $ filter ((==s) . map toLower . showConstr) 
             $ dataTypeConstrs $ dataTypeOf $ Prop



}

我认为这与我如何匹配 \ 标记有关。 但是,我试过匹配它

'\' '\' "\" "\" \ \ 也有正则表达式,但似乎没有任何效果。

关于 alex 中的 \ 是否有一些奇怪的行为?还是其他一些我看不到的小错误?

更新

我现在尝试将 @identifier 更改为:

@identifier       = (. # [ : ! = \ \ " $brackets])+

以一种灵活的方式进行“除 x 之外的任何操作”匹配,但这并没有改变输出中的任何内容。

不幸的是,很难阅读您的 lex 规则。但是你的令牌定义有两个错误。

首先是以下内容:

 "\"   {\_ s -> Lam}

应该是:

  "\"   {\_ s -> Lam}

(请注意,我们 不是 转义反斜杠。)这确实违反直觉,但这是 Alex 规则的语法,因此你不应该t 在那里引用反斜杠。 (否则,它将连续匹配两个反斜杠。)

第二个是你的规则:

    \" @string \"  { \_ s -> StringLit (tail . init $ s) }

应该是:

    \" @string* \"  { \_ s -> StringLit (tail . init $ s) }

(注意 @string 后的星号。)也就是说,您的字符串需要接受 0 个或更多字符。

如果您进行以上两项更改,您会发现您的输入现在可以顺利通过。

但是,您似乎试图在词法分析器中做太多事情:词法分析器应该非常简单;它绝对不应该包含像 portSpec 这样复杂的规则。相反,您应该简单地标记为基本成分(或多或少由 white-space 分隔,字符串除外),然后您应该使用像 Happy 这样的合适的解析器生成器来对您的语言进行实际解析。这是标准方法。