Skip to content

Commit 4b4dca0

Browse files
jammy0903claude
andcommitted
feat: C 시뮬레이터 char 배열 문자열 리터럴 + 전역 struct 지원 추가
- array.handler: char str[] = "Hello", char str[20] = "Hello" 패턴 추가 - simulator: 전역 struct 정의 사전 처리 (멀티라인 병합 포함) - simulator: clearStructDefs() 호출로 실행 간 상태 정리 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 976549b commit 4b4dca0

3 files changed

Lines changed: 227 additions & 0 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# C Playground 시뮬레이터: char 배열 + struct 지원 추가
2+
3+
**작성일**: 2026-02-04
4+
**상태**: 완료
5+
6+
---
7+
8+
## 문제 분석
9+
10+
### 1. Char 배열 (`char str[] = "Hello"`) - 패턴 누락
11+
- `array.handler.ts``PATTERNS`에 문자열 리터럴 초기화 패턴이 없었음
12+
- `char arr[4] = {'a','b','c','d'}` (brace init)만 지원
13+
- `char str[] = "Hello"`, `char str[20] = "Hello"` 패턴 추가 필요
14+
15+
### 2. Struct - 두 가지 문제
16+
17+
**문제 A: 파서가 전역 struct 정의를 무시**
18+
- `function-parser.ts``parseCode()`는 함수 정의만 추출
19+
- `struct Point { int x; int y; };`가 함수 바깥에 있으면 파서가 스킵 → StructHandler에 전달 안 됨
20+
21+
**문제 B: 멀티라인 struct가 한 줄 regex에 매칭 안 됨**
22+
- `STRUCT_DEF` regex: `/^struct\s+(\w+)\s*\{([^}]+)\}\s*;?\s*$/`
23+
- 한 줄 완결 struct만 매칭
24+
- 여러 줄로 쓰면 매칭 실패
25+
26+
---
27+
28+
## 수정 내용 (3개 파일)
29+
30+
### 1. `packages/backend/src/modules/simulators/c/handlers/array.handler.ts`
31+
- **STRING_INIT_SIZED** 패턴 추가: `char str[20] = "Hello"` (크기 명시)
32+
- **STRING_INIT_AUTO** 패턴 추가: `char str[] = "Hello"` (크기 자동 = 문자 수 + 1)
33+
- `canHandle()`, `handle()`에 새 패턴 분기 추가 (ARRAY_INIT보다 먼저)
34+
- `handleStringLiteral()` 함수 추가:
35+
- 각 문자를 ASCII 바이트로 변환
36+
- null terminator `\0` 자동 추가
37+
- 명시 크기보다 짧으면 0 패딩
38+
- Phase 4 이벤트 발생
39+
40+
### 2. `packages/backend/src/modules/simulators/c/simulator.ts`
41+
- `clearStructDefs` import 추가 (from `./handlers/struct.handler`)
42+
- `simulate()` 시작 시 `clearStructDefs()` 호출 (이전 실행 잔여 정의 제거)
43+
- `preprocessGlobalStructs()` private 메서드 추가:
44+
- 함수 영역(bodyStart~bodyEnd)을 파악하여 함수 바깥만 스캔
45+
- `struct Name {`로 시작하는 줄 감지
46+
- 한 줄 완결 struct → 그대로 `analyzeLine()` 호출
47+
- 멀티라인 struct → `}`까지 병합하여 한 줄로 만든 후 `analyzeLine()` 호출
48+
- StructHandler가 정의를 파싱하여 `structDefs` 맵에 저장
49+
50+
### 3. `packages/backend/src/modules/simulators/c/handlers/struct.handler.ts`
51+
- 코드 변경 없음 (`clearStructDefs()`는 이미 export되어 있음)
52+
- simulator.ts에서 import하여 호출만 추가
53+
54+
---
55+
56+
## 검증 방법
57+
58+
```bash
59+
# 빌드 확인
60+
pnpm --filter @codeinsight/backend build # ✅ 통과
61+
62+
# API 테스트 - char 배열
63+
curl -X POST http://localhost:3002/api/v1/simulators/c/trace \
64+
-H "Content-Type: application/json" \
65+
-d '{"code": "#include <stdio.h>\nint main() {\n char str[] = \"Hello\";\n printf(\"%s\\n\", str);\n return 0;\n}"}'
66+
67+
# API 테스트 - struct
68+
curl -X POST http://localhost:3002/api/v1/simulators/c/trace \
69+
-H "Content-Type: application/json" \
70+
-d '{"code": "#include <stdio.h>\nstruct Point {\n int x;\n int y;\n};\nint main() {\n struct Point p;\n p.x = 10;\n p.y = 20;\n printf(\"(%d, %d)\\n\", p.x, p.y);\n return 0;\n}"}'
71+
```
72+
73+
### 확인 포인트
74+
- char[]: stack에 변수 나타남, value가 "Hello", bytes에 ASCII 값 + 0 (null terminator)
75+
- struct: structDefs에 정의 등록됨, stack에 p 나타남, is_struct: true, 멤버 접근 동작

