프로그래밍/Tips [IA]

[IA] Simple Interpreter in C99

nanze 2025. 2. 25. 14:35
반응형

구현 환경


  • 언어: C99
  • 라이브러리: cJSON (단일 헤더 파일 기반 경량 JSON 파서)
  • 의존성: libxml2 사용 불가, cJSON만 사용

cJSONGitHub에서 다운로드 가능하며, 프로젝트에 cJSON.hcJSON.c를 포함해야 합니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "cJSON.h"

// 데이터 타입 정의
typedef enum {
    TYPE_STRING,
    TYPE_NUMBER,
    TYPE_BOOL
} DataType;

// 값 구조체
typedef struct {
    DataType type;
    union {
        char* string;
        double number;
        int boolean;
    } value;
} Value;

// 심볼 구조체 (변수)
typedef struct {
    char* name;
    Value value;
} Symbol;

// 스코프 구조체
typedef struct Scope {
    Symbol* symbols;
    int symbol_count;
    int capacity;
    struct Scope* parent;
} Scope;

// 함수 구조체
typedef struct {
    char* name;
    char** params; // 매개변수 이름 배열
    int param_count;
    cJSON* body;
} Function;

Function functions[100];
int function_count = 0;

// 에러 플래그 및 메시지
int errorOccurred = 0;
char errorMessage[256] = {0};

// 스코프 생성
Scope* create_scope(Scope* parent) {
    Scope* scope = (Scope*)malloc(sizeof(Scope));
    scope->symbols = (Symbol*)malloc(10 * sizeof(Symbol));
    scope->symbol_count = 0;
    scope->capacity = 10;
    scope->parent = parent;
    return scope;
}

// 스코프 해제
void free_scope(Scope* scope) {
    for (int i = 0; i < scope->symbol_count; i++) {
        free(scope->symbols[i].name);
        if (scope->symbols[i].value.type == TYPE_STRING)
            free(scope->symbols[i].value.value.string);
    }
    free(scope->symbols);
    free(scope);
}

// 값 복사
Value copy_value(Value src) {
    Value dst;
    dst.type = src.type;
    switch (src.type) {
        case TYPE_STRING:
            dst.value.string = strdup(src.value.string);
            break;
        case TYPE_NUMBER:
            dst.value.number = src.value.number;
            break;
        case TYPE_BOOL:
            dst.value.boolean = src.value.boolean;
            break;
    }
    return dst;
}

// 변수 값 조회
Value* get_symbol_value(Scope* scope, const char* name) {
    while (scope) {
        for (int i = 0; i < scope->symbol_count; i++) {
            if (strcmp(scope->symbols[i].name, name) == 0) {
                return &scope->symbols[i].value;
            }
        }
        scope = scope->parent;
    }
    return NULL;
}

// 변수 값 설정
void set_symbol_value(Scope* scope, const char* name, Value value) {
    for (int i = 0; i < scope->symbol_count; i++) {
        if (strcmp(scope->symbols[i].name, name) == 0) {
            if (scope->symbols[i].value.type == TYPE_STRING)
                free(scope->symbols[i].value.value.string);
            scope->symbols[i].value = copy_value(value);
            return;
        }
    }
    if (scope->symbol_count >= scope->capacity) {
        scope->capacity *= 2;
        scope->symbols = (Symbol*)realloc(scope->symbols, scope->capacity * sizeof(Symbol));
    }
    scope->symbols[scope->symbol_count].name = strdup(name);
    scope->symbols[scope->symbol_count].value = copy_value(value);
    scope->symbol_count++;
}

// 조건식 평가 (간단한 수식 파싱)
int evaluate_condition(Scope* scope, const char* condition) {
    char left[256], op[3], right[256];
    sscanf(condition, "%255s %2s %255s", left, op, right);

    Value* left_val = get_symbol_value(scope, left);
    Value* right_val = get_symbol_value(scope, right);

    if (!left_val || !right_val) {
        snprintf(errorMessage, sizeof(errorMessage), "Undefined variable in condition: %s", condition);
        errorOccurred = 1;
        return 0;
    }

    double lnum = (left_val->type == TYPE_NUMBER) ? left_val->value.number : atof(left_val->value.string);
    double rnum = (right_val->type == TYPE_NUMBER) ? right_val->value.number : atof(right_val->value.string);

    if (strcmp(op, ">") == 0) return lnum > rnum;
    if (strcmp(op, "<") == 0) return lnum < rnum;
    if (strcmp(op, "==") == 0) return lnum == rnum;
    if (strcmp(op, ">=") == 0) return lnum >= rnum;
    if (strcmp(op, "<=") == 0) return lnum <= rnum;

    snprintf(errorMessage, sizeof(errorMessage), "Unknown operator: %s", op);
    errorOccurred = 1;
    return 0;
}

