프로그래밍/Tips [IA]
[IA] Simple Interpreter in C99
nanze
2025. 2. 25. 14:35
반응형
구현 환경
- 언어: C99
- 라이브러리: cJSON (단일 헤더 파일 기반 경량 JSON 파서)
- 의존성: libxml2 사용 불가, cJSON만 사용
cJSON는 GitHub에서 다운로드 가능하며, 프로젝트에 cJSON.h와 cJSON.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. 에러 처리
- errorOccurred와 errorMessage로 런타임 오류를 기록하고, 실행을 중단.
- 예: 정의되지 않은 변수, 잘못된 JSON 구조, 인자 불일치 등.
6. 메모리 관리
- 모든 동적 할당된 메모리(스코프, 함수, 문자열 등)를 main 종료 시 해제.
동작 확인
- cJSON.h와 cJSON.c를 프로젝트에 추가.
- 위 코드를 컴파일: gcc -o interpreter interpreter.c cJSON.c.
- script.json을 작성 후 실행: ./interpreter.
출력은 변수 설정, 함수 실행 등의 결과를 콘솔에 표시하며, 오류가 발생하면 메시지를 출력합니다.
반응형