Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / mscorlib / system / globalization / hebrewnumber.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6
7 namespace System.Globalization {
8     using System;
9     using System.Text;
10     using System.Diagnostics.Contracts;
11
12     ////////////////////////////////////////////////////////////////////////////
13     //
14     // Used in HebrewNumber.ParseByChar to maintain the context information (
15     // the state in the state machine and current Hebrew number values, etc.)
16     // when parsing Hebrew number character by character.
17     //
18     ////////////////////////////////////////////////////////////////////////////
19     
20     internal struct HebrewNumberParsingContext {
21         // The current state of the state machine for parsing Hebrew numbers.
22         internal HebrewNumber.HS state;
23         // The current value of the Hebrew number.
24         // The final value is determined when state is FoundEndOfHebrewNumber.
25         internal int result;
26
27         public HebrewNumberParsingContext(int result) {
28             // Set the start state of the state machine for parsing Hebrew numbers.
29             state = HebrewNumber.HS.Start;
30             this.result = result;
31         }
32     }
33
34     ////////////////////////////////////////////////////////////////////////////
35     //
36     // Please see ParseByChar() for comments about different states defined here.
37     //
38     ////////////////////////////////////////////////////////////////////////////
39     
40     internal enum HebrewNumberParsingState {
41         InvalidHebrewNumber,
42         NotHebrewDigit,
43         FoundEndOfHebrewNumber,
44         ContinueParsing,
45     }
46
47     ////////////////////////////////////////////////////////////////////////////
48     //
49     // class HebrewNumber
50     //
51     //  Provides static methods for formatting integer values into
52     //  Hebrew text and parsing Hebrew number text.
53     //
54     //  Limitations:
55     //      Parse can only handles value 1 ~ 999.
56     //      ToString() can only handles 1 ~ 999. If value is greater than 5000,
57     //      5000 will be subtracted from the value.
58     //
59     ////////////////////////////////////////////////////////////////////////////
60     
61     internal class HebrewNumber {
62
63         // This class contains only static methods.  Add a private ctor so that
64         // compiler won't generate a default one for us.
65         private HebrewNumber() {
66         }
67
68         ////////////////////////////////////////////////////////////////////////////
69         //
70         //  ToString
71         //
72         //  Converts the given number to Hebrew letters according to the numeric
73         //  value of each Hebrew letter.  Basically, this converts the lunar year
74         //  and the lunar month to letters.
75         //
76         //  The character of a year is described by three letters of the Hebrew
77         //  alphabet, the first and third giving, respectively, the days of the
78         //  weeks on which the New Year occurs and Passover begins, while the
79         //  second is the initial of the Hebrew word for defective, normal, or
80         //  complete.
81         //
82         //  Defective Year : Both Heshvan and Kislev are defective (353 or 383 days)
83         //  Normal Year    : Heshvan is defective, Kislev is full  (354 or 384 days)
84         //  Complete Year  : Both Heshvan and Kislev are full      (355 or 385 days)
85         //
86         ////////////////////////////////////////////////////////////////////////////
87
88         internal static String ToString(int Number) {
89             char cTens = '\x0';
90             char cUnits;               // tens and units chars
91             int Hundreds, Tens;              // hundreds and tens values
92             StringBuilder szHebrew = new StringBuilder();
93
94
95             //
96             //  Adjust the number if greater than 5000.
97             //
98             if (Number > 5000) {
99                 Number -= 5000;
100             }
101
102             Contract.Assert(Number > 0 && Number <= 999, "Number is out of range.");;
103
104             //
105             //  Get the Hundreds.
106             //
107             Hundreds = Number / 100;
108
109             if (Hundreds > 0) {
110                 Number -= Hundreds * 100;
111                 // \x05e7 = 100
112                 // \x05e8 = 200
113                 // \x05e9 = 300
114                 // \x05ea = 400
115                 // If the number is greater than 400, use the multiples of 400.
116                 for (int i = 0; i < (Hundreds / 4) ; i++) {
117                     szHebrew.Append('\x05ea');
118                 }
119
120                 int remains = Hundreds % 4;
121                 if (remains > 0) {
122                     szHebrew.Append((char)((int)'\x05e6' + remains));
123                 }
124             }
125
126             //
127             //  Get the Tens.
128             //
129             Tens = Number / 10;
130             Number %= 10;
131
132             switch (Tens) {
133                 case ( 0 ) :
134                     cTens = '\x0';
135                     break;
136                 case ( 1 ) :
137                     cTens = '\x05d9';          // Hebrew Letter Yod
138                     break;
139                 case ( 2 ) :
140                     cTens = '\x05db';          // Hebrew Letter Kaf
141                     break;
142                 case ( 3 ) :
143                     cTens = '\x05dc';          // Hebrew Letter Lamed
144                     break;
145                 case ( 4 ) :
146                     cTens = '\x05de';          // Hebrew Letter Mem
147                     break;
148                 case ( 5 ) :
149                     cTens = '\x05e0';          // Hebrew Letter Nun
150                     break;
151                 case ( 6 ) :
152                     cTens = '\x05e1';          // Hebrew Letter Samekh
153                     break;
154                 case ( 7 ) :
155                     cTens = '\x05e2';          // Hebrew Letter Ayin
156                     break;
157                 case ( 8 ) :
158                     cTens = '\x05e4';          // Hebrew Letter Pe
159                     break;
160                 case ( 9 ) :
161                     cTens = '\x05e6';          // Hebrew Letter Tsadi
162                     break;
163             }
164
165             //
166             //  Get the Units.
167             //
168             cUnits = (char)(Number > 0 ? ((int)'\x05d0' + Number - 1) : 0);
169
170             if ((cUnits == '\x05d4') &&            // Hebrew Letter He  (5)
171                 (cTens == '\x05d9')) {              // Hebrew Letter Yod (10)
172                 cUnits = '\x05d5';                 // Hebrew Letter Vav (6)
173                 cTens  = '\x05d8';                 // Hebrew Letter Tet (9)
174             }
175
176             if ((cUnits == '\x05d5') &&            // Hebrew Letter Vav (6)
177                 (cTens == '\x05d9')) {               // Hebrew Letter Yod (10)
178                 cUnits = '\x05d6';                 // Hebrew Letter Zayin (7)
179                 cTens  = '\x05d8';                 // Hebrew Letter Tet (9)
180             }
181
182             //
183             //  Copy the appropriate info to the given buffer.
184             //
185
186             if (cTens != '\x0') {
187                 szHebrew.Append(cTens);
188             }
189
190             if (cUnits != '\x0') {
191                 szHebrew.Append(cUnits);
192             }
193
194             if (szHebrew.Length > 1) {
195                 szHebrew.Insert(szHebrew.Length - 1, '"');
196             } else {
197                 szHebrew.Append('\'');
198             }
199
200             //
201             //  Return success.
202             //
203             return (szHebrew.ToString());
204         }
205
206         ////////////////////////////////////////////////////////////////////////////
207         //
208         // Token used to tokenize a Hebrew word into tokens so that we can use in the
209         // state machine.
210         //
211         ////////////////////////////////////////////////////////////////////////////
212         
213         enum HebrewToken {
214             Invalid = -1,
215             Digit400 = 0,
216             Digit200_300 = 1,
217             Digit100 = 2,   
218             Digit10 = 3,    // 10 ~ 90
219             Digit1 = 4,     // 1, 2, 3, 4, 5, 8, 
220             Digit6_7 = 5,
221             Digit7 = 6,
222             Digit9 = 7,
223             SingleQuote = 8,
224             DoubleQuote = 9,
225         };
226
227         ////////////////////////////////////////////////////////////////////////////
228         //
229         // This class is used to map a token into its Hebrew digit value.
230         //
231         ////////////////////////////////////////////////////////////////////////////
232         
233         class HebrewValue {
234             internal HebrewToken token;
235             internal int value;
236             internal HebrewValue(HebrewToken token, int value) {
237                 this.token = token;
238                 this.value = value;
239             }
240         }
241
242         //
243         // Map a Hebrew character from U+05D0 ~ U+05EA to its digit value.
244         // The value is -1 if the Hebrew character does not have a associated value.
245         //
246         static HebrewValue[] HebrewValues = {
247             new HebrewValue(HebrewToken.Digit1, 1) , // '\x05d0
248             new HebrewValue(HebrewToken.Digit1, 2) , // '\x05d1
249             new HebrewValue(HebrewToken.Digit1, 3) , // '\x05d2
250             new HebrewValue(HebrewToken.Digit1, 4) , // '\x05d3
251             new HebrewValue(HebrewToken.Digit1, 5) , // '\x05d4
252             new HebrewValue(HebrewToken.Digit6_7,6) , // '\x05d5
253             new HebrewValue(HebrewToken.Digit6_7,7) , // '\x05d6
254             new HebrewValue(HebrewToken.Digit1, 8) , // '\x05d7
255             new HebrewValue(HebrewToken.Digit9, 9) , // '\x05d8
256             new HebrewValue(HebrewToken.Digit10, 10) , // '\x05d9;          // Hebrew Letter Yod
257             new HebrewValue(HebrewToken.Invalid, -1) , // '\x05da; 
258             new HebrewValue(HebrewToken.Digit10, 20) , // '\x05db;          // Hebrew Letter Kaf
259             new HebrewValue(HebrewToken.Digit10, 30) , // '\x05dc;          // Hebrew Letter Lamed
260             new HebrewValue(HebrewToken.Invalid, -1) , // '\x05dd;
261             new HebrewValue(HebrewToken.Digit10, 40) , // '\x05de;          // Hebrew Letter Mem
262             new HebrewValue(HebrewToken.Invalid, -1) , // '\x05df;
263             new HebrewValue(HebrewToken.Digit10, 50) , // '\x05e0;          // Hebrew Letter Nun
264             new HebrewValue(HebrewToken.Digit10, 60) , // '\x05e1;          // Hebrew Letter Samekh
265             new HebrewValue(HebrewToken.Digit10, 70) , // '\x05e2;          // Hebrew Letter Ayin
266             new HebrewValue(HebrewToken.Invalid, -1) , // '\x05e3;
267             new HebrewValue(HebrewToken.Digit10, 80) , // '\x05e4;          // Hebrew Letter Pe
268             new HebrewValue(HebrewToken.Invalid, -1) , // '\x05e5;
269             new HebrewValue(HebrewToken.Digit10, 90) , // '\x05e6;          // Hebrew Letter Tsadi
270             new HebrewValue(HebrewToken.Digit100, 100) , // '\x05e7;
271             new HebrewValue(HebrewToken.Digit200_300, 200) , // '\x05e8;
272             new HebrewValue(HebrewToken.Digit200_300, 300) , // '\x05e9;
273             new HebrewValue(HebrewToken.Digit400, 400) , // '\x05ea;
274         };
275
276         const int minHebrewNumberCh = 0x05d0;
277         static char maxHebrewNumberCh = (char)(minHebrewNumberCh + HebrewValues.Length - 1);
278
279         ////////////////////////////////////////////////////////////////////////////
280         //
281         // Hebrew number parsing State
282         // The current state and the next token will lead to the next state in the state machine.
283         // DQ = Double Quote
284         //
285         ////////////////////////////////////////////////////////////////////////////
286         
287         internal enum HS {
288             _err = -1,          // an error state
289             Start = 0,
290             S400 = 1,           // a Hebrew digit 400
291             S400_400 = 2,       // Two Hebrew digit 400
292             S400_X00 = 3,       // Two Hebrew digit 400 and followed by 100
293             S400_X0  = 4,       // Hebrew digit 400 and followed by 10 ~ 90
294             X00_DQ = 5,         // A hundred number and followed by a double quote.
295             S400_X00_X0 = 6,
296             X0_DQ = 7,          // A two-digit number and followed by a double quote.
297             X = 8,              // A single digit Hebrew number.
298             X0  = 9,            // A two-digit Hebrew number
299             X00 = 10,           // A three-digit Hebrew number
300             S400_DQ = 11,       // A Hebrew digit 400 and followed by a double quote.
301             S400_400_DQ = 12,
302             S400_400_100 = 13,
303             S9 = 14,            // Hebrew digit 9
304             X00_S9 = 15,        // A hundered number and followed by a digit 9
305             S9_DQ = 16,         // Hebrew digit 9 and followed by a double quote
306             END = 100,          // A terminial state is reached.
307         }
308
309         // 
310         // The state machine for Hebrew number pasing.
311         //
312         readonly static HS[][] NumberPasingState = {
313                            // 400            300/200         100             90~10           8~1      6,       7,       9,          '           "
314     /* 0 */             new HS[] {HS.S400,       HS.X00,         HS.X00,         HS.X0,          HS.X,    HS.X,    HS.X,    HS.S9,      HS._err,    HS._err},
315     /* 1: S400 */       new HS[] {HS.S400_400,   HS.S400_X00,    HS.S400_X00,    HS.S400_X0,     HS._err, HS._err, HS._err, HS.X00_S9  ,HS.END,     HS.S400_DQ},
316     /* 2: S400_400 */   new HS[] {HS._err,       HS._err,        HS.S400_400_100,HS.S400_X0,     HS._err, HS._err, HS._err, HS.X00_S9  ,HS._err,    HS.S400_400_DQ},
317     /* 3: S400_X00 */   new HS[] {HS._err,       HS._err,        HS._err,        HS.S400_X00_X0, HS._err, HS._err, HS._err, HS.X00_S9  ,HS._err,    HS.X00_DQ},
318     /* 4: S400_X0 */    new HS[] {HS._err,       HS._err,        HS._err,        HS._err,        HS._err, HS._err, HS._err, HS._err,    HS._err,    HS.X0_DQ}, 
319     /* 5: X00_DQ */     new HS[] {HS._err,       HS._err,        HS._err,        HS.END,         HS.END,  HS.END,  HS.END,  HS.END,     HS._err,    HS._err},
320     /* 6: S400_X00_X0 */new HS[] {HS._err,       HS._err,        HS._err,        HS._err,        HS._err, HS._err, HS._err, HS._err,    HS._err,    HS.X0_DQ},
321     /* 7: X0_DQ */      new HS[] {HS._err,       HS._err,        HS._err,        HS._err,        HS.END,  HS.END,  HS.END,  HS.END,     HS._err,    HS._err},
322     /* 8: X */          new HS[] {HS._err,       HS._err,        HS._err,        HS._err,        HS._err, HS._err, HS._err, HS._err,    HS.END,     HS._err},
323     /* 9: X0 */         new HS[] {HS._err,       HS._err,        HS._err,        HS._err,        HS._err, HS._err, HS._err, HS._err,    HS.END,     HS.X0_DQ},
324     /* 10: X00 */       new HS[] {HS._err,       HS._err,        HS._err,        HS.S400_X0,     HS._err, HS._err, HS._err, HS.X00_S9,  HS.END,     HS.X00_DQ},
325     /* 11: S400_DQ */   new HS[] {HS.END,        HS.END,         HS.END,         HS.END,         HS.END,  HS.END,  HS.END,  HS.END, HS._err,    HS._err},
326     /* 12: S400_400_DQ*/new HS[] {HS._err,       HS._err,        HS.END,         HS.END,         HS.END,  HS.END,  HS.END,  HS.END, HS._err,    HS._err},
327     /* 13: S400_400_100*/new HS[]{HS._err,       HS._err,        HS._err,        HS.S400_X00_X0, HS._err, HS._err, HS._err, HS.X00_S9,  HS._err,    HS.X00_DQ},
328     /* 14: S9 */        new HS[] {HS._err,       HS._err,        HS._err,        HS._err,        HS._err, HS._err, HS._err, HS._err,HS.END,    HS.S9_DQ},
329     /* 15: X00_S9 */    new HS[] {HS._err,       HS._err,        HS._err,        HS._err,        HS._err, HS._err, HS._err, HS._err,    HS._err,    HS.S9_DQ},
330     /* 16: S9_DQ */     new HS[] {HS._err,       HS._err,        HS._err,        HS._err,        HS._err, HS.END,  HS.END,  HS._err,    HS._err,    HS._err},
331     };
332
333  
334         ////////////////////////////////////////////////////////////////////////
335         //  
336         //  Actions:
337         //      Parse the Hebrew number by passing one character at a time.
338         //      The state between characters are maintained at HebrewNumberPasingContext.
339         //  Returns:
340         //      Return a enum of HebrewNumberParsingState.
341         //          NotHebrewDigit: The specified ch is not a valid Hebrew digit.
342         //          InvalidHebrewNumber: After parsing the specified ch, it will lead into
343         //              an invalid Hebrew number text.
344         //          FoundEndOfHebrewNumber: A terminal state is reached.  This means that
345         //              we find a valid Hebrew number text after the specified ch is parsed.
346         //          ContinueParsing: The specified ch is a valid Hebrew digit, and
347         //              it will lead into a valid state in the state machine, we should
348         //              continue to parse incoming characters.
349         //
350         ////////////////////////////////////////////////////////////////////////
351         
352         internal static HebrewNumberParsingState ParseByChar(char ch, ref HebrewNumberParsingContext context) {
353             HebrewToken token;
354             if (ch == '\'') {
355                 token = HebrewToken.SingleQuote;
356             } else if (ch == '\"') {
357                 token = HebrewToken.DoubleQuote;
358             } else {
359                 int index = (int)ch - minHebrewNumberCh;                
360                 if (index >= 0 && index < HebrewValues.Length ) {
361                     token = HebrewValues[index].token;
362                     if (token == HebrewToken.Invalid) {
363                         return (HebrewNumberParsingState.NotHebrewDigit);
364                     }
365                     context.result += HebrewValues[index].value;
366                 } else {
367                     // Not in valid Hebrew digit range.
368                     return (HebrewNumberParsingState.NotHebrewDigit);
369                 }
370             }
371             context.state = NumberPasingState[(int)context.state][(int)token];
372             if (context.state == HS._err) {
373                 // Invalid Hebrew state.  This indicates an incorrect Hebrew number.
374                 return (HebrewNumberParsingState.InvalidHebrewNumber);
375             }                 
376             if (context.state == HS.END) {
377                 // Reach a terminal state.
378                 return (HebrewNumberParsingState.FoundEndOfHebrewNumber);
379             }
380             // We should continue to parse.
381             return (HebrewNumberParsingState.ContinueParsing);
382         }
383
384         ////////////////////////////////////////////////////////////////////////
385         //
386         // Actions:
387         //  Check if the ch is a valid Hebrew number digit.
388         //  This function will return true if the specified char is a legal Hebrew
389         //  digit character, single quote, or double quote.
390         // Returns:
391         //  true if the specified character is a valid Hebrew number character.
392         //
393         ////////////////////////////////////////////////////////////////////////
394         
395         internal static bool IsDigit(char ch) {
396             if (ch >= minHebrewNumberCh && ch <= maxHebrewNumberCh) {
397                 return (HebrewValues[ch - minHebrewNumberCh].value >= 0);
398             }
399             return (ch == '\'' || ch == '\"');
400         }
401
402     }
403 }