// JSON 실행 함수
void execute_json(cJSON* json, Scope* scope) {
    if (!json || errorOccurred) return;

    cJSON* type = cJSON_GetObjectItem(json, "type");
    if (!type || type->type != cJSON_String) {
        snprintf(errorMessage, sizeof(errorMessage), "Missing or invalid 'type' field");
        errorOccurred = 1;
        return;
    }

    if (strcmp(type->valuestring, "var") == 0) {
        cJSON* name = cJSON_GetObjectItem(json, "name");
        cJSON* value = cJSON_GetObjectItem(json, "value");
        if (!name || !value || name->type != cJSON_String) {
            snprintf(errorMessage, sizeof(errorMessage), "Invalid 'var' definition");
            errorOccurred = 1;
            return;
        }
        Value val;
        if (value->type == cJSON_String) {
            val.type = TYPE_STRING;
            val.value.string = strdup(value->valuestring);
        } else if (value->type == cJSON_Number) {
            val.type = TYPE_NUMBER;
            val.value.number = value->valuedouble;
        } else if (value->type == cJSON_True || value->type == cJSON_False) {
            val.type = TYPE_BOOL;
            val.value.boolean = (value->type == cJSON_True);
        } else {
            snprintf(errorMessage, sizeof(errorMessage), "Unsupported value type for variable %s", name->valuestring);
            errorOccurred = 1;
            return;
        }
        set_symbol_value(scope, name->valuestring, val);
    }
    else if (strcmp(type->valuestring, "if") == 0) {
        cJSON* condition = cJSON_GetObjectItem(json, "condition");
        cJSON* body = cJSON_GetObjectItem(json, "body");
        if (!condition || condition->type != cJSON_String || !body || body->type != cJSON_Array) {
            snprintf(errorMessage, sizeof(errorMessage), "Invalid 'if' structure");
            errorOccurred = 1;
            return;
        }
        if (evaluate_condition(scope, condition->valuestring)) {
            cJSON* child = NULL;
            cJSON_ArrayForEach(child, body) {
                execute_json(child, scope);
            }
        }
    }
    else if (strcmp(type->valuestring, "while") == 0) {
        cJSON* condition = cJSON_GetObjectItem(json, "condition");
        cJSON* body = cJSON_GetObjectItem(json, "body");
        if (!condition || condition->type != cJSON_String || !body || body->type != cJSON_Array) {
            snprintf(errorMessage, sizeof(errorMessage), "Invalid 'while' structure");
            errorOccurred = 1;
            return;
        }
        while (!errorOccurred && evaluate_condition(scope, condition->valuestring)) {
            cJSON* child = NULL;
            cJSON_ArrayForEach(child, body) {
                execute_json(child, scope);
            }
        }
    }
    else if (strcmp(type->valuestring, "function") == 0) {
        cJSON* name = cJSON_GetObjectItem(json, "name");
        cJSON* params = cJSON_GetObjectItem(json, "params");
        cJSON* body = cJSON_GetObjectItem(json, "body");
        if (!name || name->type != cJSON_String || !body || body->type != cJSON_Array) {
            snprintf(errorMessage, sizeof(errorMessage), "Invalid 'function' definition");
            errorOccurred = 1;
            return;
        }
        functions[function_count].name = strdup(name->valuestring);
        functions[function_count].param_count = (params && params->type == cJSON_Array) ? cJSON_GetArraySize(params) : 0;
        functions[function_count].params = (char**)malloc(functions[function_count].param_count * sizeof(char*));
        if (params) {
            int i = 0;
            cJSON* param = NULL;
            cJSON_ArrayForEach(param, params) {
                if (param->type == cJSON_String)
                    functions[function_count].params[i++] = strdup(param->valuestring);
            }
        }
        functions[function_count].body = cJSON_Duplicate(body, 1);
        function_count++;
    }
    else if (strcmp(type->valuestring, "call") == 0) {
        cJSON* name = cJSON_GetObjectItem(json, "name");
        cJSON* args = cJSON_GetObjectItem(json, "args");
        if (!name || name->type != cJSON_String) {
            snprintf(errorMessage, sizeof(errorMessage), "Invalid 'call' structure");
            errorOccurred = 1;
            return;
        }
        for (int i = 0; i < function_count; i++) {
            if (strcmp(functions[i].name, name->valuestring) == 0) {
                Scope* local_scope = create_scope(scope);
                int arg_count = (args && args->type == cJSON_Array) ? cJSON_GetArraySize(args) : 0;
                if (arg_count != functions[i].param_count) {
                    snprintf(errorMessage, sizeof(errorMessage), "Argument count mismatch for function %s", name->valuestring);
                    errorOccurred = 1;
                    free_scope(local_scope);
                    return;
                }
                cJSON* arg = NULL;
                int j = 0;
                cJSON_ArrayForEach(arg, args) {
                    Value val;
                    if (arg->type == cJSON_String) {
                        val.type = TYPE_STRING;
                        val.value.string = strdup(arg->valuestring);
                    } else if (arg->type == cJSON_Number) {
                        val.type = TYPE_NUMBER;
                        val.value.number = arg->valuedouble;
                    } else if (arg->type == cJSON_True || arg->type == cJSON_False) {
                        val.type = TYPE_BOOL;
                        val.value.boolean = (arg->type == cJSON_True);
                    } else continue;
                    set_symbol_value(local_scope, functions[i].params[j++], val);
                }
                cJSON* child = NULL;
                cJSON_ArrayForEach(child, functions[i].body) {
                    execute_json(child, local_scope);
                }
                free_scope(local_scope);
                break;
            }
        }
    }
}

