
// C lexical analyzer. Refer to the wikipedia article: "lexical analysis".

int is_digit() {
  return (ch >= '0') && (ch <= '9');
}

int is_alpha() {
  return ((ch >= 'a') && (ch <= 'z')) ||
         ((ch >= 'A') && (ch <= 'Z'));
}

int is_alnum() {
  return is_alpha() || is_digit();
}

int is_a_to_f() {
  return ((ch >= 'a') && (ch <= 'f')) ||
         ((ch >= 'A') && (ch <= 'F'));
}

int is_hex_digit() {
  return is_digit() || is_a_to_f();
}

int my_getchar() {
  int my_ch;
  if (is_data_stream) {
    my_ch = data_stream[data_stream_idx];
    data_stream_idx = data_stream_idx + 1;
    return my_ch;
  }
  else
    return getchar();
}

void read_char() {
  ch = lookahead_ch;
  if (ch != 0) {
    lookahead_ch = my_getchar();
    if (lookahead_ch == -1)
      lookahead_ch = 0;
  }
  if ((ch == '\n') || (ch == 13) || (ch == 9)) {
    // whitespace
    if (ch == '\n') // newline
      line_count = line_count + 1;
    ch = ' ';
  }
  return;
}

void skip_blanks() {
  while (ch == ' ')
    read_char();
  return;
}

void log_comment_char(int ch) {
  if (comment_arr_idx < MAX_COMMENT_SIZE) {
    if (comment_arr_idx >= 2) {
      comment_arr[comment_arr_idx] = ch;
      comment_arr_idx = comment_arr_idx + 1;
    }
    else {
      comment_arr[comment_arr_idx] = ' ';
      comment_arr_idx = comment_arr_idx + 1;
    }
  }
  return;
}

void skip_comment() {
  int old_line_count;
  old_line_count = line_count;
  while ((ch != 0) && (old_line_count == line_count)) {
    read_char();
    log_comment_char(ch);
  }
  skip_blanks();
  return;
}

void skip_comments() {
  skip_comment();
  if (ch == '/' && lookahead_ch == '/') {
    log_comment_char('\n');
    skip_comments();
  }
  return;
}

void skip_comments_or_preprocessor_directives() {
  skip_comment();
  if ((ch == '/' && lookahead_ch == '/'))
    skip_comments_or_preprocessor_directives();
  return;
}

void add_ch_to_token() {
  token_size = token_size + 1;
  string_pool[pool_idx] = ch;
  read_char();
  pool_idx = pool_idx + 1;
  return;
}

void add_ch_to_string_token() {
  string[string_idx] = ch;
  read_char();
  string_idx = string_idx + 1;
  // TODO error if exceeds 255
  return;
}


void build_symbol_token() {
  while (is_alnum() || (ch == '_')) {
    add_ch_to_token();
  }
  return;
}

void build_string_token() {
  if (ch != 0) {
    if (ch == '\\') {
      read_char();
      if (ch == 'n')
        // line feed
        ch = '\n';
      add_ch_to_string_token();
      build_string_token();
    }
    else
    if (ch == '"') {
      // close string
      read_char();
    }
    else {
      add_ch_to_string_token();
      build_string_token();
    }
  }
  return;
}

void add_negative_sign_if_present() {
  if (ch == '-')
    add_ch_to_token();
  return;
}

void build_hex_number_token() {
  while (is_hex_digit())
    add_ch_to_token();
  return;
}

void build_number_token() {
  if (lookahead_ch == 'x') {
    // hex number
    read_char();
    read_char();
    token_size = token_size + 1;
    string_pool[pool_idx] = '$';
    pool_idx = pool_idx + 1;
    build_hex_number_token();
  }
  else
    while (is_digit())
      add_ch_to_token();
  return;
}

int is_separator() {
  return (ch == '(') || (ch == ')') || (ch == '[')
    || (ch == ']') || (ch == ',') || (ch == '<')
    || (ch == '>') || (ch == ';') || (ch == ':')
    || (ch == '=') || (ch == '+') || (ch == '-')
    || (ch == '*') || (ch == '/') || (ch == '%')
    || (ch == '&') || (ch == '^') || (ch == '|') || (ch == '~')
    || (ch == '{') || (ch == '}') || (ch == '!')
    || (ch == '#');
}

