// This is a 'toy' compiler for use in teaching an intro compilers class
// However although it was originally intended to be a very cut-down language,
// it ended up being quite a significant subset of C - certainly at least as
// powerful as the original K&R C in the early days of Unix.

// It currently lacks typedefs, enums, procedure variables and procedures as
// parameters, and a few minor details such as hex and octal constants

// The compiler generates an AST, which is a framework for the student to
// use to add some optimisations, and from the AST it generates assembly code
// for an imaginary stack machine.  This simple stack code can be expanded
// by a macro assembler into real instructions for a real machine such as
// the x86 range.  Adding a native code generator will be a student exercise.

// The compiler is written in the subset of the language which it accepts.

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

#ifndef FALSE
#define FALSE (0!=0)
#define TRUE (0==0)
#endif

#define MACHINE_DEPENDENT_POINTER_SIZE 4

#define DROP_SPECIAL_BITS ((1 << 14) - 1)
#define OPTIONAL_PHRASE (1<<14)
#define NEGATED_PHRASE (1<<15)


static char prognames[256];
static char *progname;
static char outname[256];
FILE *outfile;

int debug = FALSE;

/*  Parser support */

#define EXTENDED_ERROR_REPORTING TRUE
#include "c.h" // GENERATED GRAMMAR FROM takeon.c and c.g

// Built-in phrase codes.
// B_TAG etc generated by takeon.c

// These are implicit in the parser:
#define TYPE_WHITESPACE (-1)   // skipped internally by parser
#define TYPE_EOF 0
#define TYPE_PPP 2
#define TYPE_MMM 3
#define TYPE_CHAR 4         // any naked untyped single character
#define TYPE_KEYWORD 5      // implicit in the keyword model of the language, represented by "keywords" in the grammar

int bestparse = -1; // for error reporting.
char *looking_for = "<UNKNOWN>";    // 'while looking for <PHRASENAME>' (or literal) ...


/*  Data structures */

  // We use a stringpool, and strings are indexes into this pool.  This
  // is useful for the same reasons that the AST is an array indexed
  // by integers rather than a struct with pointers.  It may also
  // save space by reusing common strings.  And we get a free tag
  // to describe strings.  (Also we can compare strings just with
  // an integer tag comparison, if we ever want to - which is fine
  // if the table is implemented by a hash table; it's somewhat dubious
  // when just a scanned linear list, though with modern CPUs it's
  // barely worth the programmer effort to do anything smarter)

  // not everything uses this yet.  search for strdup etc to find potential problems

#define MAXPOOL (16*1024)
  char stringpool[MAXPOOL];
  int nextstring = 0;

  int str_to_pool(char *s)
  {
    int tag;
    for (tag = 0; tag <= nextstring; tag++) {
	if (strcmp(stringpool+tag, s) == 0) {
	    return tag; /* found, one way or another */
	}
    }
    return 0; /* NOT REACHED */
  }


  /*  c[] is the source character token stream */

// this struct can be replaced by parallel arrays for the purpose of
// simplifying the language in which this compiler is written, in order
// to bootstrap itself.

  struct sourceinfo { // ATOMS for processed input stream
    char *s; // string contents
    int l;   // lineno
    int col; // column
    int t;   // type - tag, "string", 'charconst', or char, so far
    char *f; // source or includefile  name
  };

  static struct sourceinfo c[128*1024];
  int nextfree = 0, arraysize = 128*1024;
  char onecharstr[2*256];
  

  /*  A[] is the Analysis record.  Contents point to a C[] structure when the item is a BIP,
     otherwise the format is [phraseno] [altno] [no-of-subphrases] [subphrases and/or BIPs...]
     for example, if A[25] contained 10, that would mean that the token for A[25] was stored
     in C[10].  (And the C string would be at &stringpool[c[A[25]]]?) */
  static int A[2*1024*1024];
  int next_free_a = 0, a_size = 2*1024*1024;
  

  /* variables used by line-reconstruction (lexer) */
  FILE *sourcefile;
  char *curfile;
  int startline = TRUE, whitespace = TRUE, lineno = 1, col = 0, ch, peek;
  
