LoginSignup
22
1

概要

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

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

アンケート.png

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

インストール方法

brew tap satooru65536/cmm
brew install cmm

使い方

使い方は SatooRu65536/cmm-compiler を見てください

実装内容

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

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

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

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

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

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

以下はPythonでのf文字列の例です

num = 65536
print(f"2^16 = {num}")  # 2^16 = 65536

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

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

実装

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

  FILE *inputFile;
  FILE *outputFile;

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

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

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

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

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

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文字列しかないので
いい感じに処理をするようにします

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 に書かせた...)

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

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 を作成して実行すると、変換してくれると思います

in.c
printf(f"num = {num:d}");
out.c
printf("num = %d", num);

最後に

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

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

最終的なコードを貼り付けておきます

#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;
}
22
1
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
1