/* Next available MSG number is     1 */

/*****************************************************************************
      CALLEX.C
      (C) Copyright 1988-1994 by Autodesk, Inc.

      This program is copyrighted by Autodesk, Inc. and is  licensed
      to you under the following conditions.  You may not distribute
      or  publish the source code of this program in any form.   You
      may  incorporate this code in object form in derivative  works
      provided  such  derivative  works  are  (i.) are  designed and
      intended  to  work  solely  with  Autodesk, Inc. products, and
      (ii.)  contain  Autodesk's  copyright  notice  "(C)  Copyright
      1988-1993 by Autodesk, Inc."

      AUTODESK  PROVIDES THIS PROGRAM "AS IS" AND WITH  ALL  FAULTS.
      AUTODESK  SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF  MER-
      CHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK,  INC.
      DOES  NOT  WARRANT THAT THE OPERATION OF THE PROGRAM  WILL  BE
      UNINTERRUPTED OR ERROR FREE.

  Description: Lexical analyser for the Geometry Calculator ADS application.

*****************************************************************************/


/****************************************************************************/
/*  INCLUDES                                                                */
/****************************************************************************/

#define MODULE_ID CALLEX_C_

#include "cal.h"

#include "xmf.h"
#include "ads_ix.h"


/****************************************************************************/
/*  EXPORTED VARIABLES                                                      */
/****************************************************************************/

lex_output_type cal_lex;              /* Output from the lexical analyser */


/****************************************************************************/
/*  STATIC VARIABLES AND FUNCTIONS                                          */
/****************************************************************************/

/* Keyword table */

static struct {char *ident; symbol_type symbol;} kw[] =
{
    /*MSG0*/"PI",      pi_sym,
    /*MSG0*/"GETVAR",  getvar_sym,
    /*MSG0*/"CVUNIT",  cvunit_sym,
};

#define NKWS  ELEMENTS(kw)            /* Number of keywords */

static char ch;                       /* The current character         */
static char *ch_ptr;                  /* Pointer to the next character */
static int  end_of_line;              /* TRUE=next_char() reached EOLN */

static void        str_toupper          _((char *str));
static symbol_type is_keyword           _((char *ident));
static void        next_char            _((void));
static void        number               _((void));
static void        number_angle_or_feet _((void));


/****************************************************************************/
/*.doc str_toupper(internal)*/
/*+
  Converts the given string to upper case (I cannot understand why this is
  not a standard function somewhere in a standard library).
-*/
/****************************************************************************/


static void
/*FCN*/str_toupper(str)

  char *str;
{
    while ((*str = ads_toupper(*str)) != 0) {
        str++;
    }
} /*str_toupper*/


/****************************************************************************/
/*.doc is_keyword(internal)*/
/*+
  Returns the keyword symbol if the given identitier is a keyword or
  symbol 'ident_sym' if it is not.
-*/
/****************************************************************************/


static symbol_type
/*FCN*/is_keyword(ident)

  char *ident;
{
    register int i;

    for (i = 0; i < NKWS; i++)
        if (strcmp(ident, kw[i].ident) == 0) {
            return(kw[i].symbol);
        }
    return(ident_sym);
} /*is_keyword*/


/****************************************************************************/
/*.doc next_char(internal)*/
/*+
  Delivers the next character from the input stream. The character is
  stored in 'ch', the pointer to the next character is 'ch_ptr'.
-*/
/****************************************************************************/


static void
/*FCN*/next_char()
{
    if (*ch_ptr != EOS) {
        ch = ads_toupper(*ch_ptr++);
    } else {
        ch = ' ';
        end_of_line = TRUE;
    }
} /*next_char*/


/****************************************************************************/
/*.doc number(internal)*/
/*+
  Parses the terminal symbol NUMBER.
-*/
/****************************************************************************/