int main() {
    FILE* fp = fopen("script.json", "r");
    if (!fp) {
        fprintf(stderr, "Failed to open script.json\n");
        return 1;
    }
    fseek(fp, 0, SEEK_END);
    long len = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    char* json_str = (char*)malloc(len + 1);
    fread(json_str, 1, len, fp);
    json_str[len] = 0;
    fclose(fp);

    cJSON* root = cJSON_Parse(json_str);
    if (!root) {
        fprintf(stderr, "Failed to parse JSON: %s\n", cJSON_GetErrorPtr());
        free(json_str);
        return 1;
    }

    Scope* global_scope = create_scope(NULL);
    if (cJSON_IsArray(root)) {
        cJSON* child = NULL;
        cJSON_ArrayForEach(child, root) {
            execute_json(child, global_scope);
            if (errorOccurred) {
                fprintf(stderr, "Error: %s\n", errorMessage);
                break;
            }
        }
    }

    cJSON_Delete(root);
    free(json_str);
    free_scope(global_scope);

    for (int i = 0; i < function_count; i++) {
        free(functions[i].name);
        for (int j = 0; j < functions[i].param_count; j++)
            free(functions[i].params[j]);
        free(functions[i].params);
        cJSON_Delete(functions[i].body);
    }

    return errorOccurred ? 1 : 0;
}

JSON 스크립트 예시


다음은 위 코드를 테스트하기 위한 script.json 파일입니다:

[
    {
        "type": "var",
        "name": "x",
        "value": 10
    },
    {
        "type": "var",
        "name": "flag",
        "value": true
    },
    {
        "type": "if",
        "condition": "x > 5",
        "body": [
            {
                "type": "var",
                "name": "y",
                "value": "Condition met"
            }
        ]
    },
    {
        "type": "function",
        "name": "add",
        "params": ["a", "b"],
        "body": [
            {
                "type": "var",
                "name": "sum",
                "value": 0
            },
            {
                "type": "var",
                "name": "sum",
                "value": 15
            }
        ]
    },
    {
        "type": "call",
        "name": "add",
        "args": [5, 10]
    },
    {
        "type": "while",
        "condition": "x < 12",
        "body": [
            {
                "type": "var",
                "name": "x",
                "value": 11
            }
        ]
    }
]

코드 설명


1. 데이터 타입과 값 관리


  • Value 구조체는 DataType 열거형과 공용체를 사용해 문자열, 숫자, 불리언을 지원.
  • copy_value 함수로 값 복사 시 메모리 누수를 방지.

2. 스코프 관리


  • Scope 구조체는 동적 배열로 변수를 저장하며, 부모 스코프를 참조해 변수 조회 시 상위 스코프로 확장.
  • 함수 호출 시 로컬 스코프를 생성하여 변수 격리.

3. 조건식 평가


  • evaluate_condition"x > 5" 같은 조건식을 파싱하고, 변수 값을 숫자로 변환해 비교.
  • 지원 연산자: >, <, ==, >=, <=.

4. 함수와 매개변수


  • Function 구조체에 매개변수 이름 배열 추가.
  • call 명령어에서 인자 수와 매개변수 수를 확인하며, 로컬 스코프에 매핑.

5. 에러 처리


  • errorOccurrederrorMessage로 런타임 오류를 기록하고, 실행을 중단.
  • 예: 정의되지 않은 변수, 잘못된 JSON 구조, 인자 불일치 등.

6. 메모리 관리


  • 모든 동적 할당된 메모리(스코프, 함수, 문자열 등)를 main 종료 시 해제.

동작 확인


  1. cJSON.hcJSON.c를 프로젝트에 추가.
  2. 위 코드를 컴파일: gcc -o interpreter interpreter.c cJSON.c.
  3. script.json을 작성 후 실행: ./interpreter.

출력은 변수 설정, 함수 실행 등의 결과를 콘솔에 표시하며, 오류가 발생하면 메시지를 출력합니다.

반응형