c言語でf文字列を使いたい!!

# 概要
みなさん、c言語は好きですか?

以下のデータから分かるように、大体の人は嫌いだと思います.

![アンケート.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2727329/7d31ca16-e752-26f6-e15b-65cf0fdbe57d.png)

そこで、私はc言語に足りない機能を追加するツール(笑)を制作しました!
単純に書くと 独自の構文 を c言語に置き換えて出力するツールです.


## インストール方法
```shell
brew tap satooru65536/cmm
brew install cmm
```

## 使い方
使い方は [SatooRu65536/cmm-compiler](https://github.com/SatooRu65536/cmm-compiler) を見てください


## 実装内容
c言語で変数を文字列内で展開するには以下のようになります.

```c
int num = 65536;
printf("2^16 = %d", num);
```

ちょっと分かりずらいですよね. 例えば以下の場合では

```c
printf("%s: %d / %d = %f", quesId, n1, n2, n3);
```

これでは目の反復横跳びで疲れてしまいます.
しかも何書いているかわからない!!

そこで私は Python のように f文字列 を使えるようにしたいと思いました.

以下はPythonでのf文字列の例です
```python
num = 65536
print(f"2^16 = {num}")  # 2^16 = 65536
```

最終的には以下のような構文を実装します

```c
int num = 65536;
printf(f"num = {num:d}");
```


## 実装
はじめに入力, 出力ファイルを読み込みます.

```c
  FILE *inputFile;
  FILE *outputFile;

  inputFile = fopen("in.c", "r");
  outputFile = fopen("out.c", "w");
```

続いて入力ファイルを1行ずつ読み込んで `parseLine関数` を呼び出します.

```c
  char line[256];
  while (fgets(line, sizeof(line), inputFile) != NULL) {
    parseLine(line, outputFile);
  }
```

そして単語ごとに切り出します!
1文字ずつ見ていき、
`"` があれば次の `"` までを文字列とし、`word` に追加、
`(` `)` `{` `}` ` (空白)` `,` `;` であれば `evalWord関数`を呼び出し、その文字を出力、
`\n` `\0` であれば `evalWord関数` を呼び出す.
といった感じです.

説明を読むよりもコードを見た方が早いですね!

```c
void parseLine(char *line, FILE *outputFile) {
  int i = 0;
  char word[256] = {'\0'};

  while (1) {
    char c = *line;

    switch (c) {
      case '"':
        // 次の " までを文字列として扱う
        word[i] = c;
        i++;
        line++;
        while (1) {
          c = *line;
          word[i] = c;
          i++;
          if (c == '"') {
            break;
          }
          line++;
        }
        break;

      case '(':
      case ')':
      case '{':
      case '}':
      case ',':
      case ';':
      case ' ':
        word[i] = '\0';
        evalWord(word, outputFile);
        fprintf(outputFile, "%c", c);
        i = 0;
        break;

      case '\0':
        word[i] = '\0';
        evalWord(word, outputFile);
        return;

      case '\n':
        word[i] = '\0';
        evalWord(word, outputFile);
        fprintf(outputFile, "%c", '\n');
        return;

      default:
        word[i] = c;
        i++;
        break;
    }
    line++;
  }
}
```

これで一単語ずつ呼び出されるようになりました!!

単語が `f"` から始まっていた場合は f文字列しかないので
いい感じに処理をするようにします

```c
void evalWord(char *word, FILE *outputFile) {
  // f文字列の場合
  if (word[0] == 'f' && word[1] == '"') {
    processFString(word, outputFile);
    return;
  }

  // それ以外の場合はそのまま書き込む
  fprintf(outputFile, "%s", word);
}

// f文字列を処理する
void processFString(char *word, FILE *outputFile) {
  char str[256] = {'\0'};
  replaceString(word, str);
  fprintf(outputFile, "%s", str);

  char vars[256] = {'\0'};
  pickupVariable(word, vars);
  fprintf(outputFile, "%s", vars);
}
```

文字列の変数をフォーマット演算子に置き換えます.
> f"{hoge:d} {fuga:lf}" -> "%d %d"

説明は...プログラムから理解しましょう!!!
(面倒で GitHub Copilot に書かせた...)
```c
void replaceString(char *word, char *str) {
  int i = 1;
  int j = 0;

  while (1) {
    char c = word[i];
    if (c == '\0') break;

    if (c != '{') {
      str[j] = c;
      j++;
    } else {
      i++;
      while (1) {
        c = word[i];
        i++;
        if (c == ':') break;
      }

      char f = 0;
      char format[5] = {'\0'};

      while (1) {
        c = word[i];
        format[f] = c;

        if (c == '}') break;

        f++;
        i++;
      }

      str[j] = '%';
      for (int k = 0; k < f; k++) str[j + k + 1] = format[k];
      j += f + 1;
    }
    i++;
  }
}
```

同様に文字列から変数名を取り出します.
> f"{hoge:d} {fuga:lf}" -> , hoge, fuga

```c
void pickupVariable(char *word, char *vars) {
  int i = 0;
  int j = 0;

  while (1) {
    char c = word[i];
    if (c == '\0') {
      break;
    }

    if (c == '{') {
      i++;
      vars[j] = ',';
      vars[j + 1] = ' ';
      j += 2;
      while (1) {
        c = word[i];

        if (c == ':') {
          while (c != '}') {
            c = word[i];
            i++;
          }
          break;
        }

        vars[j] = c;
        i++;
        j++;
      }
    }
    i++;
  }
}
```

以上で完成です!!

以下のコードが書かれた `in.c` を作成して実行すると、変換してくれると思います
```c:in.c
printf(f"num = {num:d}");
```

```c:out.c
printf("num = %d", num);
```

## 最後に
今回は単語ごとに分割して置き換えるというところまでしました.
さらに 抽象構文木 に変換して、云々すればプログラミング言語の完成ですね!

ぜひやってみてください!!

最終的なコードを貼り付けておきます
```c
#include <stdio.h>
#include <string.h>

void pickupVariable(char *word, char *vars) {
  int i = 0;
  int j = 0;

  while (1) {
    char c = word[i];
    if (c == '\0') {
      break;
    }

    if (c == '{') {
      i++;
      vars[j] = ',';
      vars[j + 1] = ' ';
      j += 2;
      while (1) {
        c = word[i];

        if (c == ':') {
          while (c != '}') {
            c = word[i];
            i++;
          }
          break;
        }

        vars[j] = c;
        i++;
        j++;
      }
    }
    i++;
  }
}

void replaceString(char *word, char *str) {
  int i = 1;
  int j = 0;

  while (1) {
    char c = word[i];
    if (c == '\0') break;

    if (c != '{') {
      str[j] = c;
      j++;
    } else {
      i++;
      while (1) {
        c = word[i];
        i++;
        if (c == ':') break;
      }

      char f = 0;
      char format[5] = {'\0'};

      while (1) {
        c = word[i];
        format[f] = c;

        if (c == '}') break;

        f++;
        i++;
      }

      str[j] = '%';
      for (int k = 0; k < f; k++) str[j + k + 1] = format[k];
      j += f + 1;
    }
    i++;
  }
}

void processFString(char *word, FILE *outputFile) {
  char str[256] = {'\0'};
  replaceString(word, str);
  fprintf(outputFile, "%s", str);

  char vars[256] = {'\0'};
  pickupVariable(word, vars);
  fprintf(outputFile, "%s", vars);
}

void evalWord(char *word, FILE *outputFile) {
  // f文字列の場合
  if (word[0] == 'f' && word[1] == '"') {
    processFString(word, outputFile);
    return;
  }

  // それ以外の場合
  fprintf(outputFile, "%s", word);
}

void parseLine(char *line, FILE *outputFile) {
  int i = 0;
  char word[256] = {'\0'};

  while (1) {
    char c = *line;

    switch (c) {
      case '"':
        // 次の " までを文字列として扱う
        word[i] = c;
        i++;
        line++;
        while (1) {
          c = *line;
          word[i] = c;
          i++;
          if (c == '"') {
            break;
          }
          line++;
        }
        break;

      case '(':
      case ')':
      case '{':
      case '}':
      case ',':
      case ';':
      case ' ':
        word[i] = '\0';
        evalWord(word, outputFile);
        fprintf(outputFile, "%c", c);
        i = 0;
        break;

      case '\0':
        word[i] = '\0';
        evalWord(word, outputFile);
        return;

      case '\n':
        word[i] = '\0';
        evalWord(word, outputFile);
        fprintf(outputFile, "%c", '\n');
        return;

      default:
        word[i] = c;
        i++;
        break;
    }
    line++;
  }
}

int main(void) {
  FILE *inputFile;
  FILE *outputFile;

  inputFile = fopen("in.c", "r");
  outputFile = fopen("out.c", "w");

  char line[256];
  while (fgets(line, sizeof(line), inputFile) != NULL) {
    parseLine(line, outputFile);
  }

  return 0;
}
```