c-next 0.1.30 → 0.1.31

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.
package/grammar/CNext.g4 CHANGED
@@ -84,6 +84,7 @@ scopeMember
84
84
  | visibilityModifier? enumDeclaration
85
85
  | visibilityModifier? bitmapDeclaration
86
86
  | visibilityModifier? registerDeclaration
87
+ | visibilityModifier? structDeclaration
87
88
  ;
88
89
 
89
90
  visibilityModifier
@@ -193,6 +194,12 @@ arrayDimension
193
194
  // ----------------------------------------------------------------------------
194
195
  variableDeclaration
195
196
  : atomicModifier? volatileModifier? constModifier? overflowModifier? type IDENTIFIER arrayDimension* ('<-' expression)? ';'
197
+ | type IDENTIFIER '(' constructorArgumentList ')' ';' // Issue #375: C++ constructor
198
+ ;
199
+
200
+ // Issue #375: Constructor argument list - identifiers only (must be const variables)
201
+ constructorArgumentList
202
+ : IDENTIFIER (',' IDENTIFIER)*
196
203
  ;
197
204
 
198
205
  // ----------------------------------------------------------------------------
@@ -264,7 +271,8 @@ thisMemberAccess
264
271
 
265
272
  // ADR-016: this.member[idx] or this.member.member[idx] for scope-local array/bit access
266
273
  thisArrayAccess
267
- : 'this' '.' IDENTIFIER '[' expression ']' // this.arr[i]
274
+ : 'this' '.' IDENTIFIER '[' expression ']' ('.' IDENTIFIER)+ // this.arr[i].field.field2...
275
+ | 'this' '.' IDENTIFIER '[' expression ']' // this.arr[i]
268
276
  | 'this' '.' IDENTIFIER '[' expression ',' expression ']' // this.reg[offset, width]
269
277
  | 'this' '.' IDENTIFIER ('.' IDENTIFIER)+ '[' expression ']' // this.GPIO7.DR_SET[i]
270
278
  | 'this' '.' IDENTIFIER ('.' IDENTIFIER)+ '[' expression ',' expression ']' // this.GPIO7.ICR1[6, 2]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -37,6 +37,8 @@
37
37
  "coverage:grammar:console": "tsx scripts/grammar-coverage.ts console",
38
38
  "unit": "vitest run",
39
39
  "unit:watch": "vitest",
40
+ "unit:coverage": "vitest run --coverage",
41
+ "unit:coverage:html": "vitest run --coverage && echo 'Coverage report: coverage/index.html'",
40
42
  "test:all": "npm run unit && npm run test:q"
41
43
  },
42
44
  "keywords": [
@@ -68,6 +70,7 @@
68
70
  ],
