module Scanf: Scanf
模組 Scanf
提供了格式化輸入函式或掃描器。
格式化輸入函式可以從任何種類的輸入讀取,包括字串、檔案,或任何可以回傳字元的東西。更一般的字元來源稱為格式化輸入通道(或掃描緩衝區),其類型為 Scanf.Scanning.in_channel
。更一般的格式化輸入函式從任何掃描緩衝區讀取,並命名為 bscanf
。
一般而言,格式化輸入函式有 3 個引數:
因此,呼叫格式化輸入函式 Scanf.bscanf
的典型範例是 bscanf ic fmt f
,其中
ic
是一個字元來源(通常是類型為 Scanf.Scanning.in_channel
的格式化輸入通道),f
是一個函式,其引數數量與根據 fmt
從輸入讀取的值數量相同。如上所述,運算式 bscanf ic "%d" f
從字元來源 ic
讀取一個十進位整數 n
,並回傳 f n
。
例如:
stdin
作為字元來源(Scanf.Scanning.stdin
是從標準輸入讀取的預定義格式化輸入通道),f
定義為 let f x = x + 1
,那麼 bscanf Scanning.stdin "%d" f
會從標準輸入讀取一個整數 n
,並回傳 f n
(也就是 n + 1
)。因此,如果我們評估 bscanf stdin "%d" f
,然後在鍵盤上輸入 41
,我們得到的結果會是 42
。
OCaml 的掃描功能讓人聯想到相應的 C 語言功能。然而,它也大不相同,更簡單,但功能更強大:格式化輸入函式是高階函數,且參數傳遞機制只是正規的函式應用,而不是命令式語言中格式化輸入典型的基於變數賦值的機制;OCaml 格式字串還具有有用的附加功能,可以輕鬆定義複雜的 token;正如函數式程式語言中所預期的那樣,格式化輸入函式也支援多型,特別是與多型使用者定義的掃描器之間的任意互動。此外,OCaml 格式化輸入功能會在編譯時進行完整類型檢查。
非同步存取
對 Scanf.Scanning.in_channel
的非同步存取可能會導致 Scanf.Scanning.in_channel
狀態無效。因此,必須同步對 Scanf.Scanning.in_channel
的並行存取(例如使用 Mutex.t
)。
module Scanning:sig
..end
type('a, 'b, 'c, 'd)
scanner =('a, Scanning.in_channel, 'b, 'c, 'a -> 'd, 'd) format6 -> 'c
格式化輸入掃描器的類型:('a, 'b, 'c, 'd) scanner
是一種格式化輸入函式的類型,該函式會根據某個格式字串,從某個格式化輸入通道讀取資料;更準確地說,如果 scan
是一些格式化輸入函式,那麼 scan
會將
ic fmt ff
套用至格式字串 fmt
所指定的所有引數,當 scan
從 Scanf.Scanning.in_channel
格式化輸入通道 ic
讀取這些引數時。
例如,下面的 Scanf.scanf
函式的類型為 ('a, 'b, 'c, 'd) scanner
,因為它是從 Scanf.Scanning.stdin
讀取的格式化輸入函式:scanf fmt f
會將 f
套用至 fmt
所指定的引數,並根據預期從 stdin
讀取這些引數。
如果格式 fmt
有一些 %r
指示,則必須在接收器函式 f
之前提供對應的格式化輸入函式。例如,如果 read_elem
是類型為 t
的值的輸入函式,則 bscanf ic "%r;" read_elem f
會讀取類型為 t
的值 v
,然後讀取一個 ';'
字元,並回傳 f v
。
type('a, 'b, 'c, 'd)
scanner_opt =('a, Scanning.in_channel, 'b, 'c, 'a -> 'd option, 'd) format6 ->
'c
exception Scan_failure of string
當無法根據格式字串規格讀取輸入時,格式化輸入函式通常會引發例外狀況 Scan_failure
。
val bscanf : Scanning.in_channel -> ('a, 'b, 'c, 'd) scanner
bscanf ic fmt r1 ... rN f
會從 Scanf.Scanning.in_channel
格式化輸入通道 ic
讀取字元,並根據格式字串 fmt
將其轉換為值。最後一個步驟,接收器函式 f
會套用至讀取的值,並產生 bscanf
呼叫的結果。
例如,如果 f
是函式 fun s i -> i + 1
,則 Scanf.sscanf "x = 1" "%s = %i" f
會回傳 2
。
引數 r1
到 rN
是使用者定義的輸入函式,這些函式會讀取格式字串中指定之對應於 %r
轉換的引數。
val bscanf_opt : Scanning.in_channel -> ('a, 'b, 'c, 'd) scanner_opt
與 Scanf.bscanf
相同,但掃描失敗時會回傳 None
。
格式字串是一個字元字串,其中包含三種類型的物件:
f
的一個引數(請參閱格式字串中的轉換規格),如上所述,格式字串中的純文字字元只會與輸入的下一個字元進行比對;但是,有兩個字元是此規則的特殊例外:空格字元(' '
或 ASCII 代碼 32)和換行字元('\n'
或 ASCII 代碼 10)。空格並不比對單一空格字元,而是比對輸入中任何數量的「空白」。更準確地說,格式字串中的空格會比對任何數量的 Tab、空格、換行和歸位字元。同樣地,格式字串中的換行字元會比對單一換行字元,或歸位字元後接一個換行字元。
比對任何數量的空白時,格式字串中的空格也會比對完全沒有空白;因此,當讀取輸入中具有各種空白時,呼叫 bscanf ib
會成功,並回傳
"Price = %d $" (fun p -> p)1
,例如 Price = 1 $
、Price = 1 $
,甚至 Price=1$
。
轉換規格包含 %
字元,後接一個可選的旗標、一個可選的欄位寬度,以及一個或兩個轉換字元。
轉換字元及其含義如下:
d
:讀取一個可選帶正負號的十進位整數(0-9
+)。i
:讀取一個可選帶正負號的整數(了解十進位(0-9
+)、十六進位(0x[0-9a-f]+
和 0X[0-9A-F]+
)、八進位(0o[0-7]+
)和二進位(0b[0-1]+
)標記的常用輸入慣例)。u
:讀取一個不帶正負號的十進位整數。x
或 X
:讀取一個無符號十六進位整數 ([0-9a-fA-F]+
)。o
:讀取一個無符號八進位整數 ([0-7]+
)。s
:讀取一個字串參數,盡可能擴展,直到滿足以下邊界條件S
:讀取一個帶分隔符號的字串參數 (分隔符號和特殊跳脫字元遵循 OCaml 的詞法慣例)。c
:讀取單一字元。若要測試目前的輸入字元而不讀取它,請指定空欄位寬度,即使用規格 %0c
。如果欄位寬度規格大於 1,則引發 Invalid_argument
。C
:讀取單一帶分隔符號的字元 (分隔符號和特殊跳脫字元遵循 OCaml 的詞法慣例)。f
、e
、E
、g
、G
:讀取一個可選帶正負號的十進位浮點數,格式為 dddd.ddd
e/E+-dd
。h
、H
:讀取一個可選帶正負號的十六進位浮點數。F
:根據 OCaml 的詞法慣例讀取浮點數 (因此,如果未提及指數部分,則小數點是強制性的)。B
:讀取一個布林參數 (true
或 false
)。b
:讀取一個布林參數 (為了向後相容性;請勿在新程式中使用)。ld
、li
、lu
、lx
、lX
、lo
:讀取一個 int32
參數,格式由第二個字母指定,用於一般整數。nd
、ni
、nu
、nx
、nX
、no
:讀取一個 nativeint
參數,格式由第二個字母指定,用於一般整數。Ld
、Li
、Lu
、Lx
、LX
、Lo
:讀取一個 int64
參數,格式由第二個字母指定,用於一般整數。[ 範圍 ]
:讀取符合字元範圍 range
中所提及的其中一個字元 (如果範圍以 ^
開頭,則為未提及)。讀取一個 string
,如果下一個輸入字元不符合範圍,則可以是空的。從 c1
到 c2
(包含) 的字元集以 c1-c2
表示。因此,%[0-9]
返回一個表示十進位數字的字串,如果找不到十進位數字,則返回一個空字串;類似地,%[0-9a-f]
返回一個十六進位數字字串。如果右括號出現在範圍中,則必須作為範圍的第一個字元出現 (或者在範圍否定時緊接在 ^
之後);因此,[]]
符合一個 ]
字元,而 [^]]
符合任何不是 ]
的字元。使用 %%
和 %@
在範圍中包含 %
或 @
。r
:使用者定義的讀取器。取得下一個 ri
格式化輸入函數,並將其應用於掃描緩衝區 ib
,以讀取下一個參數。因此,輸入函數 ri
的類型必須為 Scanning.in_channel -> 'a
,而讀取的參數類型為 'a
。{ fmt %}
:讀取格式字串參數。讀取的格式字串必須與格式字串規格 fmt
具有相同的類型。例如,"%{ %i %}"
讀取任何可以讀取 int
類型值的格式字串;因此,如果 s
是字串 "fmt:\"number is %u\""
,則 Scanf.sscanf s "fmt: %{%i%}"
成功並返回格式字串 "number is %u"
。( fmt %)
:掃描子格式取代。在輸入中讀取格式字串 rf
,然後繼續使用 rf
掃描,而不是使用 fmt
掃描。格式字串 rf
必須與它取代的格式字串規格 fmt
具有相同的類型。例如,"%( %i %)"
讀取任何可以讀取 int
類型值的格式字串。此轉換返回讀取的格式字串 rf
,然後返回使用 rf
讀取的值。因此,如果 s
是字串 "\"%4d\"1234.00"
,則 Scanf.sscanf s "%(%i%)" (fun fmt i -> fmt, i)
會計算為 ("%4d", 1234)
。此行為不僅僅是格式取代,因為此轉換返回讀取的格式字串作為額外參數。如果您需要純格式取代,請使用特殊旗標 _
捨棄多餘的參數:轉換 %_( fmt %)
讀取格式字串 rf
,然後行為與格式字串 rf
相同。因此,如果 s
是字串 "\"%4d\"1234.00"
,則 Scanf.sscanf s "%_(%i%)"
相當於 Scanf.sscanf "1234.00" "%4d"
。l
:返回到目前為止讀取的行數。n
:返回到目前為止讀取的字元數。N
或 L
:返回到目前為止讀取的標記數。!
:符合輸入結束條件。%
:符合輸入中的一個 %
字元。@
:符合輸入中的一個 @
字元。,
:不做任何事。在引入轉換的 %
字元之後,可能會有特殊旗標 _
:接下來的轉換照常發生,但會捨棄結果值。例如,如果 f
是函數 fun i -> i + 1
,且 s
是字串 "x = 1"
,則 Scanf.sscanf s "%_s = %i" f
返回 2
。
欄位寬度由一個可選整數字面值組成,指示要讀取的標記最大寬度。例如,%6d
讀取一個整數,最多有 6 個十進位數字;%4f
讀取一個最多有 4 個字元的浮點數;且 %8[\000-\255]
返回下 8 個字元 (或如果輸入中可用的字元少於 8 個,則返回所有可用的字元)。
注意事項
%s
轉換永遠成功:在這種情況下,它只會返回 ""
。'_'
字元可能會出現在數字中 (這讓人想起常見的 OCaml 詞法慣例)。如果需要更嚴格的掃描,請使用範圍轉換工具,而不是數字轉換。scanf
工具不適用於繁重的詞法分析和剖析。如果它對於您的需求而言不夠明確,則存在幾種替代方案:正規表示式 (模組 Str
)、串流剖析器、ocamllex
產生的詞法分析器、ocamlyacc
產生的剖析器。掃描指示會出現在字串轉換 %s
和 %[ 範圍 ]
之後,以分隔標記的結尾。掃描指示由 @
字元引入,後跟某些純字元 c
。這表示字串標記應在下一個符合的 c
之前結束 (且會跳過該 c
)。如果未遇到 c
字元,則字串標記會盡可能擴展。例如,"%s@\t"
會讀取字串,直到下一個 Tab 字元或輸入結尾。如果 @
字元出現在格式字串中的任何其他位置,則會將其視為純字元。
注意事項
%
和 @
字元必須使用 %%
和 %@
跳脫;此規則仍適用於範圍規格和掃描指示。例如,格式 "%s@%%"
會讀取字串,直到下一個 %
字元,而格式 "%s@%@"
會讀取字串,直到下一個 @
。Printf
模組的格式字串相比,掃描指示在 Scanf
格式字串的語法中引入了細微的差異。不過,掃描指示與 Format
模組中使用的指示類似;因此,當產生要由 Scanf.bscanf
掃描的格式化文字時,最好使用 Format
模組中的列印函數 (或者,如果您需要使用 Printf
中的函數,請禁止或仔細檢查包含 '@'
字元的格式字串)。當無法根據格式字串讀取輸入時,掃描器可能會引發以下例外狀況
Scanf.Scan_failure
。Failure
。End_of_file
。Invalid_argument
。注意事項
%s
轉換永遠不會引發例外狀況 End_of_file
:如果到達輸入結尾,轉換會成功,並且只會返回到目前為止讀取的字元,如果從未讀取任何字元,則返回 ""
。val sscanf : string -> ('a, 'b, 'c, 'd) scanner
與 Scanf.bscanf
相同,但從指定的字串讀取。
val sscanf_opt : string -> ('a, 'b, 'c, 'd) scanner_opt
與 Scanf.sscanf
相同,但掃描失敗時會回傳 None
。
val scanf : ('a, 'b, 'c, 'd) scanner
與 Scanf.bscanf
相同,但會從預先定義的格式化輸入通道 Scanf.Scanning.stdin
讀取,該通道連接至 stdin
。
val scanf_opt : ('a, 'b, 'c, 'd) scanner_opt
與 Scanf.scanf
相同,但掃描失敗時會回傳 None
。
val kscanf : Scanning.in_channel ->
(Scanning.in_channel -> exn -> 'd) -> ('a, 'b, 'c, 'd) scanner
與 Scanf.bscanf
相同,但會額外接收一個函式參數 ef
,當發生錯誤時會呼叫此函式:如果掃描過程或某些轉換失敗,掃描函式會中止並呼叫錯誤處理函式 ef
,並將格式化輸入通道和導致掃描過程中止的例外作為參數傳遞。
val ksscanf : string ->
(Scanning.in_channel -> exn -> 'd) -> ('a, 'b, 'c, 'd) scanner
與 Scanf.kscanf
相同,但會從給定的字串讀取。
val bscanf_format : Scanning.in_channel ->
('a, 'b, 'c, 'd, 'e, 'f) format6 ->
(('a, 'b, 'c, 'd, 'e, 'f) format6 -> 'g) -> 'g
bscanf_format ic fmt f
會根據給定的格式字串 fmt
,從格式化輸入通道 ic
讀取格式字串的 token,並將 f
應用於結果的格式字串值。
Scan_failure
例外,如果讀取的格式字串值的類型與 fmt
的類型不同。val sscanf_format : string ->
('a, 'b, 'c, 'd, 'e, 'f) format6 ->
(('a, 'b, 'c, 'd, 'e, 'f) format6 -> 'g) -> 'g
與 Scanf.bscanf_format
相同,但會從給定的字串讀取。
val format_from_string : string ->
('a, 'b, 'c, 'd, 'e, 'f) format6 ->
('a, 'b, 'c, 'd, 'e, 'f) format6
format_from_string s fmt
會根據給定的格式字串 fmt
,將字串參數轉換為格式字串。
Scan_failure
例外,如果 s
被視為格式字串時,其類型與 fmt
的類型不同。val unescaped : string -> string
unescaped s
會回傳 s
的副本,其中跳脫序列(根據 OCaml 的詞彙慣例)會被替換為其對應的特殊字元。更精確地說,Scanf.unescaped
具有以下屬性:對於所有字串 s
,Scanf.unescaped (String.escaped s) = s
。
即使參數中沒有跳脫序列,也總是回傳參數的副本。
Scan_failure
例外,如果 s
沒有被正確地跳脫(亦即,s
具有無效的跳脫序列或未被正確跳脫的特殊字元)。例如,Scanf.unescaped "\""
就會失敗。