%{ // -*- c++ -*-
//                          Package   : omniidl
// idl.ll                   Created on: 1999/10/05
//			    Author    : Duncan Grisby (dpg1)
//
//    Copyright (C) 2003-2017 Apasphere Ltd
//    Copyright (C) 1999      AT&T Laboratories Cambridge
//
//  This file is part of omniidl.
//
//  omniidl 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, see http://www.gnu.org/licenses/
//
// Description:
//   
//   Flex lexical analyser

#define YY_NEVER_INTERACTIVE 1

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

#include <idlerr.h>
#include <idlutil.h>
#include <idlrepoId.h>
#include <idlscope.h>
#include <idlexpr.h>
#include <idlast.h>

#include <y.tab.h>

char*       currentFile;
IDL_Boolean mainFile  = 1; // Are we processing the main file
int         nestDepth = 0; // #include nesting depth

char octalToChar(char* s);
char hexToChar(char* s);
char escapeToChar(char* s);
IDL_UShort octalToWChar(char* s);
IDL_UShort hexToWChar(char* s);
IDL_UShort escapeToWChar(char* s);
char* escapedStringToString(char* s);
IDL_UShort* escapedStringToWString(char* s);
void parseLineDirective(char* s);

// We never run on a tty
int not_isatty(int fd) {
   return 0;
}
#define isatty not_isatty

%}

%option noyywrap
%option yylineno

%x comment
%s known_pragma
%x unknown_pragma
%x omni_pragma

WS          [ \t\v\n\f\r]
SPACE       [ \t]

ASCIILETTER [a-zA-Z]
IDCHAR      [a-zA-Z0-9_]
IDENT       ({ASCIILETTER}{IDCHAR}*)

DECDIGIT    [0-9]
DECSTART    [1-9]
OCTSTART    0
OCTDIGIT    [0-7]
HEXSTART    (0[xX])
HEXDIGIT    [0-9a-fA-F]

EXPONENT    ([Ee][+-]?{DECDIGIT}+)

