deukpack 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +6 -0
  3. package/README.ko.md +138 -0
  4. package/README.md +182 -0
  5. package/RELEASING.md +71 -0
  6. package/bin/deukpack.js +9 -0
  7. package/dist/ast/DeukPackASTBuilder.d.ts +153 -0
  8. package/dist/ast/DeukPackASTBuilder.d.ts.map +1 -0
  9. package/dist/ast/DeukPackASTBuilder.js +931 -0
  10. package/dist/ast/DeukPackASTBuilder.js.map +1 -0
  11. package/dist/codegen/CSharpGenerator.d.ts +136 -0
  12. package/dist/codegen/CSharpGenerator.d.ts.map +1 -0
  13. package/dist/codegen/CSharpGenerator.js +2303 -0
  14. package/dist/codegen/CSharpGenerator.js.map +1 -0
  15. package/dist/codegen/CodeGenerator.d.ts +11 -0
  16. package/dist/codegen/CodeGenerator.d.ts.map +1 -0
  17. package/dist/codegen/CodeGenerator.js +11 -0
  18. package/dist/codegen/CodeGenerator.js.map +1 -0
  19. package/dist/codegen/CppGenerator.d.ts +23 -0
  20. package/dist/codegen/CppGenerator.d.ts.map +1 -0
  21. package/dist/codegen/CppGenerator.js +215 -0
  22. package/dist/codegen/CppGenerator.js.map +1 -0
  23. package/dist/codegen/HighPerformanceCSharpGenerator.d.ts +29 -0
  24. package/dist/codegen/HighPerformanceCSharpGenerator.d.ts.map +1 -0
  25. package/dist/codegen/HighPerformanceCSharpGenerator.js +486 -0
  26. package/dist/codegen/HighPerformanceCSharpGenerator.js.map +1 -0
  27. package/dist/core/DeukPackEngine.d.ts +69 -0
  28. package/dist/core/DeukPackEngine.d.ts.map +1 -0
  29. package/dist/core/DeukPackEngine.js +379 -0
  30. package/dist/core/DeukPackEngine.js.map +1 -0
  31. package/dist/core/DeukPackGenerator.d.ts +9 -0
  32. package/dist/core/DeukPackGenerator.d.ts.map +1 -0
  33. package/dist/core/DeukPackGenerator.js +15 -0
  34. package/dist/core/DeukPackGenerator.js.map +1 -0
  35. package/dist/core/DeukParser.d.ts +12 -0
  36. package/dist/core/DeukParser.d.ts.map +1 -0
  37. package/dist/core/DeukParser.js +27 -0
  38. package/dist/core/DeukParser.js.map +1 -0
  39. package/dist/core/IdlParser.d.ts +27 -0
  40. package/dist/core/IdlParser.d.ts.map +1 -0
  41. package/dist/core/IdlParser.js +157 -0
  42. package/dist/core/IdlParser.js.map +1 -0
  43. package/dist/core/ProtoParser.d.ts +12 -0
  44. package/dist/core/ProtoParser.d.ts.map +1 -0
  45. package/dist/core/ProtoParser.js +27 -0
  46. package/dist/core/ProtoParser.js.map +1 -0
  47. package/dist/csharp/DpExcelProtocol.cs +3005 -0
  48. package/dist/csharp/DpProtocolLibrary.cs +13 -0
  49. package/dist/index.d.ts +22 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +43 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/lexer/DeukLexer.d.ts +31 -0
  54. package/dist/lexer/DeukLexer.d.ts.map +1 -0
  55. package/dist/lexer/DeukLexer.js +292 -0
  56. package/dist/lexer/DeukLexer.js.map +1 -0
  57. package/dist/lexer/IdlLexer.d.ts +33 -0
  58. package/dist/lexer/IdlLexer.d.ts.map +1 -0
  59. package/dist/lexer/IdlLexer.js +286 -0
  60. package/dist/lexer/IdlLexer.js.map +1 -0
  61. package/dist/native/NativeDeukPackEngine.d.ts +30 -0
  62. package/dist/native/NativeDeukPackEngine.d.ts.map +1 -0
  63. package/dist/native/NativeDeukPackEngine.js +99 -0
  64. package/dist/native/NativeDeukPackEngine.js.map +1 -0
  65. package/dist/proto/ProtoASTBuilder.d.ts +29 -0
  66. package/dist/proto/ProtoASTBuilder.d.ts.map +1 -0
  67. package/dist/proto/ProtoASTBuilder.js +239 -0
  68. package/dist/proto/ProtoASTBuilder.js.map +1 -0
  69. package/dist/proto/ProtoLexer.d.ts +29 -0
  70. package/dist/proto/ProtoLexer.d.ts.map +1 -0
  71. package/dist/proto/ProtoLexer.js +264 -0
  72. package/dist/proto/ProtoLexer.js.map +1 -0
  73. package/dist/proto/ProtoTypes.d.ts +40 -0
  74. package/dist/proto/ProtoTypes.d.ts.map +1 -0
  75. package/dist/proto/ProtoTypes.js +37 -0
  76. package/dist/proto/ProtoTypes.js.map +1 -0
  77. package/dist/protocols/BinaryProtocol.d.ts +7 -0
  78. package/dist/protocols/BinaryProtocol.d.ts.map +1 -0
  79. package/dist/protocols/BinaryProtocol.js +11 -0
  80. package/dist/protocols/BinaryProtocol.js.map +1 -0
  81. package/dist/protocols/BinaryWriter.d.ts +22 -0
  82. package/dist/protocols/BinaryWriter.d.ts.map +1 -0
  83. package/dist/protocols/BinaryWriter.js +104 -0
  84. package/dist/protocols/BinaryWriter.js.map +1 -0
  85. package/dist/protocols/CompactProtocol.d.ts +7 -0
  86. package/dist/protocols/CompactProtocol.d.ts.map +1 -0
  87. package/dist/protocols/CompactProtocol.js +11 -0
  88. package/dist/protocols/CompactProtocol.js.map +1 -0
  89. package/dist/protocols/ExcelProtocol.d.ts +98 -0
  90. package/dist/protocols/ExcelProtocol.d.ts.map +1 -0
  91. package/dist/protocols/ExcelProtocol.js +639 -0
  92. package/dist/protocols/ExcelProtocol.js.map +1 -0
  93. package/dist/protocols/JsonProtocol.d.ts +68 -0
  94. package/dist/protocols/JsonProtocol.d.ts.map +1 -0
  95. package/dist/protocols/JsonProtocol.js +422 -0
  96. package/dist/protocols/JsonProtocol.js.map +1 -0
  97. package/dist/protocols/WireProtocol.d.ts +348 -0
  98. package/dist/protocols/WireProtocol.d.ts.map +1 -0
  99. package/dist/protocols/WireProtocol.js +912 -0
  100. package/dist/protocols/WireProtocol.js.map +1 -0
  101. package/dist/serialization/WireDeserializer.d.ts +8 -0
  102. package/dist/serialization/WireDeserializer.d.ts.map +1 -0
  103. package/dist/serialization/WireDeserializer.js +13 -0
  104. package/dist/serialization/WireDeserializer.js.map +1 -0
  105. package/dist/serialization/WireSerializer.d.ts +20 -0
  106. package/dist/serialization/WireSerializer.d.ts.map +1 -0
  107. package/dist/serialization/WireSerializer.js +100 -0
  108. package/dist/serialization/WireSerializer.js.map +1 -0
  109. package/dist/types/DeukPackTypes.d.ts +291 -0
  110. package/dist/types/DeukPackTypes.d.ts.map +1 -0
  111. package/dist/types/DeukPackTypes.js +76 -0
  112. package/dist/types/DeukPackTypes.js.map +1 -0
  113. package/dist/utils/EndianUtils.d.ts +11 -0
  114. package/dist/utils/EndianUtils.d.ts.map +1 -0
  115. package/dist/utils/EndianUtils.js +32 -0
  116. package/dist/utils/EndianUtils.js.map +1 -0
  117. package/dist/utils/PerformanceMonitor.d.ts +26 -0
  118. package/dist/utils/PerformanceMonitor.d.ts.map +1 -0
  119. package/dist/utils/PerformanceMonitor.js +57 -0
  120. package/dist/utils/PerformanceMonitor.js.map +1 -0
  121. package/package.json +77 -0
  122. package/scripts/build_deukpack.js +669 -0