static void
/*FCN*/number()
{
    char *sym_begin;
    char num_str[MAX_SYMBOL_LENGTH+1];
    int  sym_len;
    int  success;

    if (cal_err)
        return;

    cal_lex.sym = int_sym;
    sym_begin   =  ch_ptr - 1;

    while (ads_isdigit(ch)) {
        next_char();
    }

    if (ch == '.') {
        cal_lex.sym = real_sym;
        next_char();
        while (ads_isdigit(ch)) {
            next_char();
        }
    }
    if (ch == /*MSG0*/'E') {
        cal_lex.sym = real_sym;
        next_char();
        if ((ch == '+') || (ch == '-'))
            next_char();
        if (!(ads_isdigit(ch))) {
            error(1, NULL);
            return;
        }
        do {
            next_char();
        } while (ads_isdigit(ch));
    }

    sym_len = ch_ptr - sym_begin - 1;
    if (end_of_line) {
        sym_len++;
    }
    if (sym_len > MAX_SYMBOL_LENGTH) {
        sym_len = MAX_SYMBOL_LENGTH;
    }

    strncpy(num_str, sym_begin, sym_len);
    num_str[sym_len] = EOS;

    success = sscanf(num_str, "%lf", &cal_lex.real_num);
    if (success != 1) {
        error(1, num_str);
        return;
    }
    if ((errno == ERANGE) || (errno == EDOM) ||
        (cal_lex.real_num == HUGE_VAL)) {

        error(1, num_str);
        return;
    }
    if ((cal_lex.sym == int_sym) &&
        ((cal_lex.real_num > 32767) || (cal_lex.real_num < -32768))) {
        error(3, NULL);
        return;
    }
} /*number*/


/****************************************************************************/
/*.doc number_angle_or_feet(internal)*/
/*+
  Parses the terminal symbols NUMBER, ANGLE and FEET-INCHES.
-*/
/****************************************************************************/


static void
/*FCN*/number_angle_or_feet()
{
    if (cal_err)
        return;

    number();
    if (cal_err)
        return;

    if (ch == /*MSG0*/'G') {                  /*grads*/
        cal_lex.sym      = real_sym;
        cal_lex.real_num = cal_lex.real_num * 0.9;
        next_char();

    } else if (ch == /*MSG0*/'R') {           /*radians*/
        cal_lex.sym      = real_sym;
        cal_lex.real_num = cal_lex.real_num / DEGRAD;
        next_char();

    } else if (ch == /*MSG0*/'D') {           /*<deg>d<min>'<sec>"*/
        double deg, min, sec;

        deg = cal_lex.real_num;
        min = sec = 0.0;

        next_char();
        if (!ads_isdigit(ch)) {
            goto End_degminsec;
        }
        number();

        if (ch == '\'') {
            min = cal_lex.real_num;
            next_char();
            if (!ads_isdigit(ch)) {
                goto End_degminsec;
            }
            number();
        }

        if (ch == '"') {
            sec = cal_lex.real_num;
            next_char();
        } else {
            error(34, NULL);
            return;
        }

End_degminsec:
        if (cal_err)
            return;

        cal_lex.sym      = real_sym;
        cal_lex.real_num = deg + min / 60.0 + sec / 3600.0;

    } else if ((ch == '\'') || (ch == '"')) { /*<feet>'<inches>"*/
        double feet = 0, inches = 0;
        
        if (ch == '\'') {
            feet = cal_lex.real_num;

            next_char();
            if (ch == '-') {
                next_char();
            }

            if (ads_isdigit(ch)) {
                number();
                if (ch != '"') {
                    error(22, NULL);
                    return;
                }
                inches = cal_lex.real_num;
                next_char();
            }
        } else {
            inches = cal_lex.real_num;
            next_char();
        }
        
        if (cal_err)
            return;
        
        cal_lex.sym      = real_sym;
        cal_lex.real_num = 12 * feet + inches;
    } /*if*/

} /*number_angle_or_feet*/


/****************************************************************************/
/*.doc cal_next_symbol(internal)*/
/*+
  Delivers the next symbol from the input stream. The returned symbol 
  is stored in the global structure 'cal_lex'.
-*/
/****************************************************************************/


