MMIX assembly language floating point number preprocessor – GNU Bison and GNU Flex version

Here’s a GNU Bison and GNU Flex version of my MMIX assembly language floating point number preprocessor. The code was developed using GNU Bison version 1.875d and GNU Flex version 2.5.

This version allows for entering more complex expressions of floating point numbers involving addition, subtraction, multiplication and division. Extending the grammar to cover trigonometric functions and square root is left as an exercise to the reader.

* Here are some constants from Don Knuth's world:
MIX			FLOAT	1009	comments are still allowed
MMIX			FLOAT	2009	after the operand field

* Here are some well known constants from the mathematical world:
Pi			FLOAT	3.14159265358979323846	TeX's ultimate version number
e			FLOAT	2.7182818284590452354	ditto for METAFONT

* Here are lists of simple numbers and some irrational numbers:
ListOfNumbers		FLOAT	1,2,3
Expressions		FLOAT	1/3,2/3

* 32-bit floating point numbers are also available.
* But do not use them if you do not need them,
* FLOATs have better properties than SINGLEs.
MIX32			SINGLE	1009
MMIX32			SINGLE	2009
NextMMIX		SINGLE	2009+1000	the next MMIX?

%{
/*
  mmixalfpp-parser.y - yacc parser for mmixalfpp

  Copyright © 2004 Trond Endrestøl <Trond.Endrestol@ximalas.info>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  MA 02111-1307 USA.
*/

#define YYDEBUG 0
#define YYERROR_VERBOSE 1

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int yylex(void);
void yyerror(char const *);
void print_hex(double d);

#define isletter(c) (isalpha((c)) || (c) == '_' || (c) == ':' || (c) > 126)
#define iswhitespace(c) ((c) == ' ' || (c) == '\t')

enum floattype_t { SINGLE, FLOAT } floattype;
union { float f; unsigned char b[4]; } singleprec;
union { double d; unsigned char b[8]; } doubleprec;

FILE *infile, *outfile;
const size_t bufferlen = 1024;

unsigned char *buffer, *keyword, *comment, *p;

char *progname, *infilename;
extern int yylineno;

%}

%union {
  double d;
}

%token <d> NUMBER
%token     COMMA
%left      MINUS    PLUS
%left      ASTERISK SLASH
%left      UMINUS
%token     LPAREN
%token     RPAREN

%type <d> input
%type <d> expr

%%

input:		  expr             { print_hex($1); }
		| input COMMA expr { fputc(',', outfile); print_hex($3); }
		| error expr       { $$ = $2; yyerrok; }
		;

expr:		  NUMBER                   { $$ = $1; }
		| PLUS   expr %prec UMINUS { $$ = $2; }
		| MINUS  expr %prec UMINUS { $$ = -$2; }
		| expr   PLUS     expr     { $$ = $1 + $3; }
		| expr   MINUS    expr     { $$ = $1 - $3; }
		| expr   ASTERISK expr     { $$ = $1 * $3; }
		| expr   SLASH    expr     { $$ = $1 / $3; }
		| LPAREN expr     RPAREN   { $$ = $2; }
		| LPAREN error    RPAREN   { yyerrok; }
		;

%%

void yyerror(char const *s)
{
  fprintf(stderr,
          "%s: %s: %d: %s\n",
          progname, infilename, yylineno, s);
} /* yyerror() */

void print_hex(double d)
{
  fputc('#', outfile);

  if (floattype == FLOAT) {
    doubleprec.d = d;
#if BYTE_ORDER == LITTLE_ENDIAN
    fprintf(outfile, "%02x%02x%02x%02x%02x%02x%02x%02x",
            doubleprec.b[7], doubleprec.b[6], doubleprec.b[5], doubleprec.b[4],
            doubleprec.b[3], doubleprec.b[2], doubleprec.b[1], doubleprec.b[0]);
#elif BYTE_ORDER == BIG_ENDIAN
    fprintf(outfile, "%02x%02x%02x%02x%02x%02x%02x%02x",
            doubleprec.b[0], doubleprec.b[1], doubleprec.b[2], doubleprec.b[3],
            doubleprec.b[4], doubleprec.b[5], doubleprec.b[6], doubleprec.b[7]);
#else
#error unknown endian
#endif
  } /* if */
  else {
    singleprec.f = d;
#if BYTE_ORDER == LITTLE_ENDIAN
    fprintf(outfile, "%02x%02x%02x%02x",
            singleprec.b[3], singleprec.b[2], singleprec.b[1], singleprec.b[0]);
#elif BYTE_ORDER == BIG_ENDIAN
    fprintf(outfile, "%02x%02x%02x%02x",
            singleprec.b[0], singleprec.b[1], singleprec.b[2], singleprec.b[3]);
#endif
  } /* else*/
} /* print_hex() */

