좋은 오류 메시지를 생성하고 오버플로를 제대로 확인하려는 경우 구문 분석 번호가 꽤 복잡 할 수 있습니다. 당신은 또한 오류가 발생한 후 역 추적 할 필요가 이상적 것처럼 있도록,이 구현 조금 복잡 것이다 오버 플로우에 좋은 오류 메시지를 생성
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"
:
다음
은 간단한 FParsec의 전화 번호 파서의 구현 오류 위치는 숫자 리터럴의 시작 부분에서 끝납니다 (예제는
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 빠른 응답, Stephan에게 감사드립니다. "FParsec의 CharStream을 ... 변경"함으로써, 나는 CharStream의 낮은 수준의 조작을 의미했습니다. 나는 첫 번째 접근법을 간단하고 이해하기 쉽도록 갈 것입니다. BTW, 레이블이있는 결합자를 사용하는 비용은 얼마입니까? 파서에서 어디서나 레이블을 사용하면 비용이 많이 듭니까? – pad
방금 첫 번째 버전에서 오버플로 예외를보다 효과적으로 처리 할 수있는 방법에 대한 의견을 추가했습니다. 라벨에 관한 내용은 다릅니다. 'choiceL'은 개별 오류 메시지를 수집 할 필요가 없기 때문에 실제로'choice'보다 빠릅니다. 일반적으로 '>'과 유사한 연결자의 오버 헤드는 중요하지 않은 응용 프로그램에서는 거의 측정 할 수 없습니다. FParsec 파서에서 성능 문제를 실제로 발견하면 항상 더 빨리 수행 할 수있는 방법이 있습니다. –
자세한 답변을 보내 주셔서 감사합니다.이 경우에는'skstring'을'pstring'보다 선호해야합니다. – pad