%{
/*
 * mwrap.y
 *   Parser for mwrap.
 *
 * Copyright (c) 2007  David Bindel
 * See the file COPYING for copying permissions
 */

#include <stdlib.h>
#include <string.h>
#include <string>
#include "mwrap-ast.h"

extern "C" {
    int yylex();
    int yywrap();
    int yyerror(const char* s);
}

using std::string;

bool  mw_use_gpu = 0;         // Use GPU?
bool  mw_generate_catch = false;  // Catch C++ exceptions?
bool  mw_use_cpp_complex = false; // Use C++ complex types?
bool  mw_use_c99_complex = false; // Use C99 complex types?
int   mw_promote_int = 0;     // Convert integer types to mwSize?
int   mw_use_int32_t = 0;     // Use C99 int32_t?
int   mw_use_int64_t = 0;     // Use C99 int64_t?
int   mw_use_uint32_t = 0;     // Use C99 uint32_t?
int   mw_use_uint64_t = 0;     // Use C99 uint64_t?
int   mw_use_ulong = 0;     // Use unsigned long?
int   mw_use_uint = 0;     // Use unsigned int?
int   mw_use_ushort = 0;     // Use unsigned short?
int   mw_use_uchar = 0;     // Use unsigned char?
int   listing_flag = 0;           // Output filenames from @ commands?
int   mbatching_flag = 0;         // Output on @ commands?
int   linenum = 0;                // Lexer line number
FILE* outfp   = 0;                // MATLAB output file
FILE* outcfp  = 0;                // C output file

static int    type_errs = 0;            // Number of typecheck errors
static int    func_id = 0;              // Assign stub numbers
static Func*  funcs   = 0;              // AST - linked list of functions
static Func*  lastfunc = 0;             // Last link in funcs list
static const char*  mexfunc = "mexfunction";  // Name of mex function
static string current_ifname;           // Current input file name


#define MAX_INCLUDE_DEPTH 10
static string include_stack_names[MAX_INCLUDE_DEPTH];
extern int include_stack_ptr;

extern "C" void set_include_name(const char* s)
{
    include_stack_names[include_stack_ptr] = current_ifname;
    current_ifname = s;
}

extern "C" void get_include_name()
{
    current_ifname = include_stack_names[include_stack_ptr].c_str();
}


inline void add_func(Func* func)
{
    static std::map<string,Func*> func_lookup;
    if (!funcs) {
        funcs = func;
        lastfunc = func;
        return;
    } 

    Func*& func_ptr = func_lookup[id_string(func)];
    if (func_ptr) {
        func_ptr->same_next = func;
    } else {
        lastfunc->next = func;
        lastfunc = func;
    }
    func_ptr = func;
}

%}

%union {
    char* string;
    struct Func* func;
    struct Var* var;
    struct TypeQual* qual;
    struct Expr* expr;
    struct InheritsDecl* inherits;
    char c;
}

%token NON_C_LINE
%token NEW TYPEDEF CLASS FORTRAN
%token <string> ID 
%token <string> NUMBER STRING
%token <char> INPUT OUTPUT INOUT
%token <char> CPU GPU

%type <func> func funcall
%type <var>  var basevar args argsrest
%type <c>    iospec
%type <c>    devicespec
%type <qual> quals aqual
%type <expr> arrayspec exprs exprrest expr
%type <inherits> inheritslist inheritsrest

%error-verbose

%%
statements: statement statements | ;

statement:
  basevar '=' funcall { 
      $3->ret = $1; 
      $3->id = ++func_id;
      type_errs += typecheck($3, linenum);
      if (outfp)
          print_matlab_call(outfp, $3, mexfunc); 
      add_func($3);
  }
  | funcall { 
      $1->id = ++func_id;
      type_errs += typecheck($1, linenum);
      if (outfp)
          print_matlab_call(outfp, $1, mexfunc); 
      add_func($1);
  } 
  | tdef 
  | classdef 
  | NON_C_LINE 
  | error ';' { yyerrok; } ;

tdef: 
  TYPEDEF ID ID ';' { 
      if (strcmp($2, "numeric") == 0) {
          add_scalar_type($3);
      } else if (strcmp($2, "dcomplex") == 0) {
          add_zscalar_type($3);
      } else if (strcmp($2, "fcomplex") == 0) {
          add_cscalar_type($3);
      } else if (strcmp($2, "mxArray") == 0) {
          add_mxarray_type($3);
      } else {
          fprintf(stderr, "Unrecognized typespace: %s\n", $2);
          ++type_errs;
      }
      delete[] $2;
      delete[] $3;
  } ;

classdef:
  CLASS ID ':' inheritslist ';' {
      add_inherits($2, $4);
      delete[] $2;
      destroy($4);
  }