STR         (\"([^\"]|\\\")*\")

%%

abstract        return ABSTRACT;
any             return ANY;
attribute       return ATTRIBUTE;
boolean         return BOOLEAN;
case            return CASE;
char            return CHAR;
const           return CONST;
context         return CONTEXT;
custom          return CUSTOM;
default         return DEFAULT;
double          return DOUBLE;
enum            return ENUM;
exception       return EXCEPTION;
factory         return FACTORY;
FALSE           return FALSE_;
fixed           return FIXED;
float           return FLOAT;
in              return IN;
inout           return INOUT;
interface       return INTERFACE;
local           return LOCAL;
long            return LONG;
module          return MODULE;
native          return NATIVE;
Object          return OBJECT;
octet           return OCTET;
oneway          return ONEWAY;
out             return OUT;
private         return PRIVATE;
public          return PUBLIC;
raises          return RAISES;
readonly        return READONLY;
sequence        return SEQUENCE;
short           return SHORT;
string          return STRING;
struct          return STRUCT;
supports        return SUPPORTS;
switch          return SWITCH;
TRUE            return TRUE_;
truncatable     return TRUNCATABLE;
typedef         return TYPEDEF;
union           return UNION;
unsigned        return UNSIGNED;
ValueBase       return VALUEBASE;
valuetype       return VALUETYPE;
void            return VOID;
wchar           return WCHAR;
wstring         return WSTRING;

"<<"            return LEFT_SHIFT;
">>"            return RIGHT_SHIFT;
"::"            return SCOPE_DELIM;

{IDENT} {
  yylval.id_val = idl_strdup(yytext);
  return IDENTIFIER;
}

_{IDENT} {
  yylval.id_val = idl_strdup(yytext);
  return IDENTIFIER;
}

{DECSTART}{DECDIGIT}* {
  errno = 0;
  yylval.int_literal_val = idl_strtoul(yytext, 10);
  if (errno == ERANGE) {
    IdlError(currentFile, yylineno,
	     "Integer literal '%s' is too big", yytext);
  }
  return INTEGER_LITERAL;
}

{OCTSTART}{OCTDIGIT}+ {
  errno = 0;
  yylval.int_literal_val = idl_strtoul(yytext, 8);
  if (errno == ERANGE) {
    IdlError(currentFile, yylineno,
	     "Integer literal '%s' is too big", yytext);
  }
  return INTEGER_LITERAL;
}

{HEXSTART}{HEXDIGIT}+ {
  errno = 0;
  yylval.int_literal_val = idl_strtoul(yytext, 16);
  if (errno == ERANGE) {
    IdlError(currentFile, yylineno,
	     "Integer literal '%s' is too big", yytext);
  }
  return INTEGER_LITERAL;
}

"0" {
  yylval.int_literal_val = 0;
  return INTEGER_LITERAL;
}

"'"."'" {
  yylval.char_val = yytext[1];
  return CHARACTER_LITERAL;
}

"'"\\([0-7]{1,3})"'" {
  yytext[yyleng-1] = '\0';
  yylval.char_val = octalToChar(yytext + 1);
  return CHARACTER_LITERAL;
}

"'"\\x({HEXDIGIT}{1,2})"'" {
  yytext[yyleng-1] = '\0';
  yylval.char_val = hexToChar(yytext + 1);
  return CHARACTER_LITERAL;
}

"'"\\u({HEXDIGIT}{1,4})"'" {
  IdlError(currentFile, yylineno,
	   "\\u may only be used in wide characters and strings");
  yylval.char_val = 0;
  return CHARACTER_LITERAL;
}

"'"\\."'" {
  yytext[yyleng-1] = '\0';
  yylval.char_val = escapeToChar(yytext + 1);
  return CHARACTER_LITERAL;
}

L"'"."'" {
  yylval.wchar_val = yytext[2];
  return WIDE_CHARACTER_LITERAL;
}

L"'"\\([0-7]{1,3})"'" {
  yytext[yyleng-1] = '\0';
  yylval.wchar_val = octalToWChar(yytext + 2);
  return WIDE_CHARACTER_LITERAL;
}

L"'"\\x({HEXDIGIT}{1,2})"'" {
  yytext[yyleng-1] = '\0';
  yylval.wchar_val = hexToWChar(yytext + 2);
  return WIDE_CHARACTER_LITERAL;
}

L"'"\\u({HEXDIGIT}{1,4})"'" {
  yytext[yyleng-1] = '\0';
  yylval.wchar_val = hexToWChar(yytext + 2);
  return WIDE_CHARACTER_LITERAL;
}

L"'"\\."'" {
  yytext[yyleng-1] = '\0';
  yylval.wchar_val = escapeToWChar(yytext + 2);
  return WIDE_CHARACTER_LITERAL;
}

<INITIAL>{DECDIGIT}+{EXPONENT} {
  // Only deal with floats in INITIAL state, so version pragmas don't
  // get interpreted as floats.
  yylval.float_literal_val = idl_strtod(yytext);
  return FLOATING_PT_LITERAL;
}

<INITIAL>{DECDIGIT}*"."{DECDIGIT}+{EXPONENT}? {
  yylval.float_literal_val = idl_strtod(yytext);
  return FLOATING_PT_LITERAL;
}

<INITIAL>{DECDIGIT}+"."{DECDIGIT}*{EXPONENT}? {
  yylval.float_literal_val = idl_strtod(yytext);
  return FLOATING_PT_LITERAL;
}

{STR} {
  yytext[yyleng-1] = '\0';
  yylval.string_val = escapedStringToString(yytext + 1);
  return STRING_LITERAL;
}

L{STR} {
  yytext[yyleng-1] = '\0';
  yylval.wstring_val = escapedStringToWString(yytext + 2);
  return WIDE_STRING_LITERAL;
}

{DECDIGIT}+"."{DECDIGIT}*[dD] {
  yylval.fixed_val = new IDL_Fixed(yytext, currentFile, yylineno);
  return FIXED_PT_LITERAL;
}

{DECDIGIT}*"."{DECDIGIT}+[dD] {
  yylval.fixed_val = new IDL_Fixed(yytext, currentFile, yylineno);
  return FIXED_PT_LITERAL;
}

{DECDIGIT}+[dD] {
  yylval.fixed_val = new IDL_Fixed(yytext, currentFile, yylineno);
  return FIXED_PT_LITERAL;
}

"//".*\n { Comment::add(yytext, currentFile, yylineno-1); }

"/*" {
  Comment::add(yytext, currentFile, yylineno);
  BEGIN(comment);
}

<comment>[^*\n]*      { Comment::append(yytext); }
<comment>"*"+[^*/\n]* { Comment::append(yytext); }
<comment>\n           { Comment::append(yytext); }
<comment>"*"+"/"      { Comment::append(yytext); BEGIN(INITIAL); }

"__omni_pragma"{WS}+ {
  BEGIN(omni_pragma);
  return OMNI_PRAGMA;
}

<omni_pragma>{WS}+"__omni_endpragma" {
  BEGIN(INITIAL);
  return END_PRAGMA;
}

<omni_pragma>[^ \t\v\n\f\r]+ {
  yylval.string_val = idl_strdup(yytext);
  return UNKNOWN_PRAGMA_BODY;
}
<omni_pragma>{WS}+ {
  yylval.string_val = idl_strdup(yytext);
  return UNKNOWN_PRAGMA_BODY;
}


^{SPACE}*#{SPACE}*pragma{SPACE}*prefix{SPACE}* {
  BEGIN(known_pragma);
  return PRAGMA_PREFIX;
}

