Cプログラミング
 Computer >> コンピューター >  >> プログラミング >> Cプログラミング

C言語でトークンを検出するプログラムの作成方法|字句解析の基本と実装例

本記事では、C言語のソースコードからトークンを検出するCプログラムを作成します。これはコンパイラにおける字句解析(レキシカルアナリシス)のフェーズに相当します。字句解析器(レキサー)はコンパイラの構成要素の一つであり、プログラムからトークンを切り出し、後段の構文解析器(パーサー)へ受け渡す役割を担います。

トークンとは何か

トークンとは、ソースコードを構成する最小単位のことです。C言語におけるトークンは、主に次のいずれかに分類されます。

  • キーワード(予約語)
  • 識別子(変数名・関数名など)
  • 定数
  • 文字列リテラル
  • 記号(演算子・区切り文字)

C言語における各種トークンの例

キーワード : for, if, include など
識別子     : 変数、関数 など
区切り文字 : ',', ';' など
演算子     : '-', '=', '++' など

トークン検出プログラムの実装

以下が、Cプログラム内のトークンを検出するサンプルコードです。

#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
bool isValidDelimiter(char ch) {
    if (ch == ' ' || ch == '+' || ch == '-' || ch == '*' ||
    ch == '/' || ch == ',' || ch == ';' || ch == '>' ||
    ch == '<' || ch == '=' || ch == '(' || ch == ')' ||
    ch == '[' || ch == ']' || ch == '{' || ch == '}')
    return (true);
    return (false);
}
bool isValidOperator(char ch){
    if (ch == '+' || ch == '-' || ch == '*' ||
    ch == '/' || ch == '>' || ch == '<' ||
    ch == '=')
    return (true);
    return (false);
}
// 文字列が有効な識別子(IDENTIFIER)であれば true を返す
bool isvalidIdentifier(char* str){
    if (str[0] == '0' || str[0] == '1' || str[0] == '2' ||
    str[0] == '3' || str[0] == '4' || str[0] == '5' ||
    str[0] == '6' || str[0] == '7' || str[0] == '8' ||
    str[0] == '9' || isValidDelimiter(str[0]) == true)
    return (false);
    return (true);
}
bool isValidKeyword(char* str) {
    if (!strcmp(str, "if") || !strcmp(str, "else") || !strcmp(str, "while") || !strcmp(str, "do") || !strcmp(str, "break") || !strcmp(str, "continue") || !strcmp(str, "int")
    || !strcmp(str, "double") || !strcmp(str, "float") || !strcmp(str, "return") || !strcmp(str, "char") || !strcmp(str, "case") || !strcmp(str, "char")
    || !strcmp(str, "sizeof") || !strcmp(str, "long") || !strcmp(str, "short") || !strcmp(str, "typedef") || !strcmp(str, "switch") || !strcmp(str, "unsigned")
    || !strcmp(str, "void") || !strcmp(str, "static") || !strcmp(str, "struct") || !strcmp(str, "goto"))
    return (true);
    return (false);
}
bool isValidInteger(char* str) {
    int i, len = strlen(str);
    if (len == 0)
    return (false);
    for (i = 0; i < len; i++) {
        if (str[i] != '0' && str[i] != '1' && str[i] != '2'&& str[i] != '3' && str[i] != '4' && str[i] != '5'
        && str[i] != '6' && str[i] != '7' && str[i] != '8' && str[i] != '9' || (str[i] == '-' && i > 0))
        return (false);
    }
    return (true);
}
bool isRealNumber(char* str) {
    int i, len = strlen(str);
    bool hasDecimal = false;
    if (len == 0)
    return (false);
    for (i = 0; i < len; i++) {
        if (str[i] != '0' && str[i] != '1' && str[i] != '2' && str[i] != '3' && str[i] != '4' && str[i]
        != '5' && str[i] != '6' && str[i] != '7' && str[i] != '8'
        && str[i] != '9' && str[i] != '.' || (str[i] == '-' && i > 0))
        return (false);
            if (str[i] == '.')
        hasDecimal = true;
    }
    return (hasDecimal);
}
char* subString(char* str, int left, int right) {
    int i;
    char* subStr = (char*)malloc( sizeof(char) * (right - left + 2));
    for (i = left; i <= right; i++)
        subStr[i - left] = str[i];
    subStr[right - left + 1] = '\0';
    return (subStr);
}
void detectTokens(char* str) {
    int left = 0, right = 0;
    int length = strlen(str);
    while (right <= length && left <= right) {
        if (isValidDelimiter(str[right]) == false)
        right++;
        if (isValidDelimiter(str[right]) == true && left == right) {
            if (isValidOperator(str[right]) == true)
            printf("Valid operator : '%c'\n", str[right]);
            right++;
            left = right;
        } else if (isValidDelimiter(str[right]) == true && left != right || (right == length && left != right)) {
            char* subStr = subString(str, left, right - 1);
            if (isValidKeyword(subStr) == true)
                printf("Valid keyword : '%s'\n", subStr);
            else if (isValidInteger(subStr) == true)
                printf("Valid Integer : '%s'\n", subStr);
            else if (isRealNumber(subStr) == true)
                printf("Real Number : '%s'\n", subStr);
            else if (isvalidIdentifier(subStr) == true
                && isValidDelimiter(str[right - 1]) == false)
            printf("Valid Identifier : '%s'\n", subStr);
            else if (isvalidIdentifier(subStr) == false
                && isValidDelimiter(str[right - 1]) == false)
            printf("Invalid Identifier : '%s'\n", subStr);
            left = right;
        }
    }
    return;
}
int main(){
    char str[100] = "float x = a + 1b; ";
    printf("The Program is : '%s' \n", str);
    printf("All Tokens are : \n");
    detectTokens(str);
    return (0);
}