void
/*FCN*/cal_next_symbol()
{
    char         *sym_begin;
    int          sym_len;
    register int i;

    if (cal_err)
        return;

    while ((ch == ' ') && !end_of_line) {
        next_char();
    }

    if (ch == ' ') {
        cal_lex.sym = no_sym;

    } else if (ads_isalpha(ch)) {

        cal_lex.sym = ident_sym;
        sym_begin   = ch_ptr - 1;

        do {
            next_char();
        } while (ads_isalnum(ch) || (ch == '_'));

        sym_len = ch_ptr - sym_begin - 1;
        if (end_of_line) {
            sym_len++;
        }
        if (sym_len > MAX_SYMBOL_LENGTH) {
            sym_len = MAX_SYMBOL_LENGTH;
        }

        strncpy(cal_lex.id, sym_begin, sym_len);
        cal_lex.id[sym_len] = EOS;
        str_toupper(cal_lex.id);

        /* Is it a keyword ? */

        cal_lex.sym = is_keyword(cal_lex.id);
        if (cal_lex.sym != ident_sym)
            return;

        /* Is it a function name? */

        for (i = 0; i < cal_funcs_number; i++) {
            if (strcmp(cal_lex.id, cal_funcs_table[i].name) == 0) {
                cal_lex.sym      = func_sym;
                cal_lex.func_ptr = cal_funcs_table[i].func;
                return;
            } /*if*/
        } /*for*/

    } else if (ch == '\'') {
        /* The name of AutoLISP symbol enclosed in apostrophes */
        
        cal_lex.sym = ident_sym;
        next_char();
        
        i = 0;
        while (!end_of_line && (i < MAX_SYMBOL_LENGTH) && (ch != '\'')) {
            cal_lex.id[i++] = ch;
            next_char();
        }
        cal_lex.id[i] = EOS;

        if (end_of_line) {
            error(9, NULL);
            return;
        } if (ch != '\'') {
            error(31, cal_lex.id);
            return;
        }
        
        next_char();

    } else if (ads_isdigit(ch) || (ch == '.')) {
        number_angle_or_feet();

    } else if (ch == '[') { cal_lex.sym = lbracket_sym;  next_char(); }
    else   if (ch == ']') { cal_lex.sym = rbracket_sym;  next_char(); }
    else   if (ch == ',') { cal_lex.sym = comma_sym;     next_char(); }
    else   if (ch == '-') { cal_lex.sym = minus_sym;     next_char(); }
    else   if (ch == '+') { cal_lex.sym = plus_sym;      next_char(); }
    else   if (ch == '*') { cal_lex.sym = asterisk_sym;  next_char(); }
    else   if (ch == '/') { cal_lex.sym = slash_sym;     next_char(); }
    else   if (ch == '(') { cal_lex.sym = lparent_sym;   next_char(); }
    else   if (ch == ')') { cal_lex.sym = rparent_sym;   next_char(); }
    else   if (ch == '^') { cal_lex.sym = caret_sym;     next_char(); }
    else   if (ch == '@') { cal_lex.sym = at_sym;        next_char(); }
    else   if (ch == '<') { cal_lex.sym = lessthan_sym;  next_char(); }
    else   if (ch == '&') { cal_lex.sym = ampersand_sym; next_char(); }
    else   if (ch == '=') { cal_lex.sym = equal_sym;     next_char(); }
    else {
        cal_lex.id[0] = ch;
        cal_lex.id[1] = EOS;
        error(2, cal_lex.id);
        return;
    }

} /*cal_next_symbol*/


/****************************************************************************/
/*.doc cal_lex_start(external)*/
/*+
  Initializes the lexical analyser with the input string 'line'. Reads the 
  first symbol ahead.
-*/
/****************************************************************************/


void
/*FCN*/cal_lex_start(line)

  char *line;
{
    cal_err     = 0;
    errno       = 0;
    ch          = ' ';
    ch_ptr      = line;
    end_of_line = FALSE;
    next_char();
    cal_next_symbol();
} /*cal_lex_start*/