int is_two_character_separator(int first, int second) {
  return ((first == '=') && (second == '='))
        || ((first == '!') && (second == '='))
        || ((first == '<') && (second == '='))
        || ((first == '>') && (second == '='))
        || ((first == '>') && (second == '>'))
        || ((first == '<') && (second == '<'))
        || ((first == '+') && (second == '+'))
        || ((first == '-') && (second == '-'))
        || ((first == '&') && (second == '&'))
        || ((first == '|') && (second == '|'));
}

void build_separator_token() {
  int old_ch;
  old_ch = ch;
  add_ch_to_token();
  if (is_two_character_separator(old_ch, ch))
    add_ch_to_token();
  return;
}

void finalize_token() {
  // set size tag
  string_pool[pool_idx - token_size - 1] = token_size;
  // null terminated string
  string_pool[pool_idx] = 0;
  pool_idx = pool_idx + 1;
  return;
}

int get_c_token() {
  int token;
  token = pool_idx;
  token_size = 0;
  skip_blanks();
  if (ch == '/' && lookahead_ch == '/')
    skip_comments();

  if (ch == 0)
    // eof
    token = 0;
  else {
    if (is_separator()) {
      pool_idx = pool_idx + 1;
      build_separator_token();
      finalize_token();
      token = intern(token);
    }
    else if (is_digit() || (ch == '-')) {
      // number
      pool_idx = pool_idx + 1;
      add_negative_sign_if_present();
      build_number_token();
      finalize_token();
      token = intern(token);
    }
    else if (is_alpha() || (ch == '_')) {
      // identifier
      pool_idx = pool_idx + 1;
      add_ch_to_token();
      build_symbol_token();
      finalize_token();
      token = intern(token);
    }
    else if (ch == '\'') {
      // character
      read_char();
      if (ch == '\\') {
        // escape character
        read_char();
        if (ch == 'n')
          // line feed
          ch = '\n';
      }
      token = itoa_char_token(ch);
      // read close quote
      read_char();
      read_char();
    }
    else if (ch == '"') {
      // string
      string_idx = 0;
      read_char();
      build_string_token();
    }
    else {
      // error
      pool_idx = pool_idx + 1;
      add_ch_to_token();
      finalize_token();
    }
  }
  return token;
}

void set_unary_minus() {
  unary_minus_tok = pool_idx;
  // size tag
  string_pool[pool_idx] = 4;
  pool_idx = pool_idx + 1;
  string_pool[pool_idx] = '#';
  pool_idx = pool_idx + 1;
  string_pool[pool_idx] = 'N';
  pool_idx = pool_idx + 1;
  string_pool[pool_idx] = 'E';
  pool_idx = pool_idx + 1;
  string_pool[pool_idx] = 'G';
  pool_idx = pool_idx + 1;
  // null terminated string
  string_pool[pool_idx] = 0;
  pool_idx = pool_idx + 1;
  return;
}

int next_token_ring_idx() {
  if (token_ring_idx == MAX_TOKEN_RING)
    return 0;
  else
    return token_ring_idx + 1;
}

void increment_token_ring_idx() {
  token_ring_idx = token_ring_idx + 1;
  if (token_ring_idx == MAX_TOKEN_RING + 1)
    token_ring_idx = 0;
  return;
}

int get_token() {
  token_ring[token_ring_idx] = get_c_token();
  token_ring_line_count[token_ring_idx] = line_count;
  increment_token_ring_idx();
  return token_ring[token_ring_idx];
}

int lookahead() {
  int next_idx;
  next_idx = token_ring_idx + 1;
  if (next_idx == MAX_TOKEN_RING + 1)
    next_idx = 0;
  return token_ring[next_idx];
}

//int double_lookahead() {
//  int next_idx;
//  if (token_ring_idx == 0)
//    next_idx = 2;
//  else
//  if (token_ring_idx == 1)
//    next_idx = MAX_TOKEN_RING;
//  else
//    next_idx = token_ring_idx - 2;
//  return token_ring[next_idx];
//}

int triple_lookahead() {
  int next_idx;
  if (token_ring_idx == 0)
    next_idx = MAX_TOKEN_RING;
  else
    next_idx = token_ring_idx - 1;
  return token_ring[next_idx];
}