69
71
  "devDependencies": {
70
72
  "@types/node": "^25.0.3",
73
+ "@vitest/coverage-v8": "^3.2.4",
71
74
  "antlr4ng-cli": "^2.0.0",
72
75
  "husky": "^9.1.7",
73
76
  "lint-staged": "^16.2.7",
@@ -1665,6 +1665,10 @@ export default class CodeGenerator implements IOrchestrator {
1665
1665
  const scopeDecl = decl.scopeDeclaration()!;
1666
1666
  const scopeName = scopeDecl.IDENTIFIER().getText();
1667
1667
 
1668
+ // Set scope context for scoped type resolution (this.Type)
1669
+ const savedScope = this.context.currentScope;
1670
+ this.context.currentScope = scopeName;
1671
+
1668
1672
  for (const member of scopeDecl.scopeMember()) {
1669
1673
  if (member.functionDeclaration()) {
1670
1674
  const funcDecl = member.functionDeclaration()!;
@@ -1682,6 +1686,9 @@ export default class CodeGenerator implements IOrchestrator {
1682
1686
  this.registerCallbackType(fullName, funcDecl);
1683
1687
  }
1684
1688
  }
1689
+
1690
+ // Restore previous scope context
1691
+ this.context.currentScope = savedScope;
1685
1692
  }
1686
1693
 
1687
1694
  // ADR-029: Track callback field types in structs
@@ -3173,12 +3180,24 @@ export default class CodeGenerator implements IOrchestrator {
3173
3180
  // ADR-029: Check if this is a callback type
3174
3181
  isCallback = this.callbackTypes.has(typeName);
3175
3182
  } else if (typeCtx.qualifiedType()) {
3176
- // ADR-016: Handle qualified enum types like Scope.EnumType
3183
+ // ADR-016: Handle qualified types like Scope.Type
3177
3184
  typeName = typeCtx
3178
3185
  .qualifiedType()!
3179
3186
  .IDENTIFIER()
3180
3187
  .map((id) => id.getText())
3181
3188
  .join("_");
3189
+ // Check if this is a struct type
3190
+ isStruct = this.isStructType(typeName);
3191
+ } else if (typeCtx.scopedType()) {
3192
+ // ADR-016: Handle scoped types like this.Type (inside a scope)
3193
+ const localTypeName = typeCtx.scopedType()!.IDENTIFIER().getText();
3194
+ if (this.context.currentScope) {
3195
+ typeName = `${this.context.currentScope}_${localTypeName}`;
3196
+ } else {
3197
+ typeName = localTypeName;
3198
+ }
3199
+ // Check if this is a struct type
3200
+ isStruct = this.isStructType(typeName);
3182
3201
  } else if (typeCtx.stringType()) {
3183
3202
  // ADR-045: String parameter
3184
3203
  isString = true;
@@ -5232,6 +5251,12 @@ export default class CodeGenerator implements IOrchestrator {
5232
5251
  // ========================================================================
5233
5252
 
5234
5253
  private generateVariableDecl(ctx: Parser.VariableDeclarationContext): string {
5254
+ // Issue #375: Check for C++ constructor syntax
5255
+ const constructorArgList = ctx.constructorArgumentList();
5256
+ if (constructorArgList) {
5257
+ return this._generateConstructorDecl(ctx, constructorArgList);
5258
+ }
5259
+
5235
5260
  const constMod = ctx.constModifier() ? "const " : "";
5236
5261
  // ADR-049: Add volatile for atomic variables
5237
5262
  const atomicMod = ctx.atomicModifier() ? "volatile " : "";
@@ -5647,6 +5672,75 @@ export default class CodeGenerator implements IOrchestrator {
5647
5672
  return decl + ";";
5648
5673
  }
5649
5674
 
5675
+ /**
5676
+ * Issue #375: Generate C++ constructor-style declaration
5677
+ * Validates that all arguments are const variables.
5678
+ * Example: `Adafruit_MAX31856 thermocouple(pinConst);` -> `Adafruit_MAX31856 thermocouple(pinConst);`
5679
+ */
5680
+ private _generateConstructorDecl(
5681
+ ctx: Parser.VariableDeclarationContext,
5682
+ argListCtx: Parser.ConstructorArgumentListContext,
5683
+ ): string {
5684
+ const type = this._generateType(ctx.type());
5685
+ const name = ctx.IDENTIFIER().getText();
5686
+ const line = ctx.start?.line ?? 0;
5687
+
5688
+ // Collect and validate all arguments
5689
+ const argIdentifiers = argListCtx.IDENTIFIER();
5690
+ const resolvedArgs: string[] = [];
5691
+
5692
+ for (const argNode of argIdentifiers) {
5693
+ const argName = argNode.getText();
5694
+
5695
+ // Check if it exists in type registry
5696
+ const typeInfo = this.context.typeRegistry.get(argName);
5697
+
5698
+ // Also check scoped variables if inside a scope
5699
+ let scopedArgName = argName;
5700
+ let scopedTypeInfo = typeInfo;
5701
+ if (!typeInfo && this.context.currentScope) {
5702
+ scopedArgName = `${this.context.currentScope}_${argName}`;
5703
+ scopedTypeInfo = this.context.typeRegistry.get(scopedArgName);
5704
+ }
5705
+
5706
+ if (!typeInfo && !scopedTypeInfo) {
5707
+ throw new Error(
5708
+ `Error at line ${line}: Constructor argument '${argName}' is not declared`,
5709
+ );
5710
+ }
5711
+
5712
+ const finalTypeInfo = typeInfo ?? scopedTypeInfo!;
5713
+ const finalArgName = typeInfo ? argName : scopedArgName;
5714
+
5715
+ // Check if it's const
5716
+ if (!finalTypeInfo.isConst) {
5717
+ throw new Error(
5718
+ `Error at line ${line}: Constructor argument '${argName}' must be const. ` +
5719
+ `C++ constructors in C-Next only accept const variables.`,
5720
+ );
5721
+ }
5722
+
5723
+ resolvedArgs.push(finalArgName);
5724
+ }
5725
+
5726
+ // Track the variable in type registry (as an external C++ type)
5727
+ this.context.typeRegistry.set(name, {
5728
+ baseType: type,
5729
+ bitWidth: 0, // Unknown for C++ types
5730
+ isArray: false,
5731
+ arrayDimensions: [],
5732
+ isConst: false,
5733
+ isExternalCppType: true,
5734
+ });
5735
+
5736
+ // Track as local variable if inside function body
5737
+ if (this.context.inFunctionBody) {
5738
+ this.context.localVariables.add(name);
5739
+ }
5740
+
5741
+ return `${type} ${name}(${resolvedArgs.join(", ")});`;
5742
+ }
5743
+
5650
5744
  /**
5651
5745
  * ADR-015: Get the appropriate zero initializer for a type
5652
5746
  * ADR-017: Handle enum types by initializing to first member
@@ -5660,6 +5754,64 @@ export default class CodeGenerator implements IOrchestrator {
5660
5754
  return "{0}";
5661
5755
  }
5662
5756
 
5757
+ // ADR-016: Check for scoped types (this.Type)
5758
+ // These are always struct/enum types defined in a scope
5759
+ if (typeCtx.scopedType()) {
5760
+ const localTypeName = typeCtx.scopedType()!.IDENTIFIER().getText();
5761
+ const fullTypeName = this.context.currentScope
5762
+ ? `${this.context.currentScope}_${localTypeName}`
5763
+ : localTypeName;
5764
+
5765
+ // Check if it's an enum
5766
+ if (this.symbols!.knownEnums.has(fullTypeName)) {
5767
+ const members = this.symbols!.enumMembers.get(fullTypeName);
5768
+ if (members) {
5769
+ for (const [memberName, value] of members.entries()) {
5770
+ if (value === 0) {
5771
+ return `${fullTypeName}_${memberName}`;
5772
+ }
5773
+ }
5774
+ const firstMember = members.keys().next().value;
5775
+ if (firstMember) {
5776
+ return `${fullTypeName}_${firstMember}`;
5777
+ }
5778
+ }
5779
+ return `(${fullTypeName})0`;
5780
+ }
5781
+
5782
+ // Otherwise it's a struct - use {0}
5783
+ return "{0}";
5784
+ }
5785
+
5786
+ // ADR-016: Check for qualified types (Scope.Type)
5787
+ if (typeCtx.qualifiedType()) {
5788
+ const qualifiedCtx = typeCtx.qualifiedType()!;
5789
+ const parts = qualifiedCtx.IDENTIFIER();
5790
+ const scopeName = parts[0].getText();
5791
+ const localTypeName = parts[1].getText();
5792
+ const fullTypeName = `${scopeName}_${localTypeName}`;
5793
+
5794
+ // Check if it's an enum
5795
+ if (this.symbols!.knownEnums.has(fullTypeName)) {
5796
+ const members = this.symbols!.enumMembers.get(fullTypeName);
5797
+ if (members) {
5798
+ for (const [memberName, value] of members.entries()) {
5799
+ if (value === 0) {
5800
+ return `${fullTypeName}_${memberName}`;
5801
+ }
5802
+ }
5803
+ const firstMember = members.keys().next().value;
5804
+ if (firstMember) {
5805
+ return `${fullTypeName}_${firstMember}`;
5806
+ }
5807
+ }
5808
+ return `(${fullTypeName})0`;
5809
+ }
5810
+
5811
+ // Otherwise it's a struct - use {0}
5812
+ return "{0}";
5813
+ }
5814
+
5663
5815
  // Check for user-defined types (structs/classes/enums)
5664
5816
  if (typeCtx.userType()) {
5665
5817
  const typeName = typeCtx.userType()!.getText();
@@ -6839,6 +6991,18 @@ export default class CodeGenerator implements IOrchestrator {
6839
6991
  if (parts.length === 1) {
6840
6992
  return `${scopeName}_${parts[0]}[${expr}] ${cOp} ${value};`;
6841
6993
  }
6994
+
6995
+ // Check if this is array-then-field (this.arr[i].field) vs field-then-array (this.a.b[i])
6996
+ // by examining the context text - if '[' comes right after first identifier, it's array-first
6997
+ const ctxText = thisArrayAccessCtx.getText();
6998
+ const firstIdEnd = ctxText.indexOf(parts[0]) + parts[0].length;
6999
+
7000
+ if (ctxText.charAt(firstIdEnd) === "[") {
7001
+ // Array access comes first: this.buffer[i].field -> Scope_buffer[i].field
7002
+ return `${scopeName}_${parts[0]}[${expr}].${parts.slice(1).join(".")} ${cOp} ${value};`;
7003
+ }
7004
+
7005
+ // Field access comes first: this.GPIO7.DR_SET[i] -> Scope_GPIO7.DR_SET[i]
6842
7006
  return `${scopeName}_${parts[0]}.${parts.slice(1).join(".")}[${expr}] ${cOp} ${value};`;
6843
7007
  }
6844
7008
  }
@@ -7530,6 +7694,19 @@ export default class CodeGenerator implements IOrchestrator {
7530
7694
  // ADR-016: Validate visibility before allowing cross-scope access
7531
7695
  const memberName = parts[1];
7532
7696
  this.validateCrossScopeVisibility(firstId, memberName);
7697
+
7698
+ // Check if the scope variable is a struct type - if so, remaining parts
7699
+ // are struct field access and should use '.' not '_'
7700
+ // e.g., global.Motor.current.speed -> Motor_current.speed
7701
+ if (parts.length > 2) {
7702
+ const scopeVarName = `${firstId}_${memberName}`;
7703
+ const scopeVarType = this.context.typeRegistry.get(scopeVarName);
7704
+ if (scopeVarType && this._isKnownStruct(scopeVarType.baseType)) {
7705
+ // Scope variable is a struct - use '.' for field access
7706
+ return `${scopeVarName}.${parts.slice(2).join(".")}`;
7707
+ }
7708
+ }
7709
+
7533
7710
  // Issue #304: Use :: for C++ namespaces, _ for C-Next scopes
7534
7711
  return parts.join(this.getScopeSeparator(isCppAccess));
7535
7712
  }
@@ -7655,6 +7832,19 @@ export default class CodeGenerator implements IOrchestrator {
7655
7832
  if (parts.length === 1) {
7656
7833
  return `${scopeName}_${parts[0]}[${expr}]`;
7657
7834
  }
7835
+
7836
+ // Check if this is array-then-field (this.arr[i].field) vs field-then-array (this.a.b[i])
7837
+ // by examining the context text - if '[' comes right after first identifier, it's array-first
7838
+ const ctxText = ctx.getText();
7839
+ const firstIdEnd = ctxText.indexOf(parts[0]) + parts[0].length;
7840
+ const bracketPos = ctxText.indexOf("[");
7841
+
7842
+ if (bracketPos === firstIdEnd + 1 || ctxText.charAt(firstIdEnd) === "[") {
7843
+ // Array access comes first: this.buffer[i].field -> Scope_buffer[i].field
7844
+ return `${scopeName}_${parts[0]}[${expr}].${parts.slice(1).join(".")}`;
7845
+ }
7846
+
7847
+ // Field access comes first: this.GPIO7.DR_SET[i] -> Scope_GPIO7.DR_SET[i]
7658
7848
  return `${scopeName}_${parts[0]}.${parts.slice(1).join(".")}[${expr}]`;
7659
7849
  }
7660
7850
 
@@ -9589,6 +9779,18 @@ export default class CodeGenerator implements IOrchestrator {
9589
9779
  // ADR-016: Validate visibility before allowing cross-scope access
9590
9780
  const memberName = parts[1];
9591
9781
  this.validateCrossScopeVisibility(firstPart, memberName);
9782
+
9783
+ // Check if the scope variable is a struct type - if so, remaining parts
9784
+ // are struct field access and should use '.' not '_'
9785
+ // e.g., Motor.current.speed -> Motor_current.speed (not Motor_current_speed)
9786
+ if (parts.length > 2) {
9787
+ const scopeVarName = `${firstPart}_${memberName}`;
9788
+ const scopeVarType = this.context.typeRegistry.get(scopeVarName);
9789
+ if (scopeVarType && this._isKnownStruct(scopeVarType.baseType)) {
9790
+ // Scope variable is a struct - use '.' for field access
9791
+ return `${scopeVarName}.${parts.slice(2).join(".")}`;
9792
+ }
9793
+ }
9592
9794
  return parts.join("_");
9593
9795
  }
9594
9796
 
@@ -9737,6 +9939,10 @@ export default class CodeGenerator implements IOrchestrator {
9737
9939
  const identifiers = ctx.qualifiedType()!.IDENTIFIER();
9738
9940
  const scopeName = identifiers[0].getText();
9739
9941
  const typeName = identifiers[1].getText();
9942
+
9943
+ // Check visibility for scoped types (structs, enums, bitmaps)
9944
+ this._validateCrossScopeVisibility(scopeName, typeName);
9945
+
9740
9946
  return `${scopeName}_${typeName}`;
9741
9947
  }
9742
9948
  if (ctx.userType()) {
@@ -240,7 +240,7 @@ class SymbolCollector {
240
240
  if (decl.bitmapDeclaration()) {
241
241
  this.collectBitmap(decl.bitmapDeclaration()!);
242
242
  }
243
- // Also collect bitmaps inside scopes first
243
+ // Also collect bitmaps and structs inside scopes first
244
244
  if (decl.scopeDeclaration()) {
245
245
  const scopeDecl = decl.scopeDeclaration()!;
246
246
  const scopeName = scopeDecl.IDENTIFIER().getText();
@@ -248,6 +248,10 @@ class SymbolCollector {
248
248
  if (member.bitmapDeclaration()) {
249
249
  this.collectBitmap(member.bitmapDeclaration()!, scopeName);
250
250
  }
251
+ // Collect scoped structs early so they're available as types
252
+ if (member.structDeclaration()) {
253
+ this.collectStruct(member.structDeclaration()!, scopeName);
254
+ }
251
255
  }
252
256
  }
253
257
  }
@@ -378,6 +382,17 @@ class SymbolCollector {
378
382
  }
379
383
  }
380
384
  }
385
+
386
+ // Handle struct declarations inside scopes
387
+ // Note: Struct collection happens in first pass for early availability,
388
+ // but we still need to add to scope members and visibility here
389
+ if (member.structDeclaration()) {
390
+ const structDecl = member.structDeclaration()!;
391
+ const structName = structDecl.IDENTIFIER().getText();
392
+ members.add(structName);
393
+ memberVisibility.set(structName, visibility);
394
+ // Note: collectStruct already called in first pass
395
+ }
381
396
  }
382
397
 
383
398
  this._scopeMembers.set(name, members);
@@ -523,10 +538,16 @@ class SymbolCollector {
523
538
 
524
539
  /**
525
540
  * Collect struct declarations and track field types
541
+ * @param structDecl The struct declaration context
542
+ * @param scopeName Optional scope name for scoped struct declarations
526
543
  */
527
- private collectStruct(structDecl: Parser.StructDeclarationContext): void {
544
+ private collectStruct(
545
+ structDecl: Parser.StructDeclarationContext,
546
+ scopeName?: string,
547
+ ): void {
528
548
  const name = structDecl.IDENTIFIER().getText();
529
- this._knownStructs.add(name);
549
+ const fullName = scopeName ? `${scopeName}_${name}` : name;
550
+ this._knownStructs.add(fullName);
530
551
 
531
552
  const fields = new Map<string, string>();
532
553
  const arrayFields = new Set<string>();
@@ -584,9 +605,9 @@ class SymbolCollector {
584
605
  }
585
606
  }
586
607
 
587
- this._structFields.set(name, fields);
588
- this._structFieldArrays.set(name, arrayFields);
589
- this._structFieldDimensions.set(name, fieldDimensions);
608
+ this._structFields.set(fullName, fields);
609
+ this._structFieldArrays.set(fullName, arrayFields);
610
+ this._structFieldDimensions.set(fullName, fieldDimensions);
590
611
  }
591
612
 
592
613
  /**
@@ -53,6 +53,39 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
53
53
  const varDecl = member.variableDeclaration()!;
54
54
  const varName = varDecl.IDENTIFIER().getText();
55
55
 
56
+ // Issue #375: Check for constructor syntax
57
+ const constructorArgList = varDecl.constructorArgumentList();
58
+ if (constructorArgList) {
59
+ // ADR-016: All scope variables are emitted at file scope
60
+ const type = orchestrator.generateType(varDecl.type());
61
+ const fullName = `${name}_${varName}`;
62
+ const prefix = isPrivate ? "static " : "";
63
+
64
+ // Validate and resolve constructor arguments
65
+ const argIdentifiers = constructorArgList.IDENTIFIER();
66
+ const resolvedArgs: string[] = [];
67
+ const line = varDecl.start?.line ?? 0;
68
+
69
+ for (const argNode of argIdentifiers) {
70
+ const argName = argNode.getText();
71
+ // Arguments must be resolved with scope prefix
72
+ const scopedArgName = `${name}_${argName}`;
73
+
74
+ // Check if it's const using orchestrator
75
+ if (!orchestrator.isConstValue(scopedArgName)) {
76
+ throw new Error(
77
+ `Error at line ${line}: Constructor argument '${argName}' must be const. ` +
78
+ `C++ constructors in C-Next only accept const variables.`,
79
+ );
80
+ }
81
+
82
+ resolvedArgs.push(scopedArgName);
83
+ }
84
+
85
+ lines.push(`${prefix}${type} ${fullName}(${resolvedArgs.join(", ")});`);
86
+ continue;
87
+ }
88
+
56
89
  // Issue #282: Check if this is a const variable - const values should be inlined
57
90
  const isConst = varDecl.constModifier() !== null;
58
91
 
@@ -170,6 +203,16 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
170
203
  );
171
204
  lines.push(result.code);
172
205
  }
206
+
207
+ // Handle struct declarations inside scopes
208
+ // Issue #369: Skip struct definition if self-include was added (it will be in the header)
209
+ if (member.structDeclaration() && !state.selfIncludeAdded) {
210
+ const structDecl = member.structDeclaration()!;
211
+ lines.push("");
212
+ lines.push(
213
+ generateScopedStructInline(structDecl, name, input, orchestrator),
214
+ );
215
+ }
173
216
  }
174
217
 
175
218
  lines.push("");
@@ -320,4 +363,51 @@ function generateScopedBitmapInline(
320
363
  return lines.join("\n");
321
364
  }
322
365
 
366
+ /**
367
+ * Generate struct inside a scope with proper prefixing.
368
+ * Struct fields maintain their original types.
369
+ */
370
+ function generateScopedStructInline(
371
+ node: Parser.StructDeclarationContext,
372
+ scopeName: string,
373
+ input: IGeneratorInput,
374
+ orchestrator: IOrchestrator,
375
+ ): string {
376
+ const name = node.IDENTIFIER().getText();
377
+ const fullName = `${scopeName}_${name}`;
378
+
379
+ const lines: string[] = [];
380
+ lines.push(`typedef struct ${fullName} {`);
381
+
382
+ // Process struct members
383
+ for (const member of node.structMember()) {
384
+ const fieldName = member.IDENTIFIER().getText();
385
+ const fieldType = orchestrator.generateType(member.type());
386
+
387
+ // Handle array dimensions if present
388
+ const arrayDims = member.arrayDimension();
389
+ let dimStr = "";
390
+ if (arrayDims.length > 0) {
391
+ dimStr = orchestrator.generateArrayDimensions(arrayDims);
392
+ }
393
+
394
+ // Handle string capacity for string fields
395
+ if (member.type().stringType()) {
396
+ const stringCtx = member.type().stringType()!;
397
+ const intLiteral = stringCtx.INTEGER_LITERAL();
398
+ if (intLiteral) {
399
+ const capacity = parseInt(intLiteral.getText(), 10);
400
+ dimStr += `[${capacity + 1}]`;
401
+ }
402
+ }
403
+
404
+ lines.push(` ${fieldType} ${fieldName}${dimStr};`);
405
+ }
406
+
407
+ lines.push(`} ${fullName};`);
408
+ lines.push("");
409
+
410
+ return lines.join("\n");
411
+ }
412
+
323
413
  export default generateScope;
@@ -17,6 +17,7 @@ type TTypeInfo = {
17
17
  isString?: boolean;
18
18
  stringCapacity?: number;
19
19
  isAtomic?: boolean;
20
+ isExternalCppType?: boolean; // Issue #375: C++ types instantiated via constructor
20
21
  };
21
22
 
22
23
  export default TTypeInfo;