^{SPACE}*#{SPACE}*pragma{SPACE}*ID{SPACE}* {
  BEGIN(known_pragma);
  return PRAGMA_ID;
}

^{SPACE}*#{SPACE}*pragma{SPACE}*version{SPACE}* {
  BEGIN(known_pragma);
  return PRAGMA_VERSION;
}

<known_pragma>{SPACE}+ { /* Eat spaces and tabs */ }

^{SPACE}*#{SPACE}*pragma{SPACE}* {
  BEGIN(unknown_pragma);
  return PRAGMA;
}

<unknown_pragma>([^\\\n\r]|(\\[^\n\r]))+ {
  yylval.string_val = idl_strdup(yytext);
  return UNKNOWN_PRAGMA_BODY;
}

<INITIAL,known_pragma,unknown_pragma>\\(\n|(\r\n)) {
  /* Continue line if it ends with \ */
}

<known_pragma,unknown_pragma>\n|(\r\n) {
  BEGIN(INITIAL);
  return END_PRAGMA;
}

^{SPACE}*#{SPACE}*{DECDIGIT}+{SPACE}+{STR}{SPACE}+{DECDIGIT}*{SPACE}*\n {
  parseLineDirective(yytext);
}

^{SPACE}*#{SPACE}*{DECDIGIT}+{SPACE}+{STR}{SPACE}*\n {
  parseLineDirective(yytext);
}

^{SPACE}*#{SPACE}*{DECDIGIT}{SPACE}*\n {
  parseLineDirective(yytext);
}

<INITIAL>{WS} { /* Eat white space */ }

. {
  return yytext[0];
}

%%

char octalToChar(char* s) {
  unsigned long ret = strtoul(s+1, 0, 8);

  if (ret > 255) {
    IdlError(currentFile, yylineno, "Octal character value '%s' too big", s);
  }

  return ret;
}

char hexToChar(char* s)  {
  unsigned long ret = strtoul(s+2, 0, 16);

  return ret;
}

char escapeToChar(char* s) {
  switch (s[1]) {
  case 'n':  return '\n';
  case 't':  return '\t';
  case 'v':  return '\v';
  case 'b':  return '\b';
  case 'r':  return '\r';
  case 'f':  return '\f';
  case 'a':  return '\a';
  case '\\': return '\\';
  case '?':  return '?';
  case '\'': return '\'';
  case '\"': return '\"';
  }
  IdlWarning(currentFile, yylineno,
	     "Behaviour for escape sequence '%s' is undefined by IDL; "
	     "using '%c'", s, s[1]);
  return s[1];
}

IDL_UShort octalToWChar(char* s) {
  unsigned long ret = strtoul(s+1, 0, 8);

  if (ret > 255) { // This really is meant to be 255
    IdlError(currentFile, yylineno, "Octal character value '%s' too big", s);
  }

  return ret;
}

IDL_UShort hexToWChar(char* s) {
  unsigned long ret = strtoul(s+2, 0, 16);

  return ret;
}

IDL_UShort escapeToWChar(char* s) {
  switch (s[1]) {
  case 'n':  return '\n';
  case 't':  return '\t';
  case 'v':  return '\v';
  case 'b':  return '\b';
  case 'r':  return '\r';
  case 'f':  return '\f';
  case 'a':  return '\a';
  case '\\': return '\\';
  case '?':  return '?';
  case '\'': return '\'';
  case '\"': return '\"';
  }
  IdlWarning(currentFile, yylineno,
	     "Behaviour for escape sequence '%s' is undefined by IDL; "
	     "using '%c'.", s, s[1]);
  return s[1];
}

