エラーメッセージを生成し、オーバーフローを適切にチェックしたい場合は、解析番号をかなり乱雑にすることができます。
次はあなたの番号パーサの簡単なFParsecの実装です:ようにあなたが理想的にも、エラーが発生した後に後戻りする必要があるだろうとオーバーフローの良いエラーメッセージを生成
let numeralOrDecimal : Parser<_, unit> =
// note: doesn't parse a float exponent suffix
numberLiteral NumberLiteralOptions.AllowFraction "number"
|>> fun num ->
// raises an exception on overflow
if num.IsInteger then Numeral(int num.String)
else Decimal(float num.String)
let hexNumber =
pstring "#x" >>. many1SatisfyL isHex "hex digit"
|>> fun hexStr ->
// raises an exception on overflow
Hexadecimal(System.Convert.ToInt32(hexStr, 16))
let binaryNumber =
pstring "#b" >>. many1SatisfyL (fun c -> c = '0' || c = '1') "binary digit"
|>> fun hexStr ->
// raises an exception on overflow
Binary(System.Convert.ToInt32(hexStr, 2))
let number =
choiceL [numeralOrDecimal
hexNumber
binaryNumber]
"number literal"
は、この実装を少し複雑になりますエラー位置は数字リテラルの先頭になります(例についてはnumberLiteralドキュメントを参照)。
優雅可能オーバーフロー例外を処理するための簡単な方法は、以下のような少しの例外処理コンビネータを使用することです:
let mayThrow (p: Parser<'t,'u>) : Parser<'t,'u> =
fun stream ->
let state = stream.State
try
p stream
with e -> // catching all exceptions is somewhat dangerous
stream.BacktrackTo(state)
Reply(FatalError, messageError e.Message)
あなたは、私はよく分からない
let number = mayThrow (choiceL [...] "number literal")
を書くことができ何を「FParsecのCharStream
を変更することで条件付き解析を容易にする」と言う意味ですが、次のサンプルではCharStream
メソッドを直接使用する低レベル実装を作成する方法を示しています。
type NumberStyles = System.Globalization.NumberStyles
let invariantCulture = System.Globalization.CultureInfo.InvariantCulture
let number: Parser<Number, unit> =
let expectedNumber = expected "number"
let inline isBinary c = c = '0' || c = '1'
let inline hex2int c = (int c &&& 15) + (int c >>> 6)*9
let hexStringToInt (str: string) = // does no argument or overflow checking
let mutable n = 0
for c in str do
n <- n*16 + hex2int c
n
let binStringToInt (str: string) = // does no argument or overflow checking
let mutable n = 0
for c in str do
n <- n*2 + (int c - int '0')
n
let findIndexOfFirstNonNull (str: string) =
let mutable i = 0
while i < str.Length && str.[i] = '0' do
i <- i + 1
i
let isHexFun = id isHex // tricks the compiler into caching the function object
let isDigitFun = id isDigit
let isBinaryFun = id isBinary
fun stream ->
let start = stream.IndexToken
let cs = stream.Peek2()
match cs.Char0, cs.Char1 with
| '#', 'x' ->
stream.Skip(2)
let str = stream.ReadCharsOrNewlinesWhile(isHexFun, false)
if str.Length <> 0 then
let i = findIndexOfFirstNonNull str
let length = str.Length - i
if length < 8 || (length = 8 && str.[i] <= '7') then
Reply(Hexadecimal(hexStringToInt str))
else
stream.Seek(start)
Reply(Error, messageError "hex number literal is too large for 32-bit int")
else
Reply(Error, expected "hex digit")
| '#', 'b' ->
stream.Skip(2)
let str = stream.ReadCharsOrNewlinesWhile(isBinaryFun, false)
if str.Length <> 0 then
let i = findIndexOfFirstNonNull str
let length = str.Length - i
if length < 32 then
Reply(Binary(binStringToInt str))
else
stream.Seek(start)
Reply(Error, messageError "binary number literal is too large for 32-bit int")
else
Reply(Error, expected "binary digit")
| c, _ ->
if not (isDigit c) then Reply(Error, expectedNumber)
else
stream.SkipCharsOrNewlinesWhile(isDigitFun) |> ignore
if stream.Skip('.') then
let n2 = stream.SkipCharsOrNewlinesWhile(isDigitFun)
if n2 <> 0 then
// we don't parse any exponent, as in the other example
let mutable result = 0.
if System.Double.TryParse(stream.ReadFrom(start),
NumberStyles.AllowDecimalPoint,
invariantCulture,
&result)
then Reply(Decimal(result))
else
stream.Seek(start)
Reply(Error, messageError "decimal literal is larger than System.Double.MaxValue")
else
Reply(Error, expected "digit")
else
let decimalString = stream.ReadFrom(start)
let mutable result = 0
if System.Int32.TryParse(stream.ReadFrom(start),
NumberStyles.None,
invariantCulture,
&result)
then Reply(Numeral(result))
else
stream.Seek(start)
Reply(Error, messageError "decimal number literal is too large for 32-bit int")
この実装は、システムの方法の助けを借りずに進とバイナリ番号を解析しますが、それ最終的に委譲しInt32.TryParseとDouble.TryParse方法に小数の解析。
私が言ったように:それは面倒です。
+1、高速応答のおかげで、ステファン。 "FParsecのCharStreamを...変更する"とは、CharStreamの低レベル操作を意味していました。私は最初のアプローチ、簡単で理解できるように行きます。ところで、ラベルでコンビネータを使用するコストはいくらですか?パーサ内のどこにでもラベルを使用すれば、コストがかかりますか? – pad
私は、最初のバージョンのオーバーフロー例外をどのように扱うことができるかについてコメントを追加しました。ラベルに関して:それは依存しています。 'choiceL'は実際には' choice'よりも高速です。個々のエラーメッセージを収集する必要がないからです。一般に、 '>'と類似のコンビネータのオーバーヘッドは、自明でないアプリケーションではほとんど測定できません。 FParsecパーサーで実際にパフォーマンスの問題を発見した場合は、常に高速化する方法があります。 –
詳細な回答ありがとうございます。この場合、 'skstring'を' pstring'よりも優先すべきですか? – pad