SphinxBase  0.6
jsgf.c
Go to the documentation of this file.
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 
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 *
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
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 
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 *
434 {
435  return rule->name;
436 }
437 
438 int
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 *
494  logmath_t *lmath, float32 lw)
495 {
496  return jsgf_build_fsg_internal(grammar, rule, lmath, lw, TRUE);
497 }
498 
499 fsg_model_t *
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 }