home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume11 / number / number.c < prev    next >
C/C++ Source or Header  |  1987-08-31  |  14KB  |  660 lines

  1. #include <stdio.h>
  2. #include <ctype.h>
  3. /*
  4.  * number
  5.  *
  6.  *    Number is a program that counts in lots of languages.
  7.  *    It was originally written during a sanity break while
  8.  *    I was writing my PhD thesis.  That version got left
  9.  *    on a machine somewhere in upstate New York.  This one
  10.  *    was done while on leave in Grenoble, after realizing
  11.  *    that I hadn't written a computer program in over two
  12.  *    months.
  13.  *
  14.  *    Number is inspired, of course, from /usr/games/number, but
  15.  *    it uses a series of grammars that define counting in
  16.  *    different languages.  The language that is used to
  17.  *    write the grammars is described below, in evalrule().
  18.  *    If you write any new grammars, I'd greatly appreciate
  19.  *    having them.  Grammars aren't very hard to write, if
  20.  *    you know how to count in something that isn't defined
  21.  *    here.  The longest grammar (french) only has 30 rules
  22.  *    and 5 macros, and correctly pronounces any number less
  23.  *    1,000,000,000,000.  The shortest is for cantonese, which
  24.  *    has 14 rules.
  25.  *
  26.  * A note on the output of number:
  27.  *
  28.  *    The characters that are output conform to the TIRA
  29.  *    character representation standard.  Essentially, strings
  30.  *    in anything except the latin alphabet (what you're reading
  31.  *    now) are preceded by an indication of the alphabet that
  32.  *    they are part of.  The exceptions to this are mandarin,
  33.  *    cantonese and japanese.  These three are written in
  34.  *    pin-yin, roughly Wade Giles, and romanji, respectively.
  35.  *    The only other thing special about this format is that
  36.  *    accents and tone markings are given in [] brackets
  37.  *    before the letter to which they are attached.
  38.  *
  39.  *    TIRA stands for Textual Information Retrieval and Analysis
  40.  *    research group, and is a research group at the University
  41.  *    of Chicago containing computer and information scientists,
  42.  *    literary scholars and linguists.  TIRA is working on a
  43.  *    research environment for doing textual research.  Watch
  44.  *    this space.
  45.  *
  46.  * Copyright 1987, Scott Deerwester.
  47.  *
  48.  *    This code may be freely distributed and copied, provided
  49.  *    that a copy of this notice accompanies all copies and
  50.  *    that no copy is sold for profit.
  51.  */
  52.  
  53. /*
  54.  *    Constants for array bounds.  Both of these are overkill.
  55.  */
  56.  
  57. #define    MAXRULES    100
  58. #define    MAXSPECIALS    50
  59.  
  60. /*
  61.  *    Structure to hold macro definitions.
  62.  */
  63.  
  64. struct {
  65.     char    c;
  66.     char    *rule;
  67. } specials [MAXSPECIALS];
  68.  
  69. int    nspecials = 0;
  70. int    maxdigits;
  71.  
  72. /*
  73.  *    Definition of a grammar rule.
  74.  */
  75.  
  76. struct {
  77.     int    base;
  78. #ifdef    COND
  79.     int    cond;
  80. #endif    COND
  81.     char    *rule;
  82. } rule[MAXRULES];
  83.  
  84. int    nrules;
  85. char    *lang = "english";        /* You can change this if you like */
  86. char    *malloc ();
  87. unsigned long    parsenumber();
  88. long    atol(), random();
  89.  
  90. int    dbgflag = 0;
  91.  
  92. main (argc, argv)
  93.      char *argv[];
  94. {
  95.         int errflg = 0;
  96.  
  97.     chkdbg ();
  98.     srandom (getpid ());
  99.     domaxdigits ();
  100. /*
  101.  *    Someday, maybe, I'll enable this to take a number
  102.  *    on the command line.
  103.  */
  104.  
  105.         switch (argc) {
  106.     case 1:    break;
  107.     case 2:    lang = argv[1];    break;
  108.     default:
  109.         errflg++;
  110.             break;
  111.     }
  112.  
  113.     if (errflg)
  114.     {
  115.             fprintf (stderr, "Usage: number [language]\n");
  116.         exit (0);
  117.     }
  118.  
  119. /*
  120.  *    read_grammar finds the grammar for the language and
  121.  *    reads it in.  It exits if it can't find the grammar.
  122.  */
  123.     read_grammar (lang);
  124. /*
  125.  *    Main loop.  Read in numbers.  Make sure that the input
  126.  *    is a number, and spell it in the requested language.
  127.  */
  128.     while (1)
  129.     {
  130.         char lbuf [512];
  131.         register i, l;
  132.         unsigned long u;
  133.         long n;
  134.  
  135.         if (isatty (0))
  136.             printf ("> ");
  137.  
  138.         if (!gets (lbuf))
  139.         {
  140.             break;
  141.         }
  142.  
  143.         if ((l = strlen (lbuf)) > maxdigits)
  144.         {
  145.             printf ("My limit is ");
  146.             for (i = 0; i < maxdigits; i++)
  147.                 putchar ('9');
  148.             putchar ('\n');
  149.             continue;
  150.         } else if (l == 0)
  151.             continue;
  152.  
  153.         n = 0;
  154.         if (sscanf (lbuf, "%ld", &n) != 1)
  155.         {
  156.             printf ("%s is not a non-negative integer.\n", lbuf);
  157.             continue;
  158.         }
  159.  
  160.         if (n < 0)
  161.         {
  162.             printf ("I don't handle negative numbers.\n");
  163.             continue;
  164.         }
  165.  
  166.         sscanf (lbuf, "%ld", &u);
  167.         spell (u, 0);
  168.         outchar ('\n');
  169.     }
  170.     outchar ('\n');
  171. }
  172.  
  173. domaxdigits ()
  174. {
  175.     unsigned long maxint = 0;
  176.     register i;
  177.     char str [128];
  178.  
  179.     for (i = 0; i < sizeof (long) * 8; i++)
  180.         maxint |= 1 << i;
  181.     sprintf (str, "%lu", maxint);
  182.     maxdigits = strlen (str) - 1;
  183.  
  184. dbg ("domaxdigits computes %lu as %d reliable digits.\n", maxint, maxdigits);
  185. }
  186.  
  187. /*
  188.  *    read_to_eol is equivalent to fgets, except that it
  189.  *    reads the string into a temporary buffer, allocates
  190.  *    enough space for it, and copies the string into the
  191.  *    allocated space.  In other words, it does what fgets()
  192.  *    would do if C had proper memory management. :-)
  193.  */
  194.  
  195. char *read_to_eol (fp)
  196.     FILE *fp;
  197. {
  198.     char    *tmpbuf, *cp;
  199.     char    rbuf [512];
  200.     register l = 0;
  201.  
  202.     cp = rbuf;
  203.     while (1)
  204.     {
  205.         fgets (cp, sizeof (rbuf) - l, fp);
  206.         l = strlen (rbuf);
  207.         if (rbuf [l - 2] != '\\')
  208.             break;
  209.         cp = rbuf + l - 2;
  210.         if (getc (fp) != '\t')
  211.         {
  212.             fprintf (stderr, "read_to_eol didn't find a tab\n");
  213.             exit (0);
  214.         }
  215.         if (l >= sizeof (rbuf))
  216.         {
  217.             fprintf (stderr, "rule too long in read_to_eol\n");
  218.             exit (0);
  219.         }
  220.     }
  221.     
  222.     tmpbuf = malloc (l = strlen (rbuf));
  223.     rbuf [l - 1] = '\0';    /* get rid of the newline */
  224.  
  225.     strcpy (tmpbuf, rbuf);
  226.  
  227.     return (tmpbuf);
  228. }
  229.  
  230.  
  231. static char filename[128];
  232.  
  233. /*
  234.  *    Cutesy error messages.  They all say the same thing.
  235.  */
  236.  
  237. char *errorfmt[] =
  238. {
  239.     "No se habla \"%s\".  Se habla:\n",
  240.     "I don't speak \"%s\".  I speak:\n",
  241.     "On ne parle pas \"%s\" ici.  On parle plut[^]ot:\n",
  242.     "Ich kann nicht \"%s\" sprechen.  Ich spreche:\n",
  243.     "Ng[?]o [_]m s[^]ik g[']ong \"%s\" w[`]a.  Ng[?]o s[^]ik:\n",
  244.     "W[?]o b[`]u hu[`]e \"%s\".  W[?]o hu[`]e:\n",
  245.     "\CYR'Ya' n'ye' govor'yu' po-\"%s\".  'Ya' govor'yu':\n",
  246.     "Nt[`]e \"%s\" kan m[`]en.  N[`]e be:\n"
  247. };
  248.  
  249. #define    nerrfmt    8
  250.  
  251. rand (n)
  252. {
  253.     return (random () % n);
  254. }
  255.  
  256. /*
  257.  *    read_grammar depends on a set of grammar files being
  258.  *    found in GRAMMARDIR.  It expects to find a file with
  259.  *    the name of its parameter, which it opens and reads.
  260.  *    If it can't find one, it prints out a message saying
  261.  *    that it doesn't speak the language, and lists the
  262.  *    known languages by exec'ing /bin/ls.  Note that this
  263.  *    is equivalent to exitting.  It simply puts each of
  264.  *    the rules and macros into arrays.  The format of the
  265.  *    rules in the grammar files is:
  266.  *
  267.  *        n \t rule
  268.  *
  269.  *    where "n" is the base unit of the rule, and "rule"
  270.  *    conforms to the syntax described below in evalrule().
  271.  *    Macros definitions are of the form:
  272.  *
  273.  *        / \t c \t rule
  274.  *
  275.  *    where "c" is the character to be expanded.  The character
  276.  *    must not be a reserved character.
  277.  *
  278.  *    Grammars may also contain comment lines, which begin with
  279.  *    a '#'.
  280.  */
  281. read_grammar (lang)
  282.     char *lang;
  283. {
  284.     register i, c;
  285.     FILE *fp;
  286.  
  287.     strcat (filename, GRAMMARDIR);
  288.     strcat (filename, lang);
  289.  
  290.     if ((fp = fopen (filename, "r")) == NULL)
  291.     {
  292.         if ((fp = fopen (lang, "r")) == NULL)
  293.         {
  294.             printf (errorfmt [rand (nerrfmt)], lang);
  295.             execl ("/bin/ls", "number-ls", GRAMMARDIR, 0);
  296.         }
  297.     }
  298.  
  299.     for (i = 0; !feof (fp);)
  300.     {
  301. #ifdef    COND
  302.         rule[i].cond = 0;
  303. #endif    COND
  304.  
  305.         if ((c = getc (fp)) == '/')
  306.         {
  307.             register j;
  308.  
  309.             while ((c = getc (fp)) == '\t')
  310.                 ;
  311.             j = nspecials++;
  312.             specials[j].c = c;
  313.             while ((c = getc (fp)) == '\t')
  314.                 ;
  315.             ungetc (c, fp);
  316.             specials[j].rule = read_to_eol (fp);
  317.  
  318. dbg ("macro '%c': %s\n", specials[j].c, specials[j].rule);
  319.  
  320.             continue;
  321.         } else if (c == EOF)
  322.         {
  323.             break;
  324.         } else if (c == '\n')
  325.         {
  326.             continue;
  327.         } else if (c == '#')
  328.         {
  329.             while (getc (fp) != '\n')
  330.                 ;
  331.             continue;
  332.         } else if (!isdigit (c))
  333.         {
  334.             printf ("Read a '%c' in rule %d\n", c, i);
  335.             break;
  336.         } else
  337.             ungetc (c, fp);
  338.  
  339.         if (fscanf (fp, "%d", &rule[i].base) != 1)
  340.             break;
  341.  
  342.         if ((c = getc (fp)) != '\t')
  343.         {
  344. #ifdef COND
  345.             rule[i].cond = c;
  346. #endif COND
  347.             while (getc (fp) != '\t')
  348.                 ;
  349.         }
  350.  
  351.         rule[i].rule = read_to_eol (fp);
  352.  
  353. dbg ("rule %d: %d %s\n", i, rule[i].base, rule[i].rule);
  354.  
  355.         i++;
  356.     }
  357.     nrules = i;
  358. }
  359.  
  360. /*
  361.  *    spell is the function called to spell a number.  It
  362.  *    is initially called with condition 'I' (init).  This
  363.  *    is a hack to get around the problem of when to pronounce
  364.  *    0.  Spell essentially just figures out what the appropriate
  365.  *    rule is, and calls evalrule() to do the work.
  366.  */
  367. spell (n, level)
  368.     unsigned long n;
  369. {
  370.     register i;
  371.  
  372.     if (n == 0 && level)
  373.         return;
  374.  
  375.     for (i = nrules - 1; rule[i].base > n; i--)
  376.         ;
  377.  
  378.     evalrule (rule[i].rule, rule[i].base, n, level);
  379. }
  380.  
  381. /*
  382.  * next
  383.  *    This is a simple function to bounce around in strings
  384.  *    with a syntax that includes balanced parens and double
  385.  *    quotes. There's something like this in Icon, but this
  386.  *    program is in C, so...
  387.  */
  388. char *next (s, c)
  389.     char *s, c;
  390. {
  391.     register char *e;
  392.  
  393.     for (e = s; *e != c; e++)
  394.     {
  395.         if (*e == '"')
  396.             e = next (e + 1, '"');
  397.         if (*e == '(' && c != '"')
  398.             e = next (e + 1, ')');
  399.     }
  400.     return (e);
  401. }
  402.  
  403. /*
  404.  *    evalrule does the dirty work.  It takes a rule, a
  405.  *    base, and a number, and prints the number according
  406.  *    to the rule.  Rules may use the following characters:
  407.  *
  408.  *    B    the base
  409.  *    %    n % base
  410.  *    /    n / base
  411.  *    ,    no-op
  412.  *    "..."    for strings
  413.  *
  414.  *    conditionals are of the form:
  415.  *
  416.  *        (L C R \t rule)
  417.  *
  418.  *    where L and R are either a special character or a
  419.  *    number, and C is one of '>', '<', '=' and '~', meaning,
  420.  *    of course, less than, greater than, equal, and not equal.
  421.  *    Conditionals are evaluated by doconditional(), which
  422.  *    evaluates the condition, and, if it is true, evaluates
  423.  *    the rule.
  424.  *
  425.  *    To give an example of a rule, taken from the grammar
  426.  *    for mandarin:
  427.  *
  428.  *    10    / "sh[']i" %
  429.  *
  430.  *    means that if the largest number that is smaller than
  431.  *    the number we're trying to say is 10, then we say the
  432.  *    number by saying the number divided by 10, followed
  433.  *    by the word "sh[']i", followed by the remainder of the
  434.  *    number divided by ten.  In other words, to say 23,
  435.  *    you say (23 / 10) = 2, then "sh[']i", then (23 % 10) = 3,
  436.  *    or 2 "sh[']i" 3.  After evaluating the rules for 2 and
  437.  *    3, the string "e[`]r sh[']i s[^]an" is printed.
  438.  */
  439.  
  440. evalrule (rule, base, n, level)
  441.     char    *rule;
  442.     unsigned long    n;
  443.     int    base, level;
  444. {    
  445.     register j, c;
  446.  
  447. dbg ("evalrule (\"%s\", %d, %ld)\n", rule, base, n);
  448.  
  449.     while (c = *rule)
  450.     {
  451.         if (isdigit (c))
  452.         {
  453.             spell (atol (rule), level + 1);
  454.             while (isdigit (*++rule))
  455.                 ;
  456.             continue;
  457.         } else switch (c) {
  458.         case ',':    break;
  459.         case 'B':    spell ((long) base, level + 1);    break;
  460.         case '%':    spell (n % base, level + 1);    break;
  461.         case '/':    spell (n / base, level + 1);    break;
  462.  
  463.         case '"':    while ((c = *++rule) != '"')
  464.                     outchar (c);
  465.                 break;
  466.         case '(':    docondition (rule, base, n, level);
  467.                 rule = next (rule + 1, ')');
  468.                 break;
  469.         default:    for (j = 0; j < nspecials; j++)
  470.                 {
  471.                     if (specials[j].c == c)
  472.                     {
  473.                     evalrule (specials[j].rule, base,
  474.                           n, level);
  475.                     break;
  476.                     }
  477.                 }
  478.                 if (j == nspecials)
  479.                     outchar (c);
  480.         }
  481.         rule++;
  482.     }
  483. }
  484. /*
  485.  *    docondition evaluates conditionals, which are delimited
  486.  *    by parentheses, and which contain two parts: a very
  487.  *    simple Boolean expression and a rule.  The Boolean
  488.  *    expression can, at the moment, only be a simple comparison.
  489.  *    OR's (if the conditions are exclusive) can be done by
  490.  *    putting multiple conditions in a row, and AND's by
  491.  *    making the rule a conditional.  docondition calls
  492.  *    parsecond (parse conditional) to pick out the various
  493.  *    parts of the conditional, evaluates the comparison,
  494.  *    and calls evalrule with the rule as an argument if the
  495.  *    comparison evaluates to true.
  496.  *
  497.  *    Two additional special characters that are accepted here
  498.  *    are:
  499.  *
  500.  *        L    Current recursion level
  501.  *        #    The number itself
  502.  */
  503. docondition (rule, base, n, level)
  504.     char    *rule;
  505.     unsigned long    n;
  506.     int    base, level;
  507. {
  508.     char    subrule [128];
  509.     unsigned long    leftside, rightside;
  510.     int    truth;
  511.     char    comparator;
  512.  
  513. /*
  514.  *    This is to check for bad grammars or buggy parser.
  515.  */
  516.     if (!parsecond (rule, base, n, level,
  517.             &leftside, &comparator, &rightside, subrule))
  518.     {
  519.         printf ("Gagged on rule \"%s\"\n", rule);
  520.         return;
  521.     }
  522.  
  523.     switch (comparator) {
  524.     case '>':    truth = leftside > rightside;    break;
  525.     case '=':    truth = leftside == rightside;    break;
  526.     case '<':    truth = leftside < rightside;    break;
  527.     case '~':    truth = leftside != rightside;    break;
  528.     }
  529.  
  530. dbg ("docondition (%d, %d, %d %c %d) -> %s\n",
  531.     base, n, leftside, comparator, rightside,
  532.     truth ? subrule : "FAILS");
  533.  
  534.     if (!truth)
  535.         return;
  536.  
  537.     evalrule (subrule, base, n, level);
  538. }
  539.  
  540. /*
  541.  *    parsecond parses the rule according to the base,
  542.  *    and assigns the parts to the variables passed
  543.  *    as arguments.
  544.  */
  545.  
  546. parsecond (rule, base, n, level, lp, cp, rp, subrule)
  547.     char    *rule, *cp, *subrule;
  548.     unsigned long    *lp, *rp, n;
  549.     int    base, level;
  550. {
  551.     char    *index(), *rindex();
  552.     register char *start, *end;
  553.     char    leftstring[20], rightstring[20];
  554.  
  555.     if (sscanf (rule, "(%s %c %s", leftstring, cp, rightstring) != 3)
  556.     {
  557.  
  558. dbg ("parsecond failed sscanf (\"%s\", ...)\n", rule);
  559.  
  560.         return (0);
  561.     }
  562.  
  563.     *rp = parsenumber (rightstring, base, n, level);
  564.     *lp = parsenumber (leftstring, base, n, level);
  565.  
  566.     if (!(start = index (rule, '\t')))
  567.     {
  568.  
  569. dbg ("parsecond couldn't find a tab in \"%s\"\n", rule);
  570.  
  571.         return (0);
  572.     }
  573.  
  574.     end = next (++start, ')');
  575.  
  576.     while (start < end)
  577.         *subrule++ = *start++;
  578.         
  579.     *subrule = '\0';
  580.     return (1);
  581. }
  582.  
  583. /*
  584.  *    parsenumber figures out the numerical value of the
  585.  *    string that it is passed, based on the base and the
  586.  *    number n.
  587.  */
  588.  
  589. unsigned long parsenumber (s, base, n, level)
  590.     unsigned long n;
  591.     char *s;
  592. {
  593.     if (isdigit (s[0]))
  594.         return (atoi (s));
  595.  
  596.     switch (s[0]) {
  597.     case '/':    return (n / base);
  598.     case '%':    return (n % base);
  599.     case '#':    return (n);
  600.     case 'L':    return (level);
  601.     case 'B':    return (base);
  602.     default:    fprintf (stderr, "bad number string \"%s\"\n", s);
  603.             return (-1);
  604.     }
  605. }
  606.  
  607. /*
  608.  *    outchar is a slightly clever version of putchar.  It
  609.  *    won't put a space at the beginning of a line, and it
  610.  *    won't put two spaces in a row.
  611.  */
  612.  
  613. outchar (c)
  614. {
  615.     static    lastspace = 0,
  616.         bol = 1;
  617.  
  618.     if ((lastspace || bol) && c == ' ')
  619.         return;
  620.  
  621.     if (c == '\n')
  622.         bol = 1;
  623.     else
  624.         bol = 0;
  625.  
  626.     if (c == ' ')
  627.         lastspace = 1;
  628.     else
  629.         lastspace = 0;
  630.  
  631.     putchar (c);
  632. }
  633.  
  634. /*
  635.  *    Well, see, I had this bug, and I left my debugger in
  636.  *    Chicago, and...
  637.  */
  638.  
  639. dbg (fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
  640.     char *fmt, *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8, *a9;
  641. {
  642.     int tmpdbgflag = dbgflag;
  643.  
  644.     if (dbgflag > 0)
  645.     {
  646.         dbgflag = 0;
  647.         fprintf (stderr, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
  648.         dbgflag = tmpdbgflag;
  649.     }
  650. }
  651.  
  652. chkdbg ()
  653. {
  654.     extern char *getenv ();
  655.     register char *cp;
  656.  
  657.     if ((dbgflag == 0) && (cp = getenv ("DEBUG=")))
  658.         dbgflag = atoi (cp);
  659. }
  660.