実行結果

The Program is : 'float x = a + 1b; '
All Tokens are :
Valid keyword : 'float'
Valid Identifier : 'x'
Valid operator : '='
Valid Identifier : 'a'
Valid operator : '+'
Invalid Identifier : '1b'

プログラムの仕組み

このプログラムは、入力文字列を先頭から順に走査し、区切り文字(空白や記号など)で区切られた部分文字列を切り出します。切り出した部分文字列に対して、以下の順序で判定を行います。

  1. isValidKeyword():キーワード(if、int、float など)かどうか
  2. isValidInteger():整数リテラルかどうか
  3. isRealNumber():小数点を含む実数かどうか
  4. isvalidIdentifier():数字で始まらず、区切り文字でもない有効な識別子かどうか

これらのいずれにも該当しない場合、その文字列は「無効な識別子」として出力されます。実行例では、float がキーワード、xa が有効な識別子、=+ が演算子として正しく認識されている一方、数字で始まる 1b は無効な識別子と判定されます。このように、字句解析の基本的な流れを簡潔なコードで体験できる好例といえるでしょう。

  1. 【初心者向け】平行四辺形の外周(周長)を計算するC言語プログラム

    本記事では、2つの辺の長さが与えられた平行四辺形の外周(周囲の長さ)を計算し、その結果を表示するC言語プログラムを紹介します。数式の考え方からアルゴリズム、実際のコードまで順を追って解説していきます。 平行四辺形とは? 平行四辺形とは、次のような性質を持つ四角形の一種です。 向かい合う2組の辺がそれぞれ平行である 向かい合う角の大きさが互いに等しい 2本の対角線が互いの中点で交わる 下の図では、「a」と「b」が平行四辺形の隣り合う2つの辺の長さを表しています。 平行四辺形の外周の求め方 平行四辺形の外周(周長)は、次の式で定義されます。 外周 = 2 × (a + b)   = 2 ×

  2. 【Python入門】有向グラフにサイクル(閉路)が存在するかを検出するプログラムの作り方

    本記事では、「与えられた有向グラフの中にサイクル(閉路)が存在するかどうかを判定する」という問題を、Pythonを使って解決する方法を解説します。 問題の概要 問題文: 有向グラフが与えられたとき、そのグラフにサイクルが含まれているかどうかを判定してください。少なくとも1つのサイクルが存在する場合は True を、存在しない場合は False を出力します。 この問題は、グラフ理論における基本的かつ重要なトピックの一つです。例えば、タスクのスケジューリングや依存関係の管理において、循環参照(デッドロック)を検出する場面などで応用されます。 判定には深さ優先探索(DFS)を利用します。ポイントは