// 
// tokenizer.c ... 字句解析器
// 

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "base.h"
#include "tokenizer.h"

#define TOKEN_BUFFER_CAPACITY 5000    // 字句の最大格納個数

static char *add_spaces(const char *src) {
  // 字句を ' ' で区切れるようにする前処理
  // '(' や ')' の前後に ' ' を追加し,空白類を ' ' に統一
  size_t len = strlen(src)*3 + 1;    // 最大長(前後に全て空白の入る場合)
  char *buf = (char *) malloc_or_exit(len);
  char *dst = buf;
  for ( ; *src != '\0'; src++) {
    char c = *src;
    if (c == '(' || c == ')') *dst++ = ' ';
    *dst++ = (isspace(c)) ? ' ' : c;    // isspace() は改行文字も検出
    if (c == '(' || c == ')') *dst++ = ' ';
  }
  *dst = '\0';
  return buf;
}

static char *source_buffer = NULL;
static char *current_token = NULL;

static void preprocess(const char *source) {
  source_buffer = add_spaces(source);    // '(' や ')' の前後に ' ' を追加
  current_token = strtok(source_buffer, " ");    // 空白文字までの字句を切り出す
}

static char *read_token(void) {
  char *tok = current_token;
  current_token = strtok(NULL, " ");    // 空白文字までの字句を切り出す
  return tok;
}

static char *token_buffer[TOKEN_BUFFER_CAPACITY];    // 複製・加工用
static int ntokens = 0;    // 切り出した字句数

void tokenize(const char *source) {
  assert(source != NULL);
  preprocess(source);
  char *tok;
  while ((tok = read_token()) != NULL) {
    if (ntokens >= TOKEN_BUFFER_CAPACITY) error_exit("token buffer is full");
    token_buffer[ntokens++] = tok;
  }
}

char *next_token(void) {
  static int token_index = 0;    // 読み出した字句数
  if (token_index < ntokens) {
    return token_buffer[token_index++];
  } else {
    return NULL;    // 入力終端
  }
}

int equal_token(char *tok, const char *str) {
  assert(tok != NULL && str != NULL);
  return (strcmp(tok, str) == 0);
}

int is_number_token(char *tok) {
  // 数 ::= 符号? 数字 数字*    (省略可能な符号の後に数字が1個以上並んだもの)
  // 符号 ::= + | -
  // 数字 ::= 0 | 1 | … | 9
  assert(tok != NULL);
  if (*tok == '+' || *tok == '-') tok++;
  if (! isdigit(*tok)) return 0;
  while (isdigit(*tok)) tok++;
  return *tok == '\0';
}

static int issymbol(char c) {
  static char symbols[] = "*/%^=<>?!$&:_~.";
  char *p;
  for (p = symbols; *p != '\0'; p++) {
    if (*p == c) return 1;
  }
  return 0;
}

int is_name_token(char *tok) {
  // 名前 ::= 符号 | 開始文字 後続文字*
  // 開始文字 ::= 英字 | 記号
  // 後続文字 ::= 英字 | 数字 | 記号 | 符号
  // 符号 ::= + | -
  // 英字 ::= a | b | … | z | A | B | … | Z
  // 記号 ::= * | / | % | ^ | = | < | > | ? | ! | $ | & | : | _ | ~ | .
  // 数字 ::= 0 | 1 | … | 9
  assert(tok != NULL);
  if (equal_token(tok, "+") || equal_token(tok, "-")) return 1;
  if (! isalpha(*tok) && ! issymbol(*tok)) return 0;
  while (isalnum(*tok) || issymbol(*tok) || *tok == '+' || *tok == '-') tok++;
  return *tok == '\0';
}

int token_to_int(char *tok) {
  assert(is_number_token(tok));
  int num;
  sscanf(tok, "%d", &num);    // 正しく変換できるのは int の範囲内だけ
  return num;
}