packages/backend/src/modules/simulators/c/handlers/array.handler.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* - int arr[5] = {1, 2, 3, 4, 5};
77
* - float arr[3] = {1.5, 2.5, 3.5};
88
* - char arr[4] = {'a', 'b', 'c', 'd'};
9+
* - char str[] = "Hello";
10+
* - char str[20] = "Hello";
911
* - double arr[2];
1012
* - arr[0] = 10;
1113
*/
@@ -15,6 +17,10 @@ import { cTypeRegistry, type TypeInfo } from '../types';
1517

1618
// 패턴 정의 (모든 타입 지원)
1719
const PATTERNS = {
20+
// char str[20] = "Hello"; (크기 명시, 더 구체적이므로 먼저)
21+
STRING_INIT_SIZED: /^char\s+(\w+)\s*\[\s*(\d+)\s*\]\s*=\s*"([^"]*)"/,
22+
// char str[] = "Hello"; (크기 자동 계산)
23+
STRING_INIT_AUTO: /^char\s+(\w+)\s*\[\s*\]\s*=\s*"([^"]*)"/,
1824
// type arr[5] = {values};
1925
ARRAY_INIT: /^(unsigned\s+)?(\w+(?:\s+\w+)?)\s+(\w+)\s*\[\s*(\d+)\s*\]\s*=\s*\{([^}]+)\}/,
2026
// type arr[5];
@@ -29,13 +35,27 @@ export const ArrayHandler: CodeHandler = {
2935

3036
canHandle(code: string): boolean {
3137
return (
38+
PATTERNS.STRING_INIT_SIZED.test(code) ||
39+
PATTERNS.STRING_INIT_AUTO.test(code) ||
3240
PATTERNS.ARRAY_INIT.test(code) ||
3341
PATTERNS.ARRAY_DECL.test(code) ||
3442
PATTERNS.ARRAY_ASSIGN.test(code)
3543
);
3644
},
3745

3846
handle(ctx: SimContext, lineNum: number, code: string): Step | null {
47+
// char str[20] = "Hello"; (크기 명시 — 더 구체적이므로 먼저 체크)
48+
const strSized = code.match(PATTERNS.STRING_INIT_SIZED);
49+
if (strSized) {
50+
return handleStringLiteral(ctx, lineNum, code, strSized[1], strSized[3], parseInt(strSized[2]));
51+
}
52+
53+
// char str[] = "Hello"; (크기 자동)
54+
const strAuto = code.match(PATTERNS.STRING_INIT_AUTO);
55+
if (strAuto) {
56+
return handleStringLiteral(ctx, lineNum, code, strAuto[1], strAuto[2]);
57+
}
58+
3959
// type arr[5] = {values};
4060
const arrInit = code.match(PATTERNS.ARRAY_INIT);
4161
if (arrInit) {
@@ -304,4 +324,67 @@ function handleArrayAssign(
304324
return ctx.createStep(lineNum, code, explanation);
305325
}
306326

327+
/**
328+
* 문자열 리터럴로 char 배열 선언
329+
* char str[] = "Hello"; 또는 char str[20] = "Hello";
330+
*/
331+
function handleStringLiteral(
332+
ctx: SimContext,
333+
lineNum: number,
334+
code: string,
335+
name: string,
336+
literal: string,
337+
explicitSize?: number
338+
): Step {
339+
const chars = literal.split('');
340+
const size = explicitSize || chars.length + 1; // +1 for null terminator
341+
const addr = ctx.allocateStack(size);
342+
343+
// 바이트 배열: 각 문자 ASCII + null terminator + 패딩
344+
const bytes = chars.map((c) => c.charCodeAt(0));
345+
bytes.push(0); // null terminator '\0'
346+
while (bytes.length < size) bytes.push(0); // 나머지 패딩
347+
348+
// 표시용 문자 목록
349+
const displayChars = [...chars.map((c) => `'${c}'`), "'\\0'"];
350+
351+
ctx.variables.set(name, {
352+
address: ctx.toHex(addr),
353+
type: `char[${size}]`,
354+
size,
355+
bytes,
356+
value: `"${literal}"`,
357+
is_array: true,
358+
array_size: size,
359+
element_type: 'char',
360+
element_size: 1,
361+
});
362+
363+
// Phase 4: 배열 선언 이벤트
364+
if (ctx.addEvent && ctx.getCurrentFrame) {
365+
ctx.addEvent({
366+
type: 'variable',
367+
action: 'declare',
368+
frame: ctx.getCurrentFrame(),
369+
name,
370+
varType: `char[${size}]`,
371+
value: `"${literal}"`,
372+
address: ctx.toHex(addr),
373+
size,
374+
});
375+
}
376+
377+
const explanation = `📚 문자열 '${name}' 선언: "${literal}"
378+
379+
• 타입: char[${size}] (${size}바이트)
380+
• 스택 주소: ${ctx.toHex(addr)}
381+
• 문자 구성: {${displayChars.join(', ')}}
382+
• 바이트: [${bytes.slice(0, chars.length + 1).join(', ')}]
383+
384+
💡 C 문자열은 char 배열 + null terminator('\\0')
385+
"${literal}" → ${chars.length}글자 + '\\0' = ${chars.length + 1}바이트 필요${explicitSize && explicitSize > chars.length + 1 ? `\n 선언 크기 ${explicitSize} > 필요 크기 ${chars.length + 1} → 나머지는 0으로 채워짐` : ''}`;
386+
387+
return ctx.createStep(lineNum, code, explanation);
388+
}
389+
307390
export default ArrayHandler;

packages/backend/src/modules/simulators/c/simulator.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CallStack, ScopeManager, type Step, type MemoryBlock, type Variable, ty
1111
import { registry, type SimContext } from './handlers';
1212
import { ExpressionEvaluator, type EvalContext } from './evaluator';
1313
import { FrameManager, ParameterSetup } from './execution';
14+
import { clearStructDefs } from './handlers/struct.handler';
1415
import type { VisualizationEvent } from '@codeinsight/shared';
1516

1617
/**
@@ -92,6 +93,12 @@ class CSimulator implements SimContext, EvalContext {
9293
this.parseResult = parseCode(code);
9394
const { functions, sourceLines } = this.parseResult;
9495

96+
// 이전 실행 잔여 struct 정의 정리
97+
clearStructDefs();
98+
99+
// 전역 struct 정의 사전 처리 (함수 바깥의 struct 정의를 스캔)
100+
this.preprocessGlobalStructs(sourceLines);
101+
95102
// main 함수 찾기
96103
const mainFunc = functions.get('main');
97104
if (!mainFunc) {
@@ -192,6 +199,68 @@ ${headerExplanations}
192199
return steps;
193200
}
194201

202+
/**
203+
* 전역 struct 정의 사전 처리
204+
* 함수 바깥의 struct 정의를 찾아 StructHandler에 위임
205+
* 멀티라인 struct도 한 줄로 병합하여 처리
206+
*/
207+
private preprocessGlobalStructs(sourceLines: string[]): void {
208+
// 함수 영역을 파악 (함수 본문 내부인지 판별용)
209+
const functionRanges: Array<{ start: number; end: number }> = [];
210+
if (this.parseResult) {
211+
for (const [, func] of this.parseResult.functions) {
212+
// bodyStart/bodyEnd는 1-indexed
213+
functionRanges.push({ start: func.bodyStart, end: func.bodyEnd });
214+
}
215+
}
216+
217+
const isInsideFunction = (lineIdx: number): boolean => {
218+
const lineNum = lineIdx + 1; // 1-indexed
219+
return functionRanges.some((r) => lineNum >= r.start && lineNum <= r.end);
220+
};
221+
222+
let i = 0;
223+
while (i < sourceLines.length) {
224+
if (isInsideFunction(i)) {
225+
i++;
226+
continue;
227+
}
228+
229+
const line = sourceLines[i].trim();
230+
231+
// struct 키워드로 시작하고 멤버 정의를 포함하는 패턴 감지
232+
if (/^struct\s+\w+\s*\{/.test(line)) {
233+
// 한 줄에 완결되는 경우: struct Point { int x; int y; };
234+
if (/\}/.test(line)) {
235+
const cleanCode = line.replace(/;$/, '').trim();
236+
this.analyzeLine(i + 1, cleanCode);
237+
i++;
238+
continue;
239+
}
240+
241+
// 멀티라인 struct: 닫는 }까지 병합
242+
let merged = line;
243+
let j = i + 1;
244+
while (j < sourceLines.length) {
245+
const nextLine = sourceLines[j].trim();
246+
merged += ' ' + nextLine;
247+
if (nextLine.includes('}')) {
248+
break;
249+
}
250+
j++;
251+
}
252+
253+
// 병합된 한 줄을 정리하고 analyzeLine에 전달
254+
const cleanMerged = merged.replace(/\s+/g, ' ').replace(/;$/, '').trim();
255+
this.analyzeLine(i + 1, cleanMerged);
256+
i = j + 1;
257+
continue;
258+
}
259+
260+
i++;
261+
}
262+
}
263+
195264
/**
196265
* 함수 진입 스텝 생성
197266
*/

0 commit comments

Comments
 (0)