inheritslist:
  ID inheritsrest { $$ = new InheritsDecl($1, $2); } ;

inheritsrest:
  ',' ID inheritsrest { $$ = new InheritsDecl($2, $3); }
  | { $$ = NULL; } ;

funcall: func '(' args ')' ';' { $$ = $1; $$->args = $3; } ;

args:
  var argsrest { $$ = $1; $$->next = $2; }
  | { $$ = NULL; } ;

argsrest:
  ',' var argsrest {$$ = $2; $$->next = $3; }
  | { $$ = NULL; } ;

basevar: ID ID               { $$ = new Var('c', 'o', promote_int($1), NULL, $2); }
basevar: ID quals ID         { $$ = new Var('c', 'o', promote_int($1), $2,   $3); }
basevar: ID ID aqual         { $$ = new Var('c', 'o', promote_int($1), $3,   $2); }

var: devicespec iospec ID ID            { $$ = new Var($1, $2,  promote_int($3), NULL, $4); }
var: devicespec iospec ID quals ID      { $$ = new Var($1, $2,  promote_int($3), $4,   $5); }
var: devicespec iospec ID ID aqual      { $$ = new Var($1, $2,  promote_int($3), $5,   $4); }

var: devicespec iospec ID NUMBER        { $$ = new Var($1, $2,  promote_int($3), NULL, $4); }
var: devicespec iospec ID quals NUMBER  { $$ = new Var($1, $2,  promote_int($3), $4,   $5); }

var: devicespec iospec ID STRING        { $$ = new Var($1, $2,  promote_int($3), NULL, $4); }
var: devicespec iospec ID quals STRING  { $$ = new Var($1, $2,  promote_int($3), $4,   $5); }

devicespec: 
  CPU   { $$ = 'c'; }
  | GPU { $$ = 'g'; }
  |     { $$ = 'c'; } ;

iospec:
  INPUT    { $$ = 'i'; }
  | OUTPUT { $$ = 'o'; }
  | INOUT  { $$ = 'b'; }
  |        { $$ = 'i'; } ;

quals: 
  '*'         { $$ = new TypeQual('*', NULL); }
  | '&'       { $$ = new TypeQual('&', NULL); } 
  | aqual     { $$ = $1; } ;

aqual:
  arrayspec       { $$ = new TypeQual('a', $1); } 
  | arrayspec '&' { $$ = new TypeQual('r', $1); } ;

arrayspec: '[' exprs ']' { $$ = $2; } ;

exprs: 
  expr exprrest { $$ = $1; $$->next = $2; }
  |             { $$ = NULL; } 

exprrest: 
  ',' expr exprrest { $$ = $2; $$->next = $3; }
  |                 { $$ = NULL; }

expr: 
  ID       { $$ = new Expr($1); }
  | NUMBER { $$ = new Expr($1); }

func: 
  ID '-' '>' ID '.' ID { $$ = new Func($1, $4, $6, current_ifname, linenum); }
  | ID          { $$ = new Func(NULL, NULL, $1, current_ifname, linenum); } 
  | FORTRAN ID  { $$ = new Func(NULL, NULL, $2, current_ifname, linenum); 
                  $$->fort = true;
                } 
  | NEW ID  { $$ = new Func(NULL, $2, mwrap_strdup("new"), 
                          current_ifname, linenum); 
            }
  ;

%%
#include <stdio.h>
#include <string.h>

extern FILE* yyin;

int yywrap()
{
    return 1;
}

int yyerror(const char* s)
{
    fprintf(stderr, "Parse error (%s:%d): %s\n", current_ifname.c_str(),
            linenum, s);
    return 0;
}

char* mwrap_strdup(const char* s)
{
    char* result = new char[strlen(s)+1];
    strcpy(result, s);
    return result;
}

const char* usage_string =
"Usage: mwrap [-mex outputmex] [-m output.m] [-c outputmex.c] infile1 ...\n"
"Try 'mwrap --help' for more information.\n";

const char* help_string =
"mwrap 1.3 - MEX file generator for MATLAB and Octave\n"
"\n"
"Syntax:\n"
"  mwrap [-mex outputmex] [-m output.m] [-c outputmex.c] [-mb] [-list]\n"
"        [-catch] [-i8] [-c99complex] [-cppcomplex] [-gpu] infile1 infile2 ...\n"
"\n"
"  -mex outputmex -- specify the MATLAB mex function name\n"
"  -m output.m    -- generate the MATLAB stub called output.m\n"
"  -c outputmex.c -- generate the C file outputmex.c\n"
"  -mb            -- generate .m files specified with @ redirections\n"
"  -list          -- list files specified with @ redirections\n"
"  -catch         -- generate C++ exception handling code\n"
"  -i8            -- convert int, long, uint, ulong to int64_t, uint64_t\n"
"  -c99complex    -- add support code for C99 complex types\n"
"  -cppcomplex    -- add support code for C++ complex types\n"
"  -gpu           -- add support code for MATLAB gpuArray\n"
"\n";

