c-next 0.1.18 → 0.1.20
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/README.md
CHANGED
|
@@ -663,44 +663,45 @@ Decisions are documented in `/docs/decisions/`:
|
|
|
663
663
|
|
|
664
664
|
### Implemented
|
|
665
665
|
|
|
666
|
-
| ADR | Title | Description
|
|
667
|
-
| --------------------------------------------------------------------- | ----------------------- |
|
|
668
|
-
| [ADR-001](docs/decisions/adr-001-assignment-operator.md) | Assignment Operator | `<-` for assignment, `=` for comparison
|
|
669
|
-
| [ADR-003](docs/decisions/adr-003-static-allocation.md) | Static Allocation | No dynamic memory after init
|
|
670
|
-
| [ADR-004](docs/decisions/adr-004-register-bindings.md) | Register Bindings | Type-safe hardware access
|
|
671
|
-
| [ADR-006](docs/decisions/adr-006-simplified-references.md) | Simplified References | Pass by reference, no pointer syntax
|
|
672
|
-
| [ADR-007](docs/decisions/adr-007-type-aware-bit-indexing.md) | Type-Aware Bit Indexing | Integers as bit arrays, `.length` property
|
|
673
|
-
| [ADR-010](docs/decisions/adr-010-c-interoperability.md) | C Interoperability | Unified ANTLR parser architecture
|
|
674
|
-
| [ADR-011](docs/decisions/adr-011-vscode-extension.md) | VS Code Extension | Live C preview with syntax highlighting
|
|
675
|
-
| [ADR-012](docs/decisions/adr-012-static-analysis.md) | Static Analysis | cppcheck integration for generated C
|
|
676
|
-
| [ADR-013](docs/decisions/adr-013-const-qualifier.md) | Const Qualifier | Compile-time const enforcement
|
|
677
|
-
| [ADR-014](docs/decisions/adr-014-structs.md) | Structs | Data containers without methods
|
|
678
|
-
| [ADR-015](docs/decisions/adr-015-null-state.md) | Null State | Zero initialization for all variables
|
|
679
|
-
| [ADR-016](docs/decisions/adr-016-scope.md) | Scope | `this.`/`global.` explicit qualification
|
|
680
|
-
| [ADR-017](docs/decisions/adr-017-enums.md) | Enums | Type-safe enums with C-style casting
|
|
681
|
-
| [ADR-030](docs/decisions/adr-030-forward-declarations.md) | Define-Before-Use | Functions must be defined before called
|
|
682
|
-
| [ADR-037](docs/decisions/adr-037-preprocessor.md) | Preprocessor | Flag-only defines, const for values
|
|
683
|
-
| [ADR-043](docs/decisions/adr-043-comments.md) | Comments | Comment preservation with MISRA compliance
|
|
684
|
-
| [ADR-044](docs/decisions/adr-044-primitive-types.md) | Primitive Types | Fixed-width types with `clamp`/`wrap` overflow
|
|
685
|
-
| [ADR-024](docs/decisions/adr-024-type-casting.md) | Type Casting | Widening implicit, narrowing uses bit indexing
|
|
686
|
-
| [ADR-022](docs/decisions/adr-022-conditional-expressions.md) | Conditional Expressions | Ternary with required parens, boolean condition, no nesting
|
|
687
|
-
| [ADR-025](docs/decisions/adr-025-switch-statements.md) | Switch Statements | Safe switch with braces, `\|\|` syntax, counted `default(n)`
|
|
688
|
-
| [ADR-029](docs/decisions/adr-029-function-pointers.md) | Callbacks | Function-as-Type pattern with nominal typing
|
|
689
|
-
| [ADR-045](docs/decisions/adr-045-string-type.md) | Bounded Strings | `string<N>` with compile-time safety
|
|
690
|
-
| [ADR-023](docs/decisions/adr-023-sizeof.md) | Sizeof | Type/value size queries with safety checks
|
|
691
|
-
| [ADR-027](docs/decisions/adr-027-do-while.md) | Do-While | `do { } while ()` with boolean condition (E0701)
|
|
692
|
-
| [ADR-032](docs/decisions/adr-032-nested-structs.md) | Nested Structs | Named nested structs only (no anonymous)
|
|
693
|
-
| [ADR-035](docs/decisions/adr-035-array-initializers.md) | Array Initializers | `[1, 2, 3]` syntax with `[0*]` fill-all
|
|
694
|
-
| [ADR-036](docs/decisions/adr-036-multidimensional-arrays.md) | Multi-dim Arrays | `arr[i][j]` with compile-time bounds enforcement
|
|
695
|
-
| [ADR-040](docs/decisions/adr-040-isr-declaration.md) | ISR Type | Built-in `ISR` type for `void(void)` function pointers
|
|
696
|
-
| [ADR-034](docs/decisions/adr-034-bit-fields.md) | Bitmap Types | `bitmap8`/`bitmap16`/`bitmap32` for portable bit-packed data
|
|
697
|
-
| [ADR-048](docs/decisions/adr-048-cli-executable.md) | CLI Executable | `cnext` command with smart defaults
|
|
698
|
-
| [ADR-049](docs/decisions/adr-049-atomic-types.md) | Atomic Types | `atomic` keyword with LDREX/STREX or PRIMASK fallback
|
|
699
|
-
| [ADR-050](docs/decisions/adr-050-critical-sections.md) | Critical Sections | `critical { }` blocks with PRIMASK save/restore
|
|
700
|
-
| [ADR-108](docs/decisions/adr-108-volatile-keyword.md) | Volatile Variables | `volatile` keyword prevents compiler optimization
|
|
701
|
-
| [ADR-
|
|
702
|
-
| [ADR-
|
|
703
|
-
| [ADR-
|
|
666
|
+
| ADR | Title | Description |
|
|
667
|
+
| --------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------- |
|
|
668
|
+
| [ADR-001](docs/decisions/adr-001-assignment-operator.md) | Assignment Operator | `<-` for assignment, `=` for comparison |
|
|
669
|
+
| [ADR-003](docs/decisions/adr-003-static-allocation.md) | Static Allocation | No dynamic memory after init |
|
|
670
|
+
| [ADR-004](docs/decisions/adr-004-register-bindings.md) | Register Bindings | Type-safe hardware access |
|
|
671
|
+
| [ADR-006](docs/decisions/adr-006-simplified-references.md) | Simplified References | Pass by reference, no pointer syntax |
|
|
672
|
+
| [ADR-007](docs/decisions/adr-007-type-aware-bit-indexing.md) | Type-Aware Bit Indexing | Integers as bit arrays, `.length` property |
|
|
673
|
+
| [ADR-010](docs/decisions/adr-010-c-interoperability.md) | C Interoperability | Unified ANTLR parser architecture |
|
|
674
|
+
| [ADR-011](docs/decisions/adr-011-vscode-extension.md) | VS Code Extension | Live C preview with syntax highlighting |
|
|
675
|
+
| [ADR-012](docs/decisions/adr-012-static-analysis.md) | Static Analysis | cppcheck integration for generated C |
|
|
676
|
+
| [ADR-013](docs/decisions/adr-013-const-qualifier.md) | Const Qualifier | Compile-time const enforcement |
|
|
677
|
+
| [ADR-014](docs/decisions/adr-014-structs.md) | Structs | Data containers without methods |
|
|
678
|
+
| [ADR-015](docs/decisions/adr-015-null-state.md) | Null State | Zero initialization for all variables |
|
|
679
|
+
| [ADR-016](docs/decisions/adr-016-scope.md) | Scope | `this.`/`global.` explicit qualification |
|
|
680
|
+
| [ADR-017](docs/decisions/adr-017-enums.md) | Enums | Type-safe enums with C-style casting |
|
|
681
|
+
| [ADR-030](docs/decisions/adr-030-forward-declarations.md) | Define-Before-Use | Functions must be defined before called |
|
|
682
|
+
| [ADR-037](docs/decisions/adr-037-preprocessor.md) | Preprocessor | Flag-only defines, const for values |
|
|
683
|
+
| [ADR-043](docs/decisions/adr-043-comments.md) | Comments | Comment preservation with MISRA compliance |
|
|
684
|
+
| [ADR-044](docs/decisions/adr-044-primitive-types.md) | Primitive Types | Fixed-width types with `clamp`/`wrap` overflow |
|
|
685
|
+
| [ADR-024](docs/decisions/adr-024-type-casting.md) | Type Casting | Widening implicit, narrowing uses bit indexing |
|
|
686
|
+
| [ADR-022](docs/decisions/adr-022-conditional-expressions.md) | Conditional Expressions | Ternary with required parens, boolean condition, no nesting |
|
|
687
|
+
| [ADR-025](docs/decisions/adr-025-switch-statements.md) | Switch Statements | Safe switch with braces, `\|\|` syntax, counted `default(n)` |
|
|
688
|
+
| [ADR-029](docs/decisions/adr-029-function-pointers.md) | Callbacks | Function-as-Type pattern with nominal typing |
|
|
689
|
+
| [ADR-045](docs/decisions/adr-045-string-type.md) | Bounded Strings | `string<N>` with compile-time safety |
|
|
690
|
+
| [ADR-023](docs/decisions/adr-023-sizeof.md) | Sizeof | Type/value size queries with safety checks |
|
|
691
|
+
| [ADR-027](docs/decisions/adr-027-do-while.md) | Do-While | `do { } while ()` with boolean condition (E0701) |
|
|
692
|
+
| [ADR-032](docs/decisions/adr-032-nested-structs.md) | Nested Structs | Named nested structs only (no anonymous) |
|
|
693
|
+
| [ADR-035](docs/decisions/adr-035-array-initializers.md) | Array Initializers | `[1, 2, 3]` syntax with `[0*]` fill-all |
|
|
694
|
+
| [ADR-036](docs/decisions/adr-036-multidimensional-arrays.md) | Multi-dim Arrays | `arr[i][j]` with compile-time bounds enforcement |
|
|
695
|
+
| [ADR-040](docs/decisions/adr-040-isr-declaration.md) | ISR Type | Built-in `ISR` type for `void(void)` function pointers |
|
|
696
|
+
| [ADR-034](docs/decisions/adr-034-bit-fields.md) | Bitmap Types | `bitmap8`/`bitmap16`/`bitmap32` for portable bit-packed data |
|
|
697
|
+
| [ADR-048](docs/decisions/adr-048-cli-executable.md) | CLI Executable | `cnext` command with smart defaults |
|
|
698
|
+
| [ADR-049](docs/decisions/adr-049-atomic-types.md) | Atomic Types | `atomic` keyword with LDREX/STREX or PRIMASK fallback |
|
|
699
|
+
| [ADR-050](docs/decisions/adr-050-critical-sections.md) | Critical Sections | `critical { }` blocks with PRIMASK save/restore |
|
|
700
|
+
| [ADR-108](docs/decisions/adr-108-volatile-keyword.md) | Volatile Variables | `volatile` keyword prevents compiler optimization |
|
|
701
|
+
| [ADR-046](docs/decisions/adr-046-nullable-c-interop.md) | Nullable C Interop | `c_` prefix for nullable C pointer types (supersedes ADR-047) |
|
|
702
|
+
| [ADR-047](docs/decisions/adr-047-nullable-types.md) | NULL for C Interop | `NULL` keyword for C stream functions (superseded by ADR-046) |
|
|
703
|
+
| [ADR-052](docs/decisions/adr-052-safe-numeric-literal-generation.md) | Safe Numeric Literals | `type_MIN`/`type_MAX` constants + safe hex conversion |
|
|
704
|
+
| [ADR-053](docs/decisions/adr-053-transpiler-pipeline-architecture.md) | Transpiler Pipeline | Unified multi-pass pipeline with header symbol extraction |
|
|
704
705
|
|
|
705
706
|
### Research (v1 Roadmap)
|
|
706
707
|
|
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import ESourceLanguage from "../types/ESourceLanguage";
|
|
|
10
10
|
import ESymbolKind from "../types/ESymbolKind";
|
|
11
11
|
import CommentExtractor from "./CommentExtractor";
|
|
12
12
|
import CommentFormatter from "./CommentFormatter";
|
|
13
|
+
import IncludeDiscovery from "../lib/IncludeDiscovery";
|
|
13
14
|
import IComment from "./types/IComment";
|
|
14
15
|
import TYPE_WIDTH from "./types/TYPE_WIDTH";
|
|
15
16
|
import C_TYPE_WIDTH from "./types/C_TYPE_WIDTH";
|
|
@@ -1444,6 +1445,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1444
1445
|
// C-Next does NOT hardcode any libraries - all includes must be explicit
|
|
1445
1446
|
// ADR-043: Comments before first include become file-level comments
|
|
1446
1447
|
// ADR-010: Transform .cnx includes to .h, reject implementation files
|
|
1448
|
+
// E0504: Cache include paths for performance (computed once, used for all includes)
|
|
1449
|
+
const includePaths = this.sourcePath
|
|
1450
|
+
? IncludeDiscovery.discoverIncludePaths(this.sourcePath)
|
|
1451
|
+
: [];
|
|
1447
1452
|
for (const includeDir of tree.includeDirective()) {
|
|
1448
1453
|
const leadingComments = this.getLeadingComments(includeDir);
|
|
1449
1454
|
output.push(...this.formatLeadingComments(leadingComments));
|
|
@@ -1455,6 +1460,14 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1455
1460
|
lineNumber,
|
|
1456
1461
|
);
|
|
1457
1462
|
|
|
1463
|
+
// E0504: Check if a .cnx alternative exists for .h/.hpp includes
|
|
1464
|
+
this.typeValidator!.validateIncludeNoCnxAlternative(
|
|
1465
|
+
includeDir.getText(),
|
|
1466
|
+
lineNumber,
|
|
1467
|
+
this.sourcePath,
|
|
1468
|
+
includePaths,
|
|
1469
|
+
);
|
|
1470
|
+
|
|
1458
1471
|
const transformedInclude = this.transformIncludeDirective(
|
|
1459
1472
|
includeDir.getText(),
|
|
1460
1473
|
);
|
|
@@ -4012,6 +4025,64 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
4012
4025
|
return false;
|
|
4013
4026
|
}
|
|
4014
4027
|
|
|
4028
|
+
/**
|
|
4029
|
+
* Issue #308: Check if a member access expression is accessing an array member.
|
|
4030
|
+
* For example, result.data where data is a u8[6] array member.
|
|
4031
|
+
* When passing such expressions to functions, the array should naturally decay
|
|
4032
|
+
* to a pointer, so we should NOT add & operator.
|
|
4033
|
+
*
|
|
4034
|
+
* Note: Currently handles single-level member access only (e.g., result.data).
|
|
4035
|
+
* Nested access like outer.inner.data would require traversing the postfix chain
|
|
4036
|
+
* to resolve intermediate struct types. This is acceptable since issue #308
|
|
4037
|
+
* involves single-level access patterns.
|
|
4038
|
+
*
|
|
4039
|
+
* @param ctx - The expression context
|
|
4040
|
+
* @returns true if the expression is a member access to an array field
|
|
4041
|
+
*/
|
|
4042
|
+
private isMemberAccessToArray(ctx: Parser.ExpressionContext): boolean {
|
|
4043
|
+
const postfix = this.getPostfixExpression(ctx);
|
|
4044
|
+
if (!postfix) return false;
|
|
4045
|
+
|
|
4046
|
+
const ops = postfix.postfixOp();
|
|
4047
|
+
if (ops.length === 0) return false;
|
|
4048
|
+
|
|
4049
|
+
// Last operator must be member access (.identifier)
|
|
4050
|
+
const lastOp = ops[ops.length - 1];
|
|
4051
|
+
const memberName = lastOp.IDENTIFIER()?.getText();
|
|
4052
|
+
if (!memberName) return false;
|
|
4053
|
+
|
|
4054
|
+
// Get the base identifier to find the struct type
|
|
4055
|
+
const primary = postfix.primaryExpression();
|
|
4056
|
+
if (!primary) return false;
|
|
4057
|
+
const baseId = primary.IDENTIFIER()?.getText();
|
|
4058
|
+
if (!baseId) return false;
|
|
4059
|
+
|
|
4060
|
+
// Look up the struct type from either:
|
|
4061
|
+
// 1. Local variable: typeRegistry.get(baseId).baseType
|
|
4062
|
+
// 2. Parameter: currentParameters.get(baseId).baseType
|
|
4063
|
+
let structType: string | undefined;
|
|
4064
|
+
|
|
4065
|
+
const typeInfo = this.context.typeRegistry.get(baseId);
|
|
4066
|
+
if (typeInfo) {
|
|
4067
|
+
structType = typeInfo.baseType;
|
|
4068
|
+
} else {
|
|
4069
|
+
const paramInfo = this.context.currentParameters.get(baseId);
|
|
4070
|
+
if (paramInfo) {
|
|
4071
|
+
structType = paramInfo.baseType;
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
if (!structType) return false;
|
|
4076
|
+
|
|
4077
|
+
// Check if this struct member is an array
|
|
4078
|
+
const memberInfo = this.getMemberTypeInfo(structType, memberName);
|
|
4079
|
+
if (memberInfo?.isArray) {
|
|
4080
|
+
return true;
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
return false;
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4015
4086
|
/**
|
|
4016
4087
|
* Check if an expression is a simple literal (number, bool, etc.)
|
|
4017
4088
|
* Navigates: expression -> ternaryExpression -> orExpression -> ... -> primaryExpression -> literal
|
|
@@ -4172,6 +4243,13 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
4172
4243
|
// Check if it's a member access or array access (lvalue) - needs &
|
|
4173
4244
|
const lvalueType = this.getLvalueType(ctx);
|
|
4174
4245
|
if (lvalueType) {
|
|
4246
|
+
// Issue #308: If member access to an array, don't add & - arrays decay to pointers
|
|
4247
|
+
// For example: result.data where data is u8[6] should pass as result.data (decays to uint8_t*)
|
|
4248
|
+
// NOT &result.data (which gives uint8_t (*)[6] - wrong type)
|
|
4249
|
+
if (lvalueType === "member" && this.isMemberAccessToArray(ctx)) {
|
|
4250
|
+
return this._generateExpression(ctx);
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4175
4253
|
// Issue #251/#252: In C++ mode, struct member access may need temp variable
|
|
4176
4254
|
if (
|
|
4177
4255
|
lvalueType === "member" &&
|
|
@@ -5497,6 +5575,14 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5497
5575
|
return "{}";
|
|
5498
5576
|
}
|
|
5499
5577
|
|
|
5578
|
+
// Issue #309: In C++ mode, unknown user types (external libraries)
|
|
5579
|
+
// should use {} instead of {0} because they may have non-trivial
|
|
5580
|
+
// constructors. Known structs (C-Next or C headers) are POD types
|
|
5581
|
+
// where {0} works fine.
|
|
5582
|
+
if (this.cppMode && !this._isKnownStruct(typeName)) {
|
|
5583
|
+
return "{}";
|
|
5584
|
+
}
|
|
5585
|
+
|
|
5500
5586
|
return "{0}";
|
|
5501
5587
|
}
|
|
5502
5588
|
|
|
@@ -7913,7 +7999,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
7913
7999
|
currentIdentifier = memberName; // Track for .length lookups
|
|
7914
8000
|
isGlobalAccess = true; // Mark that we're in a global access chain
|
|
7915
8001
|
// Issue #304: Check if this is a C++ scope symbol (namespace, class, enum)
|
|
7916
|
-
|
|
8002
|
+
// Issue #314: In C++ mode, global.X.method() should always use :: syntax
|
|
8003
|
+
// because the programmer is explicitly requesting C++ static method access,
|
|
8004
|
+
// even for undeclared external symbols (e.g., Arduino's Serial class)
|
|
8005
|
+
if (this.isCppScopeSymbol(memberName) || this.cppMode) {
|
|
7917
8006
|
isCppAccessChain = true;
|
|
7918
8007
|
}
|
|
7919
8008
|
// Check if this first identifier is a register
|
|
@@ -8498,9 +8587,13 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
8498
8587
|
}
|
|
8499
8588
|
// Issue #304: C++ class/namespace static member access uses :: (e.g., CommandHandler::execute)
|
|
8500
8589
|
// Also handles nested namespaces (hw::nested::configure) by checking if result already contains ::
|
|
8590
|
+
// Issue #314: Also use :: for global.X.method() in C++ mode with undeclared external classes
|
|
8591
|
+
// (e.g., Arduino's Serial class) - the global. prefix explicitly requests C++ static access
|
|
8501
8592
|
else if (
|
|
8502
8593
|
isCppAccessChain &&
|
|
8503
|
-
(this.isCppScopeSymbol(result) ||
|
|
8594
|
+
(this.isCppScopeSymbol(result) ||
|
|
8595
|
+
result.includes("::") ||
|
|
8596
|
+
isGlobalAccess)
|
|
8504
8597
|
) {
|
|
8505
8598
|
result = `${result}::${memberName}`;
|
|
8506
8599
|
} else {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Extracted from CodeGenerator for better separation of concerns
|
|
4
4
|
* Issue #63: Validation logic separated for independent testing
|
|
5
5
|
*/
|
|
6
|
+
import { dirname, resolve, join } from "path";
|
|
6
7
|
import * as Parser from "../parser/grammar/CNextParser";
|
|
7
8
|
import SymbolCollector from "./SymbolCollector";
|
|
8
9
|
import SymbolTable from "../symbols/SymbolTable";
|
|
@@ -85,6 +86,75 @@ class TypeValidator {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/**
|
|
90
|
+
* E0504: Validate that a .cnx alternative doesn't exist for a .h/.hpp include
|
|
91
|
+
* This helps during codebase migration by alerting developers when they should
|
|
92
|
+
* be using the C-Next version of a file instead of the C header.
|
|
93
|
+
*
|
|
94
|
+
* @param includeText - The full #include directive text
|
|
95
|
+
* @param lineNumber - Line number for error reporting
|
|
96
|
+
* @param sourcePath - Path to the source file (for resolving relative includes)
|
|
97
|
+
* @param includePaths - Array of directories to search for includes
|
|
98
|
+
* @param fileExists - Function to check if a file exists (injectable for testing)
|
|
99
|
+
*/
|
|
100
|
+
validateIncludeNoCnxAlternative(
|
|
101
|
+
includeText: string,
|
|
102
|
+
lineNumber: number,
|
|
103
|
+
sourcePath: string | null,
|
|
104
|
+
includePaths: string[],
|
|
105
|
+
fileExists: (path: string) => boolean = (p) => require("fs").existsSync(p),
|
|
106
|
+
): void {
|
|
107
|
+
// Extract the file path from #include directive
|
|
108
|
+
const angleMatch = includeText.match(/#\s*include\s*<([^>]+)>/);
|
|
109
|
+
const quoteMatch = includeText.match(/#\s*include\s*"([^"]+)"/);
|
|
110
|
+
|
|
111
|
+
const includePath = angleMatch?.[1] || quoteMatch?.[1];
|
|
112
|
+
if (!includePath) {
|
|
113
|
+
return; // Malformed include, let other validation handle it
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Skip if already a .cnx include
|
|
117
|
+
if (includePath.endsWith(".cnx")) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Only check .h and .hpp files
|
|
122
|
+
const ext = includePath
|
|
123
|
+
.substring(includePath.lastIndexOf("."))
|
|
124
|
+
.toLowerCase();
|
|
125
|
+
if (ext !== ".h" && ext !== ".hpp") {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Build the .cnx alternative path
|
|
130
|
+
const cnxPath = includePath.replace(/\.(h|hpp)$/i, ".cnx");
|
|
131
|
+
|
|
132
|
+
if (quoteMatch) {
|
|
133
|
+
// Quoted include: resolve relative to source file's directory
|
|
134
|
+
if (sourcePath) {
|
|
135
|
+
const sourceDir = dirname(sourcePath);
|
|
136
|
+
const fullCnxPath = resolve(sourceDir, cnxPath);
|
|
137
|
+
if (fileExists(fullCnxPath)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`E0504: Found #include "${includePath}" but '${cnxPath}' exists at the same location.\n` +
|
|
140
|
+
` Use #include "${cnxPath}" instead to use the C-Next version. Line ${lineNumber}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} else if (angleMatch) {
|
|
145
|
+
// Angle bracket include: search through include paths
|
|
146
|
+
for (const searchDir of includePaths) {
|
|
147
|
+
const fullCnxPath = join(searchDir, cnxPath);
|
|
148
|
+
if (fileExists(fullCnxPath)) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`E0504: Found #include <${includePath}> but '${cnxPath}' exists at the same location.\n` +
|
|
151
|
+
` Use #include <${cnxPath}> instead to use the C-Next version. Line ${lineNumber}`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
88
158
|
// ========================================================================
|
|
89
159
|
// Bitmap Field Validation (ADR-034)
|
|
90
160
|
// ========================================================================
|
|
@@ -30,15 +30,6 @@ import generateScopedRegister from "./ScopedRegisterGenerator";
|
|
|
30
30
|
* - Visibility control (private -> static, public -> extern)
|
|
31
31
|
* - Organization without runtime overhead
|
|
32
32
|
*/
|
|
33
|
-
/**
|
|
34
|
-
* Issue #232: Information about a single-function variable
|
|
35
|
-
*/
|
|
36
|
-
interface ISingleFunctionVar {
|
|
37
|
-
varDecl: Parser.VariableDeclarationContext;
|
|
38
|
-
targetFunction: string;
|
|
39
|
-
isPrivate: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
33
|
const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
43
34
|
node: Parser.ScopeDeclarationContext,
|
|
44
35
|
input: IGeneratorInput,
|
|
@@ -53,10 +44,6 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
53
44
|
const lines: string[] = [];
|
|
54
45
|
lines.push(`/* Scope: ${name} */`);
|
|
55
46
|
|
|
56
|
-
// Issue #232: First pass - identify single-function variables
|
|
57
|
-
// Maps functionName -> list of variable declarations that are local to it
|
|
58
|
-
const localVarsForFunction = new Map<string, ISingleFunctionVar[]>();
|
|
59
|
-
|
|
60
47
|
for (const member of node.scopeMember()) {
|
|
61
48
|
const visibility = member.visibilityModifier()?.getText() || "private";
|
|
62
49
|
const isPrivate = visibility === "private";
|
|
@@ -66,39 +53,16 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
66
53
|
const varDecl = member.variableDeclaration()!;
|
|
67
54
|
const varName = varDecl.IDENTIFIER().getText();
|
|
68
55
|
|
|
69
|
-
// Issue #282: Check if this is a const variable - const values should be inlined
|
|
70
|
-
// not injected as local variables
|
|
56
|
+
// Issue #282: Check if this is a const variable - const values should be inlined
|
|
71
57
|
const isConst = varDecl.constModifier() !== null;
|
|
72
58
|
|
|
73
|
-
// Issue #232: Check if this PRIVATE variable is used in only one function
|
|
74
|
-
// PUBLIC variables must stay file-scope because they can be accessed from outside
|
|
75
|
-
// Issue #282: Skip const variables - they should be inlined, not made local
|
|
76
|
-
const targetFunc =
|
|
77
|
-
isPrivate && !isConst
|
|
78
|
-
? input.symbols?.getSingleFunctionForVariable(name, varName)
|
|
79
|
-
: null;
|
|
80
|
-
|
|
81
|
-
if (targetFunc) {
|
|
82
|
-
// Single-function private variable - emit as local, not file-scope
|
|
83
|
-
if (!localVarsForFunction.has(targetFunc)) {
|
|
84
|
-
localVarsForFunction.set(targetFunc, []);
|
|
85
|
-
}
|
|
86
|
-
localVarsForFunction.get(targetFunc)!.push({
|
|
87
|
-
varDecl,
|
|
88
|
-
targetFunction: targetFunc,
|
|
89
|
-
isPrivate,
|
|
90
|
-
});
|
|
91
|
-
// Don't emit file-scope declaration
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
59
|
// Issue #282: Private const variables should be inlined, not emitted at file scope
|
|
96
60
|
// The inlining happens in CodeGenerator when resolving this.CONST_NAME
|
|
97
61
|
if (isPrivate && isConst) {
|
|
98
62
|
continue;
|
|
99
63
|
}
|
|
100
64
|
|
|
101
|
-
//
|
|
65
|
+
// ADR-016: All scope variables are emitted at file scope (static-like persistence)
|
|
102
66
|
const type = orchestrator.generateType(varDecl.type());
|
|
103
67
|
const fullName = `${name}_${varName}`;
|
|
104
68
|
// Issue #282: Add 'const' modifier for const variables
|
|
@@ -149,7 +113,7 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
149
113
|
|
|
150
114
|
// Issue #281: Generate body FIRST to track parameter modifications,
|
|
151
115
|
// then generate parameter list using that tracking info
|
|
152
|
-
|
|
116
|
+
const body = orchestrator.generateBlock(funcDecl.block());
|
|
153
117
|
|
|
154
118
|
// Issue #281: Update symbol's parameter info with auto-const before generating params
|
|
155
119
|
orchestrator.updateFunctionParamsAutoConst(fullName);
|
|
@@ -159,52 +123,6 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
159
123
|
? orchestrator.generateParameterList(funcDecl.parameterList()!)
|
|
160
124
|
: "void";
|
|
161
125
|
|
|
162
|
-
// Issue #232: Inject local variable declarations for single-function vars
|
|
163
|
-
const localVars = localVarsForFunction.get(funcName);
|
|
164
|
-
if (localVars && localVars.length > 0) {
|
|
165
|
-
// Generate local variable declarations
|
|
166
|
-
const localDecls: string[] = [];
|
|
167
|
-
for (const { varDecl } of localVars) {
|
|
168
|
-
const varType = orchestrator.generateType(varDecl.type());
|
|
169
|
-
const varName = varDecl.IDENTIFIER().getText();
|
|
170
|
-
const varFullName = `${name}_${varName}`;
|
|
171
|
-
|
|
172
|
-
const arrayDims = varDecl.arrayDimension();
|
|
173
|
-
const isArray = arrayDims.length > 0;
|
|
174
|
-
let decl = ` ${varType} ${varFullName}`;
|
|
175
|
-
if (isArray) {
|
|
176
|
-
decl += orchestrator.generateArrayDimensions(arrayDims);
|
|
177
|
-
}
|
|
178
|
-
// ADR-045: Add string capacity dimension for string arrays
|
|
179
|
-
if (varDecl.type().stringType()) {
|
|
180
|
-
const stringCtx = varDecl.type().stringType()!;
|
|
181
|
-
const intLiteral = stringCtx.INTEGER_LITERAL();
|
|
182
|
-
if (intLiteral) {
|
|
183
|
-
const capacity = parseInt(intLiteral.getText(), 10);
|
|
184
|
-
decl += `[${capacity + 1}]`;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (varDecl.expression()) {
|
|
188
|
-
decl += ` = ${orchestrator.generateExpression(varDecl.expression()!)}`;
|
|
189
|
-
} else {
|
|
190
|
-
decl += ` = ${orchestrator.getZeroInitializer(varDecl.type(), isArray)}`;
|
|
191
|
-
}
|
|
192
|
-
localDecls.push(decl + ";");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Inject after opening brace: { -> {\n local_var = init;
|
|
196
|
-
// Body format is typically "{\n ...statements...\n}"
|
|
197
|
-
const openBraceIdx = body.indexOf("{");
|
|
198
|
-
if (openBraceIdx !== -1) {
|
|
199
|
-
const afterBrace = body.substring(openBraceIdx + 1);
|
|
200
|
-
body =
|
|
201
|
-
body.substring(0, openBraceIdx + 1) +
|
|
202
|
-
"\n" +
|
|
203
|
-
localDecls.join("\n") +
|
|
204
|
-
afterBrace;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
126
|
// ADR-016: Exit function body context
|
|
209
127
|
orchestrator.exitFunctionBody();
|
|
210
128
|
orchestrator.setCurrentFunctionName(null); // Issue #269: Clear function name
|
|
@@ -16,6 +16,7 @@ import TGeneratorEffect from "../TGeneratorEffect";
|
|
|
16
16
|
import IGeneratorInput from "../IGeneratorInput";
|
|
17
17
|
import IGeneratorState from "../IGeneratorState";
|
|
18
18
|
import IOrchestrator from "../IOrchestrator";
|
|
19
|
+
import ESymbolKind from "../../../types/ESymbolKind";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Issue #304: Map C-Next type to C type for static_cast.
|
|
@@ -38,6 +39,14 @@ const mapTypeToCType = (cnxType: string): string => {
|
|
|
38
39
|
return TYPE_MAP[cnxType] || cnxType;
|
|
39
40
|
};
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Issue #315: Small primitive types that are always passed by value.
|
|
44
|
+
* These match the types used in Issue #269 for pass-by-value optimization.
|
|
45
|
+
* For cross-file function calls, we use these types directly since we can't
|
|
46
|
+
* know if the parameter is modified (that info is only in the source file).
|
|
47
|
+
*/
|
|
48
|
+
const SMALL_PRIMITIVE_TYPES = new Set(["u8", "u16", "i8", "i16", "bool"]);
|
|
49
|
+
|
|
41
50
|
/**
|
|
42
51
|
* Issue #304: Wrap argument with static_cast if it's a C++ enum class
|
|
43
52
|
* being passed to an integer parameter.
|
|
@@ -115,7 +124,27 @@ const generateFunctionCall = (
|
|
|
115
124
|
.map((e, idx) => {
|
|
116
125
|
// Get function signature for parameter type info
|
|
117
126
|
const sig = input.functionSignatures.get(funcExpr);
|
|
118
|
-
|
|
127
|
+
let targetParam = sig?.parameters[idx];
|
|
128
|
+
// Issue #315: Track if we got param info from SymbolTable (cross-file function)
|
|
129
|
+
let isCrossFileFunction = false;
|
|
130
|
+
|
|
131
|
+
// Issue #315: If no local signature, try SymbolTable for cross-file functions
|
|
132
|
+
if (!targetParam && input.symbolTable) {
|
|
133
|
+
const symbols = input.symbolTable.getOverloads(funcExpr);
|
|
134
|
+
for (const sym of symbols) {
|
|
135
|
+
if (sym.kind === ESymbolKind.Function && sym.parameters?.[idx]) {
|
|
136
|
+
// Map symbol parameter to targetParam format (IFunctionSignature.parameters)
|
|
137
|
+
targetParam = {
|
|
138
|
+
name: sym.parameters[idx].name,
|
|
139
|
+
baseType: sym.parameters[idx].type,
|
|
140
|
+
isConst: sym.parameters[idx].isConst,
|
|
141
|
+
isArray: sym.parameters[idx].isArray,
|
|
142
|
+
};
|
|
143
|
+
isCrossFileFunction = true;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
119
148
|
|
|
120
149
|
if (!isCNextFunc) {
|
|
121
150
|
// C function: pass-by-value, just generate the expression
|
|
@@ -134,13 +163,26 @@ const generateFunctionCall = (
|
|
|
134
163
|
targetParam && orchestrator.isFloatType(targetParam.baseType);
|
|
135
164
|
const isEnumParam =
|
|
136
165
|
targetParam && orchestrator.getKnownEnums().has(targetParam.baseType);
|
|
137
|
-
// Issue #269: Check if small unmodified primitive
|
|
166
|
+
// Issue #269: Check if small unmodified primitive (for local functions)
|
|
138
167
|
const isPrimitivePassByValue = orchestrator.isParameterPassByValue(
|
|
139
168
|
funcExpr,
|
|
140
169
|
idx,
|
|
141
170
|
);
|
|
171
|
+
// Issue #315: For cross-file functions ONLY, check if it's a small primitive type
|
|
172
|
+
// that should always be passed by value (u8, u16, i8, i16, bool).
|
|
173
|
+
// We only do this for cross-file functions because for local functions,
|
|
174
|
+
// isPrimitivePassByValue correctly considers whether the parameter is modified.
|
|
175
|
+
const isSmallPrimitive =
|
|
176
|
+
isCrossFileFunction &&
|
|
177
|
+
targetParam &&
|
|
178
|
+
SMALL_PRIMITIVE_TYPES.has(targetParam.baseType);
|
|
142
179
|
|
|
143
|
-
if (
|
|
180
|
+
if (
|
|
181
|
+
isFloatParam ||
|
|
182
|
+
isEnumParam ||
|
|
183
|
+
isPrimitivePassByValue ||
|
|
184
|
+
isSmallPrimitive
|
|
185
|
+
) {
|
|
144
186
|
// Target parameter is pass-by-value: pass value directly
|
|
145
187
|
const argCode = orchestrator.generateExpression(e);
|
|
146
188
|
// Issue #304: Wrap with static_cast if C++ enum class → integer
|