[9d3a4cc] | 1 | \chapter{\texorpdfstring{\CFA}{Cforall} Enumeration} |
---|
[956299b] | 2 | |
---|
[7568e5c] | 3 | \CFA extends C-Style enumeration by adding a number of new features that bring enumerations in line with other modern programming languages. |
---|
| 4 | Any enumeration extensions must be intuitive to C programmers in syntax and semantics. |
---|
| 5 | The following sections detail my new contributions to enumerations in \CFA. |
---|
[38e20a80] | 6 | |
---|
| 7 | |
---|
[0c51c8b4] | 8 | \section{Syntax} |
---|
[c588acb] | 9 | |
---|
[7568e5c] | 10 | \CFA extends the C enumeration declaration \see{\VRef{s:CEnumeration}} by parameterizing with a type (like a generic type) and adding Plan-9 inheritance \see{\VRef{s:CFAInheritance}} using an @inline@ to another enumeration type. |
---|
[c588acb] | 11 | \begin{cfa}[identifierstyle=\linespread{0.9}\it] |
---|
[e561551] | 12 | $\it enum$-specifier: |
---|
[a03ed29] | 13 | enum @(type-specifier$\(_{opt}\)$)@ identifier$\(_{opt}\)$ { cfa-enumerator-list } |
---|
| 14 | enum @(type-specifier$\(_{opt}\)$)@ identifier$\(_{opt}\)$ { cfa-enumerator-list , } |
---|
[e561551] | 15 | enum @(type-specifier$\(_{opt}\)$)@ identifier |
---|
[a03ed29] | 16 | cfa-enumerator-list: |
---|
| 17 | cfa-enumerator |
---|
[c588acb] | 18 | cfa-enumerator-list, cfa-enumerator |
---|
[a03ed29] | 19 | cfa-enumerator: |
---|
[e561551] | 20 | enumeration-constant |
---|
[c588acb] | 21 | @inline $\color{red}enum$-type-name@ |
---|
| 22 | enumeration-constant = constant-expression |
---|
[38e20a80] | 23 | \end{cfa} |
---|
| 24 | |
---|
[e561551] | 25 | |
---|
[0c51c8b4] | 26 | \section{Operations} |
---|
[e561551] | 27 | |
---|
[c588acb] | 28 | \CFA enumerations have access to the three enumerations properties \see{\VRef{s:Terminology}}: label, order (position), and value via three overloaded functions @label@, @posn@, and @value@ \see{\VRef{c:trait} for details}. |
---|
| 29 | \CFA auto-generates these functions for every \CFA enumeration. |
---|
| 30 | \begin{cfa} |
---|
| 31 | enum(int) E { A = 3 } e = A; |
---|
| 32 | sout | A | @label@( A ) | @posn@( A ) | @value@( A ); |
---|
| 33 | sout | e | @label@( e ) | @posn@( e ) | @value@( e ); |
---|
| 34 | A A 0 3 |
---|
| 35 | A A 0 3 |
---|
| 36 | \end{cfa} |
---|
| 37 | For output, the default is to print the label. |
---|
| 38 | An alternate way to get an enumerator's position is to cast it to @int@. |
---|
| 39 | \begin{cfa} |
---|
| 40 | sout | A | label( A ) | @(int)A@ | value( A ); |
---|
| 41 | sout | A | label( A ) | @(int)A@ | value( A ); |
---|
| 42 | A A @0@ 3 |
---|
| 43 | A A @0@ 3 |
---|
[e561551] | 44 | \end{cfa} |
---|
[7568e5c] | 45 | Finally, \CFA introduces an additional enumeration pseudo-function @countof@ (like @sizeof@, @typeof@) that returns the number of enumerators in an enumeration. |
---|
[e561551] | 46 | \begin{cfa} |
---|
[0c51c8b4] | 47 | enum(int) E { A, B, C, D } e; |
---|
| 48 | countof( E ); // 4, type argument |
---|
| 49 | countof( e ); // 4, variable argument |
---|
[e561551] | 50 | \end{cfa} |
---|
[7568e5c] | 51 | This built-in function replaces the C idiom for automatically computing the number of enumerators \see{\VRef{s:Usage}}. |
---|
[c588acb] | 52 | \begin{cfa} |
---|
| 53 | enum E { A, B, C, D, @N@ }; // N == 4 |
---|
| 54 | \end{cfa} |
---|
| 55 | |
---|
| 56 | \section{Opaque Enumeration} |
---|
| 57 | \label{s:OpaqueEnum} |
---|
[e561551] | 58 | |
---|
[7568e5c] | 59 | When an enumeration type is empty. it is an \newterm{opaque} enumeration. |
---|
[c588acb] | 60 | \begin{cfa} |
---|
| 61 | enum@()@ Mode { O_RDONLY, O_WRONLY, O_CREAT, O_TRUNC, O_APPEND }; |
---|
| 62 | \end{cfa} |
---|
[7568e5c] | 63 | Here, the compiler chooses the internal representation, which is hidden, so the enumerators cannot be initialized. |
---|
| 64 | Compared to the C enum, opaque enums are more restrictive regarding typing and cannot be implicitly converted to integers. |
---|
[e561551] | 65 | \begin{cfa} |
---|
[c588acb] | 66 | Mode mode = O_RDONLY; |
---|
| 67 | int www @=@ mode; $\C{// disallowed}$ |
---|
| 68 | \end{cfa} |
---|
[7568e5c] | 69 | Opaque enumerations have only two attribute properties, @label@ and @posn@. |
---|
[c588acb] | 70 | \begin{cfa} |
---|
| 71 | char * s = label( O_TRUNC ); $\C{// "O\_TRUNC"}$ |
---|
| 72 | int open = posn( O_WRONLY ); $\C{// 1}$ |
---|
| 73 | \end{cfa} |
---|
[7568e5c] | 74 | Equality and relational operations are available. |
---|
[c588acb] | 75 | \begin{cfa} |
---|
| 76 | if ( mode @==@ O_CREAT ) ... |
---|
| 77 | bool b = mode @<@ O_APPEND; |
---|
[e561551] | 78 | \end{cfa} |
---|
| 79 | |
---|
| 80 | |
---|
[c588acb] | 81 | \section{Typed Enumeration} |
---|
[e561551] | 82 | \label{s:EnumeratorTyping} |
---|
| 83 | |
---|
[7568e5c] | 84 | When an enumeration type is specified, all enumerators have that type and can be initialized with constants of that type or compile-time convertible to that type. |
---|
| 85 | Figure~\ref{f:EumeratorTyping} shows a series of examples illustrating that all \CFA types can be used with an enumeration, and each type's values are used to set the enumerator constants. |
---|
| 86 | Note the use of the synonyms @Liz@ and @Beth@ in the last declaration. |
---|
[e561551] | 87 | Because enumerators are constants, the enumeration type is implicitly @const@, so all the enumerator types in Figure~\ref{f:EumeratorTyping} are logically rewritten with @const@. |
---|
| 88 | |
---|
| 89 | \begin{figure} |
---|
| 90 | \begin{cfa} |
---|
| 91 | // integral |
---|
| 92 | enum( @char@ ) Currency { Dollar = '$\textdollar$', Cent = '$\textcent$', Yen = '$\textyen$', Pound = '$\textsterling$', Euro = 'E' }; |
---|
| 93 | enum( @signed char@ ) srgb { Red = -1, Green = 0, Blue = 1 }; |
---|
| 94 | enum( @long long int@ ) BigNum { X = 123_456_789_012_345, Y = 345_012_789_456_123 }; |
---|
| 95 | // non-integral |
---|
| 96 | enum( @double@ ) Math { PI_2 = 1.570796, PI = 3.141597, E = 2.718282 }; |
---|
| 97 | enum( @_Complex@ ) Plane { X = 1.5+3.4i, Y = 7+3i, Z = 0+0.5i }; |
---|
| 98 | // pointer |
---|
[38e20a80] | 99 | enum( @char *@ ) Name { Fred = "FRED", Mary = "MARY", Jane = "JANE" }; |
---|
[e561551] | 100 | int i, j, k; |
---|
| 101 | enum( @int *@ ) ptr { I = &i, J = &j, K = &k }; |
---|
| 102 | enum( @int &@ ) ref { I = i, J = j, K = k }; |
---|
| 103 | // tuple |
---|
| 104 | enum( @[int, int]@ ) { T = [ 1, 2 ] }; $\C{// new \CFA type}$ |
---|
| 105 | // function |
---|
| 106 | void f() {...} void g() {...} |
---|
| 107 | enum( @void (*)()@ ) funs { F = f, G = g }; |
---|
| 108 | // aggregate |
---|
| 109 | struct Person { char * name; int age, height; }; |
---|
[c588acb] | 110 | enum( @Person@ ) friends { @Liz@ = { "ELIZABETH", 22, 170 }, @Beth@ = Liz, |
---|
[e561551] | 111 | Jon = { "JONATHAN", 35, 190 } }; |
---|
| 112 | \end{cfa} |
---|
[c588acb] | 113 | % synonym feature unimplemented |
---|
[e561551] | 114 | \caption{Enumerator Typing} |
---|
| 115 | \label{f:EumeratorTyping} |
---|
| 116 | \end{figure} |
---|
| 117 | |
---|
| 118 | An advantage of the typed enumerations is eliminating the \emph{harmonizing} problem between an enumeration and companion data \see{\VRef{s:Usage}}: |
---|
| 119 | \begin{cfa} |
---|
| 120 | enum( char * ) integral_types { |
---|
| 121 | chr = "char", schar = "signed char", uschar = "unsigned char", |
---|
| 122 | sshort = "signed short int", ushort = "unsigned short int", |
---|
| 123 | sint = "signed int", usint = "unsigned int", |
---|
| 124 | ... |
---|
| 125 | }; |
---|
| 126 | \end{cfa} |
---|
[7568e5c] | 127 | Note that the enumeration type can be a structure (see @Person@ in Figure~\ref{f:EumeratorTyping}), so it is possible to have the equivalent of multiple arrays of companion data using an array of structures. |
---|
[e561551] | 128 | |
---|
[c588acb] | 129 | While the enumeration type can be any C aggregate, the aggregate's \CFA constructors are \emph{not} used to evaluate an enumerator's value. |
---|
[e561551] | 130 | \CFA enumeration constants are compile-time values (static); |
---|
| 131 | calling constructors happens at runtime (dynamic). |
---|
| 132 | |
---|
[29c8675] | 133 | \section{Implementation} |
---|
| 134 | \CFA-cc is is a transpiler that translates \CFA code into C, which can later be compiled by a C compiler. |
---|
[e561551] | 135 | |
---|
[29c8675] | 136 | During the transpilation, \CFA-cc breaks a \CFA enumeration definition into a definition of a C enumeration with the same name and auxiliary arrays: a label array and a value array for a typed enumeration. |
---|
| 137 | For example: |
---|
| 138 | \begin{cfa} |
---|
| 139 | // CFA (source): |
---|
| 140 | enum(T) E { E1=t1, E2=t2, E3=t3 }; |
---|
| 141 | \end{cfa} |
---|
| 142 | is compiled into: |
---|
| 143 | \begin{cfa} |
---|
| 144 | // C (transpiled by cfa-cc): |
---|
| 145 | enum E { E1, E2, E3 }; |
---|
| 146 | const char * E_labels[3] = { "E1", "E2", "E3" }; |
---|
| 147 | const T E_values [3] = { t1, t2, t3 }; |
---|
| 148 | \end{cfa} |
---|
[d93b813] | 149 | The generated C enumeration will have enumerator values resemble \CFA enumerator positions thanks to C's auto-initialization scheme. |
---|
| 150 | A \CFA enumeration variable definition is same in \CFA and C, before or after the transpilation. |
---|
| 151 | For example: |
---|
| 152 | \begin{cfa} |
---|
| 153 | enum E e = E1; |
---|
| 154 | e; |
---|
| 155 | \end{cfa} |
---|
| 156 | These two expressions will not change by \CFA-cc. A \CFA enumeration variable will always have the same underlying representation as its generated |
---|
| 157 | C enumeration. This implies \CFA enumeration variable does not take up extra memory and \CFA enumeration use @posn@ as its underlying representation. |
---|
| 158 | |
---|
| 159 | Notice that value and label arrays are dynamically allocated data structures that take up |
---|
[29c8675] | 160 | memory. If an enumeration is globally defined, the arrays are allocated in the @.data@ section and will be initialized before the program execution. |
---|
| 161 | Otherwise, if an enumeration has its definition in a local scope, these arrays will be allocated on the stack and be initialized when the program counter |
---|
| 162 | reaches the code location of the enumeration definition. |
---|
[e561551] | 163 | |
---|
[29c8675] | 164 | % This bring a considerable overhead to the program, in terms of both execution time and storage. |
---|
| 165 | % An opaque enumeration has no overhead |
---|
| 166 | % for values, and it has been suggested as a future work to leave as an option to not generate the label array. |
---|
| 167 | |
---|
| 168 | Alongs with the enumeration defintion, \CFA-cc adds defintions of attribute functions: @posn@, @label@ and @value@: |
---|
| 169 | \begin{cfa} |
---|
| 170 | inline int posn( E e ) { return (int) e; } |
---|
| 171 | inline const * label( E e ) { return E_labels[ (int) e ]; } |
---|
| 172 | inline const * E_value( E e ) { return E_values[ (int) e ]; } |
---|
| 173 | \end{cfa} |
---|
| 174 | These functions are not implemented in \CFA code: they are Abstract Syntax Tree (AST) nodes appends to the Abstract Syntax Tree (AST). |
---|
| 175 | Notably, the AST subnode for the "cast to @int@" expression inside the functions is annotated as reinterpreted casts. |
---|
| 176 | In order words, the effect of a case is only to change the type of an expression, and it stops further reduction on the expression \see{\VRef{s:ValueConversion}}. |
---|
| 177 | |
---|
| 178 | Consequently, \CFA enumeration comes with space and runtime overhead, both for enumeration definition and function call to attribute functions. \CFA made efforts to reduce the runtime |
---|
| 179 | overhead on function calls by aggressively reducing @label()@ and @value()@ function calls on an enumeration constant to a constant expression. The interpreted casts are extraneous |
---|
| 180 | after type checking and removed in later steps. A @label()@ and @value()@ call on an enumeration variable is a lookup of an element of an array of constant values, and it is up to the |
---|
| 181 | C compiler to optimize its runtime. While OpaqueEnum is effectively an "opt-out" of the value overhead, it has been suggested that an option to "opt-out" from labels be added as well. |
---|
| 182 | A @label()@ function definition is still necessary to accomplish enumeration traits. But it will return an empty string for an enumeration label when "opt-out" or the enumerator name |
---|
| 183 | when it is called on an enumeration constant. It will allow a user not to pay the overhead for labels when the enumerator names of a particular enumerated type are not helpful. |
---|
| 184 | |
---|
| 185 | \section{Value Conversion} |
---|
| 186 | \label{s:ValueConversion} |
---|
[e561551] | 187 | C has an implicit type conversion from an enumerator to its base type @int@. |
---|
[c588acb] | 188 | Correspondingly, \CFA has an implicit conversion from a typed enumerator to its base type, allowing typed enumeration to be seamlessly used as the value of its base type |
---|
| 189 | For example, using type @Currency@ in \VRef[Figure]{f:EumeratorTyping}: |
---|
[e561551] | 190 | \begin{cfa} |
---|
[c588acb] | 191 | char currency = Dollar; $\C{// implicit conversion to base type}$ |
---|
| 192 | void foo( char ); |
---|
| 193 | foo( Dollar ); $\C{// implicit conversion to base type}$ |
---|
[e561551] | 194 | \end{cfa} |
---|
[c588acb] | 195 | The implicit conversion induces a \newterm{value cost}, which is a new category (8 tuple) in \CFA's conversion cost model \see{\VRef{s:ConversionCost}} to disambiguate function overloading over a \CFA enumeration and its base type. |
---|
[fcf3493] | 196 | \begin{cfa} |
---|
| 197 | void baz( char ch ); $\C{// (1)}$ |
---|
| 198 | void baz( Currency cu ); $\C{// (2)}$ |
---|
[c588acb] | 199 | baz( Dollar ); |
---|
[fcf3493] | 200 | \end{cfa} |
---|
[c588acb] | 201 | While both @baz@ functions are applicable to the enumerator @Dollar@, @candidate (1)@ comes with a @value@ cost for the conversion to the enumeration's base type, while @candidate (2)@ has @zero@ cost. |
---|
| 202 | Hence, \CFA chooses the exact match. |
---|
| 203 | Value cost is defined to be a more significant factor than an @unsafe@ but less than the other conversion costs: @(unsafe,@ {\color{red}@value@}@, poly, safe, sign, vars, specialization,@ @reference)@. |
---|
[38e20a80] | 204 | \begin{cfa} |
---|
[c588acb] | 205 | void bar( @int@ ); |
---|
| 206 | Math x = PI; $\C{// (1)}$ |
---|
| 207 | double x = 5.5; $\C{// (2)}$ |
---|
| 208 | bar( x ); $\C{// costs (1, 0, 0, 0, 0, 0, 0, 0) or (0, 1, 0, 0, 0, 0, 0, 0)}$ |
---|
[38e20a80] | 209 | \end{cfa} |
---|
[7568e5c] | 210 | Here, the candidate (1) has a @value@ conversion cost to convert to the base type, while the candidate (2) has an @unsafe@ conversion from @double@ to @int@, |
---|
| 211 | which is a more expensive conversion. |
---|
[c588acb] | 212 | Hence, @bar( x )@ resolves @x@ as type @Math@. |
---|
[38e20a80] | 213 | |
---|
[c588acb] | 214 | % \begin{cfa} |
---|
| 215 | % forall(T | @CfaEnum(T)@) void bar(T); |
---|
| 216 | % |
---|
| 217 | % bar(a); $\C{// (3), with cost (0, 0, 1, 0, 0, 0, 0, 0)}$ |
---|
| 218 | % \end{cfa} |
---|
| 219 | % % @Value@ is designed to be less significant than @poly@ to allow function being generic over \CFA enumeration (see ~\ref{c:trait}). |
---|
| 220 | % Being generic over @CfaEnum@ traits (a pre-defined interface for \CFA enums) is a practice in \CFA to implement functions over \CFA enumerations, as will see in chapter~\ref{c:trait}. |
---|
| 221 | % @Value@ is a being a more significant cost than @poly@ implies if a overloaeded function defined for @CfaEnum@ (and other generic type), \CFA always try to resolve it as a @CfaEnum@, rather to insert a @value@ conversion. |
---|
[38e20a80] | 222 | |
---|
[f632117] | 223 | |
---|
[d93b813] | 224 | \section{Initialization} |
---|
[acab1bd] | 225 | \CFA extends C's auto-initialization scheme to \CFA enumeration. For an enumeration type with base type T, the initialization scheme is the following: |
---|
| 226 | \begin{enumerate} |
---|
| 227 | \item the first enumerator is initialized with @T@'s @zero_t@. |
---|
| 228 | \item Every other enumerator is initialized with its previous enumerator's value "+1", where "+1" is defined in terms of overloaded operator @?+?(T, one_t)@. |
---|
| 229 | \end{enumerate} |
---|
[7568e5c] | 230 | |
---|
| 231 | \begin{cfa} |
---|
| 232 | struct S { int i; }; |
---|
| 233 | S ?+?( S & s, one_t ) { return s.i++; } |
---|
| 234 | void ?{}( S & s, zero_t ) { s.i = 0; } |
---|
| 235 | enum(S) E { A, B, C, D }; |
---|
| 236 | \end{cfa} |
---|
[c588acb] | 237 | |
---|
[29c8675] | 238 | The restriction on C's enumeration initializers being constant expression is relaxed on \CFA enumeration. |
---|
| 239 | Therefore, an enumerator initializer allows function calls like @?+?( S & s, one_t )@ and @?{}( S & s, zero_t )@. |
---|
| 240 | It is because the values of \CFA enumerators are not stored in the compiled enumeration body but in the @value@ array, which |
---|
| 241 | allows dynamic initialization. |
---|
| 242 | |
---|
[0c51c8b4] | 243 | \section{Subset} |
---|
| 244 | |
---|
| 245 | An enumeration's type can be another enumeration. |
---|
| 246 | \begin{cfa} |
---|
[3b10778] | 247 | enum( char ) Letter { A = 'A', ..., Z = 'Z' }; |
---|
| 248 | enum( @Letter@ ) Greek { Alph = @A@, Beta = @B@, Gamma = @G@, ..., Zeta = @Z@ }; // alphabet intersection |
---|
| 249 | \end{cfa} |
---|
| 250 | Enumeration @Greek@ may have more or less enumerators than @Letter@, but its enumerator values \emph{must} be from @Letter@. |
---|
| 251 | Therefore, the set of @Greek@ enumerator values in a subset of the @Letter@ enumerator values. |
---|
| 252 | @Letter@ is type compatible with enumeration @Letter@ because value conversions are inserted whenever @Letter@ is used in place of @Greek@. |
---|
| 253 | \begin{cfa} |
---|
| 254 | Letter l = A; $\C{// allowed}$ |
---|
| 255 | Greek g = Alph; $\C{// allowed}$ |
---|
| 256 | l = Alph; $\C{// allowed, conversion to base type}$ |
---|
| 257 | g = A; $\C{// {\color{red}disallowed}}$ |
---|
| 258 | void foo( Letter ); |
---|
| 259 | foo( Beta ); $\C{// allowed, conversion to base type}$ |
---|
| 260 | void bar( Greek ); |
---|
| 261 | bar( A ); $\C{// {\color{red}disallowed}}$ |
---|
[0c51c8b4] | 262 | \end{cfa} |
---|
[7568e5c] | 263 | Hence, @Letter@ enumerators are not type-compatible with the @Greek@ enumeration, but the reverse is true. |
---|
[0c51c8b4] | 264 | |
---|
| 265 | |
---|
| 266 | \section{Inheritance} |
---|
| 267 | \label{s:CFAInheritance} |
---|
[e561551] | 268 | |
---|
[fcf3493] | 269 | \CFA Plan-9 inheritance may be used with \CFA enumerations, where Plan-9 inheritance is containment inheritance with implicit unscoping (like a nested unnamed @struct@/@union@ in C). |
---|
[c588acb] | 270 | Containment is nominative: an enumeration inherits all enumerators from another enumeration by declaring an @inline statement@ in its enumerator lists. |
---|
[956299b] | 271 | \begin{cfa} |
---|
[3b10778] | 272 | enum( char * ) Names { /* $\see{\VRef[Figure]{f:EumeratorTyping}}$ */ }; |
---|
[e561551] | 273 | enum( char * ) Names2 { @inline Names@, Jack = "JACK", Jill = "JILL" }; |
---|
| 274 | enum( char * ) Names3 { @inline Names2@, Sue = "SUE", Tom = "TOM" }; |
---|
[956299b] | 275 | \end{cfa} |
---|
[1697c40] | 276 | In the preceding example, @Names2@ is defined with five enumerators, three of which are from @Name@ through containment, and two are self-declared. |
---|
[c588acb] | 277 | @Names3@ inherits all five members from @Names2@ and declares two additional enumerators. |
---|
| 278 | Hence, enumeration inheritance forms a subset relationship. |
---|
| 279 | Specifically, the inheritance relationship for the example above is: |
---|
[1697c40] | 280 | \begin{cfa} |
---|
| 281 | Names $\(\subset\)$ Names2 $\(\subset\)$ Names3 $\C{// enum type of Names}$ |
---|
| 282 | \end{cfa} |
---|
| 283 | |
---|
| 284 | Inheritance can be nested, and a \CFA enumeration can inline enumerators from more than one \CFA enumeration, forming a tree-like hierarchy. |
---|
[7568e5c] | 285 | However, the uniqueness of the enumeration name applies to enumerators, including those from supertypes, meaning an enumeration cannot name an enumerator with the same label as its subtype's members or inherits |
---|
| 286 | from multiple enumeration that has overlapping enumerator labels. Consequently, a new type cannot inherit from an enumeration and its supertype or two enumerations with a |
---|
| 287 | common supertype (the diamond problem) since such would unavoidably introduce duplicate enumerator labels. |
---|
[1697c40] | 288 | |
---|
[c141c09] | 289 | The base type must be consistent between subtype and supertype. |
---|
[7568e5c] | 290 | When an enumeration inherits enumerators from another enumeration, it copies the enumerators' @value@ and @label@, even if the @value@ is auto-initialized. |
---|
[c588acb] | 291 | However, the position of the underlying representation is the order of the enumerator in the new enumeration. |
---|
[956299b] | 292 | \begin{cfa} |
---|
[3b10778] | 293 | enum() E1 { B }; $\C{// B}$ |
---|
| 294 | enum() E2 { C, D }; $\C{// C D}$ |
---|
| 295 | enum() E3 { inline E1, inline E2, E }; $\C{// {\color{red}[\(_{E1}\)} B {\color{red}]} {\color{red}[\(_{E2}\)} C D {\color{red}]} E}$ |
---|
| 296 | enum() E4 { A, inline E3, F}; $\C{// A {\color{blue}[\(_{E3}\)} {\color{red}[\(_{E1}\)} B {\color{red}]} {\color{red}[\(_{E2}\)} C D {\color{red}]} E {\color{blue}]} F}$ |
---|
[e561551] | 297 | \end{cfa} |
---|
[3b10778] | 298 | In the example, @B@ is at position 0 in @E1@ and @E3@, but position 1 in @E4@ as @A@ takes position 0 in @E4@. |
---|
| 299 | @C@ is at position 0 in @E2@, 1 in @E3@, and 2 in @E4@. |
---|
| 300 | @D@ is at position 1 in @E2@, 2 in @E3@, and 3 in @E4@. |
---|
[e561551] | 301 | |
---|
[3b10778] | 302 | A subtype enumeration can be casted, or implicitly converted into its supertype, with a @safe@ cost, called \newterm{enumeration conversion}. |
---|
[e561551] | 303 | \begin{cfa} |
---|
[c588acb] | 304 | enum E2 e2 = C; |
---|
[63d5b9c8] | 305 | posn( e2 ); $\C[1.75in]{// 0}$ |
---|
[92a0ee8] | 306 | enum E3 e3 = e2; $\C{// Assignment with enumeration conversion E2 to E3}$ |
---|
[c1c0efdb] | 307 | posn( e2 ); $\C{// 1 cost}$ |
---|
[c588acb] | 308 | void foo( E3 e ); |
---|
[92a0ee8] | 309 | foo( e2 ); $\C{// Type compatible with enumeration conversion E2 to E3}$ |
---|
| 310 | posn( (E3)e2 ); $\C{// Explicit cast with enumeration conversion E2 to E3}$ |
---|
| 311 | E3 e31 = B; $\C{// No conversion: E3.B}$ |
---|
[c1c0efdb] | 312 | posn( e31 ); $\C{// 0 cost}\CRT$ |
---|
[c588acb] | 313 | \end{cfa} |
---|
| 314 | The last expression is unambiguous. |
---|
[c1c0efdb] | 315 | While both @E2.B@ and @E3.B@ are valid candidates, @E2.B@ has an associated safe cost and @E3.B@ needs no conversion (@zero@ cost). |
---|
| 316 | \CFA selects the lowest cost candidate @E3.B@. |
---|
[e561551] | 317 | |
---|
| 318 | For the given function prototypes, the following calls are valid. |
---|
| 319 | \begin{cquote} |
---|
| 320 | \begin{tabular}{ll} |
---|
| 321 | \begin{cfa} |
---|
| 322 | void f( Names ); |
---|
| 323 | void g( Names2 ); |
---|
| 324 | void h( Names3 ); |
---|
| 325 | void j( const char * ); |
---|
| 326 | \end{cfa} |
---|
| 327 | & |
---|
| 328 | \begin{cfa} |
---|
| 329 | f( Fred ); |
---|
| 330 | g( Fred ); g( Jill ); |
---|
| 331 | h( Fred ); h( Jill ); h( Sue ); |
---|
| 332 | j( Fred ); j( Jill ); j( Sue ); j( "WILL" ); |
---|
| 333 | \end{cfa} |
---|
| 334 | \end{tabular} |
---|
| 335 | \end{cquote} |
---|
| 336 | Note, the validity of calls is the same for call-by-reference as for call-by-value, and @const@ restrictions are the same as for other types. |
---|
| 337 | |
---|
[3b10778] | 338 | |
---|
[63d5b9c8] | 339 | \subsection{Offset Calculation} |
---|
[3b10778] | 340 | |
---|
[63d5b9c8] | 341 | As discussed in \VRef{s:OpaqueEnum}, \CFA chooses position as a representation of a \CFA enumeration variable. |
---|
[3b10778] | 342 | When a cast or implicit conversion moves an enumeration from subtype to supertype, the position can be unchanged or increase. |
---|
| 343 | \CFA determines the position offset with an \newterm{offset calculation} function. |
---|
[63d5b9c8] | 344 | |
---|
| 345 | \begin{figure} |
---|
| 346 | \begin{cfa} |
---|
| 347 | struct Enumerator; |
---|
| 348 | struct CFAEnum { vector<variant<CFAEnum, Enumerator>> members; string name; }; |
---|
| 349 | inline static bool operator==(CFAEnum& lhs, CFAEnum& rhs) { return lhs.name == rhs.name; } |
---|
| 350 | pair<bool, int> calculateEnumOffset(CFAEnum src, CFAEnum dst) { |
---|
| 351 | int offset = 0; |
---|
[3b10778] | 352 | if ( src == dst ) return make_pair(true, 0); |
---|
| 353 | for ( auto v : dst.members ) { |
---|
| 354 | if ( holds_alternative<Enumerator>(v) ) { |
---|
[63d5b9c8] | 355 | offset++; |
---|
| 356 | } else { |
---|
| 357 | auto m = get<CFAEnum>(v); |
---|
[3b10778] | 358 | if ( m == src ) @return@ make_pair( true, offset ); |
---|
| 359 | auto dist = calculateEnumOffset( src, m ); |
---|
| 360 | if ( dist.first ) { |
---|
| 361 | @return@ make_pair( true, offset + dist.second ); |
---|
[63d5b9c8] | 362 | } else { |
---|
| 363 | offset += dist.second; |
---|
| 364 | } |
---|
| 365 | } |
---|
| 366 | } |
---|
[3b10778] | 367 | @return@ make_pair( false, offset ); |
---|
[63d5b9c8] | 368 | } |
---|
| 369 | \end{cfa} |
---|
| 370 | \caption{Compute Offset from Subtype Enumeration to a Supertype} |
---|
| 371 | \label{s:OffsetSubtypeSuperType} |
---|
| 372 | \end{figure} |
---|
| 373 | |
---|
[3b10778] | 374 | Figure~\ref{s:OffsetSubtypeSuperType} shows an outline of the offset calculation in \CC. |
---|
| 375 | Structure @CFAEnum@ represents the \CFA enumeration with a vector of variants of @CFAEnum@ or @Enumerator@. |
---|
| 376 | The algorithm takes two @CFAEnums@ parameters, @src@ and @dst@, with @src@ being the type of expression the conversion applies to, and @dst@ being the type the expression is cast to. |
---|
| 377 | The algorithm iterates over the members in @dst@ to find @src@. |
---|
[7568e5c] | 378 | If a member is an enumerator of @dst@, the positions of all subsequent members are incremented by one. |
---|
[3b10778] | 379 | If the current member is @dst@, the function returns true indicating \emph{found} and the accumulated offset. |
---|
| 380 | Otherwise, the algorithm recurses into the current @CFAEnum@ @m@ to check if its @src@ is convertible to @m@. |
---|
| 381 | If @src@ is convertible to the current member @m@, this means @src@ is a subtype-of-subtype of @dst@. |
---|
| 382 | The offset between @src@ and @dst@ is the sum of the offset of @m@ in @dst@ and the offset of @src@ in @m@. |
---|
| 383 | If @src@ is not a subtype of @m@, the loop continues but with the offset shifted by the size of @m@. |
---|
| 384 | If the loop ends, than @src@ is not convertible to @dst@, and false is returned. |
---|
| 385 | |
---|
[c588acb] | 386 | |
---|
[0c51c8b4] | 387 | \section{Control Structures} |
---|
[956299b] | 388 | |
---|
| 389 | Enumerators can be used in multiple contexts. |
---|
| 390 | In most programming languages, an enumerator is implicitly converted to its value (like a typed macro substitution). |
---|
| 391 | However, enumerator synonyms and typed enumerations make this implicit conversion to value incorrect in some contexts. |
---|
[38e20a80] | 392 | In these contexts, a programmer's intuition assumes an implicit conversion to position. |
---|
[956299b] | 393 | |
---|
[09dd830] | 394 | For example, an intuitive use of enumerations is with the \CFA @switch@/@choose@ statement, where @choose@ performs an implicit @break@ rather than a fall-through at the end of a @case@ clause. |
---|
[10a99d87] | 395 | (For this discussion, ignore the fact that @case@ requires a compile-time constant.) |
---|
| 396 | \begin{cfa}[belowskip=0pt] |
---|
[956299b] | 397 | enum Count { First, Second, Third, Fourth }; |
---|
| 398 | Count e; |
---|
| 399 | \end{cfa} |
---|
[10a99d87] | 400 | \begin{cquote} |
---|
| 401 | \setlength{\tabcolsep}{15pt} |
---|
| 402 | \noindent |
---|
| 403 | \begin{tabular}{@{}ll@{}} |
---|
| 404 | \begin{cfa}[aboveskip=0pt] |
---|
[956299b] | 405 | |
---|
| 406 | choose( e ) { |
---|
| 407 | case @First@: ...; |
---|
| 408 | case @Second@: ...; |
---|
| 409 | case @Third@: ...; |
---|
| 410 | case @Fourth@: ...; |
---|
| 411 | } |
---|
| 412 | \end{cfa} |
---|
| 413 | & |
---|
[10a99d87] | 414 | \begin{cfa}[aboveskip=0pt] |
---|
[956299b] | 415 | // rewrite |
---|
| 416 | choose( @value@( e ) ) { |
---|
| 417 | case @value@( First ): ...; |
---|
| 418 | case @value@( Second ): ...; |
---|
| 419 | case @value@( Third ): ...; |
---|
| 420 | case @value@( Fourth ): ...; |
---|
| 421 | } |
---|
| 422 | \end{cfa} |
---|
| 423 | \end{tabular} |
---|
| 424 | \end{cquote} |
---|
[de3a579] | 425 | Here, the intuitive code on the left is implicitly transformed into the standard implementation on the right, using the value of the enumeration variable and enumerators. |
---|
[f9da761] | 426 | However, this implementation is fragile, \eg if the enumeration is changed to: |
---|
[956299b] | 427 | \begin{cfa} |
---|
| 428 | enum Count { First, Second, Third @= First@, Fourth }; |
---|
| 429 | \end{cfa} |
---|
[10a99d87] | 430 | making @Third == First@ and @Fourth == Second@, causing a compilation error because of duplicate @case@ clauses. |
---|
[ec20ab9] | 431 | To better match with programmer intuition, \CFA toggles between value and position semantics depending on the language context. |
---|
[09dd830] | 432 | For conditional clauses and switch statements, \CFA uses the robust position implementation. |
---|
[956299b] | 433 | \begin{cfa} |
---|
[10a99d87] | 434 | if ( @posn@( e ) < posn( Third ) ) ... |
---|
| 435 | choose( @posn@( e ) ) { |
---|
| 436 | case @posn@( First ): ...; |
---|
| 437 | case @posn@( Second ): ...; |
---|
| 438 | case @posn@( Third ): ...; |
---|
| 439 | case @posn@( Fourth ): ...; |
---|
[956299b] | 440 | } |
---|
| 441 | \end{cfa} |
---|
| 442 | |
---|
[10a99d87] | 443 | \CFA provides a special form of for-control for enumerating through an enumeration, where the range is a type. |
---|
[956299b] | 444 | \begin{cfa} |
---|
[10a99d87] | 445 | for ( cx; @Count@ ) { sout | cx | nonl; } sout | nl; |
---|
[c588acb] | 446 | for ( cx; ~= Count ) { sout | cx | nonl; } sout | nl; |
---|
[10a99d87] | 447 | for ( cx; -~= Count ) { sout | cx | nonl; } sout | nl; |
---|
| 448 | First Second Third Fourth |
---|
| 449 | First Second Third Fourth |
---|
| 450 | Fourth Third Second First |
---|
[956299b] | 451 | \end{cfa} |
---|
[10a99d87] | 452 | The enumeration type is syntax sugar for looping over all enumerators and assigning each enumerator to the loop index, whose type is inferred from the range type. |
---|
| 453 | The prefix @+~=@ or @-~=@ iterate forward or backwards through the inclusive enumeration range, where no prefix defaults to @+~=@. |
---|
[7d9a805b] | 454 | |
---|
[10a99d87] | 455 | C has an idiom for @if@ and loop predicates of comparing the predicate result ``not equal to 0''. |
---|
[dc1c430] | 456 | \begin{cfa} |
---|
[c588acb] | 457 | if ( x + y /* != 0 */ ) ... |
---|
| 458 | while ( p /* != 0 */ ) ... |
---|
[dc1c430] | 459 | \end{cfa} |
---|
[10a99d87] | 460 | This idiom extends to enumerations because there is a boolean conversion in terms of the enumeration value, if and only if such a conversion is available. |
---|
| 461 | For example, such a conversion exists for all numerical types (integral and floating-point). |
---|
| 462 | It is possible to explicitly extend this idiom to any typed enumeration by overloading the @!=@ operator. |
---|
[dc1c430] | 463 | \begin{cfa} |
---|
[10a99d87] | 464 | bool ?!=?( Name n, zero_t ) { return n != Fred; } |
---|
| 465 | Name n = Mary; |
---|
| 466 | if ( n ) ... // result is true |
---|
[dc1c430] | 467 | \end{cfa} |
---|
[10a99d87] | 468 | Specialize meanings are also possible. |
---|
[dc1c430] | 469 | \begin{cfa} |
---|
| 470 | enum(int) ErrorCode { Normal = 0, Slow = 1, Overheat = 1000, OutOfResource = 1001 }; |
---|
[10a99d87] | 471 | bool ?!=?( ErrorCode ec, zero_t ) { return ec >= Overheat; } |
---|
| 472 | ErrorCode code = ...; |
---|
| 473 | if ( code ) { problem(); } |
---|
[dc1c430] | 474 | \end{cfa} |
---|
[7d9a805b] | 475 | |
---|
[f632117] | 476 | |
---|
[0c51c8b4] | 477 | \section{Dimension} |
---|
[f632117] | 478 | |
---|
[c588acb] | 479 | \VRef{s:EnumeratorTyping} introduces the harmonizing problem between an enumeration and secondary information. |
---|
[10a99d87] | 480 | When possible, using a typed enumeration for the secondary information is the best approach. |
---|
| 481 | However, there are times when combining these two types is not possible. |
---|
| 482 | For example, the secondary information might precede the enumeration and/or its type is needed directly to declare parameters of functions. |
---|
| 483 | In these cases, having secondary arrays of the enumeration size are necessary. |
---|
[f632117] | 484 | |
---|
[10a99d87] | 485 | To support some level of harmonizing in these cases, an array dimension can be defined using an enumerator type, and the enumerators used as subscripts. |
---|
[dc1c430] | 486 | \begin{cfa} |
---|
[c141c09] | 487 | enum E1 { A, B, C, N }; // possibly predefined |
---|
| 488 | enum(int) E2 { A, B, C }; |
---|
[c588acb] | 489 | float H1[N] = { [A] :$\footnotemark$ 3.4, [B] : 7.1, [C] : 0.01 }; // C |
---|
[c141c09] | 490 | float H2[@E2@] = { [A] : 3.4, [B] : 7.1, [C] : 0.01 }; // CFA |
---|
[d69f7114] | 491 | \end{cfa} |
---|
[c588acb] | 492 | \footnotetext{C uses symbol \lstinline{'='} for designator initialization, but \CFA changes it to \lstinline{':'} because of problems with tuple syntax.} |
---|
[10a99d87] | 493 | This approach is also necessary for a predefined typed enumeration (unchangeable), when additional secondary-information need to be added. |
---|
[c588acb] | 494 | The array subscript operator, namely @?[?]@, is overloaded so that when a \CFA enumerator is used as an array index, it implicitly converts to its position over value to sustain data harmonization. |
---|
| 495 | This behaviour can be reverted by explicit overloading: |
---|
[c141c09] | 496 | \begin{cfa} |
---|
[c588acb] | 497 | float ?[?]( float * arr, E2 index ) { return arr[ value( index ) ]; } |
---|
[c141c09] | 498 | \end{cfa} |
---|
[c1c0efdb] | 499 | While enumerator labels @A@, @B@ and @C@ are being defined twice in different enumerations, they are unambiguous within the context. |
---|
| 500 | Designators in @H1@ are unambiguous becasue @E2@ has a @value@ cost to @int@, which is more expensive than @safe@ cost from C-Enum @E1@ to @int@. |
---|
| 501 | Designators in @H2@ are resolved as @E2@ because when a \CFA enumeration type is being used as an array dimension, \CFA adds the enumeration type to the initializer's resolution context. |
---|
| 502 | |
---|
[bc17be98] | 503 | |
---|
[0c51c8b4] | 504 | \section{I/O} |
---|
[c1c0efdb] | 505 | |
---|
[92a0ee8] | 506 | As seen in multiple examples, \CFA enumerations can be printed and the default property printed is the enumerator's label, which is similar in other programming languages. |
---|
[6f47834] | 507 | However, very few programming languages provide a mechanism to read in enumerator values. |
---|
| 508 | Even the @boolean@ type in many languages does not have a mechanism for input using the enumerators @true@ or @false@. |
---|
| 509 | \VRef[Figure]{f:EnumerationI/O} show \CFA enumeration input based on the enumerator labels. |
---|
| 510 | When the enumerator labels are packed together in the input stream, the input algorithm scans for the longest matching string. |
---|
[7568e5c] | 511 | For basic types in \CFA, the rule is that the same constants used to initialize a variable in a program are available to initialize a variable using input, where string constants can be quoted or unquoted. |
---|
[6f47834] | 512 | |
---|
| 513 | \begin{figure} |
---|
| 514 | \begin{cquote} |
---|
| 515 | \setlength{\tabcolsep}{15pt} |
---|
| 516 | \begin{tabular}{@{}ll@{}} |
---|
| 517 | \begin{cfa} |
---|
| 518 | int main() { |
---|
| 519 | enum(int ) E { BBB = 3, AAA, AA, AB, B }; |
---|
| 520 | E e; |
---|
| 521 | |
---|
[3ac5fd8] | 522 | try { |
---|
| 523 | for () { |
---|
| 524 | try { |
---|
| 525 | @sin | e@; |
---|
| 526 | } catch( missing_data * ) { |
---|
| 527 | sout | "missing data"; |
---|
| 528 | continue; // try again |
---|
| 529 | } |
---|
| 530 | sout | e | "= " | value( e ); |
---|
[6f47834] | 531 | } |
---|
[3ac5fd8] | 532 | } catch( end_of_file ) {} |
---|
[6f47834] | 533 | } |
---|
| 534 | \end{cfa} |
---|
| 535 | & |
---|
| 536 | \begin{cfa} |
---|
| 537 | $\rm input$ |
---|
| 538 | BBBABAAAAB |
---|
| 539 | BBB AAA AA AB B |
---|
| 540 | |
---|
| 541 | $\rm output$ |
---|
| 542 | BBB = 3 |
---|
| 543 | AB = 6 |
---|
| 544 | AAA = 4 |
---|
| 545 | AB = 6 |
---|
| 546 | BBB = 3 |
---|
| 547 | AAA = 4 |
---|
| 548 | AA = 5 |
---|
| 549 | AB = 6 |
---|
| 550 | B = 7 |
---|
| 551 | |
---|
| 552 | \end{cfa} |
---|
| 553 | \end{tabular} |
---|
| 554 | \end{cquote} |
---|
| 555 | \caption{Enumeration I/O} |
---|
| 556 | \label{f:EnumerationI/O} |
---|
| 557 | \end{figure} |
---|
| 558 | |
---|
| 559 | |
---|
[7d9a805b] | 560 | \section{Planet Example} |
---|
| 561 | |
---|
[f632117] | 562 | \VRef[Figure]{f:PlanetExample} shows an archetypal enumeration example illustrating most of the \CFA enumeration features. |
---|
[caaf424] | 563 | @Planet@ is an enumeration of type @MR@. |
---|
[09dd830] | 564 | Each planet enumerator is initialized to a specific mass/radius, @MR@, value. |
---|
[caaf424] | 565 | The unnamed enumeration provides the gravitational-constant enumerator @G@. |
---|
| 566 | Function @surfaceGravity@ uses the @with@ clause to remove @p@ qualification from fields @mass@ and @radius@. |
---|
[29092213] | 567 | The program main uses the pseudo function @countof@ to obtain the number of enumerators in @Planet@, and safely converts the random value into a @Planet@ enumerator using @fromInt@. |
---|
| 568 | The resulting random orbital-body is used in a @choose@ statement. |
---|
[09dd830] | 569 | The enumerators in the @case@ clause use the enumerator position for testing. |
---|
[29092213] | 570 | The prints use @label@ to print an enumerator's name. |
---|
[29c8675] | 571 | Finally, a loop enumerates through the planets computing the weight on each planet for a given Earth mass. |
---|
[29092213] | 572 | The print statement does an equality comparison with an enumeration variable and enumerator (@p == MOON@). |
---|
[7d9a805b] | 573 | |
---|
| 574 | \begin{figure} |
---|
[caaf424] | 575 | \small |
---|
[7d9a805b] | 576 | \begin{cfa} |
---|
[c588acb] | 577 | struct MR { double mass, radius; }; $\C[3.5in]{// planet definition}$ |
---|
[29092213] | 578 | enum( @MR@ ) Planet { $\C{// typed enumeration}$ |
---|
[f632117] | 579 | // mass (kg) radius (km) |
---|
| 580 | MERCURY = { 0.330_E24, 2.4397_E6 }, |
---|
| 581 | VENUS = { 4.869_E24, 6.0518_E6 }, |
---|
[7d9a805b] | 582 | EARTH = { 5.976_E24, 6.3781_E6 }, |
---|
[29092213] | 583 | MOON = { 7.346_E22, 1.7380_E6 }, $\C{// not a planet}$ |
---|
[f632117] | 584 | MARS = { 0.642_E24, 3.3972_E6 }, |
---|
| 585 | JUPITER = { 1898._E24, 71.492_E6 }, |
---|
| 586 | SATURN = { 568.8_E24, 60.268_E6 }, |
---|
| 587 | URANUS = { 86.86_E24, 25.559_E6 }, |
---|
| 588 | NEPTUNE = { 102.4_E24, 24.746_E6 }, |
---|
[29092213] | 589 | PLUTO = { 1.303_E22, 1.1880_E6 }, $\C{// not a planet}$ |
---|
[7d9a805b] | 590 | }; |
---|
[29092213] | 591 | enum( double ) { G = 6.6743_E-11 }; $\C{// universal gravitational constant (m3 kg-1 s-2)}$ |
---|
[caaf424] | 592 | static double surfaceGravity( Planet p ) @with( p )@ { |
---|
[29092213] | 593 | return G * mass / ( radius @\@ 2 ); $\C{// no qualification, exponentiation}$ |
---|
[7d9a805b] | 594 | } |
---|
| 595 | static double surfaceWeight( Planet p, double otherMass ) { |
---|
| 596 | return otherMass * surfaceGravity( p ); |
---|
| 597 | } |
---|
| 598 | int main( int argc, char * argv[] ) { |
---|
[29092213] | 599 | if ( argc != 2 ) @exit@ | "Usage: " | argv[0] | "earth-weight"; // terminate program |
---|
[7d9a805b] | 600 | double earthWeight = convert( argv[1] ); |
---|
[d69f7114] | 601 | double earthMass = earthWeight / surfaceGravity( EARTH ); |
---|
[29092213] | 602 | Planet rp = @fromInt@( prng( @countof@( Planet ) ) ); $\C{// select random orbiting body}$ |
---|
| 603 | @choose( rp )@ { $\C{// implicit breaks}$ |
---|
[caaf424] | 604 | case MERCURY, VENUS, EARTH, MARS: |
---|
[62a38e7] | 605 | sout | @rp@ | "is a rocky planet"; |
---|
[29092213] | 606 | case JUPITER, SATURN, URANUS, NEPTUNE: |
---|
[62a38e7] | 607 | sout | rp | "is a gas-giant planet"; |
---|
[caaf424] | 608 | default: |
---|
[62a38e7] | 609 | sout | rp | "is not a planet"; |
---|
[caaf424] | 610 | } |
---|
[c588acb] | 611 | for ( @p; Planet@ ) { $\C{// enumerate}\CRT$ |
---|
[62a38e7] | 612 | sout | "Your weight on" | ( @p == MOON@ ? "the" : " " ) | p |
---|
[29092213] | 613 | | "is" | wd( 1,1, surfaceWeight( p, earthMass ) ) | "kg"; |
---|
[7d9a805b] | 614 | } |
---|
| 615 | } |
---|
[f632117] | 616 | $\$$ planet 100 |
---|
[caaf424] | 617 | JUPITER is a gas-giant planet |
---|
[f632117] | 618 | Your weight on MERCURY is 37.7 kg |
---|
| 619 | Your weight on VENUS is 90.5 kg |
---|
| 620 | Your weight on EARTH is 100.0 kg |
---|
[caaf424] | 621 | Your weight on the MOON is 16.6 kg |
---|
[f632117] | 622 | Your weight on MARS is 37.9 kg |
---|
| 623 | Your weight on JUPITER is 252.8 kg |
---|
| 624 | Your weight on SATURN is 106.6 kg |
---|
| 625 | Your weight on URANUS is 90.5 kg |
---|
| 626 | Your weight on NEPTUNE is 113.8 kg |
---|
[29092213] | 627 | Your weight on PLUTO is 6.3 kg |
---|
[7d9a805b] | 628 | \end{cfa} |
---|
| 629 | \caption{Planet Example} |
---|
| 630 | \label{f:PlanetExample} |
---|
| 631 | \end{figure} |
---|