👉 TOML 語言廣泛用於 Rust 專案當中,cargo new 一個新專案時都會新建一個 toml 的配置文件,所以學習 Rust 過程中也有必要熟悉 TOML 語言的相關語法,從而熟練在 Rust 專案中編輯配置文件。
👉 本文來自 toml-lang 的中文翻譯版本,本文收錄在此處,只是方便 Rust 學習者統一查閱,若是發現有任何錯誤或需要完善地方,請在 toml.io 原專案倉庫指出或修改錯誤。
TOML v1.0.0#
全稱:Tom 的(語義)明顯、(配置)最小化的語言。(Tom's Obvious, Minimal Language)
作者:Tom Preston-Werner、Pradyun Gedam 等人。
宗旨#
TOML 旨在成為一個語義明顯且易於閱讀的最小化配置文件格式。
TOML 被設計成可以無歧義地映射為哈希表。
TOML 應該能很容易地被解析成各種語言中的數據結構。
目錄#
規範#
- TOML 是大小寫敏感的。
- TOML 文件必須是合法的 UTF-8 編碼的 Unicode 文檔。
- 空白是指製表符(0x09)或空格(0x20)。
- 換行是指 LF(0x0A)或 CRLF(0x0D0A)。
註釋#
井字符將該行餘下的部分標記為註釋,除非它在字符串中。
# 這是一個全行註釋
key = "value" # 這是一個行末註釋
another = "# 這不是一個註釋"
除製表符以外的控制字符(U+0000 至 U+0008,U+000A 至 U+001F,U+007F)不允許出現在註釋中。
鍵值對#
TOML 文檔最基本的構成區塊是鍵值對。
鍵名在等號的左邊而值在右邊。
鍵名和鍵值周圍的空白會被忽略。
鍵、等號和值必須在同一行(不過有些值可以跨多行)。
key = "value"
值必須是下述類型之一。
不指定值是非法的。
key = # 非法
鍵值對後必須換行(或結束文件)。
(例外見內聯表)
first = "Tom" last = "Preston-Werner" # 非法
鍵名#
鍵名可以是裸露的,引號引起的,或點分隔的。
裸鍵只能包含 ASCII 字母,ASCII 數字,下劃線和短橫線(A-Za-z0-9_-
)。
注意裸鍵允許僅由純 ASCII 數字構成,例如 1234
,但是是被理解為字符串的。
key = "value"
bare_key = "value"
bare-key = "value"
1234 = "value"
引號鍵遵循與基本字符串或字面量字符串相同的規則並允許你使用更為廣泛的鍵名。
除非明顯必要,使用裸鍵方為最佳實踐。
"127.0.0.1" = "value"
"character encoding" = "value"
"ʎǝʞ" = "value"
'key2' = "value"
'quoted "value"' = "value"
裸鍵中不能為空,但空引號鍵是允許的(雖然不建議如此)。
= "no key name" # 非法
"" = "blank" # 合法但不鼓勵
'' = 'blank' # 合法但不鼓勵
點分隔鍵是一系列通過點相連的裸鍵或引號鍵。
這允許了你將相近屬性放在一起:
name = "Orange"
physical.color = "orange"
physical.shape = "round"
site."google.com" = true
等價於 JSON 的如下結構:
{
"name": "Orange",
"physical": {
"color": "orange",
"shape": "round"
},
"site": {
"google.com": true
}
}
有關點分隔鍵定義表的詳細信息,請參閱後文表一節。
點分隔符周圍的空白會被忽略。
不過,最佳實踐是不要使用任何不必要的空白。
fruit.name = "banana" # 這是最佳實踐
fruit. color = "yellow" # 等同於 fruit.color
fruit . flavor = "banana" # 等同於 fruit.flavor
縮進被作為空白對待而被忽略。
多次定義同一個鍵是非法的。
# 不要這樣做
name = "Tom"
name = "Pradyun"
注意裸鍵和引號鍵是等價的:
# 這是不可行的
spelling = "favorite"
"spelling" = "favourite"
只要一個鍵還沒有被直接定義過,你就仍可以對它和它下屬的鍵名賦值。
# 這使“fruit”鍵作為表存在。
fruit.apple.smooth = true
# 所以接下來你可以像中這樣對“fruit”表添加內容:
fruit.orange = 2
# 以下是非法的
# 這將 fruit.apple 的值定義為一個整數。
fruit.apple = 1
# 但接下來這將 fruit.apple 像表一樣對待了。
# 整數不能變成表。
fruit.apple.smooth = true
不鼓勵跳躍式地定義點分隔鍵。
# 合法但不鼓勵
apple.type = "水果"
orange.type = "水果"
apple.skin = "薄"
orange.skin = "厚"
apple.color = "紅"
orange.color = "橙"
# 建議
apple.type = "水果"
apple.skin = "薄"
apple.color = "紅"
orange.type = "水果"
orange.skin = "厚"
orange.color = "紅"
由於裸鍵可以僅由 ASCII 整數構成,所以可能寫出看起來像浮點數、但實際上是兩部分的點分隔鍵。
除非你有充分的理由(基本不太會),否則不要這樣做。
3.14159 = "派"
上面的 TOML 對應以下 JSON。
{ "3": { "14159": "派" } }
字符串#
共有四種方式來表示字符串:基本字符串、多行基本字符串、字面量和多行字面量。
所有字符串都只能包含有效的 UTF-8 字符。
基本字符串由引號("
)包裹。
任何 Unicode 字符都可以使用,除了那些必須轉義的:引號,反斜杠,以及除製表符外的控制字符(U+0000 至 U+0008,U+000A 至 U+001F,U+007F)。
str = "我是一個字符串。\"你可以把我引起來\"。姓名\tJos\u00E9\n位置\t舊金山。"
為了方便,一些流行的字符有其簡便轉義寫法。
\b - backspace (U+0008)
\t - tab (U+0009)
\n - linefeed (U+000A)
\f - form feed (U+000C)
\r - carriage return (U+000D)
\" - quote (U+0022)
\\ - backslash (U+005C)
\uXXXX - unicode (U+XXXX)
\UXXXXXXXX - unicode (U+XXXXXXXX)
任何 Unicode 字符都可以用 \uXXXX
或 \UXXXXXXXX
的形式來轉義。
轉義碼必須是有效的 Unicode 標量值。
所有上面未列出的其它轉義序列都是保留的;如果用了,TOML 應當產生錯誤。
有時你需要表示一小篇文本(例如譯文)或者想要對非常長的字符串進行折行。
TOML 對此進行了簡化。
多行基本字符串由三個引號包裹,允許折行。
緊隨開頭引號的那個換行會被去除。
其它空白和換行會被原樣保留。
str1 = """
玫瑰是紅色的
紫羅蘭是藍色的"""
TOML 解析器可以相對靈活地解析成對所在平台有效的換行字符。
# 在 Unix 系統,上面的多行字符串可能等同於:
str2 = "玫瑰是紅色的\n紫羅蘭是藍色的"
# 在 Windows 系統,它可能等價於:
str3 = "玫瑰是紅色的\r\n紫羅蘭是藍色的"
想書寫長字符串卻不想引入無關空白,可以用 “行末反斜杠”。
當一行的最後一個非空白字符是未被轉義的 \
時,它會連同它後面的所有空白(包括換行)一起被去除,直到下個非空白字符或結束引號為止。
所有對基本字符串有效的轉義序列,對多行基本字符串也同樣適用。
# 下列字符串的每一個字節都完全相同:
str1 = "The quick brown fox jumps over the lazy dog."
str2 = """
The quick brown \
fox jumps over \
the lazy dog."""
str3 = """\
The quick brown \
fox jumps over \
the lazy dog.\
"""
任何 Unicode 字符都可以使用,除了那些必須被轉義的:反斜杠和除製表符、換行符、回車符外的控制字符(U+0000 至 U+0008,U+000B,U+000C,U+000E 至 U+001F,U+007F)。
你可以在多行基本字符串內的任何地方寫一個引號或兩個毗連的引號。
它們也可以寫在緊鄰界分符內的位置。
str4 = """這有兩個引號:""。夠簡單。"""
# str5 = """這有兩個引號:"""。""" # 非法
str5 = """這有三個引號:""\"。"""
str6 = """這有十五個引號:""\"""\"""\"""\"""\"。"""
# "這,"她說,"只是個無意義的條款。"
str7 = """"這,"她說,"只是個無意義的條款。""""
如果你常常要指定 Windows 路徑或正則表達式,那麼必須轉義反斜杠就馬上成為囉嗦而易錯的了。
為了幫助搞定這點,TOML 支持字面量字符串,它完全不允許轉義。
字面量字符串由單引號包裹。
類似於基本字符串,他們只能表現為單行:
# 所見即所得。
winpath = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted = 'Tom "Dubs" Preston-Werner'
regex = '<\i\c*\s*>'
由於沒有轉義,無法在由單引號包裹的字面量字符串中寫入單引號。
萬幸,TOML 支持一種多行版本的字面量字符串來解決這個問題。
多行字面量字符串兩側各有三個單引號來包裹,允許換行。
類似於字面量字符串,無論任何轉義都不存在。
緊隨開始標記的那個換行會被剔除。
開始結束標記之間的所有其它內容會原樣對待。
regex2 = '''I [dw]on't need \d{2} apples'''
lines = '''
原始字符串中的
第一個換行被剔除了。
所有其它空白
都保留了。
'''
你可以在多行字面量字符串中的任何位置寫一個或兩個單引號,但三個以上的單引號序列不可以。
quot15 = '''這有十五個引號:"""""""""""""""'''
# apos15 = '''這有十五個撇號:'''''''''''''''''' # 非法
apos15 = "這有十五個撇號:'''''''''''''''"
# '那,'她說,'仍然沒有意義。'
str = ''''那,'她說,'仍然沒有意義。''''
除製表符以外的所有控制字符都不允許出現在字面量字符串中。
因此,對於二進制數據,建議你使用 Base64 或其它合適的 ASCII 或 UTF-8 編碼。
對那些編碼的處理方式,將交由應用程序自己來確定。
整數#
整數是純數字。
正數可以有加號前綴。
負數的前綴是減號。
int1 = +99
int2 = 42
int3 = 0
int4 = -17
對於大數,你可以在數字之間用下劃線來增強可讀性。
每個下劃線兩側必須至少有一個數字。
int5 = 1_000
int6 = 5_349_221
int7 = 53_49_221 # 印度記數體系分組
int8 = 1_2_3_4_5 # 合法但不鼓勵
前導零是不允許的。
整數值 -0
與 +0
是有效的,並等同於無前綴的零。
非負整數值也可以用十六進制、八進制或二進制來表示。
在這些格式中,+
不被允許,而(前綴後的)前導零是允許的。
十六進制值大小寫不敏感。
數字間的下劃線是允許的(但不能存在於前綴和值之間)。
# 帶有 `0x` 前綴的十六進制
hex1 = 0xDEADBEEF
hex2 = 0xdeadbeef
hex3 = 0xdead_beef
# 帶有 `0o` 前綴的八進制
oct1 = 0o01234567
oct2 = 0o755 # 對於表示 Unix 文件權限很有用
# 帶有 `0b` 前綴的二進制
bin1 = 0b11010110
任何 64 位有符號整數(從 −2^63 到 2^63−1)都應當被接受並無損處理。
如果無法無損表現某個整數,則必須拋出錯誤。
浮點數#
浮點數應當被實現為 IEEE 754 binary64 值。
一個浮點數由一個整數部分(遵從與十進制整數值相同的規則)後跟上一個小數部分和 / 或一個指數部分組成。
如果小數部分和指數部分兼有,那小數部分必須在指數部分前面。
# 小數
flt1 = +1.0
flt2 = 3.1415
flt3 = -0.01
# 指數
flt4 = 5e+22
flt5 = 1e06
flt6 = -2E-2
# 都有
flt7 = 6.626e-34
小數部分是一個小數點後跟一個或多個數字。
一個指數部分是一個 E(大小寫均可)後跟一個整數部分(遵從與十進制整數值相同的規則,但可以包含前導零)。
小數點,如果有用到的話,每側必須緊鄰至少一個數字。
# 非法的浮點數
invalid_float_1 = .7
invalid_float_2 = 7.
invalid_float_3 = 3.e+20
與整數相似,你可以使用下劃線來增強可讀性。
每個下劃線必須被至少一個數字圍繞。
flt8 = 224_617.445_991_228
浮點數值 -0.0
與 +0.0
是有效的,並且應當遵從 IEEE 754。
特殊浮點值也能夠表示。
它們是小寫的。
# 無窮
sf1 = inf # 正無窮
sf2 = +inf # 正無窮
sf3 = -inf # 負無窮
# 非數
sf4 = nan # 實際上對應信號非數碼還是靜默非數碼,取決於實現
sf5 = +nan # 等同於 `nan`
sf6 = -nan # 有效,實際碼取決於實現
布爾值#
布爾值就是你所慣用的那樣。
要小寫。
bool1 = true
bool2 = false
坐標日期時刻#
要準確地表示世上的一個特定時間,你可以使用指定了時區偏移量的 RFC 3339 格式的日期時刻。
odt1 = 1979-05-27T07:32:00Z
odt2 = 1979-05-27T00:32:00-07:00
odt3 = 1979-05-27T00:32:00.999999-07:00
出於可讀性的目的,你可以用一個空格字符替代日期和時刻之間的 T(RFC 3339 的第 5.6 節中允許了這樣做)。
odt4 = 1979-05-27 07:32:00Z
毫秒級的精度是必須的。
更高精度的小數秒取決於實現。
如果它的值超出了實現所支持的精度,那超出的部分必須被舍棄,而不能四捨五入。
各地日期時刻#
如果你省略了 RFC 3339 日期時刻中的時區偏移量,這表示該日期時刻的使用並不涉及时區偏移。
在沒有其它信息的情況下,並不知道它究竟該被轉化成世上的哪一刻。
如果仍被要求轉化,那結果將取決於實現。
ldt1 = 1979-05-27T07:32:00
ldt2 = 1979-05-27T00:32:00.999999
毫秒級的精度是必須的。
更高精度的小數秒取決於實現。
如果它的值超出了實現所支持的精度,那多餘的部分必須被舍棄,而不能四捨五入。
各地日期#
如果你只寫了 RFC 3339 日期時刻中的日期部分,那它表示一整天,同時也不涉及时區偏移。
ld1 = 1979-05-27
各地時刻#
如果你只寫了 RFC 3339 日期時刻中的時刻部分,它將只表示一天之中的那個時刻,而與任何特定的日期無關、亦不涉及时區偏移。
lt1 = 07:32:00
lt2 = 00:32:00.999999
毫秒級的精度是必須的。
更高精度的小數秒取決於實現。
如果它的值超出了實現所支持的精度,那多餘的部分必須被舍棄,而不能四捨五入。
數組#
數組是內含值的方括號。
空白會被忽略。
子元素由逗號分隔。
數組可以包含與鍵值對所允許的相同數據類型的值。
可以混合不同類型的值。
integers = [ 1, 2, 3 ]
colors = [ "紅", "黃", "綠" ]
nested_array_of_ints = [ [ 1, 2 ], [3, 4, 5] ]
nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
string_array = [ "所有的", '字符串', """是相同的""", '''類型''' ]
# 允許混合類型的數組
numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ]
contributors = [
"Foo Bar <[email protected]>",
{ name = "Baz Qux", email = "[email protected]", url = "https://example.com/bazqux" }
]
數組可以跨行。
數組的最後一個值後面可以有終逗號(也稱為尾逗號)。
值、逗號、結束括號前可以存在任意數量的換行和註釋。
數組值和逗號之間的縮進被作為空白對待而被忽略。
integers2 = [
1, 2, 3
]
integers3 = [
1,
2, # 這是可以的
]
表#
表(也被稱為哈希表或字典)是鍵值對的集合。
它們由表頭定義,連同方括號作為單獨的行出現。
看得出表頭不同於數組,因為數組只有值。
[table]
在它下方,直至下個表頭或文件結束,都是這個表的鍵值對。
表不保證保持鍵值對的指定順序。
[table-1]
key1 = "some string"
key2 = 123
[table-2]
key1 = "another string"
key2 = 456
表名的規則與鍵名相同(見前文鍵名定義)。
[dog."tater.man"]
type.name = "pug"
等價於 JSON 的如下結構:
{ "dog": { "tater.man": { "type": { "name": "pug" } } } }
鍵名周圍的空白會被忽略。
然而,最佳實踐還是不要有任何多餘的空白。
[a.b.c] # 這是最佳實踐
[ d.e.f ] # 等同於 [d.e.f]
[ g . h . i ] # 等同於 [g.h.i]
[ j . "ʞ" . 'l' ] # 等同於 [j."ʞ".'l']
縮進被作為空白對待而被忽略。
你不必層層完整地寫出你不想寫的所有途徑的父表。
TOML 知道該怎麼辦。
# [x] 你
# [x.y] 不
# [x.y.z] 需要這些
[x.y.z.w] # 來讓這生效
[x] # 後置父表定義是可以的
空表是允許的,只要裡面沒有鍵值對就行了。
類似於鍵名,你不能重複定義一個表。
這樣做是非法的。
# 不要這樣做
[fruit]
apple = "紅"
[fruit]
orange = "橙"
# 也不要這樣做
[fruit]
apple = "紅"
[fruit.apple]
texture = "光滑"
不鼓勵無序地定義表。
# 有效但不鼓勵
[fruit.apple]
[animal]
[fruit.orange]
# 推薦
[fruit.apple]
[fruit.orange]
[animal]
頂層表,又被稱為根表,於文檔開始處開始並在第一個表頭(或文件結束處)前結束。
不同於其它表,它沒有名字且無法後置。
# 頂層表開始。
name = "Fido"
breed = "pug"
# 頂層表結束。
[owner]
name = "Regina Dogman"
member_since = 1999-08-04
點分隔鍵為最後一個鍵名前的每個鍵名創建並定義一個表,倘若這些表尚未被創建的話。
fruit.apple.color = "red"
# 定義一個名為 fruit 的表
# 定義一個名為 fruit.apple 的表
fruit.apple.taste.sweet = true
# 定義一個名為 fruit.apple.taste 的表
# fruit 和 fruit.apple 已經創建過了
由於表不能定義多於一次,不允許使用 [table]
頭重定義這樣的表。
同樣地,使用點分隔鍵來重定義已經以 [table]
形式定義過的表也是不允許的。
不過,[table]
形式可以被用來定義通過點分隔鍵定義的表中的子表。
[fruit]
apple.color = "紅"
apple.taste.sweet = true
# [fruit.apple] # 非法
# [fruit.apple.taste] # 非法
[fruit.apple.texture] # 你可以添加子表
smooth = true
內聯表#
內聯表提供了一種更為緊湊的語法來表示表。
對於否則就很囉嗦的成組數據,這尤其有用。
內聯表被完整地定義在花括號之中:{
和 }
。
括號中,可以出現零或更多個以逗號分隔的鍵值對。
鍵值對採取與標準表中的鍵值對相同的形式。
什麼類型的值都可以,包括內聯表。
內聯表得出現在同一行內。
內聯表中,最後一對鍵值對後不允許終逗號(也稱為尾逗號)。
不允許花括號中出現任何換行,除非在值中它們合法。
即便如此,也強烈不建議把一個內聯表搞成縱跨多行的樣子。
如果你發現自己真的需要,那意味著你應該使用標準表。
name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
animal = { type.name = "pug" }
上述內聯表等同於下面的標準表定義:
[name]
first = "Tom"
last = "Preston-Werner"
[point]
x = 1
y = 2
[animal]
type.name = "pug"
內聯表是獨立自足的,在內部定義全部的鍵與子表。
不能在括號以外的地方,再添加鍵與子表。
[product]
type = { name = "Nail" }
# type.edible = false # 非法
類似地,內聯表不能被用於向一個已定義的表添加鍵或子表。
[product]
type.name = "Nail"
# type = { edible = false } # 非法
表數組#
最後一個還沒講到的語法允許你寫表數組。
這可以通過把表名寫在雙方括號里的表頭來表示。
表頭的第一例定義了這個數組及其首個表元素,而後續的每個則在該數組中創建並定義一個新的表元素。
這些表按出現順序插入該數組。
[[products]]
name = "Hammer"
sku = 738594937
[[products]] # 數組里的空表
[[products]]
name = "Nail"
sku = 284758393
color = "gray"
等價於 JSON 的如下結構。
{
"products": [
{ "name": "Hammer", "sku": 738594937 },
{ },
{ "name": "Nail", "sku": 284758393, "color": "gray" }
]
}
任何對表數組的引用都指向該數組里最近定義的表元素。
這允許你在最近的表內定義子表,甚至子表數組。
[[fruits]]
name = "apple"
[fruits.physical] # 子表
color = "red"
shape = "round"
[[fruits.varieties]] # 嵌套表數組
name = "red delicious"
[[fruits.varieties]]
name = "granny smith"
[[fruits]]
name = "banana"
[[fruits.varieties]]
name = "plantain"
上述 TOML 等價於 JSON 的如下結構。
{
"fruits": [
{
"name": "apple",
"physical": {
"color": "red",
"shape": "round"
},
"varieties": [
{ "name": "red delicious" },
{ "name": "granny smith" }
]
},
{
"name": "banana",
"varieties": [
{ "name": "plantain" }
]
}
]
}
如果一個表或表數組的父級是一個數組元素,該元素必須在定義子級前先定義。
順序顛倒的行為,必須在解析時報錯。
# 非法的 TOML 文檔
[fruit.physical] # 子表,但它應該隸屬於哪個父元素?
color = "red"
shape = "round"
[[fruit]] # 解析器必須在發現“fruit”是數組而非表時拋出錯誤
name = "apple"
若試圖向一個靜態定義的數組追加內容,即便數組尚且為空,也必須在解析時報錯。
# 非法的 TOML 文檔
fruits = []
[[fruits]] # 不允許
若試圖用已經確定為數組的名稱定義表,必須在解析時報錯。
將數組重定義為普通表的行為,也必須在解析時報錯。
# 非法的 TOML 文檔
[[fruits]]
name = "apple"
[[fruits.varieties]]
name = "red delicious"
# 非法:該表與之前的表數組相衝突
[fruits.varieties]
name = "granny smith"
[fruits.physical]
color = "red"
shape = "round"
# 非法:該表數組與之前的表相衝突
[[fruits.physical]]
color = "green"
你也可以適當使用內聯表:
points = [ { x = 1, y = 2, z = 3 },
{ x = 7, y = 8, z = 9 },
{ x = 2, y = 4, z = 8 } ]
文件擴展名#
TOML 文件應當使用 .toml
擴展名。
MIME 類型#
在互聯網上傳輸 TOML 文件時,恰當的 MIME 類型是 application/toml
。
ABNF 語法#
TOML 語法的嚴謹說明,由一個 ABNF 文件另行提供。