]> git.lizzy.rs Git - rust.git/blob - src/etc/unicode.py
librustc: Don't try to perform the magical
[rust.git] / src / etc / unicode.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2011-2013 The Rust Project Developers. See the COPYRIGHT
4 # file at the top-level directory of this distribution and at
5 # http://rust-lang.org/COPYRIGHT.
6 #
7 # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
8 # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
9 # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
10 # option. This file may not be copied, modified, or distributed
11 # except according to those terms.
12
13 # This digests UnicodeData.txt and DerivedCoreProperties.txt and emits rust
14 # code covering the core properties. Since this is a pretty rare event we
15 # just store this out-of-line and check the unicode.rs file into git.
16 #
17 # The emitted code is "the minimum we think is necessary for libstd", that
18 # is, to support basic operations of the compiler and "most nontrivial rust
19 # programs". It is not meant to be a complete implementation of unicode.
20 # For that we recommend you use a proper binding to libicu.
21
22 import fileinput, re, os, sys, operator
23
24
25 def fetch(f):
26     if not os.path.exists(f):
27         os.system("curl -O http://www.unicode.org/Public/UNIDATA/%s"
28                   % f)
29
30     if not os.path.exists(f):
31         sys.stderr.write("cannot load %s" % f)
32         exit(1)
33
34
35 def load_unicode_data(f):
36     fetch(f)
37     gencats = {}
38     upperlower = {}
39     lowerupper = {}
40     combines = []
41     canon_decomp = {}
42     compat_decomp = {}
43     curr_cat = ""
44     curr_combine = ""
45     c_lo = 0
46     c_hi = 0
47     com_lo = 0
48     com_hi = 0
49
50     for line in fileinput.input(f):
51         fields = line.split(";")
52         if len(fields) != 15:
53             continue
54         [code, name, gencat, combine, bidi,
55          decomp, deci, digit, num, mirror,
56          old, iso, upcase, lowcase, titlecase ] = fields
57
58         code_org = code
59         code     = int(code, 16)
60
61         # generate char to char direct common and simple conversions
62         # uppercase to lowercase
63         if gencat == "Lu" and lowcase != "" and code_org != lowcase:
64             upperlower[code] = int(lowcase, 16)
65
66         # lowercase to uppercase
67         if gencat == "Ll" and upcase != "" and code_org != upcase:
68             lowerupper[code] = int(upcase, 16)
69
70         if decomp != "":
71             if decomp.startswith('<'):
72                 seq = []
73                 for i in decomp.split()[1:]:
74                     seq.append(int(i, 16))
75                 compat_decomp[code] = seq
76             else:
77                 seq = []
78                 for i in decomp.split():
79                     seq.append(int(i, 16))
80                 canon_decomp[code] = seq
81
82         if curr_cat == "":
83             curr_cat = gencat
84             c_lo = code
85             c_hi = code
86
87         if curr_cat == gencat:
88             c_hi = code
89         else:
90             if curr_cat not in gencats:
91                 gencats[curr_cat] = []
92
93             gencats[curr_cat].append((c_lo, c_hi))
94             curr_cat = gencat
95             c_lo = code
96             c_hi = code
97
98         if curr_combine == "":
99             curr_combine = combine
100             com_lo = code
101             com_hi = code
102
103         if curr_combine == combine:
104             com_hi = code
105         else:
106             if curr_combine != "0":
107                 combines.append((com_lo, com_hi, curr_combine))
108             curr_combine = combine
109             com_lo = code
110             com_hi = code
111
112     return (canon_decomp, compat_decomp, gencats, combines, lowerupper, upperlower)
113
114 def load_properties(f, interestingprops):
115     fetch(f)
116     props = {}
117     re1 = re.compile("^([0-9A-F]+) +; (\w+)")
118     re2 = re.compile("^([0-9A-F]+)\.\.([0-9A-F]+) +; (\w+)")
119
120     for line in fileinput.input(f):
121         prop = None
122         d_lo = 0
123         d_hi = 0
124         m = re1.match(line)
125         if m:
126             d_lo = m.group(1)
127             d_hi = m.group(1)
128             prop = m.group(2)
129         else:
130             m = re2.match(line)
131             if m:
132                 d_lo = m.group(1)
133                 d_hi = m.group(2)
134                 prop = m.group(3)
135             else:
136                 continue
137         if prop not in interestingprops:
138             continue
139         d_lo = int(d_lo, 16)
140         d_hi = int(d_hi, 16)
141         if prop not in props:
142             props[prop] = []
143         props[prop].append((d_lo, d_hi))
144     return props
145
146 def escape_char(c):
147     if c <= 0xff:
148         return "'\\x%2.2x'" % c
149     if c <= 0xffff:
150         return "'\\u%4.4x'" % c
151     return "'\\U%8.8x'" % c
152
153 def ch_prefix(ix):
154     if ix == 0:
155         return "        "
156     if ix % 2 == 0:
157         return ",\n        "
158     else:
159         return ", "
160
161 def emit_bsearch_range_table(f):
162     f.write("""
163 fn bsearch_range_table(c: char, r: &'static [(char,char)]) -> bool {
164     use cmp::{Equal, Less, Greater};
165     use slice::ImmutableVector;
166     use option::None;
167     r.bsearch(|&(lo,hi)| {
168         if lo <= c && c <= hi { Equal }
169         else if hi < c { Less }
170         else { Greater }
171     }) != None
172 }\n
173 """);
174
175 def emit_property_module(f, mod, tbl):
176     f.write("pub mod %s {\n" % mod)
177     keys = tbl.keys()
178     keys.sort()
179
180     for cat in keys:
181         if cat not in ["Nd", "Nl", "No", "Cc",
182             "XID_Start", "XID_Continue", "Alphabetic",
183             "Lowercase", "Uppercase", "White_Space"]:
184             continue
185         f.write("    static %s_table : &'static [(char,char)] = &[\n" % cat)
186         ix = 0
187         for pair in tbl[cat]:
188             f.write(ch_prefix(ix))
189             f.write("(%s, %s)" % (escape_char(pair[0]), escape_char(pair[1])))
190             ix += 1
191         f.write("\n    ];\n\n")
192
193         f.write("    pub fn %s(c: char) -> bool {\n" % cat)
194         f.write("        super::bsearch_range_table(c, %s_table)\n" % cat)
195         f.write("    }\n\n")
196     f.write("}\n\n")
197
198
199 def emit_conversions_module(f, lowerupper, upperlower):
200     f.write("pub mod conversions {")
201     f.write("""
202     use cmp::{Equal, Less, Greater};
203     use slice::ImmutableVector;
204     use tuple::Tuple2;
205     use option::{Option, Some, None};
206
207     pub fn to_lower(c: char) -> char {
208         match bsearch_case_table(c, LuLl_table) {
209           None        => c,
210           Some(index) => LuLl_table[index].val1()
211         }
212     }
213
214     pub fn to_upper(c: char) -> char {
215         match bsearch_case_table(c, LlLu_table) {
216             None        => c,
217             Some(index) => LlLu_table[index].val1()
218         }
219     }
220
221     fn bsearch_case_table(c: char, table: &'static [(char, char)]) -> Option<uint> {
222         table.bsearch(|&(key, _)| {
223             if c == key { Equal }
224             else if key < c { Less }
225             else { Greater }
226         })
227     }
228
229 """);
230     emit_caseconversion_table(f, "LuLl", upperlower)
231     emit_caseconversion_table(f, "LlLu", lowerupper)
232     f.write("}\n")
233
234 def emit_caseconversion_table(f, name, table):
235     f.write("    static %s_table : &'static [(char, char)] = &[\n" % name)
236     sorted_table = sorted(table.iteritems(), key=operator.itemgetter(0))
237     ix = 0
238     for key, value in sorted_table:
239         f.write(ch_prefix(ix))
240         f.write("(%s, %s)" % (escape_char(key), escape_char(value)))
241         ix += 1
242     f.write("\n    ];\n\n")
243
244 def format_table_content(f, content, indent):
245     line = " "*indent
246     first = True
247     for chunk in content.split(","):
248         if len(line) + len(chunk) < 98:
249             if first:
250                 line += chunk
251             else:
252                 line += ", " + chunk
253             first = False
254         else:
255             f.write(line + ",\n")
256             line = " "*indent + chunk
257     f.write(line)
258
259 def emit_core_norm_module(f, canon, compat):
260     canon_keys = canon.keys()
261     canon_keys.sort()
262
263     compat_keys = compat.keys()
264     compat_keys.sort()
265     f.write("pub mod normalization {\n");
266     f.write("    use option::Option;\n");
267     f.write("    use option::{Some, None};\n");
268     f.write("    use slice::ImmutableVector;\n");
269     f.write("""
270     fn bsearch_table(c: char, r: &'static [(char, &'static [char])]) -> Option<&'static [char]> {
271         use cmp::{Equal, Less, Greater};
272         match r.bsearch(|&(val, _)| {
273             if c == val { Equal }
274             else if val < c { Less }
275             else { Greater }
276         }) {
277             Some(idx) => {
278                 let (_, result) = r[idx];
279                 Some(result)
280             }
281             None => None
282         }
283     }\n\n
284 """)
285
286     f.write("    // Canonical decompositions\n")
287     f.write("    static canonical_table : &'static [(char, &'static [char])] = &[\n")
288     data = ""
289     first = True
290     for char in canon_keys:
291         if not first:
292             data += ","
293         first = False
294         data += "(%s,&[" % escape_char(char)
295         first2 = True
296         for d in canon[char]:
297             if not first2:
298                 data += ","
299             first2 = False
300             data += escape_char(d)
301         data += "])"
302     format_table_content(f, data, 8)
303     f.write("\n    ];\n\n")
304
305     f.write("    // Compatibility decompositions\n")
306     f.write("    static compatibility_table : &'static [(char, &'static [char])] = &[\n")
307     data = ""
308     first = True
309     for char in compat_keys:
310         if not first:
311             data += ","
312         first = False
313         data += "(%s,&[" % escape_char(char)
314         first2 = True
315         for d in compat[char]:
316             if not first2:
317                 data += ","
318             first2 = False
319             data += escape_char(d)
320         data += "])"
321     format_table_content(f, data, 8)
322     f.write("\n    ];\n\n")
323
324     f.write("""
325     pub fn decompose_canonical(c: char, i: |char|) { d(c, i, false); }
326
327     pub fn decompose_compatible(c: char, i: |char|) { d(c, i, true); }
328
329     fn d(c: char, i: |char|, k: bool) {
330         use iter::Iterator;
331
332         // 7-bit ASCII never decomposes
333         if c <= '\\x7f' { i(c); return; }
334
335         // Perform decomposition for Hangul
336         if (c as u32) >= S_BASE && (c as u32) < (S_BASE + S_COUNT) {
337             decompose_hangul(c, i);
338             return;
339         }
340
341         // First check the canonical decompositions
342         match bsearch_table(c, canonical_table) {
343             Some(canon) => {
344                 for x in canon.iter() {
345                     d(*x, |b| i(b), k);
346                 }
347                 return;
348             }
349             None => ()
350         }
351
352         // Bottom out if we're not doing compat.
353         if !k { i(c); return; }
354
355         // Then check the compatibility decompositions
356         match bsearch_table(c, compatibility_table) {
357             Some(compat) => {
358                 for x in compat.iter() {
359                     d(*x, |b| i(b), k);
360                 }
361                 return;
362             }
363             None => ()
364         }
365
366         // Finally bottom out.
367         i(c);
368     }
369
370     // Constants from Unicode 6.2.0 Section 3.12 Conjoining Jamo Behavior
371     static S_BASE: u32 = 0xAC00;
372     static L_BASE: u32 = 0x1100;
373     static V_BASE: u32 = 0x1161;
374     static T_BASE: u32 = 0x11A7;
375     static L_COUNT: u32 = 19;
376     static V_COUNT: u32 = 21;
377     static T_COUNT: u32 = 28;
378     static N_COUNT: u32 = (V_COUNT * T_COUNT);
379     static S_COUNT: u32 = (L_COUNT * N_COUNT);
380
381     // Decompose a precomposed Hangul syllable
382     fn decompose_hangul(s: char, f: |char|) {
383         use cast::transmute;
384
385         let si = s as u32 - S_BASE;
386
387         let li = si / N_COUNT;
388         unsafe {
389             f(transmute(L_BASE + li));
390
391             let vi = (si % N_COUNT) / T_COUNT;
392             f(transmute(V_BASE + vi));
393
394             let ti = si % T_COUNT;
395             if ti > 0 {
396                 f(transmute(T_BASE + ti));
397             }
398         }
399     }
400 }
401
402 """)
403
404 def emit_std_norm_module(f, combine):
405     f.write("pub mod normalization {\n");
406     f.write("    use option::{Some, None};\n");
407     f.write("    use slice::ImmutableVector;\n");
408
409     f.write("""
410     fn bsearch_range_value_table(c: char, r: &'static [(char, char, u8)]) -> u8 {
411         use cmp::{Equal, Less, Greater};
412         match r.bsearch(|&(lo, hi, _)| {
413             if lo <= c && c <= hi { Equal }
414             else if hi < c { Less }
415             else { Greater }
416         }) {
417             Some(idx) => {
418                 let (_, _, result) = r[idx];
419                 result
420             }
421             None => 0
422         }
423     }\n\n
424 """)
425
426     f.write("    static combining_class_table : &'static [(char, char, u8)] = &[\n")
427     ix = 0
428     for pair in combine:
429         f.write(ch_prefix(ix))
430         f.write("(%s, %s, %s)" % (escape_char(pair[0]), escape_char(pair[1]), pair[2]))
431         ix += 1
432     f.write("\n    ];\n\n")
433
434     f.write("    pub fn canonical_combining_class(c: char) -> u8 {\n"
435         + "        bsearch_range_value_table(c, combining_class_table)\n"
436         + "    }\n")
437     f.write("}\n")
438
439
440 preamble = '''// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
441 // file at the top-level directory of this distribution and at
442 // http://rust-lang.org/COPYRIGHT.
443 //
444 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
445 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
446 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
447 // option. This file may not be copied, modified, or distributed
448 // except according to those terms.
449
450 // NOTE: The following code was generated by "src/etc/unicode.py", do not edit directly
451
452 #![allow(missing_doc, non_uppercase_statics)]
453
454 '''
455
456 (canon_decomp, compat_decomp, gencats,
457  combines, lowerupper, upperlower) = load_unicode_data("UnicodeData.txt")
458
459 def gen_core_unicode():
460     r = "core_unicode.rs"
461     if os.path.exists(r):
462         os.remove(r);
463     with open(r, "w") as rf:
464         # Preamble
465         rf.write(preamble)
466
467         emit_bsearch_range_table(rf);
468         emit_property_module(rf, "general_category", gencats)
469
470         emit_core_norm_module(rf, canon_decomp, compat_decomp)
471
472         derived = load_properties("DerivedCoreProperties.txt",
473                 ["XID_Start", "XID_Continue", "Alphabetic", "Lowercase", "Uppercase"])
474
475         emit_property_module(rf, "derived_property", derived)
476
477         props = load_properties("PropList.txt", ["White_Space"])
478         emit_property_module(rf, "property", props)
479         emit_conversions_module(rf, lowerupper, upperlower)
480
481 def gen_std_unicode():
482     r = "std_unicode.rs"
483     if os.path.exists(r):
484         os.remove(r);
485     with open(r, "w") as rf:
486         # Preamble
487         rf.write(preamble)
488         emit_std_norm_module(rf, combines)
489
490 gen_core_unicode()
491 gen_std_unicode()