int main(int argc, char** argv)
{
    int j;
    int err_flag = 0;
    init_scalar_types();

    if (argc == 1) {
        fprintf(stderr, "%s", help_string);
        return 0;
    } else {
        bool emitted_mex_init = false;
        const char* mfile = NULL;
        const char* cfile = NULL;

        for (j = 1; j < argc; ++j) {
            if (strcmp(argv[j], "--help") == 0) {
                fprintf(stderr, "%s", help_string);
                return 0;
            }
            if (strcmp(argv[j], "-m") == 0 && j+1 < argc)
                mfile = argv[++j];
            else if (strcmp(argv[j], "-c") == 0 && j+1 < argc)
                cfile = argv[++j];
            else if (strcmp(argv[j], "-mex") == 0 && j+1 < argc)
                mexfunc = argv[++j];
            else if (strcmp(argv[j], "-mb") == 0)
                mbatching_flag = 1;
            else if (strcmp(argv[j], "-list") == 0)
                listing_flag = 1;
            else if (strcmp(argv[j], "-catch") == 0)
                mw_generate_catch = true;
            else if (strcmp(argv[j], "-i8") == 0)
                mw_promote_int = 4;
            else if (strcmp(argv[j], "-c99complex") == 0)
                mw_use_c99_complex = true;
            else if (strcmp(argv[j], "-cppcomplex") == 0)
                mw_use_cpp_complex = true;
            else if (strcmp(argv[j], "-gpu") == 0)
                mw_use_gpu = true;
        }

        if (mw_use_c99_complex || mw_use_cpp_complex) {
            add_zscalar_type("dcomplex");
            add_cscalar_type("fcomplex");
        }

        /* Pre-scan: count input files before opening any output files */
        int yyin_count = 0;
        for (j = 1; j < argc; ++j) {
            if (strcmp(argv[j], "-m") == 0 ||
                strcmp(argv[j], "-c") == 0 ||
                strcmp(argv[j], "-mex") == 0)
                ++j;
            else if (strcmp(argv[j], "--help") == 0 ||
                     strcmp(argv[j], "-mb") == 0 ||
                     strcmp(argv[j], "-list") == 0 ||
                     strcmp(argv[j], "-catch") == 0 ||
                     strcmp(argv[j], "-i8") == 0 ||
                     strcmp(argv[j], "-c99complex") == 0 ||
                     strcmp(argv[j], "-cppcomplex") == 0 ||
                     strcmp(argv[j], "-gpu") == 0)
                ;
            else
                ++yyin_count;
        }

        if (yyin_count == 0) {
            fprintf(stderr, "%s", usage_string);
            return 0;
        }

        /* Now safe to open output files */
        if (mfile)
            outfp = fopen(mfile, "w+");
        if (cfile)
            outcfp = fopen(cfile, "w+");

        for (j = 1; j < argc; ++j) {
            if (strcmp(argv[j], "-m") == 0 ||
                strcmp(argv[j], "-c") == 0 ||
                strcmp(argv[j], "-mex") == 0)
                ++j;
            else if (strcmp(argv[j], "-mb") == 0 ||
                     strcmp(argv[j], "-list") == 0 ||
                     strcmp(argv[j], "-catch") == 0 ||
                     strcmp(argv[j], "-i8") == 0 ||
                     strcmp(argv[j], "-c99complex") == 0 ||
                     strcmp(argv[j], "-cppcomplex") == 0 ||
                     strcmp(argv[j], "-gpu") == 0)
                ;
            else {
                linenum = 1;
                type_errs = 0;
                yyin = fopen(argv[j], "r");
                if (yyin) {
                    current_ifname = argv[j];
                    if (outcfp && !emitted_mex_init) {
                        print_mex_init(outcfp);
                        emitted_mex_init = true;
                    }
                    err_flag += yyparse();
                    fclose(yyin);
                } else {
                    fprintf(stderr, "Could not read %s\n", argv[j]);
                }
                if (type_errs)
                    fprintf(stderr, "%s: %d type errors detected\n",
                            argv[j], type_errs);
                err_flag += type_errs;
            }
        }
    }
    if (!err_flag && outcfp)
        print_mex_file(outcfp, funcs);
    destroy(funcs);
    destroy_inherits();
    if (outfp)
        fclose(outfp);
    if (outcfp)
        fclose(outcfp);
    return err_flag;
}