char* escapedStringToString(char* s) {
  int   len = strlen(s);
  char* ret = new char[len+1];
  char  tmp[8];

  int from, to, i;

  for (from=0, to=0; from < len; from++, to++) {

    if (s[from] == '\\') {
      tmp[0] = s[from++];

      if ('0' <= s[from] && s[from] <= '7') {
	// Octal
	for (i=1;
	     i < 4 && from < len && '0' <= s[from] && s[from] <= '7';
	     from++, i++) tmp[i] = s[from];

	tmp[i]  = '\0';
	ret[to] = octalToChar(tmp);
	from--;
      }
      else if (s[from] == 'x') {
	// Hex
	tmp[1] = s[from++];
	for (i=2; i < 4 && from < len && isxdigit(s[from]); from++, i++)
	  tmp[i] = s[from];

	tmp[i]  = '\0';
	ret[to] = hexToChar(tmp);
	from--;
      }
      else if (s[from] == 'u') {
	// Wide hex not allowed
	IdlError(currentFile, yylineno,
		 "\\u may only be used in wide characters and strings");
	ret[to] = '!';
      }
      else {
	tmp[1]  = s[from];
	tmp[2]  = '\0';
	ret[to] = escapeToChar(tmp);
      }
      if (ret[to] == '\0') {
	IdlError(currentFile, yylineno, "String cannot contain \\0");
	ret[to] = '!';
      }
    }
    else ret[to] = s[from];
  }
  ret[to] = '\0';
  return ret;
}

IDL_UShort* escapedStringToWString(char* s) {
  int         len = strlen(s);
  IDL_UShort* ret = new IDL_UShort[len+1];
  char        tmp[8];

  int from, to, i;

  for (from=0, to=0; from < len; from++, to++) {

    if (s[from] == '\\') {
      tmp[0] = s[from++];

      if ('0' <= s[from] && s[from] <= '7') {
	// Octal
	for (i=1;
	     i < 4 && from < len && '0' <= s[from] && s[from] <= '7';
	     from++, i++) tmp[i] = s[from];

	tmp[i]  = '\0';
	ret[to] = octalToWChar(tmp);
	from--;
      }
      else if (s[from] == 'x') {
	// Hex
	tmp[1] = s[from++];
	for (i=2; i < 4 && from < len && isxdigit(s[from]); from++, i++)
	  tmp[i] = s[from];

	tmp[i]  = '\0';
	ret[to] = hexToWChar(tmp);
	from--;
      }
      else if (s[from] == 'u') {
	// Wide hex
	tmp[1] = s[from++];
	for (i=2; i < 6 && from < len && isxdigit(s[from]); from++, i++)
	  tmp[i] = s[from];

	tmp[i]  = '\0';
	ret[to] = hexToWChar(tmp);
	from--;
      }
      else {
	tmp[1]  = s[from];
	tmp[2]  = '\0';
	ret[to] = escapeToWChar(tmp);
      }
      if (ret[to] == 0) {
	IdlError(currentFile, yylineno,
		 "Wide string cannot contain wide character zero");
	ret[to] = '!';
      }
    }
    else
      ret[to] = s[from];
  }
  ret[to] = 0;
  return ret;
}

void parseLineDirective(char* s) {
  char* file    = new char[strlen(s) + 1];
  long int line = 0, mode = 0;
  int cnt       = sscanf(s, "# %ld \"%[^\"]\" %ld", &line, file, &mode);

  assert(cnt >= 1);

  if (cnt > 1) {
    if (cnt == 3) {
      if (mode == 1) {
	// New #included file
	++nestDepth;
	mainFile = 0;
	Prefix::newFile();
      }
      else if (mode == 2) {
	// Return from #include
	if (--nestDepth == 0) mainFile = 1;
	Prefix::endFile();
      }
    }
    delete [] currentFile;
    // cccp escapes \ characters, so use the normal string parser
    currentFile = escapedStringToString(file);
    delete [] file;
    if (mainFile)
      AST::tree()->setFile(currentFile);
  }
  yylineno = line;
}
