Initial import to Tizen
[profile/ivi/sphinxbase.git] / src / libsphinxbase / lm / jsgf.c
1 /* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* ====================================================================
3  * Copyright (c) 2007 Carnegie Mellon University.  All rights
4  * reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer. 
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  *
18  * This work was supported in part by funding from the Defense Advanced 
19  * Research Projects Agency and the National Science Foundation of the 
20  * United States of America, and the CMU Sphinx Speech Consortium.
21  *
22  * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND 
23  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
24  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
26  * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
28  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
30  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
31  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
32  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33  *
34  * ====================================================================
35  *
36  */
37
38 #include <string.h>
39 #include <assert.h>
40
41 #include "sphinxbase/ckd_alloc.h"
42 #include "sphinxbase/strfuncs.h"
43 #include "sphinxbase/hash_table.h"
44 #include "sphinxbase/err.h"
45
46 #include "jsgf_internal.h"
47 #include "jsgf_parser.h"
48 #include "jsgf_scanner.h"
49
50 int yyparse (yyscan_t yyscanner, jsgf_t *jsgf);
51
52 /**
53  * \file jsgf.c
54  *
55  * This file implements the data structures for parsing JSGF grammars
56  * into Sphinx finite-state grammars.
57  **/
58
59 jsgf_atom_t *
60 jsgf_atom_new(char *name, float weight)
61 {
62     jsgf_atom_t *atom;
63
64     atom = ckd_calloc(1, sizeof(*atom));
65     atom->name = ckd_salloc(name);
66     atom->weight = weight;
67     return atom;
68 }
69
70 int
71 jsgf_atom_free(jsgf_atom_t *atom)
72 {
73     if (atom == NULL)
74         return 0;
75     ckd_free(atom->name);
76     ckd_free(atom);
77     return 0;
78 }
79
80 jsgf_t *
81 jsgf_grammar_new(jsgf_t *parent)
82 {
83     jsgf_t *grammar;
84
85     grammar = ckd_calloc(1, sizeof(*grammar));
86     /* If this is an imported/subgrammar, then we will share a global
87      * namespace with the parent grammar. */
88     if (parent) {
89         grammar->rules = parent->rules;
90         grammar->imports = parent->imports;
91         grammar->searchpath = parent->searchpath;
92         grammar->parent = parent;
93     }
94     else {
95         char *jsgf_path;
96
97         grammar->rules = hash_table_new(64, 0);
98         grammar->imports = hash_table_new(16, 0);
99
100         /* Silvio Moioli: no getenv() in Windows CE */
101         #if !defined(_WIN32_WCE)
102         if ((jsgf_path = getenv("JSGF_PATH")) != NULL) {
103             char *word, *c;
104
105             /* FIXME: This should be a function in libsphinxbase. */
106             /* FIXME: Also nextword() is totally useless... */
107             word = jsgf_path = ckd_salloc(jsgf_path);
108             while ((c = strchr(word, ':'))) {
109                 *c = '\0';
110                 grammar->searchpath = glist_add_ptr(grammar->searchpath, word);
111                 word = c + 1;
112             }
113             grammar->searchpath = glist_add_ptr(grammar->searchpath, word);
114             grammar->searchpath = glist_reverse(grammar->searchpath);
115         }
116         else {
117             /* Default to current directory. */
118             grammar->searchpath = glist_add_ptr(grammar->searchpath, ckd_salloc("."));
119         }
120         #endif 
121     }
122
123     return grammar;
124 }
125
126 void
127 jsgf_grammar_free(jsgf_t *jsgf)
128 {
129     /* FIXME: Probably should just use refcounting instead. */
130     if (jsgf->parent == NULL) {
131         hash_iter_t *itor;
132         gnode_t *gn;
133
134         for (itor = hash_table_iter(jsgf->rules); itor;
135              itor = hash_table_iter_next(itor)) {
136             ckd_free((char *)itor->ent->key);
137             jsgf_rule_free((jsgf_rule_t *)itor->ent->val);
138         }
139         hash_table_free(jsgf->rules);
140         for (itor = hash_table_iter(jsgf->imports); itor;
141              itor = hash_table_iter_next(itor)) {
142             ckd_free((char *)itor->ent->key);
143             jsgf_grammar_free((jsgf_t *)itor->ent->val);
144         }
145         hash_table_free(jsgf->imports);
146         for (gn = jsgf->searchpath; gn; gn = gnode_next(gn))
147             ckd_free(gnode_ptr(gn));
148         glist_free(jsgf->searchpath);
149         for (gn = jsgf->links; gn; gn = gnode_next(gn))
150             ckd_free(gnode_ptr(gn));
151         glist_free(jsgf->links);
152     }
153     ckd_free(jsgf->name);
154     ckd_free(jsgf->version);
155     ckd_free(jsgf->charset);
156     ckd_free(jsgf->locale);
157     ckd_free(jsgf);
158 }
159
160 static void
161 jsgf_rhs_free(jsgf_rhs_t *rhs)
162 {
163     gnode_t *gn;
164
165     if (rhs == NULL)
166         return;
167
168     jsgf_rhs_free(rhs->alt);
169     for (gn = rhs->atoms; gn; gn = gnode_next(gn))
170         jsgf_atom_free(gnode_ptr(gn));
171     glist_free(rhs->atoms);
172     ckd_free(rhs);
173 }
174
175 jsgf_atom_t *
176 jsgf_kleene_new(jsgf_t *jsgf, jsgf_atom_t *atom, int plus)
177 {
178     jsgf_rule_t *rule;
179     jsgf_atom_t *rule_atom;
180     jsgf_rhs_t *rhs;
181
182     /* Generate an "internal" rule of the form (<NULL> | <name> <g0006>) */
183     /* Or if plus is true, (<name> | <name> <g0006>) */
184     rhs = ckd_calloc(1, sizeof(*rhs));
185     if (plus)
186         rhs->atoms = glist_add_ptr(NULL, jsgf_atom_new(atom->name, 1.0));
187     else
188         rhs->atoms = glist_add_ptr(NULL, jsgf_atom_new("<NULL>", 1.0));
189     rule = jsgf_define_rule(jsgf, NULL, rhs, 0);
190     rule_atom = jsgf_atom_new(rule->name, 1.0);
191     rhs = ckd_calloc(1, sizeof(*rhs));
192     rhs->atoms = glist_add_ptr(NULL, rule_atom);
193     rhs->atoms = glist_add_ptr(rhs->atoms, atom);
194     rule->rhs->alt = rhs;
195
196     return jsgf_atom_new(rule->name, 1.0);
197 }
198
199 jsgf_rule_t *
200 jsgf_optional_new(jsgf_t *jsgf, jsgf_rhs_t *exp)
201 {
202     jsgf_rhs_t *rhs = ckd_calloc(1, sizeof(*rhs));
203     jsgf_atom_t *atom = jsgf_atom_new("<NULL>", 1.0);
204     rhs->alt = exp;
205     rhs->atoms = glist_add_ptr(NULL, atom);
206     return jsgf_define_rule(jsgf, NULL, rhs, 0);
207 }
208
209 void
210 jsgf_add_link(jsgf_t *grammar, jsgf_atom_t *atom, int from, int to)
211 {
212     jsgf_link_t *link;
213
214     link = ckd_calloc(1, sizeof(*link));
215     link->from = from;
216     link->to = to;
217     link->atom = atom;
218     grammar->links = glist_add_ptr(grammar->links, link);
219 }
220
221 static char *
222 extract_grammar_name(char *rule_name)
223 {
224     char* dot_pos;
225     char* grammar_name = ckd_salloc(rule_name+1);
226     if ((dot_pos = strrchr(grammar_name + 1, '.')) == NULL) {
227         ckd_free(grammar_name);
228         return NULL;
229     }
230     *dot_pos='\0';
231     return grammar_name;
232 }
233
234 static char *
235 jsgf_fullname(jsgf_t *jsgf, const char *name)
236 {
237     char *fullname;
238
239     /* Check if it is already qualified */
240     if (strchr(name + 1, '.'))
241         return ckd_salloc(name);
242
243     /* Skip leading < in name */
244     fullname = ckd_malloc(strlen(jsgf->name) + strlen(name) + 4);
245     sprintf(fullname, "<%s.%s", jsgf->name, name + 1);
246     return fullname;
247 }
248
249 static char *
250 jsgf_fullname_from_rule(jsgf_rule_t *rule, const char *name)
251 {
252     char *fullname, *grammar_name;
253
254     /* Check if it is already qualified */
255     if (strchr(name + 1, '.'))
256         return ckd_salloc(name);
257
258     /* Skip leading < in name */
259     if ((grammar_name = extract_grammar_name(rule->name)) == NULL)
260         return ckd_salloc(name);
261     fullname = ckd_malloc(strlen(grammar_name) + strlen(name) + 4);
262     sprintf(fullname, "<%s.%s", grammar_name, name + 1);
263     ckd_free(grammar_name);
264
265     return fullname;
266 }
267
268 /* Extract as rulename everything after the secondlast dot, if existent. 
269  * Because everything before the secondlast dot is the path-specification. */
270 static char *
271 importname2rulename(char *importname)
272 {
273     char *rulename = ckd_salloc(importname);
274     char *last_dotpos;
275     char *secondlast_dotpos;
276
277     if ((last_dotpos = strrchr(rulename+1, '.')) != NULL) {
278         *last_dotpos='\0';
279         if ((secondlast_dotpos = strrchr(rulename+1, '.')) != NULL) {
280             *last_dotpos='.';
281             *secondlast_dotpos='<';
282             secondlast_dotpos = ckd_salloc(secondlast_dotpos);
283             ckd_free(rulename);
284             return secondlast_dotpos;
285         }
286         else {
287             *last_dotpos='.';
288             return rulename;
289         }
290     }
291     else {
292         return rulename;
293     }
294 }
295
296 static int expand_rule(jsgf_t *grammar, jsgf_rule_t *rule);
297 static int
298 expand_rhs(jsgf_t *grammar, jsgf_rule_t *rule, jsgf_rhs_t *rhs)
299 {
300     gnode_t *gn;
301     int lastnode;
302
303     /* Last node expanded in this sequence. */
304     lastnode = rule->entry;
305
306     /* Iterate over atoms in rhs and generate links/nodes */
307     for (gn = rhs->atoms; gn; gn = gnode_next(gn)) {
308         jsgf_atom_t *atom = gnode_ptr(gn);
309         if (jsgf_atom_is_rule(atom)) {
310             jsgf_rule_t *subrule;
311             char *fullname;
312             gnode_t *subnode;
313             void *val;
314
315             /* Special case for <NULL> and <VOID> pseudo-rules */
316             if (0 == strcmp(atom->name, "<NULL>")) {
317                 /* Emit a NULL transition */
318                 jsgf_add_link(grammar, atom,
319                               lastnode, grammar->nstate);
320                 lastnode = grammar->nstate;
321                 ++grammar->nstate;
322                 continue;
323             }
324             else if (0 == strcmp(atom->name, "<VOID>")) {
325                 /* Make this entire RHS unspeakable */
326                 return -1;
327             }
328
329             fullname = jsgf_fullname_from_rule(rule, atom->name);
330             if (hash_table_lookup(grammar->rules, fullname, &val) == -1) {
331                 E_ERROR("Undefined rule in RHS: %s\n", fullname);
332                 ckd_free(fullname);
333                 return -1;
334             }
335             ckd_free(fullname);
336             subrule = val;
337             /* Look for this in the stack of expanded rules */
338             for (subnode = grammar->rulestack; subnode; subnode = gnode_next(subnode))
339                 if (gnode_ptr(subnode) == (void *)subrule)
340                     break;
341             if (subnode != NULL) {
342                 /* Allow right-recursion only. */
343                 if (gnode_next(gn) != NULL) {
344                     E_ERROR("Only right-recursion is permitted (in %s.%s)\n",
345                             grammar->name, rule->name);
346                     return -1;
347                 }
348                 /* Add a link back to the beginning of this rule instance */
349                 E_INFO("Right recursion %s %d => %d\n", atom->name, lastnode, subrule->entry);
350                 jsgf_add_link(grammar, atom, lastnode, subrule->entry);
351             }
352             else {
353                 /* Expand the subrule */
354                 if (expand_rule(grammar, subrule) == -1)
355                     return -1;
356                 /* Add a link into the subrule. */
357                 jsgf_add_link(grammar, atom,
358                          lastnode, subrule->entry);
359                 lastnode = subrule->exit;
360             }
361         }
362         else {
363             /* Add a link for this token and create a new exit node. */
364             jsgf_add_link(grammar, atom,
365                      lastnode, grammar->nstate);
366             lastnode = grammar->nstate;
367             ++grammar->nstate;
368         }
369     }
370
371     return lastnode;
372 }
373
374 static int
375 expand_rule(jsgf_t *grammar, jsgf_rule_t *rule)
376 {
377     jsgf_rhs_t *rhs;
378     float norm;
379
380     /* Push this rule onto the stack */
381     grammar->rulestack = glist_add_ptr(grammar->rulestack, rule);
382
383     /* Normalize weights for all alternatives exiting rule->entry */
384     norm = 0;
385     for (rhs = rule->rhs; rhs; rhs = rhs->alt) {
386         if (rhs->atoms) {
387             jsgf_atom_t *atom = gnode_ptr(rhs->atoms);
388             norm += atom->weight;
389         }
390     }
391
392     rule->entry = grammar->nstate++;
393     rule->exit = grammar->nstate++;
394     if (norm == 0) norm = 1;
395     for (rhs = rule->rhs; rhs; rhs = rhs->alt) {
396         int lastnode;
397
398         if (rhs->atoms) {
399             jsgf_atom_t *atom = gnode_ptr(rhs->atoms);
400             atom->weight /= norm;
401         }
402         lastnode = expand_rhs(grammar, rule, rhs);
403         if (lastnode == -1) {
404             return -1;
405         }
406         else {
407             jsgf_add_link(grammar, NULL, lastnode, rule->exit);
408         }
409     }
410
411     /* Pop this rule from the rule stack */
412     grammar->rulestack = gnode_free(grammar->rulestack, NULL);
413     return rule->exit;
414 }
415
416 jsgf_rule_iter_t *
417 jsgf_rule_iter(jsgf_t *grammar)
418 {
419     return hash_table_iter(grammar->rules);
420 }
421
422 jsgf_rule_t *
423 jsgf_get_rule(jsgf_t *grammar, char const *name)
424 {
425     void *val;
426
427     if (hash_table_lookup(grammar->rules, name, &val) < 0)
428         return NULL;
429     return (jsgf_rule_t *)val;
430 }
431
432 char const *
433 jsgf_rule_name(jsgf_rule_t *rule)
434 {
435     return rule->name;
436 }
437
438 int
439 jsgf_rule_public(jsgf_rule_t *rule)
440 {
441     return rule->public;
442 }
443
444 static fsg_model_t *
445 jsgf_build_fsg_internal(jsgf_t *grammar, jsgf_rule_t *rule,
446                         logmath_t *lmath, float32 lw, int do_closure)
447 {
448     fsg_model_t *fsg;
449     glist_t nulls;
450     gnode_t *gn;
451
452     /* Clear previous links */
453     for (gn = grammar->links; gn; gn = gnode_next(gn)) {
454         ckd_free(gnode_ptr(gn));
455     }
456     glist_free(grammar->links);
457     grammar->links = NULL;
458     rule->entry = rule->exit = 0;
459     grammar->nstate = 0;
460     expand_rule(grammar, rule);
461
462     fsg = fsg_model_init(rule->name, lmath, lw, grammar->nstate);
463     fsg->start_state = rule->entry;
464     fsg->final_state = rule->exit;
465     grammar->links = glist_reverse(grammar->links);
466     for (gn = grammar->links; gn; gn = gnode_next(gn)) {
467         jsgf_link_t *link = gnode_ptr(gn);
468
469         if (link->atom) {
470             if (jsgf_atom_is_rule(link->atom)) {
471                 fsg_model_null_trans_add(fsg, link->from, link->to,
472                                         logmath_log(lmath, link->atom->weight));
473             }
474             else {
475                 int wid = fsg_model_word_add(fsg, link->atom->name);
476                 fsg_model_trans_add(fsg, link->from, link->to,
477                                    logmath_log(lmath, link->atom->weight), wid);
478             }
479         }
480         else {
481             fsg_model_null_trans_add(fsg, link->from, link->to, 0);
482         }            
483     }
484     if (do_closure) {
485         nulls = fsg_model_null_trans_closure(fsg, NULL);
486         glist_free(nulls);
487     }
488
489     return fsg;
490 }
491
492 fsg_model_t *
493 jsgf_build_fsg(jsgf_t *grammar, jsgf_rule_t *rule,
494                logmath_t *lmath, float32 lw)
495 {
496     return jsgf_build_fsg_internal(grammar, rule, lmath, lw, TRUE);
497 }
498
499 fsg_model_t *
500 jsgf_build_fsg_raw(jsgf_t *grammar, jsgf_rule_t *rule,
501                    logmath_t *lmath, float32 lw)
502 {
503     return jsgf_build_fsg_internal(grammar, rule, lmath, lw, FALSE);
504 }
505
506 int
507 jsgf_write_fsg(jsgf_t *grammar, jsgf_rule_t *rule, FILE *outfh)
508 {
509     fsg_model_t *fsg;
510     logmath_t *lmath = logmath_init(1.0001, 0, 0);
511
512     if ((fsg = jsgf_build_fsg_raw(grammar, rule, lmath, 1.0)) == NULL)
513         goto error_out;
514
515     fsg_model_write(fsg, outfh);
516     logmath_free(lmath);
517     return 0;
518
519 error_out:
520     logmath_free(lmath);
521     return -1;
522 }
523 jsgf_rule_t *
524 jsgf_define_rule(jsgf_t *jsgf, char *name, jsgf_rhs_t *rhs, int public)
525 {
526     jsgf_rule_t *rule;
527     void *val;
528
529     if (name == NULL) {
530         name = ckd_malloc(strlen(jsgf->name) + 16);
531         sprintf(name, "<%s.g%05d>", jsgf->name, hash_table_inuse(jsgf->rules));
532     }
533     else {
534         char *newname;
535
536         newname = jsgf_fullname(jsgf, name);
537         name = newname;
538     }
539
540     rule = ckd_calloc(1, sizeof(*rule));
541     rule->refcnt = 1;
542     rule->name = ckd_salloc(name);
543     rule->rhs = rhs;
544     rule->public = public;
545
546     E_INFO("Defined rule: %s%s\n",
547            rule->public ? "PUBLIC " : "",
548            rule->name);
549     val = hash_table_enter(jsgf->rules, name, rule);
550     if (val != (void *)rule) {
551         E_WARN("Multiply defined symbol: %s\n", name);
552     }
553     return rule;
554 }
555
556 jsgf_rule_t *
557 jsgf_rule_retain(jsgf_rule_t *rule)
558 {
559     ++rule->refcnt;
560     return rule;
561 }
562
563 int
564 jsgf_rule_free(jsgf_rule_t *rule)
565 {
566     if (rule == NULL)
567         return 0;
568     if (--rule->refcnt > 0)
569         return rule->refcnt;
570     jsgf_rhs_free(rule->rhs);
571     ckd_free(rule->name);
572     ckd_free(rule);
573     return 0;
574 }
575
576
577 /* FIXME: This should go in libsphinxutil */
578 static char *
579 path_list_search(glist_t paths, char *path)
580 {
581     gnode_t *gn;
582
583     for (gn = paths; gn; gn = gnode_next(gn)) {
584         char *fullpath;
585         FILE *tmp;
586
587         fullpath = string_join(gnode_ptr(gn), "/", path, NULL);
588         tmp = fopen(fullpath, "r");
589         if (tmp != NULL) {
590             fclose(tmp);
591             return fullpath;
592         }
593         else
594             ckd_free(fullpath);
595     }
596     return NULL;
597 }
598
599 jsgf_rule_t *
600 jsgf_import_rule(jsgf_t *jsgf, char *name)
601 {
602     char *c, *path, *newpath;
603     size_t namelen, packlen;
604     void *val;
605     jsgf_t *imp;
606     int import_all;
607
608     /* Trim the leading and trailing <> */
609     namelen = strlen(name);
610     path = ckd_malloc(namelen - 2 + 6); /* room for a trailing .gram */
611     strcpy(path, name + 1);
612     /* Split off the first part of the name */
613     c = strrchr(path, '.');
614     if (c == NULL) {
615         E_ERROR("Imported rule is not qualified: %s\n", name);
616         ckd_free(path);
617         return NULL;
618     }
619     packlen = c - path;
620     *c = '\0';
621
622     /* Look for import foo.* */
623     import_all = (strlen(name) > 2 && 0 == strcmp(name + namelen - 3, ".*>"));
624
625     /* Construct a filename. */
626     for (c = path; *c; ++c)
627         if (*c == '.') *c = '/';
628     strcat(path, ".gram");
629     newpath = path_list_search(jsgf->searchpath, path);
630     ckd_free(path);
631     if (newpath == NULL)
632         return NULL;
633
634     path = newpath;
635     E_INFO("Importing %s from %s to %s\n", name, path, jsgf->name);
636
637     /* FIXME: Also, we need to make sure that path is fully qualified
638      * here, by adding any prefixes from jsgf->name to it. */
639     /* See if we have parsed it already */
640     if (hash_table_lookup(jsgf->imports, path, &val) == 0) {
641         E_INFO("Already imported %s\n", path);
642         imp = val;
643         ckd_free(path);
644     }
645     else {
646         /* If not, parse it. */
647         imp = jsgf_parse_file(path, jsgf);
648         val = hash_table_enter(jsgf->imports, path, imp);
649         if (val != (void *)imp) {
650             E_WARN("Multiply imported file: %s\n", path);
651         }
652     }
653     if (imp != NULL) {
654         hash_iter_t *itor;
655         /* Look for public rules matching rulename. */
656         for (itor = hash_table_iter(imp->rules); itor;
657              itor = hash_table_iter_next(itor)) {
658             hash_entry_t *he = itor->ent;
659             jsgf_rule_t *rule = hash_entry_val(he);
660             int rule_matches;
661             char *rule_name = importname2rulename(name);
662
663             if (import_all) {
664                 /* Match package name (symbol table is shared) */
665                 rule_matches = !strncmp(rule_name, rule->name, packlen + 1);
666             }
667             else {
668                 /* Exact match */
669                 rule_matches = !strcmp(rule_name, rule->name);
670             }
671             ckd_free(rule_name);
672             if (rule->public && rule_matches) {
673                 void *val;
674                 char *newname;
675
676                 /* Link this rule into the current namespace. */
677                 c = strrchr(rule->name, '.');
678                 assert(c != NULL);
679                 newname = jsgf_fullname(jsgf, c);
680
681                 E_INFO("Imported %s\n", newname);
682                 val = hash_table_enter(jsgf->rules, newname,
683                                        jsgf_rule_retain(rule));
684                 if (val != (void *)rule) {
685                     E_WARN("Multiply defined symbol: %s\n", newname);
686                 }
687                 if (!import_all) {
688                     hash_table_iter_free(itor);
689                     return rule;
690                 }
691             }
692         }
693     }
694
695     return NULL;
696 }
697
698 jsgf_t *
699 jsgf_parse_file(const char *filename, jsgf_t *parent)
700 {
701     yyscan_t yyscanner;
702     jsgf_t *jsgf;
703     int yyrv;
704     FILE *in = NULL;
705
706     yylex_init(&yyscanner);
707     if (filename == NULL) {
708         yyset_in(stdin, yyscanner);
709     }
710     else {
711         in = fopen(filename, "r");
712         if (in == NULL) {
713             fprintf(stderr, "Failed to open %s for parsing: %s\n",
714                     filename, strerror(errno));
715             return NULL;
716         }
717         yyset_in(in, yyscanner);
718     }
719
720     jsgf = jsgf_grammar_new(parent);
721     yyrv = yyparse(yyscanner, jsgf);
722     if (yyrv != 0) {
723         fprintf(stderr, "JSGF parse of %s failed\n",
724                 filename ? filename : "(stdin)");
725         jsgf_grammar_free(jsgf);
726         yylex_destroy(yyscanner);
727         return NULL;
728     }
729     if (in)
730         fclose(in);
731     yylex_destroy(yyscanner);
732
733     return jsgf;
734 }