@@ -0,0 +1,2303 @@
1
+ "use strict";
2
+ /**
3
+ * DeukPack C# Generator
4
+ * C# code generation
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.CSharpGenerator = void 0;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const DeukPackTypes_1 = require("../types/DeukPackTypes");
44
+ const CodeGenerator_1 = require("./CodeGenerator");
45
+ class CSharpGenerator extends CodeGenerator_1.CodeGenerator {
46
+ async generate(ast, _options) {
47
+ this._genOptions = _options;
48
+ // Generate separate files per IDL source file
49
+ const files = {};
50
+ // Group by source file
51
+ const fileGroups = this.groupBySourceFile(ast);
52
+ const metaTableDefsEarly = this.collectMetaTableDefinitions(ast);
53
+ this.validateTableLinkFields(ast, metaTableDefsEarly);
54
+ if (_options.efSupport) {
55
+ this._efMetaRowInfo = new Map();
56
+ for (const d of metaTableDefsEarly) {
57
+ this._efMetaRowInfo.set(d.rowTypeFull, { category: d.category, keyFieldNames: d.keyFieldNames });
58
+ }
59
+ }
60
+ else {
61
+ this._efMetaRowInfo = null;
62
+ }
63
+ for (const [sourceFile, definitions] of Object.entries(fileGroups)) {
64
+ const lines = [];
65
+ // Header
66
+ lines.push('/**');
67
+ lines.push(' * Autogenerated by DeukPack v1.0.0');
68
+ lines.push(' *');
69
+ lines.push(' * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING');
70
+ lines.push(' * @generated');
71
+ lines.push(' */');
72
+ lines.push('using System;');
73
+ lines.push('using System.Collections;');
74
+ lines.push('using System.Collections.Generic;');
75
+ lines.push('using System.Text;');
76
+ lines.push('using System.IO;');
77
+ lines.push('using System.Runtime.Serialization;');
78
+ lines.push('using System.Linq;');
79
+ lines.push('using DeukPack.Protocol;');
80
+ if (_options.efSupport && definitions.structs.some((s) => this._efMetaRowInfo?.has(this.getStructFullName(s, ast)))) {
81
+ lines.push('using System.ComponentModel.DataAnnotations;');
82
+ lines.push('using System.ComponentModel.DataAnnotations.Schema;');
83
+ }
84
+ lines.push('');
85
+ // Group by namespace within this file
86
+ const namespaceGroups = this.groupByNamespace({
87
+ namespaces: ast.namespaces,
88
+ structs: definitions.structs,
89
+ enums: definitions.enums,
90
+ services: definitions.services,
91
+ typedefs: definitions.typedefs,
92
+ constants: definitions.constants,
93
+ includes: [],
94
+ annotations: {}
95
+ });
96
+ for (const [namespace, namespaceDefs] of Object.entries(namespaceGroups)) {
97
+ // Skip empty namespaces
98
+ const hasContent = namespaceDefs.enums.length > 0 ||
99
+ namespaceDefs.structs.length > 0 ||
100
+ namespaceDefs.typedefs.length > 0 ||
101
+ namespaceDefs.constants.length > 0 ||
102
+ namespaceDefs.services?.length > 0;
103
+ if (!hasContent) {
104
+ continue;
105
+ }
106
+ // Namespace
107
+ lines.push(`namespace ${namespace}`);
108
+ lines.push('{');
109
+ lines.push('');
110
+ // Protocol type alias (DeukPack.Protocol namespace)
111
+ lines.push(' using DpProtocol = DeukPack.Protocol.DpProtocol;');
112
+ lines.push('');
113
+ // Generate typedefs FIRST (must be at namespace start in C#)
114
+ for (const typedef of namespaceDefs.typedefs) {
115
+ lines.push(...this.generateTypedef(typedef));
116
+ lines.push('');
117
+ }
118
+ // Generate enums
119
+ for (const enumDef of namespaceDefs.enums) {
120
+ lines.push(...this.generateEnum(enumDef));
121
+ lines.push('');
122
+ }
123
+ // Generate structs (pass current namespace for default value resolution e.g. msgInfo = { "msgId": id_e.req_login })
124
+ for (const struct of namespaceDefs.structs) {
125
+ lines.push(...this.generateStruct(struct, ast, namespace));
126
+ lines.push('');
127
+ }
128
+ // Generate constants inside a static class (C# requirement)
129
+ if (namespaceDefs.constants.length > 0) {
130
+ lines.push(' public static class Constants');
131
+ lines.push(' {');
132
+ for (const constant of namespaceDefs.constants) {
133
+ lines.push(...this.generateConstant(constant, ast));
134
+ }
135
+ lines.push(' }');
136
+ lines.push('');
137
+ }
138
+ // Generate services (interface + version helper)
139
+ const services = namespaceDefs.services;
140
+ if (services && services.length > 0) {
141
+ for (const svc of services) {
142
+ lines.push(...this.generateService(svc, ast, namespace));
143
+ lines.push('');
144
+ }
145
+ }
146
+ lines.push('}');
147
+ lines.push('');
148
+ }
149
+ // Generate filename from source file
150
+ const filename = this.getFilenameFromSource(sourceFile);
151
+ files[filename] = lines.join('\n');
152
+ }
153
+ // table 키워드: 테이블 저장소(매니저)용 레지스트리 코드 항상 자동생성. 메타 테이블이 있으면 switch에 등록 → 리플렉션 없이 타입 접근.
154
+ const schemaFingerprint = this.computeSchemaFingerprint(ast);
155
+ files['MetaTableRegistry.g.cs'] = this.generateMetaTableRegistry(metaTableDefsEarly, schemaFingerprint);
156
+ if (_options.efSupport && metaTableDefsEarly.length > 0) {
157
+ files['DeukPackDbContext.g.cs'] = this.generateEfDbContext(metaTableDefsEarly);
158
+ }
159
+ return files;
160
+ }
161
+ /** Entity Framework Core: DbContext + DbSet per 메타 테이블. efSupport 시에만 호출. */
162
+ generateEfDbContext(metaTableDefs) {
163
+ const lines = [];
164
+ lines.push('/**');
165
+ lines.push(' * Autogenerated by DeukPack (EF support). DbContext for meta tables.');
166
+ lines.push(' * Add Microsoft.EntityFrameworkCore + provider (e.g. Sqlite) to your project.');
167
+ lines.push(' * @generated');
168
+ lines.push(' */');
169
+ lines.push('using System;');
170
+ lines.push('using Microsoft.EntityFrameworkCore;');
171
+ lines.push('');
172
+ lines.push('namespace Generated');
173
+ lines.push('{');
174
+ lines.push(' public class DeukPackDbContext : DbContext');
175
+ lines.push(' {');
176
+ lines.push(' public DeukPackDbContext(DbContextOptions<DeukPackDbContext> options) : base(options) { }');
177
+ lines.push('');
178
+ for (const d of metaTableDefs) {
179
+ const propName = d.category.charAt(0).toUpperCase() + d.category.slice(1).replace(/_([a-z])/g, (_, c) => c.toUpperCase());
180
+ lines.push(` public DbSet<${d.rowTypeFull}> ${propName} => Set<${d.rowTypeFull}>();`);
181
+ }
182
+ lines.push('');
183
+ lines.push(' protected override void OnModelCreating(ModelBuilder modelBuilder)');
184
+ lines.push(' {');
185
+ for (const d of metaTableDefs) {
186
+ lines.push(` modelBuilder.Entity<${d.rowTypeFull}>().ToTable("${this.escapeCSharpStringContent(d.category)}");`);
187
+ if (d.keyFieldNames.length > 1) {
188
+ const keyProps = d.keyFieldNames.map((k) => k.charAt(0).toUpperCase() + k.slice(1).replace(/_([a-z])/g, (_, c) => c.toUpperCase()));
189
+ lines.push(` modelBuilder.Entity<${d.rowTypeFull}>().HasKey(e => new { ${keyProps.map((p) => `e.${p}`).join(', ')} });`);
190
+ }
191
+ }
192
+ lines.push(' }');
193
+ lines.push(' }');
194
+ lines.push('}');
195
+ lines.push('');
196
+ return lines.join('\n');
197
+ }
198
+ /** tablelink<Table, Key>: 대상 테이블 row 타입에 지정한 키 필드가 없으면 오류. */
199
+ validateTableLinkFields(ast, metaTableDefs) {
200
+ for (const struct of ast.structs || []) {
201
+ for (const field of struct.fields || []) {
202
+ const t = field.type;
203
+ if (typeof t !== 'object' || !t || t.type !== 'tablelink')
204
+ continue;
205
+ const tableCategory = t.tableCategory;
206
+ const keyField = t.keyField;
207
+ const def = metaTableDefs.find((d) => d.category === tableCategory || d.containerTypeFull.startsWith(tableCategory + '.'));
208
+ if (!def)
209
+ continue;
210
+ const rowTypeFull = def.rowTypeFull;
211
+ const rowStruct = ast.structs?.find((s) => {
212
+ const ns = this.getStructNamespace(s, ast);
213
+ return (ns ? ns + '.' : '') + s.name === rowTypeFull;
214
+ });
215
+ const hasKey = rowStruct?.fields?.some((f) => f.name === keyField);
216
+ const hasLegacyMid = keyField === 'tuid' && rowStruct?.fields?.some((f) => f.name === 'meta_id');
217
+ if (!hasKey && !hasLegacyMid) {
218
+ throw new DeukPackTypes_1.DeukPackException(`tablelink<${tableCategory}, ${keyField}>: 대상 테이블 row 타입 "${rowTypeFull}"에 키 필드 "${keyField}"가 없습니다.`);
219
+ }
220
+ }
221
+ }
222
+ }
223
+ /** include 포함 전체 AST 기준 와이어/구조 영향 스키마 지문. 정의 변경 시 값이 바뀌어 스키마 버전 변경 여부를 코드젠 수준에서 판단 가능. */
224
+ computeSchemaFingerprint(ast) {
225
+ const parts = [];
226
+ const nsList = [...(ast.namespaces || [])].sort((a, b) => (a.name || '').localeCompare(b.name || ''));
227
+ for (const ns of nsList) {
228
+ parts.push(`ns:${ns.name}`);
229
+ }
230
+ const structs = [...(ast.structs || [])].sort((a, b) => a.name.localeCompare(b.name));
231
+ for (const s of structs) {
232
+ parts.push(`record:${s.name}`);
233
+ const fields = [...(s.fields || [])].sort((a, b) => a.id - b.id);
234
+ for (const f of fields) {
235
+ parts.push(`f:${f.id}:${f.name}:${this.canonicalTypeStringForFingerprint(f.type)}`);
236
+ }
237
+ }
238
+ const enums = [...(ast.enums || [])].sort((a, b) => a.name.localeCompare(b.name));
239
+ for (const e of enums) {
240
+ const vals = Object.entries(e.values || {}).sort(([k1], [k2]) => k1.localeCompare(k2));
241
+ parts.push(`enum:${e.name}:${vals.map(([k, v]) => `${k}=${v}`).join(',')}`);
242
+ }
243
+ const typedefs = [...(ast.typedefs || [])].sort((a, b) => a.name.localeCompare(b.name));
244
+ for (const t of typedefs) {
245
+ parts.push(`typedef:${t.name}:${this.canonicalTypeStringForFingerprint(t.type)}`);
246
+ }
247
+ const str = parts.join('|');
248
+ const h = this.djb2Hash(str);
249
+ return (h >>> 0).toString(16).padStart(8, '0');
250
+ }
251
+ canonicalTypeStringForFingerprint(type) {
252
+ if (type == null)
253
+ return '?';
254
+ if (typeof type === 'string')
255
+ return type;
256
+ if (typeof type === 'object') {
257
+ if (type.type === 'list')
258
+ return `list<${this.canonicalTypeStringForFingerprint(type.elementType)}>`;
259
+ if (type.type === 'set')
260
+ return `set<${this.canonicalTypeStringForFingerprint(type.elementType)}>`;
261
+ if (type.type === 'map')
262
+ return `map<${this.canonicalTypeStringForFingerprint(type.keyType)},${this.canonicalTypeStringForFingerprint(type.valueType)}>`;
263
+ if (type.type === 'tablelink')
264
+ return `tablelink<${type.tableCategory},${type.keyField}>`;
265
+ }
266
+ return String(type);
267
+ }
268
+ djb2Hash(str) {
269
+ let h = 5381;
270
+ for (let i = 0; i < str.length; i++) {
271
+ h = ((h << 5) + h) + str.charCodeAt(i);
272
+ }
273
+ return h;
274
+ }
275
+ /** 메타 테이블(table/container) 목록 수집: 카테고리, 컨테이너 타입 풀네임, row 타입 풀네임, 키 필드명 목록 */
276
+ collectMetaTableDefinitions(ast) {
277
+ const list = [];
278
+ for (const struct of ast.structs || []) {
279
+ if (!this.isMetaContainerStruct(struct))
280
+ continue;
281
+ const ns = this.getStructNamespace(struct, ast);
282
+ if (!ns || !ns.endsWith('_meta'))
283
+ continue;
284
+ const category = ns.replace(/_meta$/, '');
285
+ const containerTypeFull = `${ns}.${struct.name}`;
286
+ const infosType = struct.fields && struct.fields[1] && typeof struct.fields[1].type === 'object' && struct.fields[1].type.type === 'map'
287
+ ? struct.fields[1].type.valueType
288
+ : null;
289
+ const rowTypeShort = infosType ? this.getCSharpType(infosType, ast, ns) : 'IDeukPack';
290
+ const rowTypeFull = rowTypeShort.includes('.') ? rowTypeShort : `${ns}.${rowTypeShort}`;
291
+ let keyFieldNames = struct.keyFieldNames;
292
+ if (!keyFieldNames?.length && struct.annotations?.['key']) {
293
+ const raw = String(struct.annotations['key']).replace(/^["']|["']$/g, '').trim();
294
+ if (raw)
295
+ keyFieldNames = raw.split(',').map((s) => s.trim()).filter(Boolean);
296
+ }
297
+ if (!keyFieldNames?.length)
298
+ keyFieldNames = ['tuid'];
299
+ list.push({ category, containerTypeFull, rowTypeFull, keyFieldNames });
300
+ }
301
+ return list;
302
+ }
303
+ /** MetaTableRegistry.g.cs 생성 — GetContainerType/GetRowType/GetKeyFieldNames/CreateEmptyTemplate, SchemaFingerprint, 리플렉션 제거 */
304
+ generateMetaTableRegistry(defs, schemaFingerprint) {
305
+ const lines = [];
306
+ lines.push('/**');
307
+ lines.push(' * Autogenerated by DeukPack. Meta table registry — use instead of reflection for container/row type lookup and empty template.');
308
+ lines.push(' * When using table/container, ensure TableHeader and TableInfos types are available (e.g. include deuk.deuk or shared IDL).');
309
+ lines.push(' * @generated');
310
+ lines.push(' */');
311
+ lines.push('using System;');
312
+ lines.push('using DeukPack.Protocol;');
313
+ lines.push('');
314
+ lines.push('namespace Generated');
315
+ lines.push('{');
316
+ lines.push(' public static class MetaTableRegistry');
317
+ lines.push(' {');
318
+ lines.push(' /// <summary>include 포함 전체 스키마(구조/와이어 영향)의 지문. 정의 변경 시 값이 바뀌어 스키마 버전 변경 여부 판단에 사용.</summary>');
319
+ lines.push(` public static readonly string SchemaFingerprint = "${this.escapeCSharpStringContent(schemaFingerprint)}";`);
320
+ lines.push('');
321
+ lines.push(' public static Type GetContainerType(string category)');
322
+ lines.push(' {');
323
+ lines.push(' if (category == null) return null;');
324
+ lines.push(' switch (category)');
325
+ lines.push(' {');
326
+ for (const d of defs) {
327
+ lines.push(` case "${this.escapeCSharpStringContent(d.category)}": return typeof(${d.containerTypeFull});`);
328
+ }
329
+ lines.push(' default: return null;');
330
+ lines.push(' }');
331
+ lines.push(' }');
332
+ lines.push('');
333
+ lines.push(' public static Type GetRowType(string category)');
334
+ lines.push(' {');
335
+ lines.push(' if (category == null) return null;');
336
+ lines.push(' switch (category)');
337
+ lines.push(' {');
338
+ for (const d of defs) {
339
+ lines.push(` case "${this.escapeCSharpStringContent(d.category)}": return typeof(${d.rowTypeFull});`);
340
+ }
341
+ lines.push(' default: return null;');
342
+ lines.push(' }');
343
+ lines.push(' }');
344
+ lines.push('');
345
+ lines.push(' /// <summary>카테고리별 키 필드명(단일 또는 복합). key 미선언 시 ["tuid"]. 매니저 검색·엑셀 칼럼 순서·보조 인덱스 판단용.</summary>');
346
+ lines.push(' public static System.Collections.Generic.IReadOnlyList<string> GetKeyFieldNames(string category)');
347
+ lines.push(' {');
348
+ lines.push(' if (category == null) return null;');
349
+ lines.push(' switch (category)');
350
+ lines.push(' {');
351
+ for (const d of defs) {
352
+ const arrLiteral = 'new string[] { ' + d.keyFieldNames.map((k) => `"${this.escapeCSharpStringContent(k)}"`).join(', ') + ' }';
353
+ lines.push(` case "${this.escapeCSharpStringContent(d.category)}": return ${arrLiteral};`);
354
+ }
355
+ lines.push(' default: return null;');
356
+ lines.push(' }');
357
+ lines.push(' }');
358
+ lines.push('');
359
+ lines.push(' /// <summary>카테고리별 빈 메타 테이블 템플릿 생성 (Default_Header + 빈 Infos). 로더/툴에서 초기 컨테이너가 필요할 때 사용.</summary>');
360
+ lines.push(' public static IDeukMetaContainer CreateEmptyTemplate(string category)');
361
+ lines.push(' {');
362
+ lines.push(' if (category == null) return null;');
363
+ lines.push(' switch (category)');
364
+ lines.push(' {');
365
+ for (const d of defs) {
366
+ lines.push(` case "${this.escapeCSharpStringContent(d.category)}": return (IDeukMetaContainer)${d.containerTypeFull}.CreateDefault();`);
367
+ }
368
+ lines.push(' default: return null;');
369
+ lines.push(' }');
370
+ lines.push(' }');
371
+ lines.push(' }');
372
+ lines.push('}');
373
+ lines.push('');
374
+ return lines.join('\n');
375
+ }
376
+ groupBySourceFile(ast) {
377
+ const groups = {};
378
+ const ensure = (sourceFile) => {
379
+ if (!groups[sourceFile]) {
380
+ groups[sourceFile] = { enums: [], structs: [], typedefs: [], constants: [], services: [] };
381
+ }
382
+ };
383
+ for (const enumDef of ast.enums) {
384
+ const sourceFile = enumDef.sourceFile || 'unknown';
385
+ ensure(sourceFile);
386
+ groups[sourceFile].enums.push(enumDef);
387
+ }
388
+ for (const struct of ast.structs) {
389
+ const sourceFile = struct.sourceFile || 'unknown';
390
+ ensure(sourceFile);
391
+ groups[sourceFile].structs.push(struct);
392
+ }
393
+ for (const typedef of ast.typedefs) {
394
+ const sourceFile = typedef.sourceFile || 'unknown';
395
+ ensure(sourceFile);
396
+ groups[sourceFile].typedefs.push(typedef);
397
+ }
398
+ for (const constant of ast.constants) {
399
+ const sourceFile = constant.sourceFile || 'unknown';
400
+ ensure(sourceFile);
401
+ groups[sourceFile].constants.push(constant);
402
+ }
403
+ for (const service of ast.services) {
404
+ const sourceFile = service.sourceFile || 'unknown';
405
+ ensure(sourceFile);
406
+ groups[sourceFile].services.push(service);
407
+ }
408
+ return groups;
409
+ }
410
+ getFilenameFromSource(sourceFile) {
411
+ // Extract filename without extension and path
412
+ const filename = sourceFile.split('/').pop()?.split('\\').pop() || 'unknown';
413
+ let nameWithoutExt = filename.replace(/\.thrift$/i, '').replace(/\.deuk$/i, '');
414
+ // .Thrift.cs 같은 중복 확장자 방지
415
+ if (nameWithoutExt.endsWith('.Thrift') || nameWithoutExt.endsWith('.thrift')) {
416
+ const baseName = nameWithoutExt.replace(/\.(Thrift|thrift)$/i, '');
417
+ return `${baseName}.cs`;
418
+ }
419
+ // Windows 예약어 방지 (COM, PRN, AUX, NUL, CON, LPT1-9 등)
420
+ const windowsReservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];
421
+ const upperName = nameWithoutExt.toUpperCase();
422
+ if (windowsReservedNames.includes(upperName) || nameWithoutExt === '' || nameWithoutExt === 'nul') {
423
+ // 예약어나 빈 이름이면 'unknown' 사용
424
+ return 'unknown.cs';
425
+ }
426
+ return `${nameWithoutExt}.cs`;
427
+ }
428
+ generateEnum(enumDef) {
429
+ const lines = [];
430
+ lines.push('/**');
431
+ lines.push(' * Autogenerated by DeukPack v1.0.0');
432
+ lines.push(' *');
433
+ lines.push(' * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING');
434
+ lines.push(' * @generated');
435
+ lines.push(' */');
436
+ lines.push('');
437
+ const enumAttrs = enumDef.csharpAttributes;
438
+ if (enumAttrs?.length) {
439
+ for (const attr of enumAttrs) {
440
+ lines.push(` ${attr}`);
441
+ }
442
+ }
443
+ lines.push(` public enum ${enumDef.name}`);
444
+ lines.push(' {');
445
+ const entries = Object.entries(enumDef.values);
446
+ for (let i = 0; i < entries.length; i++) {
447
+ const entry = entries[i];
448
+ if (entry) {
449
+ const [name, value] = entry;
450
+ const comma = i < entries.length - 1 ? ',' : '';
451
+ const comment = enumDef.valueComments?.[name];
452
+ if (comment) {
453
+ const escaped = comment.replace(/"/g, '\\"').replace(/\r?\n/g, ' ');
454
+ lines.push(` [System.ComponentModel.Description("${escaped}")]`);
455
+ }
456
+ lines.push(` ${name} = ${value}${comma}`);
457
+ }
458
+ }
459
+ lines.push(' }');
460
+ return lines;
461
+ }
462
+ /** 테이블 행 타입 여부: 어떤 meta container의 infos map value type으로 쓰이는지 */
463
+ isTableRowType(struct, ast) {
464
+ const rowNs = this.getStructNamespace(struct, ast);
465
+ const rowFull = `${rowNs}.${struct.name}`;
466
+ for (const s of ast.structs || []) {
467
+ if (!this.isMetaContainerStruct(s) || !s.fields || s.fields.length < 2)
468
+ continue;
469
+ const infosType = s.fields[1].type;
470
+ if (typeof infosType !== 'object' || !infosType || infosType.type !== 'map')
471
+ continue;
472
+ const valueType = infosType.valueType;
473
+ if (!valueType)
474
+ continue;
475
+ const containerNs = this.getStructNamespace(s, ast);
476
+ const rowTypeShort = this.getCSharpType(valueType, ast, containerNs);
477
+ const rowTypeFull = rowTypeShort.includes('.') ? rowTypeShort : `${containerNs}.${rowTypeShort}`;
478
+ if (rowTypeFull === rowFull)
479
+ return true;
480
+ }
481
+ return false;
482
+ }
483
+ generateStruct(struct, ast, currentNamespace) {
484
+ const lines = [];
485
+ const ns = currentNamespace ?? this.getStructNamespace(struct, ast);
486
+ const fullName = this.getStructFullName(struct, ast);
487
+ const efMeta = this._efMetaRowInfo;
488
+ const efInfo = efMeta?.get(fullName) ?? null;
489
+ this._efCurrentRowInfo = efInfo;
490
+ // 테이블 행 record: 1-4번 예약. 5번부터만 사용. 옵션 tableRowReserved14 시 1-4 사용하면 컴파일 오류
491
+ const opts = this._genOptions;
492
+ if (opts?.tableRowReserved14 && this.isTableRowType(struct, ast) && struct.fields) {
493
+ const reserved = [1, 2, 3, 4];
494
+ for (const f of struct.fields) {
495
+ const id = typeof f.id === 'number' ? f.id : parseInt(String(f.id), 10);
496
+ if (!isNaN(id) && reserved.includes(id)) {
497
+ throw new Error(`Table row record '${struct.name}' must not use field indices 1-4 (reserved for tuid, tid, name, note). Use 5 and above. Invalid field: ${f.id} ${f.name}`);
498
+ }
499
+ }
500
+ }
501
+ lines.push('/**');
502
+ lines.push(' * Autogenerated by DeukPack v1.0.0');
503
+ lines.push(' *');
504
+ lines.push(' * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING');
505
+ lines.push(' * @generated');
506
+ lines.push(' */');
507
+ lines.push('');
508
+ const structAttrs = struct.csharpAttributes;
509
+ if (structAttrs?.length) {
510
+ for (const attr of structAttrs) {
511
+ lines.push(` ${attr}`);
512
+ }
513
+ }
514
+ if (efInfo) {
515
+ lines.push(` [Table("${this.escapeCSharpStringContent(efInfo.category)}")]`);
516
+ }
517
+ const isMetaContainer = this.isMetaContainerStruct(struct);
518
+ const metaContainerDataType = isMetaContainer && struct.fields && struct.fields.length >= 2
519
+ && typeof struct.fields[1].type === 'object' && struct.fields[1].type && struct.fields[1].type.type === 'map'
520
+ ? this.getCSharpType(struct.fields[1].type.valueType, ast, ns)
521
+ : null;
522
+ const implList = ['IDeukPack'];
523
+ if (isMetaContainer)
524
+ implList.push('IDeukMetaContainer');
525
+ if (metaContainerDataType)
526
+ implList.push(`IDeukMetaContainer<${metaContainerDataType}>`);
527
+ lines.push(` [System.Serializable]`);
528
+ lines.push(` public class ${struct.name} : ${implList.join(', ')}`);
529
+ lines.push(' {');
530
+ // Generate fields: 같은 네임스페이스면 타입 생략 → 내부처리는 항상 풀네임(ns)으로 구동
531
+ for (const field of struct.fields) {
532
+ lines.push(...this.generateField(field, ast, ns, struct));
533
+ }
534
+ // 기본값 공통: enum/구조체/primitive 모두 정적 Default_<필드명>으로 노출 (리플렉션·어셈블리 이슈 회피)
535
+ const defaultProps = [];
536
+ let msgIdLikeFieldName = null;
537
+ for (const field of struct.fields) {
538
+ const defaultExpr = this.getFieldDefaultExpression(field, ast, ns, struct);
539
+ if (defaultExpr === null)
540
+ continue;
541
+ const csharpType = this.getCSharpType(field.type, ast, ns);
542
+ const propName = this.capitalize(field.name);
543
+ defaultProps.push(` public static ${csharpType} Default_${propName} => ${defaultExpr};`);
544
+ // 메시지 ID용: 타입이 MsgId 멤버를 가진 struct면 DefaultMessageId에서 참조 (이름 하드코딩 없음)
545
+ if (typeof field.type === 'string' && ast) {
546
+ const typeFull = this.resolveTypeToFullName(field.type, ns, ast);
547
+ const targetStruct = this.findStruct(ast, typeFull, ns);
548
+ if (targetStruct?.fields?.some(f => f.name.toLowerCase() === 'msgid')) {
549
+ msgIdLikeFieldName = propName;
550
+ }
551
+ }
552
+ }
553
+ if (defaultProps.length > 0) {
554
+ lines.push('');
555
+ lines.push(' /// <summary>기본값 정적 노출 (enum/구조체 공통, 리플렉션 없이 조회)</summary>');
556
+ lines.push(...defaultProps);
557
+ }
558
+ if (msgIdLikeFieldName !== null) {
559
+ lines.push(` /// <summary>Message ID for protocol handler registration (Default_* combination)</summary>`);
560
+ lines.push(` public static int DefaultMessageId => Default_${msgIdLikeFieldName}.MsgId;`);
561
+ }
562
+ lines.push('');
563
+ lines.push(' // Serialization methods');
564
+ lines.push(' public void Write(DpProtocol oprot)');
565
+ lines.push(' {');
566
+ // default 있는 struct는 의미상 null이 될 수 없음 → 필드 초기화·Read() 쪽에서만 보장
567
+ lines.push(' DpRecord struc = new DpRecord("' + struct.name + '");');
568
+ lines.push(' oprot.WriteStructBegin(struc);');
569
+ for (const field of struct.fields) {
570
+ const fieldName = this.capitalize(field.name);
571
+ const writeCondition = this.generateWriteCondition(field, fieldName, ast, ns);
572
+ lines.push(` ${writeCondition}`);
573
+ lines.push(' {');
574
+ lines.push(' DpColumn field = new DpColumn("' + field.name + '", ' + this.getTType(field.type, ast, ns) + ', ' + field.id + ');');
575
+ lines.push(' oprot.WriteFieldBegin(field);');
576
+ lines.push(' ' + this.generateWriteField(field, ast, ns));
577
+ lines.push(' oprot.WriteFieldEnd();');
578
+ lines.push(' }');
579
+ }
580
+ lines.push(' oprot.WriteFieldStop();');
581
+ lines.push(' oprot.WriteStructEnd();');
582
+ lines.push(' }');
583
+ lines.push('');
584
+ lines.push(' public void Read(DpProtocol iprot)');
585
+ lines.push(' {');
586
+ // 코드생성기 기준: Read() 진입 시 모든 필드를 기본값으로 초기화 → wire에 없는 필드는 항상 기본값 보장 (null/미초기화 방지)
587
+ for (const field of struct.fields) {
588
+ const fieldName = this.capitalize(field.name);
589
+ const rhs = this.generateCreateDefaultRhs(field, ast, ns, struct);
590
+ lines.push(` this.${fieldName} = ${rhs};`);
591
+ }
592
+ lines.push(' DpColumn field;');
593
+ lines.push(' iprot.ReadStructBegin();');
594
+ lines.push(' while (true)');
595
+ lines.push(' {');
596
+ lines.push(' field = iprot.ReadFieldBegin();');
597
+ lines.push(' if (field.Type == DpWireType.Stop)');
598
+ lines.push(' {');
599
+ lines.push(' break;');
600
+ lines.push(' }');
601
+ lines.push(' switch (field.ID)');
602
+ lines.push(' {');
603
+ for (const field of struct.fields) {
604
+ lines.push(' case ' + field.id + ':');
605
+ lines.push(' if (field.Type == ' + this.getTType(field.type, ast, ns) + ')');
606
+ lines.push(' {');
607
+ lines.push(' ' + this.generateReadField(field, ast, ns));
608
+ const parentName = this.capitalize(field.name);
609
+ const hasDefault = field.defaultValue !== undefined;
610
+ const isStructField = typeof field.type === 'string' && ast && (this.isStructType(field.type, ast, ns) || this.isStructCSharpType(this.getCSharpType(field.type, ast, ns), ast));
611
+ if (hasDefault && isStructField && typeof field.type === 'string') {
612
+ for (const ensureLine of this.generateEnsureNestedDefaults(parentName, field.type, ast, ns)) {
613
+ lines.push(' ' + ensureLine);
614
+ }
615
+ }
616
+ lines.push(' }');
617
+ lines.push(' else');
618
+ lines.push(' {');
619
+ lines.push(' DpProtocolUtil.Skip(iprot, field.Type);');
620
+ lines.push(' }');
621
+ lines.push(' break;');
622
+ }
623
+ lines.push(' default:');
624
+ lines.push(' DpProtocolUtil.Skip(iprot, field.Type);');
625
+ lines.push(' break;');
626
+ lines.push(' }');
627
+ lines.push(' iprot.ReadFieldEnd();');
628
+ lines.push(' }');
629
+ lines.push(' iprot.ReadStructEnd();');
630
+ lines.push(' }');
631
+ lines.push('');
632
+ // Generate Clone method
633
+ lines.push(' // Clone method');
634
+ lines.push(' public ' + struct.name + ' Clone()');
635
+ lines.push(' {');
636
+ lines.push(' var clone = new ' + struct.name + '();');
637
+ for (const field of struct.fields) {
638
+ const fieldName = this.capitalize(field.name);
639
+ lines.push(' ' + this.generateCloneField(field, fieldName, ast, ns));
640
+ }
641
+ lines.push(' return clone;');
642
+ lines.push(' }');
643
+ lines.push('');
644
+ // Explicit interface implementation for IDeukPack.Clone()
645
+ lines.push(' // Explicit interface implementation');
646
+ lines.push(' object IDeukPack.Clone()');
647
+ lines.push(' {');
648
+ lines.push(' return this.Clone();');
649
+ lines.push(' }');
650
+ lines.push('');
651
+ // CreateDefault: 재귀적으로 nested struct 초기화 (Clone/ToString과 동일 패턴)
652
+ lines.push(' // CreateDefault: recursive default instance');
653
+ lines.push(' public static ' + struct.name + ' CreateDefault()');
654
+ lines.push(' {');
655
+ lines.push(' var o = new ' + struct.name + '();');
656
+ for (const field of struct.fields) {
657
+ const fieldName = this.capitalize(field.name);
658
+ const rhs = this.generateCreateDefaultRhs(field, ast, ns, struct);
659
+ lines.push(' o.' + fieldName + ' = ' + rhs + ';');
660
+ }
661
+ lines.push(' return o;');
662
+ lines.push(' }');
663
+ lines.push('');
664
+ // Generate ToString method for object dump
665
+ lines.push(' public override string ToString() { return ToString(""); }');
666
+ lines.push(' public string ToString(string indent)');
667
+ lines.push(' {');
668
+ lines.push(' var sb = new StringBuilder();');
669
+ lines.push(' var ci = indent + " ";');
670
+ lines.push(' sb.Append("' + struct.name + ' {");');
671
+ if (struct.fields.length === 0) {
672
+ lines.push(' sb.Append("}");');
673
+ }
674
+ else {
675
+ lines.push(' sb.AppendLine();');
676
+ for (const field of struct.fields) {
677
+ const fieldName = this.capitalize(field.name);
678
+ lines.push(' ' + this.generateToStringField(field, fieldName, ast, ns));
679
+ }
680
+ lines.push(' sb.Append(indent).Append("}");');
681
+ }
682
+ lines.push(' return sb.ToString();');
683
+ lines.push(' }');
684
+ lines.push('');
685
+ this._efCurrentRowInfo = null;
686
+ // Generate schema metadata (득팩 스키마 타입)
687
+ lines.push(' // Schema metadata');
688
+ lines.push(' public static DpSchema GetSchema()');
689
+ lines.push(' {');
690
+ lines.push(' return SchemaInfo.Value;');
691
+ lines.push(' }');
692
+ lines.push('');
693
+ const structDoc = struct.docComment != null ? this.escapeCSharpString(struct.docComment) : 'null';
694
+ const structAnn = struct.annotations != null && Object.keys(struct.annotations).length > 0
695
+ ? this.dictToCSharpAnnotations(struct.annotations)
696
+ : 'null';
697
+ lines.push(' private static readonly Lazy<DpSchema> SchemaInfo = new Lazy<DpSchema>(() =>');
698
+ lines.push(' {');
699
+ lines.push(' var schema = new DpSchema');
700
+ lines.push(' {');
701
+ lines.push(' Name = "' + this.escapeCSharpStringContent(struct.name) + '",');
702
+ lines.push(' Type = DpDefinitionKind.Struct,');
703
+ lines.push(' DocComment = ' + structDoc + ',');
704
+ lines.push(' Annotations = ' + structAnn + ',');
705
+ lines.push(' Fields = new Dictionary<int, DpFieldSchema>');
706
+ lines.push(' {');
707
+ const structFields = struct.fields ?? [];
708
+ for (let orderIdx = 0; orderIdx < structFields.length; orderIdx++) {
709
+ const field = structFields[orderIdx];
710
+ const fieldSchema = this.generateFieldSchema(field, ast, ns, orderIdx);
711
+ lines.push(` { ${field.id}, ${fieldSchema} },`);
712
+ }
713
+ lines.push(' }');
714
+ lines.push(' };');
715
+ lines.push(' return schema;');
716
+ lines.push(' });');
717
+ if (isMetaContainer && struct.fields.length >= 2) {
718
+ const headerField = struct.fields[0];
719
+ const infosField = struct.fields[1];
720
+ const headerType = this.getCSharpType(headerField.type, ast, ns);
721
+ const infosValueType = typeof infosField.type === 'object' && infosField.type && infosField.type.type === 'map'
722
+ ? this.getCSharpType(infosField.type.valueType, ast, ns)
723
+ : 'IDeukPack';
724
+ lines.push('');
725
+ lines.push(' // IDeukMetaContainer: 리플렉션 없이 메타 매니저에서 접근');
726
+ lines.push(` object IDeukMetaContainer.Header { get => Header; set => Header = (${headerType})value; }`);
727
+ lines.push(` IReadOnlyDictionary<long, IDeukPack> IDeukMetaContainer.Infos => new DpMetaInfosWrapper<${infosValueType}>(Infos);`);
728
+ lines.push(' // IDeukMetaContainer<T>: 데이터 타입 지정 접근');
729
+ lines.push(` IReadOnlyDictionary<long, ${infosValueType}> IDeukMetaContainer<${infosValueType}>.Data => Infos;`);
730
+ }
731
+ lines.push(' }');
732
+ return lines;
733
+ }
734
+ /** table 키워드: struct 이름이 table 또는 container(호환)이고 header + infos(map) 필드면 메타 테이블로 간주 */
735
+ isMetaContainerStruct(struct) {
736
+ if ((struct.name !== 'table' && struct.name !== 'container') || !struct.fields || struct.fields.length < 2)
737
+ return false;
738
+ const a = struct.fields[0].name.toLowerCase();
739
+ const b = struct.fields[1].name.toLowerCase();
740
+ if (a !== 'header' || b !== 'infos')
741
+ return false;
742
+ const infosType = struct.fields[1].type;
743
+ return typeof infosType === 'object' && infosType !== null && infosType.type === 'map';
744
+ }
745
+ generateFieldSchema(field, ast, currentNamespace, order = 0) {
746
+ const typeInfo = this.getSchemaTypeInfo(field.type, ast, currentNamespace);
747
+ const defaultValue = field.defaultValue !== undefined
748
+ ? this.serializeDefaultValueToSchemaExpression(field.defaultValue)
749
+ : 'null';
750
+ const docComment = field.docComment != null ? this.escapeCSharpString(field.docComment) : 'null';
751
+ const annotations = field.annotations != null && Object.keys(field.annotations).length > 0
752
+ ? this.dictToCSharpAnnotations(field.annotations)
753
+ : 'null';
754
+ const lines = [];
755
+ lines.push('new DpFieldSchema');
756
+ lines.push(' {');
757
+ lines.push(` Id = ${field.id},`);
758
+ lines.push(` Order = ${order},`);
759
+ lines.push(` Name = "${this.escapeCSharpStringContent(field.name)}",`);
760
+ lines.push(` Type = ${typeInfo.type},`);
761
+ lines.push(` TypeName = "${this.escapeCSharpStringContent(typeInfo.typeName)}",`);
762
+ lines.push(` Required = ${field.required ? 'true' : 'false'},`);
763
+ lines.push(` DefaultValue = ${defaultValue},`);
764
+ lines.push(` DocComment = ${docComment},`);
765
+ lines.push(` Annotations = ${annotations}`);
766
+ lines.push(' }');
767
+ return lines.join('\n');
768
+ }
769
+ /** Full C# string literal including quotes (e.g. for DocComment = "..." ). */
770
+ escapeCSharpString(s) {
771
+ if (s == null)
772
+ return 'null';
773
+ return '"' + this.escapeCSharpStringContent(s) + '"';
774
+ }
775
+ /** Escape content for use inside C# "..." (no surrounding quotes). */
776
+ escapeCSharpStringContent(s) {
777
+ return String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/\t/g, '\\t');
778
+ }
779
+ dictToCSharpAnnotations(ann) {
780
+ if (!ann || Object.keys(ann).length === 0)
781
+ return 'null';
782
+ const entries = Object.entries(ann).map(([k, v]) => `{ ${this.escapeCSharpString(k)}, ${this.escapeCSharpString(v)} }`).join(', ');
783
+ return `new Dictionary<string, string> { ${entries} }`;
784
+ }
785
+ /** 득팩 표준 타입명으로 TypeName 반환 (엑셀 Row2 등). 레거시 i16/i32/i64 → int16/int32/int64, list→요소타입만, map→map<K,V>. */
786
+ getSchemaTypeInfo(type, ast, currentNamespace) {
787
+ const legacyToStandard = { 'i8': 'int8', 'i16': 'int16', 'i32': 'int32', 'i64': 'int64' };
788
+ const toStandardTypeName = (t) => legacyToStandard[t] ?? t;
789
+ if (typeof type === 'string') {
790
+ const primitiveTypes = {
791
+ 'bool': 'DpSchemaType.Bool',
792
+ 'byte': 'DpSchemaType.Byte',
793
+ 'int8': 'DpSchemaType.Byte',
794
+ 'int16': 'DpSchemaType.I16',
795
+ 'int32': 'DpSchemaType.I32',
796
+ 'int64': 'DpSchemaType.I64',
797
+ 'i8': 'DpSchemaType.Byte',
798
+ 'i16': 'DpSchemaType.I16',
799
+ 'i32': 'DpSchemaType.I32',
800
+ 'i64': 'DpSchemaType.I64',
801
+ 'float': 'DpSchemaType.Double',
802
+ 'double': 'DpSchemaType.Double',
803
+ 'string': 'DpSchemaType.String',
804
+ 'binary': 'DpSchemaType.Binary',
805
+ 'datetime': 'DpSchemaType.I64',
806
+ 'timestamp': 'DpSchemaType.I64',
807
+ 'date': 'DpSchemaType.I32',
808
+ 'time': 'DpSchemaType.I32',
809
+ 'decimal': 'DpSchemaType.String',
810
+ 'numeric': 'DpSchemaType.String'
811
+ };
812
+ if (primitiveTypes[type])
813
+ return { type: primitiveTypes[type], typeName: toStandardTypeName(type) };
814
+ const resolvedStr = this.getResolvedWireTypeString(type, ast, currentNamespace);
815
+ if (primitiveTypes[resolvedStr])
816
+ return { type: primitiveTypes[resolvedStr], typeName: toStandardTypeName(resolvedStr) };
817
+ const resolved = ast ? this.resolveTypeToASTDefinition(resolvedStr, currentNamespace, ast) : null;
818
+ if (resolved?.kind === 'enum')
819
+ return { type: 'DpSchemaType.Enum', typeName: type };
820
+ if (resolved?.kind === 'record')
821
+ return { type: 'DpSchemaType.Struct', typeName: type };
822
+ if (resolved?.kind === 'primitive')
823
+ return { type: 'DpSchemaType.I32', typeName: toStandardTypeName(type) };
824
+ return { type: 'DpSchemaType.Struct', typeName: type };
825
+ }
826
+ if (typeof type === 'object' && type !== null) {
827
+ if (type.type === 'list') {
828
+ const elemInfo = this.getSchemaTypeInfo(type.elementType, ast, currentNamespace);
829
+ return { type: 'DpSchemaType.List', typeName: elemInfo.typeName };
830
+ }
831
+ if (type.type === 'set') {
832
+ const elemInfo = this.getSchemaTypeInfo(type.elementType, ast, currentNamespace);
833
+ return { type: 'DpSchemaType.Set', typeName: elemInfo.typeName };
834
+ }
835
+ if (type.type === 'map') {
836
+ const keyInfo = this.getSchemaTypeInfo(type.keyType, ast, currentNamespace);
837
+ const valueInfo = this.getSchemaTypeInfo(type.valueType, ast, currentNamespace);
838
+ return { type: 'DpSchemaType.Map', typeName: `map<${keyInfo.typeName}, ${valueInfo.typeName}>` };
839
+ }
840
+ }
841
+ return { type: 'DpSchemaType.Struct', typeName: 'object' };
842
+ }
843
+ /** C# expression for DpFieldSchema.DefaultValue (object) – full value for schema recovery. */
844
+ serializeDefaultValueToSchemaExpression(value) {
845
+ if (value === null || value === undefined)
846
+ return 'null';
847
+ if (typeof value === 'string')
848
+ return this.escapeCSharpString(value);
849
+ if (typeof value === 'number')
850
+ return value.toString();
851
+ if (typeof value === 'boolean')
852
+ return value ? 'true' : 'false';
853
+ if (Array.isArray(value)) {
854
+ const elements = value.map(v => `(object)(${this.serializeDefaultValueToSchemaExpression(v)})`).join(', ');
855
+ return `new List<object> { ${elements} }`;
856
+ }
857
+ if (typeof value === 'object') {
858
+ const entries = Object.entries(value).map(([k, v]) => `{ ${this.escapeCSharpString(k)}, (object)(${this.serializeDefaultValueToSchemaExpression(v)}) }`).join(', ');
859
+ return `new Dictionary<string, object> { ${entries} }`;
860
+ }
861
+ return 'null';
862
+ }
863
+ /**
864
+ * message<> 첫 필드 기본값: struct.annotations.msgId + 첫 필드 타입의 AST만 사용.
865
+ * 타입명/프로퍼티명 하드코딩 없음 — 첫 필드 struct에서 'result' 포함 필드·메시지 id용 필드 이름을 AST로 조회.
866
+ */
867
+ buildMessageFirstFieldDefault(parentStruct, field, ast, currentNamespace) {
868
+ const msgId = parentStruct.annotations?.['msgId'];
869
+ if (msgId == null || parentStruct.fields?.[0] !== field)
870
+ return null;
871
+ const typeStr = typeof field.type === 'string' ? field.type : '';
872
+ if (!typeStr)
873
+ return null;
874
+ const msgInfoFull = this.resolveTypeToFullName(typeStr, currentNamespace, ast);
875
+ const msgInfoStruct = this.findStruct(ast, typeStr, currentNamespace);
876
+ if (!msgInfoStruct?.fields?.length)
877
+ return null;
878
+ const idField = msgInfoStruct.fields.find(f => /msgid|msg_id|messageid/i.test(f.name || ''));
879
+ const resultField = msgInfoStruct.fields.find(f => (f.name || '').toLowerCase().includes('result'));
880
+ if (!resultField)
881
+ return null;
882
+ const resultTypeStr = typeof resultField.type === 'string' ? resultField.type : (resultField.type?.name ?? '');
883
+ if (!resultTypeStr)
884
+ return null;
885
+ const resultTypeFull = this.resolveTypeToFullName(resultTypeStr, this.getStructNamespace(msgInfoStruct, ast), ast);
886
+ const resultPropName = this.capitalize(resultField.name);
887
+ const idPropName = idField ? this.capitalize(idField.name) : 'MsgId'; // AST에 msgid 계열 필드 없을 때만 fallback
888
+ return `new ${msgInfoFull}() { ${idPropName} = ${msgId}, ${resultPropName} = ${resultTypeFull}.CreateDefault() }`;
889
+ }
890
+ /** 필드 기본값의 C# 우변 식 반환 (인스턴스 초기화·Default_ 정적 프로퍼티 공용). enum/struct/primitive 공통. */
891
+ getFieldDefaultExpression(field, ast, currentNamespace, parentStruct) {
892
+ if (parentStruct && ast && currentNamespace) {
893
+ const msgDefault = this.buildMessageFirstFieldDefault(parentStruct, field, ast, currentNamespace);
894
+ if (msgDefault != null)
895
+ return msgDefault;
896
+ }
897
+ if (field.defaultValue === undefined)
898
+ return null;
899
+ const objInit = this.getCSharpStructObjectInitializer(field.defaultValue, field.type, currentNamespace, ast);
900
+ if (objInit !== null)
901
+ return objInit;
902
+ const prim = this.getCSharpDefaultValue(field.defaultValue, field.type, ast, currentNamespace);
903
+ return prim !== 'null' ? prim : null;
904
+ }
905
+ generateField(field, ast, currentNamespace, parentStruct) {
906
+ const lines = [];
907
+ const fieldAttrs = field.csharpAttributes;
908
+ if (fieldAttrs?.length) {
909
+ for (const attr of fieldAttrs) {
910
+ lines.push(` ${attr}`);
911
+ }
912
+ }
913
+ const efInfo = this._efCurrentRowInfo;
914
+ if (efInfo) {
915
+ if (efInfo.keyFieldNames.includes(field.name)) {
916
+ lines.push(' [Key]');
917
+ }
918
+ lines.push(` [Column("${this.escapeCSharpStringContent(field.name)}")]`);
919
+ }
920
+ const csharpType = this.getCSharpType(field.type, ast, currentNamespace);
921
+ let defaultPart = '';
922
+ const defaultExpr = this.getFieldDefaultExpression(field, ast, currentNamespace, parentStruct);
923
+ if (defaultExpr !== null) {
924
+ defaultPart = ` = ${defaultExpr}`;
925
+ }
926
+ // Apache 호환: 프로퍼티로 생성 (EF Core 매핑·null 동작 일치). 기본값 있을 때만 끝에 ; (없으면 }; 불가)
927
+ lines.push(` public ${csharpType} ${this.capitalize(field.name)} { get; set; }${defaultPart}${defaultPart ? ';' : ''}`);
928
+ return lines;
929
+ }
930
+ /** Struct의 풀네임 (namespace.shortName) */
931
+ getStructFullName(struct, ast) {
932
+ const ns = this.getStructNamespace(struct, ast);
933
+ const short = struct.name.includes('.') ? (struct.name.split('.').pop() ?? struct.name) : struct.name;
934
+ return `${ns}.${short}`;
935
+ }
936
+ /** Enum의 풀네임 (namespace.shortName) */
937
+ getEnumFullName(enumDef, ast) {
938
+ const ns = this.getEnumNamespace(enumDef, ast);
939
+ const short = enumDef.name.includes('.') ? (enumDef.name.split('.').pop() ?? enumDef.name) : enumDef.name;
940
+ return `${ns}.${short}`;
941
+ }
942
+ /** Typedef의 풀네임 (namespace.shortName) */
943
+ getTypedefFullName(typedef, ast) {
944
+ const ns = this.getTypedefNamespace(typedef, ast);
945
+ const short = typedef.name.includes('.') ? (typedef.name.split('.').pop() ?? typedef.name) : typedef.name;
946
+ return `${ns}.${short}`;
947
+ }
948
+ /** 타입 문자열을 풀네임으로 해석. AST의 struct/enum/typedef 기준으로만 매칭 (이름·점 휴리스틱 없음). */
949
+ resolveTypeToFullName(typeStr, currentNamespace, ast) {
950
+ if (!typeStr || typeof typeStr !== 'string')
951
+ return typeStr;
952
+ const primitives = ['bool', 'byte', 'int8', 'int16', 'int32', 'int64', 'float', 'double', 'string', 'binary', 'datetime', 'timestamp', 'date', 'time', 'decimal', 'numeric'];
953
+ if (primitives.includes(typeStr))
954
+ return typeStr;
955
+ if (!ast)
956
+ return typeStr;
957
+ if (typeStr.includes('.')) {
958
+ for (const s of ast.structs ?? []) {
959
+ if (this.getStructFullName(s, ast) === typeStr)
960
+ return typeStr;
961
+ }
962
+ for (const e of ast.enums ?? []) {
963
+ if (this.getEnumFullName(e, ast) === typeStr)
964
+ return typeStr;
965
+ }
966
+ for (const t of ast.typedefs ?? []) {
967
+ if (this.getTypedefFullName(t, ast) === typeStr)
968
+ return typeStr;
969
+ }
970
+ return typeStr;
971
+ }
972
+ const sameNsFull = currentNamespace ? `${currentNamespace}.${typeStr}` : '';
973
+ if (sameNsFull) {
974
+ for (const s of ast.structs ?? []) {
975
+ if (this.getStructFullName(s, ast) === sameNsFull)
976
+ return sameNsFull;
977
+ }
978
+ for (const e of ast.enums ?? []) {
979
+ if (this.getEnumFullName(e, ast) === sameNsFull)
980
+ return sameNsFull;
981
+ }
982
+ for (const t of ast.typedefs ?? []) {
983
+ if (this.getTypedefFullName(t, ast) === sameNsFull)
984
+ return sameNsFull;
985
+ }
986
+ }
987
+ const matches = [];
988
+ for (const s of ast.structs ?? []) {
989
+ const full = this.getStructFullName(s, ast);
990
+ if (full.endsWith('.' + typeStr))
991
+ matches.push(full);
992
+ }
993
+ for (const e of ast.enums ?? []) {
994
+ const full = this.getEnumFullName(e, ast);
995
+ if (full.endsWith('.' + typeStr))
996
+ matches.push(full);
997
+ }
998
+ for (const t of ast.typedefs ?? []) {
999
+ const full = this.getTypedefFullName(t, ast);
1000
+ if (full.endsWith('.' + typeStr))
1001
+ matches.push(full);
1002
+ }
1003
+ if (matches.length === 0)
1004
+ return typeStr;
1005
+ if (currentNamespace && matches.some(m => m.startsWith(currentNamespace + '.'))) {
1006
+ return matches.find(m => m.startsWith(currentNamespace + '.')) ?? matches[0] ?? typeStr;
1007
+ }
1008
+ return matches[0] ?? typeStr;
1009
+ }
1010
+ /**
1011
+ * AST 기준 타입 판별: 휴리스틱 없이 struct/enum/typedef 조회만 사용.
1012
+ * AST에 정의되지 않은 타입(typedef/struct/enum에 없음) → primitive 로 간주.
1013
+ * @returns 'primitive' | { kind: 'record', def } | { kind: 'enum', def } | null (미정의 타입, 득팩 표준 record)
1014
+ */
1015
+ resolveTypeToASTDefinition(type, currentNamespace, ast, visitedTypedefs = new Set()) {
1016
+ if (!ast)
1017
+ return null;
1018
+ if (typeof type === 'object' && type !== null && 'type' in type) {
1019
+ if (type.type === 'list' || type.type === 'set' || type.type === 'map')
1020
+ return null;
1021
+ }
1022
+ if (typeof type !== 'string')
1023
+ return null;
1024
+ const fullName = this.resolveTypeToFullName(type, currentNamespace, ast);
1025
+ if (ast.typedefs && ast.typedefs.length > 0) {
1026
+ for (const typedef of ast.typedefs) {
1027
+ if (this.getTypedefFullName(typedef, ast) !== fullName)
1028
+ continue;
1029
+ if (visitedTypedefs.has(fullName))
1030
+ return null;
1031
+ visitedTypedefs.add(fullName);
1032
+ const inner = typeof typedef.type === 'string'
1033
+ ? this.resolveTypeToASTDefinition(typedef.type, currentNamespace, ast, visitedTypedefs)
1034
+ : null;
1035
+ visitedTypedefs.delete(fullName);
1036
+ return inner;
1037
+ }
1038
+ }
1039
+ for (const s of ast.structs ?? []) {
1040
+ if (this.getStructFullName(s, ast) === fullName)
1041
+ return { kind: 'record', def: s };
1042
+ }
1043
+ for (const e of ast.enums ?? []) {
1044
+ if (this.getEnumFullName(e, ast) === fullName)
1045
+ return { kind: 'enum', def: e };
1046
+ }
1047
+ // AST에 정의되지 않은 타입 = 원시 타입 (휴리스틱 없음)
1048
+ return { kind: 'primitive' };
1049
+ }
1050
+ /**
1051
+ * Apache 호환: default 있는 struct 필드 할당 후, 그 내부의 default 있는 nested struct가 null이면 CreateDefault()로 채움.
1052
+ * (기본값이 있는 중첩 필드는 AST 기준으로만 처리, 타입명 하드코딩 없음)
1053
+ */
1054
+ generateEnsureNestedDefaults(parentFieldName, fieldType, ast, currentNamespace) {
1055
+ const target = this.findStruct(ast, fieldType, currentNamespace);
1056
+ if (!target || !target.fields)
1057
+ return [];
1058
+ const targetNs = this.getStructNamespace(target, ast);
1059
+ const out = [];
1060
+ for (const nested of target.fields) {
1061
+ if (nested.defaultValue === undefined)
1062
+ continue;
1063
+ const nestedCSharpType = this.getCSharpType(nested.type, ast, targetNs);
1064
+ const isNestedStruct = typeof nested.type === 'string' && (this.isStructType(nested.type, ast, targetNs) ||
1065
+ this.isStructCSharpType(nestedCSharpType, ast));
1066
+ if (!isNestedStruct)
1067
+ continue;
1068
+ const nestedFieldName = this.capitalize(nested.name);
1069
+ out.push(`if (this.${parentFieldName} != null && this.${parentFieldName}.${nestedFieldName} == null) this.${parentFieldName}.${nestedFieldName} = ${nestedCSharpType}.CreateDefault();`);
1070
+ }
1071
+ return out;
1072
+ }
1073
+ /** AST에서 풀네임으로만 struct 정의 찾기. typedef이면 대상 타입으로 따라감. */
1074
+ findStruct(ast, typeName, currentNamespace) {
1075
+ if (!ast || !ast.structs || typeof typeName !== 'string')
1076
+ return null;
1077
+ const fullName = this.resolveTypeToFullName(typeName, currentNamespace, ast);
1078
+ for (const s of ast.structs) {
1079
+ if (this.getStructFullName(s, ast) === fullName)
1080
+ return s;
1081
+ }
1082
+ let typedef = this.findTypedef(ast, typeName, currentNamespace);
1083
+ if (!typedef && typeName.includes('.')) {
1084
+ const shortName = typeName.split('.').pop() ?? typeName;
1085
+ typedef = this.findTypedef(ast, shortName, currentNamespace);
1086
+ }
1087
+ if (!typedef && ast.typedefs?.length) {
1088
+ const shortName = typeName.includes('.') ? (typeName.split('.').pop() ?? typeName) : typeName;
1089
+ for (const t of ast.typedefs) {
1090
+ const tShort = t.name.includes('.') ? (t.name.split('.').pop() ?? t.name) : t.name;
1091
+ if (tShort === shortName && typeof t.type === 'string') {
1092
+ typedef = t;
1093
+ break;
1094
+ }
1095
+ }
1096
+ }
1097
+ if (typedef && typeof typedef.type === 'string')
1098
+ return this.findStruct(ast, typedef.type, currentNamespace);
1099
+ return null;
1100
+ }
1101
+ /** AST에서 풀네임 또는 short name( currentNamespace 있을 때)으로 typedef 정의 찾기 */
1102
+ findTypedef(ast, typeName, currentNamespace) {
1103
+ if (!ast?.typedefs || typeof typeName !== 'string')
1104
+ return null;
1105
+ const fullName = this.resolveTypeToFullName(typeName, currentNamespace, ast);
1106
+ for (const t of ast.typedefs) {
1107
+ if (this.getTypedefFullName(t, ast) === fullName)
1108
+ return t;
1109
+ }
1110
+ return null;
1111
+ }
1112
+ /**
1113
+ * Struct 필드 기본값 객체 → C# 객체 초기화.
1114
+ * Apache 호환: 특정 필드만 지정해도, 지정되지 않은 하위 struct 멤버는 CreateDefault()로 할당.
1115
+ * 구조체·enum·변수 참조는 항상 풀네임으로 생성.
1116
+ */
1117
+ getCSharpStructObjectInitializer(defaultValue, fieldType, currentNamespace, ast) {
1118
+ if (typeof defaultValue !== 'object' || defaultValue === null || Array.isArray(defaultValue)) {
1119
+ return null;
1120
+ }
1121
+ const keys = Object.keys(defaultValue);
1122
+ const typeName = typeof fieldType === 'string' ? fieldType : '';
1123
+ const typeFullName = ast ? this.resolveTypeToFullName(typeName, currentNamespace, ast) : typeName;
1124
+ const csharpType = this.getCSharpType(fieldType, ast, currentNamespace);
1125
+ let targetStruct = this.findStruct(ast, typeFullName, currentNamespace);
1126
+ if (!targetStruct && typeName && ast?.typedefs?.length) {
1127
+ const shortName = typeFullName.includes('.') ? typeFullName.split('.').pop() : typeFullName;
1128
+ for (const t of ast.typedefs) {
1129
+ const tShort = t.name.includes('.') ? (t.name.split('.').pop() ?? t.name) : t.name;
1130
+ if (tShort === shortName && typeof t.type === 'string') {
1131
+ targetStruct = this.findStruct(ast, t.type, currentNamespace);
1132
+ break;
1133
+ }
1134
+ }
1135
+ }
1136
+ if (!targetStruct && typeName) {
1137
+ throw new Error(`[CSharpGenerator] Struct not found for default value object: ${typeFullName}`);
1138
+ }
1139
+ const assignments = [];
1140
+ const assignedByPush = new Set();
1141
+ for (const key of keys) {
1142
+ const val = defaultValue[key];
1143
+ const propName = this.capitalize(key);
1144
+ const member = targetStruct?.fields?.find(f => f.name.toLowerCase() === key.toLowerCase());
1145
+ if (!member && val !== null && val !== undefined) {
1146
+ throw new Error(`[CSharpGenerator] Field '${key}' not found in struct ${csharpType} (default value)`);
1147
+ }
1148
+ const memberType = member?.type;
1149
+ const memberTypeFull = (typeof memberType === 'string' && ast) ? this.resolveTypeToFullName(memberType, currentNamespace, ast) : memberType;
1150
+ const rhs = this.getCSharpDefaultValueForStructMember(val, memberTypeFull, currentNamespace, ast, csharpType, key);
1151
+ if (rhs !== null) {
1152
+ assignments.push(`${propName} = ${rhs}`);
1153
+ assignedByPush.add(key.toLowerCase());
1154
+ }
1155
+ }
1156
+ if (targetStruct && ast) {
1157
+ for (const member of targetStruct.fields) {
1158
+ if (assignedByPush.has(member.name.toLowerCase()))
1159
+ continue;
1160
+ if (typeof member.type !== 'string' || this.isEnumType(member.type, ast, currentNamespace))
1161
+ continue;
1162
+ const isNestedStruct = this.isStructType(member.type, ast, currentNamespace) ||
1163
+ this.isStructCSharpType(this.getCSharpType(member.type, ast, currentNamespace), ast);
1164
+ if (!isNestedStruct)
1165
+ continue;
1166
+ const nestedType = this.getCSharpType(member.type, ast, currentNamespace);
1167
+ assignments.push(`${this.capitalize(member.name)} = ${nestedType}.CreateDefault()`);
1168
+ }
1169
+ }
1170
+ if (assignments.length === 0)
1171
+ return null;
1172
+ return `new ${csharpType}() { ${assignments.join(', ')} }`;
1173
+ }
1174
+ /**
1175
+ * Struct 멤버 기본값 → C# 우변 식.
1176
+ * enum/구조체/변수 참조는 항상 풀네임으로 생성. 필드 타입(풀네임)만 보고 분기.
1177
+ */
1178
+ getCSharpDefaultValueForStructMember(val, memberType, currentNamespace, ast, structName, fieldName) {
1179
+ if (val === null || val === undefined)
1180
+ return null;
1181
+ const typeStr = typeof memberType === 'string' ? memberType : undefined;
1182
+ if (typeStr === undefined) {
1183
+ throw new Error(`[CSharpGenerator] Member type required for default value (struct: ${structName ?? '?'}, field: ${fieldName ?? '?'}). No fallback.`);
1184
+ }
1185
+ // 모든 타입 참조를 풀네임으로 통일 (구조체·enum 동일)
1186
+ const typeFull = ast ? this.resolveTypeToFullName(typeStr, currentNamespace, ast) : typeStr;
1187
+ const resolvedCSharpType = this.getCSharpType(memberType, ast, currentNamespace);
1188
+ const isEnum = ast && this.isEnumTypeFullName(typeFull, ast, currentNamespace);
1189
+ const isStruct = ast && this.isStructTypeFullName(typeFull, ast, currentNamespace);
1190
+ const isNumeric = ['int', 'short', 'long', 'byte'].includes(resolvedCSharpType);
1191
+ const quote = (s) => `"${String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
1192
+ switch (typeof val) {
1193
+ case 'number':
1194
+ if (isEnum) {
1195
+ throw new Error(`[CSharpGenerator] Enum field '${fieldName}' does not accept numeric default (struct: ${structName ?? '?'}).`);
1196
+ }
1197
+ if (resolvedCSharpType === 'bool')
1198
+ return val ? 'true' : 'false';
1199
+ if (resolvedCSharpType === 'long' || typeStr === 'int64')
1200
+ return `${val}L`;
1201
+ return `${val}`;
1202
+ case 'boolean':
1203
+ if (isNumeric)
1204
+ return val ? '1' : '0';
1205
+ return val ? 'true' : 'false';
1206
+ case 'object':
1207
+ if (Array.isArray(val))
1208
+ return null;
1209
+ if (!isStruct) {
1210
+ throw new Error(`[CSharpGenerator] Object default value for non-struct type '${typeFull}' (struct: ${structName ?? '?'}, field: ${fieldName ?? '?'}).`);
1211
+ }
1212
+ const nestedNs = typeFull.includes('.') ? typeFull.split('.').slice(0, -1).join('.') : currentNamespace;
1213
+ const nested = this.getCSharpStructObjectInitializer(val, typeFull, nestedNs ?? currentNamespace, ast);
1214
+ const csharpTypeName = this.getCSharpType(memberType, ast, currentNamespace);
1215
+ return nested ?? `${csharpTypeName}.CreateDefault()`;
1216
+ case 'string': {
1217
+ const hasDot = val.includes('.') && val.split('.').length >= 2;
1218
+ const valuePart = hasDot ? (val.split('.').pop() ?? val) : '';
1219
+ // C# 생성물에 원시 타입명(i32 등)이 나오면 안 됨. 숫자 필드 enum 기본값은 항상 enum 풀네임으로 생성
1220
+ const isPrimitive = ast && this.isPrimitiveSchemaType(typeFull, ast, currentNamespace);
1221
+ let fullEnumExpr;
1222
+ if (hasDot) {
1223
+ if (isPrimitive && ast) {
1224
+ const enumFull = this.findEnumFullNameByValueName(ast, valuePart, currentNamespace);
1225
+ if (!enumFull)
1226
+ throw new Error(`[CSharpGenerator] Cannot emit primitive in C#; no enum found for value '${valuePart}'. Struct: ${structName ?? '?'}, field: ${fieldName ?? '?'}.`);
1227
+ fullEnumExpr = `${enumFull}.${valuePart}`;
1228
+ }
1229
+ else {
1230
+ fullEnumExpr = `${typeFull}.${valuePart}`;
1231
+ }
1232
+ }
1233
+ else {
1234
+ fullEnumExpr = currentNamespace && val.split('.').length === 2 ? `${currentNamespace}.${val}` : val;
1235
+ }
1236
+ if (resolvedCSharpType === 'string')
1237
+ return quote(val);
1238
+ if (resolvedCSharpType === 'bool' && (val === 'true' || val === 'false'))
1239
+ return val;
1240
+ if (hasDot) {
1241
+ if (isEnum) {
1242
+ const enumDef = this.findEnumByFullName(ast ?? undefined, typeFull, currentNamespace);
1243
+ if (!enumDef || !Object.prototype.hasOwnProperty.call(enumDef.values, valuePart)) {
1244
+ throw new Error(`[CSharpGenerator] Enum '${typeFull}' has no value '${valuePart}' (struct: ${structName ?? '?'}, field: ${fieldName ?? '?'}).`);
1245
+ }
1246
+ return fullEnumExpr;
1247
+ }
1248
+ if (isNumeric) {
1249
+ if (!fullEnumExpr.includes('.')) {
1250
+ throw new Error(`[CSharpGenerator] Numeric field '${fieldName}' requires full enum reference (e.g. namespace.id_e.xxx), got: '${val}' (struct: ${structName ?? '?'}).`);
1251
+ }
1252
+ return `(int)${fullEnumExpr}`;
1253
+ }
1254
+ throw new Error(`[CSharpGenerator] Enum literal '${val}' but field type '${typeFull}' is not enum or numeric (struct: ${structName ?? '?'}, field: ${fieldName ?? '?'}).`);
1255
+ }
1256
+ if (isEnum) {
1257
+ throw new Error(`[CSharpGenerator] Enum field '${fieldName}' requires enum literal (e.g. id_e.xxx), got: '${val}' (struct: ${structName ?? '?'}).`);
1258
+ }
1259
+ if (isNumeric)
1260
+ return quote(val);
1261
+ throw new Error(`[CSharpGenerator] String default value for type '${typeFull}' not supported (struct: ${structName ?? '?'}, field: ${fieldName ?? '?'}).`);
1262
+ }
1263
+ default:
1264
+ throw new Error(`[CSharpGenerator] Unsupported default value type for '${typeFull}' (struct: ${structName ?? '?'}, field: ${fieldName ?? '?'}).`);
1265
+ }
1266
+ }
1267
+ generateWriteCondition(field, fieldName, ast, currentNamespace) {
1268
+ const explicitDefaultExpr = field.defaultValue !== undefined && ast
1269
+ ? this.getCSharpDefaultValue(field.defaultValue, field.type, ast, currentNamespace)
1270
+ : null;
1271
+ // tablelink is always long (value type)
1272
+ if (typeof field.type === 'object' && field.type !== null && field.type.type === 'tablelink') {
1273
+ return `if (this.${fieldName} != 0L) // tablelink is long, never null`;
1274
+ }
1275
+ if (typeof field.type === 'string') {
1276
+ const primitiveTypes = ['bool', 'byte', 'int8', 'int16', 'int32', 'int64', 'float', 'double'];
1277
+ if (primitiveTypes.includes(field.type)) {
1278
+ if (explicitDefaultExpr !== null && explicitDefaultExpr !== 'null') {
1279
+ const longSuffix = (field.type === 'int64' && /^\d+$/.test(explicitDefaultExpr)) ? 'L' : '';
1280
+ return `if (this.${fieldName} != ${explicitDefaultExpr}${longSuffix}) // skip when equals default`;
1281
+ }
1282
+ switch (field.type) {
1283
+ case 'bool':
1284
+ return `if (this.${fieldName}) // bool is never null, check if true`;
1285
+ case 'byte':
1286
+ case 'int8':
1287
+ return `if (this.${fieldName} != 0) // byte is never null, check if non-zero`;
1288
+ case 'int16':
1289
+ return `if (this.${fieldName} != 0) // short is never null, check if non-zero`;
1290
+ case 'int32':
1291
+ return `if (this.${fieldName} != 0) // int is never null, check if non-zero`;
1292
+ case 'int64':
1293
+ return `if (this.${fieldName} != 0L) // long is never null, check if non-zero`;
1294
+ case 'float':
1295
+ case 'double':
1296
+ return `if (this.${fieldName} != 0.0) // double is never null, check if non-zero`;
1297
+ default:
1298
+ return `if (this.${fieldName} != null)`;
1299
+ }
1300
+ }
1301
+ if (ast && this.isEnumType(field.type, ast, currentNamespace)) {
1302
+ if (explicitDefaultExpr !== null && explicitDefaultExpr !== 'null') {
1303
+ return `if (this.${fieldName} != ${explicitDefaultExpr}) // skip when equals default`;
1304
+ }
1305
+ return `if (true) // enum is never null, always write`;
1306
+ }
1307
+ if (ast) {
1308
+ const resolvedType = this.getCSharpType(field.type, ast, currentNamespace);
1309
+ const valueTypeConditions = [
1310
+ ['long', 'if (this.${fieldName} != 0L) // long (typedef) is never null'],
1311
+ ['int', 'if (this.${fieldName} != 0) // int (typedef) is never null'],
1312
+ ['short', 'if (this.${fieldName} != 0) // short (typedef) is never null'],
1313
+ ['byte', 'if (this.${fieldName} != 0) // byte (typedef) is never null'],
1314
+ ['bool', 'if (this.${fieldName}) // bool (typedef) is never null'],
1315
+ ['double', 'if (this.${fieldName} != 0.0) // double (typedef) is never null'],
1316
+ ['float', 'if (this.${fieldName} != 0f) // float (typedef) is never null'],
1317
+ ['Single', 'if (this.${fieldName} != 0f) // float (typedef) is never null'],
1318
+ ['DateTime', 'if (this.${fieldName} != default(DateTime)) // datetime (typedef)'],
1319
+ ['TimeSpan', 'if (this.${fieldName} != default(TimeSpan)) // time (typedef)'],
1320
+ ['decimal', 'if (this.${fieldName} != 0m) // decimal (typedef) is never null'],
1321
+ ];
1322
+ for (const [t, cond] of valueTypeConditions) {
1323
+ if (resolvedType === t)
1324
+ return cond.replace('${fieldName}', fieldName);
1325
+ }
1326
+ }
1327
+ if (field.type === 'string') {
1328
+ if (explicitDefaultExpr !== null) {
1329
+ // default "" 등: null이 아니고 기본값이 아닐 때만 Write
1330
+ return `if (this.${fieldName} != null && this.${fieldName} != ${explicitDefaultExpr}) // skip when null or default`;
1331
+ }
1332
+ return `if (this.${fieldName} != null)`;
1333
+ }
1334
+ // typedef로 정의된 primitive 타입인지 확인
1335
+ if (ast && this.isPrimitiveType(field.type, ast, currentNamespace)) {
1336
+ if (explicitDefaultExpr !== null && explicitDefaultExpr !== 'null') {
1337
+ const resolvedType = this.getCSharpType(field.type, ast, currentNamespace);
1338
+ const longSuffix = (resolvedType === 'long' && /^\d+$/.test(explicitDefaultExpr)) ? 'L' : '';
1339
+ return `if (this.${fieldName} != ${explicitDefaultExpr}${longSuffix}) // skip when equals default`;
1340
+ }
1341
+ const resolvedType = this.getCSharpType(field.type, ast, currentNamespace);
1342
+ if (resolvedType === 'long') {
1343
+ return `if (this.${fieldName} != 0L) // long is never null, check if non-zero`;
1344
+ }
1345
+ if (resolvedType === 'int') {
1346
+ return `if (this.${fieldName} != 0) // int is never null, check if non-zero`;
1347
+ }
1348
+ if (resolvedType === 'double') {
1349
+ return `if (this.${fieldName} != 0.0) // double is never null, check if non-zero`;
1350
+ }
1351
+ if (resolvedType === 'DateTime') {
1352
+ return `if (this.${fieldName} != default(DateTime)) // datetime/date/timestamp`;
1353
+ }
1354
+ if (resolvedType === 'TimeSpan') {
1355
+ return `if (this.${fieldName} != default(TimeSpan)) // time`;
1356
+ }
1357
+ if (resolvedType === 'decimal') {
1358
+ return `if (this.${fieldName} != 0m) // decimal/numeric`;
1359
+ }
1360
+ }
1361
+ // struct 타입도 nullable이 아니지만, null 체크 필요 (reference type)
1362
+ return `if (this.${fieldName} != null)`;
1363
+ }
1364
+ // reference 타입 (struct, list, set, map 등)은 null 체크
1365
+ return `if (this.${fieldName} != null)`;
1366
+ }
1367
+ /** 레거시 Thrift typedef i64 _link_* / _linktid_* 타입명 여부 (AST에 typedef 없을 때 long 처리용). */
1368
+ isLinkTypedefName(typeName) {
1369
+ return typeof typeName === 'string' && (typeName.startsWith('_link_') || typeName.startsWith('_linktid_'));
1370
+ }
1371
+ /** currentNamespace 지정 시 다른 네임스페이스 타입은 풀네임으로 반환. AST 조회만 사용(휴리스틱 없음). */
1372
+ getCSharpType(type, ast, currentNamespace) {
1373
+ if (typeof type === 'string') {
1374
+ switch (type) {
1375
+ case 'bool': return 'bool';
1376
+ case 'byte': return 'byte';
1377
+ case 'int8': return 'sbyte';
1378
+ case 'int16': return 'short';
1379
+ case 'int32': return 'int';
1380
+ case 'int64': return 'long';
1381
+ case 'float': return 'float';
1382
+ case 'double': return 'double';
1383
+ case 'string': return 'string';
1384
+ case 'binary': return 'byte[]';
1385
+ case 'datetime':
1386
+ case 'timestamp':
1387
+ case 'date': return 'DateTime';
1388
+ case 'time': return 'TimeSpan';
1389
+ case 'decimal':
1390
+ case 'numeric': return 'decimal';
1391
+ default: {
1392
+ const primitives = ['bool', 'byte', 'int8', 'int16', 'int32', 'int64', 'float', 'double', 'string', 'binary', 'datetime', 'timestamp', 'date', 'time', 'decimal', 'numeric'];
1393
+ const typedefDef = ast ? this.findTypedef(ast, type, currentNamespace) : null;
1394
+ if (typedefDef)
1395
+ return this.getCSharpType(typedefDef.type, ast, currentNamespace);
1396
+ // 레거시 Thrift typedef i64 _link_* / _linktid_*: AST에 typedef 없을 때 long으로 처리 (코드젠/마이그레이터 호환)
1397
+ if (this.isLinkTypedefName(type))
1398
+ return 'long';
1399
+ const fullName = ast ? this.resolveTypeToFullName(type, currentNamespace, ast) : type;
1400
+ if (primitives.includes(fullName)) {
1401
+ return this.getCSharpType(fullName, ast, currentNamespace);
1402
+ }
1403
+ return fullName;
1404
+ }
1405
+ }
1406
+ }
1407
+ if (typeof type === 'object' && type.type) {
1408
+ switch (type.type) {
1409
+ case 'list':
1410
+ return `List<${this.getCSharpType(type.elementType, ast, currentNamespace)}>`;
1411
+ case 'set':
1412
+ return `HashSet<${this.getCSharpType(type.elementType, ast, currentNamespace)}>`;
1413
+ case 'map':
1414
+ return `Dictionary<${this.getCSharpType(type.keyType, ast, currentNamespace)}, ${this.getCSharpType(type.valueType, ast, currentNamespace)}>`;
1415
+ case 'tablelink':
1416
+ return 'long';
1417
+ default:
1418
+ return 'object';
1419
+ }
1420
+ }
1421
+ return 'object';
1422
+ }
1423
+ getCSharpDefaultValue(value, type, ast, currentNamespace) {
1424
+ // 숫자 값이 list/set/map 타입에 할당된 경우 (잘못된 IDL 정의)
1425
+ if (typeof value === 'number' && typeof type === 'object' && type !== null && type.type) {
1426
+ switch (type.type) {
1427
+ case 'list':
1428
+ const listElemType = this.getCSharpType(type.elementType, ast, currentNamespace);
1429
+ return `new List<${listElemType}>()`;
1430
+ case 'set':
1431
+ const setElemType = this.getCSharpType(type.elementType, ast, currentNamespace);
1432
+ return `new HashSet<${setElemType}>()`;
1433
+ case 'map':
1434
+ const keyType = this.getCSharpType(type.keyType, ast, currentNamespace);
1435
+ const valueType = this.getCSharpType(type.valueType, ast, currentNamespace);
1436
+ return `new Dictionary<${keyType}, ${valueType}>()`;
1437
+ }
1438
+ }
1439
+ if (typeof value === 'string') {
1440
+ // enum 값인지 확인 (예: community_type_e.None, mo_define.level_exp_type_e.None)
1441
+ if (value.includes('.')) {
1442
+ const parts = value.split('.');
1443
+ const possibleEnumName = parts.length >= 2 ? parts.slice(0, -1).join('.') : '';
1444
+ const valuePart = parts.length >= 2 ? (parts[parts.length - 1] ?? '') : '';
1445
+ if (parts.length >= 2) {
1446
+ // AST에서 enum 찾기 → 항상 풀네임으로 출력
1447
+ if (ast && this.isEnumType(possibleEnumName, ast, currentNamespace)) {
1448
+ const enumDef = this.findEnumByFullName(ast, possibleEnumName, currentNamespace);
1449
+ if (enumDef)
1450
+ return `${this.getEnumFullName(enumDef, ast)}.${valuePart}`;
1451
+ return value;
1452
+ }
1453
+ // 접두어가 원시 타입(i32 등)이면 C# 생성물에 나오면 안 됨 → 값 이름으로 enum 풀네임 조회 후 반환
1454
+ if (ast && valuePart && this.isPrimitiveSchemaType(possibleEnumName, ast, currentNamespace)) {
1455
+ const enumFull = this.findEnumFullNameByValueName(ast, valuePart, currentNamespace);
1456
+ if (enumFull)
1457
+ return `${enumFull}.${valuePart}`;
1458
+ }
1459
+ }
1460
+ // version.app_version 같은 상수 참조인지 확인
1461
+ if (ast) {
1462
+ const resolved = this.resolveConstant(value, ast);
1463
+ if (resolved !== null) {
1464
+ return String(resolved);
1465
+ }
1466
+ }
1467
+ // 접두어가 타입(enum/struct)이면 풀네임으로 해석 후 반환
1468
+ if (ast && possibleEnumName && valuePart) {
1469
+ const prefixFull = this.resolveTypeToFullName(possibleEnumName, currentNamespace, ast);
1470
+ if (prefixFull && prefixFull !== possibleEnumName)
1471
+ return `${prefixFull}.${valuePart}`;
1472
+ }
1473
+ return value;
1474
+ }
1475
+ // Check if it's a boolean string
1476
+ if (value === 'true' || value === 'false') {
1477
+ return value;
1478
+ }
1479
+ return `"${value}"`;
1480
+ }
1481
+ if (typeof value === 'number') {
1482
+ return value.toString();
1483
+ }
1484
+ if (typeof value === 'boolean') {
1485
+ return value ? 'true' : 'false';
1486
+ }
1487
+ // null, undefined 또는 타입만 있고 값이 없는 경우
1488
+ if (value === null || value === undefined || typeof value === 'object') {
1489
+ // {} 등 빈 객체 + struct 타입: 하위 모든 구조체가 할당되도록 CreateDefault() 사용
1490
+ if (typeof type === 'string' && ast && this.isStructType(type, ast, currentNamespace)) {
1491
+ const csharpType = this.getCSharpType(type, ast, currentNamespace);
1492
+ return `${csharpType}.CreateDefault()`;
1493
+ }
1494
+ // 타입에 따른 기본값 설정
1495
+ if (typeof type === 'object' && type !== null && type.type) {
1496
+ switch (type.type) {
1497
+ case 'list':
1498
+ const listElemType2 = this.getCSharpType(type.elementType, ast, currentNamespace);
1499
+ return `new List<${listElemType2}>()`;
1500
+ case 'set':
1501
+ const setElemType2 = this.getCSharpType(type.elementType, ast, currentNamespace);
1502
+ return `new HashSet<${setElemType2}>()`;
1503
+ case 'map':
1504
+ const keyType2 = this.getCSharpType(type.keyType, ast, currentNamespace);
1505
+ const valueType2 = this.getCSharpType(type.valueType, ast, currentNamespace);
1506
+ return `new Dictionary<${keyType2}, ${valueType2}>()`;
1507
+ case 'tablelink':
1508
+ return '0L';
1509
+ default:
1510
+ return 'null';
1511
+ }
1512
+ }
1513
+ if (typeof type === 'string') {
1514
+ switch (type) {
1515
+ case 'bool': return 'false';
1516
+ case 'byte':
1517
+ case 'int8':
1518
+ case 'int16':
1519
+ case 'int32':
1520
+ case 'int64':
1521
+ case 'float':
1522
+ case 'double': return type === 'int64' ? '0L' : '0';
1523
+ case 'string': return '""';
1524
+ case 'datetime':
1525
+ case 'timestamp':
1526
+ case 'date': return 'default(DateTime)';
1527
+ case 'time': return 'default(TimeSpan)';
1528
+ case 'decimal':
1529
+ case 'numeric': return '0m';
1530
+ default:
1531
+ // enum이면 첫 번째 값 또는 기본값 (AST 조회)
1532
+ const enumResolved = ast ? this.resolveTypeToASTDefinition(type, currentNamespace, ast) : null;
1533
+ if (enumResolved?.kind === 'enum' && ast && enumResolved.def.values && typeof enumResolved.def.values === 'object') {
1534
+ const entries = Object.entries(enumResolved.def.values);
1535
+ const firstEntry = entries[0];
1536
+ if (firstEntry) {
1537
+ const enumFullName = this.getEnumFullName(enumResolved.def, ast);
1538
+ return `${enumFullName}.${firstEntry[0]}`;
1539
+ }
1540
+ }
1541
+ return 'null';
1542
+ }
1543
+ }
1544
+ return 'null';
1545
+ }
1546
+ return 'null';
1547
+ }
1548
+ capitalize(str) {
1549
+ return str.charAt(0).toUpperCase() + str.slice(1);
1550
+ }
1551
+ /** typedef를 따라 최종 wire 타입 문자열 반환 (타입 매칭용). 객체(list/set/map)는 그대로 두고 getTType에서 처리. */
1552
+ getResolvedWireTypeString(type, ast, currentNamespace) {
1553
+ if (typeof type !== 'string')
1554
+ return typeof type === 'object' && type?.type ? type.type : 'record';
1555
+ const typedefDef = ast ? this.findTypedef(ast, type, currentNamespace) : null;
1556
+ if (typedefDef) {
1557
+ const inner = typedefDef.type;
1558
+ if (typeof inner === 'string')
1559
+ return this.getResolvedWireTypeString(inner, ast, currentNamespace);
1560
+ }
1561
+ if (this.isLinkTypedefName(type))
1562
+ return 'int64';
1563
+ return type;
1564
+ }
1565
+ /** Wire type: enum→I32, typedef→해당 primitive/enum/struct/list/set/map, 그 외 custom→Struct. */
1566
+ getTType(type, ast, currentNamespace) {
1567
+ if (typeof type === 'string') {
1568
+ const typedefDef = ast ? this.findTypedef(ast, type, currentNamespace) : null;
1569
+ if (typedefDef && typeof typedefDef.type !== 'string')
1570
+ return this.getTType(typedefDef.type, ast, currentNamespace);
1571
+ const resolved = ast ? this.getResolvedWireTypeString(type, ast, currentNamespace) : type;
1572
+ switch (resolved) {
1573
+ case 'bool': return 'DpWireType.Bool';
1574
+ case 'byte': return 'DpWireType.Byte';
1575
+ case 'int8': return 'DpWireType.Byte';
1576
+ case 'int16': return 'DpWireType.I16';
1577
+ case 'int32': return 'DpWireType.I32';
1578
+ case 'int64': return 'DpWireType.I64';
1579
+ case 'float':
1580
+ case 'double': return 'DpWireType.Double';
1581
+ case 'string': return 'DpWireType.String';
1582
+ case 'binary': return 'DpWireType.String';
1583
+ case 'datetime':
1584
+ case 'timestamp': return 'DpWireType.I64';
1585
+ case 'date':
1586
+ case 'time': return 'DpWireType.I32';
1587
+ case 'decimal':
1588
+ case 'numeric': return 'DpWireType.String';
1589
+ default:
1590
+ if (this.isLinkTypedefName(resolved))
1591
+ return 'DpWireType.I64';
1592
+ if (ast && this.resolveTypeToASTDefinition(resolved, currentNamespace, ast)?.kind === 'enum')
1593
+ return 'DpWireType.I32';
1594
+ return 'DpWireType.Struct';
1595
+ }
1596
+ }
1597
+ if (typeof type === 'object' && type.type) {
1598
+ switch (type.type) {
1599
+ case 'list': return 'DpWireType.List';
1600
+ case 'set': return 'DpWireType.Set';
1601
+ case 'map': return 'DpWireType.Map';
1602
+ case 'tablelink': return 'DpWireType.I64';
1603
+ default: return 'DpWireType.Struct';
1604
+ }
1605
+ }
1606
+ return 'DpWireType.Struct';
1607
+ }
1608
+ generateWriteField(field, ast, currentNamespace) {
1609
+ const fieldName = this.capitalize(field.name);
1610
+ const tType = this.getTType(field.type, ast, currentNamespace);
1611
+ if (typeof field.type === 'string') {
1612
+ switch (field.type) {
1613
+ case 'bool': return `DeukPackSerializer.WriteValue(oprot, ${tType}, this.${fieldName});`;
1614
+ case 'byte':
1615
+ case 'int8':
1616
+ case 'int16':
1617
+ case 'int32':
1618
+ case 'int64':
1619
+ case 'float':
1620
+ case 'double': return `DeukPackSerializer.WriteValue(oprot, ${tType}, this.${fieldName});`;
1621
+ case 'string': return `DeukPackSerializer.WriteValue(oprot, ${tType}, this.${fieldName});`;
1622
+ case 'binary': return `DeukPackSerializer.WriteValue(oprot, ${tType}, this.${fieldName});`;
1623
+ case 'datetime':
1624
+ case 'timestamp': return `DeukPackSerializer.WriteValue(oprot, DpWireType.I64, this.${fieldName}.Ticks);`;
1625
+ case 'date': return `DeukPackSerializer.WriteValue(oprot, DpWireType.I32, this.${fieldName}.Year * 10000 + this.${fieldName}.Month * 100 + this.${fieldName}.Day);`;
1626
+ case 'time': return `DeukPackSerializer.WriteValue(oprot, DpWireType.I32, (int)this.${fieldName}.TotalMilliseconds);`;
1627
+ case 'decimal':
1628
+ case 'numeric': return `DeukPackSerializer.WriteValue(oprot, DpWireType.String, this.${fieldName}.ToString(System.Globalization.CultureInfo.InvariantCulture));`;
1629
+ default: return `DeukPackSerializer.WriteValue(oprot, ${tType}, this.${fieldName});`; // Recursive for custom types
1630
+ }
1631
+ }
1632
+ if (typeof field.type === 'object' && 'type' in field.type) {
1633
+ switch (field.type.type) {
1634
+ case 'list': return this.generateWriteList(field, ast, currentNamespace);
1635
+ case 'set': return this.generateWriteSet(field, ast, currentNamespace);
1636
+ case 'map': return this.generateWriteMap(field, ast, currentNamespace);
1637
+ default: return `DeukPackSerializer.WriteValue(oprot, ${tType}, this.${fieldName});`;
1638
+ }
1639
+ }
1640
+ return `DeukPackSerializer.WriteValue(oprot, ${tType}, this.${fieldName});`;
1641
+ }
1642
+ generateReadField(field, ast, currentNamespace) {
1643
+ const fieldName = this.capitalize(field.name);
1644
+ const tType = this.getTType(field.type, ast, currentNamespace);
1645
+ const csharpType = this.getCSharpType(field.type, ast, currentNamespace);
1646
+ if (typeof field.type === 'string') {
1647
+ switch (field.type) {
1648
+ case 'bool': return `this.${fieldName} = (bool)DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(bool));`;
1649
+ case 'byte': return `this.${fieldName} = (byte)DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(byte));`;
1650
+ case 'int8': return `this.${fieldName} = (sbyte)DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(sbyte));`;
1651
+ case 'int16': return `this.${fieldName} = (short)DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(short));`;
1652
+ case 'int32': return `this.${fieldName} = (int)DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(int));`;
1653
+ case 'int64': return `this.${fieldName} = (long)DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(long));`;
1654
+ case 'float': return `this.${fieldName} = (float)DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(float));`;
1655
+ case 'double': return `this.${fieldName} = (double)DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(double));`;
1656
+ case 'string': return `this.${fieldName} = (string)DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(string));`;
1657
+ case 'binary': return `this.${fieldName} = (byte[])DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(byte[]));`;
1658
+ case 'datetime':
1659
+ case 'timestamp': return `this.${fieldName} = new DateTime((long)DeukPackSerializer.ReadValue(iprot, DpWireType.I64, typeof(long)), DateTimeKind.Utc);`;
1660
+ case 'date': {
1661
+ const v = `(int)DeukPackSerializer.ReadValue(iprot, DpWireType.I32, typeof(int))`;
1662
+ return `var _${fieldName} = ${v}; this.${fieldName} = _${fieldName} == 0 ? default(DateTime) : new DateTime(_${fieldName} / 10000, (_${fieldName} / 100) % 100, _${fieldName} % 100);`;
1663
+ }
1664
+ case 'time': return `this.${fieldName} = TimeSpan.FromMilliseconds((int)DeukPackSerializer.ReadValue(iprot, DpWireType.I32, typeof(int)));`;
1665
+ case 'decimal':
1666
+ case 'numeric': return `var _${fieldName}Str = (string)DeukPackSerializer.ReadValue(iprot, DpWireType.String, typeof(string)); this.${fieldName} = string.IsNullOrEmpty(_${fieldName}Str) ? 0m : decimal.Parse(_${fieldName}Str, System.Globalization.CultureInfo.InvariantCulture);`;
1667
+ default: {
1668
+ const readExpr = `(${csharpType})DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(${csharpType}))`;
1669
+ const isStruct = ast && (this.isStructType(field.type, ast, currentNamespace) || this.isStructCSharpType(csharpType, ast));
1670
+ return isStruct ? `this.${fieldName} = ${readExpr} ?? ${csharpType}.CreateDefault();` : `this.${fieldName} = ${readExpr};`;
1671
+ }
1672
+ }
1673
+ }
1674
+ if (typeof field.type === 'object' && 'type' in field.type) {
1675
+ switch (field.type.type) {
1676
+ case 'list': return this.generateReadList(field, ast, currentNamespace);
1677
+ case 'set': return this.generateReadSet(field, ast, currentNamespace);
1678
+ case 'map': return this.generateReadMap(field, ast, currentNamespace);
1679
+ default: {
1680
+ const readExpr = `(${csharpType})DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(${csharpType}))`;
1681
+ const isStruct = ast && this.isStructCSharpType(csharpType, ast);
1682
+ return isStruct ? `this.${fieldName} = ${readExpr} ?? ${csharpType}.CreateDefault();` : `this.${fieldName} = ${readExpr};`;
1683
+ }
1684
+ }
1685
+ }
1686
+ const readExpr = `(${csharpType})DeukPackSerializer.ReadValue(iprot, ${tType}, typeof(${csharpType}))`;
1687
+ const isStruct = ast && (this.isStructType(field.type, ast, currentNamespace) || this.isStructCSharpType(csharpType, ast));
1688
+ return isStruct ? `this.${fieldName} = ${readExpr} ?? ${csharpType}.CreateDefault();` : `this.${fieldName} = ${readExpr};`;
1689
+ }
1690
+ generateWriteList(field, ast, currentNamespace) {
1691
+ const fieldName = this.capitalize(field.name);
1692
+ if (typeof field.type === 'object' && 'type' in field.type && field.type.type === 'list') {
1693
+ const elementType = this.getTType(field.type.elementType, ast, currentNamespace);
1694
+ const csharpElementType = this.getCSharpType(field.type.elementType, ast, currentNamespace);
1695
+ return `DeukPackSerializer.WriteList<${csharpElementType}>(oprot, ${elementType}, this.${fieldName});`;
1696
+ }
1697
+ return `DeukPackSerializer.WriteValue(oprot, DpWireType.List, this.${fieldName});`;
1698
+ }
1699
+ generateWriteSet(field, ast, currentNamespace) {
1700
+ const fieldName = this.capitalize(field.name);
1701
+ if (typeof field.type === 'object' && 'type' in field.type && field.type.type === 'set') {
1702
+ const elementType = this.getTType(field.type.elementType, ast, currentNamespace);
1703
+ const csharpElementType = this.getCSharpType(field.type.elementType, ast, currentNamespace);
1704
+ return `DeukPackSerializer.WriteSet<${csharpElementType}>(oprot, ${elementType}, this.${fieldName});`;
1705
+ }
1706
+ return `DeukPackSerializer.WriteValue(oprot, DpWireType.Set, this.${fieldName});`;
1707
+ }
1708
+ generateWriteMap(field, ast, currentNamespace) {
1709
+ const fieldName = this.capitalize(field.name);
1710
+ if (typeof field.type === 'object' && 'type' in field.type && field.type.type === 'map') {
1711
+ const keyType = this.getTType(field.type.keyType, ast, currentNamespace);
1712
+ const valueType = this.getTType(field.type.valueType, ast, currentNamespace);
1713
+ const csharpKeyType = this.getCSharpType(field.type.keyType, ast, currentNamespace);
1714
+ const csharpValueType = this.getCSharpType(field.type.valueType, ast, currentNamespace);
1715
+ return `DeukPackSerializer.WriteMap<${csharpKeyType}, ${csharpValueType}>(oprot, ${keyType}, ${valueType}, this.${fieldName});`;
1716
+ }
1717
+ return `DeukPackSerializer.WriteValue(oprot, DpWireType.Map, this.${fieldName});`;
1718
+ }
1719
+ // generateWriteListElement is no longer needed - handled by DeukPackSerializer recursively
1720
+ generateReadList(field, ast, currentNamespace) {
1721
+ const fieldName = this.capitalize(field.name);
1722
+ if (typeof field.type === 'object' && 'type' in field.type && field.type.type === 'list') {
1723
+ const elementType = this.getTType(field.type.elementType, ast, currentNamespace);
1724
+ const csharpElementType = this.getCSharpType(field.type.elementType, ast, currentNamespace);
1725
+ return `this.${fieldName} = DeukPackSerializer.ReadList<${csharpElementType}>(iprot, ${elementType});`;
1726
+ }
1727
+ const csharpType = this.getCSharpType(field.type, ast, currentNamespace);
1728
+ return `this.${fieldName} = (${csharpType})DeukPackSerializer.ReadValue(iprot, DpWireType.List, typeof(${csharpType}));`;
1729
+ }
1730
+ generateReadSet(field, ast, currentNamespace) {
1731
+ const fieldName = this.capitalize(field.name);
1732
+ if (typeof field.type === 'object' && 'type' in field.type && field.type.type === 'set') {
1733
+ const elementType = this.getTType(field.type.elementType, ast, currentNamespace);
1734
+ const csharpElementType = this.getCSharpType(field.type.elementType, ast, currentNamespace);
1735
+ return `this.${fieldName} = DeukPackSerializer.ReadSet<${csharpElementType}>(iprot, ${elementType});`;
1736
+ }
1737
+ const csharpType = this.getCSharpType(field.type, ast, currentNamespace);
1738
+ return `this.${fieldName} = (${csharpType})DeukPackSerializer.ReadValue(iprot, DpWireType.Set, typeof(${csharpType}));`;
1739
+ }
1740
+ generateReadMap(field, ast, currentNamespace) {
1741
+ const fieldName = this.capitalize(field.name);
1742
+ if (typeof field.type === 'object' && 'type' in field.type && field.type.type === 'map') {
1743
+ const keyType = this.getTType(field.type.keyType, ast, currentNamespace);
1744
+ const valueType = this.getTType(field.type.valueType, ast, currentNamespace);
1745
+ const csharpKeyType = this.getCSharpType(field.type.keyType, ast, currentNamespace);
1746
+ const csharpValueType = this.getCSharpType(field.type.valueType, ast, currentNamespace);
1747
+ return `this.${fieldName} = DeukPackSerializer.ReadMap<${csharpKeyType}, ${csharpValueType}>(iprot, ${keyType}, ${valueType});`;
1748
+ }
1749
+ const csharpType = this.getCSharpType(field.type, ast, currentNamespace);
1750
+ return `this.${fieldName} = (${csharpType})DeukPackSerializer.ReadValue(iprot, DpWireType.Map, typeof(${csharpType}));`;
1751
+ }
1752
+ // generateReadListElement is no longer needed - handled by DeukPackSerializer recursively
1753
+ groupByNamespace(ast) {
1754
+ const groups = {};
1755
+ const namespaces = ast.namespaces.map(ns => ns.name);
1756
+ if (namespaces.length === 0) {
1757
+ namespaces.push('Generated');
1758
+ }
1759
+ for (const ns of namespaces) {
1760
+ groups[ns] = { enums: [], structs: [], typedefs: [], constants: [], services: [] };
1761
+ }
1762
+ for (const enumDef of ast.enums) {
1763
+ const ns = this.getEnumNamespace(enumDef, ast);
1764
+ if (groups[ns])
1765
+ groups[ns].enums.push(enumDef);
1766
+ }
1767
+ for (const struct of ast.structs) {
1768
+ const ns = this.getStructNamespace(struct, ast);
1769
+ if (groups[ns])
1770
+ groups[ns].structs.push(struct);
1771
+ }
1772
+ for (const typedef of ast.typedefs) {
1773
+ const ns = this.getTypedefNamespace(typedef, ast);
1774
+ if (groups[ns])
1775
+ groups[ns].typedefs.push(typedef);
1776
+ }
1777
+ for (const constant of ast.constants) {
1778
+ const ns = this.getConstantNamespace(constant, ast);
1779
+ if (groups[ns])
1780
+ groups[ns].constants.push(constant);
1781
+ }
1782
+ for (const service of ast.services || []) {
1783
+ const ns = this.getServiceNamespace(service, ast);
1784
+ if (groups[ns])
1785
+ groups[ns].services.push(service);
1786
+ }
1787
+ return groups;
1788
+ }
1789
+ getServiceNamespace(service, ast) {
1790
+ const mapped = service.sourceFile && ast.fileNamespaceMap ? ast.fileNamespaceMap[service.sourceFile] : undefined;
1791
+ if (mapped)
1792
+ return mapped;
1793
+ if (service.sourceFile) {
1794
+ const ns = ast.namespaces.find(n => (n.language === '*' || n.language === 'csharp') && n.sourceFile === service.sourceFile);
1795
+ if (ns && ns.name)
1796
+ return ns.name;
1797
+ }
1798
+ const def = ast.namespaces.find(n => n.language === '*' || n.language === 'csharp');
1799
+ return (def && def.name) ? def.name : 'Generated';
1800
+ }
1801
+ generateService(service, ast, namespace) {
1802
+ const lines = [];
1803
+ const opts = this._genOptions;
1804
+ lines.push(` /// <summary>Thrift service: ${service.name}</summary>`);
1805
+ lines.push(` public interface I${service.name}`);
1806
+ lines.push(' {');
1807
+ for (const method of service.methods) {
1808
+ const retCs = this.getCSharpType(method.returnType, ast, namespace);
1809
+ const params = (method.parameters || []).map(p => `${this.getCSharpType(p.type, ast, namespace)} ${p.name}`).join(', ');
1810
+ lines.push(` ${retCs} ${method.name}(${params});`);
1811
+ }
1812
+ lines.push(' }');
1813
+ lines.push('');
1814
+ lines.push(` public static partial class ${service.name}`);
1815
+ lines.push(' {');
1816
+ for (const method of service.methods) {
1817
+ const retCs = this.getCSharpType(method.returnType, ast, namespace);
1818
+ const params = (method.parameters || []).map(p => `${this.getCSharpType(p.type, ast, namespace)} ${p.name}`).join(', ');
1819
+ if (service.name === 'ThriftDefineService' && method.name === 'GetDefineVersion') {
1820
+ let versionValue = '"unknown"';
1821
+ if (opts?.defineVersionFile) {
1822
+ try {
1823
+ const content = fs.readFileSync(path.resolve(opts.defineVersionFile), 'utf-8').trim();
1824
+ versionValue = JSON.stringify(content);
1825
+ }
1826
+ catch {
1827
+ versionValue = '"unknown"';
1828
+ }
1829
+ }
1830
+ lines.push(` public static ${retCs} ${method.name}(${params}) => ${versionValue};`);
1831
+ }
1832
+ else {
1833
+ lines.push(` public static ${retCs} ${method.name}(${params}) => default;`);
1834
+ }
1835
+ }
1836
+ lines.push(' }');
1837
+ return lines;
1838
+ }
1839
+ getEnumNamespace(enumDef, ast) {
1840
+ // 파일 경로 기반으로 네임스페이스 찾기
1841
+ if (enumDef.sourceFile && ast.fileNamespaceMap) {
1842
+ const namespace = ast.fileNamespaceMap[enumDef.sourceFile];
1843
+ if (namespace) {
1844
+ return namespace;
1845
+ }
1846
+ }
1847
+ // Enum의 sourceFile과 같은 파일의 네임스페이스 찾기
1848
+ if (enumDef.sourceFile) {
1849
+ const namespace = ast.namespaces.find(ns => (ns.language === '*' || ns.language === 'csharp') &&
1850
+ ns.sourceFile === enumDef.sourceFile);
1851
+ if (namespace) {
1852
+ return namespace.name;
1853
+ }
1854
+ }
1855
+ // Fallback: 첫 번째 네임스페이스 또는 'Generated'
1856
+ const namespace = ast.namespaces.find(ns => ns.language === '*' || ns.language === 'csharp');
1857
+ return namespace ? namespace.name : 'Generated';
1858
+ }
1859
+ getStructNamespace(struct, ast) {
1860
+ // 파일 경로 기반으로 네임스페이스 찾기
1861
+ if (struct.sourceFile && ast.fileNamespaceMap) {
1862
+ const namespace = ast.fileNamespaceMap[struct.sourceFile];
1863
+ if (namespace) {
1864
+ return namespace;
1865
+ }
1866
+ }
1867
+ // 구조체의 sourceFile과 같은 파일의 네임스페이스 찾기
1868
+ if (struct.sourceFile) {
1869
+ const namespace = ast.namespaces.find(ns => (ns.language === '*' || ns.language === 'csharp') &&
1870
+ ns.sourceFile === struct.sourceFile);
1871
+ if (namespace) {
1872
+ return namespace.name;
1873
+ }
1874
+ }
1875
+ // Fallback: 첫 번째 네임스페이스 또는 'Generated'
1876
+ const namespace = ast.namespaces.find(ns => ns.language === '*' || ns.language === 'csharp');
1877
+ return namespace ? namespace.name : 'Generated';
1878
+ }
1879
+ getTypedefNamespace(typedef, ast) {
1880
+ const norm = (p) => p.replace(/\\/g, '/');
1881
+ if (typedef.sourceFile && ast.fileNamespaceMap) {
1882
+ const key = ast.fileNamespaceMap[typedef.sourceFile];
1883
+ if (key)
1884
+ return key;
1885
+ const normalized = norm(typedef.sourceFile);
1886
+ const entry = Object.entries(ast.fileNamespaceMap).find(([k]) => norm(k) === normalized);
1887
+ if (entry)
1888
+ return entry[1];
1889
+ }
1890
+ if (typedef.sourceFile) {
1891
+ const ns = ast.namespaces.find(ns => (ns.language === '*' || ns.language === 'csharp') &&
1892
+ ns.sourceFile && norm(ns.sourceFile) === norm(typedef.sourceFile));
1893
+ if (ns)
1894
+ return ns.name;
1895
+ }
1896
+ // Fallback: 첫 번째 네임스페이스 또는 'Generated'
1897
+ const namespace = ast.namespaces.find(ns => ns.language === '*' || ns.language === 'csharp');
1898
+ return namespace ? namespace.name : 'Generated';
1899
+ }
1900
+ getConstantNamespace(constant, ast) {
1901
+ // 파일 경로 기반으로 네임스페이스 찾기
1902
+ if (constant.sourceFile && ast.fileNamespaceMap) {
1903
+ const namespace = ast.fileNamespaceMap[constant.sourceFile];
1904
+ if (namespace) {
1905
+ return namespace;
1906
+ }
1907
+ }
1908
+ // Constant의 sourceFile과 같은 파일의 네임스페이스 찾기
1909
+ if (constant.sourceFile) {
1910
+ const namespace = ast.namespaces.find(ns => (ns.language === '*' || ns.language === 'csharp') &&
1911
+ ns.sourceFile === constant.sourceFile);
1912
+ if (namespace) {
1913
+ return namespace.name;
1914
+ }
1915
+ }
1916
+ // Fallback: 첫 번째 네임스페이스 또는 'Generated'
1917
+ const namespace = ast.namespaces.find(ns => ns.language === '*' || ns.language === 'csharp');
1918
+ return namespace ? namespace.name : 'Generated';
1919
+ }
1920
+ /** AST 조회로만 판별. currentNamespace 있으면 short name 해석. */
1921
+ isPrimitiveType(type, ast, currentNamespace) {
1922
+ return this.resolveTypeToASTDefinition(type, currentNamespace, ast)?.kind === 'primitive';
1923
+ }
1924
+ /** AST 조회로만 판별 (휴리스틱 없음). currentNamespace 있으면 short name 해석. */
1925
+ isEnumType(type, ast, currentNamespace) {
1926
+ return this.resolveTypeToASTDefinition(type, currentNamespace, ast)?.kind === 'enum';
1927
+ }
1928
+ /** AST 조회로만 판별. currentNamespace 있으면 short name 해석. */
1929
+ isEnumTypeFullName(typeStr, ast, currentNamespace) {
1930
+ return this.resolveTypeToASTDefinition(typeStr, currentNamespace, ast)?.kind === 'enum';
1931
+ }
1932
+ /** 풀네임 또는 short name( currentNamespace 있을 때)으로 enum 정의 반환 */
1933
+ findEnumByFullName(ast, typeStr, currentNamespace) {
1934
+ if (!ast?.enums || !typeStr)
1935
+ return null;
1936
+ const fullName = currentNamespace !== undefined ? this.resolveTypeToFullName(typeStr, currentNamespace, ast) : typeStr;
1937
+ for (const enumDef of ast.enums) {
1938
+ if (this.getEnumFullName(enumDef, ast) === fullName)
1939
+ return enumDef;
1940
+ }
1941
+ return null;
1942
+ }
1943
+ /** 원시 스키마 타입이면 true (typedef 포함, AST 기준). C# 출력에 원시 타입명(i32 등)이 나오면 안 됨. */
1944
+ isPrimitiveSchemaType(typeStr, ast, currentNamespace) {
1945
+ return ast ? this.resolveTypeToASTDefinition(typeStr, currentNamespace, ast)?.kind === 'primitive' : false;
1946
+ }
1947
+ /** 값 이름으로 AST에 정의된 enum 풀네임 반환. 동일 값 이름이 여러 enum에 있으면 currentNamespace와 같은 네임스페이스 enum 우선. */
1948
+ findEnumFullNameByValueName(ast, valueName, currentNamespace) {
1949
+ if (!ast?.enums || !valueName)
1950
+ return null;
1951
+ let fallback = null;
1952
+ for (const e of ast.enums) {
1953
+ if (!Object.prototype.hasOwnProperty.call(e.values, valueName))
1954
+ continue;
1955
+ const full = this.getEnumFullName(e, ast);
1956
+ if (currentNamespace && full.startsWith(currentNamespace + '.'))
1957
+ return full;
1958
+ if (!fallback)
1959
+ fallback = full;
1960
+ }
1961
+ return fallback;
1962
+ }
1963
+ /** C# 타입 이름이 AST에 정의된 struct인지. AST 조회만 사용. */
1964
+ isStructCSharpType(csharpTypeName, ast) {
1965
+ return this.resolveTypeToASTDefinition(csharpTypeName, undefined, ast)?.kind === 'record';
1966
+ }
1967
+ /** AST 조회로만 판별. currentNamespace 있으면 short name 해석. */
1968
+ isStructTypeFullName(typeStr, ast, currentNamespace) {
1969
+ return this.resolveTypeToASTDefinition(typeStr, currentNamespace, ast)?.kind === 'record';
1970
+ }
1971
+ /** 필드 타입이 AST에 정의된 struct인지. list/set/map 요소가 아닌 필드 타입 자체가 struct일 때 true. currentNamespace 있으면 short name 해석. */
1972
+ isStructType(type, ast, currentNamespace) {
1973
+ return this.resolveTypeToASTDefinition(type, currentNamespace, ast)?.kind === 'record';
1974
+ }
1975
+ /** CreateDefault() 내 필드 초기화 RHS: IDL 기본값이 있으면 사용(호환), 없으면 struct→CreateDefault(), list/set/map→빈 컬렉션, primitive/enum→타입 기본값 */
1976
+ generateCreateDefaultRhs(field, ast, _currentNamespace, parentStruct) {
1977
+ if (parentStruct && ast && _currentNamespace) {
1978
+ const msgDefault = this.buildMessageFirstFieldDefault(parentStruct, field, ast, _currentNamespace);
1979
+ if (msgDefault != null)
1980
+ return msgDefault;
1981
+ }
1982
+ // 필드에 명시된 기본값이 있으면 CreateDefault()에서도 동일하게 사용
1983
+ if (field.defaultValue !== undefined && ast) {
1984
+ const objInit = this.getCSharpStructObjectInitializer(field.defaultValue, field.type, _currentNamespace, ast);
1985
+ if (objInit !== null)
1986
+ return objInit;
1987
+ const explicitDefault = this.getCSharpDefaultValue(field.defaultValue, field.type, ast, _currentNamespace);
1988
+ if (explicitDefault !== 'null')
1989
+ return explicitDefault;
1990
+ }
1991
+ const opt = !field.required;
1992
+ if (typeof field.type === 'string') {
1993
+ switch (field.type) {
1994
+ case 'bool': return 'false';
1995
+ case 'byte':
1996
+ case 'int8':
1997
+ case 'int16':
1998
+ case 'int32':
1999
+ case 'int64':
2000
+ case 'float':
2001
+ case 'double': return field.type === 'int64' ? '0L' : '0';
2002
+ case 'string': return '""';
2003
+ case 'binary': return opt ? 'null' : 'new byte[0]';
2004
+ case 'datetime':
2005
+ case 'timestamp':
2006
+ case 'date': return 'default(DateTime)';
2007
+ case 'time': return 'default(TimeSpan)';
2008
+ case 'decimal':
2009
+ case 'numeric': return '0m';
2010
+ default: {
2011
+ const resolved = ast ? this.getCSharpType(field.type, ast, _currentNamespace) : '';
2012
+ if (resolved === 'string')
2013
+ return '""';
2014
+ if (resolved === 'bool')
2015
+ return 'false';
2016
+ if (resolved === 'int' || resolved === 'short' || resolved === 'byte')
2017
+ return '0';
2018
+ if (resolved === 'long')
2019
+ return '0L';
2020
+ if (resolved === 'double')
2021
+ return '0';
2022
+ if (resolved === 'DateTime')
2023
+ return 'default(DateTime)';
2024
+ if (resolved === 'TimeSpan')
2025
+ return 'default(TimeSpan)';
2026
+ if (resolved === 'decimal')
2027
+ return '0m';
2028
+ if (ast && this.isStructType(field.type, ast, _currentNamespace)) {
2029
+ const typeForDefault = this.getCSharpType(field.type, ast, _currentNamespace);
2030
+ return `${typeForDefault}.CreateDefault()`;
2031
+ }
2032
+ if (ast && this.isEnumType(field.type, ast, _currentNamespace)) {
2033
+ const typeForCast = this.getCSharpType(field.type, ast, _currentNamespace);
2034
+ return `(${typeForCast})0`;
2035
+ }
2036
+ return 'null';
2037
+ }
2038
+ }
2039
+ }
2040
+ if (typeof field.type === 'object' && field.type !== null && 'type' in field.type) {
2041
+ switch (field.type.type) {
2042
+ case 'list': {
2043
+ const elem = this.getCSharpType(field.type.elementType, ast, _currentNamespace);
2044
+ return `new List<${elem}>()`;
2045
+ }
2046
+ case 'set': {
2047
+ const elem = this.getCSharpType(field.type.elementType, ast, _currentNamespace);
2048
+ return `new HashSet<${elem}>()`;
2049
+ }
2050
+ case 'map': {
2051
+ const k = this.getCSharpType(field.type.keyType, ast, _currentNamespace);
2052
+ const v = this.getCSharpType(field.type.valueType, ast, _currentNamespace);
2053
+ return `new Dictionary<${k}, ${v}>()`;
2054
+ }
2055
+ case 'tablelink':
2056
+ return '0L';
2057
+ default: return 'null';
2058
+ }
2059
+ }
2060
+ return 'null';
2061
+ }
2062
+ generateCloneField(field, fieldName, ast, currentNamespace) {
2063
+ const opt = !field.required;
2064
+ const wrapOpt = (stmt) => opt ? `clone.${fieldName} = this.${fieldName} != null ? (${stmt}) : null;` : `clone.${fieldName} = ${stmt};`;
2065
+ if (typeof field.type === 'string') {
2066
+ switch (field.type) {
2067
+ case 'bool':
2068
+ case 'byte':
2069
+ case 'int8':
2070
+ case 'int16':
2071
+ case 'int32':
2072
+ case 'int64':
2073
+ case 'float':
2074
+ case 'double':
2075
+ case 'datetime':
2076
+ case 'timestamp':
2077
+ case 'date':
2078
+ case 'time':
2079
+ case 'decimal':
2080
+ case 'numeric':
2081
+ return `clone.${fieldName} = this.${fieldName};`;
2082
+ case 'string':
2083
+ return `clone.${fieldName} = this.${fieldName};`;
2084
+ case 'binary':
2085
+ return wrapOpt(`this.${fieldName}.Clone()`);
2086
+ default:
2087
+ if (ast && (this.isEnumType(field.type, ast, currentNamespace) || this.isPrimitiveType(field.type, ast, currentNamespace) || !this.isStructType(field.type, ast, currentNamespace))) {
2088
+ return `clone.${fieldName} = this.${fieldName};`;
2089
+ }
2090
+ return wrapOpt(`this.${fieldName}.Clone()`);
2091
+ }
2092
+ }
2093
+ if (typeof field.type === 'object' && 'type' in field.type) {
2094
+ switch (field.type.type) {
2095
+ case 'list':
2096
+ return wrapOpt(`this.${fieldName}.Select(item => ${this.generateCloneElement(field.type.elementType, ast, 'item', currentNamespace)}).ToList()`);
2097
+ case 'set':
2098
+ return wrapOpt(`this.${fieldName}.Select(item => ${this.generateCloneElement(field.type.elementType, ast, 'item', currentNamespace)}).ToHashSet()`);
2099
+ case 'map': {
2100
+ const keyClone = this.generateCloneElement(field.type.keyType, ast, 'kvp.Key', currentNamespace);
2101
+ const valueClone = this.generateCloneElement(field.type.valueType, ast, 'kvp.Value', currentNamespace);
2102
+ return wrapOpt(`this.${fieldName}.ToDictionary(kvp => ${keyClone}, kvp => ${valueClone})`);
2103
+ }
2104
+ case 'tablelink':
2105
+ return `clone.${fieldName} = this.${fieldName};`;
2106
+ default:
2107
+ return wrapOpt(`this.${fieldName}.Clone()`);
2108
+ }
2109
+ }
2110
+ return wrapOpt(`this.${fieldName}.Clone()`);
2111
+ }
2112
+ generateCloneElement(elementType, ast, varName = 'item', currentNamespace) {
2113
+ // 1. 객체 타입인 경우
2114
+ if (typeof elementType === 'object' && elementType !== null) {
2115
+ if ('type' in elementType) {
2116
+ switch (elementType.type) {
2117
+ case 'tablelink':
2118
+ return varName;
2119
+ case 'list':
2120
+ return `${varName}.Select(item => ${this.generateCloneElement(elementType.elementType, ast, 'item', currentNamespace)}).ToList()`;
2121
+ case 'set':
2122
+ return `${varName}.Select(item => ${this.generateCloneElement(elementType.elementType, ast, 'item', currentNamespace)}).ToHashSet()`;
2123
+ case 'map': {
2124
+ const keyClone = this.generateCloneElement(elementType.keyType, ast, 'kvp.Key', currentNamespace);
2125
+ const valueClone = this.generateCloneElement(elementType.valueType, ast, 'kvp.Value', currentNamespace);
2126
+ return `${varName}.ToDictionary(kvp => ${keyClone}, kvp => ${valueClone})`;
2127
+ }
2128
+ default:
2129
+ return `${varName}.Clone()`;
2130
+ }
2131
+ }
2132
+ return `${varName}.Clone()`;
2133
+ }
2134
+ // 2. 문자열 타입인 경우
2135
+ if (typeof elementType === 'string') {
2136
+ // 기본 primitive 타입 처리
2137
+ const primitiveTypes = ['bool', 'byte', 'int8', 'int16', 'int32', 'int64', 'float', 'double', 'string'];
2138
+ if (primitiveTypes.includes(elementType)) {
2139
+ return varName; // primitive는 그대로 사용
2140
+ }
2141
+ // AST를 사용하여 typedef/enum 확인
2142
+ if (ast) {
2143
+ // enum 타입은 Clone() 불필요
2144
+ if (this.isEnumType(elementType, ast, currentNamespace)) {
2145
+ return varName;
2146
+ }
2147
+ // typedef로 정의된 primitive 타입인지 확인
2148
+ if (this.isPrimitiveType(elementType, ast, currentNamespace)) {
2149
+ return varName;
2150
+ }
2151
+ // getCSharpType으로 실제 타입 확인
2152
+ const resolvedType = this.getCSharpType(elementType, ast);
2153
+ const csharpPrimitiveTypes = ['bool', 'byte', 'short', 'int', 'long', 'double', 'string'];
2154
+ if (csharpPrimitiveTypes.includes(resolvedType)) {
2155
+ return varName;
2156
+ }
2157
+ }
2158
+ if (elementType === 'binary') {
2159
+ return `${varName}.Clone()`;
2160
+ }
2161
+ return `${varName}.Clone()`;
2162
+ }
2163
+ return `${varName}.Clone()`;
2164
+ }
2165
+ generateToStringField(field, fieldName, ast, currentNamespace) {
2166
+ const csharpType = this.getCSharpType(field.type, ast, currentNamespace);
2167
+ const isStruct = ast && (this.isStructType(field.type, ast, currentNamespace) || this.isStructCSharpType(csharpType, ast));
2168
+ if (typeof field.type === 'string') {
2169
+ const primitiveTypes = ['bool', 'byte', 'int8', 'int16', 'int32', 'int64', 'float', 'double'];
2170
+ const dbTypes = ['datetime', 'timestamp', 'date', 'time', 'decimal', 'numeric'];
2171
+ if (primitiveTypes.includes(field.type) || field.type === 'string') {
2172
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}).AppendLine(",");`;
2173
+ }
2174
+ if (dbTypes.includes(field.type)) {
2175
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}.ToString()).AppendLine(",");`;
2176
+ }
2177
+ if (field.type === 'binary') {
2178
+ return `sb.Append(ci).Append("${field.name}: [").Append(this.${fieldName}?.Length ?? 0).AppendLine(" bytes],");`;
2179
+ }
2180
+ if (ast) {
2181
+ const resolvedType = this.getCSharpType(field.type, ast, currentNamespace);
2182
+ const csharpValueTypes = ['bool', 'byte', 'short', 'int', 'long', 'double', 'float', 'Single'];
2183
+ if (csharpValueTypes.includes(resolvedType) || this.isEnumType(field.type, ast, currentNamespace)) {
2184
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}.ToString()).AppendLine(",");`;
2185
+ }
2186
+ }
2187
+ if (isStruct) {
2188
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}?.ToString(ci) ?? "null").AppendLine(",");`;
2189
+ }
2190
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}?.ToString() ?? "null").AppendLine(",");`;
2191
+ }
2192
+ if (typeof field.type === 'object' && 'type' in field.type) {
2193
+ switch (field.type.type) {
2194
+ case 'tablelink':
2195
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}.ToString()).AppendLine(",");`;
2196
+ case 'list':
2197
+ return `sb.Append(ci).Append("${field.name}: [").Append(this.${fieldName}?.Count ?? 0).AppendLine(" items],");`;
2198
+ case 'set':
2199
+ return `sb.Append(ci).Append("${field.name}: {").Append(this.${fieldName}?.Count ?? 0).AppendLine(" items},");`;
2200
+ case 'map':
2201
+ return `sb.Append(ci).Append("${field.name}: {").Append(this.${fieldName}?.Count ?? 0).AppendLine(" pairs},");`;
2202
+ default:
2203
+ if (isStruct) {
2204
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}?.ToString(ci) ?? "null").AppendLine(",");`;
2205
+ }
2206
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}?.ToString() ?? "null").AppendLine(",");`;
2207
+ }
2208
+ }
2209
+ if (isStruct) {
2210
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}?.ToString(ci) ?? "null").AppendLine(",");`;
2211
+ }
2212
+ if (ast && (this.isPrimitiveType(field.type, ast, currentNamespace) || this.isEnumType(field.type, ast, currentNamespace))) {
2213
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}.ToString()).AppendLine(",");`;
2214
+ }
2215
+ return `sb.Append(ci).Append("${field.name}: ").Append(this.${fieldName}?.ToString() ?? "null").AppendLine(",");`;
2216
+ }
2217
+ resolveConstant(value, ast) {
2218
+ // value 형식: "namespace.constant_name" 또는 "constant_name"
2219
+ const parts = value.split('.');
2220
+ if (parts.length === 0)
2221
+ return null;
2222
+ // constant_name만 있는 경우 또는 namespace.constant_name인 경우
2223
+ const constantName = parts[parts.length - 1];
2224
+ const namespace = parts.length > 1 ? parts.slice(0, -1).join('.') : '';
2225
+ // AST에서 상수 찾기
2226
+ for (const constant of ast.constants) {
2227
+ const constName = constant.name.split('.').pop() || constant.name;
2228
+ const constFullName = constant.name;
2229
+ const constNamespace = constant.name.includes('.')
2230
+ ? constant.name.substring(0, constant.name.lastIndexOf('.'))
2231
+ : '';
2232
+ // 이름이 일치하는지 확인 (전체 이름 또는 짧은 이름)
2233
+ const nameMatches = constName === constantName || constFullName === value;
2234
+ if (nameMatches) {
2235
+ // namespace가 지정되었으면 namespace도 일치해야 함
2236
+ if (namespace && constNamespace && constNamespace !== namespace) {
2237
+ continue;
2238
+ }
2239
+ // 상수 값이 숫자인 경우 반환
2240
+ if (typeof constant.value === 'number') {
2241
+ return constant.value;
2242
+ }
2243
+ // 상수 값이 문자열인데 숫자로 파싱 가능한 경우
2244
+ if (typeof constant.value === 'string') {
2245
+ const parsed = parseFloat(constant.value);
2246
+ if (!isNaN(parsed)) {
2247
+ return parsed;
2248
+ }
2249
+ }
2250
+ }
2251
+ }
2252
+ // 찾지 못한 경우 로그 출력 (디버깅용)
2253
+ console.warn(`[DeukPack] Could not resolve constant: ${value}`);
2254
+ return null;
2255
+ }
2256
+ generateTypedef(typedef) {
2257
+ const lines = [];
2258
+ // IDL typedef → C# using alias (C# doesn't support typedef)
2259
+ // Map primitive/schema types to C# types
2260
+ const csharpTypeMap = {
2261
+ 'long': 'System.Int64',
2262
+ 'int64': 'System.Int64',
2263
+ 'int32': 'System.Int32',
2264
+ 'int16': 'System.Int16',
2265
+ 'int8': 'System.SByte',
2266
+ 'string': 'System.String',
2267
+ 'int': 'System.Int32',
2268
+ 'bool': 'System.Boolean',
2269
+ 'byte': 'System.Byte',
2270
+ 'float': 'System.Single',
2271
+ 'double': 'System.Double',
2272
+ 'binary': 'byte[]'
2273
+ };
2274
+ let csharpType;
2275
+ if (typeof typedef.type === 'string') {
2276
+ csharpType = csharpTypeMap[typedef.type] || this.getCSharpType(typedef.type);
2277
+ }
2278
+ else {
2279
+ csharpType = this.getCSharpType(typedef.type);
2280
+ }
2281
+ // Generate using alias at namespace start (C# requirement)
2282
+ lines.push(` using ${typedef.name} = ${csharpType};`);
2283
+ return lines;
2284
+ }
2285
+ generateConstant(constant, ast) {
2286
+ const lines = [];
2287
+ const csharpType = this.getCSharpType(constant.type, ast);
2288
+ const value = this.getCSharpDefaultValue(constant.value, constant.type, ast);
2289
+ // C#에서 Dictionary, List, HashSet 같은 참조 타입은 const로 선언할 수 없으므로 readonly로 변경
2290
+ const isReferenceType = typeof constant.type === 'object' && constant.type !== null &&
2291
+ (constant.type.type === 'map' || constant.type.type === 'list' || constant.type.type === 'set') ||
2292
+ (typeof csharpType === 'string' && (csharpType.startsWith('Dictionary') || csharpType.startsWith('List') || csharpType.startsWith('HashSet')));
2293
+ if (isReferenceType) {
2294
+ lines.push(` public static readonly ${csharpType} ${constant.name} = ${value};`);
2295
+ }
2296
+ else {
2297
+ lines.push(` public const ${csharpType} ${constant.name} = ${value};`);
2298
+ }
2299
+ return lines;
2300
+ }
2301
+ }
2302
+ exports.CSharpGenerator = CSharpGenerator;
2303
+ //# sourceMappingURL=CSharpGenerator.js.map