- Make the NDK compatible with the MSDDK again.
[reactos.git] / reactos / tools / wmc / mcy.y
1 /*
2 * Wine Message Compiler parser
3 *
4 * Copyright 2000 Bertho A. Stultiens (BS)
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 * NOTES:
21 *
22 * The basic grammar of the file is yet another example of, humpf,
23 * design. There is is mix of context-insensitive and -sentitive
24 * stuff, which makes it rather complicated.
25 * The header definitions are all context-insensitive because they have
26 * delimited arguments, whereas the message headers are (semi-) context-
27 * sensitive and the messages themselves are, well, RFC82[12] delimited.
28 * This mixture seems to originate from the time that ms and ibm were
29 * good friends and developing os/2 according to the "compatibility"
30 * switch and reading some comments here and there.
31 *
32 * I'll ignore most of the complications and concentrate on the concept
33 * which allows me to use yacc. Basically, everything is context-
34 * insensitive now, with the exception of the message-text itself and
35 * the preceding language declaration.
36 *
37 */
38
39 %{
40
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <assert.h>
44
45 #include "config.h"
46
47 #include "utils.h"
48 #include "wmc.h"
49 #include "lang.h"
50
51 static const char err_syntax[] = "Syntax error";
52 static const char err_number[] = "Number expected";
53 static const char err_ident[] = "Identifier expected";
54 static const char err_assign[] = "'=' expected";
55 static const char err_popen[] = "'(' expected";
56 static const char err_pclose[] = "')' expected";
57 static const char err_colon[] = "':' expected";
58 static const char err_msg[] = "Message expected";
59
60 /* Scanner switches */
61 int want_nl = 0; /* Request next newlinw */
62 int want_line = 0; /* Request next complete line */
63 int want_file = 0; /* Request next ident as filename */
64
65 node_t *nodehead = NULL; /* The list of all parsed elements */
66 static node_t *nodetail = NULL;
67 lan_blk_t *lanblockhead; /* List of parsed elements transposed */
68
69 static int base = 16; /* Current printout base to use (8, 10 or 16) */
70 static WCHAR *cast = NULL; /* Current typecast to use */
71
72 static int last_id = 0; /* The last message ID parsed */
73 static int last_sev = 0; /* Last severity code parsed */
74 static int last_fac = 0; /* Last facility code parsed */
75 static WCHAR *last_sym = NULL;/* Last alias symbol parsed */
76 static int have_sev; /* Set if severity parsed for current message */
77 static int have_fac; /* Set if facility parsed for current message */
78 static int have_sym; /* Set is symbol parsed for current message */
79
80 static cp_xlat_t *cpxlattab = NULL; /* Codepage translation table */
81 static int ncpxlattab = 0;
82
83 /* Prototypes */
84 static WCHAR *merge(WCHAR *s1, WCHAR *s2);
85 static lanmsg_t *new_lanmsg(lan_cp_t *lcp, WCHAR *msg);
86 static msg_t *add_lanmsg(msg_t *msg, lanmsg_t *lanmsg);
87 static msg_t *complete_msg(msg_t *msg, int id);
88 static void add_node(node_e type, void *p);
89 static void do_add_token(tok_e type, token_t *tok, const char *code);
90 static void test_id(int id);
91 static int check_languages(node_t *head);
92 static lan_blk_t *block_messages(node_t *head);
93 static void add_cpxlat(int lan, int cpin, int cpout);
94 static cp_xlat_t *find_cpxlat(int lan);
95
96 %}
97
98
99 %union {
100 WCHAR *str;
101 unsigned num;
102 token_t *tok;
103 lanmsg_t *lmp;
104 msg_t *msg;
105 lan_cp_t lcp;
106 }
107
108
109 %token tSEVNAMES tFACNAMES tLANNAMES tBASE tCODEPAGE
110 %token tTYPEDEF tNL tSYMNAME tMSGEND
111 %token tSEVERITY tFACILITY tLANGUAGE tMSGID
112 %token <str> tIDENT tLINE tFILE tCOMMENT
113 %token <num> tNUMBER
114 %token <tok> tTOKEN
115
116 %type <str> alias lines
117 %type <num> optcp id msgid clan
118 %type <tok> token
119 %type <lmp> body
120 %type <msg> bodies msg
121 %type <lcp> lang
122
123 %%
124 file : items {
125 if(!check_languages(nodehead))
126 xyyerror("No messages defined");
127 lanblockhead = block_messages(nodehead);
128 }
129 ;
130
131 items : decl
132 | items decl
133 ;
134
135 decl : global
136 | msg { add_node(nd_msg, $1); }
137 | tCOMMENT { add_node(nd_comment, $1); }
138 | error { xyyerror(err_syntax); /* `Catch all' error */ }
139 ;
140
141 global : tSEVNAMES '=' '(' smaps ')'
142 | tSEVNAMES '=' '(' smaps error { xyyerror(err_pclose); }
143 | tSEVNAMES '=' error { xyyerror(err_popen); }
144 | tSEVNAMES error { xyyerror(err_assign); }
145 | tFACNAMES '=' '(' fmaps ')'
146 | tFACNAMES '=' '(' fmaps error { xyyerror(err_pclose); }
147 | tFACNAMES '=' error { xyyerror(err_popen); }
148 | tFACNAMES error { xyyerror(err_assign); }
149 | tLANNAMES '=' '(' lmaps ')'
150 | tLANNAMES '=' '(' lmaps error { xyyerror(err_pclose); }
151 | tLANNAMES '=' error { xyyerror(err_popen); }
152 | tLANNAMES error { xyyerror(err_assign); }
153 | tCODEPAGE '=' '(' cmaps ')'
154 | tCODEPAGE '=' '(' cmaps error { xyyerror(err_pclose); }
155 | tCODEPAGE '=' error { xyyerror(err_popen); }
156 | tCODEPAGE error { xyyerror(err_assign); }
157 | tTYPEDEF '=' tIDENT { cast = $3; }
158 | tTYPEDEF '=' error { xyyerror(err_number); }
159 | tTYPEDEF error { xyyerror(err_assign); }
160 | tBASE '=' tNUMBER {
161 switch(base)
162 {
163 case 8:
164 case 10:
165 case 16:
166 base = $3;
167 break;
168 default:
169 xyyerror("Numberbase must be 8, 10 or 16");
170 }
171 }
172 | tBASE '=' error { xyyerror(err_number); }
173 | tBASE error { xyyerror(err_assign); }
174 ;
175
176 /*----------------------------------------------------------------------
177 * SeverityNames mapping
178 */
179 smaps : smap
180 | smaps smap
181 | error { xyyerror(err_ident); }
182 ;
183
184 smap : token '=' tNUMBER alias {
185 $1->token = $3;
186 $1->alias = $4;
187 if($3 & (~0x3))
188 xyyerror("Severity value out of range (0x%08x > 0x3)", $3);
189 do_add_token(tok_severity, $1, "severity");
190 }
191 | token '=' error { xyyerror(err_number); }
192 | token error { xyyerror(err_assign); }
193 ;
194
195 /*----------------------------------------------------------------------
196 * FacilityNames mapping
197 */
198 fmaps : fmap
199 | fmaps fmap
200 | error { xyyerror(err_ident); }
201 ;
202
203 fmap : token '=' tNUMBER alias {
204 $1->token = $3;
205 $1->alias = $4;
206 if($3 & (~0xfff))
207 xyyerror("Facility value out of range (0x%08x > 0xfff)", $3);
208 do_add_token(tok_facility, $1, "facility");
209 }
210 | token '=' error { xyyerror(err_number); }
211 | token error { xyyerror(err_assign); }
212 ;
213
214 alias : /* Empty */ { $$ = NULL; }
215 | ':' tIDENT { $$ = $2; }
216 | ':' error { xyyerror(err_ident); }
217 ;
218
219 /*----------------------------------------------------------------------
220 * LanguageNames mapping
221 */
222 lmaps : lmap
223 | lmaps lmap
224 | error { xyyerror(err_ident); }
225 ;
226
227 lmap : token '=' tNUMBER setfile ':' tFILE optcp {
228 $1->token = $3;
229 $1->alias = $6;
230 $1->codepage = $7;
231 do_add_token(tok_language, $1, "language");
232 if(!find_language($1->token) && !find_cpxlat($1->token))
233 yywarning("Language 0x%x not built-in, using codepage %d; use explicit codepage to override", $1->token, WMC_DEFAULT_CODEPAGE);
234 }
235 | token '=' tNUMBER setfile ':' error { xyyerror("Filename expected"); }
236 | token '=' tNUMBER error { xyyerror(err_colon); }
237 | token '=' error { xyyerror(err_number); }
238 | token error { xyyerror(err_assign); }
239 ;
240
241 optcp : /* Empty */ { $$ = 0; }
242 | ':' tNUMBER { $$ = $2; }
243 | ':' error { xyyerror("Codepage-number expected"); }
244 ;
245
246 /*----------------------------------------------------------------------
247 * Codepages mapping
248 */
249 cmaps : cmap
250 | cmaps cmap
251 | error { xyyerror(err_ident); }
252 ;
253
254 cmap : clan '=' tNUMBER ':' tNUMBER {
255 static const char err_nocp[] = "Codepage %d not builtin; cannot convert";
256 if(find_cpxlat($1))
257 xyyerror("Codepage translation already defined for language 0x%x", $1);
258 if($3 && !find_codepage($3))
259 xyyerror(err_nocp, $3);
260 if($5 && !find_codepage($5))
261 xyyerror(err_nocp, $5);
262 add_cpxlat($1, $3, $5);
263 }
264 | clan '=' tNUMBER ':' error { xyyerror(err_number); }
265 | clan '=' tNUMBER error { xyyerror(err_colon); }
266 | clan '=' error { xyyerror(err_number); }
267 | clan error { xyyerror(err_assign); }
268 ;
269
270 clan : tNUMBER { $$ = $1; }
271 | tTOKEN {
272 if($1->type != tok_language)
273 xyyerror("Language name or code expected");
274 $$ = $1->token;
275 }
276 ;
277
278 /*----------------------------------------------------------------------
279 * Message-definition parsing
280 */
281 msg : msgid sevfacsym { test_id($1); } bodies { $$ = complete_msg($4, $1); }
282 ;
283
284 msgid : tMSGID '=' id {
285 if($3 & (~0xffff))
286 xyyerror("Message ID value out of range (0x%08x > 0xffff)", $3);
287 $$ = $3;
288 }
289 | tMSGID error { xyyerror(err_assign); }
290 ;
291
292 id : /* Empty */ { $$ = ++last_id; }
293 | tNUMBER { $$ = last_id = $1; }
294 | '+' tNUMBER { $$ = last_id += $2; }
295 | '+' error { xyyerror(err_number); }
296 ;
297
298 sevfacsym: /* Empty */ { have_sev = have_fac = have_sym = 0; }
299 | sevfacsym sev { if(have_sev) xyyerror("Severity already defined"); have_sev = 1; }
300 | sevfacsym fac { if(have_fac) xyyerror("Facility already defined"); have_fac = 1; }
301 | sevfacsym sym { if(have_sym) xyyerror("Symbolname already defined"); have_sym = 1; }
302 ;
303
304 sym : tSYMNAME '=' tIDENT { last_sym = $3; }
305 | tSYMNAME '=' error { xyyerror(err_ident); }
306 | tSYMNAME error { xyyerror(err_assign); }
307 ;
308
309 sev : tSEVERITY '=' token {
310 token_t *tok = lookup_token($3->name);
311 if(!tok)
312 xyyerror("Undefined severityname");
313 if(tok->type != tok_severity)
314 xyyerror("Identifier is not of class 'severity'");
315 last_sev = tok->token;
316 }
317 | tSEVERITY '=' error { xyyerror(err_ident); }
318 | tSEVERITY error { xyyerror(err_assign); }
319 ;
320
321 fac : tFACILITY '=' token {
322 token_t *tok = lookup_token($3->name);
323 if(!tok)
324 xyyerror("Undefined facilityname");
325 if(tok->type != tok_facility)
326 xyyerror("Identifier is not of class 'facility'");
327 last_fac = tok->token;
328 }
329 | tFACILITY '=' error { xyyerror(err_ident); }
330 | tFACILITY error { xyyerror(err_assign); }
331 ;
332
333 /*----------------------------------------------------------------------
334 * Message-text parsing
335 */
336 bodies : body { $$ = add_lanmsg(NULL, $1); }
337 | bodies body { $$ = add_lanmsg($1, $2); }
338 | error { xyyerror("'Language=...' (start of message text-definition) expected"); }
339 ;
340
341 body : lang setline lines tMSGEND { $$ = new_lanmsg(&$1, $3); }
342 ;
343
344 /*
345 * The newline is to be able to set the codepage
346 * to the language based codepage for the next
347 * message to be parsed.
348 */
349 lang : tLANGUAGE setnl '=' token tNL {
350 token_t *tok = lookup_token($4->name);
351 cp_xlat_t *cpx;
352 if(!tok)
353 xyyerror("Undefined language");
354 if(tok->type != tok_language)
355 xyyerror("Identifier is not of class 'language'");
356 if((cpx = find_cpxlat(tok->token)))
357 {
358 set_codepage($$.codepage = cpx->cpin);
359 }
360 else if(!tok->codepage)
361 {
362 const language_t *lan = find_language(tok->token);
363 if(!lan)
364 {
365 /* Just set default; warning was given while parsing languagenames */
366 set_codepage($$.codepage = WMC_DEFAULT_CODEPAGE);
367 }
368 else
369 {
370 /* The default seems to be to use the DOS codepage... */
371 set_codepage($$.codepage = lan->doscp);
372 }
373 }
374 else
375 set_codepage($$.codepage = tok->codepage);
376 $$.language = tok->token;
377 }
378 | tLANGUAGE setnl '=' token error { xyyerror("Missing newline"); }
379 | tLANGUAGE setnl '=' error { xyyerror(err_ident); }
380 | tLANGUAGE error { xyyerror(err_assign); }
381 ;
382
383 lines : tLINE { $$ = $1; }
384 | lines tLINE { $$ = merge($1, $2); }
385 | error { xyyerror(err_msg); }
386 | lines error { xyyerror(err_msg); }
387 ;
388
389 /*----------------------------------------------------------------------
390 * Helper rules
391 */
392 token : tIDENT { $$ = xmalloc(sizeof(token_t)); $$->name = $1; }
393 | tTOKEN { $$ = $1; }
394 ;
395
396 setnl : /* Empty */ { want_nl = 1; }
397 ;
398
399 setline : /* Empty */ { want_line = 1; }
400 ;
401
402 setfile : /* Empty */ { want_file = 1; }
403 ;
404
405 %%
406
407 static WCHAR *merge(WCHAR *s1, WCHAR *s2)
408 {
409 int l1 = unistrlen(s1);
410 int l2 = unistrlen(s2);
411 s1 = xrealloc(s1, (l1 + l2 + 1) * sizeof(*s1));
412 unistrcpy(s1+l1, s2);
413 free(s2);
414 return s1;
415 }
416
417 static void do_add_token(tok_e type, token_t *tok, const char *code)
418 {
419 token_t *tp = lookup_token(tok->name);
420 if(tp)
421 {
422 if(tok->type != type)
423 yywarning("Type change in token");
424 if(tp != tok)
425 xyyerror("Overlapping token not the same");
426 /* else its already defined and changed */
427 if(tok->fixed)
428 xyyerror("Redefinition of %s", code);
429 tok->fixed = 1;
430 }
431 else
432 {
433 add_token(type, tok->name, tok->token, tok->codepage, tok->alias, 1);
434 free(tok);
435 }
436 }
437
438 static lanmsg_t *new_lanmsg(lan_cp_t *lcp, WCHAR *msg)
439 {
440 lanmsg_t *lmp = (lanmsg_t *)xmalloc(sizeof(lanmsg_t));
441 lmp->lan = lcp->language;
442 lmp->cp = lcp->codepage;
443 lmp->msg = msg;
444 lmp->len = unistrlen(msg) + 1; /* Include termination */
445 if(lmp->len > 4096)
446 yywarning("Message exceptionally long; might be a missing termination");
447 return lmp;
448 }
449
450 static msg_t *add_lanmsg(msg_t *msg, lanmsg_t *lanmsg)
451 {
452 int i;
453 if(!msg)
454 msg = xmalloc(sizeof(msg_t));
455 msg->msgs = xrealloc(msg->msgs, (msg->nmsgs+1) * sizeof(*(msg->msgs)));
456 msg->msgs[msg->nmsgs] = lanmsg;
457 msg->nmsgs++;
458 for(i = 0; i < msg->nmsgs-1; i++)
459 {
460 if(msg->msgs[i]->lan == lanmsg->lan)
461 xyyerror("Message for language 0x%x already defined", lanmsg->lan);
462 }
463 return msg;
464 }
465
466 static int sort_lanmsg(const void *p1, const void *p2)
467 {
468 return (*(lanmsg_t **)p1)->lan - (*(lanmsg_t **)p2)->lan;
469 }
470
471 static msg_t *complete_msg(msg_t *mp, int id)
472 {
473 assert(mp != NULL);
474 mp->id = id;
475 if(have_sym)
476 mp->sym = last_sym;
477 else
478 xyyerror("No symbolic name defined for message id %d", id);
479 mp->sev = last_sev;
480 mp->fac = last_fac;
481 qsort(mp->msgs, mp->nmsgs, sizeof(*(mp->msgs)), sort_lanmsg);
482 mp->realid = id | (last_sev << 30) | (last_fac << 16);
483 if(custombit)
484 mp->realid |= 1 << 29;
485 mp->base = base;
486 mp->cast = cast;
487 return mp;
488 }
489
490 static void add_node(node_e type, void *p)
491 {
492 node_t *ndp = (node_t *)xmalloc(sizeof(node_t));
493 ndp->type = type;
494 ndp->u.all = p;
495
496 if(nodetail)
497 {
498 ndp->prev = nodetail;
499 nodetail->next = ndp;
500 nodetail = ndp;
501 }
502 else
503 {
504 nodehead = nodetail = ndp;
505 }
506 }
507
508 static void test_id(int id)
509 {
510 node_t *ndp;
511 for(ndp = nodehead; ndp; ndp = ndp->next)
512 {
513 if(ndp->type != nd_msg)
514 continue;
515 if(ndp->u.msg->id == id && ndp->u.msg->sev == last_sev && ndp->u.msg->fac == last_fac)
516 xyyerror("MessageId %d with facility 0x%x and severity 0x%x already defined", id, last_fac, last_sev);
517 }
518 }
519
520 static int check_languages(node_t *head)
521 {
522 static char err_missing[] = "Missing definition for language 0x%x; MessageID %d, facility 0x%x, severity 0x%x";
523 node_t *ndp;
524 int nm = 0;
525 msg_t *msg = NULL;
526
527 for(ndp = head; ndp; ndp = ndp->next)
528 {
529 if(ndp->type != nd_msg)
530 continue;
531 if(!nm)
532 {
533 msg = ndp->u.msg;
534 }
535 else
536 {
537 int i;
538 msg_t *m1;
539 msg_t *m2;
540 if(ndp->u.msg->nmsgs > msg->nmsgs)
541 {
542 m1 = ndp->u.msg;
543 m2 = msg;
544 }
545 else
546 {
547 m1 = msg;
548 m2 = ndp->u.msg;
549 }
550
551 for(i = 0; i < m1->nmsgs; i++)
552 {
553 if(i > m2->nmsgs)
554 error(err_missing, m1->msgs[i]->lan, m2->id, m2->fac, m2->sev);
555 else if(m1->msgs[i]->lan < m2->msgs[i]->lan)
556 error(err_missing, m1->msgs[i]->lan, m2->id, m2->fac, m2->sev);
557 else if(m1->msgs[i]->lan > m2->msgs[i]->lan)
558 error(err_missing, m2->msgs[i]->lan, m1->id, m1->fac, m1->sev);
559 }
560 }
561 nm++;
562 }
563 return nm;
564 }
565
566 #define MSGRID(x) ((*(msg_t **)(x))->realid)
567 static int sort_msg(const void *p1, const void *p2)
568 {
569 return MSGRID(p1) > MSGRID(p2) ? 1 : (MSGRID(p1) == MSGRID(p2) ? 0 : -1);
570 /* return (*(msg_t **)p1)->realid - (*(msg_t **)p1)->realid; */
571 }
572
573 /*
574 * block_messages() basically transposes the messages
575 * from ID/language based list to a language/ID
576 * based list.
577 */
578 static lan_blk_t *block_messages(node_t *head)
579 {
580 lan_blk_t *lbp;
581 lan_blk_t *lblktail = NULL;
582 lan_blk_t *lblkhead = NULL;
583 msg_t **msgtab = NULL;
584 node_t *ndp;
585 int nmsg = 0;
586 int i;
587 int nl;
588 int factor = unicodeout ? 2 : 1;
589
590 for(ndp = head; ndp; ndp = ndp->next)
591 {
592 if(ndp->type != nd_msg)
593 continue;
594 msgtab = xrealloc(msgtab, (nmsg+1) * sizeof(*msgtab));
595 msgtab[nmsg++] = ndp->u.msg;
596 }
597
598 assert(nmsg != 0);
599 qsort(msgtab, nmsg, sizeof(*msgtab), sort_msg);
600
601 for(nl = 0; nl < msgtab[0]->nmsgs; nl++) /* This should be equal for all after check_languages() */
602 {
603 lbp = xmalloc(sizeof(lan_blk_t));
604
605 if(!lblktail)
606 {
607 lblkhead = lblktail = lbp;
608 }
609 else
610 {
611 lblktail->next = lbp;
612 lbp->prev = lblktail;
613 lblktail = lbp;
614 }
615 lbp->nblk = 1;
616 lbp->blks = xmalloc(sizeof(*lbp->blks));
617 lbp->blks[0].idlo = msgtab[0]->realid;
618 lbp->blks[0].idhi = msgtab[0]->realid;
619 /* The plus 4 is the entry header; (+3)&~3 is DWORD alignment */
620 lbp->blks[0].size = ((factor * msgtab[0]->msgs[nl]->len + 3) & ~3) + 4;
621 lbp->blks[0].msgs = xmalloc(sizeof(*lbp->blks[0].msgs));
622 lbp->blks[0].nmsg = 1;
623 lbp->blks[0].msgs[0] = msgtab[0]->msgs[nl];
624 lbp->lan = msgtab[0]->msgs[nl]->lan;
625
626 for(i = 1; i < nmsg; i++)
627 {
628 block_t *blk = &(lbp->blks[lbp->nblk-1]);
629 if(msgtab[i]->realid == blk->idhi+1)
630 {
631 blk->size += ((factor * msgtab[i]->msgs[nl]->len + 3) & ~3) + 4;
632 blk->idhi++;
633 blk->msgs = xrealloc(blk->msgs, (blk->nmsg+1) * sizeof(*blk->msgs));
634 blk->msgs[blk->nmsg++] = msgtab[i]->msgs[nl];
635 }
636 else
637 {
638 lbp->nblk++;
639 lbp->blks = xrealloc(lbp->blks, lbp->nblk * sizeof(*lbp->blks));
640 blk = &(lbp->blks[lbp->nblk-1]);
641 blk->idlo = msgtab[i]->realid;
642 blk->idhi = msgtab[i]->realid;
643 blk->size = ((factor * msgtab[i]->msgs[nl]->len + 3) & ~3) + 4;
644 blk->msgs = xmalloc(sizeof(*blk->msgs));
645 blk->nmsg = 1;
646 blk->msgs[0] = msgtab[i]->msgs[nl];
647 }
648 }
649 }
650 free(msgtab);
651 return lblkhead;
652 }
653
654 static int sc_xlat(const void *p1, const void *p2)
655 {
656 return ((cp_xlat_t *)p1)->lan - ((cp_xlat_t *)p2)->lan;
657 }
658
659 static void add_cpxlat(int lan, int cpin, int cpout)
660 {
661 cpxlattab = xrealloc(cpxlattab, (ncpxlattab+1) * sizeof(*cpxlattab));
662 cpxlattab[ncpxlattab].lan = lan;
663 cpxlattab[ncpxlattab].cpin = cpin;
664 cpxlattab[ncpxlattab].cpout = cpout;
665 ncpxlattab++;
666 qsort(cpxlattab, ncpxlattab, sizeof(*cpxlattab), sc_xlat);
667 }
668
669 static cp_xlat_t *find_cpxlat(int lan)
670 {
671 cp_xlat_t t;
672
673 if(!cpxlattab) return NULL;
674
675 t.lan = lan;
676 return (cp_xlat_t *)bsearch(&t, cpxlattab, ncpxlattab, sizeof(*cpxlattab), sc_xlat);
677 }
678