Erlang 程式經常需要處理使用未參考 Erlang 設計的資料格式的資料流。因此,OTP 支援 ASN.1 和 CORBA 以及其他介面技術。二進位資料流通常包含「符號」值,這些值在原始描述中以某種列舉宣告表示,通常是 C 的「enum」宣告。
此 EEP 提議為 Erlang 引入一個「-enum
」宣告,以便在介面的一側的原子和另一側的整數之間進行方便的映射,尤其是在位元語法中。
這取代了部分使用前處理器的情況,並以更清晰的方式表達程式設計師的意圖。
新增了一種新的宣告形式、四個新的防護 BIF 和一個新的位元語法型別指定符。
'-' 'enum' '(' identifier-and-size ',' '{' enum-binding
{',' enum-binding}* ')' '.'
其中 identifier-and-size 為
identifier
或
identifier : size
或
identifier / type-specifier-list
或
identifier : size / type-specifier-list
且 enum-binding 為
identifier '=' constant-integer-expression
或
identifier
size 和 type-specifier-list 與位元語法中的相同,只是 type-specifier-list 不得包含 Type。如果缺少 size,則它將是與整數值相容的 [8,16,32,64] 中的第一個,稍後將會描述。如果存在 size,則它必須是與整數值相容的整數。如果有符號,則必須與整數值一致。
-enum(colour, {red,orange,yellow,green,blue}).
-enum(fruit:32, {quandong,lime,banana,orange,apple}).
左括號後的識別符號稱為「列舉識別符號」,而繫結所繫結的識別符號稱為「列舉值」。
在 -include
和 -if
處理之後,任何識別符號都應最多有一個 enum 宣告。該識別符號不得為以下其中之一
integer | float | binary | bytes | bitstring | bits
此宣告僅在此 EEP 中定義的結構中具有意義;唯一受影響的現有表示法是位元語法。
在單個 enum 宣告中,不得在兩個或更多繫結中繫結列舉值。
如果第一個繫結沒有整數常數表達式,則如同出現「= 0」。如果後續繫結沒有整數常數表達式,則如同出現「= N」,其中 N 比前一個繫結的整數值大一。
在單個 enum 宣告中,不得在兩個或更多繫結中(無論是隱式還是顯式)使用整數值。
is_enum_atom(Atom, Enumeration_Identifier)
#true
,false
。如果 Enumeration_Identifier 是字面原子,則可以作為防護測試使用,如果它沒有 enum 宣告,則會發生編譯時錯誤。
is_enum_integer(Integer, Enumeration_Identifier)
#true
,false
。如果 Enumeration_Identifier 是字面原子,則可以作為防護測試使用,如果它沒有 enum 宣告,則會發生編譯時錯誤。
enum_to_atom(Integer, Enumeration_Identifier)
#當 is_enum_integer(Integer, Enumeration_Identifier)
-> 時
在 Enumeration_Identifier 的宣告中繫結到 Integer 的列舉值
否則會以 badarg
退出。
如果 Enumeration_Identifier 是字面原子,則可以在防護表達式中使用,如果它沒有 enum 宣告,則會發生編譯時錯誤。
enum_to_integer(Atom, Enumeration_Identifier)
#當 is_enum_atom(Atom, Enumeration_Identifier)
-> 時
在 Enumeration_Identifier 的宣告中 Atom 所繫結的整數值
否則會以 badarg
退出。
如果 Enumeration_Identifier 是字面原子,則可以在防護表達式中使用,如果它沒有 enum 宣告,則會發生編譯時錯誤。
所有這四個函數預計都需要 O(1) 時間,且在執行時不分配儲存空間。
位元語法段中的 Type 也可能是 Enumeration_Identifier,而對應的 Value 則將是一個原子。正在比對或建構的位元字串中的值是或將是被繫結到該原子的整數;因此,Size、Endianness、Signedness 和 Unit 會被解釋為 integer
Type。
在建構位元字串時,
V / Enumeration_Identifier ...
or V : Size / Enumeration_Identifier ...
如同
enum_to_integer(V, Enumeration_Identifier) / integer ...
or enum_to_integer(V, Enumeration_Identifier) : Size / integer ...
已撰寫,但有一個例外,現在將描述。
如果 enum 宣告中的所有整數值都是非負數,則讓 k 為最小整數,使得 2^k 大於所有整數值。如果某些為負數,則讓 k 為最小整數,使得 2^(k-1) 大於所有整數值,且 -(2^(k-1)) 小於或等於所有整數值。列舉值的段大小必須至少為 k 位元,無論實際值是多少。發現需要繞過此限制的程式設計師可以手動執行列舉值<->整數轉換;此限制的作用是防止意外的錯誤規範。enum 宣告中給定的 size 必須至少為 k。如果在位元語法中沒有給定 size,則將使用 enum 宣告中給定(或預設)的 size。
當此類段用於模式比對時,如同
integer
,enum_to_atom
,預期將在編譯時完全轉換值 V 是顯式原子的情況,因此與使用巨集和 /integer
相比沒有額外負擔。
這是受到對 PADS 和其他資料描述語言的思考的啟發。假設一個 C 程式執行類似以下的操作
enum seriousness {
not_serious = 'N',
hospitalised = 'H',
life_threatening = 'L',
congenital_abnormality = 'C',
persisting_disability = 'P',
intervention_required = 'I',
death = 'D'
};
struct Message {
char tag; /* a seriousness */
union {
int number_of_days; /* H */
float extent_of_disability; /* C or P */
char procedure_code[5]; /* I */
} supplement;
};
(Message 結構已大大簡化。)
現在假設要比對它。
-define(NOT_SERIOUS, $N).
-define(HOSPITALISED, $H).
-define(LIFE_THREATENING, $L).
-define(CONGENITAL_ABNORMALITY, $C).
-define(PERSISTING_DISABILITY, $P).
-define(INTERVENTION_REQUIRED, $I).
-define(DEATH, $D).
decode_message(B0) ->
case B0
of <<?NOT_SERIOUS, B1/binary>> ->
{{not_serious}, B1}
; <<?HOSPITALISED, NDays:32, B1/binary>> ->
{{hospitalised,NDays}, B1}
; <<?LIFE_THREATENING, B1/binary>> ->
{{life_threatening}, B1}
; <<?CONGENITAL_ABNORMALITY, Extent/float, B1/binary>> ->
{{congenital_abnormality,Extent}, B1}
; <<?PERSISTING_DISABILITY, Extent/float, B1/binary>> ->
{{persisting_abnormality,Extent}, B1}
; <<?INTERVENTION_REQUIRED, Code:5/bytes, B1/binary>> ->
{{intervention_required,Code}, B1}
; <<?DEATH, B1/binary>> ->
{{death}, B1}
end.
這存在許多問題。
big
,則必須在每個模式中重複。現在這是使用 -enum
的版本。
-enum(seriousness : 8, {
not_serious = $N,
hospitalised = $H
life_threatening = $L,
congenital_abnormality = $C,
persisting_disability = $P,
intervention_required = $I,
death = $D
}).
decode_message(B0) ->
case B0
of <<not_serious/seriousness,
B1/binary>> ->
{{not_serious}, B1}
; <<hospitalised/seriousness,
NDays:32, B1/binary>> ->
{{hospitalised,NDays}, B1}
; <<life_threatening/seriousness,
B1/binary>> ->
{{life_threatening}, B1}
; <<congenital_abnormality/seriousness,
Extent/float, B1/binary>> ->
{{congenital_abnormality,Extent}, B1}
; <<persisting_disability/seriousness,
Extent/float, B1/binary>> ->
{{persisting_abnormality,Extent}, B1}
; <<intervention_required/seriousness,
Code:5/bytes, B1/binary>> ->
{{intervention_required,Code}, B1}
; <<death/seriousness,
B1/binary>> ->
{{death}, B1}
end.
幸運的是,此功能還提供了一種使用單個防護測試來接受一組原子或整數的方式。讓我們重新建構先前的範例,以首先提取嚴重性,然後比對主體,但這次,每個形狀只有一個主體。
-enum(seriousness, {
not_serious = $N,
hospitalised = $H
life_threatening = $L,
congenital_abnormality = $C,
persisting_disability = $P,
intervention_required = $I,
death = $D
}).
-enum(no_more_info, {
not_serious = $N,
life_threatening = $L,
death = $D
}).
-enum(extent_of_impairment, {
congenital_abnormality = $C,
persisting_disability = $P
}).
decode_message(<<Seriousness/seriousness, B0/binary>>) ->
if is_enum_atom(Seriousness, no_more_info) ->
{{Seriousness}, B0}
; is_enum_atom(Seriousness, extent_of_impairment) ->
<<Extent/float, B1/binary>> = B0,
{{Seriousness,Extent}, B1}
; Seriousness =:= hospitalised ->
<<NDays:32, B1/binary>> = B0,
{{Seriousness,NDays}, B1}
; Seriousness =:= intervention_required ->
<<Code:5/bytes, B1/binary>> = B0,
{{Seriousness,Code}, B1}
end.
由於這應該可以輕鬆轉換來自 C 或 PADS 或類似形式的描述,因此 enum 宣告看起來像是 C enum 宣告。
由於在多個位置可能需要 size、signedness 和 endianness,因此將它們全部放在宣告中是有意義的,這樣它們就不必重複(因此不會重複錯誤)。
選擇新 BIF 中引數的順序是為了符合 is_record/2
中引數的順序,以便 Erlang 程式設計師熟悉。
需要新的 BIF 來解釋擴展的位元語法。它們的名稱中唯一的縮寫是 enum
,它與宣告中的關鍵字完全匹配。
新的 BIF 也可用於通過原始程式碼到原始程式碼轉換來實現擴展的位元語法;不需要對位元語法機制進行實際更改。
使用任何四個新 BIF 的程式碼將受到影響。Erlang/OTP 原始碼中最接近提及這些原子的位置是 enum_to_int
,它正在使用。可以使用交叉參考工具找到確實使用任何這些 BIF 的程式碼。
一種簡單的方法是說,只有在模組中有 -enum
宣告的情況下,BIF is_enum_atom/2
、is_enum_integer/2
、enum_to_atom/2
和 enum_to_integer/2
才在模組中有效,在這種情況下,現有程式碼將完全不受影響。
對位元語法的影響是,先前不合法的形式(其中 Type 不是現有的數值或位元字串型別之一,或 Value 是原子)將變得合法,但前提是必須由適當的 -enum
宣告授權。
沒有參考實作。但是,我們可以草擬一個。四個新的 BIF 都是簡單的表格查詢,Erlang 編譯器已經必須能夠為索引子句選擇生成這種類型的查詢。因此,它們在防護中可以安全地呼叫。由於只有當位元語法中的 Type 是編譯器已知為列舉名稱的字面原子時,它才能是列舉名稱,因此建構子
<<... V : S / T X ...>>
可以翻譯為
( V1 = enum_to_integer(V, X), <<... V1 : S / integer X ...>>)
且模式
<<... V : S / T X ...>>
可以翻譯為
<<... V' : S / integer X ...>>
透過新增
V =:= enum_to_atom(V', T)
如果 V 出現在模式的其他位置或將在上下文中繫結,則將其新增到防護中,或者
V = enum_to_atom(V', T)
if V would not otherwise become bound.
無論如何都應該允許像這樣的繫結在防護中進行,但在這種情況下,它完全是安全的,因為它是 O(1) 且不需要任何動態儲存體配置(與算術不同)。
本文檔已置於公有領域。