/* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* ==================================================================== * Copyright (c) 2007 Carnegie Mellon University. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * This work was supported in part by funding from the Defense Advanced * Research Projects Agency and the National Science Foundation of the * United States of America, and the CMU Sphinx Speech Consortium. * * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ==================================================================== * */ #include #include #include #include "util/ckd_alloc.h" #include "util/strfuncs.h" #include "util/hash_table.h" #include "util/filename.h" #include "lm/jsgf.h" #include "lm/jsgf_internal.h" #include "lm/jsgf_parser.h" #include "lm/jsgf_scanner.h" extern int yyparse(void *scanner, jsgf_t * jsgf); /** * \file jsgf.c * * This file implements the data structures for parsing JSGF grammars * into Sphinx finite-state grammars. **/ jsgf_atom_t * jsgf_atom_new(char *name, float weight) { jsgf_atom_t *atom; atom = ckd_calloc(1, sizeof(*atom)); atom->name = ckd_salloc(name); atom->weight = weight; return atom; } int jsgf_atom_free(jsgf_atom_t * atom) { if (atom == NULL) return 0; ckd_free(atom->name); ckd_free(atom); return 0; } jsgf_t * jsgf_grammar_new(jsgf_t * parent) { jsgf_t *grammar; grammar = ckd_calloc(1, sizeof(*grammar)); /* If this is an imported/subgrammar, then we will share a global * namespace with the parent grammar. */ if (parent) { grammar->rules = parent->rules; grammar->imports = parent->imports; grammar->searchpath = parent->searchpath; grammar->parent = parent; } else { grammar->rules = hash_table_new(64, 0); grammar->imports = hash_table_new(16, 0); } return grammar; } void jsgf_grammar_free(jsgf_t * jsgf) { /* FIXME: Probably should just use refcounting instead. */ if (jsgf->parent == NULL) { hash_iter_t *itor; gnode_t *gn; for (itor = hash_table_iter(jsgf->rules); itor; itor = hash_table_iter_next(itor)) { ckd_free((char *) itor->ent->key); jsgf_rule_free((jsgf_rule_t *) itor->ent->val); } hash_table_free(jsgf->rules); for (itor = hash_table_iter(jsgf->imports); itor; itor = hash_table_iter_next(itor)) { ckd_free((char *) itor->ent->key); jsgf_grammar_free((jsgf_t *) itor->ent->val); } hash_table_free(jsgf->imports); for (gn = jsgf->searchpath; gn; gn = gnode_next(gn)) ckd_free(gnode_ptr(gn)); glist_free(jsgf->searchpath); for (gn = jsgf->links; gn; gn = gnode_next(gn)) ckd_free(gnode_ptr(gn)); glist_free(jsgf->links); } ckd_free(jsgf->name); ckd_free(jsgf->version); ckd_free(jsgf->charset); ckd_free(jsgf->locale); ckd_free(jsgf); } static void jsgf_rhs_free(jsgf_rhs_t * rhs) { gnode_t *gn; if (rhs == NULL) return; jsgf_rhs_free(rhs->alt); for (gn = rhs->atoms; gn; gn = gnode_next(gn)) jsgf_atom_free(gnode_ptr(gn)); glist_free(rhs->atoms); ckd_free(rhs); } jsgf_atom_t * jsgf_kleene_new(jsgf_t * jsgf, jsgf_atom_t * atom, int plus) { jsgf_rule_t *rule; jsgf_atom_t *rule_atom; jsgf_rhs_t *rhs; /* Generate an "internal" rule of the form ( | ) */ /* Or if plus is true, ( | ) */ rhs = ckd_calloc(1, sizeof(*rhs)); if (plus) rhs->atoms = glist_add_ptr(NULL, jsgf_atom_new(atom->name, 1.0)); else rhs->atoms = glist_add_ptr(NULL, jsgf_atom_new("", 1.0)); rule = jsgf_define_rule(jsgf, NULL, rhs, 0); rule_atom = jsgf_atom_new(rule->name, 1.0); rhs = ckd_calloc(1, sizeof(*rhs)); rhs->atoms = glist_add_ptr(NULL, rule_atom); rhs->atoms = glist_add_ptr(rhs->atoms, atom); rule->rhs->alt = rhs; return jsgf_atom_new(rule->name, 1.0); } jsgf_rule_t * jsgf_optional_new(jsgf_t * jsgf, jsgf_rhs_t * exp) { jsgf_rhs_t *rhs = ckd_calloc(1, sizeof(*rhs)); jsgf_atom_t *atom = jsgf_atom_new("", 1.0); rhs->alt = exp; rhs->atoms = glist_add_ptr(NULL, atom); return jsgf_define_rule(jsgf, NULL, rhs, 0); } void jsgf_add_link(jsgf_t * grammar, jsgf_atom_t * atom, int from, int to) { jsgf_link_t *link; link = ckd_calloc(1, sizeof(*link)); link->from = from; link->to = to; link->atom = atom; grammar->links = glist_add_ptr(grammar->links, link); } static char * extract_grammar_name(char *rule_name) { char *dot_pos; char *grammar_name = ckd_salloc(rule_name + 1); if ((dot_pos = strrchr(grammar_name + 1, '.')) == NULL) { ckd_free(grammar_name); return NULL; } *dot_pos = '\0'; return grammar_name; } char const * jsgf_grammar_name(jsgf_t * jsgf) { return jsgf->name; } static char * jsgf_fullname(jsgf_t * jsgf, const char *name) { char *fullname; /* Check if it is already qualified */ if (strchr(name + 1, '.')) return ckd_salloc(name); /* Skip leading < in name */ fullname = ckd_malloc(strlen(jsgf->name) + strlen(name) + 4); sprintf(fullname, "<%s.%s", jsgf->name, name + 1); return fullname; } static char * jsgf_fullname_from_rule(jsgf_rule_t * rule, const char *name) { char *fullname, *grammar_name; /* Check if it is already qualified */ if (strchr(name + 1, '.')) return ckd_salloc(name); /* Skip leading < in name */ if ((grammar_name = extract_grammar_name(rule->name)) == NULL) return ckd_salloc(name); fullname = ckd_malloc(strlen(grammar_name) + strlen(name) + 4); sprintf(fullname, "<%s.%s", grammar_name, name + 1); ckd_free(grammar_name); return fullname; } /* Extract as rulename everything after the secondlast dot, if existent. * Because everything before the secondlast dot is the path-specification. */ static char * importname2rulename(char *importname) { char *rulename = ckd_salloc(importname); char *last_dotpos; char *secondlast_dotpos; if ((last_dotpos = strrchr(rulename + 1, '.')) != NULL) { *last_dotpos = '\0'; if ((secondlast_dotpos = strrchr(rulename + 1, '.')) != NULL) { *last_dotpos = '.'; *secondlast_dotpos = '<'; secondlast_dotpos = ckd_salloc(secondlast_dotpos); ckd_free(rulename); return secondlast_dotpos; } else { *last_dotpos = '.'; return rulename; } } else { return rulename; } } #define RECURSION -2 static int expand_rule(jsgf_t *grammar, jsgf_rule_t *rule); static int expand_rhs(jsgf_t *grammar, jsgf_rule_t *rule, jsgf_rhs_t *rhs) { gnode_t *gn; int lastnode; /* Last node expanded in this sequence. */ lastnode = rule->entry; /* Iterate over atoms in rhs and generate links/nodes */ for (gn = rhs->atoms; gn; gn = gnode_next(gn)) { jsgf_atom_t *atom = gnode_ptr(gn); if (jsgf_atom_is_rule(atom)) { jsgf_rule_t *subrule; char *fullname; gnode_t *subnode; void *val; /* Special case for and pseudo-rules */ if (0 == strcmp(atom->name, "")) { /* Emit a NULL transition */ jsgf_add_link(grammar, atom, lastnode, grammar->nstate); lastnode = grammar->nstate; ++grammar->nstate; continue; } else if (0 == strcmp(atom->name, "")) { /* Make this entire RHS unspeakable */ return -1; } fullname = jsgf_fullname_from_rule(rule, atom->name); if (hash_table_lookup(grammar->rules, fullname, &val) == -1) { E_ERROR("Undefined rule in RHS: %s\n", fullname); ckd_free(fullname); return -1; } ckd_free(fullname); /* Look for this subrule in the stack of expanded rules */ subrule = val; /* Look for this in the stack of expanded rules */ for (subnode = grammar->rulestack; subnode; subnode = gnode_next(subnode)) if (gnode_ptr(subnode) == (void *)subrule) break; if (subnode != NULL) { /* Allow right-recursion only. */ if (gnode_next(gn) != NULL) { E_ERROR ("Only right-recursion is permitted (in %s.%s)\n", grammar->name, rule->name); return -1; } /* Add a link back to the beginning of this rule instance */ E_INFO("Right recursion %s %d => %d\n", atom->name, lastnode, subrule->entry); jsgf_add_link(grammar, atom, lastnode, subrule->entry); return RECURSION; } else { /* Expand the subrule */ if (expand_rule(grammar, subrule) == -1) return -1; /* Add a link into the subrule. */ jsgf_add_link(grammar, atom, lastnode, subrule->entry); lastnode = subrule->exit; } } else { /* Add a link for this token and create a new exit node. */ jsgf_add_link(grammar, atom, lastnode, grammar->nstate); lastnode = grammar->nstate; ++grammar->nstate; } } return lastnode; } static int expand_rule(jsgf_t * grammar, jsgf_rule_t * rule) { jsgf_rhs_t *rhs; float norm; /* Push this rule onto the stack */ grammar->rulestack = glist_add_ptr(grammar->rulestack, rule); /* Normalize weights for all alternatives exiting rule->entry */ norm = 0; for (rhs = rule->rhs; rhs; rhs = rhs->alt) { if (rhs->atoms) { jsgf_atom_t *atom = gnode_ptr(rhs->atoms); norm += atom->weight; } } rule->entry = grammar->nstate++; rule->exit = grammar->nstate++; if (norm == 0) norm = 1; for (rhs = rule->rhs; rhs; rhs = rhs->alt) { int lastnode; if (rhs->atoms) { jsgf_atom_t *atom = gnode_ptr(rhs->atoms); atom->weight /= norm; } lastnode = expand_rhs(grammar, rule, rhs); if (lastnode == -1) { return -1; } else if (lastnode == RECURSION) { /* Do nothing. */ } else { jsgf_add_link(grammar, NULL, lastnode, rule->exit); } } /* Pop this rule from the rule stack */ grammar->rulestack = gnode_free(grammar->rulestack, NULL); return rule->exit; } jsgf_rule_iter_t * jsgf_rule_iter(jsgf_t * grammar) { return hash_table_iter(grammar->rules); } jsgf_rule_t * jsgf_get_rule(jsgf_t * grammar, char const *name) { void *val; char *fullname; fullname = string_join("<", name, ">", NULL); if (hash_table_lookup(grammar->rules, fullname, &val) < 0) { ckd_free(fullname); return NULL; } ckd_free(fullname); return (jsgf_rule_t *) val; } jsgf_rule_t * jsgf_get_public_rule(jsgf_t * grammar) { jsgf_rule_iter_t *itor; jsgf_rule_t *public_rule = NULL; for (itor = jsgf_rule_iter(grammar); itor; itor = jsgf_rule_iter_next(itor)) { jsgf_rule_t *rule = jsgf_rule_iter_rule(itor); if (jsgf_rule_public(rule)) { const char *rule_name = jsgf_rule_name(rule); char *dot_pos; if ((dot_pos = strrchr(rule_name + 1, '.')) == NULL) { public_rule = rule; jsgf_rule_iter_free(itor); break; } if (0 == strncmp(rule_name + 1, jsgf_grammar_name(grammar), dot_pos - rule_name - 1)) { public_rule = rule; jsgf_rule_iter_free(itor); break; } } } return public_rule; } char const * jsgf_rule_name(jsgf_rule_t * rule) { return rule->name; } int jsgf_rule_public(jsgf_rule_t * rule) { return rule->is_public; } static fsg_model_t * jsgf_build_fsg_internal(jsgf_t * grammar, jsgf_rule_t * rule, logmath_t * lmath, float32 lw, int do_closure) { fsg_model_t *fsg; glist_t nulls; gnode_t *gn; if (grammar == NULL || rule == NULL) return NULL; /* Clear previous links */ for (gn = grammar->links; gn; gn = gnode_next(gn)) { ckd_free(gnode_ptr(gn)); } glist_free(grammar->links); grammar->links = NULL; rule->entry = rule->exit = 0; grammar->nstate = 0; expand_rule(grammar, rule); fsg = fsg_model_init(rule->name, lmath, lw, grammar->nstate); fsg->start_state = rule->entry; fsg->final_state = rule->exit; grammar->links = glist_reverse(grammar->links); for (gn = grammar->links; gn; gn = gnode_next(gn)) { jsgf_link_t *link = gnode_ptr(gn); if (link->atom) { if (jsgf_atom_is_rule(link->atom)) { fsg_model_null_trans_add(fsg, link->from, link->to, logmath_log(lmath, link->atom->weight)); } else { int wid = fsg_model_word_add(fsg, link->atom->name); fsg_model_trans_add(fsg, link->from, link->to, logmath_log(lmath, link->atom->weight), wid); } } else { fsg_model_null_trans_add(fsg, link->from, link->to, 0); } } if (do_closure) { nulls = fsg_model_null_trans_closure(fsg, NULL); glist_free(nulls); } return fsg; } fsg_model_t * jsgf_build_fsg(jsgf_t * grammar, jsgf_rule_t * rule, logmath_t * lmath, float32 lw) { return jsgf_build_fsg_internal(grammar, rule, lmath, lw, TRUE); } fsg_model_t * jsgf_build_fsg_raw(jsgf_t * grammar, jsgf_rule_t * rule, logmath_t * lmath, float32 lw) { return jsgf_build_fsg_internal(grammar, rule, lmath, lw, FALSE); } fsg_model_t * jsgf_read_file(const char *file, logmath_t * lmath, float32 lw) { fsg_model_t *fsg; jsgf_rule_t *rule; jsgf_t *jsgf; jsgf_rule_iter_t *itor; if ((jsgf = jsgf_parse_file(file, NULL)) == NULL) { E_ERROR("Error parsing file: %s\n", file); return NULL; } rule = NULL; for (itor = jsgf_rule_iter(jsgf); itor; itor = jsgf_rule_iter_next(itor)) { rule = jsgf_rule_iter_rule(itor); if (jsgf_rule_public(rule)) { jsgf_rule_iter_free(itor); break; } } if (rule == NULL) { E_ERROR("No public rules found in %s\n", file); return NULL; } fsg = jsgf_build_fsg(jsgf, rule, lmath, lw); jsgf_grammar_free(jsgf); return fsg; } fsg_model_t * jsgf_read_string(const char *string, logmath_t * lmath, float32 lw) { fsg_model_t *fsg; jsgf_rule_t *rule; jsgf_t *jsgf; jsgf_rule_iter_t *itor; if ((jsgf = jsgf_parse_string(string, NULL)) == NULL) { E_ERROR("Error parsing input string\n"); return NULL; } rule = NULL; for (itor = jsgf_rule_iter(jsgf); itor; itor = jsgf_rule_iter_next(itor)) { rule = jsgf_rule_iter_rule(itor); if (jsgf_rule_public(rule)) { jsgf_rule_iter_free(itor); break; } } if (rule == NULL) { jsgf_grammar_free(jsgf); E_ERROR("No public rules found in input string\n"); return NULL; } fsg = jsgf_build_fsg(jsgf, rule, lmath, lw); jsgf_grammar_free(jsgf); return fsg; } int jsgf_write_fsg(jsgf_t * grammar, jsgf_rule_t * rule, FILE * outfh) { fsg_model_t *fsg; logmath_t *lmath = logmath_init(1.0001, 0, 0); if ((fsg = jsgf_build_fsg_raw(grammar, rule, lmath, 1.0)) == NULL) goto error_out; fsg_model_write(fsg, outfh); logmath_free(lmath); return 0; error_out: logmath_free(lmath); return -1; } jsgf_rule_t * jsgf_define_rule(jsgf_t * jsgf, char *name, jsgf_rhs_t * rhs, int is_public) { jsgf_rule_t *rule; void *val; if (name == NULL) { name = ckd_malloc(strlen(jsgf->name) + 16); sprintf(name, "<%s.g%05d>", jsgf->name, hash_table_inuse(jsgf->rules)); } else { char *newname; newname = jsgf_fullname(jsgf, name); name = newname; } rule = ckd_calloc(1, sizeof(*rule)); rule->refcnt = 1; rule->name = ckd_salloc(name); rule->rhs = rhs; rule->is_public = is_public; E_INFO("Defined rule: %s%s\n", rule->is_public ? "PUBLIC " : "", rule->name); val = hash_table_enter(jsgf->rules, name, rule); if (val != (void *) rule) { E_WARN("Multiply defined symbol: %s\n", name); } return rule; } jsgf_rule_t * jsgf_rule_retain(jsgf_rule_t * rule) { ++rule->refcnt; return rule; } int jsgf_rule_free(jsgf_rule_t * rule) { if (rule == NULL) return 0; if (--rule->refcnt > 0) return rule->refcnt; jsgf_rhs_free(rule->rhs); ckd_free(rule->name); ckd_free(rule); return 0; } /* FIXME: This should go in libsphinxutil */ static char * path_list_search(glist_t paths, char *path) { gnode_t *gn; for (gn = paths; gn; gn = gnode_next(gn)) { char *fullpath; FILE *tmp; fullpath = string_join(gnode_ptr(gn), "/", path, NULL); tmp = fopen(fullpath, "r"); if (tmp != NULL) { fclose(tmp); return fullpath; } else { ckd_free(fullpath); } } return NULL; } jsgf_rule_t * jsgf_import_rule(jsgf_t * jsgf, char *name) { char *c, *path, *newpath; size_t namelen, packlen; void *val; jsgf_t *imp; int import_all; /* Trim the leading and trailing <> */ namelen = strlen(name); path = ckd_malloc(namelen - 2 + 6); /* room for a trailing .gram */ strcpy(path, name + 1); /* Split off the first part of the name */ c = strrchr(path, '.'); if (c == NULL) { E_ERROR("Imported rule is not qualified: %s\n", name); ckd_free(path); return NULL; } packlen = c - path; *c = '\0'; /* Look for import foo.* */ import_all = (strlen(name) > 2 && 0 == strcmp(name + namelen - 3, ".*>")); /* Construct a filename. */ for (c = path; *c; ++c) if (*c == '.') *c = '/'; strcat(path, ".gram"); newpath = path_list_search(jsgf->searchpath, path); if (newpath == NULL) { E_ERROR("Failed to find grammar %s\n", path); ckd_free(path); return NULL; } ckd_free(path); path = newpath; E_INFO("Importing %s from %s to %s\n", name, path, jsgf->name); /* FIXME: Also, we need to make sure that path is fully qualified * here, by adding any prefixes from jsgf->name to it. */ /* See if we have parsed it already */ if (hash_table_lookup(jsgf->imports, path, &val) == 0) { E_INFO("Already imported %s\n", path); imp = val; ckd_free(path); } else { /* If not, parse it. */ imp = jsgf_parse_file(path, jsgf); val = hash_table_enter(jsgf->imports, path, imp); if (val != (void *) imp) { E_WARN("Multiply imported file: %s\n", path); } } if (imp != NULL) { hash_iter_t *itor; /* Look for public rules matching rulename. */ for (itor = hash_table_iter(imp->rules); itor; itor = hash_table_iter_next(itor)) { hash_entry_t *he = itor->ent; jsgf_rule_t *rule = hash_entry_val(he); int rule_matches; char *rule_name = importname2rulename(name); if (import_all) { /* Match package name (symbol table is shared) */ rule_matches = !strncmp(rule_name, rule->name, packlen + 1); } else { /* Exact match */ rule_matches = !strcmp(rule_name, rule->name); } ckd_free(rule_name); if (rule->is_public && rule_matches) { void *val; char *newname; /* Link this rule into the current namespace. */ c = strrchr(rule->name, '.'); assert(c != NULL); newname = jsgf_fullname(jsgf, c); E_INFO("Imported %s\n", newname); val = hash_table_enter(jsgf->rules, newname, jsgf_rule_retain(rule)); if (val != (void *) rule) { E_WARN("Multiply defined symbol: %s\n", newname); } if (!import_all) { hash_table_iter_free(itor); return rule; } } } } return NULL; } static void jsgf_set_search_path(jsgf_t * jsgf, const char *filename) { char *jsgf_path; #if !defined(_WIN32_WCE) if ((jsgf_path = getenv("JSGF_PATH")) != NULL) { char *word, *c; word = jsgf_path = ckd_salloc(jsgf_path); while ((c = strchr(word, ':'))) { *c = '\0'; jsgf->searchpath = glist_add_ptr(jsgf->searchpath, word); word = c + 1; } jsgf->searchpath = glist_add_ptr(jsgf->searchpath, word); jsgf->searchpath = glist_reverse(jsgf->searchpath); return; } #endif if (!filename) { jsgf->searchpath = glist_add_ptr(jsgf->searchpath, ckd_salloc(".")); return; } jsgf_path = ckd_salloc(filename); path2dirname(filename, jsgf_path); jsgf->searchpath = glist_add_ptr(jsgf->searchpath, jsgf_path); } jsgf_t * jsgf_parse_file(const char *filename, jsgf_t * parent) { yyscan_t yyscanner; jsgf_t *jsgf; int yyrv; FILE *in = NULL; yylex_init(&yyscanner); if (filename == NULL) { yyset_in(stdin, yyscanner); } else { in = fopen(filename, "r"); if (in == NULL) { E_ERROR_SYSTEM("Failed to open %s for parsing", filename); return NULL; } yyset_in(in, yyscanner); } jsgf = jsgf_grammar_new(parent); if (!parent) jsgf_set_search_path(jsgf, filename); yyrv = yyparse(yyscanner, jsgf); if (yyrv != 0) { E_ERROR("Failed to parse JSGF grammar from '%s'\n", filename ? filename : "(stdin)"); jsgf_grammar_free(jsgf); yylex_destroy(yyscanner); return NULL; } if (in) fclose(in); yylex_destroy(yyscanner); return jsgf; } jsgf_t * jsgf_parse_string(const char *string, jsgf_t * parent) { yyscan_t yyscanner; jsgf_t *jsgf; int yyrv; YY_BUFFER_STATE buf; yylex_init(&yyscanner); buf = yy_scan_string(string, yyscanner); jsgf = jsgf_grammar_new(parent); if (!parent) jsgf_set_search_path(jsgf, NULL); yyrv = yyparse(yyscanner, jsgf); if (yyrv != 0) { E_ERROR("Failed to parse JSGF grammar from input string\n"); jsgf_grammar_free(jsgf); yy_delete_buffer(buf, yyscanner); yylex_destroy(yyscanner); return NULL; } yy_delete_buffer(buf, yyscanner); yylex_destroy(yyscanner); return jsgf; } jsgf_rule_iter_t * jsgf_rule_iter_next(jsgf_rule_iter_t *itor) { return hash_table_iter_next(itor); } jsgf_rule_t *jsgf_rule_iter_rule(jsgf_rule_iter_t *itor) { return ((jsgf_rule_t *)(itor)->ent->val); } void jsgf_rule_iter_free(jsgf_rule_iter_t *itor) { return hash_table_iter_free(itor); }