void process_line(void)
{
  p = buffer;

  while (p != keyword) { /* Print all before keyword */
    fputc(*p++, outfile);
  } /* while */

  p += 5; /* Skip keyword */

  if (floattype == FLOAT) {
    fputs("OCTA", outfile);
  } /* if */
  else {
    p++; /* Skip the E in SINGLE */
    fputs("TETRA", outfile);
  } /* else */

  while (iswhitespace(*p)) { /* Print the whitespace after the keyword */
    fputc(*p++, outfile);
  } /* while */

  if ( (comment = strchr(p, '\t')) == NULL) { /* Scan for the comment field, a newline, or EOL */
    if ( (comment = strchr(p, ' ')) == NULL) {
      if ( (comment = strchr(p, '\n')) == NULL) {
        comment = strchr(p, '\0');
      } /* if */
    } /* if */
  } /* if */

  yyparse(); /* Get the ball running */

  while (*p != '\0') { /* Print the remainder of the line, if any */
    fputc(*p++, outfile);
  } /* while */
} /* process_line() */

int main(int argc, char **argv)
{
  progname = argv[0];

  if (argc < 3) {
    fprintf(stderr, "Usage: %s <infile | - > <outfile | - >\n", progname);
    return EXIT_FAILURE;
  } /* if */

  if (!strcmp(argv[1], "-")) {
    infile = stdin;
    infilename = "stdin";
  } /* if */
  else {
    infilename = argv[1];

    if ( (infile = fopen(infilename, "r")) == NULL) {
      fprintf(stderr, "%s: could not open %s for reading\n", progname, infilename);
      return EXIT_FAILURE;
    } /* if */
  } /* else */

  if (!strcmp(argv[2], "-")) {
    outfile = stdout;
  } /* if */
  else {
    if ( (outfile = fopen(argv[2], "w")) == NULL) {
      fprintf(stderr, "%s: could not open %s for writing\n", progname, argv[2]);
      return EXIT_FAILURE;
    } /* if */
  } /* else */

  if ( (buffer = malloc(bufferlen * sizeof(char))) == NULL) {
    fprintf(stderr,
            "%s: could not allocate %lu bytes for buffer\n",
            progname, (unsigned long)bufferlen);
    return EXIT_FAILURE;
  } /* if */

  while (!feof(infile)) { /* Read until the end of infile */
    if (fgets(buffer, bufferlen, infile) == NULL) { /* Read the next line from infile */
      if (feof(infile)) { /* We are at the end of infile */
        return EXIT_SUCCESS; /* Everything is OK */
      } /* if */
      else {
        fprintf(stderr,
                "%s: could not read line from %s\n",
                progname, argv[1]);
        return EXIT_FAILURE;
      } /* else */
    } /* if */

    if (isletter(buffer[0]) || isdigit(buffer[0]) || iswhitespace(buffer[0])) { /* This line is not a comment */
      if ( (keyword = strstr(buffer, "FLOAT")) != NULL) { /* Found FLOAT */
        floattype = FLOAT;
        process_line();
      } /* if */
      else if ( (keyword = strstr(buffer, "SINGLE")) != NULL) { /* Found SINGLE */
        floattype = SINGLE;
        process_line();
      } /* else if */
      else { /* This line does not contain SINGLE nor FLOAT */
        fputs(buffer, outfile);
      } /* else*/
    } /* if */
    else { /* This line is a comment */
      fputs(buffer, outfile);
    } /* else */

    yylineno++;
  } /* while */

  /* NOTREACHED */
  return EXIT_SUCCESS;
} /* main() */

/* End of mmixalfpp-parser.y */
/*
  mmixalfpp-lexer.l - lexer for mmixalfpp

  Copyright © 2004 Trond Endrestøl <Trond.Endrestol@ximalas.info>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  MA 02111-1307 USA.
*/

%{

#include "mmixalfpp-parser.h"

#include <stdio.h>

extern unsigned char *comment, *p;

#define YY_INPUT(buf,result,max_size) \
  { \
    result = (p == comment) ? YY_NULL : (buf[0] = *p++, 1); \
  }

%}

%option 8bit
%option case-sensitive
%option noyywrap
%option yylineno

digit		[0-9]
number1		{digit}+\.?([eE][-+]?{digit}+)?
number2		{digit}*\.{digit}+([eE][-+]?{digit}+)?
number		{number1}|{number2}

%%

{number}		yylval.d = atof(yytext); return NUMBER;
","			yylval.d = 0; return COMMA;
"+"			yylval.d = 0; return PLUS;
"-"			yylval.d = 0; return MINUS;
"*"			yylval.d = 0; return ASTERISK;
"/"			yylval.d = 0; return SLASH;
"("			yylval.d = 0; return LPAREN;
")"			yylval.d = 0; return RPAREN;

%%

/* End of mmixalfpp-lexer.l */