我試圖做一些測試與自定義正則表達式引擎,但我厭倦了手工出寫的NFA的初步解釋,所以我試着讓一個解析器成功一點點。通常當人們解析一個正則表達式時,他們會創建多箇中間結構,最終轉換成最終的機器。對於我簡單的NFA定義,我相信解析實際上可以一次完成,儘管我還沒有確定(a)爲什麼它實際上不能或(b)怎麼做,雖然我的解析器可以解析非常簡單聲明。
的(簡化的)狀態的實體一樣定義,以便[1]:
type Tag = Int
data State a =
Literal Tag a (State a)
| Split (State a) (State a)
| OpenGroup Tag (State a)
| CloseGroup Tag (State a)
| Accept -- end of expression like "abc"
| Final -- end of expression like "abc$"
的標籤,以允許顯示和等式實例即使最終NFA可包含週期。例如,爲了表達
-- "^a+(b*)c$"
我可以通過移植C實現用[2]
c = Literal 3 'c' $ Final 1
b = OpenGroup 1 $ Literal 2 'b' bs
bs = Split b $ CloseGroup 1 c
expr = Literal 1 'a' $ Split expr bs
我製成的運作基於堆棧的機器解析器此語法(SAN的組標籤)進行建模的Thompson NFA實現到Haskell,但這需要兩次通過[3]來構建,並且需要三分之一才能以上述結構結束。
因此構建經秒差距這個結構,我讀到遞歸建立一個列表狀結構本網站上的條目,並想出了以下內容:
import Control.Applicative
import Text.Parsec hiding (many, optional, (<|>))
import Text.ExpressionEngine.Types
import qualified Text.ExpressionEngine.Types as T
type ParserState = Int
type ExpParser = Parsec String ParserState
type ExpParserS a = ExpParser (T.State a)
parseExpression :: String -> T.State Char
parseExpression e = case runParser p 1 e e of
Left err -> error $ show err
Right r -> r
where
p = p_rec_many p_char $ p_end 1
p_rec_many :: ExpParser (T.State a -> T.State a) -> ExpParserS a -> ExpParserS a
p_rec_many p e = many'
where
many' = p_some <|> e
p_some = p <*> many'
p_end :: Int -> ExpParserS a
p_end n = (Final n <$ char '$') <|> (Accept n <$ eof)
step_index :: ExpParser Int
step_index = do
index <- getState
updateState succ
return index
p_char = do
c <- noneOf "^.[$()|*+?{\\"
i <- step_index
return $ Literal i c
有一點是可以充分解析字符串像「ab」和「abc $」[4]。
問題
當我下一步的問題就來了:解析「|」或陳述。這應該工作的方式是,一個字符串,如:
-- "(a|b|c)$"
應該創建以下結構:
final = Final 1
c = Literal 3 'c' final
b = Split (Literal 2 'b' final) c
a = Split (Literal 1 'a' final) b
因此,這意味着,將建立或聲明必須經過它自帶的另一種表達解析器並將其傳遞給所有分支(我不相信將Split改爲列表會改變任何內容,因爲每個條目都必須接收相同的下列表達式)。我的嘗試是:
p_regex :: T.State Char -> ExpParser (T.State Char)
p_regex e = do
first <- p_rec_many p_char $ pure e
(Split first <$> (char '|' *> p_regex e)) <|> return first
和主解析器變化:
parseExpression :: String -> T.State Char
parseExpression e = case runParser p 1 e e of
Left err -> error $ show err
Right r -> r
where
p = p_regex <*> p_end 1
但這未能類型檢查[5]。我希望這是正確的,因爲p_regex必須有一個構建的(State a)對象,並且使用p_rec_many構建「Literal」鏈似乎也可以這樣工作。
也許我應該使用buildExpressionTable?這可能有助於解決這個特定問題,因爲我可以將('$'< |> eof)作爲最高優先級。我開始嘗試它,但我無法想象我將如何處理諸如明星加號和問號運算符之類的事情,因爲這些都必須引用自己。 (編輯:我再次嘗試buildExpressionTable,它在我看來,它是太簡單了,我想做什麼。它不能本地處理堆疊後綴運算符[例如「a?*」],和我的計劃是讓$'< |> eof「最高優先級將不起作用,因爲它只會附加到最後解析的'term',而不是整個字符串。即使我可以這樣做,'$ 「運營商將反向應用:它應該是最後解析的詞,並輸送到前面的術語我這個工作,我就越想知道如果我不應該只是分析前反向表達串越)
。問題
那麼,我做錯了什麼?我確信有一種方法可以做我想做的事情,但到目前爲止我還沒有弄明白。感謝您的時間。
腳註
[1]如果你想看到什麼我真正使用它可以發現here。
[2]爲打開/ CloseGroup標籤的想法是跟蹤而NFA運行組相匹配。列出的表達式中的位置可能不完全正確,但如果遇到CloseGroup標記,只有在找到相應的OpenGroup時纔會創建捕獲組,則此方法將正常工作(即,在上例中,如果至少有一個'b'被看到)。
所有其它標記建設是正確的,我測試過,這個NFA預期相匹配的字符串。
[3]湯普森的實現描述爲here,我的端口可以看到here。這完美地構建了NFA子集,但是在所產生的結構中,每個下一個狀態都將被包裝在一個Just中。這是因爲我使用Nothing來表示懸掛指針,並且稍後的步驟將在正確的下一步中進行修補。我可以通過將所有(Just狀態)條目轉換爲(狀態)條目來將此結構轉換爲上面列出的結構,但這將是第三遍。這個實現已經需要第一遍將正則表達式轉換爲後綴表示法。
[4]在
Literal 1 'a' (Literal 2 'b' (Accept 1))
和
Literal 1 'a' (Literal 2 'b' (Literal 3 'c' (Final 1)))
分別得到的。
[5]
Couldn't match expected type `a0 -> b0'
with actual type `ExpParser (T.State Char)'
Expected type: T.State Char -> a0 -> b0
Actual type: T.State Char -> ExpParser (T.State Char)
In the first argument of `(<*>)', namely `p_regex'
In the expression: p_regex <*> p_end 1
感謝您的回答。我知道我的問題很長,但很難:如果你提出的問題太長,沒有人會閱讀,但如果你的問題太短,那麼你可以通過「不要做你真正想做你的事情,你應該只用X代替「,這是浪費每個人的時間,並可以防止人們給出真正的答案。 在這種情況下,我正在考慮給予您答案,因爲您確實回答了我的一個問題:「有可能嗎?」接受我自己的答案可能是跛腳的。 – Jason 2013-05-16 19:14:43
哦和P.S .:我有'p_regex <*> p_end 1'的原因是因爲我期待它的行爲像一個應用,即:'(+)<$> [1] <*> [2]'會。我期待這個,因爲我在其他地方使用parsec這個應用程序的風格,它工作正常。我認爲在這種情況下,問題是我正在創建一個函數,該函數依賴於直到最後才能發生的解析。 – Jason 2013-05-16 19:21:01