#include "lexer.c"
#include "parser.c"
#include "ast.h"
#include "ast.c"
#define fixup(x) while (astop(x) == AST_REDIRECT) x = leftchild(x)
#ifdef DEBUG_DRAW_TREES
#include "drawtree.c"
#else
void draw_tree(int root) { }
void draw_tree_orig(int root) { }
#endif
#include "types.c"
#include "blockscope.c"
#include "nametable.c"
#include "grammar.c"
#include "show_ast.c"
//#include "walk.c"
#include "fold.c"
#include "identity.c"
#include "fix_structs.c"
#include "insert_casts.c"
#include "insert_offsets.c"
#include "loops.c"
#include "switch.c"
#include "procfn.c"
#include "booleans.c"
#include "genstack.c"

int main(int argc, char **argv) {
  int opt_3address = FALSE, opt_debug = FALSE, opt_stack = FALSE, opt_c = FALSE, opt_execute = FALSE, opt_optimiser = FALSE;
  char *s;

  /*  Handle program arguments */

  /*  Get clean version of executable name.  Should work on most existing systems (2006) */
  strcpy(prognames, argv[0]); progname = prognames;
  if ((s = strrchr(progname, '/')) != NULL) progname = s+1;  // Unix
  if ((s = strrchr(progname, '\\')) != NULL) progname = s+1; // M$
  if ((s = strrchr(progname, ']')) != NULL) progname = s+1;  // Dec
  if ((s = strrchr(progname, ';')) != NULL) *s = '\0';       // Version no's
  if (((s = strrchr(progname, '.')) != NULL) && (strcasecmp(s, ".exe") == 0)) *s = '\0';
  if (((s = strrchr(progname, '.')) != NULL) && (strcasecmp(s, ".com") == 0)) *s = '\0';


 moreopt:
  if ((argc >= 2) && (*argv[1] == '-') && (argv[1][2] == '\0') ) {
  if (argv[1][1] == 'O') {
    argv++; argc--; opt_optimiser = TRUE; goto moreopt;
  }

  if (argv[1][1] == 'd') {
    argv++; argc--; debug = TRUE; opt_debug = TRUE; goto moreopt;
  }

  if (argv[1][1] == '3') {
    argv++; argc--; opt_3address = TRUE; goto moreopt;
  }

  if (argv[1][1] == 's') {
    argv++; argc--; opt_stack = TRUE; goto moreopt;
  }

  if (argv[1][1] == 'c') {
    argv++; argc--; opt_c = TRUE; goto moreopt;
  }

  if (argv[1][1] == 'e') {
    argv++; argc--; opt_execute = TRUE; goto moreopt;
  }

  if (argv[1][1] == 'h') {
      fprintf(stderr, "%s:\n", progname);
      fprintf(stderr, "\t-3\tgenerate low-level 3-address code using c\n");
      fprintf(stderr, "\t-c\tgenerate high-level c translation\n");
      fprintf(stderr, "\t-s\tgenerate stack-based assembly code\n");
      fprintf(stderr, "\t-e\texecute directly\n");
      fprintf(stderr, "\t-d\tdebug\n");
      fprintf(stderr, "\t-h\thelp (this info)\n");
      exit(0);
  }
}
  if (argc != 2) {
    fprintf(stderr, "syntax: %s [-3cdehs] filename\n", progname);
    exit(1);
  }

  if (!(opt_3address || opt_c || opt_execute || opt_stack)) opt_stack = TRUE;

  if (argv[1][0] == '-' && argv[1][1] == '\0') {
    sourcefile = fopen("/dev/null", "r");
  } else {
    sourcefile = fopen(argv[1], "r");
  }
  if (sourcefile == NULL) {
    fprintf(stderr, "%s: %s - %s\n", progname, strerror(errno), argv[1]);
    exit(errno);
  }
  if (opt_execute) {
    outfile = stdout;
  } else {
    char *s;
    sprintf(outname, "%s", argv[1]);
    s = strrchr(outname, '.');
    if (s == NULL) s = outname+strlen(outname);
    if (opt_3address || opt_c) {
      sprintf(s, "%s", ".c");
    } else if (opt_stack) {
      sprintf(s, "%s", ".asm");
    } else {
      fprintf(stderr, "Won't\n"); exit(123); // shouldn't happen
    }
    //outfile = fopen(outname, "w");
    //if (outfile == NULL) {
    //  fprintf(stderr, "%s: cannot output to %s - %s\n", progname, outname, strerror(errno));
    //}
  }

  curfile = argv[1]; startline = TRUE; whitespace = TRUE;

  /*  Lexical scan */
  line_reconstruction(); // Effectively, lexing.
  dump_source();

  /*  Call the parser */

  //resume:
  if (!parse(PHRASE_BASE, 0)) {
    /*  Attempt to print a sensible error if the parse failed */
    if (bestparse == nextfree) {
      fprintf(stderr, "\"%s\", Line %d, Col %d: Premature end of file while looking for %s\n",
                       argv[1], c[bestparse].l, c[bestparse].col+1, looking_for);
      exit(1);
    } else {
      error_line(c[bestparse].l);
      fprintf(stderr, "\"%s\", Line %d, Col %d: Syntax error while looking for %s near ",
                       argv[1], c[bestparse].l, c[bestparse].col+1, looking_for);
      fprintf(stderr, "%s\n", escape(c[bestparse].s,c[bestparse].t));
      exit(1);
    }
  } else {
    int rslt_errs = 0;
    int T[4];
    int program;

    program = build_ast(0); // only compile if all the source parsed OK

    T[1] = program;
    T[2] = -1; // not a hole.  This is a terminator.
    T[3] = BLOCK_EXTERNALS;
    program = mk(AST_Scope, 3, T);

    {int count;
      do {
        count = fold_constant_expressions(program); while (astop(program) == AST_REDIRECT) program = leftchild(program);
        count += fold_identities(program); while (astop(program) == AST_REDIRECT) program = leftchild(program);
      } while (count > 0);
    }

    fprintf(stderr, "assign_loop_labels(%d)\n", program);
    assign_loop_labels(program, -1, -1, -1); while (astop(program) == AST_REDIRECT) program = leftchild(program);

    fprintf(stderr, "build_switch_table()\n");
    build_switch_table(program, -1);  // must come after folding

    fprintf(stderr, "build_block_scope()\n");
    build_block_scope(program, -1, -1);

    // print ast here
    //    print_all_trips();

    fprintf(stderr, "fix_structs()\n"); // handle tags that are pointers to struct types
    fix_structs(program); // must be after block scopes.

    fprintf(stderr, "check_function_results()\n");
    check_function_results(program, -1, &rslt_errs);

    fprintf(stderr, "insert_offsets()\n"); // insert field offsets, array/struct sizes.
    insert_offsets(program); // must be after block scopes.

    fprintf(stderr, "insert_casts()\n");
    insert_casts(program); // must be after block scopes.

    // folding should really be in two phases - 'must fold' and 'may fold'.
    // 'must fold' is done first, so that constant initialisers and case labels etc
    // are forced to be constants if they were entered as constant expressions;
    // 'may fold' are the other cases, and those ones require proper type
    // information, to handle the usual nasty signed/unsigned/width cases.


    fprintf(stderr, "tweak_booleans()\n");
    tweak_booleans(program); while (astop(program) == AST_REDIRECT) program = leftchild(program);

    show_ast(program, 0, -1); // in pre-order traverse order

#ifdef DEBUG_DRAW_TREES
    //    draw_selected_trees(program);
#endif
    stack_data(); stack_code(program, -1, -1, -1);

    fflush(stdout);
  }

  exit(0);
  /* NOT REACHED */ return(1);
}
