作者
Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz>
狀態
草案
類型
標準追蹤
建立日期
2008-07-09
Erlang 版本
OTP_R12B-4

EEP 13:-enum 宣告 #

摘要 #

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) #

  • 當 Enumeration_Identifier 是一個宣告為列舉識別符號的原子,且 Atom 是該宣告中的列舉值之一時,為 true
  • 否則為 false

如果 Enumeration_Identifier 是字面原子,則可以作為防護測試使用,如果它沒有 enum 宣告,則會發生編譯時錯誤。

is_enum_integer(Integer, Enumeration_Identifier) #

  • 當 Enumeration_Identifier 是一個宣告為列舉識別符號的原子,且 Integer 是在該宣告的其中一個繫結中用作值的整數時,為 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。

當此類段用於模式比對時,如同

  • 首先,提取整數,如同 Type 為 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.

這存在許多問題。

  • 必須使用巨集;模式中不允許使用函數。
  • 沒有任何東西可以將這些巨集連結成一組。
  • 因此,沒有任何幫助來檢查您是否正在使用正確的巨集。
  • 沒有任何詞可以將它們關聯回原始 enum。
  • 如果 size 不是 8,則必須在每個模式中重複。
  • 如果 Endianness 不是 big,則必須在每個模式中重複。
  • 如果 size 錯誤,那就糟糕了。
  • 如果使用了錯誤列表中的巨集,那就糟糕了。
  • 您不能將相同的列舉值名稱用於多個列舉,除非它們在兩者中恰好具有相同的值。
  • 如果在計算中傳遞巨集,它們對追蹤器和偵錯器而言看起來就像數字;它們沒有執行時符號值。

現在這是使用 -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/2is_enum_integer/2enum_to_atom/2enum_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) 且不需要任何動態儲存體配置(與算術不同)。

版權 #

本文檔已置於公有領域。