/* dhcpxlt.c Translate old (Beta 4 and previous) dhcpd config files... */ /* * Copyright (c) 1995, 1996 The Internet Software Consortium. * 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. * 3. Neither the name of The Internet Software Consortium nor the names * of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS 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 THE INTERNET SOFTWARE CONSORTIUM OR * CONTRIBUTORS 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. * * This software has been written for the Internet Software Consortium * by Ted Lemon in cooperation with Vixie * Enterprises. To learn more about the Internet Software Consortium, * see ``http://www.vix.com/isc''. To learn more about Vixie * Enterprises, see ``http://www.vix.com''. */ #ifndef lint static char copyright[] = "$Id: dhcpxlt.c,v 1.7 1996/09/02 21:17:26 mellon Exp $ Copyright (c) 1996 The Internet Software Consortium. All rights reserved.\n"; #endif /* not lint */ #include "dhcpd.h" #include "dhctoken.h" int log_priority; int log_perror = 1; int main (argc, argv, envp) int argc; char **argv; char **envp; { initialize_universes (); readconf (); exit (0); } void cleanup () { } /* conf-file :== statements declarations :== | declaration | declarations declaration */ int readconf () { FILE *cfile; char *val; int token; new_parse ("stdin"); cfile = stdin; do { token = peek_token (&val, cfile); if (token == EOF) break; convert_statement (cfile); } while (1); token = next_token (&val, cfile); return 0; } /* statement :== host_statement */ void convert_statement (cfile) FILE *cfile; { int token; char *val; jmp_buf bc; switch (next_token (&val, cfile)) { case HOST: if (!setjmp (bc)) { convert_host_statement (cfile, jref (bc)); } break; case LEASE: if (!setjmp (bc)) { convert_lease_statement (cfile, jref (bc)); } break; case TIMESTAMP: if (!setjmp (bc)) { convert_timestamp (cfile, jref (bc)); } break; case SHARED_NETWORK: if (!setjmp (bc)) { convert_shared_net_statement (cfile, jref (bc)); } break; case SUBNET: if (!setjmp (bc)) { convert_subnet_statement (cfile, jref (bc)); goto need_semi; } break; case VENDOR_CLASS: if (!setjmp (bc)) { convert_class_statement (cfile, jref (bc), 0); } break; case USER_CLASS: if (!setjmp (bc)) { convert_class_statement (cfile, jref (bc), 1); } break; case DEFAULT_LEASE_TIME: if (!setjmp (bc)) { convert_lease_time (cfile, jref (bc), "default-lease-time"); goto need_semi; } break; case MAX_LEASE_TIME: if (!setjmp (bc)) { convert_lease_time (cfile, jref (bc), "max-lease-time"); goto need_semi; } break; case DYNAMIC_BOOTP_LEASE_CUTOFF: if (!setjmp (bc)) { convert_date (cfile, jref (bc), "dynamic-bootp-lease-cutoff"); goto need_semi; } break; case DYNAMIC_BOOTP_LEASE_LENGTH: if (!setjmp (bc)) { convert_lease_time (cfile, jref (bc), "dynamic-bootp-lease-length "); goto need_semi; } break; case BOOT_UNKNOWN_CLIENTS: token = next_token (&val, cfile); if (token != NUMBER || (strcmp (val, "0") && strcmp (val, "1"))) { parse_warn ("0 or 1 expected"); skip_to_semi (cfile); break; } indent (0); printf ("boot-unknown-clients %s;\n", val); goto need_semi; case NEXT_SERVER: if (!setjmp (bc)) { indent (0); printf ("next-server "); convert_ip_addr_or_hostname (cfile, jref (bc), 0); printf (";\n"); goto need_semi; } break; case OPTION: if (!setjmp (bc)) { convert_option_decl (cfile, jref (bc)); goto need_semi; } break; case SERVER_IDENTIFIER: if (!setjmp (bc)) { indent (0); printf ("server-identifier "); convert_ip_addr_or_hostname (cfile, jref (bc), 0); printf (";\n"); goto need_semi; } break; default: parse_warn ("expecting a declaration."); skip_to_semi (cfile); break; } return; need_semi: token = next_token (&val, cfile); if (token != SEMI) { parse_warn ("semicolon expected"); skip_to_semi (cfile); } } void skip_to_semi (cfile) FILE *cfile; { int token; char *val; do { token = next_token (&val, cfile); } while (token != SEMI && token != EOF); } /* host_statement :== HOST hostname declarations SEMI host_declarations :== | host_declaration | host_declarations host_declaration SEMI */ void convert_host_statement (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; indent (0); printf ("host "); convert_host_name (cfile, bc); printf (" {\n"); indent (2); do { token = peek_token (&val, cfile); if (token == SEMI) { token = next_token (&val, cfile); break; } convert_host_decl (cfile, bc); } while (1); indent (-2); indent (0); printf ("}\n"); } /* host_name :== identifier | host_name DOT identifier */ void convert_host_name (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; /* Read a dotted hostname... */ do { /* Read a token, which should be an identifier. */ token = next_token (&val, cfile); if (!is_identifier (token)) { parse_warn ("expecting an identifier in hostname"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } /* Spit it out... */ fputs (val, stdout); /* Look for a dot; if it's there, keep going, otherwise we're done. */ token = peek_token (&val, cfile); if (token == DOT) { token = next_token (&val, cfile); putchar ('.'); } } while (token == DOT); } /* class_statement :== VENDOR_CLASS STRING class_declarations SEMI | USER_CLASS class_declarations SEMI class_declarations :== | option_declaration | option_declarations option_declaration SEMI */ void convert_class_statement (cfile, bc, type) FILE *cfile; jbp_decl (bc); int type; { char *val; int token; token = next_token (&val, cfile); if (token != STRING) { parse_warn ("Expecting class name"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } indent (0); if (type) printf ("user-class %s {\n", val); else printf ("vendor-class %s {\n", val); indent (2); do { token = peek_token (&val, cfile); if (token == SEMI) { token = next_token (&val, cfile); break; } else { convert_class_decl (cfile, bc); } } while (1); indent (-2); indent (0); printf ("}\n"); } /* class_declaration :== filename_declaration | option_declaration | DEFAULT_LEASE_TIME NUMBER | MAX_LEASE_TIME NUMBER */ void convert_class_decl (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; token = next_token (&val, cfile); switch (token) { case FILENAME: convert_filename_decl (cfile, bc); break; case OPTION: convert_option_decl (cfile, bc); break; default: parse_warn ("expecting a dhcp option declaration."); skip_to_semi (cfile); longjmp (jdref (bc), 1); break; } } /* lease_time :== NUMBER */ void convert_lease_time (cfile, bc, name) FILE *cfile; jbp_decl (bc); char *name; { char *val; int token; token = next_token (&val, cfile); if (token != NUMBER) { parse_warn ("Expecting numeric lease time"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } indent (0); printf ("%s %s;\n", name, val); } /* shared_network_statement :== SHARED_NETWORK subnet_statements SEMI subnet_statements :== subnet_statement | subnet_statements subnet_statement */ void convert_shared_net_statement (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; /* Get the name of the shared network... */ token = next_token (&val, cfile); if (!is_identifier (token) && token != STRING) { skip_to_semi (cfile); parse_warn ("expecting shared network name"); longjmp (jdref (bc), 1); } if (val [0] == 0) { parse_warn ("zero-length shared network name"); val = ""; token = STRING; } indent (0); if (token == STRING) printf ("shared-network \"%s\" {\n", val); else printf ("shared-network %s {\n", val); indent (2); do { token = next_token (&val, cfile); switch (token) { case SEMI: indent (-2); indent (0); printf ("}\n"); return; case SUBNET: convert_subnet_statement (cfile, bc); break; case OPTION: convert_option_decl (cfile, bc); break; case DEFAULT_LEASE_TIME: convert_lease_time (cfile, bc, "default-lease-time"); break; case MAX_LEASE_TIME: convert_lease_time (cfile, bc, "max-lease-time"); break; case DYNAMIC_BOOTP_LEASE_CUTOFF: convert_date (cfile, bc, "dynamic-bootp-lease-cutoff"); break; case DYNAMIC_BOOTP_LEASE_LENGTH: convert_lease_time (cfile, bc, "dynamic-bootp-lease-length"); break; case NEXT_SERVER: indent (0); printf ("next-server "); convert_ip_addr_or_hostname (cfile, bc, 0); printf (";\n"); break; case BOOT_UNKNOWN_CLIENTS: token = next_token (&val, cfile); if (token != NUMBER || (strcmp (val, "0") && strcmp (val, "1"))) { parse_warn ("0 or 1 expected"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } printf ("boot-unknown-clients %s;\n", val); break; default: parse_warn ("expecting subnet declaration"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } } while (1); } /* subnet_statement :== SUBNET net NETMASK netmask declarations host_declarations :== | host_declaration | host_declarations host_declaration SEMI */ void convert_subnet_statement (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; indent (0); printf ("subnet "); /* Get the network number... */ convert_numeric_aggregate (cfile, bc, 4, DOT, 10, 8); token = next_token (&val, cfile); if (token != NETMASK) { parse_warn ("Expecting netmask"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } printf (" netmask "); /* Get the netmask... */ convert_numeric_aggregate (cfile, bc, 4, DOT, 10, 8); printf (" {\n"); indent (2); do { token = peek_token (&val, cfile); if (token == SEMI || token == SUBNET) break; convert_subnet_decl (cfile, bc); } while (1); indent (-2); indent (0); printf ("}\n"); } /* subnet_declaration :== hardware_declaration | filename_declaration | fixed_addr_declaration | option_declaration */ void convert_subnet_decl (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; token = next_token (&val, cfile); switch (token) { case RANGE: convert_address_range (cfile, bc); break; case OPTION: convert_option_decl (cfile, bc); break; case DEFAULT_LEASE_TIME: convert_lease_time (cfile, bc, "default-lease-time"); break; case MAX_LEASE_TIME: convert_lease_time (cfile, bc, "max-lease-time"); break; case DYNAMIC_BOOTP_LEASE_CUTOFF: convert_date (cfile, bc, "dynamic-bootp-lease-cutoff"); break; case DYNAMIC_BOOTP_LEASE_LENGTH: convert_lease_time (cfile, bc, "dynamic-bootp-lease-length"); break; case NEXT_SERVER: indent (0); printf ("next-server "); convert_ip_addr_or_hostname (cfile, bc, 0); break; case BOOT_UNKNOWN_CLIENTS: token = next_token (&val, cfile); if (token != NUMBER || (strcmp (val, "0") && strcmp (val, "1"))) { parse_warn ("0 or 1 expected"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } indent (0); printf ("boot-unknown-clients %s;\n", val); break; default: parse_warn ("expecting a subnet declaration."); skip_to_semi (cfile); longjmp (jdref (bc), 1); break; } } /* host_declaration :== hardware_declaration | filename_declaration | fixed_addr_declaration | option_declaration | max_lease_declaration | default_lease_declaration */ void convert_host_decl (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; token = next_token (&val, cfile); switch (token) { case HARDWARE: convert_hardware_decl (cfile, bc); break; case FILENAME: convert_filename_decl (cfile, bc); break; case SERVER_NAME: convert_servername_decl (cfile, bc); break; case FIXED_ADDR: convert_fixed_addr_decl (cfile, bc); break; case OPTION: convert_option_decl (cfile, bc); break; case DEFAULT_LEASE_TIME: convert_lease_time (cfile, bc, "default-lease-time"); break; case MAX_LEASE_TIME: convert_lease_time (cfile, bc, "max-lease-time"); break; case DYNAMIC_BOOTP_LEASE_CUTOFF: convert_date (cfile, bc, "dynamic-bootp-lease-cutoff"); break; case DYNAMIC_BOOTP_LEASE_LENGTH: convert_lease_time (cfile, bc, "dynamic-bootp-lease-length"); break; case NEXT_SERVER: indent (0); printf ("next-server "); convert_ip_addr_or_hostname (cfile, bc, 0); printf (";\n"); break; default: parse_warn ("expecting a dhcp option declaration."); skip_to_semi (cfile); longjmp (jdref (bc), 1); break; } } /* hardware_decl :== HARDWARE ETHERNET NUMBER COLON NUMBER COLON NUMBER COLON NUMBER COLON NUMBER COLON NUMBER */ void convert_hardware_decl (cfile, bc) FILE *cfile; jbp_decl (bc); { indent (0); printf ("hardware "); convert_hardware_addr (cfile, bc); printf (";\n"); } void convert_hardware_addr (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; token = next_token (&val, cfile); switch (token) { case ETHERNET: break; #ifdef ARPHRD_IEEE802 /* XXX */ case TOKEN_RING: break; #endif default: parse_warn ("expecting a network hardware type"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); putchar (' '); /* Parse the hardware address information. Technically, it would make a lot of sense to restrict the length of the data we'll accept here to the length of a particular hardware address type. Unfortunately, there are some broken clients out there that put bogus data in the chaddr buffer, and we accept that data in the lease file rather than simply failing on such clients. Yuck. */ convert_numeric_aggregate (cfile, bc, 0, COLON, 16, 8); } /* filename_decl :== FILENAME STRING */ void convert_filename_decl (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; token = next_token (&val, cfile); if (token != STRING) { parse_warn ("filename must be a string"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } indent (0); printf ("filename \"%s\";\n", val); } /* servername_decl :== SERVER_NAME STRING */ void convert_servername_decl (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; token = next_token (&val, cfile); if (token != STRING) { parse_warn ("server name must be a string"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } indent (0); printf ("server-name \"%s\";\n", val); } /* ip_addr_or_hostname :== ip_address | hostname ip_address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER Parse an ip address or a hostname. If uniform is zero, put in a TREE_LIMIT node to catch hostnames that evaluate to more than one IP address. */ void convert_ip_addr_or_hostname (cfile, bc, uniform) FILE *cfile; jbp_decl (bc); int uniform; { char *val; int token; token = peek_token (&val, cfile); if (is_identifier (token)) convert_host_name (cfile, bc); else if (token == NUMBER) convert_numeric_aggregate (cfile, bc, 4, DOT, 10, 8); else { parse_warn ("%s (%d): expecting IP address or hostname", val, token); skip_to_semi (cfile); longjmp (jdref (bc), 1); } } /* fixed_addr_clause :== FIXED_ADDR fixed_addr_decls fixed_addr_decls :== ip_addr_or_hostname | fixed_addr_decls ip_addr_or_hostname */ void convert_fixed_addr_decl (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; indent (0); printf ("fixed-address "); do { convert_ip_addr_or_hostname (cfile, bc, 0); token = peek_token (&val, cfile); if (token == COMMA) { putchar (','); putchar (' '); token = next_token (&val, cfile); } } while (token == COMMA); printf (";\n"); } /* option_declaration :== OPTION identifier DOT identifier | OPTION identifier Option syntax is handled specially through format strings, so it would be painful to come up with BNF for it. However, it always starts as above. */ void convert_option_decl (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; char *fmt; struct option *option; indent (0); printf ("option "); token = next_token (&val, cfile); if (!is_identifier (token)) { parse_warn ("expecting identifier after option keyword."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); /* Look up the actual option info... */ option = (struct option *)hash_lookup (dhcp_universe.hash, val, 0); token = peek_token (&val, cfile); if (token == DOT) { /* Go ahead and take the DOT token... */ token = next_token (&val, cfile); putchar ('.'); /* The next token should be an identifier... */ token = next_token (&val, cfile); if (!is_identifier (token)) { parse_warn ("expecting identifier after '.'"); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); option = (struct option *)hash_lookup (dhcp_universe.hash, val, 0); } /* If we didn't get an option structure, it's an undefined option. */ if (!option) { parse_warn ("no option named %s", val); skip_to_semi (cfile); longjmp (jdref (bc), 1); } /* Convert the option data... */ do { /* Set a flag if this is an array of a simple type (i.e., not an array of pairs of IP addresses, or something like that. */ int uniform = option -> format [1] == 'A'; putchar (' '); for (fmt = option -> format; *fmt; fmt++) { if (*fmt == 'A') break; switch (*fmt) { case 'X': token = peek_token (&val, cfile); if (token == NUMBER_OR_NAME || token == NUMBER) { do { token = next_token (&val, cfile); fputs (val, stdout); if (token == COLON) { token = next_token (&val, cfile); putchar (':'); } } while (token == COLON); } else if (token == STRING) { token = next_token (&val, cfile); printf ("\"%s\"", val); } else { parse_warn ("expecting string %s.", "or hexadecimal data"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } break; case 't': /* Text string... */ token = next_token (&val, cfile); if (token != STRING && !is_identifier (token)) { parse_warn ("expecting string."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } printf ("\"%s\"", val); break; case 'I': /* IP address or hostname. */ convert_ip_addr_or_hostname (cfile, bc, uniform); break; case 'L': /* Unsigned 32-bit integer... */ case 'l': /* Signed 32-bit integer... */ case 's': /* Signed 16-bit integer. */ case 'S': /* Unsigned 16-bit integer. */ case 'b': /* Signed 8-bit integer. */ case 'B': /* Unsigned 8-bit integer. */ token = next_token (&val, cfile); if (token != NUMBER) { parse_warn ("expecting number."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); break; case 'f': /* Boolean flag. */ token = next_token (&val, cfile); if (!is_identifier (token)) { parse_warn ("expecting identifier."); bad_flag: if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } if (!strcasecmp (val, "true") || !strcasecmp (val, "on")) ; else if (!strcasecmp (val, "false") || !strcasecmp (val, "off")) ; else { parse_warn ("expecting boolean."); goto bad_flag; } fputs (val, stdout); break; default: warn ("Bad format %c in parse_option_decl.", *fmt); skip_to_semi (cfile); longjmp (jdref (bc), 1); } } if (*fmt == 'A') { token = peek_token (&val, cfile); if (token == COMMA) { token = next_token (&val, cfile); putchar (','); continue; } break; } } while (*fmt == 'A'); printf (";\n"); } /* timestamp :== TIMESTAMP date SEMI Timestamps are actually not used in dhcpd.conf, which is a static file, but rather in the database file and the journal file. */ void convert_timestamp (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; convert_date (cfile, bc, "timestamp"); token = next_token (&val, cfile); if (token != SEMI) { parse_warn ("semicolon expected"); skip_to_semi (cfile); longjmp (jdref (bc), 1); } } /* lease_decl :== LEASE ip_address lease_modifiers SEMI lease_modifiers :== | lease_modifier | lease_modifier lease_modifiers lease_modifier :== STARTS date | ENDS date | UID hex_numbers | HOST identifier | CLASS identifier | TIMESTAMP number | DYNAMIC_BOOTP */ void convert_lease_statement (cfile, bc) FILE *cfile; jbp_decl (bc); { char *val; int token; indent (0); printf ("lease "); /* Get the address for which the lease has been issued. */ convert_numeric_aggregate (cfile, bc, 4, DOT, 10, 8); printf (" {\n"); indent (2); do { token = next_token (&val, cfile); switch (token) { case STARTS: convert_date (cfile, bc, "starts"); break; case ENDS: convert_date (cfile, bc, "ends"); break; case TIMESTAMP: convert_date (cfile, bc, "timestamp"); break; /* Colon-seperated hexadecimal octets... */ case UID: indent (0); token = peek_token (&val, cfile); if (token == STRING) { token = next_token (&val, cfile); printf ("uid \"%s\";\n", val); } else { printf ("uid "); convert_numeric_aggregate (cfile, bc, 0, ':', 16, 8); } printf (";\n"); break; case HARDWARE: indent (0); printf ("hardware "); convert_hardware_addr (cfile, bc); printf (";\n"); break; case DYNAMIC_BOOTP: indent (0); printf ("dynamic-bootp;\n"); break; case SEMI: break; default: skip_to_semi (cfile); longjmp (jdref (bc), 1); } } while (token != SEMI); indent (-2); indent (0); printf ("}\n"); } /* address_range :== RANGE ip_address ip_address | RANGE dynamic_bootp_statement ip_address ip_address */ void convert_address_range (cfile, bc) FILE *cfile; jbp_decl (bc); { int token; char *val; indent (0); printf ("range "); if ((token = peek_token (&val, cfile)) == DYNAMIC_BOOTP) { token = next_token (&val, cfile); printf ("dynamic-bootp "); } /* Bottom address in the range... */ convert_numeric_aggregate (cfile, bc, 4, DOT, 10, 8); putchar (' '); /* Top address in the range... */ convert_numeric_aggregate (cfile, bc, 4, DOT, 10, 8); printf (";\n"); } /* date :== NUMBER NUMBER/NUMBER/NUMBER NUMBER:NUMBER:NUMBER Dates are always in GMT; first number is day of week; next is year/month/day; next is hours:minutes:seconds on a 24-hour clock. */ void convert_date (cfile, bc, name) FILE *cfile; jbp_decl (bc); char *name; { char *val; int token; indent (0); fputs (name, stdout); putchar (' '); /* Day of week... */ token = next_token (&val, cfile); if (token != NUMBER) { parse_warn ("numeric day of week expected."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); putchar (' '); /* Year... */ token = next_token (&val, cfile); if (token != NUMBER) { parse_warn ("numeric year expected."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); putchar ('/'); /* Slash seperating year from month... */ token = next_token (&val, cfile); if (token != SLASH) { parse_warn ("expected slash seperating year from month."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } /* Month... */ token = next_token (&val, cfile); if (token != NUMBER) { parse_warn ("numeric month expected."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); putchar ('/'); /* Slash seperating month from day... */ token = next_token (&val, cfile); if (token != SLASH) { parse_warn ("expected slash seperating month from day."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } /* Month... */ token = next_token (&val, cfile); if (token != NUMBER) { parse_warn ("numeric day of month expected."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); putchar (' '); /* Hour... */ token = next_token (&val, cfile); if (token != NUMBER) { parse_warn ("numeric hour expected."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); putchar (':'); /* Colon seperating hour from minute... */ token = next_token (&val, cfile); if (token != COLON) { parse_warn ("expected colon seperating hour from minute."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } /* Minute... */ token = next_token (&val, cfile); if (token != NUMBER) { parse_warn ("numeric minute expected."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); putchar (':'); /* Colon seperating minute from second... */ token = next_token (&val, cfile); if (token != COLON) { parse_warn ("expected colon seperating hour from minute."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } /* Second... */ token = next_token (&val, cfile); if (token != NUMBER) { parse_warn ("numeric minute expected."); if (token != SEMI) skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); printf (";\n"); } /* No BNF for numeric aggregates - that's defined by the caller. What this function does is to parse a sequence of numbers seperated by the token specified in seperator. If max is zero, any number of numbers will be parsed; otherwise, exactly max numbers are expected. Base and size tell us how to internalize the numbers once they've been tokenized. */ void convert_numeric_aggregate (cfile, bc, max, seperator, base, size) FILE *cfile; jbp_decl (bc); int max; int seperator; int base; int size; { char *val; int token; int count = 0; do { if (count) { token = peek_token (&val, cfile); if (token != seperator) { if (!max) break; parse_warn ("too few numbers."); skip_to_semi (cfile); longjmp (jdref (bc), 1); } token = next_token (&val, cfile); fputs (val, stdout); } token = next_token (&val, cfile); /* Allow NUMBER_OR_NAME if base is 16. */ if (token != NUMBER && (base != 16 || token != NUMBER_OR_NAME)) { parse_warn ("expecting numeric value."); skip_to_semi (cfile); longjmp (jdref (bc), 1); } fputs (val, stdout); } while (++count != max); } void indent (i) int i; { static int indent = 0; int k; if (!i) { if (comment_index) { fwrite (comments, comment_index, 1, stdout); comment_index = 0; } for (k = 0; k < indent / 8; k++) putchar ('\t'); for (k = 0; k < indent % 8; k++) putchar (' '); } else { indent += i; } }